diff --git a/server.js b/server.js index 7abfd0f..a247140 100644 --- a/server.js +++ b/server.js @@ -8,105 +8,12 @@ const API_BASE = "https://devbandungraya.aplikasi.web.id/one-api-doctor/doctor_mitra"; const sessionKey = "doclink_session"; - -const mockUser = { - name: "dr. Fajri", - role: "Dokter Mitra", - hospital: "Pramita Bandungraya", - doctorId: "DR-10024", +const sampleLogin = { + username: "yogayogi", + doctorId: "31010002", + password: "123456", }; -const mockOrders = [ - { - id: "ORD-24001", - patient: "Siti Amelia", - doctor: "dr. Fajri", - updated: "2m ago", - status: "Processing", - tone: "warning", - mode: "Inpatient", - age: "34", - gender: "F", - tests: ["Hematology", "Glucose", "Urine"], - diagnosis: "Check-up rutin", - message: "Prioritize fasting sample and note dizziness symptom.", - }, - { - id: "ORD-24002", - patient: "Budi Santoso", - doctor: "dr. Fajri", - updated: "14m ago", - status: "Ready", - tone: "success", - mode: "Outpatient", - age: "52", - gender: "M", - tests: ["Lipid Profile", "Liver Function"], - diagnosis: "Kontrol hipertensi", - message: "Call if LDL exceeds threshold.", - }, - { - id: "ORD-24003", - patient: "Nia Putri", - doctor: "dr. Fajri", - updated: "40m ago", - status: "Needs review", - tone: "danger", - mode: "Emergency", - age: "27", - gender: "F", - tests: ["Electrolytes", "CBC", "CRP"], - diagnosis: "Dehydration", - message: "Urgent release once CRP is complete.", - }, -]; - -const mockResults = [ - { - id: "RES-8821", - patient: "Siti Amelia", - test: "CBC", - status: "Released", - tone: "success", - date: "13 Apr 2026", - summary: "Hemoglobin slightly below baseline.", - value: "11.2 g/dL", - }, - { - id: "RES-8822", - patient: "Budi Santoso", - test: "Lipid Profile", - status: "Pending", - tone: "warning", - date: "13 Apr 2026", - summary: "Awaiting final approval.", - value: "LDL 145 mg/dL", - }, - { - id: "RES-8823", - patient: "Nia Putri", - test: "CRP", - status: "Review", - tone: "danger", - date: "12 Apr 2026", - summary: "Result flagged for clinical review.", - value: "18.4 mg/L", - }, -]; - -const mockFppGroups = [ - { group: "HEMATOLOGI", count: 6, desc: "Complete blood count and related panels." }, - { group: "KLINIK RUTIN", count: 5, desc: "Daily screening and basic chemistry." }, - { group: "IMUNOLOGI", count: 3, desc: "Inflammation and antibody markers." }, - { group: "URINALISA", count: 4, desc: "Urine screening and microscopic checks." }, -]; - -const mockPatients = [ - { name: "Siti Amelia", mrn: "MRN-10011", gender: "Female", lastVisit: "Today", note: "Active order" }, - { name: "Budi Santoso", mrn: "MRN-10012", gender: "Male", lastVisit: "Yesterday", note: "Repeat visit" }, - { name: "Nia Putri", mrn: "MRN-10013", gender: "Female", lastVisit: "12 Apr", note: "New registration" }, -]; - function escapeHtml(value) { return String(value) .replaceAll("&", "&") @@ -131,6 +38,16 @@ function statusBadge(text) { return `${escapeHtml(text)}`; } +function emptyState(title, text, action = "") { + return ` +
+ ${escapeHtml(title)} +

${escapeHtml(text)}

+ ${action} +
+ `; +} + function icon(name) { const map = { search: @@ -273,8 +190,8 @@ function desktopNav(activePath) {
DL
- DocLink Web - ${escapeHtml(mockUser.hospital)} + DocLink Pramita + Workflow shell
@@ -349,12 +266,17 @@ function panelHeader(title, text, action = "") { `; } -function dashboardPage() { +async function dashboardPage(session) { + const [orders, results, fpp] = await Promise.all([ + loadOrders(session, "", "All"), + loadResults(session, ""), + loadFpp(session, "All"), + ]); const stats = [ - { label: "Orders today", value: "28", trend: "+12%", hint: "Vs yesterday" }, - { label: "Results pending", value: "7", trend: "-3%", hint: "Still waiting release" }, - { label: "FPP items", value: "14", trend: "+2", hint: "Active reference set" }, - { label: "Special messages", value: "5", trend: "+1", hint: "Need follow-up" }, + { 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" }, @@ -405,18 +327,21 @@ function dashboardPage() { PatientOrderStatusUpdated - ${mockOrders - .map( - (order) => ` - - ${escapeHtml(order.patient)}
${escapeHtml(order.mode)} - ${escapeHtml(order.id)}
${escapeHtml(order.diagnosis)} - ${statusBadge(order.status)} - ${escapeHtml(order.updated)} - - `, - ) - .join("")} + ${orders.length + ? orders + .slice(0, 5) + .map( + (order) => ` + + ${escapeHtml(order.patient || "Unknown patient")}
${escapeHtml(order.mode || "-")} + ${escapeHtml(order.id)}
${escapeHtml(order.diagnosis || "-")} + ${statusBadge(order.status)} + ${escapeHtml(order.updated || "-")} + + `, + ) + .join("") + : `${emptyState("No orders returned", "The order endpoint did not return any rows for this session.")}`} @@ -449,7 +374,7 @@ function dashboardPage() { } function renderOrdersTable(orders, selectedOrderId, filter = "All") { - const selected = orders.find((item) => item.id === selectedOrderId) || orders[0] || mockOrders[0]; + const selected = orders.find((item) => item.id === selectedOrderId) || orders[0] || null; return `
@@ -464,68 +389,80 @@ function renderOrdersTable(orders, selectedOrderId, filter = "All") { ) .join("")} -
- - - - - - ${orders - .map( - (order) => ` - - - - - - - - `, - ) - .join("")} - -
PatientOrder IDDoctorStatusUpdated
${escapeHtml(order.patient)}
${escapeHtml(order.mode)}
${escapeHtml(order.id)}${escapeHtml(order.doctor)}${statusBadge(order.status)}${escapeHtml(order.updated)}
-
-
- ${orders - .map( - (order) => ` -
-
-
- ${escapeHtml(order.patient)} -

${escapeHtml(order.id)} · ${escapeHtml(order.mode)}

-
- ${statusBadge(order.status)} -
-
- ${escapeHtml(order.doctor)} - ${escapeHtml(order.updated)} -
-
- `, - ) - .join("")} -
+ ${ + orders.length + ? ` +
+ + + + + + ${orders + .map( + (order) => ` + + + + + + + + `, + ) + .join("")} + +
PatientOrder IDDoctorStatusUpdated
${escapeHtml(order.patient || "Unknown patient")}
${escapeHtml(order.mode || "-")}
${escapeHtml(order.id)}${escapeHtml(order.doctor || "-")}${statusBadge(order.status)}${escapeHtml(order.updated || "-")}
+
+
+ ${orders + .map( + (order) => ` +
+
+
+ ${escapeHtml(order.patient || "Unknown patient")} +

${escapeHtml(order.id || "-")} · ${escapeHtml(order.mode || "-")}

+
+ ${statusBadge(order.status)} +
+
+ ${escapeHtml(order.doctor || "-")} + ${escapeHtml(order.updated || "-")} +
+
+ `, + ) + .join("")} +
+ ` + : emptyState("No orders returned", "The order endpoint did not return any rows for this filter.") + }
`; @@ -542,31 +479,28 @@ function renderOrderDetail(order) { )}
Status
${statusBadge(order.status)}
-
Patient${escapeHtml(order.patient)}${escapeHtml(order.mode)} · ${escapeHtml(order.age)} years
-
Doctor${escapeHtml(order.doctor)}${escapeHtml(mockUser.hospital)}
+
Patient${escapeHtml(order.patient || "-")}${escapeHtml(order.updated || "-")}
+
Doctor ID${escapeHtml(order.doctor || "-")}Pramita Bandungraya
- ${panelHeader("Requested tests", "List/table treatment on desktop, cards on smaller screens.")} + ${panelHeader("Order details", "The source API returns identity fields, so the card copies stay aligned with the payload.")}
- ${order.tests - .map( - (test) => ` -
${escapeHtml(test)}

Included in the current order bundle.

- `, - ) - .join("")} +
Order date

${escapeHtml(order.orderDate || order.updated || "-")}

+
NIK

${escapeHtml(order.orderNik || "-")}

+
Phone

${escapeHtml(order.orderHp || "-")}

+
Address

${escapeHtml(order.orderAddress || "-")}

@@ -674,17 +608,20 @@ function renderOrderForm(step, stepKey = "demografi") { } function renderResultsTable(results, selectedResultId) { - const selected = results.find((item) => item.id === selectedResultId) || results[0] || mockResults[0]; + const selected = results.find((item) => item.id === selectedResultId) || results[0] || null; return `
${panelHeader("Result history", "Desktop shows table detail. Mobile collapses into stacked cards.")}
-
Released

24 today

-
Pending

7 need approval

-
Reviewed

12 flagged

+
Released

${results.filter((item) => item.status === "Released").length} items

+
Pending

${results.filter((item) => item.status === "Pending").length} items

+
Reviewed

${results.filter((item) => item.status === "Review").length} items

-
+ ${ + results.length + ? ` +
@@ -694,56 +631,73 @@ function renderResultsTable(results, selectedResultId) { .map( (result) => ` - + - + - + `, ) .join("")}
PatientResult IDTestStatusDate
${escapeHtml(result.patient)}
${escapeHtml(result.summary)}
${escapeHtml(result.patient || "Unknown patient")}
${escapeHtml(result.summary || "-")}
${escapeHtml(result.id)}${escapeHtml(result.test)}${escapeHtml(result.test || "-")} ${statusBadge(result.status)}${escapeHtml(result.date)}${escapeHtml(result.date || "-")}
-
-
- ${results - .map( - (result) => ` -
-
-
- ${escapeHtml(result.patient)} -

${escapeHtml(result.test)} · ${escapeHtml(result.date)}

-
- ${statusBadge(result.status)} -
-
- Open detail -

${escapeHtml(result.summary)}

-
- `, - ) - .join("")} -
+
+
+ ${results + .map( + (result) => ` +
+
+
+ ${escapeHtml(result.patient || "Unknown patient")} +

${escapeHtml(result.test || "-")} · ${escapeHtml(result.date || "-")}

+
+ ${statusBadge(result.status)} +
+
+ Open detail +

${escapeHtml(result.summary || "-")}

+
+ `, + ) + .join("")} +
+ ` + : emptyState("No results returned", "The result endpoint did not return any rows for this search.") + }
`; } function renderResultDetail(result) { + if (!result) { + return ` +
+ ${panelHeader("Result detail", "The API returned no matching result.", 'Back to results')} + ${emptyState("No result found", "No detail payload was returned for this result ID.")} +
+ `; + } return `
${panelHeader(`${result.patient} · ${result.id}`, "Result detail with summary, status, and interpretation fields.", 'Back to results')} @@ -770,12 +724,13 @@ function renderResultDetail(result) { } function renderFpp(groups) { + const groupNames = ["All", ...Array.from(new Set(groups.items.map((item) => item.group)))]; return `
${panelHeader("FPP catalog", "Filter blocks and list cards on mobile, richer panel on desktop.")}
- ${["All", ...mockFppGroups.map((item) => item.group)] + ${groupNames .map( (item) => `
- ${groups.items - .map( - (item) => ` -
-
-
-

${escapeHtml(item.group)}

-

${escapeHtml(item.desc)}

-
- ${escapeHtml(item.count)} items -
-
- ${["Filter", "Inspect", "Select", "Export"] - .map( - (action) => ` -
- ${escapeHtml(action)} -

Workflow action for ${escapeHtml(item.group)}.

+ ${ + groups.items.length + ? groups.items + .map( + (item) => ` +
+
+
+

${escapeHtml(item.group)}

+

${escapeHtml(item.desc)}

- `, - ) - .join("")} -
-
- `, - ) - .join("")} + ${escapeHtml(item.count)} items +
+
+ ${["Filter", "Inspect", "Select", "Export"] + .map( + (action) => ` +
+ ${escapeHtml(action)} +

Workflow action for ${escapeHtml(item.group)}.

+
+ `, + ) + .join("")} +
+
+ `, + ) + .join("") + : `
${emptyState("No FPP items", "The API returned no catalog rows for this filter.")}
` + }
`; } -function renderPatients() { +async function renderPatients(session) { + const orders = await loadOrders(session, "", "All"); + const recentPatients = Array.from( + new Map(orders.filter((order) => order.patient).map((order) => [order.patient, order])).values(), + ).slice(0, 4); return `
${panelHeader("Patient registration", "A landing zone for registration, lookup, and intake shortcuts.", 'Start registration')}
${[ - ["Today's intake", "11", "New or updated patients"], - ["Active visits", "7", "Cases linked to lab orders"], - ["QR scans", "4", "Fast entry from QR code"], - ["Needs review", "2", "Patients waiting verification"], + ["Today's intake", String(orders.length), "From API response"], + ["Active visits", String(orders.filter((item) => item.status === "Processing").length), "From API response"], + ["QR scans", String(orders.filter((item) => String(item.mode).toLowerCase().includes("qr")).length), "From API response"], + ["Needs review", String(orders.filter((item) => item.status === "Needs review").length), "From API response"], ] .map( ([label, value, hint]) => ` @@ -879,37 +842,43 @@ function renderPatients() {
`; } -function renderSettings() { +function renderSettings(session) { return `
${panelHeader("Account", "Profile data and security entry points.")}
-
Name${escapeHtml(mockUser.name)}
-
Doctor ID${escapeHtml(mockUser.doctorId)}
-
Role${escapeHtml(mockUser.role)}
-
Hospital${escapeHtml(mockUser.hospital)}
+
Name${escapeHtml(session?.username || "-")}
+
Doctor ID${escapeHtml(session?.doctorCode || session?.doctorId || "-")}
+
RoleDoctor
+
Internal ID${escapeHtml(session?.doctorId || "-")}
- +
@@ -965,9 +934,9 @@ function loginPage({ error = "" } = {}) {
${error ? `
${escapeHtml(error)}
` : ""}
- - - + + +
@@ -1033,37 +1002,37 @@ function orderNewPage(path) { }); } -function ordersPage({ query = {}, orders = mockOrders, selectedOrderId = mockOrders[0].id } = {}) { +function ordersPage({ query = {}, orders = [], selectedOrderId = "" } = {}) { return layout("Orders", `
${renderOrdersTable(orders, selectedOrderId, query.status || "All")}
`, { authenticated: true, activePath: "/orders", }); } -function resultsPage({ query = {}, results = mockResults, selectedResultId = mockResults[0].id } = {}) { +function resultsPage({ query = {}, results = [], selectedResultId = "" } = {}) { return layout("Results", `
${renderResultsTable(results, selectedResultId)}
`, { authenticated: true, activePath: "/results", }); } -function fppPage({ group = "All", groups = mockFppGroups } = {}) { +function fppPage({ group = "All", groups = [] } = {}) { return layout("FPP", `
${renderFpp({ items: groups, filter: group })}
`, { authenticated: true, activePath: "/fpp", }); } -function patientsPage() { - return layout("Patients", renderPatients(), { authenticated: true, activePath: "/patients" }); +async function patientsPage(session) { + return layout("Patients", await renderPatients(session), { authenticated: true, activePath: "/patients" }); } -function settingsPage() { - return layout("Settings", renderSettings(), { authenticated: true, activePath: "/settings" }); +function settingsPage(session) { + return layout("Settings", renderSettings(session), { authenticated: true, activePath: "/settings" }); } -function changePasswordPage() { - return layout("Change Password", renderChangePassword(), { +function changePasswordPage(session) { + return layout("Change Password", renderChangePassword(session), { authenticated: true, activePath: "/settings/change-password", }); @@ -1168,7 +1137,15 @@ function requireAuth(req, res) { async function fetchJson(url, options = {}) { const response = await fetch(url, options); const contentType = response.headers.get("content-type") || ""; - const body = contentType.includes("application/json") ? await response.json() : await response.text(); + const text = await response.text(); + let body = text; + if (contentType.includes("application/json") || /^\s*[\[{]/.test(text)) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } if (!response.ok) { const error = new Error(`Upstream error ${response.status}`); error.status = response.status; @@ -1194,13 +1171,18 @@ async function apiPost(path, payload, token = "") { function normalizeSession(payload) { const data = payload?.data || payload?.result || payload; + const user = data?.user || data?.data || {}; const token = data?.token || data?.access_token || data?.accessToken || payload?.token || ""; - const username = data?.username || data?.M_UserUsername || data?.name || ""; - const doctorId = data?.doctor_id || data?.M_UserID || data?.doctorId || ""; + const username = user?.M_UserUsername || data?.username || data?.M_UserUsername || data?.name || ""; + const doctorId = user?.M_UserM_DoctorID || data?.doctor_id || data?.doctorId || ""; + const doctorCode = user?.M_UserM_DoctorCode || data?.doctor_code || ""; + const userId = user?.M_UserID || data?.M_UserID || data?.user_id || ""; return { token, username, doctorId, + doctorCode, + userId, raw: payload, }; } @@ -1217,52 +1199,37 @@ function extractArray(payload) { } function normalizeOrder(raw, index = 0) { - const testsSource = raw?.details || raw?.tests || raw?.items || raw?.order_details || []; - const tests = Array.isArray(testsSource) - ? testsSource - .map((item) => { - if (typeof item === "string") return item; - return item?.name || item?.test_name || item?.detail_name || item?.exam || item?.label || ""; - }) - .filter(Boolean) - : []; const status = raw?.status || raw?.order_status || raw?.state || "Processing"; return { - id: - raw?.order_patient_id || - raw?.order_id || - raw?.id || - raw?.OrderPatientID || - raw?.OrderID || - `ORD-${String(index + 1).padStart(5, "0")}`, - patient: raw?.patient_name || raw?.name || raw?.patient || raw?.patient_fullname || "Patient", - doctor: raw?.doctor_name || raw?.doctor || raw?.M_UserUsername || mockUser.name, - updated: raw?.updated_at || raw?.updated || raw?.created_at || "Today", + id: raw?.order_patient_id || raw?.order_id || raw?.id || raw?.OrderPatientID || raw?.OrderID || "", + patient: raw?.order_name || raw?.patient_name || raw?.name || raw?.patient || raw?.patient_fullname || "", + doctor: raw?.doctor_id || raw?.doctor_name || raw?.doctor || raw?.M_UserUsername || "", + updated: raw?.order_date || raw?.updated_at || raw?.updated || raw?.created_at || "", status, tone: statusClass(status), - mode: raw?.mode || raw?.visit_type || raw?.patient_type || "Outpatient", + mode: raw?.mode || raw?.visit_type || raw?.patient_type || "", age: String(raw?.age || raw?.patient_age || ""), gender: raw?.gender || raw?.patient_gender || "", - tests, - diagnosis: raw?.patient_diagnosa || raw?.diagnosis || raw?.note || "", - message: raw?.message || raw?.patient_note || "", + tests: [], + diagnosis: raw?.order_nik || raw?.patient_diagnosa || raw?.diagnosis || raw?.note || "", + message: raw?.order_address || raw?.message || raw?.patient_note || "", + orderDate: raw?.order_date || "", + orderNik: raw?.order_nik || "", + orderHp: raw?.order_hp || "", + orderAddress: raw?.order_address || "", + orderDob: raw?.order_dob || "", }; } function normalizeResult(raw, index = 0) { const status = raw?.status || raw?.result_status || "Released"; return { - id: - raw?.result_id || - raw?.order_id || - raw?.id || - raw?.hasil_id || - `RES-${String(index + 1).padStart(4, "0")}`, - patient: raw?.patient_name || raw?.name || raw?.patient || "Patient", - test: raw?.test_name || raw?.item_name || raw?.test || "Result", + 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 || "", status, tone: statusClass(status), - date: raw?.date || raw?.created_at || raw?.updated_at || "Today", + 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 || "", }; @@ -1275,13 +1242,14 @@ async function loadOrders(session, search = "", status = "All") { "/order/search_order_pasien_by_doktorid", { token: session.token, - OrderPatientM_DoctorID: session.doctorId || mockUser.doctorId, + OrderPatientM_DoctorID: session.doctorId || sampleLogin.doctorId, search: term, }, session.token, ); const rows = extractArray(payload) || []; - const orders = rows.map((row, index) => normalizeOrder(row, index)).filter((item) => { + return rows.map((row, index) => normalizeOrder(row, index)).filter((item) => { + if (!item.id) return false; const matchesSearch = !term || [item.id, item.patient, item.status, item.diagnosis, item.message].some((value) => @@ -1290,39 +1258,38 @@ async function loadOrders(session, search = "", status = "All") { const matchesStatus = !status || status === "All" || item.status === status; return matchesSearch && matchesStatus; }); - if (orders.length) return orders; } catch { - // Fall through to mock data. + return []; } - return filterOrders(term, status); + return []; } async function loadResults(session, search = "") { const term = String(search || "").trim(); try { + const orders = await loadOrders(session, "", "All"); + const orderId = session.orderId || orders[0]?.id || ""; const payload = await apiPost( "/order/hasil_belum_keluar_by_id", - { token: session.token, order_id: session.orderId || "" }, + { token: session.token, order_id: orderId }, session.token, ); const rows = extractArray(payload) || []; - const results = rows.map((row, index) => normalizeResult(row, index)).filter((item) => { + return rows.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) => String(value).toLowerCase().includes(term.toLowerCase()), ); }); - if (results.length) return results; } catch { - // Fall through to mock data. + return []; } - return filterResults(term); + return []; } async function loadResultDetail(session, resultId) { - const fallback = mockResults.find((item) => item.id === resultId) || mockResults[0]; try { - // Inferred from project-specs note about an additional result base path. const payload = await apiPost( "/result/getResult", { @@ -1334,17 +1301,12 @@ async function loadResultDetail(session, resultId) { ); const rows = extractArray(payload); if (rows?.length) return normalizeResult(rows[0], 0); - if (payload && typeof payload === "object") { - return { - ...fallback, - ...normalizeResult(payload, 0), - id: resultId || fallback.id, - }; - } + if (payload && typeof payload === "object") return normalizeResult(payload, 0); + const related = await loadResults(session, resultId); + return related.find((item) => item.id === resultId) || null; } catch { - // Fall through to local seed data when the upstream result API is unavailable. + return null; } - return fallback; } async function loadFpp(session, group = "All") { @@ -1356,38 +1318,35 @@ async function loadFpp(session, group = "All") { count: Number(row?.count || row?.total || row?.qty || 1), desc: row?.description || row?.desc || "Laboratory reference item.", })); - if (items.length) { - const filtered = group === "All" ? items : items.filter((item) => item.group === group); - return { items: filtered, filter: group }; - } + const filtered = group === "All" ? items : items.filter((item) => item.group === group); + return { items: filtered, filter: group }; } catch { - // Fall through to mock data. + return { items: [], filter: group }; } - const items = group === "All" ? mockFppGroups : mockFppGroups.filter((item) => item.group === group); - return { items, filter: group }; } async function loadOrderDetail(session, orderId) { - const order = mockOrders.find((item) => item.id === orderId) || mockOrders[0]; - const detail = { ...order }; - try { - const saran = await apiPost( + const [orders, saran, hasil] = await Promise.all([ + loadOrders(session, "", "All"), + apiPost( "/order/get_order_saran_by_order_patient_id", { token: session.token, order_patient_id: orderId }, session.token, - ); - detail.apiSaran = extractArray(saran) || saran?.message || saran?.note || saran?.result || ""; - } catch { - detail.apiSaran = ""; - } - try { - const hasil = await apiPost( + ).catch(() => null), + apiPost( "/order/hasil_belum_keluar_by_id", { token: session.token, order_id: orderId }, session.token, - ); - detail.apiHasil = extractArray(hasil) || hasil?.message || hasil?.note || hasil?.result || ""; + ).catch(() => null), + ]); + const order = orders.find((item) => item.id === orderId) || orders[0] || null; + if (!order) return null; + const detail = { ...order }; + try { + detail.apiSaran = saran ? extractArray(saran) || saran?.message || saran?.note || saran?.result || "" : ""; + detail.apiHasil = hasil ? extractArray(hasil) || hasil?.message || hasil?.note || hasil?.result || "" : ""; } catch { + detail.apiSaran = ""; detail.apiHasil = ""; } return detail; @@ -1421,32 +1380,9 @@ async function readBody(req) { return Object.fromEntries(new URLSearchParams(raw)); } -function filterOrders(search, status) { - const term = String(search || "").trim().toLowerCase(); - return mockOrders.filter((order) => { - const matchesSearch = - !term || - [order.id, order.patient, order.status, order.diagnosis, order.message].some((value) => - String(value).toLowerCase().includes(term), - ); - const matchesStatus = !status || status === "All" || order.status === status; - return matchesSearch && matchesStatus; - }); -} - -function filterResults(search) { - const term = String(search || "").trim().toLowerCase(); - return mockResults.filter((result) => { - if (!term) return true; - return [result.id, result.patient, result.test, result.status, result.summary].some((value) => - String(value).toLowerCase().includes(term), - ); - }); -} - function fragmentOrdersTable(search = "", status = "All", ordersData = null) { - const orders = ordersData || filterOrders(search, status); - const selected = orders[0] || mockOrders[0]; + const orders = ordersData || []; + const selected = orders[0] || null; return `
@@ -1461,107 +1397,132 @@ function fragmentOrdersTable(search = "", status = "All", ordersData = null) { ) .join("")} -
- - - - - - ${orders - .map( - (order) => ` - - - - - - - - `, - ) - .join("")} - -
PatientOrder IDDoctorStatusUpdated
${escapeHtml(order.patient)}
${escapeHtml(order.mode)}
${escapeHtml(order.id)}${escapeHtml(order.doctor)}${statusBadge(order.status)}${escapeHtml(order.updated)}
-
+ ${ + orders.length + ? ` +
+ + + + + + ${orders + .map( + (order) => ` + + + + + + + + `, + ) + .join("")} + +
PatientOrder IDDoctorStatusUpdated
${escapeHtml(order.patient || "Unknown patient")}
${escapeHtml(order.mode || "-")}
${escapeHtml(order.id)}${escapeHtml(order.doctor || "-")}${statusBadge(order.status)}${escapeHtml(order.updated || "-")}
+
+ ` + : emptyState("No orders returned", "The order endpoint did not return any rows for this filter.") + }
`; } function fragmentResultsTable(search = "", resultsData = null) { - const results = resultsData || filterResults(search); - const selected = results[0] || mockResults[0]; + const results = resultsData || []; + const selected = results[0] || null; return `
${panelHeader("Result history", "Desktop shows table detail. Mobile collapses into stacked cards.")}
-
Released

24 today

-
Pending

7 need approval

-
Reviewed

12 flagged

-
-
- - - - - - ${results - .map( - (result) => ` - - - - - - - - `, - ) - .join("")} - -
PatientResult IDTestStatusDate
${escapeHtml(result.patient)}
${escapeHtml(result.summary)}
${escapeHtml(result.id)}${escapeHtml(result.test)}${statusBadge(result.status)}${escapeHtml(result.date)}
+
Released

${results.filter((item) => item.status === "Released").length} items

+
Pending

${results.filter((item) => item.status === "Pending").length} items

+
Reviewed

${results.filter((item) => item.status === "Review").length} items

+ ${ + results.length + ? ` +
+ + + + + + ${results + .map( + (result) => ` + + + + + + + + `, + ) + .join("")} + +
PatientResult IDTestStatusDate
${escapeHtml(result.patient || "Unknown patient")}
${escapeHtml(result.summary || "-")}
${escapeHtml(result.id)}${escapeHtml(result.test || "-")}${statusBadge(result.status)}${escapeHtml(result.date || "-")}
+
+ ` + : emptyState("No results returned", "The result endpoint did not return any rows for this search.") + }
`; } function fragmentFpp(group = "All", itemsData = null) { - const items = itemsData || (group === "All" ? mockFppGroups : mockFppGroups.filter((item) => item.group === group)); + const items = itemsData || []; + const groups = ["All", ...Array.from(new Set(items.map((item) => item.group)))]; return `
${panelHeader("FPP catalog", "Filter blocks and list cards on mobile, richer panel on desktop.")}
- ${["All", ...mockFppGroups.map((item) => item.group)] + ${groups .map( (item) => ` ${escapeHtml(item)} @@ -1571,33 +1532,37 @@ function fragmentFpp(group = "All", itemsData = null) {
- ${items - .map( - (item) => ` -
-
-
-

${escapeHtml(item.group)}

-

${escapeHtml(item.desc)}

-
- ${escapeHtml(item.count)} items -
-
- ${["Filter", "Inspect", "Select", "Export"] - .map( - (action) => ` -
- ${escapeHtml(action)} -

Workflow action for ${escapeHtml(item.group)}.

+ ${ + items.length + ? items + .map( + (item) => ` +
+
+
+

${escapeHtml(item.group)}

+

${escapeHtml(item.desc)}

- `, - ) - .join("")} -
-
- `, - ) - .join("")} + ${escapeHtml(item.count)} items +
+
+ ${["Filter", "Inspect", "Select", "Export"] + .map( + (action) => ` +
+ ${escapeHtml(action)} +

Workflow action for ${escapeHtml(item.group)}.

+
+ `, + ) + .join("")} +
+
+ `, + ) + .join("") + : `
${emptyState("No FPP items", "The API returned no catalog rows for this filter.")}
` + }
`; @@ -1608,7 +1573,9 @@ function fragmentOrderStep(step) { } function fragmentPesanKhusus(orderId) { - const order = mockOrders.find((item) => item.id === orderId) || mockOrders[0]; + const order = orderId + ? { id: orderId, patient: "", status: "Processing", message: "", apiSaran: "" } + : { id: "", patient: "", status: "Processing", message: "", apiSaran: "" }; return `