feat(settings): a recurring cost's splits are editable

The owner's first report: a cost's post was fixed at creation and could not be
changed. RecurringCost has carried splits and splitMode with the same shape as
Purchase since the first version, so this wires up a capability the data model
already had rather than adding one — and a bill can now span envelopes.

SplitEditor is narrowed from PurchaseDraft to the four fields it actually
reads. PurchaseDraft satisfies that structurally, so the dialog's call site
does not change at all.

The anchoring control is disabled once a cost has charges, matching the store's
refusal rather than offering an action that would fail on save.

Also fixes a carried item: the anchoring handler now expands a month-only
startDate in the same mutate call that switches to lastCharge anchoring,
matching the normalisation the recurrence-kind handler already had — an
everyNMonths cost anchored to lastCharge needs a day-granular startDate, and
the store validates that pairing on every write.
This commit is contained in:
Lucas Winther
2026-09-05 23:18:53 +02:00
parent 61e3773733
commit ccde30b8ea
4 changed files with 211 additions and 19 deletions
+5 -5
View File
@@ -5,17 +5,17 @@ import { NativeSelect } from "@/components/ui/native-select";
import { Label } from "@/components/ui/label";
import { Segmented } from "./Segmented.tsx";
import { useMoneyFormat } from "../hooks/useMoneyFormat.ts";
import type { PurchaseDraft, SplitDraft } from "../purchaseForm.ts";
import type { SplitDraft, SplitTarget } from "../purchaseForm.ts";
import { splitBalance } from "../purchaseForm.ts";
import type { Post } from "../../domain/types.ts";
interface Props {
draft: PurchaseDraft;
interface Props<T extends SplitTarget> {
draft: T;
posts: Post[];
onChange: (next: PurchaseDraft) => void;
onChange: (next: T) => void;
}
export function SplitEditor({ draft, posts, onChange }: Props) {
export function SplitEditor<T extends SplitTarget>({ draft, posts, onChange }: Props<T>) {
const balance = splitBalance(draft, useDataset().settings.digits);
const fmt = useMoneyFormat();
const unit = draft.splitMode === "percent" ? "%" : draft.currency;