Materialliste: zweispaltiges Layout mit Suche
CI / backend-tests (push) Failing after 1m21s
CI / frontend-build (push) Successful in 23s

Formular (Anlegen/Bearbeiten) links fix, Liste rechts mit Suchfeld oben
(Name/Artikelnummer) - bei 153 Materialien war die reine Scroll-Liste ohne
Suche kaum nutzbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-04 23:00:01 +02:00
co-authored by Claude Sonnet 5
parent 423aece15f
commit 6f047e3033
+82 -55
View File
@@ -26,6 +26,7 @@ export function MaterialSection({ materialien, onGeaendert, onFehler }: Props) {
const [formular, setFormular] = useState(LEERES_FORMULAR); const [formular, setFormular] = useState(LEERES_FORMULAR);
const [bearbeiteteId, setBearbeiteteId] = useState<number | null>(null); const [bearbeiteteId, setBearbeiteteId] = useState<number | null>(null);
const [wirdGespeichert, setWirdGespeichert] = useState(false); const [wirdGespeichert, setWirdGespeichert] = useState(false);
const [suche, setSuche] = useState("");
function bearbeiten(material: Material) { function bearbeiten(material: Material) {
setBearbeiteteId(material.id); setBearbeiteteId(material.id);
@@ -65,21 +66,31 @@ export function MaterialSection({ materialien, onGeaendert, onFehler }: Props) {
} }
} }
const sucheNormalisiert = suche.trim().toLowerCase();
const gefiltert = sucheNormalisiert
? materialien.filter(
(m) =>
m.name.toLowerCase().includes(sucheNormalisiert) ||
(m.artikelnummer ?? "").toLowerCase().includes(sucheNormalisiert)
)
: materialien;
return ( return (
<section className="section"> <section className="section">
<h2>Material {bearbeiteteId !== null ? "bearbeiten" : "anlegen"}</h2> <h2>Material</h2>
<div className="card"> <div style={{ display: "flex", flexDirection: "row", alignItems: "flex-start", gap: "1.25rem" }}>
<div className="stack" style={{ marginBottom: "1.25rem" }}> <div className="card" style={{ flex: "0 0 20rem", width: "20rem", boxSizing: "border-box" }}>
<div className="field"> <h3 style={{ marginTop: 0 }}>{bearbeiteteId !== null ? "Material bearbeiten" : "Material anlegen"}</h3>
<label>Name</label> <div className="stack">
<input <div className="field">
className="input" <label>Name</label>
value={formular.name} <input
onChange={(e) => setFormular({ ...formular, name: e.target.value })} className="input"
/> value={formular.name}
</div> onChange={(e) => setFormular({ ...formular, name: e.target.value })}
<div className="row"> />
<div className="field" style={{ flex: 1 }}> </div>
<div className="field">
<label>Artikelnummer</label> <label>Artikelnummer</label>
<input <input
className="input" className="input"
@@ -87,7 +98,7 @@ export function MaterialSection({ materialien, onGeaendert, onFehler }: Props) {
onChange={(e) => setFormular({ ...formular, artikelnummer: e.target.value })} onChange={(e) => setFormular({ ...formular, artikelnummer: e.target.value })}
/> />
</div> </div>
<div className="field" style={{ width: "7rem" }}> <div className="field">
<label>Einheit</label> <label>Einheit</label>
<input <input
className="input" className="input"
@@ -95,51 +106,67 @@ export function MaterialSection({ materialien, onGeaendert, onFehler }: Props) {
onChange={(e) => setFormular({ ...formular, einheit: e.target.value })} onChange={(e) => setFormular({ ...formular, einheit: e.target.value })}
/> />
</div> </div>
</div> <div className="field">
<div className="field"> <label>Materialtyp</label>
<label>Materialtyp</label> <select
<select className="input"
className="input" value={formular.materialtyp}
value={formular.materialtyp} onChange={(e) => setFormular({ ...formular, materialtyp: e.target.value })}
onChange={(e) => setFormular({ ...formular, materialtyp: e.target.value })} >
> {MATERIALTYPEN.map((t) => (
{MATERIALTYPEN.map((t) => ( <option key={t.value} value={t.value}>
<option key={t.value} value={t.value}> {t.label}
{t.label} </option>
</option> ))}
))} </select>
</select> </div>
</div> <div className="row">
<div className="row"> <button
<button className="btn btn-primary"
className="btn btn-primary" onClick={speichern}
onClick={speichern} disabled={!formular.name || !formular.einheit || wirdGespeichert}
disabled={!formular.name || !formular.einheit || wirdGespeichert} >
> {bearbeiteteId !== null ? "Speichern" : "Anlegen"}
{bearbeiteteId !== null ? "Speichern" : "Anlegen"}
</button>
{bearbeiteteId !== null && (
<button className="btn btn-secondary" onClick={zuruecksetzen}>
Abbrechen
</button> </button>
)} {bearbeiteteId !== null && (
<button className="btn btn-secondary" onClick={zuruecksetzen}>
Abbrechen
</button>
)}
</div>
</div> </div>
</div> </div>
{materialien.length > 0 && ( <div style={{ flex: "1 1 0%", minWidth: 0 }}>
<ul className="card-list"> <input
{materialien.map((m) => ( className="input input-search"
<li key={m.id} className="row-between" style={{ padding: "0.4rem 0" }}> style={{ marginBottom: "1rem" }}
<span> placeholder={`Suche in ${materialien.length} Materialien…`}
{m.name} <span className="text-muted">({m.einheit})</span> value={suche}
</span> onChange={(e) => setSuche(e.target.value)}
<button className="btn btn-secondary" onClick={() => bearbeiten(m)}> />
Bearbeiten {gefiltert.length === 0 && <p className="text-muted">Keine Treffer.</p>}
</button> {gefiltert.length > 0 && (
</li> <ul className="card-list">
))} {gefiltert.map((m) => (
</ul> <li key={m.id} className="card row-between" style={{ padding: "0.6rem 1rem" }}>
)} <span>
{m.name} <span className="text-muted">({m.einheit})</span>
{m.artikelnummer && <span className="text-muted"> · {m.artikelnummer}</span>}
{m.materialtyp !== "standard" && (
<span className="badge badge-neutral" style={{ marginLeft: "0.5rem" }}>
{m.materialtyp}
</span>
)}
</span>
<button className="btn btn-secondary" onClick={() => bearbeiten(m)}>
Bearbeiten
</button>
</li>
))}
</ul>
)}
</div>
</div> </div>
</section> </section>
); );