Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 18s
112 lines
5.2 KiB
TypeScript
112 lines
5.2 KiB
TypeScript
import React, { useState } from 'react';
|
|
import type { SuggestedCorrection, MasterGroceryItem } from '../types';
|
|
import { approveCorrection, rejectCorrection } from '../services/supabaseClient';
|
|
import { logger } from '../services/logger';
|
|
import { CheckIcon } from './icons/CheckIcon';
|
|
import { XMarkIcon } from './icons/XMarkIcon';
|
|
import { LoadingSpinner } from './LoadingSpinner';
|
|
import { ConfirmationModal } from './ConfirmationModal';
|
|
|
|
interface CorrectionRowProps {
|
|
correction: SuggestedCorrection;
|
|
masterItems: MasterGroceryItem[];
|
|
onProcessed: (correctionId: number) => void;
|
|
}
|
|
|
|
export const CorrectionRow: React.FC<CorrectionRowProps> = ({ correction, masterItems, onProcessed }) => {
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [actionToConfirm, setActionToConfirm] = useState<'approve' | 'reject' | null>(null);
|
|
|
|
// Helper to make the suggested value more readable for the admin.
|
|
const formatSuggestedValue = () => {
|
|
const { correction_type, suggested_value } = correction;
|
|
if (correction_type === 'WRONG_PRICE') {
|
|
const priceInCents = parseInt(suggested_value, 10);
|
|
if (!isNaN(priceInCents)) {
|
|
return `$${(priceInCents / 100).toFixed(2)}`;
|
|
}
|
|
}
|
|
if (correction_type === 'INCORRECT_ITEM_LINK') {
|
|
const masterItemId = parseInt(suggested_value, 10);
|
|
const item = masterItems.find(mi => mi.id === masterItemId);
|
|
return item ? `${item.name} (ID: ${masterItemId})` : `Unknown Item (ID: ${masterItemId})`;
|
|
}
|
|
return suggested_value;
|
|
};
|
|
|
|
const handleConfirm = async () => {
|
|
if (!actionToConfirm) return;
|
|
|
|
setIsProcessing(true);
|
|
setIsModalOpen(false);
|
|
setError(null);
|
|
try {
|
|
if (actionToConfirm === 'approve') {
|
|
await approveCorrection(correction.id);
|
|
logger.info(`Correction ${correction.id} approved.`);
|
|
} else {
|
|
await rejectCorrection(correction.id);
|
|
logger.info(`Correction ${correction.id} rejected.`);
|
|
}
|
|
onProcessed(correction.id);
|
|
} catch (err) {
|
|
// This is a type-safe way to handle errors. We check if the caught
|
|
// object is an instance of Error before accessing its message property.
|
|
const errorMessage = err instanceof Error ? err.message : `An unknown error occurred while trying to ${actionToConfirm} the correction.`;
|
|
logger.error(`Failed to ${actionToConfirm} correction ${correction.id}`, { error: errorMessage });
|
|
setError(errorMessage);
|
|
setIsProcessing(false);
|
|
}
|
|
setActionToConfirm(null);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<ConfirmationModal
|
|
isOpen={isModalOpen}
|
|
onClose={() => setIsModalOpen(false)}
|
|
onConfirm={handleConfirm}
|
|
title={actionToConfirm === 'approve' ? 'Approve Correction' : 'Reject Correction'}
|
|
message={
|
|
actionToConfirm === 'approve' ? (
|
|
<>
|
|
Are you sure you want to approve this correction?
|
|
<strong className="block mt-2">This will permanently modify the original flyer item.</strong>
|
|
</>
|
|
) : (
|
|
'Are you sure you want to reject this correction?'
|
|
)
|
|
}
|
|
confirmButtonText={actionToConfirm === 'approve' ? 'Approve' : 'Reject'}
|
|
confirmButtonClass={
|
|
actionToConfirm === 'approve'
|
|
? 'bg-green-600 hover:bg-green-700 focus:ring-green-500'
|
|
: 'bg-red-600 hover:bg-red-700 focus:ring-red-500'
|
|
}
|
|
/>
|
|
<tr>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm font-medium text-gray-900 dark:text-white">{correction.flyer_item_name}</div>
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">Original Price: {correction.flyer_item_price_display}</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{correction.correction_type}</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-semibold text-blue-600 dark:text-blue-400">{formatSuggestedValue()}</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{correction.user_email || 'Unknown'}</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{new Date(correction.created_at).toLocaleDateString()}</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
{isProcessing ? (
|
|
<div className="flex justify-end items-center"><LoadingSpinner /></div>
|
|
) : (
|
|
<div className="flex items-center justify-end space-x-2">
|
|
<button onClick={() => { setActionToConfirm('approve'); setIsModalOpen(true); }} className="p-1.5 text-green-600 hover:bg-green-100 dark:hover:bg-green-800/50 rounded-md" title="Approve"><CheckIcon className="w-5 h-5" /></button>
|
|
<button onClick={() => { setActionToConfirm('reject'); setIsModalOpen(true); }} className="p-1.5 text-red-600 hover:bg-red-100 dark:hover:bg-red-800/50 rounded-md" title="Reject"><XMarkIcon className="w-5 h-5" /></button>
|
|
</div>
|
|
)}
|
|
{error && <p className="text-xs text-red-500 mt-1 text-right">{error}</p>}
|
|
</td>
|
|
</tr>
|
|
</>
|
|
);
|
|
}; |