feat: add info field to articles for shop display
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>
This commit is contained in:
@@ -605,6 +605,9 @@ function render(items) {
|
|||||||
// Sale-Badge anzeigen
|
// Sale-Badge anzeigen
|
||||||
const saleBadge = item.sale ? '<div class="sale-badge">SALE 🔥</div>' : '';
|
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 `
|
return `
|
||||||
<div class="card-tile">
|
<div class="card-tile">
|
||||||
${saleBadge}
|
${saleBadge}
|
||||||
@@ -615,6 +618,7 @@ function render(items) {
|
|||||||
<div class="card-title">${item.artikel}</div>
|
<div class="card-title">${item.artikel}</div>
|
||||||
<div class="card-price">${priceText}</div>
|
<div class="card-price">${priceText}</div>
|
||||||
<div class="card-sub">${item.rows.length} Größen</div>
|
<div class="card-sub">${item.rows.length} Größen</div>
|
||||||
|
${infoText}
|
||||||
${stockBadge}
|
${stockBadge}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
@@ -692,7 +696,9 @@ document.addEventListener("click", (e) => {
|
|||||||
media.innerHTML = img ? `<img src="${img}" alt="${artikel}">` : "";
|
media.innerHTML = img ? `<img src="${img}" alt="${artikel}">` : "";
|
||||||
document.getElementById("detailTitle").textContent = artikel;
|
document.getElementById("detailTitle").textContent = artikel;
|
||||||
document.getElementById("detailPrice").textContent = detailBtn.dataset.preis || "Preis auf Anfrage";
|
document.getElementById("detailPrice").textContent = detailBtn.dataset.preis || "Preis auf Anfrage";
|
||||||
document.getElementById("detailSizes").textContent = `${item.rows.length} Größen`;
|
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 list = item.rows.map(r => {
|
||||||
const stock = Number(r.gezaehlt) || 0;
|
const stock = Number(r.gezaehlt) || 0;
|
||||||
let stockInfo = '';
|
let stockInfo = '';
|
||||||
|
|||||||
34
wawi/app.py
34
wawi/app.py
@@ -167,6 +167,7 @@ def init_db() -> None:
|
|||||||
ensure_price_column(db)
|
ensure_price_column(db)
|
||||||
ensure_image_column(db)
|
ensure_image_column(db)
|
||||||
ensure_sale_column(db)
|
ensure_sale_column(db)
|
||||||
|
ensure_info_column(db)
|
||||||
ensure_orders_columns(db)
|
ensure_orders_columns(db)
|
||||||
ensure_payment_columns(db)
|
ensure_payment_columns(db)
|
||||||
ensure_indexes(db)
|
ensure_indexes(db)
|
||||||
@@ -201,6 +202,16 @@ def ensure_sale_column(db: sqlite3.Connection) -> None:
|
|||||||
logger.info("Sale-Spalte für items erstellt")
|
logger.info("Sale-Spalte für items erstellt")
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_info_column(db: sqlite3.Connection) -> None:
|
||||||
|
"""Fügt info-Spalte für zusätzliche Artikelinformationen hinzu."""
|
||||||
|
cols = db.execute("PRAGMA table_info(items)").fetchall()
|
||||||
|
if any(c["name"] == "info" for c in cols):
|
||||||
|
return
|
||||||
|
db.execute("ALTER TABLE items ADD COLUMN info TEXT")
|
||||||
|
db.commit()
|
||||||
|
logger.info("Info-Spalte für items erstellt")
|
||||||
|
|
||||||
|
|
||||||
def ensure_orders_columns(db: sqlite3.Connection) -> None:
|
def ensure_orders_columns(db: sqlite3.Connection) -> None:
|
||||||
"""Sorgt für alle nachträglich eingeführten Orders‑Spalten."""
|
"""Sorgt für alle nachträglich eingeführten Orders‑Spalten."""
|
||||||
cols = db.execute("PRAGMA table_info(orders)").fetchall()
|
cols = db.execute("PRAGMA table_info(orders)").fetchall()
|
||||||
@@ -475,6 +486,7 @@ def new_item():
|
|||||||
if uploaded:
|
if uploaded:
|
||||||
bild_url = uploaded
|
bild_url = uploaded
|
||||||
sale = 1 if request.form.get("sale") else 0
|
sale = 1 if request.form.get("sale") else 0
|
||||||
|
info = (request.form.get("info") or "").strip() or None
|
||||||
soll = int(request.form.get("soll") or 0)
|
soll = int(request.form.get("soll") or 0)
|
||||||
gezaehlt = int(request.form.get("gezaehlt") or 0)
|
gezaehlt = int(request.form.get("gezaehlt") or 0)
|
||||||
verkaeufe = int(request.form.get("verkaeufe") or 0)
|
verkaeufe = int(request.form.get("verkaeufe") or 0)
|
||||||
@@ -483,12 +495,12 @@ def new_item():
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO items (artikel, groesse, preis, bild_url, sale, soll, gezaehlt, verkaeufe, created_at, updated_at)
|
INSERT INTO items (artikel, groesse, preis, bild_url, sale, info, soll, gezaehlt, verkaeufe, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(artikel, groesse, preis, bild_url, sale, soll, gezaehlt, verkaeufe, now_iso(), now_iso()),
|
(artikel, groesse, preis, bild_url, sale, info, soll, gezaehlt, verkaeufe, now_iso(), now_iso()),
|
||||||
)
|
)
|
||||||
db.execute("UPDATE items SET preis = ?, bild_url = ?, sale = ? WHERE artikel = ?", (preis, bild_url, sale, artikel))
|
db.execute("UPDATE items SET preis = ?, bild_url = ?, sale = ?, info = ? WHERE artikel = ?", (preis, bild_url, sale, info, artikel))
|
||||||
db.commit()
|
db.commit()
|
||||||
return redirect(url_for("bp.index"))
|
return redirect(url_for("bp.index"))
|
||||||
|
|
||||||
@@ -513,6 +525,7 @@ def edit_item(item_id: int):
|
|||||||
if uploaded:
|
if uploaded:
|
||||||
bild_url = uploaded
|
bild_url = uploaded
|
||||||
sale = 1 if request.form.get("sale") else 0
|
sale = 1 if request.form.get("sale") else 0
|
||||||
|
info = (request.form.get("info") or "").strip() or None
|
||||||
soll = int(request.form.get("soll") or 0)
|
soll = int(request.form.get("soll") or 0)
|
||||||
gezaehlt = int(request.form.get("gezaehlt") or 0)
|
gezaehlt = int(request.form.get("gezaehlt") or 0)
|
||||||
verkaeufe = int(request.form.get("verkaeufe") or 0)
|
verkaeufe = int(request.form.get("verkaeufe") or 0)
|
||||||
@@ -520,12 +533,12 @@ def edit_item(item_id: int):
|
|||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE items
|
UPDATE items
|
||||||
SET artikel = ?, groesse = ?, preis = ?, bild_url = ?, sale = ?, soll = ?, gezaehlt = ?, verkaeufe = ?, updated_at = ?
|
SET artikel = ?, groesse = ?, preis = ?, bild_url = ?, sale = ?, info = ?, soll = ?, gezaehlt = ?, verkaeufe = ?, updated_at = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
""",
|
""",
|
||||||
(artikel, groesse, preis, bild_url, sale, soll, gezaehlt, verkaeufe, now_iso(), item_id),
|
(artikel, groesse, preis, bild_url, sale, info, soll, gezaehlt, verkaeufe, now_iso(), item_id),
|
||||||
)
|
)
|
||||||
db.execute("UPDATE items SET preis = ?, bild_url = ?, sale = ? WHERE artikel = ?", (preis, bild_url, sale, artikel))
|
db.execute("UPDATE items SET preis = ?, bild_url = ?, sale = ?, info = ? WHERE artikel = ?", (preis, bild_url, sale, info, artikel))
|
||||||
db.commit()
|
db.commit()
|
||||||
return redirect(url_for("bp.index"))
|
return redirect(url_for("bp.index"))
|
||||||
|
|
||||||
@@ -719,7 +732,7 @@ def build_bestand() -> list[dict]:
|
|||||||
"""Aggregiert DB‑Zeilen in die Struktur der Live‑Bestand Ansicht."""
|
"""Aggregiert DB‑Zeilen in die Struktur der Live‑Bestand Ansicht."""
|
||||||
rows = get_db().execute(
|
rows = get_db().execute(
|
||||||
"""
|
"""
|
||||||
SELECT artikel, groesse, preis, bild_url, sale, soll, gezaehlt, verkaeufe
|
SELECT artikel, groesse, preis, bild_url, sale, info, soll, gezaehlt, verkaeufe
|
||||||
FROM items
|
FROM items
|
||||||
ORDER BY artikel, groesse
|
ORDER BY artikel, groesse
|
||||||
"""
|
"""
|
||||||
@@ -732,7 +745,7 @@ def build_bestand() -> list[dict]:
|
|||||||
continue
|
continue
|
||||||
item = data.setdefault(
|
item = data.setdefault(
|
||||||
artikel,
|
artikel,
|
||||||
{"artikel": artikel, "preis": 0, "bild_url": "", "sale": 0, "rows": [], "totals": {"soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": 0, "verkaeufe": 0}},
|
{"artikel": artikel, "preis": 0, "bild_url": "", "sale": 0, "info": None, "rows": [], "totals": {"soll": 0, "gezaehlt": 0, "abweichung": 0, "fehlbestand": 0, "verkaeufe": 0}},
|
||||||
)
|
)
|
||||||
if not item["preis"]:
|
if not item["preis"]:
|
||||||
item["preis"] = float(r["preis"] or 0)
|
item["preis"] = float(r["preis"] or 0)
|
||||||
@@ -741,6 +754,9 @@ def build_bestand() -> list[dict]:
|
|||||||
# Sale-Status (1 wenn mindestens eine Größe sale=1 hat)
|
# Sale-Status (1 wenn mindestens eine Größe sale=1 hat)
|
||||||
if r["sale"]:
|
if r["sale"]:
|
||||||
item["sale"] = 1
|
item["sale"] = 1
|
||||||
|
# Info-Text (nur wenn vorhanden)
|
||||||
|
if r["info"] and not item["info"]:
|
||||||
|
item["info"] = (r["info"] or "").strip()
|
||||||
soll = int(r["soll"] or 0)
|
soll = int(r["soll"] or 0)
|
||||||
gezaehlt = int(r["gezaehlt"] or 0)
|
gezaehlt = int(r["gezaehlt"] or 0)
|
||||||
verkaeufe = int(r["verkaeufe"] or 0)
|
verkaeufe = int(r["verkaeufe"] or 0)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
<input type="checkbox" name="sale" value="1" {% if item and item.sale %}checked{% endif %} style="width: auto; height: 18px;" />
|
<input type="checkbox" name="sale" value="1" {% if item and item.sale %}checked{% endif %} style="width: auto; height: 18px;" />
|
||||||
<span style="color: var(--text);">Sale / Abverkauf 🔥</span>
|
<span style="color: var(--text);">Sale / Abverkauf 🔥</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label style="grid-column: 1 / -1;">
|
||||||
|
Info-Text für Shop (optional)
|
||||||
|
<textarea name="info" rows="3" placeholder="z.B. Lieferzeit 2-3 Wochen, limitierte Auflage, etc.">{{ item.info if item and item.info else '' }}</textarea>
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Soll
|
Soll
|
||||||
<input type="number" name="soll" min="0" value="{{ item.soll if item else 0 }}" />
|
<input type="number" name="soll" min="0" value="{{ item.soll if item else 0 }}" />
|
||||||
|
|||||||
Reference in New Issue
Block a user