fix(pos2): wait for branch before fetching menu + add left category sidebar
CI/CD / CI · API (dotnet build + test) (push) Failing after 3m20s
CI/CD / CI · Admin API (dotnet build) (push) Failing after 3m19s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 41s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Has been cancelled
CI/CD / CI · API (dotnet build + test) (push) Failing after 3m20s
CI/CD / CI · Admin API (dotnet build) (push) Failing after 3m19s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 41s
CI/CD / CI · Website (tsc) (push) Successful in 44s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Has been cancelled
Race fix: orderBranchId now returns `undefined` (not null) while the /branches query is in flight. usePos2Menu treats undefined as "not yet determined" and skips the fetch, preventing getBranchMenu(cafeId, null) → empty array. Once branchesFetched=true, orderBranchId resolves to the correct branchId (or null for café-wide fallback). Layout: desktop order screen now shows a left vertical category sidebar (116 px, md+) instead of horizontal chips, giving the classic POS sidebar feel. Horizontal chips kept for mobile (<md). Menu grid columns adjusted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,20 +69,25 @@ export function Pos2Screen() {
|
||||
// Resolve a VALID branch (auto-pick the first) exactly like the classic POS —
|
||||
// the menu/tables are branch-scoped, so a null or stale stored branchId would
|
||||
// otherwise load an empty menu. v2 has no branch picker, so it must self-heal.
|
||||
const { data: branches = [] } = useQuery({
|
||||
//
|
||||
// IMPORTANT: orderBranchId returns `undefined` while branches are still loading.
|
||||
// usePos2Menu treats `undefined` as "not yet determined" and pauses the query so
|
||||
// we never fire getBranchMenu(cafeId, null) which returns an empty array.
|
||||
const { data: branches = [], isFetched: branchesFetched } = useQuery({
|
||||
queryKey: ["branches", cafeId],
|
||||
queryFn: () => apiGet<{ id: string; name: string }[]>(`/api/cafes/${cafeId}/branches`),
|
||||
enabled: !!cafeId,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (branches.length === 0) return;
|
||||
if (!branchesFetched || branches.length === 0) return;
|
||||
const valid = branchId && branches.some((b) => b.id === branchId);
|
||||
if (!valid) setBranchId(branches[0]!.id);
|
||||
}, [branches, branchId, setBranchId]);
|
||||
const orderBranchId = useMemo(() => {
|
||||
}, [branchesFetched, branches, branchId, setBranchId]);
|
||||
const orderBranchId = useMemo<string | null | undefined>(() => {
|
||||
if (!branchesFetched) return undefined; // still loading → pause the menu query
|
||||
if (branchId && branches.some((b) => b.id === branchId)) return branchId;
|
||||
return branches[0]?.id ?? null;
|
||||
}, [branchId, branches]);
|
||||
return branches[0]?.id ?? null; // null = no branches → café-wide fallback
|
||||
}, [branchesFetched, branchId, branches]);
|
||||
|
||||
const { data: categories } = usePos2Categories(cafeId);
|
||||
const { data: menu, isLoading: menuLoading, isError: menuError, refetch: refetchMenu } = usePos2Menu(cafeId, orderBranchId);
|
||||
@@ -447,8 +452,29 @@ export function Pos2Screen() {
|
||||
</header>
|
||||
|
||||
<div className="flex min-h-0 flex-1">
|
||||
{/* ── Left: vertical category sidebar (desktop) ── */}
|
||||
<nav className="hidden w-[116px] shrink-0 flex-col gap-0.5 overflow-y-auto border-e border-border bg-card p-2 md:flex">
|
||||
{catChips.map((c) => (
|
||||
<button
|
||||
key={c.id}
|
||||
type="button"
|
||||
onClick={() => setCat(c.id)}
|
||||
className={cn(
|
||||
"w-full cursor-pointer rounded-xl px-2 py-3 text-center text-xs font-semibold leading-tight transition-colors",
|
||||
cat === c.id
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{c.name}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* ── Center: menu items ── */}
|
||||
<main className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<div className="flex shrink-0 gap-2 overflow-x-auto border-b border-border px-4 py-2.5">
|
||||
{/* Horizontal chips — mobile only */}
|
||||
<div className="flex shrink-0 gap-2 overflow-x-auto border-b border-border px-4 py-2.5 md:hidden">
|
||||
{catChips.map((c) => (
|
||||
<button
|
||||
key={c.id}
|
||||
@@ -463,6 +489,7 @@ export function Pos2Screen() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{menuLoading ? (
|
||||
<div className="flex flex-1 items-center justify-center text-muted-foreground">
|
||||
<Loader2 className="size-6 animate-spin" />
|
||||
@@ -475,7 +502,7 @@ export function Pos2Screen() {
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid min-h-0 flex-1 auto-rows-min grid-cols-2 gap-3 overflow-y-auto p-4 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||
<div className="grid min-h-0 flex-1 auto-rows-min grid-cols-2 gap-3 overflow-y-auto p-3 sm:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{visibleItems.map((it) => (
|
||||
<button
|
||||
key={it.id}
|
||||
@@ -499,7 +526,8 @@ export function Pos2Screen() {
|
||||
)}
|
||||
</main>
|
||||
|
||||
<aside className="hidden w-[380px] shrink-0 border-s border-border bg-card lg:flex lg:flex-col">
|
||||
{/* ── Right: order ticket (desktop) ── */}
|
||||
<aside className="hidden w-[360px] shrink-0 border-s border-border bg-card lg:flex lg:flex-col">
|
||||
<Ticket {...ticketProps} />
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,13 @@ export function usePos2Categories(cafeId?: string | null) {
|
||||
}
|
||||
|
||||
/** Branch-scoped menu (effective prices) when a branch is selected; otherwise the
|
||||
* café-wide menu. Both normalize to MenuItem so the cart store can consume them. */
|
||||
* café-wide menu. Both normalize to MenuItem so the cart store can consume them.
|
||||
*
|
||||
* Pass `branchId = undefined` (not null) while still determining which branch to
|
||||
* use — the query will pause until branchId is resolved. Once resolved:
|
||||
* • string → branch-scoped menu via getBranchMenu
|
||||
* • null → café-wide fallback via /menu/items
|
||||
*/
|
||||
export function usePos2Menu(cafeId?: string | null, branchId?: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ["pos2-menu", cafeId, branchId ?? "cafe"],
|
||||
@@ -33,7 +39,8 @@ export function usePos2Menu(cafeId?: string | null, branchId?: string | null) {
|
||||
}
|
||||
return apiGet<MenuItem[]>(`/api/cafes/${cafeId}/menu/items`);
|
||||
},
|
||||
enabled: !!cafeId,
|
||||
// branchId === undefined means "still determining" — don't fire yet
|
||||
enabled: !!cafeId && branchId !== undefined,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user