Files
ecw-charge-validation/web/templates/base.html
T
2026-10-30 10:58:35 +05:30

270 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="run-token" content="{{ run_token }}">
<title>{% block title %}Charge Validation{% endblock %} · eCW</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%232e3f36'/%3E%3Ctext x='16' y='21' font-family='Segoe UI,Arial' font-size='13' font-weight='800' text-anchor='middle' fill='%23fff'%3ECV%3C/text%3E%3C/svg%3E">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@200;300;500;600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
{% block head %}{% endblock %}
<style>
/* Sage (operator, 2026-10-30, the approved mockup): a deep sage side
menu, white rounded cards on soft sage-grey, a lime highlight; Pass
green, Fail orange, Review blue everywhere (told apart by lightness
too, not hue alone). The old names stay so every page follows. */
:root {
--navy: #2e3f36; --navy-2: #24332b; --bg: #f2f4f1; --panel: #ffffff;
--text: #1b2420; --muted: #5b6661; --line: #e2e6e2; --head: #eef2ee;
--accent: #4a6b58; --accent-dark: #3b5747; --lime: #d4e88a;
--pass: #245c3a; --pass-bg: #e3f0e7; --pass-bar: #4a6b58;
--fail: #9a4312; --fail-bg: #fbe9dd; --fail-bar: #d9772e;
--review: #244f96; --review-bg: #e3ecfa; --review-bar: #3b6fc4;
--stripe: #f8faf8; --hilite: #eef4ef; --sel: #dfe9e2;
--shadow: 0 1px 2px rgba(27, 36, 32, .05), 0 2px 10px rgba(27, 36, 32, .04);
--display: Outfit, "Segoe UI", system-ui, sans-serif;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text);
font: 14px/1.45 Inter, "Segoe UI", system-ui, sans-serif;
min-height: 100vh; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.shell { display: flex; flex-wrap: wrap; gap: 20px; padding: 20px;
min-height: 100vh; align-items: stretch; }
.page { flex: 999 1 560px; min-width: 0; display: flex;
flex-direction: column; }
aside.side { flex: 1 1 232px; max-width: 260px; min-width: 220px;
background: var(--navy); color: #f2f4f1; border-radius: 24px;
padding: 22px 14px; display: flex; flex-direction: column;
gap: 24px; }
aside.side .brand { color: #f2f4f1; display: flex; align-items: center;
gap: 12px; padding: 0 8px; }
aside.side .brand:hover { text-decoration: none; }
aside.side .mark { display: inline-grid; place-items: center; width: 38px;
height: 38px; border-radius: 12px; background: var(--lime);
color: var(--navy); font-size: 13px; font-weight: 800;
flex: none; }
aside.side .brand b { display: block; font-family: var(--display);
font-weight: 500; font-size: 17px; }
aside.side .brand small { color: #b9c6be; font-size: 12px; }
.pill { border-radius: 999px; padding: 3px 10px; font-size: 11px;
font-weight: 700; letter-spacing: .4px; background: var(--lime);
color: var(--navy); align-self: flex-start; margin-left: 8px; }
.pill.LIVE-WRITE { background: var(--fail-bar); color: #fff; }
aside.side nav.links { display: flex; flex-direction: column; gap: 4px; }
aside.side nav.links a { color: #e3e9e5; font-weight: 500; height: 44px;
padding: 0 14px; border-radius: 12px;
display: flex; align-items: center; gap: 12px;
transition: background .15s; }
aside.side nav.links a::before { content: ""; width: 18px; height: 18px;
flex: none; background: currentColor;
-webkit-mask: var(--ic) center / contain no-repeat;
mask: var(--ic) center / contain no-repeat; }
aside.side nav.links a:hover { background: rgba(242, 244, 241, .08);
text-decoration: none; }
aside.side nav.links a.on { background: var(--lime); color: var(--navy);
font-weight: 700; }
aside.side nav.links a[href="/"] { --ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Crect x='3' y='3' width='7' height='9' rx='1.5'/%3E%3Crect x='14' y='3' width='7' height='5' rx='1.5'/%3E%3Crect x='14' y='12' width='7' height='9' rx='1.5'/%3E%3Crect x='3' y='16' width='7' height='5' rx='1.5'/%3E%3C/svg%3E"); }
aside.side nav.links a[href="/days"] { --ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Crect x='4' y='3' width='16' height='18' rx='2'/%3E%3Cpath d='M8 8h8M8 12h8M8 16h5'/%3E%3C/svg%3E"); }
aside.side nav.links a[href="/runs"] { --ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12h4l3 8 4-16 3 8h4'/%3E%3C/svg%3E"); }
aside.side nav.links a[href$="/users"] { --ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20c.8-3.5 3.4-5.5 6.5-5.5s5.7 2 6.5 5.5M16 4.5a3.5 3.5 0 0 1 0 7M18 14.8c1.8.8 3 2.6 3.5 5.2'/%3E%3C/svg%3E"); }
aside.side nav.links a[href$="/signins"] { --ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7v5l3 2'/%3E%3C/svg%3E"); }
aside.side .who { margin-top: auto; display: flex; align-items: center;
gap: 12px; padding: 12px; border-radius: 16px;
background: rgba(242, 244, 241, .08); color: #f2f4f1; }
aside.side .who:hover { text-decoration: none;
background: rgba(242, 244, 241, .14); }
aside.side .who b { display: block; font-size: 14px; }
aside.side .who small { color: #b9c6be; font-size: 12px; }
aside.side .avatar { display: inline-grid; place-items: center; width: 36px;
height: 36px; border-radius: 50%; background: var(--accent);
color: #fff; font-size: 13px; font-weight: 700; flex: none; }
aside.side form.out { margin: 0; padding: 0 8px; }
aside.side form.out button { width: 100%; background: transparent;
border-color: rgba(242, 244, 241, .3);
font-weight: 500; }
aside.side form.out button:hover { background: rgba(242, 244, 241, .08); }
.msg { border-radius: 12px; padding: 10px 14px; margin-bottom: 14px; }
.msg.bad { background: var(--fail-bg); color: var(--fail); }
.msg.good { background: var(--pass-bg); color: var(--pass); }
nav.crumbs { color: var(--muted); padding: 4px 4px 0; font-size: 13px; }
nav.crumbs a { color: var(--muted); }
nav.crumbs .sep { margin: 0 8px; color: #8a958f; }
nav.crumbs .here { color: var(--text); font-weight: 600; }
main { flex: 1; width: 100%; padding: 14px 4px 28px; }
footer.foot { color: var(--muted); font-size: 12px; padding: 0 4px; }
footer.foot .wrap { display: flex; gap: 16px; align-items: center;
justify-content: space-between; flex-wrap: wrap;
padding: 12px 0; border-top: 1px solid var(--line); }
footer.foot b { color: var(--text); font-weight: 600; }
/* Motion: short, never in the way, off when Windows asks for less. */
/* Movement only, never opacity: a tab the browser holds in the
background must not leave the page faded. */
@keyframes rise { from { transform: translateY(8px); }
to { transform: none; } }
.card, .tile { animation: rise .35s ease both; }
.tiles > :nth-child(2) { animation-delay: .06s; }
.tiles > :nth-child(3) { animation-delay: .12s; }
.tiles > :nth-child(4) { animation-delay: .18s; }
a.tile, a.card { transition: transform .15s, box-shadow .15s,
border-color .15s; }
a.tile:hover, a.card:hover { transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(27, 36, 32, .10); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation: none !important;
transition: none !important; }
}
h1 { font-family: var(--display); font-weight: 300; font-size: 30px;
letter-spacing: -.3px; margin: 0 0 4px; }
.sub { color: var(--muted); margin: 0 0 16px; }
.card { background: var(--panel); border: 0; border-radius: 22px;
padding: 20px 22px; box-shadow: var(--shadow); margin-bottom: 18px; }
.card h2 { font-family: var(--display); font-weight: 500; font-size: 19px;
margin: 0 0 10px; }
.tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px; margin-bottom: 18px; }
.tile { background: var(--panel); border: 2px solid transparent;
border-radius: 20px; padding: 16px 18px; box-shadow: var(--shadow);
color: var(--text); display: block; }
.tile .n { font-family: var(--display); font-size: 36px; font-weight: 500;
line-height: 1.1; display: block; }
.tile .l { color: var(--muted); font-size: 13px; }
.tile.Pass .n { color: var(--pass); }
.tile.Fail .n { color: var(--fail); }
.tile.Review .n { color: var(--review); }
a.tile:hover { text-decoration: none; border-color: var(--accent); }
a.tile.on { border-color: var(--accent); }
select, input, button { font: inherit; padding: 8px 12px;
border: 1px solid #d5dbd6; border-radius: 10px;
background: var(--panel); color: var(--text); }
button, .btn { background: var(--accent); color: #fff;
border: 1px solid var(--accent); border-radius: 10px;
padding: 8px 16px; cursor: pointer; font-weight: 600;
display: inline-block; }
button:hover, .btn:hover { background: var(--accent-dark);
text-decoration: none; }
button.plain { background: var(--panel); color: var(--text);
border-color: #c9d2de; font-weight: 400; }
button.plain:hover { background: var(--hilite); }
button[disabled] { opacity: .5; cursor: default; }
.cat { display: inline-block; padding: 2px 10px; border-radius: 999px;
font-weight: 600; font-size: 12px; }
.cat.Pass { background: var(--pass-bg); color: var(--pass); }
.cat.Fail { background: var(--fail-bg); color: var(--fail); }
.cat.Review { background: var(--review-bg); color: var(--review); }
.empty { background: var(--panel); border: 1px dashed #c9d2de;
border-radius: 10px; padding: 26px; color: var(--muted);
text-align: center; }
.note { color: var(--muted); font-size: 12px; }
/* Tables: short, no sideways scroll; long text cut on one line. */
table.list { width: 100%; border-collapse: separate; border-spacing: 0;
background: var(--panel); border: 0; box-shadow: var(--shadow);
border-radius: 18px; overflow: hidden; table-layout: fixed; }
table.list th, table.list td { padding: 9px 12px; text-align: left;
border-bottom: 1px solid var(--line);
overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; }
table.list th { background: var(--head); font-size: 12px;
color: var(--muted); font-weight: 600; }
table.list tbody tr { cursor: pointer; }
table.list tbody tr:nth-child(even) td { background: var(--stripe); }
table.list tbody tr:hover td { background: var(--hilite); }
table.list tbody tr.sel td { background: var(--sel); }
table.list tbody tr:last-child td { border-bottom: 0; }
td.claim { font-weight: 600; }
.pager { display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
margin: 12px 0; }
.pager button { padding: 4px 10px; background: var(--panel);
color: var(--text); border-color: #c9d2de; font-weight: 400; }
.pager button.cur { background: var(--accent); color: #fff;
border-color: var(--accent); }
.pager button[disabled]:not(.cur) { opacity: .4; }
.pager .info { color: var(--muted); margin-left: 8px; }
form.search { display: flex; gap: 8px; align-items: center;
flex-wrap: wrap; }
form.search input { min-width: 220px; }
/* Side panels. The page under an open panel does not scroll: one scroll
bar, the panel's (operator, 2026-10-15; review 2026-10-19). */
body.drawer-open { overflow: hidden; }
.drawer { position: fixed; top: 0; right: 0; height: 100vh;
width: min(480px, 100vw); background: var(--panel);
border-left: 1px solid var(--line); z-index: 20;
box-shadow: -8px 0 24px rgba(27, 36, 32, .12);
padding: 0 0 30px; overflow-y: auto; }
.drawer[hidden] { display: none; }
.drawer .top { display: flex; align-items: center; gap: 10px;
background: var(--navy); color: #fff; padding: 12px 18px;
position: sticky; top: 0; }
.drawer .top h2 { font-size: 16px; margin: 0; }
.drawer .top .close { margin-left: auto; background: transparent;
border-color: #6f86a3; font-weight: 400; }
.drawer .body { padding: 14px 18px; }
.drawer dl { display: grid; grid-template-columns: 140px 1fr;
gap: 6px 12px; margin: 0; font-size: 13px; }
.drawer dt { color: var(--muted); }
.drawer dd { margin: 0; white-space: pre-wrap; word-break: break-word; }
.drawer h3 { font-size: 14px; margin: 16px 0 6px; }
.drawer pre { background: var(--head); padding: 10px; border-radius: 6px;
white-space: pre-wrap; font-size: 12px; margin: 0; }
@media (max-width: 760px) {
.tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.shell { padding: 12px; gap: 12px; }
aside.side { max-width: none; flex-basis: 100%; padding: 14px; gap: 12px; }
aside.side nav.links { flex-direction: row; flex-wrap: wrap; }
aside.side .who { margin-top: 0; }
}
{% block style %}{% endblock %}
</style>
<script>
// Shared by every page, defined before any page script runs: one side
// panel open at a time; Escape and every Close button shut it.
const $ = id => document.getElementById(id);
const CATEGORIES = {{ categories | list | tojson }};
function openPanel(id) {
document.querySelectorAll('.drawer').forEach(p => p.hidden = p.id !== id);
document.body.classList.toggle('drawer-open', id !== null);
}
const closePanels = () => openPanel(null);
document.addEventListener('click', e => {
if (e.target.closest('.drawer .close')) closePanels();
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closePanels();
});
</script>
</head>
<body>
{% set section = crumbs[1][0] if crumbs | length > 1 else crumbs[0][0] %}
<div class="shell">
{% if user %}<aside class="side">
<a class="brand" href="/"><span class="mark" aria-hidden="true">CV</span><span><b>Charge Validation</b><small>eCW · Salud</small></span></a>
<span class="pill {{ mode }}" title="SAVE_CORRECTIONS mode">{{ mode }}</span>
<nav class="links" aria-label="Main">
{% for label, link, admin in [("Dashboard", "/", False), ("Claim status", "/days", False), ("Runs", "/runs", False), ("Users", "/admin/users", True), ("Sign-ins", "/admin/signins", True)] %}{% if not admin or is_admin %}{% if label == section %}<a href="{{ link }}" class="on" aria-current="page">{{ label }}</a>{% else %}<a href="{{ link }}">{{ label }}</a>{% endif %}{% endif %}{% endfor %}
</nav>
<a class="who" href="/account/password" title="Change password"><span class="avatar" aria-hidden="true">{{ (user.display_name or "?")[:1] | upper }}</span><span><b>{{ user.display_name }}</b><small>{{ user.role }}</small></span></a>
<form class="out" method="post" action="/signout"><input type="hidden" name="token" value="{{ run_token }}"><button type="submit">Sign out</button></form>
</aside>{% endif %}
<div class="page">
<nav class="crumbs">
<div class="wrap">{% for label, link in crumbs %}{% if not loop.first %}<span class="sep">›</span>{% endif %}{% if link %}<a href="{{ link }}">{{ label }}</a>{% else %}<span class="here">{{ label }}</span>{% endif %}{% endfor %}</div>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer class="foot">
<div class="wrap">
<span><b>eCW Charge Validation</b> · Ecare India</span>
<span class="phi">Contains patient data; do not share screenshots
outside the team.</span>
<span>Mode <b>{{ mode }}</b></span>
</div>
</footer>
</div>
</div>
{% block scripts %}{% endblock %}
</body>
</html>