mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
feat: rules engine — priority config UI + worklist scoring
- Rules engine spec v2 (priority vs automation rules distinction) - Priority Rules settings page with weight sliders, SLA config, campaign/source weights - Collapsible config sections with dynamic headers - Live worklist preview panel with client-side scoring - AI assistant panel (collapsible) with rules-engine-specific system prompt - Worklist panel: score display with SLA status dots, sort by score - Scoring library (scoring.ts) for client-side preview computation - Sidebar: Rules Engine nav item under Configuration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
src/components/rules/source-weights-panel.tsx
Normal file
48
src/components/rules/source-weights-panel.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useMemo } from 'react';
|
||||
import { WeightSliderRow } from './weight-slider-row';
|
||||
import { CollapsibleSection } from './collapsible-section';
|
||||
import { SOURCE_LABELS } from '@/lib/scoring';
|
||||
import type { PriorityConfig } from '@/lib/scoring';
|
||||
|
||||
interface SourceWeightsPanelProps {
|
||||
config: PriorityConfig;
|
||||
onChange: (config: PriorityConfig) => void;
|
||||
}
|
||||
|
||||
const SOURCE_ORDER = ['WHATSAPP', 'PHONE', 'FACEBOOK_AD', 'GOOGLE_AD', 'INSTAGRAM', 'WEBSITE', 'REFERRAL', 'WALK_IN', 'OTHER'];
|
||||
|
||||
export const SourceWeightsPanel = ({ config, onChange }: SourceWeightsPanelProps) => {
|
||||
const updateSource = (source: string, weight: number) => {
|
||||
onChange({
|
||||
...config,
|
||||
sourceWeights: { ...config.sourceWeights, [source]: weight },
|
||||
});
|
||||
};
|
||||
|
||||
const badge = useMemo(() => {
|
||||
const weights = SOURCE_ORDER.map(s => config.sourceWeights[s] ?? 5);
|
||||
const avg = weights.reduce((a, b) => a + b, 0) / weights.length;
|
||||
const highest = SOURCE_ORDER.reduce((best, s) => (config.sourceWeights[s] ?? 5) > (config.sourceWeights[best] ?? 5) ? s : best, SOURCE_ORDER[0]);
|
||||
return `Avg ${avg.toFixed(1)} · Top: ${SOURCE_LABELS[highest]}`;
|
||||
}, [config.sourceWeights]);
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
title="Source Weights"
|
||||
subtitle="Leads from higher-weighted sources get priority"
|
||||
badge={badge}
|
||||
defaultOpen={false}
|
||||
>
|
||||
<div className="divide-y divide-tertiary">
|
||||
{SOURCE_ORDER.map(source => (
|
||||
<WeightSliderRow
|
||||
key={source}
|
||||
label={SOURCE_LABELS[source] ?? source}
|
||||
weight={config.sourceWeights[source] ?? 5}
|
||||
onWeightChange={(w) => updateSource(source, w)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user