diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 3be3029..3ec3082 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import { AuthProvider, useAuth } from "./auth/AuthContext";
+import { AppShell } from "./components/AppShell";
import { AdminPage } from "./pages/AdminPage";
import { KontrollPage } from "./pages/KontrollPage";
import { LoginPage } from "./pages/LoginPage";
@@ -12,7 +13,7 @@ function GeschuetzteRoute({ children }: { children: ReactNode }) {
if (!eingeloggt) {
return ;
}
- return <>{children}>;
+ return {children};
}
function AdminRoute({ children }: { children: ReactNode }) {
@@ -23,7 +24,7 @@ function AdminRoute({ children }: { children: ReactNode }) {
if (!istAdmin) {
return ;
}
- return <>{children}>;
+ return {children};
}
export default function App() {
diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx
new file mode 100644
index 0000000..75ce031
--- /dev/null
+++ b/frontend/src/components/AppShell.tsx
@@ -0,0 +1,39 @@
+import type { ReactNode } from "react";
+import { NavLink, useNavigate } from "react-router-dom";
+
+import { useAuth } from "../auth/AuthContext";
+
+/** Gemeinsamer Rahmen (Kopfzeile + Navigation) für alle eingeloggten Screens. */
+export function AppShell({ children }: { children: ReactNode }) {
+ const { istAdmin, logout } = useAuth();
+ const navigate = useNavigate();
+
+ function handleLogout() {
+ logout();
+ navigate("/login");
+ }
+
+ return (
+
+
+
+ MABEA
+
+
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 26886f0..dd41241 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
+import "./styles/global.css";
createRoot(document.getElementById("root")!).render(
diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx
index a6f0922..3c56b9f 100644
--- a/frontend/src/pages/AdminPage.tsx
+++ b/frontend/src/pages/AdminPage.tsx
@@ -15,12 +15,16 @@ export function AdminPage() {
const [vorlagen, setVorlagen] = useState([]);
const [objekte, setObjekte] = useState([]);
const [fehler, setFehler] = useState(null);
+ const [laedt, setLaedt] = useState(true);
const [standortName, setStandortName] = useState("");
+ const [standortWirdAngelegt, setStandortWirdAngelegt] = useState(false);
+
const [objektCode, setObjektCode] = useState("");
const [objektName, setObjektName] = useState("");
const [vorlageId, setVorlageId] = useState("");
const [standortId, setStandortId] = useState("");
+ const [objektWirdAngelegt, setObjektWirdAngelegt] = useState(false);
async function ladeAlles() {
const [s, v, o] = await Promise.all([
@@ -34,17 +38,22 @@ export function AdminPage() {
}
useEffect(() => {
- ladeAlles().catch(() => setFehler("Stammdaten konnten nicht geladen werden."));
+ ladeAlles()
+ .catch(() => setFehler("Stammdaten konnten nicht geladen werden."))
+ .finally(() => setLaedt(false));
}, []);
async function standortAnlegen() {
setFehler(null);
+ setStandortWirdAngelegt(true);
try {
await apiRequest("/standorte", { method: "POST", body: { name: standortName } });
setStandortName("");
await ladeAlles();
} catch {
setFehler("Standort konnte nicht angelegt werden.");
+ } finally {
+ setStandortWirdAngelegt(false);
}
}
@@ -54,6 +63,7 @@ export function AdminPage() {
return;
}
setFehler(null);
+ setObjektWirdAngelegt(true);
const vorlage = vorlagen.find((v) => v.id === vorlageId);
try {
await apiRequest("/objekte", {
@@ -71,65 +81,123 @@ export function AdminPage() {
await ladeAlles();
} catch {
setFehler("Objekt konnte nicht angelegt werden (Code evtl. schon vergeben).");
+ } finally {
+ setObjektWirdAngelegt(false);
}
}
+ if (laedt) {
+ return (
+
+ Lade…
+
+ );
+ }
+
return (
-
+
Administration
- {fehler && {fehler}
}
+ {fehler && {fehler}
}
-
+
-
-
- Objekt aus Vorlage anlegen
-
-
-
-
setObjektCode(e.target.value)} placeholder="Code (eindeutig)" />
-
setObjektName(e.target.value)} placeholder="Name" />
-
+
+
+ setStandortName(e.target.value)}
+ placeholder="Name des Standorts"
+ />
+
+
+ {standorte.length > 0 && (
+
+ {standorte.map((s) => (
+ -
+ {s.name}
+
+ ))}
+
+ )}
-
-
- {objekte.map((o) => (
- -
- {o.name} ({o.code})
-
- ))}
-
-
+
+
+ Objekt aus Vorlage anlegen
+
+ {vorlagen.length === 0 ? (
+
+ Keine aktive Vorlage vorhanden – erst Excel-Import oder Vorlage per API anlegen.
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+ setObjektCode(e.target.value)} />
+
+
+
+ setObjektName(e.target.value)} />
+
+
+
+ )}
+
+ {objekte.length > 0 && (
+
+ {objekte.map((o) => (
+ -
+ {o.name}
+ {o.code}
+
+ ))}
+
+ )}
+
+
+
);
}
diff --git a/frontend/src/pages/KontrollPage.tsx b/frontend/src/pages/KontrollPage.tsx
index 178df48..ebca54a 100644
--- a/frontend/src/pages/KontrollPage.tsx
+++ b/frontend/src/pages/KontrollPage.tsx
@@ -170,71 +170,74 @@ export function KontrollPage() {
if (sperre) {
return (
-
+
Objekt gesperrt
-
- Wird gerade kontrolliert von {sperre.benutzer ?? "unbekannt"}, seit {sperre.seit}.
-
-
+
+
+ Wird gerade kontrolliert von {sperre.benutzer ?? "unbekannt"}, seit {sperre.seit}.
+
+
+
);
}
if (startFehler) {
return (
-
- {startFehler}
-
+
+ {startFehler}
+
);
}
return (
-
+
{objekt?.name ?? "Kontrolle"}
{!online && (
-
- Kein Netz - Eingaben werden lokal gespeichert und automatisch übertragen, sobald wieder Verbindung besteht.
-
+
+ Kein Netz – Eingaben werden lokal gespeichert und automatisch übertragen, sobald wieder Verbindung besteht.
+
)}
-
+
{positionen.map((zustand) => (
- -
-
+
-
+
{zustand.material?.name ?? `Material ${zustand.position.material_id}`}
-
+
Soll: {zustand.position.sollmenge_effektiv} {zustand.material?.einheit ?? ""}
-
+
eingabeAendern(zustand.position.material_id, e.target.value)}
- style={{ width: "5rem", fontSize: "1.1rem", padding: "0.5rem" }}
+ style={{ width: "6rem" }}
/>
{zustand.status === "fehler" && (
-
-
{zustand.fehlerText}
-