From c0568d00f909c2b8bbfa3fdf22fcaf4e64e7c45e Mon Sep 17 00:00:00 2001 From: "sas.fajri" Date: Mon, 13 Apr 2026 20:15:29 +0700 Subject: [PATCH] Use desktop order search with row selection --- server.js | 350 +++++++++++++++++++++++++++++++++-------------------- styles.css | 12 ++ 2 files changed, 231 insertions(+), 131 deletions(-) diff --git a/server.js b/server.js index a7c0738..fcfe5c9 100644 --- a/server.js +++ b/server.js @@ -31,6 +31,8 @@ function statusClass(status) { Released: "success", Pending: "warning", Review: "danger", + Confirmed: "success", + Unconfirmed: "danger", }; return mapping[status] || "neutral"; } @@ -448,17 +450,20 @@ function accountLayoutOptions(session = {}) { } async function dashboardPage(session) { - const [orders, results] = await Promise.all([ - loadOrders(session, "", "All"), + const [homeOrders, results] = await Promise.all([ + loadHomeOrders(session), loadResults(session, ""), ]); + const orders = homeOrders.items; const stats = [ - { label: "Orders today", value: String(orders.length), trend: "Live", hint: "" }, + { label: "Orders this month", value: String(homeOrders.totals.total), trend: `${homeOrders.month}/${homeOrders.year}`, hint: "" }, + { label: "Confirmed", value: String(homeOrders.totals.confirmed), trend: "Home", hint: "" }, { label: "Results pending", value: String(results.filter((item) => item.status !== "Released").length), trend: "Live", hint: "" }, + { label: "Unconfirmed", value: String(homeOrders.totals.unconfirmed), trend: "Home", hint: "" }, ]; return `
-
+
${stats .map( (item) => ` @@ -476,7 +481,7 @@ async function dashboardPage(session) {
- ${panelHeader("Recent orders", "A compact snapshot of the latest patient orders and their state.", 'View all')} + ${panelHeader("Recent orders", `Monthly snapshot from /order/home for ${homeOrders.month}/${homeOrders.year}.`, 'View all')}
@@ -497,7 +502,7 @@ async function dashboardPage(session) { `, ) .join("") - : ``} + : ``}
${emptyState("No orders returned", "The order endpoint did not return any rows for this session.")}
${emptyState("No orders returned", "The home endpoint did not return any rows for this month.")}
@@ -507,39 +512,70 @@ async function dashboardPage(session) { `; } -function renderOrdersTable(orders, selectedOrderId, filter = "All") { - const selected = orders.find((item) => item.id === selectedOrderId) || orders[0] || null; +function renderOrdersTable({ + items = [], + search = "", + month = "", + year = "", + currentPage = 1, + hasNext = false, + hasPrev = false, + selectedOrderId = "", +} = {}) { + const now = new Date(); + const resolvedMonth = String(month || now.getMonth() + 1).padStart(2, "0"); + const resolvedYear = String(year || now.getFullYear()); + const monthOptions = Array.from({ length: 12 }, (_, index) => { + const value = String(index + 1).padStart(2, "0"); + return ``; + }).join(""); + const yearStart = Number(resolvedYear) - 1; + const yearOptions = Array.from({ length: 3 }, (_, index) => { + const value = String(yearStart + index); + return ``; + }).join(""); + const prevPage = Math.max(1, Number(currentPage) - 1); + const nextPage = Math.max(1, Number(currentPage) + 1); + const selected = items.find((item) => item.id === selectedOrderId) || items[0] || null; + const selectedId = selected?.id || ""; return `
- ${panelHeader("Search orders", "Use the filter to match the old app flow without giving up desktop readability.", 'Create order')} -
- - ${["All", "Processing", "Ready", "Needs review"] - .map( - (item) => ` - - `, - ) - .join("")} + ${panelHeader("Orders", "Monthly order list from the desktop endpoint with name, month, and year filters.", 'Create order')} + +
+ + + +
+ + +
+ +
${ - orders.length + items.length ? ` -
+
- + - ${orders + ${items .map( (order) => ` - - - - - + + + + `, @@ -548,33 +584,31 @@ function renderOrdersTable(orders, selectedOrderId, filter = "All") {
PatientOrder IDDoctorStatusUpdated
PatientOrder IDOrder QRDate
${escapeHtml(order.patient || "Unknown patient")}
${escapeHtml(order.mode || "-")}
${escapeHtml(order.id)}${escapeHtml(order.doctor || "-")}${statusBadge(order.status)}
${escapeHtml(order.patient || "Unknown patient")}
${escapeHtml(order.diagnosis || "-")}
${escapeHtml(order.id || "-")}${escapeHtml(order.qrcode || "-")} ${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.") + : emptyState("No orders returned", "The desktop order endpoint did not return any rows for this filter.") } +
+ + Page ${escapeHtml(String(currentPage || 1))} + +
-
NIK

${escapeHtml(selected.orderNik || selected.diagnosis || "-")}

+
NIK

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

Phone

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

-
Address

${escapeHtml(selected.orderAddress || selected.message || "-")}

+
Address

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

+
Diagnosis

${escapeHtml(selected.diagnosis || "-")}

+
Note

${escapeHtml(selected.message || "-")}

` - : emptyState("No selected order", "The API returned no rows for this search.") + : emptyState("No selected order", "Click one row on the left to see its detail here.") }
@@ -1355,8 +1390,8 @@ async function orderNewPage(session, path) { }); } -function ordersPage({ query = {}, orders = [], selectedOrderId = "" } = {}, session = {}) { - return layout("Orders", `
${renderOrdersTable(orders, selectedOrderId, query.status || "All")}
`, { +function ordersPage(data = {}, session = {}) { + return layout("Orders", `
${renderOrdersTable(data)}
`, { authenticated: true, activePath: "/orders", ...accountLayoutOptions(session), @@ -1582,6 +1617,46 @@ function normalizeOrder(raw, index = 0) { }; } +function normalizeHomeOrder(raw, index = 0) { + const status = String(raw?.is_confirm || raw?.status || "").toUpperCase() === "Y" ? "Confirmed" : "Unconfirmed"; + return { + id: raw?.order_id || raw?.id || raw?.order_patient_id || "", + patient: raw?.order_name || raw?.patient_name || raw?.name || "", + doctor: raw?.doctor_id || raw?.doctor_name || raw?.doctor || "", + updated: raw?.order_date || raw?.updated_at || raw?.updated || "", + status, + tone: statusClass(status), + mode: raw?.LabNumber && raw?.LabNumber !== "-" ? raw.LabNumber : raw?.order_qrcode || "", + age: String(raw?.age || raw?.patient_age || ""), + gender: raw?.gender || raw?.patient_gender || "", + tests: Array.isArray(raw?.details) ? raw.details.map((item) => item?.test_name || item?.name || "").filter(Boolean) : [], + diagnosis: raw?.order_diagnosa || raw?.diagnosis || "", + message: raw?.order_note || raw?.note || "", + orderDate: raw?.order_date || "", + orderNik: raw?.order_nik || "", + orderHp: raw?.order_hp || "", + orderAddress: raw?.order_address || "", + orderDob: raw?.order_dob || "", + }; +} + +function normalizeDesktopOrder(raw, index = 0) { + return { + id: raw?.order_id || raw?.id || raw?.order_patient_id || "", + patient: raw?.order_name || raw?.patient_name || raw?.name || "", + doctor: raw?.doctor_id || raw?.doctor_name || raw?.doctor || "", + updated: raw?.order_date || raw?.updated_at || raw?.updated || "", + qrcode: raw?.order_qrcode || raw?.qrcode || "", + orderNik: raw?.order_nik || "", + orderHp: raw?.order_hp || "", + orderAddress: raw?.order_address || "", + orderDob: raw?.order_dob || "", + diagnosis: raw?.order_diagnosa || raw?.diagnosis || "", + message: raw?.order_note || raw?.note || "", + status: String(raw?.is_confirm || raw?.status || "").toUpperCase() === "Y" ? "Confirmed" : "", + }; +} + function normalizeResult(raw, index = 0) { const status = raw?.status || raw?.result_status || raw?.order_status || "Pending"; const detailsSource = raw?.details || raw?.items || raw?.order_details || []; @@ -1640,6 +1715,74 @@ async function loadOrders(session, search = "", status = "All") { return []; } +async function loadHomeOrders(session) { + try { + const now = new Date(); + const payload = await apiPost( + "/order/home", + { + token: session.token, + month: String(now.getMonth() + 1), + year: String(now.getFullYear()), + }, + session.token, + ); + const rows = extractArray(payload) || payload?.data || []; + const items = Array.isArray(rows) ? rows.map((row, index) => normalizeHomeOrder(row, index)).filter((item) => item.id) : []; + const totals = { + total: Number(payload?.total_order || payload?.total || items.length || 0), + confirmed: Number(payload?.total_confirmed || items.filter((item) => item.status === "Confirmed").length || 0), + unconfirmed: Number(payload?.total_unconfirmed || items.filter((item) => item.status === "Unconfirmed").length || 0), + }; + return { items, totals, month: String(now.getMonth() + 1), year: String(now.getFullYear()) }; + } catch { + return { items: [], totals: { total: 0, confirmed: 0, unconfirmed: 0 }, month: "", year: "" }; + } +} + +async function loadDesktopOrders(session, { search = "", month = "", year = "", currentPage = 1 } = {}) { + try { + const now = new Date(); + const resolvedMonth = String(month || now.getMonth() + 1); + const resolvedYear = String(year || now.getFullYear()); + const resolvedPage = String(Math.max(1, Number(currentPage) || 1)); + const payload = await apiPost( + "/order/search_order_pasien_by_doktorid_desktop", + { + token: session.token, + month: resolvedMonth, + year: resolvedYear, + search: String(search || ""), + current_page: resolvedPage, + OrderPatientM_DoctorID: session.doctorId || sampleLogin.doctorId, + }, + session.token, + ); + const rows = extractArray(payload) || payload?.data || []; + const items = Array.isArray(rows) ? rows.map((row, index) => normalizeDesktopOrder(row, index)).filter((item) => item.id) : []; + return { + items, + month: resolvedMonth, + year: resolvedYear, + currentPage: Number(resolvedPage), + search: String(search || ""), + hasNext: items.length > 0, + hasPrev: Number(resolvedPage) > 1, + }; + } catch { + const now = new Date(); + return { + items: [], + month: String(month || now.getMonth() + 1), + year: String(year || now.getFullYear()), + currentPage: Number(currentPage) || 1, + search: String(search || ""), + hasNext: false, + hasPrev: false, + }; + } +} + async function loadResults(session, search = "") { const term = String(search || "").trim(); try { @@ -1827,75 +1970,15 @@ async function readBody(req) { return flat; } -function fragmentOrdersTable(search = "", status = "All", ordersData = null) { - const orders = ordersData || []; - const selected = orders[0] || null; - return ` -
-
- ${panelHeader("Search orders", "Filter the list without full page refresh.")} -
- - ${["All", "Processing", "Ready", "Needs review"] - .map( - (item) => ` - - `, - ) - .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 || "-")}
-
- ` - : emptyState("No orders returned", "The order endpoint did not return any rows for this filter.") - } -
- -
- `; +function fragmentOrdersTable(search = "", month = "", year = "", currentPage = 1, selected = "", data = null) { + return renderOrdersTable({ + ...(data || {}), + search, + month, + year, + currentPage, + selectedOrderId: selected, + }); } function fragmentResultsTable(search = "", resultsData = null) { @@ -2140,8 +2223,8 @@ async function renderRoute(req, res, url) { if (path === "/orders" && isGet) { if (!requireAuth(req, res)) return; - const orders = await loadOrders(session, query.search || "", query.status || "All"); - html(res, 200, ordersPage({ query, orders, selectedOrderId: orders[0]?.id || "" }, session)); + const orders = await loadDesktopOrders(session, query); + html(res, 200, ordersPage({ ...orders, selectedOrderId: query.selected || orders.items[0]?.id || "" }, session)); return; } @@ -2235,8 +2318,13 @@ async function renderRoute(req, res, url) { if (path === "/fragments/orders/table" && isGet) { if (!requireAuth(req, res)) return; - const orders = await loadOrders(session, query.search || "", query.status || "All"); - html(res, 200, fragmentOrdersTable(query.search || "", query.status || "All", orders), { "HX-Trigger": JSON.stringify({ "doclink:orders-updated": true }) }); + const orders = await loadDesktopOrders(session, query); + html( + res, + 200, + fragmentOrdersTable(query.search || "", query.month || "", query.year || "", query.current_page || 1, query.selected || orders.items[0]?.id || "", orders), + { "HX-Trigger": JSON.stringify({ "doclink:orders-updated": true }) }, + ); return; } diff --git a/styles.css b/styles.css index e414263..6b4d0f9 100644 --- a/styles.css +++ b/styles.css @@ -561,6 +561,18 @@ table { background: rgba(255, 255, 255, 0.88); } +.order-row { + cursor: pointer; +} + +.order-row.active td { + background: rgba(185, 28, 28, 0.08); +} + +.order-row:hover td { + background: rgba(185, 28, 28, 0.05); +} + th, td { padding: 14px 16px;