refactor(mitglieder): split member profile into Stammdaten/Ausrüstung/Qualifikationen tabs with sub-tabs per qualification type

This commit is contained in:
Matthias Hochmeister
2026-04-15 13:55:16 +02:00
parent 50dbf6e9fd
commit 6ff531f79c
2 changed files with 473 additions and 383 deletions

View File

@@ -1,10 +1,11 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { import {
Box, Paper, Typography, TextField, Button, Alert, Box, Paper, Typography, TextField, Button, Alert,
CircularProgress, Divider, Autocomplete, CircularProgress, Divider, Autocomplete, Chip,
} from '@mui/material'; } from '@mui/material';
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'; import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
import RestartAltIcon from '@mui/icons-material/RestartAlt'; import RestartAltIcon from '@mui/icons-material/RestartAlt';
import GroupAddIcon from '@mui/icons-material/GroupAdd';
import { api } from '../../services/api'; import { api } from '../../services/api';
import { adminApi } from '../../services/admin'; import { adminApi } from '../../services/admin';
import { useNotification } from '../../contexts/NotificationContext'; import { useNotification } from '../../contexts/NotificationContext';
@@ -60,26 +61,45 @@ export default function DataManagementTab() {
queryKey: ['admin', 'users'], queryKey: ['admin', 'users'],
queryFn: adminApi.getUsers, queryFn: adminApi.getUsers,
}); });
const [selectedUser, setSelectedUser] = useState<UserOverview | null>(null); const [selectedUsers, setSelectedUsers] = useState<UserOverview[]>([]);
const [fdiskInput, setFdiskInput] = useState('');
const [purging, setPurging] = useState(false); const [purging, setPurging] = useState(false);
const [purgeConfirmOpen, setPurgeConfirmOpen] = useState(false); const [purgeConfirmOpen, setPurgeConfirmOpen] = useState(false);
const handleAddUser = useCallback((user: UserOverview) => {
setSelectedUsers(prev => prev.some(u => u.id === user.id) ? prev : [...prev, user]);
}, []);
const handleRemoveUser = useCallback((userId: string) => {
setSelectedUsers(prev => prev.filter(u => u.id !== userId));
}, []);
const handleAddAll = useCallback(() => {
setSelectedUsers(users);
}, [users]);
const handlePurge = useCallback(async () => { const handlePurge = useCallback(async () => {
if (!selectedUser) return; if (selectedUsers.length === 0) return;
setPurging(true); setPurging(true);
let totalDeleted = 0;
let errorCount = 0;
for (const user of selectedUsers) {
try { try {
const result = await adminApi.purgeFdiskData(selectedUser.id); const result = await adminApi.purgeFdiskData(user.id);
const total = result.profileFieldsCleared + result.ausbildungen + result.befoerderungen + result.untersuchungen + result.fahrgenehmigungen; totalDeleted += result.profileFieldsCleared + result.ausbildungen + result.befoerderungen + result.untersuchungen + result.fahrgenehmigungen;
showSuccess(`${total} FDISK-Eintraege fuer ${selectedUser.name || selectedUser.email} geloescht`); } catch {
setSelectedUser(null); errorCount++;
} catch (err: unknown) { }
const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message || 'Fehler beim Loeschen'; }
showError(msg);
} finally {
setPurging(false); setPurging(false);
setPurgeConfirmOpen(false); setPurgeConfirmOpen(false);
if (errorCount === 0) {
showSuccess(`${totalDeleted} FDISK-Eintraege fuer ${selectedUsers.length} ${selectedUsers.length === 1 ? 'Benutzer' : 'Benutzer'} geloescht`);
setSelectedUsers([]);
} else {
showError(`${errorCount} von ${selectedUsers.length} Benutzern konnten nicht verarbeitet werden`);
} }
}, [selectedUser, showSuccess, showError]); }, [selectedUsers, showSuccess, showError]);
const [states, setStates] = useState<Record<string, SectionState>>(() => const [states, setStates] = useState<Record<string, SectionState>>(() =>
Object.fromEntries(SECTIONS.map(s => [s.key, { days: s.defaultDays, previewCount: null, loading: false }])) Object.fromEntries(SECTIONS.map(s => [s.key, { days: s.defaultDays, previewCount: null, loading: false }]))
@@ -168,38 +188,73 @@ export default function DataManagementTab() {
{/* FDISK data purge */} {/* FDISK data purge */}
<Paper sx={{ p: 3, mb: 3 }}> <Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="subtitle1" fontWeight={600} sx={{ mb: 1 }}> <Typography variant="subtitle1" fontWeight={600} sx={{ mb: 1 }}>
FDISK-Daten eines Benutzers loeschen FDISK-Daten loeschen
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Loescht alle FDISK-synchronisierten Daten eines Benutzers: Profilfelder, Ausbildungen, Loescht alle FDISK-synchronisierten Daten der ausgewaehlten Benutzer: Profilfelder, Ausbildungen,
Befoerderungen, Untersuchungen und Fahrgenehmigungen. Beim naechsten FDISK-Sync werden Befoerderungen, Untersuchungen und Fahrgenehmigungen. Beim naechsten FDISK-Sync werden
die Daten erneut importiert. die Daten erneut importiert.
</Typography> </Typography>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', gap: 2, alignItems: 'center', flexWrap: 'wrap' }}>
<Autocomplete <Autocomplete
options={users} options={users.filter(u => !selectedUsers.some(s => s.id === u.id))}
loading={usersLoading} loading={usersLoading}
value={selectedUser} value={null}
onChange={(_e, v) => setSelectedUser(v)} inputValue={fdiskInput}
onInputChange={(_e, val, reason) => {
if (reason === 'input') setFdiskInput(val);
}}
onChange={(_e, user) => {
if (user) {
handleAddUser(user);
setFdiskInput('');
}
}}
getOptionLabel={(u) => `${u.name || 'Kein Name'} (${u.email})`} getOptionLabel={(u) => `${u.name || 'Kein Name'} (${u.email})`}
isOptionEqualToValue={(a, b) => a.id === b.id} isOptionEqualToValue={(a, b) => a.id === b.id}
sx={{ minWidth: 320, flex: 1 }} sx={{ minWidth: 320, flex: 1 }}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} label="Benutzer waehlen" size="small" /> <TextField {...params} label="Benutzer hinzufuegen" size="small" />
)} )}
/> />
<Button
variant="outlined"
size="small"
startIcon={<GroupAddIcon />}
onClick={handleAddAll}
disabled={usersLoading || users.length === 0 || selectedUsers.length === users.length}
>
Alle hinzufuegen
</Button>
</Box>
{selectedUsers.length > 0 && (
<>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }}>
{selectedUsers.map(u => (
<Chip
key={u.id}
label={u.name || u.email}
onDelete={() => handleRemoveUser(u.id)}
size="small"
/>
))}
</Box>
<Box sx={{ mt: 2 }}>
<Button <Button
variant="contained" variant="contained"
color="error" color="error"
disabled={!selectedUser || purging} disabled={purging}
startIcon={purging ? <CircularProgress size={16} /> : <DeleteSweepIcon />} startIcon={purging ? <CircularProgress size={16} /> : <DeleteSweepIcon />}
onClick={() => setPurgeConfirmOpen(true)} onClick={() => setPurgeConfirmOpen(true)}
> >
FDISK-Daten loeschen FDISK-Daten loeschen ({selectedUsers.length})
</Button> </Button>
</Box> </Box>
</>
)}
</Paper> </Paper>
<ConfirmDialog <ConfirmDialog
@@ -207,9 +262,14 @@ export default function DataManagementTab() {
onClose={() => !purging && setPurgeConfirmOpen(false)} onClose={() => !purging && setPurgeConfirmOpen(false)}
onConfirm={handlePurge} onConfirm={handlePurge}
title="FDISK-Daten loeschen?" title="FDISK-Daten loeschen?"
message={selectedUser ? ( message={selectedUsers.length > 0 ? (
<> <>
Alle FDISK-synchronisierten Daten fuer <strong>{selectedUser.name ?? selectedUser.email}</strong> werden geloescht: Alle FDISK-synchronisierten Daten fuer{' '}
{selectedUsers.length === 1
? <strong>{selectedUsers[0].name ?? selectedUsers[0].email}</strong>
: <strong>{selectedUsers.length} Benutzer</strong>
}{' '}
werden geloescht:
<ul style={{ margin: '8px 0', paddingLeft: 20 }}> <ul style={{ margin: '8px 0', paddingLeft: 20 }}>
<li>Profilfelder (Status, Dienstgrad, Geburtsdatum, Geburtsort, Geschlecht, Beruf, Wohnort, PLZ)</li> <li>Profilfelder (Status, Dienstgrad, Geburtsdatum, Geburtsort, Geschlecht, Beruf, Wohnort, PLZ)</li>
<li>Alle Ausbildungen, Befoerderungen, Untersuchungen und Fahrgenehmigungen</li> <li>Alle Ausbildungen, Befoerderungen, Untersuchungen und Fahrgenehmigungen</li>

View File

@@ -224,6 +224,7 @@ function MitgliedDetail() {
const [saveError, setSaveError] = useState<string | null>(null); const [saveError, setSaveError] = useState<string | null>(null);
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [qualSubTab, setQualSubTab] = useState(0);
// Atemschutz data for Qualifikationen tab // Atemschutz data for Qualifikationen tab
const [atemschutz, setAtemschutz] = useState<AtemschutzUebersicht | null>(null); const [atemschutz, setAtemschutz] = useState<AtemschutzUebersicht | null>(null);
@@ -579,8 +580,9 @@ function MitgliedDetail() {
scrollButtons="auto" scrollButtons="auto"
> >
<Tab label="Stammdaten" id="tab-0" aria-controls="tabpanel-0" /> <Tab label="Stammdaten" id="tab-0" aria-controls="tabpanel-0" />
<Tab label="Qualifikationen" id="tab-1" aria-controls="tabpanel-1" /> <Tab label="Ausrüstung & Uniform" id="tab-1" aria-controls="tabpanel-1" />
<Tab label="Einsätze" id="tab-2" aria-controls="tabpanel-2" /> <Tab label="Qualifikationen" id="tab-2" aria-controls="tabpanel-2" />
<Tab label="Einsätze" id="tab-3" aria-controls="tabpanel-3" />
</Tabs> </Tabs>
</Box> </Box>
@@ -816,98 +818,6 @@ function MitgliedDetail() {
</Card> </Card>
</Grid> </Grid>
{/* Uniform sizing + Personal equipment (merged) */}
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<SecurityIcon color="primary" />}
title="Ausrüstung & Uniform"
/>
<CardContent>
{editMode ? (
<Stack spacing={2}>
<TextField
label="T-Shirt Größe"
select
fullWidth
size="small"
value={formData.tshirt_groesse ?? ''}
onChange={(e) => handleFieldChange('tshirt_groesse', e.target.value as TshirtGroesseEnum || undefined)}
>
<MenuItem value=""></MenuItem>
{TSHIRT_GROESSE_VALUES.map((g) => (
<MenuItem key={g} value={g}>{g}</MenuItem>
))}
</TextField>
<TextField
label="Schuhgröße"
fullWidth
size="small"
value={formData.schuhgroesse ?? ''}
onChange={(e) => handleFieldChange('schuhgroesse', e.target.value || undefined)}
placeholder="z.B. 43"
/>
</Stack>
) : (
<>
<FieldRow label="T-Shirt Größe" value={profile?.tshirt_groesse ?? null} />
<FieldRow label="Schuhgröße" value={profile?.schuhgroesse ?? null} />
</>
)}
{(hasPermission('persoenliche_ausruestung:view') || hasPermission('persoenliche_ausruestung:view_all')) && (
<>
<Divider sx={{ my: 1.5 }} />
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1 }}>
Persönliche Ausrüstung
</Typography>
{personalEquipmentLoading ? (
<CircularProgress size={24} />
) : personalEquipment.length === 0 ? (
<Typography color="text.secondary" variant="body2">
Keine persönlichen Gegenstände erfasst
</Typography>
) : (
personalEquipment.map((item) => (
<Box
key={item.id}
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', py: 0.75, borderBottom: '1px solid', borderColor: 'divider', cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}
onClick={() => navigate(`/persoenliche-ausruestung/${item.id}`)}
>
<Box>
<Typography variant="body2" fontWeight={500}>{item.bezeichnung}</Typography>
{item.kategorie && (
<Typography variant="caption" color="text.secondary">{item.kategorie}</Typography>
)}
{item.eigenschaften && item.eigenschaften.length > 0 && (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 0.25 }}>
{item.eigenschaften.map((e) => (
<Chip
key={e.id}
label={`${e.name}: ${e.wert}`}
size="small"
variant="outlined"
sx={{ height: 18, fontSize: '0.65rem' }}
/>
))}
</Box>
)}
</Box>
<Chip
label={ZUSTAND_LABELS[item.zustand]}
color={ZUSTAND_COLORS[item.zustand]}
size="small"
variant="outlined"
/>
</Box>
))
)}
</>
)}
</CardContent>
</Card>
</Grid>
{/* Driving licenses */} {/* Driving licenses */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Card> <Card>
@@ -981,15 +891,131 @@ function MitgliedDetail() {
</Grid> </Grid>
</TabPanel> </TabPanel>
{/* ---- Tab 1: Qualifikationen ---- */} {/* ---- Tab 1: Ausrüstung & Uniform ---- */}
<TabPanel value={activeTab} index={1}> <TabPanel value={activeTab} index={1}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<SecurityIcon color="primary" />}
title="Ausrüstung & Uniform"
/>
<CardContent>
{editMode ? (
<Stack spacing={2}>
<TextField
label="T-Shirt Größe"
select
fullWidth
size="small"
value={formData.tshirt_groesse ?? ''}
onChange={(e) => handleFieldChange('tshirt_groesse', e.target.value as TshirtGroesseEnum || undefined)}
>
<MenuItem value=""></MenuItem>
{TSHIRT_GROESSE_VALUES.map((g) => (
<MenuItem key={g} value={g}>{g}</MenuItem>
))}
</TextField>
<TextField
label="Schuhgröße"
fullWidth
size="small"
value={formData.schuhgroesse ?? ''}
onChange={(e) => handleFieldChange('schuhgroesse', e.target.value || undefined)}
placeholder="z.B. 43"
/>
</Stack>
) : (
<>
<FieldRow label="T-Shirt Größe" value={profile?.tshirt_groesse ?? null} />
<FieldRow label="Schuhgröße" value={profile?.schuhgroesse ?? null} />
</>
)}
</CardContent>
</Card>
</Grid>
{(hasPermission('persoenliche_ausruestung:view') || hasPermission('persoenliche_ausruestung:view_all')) && (
<Grid item xs={12}>
<Card>
<CardHeader
avatar={<SecurityIcon color="primary" />}
title="Persönliche Ausrüstung"
/>
<CardContent>
{personalEquipmentLoading ? (
<CircularProgress size={24} />
) : personalEquipment.length === 0 ? (
<Typography color="text.secondary" variant="body2">
Keine persönlichen Gegenstände erfasst
</Typography>
) : (
personalEquipment.map((item) => (
<Box
key={item.id}
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', py: 0.75, borderBottom: '1px solid', borderColor: 'divider', cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}
onClick={() => navigate(`/persoenliche-ausruestung/${item.id}`)}
>
<Box>
<Typography variant="body2" fontWeight={500}>{item.bezeichnung}</Typography>
{item.kategorie && (
<Typography variant="caption" color="text.secondary">{item.kategorie}</Typography>
)}
{item.eigenschaften && item.eigenschaften.length > 0 && (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 0.25 }}>
{item.eigenschaften.map((e) => (
<Chip
key={e.id}
label={`${e.name}: ${e.wert}`}
size="small"
variant="outlined"
sx={{ height: 18, fontSize: '0.65rem' }}
/>
))}
</Box>
)}
</Box>
<Chip
label={ZUSTAND_LABELS[item.zustand]}
color={ZUSTAND_COLORS[item.zustand]}
size="small"
variant="outlined"
/>
</Box>
))
)}
</CardContent>
</Card>
</Grid>
)}
</Grid>
</TabPanel>
{/* ---- Tab 2: Qualifikationen ---- */}
<TabPanel value={activeTab} index={2}>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
<Tabs
value={qualSubTab}
onChange={(_e, v) => setQualSubTab(v)}
variant="scrollable"
scrollButtons="auto"
aria-label="Qualifikationen"
>
<Tab label="Atemschutz" id="qual-tab-0" aria-controls="qual-tabpanel-0" />
<Tab label="Ausbildungen" id="qual-tab-1" aria-controls="qual-tabpanel-1" />
<Tab label="Untersuchungen" id="qual-tab-2" aria-controls="qual-tabpanel-2" />
<Tab label="Beförderungen" id="qual-tab-3" aria-controls="qual-tabpanel-3" />
<Tab label="Fahrgenehmigungen" id="qual-tab-4" aria-controls="qual-tabpanel-4" />
</Tabs>
</Box>
{/* Sub-tab 0: Atemschutz */}
<TabPanel value={qualSubTab} index={0}>
{atemschutzLoading ? ( {atemschutzLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', pt: 6 }}> <Box sx={{ display: 'flex', justifyContent: 'center', pt: 6 }}>
<CircularProgress /> <CircularProgress />
</Box> </Box>
) : atemschutz ? ( ) : atemschutz ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card> <Card>
<CardHeader <CardHeader
avatar={<SecurityIcon color="primary" />} avatar={<SecurityIcon color="primary" />}
@@ -1081,8 +1107,6 @@ function MitgliedDetail() {
)} )}
</CardContent> </CardContent>
</Card> </Card>
</Grid>
</Grid>
) : ( ) : (
<Card> <Card>
<CardContent> <CardContent>
@@ -1103,40 +1127,20 @@ function MitgliedDetail() {
</CardContent> </CardContent>
</Card> </Card>
)} )}
</TabPanel>
{/* FDISK-synced sub-sections */} {/* Sub-tab 1: Ausbildungen */}
<Grid container spacing={3} sx={{ mt: 0 }}> <TabPanel value={qualSubTab} index={1}>
{/* Beförderungen */}
{befoerderungen.length > 0 && (
<Grid item xs={12} md={6}>
<Card>
<CardHeader
avatar={<MilitaryTechIcon color="primary" />}
title="Beförderungen"
/>
<CardContent>
{befoerderungen.map((b) => (
<FieldRow
key={b.id}
label={b.datum ? new Date(b.datum).toLocaleDateString('de-AT') : '—'}
value={b.dienstgrad}
/>
))}
</CardContent>
</Card>
</Grid>
)}
{/* Ausbildungen */}
{ausbildungen.length > 0 && (
<Grid item xs={12} md={6}>
<Card> <Card>
<CardHeader <CardHeader
avatar={<SchoolIcon color="primary" />} avatar={<SchoolIcon color="primary" />}
title="Ausbildungen" title="Ausbildungen"
/> />
<CardContent> <CardContent>
{ausbildungen.map((a) => ( {ausbildungen.length === 0 ? (
<Typography color="text.secondary" variant="body2">Keine Ausbildungen eingetragen.</Typography>
) : (
ausbildungen.map((a) => (
<Box key={a.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}> <Box key={a.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
@@ -1174,22 +1178,24 @@ function MitgliedDetail() {
</Typography> </Typography>
)} )}
</Box> </Box>
))} ))
)}
</CardContent> </CardContent>
</Card> </Card>
</Grid> </TabPanel>
)}
{/* Untersuchungen */} {/* Sub-tab 2: Untersuchungen */}
{untersuchungen.length > 0 && ( <TabPanel value={qualSubTab} index={2}>
<Grid item xs={12} md={6}>
<Card> <Card>
<CardHeader <CardHeader
avatar={<LocalHospitalIcon color="primary" />} avatar={<LocalHospitalIcon color="primary" />}
title="Untersuchungen" title="Untersuchungen"
/> />
<CardContent> <CardContent>
{untersuchungen.map((u) => ( {untersuchungen.length === 0 ? (
<Typography color="text.secondary" variant="body2">Keine Untersuchungen eingetragen.</Typography>
) : (
untersuchungen.map((u) => (
<Box key={u.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}> <Box key={u.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
@@ -1210,22 +1216,47 @@ function MitgliedDetail() {
</Typography> </Typography>
)} )}
</Box> </Box>
))} ))
)}
</CardContent> </CardContent>
</Card> </Card>
</Grid> </TabPanel>
)}
{/* Fahrgenehmigungen */} {/* Sub-tab 3: Beförderungen */}
{fahrgenehmigungen.length > 0 && ( <TabPanel value={qualSubTab} index={3}>
<Grid item xs={12} md={6}> <Card>
<CardHeader
avatar={<MilitaryTechIcon color="primary" />}
title="Beförderungen"
/>
<CardContent>
{befoerderungen.length === 0 ? (
<Typography color="text.secondary" variant="body2">Keine Beförderungen eingetragen.</Typography>
) : (
befoerderungen.map((b) => (
<FieldRow
key={b.id}
label={b.datum ? new Date(b.datum).toLocaleDateString('de-AT') : '—'}
value={b.dienstgrad}
/>
))
)}
</CardContent>
</Card>
</TabPanel>
{/* Sub-tab 4: Fahrgenehmigungen */}
<TabPanel value={qualSubTab} index={4}>
<Card> <Card>
<CardHeader <CardHeader
avatar={<DriveEtaIcon color="primary" />} avatar={<DriveEtaIcon color="primary" />}
title="Gesetzliche Fahrgenehmigungen" title="Gesetzliche Fahrgenehmigungen"
/> />
<CardContent> <CardContent>
{fahrgenehmigungen.map((f) => ( {fahrgenehmigungen.length === 0 ? (
<Typography color="text.secondary" variant="body2">Keine Fahrgenehmigungen eingetragen.</Typography>
) : (
fahrgenehmigungen.map((f) => (
<Box key={f.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}> <Box key={f.id} sx={{ py: 0.75, borderBottom: '1px solid', borderColor: 'divider' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
<Typography variant="body2" fontWeight={500}> <Typography variant="body2" fontWeight={500}>
@@ -1246,16 +1277,15 @@ function MitgliedDetail() {
</Typography> </Typography>
)} )}
</Box> </Box>
))} ))
)}
</CardContent> </CardContent>
</Card> </Card>
</Grid> </TabPanel>
)}
</Grid>
</TabPanel> </TabPanel>
{/* ---- Tab 2: Einsätze (placeholder) ---- */} {/* ---- Tab 3: Einsätze (placeholder) ---- */}
<TabPanel value={activeTab} index={2}> <TabPanel value={activeTab} index={3}>
<Card> <Card>
<CardContent> <CardContent>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 6, gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 6, gap: 2 }}>