Files
flyer-crawler.projectium.com/components/CorrectionRow.tsx
Torben Sorensen 48b7d7ce7b
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 20s
login and db work
2025-11-11 17:06:01 -08:00

93 lines
4.1 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';
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);
// 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 handleApprove = async () => {
if (!window.confirm('Are you sure you want to approve this correction? This will modify the original flyer item.')) {
return;
}
setIsProcessing(true);
setError(null);
try {
await approveCorrection(correction.id);
logger.info(`Correction ${correction.id} approved.`);
onProcessed(correction.id);
} catch (err: any) {
logger.error(`Failed to approve correction ${correction.id}`, err);
setError(err.message);
setIsProcessing(false);
}
};
const handleReject = async () => {
if (!window.confirm('Are you sure you want to reject this correction?')) {
return;
}
setIsProcessing(true);
setError(null);
try {
await rejectCorrection(correction.id);
logger.info(`Correction ${correction.id} rejected.`);
onProcessed(correction.id);
} catch (err: any) {
logger.error(`Failed to reject correction ${correction.id}`, err);
setError(err.message);
setIsProcessing(false);
}
};
return (
<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={handleApprove} 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={handleReject} 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>
);
};