// frontend\src\pages\devices\EditDeviceModal.tsx import { useState, type ComponentProps, type FormEvent } from 'react' import { DialogTitle } from '@headlessui/react' import { XMarkIcon } from '@heroicons/react/24/outline' import { ChatBubbleLeftEllipsisIcon, CheckIcon, IdentificationIcon, MapPinIcon, PencilSquareIcon, PlusCircleIcon, VideoCameraIcon, WrenchScrewdriverIcon, } from '@heroicons/react/20/solid' import Modal from '../../components/Modal' import Journal from '../../components/Journal' import Button from '../../components/Button' import LoadingSpinner from '../../components/LoadingSpinner' import type { Device, DeviceHistoryChange, DeviceHistoryEntry, } from '../../components/types' import DeviceFormFields, { SectionHeader, sectionClassName, } from './DeviceFormFields' import DeviceModalTabs, { type DeviceModalTabId } from './DeviceModalTabs' type EditDeviceModalProps = { open: boolean setOpen: (open: boolean) => void isSaving: boolean error: string | null device: Device | null devices: Device[] relatedDeviceIds: string[] onToggleRelatedDevice: (deviceId: string) => void onSubmit: (event: FormEvent) => void deviceHistory: DeviceHistoryEntry[] isHistoryLoading: boolean historyError: string | null isJournalSubmitting?: boolean onJournalEntrySubmit?: (content: string) => void | Promise onJournalEntryUpdate?: (itemId: string | number, content: string) => void | Promise } type JournalItems = ComponentProps['items'] const editDeviceTabs = [ { id: 'masterData' as const, name: 'Stammdaten', icon: IdentificationIcon, }, { id: 'location' as const, name: 'Standort', icon: MapPinIcon, }, { id: 'accessories' as const, name: 'Zubehör', icon: WrenchScrewdriverIcon, }, { id: 'history' as const, name: 'Journal', icon: ChatBubbleLeftEllipsisIcon, }, ] function formatHistoryDate(value: string) { try { return new Intl.DateTimeFormat('de-DE', { dateStyle: 'short', timeStyle: 'short', }).format(new Date(value)) } catch { return value } } function formatHistoryChange(change: DeviceHistoryChange) { const oldValue = change.oldValue || '—' const newValue = change.newValue || '—' return `${change.label}: ${oldValue} → ${newValue}` } function getTabForInvalidField(fieldName: string): DeviceModalTabId { if ( [ 'inventoryNumber', 'manufacturer', 'model', 'serialNumber', 'macAddress', 'ipAddress', 'firmwareVersion', 'loanStatus', ].includes(fieldName) ) { return 'masterData' } if (['location', 'comment', 'isBaoDevice'].includes(fieldName)) { return 'location' } return 'accessories' } function classNames(...classes: Array) { return classes.filter(Boolean).join(' ') } export default function EditDeviceModal({ open, setOpen, isSaving, error, device, devices, relatedDeviceIds, onToggleRelatedDevice, onSubmit, deviceHistory, isHistoryLoading, historyError, isJournalSubmitting = false, onJournalEntrySubmit, onJournalEntryUpdate, }: EditDeviceModalProps) { const [activeTab, setActiveTab] = useState('masterData') function handleOpenChange(nextOpen: boolean) { if (!nextOpen && isSaving) { return } setOpen(nextOpen) if (!nextOpen) { setActiveTab('masterData') } } function handleInvalid(event: FormEvent) { const target = event.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement setActiveTab(getTabForInvalidField(target.name)) } function handleFormSubmit(event: FormEvent) { if (activeTab === 'history') { event.preventDefault() return } onSubmit(event) } const historyFeedItems: JournalItems = device ? deviceHistory.map((entry) => { const journalChange = entry.changes.find((change) => change.field === 'journal') const isJournalEntry = entry.action === 'journal' || journalChange !== undefined return { id: entry.id, type: isJournalEntry ? ('journal' as const) : entry.action === 'created' ? ('created' as const) : ('edited' as const), person: { name: entry.userDisplayName || 'Unbekannt', }, content: isJournalEntry ? journalChange?.newValue || 'hat einen Journal-Eintrag hinzugefügt.' : entry.action === 'created' ? 'hat das Gerät erstellt.' : entry.changes.length > 0 ? `hat ${entry.changes.map(formatHistoryChange).join(', ')} geändert.` : 'hat das Gerät bearbeitet.', date: formatHistoryDate(entry.createdAt), datetime: entry.createdAt, editedDate: isJournalEntry && entry.updatedAt && entry.updatedAt !== entry.createdAt ? formatHistoryDate(entry.updatedAt) : undefined, editedDatetime: isJournalEntry && entry.updatedAt && entry.updatedAt !== entry.createdAt ? entry.updatedAt : undefined, canEdit: isJournalEntry && entry.canEdit, icon: isJournalEntry ? ChatBubbleLeftEllipsisIcon : entry.action === 'created' ? PlusCircleIcon : PencilSquareIcon, iconBackground: isJournalEntry ? 'bg-indigo-500' : entry.action === 'created' ? 'bg-green-500' : 'bg-blue-500', } }) : [] return (
Gerät bearbeiten {device && ( {[ device.inventoryNumber, device.milestoneDisplayName, ] .filter(Boolean) .join(' · ')} )}

Bearbeite Gerätedaten, Standort, Verleih-Status und Zubehör.

{device?.milestoneHardwareId && (

)}
{error && (
{error}
)}
{activeTab === 'history' ? (
{isHistoryLoading && (
)} {historyError && (
{historyError}
)} {!isHistoryLoading && !historyError && ( )}
) : ( )}
{activeTab !== 'history' && ( )}
) }