Added optional info text field to articles that can be managed in the backend and is displayed in the shop only when filled. The info appears in both card view and detail modal with an informative style. - Added info column to items table with migration - Updated backend edit form with textarea for info text - Modified API to include info field in bestand response - Enhanced shop frontend to display info badge when available Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
785 lines
28 KiB
HTML
Executable File
785 lines
28 KiB
HTML
Executable File
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>Hellas – Shop</title>
|
||
<style>
|
||
:root {
|
||
--bg: #0a2036;
|
||
--card: #0f2236;
|
||
--muted: #9fb1c8;
|
||
--text: #f1f4f8;
|
||
--line: rgba(255,255,255,.12);
|
||
--accent: #f3d52a;
|
||
--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;
|
||
}
|
||
.top {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
}
|
||
.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));
|
||
}
|
||
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; }
|
||
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
|
||
.card-tile {
|
||
border: 1px solid rgba(255,255,255,.08);
|
||
border-radius: 16px;
|
||
background: rgba(12,31,51,.92);
|
||
box-shadow: 0 10px 22px rgba(0,0,0,.28);
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
transition: transform .12s ease, box-shadow .12s ease;
|
||
}
|
||
.card-tile:hover { transform: translateY(-1px); box-shadow: 0 14px 26px rgba(0,0,0,.32); }
|
||
.card-media {
|
||
width: 100%;
|
||
aspect-ratio: 1 / 1;
|
||
background: rgba(0,0,0,.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: .6px;
|
||
}
|
||
.card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.card-body { padding: 12px 12px 10px; display: grid; gap: 6px; }
|
||
.card-title {
|
||
font-family: "Arial Narrow", "Helvetica Neue Condensed", Impact, "Franklin Gothic Medium", "Arial Black", sans-serif;
|
||
font-size: 16px; font-weight: 600; letter-spacing: .6px;
|
||
}
|
||
.card-price { color: var(--accent); font-weight: 800; font-size: 14px; }
|
||
.card-sub { color: var(--muted); font-size: 12px; }
|
||
.stock-badge {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
margin-top: 6px;
|
||
}
|
||
.stock-badge.available {
|
||
background: rgba(123, 213, 141, 0.15);
|
||
color: var(--ok);
|
||
border: 1px solid rgba(123, 213, 141, 0.3);
|
||
}
|
||
.stock-badge.low {
|
||
background: rgba(243, 213, 42, 0.15);
|
||
color: var(--accent);
|
||
border: 1px solid rgba(243, 213, 42, 0.3);
|
||
}
|
||
.stock-badge.unavailable {
|
||
background: rgba(255, 107, 125, 0.15);
|
||
color: var(--bad);
|
||
border: 1px solid rgba(255, 107, 125, 0.3);
|
||
}
|
||
.sale-badge {
|
||
position: absolute;
|
||
top: 8px;
|
||
left: 8px;
|
||
background: linear-gradient(135deg, #ff0000, #cc0000);
|
||
color: #fff;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
letter-spacing: 1px;
|
||
text-transform: uppercase;
|
||
box-shadow: 0 4px 12px rgba(255, 0, 0, 0.4);
|
||
z-index: 10;
|
||
animation: pulse 2s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
}
|
||
.card-tile { position: relative; }
|
||
.card-actions { padding: 0 12px 12px; display: flex; justify-content: flex-end; }
|
||
.detail-btn { width: 100%; }
|
||
.size-list { display: grid; gap: 8px; padding: 10px 2px 2px; }
|
||
.size-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; padding: 8px 10px; border-radius: 10px; }
|
||
.size-row:nth-child(odd) { background: rgba(255,255,255,.04); }
|
||
.size-meta { font-size: 13px; color: var(--text); }
|
||
.size-meta strong { font-weight: 700; letter-spacing: .2px; }
|
||
.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: 18px; font-weight: 600; letter-spacing: .6px;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.art .sub { font-size: 12px; color: var(--muted); }
|
||
.badges { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
||
.badge {
|
||
font-size: 12px; padding: 6px 10px; border-radius: 10px;
|
||
border: 1px solid rgba(255,255,255,.12);
|
||
background: rgba(255,255,255,.05);
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
font-weight: 700;
|
||
letter-spacing: .2px;
|
||
}
|
||
.thumb {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 12px;
|
||
background:
|
||
linear-gradient(135deg, rgba(255,255,255,.08), rgba(0,0,0,.2)),
|
||
radial-gradient(circle at 30% 30%, rgba(255,255,255,.25), transparent 40%);
|
||
border: 1px solid rgba(255,255,255,.12);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 11px;
|
||
color: var(--muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: .6px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.price {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: var(--accent);
|
||
font-weight: 700;
|
||
letter-spacing: .2px;
|
||
}
|
||
.content {
|
||
border-top: 1px solid rgba(255,255,255,.08);
|
||
padding: 10px 12px 12px 12px;
|
||
background: rgba(6,16,28,.3);
|
||
}
|
||
.table-wrap { overflow-x: auto; }
|
||
.table-wrap table { min-width: 520px; }
|
||
table { width: 100%; border-collapse: collapse; font-size: 13px; overflow: hidden; }
|
||
th, td { padding: 10px 8px; border-bottom: 1px solid rgba(255,255,255,.06); 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,.12); }
|
||
tbody tr:nth-child(odd) { background: rgba(255,255,255,.02); }
|
||
.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, .error {
|
||
border: 1px dashed rgba(255,255,255,.2);
|
||
border-radius: var(--radius);
|
||
padding: 16px;
|
||
color: var(--muted);
|
||
text-align: center;
|
||
}
|
||
.error { color: #ffd6de; border-color: rgba(255,107,125,.4); }
|
||
.order-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 10px;
|
||
border-radius: 10px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(255,255,255,.06);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
}
|
||
.modal {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(5,12,20,.6);
|
||
display: none;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
padding: 84px 16px 24px;
|
||
z-index: 50;
|
||
opacity: 0;
|
||
transition: opacity .18s ease;
|
||
}
|
||
.modal.open { display: flex; opacity: 1; }
|
||
.modal-card {
|
||
width: 100%;
|
||
max-width: 520px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 14px;
|
||
background: linear-gradient(180deg, rgba(18,45,70,.92), rgba(12,31,51,.98));
|
||
box-shadow: 0 16px 28px rgba(0,0,0,.35);
|
||
padding: 14px;
|
||
max-height: calc(100vh - 80px);
|
||
overflow: auto;
|
||
transform: scale(.98);
|
||
transition: transform .18s ease;
|
||
}
|
||
.modal.open .modal-card { transform: scale(1); }
|
||
.modal-card h3 { margin: 0 0 10px; }
|
||
.detail-header { display: grid; gap: 8px; }
|
||
.detail-title { font-size: 20px; font-weight: 700; letter-spacing: .4px; }
|
||
.detail-price { color: var(--accent); font-weight: 800; }
|
||
.detail-media {
|
||
width: 100%;
|
||
aspect-ratio: 4 / 3;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
background: rgba(0,0,0,.2);
|
||
}
|
||
.detail-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.modal-card { position: relative; }
|
||
.close-x {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(0,0,0,.35);
|
||
background: rgba(8,16,28,.85);
|
||
color: var(--text);
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
box-shadow: 0 4px 10px rgba(0,0,0,.35);
|
||
}
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 10px;
|
||
}
|
||
.form-grid label { display: grid; gap: 6px; font-size: 12px; color: var(--muted); }
|
||
.form-grid input, .form-grid textarea {
|
||
background: rgba(255,255,255,.06);
|
||
border: 1px solid var(--line);
|
||
color: var(--text);
|
||
padding: 8px 10px;
|
||
border-radius: 10px;
|
||
font-size: 13px;
|
||
}
|
||
.form-actions { margin-top: 12px; display: flex; gap: 10px; justify-content: flex-end; }
|
||
.btn {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
padding: 8px 12px; border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(255,255,255,.06);
|
||
color: var(--text); cursor: pointer;
|
||
}
|
||
.btn.accent { background: var(--accent); color: #071320; border-color: transparent; font-weight: 700; }
|
||
.img-modal .modal-card { max-width: 720px; padding: 10px; }
|
||
.img-modal img { width: 100%; height: auto; border-radius: 12px; display: block; }
|
||
.order-modal { z-index: 60; }
|
||
@media (max-width: 700px) {
|
||
.top { align-items: flex-start; }
|
||
.brand { width: 100%; }
|
||
.controls { width: 100%; }
|
||
.search { min-width: 100%; }
|
||
.card-media { aspect-ratio: 4 / 3; }
|
||
.order-btn { width: 100%; justify-content: center; }
|
||
}
|
||
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
|
||
|
||
/* Toast Notifications */
|
||
.toast-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 9999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
pointer-events: none;
|
||
}
|
||
.toast {
|
||
min-width: 300px;
|
||
max-width: 400px;
|
||
padding: 16px 20px;
|
||
border-radius: 12px;
|
||
background: rgba(12, 31, 51, 0.98);
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4), 0 0 1px rgba(255, 255, 255, 0.3);
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
pointer-events: all;
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||
}
|
||
.toast.show {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
.toast.success {
|
||
border-left: 4px solid var(--ok);
|
||
background: linear-gradient(90deg, rgba(123, 213, 141, 0.15), rgba(12, 31, 51, 0.98));
|
||
}
|
||
.toast.error {
|
||
border-left: 4px solid var(--bad);
|
||
background: linear-gradient(90deg, rgba(255, 107, 125, 0.15), rgba(12, 31, 51, 0.98));
|
||
}
|
||
.toast.info {
|
||
border-left: 4px solid var(--accent);
|
||
background: linear-gradient(90deg, rgba(243, 213, 42, 0.15), rgba(12, 31, 51, 0.98));
|
||
}
|
||
@media (max-width: 640px) {
|
||
.toast-container {
|
||
top: auto;
|
||
bottom: 20px;
|
||
right: 16px;
|
||
left: 16px;
|
||
}
|
||
.toast {
|
||
min-width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="wrap">
|
||
<div class="top">
|
||
<div class="brand">
|
||
<img src="logo.png" alt="Hellas 1899 Logo" />
|
||
<div>
|
||
<h1>Shop</h1>
|
||
<div class="meta" id="meta">Stand: –</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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<main>
|
||
<div class="wrap">
|
||
<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>
|
||
</div>
|
||
</main>
|
||
|
||
<div id="orderModal" class="modal order-modal" role="dialog" aria-modal="true">
|
||
<div class="modal-card">
|
||
<button class="close-x" id="orderCloseX" aria-label="Schließen">×</button>
|
||
<h3>Bestellung</h3>
|
||
<form id="orderForm">
|
||
<div class="form-grid">
|
||
<label>Artikel
|
||
<input id="fArtikel" name="artikel" readonly />
|
||
</label>
|
||
<label>Größe
|
||
<input id="fGroesse" name="groesse" readonly />
|
||
</label>
|
||
<label>Menge
|
||
<input id="fMenge" name="menge" type="number" min="1" value="1" required />
|
||
</label>
|
||
<label>Name
|
||
<input id="fName" name="name" required />
|
||
</label>
|
||
<label>Handy
|
||
<input id="fHandy" name="handy" required />
|
||
</label>
|
||
<label>E-Mail
|
||
<input id="fEmail" name="email" type="email" required />
|
||
</label>
|
||
<label>Mannschaft
|
||
<input id="fMannschaft" name="mannschaft" placeholder="z. B. U12" required />
|
||
</label>
|
||
<label style="grid-column: 1 / -1;">Notiz
|
||
<textarea id="fNotiz" name="notiz" rows="3"></textarea>
|
||
</label>
|
||
<div style="grid-column: 1 / -1; padding: 12px; background: #f0f9ff; border: 1px solid #bfdbfe; border-radius: 4px; margin-top: 8px;">
|
||
<strong>Zahlungsart wählen:</strong>
|
||
<div style="margin-top: 8px;">
|
||
<label style="display: flex; align-items: center; margin-bottom: 8px;">
|
||
<input type="radio" name="payment_method" value="paypal" checked style="margin-right: 8px;" />
|
||
<span><strong>PayPal (Familie & Freunde)</strong></span>
|
||
</label>
|
||
<div id="paypalInfo" style="margin-left: 28px; margin-bottom: 12px; font-size: 0.95em; color: #1e40af;">
|
||
Bitte sende den Betrag über PayPal an: <strong id="paypalAccount">–</strong>
|
||
</div>
|
||
<div style="margin-left: 28px; margin-bottom: 12px; padding: 10px; background: #fef3c7; border: 2px solid #f59e0b; border-radius: 6px; font-size: 0.9em; color: #92400e;">
|
||
<strong>⚠️ WICHTIG:</strong> Bitte unbedingt die Option <strong>"Familie & Freunde"</strong> wählen!<br>
|
||
Bei normalen PayPal-Zahlungen fallen zusätzliche Gebühren an.
|
||
</div>
|
||
<label style="display: flex; align-items: center;">
|
||
<input type="radio" name="payment_method" value="bar" style="margin-right: 8px;" />
|
||
<span><strong>Bar bei Abholung</strong></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="button" class="btn" id="orderCancel">Abbrechen</button>
|
||
<button type="submit" class="btn accent">Bestellen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="imgModal" class="modal img-modal" role="dialog" aria-modal="true">
|
||
<div class="modal-card">
|
||
<img id="imgPreview" src="" alt="Artikelbild" />
|
||
</div>
|
||
</div>
|
||
|
||
<div id="detailModal" class="modal" role="dialog" aria-modal="true">
|
||
<div class="modal-card">
|
||
<button class="close-x" id="detailCloseX" aria-label="Schließen">×</button>
|
||
<div class="detail-header">
|
||
<div id="detailMedia" class="detail-media"></div>
|
||
<div class="detail-title" id="detailTitle"></div>
|
||
<div class="detail-price" id="detailPrice"></div>
|
||
<div class="card-sub" id="detailSizes"></div>
|
||
</div>
|
||
<div class="size-list" id="detailSizeList"></div>
|
||
<div class="form-actions">
|
||
<button type="button" class="btn" id="detailClose">Schließen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toastContainer" class="toast-container"></div>
|
||
|
||
<script>
|
||
// Toast Notification System
|
||
function showToast(message, type = 'info') {
|
||
const container = document.getElementById('toastContainer');
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.textContent = message;
|
||
|
||
container.appendChild(toast);
|
||
|
||
// Trigger animation
|
||
setTimeout(() => toast.classList.add('show'), 10);
|
||
|
||
// Auto-remove after 4 seconds
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
setTimeout(() => container.removeChild(toast), 300);
|
||
}, 4000);
|
||
}
|
||
|
||
// Proxy der WaWi‑App (kein API‑Key im Browser nötig).
|
||
const API_URL = "/wawi/proxy/bestand";
|
||
let DATA = [];
|
||
const DATA_MAP = new Map();
|
||
|
||
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 render(items) {
|
||
const grid = document.getElementById("grid");
|
||
|
||
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 price = Number(item.preis) || 0;
|
||
const priceText = price > 0 ? `${price.toFixed(2)} €` : "Preis auf Anfrage";
|
||
const img = (item.bild_url || "").trim();
|
||
|
||
// Verfügbarkeit prüfen
|
||
const totalStock = Number(t.gezaehlt) || 0;
|
||
let stockBadge = '';
|
||
let stockClass = '';
|
||
|
||
if (totalStock === 0) {
|
||
stockBadge = '<div class="stock-badge unavailable">⛔ Nicht verfügbar</div>';
|
||
stockClass = 'unavailable';
|
||
} else if (totalStock <= 5) {
|
||
stockBadge = `<div class="stock-badge low">⚠️ Nur noch ${totalStock} verfügbar</div>`;
|
||
stockClass = 'low';
|
||
} else {
|
||
stockBadge = `<div class="stock-badge available">✓ Verfügbar (${totalStock})</div>`;
|
||
stockClass = 'available';
|
||
}
|
||
|
||
// Sale-Badge anzeigen
|
||
const saleBadge = item.sale ? '<div class="sale-badge">SALE 🔥</div>' : '';
|
||
|
||
// Info-Text (nur wenn vorhanden)
|
||
const infoText = item.info ? `<div class="card-info" style="margin-top: 8px; padding: 8px; background: rgba(243, 213, 42, 0.1); border-left: 3px solid var(--accent); border-radius: 4px; font-size: 12px; line-height: 1.4; color: var(--text);">ℹ️ ${item.info}</div>` : '';
|
||
|
||
return `
|
||
<div class="card-tile">
|
||
${saleBadge}
|
||
<div class="card-media">
|
||
${img ? `<button class="thumb-btn" data-img="${img}" aria-label="Bild vergrößern" title="Bild vergrößern" style="width:100%;height:100%;border:0;cursor:pointer;padding:0;position:relative;overflow:hidden;"><img src="${img}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';" style="width:100%;height:100%;object-fit:cover;display:block;"><div style="display:none;position:absolute;inset:0;align-items:center;justify-content:center;background:linear-gradient(135deg, rgba(42,138,138,.15), rgba(18,45,70,.2));font-size:40px;opacity:0.5;">📦</div></button>` : `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg, rgba(42,138,138,.15), rgba(18,45,70,.2));font-size:40px;opacity:0.5;">📦</div>`}
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="card-title">${item.artikel}</div>
|
||
<div class="card-price">${priceText}</div>
|
||
<div class="card-sub">${item.rows.length} Größen</div>
|
||
${infoText}
|
||
${stockBadge}
|
||
</div>
|
||
<div class="card-actions">
|
||
<button class="order-btn detail-btn" data-artikel="${item.artikel}" data-preis="${priceText}" data-img="${img}" ${totalStock === 0 ? 'disabled style="opacity: 0.5; cursor: not-allowed;"' : ''}>
|
||
${totalStock === 0 ? 'Nicht verfügbar' : 'Bestellen / Details'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join("");
|
||
}
|
||
|
||
function applyFilters() {
|
||
const q = (document.getElementById("q").value || "").trim().toLowerCase();
|
||
let items = DATA.slice();
|
||
if (q) {
|
||
items = items.filter(it => (it.artikel || "").toLowerCase().includes(q));
|
||
}
|
||
render(items);
|
||
|
||
}
|
||
|
||
async function loadData() {
|
||
const res = await fetch(API_URL);
|
||
if (!res.ok) {
|
||
const grid = document.getElementById("grid");
|
||
grid.innerHTML = `<div class="error">API Fehler (${res.status}). Bitte API‑Key prüfen.</div>`;
|
||
return;
|
||
}
|
||
DATA = await res.json();
|
||
DATA_MAP.clear();
|
||
DATA.forEach(item => DATA_MAP.set(item.artikel, item));
|
||
document.getElementById("meta").textContent = `Stand: ${new Date().toLocaleString("de-DE")}`;
|
||
applyFilters();
|
||
}
|
||
|
||
document.getElementById("q").addEventListener("input", applyFilters);
|
||
|
||
const modal = document.getElementById("orderModal");
|
||
const form = document.getElementById("orderForm");
|
||
const setField = (id, v) => document.getElementById(id).value = v || "";
|
||
const ORDER_KEY = "";
|
||
|
||
// PayPal-Konto vom Backend laden
|
||
fetch("/wawi/config")
|
||
.then(res => res.json())
|
||
.then(config => {
|
||
if (config.paypal_account) {
|
||
document.getElementById("paypalAccount").textContent = config.paypal_account;
|
||
} else {
|
||
document.getElementById("paypalAccount").textContent = "Nicht konfiguriert";
|
||
}
|
||
})
|
||
.catch(() => {
|
||
document.getElementById("paypalAccount").textContent = "Fehler beim Laden";
|
||
});
|
||
|
||
document.addEventListener("click", (e) => {
|
||
const imgBtn = e.target.closest(".thumb-btn");
|
||
if (imgBtn) {
|
||
document.getElementById("imgPreview").src = imgBtn.dataset.img;
|
||
document.getElementById("imgModal").classList.add("open");
|
||
return;
|
||
}
|
||
const detailBtn = e.target.closest(".detail-btn");
|
||
if (detailBtn) {
|
||
// Verhindere Klick auf disabled Button
|
||
if (detailBtn.disabled) return;
|
||
|
||
const artikel = detailBtn.dataset.artikel;
|
||
const item = DATA_MAP.get(artikel);
|
||
if (!item) return;
|
||
const img = (detailBtn.dataset.img || "").trim();
|
||
const media = document.getElementById("detailMedia");
|
||
media.innerHTML = img ? `<img src="${img}" alt="${artikel}">` : "";
|
||
document.getElementById("detailTitle").textContent = artikel;
|
||
document.getElementById("detailPrice").textContent = detailBtn.dataset.preis || "Preis auf Anfrage";
|
||
const sizesText = `${item.rows.length} Größen`;
|
||
const infoText = item.info ? ` · ℹ️ ${item.info}` : '';
|
||
document.getElementById("detailSizes").textContent = sizesText + infoText;
|
||
const list = item.rows.map(r => {
|
||
const stock = Number(r.gezaehlt) || 0;
|
||
let stockInfo = '';
|
||
|
||
if (stock === 0) {
|
||
stockInfo = '<span class="stock-badge unavailable">Nicht verfügbar</span>';
|
||
} else if (stock <= 3) {
|
||
stockInfo = `<span class="stock-badge low">Nur noch ${stock} verfügbar</span>`;
|
||
} else {
|
||
stockInfo = `<span class="stock-badge available">Verfügbar (${stock})</span>`;
|
||
}
|
||
|
||
return `
|
||
<div class="size-row">
|
||
<div class="size-meta">
|
||
<strong>${fmt(r.groesse)}</strong> · ${stockInfo}
|
||
</div>
|
||
${stock > 0 ? `<button class="order-btn" data-artikel="${item.artikel}" data-groesse="${fmt(r.groesse)}">Bestellen</button>` : '<span style="color: var(--muted); font-size: 11px;">–</span>'}
|
||
</div>
|
||
`;
|
||
}).join("");
|
||
document.getElementById("detailSizeList").innerHTML = list;
|
||
document.getElementById("detailModal").classList.add("open");
|
||
return;
|
||
}
|
||
const btn = e.target.closest(".order-btn");
|
||
if (!btn) return;
|
||
setField("fArtikel", btn.dataset.artikel);
|
||
setField("fGroesse", btn.dataset.groesse);
|
||
setField("fMenge", 1);
|
||
modal.classList.add("open");
|
||
});
|
||
document.getElementById("orderCancel").addEventListener("click", () => {
|
||
modal.classList.remove("open");
|
||
if (document.getElementById("detailModal").classList.contains("open")) {
|
||
document.getElementById("detailModal").querySelector(".size-row")?.scrollIntoView({ block: "nearest" });
|
||
}
|
||
});
|
||
document.getElementById("orderCloseX").addEventListener("click", () => {
|
||
modal.classList.remove("open");
|
||
});
|
||
modal.addEventListener("click", (e) => {
|
||
if (e.target === modal) modal.classList.remove("open");
|
||
});
|
||
document.getElementById("imgModal").addEventListener("click", (e) => {
|
||
if (e.target.id === "imgModal") e.currentTarget.classList.remove("open");
|
||
});
|
||
document.getElementById("detailClose").addEventListener("click", () => {
|
||
document.getElementById("detailModal").classList.remove("open");
|
||
});
|
||
document.getElementById("detailCloseX").addEventListener("click", () => {
|
||
document.getElementById("detailModal").classList.remove("open");
|
||
});
|
||
document.getElementById("detailModal").addEventListener("click", (e) => {
|
||
if (e.target.id === "detailModal") e.currentTarget.classList.remove("open");
|
||
});
|
||
|
||
form.addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
const payload = Object.fromEntries(new FormData(form).entries());
|
||
const key = ORDER_KEY || "";
|
||
const headers = { "Content-Type": "application/json" };
|
||
if (key) headers["X-Order-Key"] = key;
|
||
const res = await fetch("/wawi/order", {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify(payload)
|
||
});
|
||
if (res.ok) {
|
||
showToast("Bestellung erfolgreich gesendet! Wir melden uns bei dir.", "success");
|
||
modal.classList.remove("open");
|
||
document.getElementById("detailModal").classList.remove("open");
|
||
form.reset();
|
||
setField("fMenge", 1);
|
||
} else {
|
||
showToast("Fehler beim Senden der Bestellung. Bitte versuche es erneut.", "error");
|
||
}
|
||
});
|
||
|
||
loadData();
|
||
</script>
|
||
</body>
|
||
</html>
|