From 51e422272d97c7f5d8c5d9e88385d1baa2a3c4eb Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Sat, 30 May 2026 09:42:32 +0330 Subject: [PATCH] bugfix : remove orphan --- .gitea/workflows/ci-cd.yml | 1 - .../src/components/pos/pos-pay-panel.tsx | 22 ++++++++++ .../src/components/pos/pos-slip-modal.tsx | 31 ++++++++++++- web/dashboard/src/lib/thermal-print.ts | 44 +++++++++++++++---- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index a09eab3..c2e7822 100644 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -314,7 +314,6 @@ jobs: - name: Start main services run: | docker compose up -d \ - --remove-orphans \ --no-deps \ postgres redis api web website koja diff --git a/web/dashboard/src/components/pos/pos-pay-panel.tsx b/web/dashboard/src/components/pos/pos-pay-panel.tsx index 4254b62..5f55028 100644 --- a/web/dashboard/src/components/pos/pos-pay-panel.tsx +++ b/web/dashboard/src/components/pos/pos-pay-panel.tsx @@ -27,6 +27,13 @@ type PaymentRow = { amount: string; }; +type BranchPrintSettings = { + receiptHeader?: string | null; + receiptFooter?: string | null; + wifiPassword?: string | null; + paperWidthMm?: number; +}; + type PosPayPanelProps = { cafeId: string; numberLocale: string; @@ -48,6 +55,7 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan const [debouncedSearch, setDebouncedSearch] = useState(""); const [payMessage, setPayMessage] = useState(null); const [receiptOrder, setReceiptOrder] = useState(null); + const printSettingsBranchId = receiptOrder?.branchId ?? branchId ?? null; const [lastPaidOrderId, setLastPaidOrderId] = useState(null); const [paymentRows, setPaymentRows] = useState([ { method: "Cash", amount: "" }, @@ -79,6 +87,16 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan enabled: !!cafeId, }); + const { data: printSettings } = useQuery({ + queryKey: ["branch-print-settings", cafeId, printSettingsBranchId], + queryFn: () => + apiGet( + `/api/cafes/${cafeId}/branches/${printSettingsBranchId}/print-settings` + ), + enabled: !!cafeId && !!printSettingsBranchId, + staleTime: 5 * 60 * 1000, + }); + const displayedOrders = useMemo(() => { if (!filterTableId) return openOrders; return openOrders.filter((o) => o.tableId === filterTableId); @@ -630,6 +648,10 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan .filter(Boolean) .join(" • ") || undefined } + receiptHeader={printSettings?.receiptHeader} + receiptFooter={printSettings?.receiptFooter} + wifiPassword={printSettings?.wifiPassword} + paperWidthMm={printSettings?.paperWidthMm} onClose={() => setReceiptOrder(null)} /> ) : null} diff --git a/web/dashboard/src/components/pos/pos-slip-modal.tsx b/web/dashboard/src/components/pos/pos-slip-modal.tsx index 259fb67..091d2aa 100644 --- a/web/dashboard/src/components/pos/pos-slip-modal.tsx +++ b/web/dashboard/src/components/pos/pos-slip-modal.tsx @@ -23,6 +23,14 @@ type PosSlipModalProps = { logoUrl?: string; /** Address / phone line shown under the café name on the bill. */ tagline?: string; + /** Custom header note from branch print settings (bill only). */ + receiptHeader?: string | null; + /** Custom footer note from branch print settings (bill only). */ + receiptFooter?: string | null; + /** WiFi password printed near the bill footer. */ + wifiPassword?: string | null; + /** Paper width in mm — 58 or 80 (default 80). */ + paperWidthMm?: number; onClose: () => void; /** Full order for customer bill */ order?: Order; @@ -39,6 +47,10 @@ export function PosSlipModal({ cafeName, logoUrl, tagline, + receiptHeader, + receiptFooter, + wifiPassword, + paperWidthMm, onClose, order, kitchenLines = [], @@ -103,6 +115,9 @@ export function PosSlipModal({ cafeName, logoUrl: resolveMediaUrl(logoUrl), tagline, + header: receiptHeader?.trim() || undefined, + wifi: wifiPassword?.trim() || undefined, + paperWidthMm, title: t("billTitle"), date: formattedDate, metaRow, @@ -118,7 +133,7 @@ export function PosSlipModal({ amount: formatCurrency(p.amount, numberLocale), })), }, - footer: t("thankYou"), + footer: receiptFooter?.trim() || t("thankYou"), locale, }; @@ -147,6 +162,11 @@ export function PosSlipModal({ {variant === "bill" && tagline && (
{tagline}
)} + {variant === "bill" && receiptHeader?.trim() && ( +
+ {receiptHeader.trim()} +
+ )}
{variant === "kitchen" ? t("kitchenTitle") : t("billTitle")}
@@ -191,7 +211,14 @@ export function PosSlipModal({ ))}
-
{t("thankYou")}
+ {wifiPassword?.trim() && ( +
+ WiFi: {wifiPassword.trim()} +
+ )} +
+ {receiptFooter?.trim() || t("thankYou")} +
)} diff --git a/web/dashboard/src/lib/thermal-print.ts b/web/dashboard/src/lib/thermal-print.ts index 63c0821..9aa7473 100644 --- a/web/dashboard/src/lib/thermal-print.ts +++ b/web/dashboard/src/lib/thermal-print.ts @@ -44,6 +44,12 @@ export type ThermalSlipData = { logoUrl?: string; /** Optional tagline / address line under the café name. */ tagline?: string; + /** Optional custom header note (from branch print settings) shown under the name. */ + header?: string; + /** Optional WiFi password line printed near the footer. */ + wifi?: string; + /** Paper width in mm — 58 or 80 (default 80). Controls page width + scale. */ + paperWidthMm?: number; }; /** Absolute URL to the bundled Vazirmatn web-font (same origin). */ @@ -104,9 +110,14 @@ export function buildThermalDocument(data: ThermalSlipData): string { `; } - const footerHtml = data.footer - ? `
${esc(data.footer)}
` - : ""; + const wifiLabel = isRtl ? "وای‌فای" : "WiFi"; + const footerInner = [ + data.wifi ? `
${wifiLabel}: ${esc(data.wifi)}
` : "", + data.footer ? `
${esc(data.footer)}
` : "", + ] + .filter(Boolean) + .join(""); + const footerHtml = footerInner ? `
${footerInner}` : ""; const logoHtml = data.logoUrl ? `` @@ -114,6 +125,15 @@ export function buildThermalDocument(data: ThermalSlipData): string { const taglineHtml = data.tagline ? `
${esc(data.tagline)}
` : ""; + const headerHtml = data.header + ? `
${esc(data.header)}
` + : ""; + + // Paper width: 58 mm or 80 mm. Narrower paper gets a slightly smaller base + // font so lines don't wrap awkwardly. + const widthMm = data.paperWidthMm === 58 ? 58 : 80; + const baseFontPt = widthMm === 58 ? 10 : 11.5; + const wrapPadMm = widthMm === 58 ? "3mm 3mm 5mm" : "4mm 4.5mm 6mm"; const fontUrl = vazirmatnFontUrl(); @@ -132,18 +152,18 @@ export function buildThermalDocument(data: ThermalSlipData): string { } /* ── Page ─────────────────────────────────────────────────── */ @page { - /* 80 mm wide; height tracks the content — no blank tail */ - size: 80mm auto; + /* width tracks the configured paper; height tracks content — no blank tail */ + size: ${widthMm}mm auto; margin: 0; } /* ── Reset ────────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } /* ── Root ─────────────────────────────────────────────────── */ html, body { - width: 80mm; + width: ${widthMm}mm; direction: ${dir}; font-family: 'Vazirmatn', 'Tahoma', 'Arial', sans-serif; - font-size: 11.5pt; + font-size: ${baseFontPt}pt; font-weight: 500; line-height: 1.55; color: #000; @@ -152,7 +172,7 @@ html, body { text-rendering: optimizeLegibility; } /* ── Wrapper ──────────────────────────────────────────────── */ -.wrap { padding: 4mm 4.5mm 6mm; } +.wrap { padding: ${wrapPadMm}; } /* ── Branding header ──────────────────────────────────────── */ .logo { display: block; @@ -174,6 +194,13 @@ html, body { color: #444; margin-top: 0.5mm; } +.header-note { + font-size: 9pt; + font-weight: 600; + color: #222; + margin-top: 1mm; + white-space: pre-line; +} .doc-title { text-align: center; font-size: 10pt; @@ -233,6 +260,7 @@ hr.solid { border: none; border-top: 1px solid #000; margin: 2.5mm 0; } ${logoHtml}
${esc(data.cafeName)}
${taglineHtml} + ${headerHtml}
${esc(data.title)}
${esc(data.date)}
${data.metaRow ? `
${esc(data.metaRow)}
` : ""}