Refactor: große Dateien in kleinere Module/Komponenten aufgeteilt
Explizite Vorgabe: keine wachsenden Monolith-Dateien. AdminPage.tsx (349
Zeilen) in pages/admin/{Standort,Material,Objekt}Section.tsx aufgeteilt,
KontrollPage.tsx (277 Zeilen) in pages/kontrolle/{useKontrolle,
PositionCard,StatusBadge,types} aufgeteilt, services/kontrolle.py (332
Zeilen) in ein Package (lifecycle/erfassung/exceptions) aufgeteilt -
öffentliche API bleibt über __init__.py unverändert re-exportiert, kein
bestehender Import musste angepasst werden. Reines Refactoring, keine
Verhaltensänderung.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { apiRequest } from "../../api/client";
|
||||
import type { Material } from "../../api/types";
|
||||
|
||||
const MATERIALTYPEN = [
|
||||
{ value: "standard", label: "Standard" },
|
||||
{ value: "ablauf_charge", label: "Ablauf/Charge" },
|
||||
{ value: "geraet_sn", label: "Gerät mit Seriennummer" },
|
||||
];
|
||||
|
||||
const LEERES_FORMULAR = {
|
||||
name: "",
|
||||
artikelnummer: "",
|
||||
einheit: "Stück",
|
||||
materialtyp: "standard",
|
||||
};
|
||||
|
||||
interface Props {
|
||||
materialien: Material[];
|
||||
onGeaendert: () => Promise<void>;
|
||||
onFehler: (text: string) => void;
|
||||
}
|
||||
|
||||
export function MaterialSection({ materialien, onGeaendert, onFehler }: Props) {
|
||||
const [formular, setFormular] = useState(LEERES_FORMULAR);
|
||||
const [bearbeiteteId, setBearbeiteteId] = useState<number | null>(null);
|
||||
const [wirdGespeichert, setWirdGespeichert] = useState(false);
|
||||
|
||||
function bearbeiten(material: Material) {
|
||||
setBearbeiteteId(material.id);
|
||||
setFormular({
|
||||
name: material.name,
|
||||
artikelnummer: material.artikelnummer ?? "",
|
||||
einheit: material.einheit,
|
||||
materialtyp: material.materialtyp,
|
||||
});
|
||||
}
|
||||
|
||||
function zuruecksetzen() {
|
||||
setBearbeiteteId(null);
|
||||
setFormular(LEERES_FORMULAR);
|
||||
}
|
||||
|
||||
async function speichern() {
|
||||
setWirdGespeichert(true);
|
||||
const body = {
|
||||
name: formular.name,
|
||||
artikelnummer: formular.artikelnummer || null,
|
||||
einheit: formular.einheit,
|
||||
materialtyp: formular.materialtyp,
|
||||
};
|
||||
try {
|
||||
if (bearbeiteteId !== null) {
|
||||
await apiRequest(`/materialien/${bearbeiteteId}`, { method: "PATCH", body });
|
||||
} else {
|
||||
await apiRequest("/materialien", { method: "POST", body });
|
||||
}
|
||||
zuruecksetzen();
|
||||
await onGeaendert();
|
||||
} catch {
|
||||
onFehler("Material konnte nicht gespeichert werden.");
|
||||
} finally {
|
||||
setWirdGespeichert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<h2>Material {bearbeiteteId !== null ? "bearbeiten" : "anlegen"}</h2>
|
||||
<div className="card">
|
||||
<div className="stack" style={{ marginBottom: "1.25rem" }}>
|
||||
<div className="field">
|
||||
<label>Name</label>
|
||||
<input
|
||||
className="input"
|
||||
value={formular.name}
|
||||
onChange={(e) => setFormular({ ...formular, name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="field" style={{ flex: 1 }}>
|
||||
<label>Artikelnummer</label>
|
||||
<input
|
||||
className="input"
|
||||
value={formular.artikelnummer}
|
||||
onChange={(e) => setFormular({ ...formular, artikelnummer: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field" style={{ width: "7rem" }}>
|
||||
<label>Einheit</label>
|
||||
<input
|
||||
className="input"
|
||||
value={formular.einheit}
|
||||
onChange={(e) => setFormular({ ...formular, einheit: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Materialtyp</label>
|
||||
<select
|
||||
className="input"
|
||||
value={formular.materialtyp}
|
||||
onChange={(e) => setFormular({ ...formular, materialtyp: e.target.value })}
|
||||
>
|
||||
{MATERIALTYPEN.map((t) => (
|
||||
<option key={t.value} value={t.value}>
|
||||
{t.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="row">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={speichern}
|
||||
disabled={!formular.name || !formular.einheit || wirdGespeichert}
|
||||
>
|
||||
{bearbeiteteId !== null ? "Speichern" : "Anlegen"}
|
||||
</button>
|
||||
{bearbeiteteId !== null && (
|
||||
<button className="btn btn-secondary" onClick={zuruecksetzen}>
|
||||
Abbrechen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{materialien.length > 0 && (
|
||||
<ul className="card-list">
|
||||
{materialien.map((m) => (
|
||||
<li key={m.id} className="row-between" style={{ padding: "0.4rem 0" }}>
|
||||
<span>
|
||||
{m.name} <span className="text-muted">({m.einheit})</span>
|
||||
</span>
|
||||
<button className="btn btn-secondary" onClick={() => bearbeiten(m)}>
|
||||
Bearbeiten
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user