fix: Leads page cleanup — remove tabs, checkboxes, inline header
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:
2026-04-16 17:20:54 +05:30
parent ca482e731e
commit 5c9e70da20
4 changed files with 72 additions and 143 deletions

View File

@@ -0,0 +1,16 @@
{
"permissions": {
"allow": [
"Bash(ps -eo pid,pcpu,rss,comm -r)",
"Bash(awk 'NR<=20{printf \"%-8s %-8s %-10s %s\\\\n\", $1, $2, $3/1024 \"MB\", $4}')",
"Bash(top -l 1 -o cpu -n 15 -stats pid,command,cpu,mem,th)",
"Bash(vm_stat)",
"Bash(sysctl hw.memsize)",
"Bash(awk '{print \"Total RAM: \" $2/1024/1024/1024 \" GB\"}')",
"Bash(ps aux:*)",
"Bash(pmset -g thermlog)",
"Bash(sudo powermetrics:*)",
"Bash(sysctl machdep.xcpm.cpu_thermal_level)"
]
}
}

View File

@@ -25,6 +25,7 @@ type LeadTableProps = {
onSort: (field: string) => void;
onViewActivity?: (lead: Lead) => void;
visibleColumns?: Set<string>;
selectionMode?: 'multiple' | 'none';
};
type TableRow = {
@@ -55,6 +56,7 @@ export const LeadTable = ({
onSort,
onViewActivity,
visibleColumns,
selectionMode,
}: LeadTableProps) => {
const [expandedDupId, setExpandedDupId] = useState<string | null>(null);
@@ -118,7 +120,7 @@ export const LeadTable = ({
<div className="flex flex-1 flex-col min-h-0 overflow-hidden rounded-xl ring-1 ring-secondary">
<Table
aria-label="Leads table"
selectionMode="multiple"
selectionMode={selectionMode ?? 'multiple'}
selectionBehavior="toggle"
selectedKeys={selectedKeys}
onSelectionChange={handleSelectionChange}

View File

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

View File

@@ -35,7 +35,6 @@ export const ContactsPage = () => {
const { leads, leadActivities } = useData();
const [searchQuery, setSearchQuery] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [sortField, setSortField] = useState('createdAt');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [activityLead, setActivityLead] = useState<Lead | null>(null);
@@ -142,8 +141,9 @@ export const ContactsPage = () => {
<div className="flex-1 overflow-y-auto px-4 pt-3">
<LeadTable
leads={paged}
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
selectedIds={[]}
onSelectionChange={() => {}}
selectionMode="none"
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
@@ -157,7 +157,7 @@ export const ContactsPage = () => {
<PaginationPageDefault
page={currentPage}
total={totalPages}
onPageChange={(page) => { setCurrentPage(page); setSelectedIds([]); }}
onPageChange={(page) => { setCurrentPage(page); }}
/>
</div>
)}