feat(pos): set a per-item note on each cart line in POS v2
CI/CD / CI · API (dotnet build + test) (push) Successful in 43s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m13s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 48s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Successful in 3m9s
CI/CD / CI · API (dotnet build + test) (push) Successful in 43s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 32s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m13s
CI/CD / CI · Admin Web (tsc) (push) Successful in 40s
CI/CD / CI · Website (tsc) (push) Successful in 48s
CI/CD / CI · Koja (tsc) (push) Successful in 52s
CI/CD / Deploy · all services (push) Successful in 3m9s
The cart store, order payload (create + add-items + offline), KDS ticket and receipt already supported per-item notes — but POS v2 had no way to enter one. Adds a note button on each cart line that toggles an inline input (e.g. "no sugar"); the note shows highlighted when set and rides along to the kitchen/bar ticket. No backend change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
Search, Plus, Minus, Trash2, Send, CreditCard, Pause, SplitSquareHorizontal,
|
||||
X, WifiOff, ShoppingCart, Users, Coffee, ArrowRight, LayoutGrid, Armchair,
|
||||
Banknote, Check, Delete, ReceiptText, ShoppingBag, Loader2, RotateCcw,
|
||||
BadgePercent, Sparkles, Home,
|
||||
BadgePercent, Sparkles, Home, StickyNote,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { notify } from "@/lib/notify";
|
||||
@@ -101,6 +101,7 @@ export function Pos2Screen() {
|
||||
const addItem = useCartStore((s) => s.addItem);
|
||||
const updateQty = useCartStore((s) => s.updateQty);
|
||||
const removeItem = useCartStore((s) => s.removeItem);
|
||||
const setNotes = useCartStore((s) => s.setNotes);
|
||||
const setTableId = useCartStore((s) => s.setTableId);
|
||||
const setOrderType = useCartStore((s) => s.setOrderType);
|
||||
const hydrateFromOrder = useCartStore((s) => s.hydrateFromOrder);
|
||||
@@ -320,6 +321,7 @@ export function Pos2Screen() {
|
||||
cafeId, lines: live, subtotal, discount, tax, total, count, pendingCount,
|
||||
onBump: (id: string, d: number) => { const l = items.find((x) => x.menuItem.id === id); if (l) updateQty(id, l.quantity + d); },
|
||||
onRemove: removeItem, onSend: send, onPay: openPay, onSplit: openPay,
|
||||
onNote: (id: string, notes: string) => setNotes(id, notes),
|
||||
};
|
||||
|
||||
// ── TABLE BOARD ────────────────────────────────────────────────────────────
|
||||
@@ -669,15 +671,17 @@ function Pos2Extras({ cafeId }: { cafeId: string }) {
|
||||
}
|
||||
|
||||
// ── Order ticket ─────────────────────────────────────────────────────────────
|
||||
type TicketLine = { menuItem: MenuItem; quantity: number };
|
||||
type TicketLine = { menuItem: MenuItem; quantity: number; notes?: string };
|
||||
function Ticket({
|
||||
cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onSend, onPay, onSplit,
|
||||
cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onNote, onSend, onPay, onSplit,
|
||||
}: {
|
||||
cafeId: string; lines: TicketLine[]; subtotal: number; discount: number; tax: number; total: number;
|
||||
count: number; pendingCount: number;
|
||||
onBump: (id: string, d: number) => void; onRemove: (id: string) => void;
|
||||
onNote: (id: string, notes: string) => void;
|
||||
onSend: () => void; onPay: () => void; onSplit: () => void;
|
||||
}) {
|
||||
const [noteFor, setNoteFor] = useState<string | null>(null);
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-3">
|
||||
@@ -690,7 +694,8 @@ function Ticket({
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{lines.map((l) => (
|
||||
<li key={l.menuItem.id} className="flex items-center gap-2 rounded-xl border border-border/70 p-2">
|
||||
<li key={l.menuItem.id} className="rounded-xl border border-border/70 p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="line-clamp-1 font-medium">{l.menuItem.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{fmt(l.menuItem.price)} تومان</p>
|
||||
@@ -704,9 +709,30 @@ function Ticket({
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setNoteFor((cur) => (cur === l.menuItem.id ? null : l.menuItem.id))}
|
||||
className={`flex size-9 items-center justify-center rounded-lg hover:bg-accent active:scale-95 ${l.notes ? "text-primary" : "text-muted-foreground"}`}
|
||||
aria-label="یادداشت"
|
||||
title="یادداشت آیتم"
|
||||
>
|
||||
<StickyNote className="size-4" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onRemove(l.menuItem.id)} className="flex size-9 items-center justify-center rounded-lg text-red-500 hover:bg-red-50" aria-label="حذف">
|
||||
<Trash2 className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
{noteFor === l.menuItem.id || l.notes ? (
|
||||
<input
|
||||
value={l.notes ?? ""}
|
||||
onChange={(e) => onNote(l.menuItem.id, e.target.value)}
|
||||
placeholder="یادداشت برای آشپزخانه (مثلاً بدون شکر)"
|
||||
autoFocus={noteFor === l.menuItem.id}
|
||||
maxLength={200}
|
||||
dir="rtl"
|
||||
className="mt-2 w-full rounded-lg border border-border/70 bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
|
||||
/>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user