Files
flyer-crawler.projectium.com/components/CorrectionRow.tsx
Torben Sorensen aa5990763d
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 18s
typescript fixin
2025-11-11 22:00:46 -08:00

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>
</>
);
};