diff --git a/server.js b/server.js index 08238f4..6b014e7 100644 --- a/server.js +++ b/server.js @@ -268,6 +268,15 @@ function layout(title, body, { authenticated = false, activePath = "/", subtitle injectOrderDraftPayload(form); }); document.addEventListener('click', function (event) { + var printTrigger = event.target.closest && event.target.closest('[data-action="print-order"]'); + if (printTrigger) { + event.preventDefault(); + document.body.classList.add('print-order'); + window.setTimeout(function () { + window.print(); + }, 50); + return; + } var close = event.target.closest && event.target.closest('[data-modal-close]'); if (!close) return; event.preventDefault(); @@ -276,6 +285,9 @@ function layout(title, body, { authenticated = false, activePath = "/", subtitle document.addEventListener('keydown', function (event) { if (event.key === 'Escape') closeModal(); }); + window.addEventListener('afterprint', function () { + document.body.classList.remove('print-order'); + }); document.addEventListener('htmx:afterSwap', function (event) { if (event.detail && event.detail.target && event.detail.target.id === 'modal-root') { var root = document.getElementById('modal-root'); @@ -645,15 +657,24 @@ function qrImageUrl(value, size = 240) { return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${data}`; } +function formatOrderDisplayDate(value) { + const text = String(value || "").trim(); + const match = text.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}:\d{2}:\d{2}))?$/); + if (!match) return text; + return `${match[3]}-${match[2]}-${match[1]}${match[4] ? ` ${match[4]}` : ""}`; +} + function renderOrderSaved(order) { const details = Array.isArray(order.details) ? order.details : []; const qrText = order.orderCode || order.id || ""; + const printableDate = formatOrderDisplayDate(order.orderDate || ""); + const printableDoctor = order.doctorName || order.doctor || sampleLogin.username; return `
${panelHeader( - "Order saved", - "QR Code and review are shown after the order has been submitted successfully.", + "QR Step", + "Order is saved. Share it to WhatsApp or print the slip below.", `
Open detail Create new order @@ -661,14 +682,47 @@ function renderOrderSaved(order) { )}
- QR Code -

${escapeHtml(qrText || "-")}

-
- ${ - qrText - ? `QR code for order ${escapeHtml(qrText)}` - : `
QR code not available.
` - } +
+
+
Tanggal Order
+
${escapeHtml(printableDate || "-")}
+
Nama
+
${escapeHtml(order.patient || "-")}
+
Dokter
+
${escapeHtml(printableDoctor)}
+
+
Pemeriksaan
+
+ ${ + details.length + ? details + .map( + (item) => `
- ${escapeHtml(item.test_name || item.testName || "-")}
`, + ) + .join("") + : `
-
` + } +
+
+ ${ + qrText + ? `QR code for order ${escapeHtml(qrText)}` + : `
QR code not available.
` + } +
+
${escapeHtml(qrText || "-")}
+
+ ${ + qrText + ? `Share WhatsApp` + : "" + } + +
@@ -2250,6 +2304,7 @@ async function renderRoute(req, res, url) { message: saved.order_note || body.patient_note || "", orderDate: saved.order_date || "", orderCode: saved.order_qrcode || "", + doctorName: sessionData.username || sampleLogin.username, details: Array.isArray(saved.details) ? saved.details : Array.isArray(body.details) ? body.details : [], }; html( diff --git a/styles.css b/styles.css index 9dfd4d9..14f0b5c 100644 --- a/styles.css +++ b/styles.css @@ -906,6 +906,54 @@ tr:last-child td { text-align: center; } +.qr-slip { + width: 100%; + display: grid; + gap: 14px; +} + +.qr-slip-header { + display: grid; + grid-template-columns: 190px minmax(0, 1fr); + gap: 2px 18px; + align-items: start; + text-align: left; + font-size: 1.02rem; +} + +.qr-slip-label { + font-weight: 700; +} + +.qr-slip-value { + font-weight: 500; +} + +.qr-slip-section-title { + margin-top: 8px; + font-size: 1.4rem; + font-weight: 700; + text-align: left; +} + +.qr-slip-tests { + display: grid; + gap: 8px; + text-align: left; +} + +.qr-slip-test { + font-size: 1.15rem; + line-height: 1.3; +} + +.qr-code-text { + font-size: 1.7rem; + font-weight: 800; + letter-spacing: 0.04em; + text-align: center; +} + .qr-preview { display: grid; place-items: center; @@ -928,6 +976,104 @@ tr:last-child td { box-shadow: 0 14px 30px rgba(15, 23, 42, 0.08); } +body.print-order .bg-orb, +body.print-order .bg-grid, +body.print-order .sidebar, +body.print-order .topbar, +body.print-order .mobile-nav, +body.print-order .order-created-shell .panel .pill-row, +body.print-order .order-created-shell .qr-actions, +body.print-order .order-created-shell .card:not(.order-created-qr), +body.print-order .order-created-shell > .panel:last-child { + display: none !important; +} + +body.print-order { + background: white; +} + +body.print-order #app { + min-height: auto; +} + +body.print-order .page-shell { + display: block; + min-height: auto; +} + +body.print-order .content { + padding: 0; +} + +body.print-order .content-inner { + max-width: none; + margin: 0; + gap: 0; +} + +body.print-order .order-created-shell { + gap: 0; +} + +body.print-order .order-created-shell > .panel { + box-shadow: none; + border: none; + background: white; + padding: 0; +} + +body.print-order .order-created-shell .panel-header { + margin-bottom: 24px; +} + +body.print-order .order-created-shell .detail-grid { + grid-template-columns: 1fr; +} + +body.print-order .qr-slip { + width: 100%; +} + +body.print-order .qr-preview { + border: none; + background: transparent; + min-height: 0; + padding: 10px 0 0; +} + +body.print-order .image_qrcode { + width: 320px; + height: 320px; + box-shadow: none; +} + +body.print-order .qr-code-text { + font-size: 1.9rem; +} + +@media print { + @page { + size: auto; + margin: 12mm; + } + + body { + background: white !important; + } + + body.print-order .content { + padding: 0 !important; + } + + body.print-order .topbar, + body.print-order .sidebar, + body.print-order .mobile-nav, + body.print-order .bg-orb, + body.print-order .bg-grid { + display: none !important; + } +} + .form { display: grid; gap: 16px;