feat: checklist multi-type assignments, tab layouts for Fahrzeuge/Ausruestung, admin cleanup

- Migration 074: convert checklist vorlage single FK fields to junction tables
  (vorlage_fahrzeug_typen, vorlage_fahrzeuge, vorlage_ausruestung_typen, vorlage_ausruestungen)
- Backend checklist service: multi-type create/update/query with array fields
- Backend cleanup service: add checklist-history and reset-checklist-history targets
- Frontend types/service: singular FK fields replaced with arrays (fahrzeug_typ_ids, etc.)
- Frontend Checklisten.tsx: multi-select Autocomplete pickers for all assignment types
- Fahrzeuge.tsx/Ausruestung.tsx: add tab layout (Uebersicht + Einstellungen), inline type CRUD
- FahrzeugEinstellungen/AusruestungEinstellungen: replaced with redirects to tab URLs
- Sidebar: add Uebersicht sub-items, update Einstellungen paths to tab URLs
- DataManagementTab: add checklist-history cleanup and reset sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-03-28 18:57:46 +01:00
parent 893fbe43a0
commit 4349de9bc9
14 changed files with 1078 additions and 1188 deletions

View File

@@ -157,6 +157,34 @@ class CleanupService {
return { count, deleted: true };
}
async cleanupChecklistHistory(olderThanDays: number, confirm: boolean): Promise<CleanupResult> {
const cutoff = `${olderThanDays} days`;
if (!confirm) {
const { rows } = await pool.query(
`SELECT COUNT(*)::int AS count FROM checklist_ausfuehrungen WHERE ausgefuehrt_am < NOW() - $1::interval`,
[cutoff]
);
return { count: rows[0].count, deleted: false };
}
const { rowCount } = await pool.query(
`DELETE FROM checklist_ausfuehrungen WHERE ausgefuehrt_am < NOW() - $1::interval`,
[cutoff]
);
logger.info(`Cleanup: deleted ${rowCount} checklist executions older than ${olderThanDays} days`);
return { count: rowCount ?? 0, deleted: true };
}
async resetChecklistHistory(confirm: boolean): Promise<CleanupResult> {
if (!confirm) {
const { rows } = await pool.query(`SELECT COUNT(*)::int AS count FROM checklist_ausfuehrungen`);
return { count: rows[0].count, deleted: false };
}
await pool.query(`TRUNCATE checklist_ausfuehrungen CASCADE`);
await pool.query(`TRUNCATE checklist_faelligkeit CASCADE`);
logger.info('Cleanup: reset all checklist history');
return { count: 0, deleted: true };
}
async resetIssuesSequence(confirm: boolean): Promise<CleanupResult> {
if (!confirm) {
const { rows } = await pool.query('SELECT COUNT(*)::int AS count FROM issues');