Files
logbuch/components/LogbuchList.tsx
Reinhard X. Fürst a0fb6d8089 Various UX improvements and bug fixes
- Fix mustChangePassword session flag for users with pw=NULL
- Add PrF (Private Führung) as new ArtFuehrung type
- Split datetime-local into separate date + TimePicker5 (5-min steps, auto-repeat)
- Responsive Beginn/Ende layout: stacked on mobile, inline on desktop
- Sort BEOs alphabetically by Kürzel in selector
- Title shows active kuppel; hide user display in header
- Selected BEOs show Kürzel only (name stays in dropdown)
- Session timeout reduced to 1 hour
- Add CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 18:02:47 +02:00

137 lines
5.3 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;
}
function formatDateTime(dt: string, short = false): string {
if (!dt) return '';
const d = new Date(dt);
if (isNaN(d.getTime())) return dt;
if (short) {
const pad = (n: number) => String(n).padStart(2, '0');
return `${pad(d.getDate())}.${pad(d.getMonth() + 1)}. ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
return d.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
}
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`}>Beginn</th>
<th className={`${head} whitespace-nowrap`}>Ende</th>
<th className={head}>Art</th>
<th className={`${head} text-center`}>Besucher</th>
<th className={head}>BEOs</th>
<th className={head}>Objekte</th>
{!compact && <th className={head}>Bemerkungen</th>}
<th className={`${head} text-center`}>Aktionen</th>
</tr>
</thead>
<tbody>
{entries.map((e) => (
<tr key={e.ID} className="hover:bg-gray-50">
<td className={`${cell} whitespace-nowrap`}>{formatDateTime(e.Beginn, compact)}</td>
<td className={`${cell} whitespace-nowrap`}>{formatDateTime(e.Ende, compact)}</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} max-w-xs`}>
<span className="line-clamp-2">{e.Bemerkungen || ''}</span>
</td>
)}
<td className={`${cell} text-center whitespace-nowrap`}>
<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>
);
}