mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: wire all modals and lead activity slideout into Lead Workspace and All Leads pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ type LeadTableProps = {
|
|||||||
sortField: string;
|
sortField: string;
|
||||||
sortDirection: 'asc' | 'desc';
|
sortDirection: 'asc' | 'desc';
|
||||||
onSort: (field: string) => void;
|
onSort: (field: string) => void;
|
||||||
|
onViewActivity?: (lead: Lead) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TableRow = {
|
type TableRow = {
|
||||||
@@ -47,6 +48,7 @@ export const LeadTable = ({
|
|||||||
sortField,
|
sortField,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
onSort,
|
onSort,
|
||||||
|
onViewActivity,
|
||||||
}: LeadTableProps) => {
|
}: LeadTableProps) => {
|
||||||
const [expandedDupId, setExpandedDupId] = useState<string | null>(null);
|
const [expandedDupId, setExpandedDupId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -298,6 +300,7 @@ export const LeadTable = ({
|
|||||||
color="tertiary"
|
color="tertiary"
|
||||||
iconLeading={DotsVertical}
|
iconLeading={DotsVertical}
|
||||||
aria-label="Row actions"
|
aria-label="Row actions"
|
||||||
|
onClick={() => onViewActivity?.(lead)}
|
||||||
/>
|
/>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
|
|||||||
@@ -8,9 +8,14 @@ import { TopBar } from '@/components/layout/top-bar';
|
|||||||
import { LeadTable } from '@/components/leads/lead-table';
|
import { LeadTable } from '@/components/leads/lead-table';
|
||||||
import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
||||||
import { FilterPills } from '@/components/leads/filter-pills';
|
import { FilterPills } from '@/components/leads/filter-pills';
|
||||||
|
import { AssignModal } from '@/components/modals/assign-modal';
|
||||||
|
import { WhatsAppSendModal } from '@/components/modals/whatsapp-send-modal';
|
||||||
|
import { MarkSpamModal } from '@/components/modals/mark-spam-modal';
|
||||||
|
import { LeadActivitySlideout } from '@/components/leads/lead-activity-slideout';
|
||||||
import { useLeads } from '@/hooks/use-leads';
|
import { useLeads } from '@/hooks/use-leads';
|
||||||
import { useAuth } from '@/providers/auth-provider';
|
import { useAuth } from '@/providers/auth-provider';
|
||||||
import type { LeadSource, LeadStatus } from '@/types/entities';
|
import { useData } from '@/providers/data-provider';
|
||||||
|
import type { Lead, LeadSource, LeadStatus } from '@/types/entities';
|
||||||
|
|
||||||
type TabKey = 'new' | 'my-leads' | 'all';
|
type TabKey = 'new' | 'my-leads' | 'all';
|
||||||
|
|
||||||
@@ -35,12 +40,14 @@ export const AllLeadsPage = () => {
|
|||||||
const statusFilter: LeadStatus | undefined = tab === 'new' ? 'NEW' : undefined;
|
const statusFilter: LeadStatus | undefined = tab === 'new' ? 'NEW' : undefined;
|
||||||
const myLeadsOnly = tab === 'my-leads';
|
const myLeadsOnly = tab === 'my-leads';
|
||||||
|
|
||||||
const { leads: filteredLeads, total } = useLeads({
|
const { leads: filteredLeads, total, updateLead } = useLeads({
|
||||||
source: sourceFilter ?? undefined,
|
source: sourceFilter ?? undefined,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
search: searchQuery || undefined,
|
search: searchQuery || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { agents, templates, leadActivities } = useData();
|
||||||
|
|
||||||
// Client-side sorting
|
// Client-side sorting
|
||||||
const sortedLeads = useMemo(() => {
|
const sortedLeads = useMemo(() => {
|
||||||
const sorted = [...filteredLeads];
|
const sorted = [...filteredLeads];
|
||||||
@@ -158,6 +165,29 @@ export const AllLeadsPage = () => {
|
|||||||
{ id: 'all', label: 'All Leads', badge: tab === 'all' ? total : undefined },
|
{ id: 'all', label: 'All Leads', badge: tab === 'all' ? total : undefined },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Bulk action modal state
|
||||||
|
const [isAssignOpen, setIsAssignOpen] = useState(false);
|
||||||
|
const [isWhatsAppOpen, setIsWhatsAppOpen] = useState(false);
|
||||||
|
const [isSpamOpen, setIsSpamOpen] = useState(false);
|
||||||
|
|
||||||
|
const selectedLeadsForAction = useMemo(
|
||||||
|
() => displayLeads.filter((l) => selectedIds.includes(l.id)),
|
||||||
|
[displayLeads, selectedIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBulkAssign = () => setIsAssignOpen(true);
|
||||||
|
const handleBulkWhatsApp = () => setIsWhatsAppOpen(true);
|
||||||
|
const handleBulkSpam = () => setIsSpamOpen(true);
|
||||||
|
|
||||||
|
// Activity slideout state
|
||||||
|
const [activityLead, setActivityLead] = useState<Lead | null>(null);
|
||||||
|
const [isActivityOpen, setIsActivityOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleViewActivity = (lead: Lead) => {
|
||||||
|
setActivityLead(lead);
|
||||||
|
setIsActivityOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
<TopBar title="All Leads" subtitle={`${total} total`} />
|
<TopBar title="All Leads" subtitle={`${total} total`} />
|
||||||
@@ -237,9 +267,9 @@ export const AllLeadsPage = () => {
|
|||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<BulkActionBar
|
<BulkActionBar
|
||||||
selectedCount={selectedIds.length}
|
selectedCount={selectedIds.length}
|
||||||
onAssign={() => {}}
|
onAssign={handleBulkAssign}
|
||||||
onWhatsApp={() => {}}
|
onWhatsApp={handleBulkWhatsApp}
|
||||||
onMarkSpam={() => {}}
|
onMarkSpam={handleBulkSpam}
|
||||||
onDeselect={() => setSelectedIds([])}
|
onDeselect={() => setSelectedIds([])}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -254,6 +284,7 @@ export const AllLeadsPage = () => {
|
|||||||
sortField={sortField}
|
sortField={sortField}
|
||||||
sortDirection={sortDirection}
|
sortDirection={sortDirection}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
|
onViewActivity={handleViewActivity}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -268,6 +299,62 @@ export const AllLeadsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Bulk action modals */}
|
||||||
|
{selectedLeadsForAction.length > 0 && (
|
||||||
|
<>
|
||||||
|
<AssignModal
|
||||||
|
isOpen={isAssignOpen}
|
||||||
|
onOpenChange={setIsAssignOpen}
|
||||||
|
selectedLeads={selectedLeadsForAction}
|
||||||
|
agents={agents}
|
||||||
|
onAssign={(agentId) => {
|
||||||
|
const agentName = agents.find((a) => a.id === agentId)?.name ?? null;
|
||||||
|
selectedIds.forEach((id) => {
|
||||||
|
updateLead(id, { assignedAgent: agentName, leadStatus: 'CONTACTED' });
|
||||||
|
});
|
||||||
|
setIsAssignOpen(false);
|
||||||
|
setSelectedIds([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<WhatsAppSendModal
|
||||||
|
isOpen={isWhatsAppOpen}
|
||||||
|
onOpenChange={setIsWhatsAppOpen}
|
||||||
|
selectedLeads={selectedLeadsForAction}
|
||||||
|
templates={templates.filter((t) => t.approvalStatus === 'APPROVED')}
|
||||||
|
onSend={() => {
|
||||||
|
setIsWhatsAppOpen(false);
|
||||||
|
setSelectedIds([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bulk spam: use first selected lead for the single-lead MarkSpamModal */}
|
||||||
|
{selectedLeadsForAction.length > 0 && selectedLeadsForAction[0] && (
|
||||||
|
<MarkSpamModal
|
||||||
|
isOpen={isSpamOpen}
|
||||||
|
onOpenChange={setIsSpamOpen}
|
||||||
|
lead={selectedLeadsForAction[0]}
|
||||||
|
onConfirm={() => {
|
||||||
|
selectedIds.forEach((id) => {
|
||||||
|
updateLead(id, { isSpam: true, leadStatus: 'LOST' });
|
||||||
|
});
|
||||||
|
setIsSpamOpen(false);
|
||||||
|
setSelectedIds([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Activity slideout */}
|
||||||
|
{activityLead && (
|
||||||
|
<LeadActivitySlideout
|
||||||
|
isOpen={isActivityOpen}
|
||||||
|
onOpenChange={setIsActivityOpen}
|
||||||
|
lead={activityLead}
|
||||||
|
activities={leadActivities}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,33 +8,50 @@ import { LeadCard } from '@/components/leads/lead-card';
|
|||||||
import { AgingWidget } from '@/components/leads/aging-widget';
|
import { AgingWidget } from '@/components/leads/aging-widget';
|
||||||
import { FollowupWidget } from '@/components/leads/followup-widget';
|
import { FollowupWidget } from '@/components/leads/followup-widget';
|
||||||
import { AlertsWidget } from '@/components/leads/alerts-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 { useLeads } from '@/hooks/use-leads';
|
||||||
import { useFollowUps } from '@/hooks/use-follow-ups';
|
import { useFollowUps } from '@/hooks/use-follow-ups';
|
||||||
import type { LeadSource } from '@/types/entities';
|
import { useData } from '@/providers/data-provider';
|
||||||
|
import type { Lead, LeadSource } from '@/types/entities';
|
||||||
|
|
||||||
export const LeadWorkspacePage = () => {
|
export const LeadWorkspacePage = () => {
|
||||||
const [sourceFilter, setSourceFilter] = useState<LeadSource | null>(null);
|
const [sourceFilter, setSourceFilter] = useState<LeadSource | null>(null);
|
||||||
|
|
||||||
const { leads, total } = useLeads({ source: sourceFilter ?? undefined, status: 'NEW' });
|
const { leads, total, updateLead } = useLeads({ source: sourceFilter ?? undefined, status: 'NEW' });
|
||||||
const { leads: allLeads } = useLeads();
|
const { leads: allLeads } = useLeads();
|
||||||
const { overdue, upcoming } = useFollowUps();
|
const { overdue, upcoming } = useFollowUps();
|
||||||
|
const { agents, templates } = useData();
|
||||||
|
|
||||||
const displayLeads = leads.slice(0, 10);
|
const displayLeads = leads.slice(0, 10);
|
||||||
|
|
||||||
const handleAssign = () => {
|
// Modal state
|
||||||
// placeholder
|
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 = () => {
|
const handleMessage = (lead: Lead) => {
|
||||||
// placeholder
|
setTargetLead(lead);
|
||||||
|
setIsWhatsAppOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkSpam = () => {
|
const handleMarkSpam = (lead: Lead) => {
|
||||||
// placeholder
|
setTargetLead(lead);
|
||||||
|
setIsSpamOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMerge = () => {
|
const handleMerge = (lead: Lead) => {
|
||||||
// placeholder
|
setTargetLead(lead);
|
||||||
|
setIsMergeOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogCall = () => {
|
const handleLogCall = () => {
|
||||||
@@ -97,6 +114,49 @@ export const LeadWorkspacePage = () => {
|
|||||||
<AlertsWidget leads={allLeads} />
|
<AlertsWidget leads={allLeads} />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user