Add QR share and print actions

This commit is contained in:
sas.fajri
2026-04-13 19:09:15 +07:00
parent 24921e836c
commit 80b4f132d8
2 changed files with 211 additions and 10 deletions

View File

@@ -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 `
<div class="stack order-created-shell" data-order-saved="true">
<section class="panel">
${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.",
`<div class="pill-row">
<a class="btn btn-secondary" href="/orders/${escapeHtml(order.id || "")}">Open detail</a>
<a class="btn btn-primary" href="/orders/new">Create new order</a>
@@ -661,14 +682,47 @@ function renderOrderSaved(order) {
)}
<div class="detail-grid">
<div class="card order-created-qr">
<strong>QR Code</strong>
<p class="muted">${escapeHtml(qrText || "-")}</p>
<div class="qr-preview">
${
qrText
? `<img class="image_qrcode" src="${qrImageUrl(qrText)}" alt="QR code for order ${escapeHtml(qrText)}" loading="lazy" />`
: `<div class="note-box">QR code not available.</div>`
}
<div class="qr-slip">
<div class="qr-slip-header">
<div class="qr-slip-label">Tanggal Order</div>
<div class="qr-slip-value">${escapeHtml(printableDate || "-")}</div>
<div class="qr-slip-label">Nama</div>
<div class="qr-slip-value">${escapeHtml(order.patient || "-")}</div>
<div class="qr-slip-label">Dokter</div>
<div class="qr-slip-value">${escapeHtml(printableDoctor)}</div>
</div>
<div class="qr-slip-section-title">Pemeriksaan</div>
<div class="qr-slip-tests">
${
details.length
? details
.map(
(item) => `<div class="qr-slip-test">- ${escapeHtml(item.test_name || item.testName || "-")}</div>`,
)
.join("")
: `<div class="qr-slip-test muted">-</div>`
}
</div>
<div class="qr-preview">
${
qrText
? `<img class="image_qrcode" src="${qrImageUrl(qrText, 320)}" alt="QR code for order ${escapeHtml(qrText)}" loading="lazy" />`
: `<div class="note-box">QR code not available.</div>`
}
</div>
<div class="qr-code-text">${escapeHtml(qrText || "-")}</div>
<div class="pill-row qr-actions">
${
qrText
? `<a class="btn btn-secondary" href="${escapeHtml(
`https://wa.me/?text=${encodeURIComponent(
`Order ${order.id || ""} | ${order.patient || ""} | QR ${qrText}`,
)}`,
)}" target="_blank" rel="noreferrer">Share WhatsApp</a>`
: ""
}
<button class="btn btn-primary" type="button" data-action="print-order">Print</button>
</div>
</div>
</div>
<div class="card">
@@ -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(

View File

@@ -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;