Compact preview list below form on desktop; Beginn/Ende/Besucher in one row

- Eingabe tab shows last 5 entries below form on lg+ screens (compact mode
  hides Objekte and Bemerkungen columns); Liste tab shows full 20 entries
- Beginn, Ende, Besucher fields moved into a single flex row
- LogbuchList accepts limit and compact props

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 07:55:46 +02:00
parent aea5cc08d6
commit 34a2c6b90d
3 changed files with 61 additions and 34 deletions

View File

@@ -29,12 +29,12 @@ export default function MainClient({ kuerzel, beoId, beoName }: Props) {
function handleSaved() { function handleSaved() {
setRefreshKey((k) => k + 1); setRefreshKey((k) => k + 1);
setEditEntry(null); setEditEntry(null);
if (editEntry) setActiveTab('liste');
} }
function handleEdit(entry: LogbuchEintrag) { function handleEdit(entry: LogbuchEintrag) {
setEditEntry(entry); setEditEntry(entry);
setActiveTab('eingabe'); setActiveTab('eingabe');
window.scrollTo({ top: 0, behavior: 'smooth' });
} }
async function handleLogout() { async function handleLogout() {
@@ -84,11 +84,11 @@ export default function MainClient({ kuerzel, beoId, beoName }: Props) {
</div> </div>
{/* Eingabe/Liste-Tabs */} {/* Eingabe/Liste-Tabs */}
<div className="flex gap-1 mb-5 border-b border-gray-200"> <div className="flex gap-1 mb-4 border-b border-gray-200">
{(['eingabe', 'liste'] as const).map((tab) => ( {(['eingabe', 'liste'] as const).map((tab) => (
<button <button
key={tab} key={tab}
onClick={() => { setActiveTab(tab); if (tab === 'eingabe' && editEntry) setEditEntry(null); }} onClick={() => { setActiveTab(tab); if (tab === 'eingabe') setEditEntry(null); }}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeTab === tab activeTab === tab
? 'border-[#85B7D7] text-gray-900' ? 'border-[#85B7D7] text-gray-900'
@@ -100,10 +100,11 @@ export default function MainClient({ kuerzel, beoId, beoName }: Props) {
))} ))}
</div> </div>
{/* Eingabe-Tab: Formular + kompakte Vorschau-Liste (nur Desktop) */}
{activeTab === 'eingabe' && ( {activeTab === 'eingabe' && (
<div> <>
{editEntry && ( {editEntry && (
<div className="mb-4 text-sm text-amber-700 bg-amber-50 border border-amber-300 rounded-lg px-3 py-2"> <div className="mb-3 text-sm text-amber-700 bg-amber-50 border border-amber-300 rounded-lg px-3 py-2">
Eintrag bearbeiten (ID {editEntry.ID}) Eintrag bearbeiten (ID {editEntry.ID})
</div> </div>
)} )}
@@ -114,18 +115,40 @@ export default function MainClient({ kuerzel, beoId, beoName }: Props) {
editEntry={editEntry} editEntry={editEntry}
onSaved={handleSaved} onSaved={handleSaved}
/> />
{/* Kompakte Liste — nur auf Desktop sichtbar */}
<div className="hidden lg:block mt-5 border-t-2 border-gray-300 pt-4">
<div className="flex justify-between items-center mb-2">
<h2 className="text-sm font-semibold text-gray-600">Letzte Einträge</h2>
<button
onClick={() => setActiveTab('liste')}
className="text-xs text-blue-600 hover:underline"
>
Alle anzeigen
</button>
</div> </div>
<LogbuchList
kuppel={activeKuppel}
refreshKey={refreshKey}
onEdit={handleEdit}
limit={5}
compact
/>
</div>
</>
)} )}
{/* Liste-Tab: vollständige Liste */}
{activeTab === 'liste' && ( {activeTab === 'liste' && (
<LogbuchList <LogbuchList
kuppel={activeKuppel} kuppel={activeKuppel}
refreshKey={refreshKey} refreshKey={refreshKey}
onEdit={handleEdit} onEdit={handleEdit}
limit={20}
/> />
)} )}
<footer className="mt-8 flex justify-between items-center text-xs sm:text-sm text-gray-600 px-1 sm:px-4"> <footer className="mt-6 flex justify-between items-center text-xs sm:text-sm text-gray-600 px-1 sm:px-4">
<div> <div>
<a href="mailto:rxf@gmx.de" className="text-blue-600 hover:underline"> <a href="mailto:rxf@gmx.de" className="text-blue-600 hover:underline">
rxf@gmx.de rxf@gmx.de

View File

@@ -172,42 +172,42 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
/> />
</div> </div>
{/* Beginn / Ende — nebeneinander auf Desktop, untereinander auf Mobile */} {/* Beginn / Ende / Besucher — eine Zeile */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="flex flex-wrap gap-3 items-end">
<div> <div className="flex-1 min-w-0">
<label className={labelCls}>Beginn</label> <label className={labelCls}>Beginn</label>
<input <input
type="datetime-local" type="datetime-local"
value={beginn} value={beginn}
onChange={(e) => setBeginn(e.target.value)} onChange={(e) => setBeginn(e.target.value)}
required required
className={inputCls} className="w-full px-2 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
/> />
</div> </div>
<div> <div className="flex-1 min-w-0">
<label className={labelCls}>Ende</label> <label className={labelCls}>Ende</label>
<input <input
type="datetime-local" type="datetime-local"
value={ende} value={ende}
onChange={(e) => setEnde(e.target.value)} onChange={(e) => setEnde(e.target.value)}
required required
className={inputCls} className="w-full px-2 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm focus:border-blue-500 focus:outline-none"
/> />
</div> </div>
</div> {showBesucher && (
<div className="shrink-0">
{/* Besucher */} <label className={labelCls}>Besucher</label>
{showBesucher && <div>
<label className={labelCls}>Anzahl Besucher</label>
<input <input
type="number" type="number"
value={besucher} value={besucher}
onChange={(e) => setBesucher(parseInt(e.target.value) || 0)} onChange={(e) => setBesucher(parseInt(e.target.value) || 0)}
min={0} min={0}
max={9999} max={9999}
className="w-32 px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none" className="w-20 px-2 py-2 border-2 border-gray-400 rounded-lg bg-white text-base focus:border-blue-500 focus:outline-none"
/> />
</div>} </div>
)}
</div>
{/* BEOs */} {/* BEOs */}
<div> <div>

View File

@@ -7,6 +7,8 @@ interface Props {
kuppel: Kuppel; kuppel: Kuppel;
refreshKey: number; refreshKey: number;
onEdit: (entry: LogbuchEintrag) => void; onEdit: (entry: LogbuchEintrag) => void;
limit?: number;
compact?: boolean;
} }
function formatDateTime(dt: string): string { function formatDateTime(dt: string): string {
@@ -16,7 +18,7 @@ function formatDateTime(dt: string): string {
return d.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); 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 }: Props) { export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 20, compact = false }: Props) {
const [entries, setEntries] = useState<LogbuchEintrag[]>([]); const [entries, setEntries] = useState<LogbuchEintrag[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [deleteId, setDeleteId] = useState<number | null>(null); const [deleteId, setDeleteId] = useState<number | null>(null);
@@ -24,7 +26,7 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit }: Props) {
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
fetch(`/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=20`) fetch(`/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=${limit}`)
.then((r) => { if (!r.ok) throw new Error(); return r.json(); }) .then((r) => { if (!r.ok) throw new Error(); return r.json(); })
.then((data) => { setEntries(data); setLoading(false); }) .then((data) => { setEntries(data); setLoading(false); })
.catch(() => { setError('Fehler beim Laden.'); setLoading(false); }); .catch(() => { setError('Fehler beim Laden.'); setLoading(false); });
@@ -57,8 +59,8 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit }: Props) {
<th className="px-3 py-2 border border-gray-300">Art</th> <th className="px-3 py-2 border border-gray-300">Art</th>
<th className="px-3 py-2 border border-gray-300 text-center">Besucher</th> <th className="px-3 py-2 border border-gray-300 text-center">Besucher</th>
<th className="px-3 py-2 border border-gray-300">BEOs</th> <th className="px-3 py-2 border border-gray-300">BEOs</th>
<th className="px-3 py-2 border border-gray-300">Objekte</th> {!compact && <th className="px-3 py-2 border border-gray-300">Objekte</th>}
<th className="px-3 py-2 border border-gray-300">Bemerkungen</th> {!compact && <th className="px-3 py-2 border border-gray-300">Bemerkungen</th>}
<th className="px-3 py-2 border border-gray-300 text-center">Aktionen</th> <th className="px-3 py-2 border border-gray-300 text-center">Aktionen</th>
</tr> </tr>
</thead> </thead>
@@ -70,10 +72,12 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit }: Props) {
<td className="px-3 py-2 border border-gray-200">{e.ArtFuehrung}</td> <td className="px-3 py-2 border border-gray-200">{e.ArtFuehrung}</td>
<td className="px-3 py-2 border border-gray-200 text-center">{e.Besucher}</td> <td className="px-3 py-2 border border-gray-200 text-center">{e.Besucher}</td>
<td className="px-3 py-2 border border-gray-200">{e.BEOs || '—'}</td> <td className="px-3 py-2 border border-gray-200">{e.BEOs || '—'}</td>
<td className="px-3 py-2 border border-gray-200">{e.Objekte || '—'}</td> {!compact && <td className="px-3 py-2 border border-gray-200">{e.Objekte || '—'}</td>}
{!compact && (
<td className="px-3 py-2 border border-gray-200 max-w-xs"> <td className="px-3 py-2 border border-gray-200 max-w-xs">
<span className="line-clamp-2">{e.Bemerkungen || ''}</span> <span className="line-clamp-2">{e.Bemerkungen || ''}</span>
</td> </td>
)}
<td className="px-3 py-2 border border-gray-200 text-center whitespace-nowrap"> <td className="px-3 py-2 border border-gray-200 text-center whitespace-nowrap">
<button <button
onClick={() => onEdit(e)} onClick={() => onEdit(e)}