- {/* Inventarnummer */}
+ {/* Inventarnummer (oben links) */}
@@ -216,6 +306,8 @@ export default function DeviceDetailModal({
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [activeTab, setActiveTab] = useState<'details' | 'history'>('details');
+ const [loanModalOpen, setLoanModalOpen] = useState(false);
+ const [historyRefresh, setHistoryRefresh] = useState(0);
useEffect(() => {
if (!open || !inventoryNumber) return;
@@ -239,7 +331,9 @@ export default function DeviceDetailModal({
if (res.status === 404) {
throw new Error('Gerät wurde nicht gefunden.');
}
- throw new Error('Beim Laden der Gerätedaten ist ein Fehler aufgetreten.');
+ throw new Error(
+ 'Beim Laden der Gerätedaten ist ein Fehler aufgetreten.',
+ );
}
const data = (await res.json()) as DeviceDetail;
@@ -266,100 +360,138 @@ export default function DeviceDetailModal({
const handleClose = () => onClose();
+ const handleStartLoan = () => {
+ if (!device) return;
+ setLoanModalOpen(true);
+ };
+
return (
- }
- tone="info"
- variant="centered"
- size="xl"
- primaryAction={{
- label: 'Schließen',
- onClick: handleClose,
- variant: 'primary',
- }}
- headerExtras={
- device && (
-
- setActiveTab(id as 'details' | 'history')}
- ariaLabel="Ansicht wählen"
- />
-
- )
- }
- sidebar={
- device ? (
-
- {/* QR-Code oben, nicht scrollend */}
-
-
-
-
+ <>
+
}
+ tone="info"
+ variant="centered"
+ size="xl"
+ primaryAction={{
+ label: 'Schließen',
+ onClick: handleClose,
+ variant: 'primary',
+ }}
+ headerExtras={
+ device && (
+
+ setActiveTab(id as 'details' | 'history')}
+ ariaLabel="Ansicht wählen"
+ />
+
+ )
+ }
+ sidebar={
+ device ? (
+
+ {/* QR-Code oben, nicht scrollend */}
+
+
+
+ {device.inventoryNumber}
+
-
- #{device.inventoryNumber}
-
+
+
+
+ {/* Änderungsverlauf: nimmt den Rest der Höhe ein und scrollt intern */}
+
+
+
+
+ ) : undefined
+ }
+ >
+ {loading && (
+
+ Gerätedaten werden geladen …
+
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {!loading && !error && device && (
+ <>
+ {/* Mobile-Inhalt (Tabs steuern Ansicht) */}
+
+ {activeTab === 'details' ? (
+
+ ) : (
+
+ )}
-
-
- {/* Änderungsverlauf: nimmt den Rest der Höhe ein und scrollt intern */}
-
-
+
-
- ) : undefined
- }
- >
- {loading && (
-
- Gerätedaten werden geladen …
-
- )}
+ >
+ )}
+
- {error && (
-
- {error}
-
+ {device && (
+
setLoanModalOpen(false)}
+ device={device}
+ onUpdated={(patch) => {
+ // lokalen State aktualisieren, damit Details sofort aktualisiert sind
+ setDevice((prev) =>
+ prev
+ ? {
+ ...prev,
+ loanedTo: patch.loanedTo,
+ loanedFrom: patch.loanedFrom,
+ loanedUntil: patch.loanedUntil,
+ loanComment: patch.loanComment,
+ }
+ : prev,
+ );
+ setHistoryRefresh((prev) => prev + 1);
+ }}
+ />
)}
-
- {!loading && !error && device && (
- <>
- {/* Mobile-Inhalt (Tabs steuern Ansicht) */}
-
- {activeTab === 'details' ? (
-
- ) : (
-
- )}
-
-
- {/* Desktop-Inhalt links: nur Details, Verlauf rechts in sidebar */}
-
-
-
- >
- )}
-
+ >
);
-}
\ No newline at end of file
+}
diff --git a/app/(app)/devices/DeviceEditModal.tsx b/app/(app)/devices/DeviceEditModal.tsx
index 12d764f..2fcd724 100644
--- a/app/(app)/devices/DeviceEditModal.tsx
+++ b/app/(app)/devices/DeviceEditModal.tsx
@@ -37,6 +37,7 @@ export default function DeviceEditModal({
const [editError, setEditError] = useState(null);
const [saveLoading, setSaveLoading] = useState(false);
const [justSaved, setJustSaved] = useState(false);
+ const [historyRefresh, setHistoryRefresh] = useState(0);
useEffect(() => {
if (!open || !inventoryNumber) return;
@@ -96,6 +97,16 @@ export default function DeviceEditModal({
};
}, [open, inventoryNumber]);
+ useEffect(() => {
+ if (!justSaved) return;
+
+ const id = setTimeout(() => {
+ setJustSaved(false);
+ }, 1500); // Dauer nach Geschmack anpassen
+
+ return () => clearTimeout(id);
+ }, [justSaved]);
+
const handleFieldChange = (
field: keyof DeviceDetail,
e: ChangeEvent,
@@ -148,10 +159,8 @@ export default function DeviceEditModal({
setEditDevice(updated);
onSaved(updated);
+ // Nur Status setzen – NICHT schließen
setJustSaved(true);
- setTimeout(() => {
- onClose();
- }, 1000);
} catch (err: any) {
console.error('Error saving device', err);
setEditError(
@@ -162,7 +171,7 @@ export default function DeviceEditModal({
} finally {
setSaveLoading(false);
}
- }, [editDevice, onSaved, onClose]);
+ }, [editDevice, onSaved]);
const handleClose = () => {
if (saveLoading) return;
@@ -229,6 +238,7 @@ export default function DeviceEditModal({
key={editDevice.updatedAt}
inventoryNumber={editDevice.inventoryNumber}
asSidebar
+ refreshToken={historyRefresh}
/>
) : undefined
}
@@ -348,34 +358,6 @@ export default function DeviceEditModal({
/>
- {/* Tags */}
-
- ({ name }))}
- onChange={(next) => {
- const names = next.map((t) => t.name);
-
- setEditDevice((prev) =>
- prev ? ({ ...prev, tags: names } as DeviceDetail) : prev,
- );
-
- setAllTags((prev) => {
- const map = new Map(prev.map((t) => [t.name.toLowerCase(), t]));
- for (const t of next) {
- const key = t.name.toLowerCase();
- if (!map.has(key)) {
- map.set(key, t);
- }
- }
- return Array.from(map.values());
- });
- }}
- placeholder="z.B. Drucker, Serverraum, kritisch"
- />
-
-
{/* Netzwerkdaten */}
@@ -438,6 +420,34 @@ export default function DeviceEditModal({
/>
+ {/* Tags */}
+
+ ({ name }))}
+ onChange={(next) => {
+ const names = next.map((t) => t.name);
+
+ setEditDevice((prev) =>
+ prev ? ({ ...prev, tags: names } as DeviceDetail) : prev,
+ );
+
+ setAllTags((prev) => {
+ const map = new Map(prev.map((t) => [t.name.toLowerCase(), t]));
+ for (const t of next) {
+ const key = t.name.toLowerCase();
+ if (!map.has(key)) {
+ map.set(key, t);
+ }
+ }
+ return Array.from(map.values());
+ });
+ }}
+ placeholder="z.B. Drucker, Serverraum, kritisch"
+ />
+
+
{/* Kommentar */}
diff --git a/app/(app)/devices/DeviceHistorySidebar.tsx b/app/(app)/devices/DeviceHistorySidebar.tsx
index e0620bb..7583444 100644
--- a/app/(app)/devices/DeviceHistorySidebar.tsx
+++ b/app/(app)/devices/DeviceHistorySidebar.tsx
@@ -8,11 +8,12 @@ import Feed, {
} from '@/components/ui/Feed';
import clsx from 'clsx';
-
type Props = {
inventoryNumber: string;
/** Wenn true: wird als Inhalt für Modal.sidebar gerendert (ohne eigenes