updates in frontend based on product DB
This commit is contained in:
@@ -8,7 +8,7 @@ import { ErrorBoundary } from '../components/ErrorBoundary';
|
||||
import {
|
||||
PlusCircle, Upload, CheckCircle2, Database, Sparkles,
|
||||
RefreshCw, Search, FileSpreadsheet, Download, ShoppingBag,
|
||||
Store, LayoutGrid, Filter, ArrowRight
|
||||
Store, LayoutGrid, Filter, ArrowRight, AlertTriangle
|
||||
} from 'lucide-react';
|
||||
|
||||
const STORES_SAMPLE = [
|
||||
@@ -293,6 +293,16 @@ export function UserPage() {
|
||||
setUploadingBatch(false);
|
||||
|
||||
if (data.added_products && Array.isArray(data.added_products)) {
|
||||
// Re-read the brand from the API rather than trusting the response, so
|
||||
// the panel below is showing what the database actually holds. A card
|
||||
// built purely from the upload response is what made a failed import
|
||||
// look successful.
|
||||
const firstBrand = data.added_products[0]?.brand;
|
||||
if (firstBrand) {
|
||||
setBrandSearch(firstBrand);
|
||||
fetchBrandCatalog(firstBrand);
|
||||
}
|
||||
|
||||
const newBatchCards = data.added_products.map((item, idx) => ({
|
||||
image_id: String(item.image_id || `batch_${Date.now()}_${idx}`),
|
||||
product_name: String(item.product_name || 'Batch Product'),
|
||||
@@ -421,6 +431,13 @@ export function UserPage() {
|
||||
return Array.from(setB);
|
||||
}, [allCardProducts]);
|
||||
|
||||
// A spreadsheet import can genuinely half-succeed, and that must not be
|
||||
// painted the same green as a clean one - the whole complaint was a success
|
||||
// panel over rows that never landed.
|
||||
const batchHadErrors = Boolean(
|
||||
batchResult && ((batchResult.error_count ?? 0) > 0 || batchResult.status === 'partial')
|
||||
);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div className="min-h-screen bg-paper-100 font-sans text-ink-950">
|
||||
@@ -752,20 +769,78 @@ export function UserPage() {
|
||||
)}
|
||||
|
||||
{batchResult && (
|
||||
<div className="rounded-2xl border border-emerald-500/30 bg-emerald-50/70 p-6 shadow-sm space-y-3">
|
||||
<div className="flex items-center gap-2 text-emerald-700">
|
||||
<CheckCircle2 className="h-5 w-5" />
|
||||
<h4 className="font-bold text-sm">Batch Products Processed</h4>
|
||||
<div
|
||||
className={`rounded-2xl border p-6 shadow-sm space-y-3 ${
|
||||
batchHadErrors
|
||||
? 'border-amber-500/40 bg-amber-50/70'
|
||||
: 'border-emerald-500/30 bg-emerald-50/70'
|
||||
}`}
|
||||
>
|
||||
<div className={`flex items-center gap-2 ${batchHadErrors ? 'text-amber-700' : 'text-emerald-700'}`}>
|
||||
{batchHadErrors ? <AlertTriangle className="h-5 w-5" /> : <CheckCircle2 className="h-5 w-5" />}
|
||||
<h4 className="font-bold text-sm">
|
||||
{batchHadErrors ? 'Batch Partially Imported' : 'Batch Products Saved to Database'}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-700">{batchResult.message}</p>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => switchTab('cards')}
|
||||
className="w-full flex items-center justify-center gap-2 py-2 px-3 rounded-xl bg-emerald-600 text-white font-bold text-xs shadow-sm hover:bg-emerald-700 transition cursor-pointer"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4" /> View Batch Items as Product Cards <ArrowRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{/* Counts, so "success" is never a claim without a number
|
||||
behind it. added_count is the number of rows verified
|
||||
present in the database, not the number attempted. */}
|
||||
<div className="grid grid-cols-3 gap-2 text-center">
|
||||
<div className="rounded-xl bg-white border border-emerald-200 py-2">
|
||||
<p className="text-base font-extrabold text-emerald-700">{batchResult.added_count ?? 0}</p>
|
||||
<p className="text-[10px] font-semibold text-slate-500 uppercase">Saved</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white border border-rose-200 py-2">
|
||||
<p className="text-base font-extrabold text-rose-700">{batchResult.error_count ?? 0}</p>
|
||||
<p className="text-[10px] font-semibold text-slate-500 uppercase">Failed</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white border border-slate-200 py-2">
|
||||
<p className="text-base font-extrabold text-slate-700">{batchResult.skipped_blank_rows ?? 0}</p>
|
||||
<p className="text-[10px] font-semibold text-slate-500 uppercase">Blank Rows</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{Array.isArray(batchResult.warnings) && batchResult.warnings.length > 0 && (
|
||||
<ul className="space-y-1">
|
||||
{batchResult.warnings.map((w, idx) => (
|
||||
<li key={idx} className="flex items-start gap-1.5 text-[11px] text-amber-800">
|
||||
<AlertTriangle className="h-3 w-3 shrink-0 mt-0.5" />
|
||||
<span>{w}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{Array.isArray(batchResult.errors) && batchResult.errors.length > 0 && (
|
||||
<div className="rounded-xl bg-white border border-rose-200 p-3 space-y-1 max-h-40 overflow-y-auto no-scrollbar">
|
||||
<p className="text-[11px] font-bold text-rose-800">Rows that were not saved</p>
|
||||
{batchResult.errors.map((e, idx) => (
|
||||
<p key={idx} className="text-[11px] text-slate-600 font-mono">
|
||||
{e.row ? `Row ${e.row}: ` : ''}
|
||||
{e.product_name ? `${e.product_name} - ` : ''}
|
||||
{e.error}
|
||||
</p>
|
||||
))}
|
||||
{batchResult.errors_truncated > 0 && (
|
||||
<p className="text-[11px] text-slate-400 italic">
|
||||
+{batchResult.errors_truncated} more not shown
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(batchResult.added_count ?? 0) > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => switchTab('cards')}
|
||||
className="w-full flex items-center justify-center gap-2 py-2 px-3 rounded-xl bg-emerald-600 text-white font-bold text-xs shadow-sm hover:bg-emerald-700 transition cursor-pointer"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4" /> View Batch Items as Product Cards <ArrowRight className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user