bugfix : remove orphan
This commit is contained in:
@@ -314,7 +314,6 @@ jobs:
|
|||||||
- name: Start main services
|
- name: Start main services
|
||||||
run: |
|
run: |
|
||||||
docker compose up -d \
|
docker compose up -d \
|
||||||
--remove-orphans \
|
|
||||||
--no-deps \
|
--no-deps \
|
||||||
postgres redis api web website koja
|
postgres redis api web website koja
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ type PaymentRow = {
|
|||||||
amount: string;
|
amount: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type BranchPrintSettings = {
|
||||||
|
receiptHeader?: string | null;
|
||||||
|
receiptFooter?: string | null;
|
||||||
|
wifiPassword?: string | null;
|
||||||
|
paperWidthMm?: number;
|
||||||
|
};
|
||||||
|
|
||||||
type PosPayPanelProps = {
|
type PosPayPanelProps = {
|
||||||
cafeId: string;
|
cafeId: string;
|
||||||
numberLocale: string;
|
numberLocale: string;
|
||||||
@@ -48,6 +55,7 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan
|
|||||||
const [debouncedSearch, setDebouncedSearch] = useState("");
|
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||||
const [payMessage, setPayMessage] = useState<string | null>(null);
|
const [payMessage, setPayMessage] = useState<string | null>(null);
|
||||||
const [receiptOrder, setReceiptOrder] = useState<Order | null>(null);
|
const [receiptOrder, setReceiptOrder] = useState<Order | null>(null);
|
||||||
|
const printSettingsBranchId = receiptOrder?.branchId ?? branchId ?? null;
|
||||||
const [lastPaidOrderId, setLastPaidOrderId] = useState<string | null>(null);
|
const [lastPaidOrderId, setLastPaidOrderId] = useState<string | null>(null);
|
||||||
const [paymentRows, setPaymentRows] = useState<PaymentRow[]>([
|
const [paymentRows, setPaymentRows] = useState<PaymentRow[]>([
|
||||||
{ method: "Cash", amount: "" },
|
{ method: "Cash", amount: "" },
|
||||||
@@ -79,6 +87,16 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan
|
|||||||
enabled: !!cafeId,
|
enabled: !!cafeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: printSettings } = useQuery({
|
||||||
|
queryKey: ["branch-print-settings", cafeId, printSettingsBranchId],
|
||||||
|
queryFn: () =>
|
||||||
|
apiGet<BranchPrintSettings>(
|
||||||
|
`/api/cafes/${cafeId}/branches/${printSettingsBranchId}/print-settings`
|
||||||
|
),
|
||||||
|
enabled: !!cafeId && !!printSettingsBranchId,
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
const displayedOrders = useMemo(() => {
|
const displayedOrders = useMemo(() => {
|
||||||
if (!filterTableId) return openOrders;
|
if (!filterTableId) return openOrders;
|
||||||
return openOrders.filter((o) => o.tableId === filterTableId);
|
return openOrders.filter((o) => o.tableId === filterTableId);
|
||||||
@@ -630,6 +648,10 @@ export function PosPayPanel({ cafeId, numberLocale, branchId = null }: PosPayPan
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" • ") || undefined
|
.join(" • ") || undefined
|
||||||
}
|
}
|
||||||
|
receiptHeader={printSettings?.receiptHeader}
|
||||||
|
receiptFooter={printSettings?.receiptFooter}
|
||||||
|
wifiPassword={printSettings?.wifiPassword}
|
||||||
|
paperWidthMm={printSettings?.paperWidthMm}
|
||||||
onClose={() => setReceiptOrder(null)}
|
onClose={() => setReceiptOrder(null)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ type PosSlipModalProps = {
|
|||||||
logoUrl?: string;
|
logoUrl?: string;
|
||||||
/** Address / phone line shown under the café name on the bill. */
|
/** Address / phone line shown under the café name on the bill. */
|
||||||
tagline?: string;
|
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;
|
onClose: () => void;
|
||||||
/** Full order for customer bill */
|
/** Full order for customer bill */
|
||||||
order?: Order;
|
order?: Order;
|
||||||
@@ -39,6 +47,10 @@ export function PosSlipModal({
|
|||||||
cafeName,
|
cafeName,
|
||||||
logoUrl,
|
logoUrl,
|
||||||
tagline,
|
tagline,
|
||||||
|
receiptHeader,
|
||||||
|
receiptFooter,
|
||||||
|
wifiPassword,
|
||||||
|
paperWidthMm,
|
||||||
onClose,
|
onClose,
|
||||||
order,
|
order,
|
||||||
kitchenLines = [],
|
kitchenLines = [],
|
||||||
@@ -103,6 +115,9 @@ export function PosSlipModal({
|
|||||||
cafeName,
|
cafeName,
|
||||||
logoUrl: resolveMediaUrl(logoUrl),
|
logoUrl: resolveMediaUrl(logoUrl),
|
||||||
tagline,
|
tagline,
|
||||||
|
header: receiptHeader?.trim() || undefined,
|
||||||
|
wifi: wifiPassword?.trim() || undefined,
|
||||||
|
paperWidthMm,
|
||||||
title: t("billTitle"),
|
title: t("billTitle"),
|
||||||
date: formattedDate,
|
date: formattedDate,
|
||||||
metaRow,
|
metaRow,
|
||||||
@@ -118,7 +133,7 @@ export function PosSlipModal({
|
|||||||
amount: formatCurrency(p.amount, numberLocale),
|
amount: formatCurrency(p.amount, numberLocale),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
footer: t("thankYou"),
|
footer: receiptFooter?.trim() || t("thankYou"),
|
||||||
locale,
|
locale,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -147,6 +162,11 @@ export function PosSlipModal({
|
|||||||
{variant === "bill" && tagline && (
|
{variant === "bill" && tagline && (
|
||||||
<div className="text-center text-[10px] text-muted-foreground">{tagline}</div>
|
<div className="text-center text-[10px] text-muted-foreground">{tagline}</div>
|
||||||
)}
|
)}
|
||||||
|
{variant === "bill" && receiptHeader?.trim() && (
|
||||||
|
<div className="whitespace-pre-line text-center text-[11px] font-medium text-foreground/80">
|
||||||
|
{receiptHeader.trim()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mb-1 mt-1.5 border-y border-foreground/60 py-0.5 text-center text-xs font-bold">
|
<div className="mb-1 mt-1.5 border-y border-foreground/60 py-0.5 text-center text-xs font-bold">
|
||||||
{variant === "kitchen" ? t("kitchenTitle") : t("billTitle")}
|
{variant === "kitchen" ? t("kitchenTitle") : t("billTitle")}
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +211,14 @@ export function PosSlipModal({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="receipt-divider" />
|
<div className="receipt-divider" />
|
||||||
<div className="mt-2 text-center text-xs">{t("thankYou")}</div>
|
{wifiPassword?.trim() && (
|
||||||
|
<div className="text-center text-[11px]" dir="ltr">
|
||||||
|
WiFi: {wifiPassword.trim()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="mt-2 text-center text-xs">
|
||||||
|
{receiptFooter?.trim() || t("thankYou")}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ export type ThermalSlipData = {
|
|||||||
logoUrl?: string;
|
logoUrl?: string;
|
||||||
/** Optional tagline / address line under the café name. */
|
/** Optional tagline / address line under the café name. */
|
||||||
tagline?: string;
|
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). */
|
/** Absolute URL to the bundled Vazirmatn web-font (same origin). */
|
||||||
@@ -104,9 +110,14 @@ export function buildThermalDocument(data: ThermalSlipData): string {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const footerHtml = data.footer
|
const wifiLabel = isRtl ? "وایفای" : "WiFi";
|
||||||
? `<hr class="dashed"><div class="center sm muted">${esc(data.footer)}</div>`
|
const footerInner = [
|
||||||
: "";
|
data.wifi ? `<div class="center sm">${wifiLabel}: ${esc(data.wifi)}</div>` : "",
|
||||||
|
data.footer ? `<div class="center sm muted">${esc(data.footer)}</div>` : "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("");
|
||||||
|
const footerHtml = footerInner ? `<hr class="dashed">${footerInner}` : "";
|
||||||
|
|
||||||
const logoHtml = data.logoUrl
|
const logoHtml = data.logoUrl
|
||||||
? `<img class="logo" src="${esc(data.logoUrl)}" alt="">`
|
? `<img class="logo" src="${esc(data.logoUrl)}" alt="">`
|
||||||
@@ -114,6 +125,15 @@ export function buildThermalDocument(data: ThermalSlipData): string {
|
|||||||
const taglineHtml = data.tagline
|
const taglineHtml = data.tagline
|
||||||
? `<div class="center tagline">${esc(data.tagline)}</div>`
|
? `<div class="center tagline">${esc(data.tagline)}</div>`
|
||||||
: "";
|
: "";
|
||||||
|
const headerHtml = data.header
|
||||||
|
? `<div class="center header-note">${esc(data.header)}</div>`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// 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();
|
const fontUrl = vazirmatnFontUrl();
|
||||||
|
|
||||||
@@ -132,18 +152,18 @@ export function buildThermalDocument(data: ThermalSlipData): string {
|
|||||||
}
|
}
|
||||||
/* ── Page ─────────────────────────────────────────────────── */
|
/* ── Page ─────────────────────────────────────────────────── */
|
||||||
@page {
|
@page {
|
||||||
/* 80 mm wide; height tracks the content — no blank tail */
|
/* width tracks the configured paper; height tracks content — no blank tail */
|
||||||
size: 80mm auto;
|
size: ${widthMm}mm auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
/* ── Reset ────────────────────────────────────────────────── */
|
/* ── Reset ────────────────────────────────────────────────── */
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
/* ── Root ─────────────────────────────────────────────────── */
|
/* ── Root ─────────────────────────────────────────────────── */
|
||||||
html, body {
|
html, body {
|
||||||
width: 80mm;
|
width: ${widthMm}mm;
|
||||||
direction: ${dir};
|
direction: ${dir};
|
||||||
font-family: 'Vazirmatn', 'Tahoma', 'Arial', sans-serif;
|
font-family: 'Vazirmatn', 'Tahoma', 'Arial', sans-serif;
|
||||||
font-size: 11.5pt;
|
font-size: ${baseFontPt}pt;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -152,7 +172,7 @@ html, body {
|
|||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
/* ── Wrapper ──────────────────────────────────────────────── */
|
/* ── Wrapper ──────────────────────────────────────────────── */
|
||||||
.wrap { padding: 4mm 4.5mm 6mm; }
|
.wrap { padding: ${wrapPadMm}; }
|
||||||
/* ── Branding header ──────────────────────────────────────── */
|
/* ── Branding header ──────────────────────────────────────── */
|
||||||
.logo {
|
.logo {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -174,6 +194,13 @@ html, body {
|
|||||||
color: #444;
|
color: #444;
|
||||||
margin-top: 0.5mm;
|
margin-top: 0.5mm;
|
||||||
}
|
}
|
||||||
|
.header-note {
|
||||||
|
font-size: 9pt;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
margin-top: 1mm;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
.doc-title {
|
.doc-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
@@ -233,6 +260,7 @@ hr.solid { border: none; border-top: 1px solid #000; margin: 2.5mm 0; }
|
|||||||
${logoHtml}
|
${logoHtml}
|
||||||
<div class="cafe-name">${esc(data.cafeName)}</div>
|
<div class="cafe-name">${esc(data.cafeName)}</div>
|
||||||
${taglineHtml}
|
${taglineHtml}
|
||||||
|
${headerHtml}
|
||||||
<div class="doc-title">${esc(data.title)}</div>
|
<div class="doc-title">${esc(data.title)}</div>
|
||||||
<div class="date">${esc(data.date)}</div>
|
<div class="date">${esc(data.date)}</div>
|
||||||
${data.metaRow ? `<div class="meta">${esc(data.metaRow)}</div>` : ""}
|
${data.metaRow ? `<div class="meta">${esc(data.metaRow)}</div>` : ""}
|
||||||
|
|||||||
Reference in New Issue
Block a user