mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-12 02:38:15 +00:00
176 lines
7.6 KiB
TypeScript
176 lines
7.6 KiB
TypeScript
import { useState } from 'react';
|
|
import { AnimatePresence, motion } from 'motion/react';
|
|
|
|
import { Button } from '@/components/base/buttons/button';
|
|
import { TopBar } from '@/components/layout/top-bar';
|
|
import { KpiCards } from '@/components/leads/kpi-cards';
|
|
import { SourceGrid } from '@/components/leads/source-grid';
|
|
import { LeadCard } from '@/components/leads/lead-card';
|
|
import { AgingWidget } from '@/components/leads/aging-widget';
|
|
import { FollowupWidget } from '@/components/leads/followup-widget';
|
|
import { AlertsWidget } from '@/components/leads/alerts-widget';
|
|
import { AssignModal } from '@/components/modals/assign-modal';
|
|
import { WhatsAppSendModal } from '@/components/modals/whatsapp-send-modal';
|
|
import { MarkSpamModal } from '@/components/modals/mark-spam-modal';
|
|
import { MergeModal } from '@/components/modals/merge-modal';
|
|
import { useLeads } from '@/hooks/use-leads';
|
|
import { useFollowUps } from '@/hooks/use-follow-ups';
|
|
import { useData } from '@/providers/data-provider';
|
|
import type { Lead, LeadSource } from '@/types/entities';
|
|
|
|
export const LeadWorkspacePage = () => {
|
|
const [sourceFilter, setSourceFilter] = useState<LeadSource | null>(null);
|
|
|
|
const { leads, total, updateLead } = useLeads({ source: sourceFilter ?? undefined, status: 'NEW' });
|
|
const { leads: allLeads } = useLeads();
|
|
const { overdue, upcoming } = useFollowUps();
|
|
const { agents, templates } = useData();
|
|
|
|
const displayLeads = leads.slice(0, 10);
|
|
|
|
// Modal state
|
|
const [isAssignOpen, setIsAssignOpen] = useState(false);
|
|
const [isWhatsAppOpen, setIsWhatsAppOpen] = useState(false);
|
|
const [isSpamOpen, setIsSpamOpen] = useState(false);
|
|
const [isMergeOpen, setIsMergeOpen] = useState(false);
|
|
const [targetLead, setTargetLead] = useState<Lead | null>(null);
|
|
|
|
const handleAssign = (lead: Lead) => {
|
|
setTargetLead(lead);
|
|
setIsAssignOpen(true);
|
|
};
|
|
|
|
const handleMessage = (lead: Lead) => {
|
|
setTargetLead(lead);
|
|
setIsWhatsAppOpen(true);
|
|
};
|
|
|
|
const handleMarkSpam = (lead: Lead) => {
|
|
setTargetLead(lead);
|
|
setIsSpamOpen(true);
|
|
};
|
|
|
|
const handleMerge = (lead: Lead) => {
|
|
setTargetLead(lead);
|
|
setIsMergeOpen(true);
|
|
};
|
|
|
|
const handleLogCall = () => {
|
|
// placeholder
|
|
};
|
|
|
|
const handleUpdateStatus = () => {
|
|
// placeholder
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-1 flex-col">
|
|
<TopBar title="Lead Workspace" subtitle="Global Hospital · Last 24 hours" />
|
|
<div className="flex flex-1 overflow-hidden">
|
|
{/* Main content */}
|
|
<div className="flex-1 space-y-6 overflow-y-auto p-7">
|
|
<KpiCards leads={allLeads} />
|
|
|
|
<div>
|
|
<div className="mb-3 flex items-center justify-between">
|
|
<h2 className="font-display text-md font-bold text-primary">Lead Sources</h2>
|
|
<span className="text-xs text-quaternary">Click to filter</span>
|
|
</div>
|
|
<SourceGrid leads={allLeads} onSourceFilter={setSourceFilter} activeSource={sourceFilter} />
|
|
</div>
|
|
|
|
<div>
|
|
<div className="mb-3.5 flex items-center justify-between">
|
|
<h2 className="font-display text-md font-bold text-primary">New Leads</h2>
|
|
<Button
|
|
href={sourceFilter ? `/leads?source=${sourceFilter}` : '/leads'}
|
|
color="link-color"
|
|
size="sm"
|
|
>
|
|
View All {total} Leads →
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<AnimatePresence>
|
|
{displayLeads.map((lead) => (
|
|
<motion.div
|
|
key={lead.id}
|
|
initial={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -20, height: 0, marginBottom: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<LeadCard
|
|
lead={lead}
|
|
onAssign={handleAssign}
|
|
onMessage={handleMessage}
|
|
onMarkSpam={handleMarkSpam}
|
|
onMerge={handleMerge}
|
|
onLogCall={handleLogCall}
|
|
onUpdateStatus={handleUpdateStatus}
|
|
/>
|
|
</motion.div>
|
|
))}
|
|
</AnimatePresence>
|
|
{displayLeads.length === 0 && (
|
|
<p className="py-8 text-center text-sm text-tertiary">
|
|
No leads match the current filters.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right sidebar */}
|
|
<aside className="hidden w-80 space-y-5 overflow-y-auto border-l border-secondary bg-primary p-5 xl:block">
|
|
<AgingWidget leads={allLeads} />
|
|
<FollowupWidget overdue={overdue} upcoming={upcoming} />
|
|
<AlertsWidget leads={allLeads} />
|
|
</aside>
|
|
</div>
|
|
|
|
{/* Modals */}
|
|
{targetLead && (
|
|
<>
|
|
<AssignModal
|
|
isOpen={isAssignOpen}
|
|
onOpenChange={setIsAssignOpen}
|
|
selectedLeads={[targetLead]}
|
|
agents={agents}
|
|
onAssign={(agentId) => {
|
|
updateLead(targetLead.id, {
|
|
assignedAgent: agents.find((a) => a.id === agentId)?.name ?? null,
|
|
leadStatus: 'CONTACTED',
|
|
});
|
|
setIsAssignOpen(false);
|
|
}}
|
|
/>
|
|
<WhatsAppSendModal
|
|
isOpen={isWhatsAppOpen}
|
|
onOpenChange={setIsWhatsAppOpen}
|
|
selectedLeads={[targetLead]}
|
|
templates={templates.filter((t) => t.approvalStatus === 'APPROVED')}
|
|
onSend={() => setIsWhatsAppOpen(false)}
|
|
/>
|
|
<MarkSpamModal
|
|
isOpen={isSpamOpen}
|
|
onOpenChange={setIsSpamOpen}
|
|
lead={targetLead}
|
|
onConfirm={() => {
|
|
updateLead(targetLead.id, { isSpam: true, leadStatus: 'LOST' });
|
|
setIsSpamOpen(false);
|
|
}}
|
|
/>
|
|
<MergeModal
|
|
isOpen={isMergeOpen}
|
|
onOpenChange={setIsMergeOpen}
|
|
primaryLead={targetLead}
|
|
duplicateLead={allLeads.find((l) => l.id === targetLead.duplicateOfLeadId) ?? targetLead}
|
|
onMerge={() => setIsMergeOpen(false)}
|
|
onKeepSeparate={() => setIsMergeOpen(false)}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|