feat: dashboard widgets, auth fix, profile names, dynamic groups
- Add VehicleDashboardCard: self-contained widget modelled after AtemschutzDashboardCard, shows einsatzbereit ratio and inspection warnings inline; replaces StatsCard + InspectionAlerts in Dashboard - Add EquipmentDashboardCard: consolidated equipment status widget showing only aggregated counts (no per-item listing); replaces EquipmentAlerts component in Dashboard - Fix auth race condition: add authInitialized flag to api.ts so 401 responses during initial token validation no longer trigger a spurious redirect to /login; save intended destination before login redirect and restore it after successful auth callback - Fix profile firstname/lastname: add extractNames() helper to auth.controller.ts that falls back to splitting userinfo.name when Authentik does not provide separate given_name/family_name fields; applied on both create and update paths - Dynamic groups endpoint: replace hardcoded KNOWN_GROUPS array in events.controller.ts with a DB query (SELECT DISTINCT unnest (authentik_groups) FROM users); known slugs get German labels via lookup map, unknown slugs are humanized automatically Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -430,6 +430,48 @@ class EventsService {
|
||||
return { token, subscribeUrl };
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GROUPS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns distinct group slugs from active users as { id, label } pairs.
|
||||
* The label is derived from a known-translations map or humanized from the slug.
|
||||
*/
|
||||
async getAvailableGroups(): Promise<Array<{ id: string; label: string }>> {
|
||||
const knownLabels: Record<string, string> = {
|
||||
'dashboard_admin': 'Administratoren',
|
||||
'dashboard_mitglied': 'Mitglieder',
|
||||
'dashboard_fahrmeister': 'Fahrmeister',
|
||||
'dashboard_zeugmeister': 'Zeugmeister',
|
||||
'dashboard_atemschutz': 'Atemschutzwart',
|
||||
'dashboard_jugend': 'Feuerwehrjugend',
|
||||
'dashboard_kommandant': 'Kommandanten',
|
||||
'dashboard_moderator': 'Moderatoren',
|
||||
'feuerwehr-admin': 'Feuerwehr Admin',
|
||||
'feuerwehr-kommandant': 'Feuerwehr Kommandant',
|
||||
};
|
||||
|
||||
const humanizeGroupName = (slug: string): string => {
|
||||
if (knownLabels[slug]) return knownLabels[slug];
|
||||
const stripped = slug.startsWith('dashboard_') ? slug.slice('dashboard_'.length) : slug;
|
||||
const spaced = stripped.replace(/-/g, ' ');
|
||||
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
||||
};
|
||||
|
||||
const result = await pool.query(
|
||||
`SELECT DISTINCT unnest(authentik_groups) AS group_name
|
||||
FROM users
|
||||
WHERE is_active = true
|
||||
ORDER BY group_name`
|
||||
);
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
id: row.group_name as string,
|
||||
label: humanizeGroupName(row.group_name as string),
|
||||
}));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ICAL EXPORT
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user