"use client"; import { Check, Copy, Repeat2, Square, SquareCheckBig, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { formatAmount, formatUsd } from "@/app/lib/units"; import type { Aisle, GroceryLine, GrocerySummary, ItemId } from "@/app/types"; import { BudgetMeter } from "./BudgetMeter"; interface PanelProps { summary: GrocerySummary; budget: number; priceSourceLabel: string; onToggleOnHand: (itemId: ItemId, onHand: boolean) => void; } export function GrocerySummaryPanel({ summary, budget, priceSourceLabel, onToggleOnHand }: PanelProps) { const [copied, setCopied] = useState(false); const byAisle = groupByAisle(summary.lines); const remaining = budget - summary.checkoutTotal; async function copyList() { const text = summary.lines .filter((l) => !l.onHand) .map((l) => `☐ ${l.packages} × ${l.item.name} (${l.item.packageLabel})`) .join("\n"); try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { // Clipboard can be blocked; list remains visible on screen. } } return (
{/* --- Tactile Market Receipt Header -------------------------------- */}
= 0 ? "Left in budget" : "Over budget"} value={formatUsd(Math.abs(remaining))} tone={remaining >= 0 ? "text-status-ok" : "text-status-danger"} />
{summary.sharedItemCount > 0 && (

{summary.sharedItemCount} store item{summary.sharedItemCount > 1 ? "s" : ""} shared across meals

)}
{/* --- Shopping List Header ----------------------------------------- */}

Shopping list

Tap items you already have on hand

{summary.lines.length > 0 && ( )}
{summary.lines.length === 0 ? (

Add meals to build your aisle-sorted grocery receipt.

) : (
{byAisle.map(([aisle, lines]) => (

{aisle}

    {lines.map((line) => ( ))}
))}
)}

{priceSourceLabel}. Actual store prices vary by location.

); } function GroceryRow({ line, onToggleOnHand }: { line: GroceryLine; onToggleOnHand: PanelProps["onToggleOnHand"] }) { const { item } = line; return (
  • ); } function Stat({ label, value, hint, tone = "text-ink" }: { label: string; value: string; hint?: string; tone?: string }) { return (
    {label}
    {value}
    ); } function groupByAisle(lines: GroceryLine[]): [Aisle, GroceryLine[]][] { const groups = new Map(); for (const line of lines) { const group = groups.get(line.item.aisle) ?? []; group.push(line); groups.set(line.item.aisle, group); } return [...groups]; } // --------------------------------------------------------------------------- // GroceryListDrawer: Native mobile bottom sheet with focus trapping, // scroll locking, and @starting-style spring transition. // --------------------------------------------------------------------------- interface DrawerProps extends PanelProps { open: boolean; onClose: () => void; } export function GroceryListDrawer({ open, onClose, ...panelProps }: DrawerProps) { const dialogRef = useRef(null); useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; if (open && !dialog.open) dialog.showModal(); if (!open && dialog.open) dialog.close(); return () => { if (dialog.open) dialog.close(); }; }, [open]); return ( e.target === dialogRef.current && onClose()} aria-labelledby="grocery-drawer-title" className="sheet-dialog m-0 mt-auto max-h-[88dvh] w-full max-w-none overflow-y-auto rounded-t-2xl border-t border-line bg-surface p-0 shadow-2xl lg:hidden" >

    Market receipt & list

    ); }