Files
Hellas-Wawi/hellas_bestand.html
Bjoern Welker 81a1ed7eef Initial commit
2026-01-30 08:55:14 +01:00

398 lines
23 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="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Hellas Bestand – Übersicht</title>
<style>
:root {
--bg: #0a2036;
--card: #102741;
--muted: #9fb1c8;
--text: #f1f4f8;
--line: rgba(255,255,255,.12);
--accent: #f3d52a;
--accent-dark: #c2a51b;
--teal: #2a8a8a;
--ok: #7bd58d;
--bad: #ff6b7d;
--warn: #f0c04a;
--shadow: 0 12px 28px rgba(0,0,0,.28);
--radius: 14px;
--surface: #0f2236;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Liberation Sans", sans-serif;
background:
radial-gradient(900px 520px at 50% -10%, rgba(255,255,255,.10), transparent 60%),
radial-gradient(900px 600px at 50% 20%, rgba(16,44,70,.85), transparent 65%),
radial-gradient(1200px 700px at 50% 80%, rgba(7,26,44,.95), transparent 70%),
linear-gradient(180deg, #0a2036 0%, #0b243b 45%, #092135 100%);
color: var(--text);
line-height: 1.35;
}
header {
position: sticky;
top: 0;
z-index: 10;
background: rgba(9,24,40,.9);
border-bottom: 1px solid var(--line);
box-shadow: 0 8px 22px rgba(0,0,0,.35);
}
header::before {
content: "";
display: block;
height: 3px;
background: linear-gradient(90deg, var(--teal), rgba(42,138,138,.0));
}
.wrap {
max-width: 1100px;
margin: 0 auto;
padding: 18px 16px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand img {
height: 42px;
width: auto;
display: block;
filter: drop-shadow(0 2px 6px rgba(0,0,0,.4));
}
.top {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
h1 {
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
font-size: 24px;
margin: 0;
letter-spacing: 1px;
text-transform: uppercase;
}
.meta { color: var(--muted); font-size: 12px; letter-spacing: .3px; }
.controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.search {
min-width: 240px; max-width: 460px; flex: 1;
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 999px;
background: rgba(255,255,255,.06);
border: 1px solid var(--line);
box-shadow: 0 1px 0 rgba(255,255,255,.06) inset;
}
.search input {
width: 100%; border: 0; outline: 0; background: transparent;
color: var(--text); font-size: 14px;
}
.pill {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 12px; border-radius: 999px;
background: rgba(255,255,255,.06);
border: 1px solid var(--line);
user-select: none; cursor: pointer;
font-size: 13px; color: var(--text);
box-shadow: 0 2px 0 rgba(0,0,0,.18);
}
.pill input { accent-color: var(--accent); }
main .wrap { padding-top: 14px; padding-bottom: 28px; }
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: 10px;
margin-bottom: 14px;
}
.stat {
border: 1px solid var(--line);
border-radius: 14px;
padding: 10px 12px;
background: var(--surface);
display: grid;
gap: 4px;
box-shadow: var(--shadow);
}
.stat .label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .8px; }
.stat .value { font-size: 18px; font-weight: 700; letter-spacing: .3px; color: var(--accent); }
.grid { display: grid; gap: 12px; grid-template-columns: 1fr; }
@media (min-width: 860px) { .grid { grid-template-columns: 1fr 1fr; } }
details.card {
border: 1px solid var(--line);
border-radius: var(--radius);
background: linear-gradient(180deg, rgba(18,45,70,.92), rgba(12,31,51,.98));
box-shadow: 0 16px 28px rgba(0,0,0,.35);
overflow: clip;
}
details.card[data-status="ok"] { border-left: 4px solid rgba(123,213,141,.7); }
details.card[data-status="bad"] { border-left: 4px solid rgba(255,107,125,.8); }
details.card[data-status="warn"] { border-left: 4px solid rgba(240,192,74,.8); }
summary {
list-style: none;
cursor: pointer;
padding: 12px 12px 10px 12px;
display: flex; gap: 10px;
align-items: baseline;
justify-content: space-between;
background: rgba(8,22,36,.35);
}
summary::after {
content: "+";
margin-left: 6px;
font-size: 16px;
color: var(--accent);
font-weight: 700;
transition: opacity .15s ease;
}
details[open] summary::after { content: "–"; }
summary::-webkit-details-marker { display: none; }
.art { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.art .name {
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
font-size: 16px; font-weight: 600; letter-spacing: .8px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.art .sub { font-size: 12px; color: var(--muted); }
.badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.badge {
font-size: 11px; padding: 5px 8px; border-radius: 999px;
border: 1px solid var(--line);
background: rgba(0,0,0,.18);
color: var(--muted);
white-space: nowrap;
}
.badge strong { color: var(--text); font-weight: 650; }
.badge.ok { border-color: rgba(123,213,141,.4); color: rgba(214,246,224,.98); }
.badge.bad { border-color: rgba(255,107,125,.5); color: rgba(255,214,220,.98); }
.badge.warn { border-color: rgba(240,192,74,.5); color: rgba(255,242,206,.98); }
.content {
border-top: 1px solid var(--line);
padding: 10px 12px 12px 12px;
background: rgba(7,18,30,.4);
}
table { width: 100%; border-collapse: collapse; font-size: 13px; overflow: hidden; }
th, td { padding: 8px 6px; border-bottom: 1px solid rgba(255,255,255,.08); text-align: right; vertical-align: middle; font-variant-numeric: tabular-nums; }
th:first-child, td:first-child { text-align: left; }
th { color: var(--muted); font-weight: 600; }
tr:last-child td { border-bottom: 0; }
thead th { background: rgba(0,0,0,.18); }
tbody tr:nth-child(odd) { background: rgba(255,255,255,.03); }
.delta-pos { color: rgba(123,213,141,.98); font-weight: 700; }
.delta-neg { color: rgba(255,107,125,.98); font-weight: 700; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.footer {
margin-top: 14px; color: var(--muted); font-size: 12px;
display: flex; gap: 10px; flex-wrap: wrap; justify-content: space-between;
border-top: 1px dashed rgba(255,255,255,.12);
padding-top: 12px;
}
.empty {
border: 1px dashed rgba(255,255,255,.2);
border-radius: var(--radius);
padding: 16px;
color: var(--muted);
text-align: center;
}
</style>
</head>
<body>
<header>
<div class="wrap">
<div class="top">
<div class="brand">
<img src="logo.png" alt="Hellas 1899 Logo" />
<div>
<h1>Vereinskleidung – Übersicht</h1>
<div class="meta">Erstellt: 2026-01-28 11:25 · Quelle: Hellas_Bestand_neu.xlsx</div>
</div>
</div>
<div class="controls">
<div class="search" role="search">
<span aria-hidden="true">🔎</span>
<input id="q" type="search" placeholder="Artikel suchen… (z.B. Badehose, Hoodie, Kappe)" />
</div>
<label class="pill" title="Zeigt nur Artikel mit Abweichung ≠ 0">
<input id="onlyDiff" type="checkbox" />
Nur Abweichungen
</label>
<label class="pill" title="Alle Artikel auf-/zuklappen">
<input id="toggleAll" type="checkbox" />
Alles aufklappen
</label>
</div>
</div>
</div>
</header>
<main>
<div class="wrap">
<div id="stats" class="stats" aria-live="polite"></div>
<div id="grid" class="grid" aria-live="polite"></div>
<div class="footer">
<div>Tip: Auf einen Artikel klicken, um die Größen-Tabelle zu öffnen.</div>
<div class="small">Legende: <span class="delta-pos">+ Abweichung</span> · <span class="delta-neg">- Abweichung</span></div>
</div>
</div>
</main>
<script>
const DATA = [{"artikel": "Badehose", "rows": [{"groesse": "2", "soll": 6, "gezaehlt": 6, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "3", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "4", "soll": 3, "gezaehlt": 3, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "5", "soll": 4, "gezaehlt": 4, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "34", "soll": 8, "gezaehlt": 8, "abweichung": 0, "fehlbestand": null, "verkaeufe": 3}, {"groesse": "S", "soll": 4, "gezaehlt": 4, "abweichung": 0, "fehlbestand": null, "verkaeufe": 7}, {"groesse": "M", "soll": 5, "gezaehlt": 5, "abweichung": 0, "fehlbestand": null, "verkaeufe": 3}, {"groesse": "L", "soll": 9, "gezaehlt": 9, "abweichung": 0, "fehlbestand": null, "verkaeufe": 5}, {"groesse": "XL", "soll": 12, "gezaehlt": 12, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "XXL", "soll": 4, "gezaehlt": 4, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "32 /XXS", "soll": 10, "gezaehlt": 10, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}], "totals": {"soll": 66, "gezaehlt": 66, "abweichung": 0, "fehlbestand": null, "verkaeufe": 21}}, {"artikel": "Badehose ", "rows": [{"groesse": "36/XS", "soll": 4, "gezaehlt": 2, "abweichung": -2, "fehlbestand": null, "verkaeufe": 12}], "totals": {"soll": 4, "gezaehlt": 2, "abweichung": -2, "fehlbestand": null, "verkaeufe": 12}}, {"artikel": "Bademantel", "rows": [{"groesse": "S", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "M", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "L", "soll": 4, "gezaehlt": 4, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 6, "gezaehlt": 6, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Funktionsshirt", "rows": [{"groesse": "diverse", "soll": 3, "gezaehlt": 3, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}], "totals": {"soll": 3, "gezaehlt": 3, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}}, {"artikel": "Kaputzenjacke", "rows": [{"groesse": "158-164", "soll": 1, "gezaehlt": 3, "abweichung": 2, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 1, "gezaehlt": 3, "abweichung": 2, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Kapuzenjacke", "rows": [{"groesse": "XS", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "S", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "M", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "L", "soll": 2, "gezaehlt": 3, "abweichung": 1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XL", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "XXL", "soll": 3, "gezaehlt": 4, "abweichung": 1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXXL", "soll": 1, "gezaehlt": 0, "abweichung": -1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "YXL", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 10, "gezaehlt": 11, "abweichung": 1, "fehlbestand": null, "verkaeufe": 2}}, {"artikel": "Kapuzenjacke (alt)", "rows": [{"groesse": "L", "soll": 1, "gezaehlt": 0, "abweichung": -1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "36(YM)", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 3, "gezaehlt": 2, "abweichung": -1, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Kapuzenpullover", "rows": [{"groesse": "XS", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "S", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "M", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "L", "soll": 0, "gezaehlt": 6, "abweichung": 6, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "XL", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXL", "soll": 0, "gezaehlt": 1, "abweichung": 1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "158-164", "soll": 5, "gezaehlt": 3, "abweichung": -2, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "34 (YM)", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "36(YL)", "soll": 1, "gezaehlt": 0, "abweichung": -1, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "36(YXL)", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXXL", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 8, "gezaehlt": 12, "abweichung": 4, "fehlbestand": null, "verkaeufe": 3}}, {"artikel": "Kapuzenpullover (alt)", "rows": [{"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Rucksack (2025)", "rows": [{"groesse": "UNI", "soll": 35, "gezaehlt": 34, "abweichung": -1, "fehlbestand": null, "verkaeufe": 14}], "totals": {"soll": 35, "gezaehlt": 34, "abweichung": -1, "fehlbestand": null, "verkaeufe": 14}}, {"artikel": "Rucksack ohne Logo-Sonder", "rows": [{"groesse": "UNI", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Schwimmanzug ", "rows": [{"groesse": "30", "soll": 4, "gezaehlt": 4, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "32", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "36", "soll": 5, "gezaehlt": 5, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "S", "soll": 6, "gezaehlt": 6, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "M", "soll": 5, "gezaehlt": 5, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "L", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XL", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 22, "gezaehlt": 22, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "T-Shirt (alt)", "rows": [{"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "38 (YL)", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXXL", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "YM", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "YS", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 3, "gezaehlt": 3, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "T-Shirt (neu)", "rows": [{"groesse": "XS", "soll": 5, "gezaehlt": 5, "abweichung": 0, "fehlbestand": null, "verkaeufe": 2}, {"groesse": "S", "soll": 1, "gezaehlt": 0, "abweichung": -1, "fehlbestand": null, "verkaeufe": 3}, {"groesse": "M", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 5}, {"groesse": "L", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "XL", "soll": 4, "gezaehlt": 2, "abweichung": -2, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}], "totals": {"soll": 13, "gezaehlt": 10, "abweichung": -3, "fehlbestand": null, "verkaeufe": 13}}, {"artikel": "Team Short", "rows": [{"groesse": "XS", "soll": 7, "gezaehlt": 7, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "S", "soll": 6, "gezaehlt": 6, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "M", "soll": 8, "gezaehlt": 8, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}, {"groesse": "L", "soll": 13, "gezaehlt": 13, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XL", "soll": 9, "gezaehlt": 9, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXL", "soll": 8, "gezaehlt": 8, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 51, "gezaehlt": 51, "abweichung": 0, "fehlbestand": null, "verkaeufe": 1}}, {"artikel": "Trainingsanzug", "rows": [{"groesse": "XXS", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XS", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "S", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "M", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "L", "soll": 2, "gezaehlt": 2, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 11, "gezaehlt": 11, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}, {"artikel": "Wärmemantel", "rows": [{"groesse": "L", "soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}, {"groesse": "XXXL", "soll": 1, "gezaehlt": 1, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}], "totals": {"soll": 3, "gezaehlt": 3, "abweichung": 0, "fehlbestand": null, "verkaeufe": 0}}];
function fmt(v) {
if (v === null || v === undefined || Number.isNaN(v)) return "–";
return String(v);
}
function badge(label, value, cls="") {
if (value === null || value === undefined) return "";
return `<span class="badge ${cls}">${label}: <strong>${fmt(value)}</strong></span>`;
}
function deltaClass(d) {
if (d === null || d === undefined) return "";
if (d > 0) return "delta-pos";
if (d < 0) return "delta-neg";
return "";
}
function hasDiff(item) {
const t = item.totals || {};
return (t.abweichung ?? 0) !== 0 || (t.fehlbestand ?? 0) !== 0;
}
function totalGez(item) {
const rows = Array.isArray(item.rows) ? item.rows : [];
if (rows.length) {
return rows.reduce((acc, r) => acc + (Number(r?.gezaehlt) || 0), 0);
}
return Number(item?.totals?.gezaehlt) || 0;
}
function render(items) {
const grid = document.getElementById("grid");
const stats = document.getElementById("stats");
const sumField = (rows, key) => rows.reduce((acc, r) => acc + (Number(r?.[key]) || 0), 0);
const totals = DATA.reduce((acc, item) => {
const t = item.totals || {};
const rows = Array.isArray(item.rows) ? item.rows : [];
const rowsSoll = sumField(rows, "soll");
const rowsGez = sumField(rows, "gezaehlt");
const rowsAbw = sumField(rows, "abweichung");
acc.count += 1;
acc.withDiff += (t.abweichung ?? 0) !== 0 || (t.fehlbestand ?? 0) !== 0 ? 1 : 0;
acc.soll += rows.length ? rowsSoll : (t.soll ?? 0);
acc.gezaehlt += rows.length ? rowsGez : (t.gezaehlt ?? 0);
acc.abweichung += rows.length ? rowsAbw : (t.abweichung ?? 0);
return acc;
}, { count: 0, withDiff: 0, soll: 0, gezaehlt: 0, abweichung: 0 });
stats.innerHTML = `
<div class="stat"><div class="label">Artikel</div><div class="value">${fmt(totals.count)}</div></div>
<div class="stat"><div class="label">Mit Abweichung</div><div class="value">${fmt(totals.withDiff)}</div></div>
<div class="stat"><div class="label">Soll gesamt</div><div class="value">${fmt(totals.soll)}</div></div>
`;
if (!items.length) {
grid.innerHTML = `<div class="empty">Keine Treffer. (Suchbegriff anpassen)</div>`;
return;
}
grid.innerHTML = items.map((item, idx) => {
const t = item.totals || {};
const diff = t.abweichung ?? 0;
const fb = t.fehlbestand ?? null;
let statusCls = "ok";
if ((diff ?? 0) !== 0) statusCls = "bad";
else if ((fb ?? 0) !== 0) statusCls = "warn";
const statusText = ((diff ?? 0) === 0 && (fb ?? 0) === 0) ? "OK" : "Prüfen";
const rowsHtml = item.rows.map(r => `
<tr>
<td><strong>${fmt(r.groesse)}</strong></td>
<td>${fmt(r.soll)}</td>
<td>${fmt(r.gezaehlt)}</td>
<td class="${deltaClass(r.abweichung)}">${fmt(r.abweichung)}</td>
<td>${fmt(r.fehlbestand)}</td>
<td class="muted">${fmt(r.verkaeufe)}</td>
</tr>
`).join("");
return `
<details class="card" data-status="${statusCls}" data-idx="${idx}" ${idx < 2 ? "open" : ""}>
<summary>
<div class="art">
<div class="name">${item.artikel}</div>
<div class="sub">${item.rows.length} Größen</div>
</div>
<div class="badges">
${badge("Soll", t.soll, "")}
${badge("Gezählt", t.gezaehlt, "")}
${badge("Abw.", t.abweichung, (t.abweichung ?? 0) === 0 ? "ok" : "bad")}
${badge("Fehl", t.fehlbestand, (t.fehlbestand ?? 0) === 0 ? "ok" : "warn")}
<span class="badge ${statusCls}">${statusText}</span>
</div>
</summary>
<div class="content">
<table>
<thead>
<tr>
<th>Größe</th>
<th>Soll</th>
<th>Gezählt</th>
<th>Abweichung</th>
<th>Fehlbestand</th>
<th>Verkäufe</th>
</tr>
</thead>
<tbody>
${rowsHtml}
</tbody>
</table>
</div>
</details>
`;
}).join("");
}
function applyFilters() {
const q = (document.getElementById("q").value || "").trim().toLowerCase();
const onlyDiff = document.getElementById("onlyDiff").checked;
let items = DATA.slice();
if (q) {
items = items.filter(it => (it.artikel || "").toLowerCase().includes(q));
}
if (onlyDiff) {
items = items.filter(hasDiff);
}
items.sort((a, b) => totalGez(b) - totalGez(a));
render(items);
// Apply "toggle all" after render
const toggleAll = document.getElementById("toggleAll").checked;
document.querySelectorAll("details.card").forEach(d => d.open = toggleAll);
}
document.getElementById("q").addEventListener("input", applyFilters);
document.getElementById("onlyDiff").addEventListener("change", applyFilters);
document.getElementById("toggleAll").addEventListener("change", () => {
const on = document.getElementById("toggleAll").checked;
document.querySelectorAll("details.card").forEach(d => d.open = on);
});
applyFilters();
</script>
</body>
</html>