feat(rules): preview what a rule would change before applying it
ci / lint-test (push) Successful in 34s
ci / lint-test (push) Successful in 34s
Selecting a rule now shows the transactions it would alter, so a subset can be ticked and applied rather than trusting a bulk run. The apply step takes explicit transaction ids (the existing bulk apply_rule path), so what you tick is exactly what changes - a rule whose conditions are too broad cannot reach further than the preview showed. Matches are split into 'would change' and 'already correct'. A merchant normalisation rule matching 400 rows where 380 already hold the right value is 20 changes and 380 rows of noise; only the 20 are listed. Preview is offered for every rule including manual_only quick actions, which previously had no way to see their reach at all. A rule with no conditions matches every transaction - that is how apply already behaves, so the preview reports it prominently rather than hiding it. Applies still snapshot to rule_apply_runs, so they remain revertable.
This commit is contained in:
+21
-3
@@ -3,6 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { useRules, useCreateRule, useUpdateRule, useDeleteRule, useApplyRules, useRuleRuns, useRevertRuleRun, useTags, useParticipants } from "@/lib/hooks";
|
||||
import { CATEGORIES, formatCategory } from "@/lib/categories";
|
||||
import { RulePreviewModal } from "@/components/rule-preview-modal";
|
||||
|
||||
const FIELDS = [
|
||||
{ value: "merchant_normalized", label: "Merchant" },
|
||||
@@ -82,6 +83,7 @@ export default function RulesPage() {
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingId, setEditingId] = useState<number | null>(null);
|
||||
const [applyResult, setApplyResult] = useState<{ matched: number; transactions_affected: number } | null>(null);
|
||||
const [preview, setPreview] = useState<{ id: number; name: string } | null>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [conditions, setConditions] = useState<Condition[]>([]);
|
||||
const [actions, setActions] = useState<Actions>(EMPTY_ACTIONS);
|
||||
@@ -509,9 +511,17 @@ export default function RulesPage() {
|
||||
<p className="text-xs text-zinc-500 mt-1">{humanAction(acts, tagNames, participantNames)}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
{/* No Apply for quick actions: their conditions are empty, so a
|
||||
bulk apply would hit every transaction. They run from the
|
||||
transactions page against a selection instead. */}
|
||||
{/* Preview is safe for every rule, including quick actions:
|
||||
it writes nothing, and the apply step takes the ids you
|
||||
tick rather than re-running the conditions. */}
|
||||
<button
|
||||
onClick={() => setPreview({ id: rule.id, name: rule.name })}
|
||||
className="text-xs text-indigo-400 hover:text-indigo-300"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
{/* No blind Apply for quick actions: their conditions are
|
||||
empty, so a bulk apply would hit every transaction. */}
|
||||
{!rule.manual_only && (
|
||||
<button
|
||||
onClick={() => handleApply(rule.id)}
|
||||
@@ -554,6 +564,14 @@ export default function RulesPage() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{preview && (
|
||||
<RulePreviewModal
|
||||
ruleId={preview.id}
|
||||
ruleName={preview.name}
|
||||
onClose={() => setPreview(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user