diff --git a/app/MainClient.tsx b/app/MainClient.tsx index fb3a8a8..e16a42a 100644 --- a/app/MainClient.tsx +++ b/app/MainClient.tsx @@ -139,6 +139,8 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { kuppel={activeKuppel} refreshKey={refreshKey} onEdit={handleEdit} + currentUserKuerzel={kuerzel} + isAdmin={role?.includes('admin') ?? false} limit={5} compact /> @@ -158,6 +160,8 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) { kuppel={activeKuppel} refreshKey={refreshKey} onEdit={handleEdit} + currentUserKuerzel={kuerzel} + isAdmin={role?.includes('admin') ?? false} limit={15} /> diff --git a/app/api/logbuch/[id]/route.ts b/app/api/logbuch/[id]/route.ts index 3dcb75b..a84805d 100644 --- a/app/api/logbuch/[id]/route.ts +++ b/app/api/logbuch/[id]/route.ts @@ -11,17 +11,16 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ const logbuchId = parseInt(id); try { - // Zugriffskontrolle: Nur Ersteller oder Admin dürfen ändern - const existingRows = await query('SELECT created_by FROM logbuch WHERE ID = ?', [logbuchId]) as { created_by: number }[]; + const existingRows = await query('SELECT ID FROM logbuch WHERE ID = ?', [logbuchId]) as { ID: number }[]; if (existingRows.length === 0) { return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 }); } const isAdmin = session.role?.includes('admin'); - const createdBy = existingRows[0].created_by; - const isCreator = createdBy === null || createdBy === session.beoId; + const beoRows = await query('SELECT COUNT(*) AS cnt FROM logbuch_beos WHERE LogbuchID = ? AND BeoID = ?', [logbuchId, session.beoId]) as { cnt: number }[]; + const isBeo = (beoRows[0]?.cnt ?? 0) > 0; - if (!isAdmin && !isCreator) { + if (!isAdmin && !isBeo) { return NextResponse.json({ error: 'Keine Berechtigung zum Ändern dieses Eintrags' }, { status: 403 }); } @@ -84,17 +83,16 @@ export async function DELETE(_request: NextRequest, { params }: { params: Promis const logbuchId = parseInt(id); try { - // Zugriffskontrolle: Nur Ersteller oder Admin dürfen löschen - const existingRows = await query('SELECT created_by FROM logbuch WHERE ID = ?', [logbuchId]) as { created_by: number }[]; + const existingRows = await query('SELECT ID FROM logbuch WHERE ID = ?', [logbuchId]) as { ID: number }[]; if (existingRows.length === 0) { return NextResponse.json({ error: 'Eintrag nicht gefunden' }, { status: 404 }); } const isAdmin = session.role?.includes('admin'); - const createdBy = existingRows[0].created_by; - const isCreator = createdBy === null || createdBy === session.beoId; + const beoRows = await query('SELECT COUNT(*) AS cnt FROM logbuch_beos WHERE LogbuchID = ? AND BeoID = ?', [logbuchId, session.beoId]) as { cnt: number }[]; + const isBeo = (beoRows[0]?.cnt ?? 0) > 0; - if (!isAdmin && !isCreator) { + if (!isAdmin && !isBeo) { return NextResponse.json({ error: 'Keine Berechtigung zum Löschen dieses Eintrags' }, { status: 403 }); } diff --git a/components/LogbuchList.tsx b/components/LogbuchList.tsx index 7a8776a..80f54ca 100644 --- a/components/LogbuchList.tsx +++ b/components/LogbuchList.tsx @@ -7,6 +7,8 @@ interface Props { kuppel: Kuppel; refreshKey: number; onEdit: (entry: LogbuchEintrag) => void; + currentUserKuerzel: string; + isAdmin?: boolean; limit?: number; compact?: boolean; } @@ -50,35 +52,51 @@ function formatTime(dt: string): string { return `${pad(d.getHours())}:${pad(d.getMinutes())}`; } -export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, compact = false }: Props) { +export default function LogbuchList({ kuppel, refreshKey, onEdit, currentUserKuerzel, isAdmin = false, limit = 10, compact = false }: Props) { const [entries, setEntries] = useState([]); const [total, setTotal] = useState(0); - const [page, setPage] = useState(0); const [month, setMonth] = useState(compact ? '' : currentMonth()); - const [loading, setLoading] = useState(true); const [deleteId, setDeleteId] = useState(null); - const [error, setError] = useState(''); + const [deleteErr, setDeleteErr] = useState(''); const [printEntries, setPrintEntries] = useState(null); const [search, setSearch] = useState(''); const [activeSearch, setActiveSearch] = useState(''); const printPending = useRef(false); + // Derived page: auto-resets to 0 when filter deps change — no separate setState-in-effect needed + const filterKey = `${kuppel}|${refreshKey}|${month}|${activeSearch}`; + const [pageState, setPageState] = useState({ page: 0, key: filterKey }); + const page = pageState.key === filterKey ? pageState.page : 0; + + // Fetch result tracked by paramsKey — loading/error are derived, not set synchronously in effect + const paramsKey = `${filterKey}|${limit}|${page}`; + const [fetchResult, setFetchResult] = useState<{ ok: boolean; forParams: string } | null>(null); + const fetchError = fetchResult?.forParams === paramsKey && !fetchResult.ok ? 'Fehler beim Laden.' : ''; + const loading = !fetchError && fetchResult?.forParams !== paramsKey; + const error = fetchError || deleteErr; + useEffect(() => { const t = setTimeout(() => setActiveSearch(search.trim()), 300); return () => clearTimeout(t); }, [search]); - useEffect(() => { setPage(0); }, [kuppel, refreshKey, month, activeSearch]); - useEffect(() => { - setLoading(true); + let cancelled = false; const offset = page * limit; const url = `/api/logbuch?kuppel=${encodeURIComponent(kuppel)}&limit=${limit}&offset=${offset}` + (activeSearch ? `&search=${encodeURIComponent(activeSearch)}` : (month ? `&month=${encodeURIComponent(month)}` : '')); fetch(url) .then((r) => { if (!r.ok) throw new Error(); return r.json(); }) - .then((data) => { setEntries(data.entries); setTotal(data.total); setLoading(false); }) - .catch(() => { setError('Fehler beim Laden.'); setLoading(false); }); + .then((data) => { + if (!cancelled) { + setEntries(data.entries); + setTotal(data.total); + setFetchResult({ ok: true, forParams: paramsKey }); + setDeleteErr(''); + } + }) + .catch(() => { if (!cancelled) setFetchResult({ ok: false, forParams: paramsKey }); }); + return () => { cancelled = true; }; }, [kuppel, refreshKey, limit, page, month, activeSearch]); useEffect(() => { @@ -109,7 +127,7 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co setEntries((prev) => prev.filter((e) => e.ID !== id)); setTotal((t) => t - 1); } catch { - setError('Fehler beim Löschen.'); + setDeleteErr('Fehler beim Löschen.'); } finally { setDeleteId(null); } @@ -270,8 +288,12 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co )} - - + {(isAdmin || e.BEOs?.split(', ').includes(currentUserKuerzel)) && ( + <> + + + + )} ))} @@ -282,13 +304,13 @@ export default function LogbuchList({ kuppel, refreshKey, onEdit, limit = 10, co {!compact && total > limit && (
Seite {page + 1} von {Math.ceil(total / limit)} diff --git a/package.json b/package.json index a286d40..10b6386 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logbuch", - "version": "1.7.5", + "version": "1.7.6", "private": true, "scripts": { "dev": "next dev",