Refine dashboard and pending results

This commit is contained in:
sas.fajri
2026-04-13 15:53:21 +07:00
parent 26e9c8e522
commit 05b65b5c7a

View File

@@ -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("Todays 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;
}
}