86 lines
3.1 KiB
JavaScript
86 lines
3.1 KiB
JavaScript
// RulesScreen — promote-suggested-rules view
|
|
|
|
function RuleCard({ rule, checked, onToggle, onApply }) {
|
|
return (
|
|
<div className="rule-card">
|
|
<div className="rule-head">
|
|
<div className="rule-pattern">
|
|
<span className="pattern-prefix">payee ~=</span>
|
|
<code className="ko">"{rule.pattern}"</code>
|
|
</div>
|
|
<div className="rule-occ">
|
|
<span className="rule-num">{rule.occurrences}</span>
|
|
<span className="rule-occ-lbl">OCCURRENCES · LAST 30d</span>
|
|
</div>
|
|
<ConfidenceChip score={rule.score} tier="llm" />
|
|
</div>
|
|
<div className="rule-map">
|
|
<span className="ko">{rule.pattern}</span>
|
|
<Icon name="arrowRight" size={14} color="var(--fg-muted)" />
|
|
<span className="rule-target">{rule.target}</span>
|
|
</div>
|
|
<div className="rule-examples">
|
|
<div className="rule-ex-label">MATCHING TRANSACTIONS</div>
|
|
{rule.examples.map((ex, i) => <div key={i} className="rule-ex">{ex}</div>)}
|
|
</div>
|
|
<div className="rule-actions">
|
|
<label className="rule-promote">
|
|
<input type="checkbox" checked={checked} onChange={onToggle} />
|
|
<span>Promote on next run</span>
|
|
</label>
|
|
<div style={{ flex: 1 }}></div>
|
|
<Btn variant="primary" onClick={onApply}>APPLY RULE</Btn>
|
|
<Btn>DISMISS</Btn>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function RulesScreen({ entity }) {
|
|
const data = window.AKEFIN_DATA.ruleSuggestions;
|
|
const [promoted, setPromoted] = React.useState(new Set(data.map(r => r.id)));
|
|
const [applied, setApplied] = React.useState(new Set());
|
|
|
|
const toggle = (id) => {
|
|
setPromoted(prev => {
|
|
const next = new Set(prev);
|
|
next.has(id) ? next.delete(id) : next.add(id);
|
|
return next;
|
|
});
|
|
};
|
|
const apply = (id) => setApplied(prev => new Set([...prev, id]));
|
|
|
|
const visible = data.filter(r => !applied.has(r.id));
|
|
|
|
return (
|
|
<div className="rules-screen">
|
|
<div className="screen-head">
|
|
<div>
|
|
<h1 className="screen-title">Rule suggestions</h1>
|
|
<div className="screen-sub">{visible.length} suggested · {promoted.size} promoted on next run · last AI pass 12 minutes ago</div>
|
|
</div>
|
|
<div className="screen-actions">
|
|
<Btn><Icon name="refresh" size={13} /> RE-RUN AI PASS</Btn>
|
|
<Btn variant="primary">PROMOTE ALL</Btn>
|
|
</div>
|
|
</div>
|
|
|
|
<TermFrame title="AKEFIN — RULE PIPELINE · TIER 1" status="ok">
|
|
<span className="m">$</span> akefin rules:suggest --entity {entity === "all" ? "*" : entity} --since 30d{"\n"}
|
|
<span className="m">analysed 1,247 transactions · 5 high-frequency patterns matched ·</span>
|
|
<span style={{color:"var(--conf-rules)"}}> 218 future rows would auto-categorize</span>
|
|
</TermFrame>
|
|
|
|
<div className="rule-grid">
|
|
{visible.map(r => (
|
|
<RuleCard key={r.id} rule={r}
|
|
checked={promoted.has(r.id)}
|
|
onToggle={() => toggle(r.id)}
|
|
onApply={() => apply(r.id)} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
Object.assign(window, { RulesScreen });
|