/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState } from 'react'; import { LogEntry, Device, User } from '../types'; import { History, Search, Plus, Hammer, UserIcon, Server, Info, Save, ChevronRight } from 'lucide-react'; interface LogbookProps { logs: LogEntry[]; devices: Device[]; users: User[]; currentUser: User; onAddLog: (log: Omit) => void; } export default function Logbook({ logs, devices, users, currentUser, onAddLog }: LogbookProps) { const [searchTerm, setSearchTerm] = useState(''); const [typeFilter, setTypeFilter] = useState('all'); const [showSystem, setShowSystem] = useState(false); // Custom Maintenance Log state const [showAddLog, setShowAddLog] = useState(false); const [targetDeviceId, setTargetDeviceId] = useState(''); const [logMessage, setLogMessage] = useState(''); // Sorted list: latest logs first const sortedLogs = [...logs].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // Filter logs const filteredLogs = sortedLogs.filter(log => { if (!showSystem && log.type === 'system') return false; const matchesSearch = log.message.toLowerCase().includes(searchTerm.toLowerCase()); const matchesType = typeFilter === 'all' || log.type === typeFilter; return matchesSearch && matchesType; }); const handleSubmitLog = (e: React.FormEvent) => { e.preventDefault(); if (!logMessage.trim()) return; let finalMsg = logMessage; if (targetDeviceId) { const dev = devices.find(x => x.id === targetDeviceId); if (dev) { finalMsg = `[Maintenance on ${dev.hostname}] ${logMessage}`; } } onAddLog({ type: 'maintenance', message: `${currentUser.name} registered the following maintenance update: ${finalMsg}`, deviceId: targetDeviceId || undefined, userId: currentUser.id }); setLogMessage(''); setTargetDeviceId(''); setShowAddLog(false); }; const getLogTypeBadge = (type: string) => { switch (type) { case 'maintenance': return 'bg-amber-950 border border-amber-900/60 text-amber-400'; case 'booking': return 'bg-emerald-950 border border-emerald-900/60 text-emerald-400'; case 'status': return 'bg-cyan-950 border border-cyan-900/60 text-cyan-400'; case 'system': default: return 'bg-slate-900 border border-slate-800 text-slate-350'; } }; const getLogTypeLabel = (type: string) => { switch (type) { case 'maintenance': return 'Maintenance'; case 'booking': return 'Booking'; case 'status': return 'Status'; case 'system': default: return 'System'; } }; return (
{/* LEFT COLUMN: Chronological Log List */}

Audit Log & Maintenance Journal

Append-only history of who touched what. git blame, but for the lab.

{/* Search and Filters toolbar */}
setSearchTerm(e.target.value)} className="w-full bg-slate-950 text-slate-101 border border-slate-800 rounded-lg pl-9 pr-4 py-1.5 text-xs focus:outline-none" />
{['all', 'booking', 'maintenance', 'status'].map((type) => ( ))}
{/* Audit Log Sheet */}
{filteredLogs.length === 0 ? (

No audit records match the selected filtering rules.

) : ( filteredLogs.map((log) => { const dev = devices.find(d => d.id === log.deviceId); const user = users.find(u => u.id === log.userId); const timestampFormatted = new Date(log.timestamp).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'medium' }); return (
{getLogTypeLabel(log.type)} {new Date(log.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}

{log.message}

Calendar Time: {timestampFormatted} {user && ( Operator: {user.name} )} {dev && ( Node: {dev.hostname} )}
); }) )}
{/* RIGHT COLUMN: Interactive Maintenance Addition Form */}
{showAddLog ? (

Journal Maintenance Work