398 lines
23 KiB
HTML
398 lines
23 KiB
HTML
<!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>
|