2025-11-14 17:03:26 +01:00

252 lines
6.7 KiB
TypeScript

// app/(app)/devices/page.tsx
'use client';
import Button from '@/components/ui/Button';
import Table, { TableColumn } from '@/components/ui/Table';
import { Dropdown } from '@/components/ui/Dropdown';
import {
BookOpenIcon,
PencilIcon,
TrashIcon,
} from '@heroicons/react/24/outline';
type DeviceRow = {
id: string;
// Fachliche Felder (entsprechend deinem Prisma-Model)
name: string;
manufacturer: string;
model: string;
inventoryNumber: string;
serialNumber?: string | null;
productNumber?: string | null;
comment?: string | null;
// optionale Netzwerk-/Zugangs-Felder
ipv4Address?: string | null;
ipv6Address?: string | null;
macAddress?: string | null;
username?: string | null;
// Beziehungen (als einfache Strings für die Tabelle)
group?: string | null;
location?: string | null;
// Audit
updatedAt: string;
};
// TODO: später per Prisma laden
const mockDevices: DeviceRow[] = [
{
id: '1',
name: 'Dienstrechner Sachbearbeitung 1',
manufacturer: 'Dell',
model: 'OptiPlex 7010',
inventoryNumber: 'INV-00123',
serialNumber: 'SN-ABC-123',
productNumber: 'PN-4711',
group: 'Dienstrechner',
location: 'Raum 1.12',
comment: 'Steht am Fensterplatz',
ipv4Address: '10.0.0.12',
ipv6Address: null,
macAddress: '00-11-22-33-44-55',
username: 'sachb1',
updatedAt: '2025-01-10T09:15:00Z',
},
{
id: '2',
name: 'Monitor Lager 27"',
manufacturer: 'Samsung',
model: 'S27F350',
inventoryNumber: 'INV-00124',
serialNumber: 'SN-DEF-456',
productNumber: 'PN-0815',
group: 'Monitore',
location: 'Lager Keller',
comment: null,
ipv4Address: null,
ipv6Address: null,
macAddress: null,
username: null,
updatedAt: '2025-01-08T14:30:00Z',
},
];
function formatDate(iso: string) {
return new Intl.DateTimeFormat('de-DE', {
dateStyle: 'short',
timeStyle: 'short',
}).format(new Date(iso));
}
const columns: TableColumn<DeviceRow>[] = [
{
key: 'name',
header: 'Bezeichnung',
sortable: true,
canHide: true,
headerClassName: 'min-w-48',
cellClassName: 'font-medium text-gray-900 dark:text-white',
},
{
key: 'inventoryNumber',
header: 'Inventar-Nr.',
sortable: true,
canHide: false,
headerClassName: 'min-w-32',
},
{
key: 'manufacturer',
header: 'Hersteller',
sortable: true,
canHide: false,
},
{
key: 'model',
header: 'Modell',
sortable: true,
canHide: false,
},
{
key: 'serialNumber',
header: 'Seriennummer',
sortable: true,
canHide: true,
},
{
key: 'productNumber',
header: 'Produktnummer',
sortable: true,
canHide: true,
},
{
key: 'group',
header: 'Gruppe',
sortable: true,
canHide: true,
},
{
key: 'location',
header: 'Standort / Raum',
sortable: true,
canHide: false,
},
{
key: 'comment',
header: 'Kommentar',
sortable: false,
canHide: true,
cellClassName: 'whitespace-normal max-w-xs',
},
{
key: 'updatedAt',
header: 'Geändert am',
sortable: true,
canHide: true,
render: (row) => formatDate(row.updatedAt),
},
];
export default function DevicesPage() {
const devices = mockDevices;
return (
<>
{/* Header über der Tabelle */}
<div className="flex items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white">
Geräte
</h1>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
Übersicht aller erfassten Geräte im Inventar.
</p>
</div>
<button
type="button"
className="inline-flex items-center rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus-visible:outline-indigo-500"
>
Neues Gerät anlegen
</button>
</div>
{/* Tabelle */}
<div className="mt-8">
<Table<DeviceRow>
data={devices}
columns={columns}
getRowId={(row) => row.id}
selectable
actionsHeader=""
renderActions={(row) => (
<div className="flex justify-end">
{/* Desktop: drei Icon-Buttons nebeneinander */}
<div className="hidden gap-2 lg:flex">
<Button
variant="soft"
tone="indigo"
size="md"
icon={<BookOpenIcon className="size-5" />}
aria-label={`Details zu ${row.inventoryNumber}`}
onClick={() => console.log('Details', row.id)}
/>
<Button
variant="soft"
tone="gray"
size="md"
icon={<PencilIcon className="size-5" />}
aria-label={`Gerät ${row.inventoryNumber} bearbeiten`}
onClick={() => console.log('Bearbeiten', row.id)}
/>
<Button
variant="soft"
tone="rose"
size="md"
icon={<TrashIcon className="size-5" />}
aria-label={`Gerät ${row.inventoryNumber} löschen`}
onClick={() => console.log('Löschen', row.id)}
/>
</div>
{/* Mobile / kleine Screens: kompaktes Dropdown mit Ellipsis-Trigger */}
<div className="lg:hidden">
<Dropdown
triggerVariant="icon"
ariaLabel={`Aktionen für ${row.inventoryNumber}`}
sections={[
{
items: [
{
label: 'Details',
icon: <BookOpenIcon className="size-4" />,
onClick: () => console.log('Details', row.id),
},
{
label: 'Bearbeiten',
icon: <PencilIcon className="size-4" />,
onClick: () => console.log('Bearbeiten', row.id),
},
{
label: 'Löschen',
icon: <TrashIcon className="size-4" />,
tone: 'danger',
onClick: () => console.log('Löschen', row.id),
},
],
},
]}
/>
</div>
</div>
)}
/>
</div>
</>
);
}