- Eingabe: alle 5 Felder (Führung, Datum, Start, Ende, Besucher) in einer Zeile - Eingabe: Datum einmalig, Start- und Endzeit getrennt - Führungsarten: Kürzel werden nur in DB gespeichert, Anzeige als Klartext - Liste: Datum und Zeit getrennt in eigenen Spalten - Hintergrundfarbe #EEF4FF auf Login- und Passwort-Seite übertragen - Alle Inputfelder gleich hoch (text-sm durchgehend) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
5.5 KiB
TypeScript
143 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import type { Kuppel, LogbuchEintrag } from '@/types/logbuch';
|
|
|
|
interface Props {
|
|
kuppel: Kuppel;
|
|
refreshKey: number;
|
|
onEdit: (entry: LogbuchEintrag) => void;
|
|
limit?: number;
|
|
compact?: boolean;
|
|
}
|
|
|
|
const pad = (n: number) => String(n).padStart(2, '0');
|
|
|
|
function formatDate(dt: string, short = false): string {
|
|
if (!dt) return '';
|
|
const d = new Date(dt);
|
|
if (isNaN(d.getTime())) return dt;
|
|
if (short) return `${pad(d.getDate())}.${pad(d.getMonth() + 1)}.`;
|
|
return `${pad(d.getDate())}.${pad(d.getMonth() + 1)}.${d.getFullYear()}`;
|
|
}
|
|
|
|
function formatTime(dt: string): string {
|
|
if (!dt) return '';
|
|
const d = new Date(dt);
|
|
if (isNaN(d.getTime())) return dt;
|
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
}
|
|
|
|
export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 20, compact = false }: Props) {
|
|
const [entries, setEntries] = useState<LogbuchEintrag[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [deleteId, setDeleteId] = useState<number | null>(null);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
fetch(`/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=${limit}`)
|
|
.then((r) => { if (!r.ok) throw new Error(); return r.json(); })
|
|
.then((data) => { setEntries(data); setLoading(false); })
|
|
.catch(() => { setError('Fehler beim Laden.'); setLoading(false); });
|
|
}, [kuppel, refreshKey, limit]);
|
|
|
|
async function confirmDelete(id: number) {
|
|
try {
|
|
const res = await fetch(`/api/logbuch/${id}`, { method: 'DELETE' });
|
|
if (!res.ok) throw new Error();
|
|
setEntries((prev) => prev.filter((e) => e.ID !== id));
|
|
} catch {
|
|
setError('Fehler beim Löschen.');
|
|
} finally {
|
|
setDeleteId(null);
|
|
}
|
|
}
|
|
|
|
if (loading) return <div className="text-gray-500 text-sm py-4">Lade Einträge...</div>;
|
|
if (error) return <div className="text-red-600 text-sm py-4">{error}</div>;
|
|
if (entries.length === 0) return <div className="text-gray-500 text-sm py-4">Keine Einträge vorhanden.</div>;
|
|
|
|
const cell = compact
|
|
? 'px-1.5 py-1 border border-gray-200 text-xs'
|
|
: 'px-3 py-2 border border-gray-200';
|
|
const head = compact
|
|
? 'px-1.5 py-1 border border-gray-300 text-xs font-semibold'
|
|
: 'px-3 py-2 border border-gray-300';
|
|
|
|
return (
|
|
<div>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full border-collapse" style={{ fontSize: compact ? '0.75rem' : '0.875rem' }}>
|
|
<thead>
|
|
<tr className="bg-gray-100 text-left">
|
|
<th className={`${head} whitespace-nowrap`}>Datum</th>
|
|
<th className={`${head} whitespace-nowrap`}>Start</th>
|
|
<th className={`${head} whitespace-nowrap`}>Ende</th>
|
|
<th className={head}>Art</th>
|
|
<th className={`${head} text-center w-10`}>Bes.</th>
|
|
<th className={head}>BEOs</th>
|
|
<th className={head}>Objekte</th>
|
|
{!compact && <th className={head}>Bemerkungen</th>}
|
|
<th className={`${head} text-center print:hidden`}>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{entries.map((e) => (
|
|
<tr key={e.ID} className="hover:bg-gray-50">
|
|
<td className={`${cell} whitespace-nowrap`}>{formatDate(e.Beginn, compact)}</td>
|
|
<td className={`${cell} whitespace-nowrap`}>{formatTime(e.Beginn)}</td>
|
|
<td className={`${cell} whitespace-nowrap`}>{formatTime(e.Ende)}</td>
|
|
<td className={cell}>{e.ArtFuehrung}</td>
|
|
<td className={`${cell} text-center`}>{e.Besucher || ''}</td>
|
|
<td className={cell}>{e.BEOs || '—'}</td>
|
|
<td className={cell}>{e.Objekte || '—'}</td>
|
|
{!compact && (
|
|
<td className={cell}>{e.Bemerkungen || ''}</td>
|
|
)}
|
|
<td className={`${cell} text-center whitespace-nowrap print:hidden`}>
|
|
<button
|
|
onClick={() => onEdit(e)}
|
|
className="text-blue-600 hover:text-blue-800 mr-2 font-medium"
|
|
>
|
|
✎
|
|
</button>
|
|
<button
|
|
onClick={() => setDeleteId(e.ID)}
|
|
className="text-red-600 hover:text-red-800 font-medium"
|
|
>
|
|
✕
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{deleteId !== null && (
|
|
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
|
|
<div className="bg-white rounded-xl shadow-xl p-6 max-w-sm w-full mx-4">
|
|
<h3 className="text-lg font-semibold mb-3">Eintrag löschen?</h3>
|
|
<p className="text-sm text-gray-600 mb-5">Dieser Eintrag wird unwiderruflich gelöscht.</p>
|
|
<div className="flex gap-3 justify-end">
|
|
<button
|
|
onClick={() => setDeleteId(null)}
|
|
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-lg text-sm"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
onClick={() => confirmDelete(deleteId)}
|
|
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm"
|
|
>
|
|
Löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|