Stichtag Timeline fuer Ausgaben und Finanzuebersicht
CI / Build and Deploy (push) Successful in 2m58s

This commit is contained in:
jan
2026-05-12 01:53:57 +02:00
parent 5591d10d96
commit cee7081da6
2 changed files with 252 additions and 129 deletions
+72 -35
View File
@@ -51,6 +51,13 @@ import {
requiresManualApproval
} from "@/lib/domain";
type CutoffSelectionOption = {
value: string;
cutoffId: string;
cutoffPhase: "PRE" | "POST";
label: string;
};
type BudgetColumnProps = {
group: DashboardWorkingGroup;
workingGroups: DashboardWorkingGroup[];
@@ -141,6 +148,42 @@ function createDraft(budget: DashboardBudget): BudgetDraft {
};
}
function createCutoffSelectionValue(cutoffId: string, cutoffPhase: "PRE" | "POST") {
return `${cutoffId}:${cutoffPhase}`;
}
function parseCutoffSelectionValue(value: string) {
const [cutoffId, cutoffPhase] = value.split(":");
return {
cutoffId: cutoffId ?? "",
cutoffPhase: cutoffPhase === "POST" ? "POST" : "PRE"
} as const;
}
function getCutoffSelectionOptions(cutoffs: DashboardPeriodCutoff[]): CutoffSelectionOption[] {
if (cutoffs.length === 0) {
return [];
}
const options: CutoffSelectionOption[] = cutoffs.map((cutoff) => ({
value: createCutoffSelectionValue(cutoff.id, "PRE"),
cutoffId: cutoff.id,
cutoffPhase: "PRE" as const,
label: `Pre ${cutoff.name}`
}));
const lastCutoff = cutoffs[cutoffs.length - 1];
options.push({
value: createCutoffSelectionValue(lastCutoff.id, "POST"),
cutoffId: lastCutoff.id,
cutoffPhase: "POST" as const,
label: `Post ${lastCutoff.name}`
});
return options;
}
function StatusChips({
expense,
canEditDonations,
@@ -1078,8 +1121,12 @@ export function BudgetColumn({
const editGroup =
workingGroups.find((entry) => entry.id === draft.agId) ?? workingGroups[0] ?? group;
const editBudgets = editGroup.budgets;
const selectedCutoff =
cutoffs.find((cutoff) => cutoff.id === draft.cutoffId) ?? cutoffs[0] ?? null;
const cutoffOptions = getCutoffSelectionOptions(cutoffs);
const selectedCutoffValue = createCutoffSelectionValue(draft.cutoffId, draft.cutoffPhase);
const selectedCutoffOption =
cutoffOptions.find((option) => option.value === selectedCutoffValue) ??
cutoffOptions[0] ??
null;
return (
<Stack spacing={1}>
@@ -1143,38 +1190,24 @@ export function BudgetColumn({
</MenuItem>
))}
</TextField>
<Stack direction={{ xs: "column", sm: "row" }} gap={1}>
<TextField
select
label="Stichtag"
size="small"
value={draft.cutoffId}
onChange={(event) => updateExpenseDraft(expense, { cutoffId: event.target.value })}
fullWidth
disabled={cutoffs.length === 0}
>
{cutoffs.map((cutoff) => (
<MenuItem key={cutoff.id} value={cutoff.id}>
{cutoff.name}
</MenuItem>
))}
</TextField>
<TextField
select
label="Zuordnung"
size="small"
value={draft.cutoffPhase}
onChange={(event) =>
updateExpenseDraft(expense, {
cutoffPhase: event.target.value as "PRE" | "POST"
})
}
fullWidth
>
<MenuItem value="PRE">{`Pre ${selectedCutoff?.name ?? "Open Air"}`}</MenuItem>
<MenuItem value="POST">{`Post ${selectedCutoff?.name ?? "Open Air"}`}</MenuItem>
</TextField>
</Stack>
<TextField
select
label="Stichtag-Zuordnung"
size="small"
value={selectedCutoffOption?.value ?? ""}
onChange={(event) => {
const next = parseCutoffSelectionValue(event.target.value);
updateExpenseDraft(expense, next);
}}
fullWidth
disabled={cutoffOptions.length === 0}
>
{cutoffOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Stack direction="row" gap={1} useFlexGap flexWrap="wrap">
<Button
type="button"
@@ -1182,7 +1215,11 @@ export function BudgetColumn({
variant="contained"
disabled={busy || !draft.budgetId}
onClick={async () => {
await onUpdateExpense(expense.id, draft);
await onUpdateExpense(expense.id, {
...draft,
cutoffId: selectedCutoffOption?.cutoffId ?? draft.cutoffId,
cutoffPhase: selectedCutoffOption?.cutoffPhase ?? draft.cutoffPhase
});
setEditingExpenseId(null);
}}
>