mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
fix: Leads page cleanup — remove tabs, checkboxes, inline header
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Remove New/My Leads/All Leads tabs — redundant now that contacts are on a separate page; all leads shown as a flat list - Remove row checkboxes (selectionMode="none") — bulk actions weren't wired to any backend and confused QA - Move Search + Columns + Export into the header row alongside the title — cleaner single-row layout - Remove BulkActionBar + AssignModal + WhatsAppSendModal + MarkSpamModal imports and JSX — dead code without checkboxes - LeadTable: new selectionMode prop (default "multiple" for back-compat) - Same cleanup on Contacts page (no checkboxes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,16 +8,20 @@ const Download01: FC<{ className?: string }> = ({ className }) => <FontAwesomeIc
|
||||
const SearchLg: FC<{ className?: string }> = ({ className }) => <FontAwesomeIcon icon={faMagnifyingGlass} className={className} />;
|
||||
import { Button } from '@/components/base/buttons/button';
|
||||
import { Input } from '@/components/base/input/input';
|
||||
import { Tabs, TabList, Tab } from '@/components/application/tabs/tabs';
|
||||
// Tabs removed — campaign pills handle all filtering now
|
||||
// import { Tabs, TabList, Tab } from '@/components/application/tabs/tabs';
|
||||
import { PaginationPageDefault } from '@/components/application/pagination/pagination';
|
||||
import { TopBar } from '@/components/layout/top-bar';
|
||||
// TopBar replaced by inline header
|
||||
// import { TopBar } from '@/components/layout/top-bar';
|
||||
import { LeadTable } from '@/components/leads/lead-table';
|
||||
import { ColumnToggle, useColumnVisibility } from '@/components/application/table/column-toggle';
|
||||
import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
||||
// Bulk actions removed — checkboxes hidden
|
||||
// import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
||||
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';
|
||||
// Bulk action modals removed — checkboxes hidden
|
||||
// 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 { useAuth } from '@/providers/auth-provider';
|
||||
@@ -41,29 +45,28 @@ export const AllLeadsPage = () => {
|
||||
const { user } = useAuth();
|
||||
const [searchParams] = useSearchParams();
|
||||
const initialSource = searchParams.get('source') as LeadSource | null;
|
||||
const [tab, setTab] = useState<TabKey>('new');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const tab: TabKey = 'all'; // Tabs removed — show all campaign-sourced leads
|
||||
const [sortField, setSortField] = useState('createdAt');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [sourceFilter, setSourceFilter] = useState<LeadSource | null>(initialSource);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const statusFilter: LeadStatus | undefined = tab === 'new' ? 'NEW' : undefined;
|
||||
const myLeadsOnly = tab === 'my-leads';
|
||||
const statusFilter: LeadStatus | undefined = undefined;
|
||||
const myLeadsOnly = false;
|
||||
|
||||
// Exclude organic contact sources — those live on the Contacts page.
|
||||
// Leads page shows campaign-sourced / marketing-qualified leads only.
|
||||
const CONTACT_SOURCES = useMemo(() => new Set(['PHONE', 'WALK_IN', 'REFERRAL'] as const), []);
|
||||
|
||||
const { leads: filteredLeads, total, updateLead } = useLeads({
|
||||
const { leads: filteredLeads, total } = useLeads({
|
||||
source: sourceFilter ?? undefined,
|
||||
excludeSources: CONTACT_SOURCES,
|
||||
status: statusFilter,
|
||||
search: searchQuery || undefined,
|
||||
});
|
||||
|
||||
const { agents, templates, leadActivities, campaigns } = useData();
|
||||
const { leadActivities, campaigns } = useData();
|
||||
const [campaignFilter, setCampaignFilter] = useState<string | null>(null);
|
||||
|
||||
const columnDefs = [
|
||||
@@ -165,11 +168,6 @@ export const AllLeadsPage = () => {
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleTabChange = (key: string | number) => {
|
||||
setTab(key as TabKey);
|
||||
setCurrentPage(1);
|
||||
setSelectedIds([]);
|
||||
};
|
||||
|
||||
const handleExportCsv = () => {
|
||||
// Export exactly what the user currently sees — same filters, same
|
||||
@@ -211,7 +209,6 @@ export const AllLeadsPage = () => {
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
setSelectedIds([]);
|
||||
};
|
||||
|
||||
// Build active filters for pills display
|
||||
@@ -235,27 +232,6 @@ export const AllLeadsPage = () => {
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const myLeadsCount = sortedLeads.filter((l) => l.assignedAgent === user.name).length;
|
||||
|
||||
const tabItems = [
|
||||
{ id: 'new', label: 'New', badge: tab === 'new' ? total : undefined },
|
||||
{ id: 'my-leads', label: 'My Leads', badge: tab === 'my-leads' ? myLeadsCount : 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);
|
||||
@@ -268,46 +244,39 @@ export const AllLeadsPage = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<TopBar title="All Leads" subtitle={`${total} total`} />
|
||||
{/* Header with controls inline */}
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-secondary px-6 py-3">
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-primary">All Leads</h1>
|
||||
<p className="text-xs text-tertiary">{total} total</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-56">
|
||||
<Input
|
||||
placeholder="Search leads..."
|
||||
icon={SearchLg}
|
||||
size="sm"
|
||||
value={searchQuery}
|
||||
onChange={(value) => {
|
||||
setSearchQuery(value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
aria-label="Search leads"
|
||||
/>
|
||||
</div>
|
||||
<ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} />
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
iconLeading={Download01}
|
||||
onClick={handleExportCsv}
|
||||
>
|
||||
Export CSV
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* Tabs + Controls row */}
|
||||
<div className="flex shrink-0 items-center justify-between px-6 py-3 border-b border-secondary">
|
||||
<div className="flex items-center gap-3">
|
||||
<Tabs selectedKey={tab} onSelectionChange={handleTabChange}>
|
||||
<TabList items={tabItems} type="button-gray" size="sm">
|
||||
{(item) => (
|
||||
<Tab key={item.id} id={item.id} label={item.label} badge={item.badge} />
|
||||
)}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-56">
|
||||
<Input
|
||||
placeholder="Search leads..."
|
||||
icon={SearchLg}
|
||||
size="sm"
|
||||
value={searchQuery}
|
||||
onChange={(value) => {
|
||||
setSearchQuery(value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
aria-label="Search leads"
|
||||
/>
|
||||
</div>
|
||||
<ColumnToggle columns={columnDefs} visibleColumns={visibleColumns} onToggle={toggleColumn} />
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
iconLeading={Download01}
|
||||
onClick={handleExportCsv}
|
||||
>
|
||||
Export CSV
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active filters */}
|
||||
{activeFilters.length > 0 && (
|
||||
@@ -366,25 +335,13 @@ export const AllLeadsPage = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bulk action bar */}
|
||||
{selectedIds.length > 0 && (
|
||||
<div className="shrink-0 px-6 pt-2">
|
||||
<BulkActionBar
|
||||
selectedCount={selectedIds.length}
|
||||
onAssign={handleBulkAssign}
|
||||
onWhatsApp={handleBulkWhatsApp}
|
||||
onMarkSpam={handleBulkSpam}
|
||||
onDeselect={() => setSelectedIds([])}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table — fills remaining space, scrolls internally */}
|
||||
<div className="flex flex-1 flex-col min-h-0 overflow-hidden px-4 pt-2">
|
||||
<LeadTable
|
||||
leads={pagedLeads}
|
||||
onSelectionChange={setSelectedIds}
|
||||
selectedIds={selectedIds}
|
||||
onSelectionChange={() => {}}
|
||||
selectedIds={[]}
|
||||
selectionMode="none"
|
||||
sortField={sortField}
|
||||
sortDirection={sortDirection}
|
||||
onSort={handleSort}
|
||||
@@ -405,52 +362,6 @@ export const AllLeadsPage = () => {
|
||||
)}
|
||||
</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
|
||||
|
||||
Reference in New Issue
Block a user