Refine dashboard and pending results
This commit is contained in:
99
server.js
99
server.js
@@ -267,26 +267,17 @@ function panelHeader(title, text, action = "") {
|
||||
}
|
||||
|
||||
async function dashboardPage(session) {
|
||||
const [orders, results, fpp] = await Promise.all([
|
||||
const [orders, results] = await Promise.all([
|
||||
loadOrders(session, "", "All"),
|
||||
loadResults(session, ""),
|
||||
loadFpp(session, "All"),
|
||||
]);
|
||||
const stats = [
|
||||
{ label: "Orders today", value: String(orders.length), trend: "Live", hint: "From API response" },
|
||||
{ label: "Results pending", value: String(results.filter((item) => item.status !== "Released").length), trend: "Live", hint: "From API response" },
|
||||
{ label: "FPP items", value: String(fpp.items.length), trend: "Live", hint: "From API response" },
|
||||
{ label: "Special messages", value: String(orders.filter((item) => Boolean(item.message)).length), trend: "Live", hint: "From API response" },
|
||||
];
|
||||
const shortcuts = [
|
||||
{ title: "New order", desc: "Start registration flow", href: "/orders/new" },
|
||||
{ title: "Pending results", desc: "Review unreleased items", href: "/results/pending" },
|
||||
{ title: "FPP catalog", desc: "Inspect lab groups", href: "/fpp" },
|
||||
{ title: "Change password", desc: "Update account security", href: "/settings/change-password" },
|
||||
{ label: "Orders today", value: String(orders.length), trend: "Live", hint: "" },
|
||||
{ label: "Results pending", value: String(results.filter((item) => item.status !== "Released").length), trend: "Live", hint: "" },
|
||||
];
|
||||
return `
|
||||
<div class="stack">
|
||||
<section class="grid grid-4">
|
||||
<section class="grid grid-2">
|
||||
${stats
|
||||
.map(
|
||||
(item) => `
|
||||
@@ -296,28 +287,12 @@ async function dashboardPage(session) {
|
||||
<span class="trend">${escapeHtml(item.trend)}</span>
|
||||
</div>
|
||||
<strong>${escapeHtml(item.value)}</strong>
|
||||
<span class="muted">${escapeHtml(item.hint)}</span>
|
||||
${item.hint ? `<span class="muted">${escapeHtml(item.hint)}</span>` : ""}
|
||||
</article>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</section>
|
||||
<section class="panel">
|
||||
${panelHeader("Quick actions", "Jump into the common flows without digging through nested screens.")}
|
||||
<div class="grid grid-4">
|
||||
${shortcuts
|
||||
.map(
|
||||
(item) => `
|
||||
<a class="card" href="${item.href}">
|
||||
<strong>${escapeHtml(item.title)}</strong>
|
||||
<p class="muted">${escapeHtml(item.desc)}</p>
|
||||
<span class="pill">${icon("arrow")} Open</span>
|
||||
</a>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
</section>
|
||||
<section class="detail-grid">
|
||||
<div class="panel">
|
||||
${panelHeader("Recent orders", "A compact snapshot of the latest patient orders and their state.", '<a class="btn btn-secondary" href="/orders">View all</a>')}
|
||||
@@ -346,28 +321,6 @@ async function dashboardPage(session) {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
${panelHeader("Today’s notes", "Items that need attention now, not later.")}
|
||||
<div class="mini-list">
|
||||
${[
|
||||
["FPP ready for release", "Review Hematology group before rounding."],
|
||||
["Pending sample", "One fasting sample still waiting in the queue."],
|
||||
["Password rotation", "Prompt user to refresh credentials after 90 days."],
|
||||
]
|
||||
.map(
|
||||
([title, text]) => `
|
||||
<div class="mini-item">
|
||||
<div>
|
||||
<strong>${escapeHtml(title)}</strong>
|
||||
<p>${escapeHtml(text)}</p>
|
||||
</div>
|
||||
<span class="pill">${icon("arrow")}</span>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
@@ -713,10 +666,11 @@ function renderResultDetail(result) {
|
||||
</div>
|
||||
<aside class="panel">
|
||||
<div class="mini-list">
|
||||
<div class="mini-item"><div><strong>Date</strong><p>${escapeHtml(result.date)}</p></div></div>
|
||||
<div class="mini-item"><div><strong>Interpretation</strong><p>Borderline value requires doctor review.</p></div></div>
|
||||
<div class="mini-item"><div><strong>Action</strong><p>Keep in pending state until confirmed.</p></div></div>
|
||||
<div class="mini-item"><div><strong>Date</strong><p>${escapeHtml(result.date || "-")}</p></div></div>
|
||||
<div class="mini-item"><div><strong>Diagnosis</strong><p>${escapeHtml(result.summary || "-")}</p></div></div>
|
||||
<div class="mini-item"><div><strong>Note</strong><p>${escapeHtml(result.value || "-")}</p></div></div>
|
||||
</div>
|
||||
${result.details?.length ? `<div style="height:14px"></div><div class="mini-list">${result.details.map((item) => `<div class="mini-item"><div><strong>Detail</strong><p>${escapeHtml(item)}</p></div></div>`).join("")}</div>` : ""}
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1222,16 +1176,31 @@ function normalizeOrder(raw, index = 0) {
|
||||
}
|
||||
|
||||
function normalizeResult(raw, index = 0) {
|
||||
const status = raw?.status || raw?.result_status || "Released";
|
||||
const status = raw?.status || raw?.result_status || raw?.order_status || "Pending";
|
||||
const detailsSource = raw?.details || raw?.items || raw?.order_details || [];
|
||||
const details = Array.isArray(detailsSource)
|
||||
? detailsSource
|
||||
.map((item) => {
|
||||
if (typeof item === "string") return item;
|
||||
return item?.test_name || item?.name || item?.detail_name || item?.exam || item?.label || "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
return {
|
||||
id: raw?.result_id || raw?.order_id || raw?.id || raw?.hasil_id || "",
|
||||
patient: raw?.patient_name || raw?.name || raw?.patient || "",
|
||||
test: raw?.test_name || raw?.item_name || raw?.test || "",
|
||||
patient: raw?.order_name || raw?.patient_name || raw?.name || raw?.patient || "",
|
||||
test: details.join(", ") || raw?.test_name || raw?.item_name || raw?.test || "",
|
||||
status,
|
||||
tone: statusClass(status),
|
||||
date: raw?.date || raw?.created_at || raw?.updated_at || "",
|
||||
summary: raw?.summary || raw?.note || raw?.result_summary || "",
|
||||
value: raw?.value || raw?.result_value || raw?.result || "",
|
||||
date: raw?.order_date || raw?.date || raw?.created_at || raw?.updated_at || "",
|
||||
summary: raw?.order_diagnosa || raw?.summary || raw?.note || raw?.result_summary || "",
|
||||
value: raw?.order_note || raw?.value || raw?.result_value || raw?.result || "",
|
||||
details,
|
||||
orderCode: raw?.order_qrcode || "",
|
||||
orderDob: raw?.order_dob || "",
|
||||
orderAddress: raw?.order_address || "",
|
||||
orderNik: raw?.order_nik || "",
|
||||
orderHp: raw?.order_hp || "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1274,8 +1243,8 @@ async function loadResults(session, search = "") {
|
||||
{ token: session.token, order_id: orderId },
|
||||
session.token,
|
||||
);
|
||||
const rows = extractArray(payload) || [];
|
||||
return rows.map((row, index) => normalizeResult(row, index)).filter((item) => {
|
||||
const rawRows = extractArray(payload) || (payload?.data ? [payload.data] : []);
|
||||
return rawRows.map((row, index) => normalizeResult(row, index)).filter((item) => {
|
||||
if (!item.id) return false;
|
||||
if (!term) return true;
|
||||
return [item.id, item.patient, item.test, item.status, item.summary].some((value) =>
|
||||
@@ -1301,11 +1270,13 @@ async function loadResultDetail(session, resultId) {
|
||||
);
|
||||
const rows = extractArray(payload);
|
||||
if (rows?.length) return normalizeResult(rows[0], 0);
|
||||
if (payload?.data && typeof payload.data === "object") return normalizeResult(payload.data, 0);
|
||||
if (payload && typeof payload === "object") return normalizeResult(payload, 0);
|
||||
const related = await loadResults(session, resultId);
|
||||
return related.find((item) => item.id === resultId) || null;
|
||||
} catch {
|
||||
return null;
|
||||
const related = await loadResults(session, resultId);
|
||||
return related.find((item) => item.id === resultId) || related[0] || null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user