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:
@@ -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}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
{/* 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>
|
||||||
|
<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
|
||||||
|
|||||||
@@ -172,43 +172,43 @@ 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>
|
||||||
|
{showBesucher && (
|
||||||
|
<div className="shrink-0">
|
||||||
|
<label className={labelCls}>Besucher</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={besucher}
|
||||||
|
onChange={(e) => setBesucher(parseInt(e.target.value) || 0)}
|
||||||
|
min={0}
|
||||||
|
max={9999}
|
||||||
|
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>
|
||||||
|
|
||||||
{/* Besucher */}
|
|
||||||
{showBesucher && <div>
|
|
||||||
<label className={labelCls}>Anzahl Besucher</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={besucher}
|
|
||||||
onChange={(e) => setBesucher(parseInt(e.target.value) || 0)}
|
|
||||||
min={0}
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
{/* BEOs */}
|
{/* BEOs */}
|
||||||
<div>
|
<div>
|
||||||
<label className={labelCls}>BEOs</label>
|
<label className={labelCls}>BEOs</label>
|
||||||
|
|||||||
@@ -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>}
|
||||||
<td className="px-3 py-2 border border-gray-200 max-w-xs">
|
{!compact && (
|
||||||
<span className="line-clamp-2">{e.Bemerkungen || ''}</span>
|
<td className="px-3 py-2 border border-gray-200 max-w-xs">
|
||||||
</td>
|
<span className="line-clamp-2">{e.Bemerkungen || ''}</span>
|
||||||
|
</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)}
|
||||||
|
|||||||
Reference in New Issue
Block a user