import { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { databases, DATABASE_ID } from '@/lib/appwrite';
import { useAuth } from '@/context/AuthContext';
import { useAuditLog } from '@/hooks/useAuditLog';
import { useLagerstandorte } from '@/hooks/useLagerstandorte';
import { useColleagues } from '@/hooks/useColleagues';
import { getDaysOld, isOverdue } from '@/hooks/useAssets';
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { ArrowLeft, Pencil, Save, X } from 'lucide-react';
import { parseKommentarForDisplay } from '@/lib/kommentarAnhaenge';
import KommentarAnhaengeList from '@/components/KommentarAnhaengeList';
const STATUS_LABEL = { offen: 'Offen', in_bearbeitung: 'In Bearbeitung', entsorgt: 'Entsorgt' };
const PRIO_LABELS = { kritisch: 'Kritisch', hoch: 'Hoch', mittel: 'Mittel', niedrig: 'Niedrig' };
const PRIO_OPTIONS = ['kritisch', 'hoch', 'mittel', 'niedrig'];
const STATUS_OPTIONS = ['offen', 'in_bearbeitung', 'entsorgt'];
const BEARB_STATUS_LABELS = {
'': 'Nicht gesetzt',
portalpruefung: '\u{1F50D} Portalprüfung durchführen',
gutschreiben_entsorgen: '\u267B\uFE0F Direkt gutschreiben & entsorgen',
zurueck_hersteller: '\u{1F4E6} Zurück an Hersteller senden',
defekt_ankunft: '\u26A0\uFE0F Defekt bei Ankunft melden',
};
const BEARB_STATUS_OPTIONS = ['', 'portalpruefung', 'gutschreiben_entsorgen', 'zurueck_hersteller', 'defekt_ankunft'];
function formatTimestamp(ts) {
if (!ts) return '–';
const d = new Date(ts);
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
+ ' ' + d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
function StatusBadge({ status }) {
if (status === 'offen') return {STATUS_LABEL[status]};
if (status === 'in_bearbeitung') return {STATUS_LABEL[status]};
return {STATUS_LABEL[status]};
}
function PrioBadge({ prio }) {
if (prio === 'kritisch') return {PRIO_LABELS[prio]};
if (prio === 'hoch') return {PRIO_LABELS[prio]};
if (prio === 'mittel') return {PRIO_LABELS[prio]};
return {PRIO_LABELS[prio]};
}
function KommentarReadonly({ value }) {
const { subject, body, attachments } = parseKommentarForDisplay(value);
const empty = !subject && !(body && body.trim()) && attachments.length === 0;
if (empty) return
–
;
return (
{subject && (
{subject}
)}
{body && body.trim() ? (
{body}
) : null}
);
}
export default function AssetDetail() {
const { id } = useParams();
const navigate = useNavigate();
const { user, userMeta } = useAuth();
const { logs, loadingLogs, loadLogs, addLog } = useAuditLog();
const locationId = userMeta?.locationId || '';
const { activeLagerstandorte } = useLagerstandorte(locationId);
const { colleagues } = useColleagues(locationId);
const [asset, setAsset] = useState(null);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState({});
const loadAsset = useCallback(async () => {
try {
const doc = await databases.getDocument(DATABASE_ID, 'assets', id);
setAsset(doc);
setForm({
erlNummer: doc.erlNummer || '',
seriennummer: doc.seriennummer || '',
artikelNr: doc.artikelNr || '',
bezeichnung: doc.bezeichnung || '',
defekt: doc.defekt || '',
lagerstandortId: doc.lagerstandortId || '',
zustaendig: doc.zustaendig || '',
status: doc.status || 'offen',
prio: doc.prio || 'mittel',
bearbeitungsStatus: doc.bearbeitungsStatus || '',
kommentar: doc.kommentar || '',
});
} catch (err) {
console.error('Asset laden fehlgeschlagen:', err);
setAsset(null);
} finally {
setLoading(false);
}
}, [id]);
useEffect(() => {
loadAsset();
loadLogs(id);
}, [loadAsset, loadLogs, id]);
const userName = user?.name || user?.email || 'Unbekannt';
function buildChangeDetails(oldAsset, newForm) {
const fields = [
{ key: 'erlNummer', label: 'ERL-Nr.' },
{ key: 'seriennummer', label: 'Seriennummer' },
{ key: 'artikelNr', label: 'Artikelnr.' },
{ key: 'bezeichnung', label: 'Bezeichnung' },
{ key: 'defekt', label: 'Defekt' },
{ key: 'lagerstandortId', label: 'Lagerstandort' },
{ key: 'zustaendig', label: 'Zuständig' },
{ key: 'status', label: 'Status' },
{ key: 'prio', label: 'Priorität' },
{ key: 'bearbeitungsStatus', label: 'Bearbeitungsstatus' },
{ key: 'kommentar', label: 'Kommentar' },
];
const changes = [];
for (const f of fields) {
const oldVal = oldAsset[f.key] || '';
const newVal = newForm[f.key] || '';
if (oldVal !== newVal) {
if (f.key === 'status') {
changes.push(`${f.label}: ${STATUS_LABEL[oldVal] || oldVal} → ${STATUS_LABEL[newVal] || newVal}`);
} else if (f.key === 'bearbeitungsStatus') {
changes.push(`${f.label}: ${BEARB_STATUS_LABELS[oldVal] || oldVal || 'Nicht gesetzt'} → ${BEARB_STATUS_LABELS[newVal] || newVal || 'Nicht gesetzt'}`);
} else if (f.key === 'prio') {
changes.push(`${f.label}: ${PRIO_LABELS[oldVal] || oldVal} → ${PRIO_LABELS[newVal] || newVal}`);
} else {
changes.push(`${f.label}: "${oldVal}" → "${newVal}"`);
}
}
}
return changes.join('; ');
}
async function handleSave() {
if (!asset) return;
setSaving(true);
try {
const changeDetails = buildChangeDetails(asset, form);
if (!changeDetails) {
setEditing(false);
setSaving(false);
return;
}
const updated = await databases.updateDocument(DATABASE_ID, 'assets', id, {
...form,
lastEditedBy: userName,
});
setAsset(updated);
let logDetails = changeDetails;
if (asset.zustaendig !== form.zustaendig && form.zustaendig) {
const isSelf = form.zustaendig === userName;
const reassignInfo = isSelf
? `${userName} hat sich das Asset selbst zugewiesen`
: `${userName} hat das Asset ${form.zustaendig} zugewiesen`;
logDetails = reassignInfo + (changeDetails.replace(/Zuständig:[^;]*;?\s?/, '').trim()
? '; ' + changeDetails.replace(/Zuständig:[^;]*;?\s?/, '').trim()
: '');
}
await addLog({
assetId: id,
action: 'bearbeitet',
details: logDetails,
userId: user.$id,
userName,
});
setEditing(false);
} catch (err) {
console.error('Speichern fehlgeschlagen:', err);
alert('Speichern fehlgeschlagen: ' + (err.message || err));
} finally {
setSaving(false);
}
}
function resetForm() {
setEditing(false);
setForm({
erlNummer: asset.erlNummer || '',
seriennummer: asset.seriennummer || '',
artikelNr: asset.artikelNr || '',
bezeichnung: asset.bezeichnung || '',
defekt: asset.defekt || '',
lagerstandortId: asset.lagerstandortId || '',
zustaendig: asset.zustaendig || '',
status: asset.status || 'offen',
prio: asset.prio || 'mittel',
bearbeitungsStatus: asset.bearbeitungsStatus || '',
kommentar: asset.kommentar || '',
});
}
if (loading) {
return (
);
}
if (!asset) {
return (
Asset nicht gefunden
Das Asset mit der ID {id} existiert nicht.
);
}
const days = getDaysOld(asset.$createdAt);
const overdue = isOverdue(asset);
return (
{/* Back button */}
{/* Header area */}
Asset: {asset.erlNummer || '–'}
{overdue && (
Überfällig ({days} Tage)
)}
{/* Properties card */}
Eigenschaften
{!editing ? (
) : (
<>
>
)}
setForm(f => ({ ...f, erlNummer: v }))} />
setForm(f => ({ ...f, artikelNr: v }))} />
setForm(f => ({ ...f, bezeichnung: v }))} />
setForm(f => ({ ...f, seriennummer: v }))} mono />
setForm(f => ({ ...f, defekt: v }))} textarea className="sm:col-span-2" />
{/* Lagerstandort */}
{editing ? (
) : (
{activeLagerstandorte.find(l => l.$id === asset.lagerstandortId)?.name || '–'}
)}
{/* Zuständig */}
{editing ? (
) : (
{asset.zustaendig || '–'}
)}
{/* Status */}
{editing ? (
) : (
)}
{/* Priorität */}
{editing ? (
) : (
)}
{(form.status === 'in_bearbeitung' || asset.bearbeitungsStatus) && (
{editing ? (
) : (
{BEARB_STATUS_LABELS[asset.bearbeitungsStatus || ''] || '–'}
)}
)}
{editing ? (
Erstellt am: {formatTimestamp(asset.$createdAt)}
Erstellt von: {asset.createdBy || '–'}
Zuletzt bearbeitet von: {asset.lastEditedBy || '–'}
Alter: {days === 0 ? 'Heute' : days === 1 ? '1 Tag' : `${days} Tage`}
{/* Audit log card */}
Änderungsprotokoll
{loadingLogs &&
[System] Logs werden geladen…
}
{!loadingLogs && logs.length === 0 && (
[System] Keine Einträge vorhanden.
)}
{logs.map((log) => {
const ts = formatTimestamp(log.$createdAt);
const actionClass = log.action === 'erstellt' ? 'log-created'
: log.action === 'status_geaendert' ? 'log-status'
: 'log-edit';
return (
[{ts}]
{log.userName}
{log.action.toUpperCase()}
{log.details && {log.details}}
);
})}
);
}
function PropertyField({ label, value, editing, onChange, mono, textarea, className = '' }) {
return (
{editing ? (
textarea ? (
);
}