import React, { useState, useEffect } from 'react'; import { api } from '../api/client'; import { NavigationHeader } from '../components/NavigationHeader'; import { ProductModal } from '../components/ProductModal'; import { ErrorBoundary } from '../components/ErrorBoundary'; import { PlusCircle, Upload, CheckCircle2, Database, Sparkles, RefreshCw, Search, FileSpreadsheet, Download, LayoutGrid, AlertTriangle } from 'lucide-react'; export function UserPage() { const [entryMode, setEntryMode] = useState('single'); // 'single' or 'batch' // Single Form State const [brand, setBrand] = useState('Lion Dates'); const [productName, setProductName] = useState('Lion Dates 450g'); const [category, setCategory] = useState('Health Foods'); const [priceRange, setPriceRange] = useState('₹160-220'); const [finalPrice, setFinalPrice] = useState('185.00'); const [barcode, setBarcode] = useState('20086040'); const [hsnCode, setHsnCode] = useState('2008'); const [description, setDescription] = useState(''); const [imageUrl, setImageUrl] = useState(''); const [submittingSingle, setSubmittingSingle] = useState(false); const [singleResult, setSingleResult] = useState(null); const [singleError, setSingleError] = useState(''); // Batch Upload State const [batchFile, setBatchFile] = useState(null); const [uploadingBatch, setUploadingBatch] = useState(false); const [batchResult, setBatchResult] = useState(null); const [batchError, setBatchError] = useState(''); // Catalog Inspection & Modal const [brandSearch, setBrandSearch] = useState('Lion Dates'); const [brandProducts, setBrandProducts] = useState([]); const [loadingBrand, setLoadingBrand] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); const fetchBrandCatalog = async (bName) => { if (!bName) return; setLoadingBrand(true); try { const data = await api.getBrandProducts(bName, { limit: 50 }); const itemsList = Array.isArray(data) ? data : Array.isArray(data?.products) ? data.products : []; setBrandProducts(itemsList); } catch (e) { console.warn('Failed to fetch brand products', e); setBrandProducts([]); } finally { setLoadingBrand(false); } }; useEffect(() => { fetchBrandCatalog(brandSearch); }, [brandSearch]); const handlePresetLionDates = () => { setEntryMode('single'); setBrand('Lion Dates'); setProductName('Lion Dates 450g'); setCategory('Health Foods'); setPriceRange('₹160-220'); setFinalPrice('185.00'); setBarcode('20086040'); setHsnCode('2008'); setDescription('Premium quality Lion Dates 450g pack enriched with natural nutrients and vitamins.'); setImageUrl('https://liondates.com/cdn/shop/files/4.datespowder_lifestyle.png?v=1773397173&width=1445'); }; const handleSingleSubmit = async (e) => { e.preventDefault(); setSingleError(''); setSingleResult(null); setSubmittingSingle(true); try { const payload = { brand, product_name: productName, category, price_range: priceRange, final_selling_price: finalPrice ? parseFloat(finalPrice) : null, barcode: barcode || null, hsn_code: hsnCode || null, description: description || undefined, image_url: imageUrl || undefined, }; const data = await api.addUserProduct(payload); setSingleResult(data); setSubmittingSingle(false); // Refresh catalog list for current brand setBrandSearch(brand); fetchBrandCatalog(brand); } catch (err) { setSubmittingSingle(false); setSingleError(err.message || 'An error occurred while adding the product.'); } }; const handleBatchSubmit = async (e) => { e.preventDefault(); if (!batchFile) return; setBatchError(''); setBatchResult(null); setUploadingBatch(true); try { const data = await api.uploadUserProductsFile(batchFile); setBatchResult(data); 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); } } } catch (err) { setUploadingBatch(false); setBatchError(err.message || 'Error uploading file.'); } }; const downloadSampleCSV = () => { const csvContent = 'Brand Name,Product Name / Variant,Category,Price Range,Final Price (₹),Barcode (GTIN/EAN),HSN Code,Custom Image URL,Description\n' + 'Lion Dates,Lion Dates 450g,Health Foods,₹160-220,185.00,20086040,2008,https://liondates.com/cdn/shop/files/4.datespowder_lifestyle.png?v=1773397173&width=1445,Premium Quality Lion Dates 450g\n' + 'Naga,Naga Maida 2kg,Flour & Grains,₹90-110,98.00,8906012345001,1101,,High grade refined wheat flour maida\n'; const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.setAttribute('download', 'sample_product_batch_template.csv'); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; // 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 (
{/* Workspace banner */}

User Product Management

Single/Batch Product Uploads with Dual Persistence

{/* ========================================================================= */} {/* VIEW 1: PRODUCT ENTRY & BATCH UPLOAD */} {/* ========================================================================= */}
Database & Seed JSON Auto-Sync

Upload & Add New Product Records

Add new product variants (e.g. "Lion Dates 450g") via single form or batch CSV/Excel upload. Product details & images sync directly into PostgreSQL database tables and JSON seed catalog files.

{/* Left Form: Single vs Batch Upload Tabs */}
Auto-Enrich & Dual Persistence
{/* SINGLE PRODUCT FORM */} {entryMode === 'single' && (
{singleError && (
{singleError}
)}
setBrand(e.target.value)} placeholder="e.g. Lion Dates" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setProductName(e.target.value)} placeholder="e.g. Lion Dates 450g" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setCategory(e.target.value)} placeholder="e.g. Health Foods" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setPriceRange(e.target.value)} placeholder="e.g. ₹160-220" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setFinalPrice(e.target.value)} placeholder="e.g. 185.00" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setBarcode(e.target.value)} placeholder="e.g. 20086040" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setHsnCode(e.target.value)} placeholder="e.g. 2008" className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />
setImageUrl(e.target.value)} placeholder="https://..." className="w-full rounded-xl border border-ink-900/15 bg-white p-2.5 text-xs font-medium text-ink-950 focus:border-blue-500 focus:outline-none" />