Polish order modal and stepper UI
This commit is contained in:
77
server.js
77
server.js
@@ -179,6 +179,30 @@ function layout(title, body, { authenticated = false, activePath = "/", subtitle
|
||||
</div>
|
||||
` : body}
|
||||
</div>
|
||||
<div id="modal-root"></div>
|
||||
<script>
|
||||
(function () {
|
||||
function closeModal() {
|
||||
var modalRoot = document.getElementById('modal-root');
|
||||
if (modalRoot) modalRoot.innerHTML = '';
|
||||
}
|
||||
document.addEventListener('click', function (event) {
|
||||
var close = event.target.closest && event.target.closest('[data-modal-close]');
|
||||
if (!close) return;
|
||||
event.preventDefault();
|
||||
closeModal();
|
||||
});
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape') closeModal();
|
||||
});
|
||||
document.addEventListener('htmx:afterSwap', function (event) {
|
||||
if (event.detail && event.detail.target && event.detail.target.id === 'modal-root') {
|
||||
var root = document.getElementById('modal-root');
|
||||
if (root) root.scrollTop = 0;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
@@ -503,7 +527,11 @@ function renderOrderDetail(order) {
|
||||
return `
|
||||
<div class="stack">
|
||||
<section class="panel">
|
||||
${panelHeader(`${order.patient} · ${order.id}`, "Order detail with the same structure as the proposed master-detail workflow.", `<a class="btn btn-secondary" href="/orders/${order.id}/pesan-khusus">Pesan khusus</a>`)}
|
||||
${panelHeader(
|
||||
`${order.patient} · ${order.id}`,
|
||||
"Order detail with the same structure as the proposed master-detail workflow.",
|
||||
`<button class="btn btn-secondary" type="button" hx-get="/fragments/modals/pesan-khusus?order_id=${escapeHtml(order.id)}" hx-target="#modal-root" hx-swap="innerHTML">Pesan khusus</button>`,
|
||||
)}
|
||||
<div class="grid grid-3">
|
||||
<div class="card"><span class="muted">Status</span><div style="margin-top:8px">${statusBadge(order.status)}</div></div>
|
||||
<div class="card"><span class="muted">Patient</span><strong style="display:block; margin-top:8px">${escapeHtml(order.patient)}</strong><span class="muted">${escapeHtml(order.mode)} · ${escapeHtml(order.age)} years</span></div>
|
||||
@@ -613,7 +641,7 @@ function renderOrderForm(step, stepKey = "demografi") {
|
||||
${steps
|
||||
.map(
|
||||
(item, index) => `
|
||||
<a class="step ${index === activeIndex ? "active" : ""}" href="/orders/new/${item[0]}">
|
||||
<a class="step ${index === activeIndex ? "active" : ""}" href="/orders/new/${item[0]}" hx-get="/fragments/forms/order-step/${item[0]}" hx-target="#order-step-fragment" hx-push-url="true">
|
||||
<strong>${escapeHtml(item[1])}</strong>
|
||||
<span>${escapeHtml(item[2])}</span>
|
||||
</a>
|
||||
@@ -973,7 +1001,7 @@ function problemLoginPage() {
|
||||
|
||||
function orderNewPage(path) {
|
||||
const step = path.split("/").filter(Boolean)[2] || "demografi";
|
||||
return layout("Create Order", renderOrderForm({}, step), {
|
||||
return layout("Create Order", `<div id="order-step-fragment">${renderOrderForm({}, step)}</div>`, {
|
||||
authenticated: true,
|
||||
activePath: "/orders",
|
||||
subtitle: "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.",
|
||||
@@ -1557,27 +1585,30 @@ function fragmentOrderStep(step) {
|
||||
function fragmentPesanKhusus(orderId) {
|
||||
const order = mockOrders.find((item) => item.id === orderId) || mockOrders[0];
|
||||
return `
|
||||
<section class="panel">
|
||||
${panelHeader("Pesan khusus", "Desktop can treat this as a modal-style panel; mobile reads it as a dedicated page.", `<a class="btn btn-secondary" href="/orders/${order.id}">Back</a>`)}
|
||||
<div class="detail-grid">
|
||||
<div class="card">
|
||||
<strong>${escapeHtml(order.patient)}</strong>
|
||||
<p class="muted">${escapeHtml(order.id)}</p>
|
||||
<div style="margin-top:12px">${statusBadge(order.status)}</div>
|
||||
<p style="margin-top:14px" class="muted">${escapeHtml(order.message)}</p>
|
||||
</div>
|
||||
<form class="form card" action="/orders/${order.id}/pesan-khusus" method="post">
|
||||
<label class="field">
|
||||
<span>Special message</span>
|
||||
<textarea name="pesan_khusus" placeholder="Type a special note for this order..." required>${escapeHtml(order.message)}</textarea>
|
||||
</label>
|
||||
<div class="topbar-actions" style="justify-content:flex-start">
|
||||
<button class="btn btn-primary" type="submit">Save message</button>
|
||||
<a class="btn btn-secondary" href="/orders/${order.id}">Cancel</a>
|
||||
<div class="modal-shell" role="dialog" aria-modal="true" aria-labelledby="pesan-khusus-title">
|
||||
<div class="modal-backdrop" data-modal-close></div>
|
||||
<section class="modal-card panel">
|
||||
${panelHeader("Pesan khusus", "Desktop opens this as a modal, mobile can still use it as a full sheet.", `<button class="btn btn-secondary" type="button" data-modal-close>Close</button>`)}
|
||||
<div class="detail-grid">
|
||||
<div class="card">
|
||||
<strong>${escapeHtml(order.patient)}</strong>
|
||||
<p class="muted">${escapeHtml(order.id)}</p>
|
||||
<div style="margin-top:12px">${statusBadge(order.status)}</div>
|
||||
<p style="margin-top:14px" class="muted">${escapeHtml(order.message)}</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<form class="form card" action="/orders/${order.id}/pesan-khusus" method="post">
|
||||
<label class="field">
|
||||
<span>Special message</span>
|
||||
<textarea name="pesan_khusus" placeholder="Type a special note for this order..." required>${escapeHtml(order.message)}</textarea>
|
||||
</label>
|
||||
<div class="topbar-actions" style="justify-content:flex-start">
|
||||
<button class="btn btn-primary" type="submit">Save message</button>
|
||||
<button class="btn btn-secondary" type="button" data-modal-close>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
36
styles.css
36
styles.css
@@ -798,6 +798,31 @@ tr:last-child td {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.modal-shell {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: min(980px, 100%);
|
||||
max-height: calc(100vh - 44px);
|
||||
overflow: auto;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.desktop-only {
|
||||
display: block;
|
||||
}
|
||||
@@ -872,6 +897,17 @@ tr:last-child td {
|
||||
.auth-screen {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.modal-shell {
|
||||
padding: 0;
|
||||
place-items: stretch;
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
width: 100%;
|
||||
max-height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
|
||||
Reference in New Issue
Block a user