Polish account header and sidebar logo
This commit is contained in:
116
server.js
116
server.js
@@ -65,10 +65,10 @@ function icon(name) {
|
|||||||
return map[name] || "";
|
return map[name] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function layout(title, body, { authenticated = false, activePath = "/", subtitle = "", shell = true } = {}) {
|
function layout(title, body, { authenticated = false, activePath = "/", subtitle = "", shell = true, accountName = "", accountMeta = "" } = {}) {
|
||||||
const nav = authenticated ? desktopNav(activePath) : "";
|
const nav = authenticated ? desktopNav(activePath) : "";
|
||||||
const mobile = authenticated ? mobileNav(activePath) : "";
|
const mobile = authenticated ? mobileNav(activePath) : "";
|
||||||
const header = authenticated ? topbar(activePath, subtitle) : "";
|
const header = authenticated ? topbar(activePath, subtitle, accountName, accountMeta) : "";
|
||||||
return `<!doctype html>
|
return `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -317,7 +317,7 @@ function layout(title, body, { authenticated = false, activePath = "/", subtitle
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function topbar(activePath, subtitle) {
|
function topbar(activePath, subtitle, accountName = "", accountMeta = "") {
|
||||||
const titleMap = {
|
const titleMap = {
|
||||||
"/": ["Dashboard", "Overview of orders, results, and work queues."],
|
"/": ["Dashboard", "Overview of orders, results, and work queues."],
|
||||||
"/orders": ["Orders", "Search, review, and create patient orders."],
|
"/orders": ["Orders", "Search, review, and create patient orders."],
|
||||||
@@ -327,23 +327,8 @@ function topbar(activePath, subtitle) {
|
|||||||
"/settings": ["Settings", "Account profile and password management."],
|
"/settings": ["Settings", "Account profile and password management."],
|
||||||
};
|
};
|
||||||
const [title, fallbackSubtitle] = titleMap[activePath] || ["DocLink Web", "Responsive clinical workflow shell."];
|
const [title, fallbackSubtitle] = titleMap[activePath] || ["DocLink Web", "Responsive clinical workflow shell."];
|
||||||
const searchForm = activePath === "/orders"
|
const displayName = accountName || sampleLogin.username;
|
||||||
? `
|
const displayMeta = accountMeta || `Doctor ID ${sampleLogin.doctorId}`;
|
||||||
<form class="search" hx-get="/fragments/orders/table" hx-target="#orders-fragment" hx-push-url="true">
|
|
||||||
${icon("search")}
|
|
||||||
<input name="search" type="search" placeholder="Search patients, orders, results" />
|
|
||||||
</form>
|
|
||||||
`
|
|
||||||
: activePath === "/results"
|
|
||||||
? `
|
|
||||||
<form class="search" hx-get="/fragments/results/table" hx-target="#results-fragment" hx-push-url="true">
|
|
||||||
${icon("search")}
|
|
||||||
<input name="search" type="search" placeholder="Search results" />
|
|
||||||
</form>
|
|
||||||
`
|
|
||||||
: `
|
|
||||||
<a class="btn btn-secondary" href="/orders">Search orders</a>
|
|
||||||
`;
|
|
||||||
return `
|
return `
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="topbar-main">
|
<div class="topbar-main">
|
||||||
@@ -352,10 +337,15 @@ function topbar(activePath, subtitle) {
|
|||||||
<p class="page-subtitle">${escapeHtml(subtitle || fallbackSubtitle)}</p>
|
<p class="page-subtitle">${escapeHtml(subtitle || fallbackSubtitle)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
${searchForm}
|
|
||||||
<button class="icon-btn" type="button" data-action="alert" title="Notifications">${icon("bell")}</button>
|
|
||||||
<a class="btn btn-secondary" href="/settings">Akun</a>
|
|
||||||
<a class="btn btn-primary" href="/orders/new">${icon("plus")} New order</a>
|
<a class="btn btn-primary" href="/orders/new">${icon("plus")} New order</a>
|
||||||
|
<button class="icon-btn" type="button" data-action="alert" title="Notifications">${icon("bell")}</button>
|
||||||
|
<a class="btn btn-secondary account-chip" href="/settings">
|
||||||
|
<span class="account-avatar" aria-hidden="true">DL</span>
|
||||||
|
<span class="account-copy">
|
||||||
|
<strong>${escapeHtml(displayName)}</strong>
|
||||||
|
<small>${escapeHtml(displayMeta)}</small>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
`;
|
`;
|
||||||
@@ -366,7 +356,6 @@ function desktopNav(activePath) {
|
|||||||
["/", "Home", "Dashboard"],
|
["/", "Home", "Dashboard"],
|
||||||
["/orders", "Order", "Create & list"],
|
["/orders", "Order", "Create & list"],
|
||||||
["/results", "Result", "History"],
|
["/results", "Result", "History"],
|
||||||
["/settings", "Akun", "Profile"],
|
|
||||||
];
|
];
|
||||||
const active = (href) => activePath === href || activePath.startsWith(`${href}/`);
|
const active = (href) => activePath === href || activePath.startsWith(`${href}/`);
|
||||||
return `
|
return `
|
||||||
@@ -398,14 +387,9 @@ function desktopNav(activePath) {
|
|||||||
<span>Change password</span>
|
<span>Change password</span>
|
||||||
<small>Security</small>
|
<small>Security</small>
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-link ${activePath === "/problem-login" ? "active" : ""}" href="/problem-login">
|
|
||||||
<span>Problem login</span>
|
|
||||||
<small>Fallback state</small>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer sidebar-footer-logo">
|
||||||
<strong>DocLink Pramita</strong>
|
<img src="/logo.png" alt="Pramita Lab" class="sidebar-logo-image" />
|
||||||
<p>Orders, results, and FPP workflows.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -417,7 +401,6 @@ function mobileNav(activePath) {
|
|||||||
["/", "Home", "Dashboard"],
|
["/", "Home", "Dashboard"],
|
||||||
["/orders", "Order", "Create"],
|
["/orders", "Order", "Create"],
|
||||||
["/results", "Result", "History"],
|
["/results", "Result", "History"],
|
||||||
["/settings", "Akun", "Profile"],
|
|
||||||
];
|
];
|
||||||
const active = (href) => activePath === href || activePath.startsWith(`${href}/`);
|
const active = (href) => activePath === href || activePath.startsWith(`${href}/`);
|
||||||
return `
|
return `
|
||||||
@@ -457,6 +440,13 @@ function resolveFppRouteIds(session) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function accountLayoutOptions(session = {}) {
|
||||||
|
return {
|
||||||
|
accountName: session?.username || sampleLogin.username,
|
||||||
|
accountMeta: session?.doctorId ? `Doctor ID ${session.doctorId}` : `Doctor ID ${sampleLogin.doctorId}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function dashboardPage(session) {
|
async function dashboardPage(session) {
|
||||||
const [orders, results] = await Promise.all([
|
const [orders, results] = await Promise.all([
|
||||||
loadOrders(session, "", "All"),
|
loadOrders(session, "", "All"),
|
||||||
@@ -1361,60 +1351,67 @@ async function orderNewPage(session, path) {
|
|||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/orders",
|
activePath: "/orders",
|
||||||
subtitle: "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.",
|
subtitle: "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ordersPage({ query = {}, orders = [], selectedOrderId = "" } = {}) {
|
function ordersPage({ query = {}, orders = [], selectedOrderId = "" } = {}, session = {}) {
|
||||||
return layout("Orders", `<div id="orders-fragment">${renderOrdersTable(orders, selectedOrderId, query.status || "All")}</div>`, {
|
return layout("Orders", `<div id="orders-fragment">${renderOrdersTable(orders, selectedOrderId, query.status || "All")}</div>`, {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/orders",
|
activePath: "/orders",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resultsPage({ query = {}, results = [], selectedResultId = "" } = {}) {
|
function resultsPage({ query = {}, results = [], selectedResultId = "" } = {}, session = {}) {
|
||||||
return layout("Results", `<div id="results-fragment">${renderResultsTable(results, selectedResultId)}</div>`, {
|
return layout("Results", `<div id="results-fragment">${renderResultsTable(results, selectedResultId)}</div>`, {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/results",
|
activePath: "/results",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fppPage({ group = "All", groups = [] } = {}) {
|
function fppPage({ group = "All", groups = [] } = {}, session = {}) {
|
||||||
return layout("FPP", `<div id="fpp-fragment">${renderFpp(groups, group)}</div>`, {
|
return layout("FPP", `<div id="fpp-fragment">${renderFpp(groups, group)}</div>`, {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/fpp",
|
activePath: "/fpp",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function patientsPage(session) {
|
async function patientsPage(session) {
|
||||||
return layout("Patients", await renderPatients(session), { authenticated: true, activePath: "/patients" });
|
return layout("Patients", await renderPatients(session), { authenticated: true, activePath: "/patients", ...accountLayoutOptions(session) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function settingsPage(session) {
|
function settingsPage(session) {
|
||||||
return layout("Settings", renderSettings(session), { authenticated: true, activePath: "/settings" });
|
return layout("Settings", renderSettings(session), { authenticated: true, activePath: "/settings", ...accountLayoutOptions(session) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePasswordPage(session) {
|
function changePasswordPage(session) {
|
||||||
return layout("Change Password", renderChangePassword(session), {
|
return layout("Change Password", renderChangePassword(session), {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/settings/change-password",
|
activePath: "/settings/change-password",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function orderDetailPage(order) {
|
function orderDetailPage(order, session = {}) {
|
||||||
return layout("Order Detail", renderOrderDetail(order), {
|
return layout("Order Detail", renderOrderDetail(order), {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/orders",
|
activePath: "/orders",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resultDetailPage(result) {
|
function resultDetailPage(result, session = {}) {
|
||||||
return layout("Result Detail", renderResultDetail(result), {
|
return layout("Result Detail", renderResultDetail(result), {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
activePath: "/results",
|
activePath: "/results",
|
||||||
|
...accountLayoutOptions(session),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function specialMessagePage(order) {
|
function specialMessagePage(order, session = {}) {
|
||||||
return layout(
|
return layout(
|
||||||
"Pesan Khusus",
|
"Pesan Khusus",
|
||||||
`
|
`
|
||||||
@@ -1441,7 +1438,7 @@ function specialMessagePage(order) {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
{ authenticated: true, activePath: "/orders" },
|
{ authenticated: true, activePath: "/orders", ...accountLayoutOptions(session) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2028,6 +2025,13 @@ async function renderRoute(req, res, url) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path === "/logo.png") {
|
||||||
|
const logo = await readFile(new URL("./logo.png", import.meta.url));
|
||||||
|
res.writeHead(200, { "Content-Type": "image/png" });
|
||||||
|
res.end(logo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (path === "/login" && isGet) {
|
if (path === "/login" && isGet) {
|
||||||
html(res, 200, loginPage(), { "Cache-Control": "no-store" });
|
html(res, 200, loginPage(), { "Cache-Control": "no-store" });
|
||||||
return;
|
return;
|
||||||
@@ -2115,7 +2119,7 @@ async function renderRoute(req, res, url) {
|
|||||||
layout(
|
layout(
|
||||||
"Change Password",
|
"Change Password",
|
||||||
`<section class="panel">${panelHeader("Change password", "Inline validation and a straightforward submit path.")}<div class="note-box">Upstream change password call failed. Check API availability.</div></section>`,
|
`<section class="panel">${panelHeader("Change password", "Inline validation and a straightforward submit path.")}<div class="note-box">Upstream change password call failed. Check API availability.</div></section>`,
|
||||||
{ authenticated: true, activePath: "/settings/change-password" },
|
{ authenticated: true, activePath: "/settings/change-password", ...accountLayoutOptions(sessionData) },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2137,35 +2141,35 @@ async function renderRoute(req, res, url) {
|
|||||||
if (path === "/orders" && isGet) {
|
if (path === "/orders" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
const orders = await loadOrders(session, query.search || "", query.status || "All");
|
const orders = await loadOrders(session, query.search || "", query.status || "All");
|
||||||
html(res, 200, ordersPage({ query, orders, selectedOrderId: orders[0]?.id || "" }));
|
html(res, 200, ordersPage({ query, orders, selectedOrderId: orders[0]?.id || "" }, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === "/results" && isGet) {
|
if (path === "/results" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
const results = await loadResults(session, query.search || "");
|
const results = await loadResults(session, query.search || "");
|
||||||
html(res, 200, resultsPage({ query, results, selectedResultId: results[0]?.id || "" }));
|
html(res, 200, resultsPage({ query, results, selectedResultId: results[0]?.id || "" }, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === "/results/historical" && isGet) {
|
if (path === "/results/historical" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
const results = await loadResults(session, "");
|
const results = await loadResults(session, "");
|
||||||
html(res, 200, layout("Historical results", `<section class="panel">${panelHeader("Historical results", "A compact historic view that still fits the responsive shell.")}${results.length ? `<div class="mini-list">${results.map((item) => `<a class="mini-item" href="/results/${item.id}"><div><strong>${escapeHtml(item.patient || "Unknown patient")}</strong><p>${escapeHtml(item.test || "-")} · ${escapeHtml(item.date || "-")}</p></div>${statusBadge(item.status)}</a>`).join("")}</div>` : emptyState("No results returned", "The API returned no historical result rows.")}</section>`, { authenticated: true, activePath: "/results" }));
|
html(res, 200, layout("Historical results", `<section class="panel">${panelHeader("Historical results", "A compact historic view that still fits the responsive shell.")}${results.length ? `<div class="mini-list">${results.map((item) => `<a class="mini-item" href="/results/${item.id}"><div><strong>${escapeHtml(item.patient || "Unknown patient")}</strong><p>${escapeHtml(item.test || "-")} · ${escapeHtml(item.date || "-")}</p></div>${statusBadge(item.status)}</a>`).join("")}</div>` : emptyState("No results returned", "The API returned no historical result rows.")}</section>`, { authenticated: true, activePath: "/results", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === "/results/pending" && isGet) {
|
if (path === "/results/pending" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
const results = (await loadResults(session, "")).filter((item) => item.status !== "Released");
|
const results = (await loadResults(session, "")).filter((item) => item.status !== "Released");
|
||||||
html(res, 200, layout("Pending results", `<section class="panel">${panelHeader("Pending results", "Items that need attention before release.")}${results.length ? `<div class="grid grid-2">${results.map((item) => `<article class="card"><div class="topbar-actions" style="justify-content:space-between"><strong>${escapeHtml(item.patient || "Unknown patient")}</strong>${statusBadge(item.status)}</div><p class="muted">${escapeHtml(item.test || "-")} · ${escapeHtml(item.summary || "-")}</p><a class="btn btn-secondary" href="/results/${item.id}">Open detail</a></article>`).join("")}</div>` : emptyState("No pending results", "The API returned no unreleased rows.")}</section>`, { authenticated: true, activePath: "/results" }));
|
html(res, 200, layout("Pending results", `<section class="panel">${panelHeader("Pending results", "Items that need attention before release.")}${results.length ? `<div class="grid grid-2">${results.map((item) => `<article class="card"><div class="topbar-actions" style="justify-content:space-between"><strong>${escapeHtml(item.patient || "Unknown patient")}</strong>${statusBadge(item.status)}</div><p class="muted">${escapeHtml(item.test || "-")} · ${escapeHtml(item.summary || "-")}</p><a class="btn btn-secondary" href="/results/${item.id}">Open detail</a></article>`).join("")}</div>` : emptyState("No pending results", "The API returned no unreleased rows.")}</section>`, { authenticated: true, activePath: "/results", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === "/fpp" && isGet) {
|
if (path === "/fpp" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
const groups = await loadFppPosterGroups(session);
|
const groups = await loadFppPosterGroups(session);
|
||||||
html(res, 200, fppPage({ group: query.group || "All", groups }));
|
html(res, 200, fppPage({ group: query.group || "All", groups }, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2189,7 +2193,7 @@ async function renderRoute(req, res, url) {
|
|||||||
|
|
||||||
if (path === "/" && isGet) {
|
if (path === "/" && isGet) {
|
||||||
if (!requireAuth(req, res)) return;
|
if (!requireAuth(req, res)) return;
|
||||||
html(res, 200, layout("Dashboard", await dashboardPage(session), { authenticated: true, activePath: "/" }));
|
html(res, 200, layout("Dashboard", await dashboardPage(session), { authenticated: true, activePath: "/", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2198,10 +2202,10 @@ async function renderRoute(req, res, url) {
|
|||||||
const orderId = path.split("/")[2];
|
const orderId = path.split("/")[2];
|
||||||
const order = await loadOrderDetail(session, orderId);
|
const order = await loadOrderDetail(session, orderId);
|
||||||
if (!order) {
|
if (!order) {
|
||||||
html(res, 200, layout("Pesan Khusus", `<section class="panel">${emptyState("No order found", "The API returned no matching order for this ID.")}</section>`, { authenticated: true, activePath: "/orders" }));
|
html(res, 200, layout("Pesan Khusus", `<section class="panel">${emptyState("No order found", "The API returned no matching order for this ID.")}</section>`, { authenticated: true, activePath: "/orders", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
html(res, 200, specialMessagePage(order));
|
html(res, 200, specialMessagePage(order, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2210,10 +2214,10 @@ async function renderRoute(req, res, url) {
|
|||||||
const orderId = path.split("/")[2];
|
const orderId = path.split("/")[2];
|
||||||
const order = await loadOrderDetail(session, orderId);
|
const order = await loadOrderDetail(session, orderId);
|
||||||
if (!order) {
|
if (!order) {
|
||||||
html(res, 200, layout("Order Detail", `<section class="panel">${emptyState("No order found", "The API returned no matching order for this ID.")}</section>`, { authenticated: true, activePath: "/orders" }));
|
html(res, 200, layout("Order Detail", `<section class="panel">${emptyState("No order found", "The API returned no matching order for this ID.")}</section>`, { authenticated: true, activePath: "/orders", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
html(res, 200, orderDetailPage(order));
|
html(res, 200, orderDetailPage(order, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2222,10 +2226,10 @@ async function renderRoute(req, res, url) {
|
|||||||
const resultId = path.split("/")[2];
|
const resultId = path.split("/")[2];
|
||||||
const result = await loadResultDetail(session, resultId);
|
const result = await loadResultDetail(session, resultId);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
html(res, 200, layout("Result Detail", `<section class="panel">${emptyState("No result found", "The API returned no matching result for this ID.")}</section>`, { authenticated: true, activePath: "/results" }));
|
html(res, 200, layout("Result Detail", `<section class="panel">${emptyState("No result found", "The API returned no matching result for this ID.")}</section>`, { authenticated: true, activePath: "/results", ...accountLayoutOptions(session) }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
html(res, 200, resultDetailPage(result));
|
html(res, 200, resultDetailPage(result, session));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2323,7 +2327,7 @@ async function renderRoute(req, res, url) {
|
|||||||
layout(
|
layout(
|
||||||
"Create Order",
|
"Create Order",
|
||||||
`<section class="panel">${panelHeader("Create new order", "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.")}<div class="note-box">Upstream order create call failed. Check API availability.</div></section>`,
|
`<section class="panel">${panelHeader("Create new order", "The new project collapses the old step-heavy flow into a cleaner shell while keeping the same workflow.")}<div class="note-box">Upstream order create call failed. Check API availability.</div></section>`,
|
||||||
{ authenticated: true, activePath: "/orders" },
|
{ authenticated: true, activePath: "/orders", ...accountLayoutOptions(sessionData) },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2353,7 +2357,7 @@ async function renderRoute(req, res, url) {
|
|||||||
layout(
|
layout(
|
||||||
"Pesan Khusus",
|
"Pesan Khusus",
|
||||||
`<section class="panel">${panelHeader("Pesan khusus", "Desktop can treat this as a modal-style panel; mobile reads it as a dedicated page.")}<div class="note-box">Upstream save failed. Check API availability.</div></section>`,
|
`<section class="panel">${panelHeader("Pesan khusus", "Desktop can treat this as a modal-style panel; mobile reads it as a dedicated page.")}<div class="note-box">Upstream save failed. Check API availability.</div></section>`,
|
||||||
{ authenticated: true, activePath: "/orders" },
|
{ authenticated: true, activePath: "/orders", ...accountLayoutOptions(sessionData) },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
65
styles.css
65
styles.css
@@ -233,6 +233,24 @@ button {
|
|||||||
border: 1px solid rgba(217, 119, 6, 0.12);
|
border: 1px solid rgba(217, 119, 6, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-footer-logo {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(180deg, #d91f1f 0%, #c51f1f 100%);
|
||||||
|
border: 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-logo-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 14px;
|
||||||
|
object-fit: cover;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-footer strong {
|
.sidebar-footer strong {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
@@ -303,6 +321,53 @@ button {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account-chip {
|
||||||
|
padding-right: 18px;
|
||||||
|
min-width: 172px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-avatar {
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.3), transparent 36%),
|
||||||
|
linear-gradient(135deg, var(--brand), var(--brand-strong));
|
||||||
|
box-shadow: 0 8px 16px rgba(185, 28, 28, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
line-height: 1.05;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-copy strong {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-copy small {
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.78;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user