feat(inventory): inline site selector above items table
Items page now fetches all active sites and passes them alongside
preferredSiteId to ItemsTable. A "Working Site" row appears at the
top of the table — selecting a site calls setPreferredSite, revalidates
the page, and shows distances in the vendor sub-rows. A status hint
("Distances shown from selected site") appears when a site is active;
"No site selected — distances hidden" is the empty-state label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
79897c5b06
commit
4919b1d4e4
2 changed files with 54 additions and 12 deletions
|
|
@ -1,9 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useMemo } from "react";
|
||||
import { useState, useMemo, useTransition } from "react";
|
||||
import { Search, X, ChevronDown, ChevronRight, MapPin, Tag } from "lucide-react";
|
||||
import { formatCurrency } from "@/lib/utils";
|
||||
import { addToCart } from "@/lib/cart";
|
||||
import { setPreferredSite } from "@/app/actions/site-preference";
|
||||
|
||||
type VendorOption = {
|
||||
vendorId: string;
|
||||
|
|
@ -25,11 +26,24 @@ function formatDist(km: number) {
|
|||
return km < 1 ? `${Math.round(km * 1000)} m` : `${km.toFixed(0)} km`;
|
||||
}
|
||||
|
||||
export function ItemsTable({ items, hasSite }: { items: CatalogItem[]; hasSite: boolean }) {
|
||||
type SiteOption = { id: string; name: string; code: string };
|
||||
|
||||
export function ItemsTable({
|
||||
items,
|
||||
hasSite,
|
||||
sites = [],
|
||||
preferredSiteId = null,
|
||||
}: {
|
||||
items: CatalogItem[];
|
||||
hasSite: boolean;
|
||||
sites?: SiteOption[];
|
||||
preferredSiteId?: string | null;
|
||||
}) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
const [sortBy, setSortBy] = useState<"distance" | "price">(hasSite ? "distance" : "price");
|
||||
const [added, setAdded] = useState<Record<string, boolean>>({});
|
||||
const [sitePending, startSiteTransition] = useTransition();
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = query.toLowerCase().trim();
|
||||
|
|
@ -79,6 +93,31 @@ export function ItemsTable({ items, hasSite }: { items: CatalogItem[]; hasSite:
|
|||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Site selector */}
|
||||
{sites.length > 0 && (
|
||||
<div className="flex items-center gap-3 rounded-lg border border-neutral-200 bg-white px-4 py-3">
|
||||
<MapPin className="h-4 w-4 text-neutral-400 shrink-0" />
|
||||
<label className="text-sm font-medium text-neutral-700 whitespace-nowrap">Working Site</label>
|
||||
<select
|
||||
value={preferredSiteId ?? ""}
|
||||
disabled={sitePending}
|
||||
onChange={(e) =>
|
||||
startSiteTransition(() => setPreferredSite(e.target.value || null))
|
||||
}
|
||||
className="flex-1 max-w-xs rounded-lg border border-neutral-200 bg-neutral-50 px-3 py-1.5 text-sm text-neutral-700 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20 disabled:opacity-60"
|
||||
>
|
||||
<option value="">No site selected — distances hidden</option>
|
||||
{sites.map((s) => (
|
||||
<option key={s.id} value={s.id}>{s.name} ({s.code})</option>
|
||||
))}
|
||||
</select>
|
||||
{sitePending && <span className="text-xs text-neutral-400">Updating…</span>}
|
||||
{!sitePending && preferredSiteId && (
|
||||
<span className="text-xs text-primary-600">Distances shown from selected site</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex-1 max-w-sm">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default async function InventoryItemsPage() {
|
|||
const session = await auth();
|
||||
if (!session?.user) redirect("/login");
|
||||
|
||||
const [user, products] = await Promise.all([
|
||||
const [user, products, sites] = await Promise.all([
|
||||
db.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
include: {
|
||||
|
|
@ -33,6 +33,11 @@ export default async function InventoryItemsPage() {
|
|||
},
|
||||
orderBy: { name: "asc" },
|
||||
}),
|
||||
db.site.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { name: "asc" },
|
||||
select: { id: true, name: true, code: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const site = user?.preferredSite ?? null;
|
||||
|
|
@ -61,16 +66,14 @@ export default async function InventoryItemsPage() {
|
|||
<div className="max-w-6xl">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-neutral-900">Browse Items</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
Search the catalogue and add items to your cart.
|
||||
{site ? (
|
||||
<span className="ml-1 text-primary-600">Distances shown from {site.name}.</span>
|
||||
) : (
|
||||
<span className="ml-1 text-neutral-400">Select a site in the header to enable distance sorting.</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-neutral-500">Search the catalogue and add items to your cart.</p>
|
||||
</div>
|
||||
<ItemsTable items={items} hasSite={!!site} />
|
||||
<ItemsTable
|
||||
items={items}
|
||||
hasSite={!!site}
|
||||
sites={sites}
|
||||
preferredSiteId={user?.preferredSiteId ?? null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue