Web-UI: professionelles Redesign (Icons statt Emojis) + neue Features
Design: - Neues Icon-Set (Inline-SVG, Feather-Stil), keine Emojis mehr - Kohaerentes Design-System (Sidebar-Layout, Palette, Spacing, Light/Dark) - Alle Seiten ueberarbeitet (Login, Dashboard, Produkte, Formular, Ein-/Auslagern, Lagerorte, Benutzer, Einkaufsliste) Neue Features: - Gruppen-Verwaltung (anlegen/bearbeiten/loeschen, Produktzahl + Bestand, Gruppen-Mindestbestand) inkl. Einkaufsliste - Einfache Gruppen-Auto-Zuordnung aus OFF-Kategorie im Produktformular - Verlauf-Seite (Bewegungen: wer/was/wann) via neuem /movements-Endpoint - Einstellungen-Seite (Ablauf-Warnfrist) - Lagerorte mit optional uebergeordnetem Ort (verschachtelbar) Backend: - groups: PATCH + Anreicherung (product_count, stock) - views: /shopping-list/groups, /movements - schemas: GroupUpdate, GroupShoppingItem, MovementOut Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { api } from "../api";
|
||||
import Icon from "../components/Icon";
|
||||
import { fmt, unitOptions, unitShort } from "../units";
|
||||
|
||||
export default function CheckOut() {
|
||||
@@ -54,7 +55,7 @@ export default function CheckOut() {
|
||||
quantity: Number(quantity),
|
||||
unit,
|
||||
});
|
||||
setInfo(`Ausgelagert (${res.affected_lots.length} Charge(n) betroffen). Neuer Bestand: ${fmt(res.product_stock)} ${unitShort(product.base_unit)}`);
|
||||
setInfo(`Ausgelagert (${res.affected_lots.length} Charge(n)). Neuer Bestand: ${fmt(res.product_stock)} ${unitShort(product.base_unit)}`);
|
||||
setQuantity("");
|
||||
setProduct(await api.getProduct(product.id));
|
||||
} catch (err) {
|
||||
@@ -66,26 +67,29 @@ export default function CheckOut() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-head"><h1>📤 Auslagern</h1></div>
|
||||
{error && <div className="alert error">{error}</div>}
|
||||
{info && <div className="alert info">{info}</div>}
|
||||
<div className="page-head"><h1>Auslagern</h1></div>
|
||||
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
|
||||
{info && <div className="alert ok"><Icon name="check" size={16} />{info}</div>}
|
||||
|
||||
{!product && (
|
||||
<div className="grid-2">
|
||||
<div className="card">
|
||||
<h2>Per Barcode</h2>
|
||||
<div className="row">
|
||||
<input className="grow" placeholder="Barcode" value={barcode}
|
||||
onChange={(e) => setBarcode(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && doLookup()} />
|
||||
<div className="card-head"><Icon name="search" /><h2>Per Barcode</h2></div>
|
||||
<div className="field-inline">
|
||||
<label className="grow" style={{ margin: 0 }}>
|
||||
<input placeholder="Barcode eingeben oder scannen" value={barcode}
|
||||
onChange={(e) => setBarcode(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && doLookup()} autoFocus />
|
||||
</label>
|
||||
<button className="btn primary" onClick={doLookup}>Suchen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h2>Aus Produktliste</h2>
|
||||
<form className="row" onSubmit={doSearch}>
|
||||
<input className="grow" placeholder="Name suchen…" value={search}
|
||||
onChange={(e) => setSearch(e.target.value)} />
|
||||
<div className="card-head"><Icon name="package" /><h2>Aus Produktliste</h2></div>
|
||||
<form className="field-inline" onSubmit={doSearch}>
|
||||
<label className="grow" style={{ margin: 0 }}>
|
||||
<input placeholder="Name suchen…" value={search} onChange={(e) => setSearch(e.target.value)} />
|
||||
</label>
|
||||
<button className="btn">Suchen</button>
|
||||
</form>
|
||||
<ul className="picklist">
|
||||
@@ -104,12 +108,12 @@ export default function CheckOut() {
|
||||
{product && (
|
||||
<form className="card form-narrow" onSubmit={submit}>
|
||||
<div className="selected-product">
|
||||
{product.image_url && <img className="thumb" src={product.image_url} alt="" />}
|
||||
<div>
|
||||
<strong>{product.name}</strong>
|
||||
<div className="muted">
|
||||
Verfügbar: {fmt(product.stock)} {unitShort(product.base_unit)}
|
||||
</div>
|
||||
{product.image_url
|
||||
? <img className="thumb" src={product.image_url} alt="" />
|
||||
: <span className="thumb thumb-fallback"><Icon name="box" size={16} /></span>}
|
||||
<div className="info">
|
||||
<div className="title">{product.name}</div>
|
||||
<div className="muted small">Verfügbar: {fmt(product.stock)} {unitShort(product.base_unit)}</div>
|
||||
</div>
|
||||
<button type="button" className="btn ghost" onClick={() => setProduct(null)}>Ändern</button>
|
||||
</div>
|
||||
@@ -123,16 +127,14 @@ export default function CheckOut() {
|
||||
<label className="grow">
|
||||
Einheit
|
||||
<select value={unit} onChange={(e) => setUnit(e.target.value)}>
|
||||
{unitOptions(product).map((o) => (
|
||||
<option key={o.value} value={o.value}>{o.label}</option>
|
||||
))}
|
||||
{unitOptions(product).map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<p className="muted small">
|
||||
<p className="muted small mt-0">
|
||||
Es wird automatisch die zuerst ablaufende Charge zuerst entnommen (FEFO).
|
||||
</p>
|
||||
<button className="btn primary" disabled={busy}>{busy ? "…" : "Auslagern"}</button>
|
||||
<button className="btn primary" disabled={busy}><Icon name="checkout" size={16} />{busy ? "…" : "Auslagern"}</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user