mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
Contacts page:
- New /contacts route — shows leads with source=PHONE/WALK_IN/REFERRAL
- Leads page now excludes those sources (campaign-sourced only)
- Sidebar: Contacts nav item added for all roles; Leads added for cc-agent
- Same LeadTable + pagination + CSV export pattern as All Leads
P360 context panel for all worklist tabs (#6-10):
- WorklistPanel: onSelectLead → onSelectItem (generic WorklistSelection)
- call-desk: handleSelectItem builds ContextPanelSubject for any row type
- ContextPanelSubject type replaces (lead as any).patientId casts
- Highlight tracks row.id (mc-*/fu-*/lead-*) not lead.id
Dynamic column toggle (blank-screen fix):
- missed-calls + call-recordings refactored to React Aria dynamic
collections API (Table.Header columns={} + Table.Row columns={})
- Fixes "Cell count must match column count" crash on column hide
- Row-header column metadata in columnDefs instead of hardcoded JSX
Slot flickering fix (#2):
- Removed clinic + timeSlot from slot-fetch useEffect deps (circular
loop: effect sets clinic → clinic in deps → re-fires)
- Memoized timeSlotSelectItems
Other:
- GlobalSearch hidden (stale appointment state on navigation)
- Branch column: shows campaign name from relation, falls back to DID
- formatSource maps PHONE → "Phone"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { useMemo } from 'react';
|
|
|
|
import type { Lead, LeadSource, LeadStatus } from '@/types/entities';
|
|
import { useData } from '@/providers/data-provider';
|
|
|
|
type UseLeadsFilters = {
|
|
source?: LeadSource;
|
|
excludeSources?: Set<LeadSource>;
|
|
status?: LeadStatus;
|
|
search?: string;
|
|
};
|
|
|
|
type UseLeadsResult = {
|
|
leads: Lead[];
|
|
total: number;
|
|
updateLead: (id: string, updates: Partial<Lead>) => void;
|
|
};
|
|
|
|
export const useLeads = (filters: UseLeadsFilters = {}): UseLeadsResult => {
|
|
const { leads, updateLead } = useData();
|
|
const { source, excludeSources, status, search } = filters;
|
|
|
|
const filteredLeads = useMemo(() => {
|
|
return leads.filter((lead) => {
|
|
if (source !== undefined && lead.leadSource !== source) {
|
|
return false;
|
|
}
|
|
|
|
if (excludeSources && lead.leadSource && excludeSources.has(lead.leadSource)) {
|
|
return false;
|
|
}
|
|
|
|
if (status !== undefined && lead.leadStatus !== status) {
|
|
return false;
|
|
}
|
|
|
|
if (search !== undefined && search.trim() !== '') {
|
|
const query = search.trim().toLowerCase();
|
|
const firstName = lead.contactName?.firstName?.toLowerCase() ?? '';
|
|
const lastName = lead.contactName?.lastName?.toLowerCase() ?? '';
|
|
const fullName = `${firstName} ${lastName}`.trim();
|
|
const phones = (lead.contactPhone ?? []).map((p) => p.number.toLowerCase());
|
|
|
|
const matchesName = firstName.includes(query) || lastName.includes(query) || fullName.includes(query);
|
|
const matchesPhone = phones.some((phone) => phone.includes(query));
|
|
|
|
if (!matchesName && !matchesPhone) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}, [leads, source, excludeSources, status, search]);
|
|
|
|
return {
|
|
leads: filteredLeads,
|
|
total: filteredLeads.length,
|
|
updateLead,
|
|
};
|
|
};
|