update backend apis

This commit is contained in:
sriram
2026-08-13 16:03:51 +05:30
parent 282a494aab
commit 586eeecbf5
4 changed files with 214 additions and 29 deletions

View File

@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react';
import { Sidebar } from '../components/Sidebar';
import { TopBar } from '../components/TopBar';
import { ProductGrid } from '../components/ProductGrid';
import { BrandGrid } from '../components/BrandGrid';
import { ProductModal } from '../components/ProductModal';
import { ChatPanel } from '../components/ChatPanel';
import { api } from '../api/client';
@@ -12,6 +13,14 @@ export function HomePage() {
const [brands, setBrands] = useState([]);
const [brandsLoading, setBrandsLoading] = useState(true);
// `selectedBrand === null` already means "All brands - show every product",
// so it cannot also mean "show the brand cards". This is the explicit
// landing-vs-listing switch.
const [view, setView] = useState('brands'); // 'brands' | 'products'
const [brandCards, setBrandCards] = useState([]);
const [brandCardsLoading, setBrandCardsLoading] = useState(true);
const [brandCardsError, setBrandCardsError] = useState(null);
const [categories, setCategories] = useState([]);
const [categoriesLoading, setCategoriesLoading] = useState(false);
@@ -37,14 +46,27 @@ export function HomePage() {
.finally(() => setBrandsLoading(false));
}, []);
const loadBrandCards = useCallback(({ refresh = false } = {}) => {
setBrandCardsLoading(true);
setBrandCardsError(null);
api.getBrandCards({ refresh })
.then((res) => setBrandCards(res.brands || []))
.catch((err) => {
setBrandCards([]);
setBrandCardsError(err.message);
})
.finally(() => setBrandCardsLoading(false));
}, []);
// Initial load: health + brands, then poll health every 30s
useEffect(() => {
loadBrands();
loadBrandCards();
const checkHealth = () => api.getHealth().then(setHealth).catch(() => setHealth(null));
checkHealth();
const interval = setInterval(checkHealth, 30000);
return () => clearInterval(interval);
}, [loadBrands]);
}, [loadBrands, loadBrandCards]);
// Load categories + default browse listing whenever the brand changes
useEffect(() => {
@@ -87,13 +109,16 @@ export function HomePage() {
});
}, [selectedBrand, selectedCategory]);
// Re-browse whenever brand/category changes (only relevant once a search hasn't overridden it)
// Re-browse whenever brand/category changes (only relevant once a search
// hasn't overridden it). Gating on `view` also stops the landing page firing
// an unbounded /api/products request before the user has asked for one.
useEffect(() => {
if (mode === 'search' && !query) browse();
if (mode === 'search' && !query && view === 'products') browse();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedBrand, selectedCategory]);
}, [selectedBrand, selectedCategory, view]);
function runSearch() {
setView('products');
if (!query.trim()) {
browse();
return;
@@ -129,9 +154,9 @@ export function HomePage() {
categoriesLoading={categoriesLoading}
selectedBrand={selectedBrand}
selectedCategory={selectedCategory}
onSelectBrand={(b) => { setSelectedBrand(b); setQuery(''); }}
onSelectBrand={(b) => { setSelectedBrand(b); setQuery(''); setView('products'); }}
onSelectCategory={setSelectedCategory}
onRefreshBrands={loadBrands}
onRefreshBrands={() => { loadBrands(); loadBrandCards({ refresh: true }); }}
/>
<div className="flex flex-1 flex-col overflow-hidden">
@@ -147,36 +172,70 @@ export function HomePage() {
setSelectedCategory(null);
setQuery('');
setMode('search');
setView('brands');
}}
/>
<main className="flex-1 overflow-y-auto">
{mode === 'search' ? (
<div className="p-6">
<div className="mb-4 flex items-center justify-between">
<p className="font-display text-sm font-semibold text-ink-900">
{hasSearched
? `Results for "${query}"`
: selectedBrand
? `Browsing ${selectedBrand}${selectedCategory ? ` · ${selectedCategory}` : ''}`
: 'Browsing All Brands'}
</p>
{products.length > 0 && <p className="text-xs text-slate-500">{products.length} shown</p>}
</div>
{view === 'brands' ? (
<>
<div className="mb-4 flex items-center justify-between">
<p className="font-display text-sm font-semibold text-ink-900">Browse Brands</p>
{brandCards.length > 0 && (
<p className="text-xs text-slate-500">
{brandCards.length} brands ·{' '}
{brandCards.reduce((n, b) => n + (b.product_count || 0), 0).toLocaleString()} products
</p>
)}
</div>
<ProductGrid
products={products}
loading={productsLoading}
error={productsError}
showSimilarity={hasSearched}
emptyTitle={brands.length === 0 ? 'No catalog data yet' : 'No products found'}
emptySubtitle={
brands.length === 0
? "Run `python scripts/seed_sample_data.py` in the backend to load sample brands, or use the Admin panel to ingest a real one."
: 'Try a different brand, category, or search term.'
}
onSelect={setSelectedProduct}
/>
<BrandGrid
brands={brandCards}
loading={brandCardsLoading}
error={brandCardsError}
emptyTitle="No catalog data yet"
emptySubtitle="Run `python scripts/seed_sample_data.py` in the backend to load sample brands, or use the Admin panel to ingest a real one."
onSelect={(b) => { setSelectedBrand(b.name); setQuery(''); setView('products'); }}
/>
</>
) : (
<>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<button
onClick={() => setView('brands')}
className="text-xs text-slate-500 transition hover:text-amber-600"
>
← All brands
</button>
<p className="font-display text-sm font-semibold text-ink-900">
{hasSearched
? `Results for "${query}"`
: selectedBrand
? `Browsing ${selectedBrand}${selectedCategory ? ` · ${selectedCategory}` : ''}`
: 'Browsing All Brands'}
</p>
</div>
{products.length > 0 && <p className="text-xs text-slate-500">{products.length} shown</p>}
</div>
<ProductGrid
products={products}
loading={productsLoading}
error={productsError}
showSimilarity={hasSearched}
emptyTitle={brands.length === 0 ? 'No catalog data yet' : 'No products found'}
emptySubtitle={
brands.length === 0
? "Run `python scripts/seed_sample_data.py` in the backend to load sample brands, or use the Admin panel to ingest a real one."
: 'Try a different brand, category, or search term.'
}
onSelect={setSelectedProduct}
/>
</>
)}
</div>
) : (
<ChatPanel brand={selectedBrand} onSelectSource={setSelectedProduct} />