Polish results, patients, and nav state

This commit is contained in:
sas.fajri
2026-04-13 15:10:36 +07:00
parent a6f9b60362
commit bc6d4fcdad
2 changed files with 118 additions and 43 deletions

116
server.js
View File

@@ -200,6 +200,14 @@ function layout(title, body, { authenticated = false, activePath = "/", subtitle
var root = document.getElementById('modal-root'); var root = document.getElementById('modal-root');
if (root) root.scrollTop = 0; if (root) root.scrollTop = 0;
} }
if (event.detail && event.detail.target && event.detail.target.classList) {
event.detail.target.classList.remove('swap-in');
void event.detail.target.offsetWidth;
event.detail.target.classList.add('swap-in');
window.setTimeout(function () {
event.detail.target.classList.remove('swap-in');
}, 240);
}
}); });
})(); })();
</script> </script>
@@ -719,7 +727,7 @@ function renderResultsTable(results, selectedResultId) {
.join("")} .join("")}
</div> </div>
</section> </section>
<aside class="panel" id="result-detail-fragment"> <aside class="panel swap-zone" id="result-detail-fragment">
${panelHeader("Selected result", "Use the right pane for quick context without losing list position.", `<a class="btn btn-secondary" href="/results/${selected.id}">Open detail</a>`)} ${panelHeader("Selected result", "Use the right pane for quick context without losing list position.", `<a class="btn btn-secondary" href="/results/${selected.id}">Open detail</a>`)}
<div class="stack"> <div class="stack">
<div class="card"> <div class="card">
@@ -737,7 +745,7 @@ function renderResultsTable(results, selectedResultId) {
function renderResultDetail(result) { function renderResultDetail(result) {
return ` return `
<section class="panel"> <section class="panel swap-zone">
${panelHeader(`${result.patient} · ${result.id}`, "Result detail with summary, status, and interpretation fields.", '<a class="btn btn-secondary" href="/results">Back to results</a>')} ${panelHeader(`${result.patient} · ${result.id}`, "Result detail with summary, status, and interpretation fields.", '<a class="btn btn-secondary" href="/results">Back to results</a>')}
<div class="grid grid-3"> <div class="grid grid-3">
<div class="card"><span class="muted">Status</span><div style="margin-top:8px">${statusBadge(result.status)}</div></div> <div class="card"><span class="muted">Status</span><div style="margin-top:8px">${statusBadge(result.status)}</div></div>
@@ -763,7 +771,7 @@ function renderResultDetail(result) {
function renderFpp(groups) { function renderFpp(groups) {
return ` return `
<div class="stack"> <div class="stack swap-zone">
<section class="panel"> <section class="panel">
${panelHeader("FPP catalog", "Filter blocks and list cards on mobile, richer panel on desktop.")} ${panelHeader("FPP catalog", "Filter blocks and list cards on mobile, richer panel on desktop.")}
<div class="pill-row"> <div class="pill-row">
@@ -822,49 +830,71 @@ function renderPatients() {
return ` return `
<div class="stack"> <div class="stack">
<section class="panel"> <section class="panel">
${panelHeader("Patient registration", "Landing page for registration, lookup, and entry flow.")} ${panelHeader("Patient registration", "A landing zone for registration, lookup, and intake shortcuts.", '<a class="btn btn-primary" href="/orders/new/demografi">Start registration</a>')}
<div class="grid grid-3"> <div class="grid grid-4">
<div class="card"> ${[
<strong>New patient</strong> ["Today's intake", "11", "New or updated patients"],
<p class="muted">Open the registration stepper and start a fresh case.</p> ["Active visits", "7", "Cases linked to lab orders"],
<a class="btn btn-primary" href="/orders/new/demografi">Start registration</a> ["QR scans", "4", "Fast entry from QR code"],
</div> ["Needs review", "2", "Patients waiting verification"],
<div class="card"> ]
<strong>Lookup</strong> .map(
<p class="muted">Search an existing patient and reuse their visit data.</p> ([label, value, hint]) => `
<button class="btn btn-secondary" type="button">Search patient</button> <article class="card metric">
</div> <div class="kicker">
<div class="card"> <span>${escapeHtml(label)}</span>
<strong>QR entry</strong> <span class="trend">Live</span>
<p class="muted">Fast path for scan-based intake.</p> </div>
<a class="btn btn-secondary" href="/orders/new/qrcode">Open QR flow</a> <strong>${escapeHtml(value)}</strong>
</div> <span class="muted">${escapeHtml(hint)}</span>
</article>
`,
)
.join("")}
</div> </div>
</section> </section>
<section class="panel"> <section class="detail-grid">
${panelHeader("Recent patients", "A compact list that becomes cards on mobile.")} <div class="panel">
<div class="table-wrap"> ${panelHeader("Quick intake", "Route into the proper entry flow without forcing the user through extra screens.")}
<table> <div class="grid grid-2">
<thead> <div class="card">
<tr><th>Name</th><th>MRN</th><th>Gender</th><th>Last visit</th><th>Note</th></tr> <strong>Lookup patient</strong>
</thead> <p class="muted">Search existing records and attach them to the next order.</p>
<tbody> <div class="field" style="margin-top:12px">
${mockPatients <label><span class="muted">MRN / name</span></label>
.map( <input placeholder="Siti Amelia" />
(person) => ` </div>
<tr> <button class="btn btn-secondary" type="button" style="margin-top:12px">Search</button>
<td><strong>${escapeHtml(person.name)}</strong></td> </div>
<td>${escapeHtml(person.mrn)}</td> <div class="card">
<td>${escapeHtml(person.gender)}</td> <strong>New patient</strong>
<td>${escapeHtml(person.lastVisit)}</td> <p class="muted">Jump straight into demographic capture.</p>
<td>${escapeHtml(person.note)}</td> <div class="pill-row" style="margin-top:12px">
</tr> <a class="pill" href="/orders/new/demografi">Demografi</a>
`, <a class="pill" href="/orders/new/qrcode">QR entry</a>
) </div>
.join("")} <div class="note-box" style="margin-top:14px">Use the registration stepper when the patient is not yet in the system.</div>
</tbody> </div>
</table> </div>
</div> </div>
<aside class="panel">
${panelHeader("Recent patients", "A compact list that stays readable on mobile and still works as a table on desktop.")}
<div class="mini-list">
${mockPatients
.map(
(person) => `
<div class="mini-item">
<div>
<strong>${escapeHtml(person.name)}</strong>
<p>${escapeHtml(person.mrn)} · ${escapeHtml(person.gender)}</p>
</div>
<span class="pill">${escapeHtml(person.lastVisit)}</span>
</div>
`,
)
.join("")}
</div>
</aside>
</section> </section>
</div> </div>
`; `;

View File

@@ -205,6 +205,19 @@ button {
color: white; color: white;
background: linear-gradient(135deg, var(--brand), #0f9488); background: linear-gradient(135deg, var(--brand), #0f9488);
box-shadow: 0 12px 24px rgba(15, 118, 110, 0.24); box-shadow: 0 12px 24px rgba(15, 118, 110, 0.24);
transform: translateX(2px);
position: relative;
}
.nav-link.active::before {
content: "";
position: absolute;
left: 8px;
top: 10px;
bottom: 10px;
width: 3px;
border-radius: 99px;
background: rgba(255, 255, 255, 0.78);
} }
.nav-link small { .nav-link small {
@@ -832,6 +845,15 @@ tr:last-child td {
text-align: center; text-align: center;
} }
.mobile-nav .nav-link.active {
transform: translateY(-2px);
box-shadow: 0 14px 22px rgba(15, 118, 110, 0.18);
}
.mobile-nav .nav-link.active::before {
display: none;
}
.mobile-nav .nav-link small { .mobile-nav .nav-link small {
display: none; display: none;
} }
@@ -873,6 +895,25 @@ tr:last-child td {
margin-bottom: 18px; margin-bottom: 18px;
} }
.swap-zone {
animation: swap-in 220ms ease;
}
.swap-in {
animation: swap-in 220ms ease;
}
@keyframes swap-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.desktop-only { .desktop-only {
display: block; display: block;
} }
@@ -962,6 +1003,10 @@ tr:last-child td {
.auth-mini-grid { .auth-mini-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.mobile-nav .nav-link.active {
transform: translateY(-1px);
}
} }
@media (max-width: 620px) { @media (max-width: 620px) {