From be975f74f563efeaf6b3c0812430af79a23395cc Mon Sep 17 00:00:00 2001 From: Krao Hasanee Date: Wed, 13 May 2026 12:26:55 -0400 Subject: [PATCH] Add company tabs to client dashboard Tabs appear below stats. All four stat cards + both task columns filter to the selected company. Stats recompute client-side from fetched data. Co-Authored-By: Claude Sonnet 4.6 --- src/pages/client/ClientDashboard.jsx | 96 +++++++++++++++++----------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/src/pages/client/ClientDashboard.jsx b/src/pages/client/ClientDashboard.jsx index 23e3788..6cf2ce3 100644 --- a/src/pages/client/ClientDashboard.jsx +++ b/src/pages/client/ClientDashboard.jsx @@ -51,49 +51,31 @@ function TaskColumn({ title, tasks, projects, emptyMessage }) { export default function ClientDashboard() { const { currentUser } = useAuth(); const hasCompany = Boolean(currentUser?.company_id || currentUser?.company?.id || currentUser?.companies?.length); + const companies = (currentUser?.companies?.length ? currentUser.companies : (currentUser?.company ? [currentUser.company] : [])).slice().sort((a, b) => a.name.localeCompare(b.name)); + const [activeCompanyId, setActiveCompanyId] = useState(companies[0]?.id || null); - const [stats, setStats] = useState({ notStarted: 0, inProgress: 0, awaitingReview: 0, outstandingInvoices: 0 }); - const [reviewTasks, setReviewTasks] = useState([]); - const [inProgressTasks, setInProgressTasks] = useState([]); - const [projects, setProjects] = useState([]); + const [allTasks, setAllTasks] = useState([]); + const [allProjects, setAllProjects] = useState([]); + const [allInvoices, setAllInvoices] = useState([]); const [loading, setLoading] = useState(hasCompany); useEffect(() => { if (!hasCompany) { setLoading(false); return; } - async function load() { try { - const [ - { count: notStartedCount }, - { count: inProgressCount }, - { count: reviewCount }, - { data: sentInvoices }, - { data: activeTasks }, - ] = await withTimeout(Promise.all([ - supabase.from('tasks').select('id', { count: 'exact', head: true }).eq('status', 'not_started'), - supabase.from('tasks').select('id', { count: 'exact', head: true }).in('status', ['in_progress', 'on_hold']), - supabase.from('tasks').select('id', { count: 'exact', head: true }).eq('status', 'client_review'), - supabase.from('invoices').select('total').eq('status', 'sent'), + const [{ data: activeTasks }, { data: invoices }] = await withTimeout(Promise.all([ supabase.from('tasks').select('id, title, status, project_id').in('status', ['client_review', 'in_progress', 'on_hold', 'not_started']).order('submitted_at', { ascending: false }), + supabase.from('invoices').select('total, status, company_id').eq('status', 'sent'), ]), 12000, 'Client dashboard load'); - setStats({ - notStarted: notStartedCount || 0, - inProgress: inProgressCount || 0, - awaitingReview: reviewCount || 0, - outstandingInvoices: (sentInvoices || []).reduce((sum, inv) => sum + Number(inv.total || 0), 0), - }); - const tasks = activeTasks || []; - const review = tasks.filter(t => t.status === 'client_review'); - const inProg = tasks.filter(t => ['in_progress', 'on_hold', 'not_started'].includes(t.status)); - setReviewTasks(review); - setInProgressTasks(inProg); + setAllTasks(tasks); + setAllInvoices(invoices || []); if (tasks.length > 0) { const projectIds = [...new Set(tasks.map(t => t.project_id).filter(Boolean))]; - const { data: proj } = await supabase.from('projects').select('id, name').in('id', projectIds); - setProjects(proj || []); + const { data: proj } = await supabase.from('projects').select('id, name, company_id').in('id', projectIds); + setAllProjects(proj || []); } } catch (error) { console.error('ClientDashboard load failed:', error); @@ -101,10 +83,29 @@ export default function ClientDashboard() { setLoading(false); } } - load(); }, [hasCompany]); + const filterByCompany = (tasks) => { + if (companies.length <= 1 || !activeCompanyId) return tasks; + return tasks.filter(t => { + const proj = allProjects.find(p => p.id === t.project_id); + return proj?.company_id === activeCompanyId; + }); + }; + + const visibleTasks = filterByCompany(allTasks); + const visibleProjects = companies.length <= 1 + ? allProjects + : allProjects.filter(p => p.company_id === activeCompanyId); + const visibleInvoices = companies.length <= 1 || !activeCompanyId + ? allInvoices + : allInvoices.filter(i => i.company_id === activeCompanyId); + + const reviewTasks = visibleTasks.filter(t => t.status === 'client_review'); + const inProgressTasks = visibleTasks.filter(t => ['in_progress', 'on_hold', 'not_started'].includes(t.status)); + const outstandingInvoices = visibleInvoices.reduce((sum, inv) => sum + Number(inv.total || 0), 0); + if (loading) return

Loading...

; return ( @@ -119,34 +120,55 @@ export default function ClientDashboard() {
-
0 ? 'var(--accent)' : undefined }}>{stats.awaitingReview}
+
0 ? 'var(--accent)' : undefined }}>{reviewTasks.length}
Awaiting Review
-
{stats.inProgress}
+
{inProgressTasks.filter(t => ['in_progress', 'on_hold'].includes(t.status)).length}
In Progress
-
{stats.notStarted}
+
{inProgressTasks.filter(t => t.status === 'not_started').length}
Not Started
-
${stats.outstandingInvoices.toFixed(2)}
+
${outstandingInvoices.toFixed(2)}
Outstanding Invoices
-
+ {companies.length > 1 && ( +
+ {companies.map((company, index) => ( + + {index > 0 && |} + + + ))} +
+ )} + +