diff --git a/web/dashboard/src/components/pos2/pos2-screen.tsx b/web/dashboard/src/components/pos2/pos2-screen.tsx index 209f5fd..46bc291 100644 --- a/web/dashboard/src/components/pos2/pos2-screen.tsx +++ b/web/dashboard/src/components/pos2/pos2-screen.tsx @@ -10,7 +10,7 @@ import { useEffect, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { - Search, Plus, Minus, Trash2, Send, CreditCard, Pause, SplitSquareHorizontal, + Search, Plus, Minus, Trash2, Send, CreditCard, SplitSquareHorizontal, X, WifiOff, ShoppingCart, Users, Coffee, ArrowRight, LayoutGrid, Armchair, Banknote, Check, Delete, ReceiptText, ShoppingBag, Loader2, RotateCcw, BadgePercent, Sparkles, Home, StickyNote, @@ -24,6 +24,7 @@ import { useCartStore } from "@/lib/stores/cart.store"; import { apiGet, apiPost, ApiClientError } from "@/lib/api/client"; import { submitOrderToApi, orderAmountDue, isLocalOrder } from "@/lib/pos/submit-order"; import { requestPosPayment, posDeviceErrorMessage } from "@/lib/api/pos-device"; +import { printReceipt } from "@/lib/api/print"; import { PosCustomerPicker } from "@/components/pos/pos-customer-picker"; import { Can } from "@/components/auth/can"; import type { Customer, MenuItem, Order, TableBoardItem } from "@/lib/api/types"; @@ -107,6 +108,7 @@ export function Pos2Screen() { const hydrateFromOrder = useCartStore((s) => s.hydrateFromOrder); const clearSession = useCartStore((s) => s.clearSession); const activeOrderNo = useCartStore((s) => s.activeOrderDisplayNumber); + const activeOrderId = useCartStore((s) => s.activeOrderId); const appliedCoupon = useCartStore((s) => s.appliedCoupon); // local view state @@ -285,7 +287,10 @@ export function Pos2Screen() { loyaltyPointsToRedeem: loyaltyRedeem > 0 ? loyaltyRedeem : undefined, }); const paid = payments.reduce((s, p) => s + p.amount, 0); - notify.success(`پرداخت ${fmt(paid)} تومان ثبت شد`); + const paidOrderId = payTarget.id; + notify.success(`پرداخت ${fmt(paid)} تومان ثبت شد`, { + action: { label: "چاپ فاکتور", onClick: () => void printReceipt(cafeId as string, paidOrderId) }, + }); queryClient.invalidateQueries({ queryKey: ["tables-board", cafeId] }); queryClient.invalidateQueries({ queryKey: ["orders-open", cafeId] }); backToBoard(); @@ -302,6 +307,27 @@ export function Pos2Screen() { } }; + // Print (or reprint) the customer receipt for the active, already-saved order. + const printActiveReceipt = async () => { + if (!activeOrderId || isLocalOrder(activeOrderId)) { + notify.error("ابتدا سفارش را ثبت کنید"); + return; + } + try { + await printReceipt(cafeId as string, activeOrderId); + notify.success("فاکتور برای چاپ ارسال شد"); + } catch (e) { + const code = e instanceof ApiClientError ? e.code : ""; + notify.error( + code === "PRINTER_NOT_CONFIGURED" || code === "KITCHEN_PRINTER_NOT_CONFIGURED" + ? "پرینتر فاکتور تنظیم نشده است" + : code === "PRINTER_CONNECTION_FAILED" + ? "اتصال به پرینتر برقرار نشد" + : "چاپ فاکتور ناموفق بود", + ); + } + }; + // ── guards ─────────────────────────────────────────────────────────────── if (!cafeId) { return ( @@ -322,6 +348,8 @@ export function Pos2Screen() { 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), + canPrint: !!activeOrderId && !isLocalOrder(activeOrderId), + onPrintReceipt: printActiveReceipt, }; // ── TABLE BOARD ──────────────────────────────────────────────────────────── @@ -673,13 +701,14 @@ function Pos2Extras({ cafeId }: { cafeId: string }) { // ── Order ticket ───────────────────────────────────────────────────────────── type TicketLine = { menuItem: MenuItem; quantity: number; notes?: string }; function Ticket({ - cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onNote, onSend, onPay, onSplit, + cafeId, lines, subtotal, discount, tax, total, count, pendingCount, onBump, onRemove, onNote, onSend, onPay, onSplit, canPrint, onPrintReceipt, }: { 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; + canPrint: boolean; onPrintReceipt: () => void; }) { const [noteFor, setNoteFor] = useState(null); return ( @@ -762,8 +791,10 @@ function Ticket({ پرداخت -