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:
2026-03-16 16:20:27 +05:30
parent d98da9a1ea
commit a119fb1b67
3 changed files with 165 additions and 15 deletions

View File

@@ -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>

View File

@@ -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>
); );
}; };

View File

@@ -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>
); );
}; };