fix: Show skip/error reasons in car import results

- Backend: Include car_name in skipped response
- Frontend: Display skip details (car name + reason like "Already imported (ID: 123)")
- Frontend: Display error details with error message
- Add import process description during loading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2026-02-18 09:08:58 +09:00
parent d98e9287d3
commit 02a88da430
2 changed files with 94 additions and 32 deletions

View File

@@ -1423,6 +1423,7 @@ async def import_cars_from_carmodoo(
skipped.append({
"car_no": car_data.car_no,
"car_id": existing.id,
"car_name": car_data.car_name or existing.name or car_data.car_no,
"reason": "already exists"
})
continue

View File

@@ -199,6 +199,16 @@ export default function CarsAdminPage() {
attempts: number;
error?: string;
}>;
skipDetails?: Array<{
car_no: string;
car_id: number;
car_name: string;
reason: string;
}>;
errorDetails?: Array<{
car_no: string;
error: string;
}>;
} | null>(null);
const [pdfStatus, setPdfStatus] = useState<Record<number, boolean>>({});
const [regeneratingPdf, setRegeneratingPdf] = useState<number | null>(null);
@@ -738,6 +748,8 @@ export default function CarsAdminPage() {
pdfSuccess: data.summary.pdf_success_count || 0,
pdfFailed: data.summary.pdf_failed_count || 0,
pdfDetails,
skipDetails: data.skipped || [],
errorDetails: data.errors || [],
});
// Remove imported cars from selection and reload local cars
@@ -2207,41 +2219,53 @@ export default function CarsAdminPage() {
{/* Import Result */}
{importResult && (
<div className={`border rounded-xl p-4 mb-6 ${
importResult.pdfFailed > 0
? 'bg-amber-50 border-amber-200'
: 'bg-green-50 border-green-200'
importResult.errors > 0
? 'bg-red-50 border-red-200'
: importResult.pdfFailed > 0
? 'bg-amber-50 border-amber-200'
: 'bg-green-50 border-green-200'
}`}>
<div className="flex justify-between items-start">
<div>
<div className="flex-1">
<h3 className={`font-semibold mb-2 ${
importResult.pdfFailed > 0 ? 'text-amber-800' : 'text-green-800'
importResult.errors > 0 ? 'text-red-800' : importResult.pdfFailed > 0 ? 'text-amber-800' : 'text-green-800'
}`}>
Import Complete
</h3>
<div className="text-gray-700 space-y-1">
<div>
<span className="mr-4">Imported: {importResult.imported}</span>
<span className="mr-4">Skipped: {importResult.skipped}</span>
{importResult.errors > 0 && (
<span className="text-red-600">Errors: {importResult.errors}</span>
)}
</div>
<div className="flex items-center gap-4 mt-2">
<span className="flex items-center gap-1">
<svg className="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
PDF Success: {importResult.pdfSuccess}
<div className="flex flex-wrap gap-3">
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-green-100 text-green-800 rounded text-sm font-medium">
Imported: {importResult.imported}
</span>
{importResult.pdfFailed > 0 && (
<span className="flex items-center gap-1 text-amber-700">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
PDF Failed: {importResult.pdfFailed}
{importResult.skipped > 0 && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-yellow-100 text-yellow-800 rounded text-sm font-medium">
Skipped: {importResult.skipped}
</span>
)}
{importResult.errors > 0 && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-red-100 text-red-800 rounded text-sm font-medium">
Errors: {importResult.errors}
</span>
)}
</div>
{importResult.imported > 0 && (
<div className="flex items-center gap-4 mt-2">
<span className="flex items-center gap-1 text-sm">
<svg className="w-4 h-4 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
PDF Success: {importResult.pdfSuccess}
</span>
{importResult.pdfFailed > 0 && (
<span className="flex items-center gap-1 text-amber-700 text-sm">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
PDF Failed: {importResult.pdfFailed}
</span>
)}
</div>
)}
</div>
</div>
<button
@@ -2254,28 +2278,60 @@ export default function CarsAdminPage() {
</button>
</div>
{/* Skipped 상세 */}
{importResult.skipped > 0 && importResult.skipDetails && importResult.skipDetails.length > 0 && (
<div className="mt-3 pt-3 border-t border-yellow-200">
<h4 className="text-sm font-medium text-yellow-800 mb-1.5">Skipped:</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{importResult.skipDetails.map((s, idx) => (
<div key={idx} className="flex items-center justify-between text-sm bg-white rounded px-2 py-1">
<span className="text-gray-700 truncate mr-2">{s.car_name || s.car_no}</span>
<span className="text-yellow-700 text-xs whitespace-nowrap">
{s.reason === 'already exists' ? `Already imported (ID: ${s.car_id})` : s.reason}
</span>
</div>
))}
</div>
</div>
)}
{/* Error 상세 */}
{importResult.errors > 0 && importResult.errorDetails && importResult.errorDetails.length > 0 && (
<div className="mt-3 pt-3 border-t border-red-200">
<h4 className="text-sm font-medium text-red-800 mb-1.5">Errors:</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{importResult.errorDetails.map((e, idx) => (
<div key={idx} className="flex items-center justify-between text-sm bg-white rounded px-2 py-1">
<span className="text-gray-700">{e.car_no}</span>
<span className="text-red-600 text-xs truncate ml-2">{e.error}</span>
</div>
))}
</div>
</div>
)}
{/* PDF 실패 상세 정보 */}
{importResult.pdfFailed > 0 && importResult.pdfDetails && (
<div className="mt-4 pt-4 border-t border-amber-200">
<h4 className="text-sm font-medium text-amber-800 mb-2">
PDF (3 ):
<div className="mt-3 pt-3 border-t border-amber-200">
<h4 className="text-sm font-medium text-amber-800 mb-1.5">
PDF Failed (3 retries attempted):
</h4>
<div className="space-y-2 max-h-40 overflow-y-auto">
<div className="space-y-1 max-h-32 overflow-y-auto">
{importResult.pdfDetails
.filter(d => !d.success)
.map((detail, idx) => (
<div key={idx} className="flex items-center justify-between text-sm bg-white rounded p-2">
<div key={idx} className="flex items-center justify-between text-sm bg-white rounded px-2 py-1">
<span className="text-gray-700">
[{detail.car_id}] {detail.car_name}
</span>
<span className="text-amber-600 text-xs">
{detail.attempts} - {detail.error || 'Unknown error'}
{detail.attempts} attempts - {detail.error || 'Unknown error'}
</span>
</div>
))}
</div>
<p className="mt-3 text-xs text-amber-700">
Local Cars "PDF" .
<p className="mt-2 text-xs text-amber-700">
Retry from Local Cars tab using the "PDF" button.
</p>
</div>
)}
@@ -2391,6 +2447,11 @@ export default function CarsAdminPage() {
</>
)}
</button>
{importing && (
<div className="text-xs text-gray-500 mt-1">
Duplicate check &rarr; Images &rarr; Description &rarr; Translation &rarr; PDF &rarr; Specs
</div>
)}
<button
onClick={handleRegisterAsBanner}
disabled={importing || registeringBanners || addingToRequest || selectedCars.size === 0}