Materialliste: zweispaltiges Layout mit Suche
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:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user