Polish results, patients, and nav state
This commit is contained in:
116
server.js
116
server.js
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
|||||||
45
styles.css
45
styles.css
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user