Files
Hellas-Wawi/hellas_bestand Kopie.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>