feat(splits): save split as rule from split modal
- Checkbox in split modal: 'Also save as rule for <merchant>' - Creates a rule with apply_split action storing the participant shares - Rules engine now handles apply_split: deletes existing splits and re-applies - Bulk split mode hides the checkbox (rule wouldn't make sense for ad-hoc bulk)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParticipants, useSetSplits, useTransactionSplits, useBulkAction } from "@/lib/hooks";
|
||||
import { useParticipants, useSetSplits, useTransactionSplits, useBulkAction, useCreateRule } from "@/lib/hooks";
|
||||
|
||||
interface Split {
|
||||
participant_id: number;
|
||||
@@ -13,10 +13,11 @@ interface Props {
|
||||
transactionIds?: number[];
|
||||
amount?: number;
|
||||
description: string;
|
||||
merchant?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function SplitModal({ transactionId, transactionIds, amount, description, onClose }: Props) {
|
||||
export function SplitModal({ transactionId, transactionIds, amount, description, merchant, onClose }: Props) {
|
||||
const isBulk = !!transactionIds && transactionIds.length > 0;
|
||||
const singleId = transactionId ?? 0;
|
||||
|
||||
@@ -24,9 +25,12 @@ export function SplitModal({ transactionId, transactionIds, amount, description,
|
||||
const { data: existingSplits } = useTransactionSplits(isBulk ? 0 : singleId);
|
||||
const setSplits = useSetSplits();
|
||||
const bulkAction = useBulkAction();
|
||||
const createRule = useCreateRule();
|
||||
|
||||
const [splits, setSplitsState] = useState<Split[]>([]);
|
||||
const [error, setError] = useState("");
|
||||
const [saveAsRule, setSaveAsRule] = useState(false);
|
||||
const [ruleSaved, setRuleSaved] = useState(false);
|
||||
|
||||
// Initialise: bulk always defaults to 100% Me; single loads existing splits
|
||||
useEffect(() => {
|
||||
@@ -94,7 +98,20 @@ export function SplitModal({ transactionId, transactionIds, amount, description,
|
||||
} else {
|
||||
await setSplits.mutateAsync({ transactionId: singleId, splits });
|
||||
}
|
||||
onClose();
|
||||
if (saveAsRule && !isBulk) {
|
||||
const matchValue = merchant || (description.split(" ")[0] ?? description);
|
||||
await createRule.mutateAsync({
|
||||
name: `Split: ${merchant || description}`,
|
||||
conditions: [{ field: "merchant_normalized", operator: "contains", value: matchValue }],
|
||||
actions: { apply_split: splits },
|
||||
enabled: true,
|
||||
priority: 0,
|
||||
});
|
||||
setRuleSaved(true);
|
||||
setTimeout(onClose, 1200);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Failed to save splits");
|
||||
}
|
||||
@@ -174,6 +191,22 @@ export function SplitModal({ transactionId, transactionIds, amount, description,
|
||||
|
||||
{error && <p className="text-red-400 text-sm mb-3">{error}</p>}
|
||||
|
||||
{ruleSaved && (
|
||||
<p className="text-green-400 text-sm mb-3">Rule saved — future matching transactions will be split the same way.</p>
|
||||
)}
|
||||
|
||||
{!isBulk && (
|
||||
<label className="flex items-center gap-2 text-sm text-zinc-400 mb-3 cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={saveAsRule}
|
||||
onChange={(e) => setSaveAsRule(e.target.checked)}
|
||||
className="accent-blue-500"
|
||||
/>
|
||||
Also save as rule for <span className="text-zinc-200 font-medium">{merchant || description.split(" ")[0]}</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
|
||||
Reference in New Issue
Block a user