diff --git a/backend/src/routes/admin.routes.ts b/backend/src/routes/admin.routes.ts index 9af057d..e255780 100644 --- a/backend/src/routes/admin.routes.ts +++ b/backend/src/routes/admin.routes.ts @@ -246,6 +246,7 @@ router.delete('/cleanup/reset-buchhaltung-transaktionen', authenticate, requireP router.delete('/cleanup/reset-buchhaltung-konten', authenticate, requirePermission('admin:write'), (req, res) => { req.params.resetTarget = 'reset-buchhaltung-konten'; return resetHandler(req, res); }); router.delete('/cleanup/reset-buchhaltung-bankkonten', authenticate, requirePermission('admin:write'), (req, res) => { req.params.resetTarget = 'reset-buchhaltung-bankkonten'; return resetHandler(req, res); }); router.delete('/cleanup/reset-checklist-history', authenticate, requirePermission('admin:write'), (req, res) => { req.params.resetTarget = 'reset-checklist-history'; return resetHandler(req, res); }); +router.delete('/cleanup/reset-persoenliche-ausruestung', authenticate, requirePermission('admin:write'), (req, res) => { req.params.resetTarget = 'reset-persoenliche-ausruestung'; return resetHandler(req, res); }); router.delete('/cleanup/issues-all', authenticate, requirePermission('admin:write'), (req, res) => { req.params.resetTarget = 'issues-all'; return resetHandler(req, res); }); router.delete( @@ -289,6 +290,7 @@ const RESET_TARGETS: Record Promise<{ count: numbe 'reset-buchhaltung-transaktionen': (c) => cleanupService.resetBuchhaltungTransaktionen(c), 'reset-buchhaltung-konten': (c) => cleanupService.resetBuchhaltungKonten(c), 'reset-buchhaltung-bankkonten': (c) => cleanupService.resetBuchhaltungBankkonten(c), + 'reset-persoenliche-ausruestung': (c) => cleanupService.resetPersoenlicheAusruestung(c), }; const resetHandler = async (req: Request, res: Response): Promise => { diff --git a/backend/src/routes/personalEquipment.routes.ts b/backend/src/routes/personalEquipment.routes.ts index 0272dac..fd62e4d 100644 --- a/backend/src/routes/personalEquipment.routes.ts +++ b/backend/src/routes/personalEquipment.routes.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import personalEquipmentController from '../controllers/personalEquipment.controller'; import { authenticate } from '../middleware/auth.middleware'; -import { requirePermission } from '../middleware/rbac.middleware'; +import { requirePermission, requireAnyPermission } from '../middleware/rbac.middleware'; import { permissionService } from '../services/permission.service'; const router = Router(); @@ -9,8 +9,8 @@ const router = Router(); // Own items — any authenticated user with view permission router.get('/my', authenticate, personalEquipmentController.getMy.bind(personalEquipmentController)); -// All items — requires view_all -router.get('/', authenticate, requirePermission('persoenliche_ausruestung:view_all'), personalEquipmentController.list.bind(personalEquipmentController)); +// All items — requires view_all or create +router.get('/', authenticate, requireAnyPermission('persoenliche_ausruestung:view_all', 'persoenliche_ausruestung:create'), personalEquipmentController.list.bind(personalEquipmentController)); // By user — own data or view_all router.get('/user/:userId', authenticate, async (req, res, next) => { diff --git a/backend/src/services/cleanup.service.ts b/backend/src/services/cleanup.service.ts index a5577bb..cd6ed15 100644 --- a/backend/src/services/cleanup.service.ts +++ b/backend/src/services/cleanup.service.ts @@ -238,6 +238,21 @@ class CleanupService { logger.info(`Cleanup: truncated buchhaltung_bankkonten (${count} rows) and reset sequence`); return { count, deleted: true }; } + + async resetPersoenlicheAusruestung(confirm: boolean): Promise { + if (!confirm) { + const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM persoenliche_ausruestung WHERE geloescht_am IS NULL'); + return { count: rows[0].count, deleted: false }; + } + const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM persoenliche_ausruestung'); + const count = rows[0].count; + // Clear FK references from request positions before truncating + await pool.query(`UPDATE ausruestung_anfrage_positionen SET zuweisung_persoenlich_id = NULL, zuweisung_typ = 'keine' WHERE zuweisung_persoenlich_id IS NOT NULL`); + // CASCADE removes persoenliche_ausruestung_eigenschaften rows automatically + await pool.query('TRUNCATE persoenliche_ausruestung CASCADE'); + logger.info(`Cleanup: truncated persoenliche_ausruestung (${count} rows)`); + return { count, deleted: true }; + } } export default new CleanupService(); diff --git a/frontend/src/components/admin/DataManagementTab.tsx b/frontend/src/components/admin/DataManagementTab.tsx index 9b6d5c6..379012b 100644 --- a/frontend/src/components/admin/DataManagementTab.tsx +++ b/frontend/src/components/admin/DataManagementTab.tsx @@ -45,6 +45,7 @@ const RESET_SECTIONS: ResetSection[] = [ { key: 'reset-buchhaltung-transaktionen', label: 'Buchhaltung: Transaktionen loeschen', description: 'Alle Buchungen und Transaktionen loeschen und Nummerierung zuruecksetzen. Konten und Haushaltsjahre bleiben erhalten.' }, { key: 'reset-buchhaltung-konten', label: 'Buchhaltung: Konten loeschen', description: 'Alle Konten und alle zugehoerigen Transaktionen loeschen und Nummerierung zuruecksetzen. Haushaltsjahre bleiben erhalten.' }, { key: 'reset-buchhaltung-bankkonten', label: 'Buchhaltung: Bankkonten loeschen', description: 'Alle Bankkonten loeschen und Nummerierung zuruecksetzen.' }, + { key: 'reset-persoenliche-ausruestung', label: 'Persoenliche Ausruestung zuruecksetzen', description: 'Alle persoenlichen Ausruestungszuweisungen loeschen. Zuordnungen in Anfragen werden zurueckgesetzt.' }, ]; interface SectionState { diff --git a/frontend/src/pages/Mitglieder.tsx b/frontend/src/pages/Mitglieder.tsx index c85b6b2..7eeca8d 100644 --- a/frontend/src/pages/Mitglieder.tsx +++ b/frontend/src/pages/Mitglieder.tsx @@ -344,6 +344,22 @@ function Mitglieder() { paginationEnabled={false} stickyHeader /> + setPage(newPage)} + rowsPerPage={pageSize} + rowsPerPageOptions={[25, 50, 100, { value: -1, label: 'Alle' }]} + onRowsPerPageChange={(e) => { + setPageSize(parseInt(e.target.value, 10)); + setPage(0); + }} + labelRowsPerPage="Einträge pro Seite:" + labelDisplayedRows={({ from, to, count }) => + `${from}–${to} von ${count !== -1 ? count : `mehr als ${to}`}` + } + /> diff --git a/frontend/src/pages/PersoenlicheAusruestung.tsx b/frontend/src/pages/PersoenlicheAusruestung.tsx index a9f8de8..2a67427 100644 --- a/frontend/src/pages/PersoenlicheAusruestung.tsx +++ b/frontend/src/pages/PersoenlicheAusruestung.tsx @@ -37,6 +37,7 @@ function PersoenlicheAusruestungPage() { const canViewAll = hasPermission('persoenliche_ausruestung:view_all'); const canCreate = hasPermission('persoenliche_ausruestung:create'); + const canSeeAll = canViewAll || canCreate; const canApprove = hasPermission('ausruestungsanfrage:approve'); const [activeTab, setActiveTab] = useState(0); @@ -47,7 +48,7 @@ function PersoenlicheAusruestungPage() { // Data queries const { data: items, isLoading } = useQuery({ queryKey: ['persoenliche-ausruestung', 'all'], - queryFn: () => canViewAll ? personalEquipmentApi.getAll() : personalEquipmentApi.getMy(), + queryFn: () => canSeeAll ? personalEquipmentApi.getAll() : personalEquipmentApi.getMy(), staleTime: 2 * 60 * 1000, }); @@ -55,7 +56,7 @@ function PersoenlicheAusruestungPage() { queryKey: ['members-list-compact'], queryFn: () => membersService.getMembers({ pageSize: 500 }), staleTime: 5 * 60 * 1000, - enabled: canViewAll, + enabled: canSeeAll, }); const { data: unassignedPositions, isLoading: unassignedLoading } = useQuery({ @@ -129,7 +130,7 @@ function PersoenlicheAusruestungPage() { {label} ))} - {canViewAll && ( + {canSeeAll && ( Bezeichnung Kategorie - {canViewAll && Benutzer} + {canSeeAll && Benutzer} Größe Zustand Anschaffung @@ -185,7 +186,7 @@ function PersoenlicheAusruestungPage() { {isLoading ? ( - + Lade Daten… @@ -193,7 +194,7 @@ function PersoenlicheAusruestungPage() { ) : filtered.length === 0 ? ( - + @@ -231,7 +232,7 @@ function PersoenlicheAusruestungPage() { {item.kategorie ?? '—'} - {canViewAll && ( + {canSeeAll && ( {item.user_display_name ?? item.benutzer_name ?? '—'} diff --git a/frontend/src/services/members.ts b/frontend/src/services/members.ts index 947eeed..ef921fd 100644 --- a/frontend/src/services/members.ts +++ b/frontend/src/services/members.ts @@ -40,7 +40,7 @@ function buildParams(filters?: MemberFilters): URLSearchParams { if (filters.search) params.append('search', filters.search); if (filters.page) params.append('page', String(filters.page)); - if (filters.pageSize) params.append('pageSize', String(filters.pageSize)); + if (filters.pageSize !== undefined) params.append('pageSize', String(filters.pageSize)); filters.status?.forEach((s) => params.append('status[]', s)); filters.dienstgrad?.forEach((d) => params.append('dienstgrad[]', d));