Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save userdehghani/0c08f5d10d5f69f3671db71d826d2bb3 to your computer and use it in GitHub Desktop.

Select an option

Save userdehghani/0c08f5d10d5f69f3671db71d826d2bb3 to your computer and use it in GitHub Desktop.
Customer invoices and history — before / after
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Customer invoices and history — before / after</title>
<style>
:root {
color-scheme: light;
--bg: #f4f4f5;
--ink: #18181b;
--muted: #71717a;
--line: #e4e4e7;
--card: #ffffff;
--fill: #f4f4f5;
--soft: #fafafa;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--ink);
}
header {
max-width: 880px;
margin: 0 auto;
padding: 28px 20px 0;
}
h1 { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.02em; }
p.lead { margin: 6px 0 0; color: var(--muted); font-size: 13px; max-width: 46rem; line-height: 1.45; }
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
max-width: 880px;
margin: 16px auto 0;
padding: 0 20px;
}
.switch, .scenes {
display: inline-flex;
padding: 3px;
border: 1px solid var(--line);
border-radius: 999px;
background: white;
}
.switch button, .scenes button {
border: 0;
background: transparent;
border-radius: 999px;
padding: 6px 12px;
font: inherit;
font-size: 13px;
color: var(--muted);
cursor: pointer;
}
.switch button[aria-pressed="true"], .scenes button[aria-pressed="true"] {
background: var(--ink);
color: white;
}
.stage {
max-width: 880px;
margin: 16px auto 48px;
padding: 0 20px;
}
.panel {
background: white;
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
}
.panel h2 {
margin: 0;
padding: 16px 18px 0;
font-size: 13px;
font-weight: 650;
}
.panel .note {
margin: 4px 18px 0;
color: var(--muted);
font-size: 12px;
}
.body { padding: 16px 18px 18px; }
.title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.title-row strong { font-size: 15px; }
.count { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.count b { color: var(--ink); font-weight: 600; }
.search {
height: 36px;
border: 1px solid var(--line);
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 12px;
color: var(--muted);
font-size: 13px;
margin-bottom: 8px;
}
.row {
display: flex;
align-items: center;
gap: 10px;
min-height: 56px;
padding: 8px;
border-radius: 12px;
}
.row.selected { background: #f4f4f5; }
.row.disabled { opacity: 0.45; }
.box {
width: 16px; height: 16px; border-radius: 6px; border: 1px solid #d4d4d8; flex: none;
display: grid; place-items: center; font-size: 11px; color: white;
}
.box.on { background: #18181b; border-color: #18181b; }
.icon { width: 28px; text-align: center; color: var(--muted); flex: none; }
.name { min-width: 0; flex: 1; }
.name span { display: block; font-size: 14px; }
.name small { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
.price { font-variant-numeric: tabular-nums; font-size: 14px; }
.single {
height: 40px;
border: 1px solid var(--line);
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
padding: 0 10px;
font-size: 14px;
}
.single span { flex: 1; }
.chev { color: var(--muted); }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #e4e4e7; }
th.loud { background: #f4f4f5; color: #18181b; font-size: 14px; font-weight: 600; padding: 10px 12px; }
th.quiet { background: #fafafa; color: #71717a; font-size: 12px; font-weight: 500; padding: 8px 12px; border-bottom-color: #e4e4e7; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.paper { background: #fff; }
.paper-top { display: flex; justify-content: space-between; padding: 18px 18px 8px; }
.paper-top b { font-size: 16px; }
.paper-top span { color: var(--muted); font-size: 12px; }
.badge {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 8px;
border-radius: 999px;
background: #f4f4f5;
color: #3f3f46;
font-size: 12px;
}
.hist-old { display: grid; grid-template-columns: minmax(0,1fr) 5.6rem 28px; gap: 4px; align-items: center; padding: 12px 14px; border-bottom: 1px solid #ececee; }
.hist-old .svc { display: flex; align-items: center; gap: 10px; min-width: 0; font-size: 14px; }
.date-pill {
height: 28px;
border: 1px solid var(--line);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
color: var(--muted);
font-size: 12px;
}
.well {
width: 36px; height: 36px; display: grid; place-items: center; color: var(--muted); flex: none;
}
.status {
display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 999px;
border: 1px solid #bbf7d0; background: #f0fdf4; color: #166534; font-size: 12px;
}
[hidden] { display: none !important; }
</style>
</head>
<body>
<header>
<h1>Customer invoices and history</h1>
<p class="lead">An invoice can include several services, and each one is its own line on the PDF. History and payment history column titles match the file manager table.</p>
</header>
<div class="controls">
<div class="scenes" role="tablist">
<button type="button" data-scene="services" aria-pressed="true">Services</button>
<button type="button" data-scene="invoice" aria-pressed="false">Invoice file</button>
<button type="button" data-scene="history" aria-pressed="false">History</button>
<button type="button" data-scene="payments" aria-pressed="false">Payments</button>
</div>
<div class="switch" role="group" aria-label="Before or after">
<button type="button" data-mode="before" aria-pressed="false">Before</button>
<button type="button" data-mode="after" aria-pressed="true">After</button>
</div>
</div>
<div class="stage">
<section class="panel" data-panel="services">
<h2>Create invoice · Services</h2>
<p class="note" data-before>One service. Picking another replaces it.</p>
<p class="note" data-after hidden>Several services stay in the list. A mismatched currency stays visible and disabled.</p>
<div class="body" data-before>
<div class="title-row"><strong>Service</strong></div>
<div class="single">
<span class="icon">▢</span>
<span>Listing<small style="display:block;color:#71717a;font-size:12px">Service · LST-1</small></span>
<span class="price">12&nbsp;000&nbsp;₽</span>
<span class="chev">›</span>
</div>
</div>
<div class="body" data-after hidden>
<div class="title-row">
<strong>Services</strong>
<span class="count">2 selected · <b>20&nbsp;000&nbsp;₽</b></span>
</div>
<div class="search">⌕ Search services</div>
<div class="row selected"><span class="box on">✓</span><span class="icon">▢</span><span class="name"><span>Listing</span><small>Service · LST-1</small></span><span class="price">12&nbsp;000&nbsp;₽</span></div>
<div class="row selected"><span class="box on">✓</span><span class="icon">▢</span><span class="name"><span>Photography</span><small>Service · PHT-2</small></span><span class="price">8&nbsp;000&nbsp;₽</span></div>
<div class="row disabled"><span class="box"></span><span class="icon">▢</span><span class="name"><span>Staging</span><small>Service · USD</small></span><span class="price">$200</span></div>
</div>
</section>
<section class="panel" data-panel="invoice" hidden>
<h2>Invoice PDF</h2>
<p class="note" data-before>Both services were stored as one line.</p>
<p class="note" data-after hidden>Each selected service has its own row.</p>
<div class="paper">
<div class="paper-top"><b>Invoice</b><span>No. 1042</span></div>
<table>
<thead>
<tr><th class="quiet">Description</th><th class="quiet num">Qty</th><th class="quiet num">Price</th><th class="quiet num">Amount</th></tr>
</thead>
<tbody data-before>
<tr><td>Listing, Photography</td><td class="num">1</td><td class="num">20&nbsp;000&nbsp;₽</td><td class="num">20&nbsp;000&nbsp;₽</td></tr>
</tbody>
<tbody data-after hidden>
<tr><td>Listing</td><td class="num">1</td><td class="num">12&nbsp;000&nbsp;₽</td><td class="num">12&nbsp;000&nbsp;₽</td></tr>
<tr><td>Photography</td><td class="num">1</td><td class="num">8&nbsp;000&nbsp;₽</td><td class="num">8&nbsp;000&nbsp;₽</td></tr>
</tbody>
</table>
</div>
</section>
<section class="panel" data-panel="history" hidden>
<h2>Customer history</h2>
<p class="note" data-before>Rows with no column titles. The date is a bordered control.</p>
<p class="note" data-after hidden>Name, Type, and Date use the file manager title style. The date is a badge.</p>
<div data-before>
<div class="hist-old"><div class="svc"><span>▢</span> Listing</div><div class="date-pill">📅 Oct 02</div><span>✎</span></div>
<div class="hist-old"><div class="svc"><span>▢</span> Photography</div><div class="date-pill">📅 Sep 18</div><span>✎</span></div>
</div>
<table data-after hidden>
<thead>
<tr>
<th class="quiet">Name</th>
<th class="quiet">Type</th>
<th class="quiet">Date</th>
<th class="quiet"></th>
</tr>
</thead>
<tbody>
<tr>
<td><div class="svc" style="display:flex;gap:10px;align-items:center"><span class="well">▢</span><span>Listing<br><small style="color:#71717a">Signed</small></span></div></td>
<td style="color:#71717a">Service</td>
<td><span class="badge">Oct 02</span></td>
<td style="text-align:right;color:#71717a">⋯</td>
</tr>
<tr>
<td><div class="svc" style="display:flex;gap:10px;align-items:center"><span class="well">▢</span><span>Photography</span></div></td>
<td style="color:#71717a">Service</td>
<td><span class="badge">Sep 18</span></td>
<td style="text-align:right;color:#71717a">⋯</td>
</tr>
</tbody>
</table>
</section>
<section class="panel" data-panel="payments" hidden>
<h2>Payment history</h2>
<p class="note" data-before>Column titles are bold, on a muted fill.</p>
<p class="note" data-after hidden>Same titles as the file manager: small, muted, on the page background.</p>
<table>
<thead data-before>
<tr>
<th class="loud">Invoice</th>
<th class="loud">Date</th>
<th class="loud">Amount</th>
<th class="loud">Payment method</th>
<th class="loud">Status</th>
<th class="loud" style="text-align:right">Actions</th>
</tr>
</thead>
<thead data-after hidden>
<tr>
<th class="quiet">Invoice</th>
<th class="quiet">Date</th>
<th class="quiet">Amount</th>
<th class="quiet">Payment method</th>
<th class="quiet">Status</th>
<th class="quiet" style="text-align:right">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1042</td>
<td>Oct 02</td>
<td>20&nbsp;000&nbsp;₽</td>
<td>Card</td>
<td><span class="status">Paid</span></td>
<td style="text-align:right;color:#71717a">⋮</td>
</tr>
<tr>
<td>1038</td>
<td>Sep 18</td>
<td>12&nbsp;000&nbsp;₽</td>
<td>Cash</td>
<td><span class="status">Paid</span></td>
<td style="text-align:right;color:#71717a">⋮</td>
</tr>
</tbody>
</table>
</section>
</div>
<script>
const scenes = document.querySelectorAll("[data-scene]");
const panels = document.querySelectorAll("[data-panel]");
const modes = document.querySelectorAll("[data-mode]");
let scene = "services";
let mode = "after";
function render() {
panels.forEach(panel => {
panel.hidden = panel.dataset.panel !== scene;
});
document.querySelectorAll("[data-before], [data-after]").forEach(node => {
const panel = node.closest("[data-panel]");
const show = panel && panel.dataset.panel === scene && node.hasAttribute("data-" + mode);
node.hidden = !show;
});
scenes.forEach(button => button.setAttribute("aria-pressed", String(button.dataset.scene === scene)));
modes.forEach(button => button.setAttribute("aria-pressed", String(button.dataset.mode === mode)));
}
scenes.forEach(button => button.addEventListener("click", () => { scene = button.dataset.scene; render(); }));
modes.forEach(button => button.addEventListener("click", () => { mode = button.dataset.mode; render(); }));
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment