product images added

This commit is contained in:
2026-08-11 15:04:21 +05:30
parent 1861480b67
commit e54e3c9290
10 changed files with 627 additions and 59 deletions

View File

@@ -50,6 +50,7 @@ import {
import { FIESTA_TENANT_ID, FIESTA_PRIMARY_LOCATION_ID, str as fstr } from '../services/fiestaApi';
import { stockRowToProduct, stockRowToInventory } from '../services/fiestaMappers';
import { useStoreCatalogue, usePriceEverywhere, isPublishedItem } from '../services/storeCatalogue';
import { CardCarousel, ProductGallery } from './CatalogueGallery';
import BulkCartDrawer from './BulkCartDrawer';
import AwaitingApi from './AwaitingApi';
import { SlideDrawer, Skeleton, TH_STYLE, SURFACE_ALT, TEXT, TEXT_2, TEXT_3, BORDER, BRAND, tint, edge, StatusChip } from './consoleUi';
@@ -134,6 +135,21 @@ export default function InventoryView({
const [hoveredAdminProduct, setHoveredAdminProduct] = useState<ProductMatrixItem | null>(null);
const [localSearch, setLocalSearch] = useState('');
const storeCat = useStoreCatalogue(tenantId, primaryLocationId);
/**
* productid → every photo, for the catalogue cards.
*
* Built once rather than searching storeCat.items inside each card's render:
* the grid draws hundreds of cards and a linear scan per card is a scan per
* card per keystroke in the search box.
*/
const imagesByProduct = useMemo(() => {
const m = new Map<string, string[]>();
storeCat.items.forEach((i) => {
if (i.images?.length) m.set(i.productid, i.images);
});
return m;
}, [storeCat.items]);
const { priceEverywhere, isPending: isPricing } = usePriceEverywhere(tenantId);
const [importPrice, setImportPrice] = useState<string>('');
const [isSettingPrice, setIsSettingPrice] = useState(false);
@@ -589,11 +605,13 @@ export default function InventoryView({
<div key={prod.id} onClick={() => setSelectedAdminProduct(prod)} className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] rounded-none flex flex-col shadow-sm hover:shadow-[0_12px_24px_rgba(99,102,241,0.06)] hover:border-[#662582]/40 hover:-translate-y-1 transition-all duration-300 relative group overflow-hidden cursor-pointer">
{/* Image Section - Top */}
<div className="w-full h-48 bg-white p-4 relative overflow-hidden shrink-0 border-b border-slate-100 flex items-center justify-center">
<img
src={prod.image}
alt={prod.name}
referrerPolicy="no-referrer"
className="w-full h-full object-contain mix-blend-multiply group-hover:scale-110 transition-transform duration-700 ease-in-out"
{/* Every photo the product has. ProductMatrixItem
carries one `image` and is built by two mappers,
so the gallery is looked up by id — see
imagesByProduct. */}
<CardCarousel
images={imagesByProduct.get(prod.id) ?? [prod.image]}
alt={prod.name}
/>
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
{prod.isSample && (
@@ -906,10 +924,20 @@ export default function InventoryView({
return (
<div className="flex flex-col gap-8 pb-8">
{/* Clean Image Container */}
<div className="w-full h-64 bg-white rounded-2xl border border-slate-200 overflow-hidden relative flex items-center justify-center p-4">
<img src={selectedAdminProduct.image} alt={selectedAdminProduct.name} className="w-auto h-full max-w-full object-contain mix-blend-multiply" />
</div>
{/* Gallery. A product carries up to ten photos in the global
catalogue and only the first was ever stored, so most rows still
have one until the backfill runs — ProductGallery renders a
plain image in that case rather than empty chrome. */}
<ProductGallery
// Resolved from the catalogue row rather than from
// ProductMatrixItem, which carries a single `image` and is built
// by two different mappers — teaching both about a gallery to
// reach one drawer is more surface than this earns. Falls back to
// that single image, which is all a product has until the
// productimages backfill runs.
images={imagesByProduct.get(selectedAdminProduct.id) ?? [selectedAdminProduct.image]}
alt={selectedAdminProduct.name}
/>
{/* Title & Basics */}
<div>