feat: user data purge, breadcrumbs, first-login dialog, widget consolidation, bookkeeping cascade
- Admin can purge all personal data for a user (POST /api/admin/users/:userId/purge-data) while keeping the account; clears profile, notifications, bookings, ical tokens, preferences - Add isNewUser flag to auth callback response; first-login dialog prompts for Standesbuchnummer - Add PageBreadcrumbs component and apply to 18 sub-pages across the app - Cascade budget_typ changes from parent pot to all children recursively, converting amounts (detailliert→einfach: sum into budget_gesamt; einfach→detailliert: zero all for redistribution) - Migrate NextcloudTalkWidget to use shared WidgetCard template for consistent header styling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { settingsApi } from '../services/settings';
|
||||
@@ -293,6 +294,10 @@ function AdminSettings() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg">
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Admin', href: '/admin' },
|
||||
{ label: 'Einstellungen' },
|
||||
]} />
|
||||
<Typography variant="h4" gutterBottom sx={{ mb: 4 }}>
|
||||
Admin-Einstellungen
|
||||
</Typography>
|
||||
|
||||
@@ -779,6 +779,10 @@ function AusruestungDetailPage() {
|
||||
<DetailLayout
|
||||
title={equipment.bezeichnung}
|
||||
backTo="/ausruestung"
|
||||
breadcrumbs={[
|
||||
{ label: 'Ausrüstung', href: '/ausruestung' },
|
||||
{ label: equipment.bezeichnung },
|
||||
]}
|
||||
tabs={tabs}
|
||||
actions={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
||||
@@ -285,6 +286,10 @@ export default function AusruestungsanfrageArtikelDetail() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Ausrüstungsanfragen', href: '/ausruestungsanfrage' },
|
||||
{ label: isCreate ? 'Neuer Katalogartikel' : (artikel?.bezeichnung ?? 'Artikel') },
|
||||
]} />
|
||||
{/* Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<IconButton onClick={() => navigate('/ausruestungsanfrage?tab=2')}>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
@@ -193,6 +194,10 @@ export default function AusruestungsanfrageDetail() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Ausrüstungsanfragen', href: '/ausruestungsanfrage' },
|
||||
{ label: anfrage ? `Anfrage ${formatOrderId(anfrage)}` : 'Anfrage' },
|
||||
]} />
|
||||
{/* Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<IconButton onClick={() => navigate('/ausruestungsanfrage')}>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ArrowBack, Add as AddIcon, Delete as DeleteIcon } from '@mui/icons-mate
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
||||
@@ -168,6 +169,10 @@ export default function AusruestungsanfrageNeu() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Ausrüstungsanfragen', href: '/ausruestungsanfrage' },
|
||||
{ label: 'Neue Bestellung' },
|
||||
]} />
|
||||
{/* Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<IconButton onClick={() => navigate('/ausruestungsanfrage')}>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
||||
@@ -236,6 +237,11 @@ export default function AusruestungsanfrageZuBestellung() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Box sx={{ maxWidth: 960, mx: 'auto', px: 2, py: 3 }}>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Ausrüstungsanfragen', href: '/ausruestungsanfrage' },
|
||||
{ label: anfrage.bezeichnung || `Anfrage ${formatOrderId(anfrage)}`, href: `/ausruestungsanfrage/${requestId}` },
|
||||
{ label: 'Bestellen' },
|
||||
]} />
|
||||
|
||||
{/* ── Header ── */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 3 }}>
|
||||
|
||||
@@ -673,6 +673,10 @@ export default function BestellungDetail() {
|
||||
<PageHeader
|
||||
title={bestellung.bezeichnung}
|
||||
backTo="/bestellungen"
|
||||
breadcrumbs={[
|
||||
{ label: 'Bestellungen', href: '/bestellungen' },
|
||||
{ label: bestellung.bezeichnung },
|
||||
]}
|
||||
actions={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{canExport && !editMode && (
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ArrowBack } from '@mui/icons-material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { buchhaltungApi } from '../services/buchhaltung';
|
||||
import { TRANSAKTION_TYP_LABELS } from '../types/buchhaltung.types';
|
||||
import type { BankkontoStatementRow } from '../types/buchhaltung.types';
|
||||
@@ -61,6 +62,10 @@ export default function BuchhaltungBankkontoDetail() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Buchhaltung', href: '/buchhaltung' },
|
||||
{ label: data?.bankkonto?.bezeichnung || 'Bankkonto' },
|
||||
]} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<Tooltip title="Zurueck">
|
||||
<IconButton onClick={() => navigate('/buchhaltung?tab=2')}>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { ArrowBack, KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { buchhaltungApi } from '../services/buchhaltung';
|
||||
import { AUSGABEN_TYP_LABELS } from '../types/buchhaltung.types';
|
||||
import type { AusgabenTyp, BuchhaltungAudit } from '../types/buchhaltung.types';
|
||||
@@ -132,6 +133,10 @@ export default function BuchhaltungKontoDetail() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Box sx={{ p: 3 }}>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Buchhaltung', href: '/buchhaltung' },
|
||||
{ label: `${konto.kontonummer} — ${konto.bezeichnung}` },
|
||||
]} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<Button startIcon={<ArrowBack />} onClick={() => navigate('/buchhaltung')}>
|
||||
Zurück
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { ArrowBack, Delete, Edit, Save, Cancel } from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { buchhaltungApi } from '../services/buchhaltung';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import type { KontoFormData, BudgetTyp } from '../types/buchhaltung.types';
|
||||
@@ -172,6 +173,11 @@ export default function BuchhaltungKontoManage() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Buchhaltung', href: '/buchhaltung' },
|
||||
{ label: `${konto.kontonummer} — ${konto.bezeichnung}`, href: `/buchhaltung/konto/${id}` },
|
||||
{ label: 'Verwalten' },
|
||||
]} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<Button startIcon={<ArrowBack />} onClick={() => navigate('/buchhaltung?tab=2')}>
|
||||
Zurück
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ArrowBack, CheckCircle, Cancel, RemoveCircle } from '@mui/icons-materia
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { checklistenApi } from '../services/checklisten';
|
||||
@@ -243,6 +244,10 @@ export default function ChecklistAusfuehrung() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Checklisten', href: '/checklisten' },
|
||||
{ label: execution.vorlage_name ?? 'Checkliste' },
|
||||
]} />
|
||||
<Button startIcon={<ArrowBack />} onClick={() => navigate('/checklisten')} sx={{ mb: 2 }} size="small">
|
||||
Checklisten
|
||||
</Button>
|
||||
|
||||
@@ -284,6 +284,10 @@ function EinsatzDetail() {
|
||||
title={`Einsatz ${einsatz.einsatz_nr}`}
|
||||
subtitle={address || undefined}
|
||||
backTo="/einsaetze"
|
||||
breadcrumbs={[
|
||||
{ label: 'Einsätze', href: '/einsaetze' },
|
||||
{ label: `Einsatz ${einsatz.einsatz_nr}` },
|
||||
]}
|
||||
actions={
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Chip
|
||||
|
||||
@@ -1006,6 +1006,10 @@ function FahrzeugDetail() {
|
||||
<DetailLayout
|
||||
title={titleText}
|
||||
backTo="/fahrzeuge"
|
||||
breadcrumbs={[
|
||||
{ label: 'Fahrzeuge', href: '/fahrzeuge' },
|
||||
{ label: titleText },
|
||||
]}
|
||||
tabs={tabs}
|
||||
actions={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
@@ -143,6 +144,10 @@ export default function HaushaltsplanDetail() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Haushaltsplan', href: '/haushaltsplan' },
|
||||
{ label: planung.bezeichnung },
|
||||
]} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
|
||||
<IconButton onClick={() => navigate('/haushaltsplan')}><ArrowBack /></IconButton>
|
||||
<Typography variant="h4" sx={{ flexGrow: 1 }}>{planung.bezeichnung}</Typography>
|
||||
|
||||
@@ -264,6 +264,10 @@ export default function IssueDetail() {
|
||||
<PageHeader
|
||||
title={`${formatIssueId(issue)} — ${issue.titel}`}
|
||||
backTo="/issues"
|
||||
breadcrumbs={[
|
||||
{ label: 'Issues', href: '/issues' },
|
||||
{ label: `${formatIssueId(issue)} — ${issue.titel}` },
|
||||
]}
|
||||
actions={
|
||||
<Chip
|
||||
label={getStatusLabel(statuses, issue.status)}
|
||||
|
||||
@@ -419,6 +419,10 @@ function MitgliedDetail() {
|
||||
<PageHeader
|
||||
title={displayName}
|
||||
backTo="/mitglieder"
|
||||
breadcrumbs={[
|
||||
{ label: 'Mitglieder', href: '/mitglieder' },
|
||||
{ label: displayName },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Header card */}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { trainingApi } from '../services/training';
|
||||
import type { TeilnahmeStatus, UebungTyp, Teilnahme } from '../types/training.types';
|
||||
@@ -332,6 +333,10 @@ export default function UebungDetail() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Box sx={{ maxWidth: 720, mx: 'auto' }}>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Kalender', href: '/kalender' },
|
||||
{ label: event.titel || 'Übung' },
|
||||
]} />
|
||||
{/* Back button */}
|
||||
<Button
|
||||
startIcon={<BackIcon />}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Category as CategoryIcon,
|
||||
} from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { PageBreadcrumbs } from '../components/common';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { eventsApi } from '../services/events';
|
||||
@@ -339,6 +340,10 @@ export default function VeranstaltungKategorien() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Container maxWidth="lg" sx={{ py: 3 }}>
|
||||
<PageBreadcrumbs items={[
|
||||
{ label: 'Veranstaltungen', href: '/veranstaltungen' },
|
||||
{ label: 'Kategorien' },
|
||||
]} />
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user