feat: add Buchhaltung dashboard widget, CSV export, Bestellungen linking, recurring bookings, and approval workflow
This commit is contained in:
@@ -11,8 +11,8 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Fab,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Switch,
|
||||
Tab,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -37,12 +38,18 @@ import {
|
||||
BookmarkAdd,
|
||||
Cancel,
|
||||
Delete,
|
||||
Download,
|
||||
Edit,
|
||||
HowToReg,
|
||||
Lock,
|
||||
ThumbDown,
|
||||
ThumbUp,
|
||||
} from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { buchhaltungApi } from '../services/buchhaltung';
|
||||
import { bestellungApi } from '../services/bestellung';
|
||||
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import type {
|
||||
@@ -51,11 +58,14 @@ import type {
|
||||
Konto, KontoFormData,
|
||||
Transaktion, TransaktionFormData, TransaktionFilters,
|
||||
TransaktionStatus,
|
||||
WiederkehrendBuchung, WiederkehrendFormData,
|
||||
WiederkehrendIntervall,
|
||||
} from '../types/buchhaltung.types';
|
||||
import {
|
||||
TRANSAKTION_STATUS_LABELS,
|
||||
TRANSAKTION_STATUS_COLORS,
|
||||
TRANSAKTION_TYP_LABELS,
|
||||
INTERVALL_LABELS,
|
||||
} from '../types/buchhaltung.types';
|
||||
|
||||
// ─── helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -234,6 +244,7 @@ function TransaktionDialog({
|
||||
empfaenger_auftraggeber: '',
|
||||
verwendungszweck: '',
|
||||
beleg_nr: '',
|
||||
bestellung_id: null,
|
||||
});
|
||||
|
||||
const { data: konten = [] } = useQuery({
|
||||
@@ -242,6 +253,11 @@ function TransaktionDialog({
|
||||
enabled: form.haushaltsjahr_id > 0,
|
||||
});
|
||||
const { data: bankkonten = [] } = useQuery({ queryKey: ['bankkonten'], queryFn: buchhaltungApi.getBankkonten });
|
||||
const { data: bestellungen = [] } = useQuery({
|
||||
queryKey: ['bestellungen-all'],
|
||||
queryFn: () => bestellungApi.getOrders(),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setForm(f => ({ ...f, haushaltsjahr_id: selectedJahrId || 0, datum: today }));
|
||||
@@ -286,6 +302,13 @@ function TransaktionDialog({
|
||||
<TextField label="Empfänger/Auftraggeber" value={form.empfaenger_auftraggeber} onChange={e => setForm(f => ({ ...f, empfaenger_auftraggeber: e.target.value }))} />
|
||||
<TextField label="Verwendungszweck" value={form.verwendungszweck} onChange={e => setForm(f => ({ ...f, verwendungszweck: e.target.value }))} />
|
||||
<TextField label="Belegnummer" value={form.beleg_nr} onChange={e => setForm(f => ({ ...f, beleg_nr: e.target.value }))} />
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Bestellung verknüpfen</InputLabel>
|
||||
<Select value={form.bestellung_id ?? ''} label="Bestellung verknüpfen" onChange={e => setForm(f => ({ ...f, bestellung_id: e.target.value ? Number(e.target.value) : null }))}>
|
||||
<MenuItem value=""><em>Keine Bestellung</em></MenuItem>
|
||||
{bestellungen.map(b => <MenuItem key={b.id} value={b.id}>{b.laufende_nummer ? `#${b.laufende_nummer} – ` : ''}{b.bezeichnung}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -419,6 +442,39 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
onError: () => showError('Löschen fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const freigabeMut = useMutation({
|
||||
mutationFn: (id: number) => buchhaltungApi.requestFreigabe(id),
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); showSuccess('Freigabe angefragt'); },
|
||||
onError: () => showError('Freigabe konnte nicht angefragt werden'),
|
||||
});
|
||||
|
||||
const approveMut = useMutation({
|
||||
mutationFn: (id: number) => buchhaltungApi.approveFreigabe(id),
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); qc.invalidateQueries({ queryKey: ['buchhaltung-stats'] }); showSuccess('Freigabe genehmigt'); },
|
||||
onError: () => showError('Genehmigung fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const rejectMut = useMutation({
|
||||
mutationFn: (id: number) => buchhaltungApi.rejectFreigabe(id),
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); showSuccess('Freigabe abgelehnt'); },
|
||||
onError: () => showError('Ablehnung fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const handleExportCsv = async () => {
|
||||
if (!filters.haushaltsjahr_id) { showError('Bitte ein Haushaltsjahr auswählen'); return; }
|
||||
try {
|
||||
const blob = await buchhaltungApi.exportCsv(filters.haushaltsjahr_id);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `transaktionen_${filters.haushaltsjahr_id}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch {
|
||||
showError('Export fehlgeschlagen');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFilters(f => ({ ...f, haushaltsjahr_id: selectedJahrId || undefined }));
|
||||
}, [selectedJahrId]);
|
||||
@@ -454,6 +510,13 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
</FormControl>
|
||||
<TextField size="small" label="Suche" value={filters.search ?? ''}
|
||||
onChange={e => setFilters(f => ({ ...f, search: e.target.value || undefined }))} sx={{ minWidth: 180 }} />
|
||||
{hasPermission('buchhaltung:export') && (
|
||||
<Tooltip title="CSV exportieren">
|
||||
<IconButton size="small" onClick={handleExportCsv} disabled={!filters.haushaltsjahr_id}>
|
||||
<Download fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isLoading ? <CircularProgress /> : (
|
||||
@@ -482,7 +545,14 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
<TableCell>
|
||||
<Chip label={TRANSAKTION_TYP_LABELS[t.typ]} size="small" color={t.typ === 'einnahme' ? 'success' : 'error'} />
|
||||
</TableCell>
|
||||
<TableCell>{t.beschreibung || t.empfaenger_auftraggeber || '–'}</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}>
|
||||
<span>{t.beschreibung || t.empfaenger_auftraggeber || '–'}</span>
|
||||
{t.bestellung_id && (
|
||||
<Chip label={`Best. #${t.bestellung_id}`} size="small" variant="outlined" color="info" sx={{ fontSize: '0.7rem' }} />
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>{t.konto_kontonummer ? `${t.konto_kontonummer} ${t.konto_bezeichnung}` : '–'}</TableCell>
|
||||
<TableCell align="right" sx={{ color: t.typ === 'einnahme' ? 'success.main' : 'error.main', fontWeight: 600 }}>
|
||||
{t.typ === 'ausgabe' ? '-' : '+'}{fmtEur(t.betrag)}
|
||||
@@ -499,6 +569,27 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{t.status === 'gebucht' && hasPermission('buchhaltung:edit') && (
|
||||
<Tooltip title="Freigabe anfordern">
|
||||
<IconButton size="small" color="info" onClick={() => freigabeMut.mutate(t.id)}>
|
||||
<HowToReg fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{t.status === 'freigegeben' && hasPermission('buchhaltung:manage_accounts') && (
|
||||
<>
|
||||
<Tooltip title="Genehmigen">
|
||||
<IconButton size="small" color="success" onClick={() => approveMut.mutate(t.id)}>
|
||||
<ThumbUp fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Ablehnen">
|
||||
<IconButton size="small" color="error" onClick={() => rejectMut.mutate(t.id)}>
|
||||
<ThumbDown fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{(t.status === 'gebucht' || t.status === 'freigegeben') && hasPermission('buchhaltung:edit') && (
|
||||
<Tooltip title="Stornieren">
|
||||
<IconButton size="small" color="warning" onClick={() => stornoMut.mutate(t.id)}>
|
||||
@@ -523,9 +614,9 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
)}
|
||||
|
||||
{hasPermission('buchhaltung:create') && (
|
||||
<Fab color="primary" sx={{ position: 'fixed', bottom: 32, right: 80 }} onClick={() => setCreateOpen(true)}>
|
||||
<ChatAwareFab color="primary" onClick={() => setCreateOpen(true)}>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</ChatAwareFab>
|
||||
)}
|
||||
|
||||
<TransaktionDialog
|
||||
@@ -541,6 +632,107 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
|
||||
// ─── Tab 2: Konten ────────────────────────────────────────────────────────────
|
||||
|
||||
function WiederkehrendDialog({
|
||||
open,
|
||||
onClose,
|
||||
konten,
|
||||
bankkonten,
|
||||
existing,
|
||||
onSave,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
konten: Konto[];
|
||||
bankkonten: Bankkonto[];
|
||||
existing?: WiederkehrendBuchung;
|
||||
onSave: (data: WiederkehrendFormData) => void;
|
||||
}) {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const empty: WiederkehrendFormData = {
|
||||
bezeichnung: '',
|
||||
konto_id: null,
|
||||
bankkonto_id: null,
|
||||
typ: 'ausgabe',
|
||||
betrag: 0,
|
||||
beschreibung: '',
|
||||
empfaenger_auftraggeber: '',
|
||||
intervall: 'monatlich',
|
||||
naechste_ausfuehrung: today,
|
||||
aktiv: true,
|
||||
};
|
||||
const [form, setForm] = useState<WiederkehrendFormData>(empty);
|
||||
|
||||
useEffect(() => {
|
||||
if (existing) {
|
||||
setForm({
|
||||
bezeichnung: existing.bezeichnung,
|
||||
konto_id: existing.konto_id,
|
||||
bankkonto_id: existing.bankkonto_id,
|
||||
typ: existing.typ,
|
||||
betrag: existing.betrag,
|
||||
beschreibung: existing.beschreibung || '',
|
||||
empfaenger_auftraggeber: existing.empfaenger_auftraggeber || '',
|
||||
intervall: existing.intervall,
|
||||
naechste_ausfuehrung: existing.naechste_ausfuehrung.slice(0, 10),
|
||||
aktiv: existing.aktiv,
|
||||
});
|
||||
} else {
|
||||
setForm(empty);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [existing, open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{existing ? 'Wiederkehrende Buchung bearbeiten' : 'Wiederkehrende Buchung anlegen'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField label="Bezeichnung" value={form.bezeichnung} onChange={e => setForm(f => ({ ...f, bezeichnung: e.target.value }))} required />
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Typ</InputLabel>
|
||||
<Select value={form.typ} label="Typ" onChange={e => setForm(f => ({ ...f, typ: e.target.value as 'einnahme' | 'ausgabe' }))}>
|
||||
<MenuItem value="ausgabe">Ausgabe</MenuItem>
|
||||
<MenuItem value="einnahme">Einnahme</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Betrag (€)" type="number" value={form.betrag} onChange={e => setForm(f => ({ ...f, betrag: parseFloat(e.target.value) || 0 }))} inputProps={{ step: '0.01', min: '0' }} required />
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Intervall</InputLabel>
|
||||
<Select value={form.intervall} label="Intervall" onChange={e => setForm(f => ({ ...f, intervall: e.target.value as WiederkehrendIntervall }))}>
|
||||
{(Object.entries(INTERVALL_LABELS) as [WiederkehrendIntervall, string][]).map(([v, l]) => <MenuItem key={v} value={v}>{l}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Nächste Ausführung" type="date" value={form.naechste_ausfuehrung} onChange={e => setForm(f => ({ ...f, naechste_ausfuehrung: e.target.value }))} InputLabelProps={{ shrink: true }} required />
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Konto</InputLabel>
|
||||
<Select value={form.konto_id ?? ''} label="Konto" onChange={e => setForm(f => ({ ...f, konto_id: e.target.value ? Number(e.target.value) : null }))}>
|
||||
<MenuItem value=""><em>Kein Konto</em></MenuItem>
|
||||
{konten.map(k => <MenuItem key={k.id} value={k.id}>{k.kontonummer} – {k.bezeichnung}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Bankkonto</InputLabel>
|
||||
<Select value={form.bankkonto_id ?? ''} label="Bankkonto" onChange={e => setForm(f => ({ ...f, bankkonto_id: e.target.value ? Number(e.target.value) : null }))}>
|
||||
<MenuItem value=""><em>Kein Bankkonto</em></MenuItem>
|
||||
{bankkonten.map(bk => <MenuItem key={bk.id} value={bk.id}>{bk.bezeichnung}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField label="Beschreibung" value={form.beschreibung} onChange={e => setForm(f => ({ ...f, beschreibung: e.target.value }))} />
|
||||
<TextField label="Empfänger/Auftraggeber" value={form.empfaenger_auftraggeber} onChange={e => setForm(f => ({ ...f, empfaenger_auftraggeber: e.target.value }))} />
|
||||
<FormControlLabel
|
||||
control={<Switch checked={form.aktiv !== false} onChange={e => setForm(f => ({ ...f, aktiv: e.target.checked }))} />}
|
||||
label="Aktiv"
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Abbrechen</Button>
|
||||
<Button variant="contained" onClick={() => onSave(form)} disabled={!form.bezeichnung || !form.betrag || !form.naechste_ausfuehrung}>Speichern</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
haushaltsjahre: Haushaltsjahr[];
|
||||
selectedJahrId: number | null;
|
||||
@@ -553,6 +745,7 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
const [kontoDialog, setKontoDialog] = useState<{ open: boolean; existing?: Konto }>({ open: false });
|
||||
const [bankDialog, setBankDialog] = useState<{ open: boolean; existing?: Bankkonto }>({ open: false });
|
||||
const [jahrDialog, setJahrDialog] = useState<{ open: boolean; existing?: Haushaltsjahr }>({ open: false });
|
||||
const [wiederkehrendDialog, setWiederkehrendDialog] = useState<{ open: boolean; existing?: WiederkehrendBuchung }>({ open: false });
|
||||
|
||||
const { data: konten = [] } = useQuery({
|
||||
queryKey: ['buchhaltung-konten', selectedJahrId],
|
||||
@@ -560,6 +753,7 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
enabled: selectedJahrId != null,
|
||||
});
|
||||
const { data: bankkonten = [] } = useQuery({ queryKey: ['bankkonten'], queryFn: buchhaltungApi.getBankkonten });
|
||||
const { data: wiederkehrend = [] } = useQuery({ queryKey: ['buchhaltung-wiederkehrend'], queryFn: buchhaltungApi.getWiederkehrend });
|
||||
|
||||
const canManage = hasPermission('buchhaltung:manage_accounts');
|
||||
|
||||
@@ -611,12 +805,29 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
onError: (e: Error) => showError(e.message || 'Abschluss fehlgeschlagen'),
|
||||
});
|
||||
|
||||
const createWiederkehrendMut = useMutation({
|
||||
mutationFn: buchhaltungApi.createWiederkehrend,
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-wiederkehrend'] }); setWiederkehrendDialog({ open: false }); showSuccess('Wiederkehrende Buchung erstellt'); },
|
||||
onError: () => showError('Erstellen fehlgeschlagen'),
|
||||
});
|
||||
const updateWiederkehrendMut = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<WiederkehrendFormData> }) => buchhaltungApi.updateWiederkehrend(id, data),
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-wiederkehrend'] }); setWiederkehrendDialog({ open: false }); showSuccess('Wiederkehrende Buchung aktualisiert'); },
|
||||
onError: () => showError('Aktualisierung fehlgeschlagen'),
|
||||
});
|
||||
const deleteWiederkehrendMut = useMutation({
|
||||
mutationFn: buchhaltungApi.deleteWiederkehrend,
|
||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-wiederkehrend'] }); showSuccess('Wiederkehrende Buchung gelöscht'); },
|
||||
onError: () => showError('Löschen fehlgeschlagen'),
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Tabs value={subTab} onChange={(_, v) => setSubTab(v)} sx={{ mb: 2 }}>
|
||||
<Tab label="Konten" />
|
||||
<Tab label="Bankkonten" />
|
||||
<Tab label="Haushaltsjahre" />
|
||||
<Tab label="Wiederkehrend" />
|
||||
</Tabs>
|
||||
|
||||
{/* Sub-Tab 0: Konten */}
|
||||
@@ -775,6 +986,64 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Sub-Tab 3: Wiederkehrend */}
|
||||
{subTab === 3 && (
|
||||
<Box>
|
||||
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
{canManage && <Button variant="contained" startIcon={<AddIcon />} onClick={() => setWiederkehrendDialog({ open: true })}>Anlegen</Button>}
|
||||
</Box>
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Bezeichnung</TableCell>
|
||||
<TableCell>Typ</TableCell>
|
||||
<TableCell align="right">Betrag</TableCell>
|
||||
<TableCell>Intervall</TableCell>
|
||||
<TableCell>Nächste Ausführung</TableCell>
|
||||
<TableCell>Aktiv</TableCell>
|
||||
{canManage && <TableCell>Aktionen</TableCell>}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{wiederkehrend.length === 0 && <TableRow><TableCell colSpan={7} align="center"><Typography color="text.secondary">Keine wiederkehrenden Buchungen</Typography></TableCell></TableRow>}
|
||||
{wiederkehrend.map((w: WiederkehrendBuchung) => (
|
||||
<TableRow key={w.id} hover>
|
||||
<TableCell>{w.bezeichnung}</TableCell>
|
||||
<TableCell>
|
||||
<Chip label={TRANSAKTION_TYP_LABELS[w.typ]} size="small" color={w.typ === 'einnahme' ? 'success' : 'error'} />
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: w.typ === 'einnahme' ? 'success.main' : 'error.main', fontWeight: 600 }}>
|
||||
{w.typ === 'ausgabe' ? '-' : '+'}{fmtEur(w.betrag)}
|
||||
</TableCell>
|
||||
<TableCell>{INTERVALL_LABELS[w.intervall]}</TableCell>
|
||||
<TableCell>{fmtDate(w.naechste_ausfuehrung)}</TableCell>
|
||||
<TableCell>{w.aktiv ? <Chip label="Aktiv" size="small" color="success" /> : <Chip label="Inaktiv" size="small" color="default" />}</TableCell>
|
||||
{canManage && (
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => setWiederkehrendDialog({ open: true, existing: w })}><Edit fontSize="small" /></IconButton>
|
||||
<IconButton size="small" color="error" onClick={() => deleteWiederkehrendMut.mutate(w.id)}><Delete fontSize="small" /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<WiederkehrendDialog
|
||||
open={wiederkehrendDialog.open}
|
||||
onClose={() => setWiederkehrendDialog({ open: false })}
|
||||
konten={konten}
|
||||
bankkonten={bankkonten}
|
||||
existing={wiederkehrendDialog.existing}
|
||||
onSave={data => wiederkehrendDialog.existing
|
||||
? updateWiederkehrendMut.mutate({ id: wiederkehrendDialog.existing.id, data })
|
||||
: createWiederkehrendMut.mutate(data)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import AusruestungsanfrageWidget from '../components/dashboard/Ausruestungsanfra
|
||||
import IssueQuickAddWidget from '../components/dashboard/IssueQuickAddWidget';
|
||||
import IssueOverviewWidget from '../components/dashboard/IssueOverviewWidget';
|
||||
import ChecklistWidget from '../components/dashboard/ChecklistWidget';
|
||||
import BuchhaltungWidget from '../components/dashboard/BuchhaltungWidget';
|
||||
import { preferencesApi } from '../services/settings';
|
||||
import { configApi } from '../services/config';
|
||||
import { WidgetKey } from '../constants/widgets';
|
||||
@@ -73,7 +74,7 @@ const GROUP_ORDER: { name: GroupName; title: string }[] = [
|
||||
|
||||
// Default widget order per group (used when no preference is set)
|
||||
const DEFAULT_ORDER: Record<GroupName, string[]> = {
|
||||
status: ['vehicles', 'equipment', 'atemschutz', 'adminStatus', 'bestellungen', 'ausruestungsanfragen', 'issueOverview', 'checklistOverdue'],
|
||||
status: ['vehicles', 'equipment', 'atemschutz', 'adminStatus', 'bestellungen', 'ausruestungsanfragen', 'issueOverview', 'checklistOverdue', 'buchhaltung'],
|
||||
kalender: ['events', 'vehicleBookingList', 'vehicleBooking', 'eventQuickAdd'],
|
||||
dienste: ['bookstackRecent', 'bookstackSearch', 'vikunjaTasks', 'vikunjaQuickAdd', 'issueQuickAdd'],
|
||||
information: ['links', 'bannerWidget'],
|
||||
@@ -120,6 +121,7 @@ function Dashboard() {
|
||||
{ key: 'ausruestungsanfragen', widgetKey: 'ausruestungsanfragen', permission: 'ausruestungsanfrage:widget', component: <AusruestungsanfrageWidget /> },
|
||||
{ key: 'issueOverview', widgetKey: 'issueOverview', permission: 'issues:view_all', component: <IssueOverviewWidget /> },
|
||||
{ key: 'checklistOverdue', widgetKey: 'checklistOverdue', permission: 'checklisten:widget', component: <ChecklistWidget /> },
|
||||
{ key: 'buchhaltung', widgetKey: 'buchhaltung', permission: 'buchhaltung:widget', component: <BuchhaltungWidget /> },
|
||||
],
|
||||
kalender: [
|
||||
{ key: 'events', widgetKey: 'events', permission: 'kalender:view', component: <UpcomingEventsWidget /> },
|
||||
|
||||
Reference in New Issue
Block a user