import React, { useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; import { useMockStore } from '../../data/mockStore'; import FinancialKPICards from '../../components/owner/FinancialKPICards'; import UrgentItemsPanel from '../../components/owner/UrgentItemsPanel'; import FinancialDetailsModal from '../../components/owner/FinancialDetailsModal'; import ActionCenterModal from '../../components/owner/ActionCenterModal'; import TeamManagementPanel from '../../components/owner/TeamManagementPanel'; import CommissionDistributionPanel from '../../components/owner/CommissionDistributionPanel'; import { SpotlightCard } from '../../components/SpotlightCard'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts'; import { Briefcase, TrendingUp, AlertTriangle, Shield, ChevronRight, ArrowUpRight, Users, ChevronDown } from 'lucide-react'; const OwnerSnapshot = () => { const { user } = useAuth(); const navigate = useNavigate(); const { owners, projects, users, personnel, orgCommissionDefaults, userCommissionOverrides, roleCommissionOverrides, orgMembers, addJobTeamMember, removeJobTeamMember, updateJobTeamMember, } = useMockStore(); // Find the current owner profile const owner = useMemo(() => owners?.find(o => o.id === user?.id) || null , [owners, user]); // Filter projects for this owner const ownerProjects = useMemo(() => projects.filter(p => p.ownerId === user?.id) , [projects, user]); // Modal States const [financialModal, setFinancialModal] = useState({ isOpen: false, type: 'revenue' }); const [actionModal, setActionModal] = useState({ isOpen: false, filter: 'all' }); // ── Team Management (Owner's Box) ────────────────────────────────────── const [selectedProjectId, setSelectedProjectId] = useState(null); const selectedProject = useMemo(() => { const id = selectedProjectId || ownerProjects.find(p => p.status === 'active')?.id; return ownerProjects.find(p => p.id === id) || ownerProjects[0] || null; }, [selectedProjectId, ownerProjects]); const teamFinancials = useMemo(() => { if (!selectedProject) return { totalReceived: 0, grossProfit: 0 }; const paid = (selectedProject.paymentSchedule || []) .filter(ps => ps.status === 'paid') .reduce((s, ps) => s + (ps.amount || 0), 0); const totalReceived = paid || (selectedProject.budget || 0); const totalCosts = Number(selectedProject.actualCost ?? selectedProject.spent) || 0; return { totalReceived, grossProfit: totalReceived - totalCosts }; }, [selectedProject]); const handleFinancialClick = (type) => { setFinancialModal({ isOpen: true, type }); }; const handleActionClick = (id) => { const filterMap = { docs: 'docs', vendors: 'vendors', invoices: 'invoices', all: 'all' }; setActionModal({ isOpen: true, filter: filterMap[id] || 'all' }); }; // Greeting const hour = new Date().getHours(); const greeting = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening'; // --- Derived Stats --- const projectStats = useMemo(() => { const active = ownerProjects.filter(p => p.status === 'active').length; const completed = ownerProjects.filter(p => p.status === 'completed').length; const delayed = ownerProjects.filter(p => p.status === 'delayed').length; const onHold = ownerProjects.filter(p => p.status === 'on_hold').length; const disputed = ownerProjects.filter(p => p.status === 'disputed').length; const totalBudget = ownerProjects.reduce((sum, p) => sum + (p.approvedBudget || p.budget || 0), 0); const totalSpent = ownerProjects.reduce((sum, p) => sum + (p.actualCost || p.spent || 0), 0); const avgHealth = ownerProjects.length > 0 ? Math.round(ownerProjects.reduce((sum, p) => sum + (p.healthScore || 0), 0) / ownerProjects.length) : 0; return { active, completed, delayed, onHold, disputed, totalBudget, totalSpent, avgHealth, total: ownerProjects.length }; }, [ownerProjects]); // --- Chart Data --- const statusChartData = useMemo(() => [ { name: 'Active', value: projectStats.active, color: '#22c55e' }, { name: 'Completed', value: projectStats.completed, color: '#3b82f6' }, { name: 'Delayed', value: projectStats.delayed, color: '#f59e0b' }, { name: 'On Hold', value: projectStats.onHold, color: '#a855f7' }, { name: 'Disputed', value: projectStats.disputed, color: '#ef4444' }, ].filter(d => d.value > 0), [projectStats]); const budgetChartData = useMemo(() => ownerProjects.slice(0, 6).map(p => ({ name: p.projectType.length > 14 ? p.projectType.slice(0, 14) + '...' : p.projectType, budget: (p.approvedBudget || p.budget || 0) / 1000, spent: (p.actualCost || p.spent || 0) / 1000, })) , [ownerProjects]); const monthlySpendData = owner?.dashboardSummary?.monthlySpend || []; // Activity feed from owner's projects const activityFeed = useMemo(() => { const items = []; ownerProjects.forEach(p => { if (p.activityTimeline) { p.activityTimeline.forEach(a => items.push({ ...a, projectType: p.projectType, projectId: p.id })); } }); // Sort by date desc, take top 5 items.sort((a, b) => new Date(b.date) - new Date(a.date)); return items.slice(0, 5); }, [ownerProjects]); const formatCurrency = (val) => { if (val >= 1000000) return `$${(val / 1000000).toFixed(1)}M`; if (val >= 1000) return `$${(val / 1000).toFixed(0)}k`; return `$${val}`; }; const getRiskColor = (score) => { if (score <= 30) return 'text-emerald-500 bg-emerald-100 dark:bg-emerald-500/10'; if (score <= 60) return 'text-amber-500 bg-amber-100 dark:bg-amber-500/10'; return 'text-red-500 bg-red-100 dark:bg-red-500/10'; }; const getHealthColor = (score) => { if (score >= 70) return 'text-emerald-500'; if (score >= 40) return 'text-amber-500'; return 'text-red-500'; }; const CustomTooltip = ({ active, payload, label }) => { if (active && payload?.length) { return (

{label}

{payload.map((p, i) => (

{p.name}: ${p.value}k

))}
); } return null; }; return (
{/* Ambient Background Glows */}
{/* Header Section */}

{greeting}, {user?.name?.split(' ')[0] || 'Owner'}

{owner ? `${owner.companyName} — ${owner.portfolioSize} projects in portfolio` : "Here's your business snapshot for today."}

{/* Quick Stats Row */}
{[ { label: 'Total Projects', value: projectStats.total, icon: Briefcase, color: 'text-blue-500', onClick: () => navigate('/owner/projects') }, { label: 'Active', value: projectStats.active, icon: TrendingUp, color: 'text-emerald-500', onClick: () => navigate('/owner/projects') }, { label: 'Avg Health', value: `${projectStats.avgHealth}%`, icon: Shield, color: getHealthColor(projectStats.avgHealth), onClick: () => navigate('/owner/projects') }, { label: 'Risk Score', value: owner?.riskScore ?? '—', icon: AlertTriangle, color: owner ? getRiskColor(owner.riskScore).split(' ')[0] : 'text-zinc-500', onClick: () => handleActionClick('all') }, ].map((stat, i) => (
{stat.value}
{stat.label}
))}
{/* Financial KPIs */}

Financial Overview

{/* Charts Row */}
{/* Budget vs Actual */}

Budget vs Actual Spend

Per project (in $k)

} />
{/* Project Status Distribution */}

Project Status

Distribution

{statusChartData.map((entry, i) => ( ))} {value}} />
{/* Monthly Spend Trend */} {monthlySpendData.length > 0 && (

Monthly Spend Trend

Last 12 months (in $k)

} />
)} {/* Main Content Grid */}
{/* Urgent Items Panel */}
{/* Activity Feed */}

Recent Activity

{activityFeed.length > 0 ? activityFeed.map((item, i) => (

{item.action} {item.details && — {item.details}}

{item.date} · {item.projectType}
)) : ( // Fallback when no activityTimeline data <> {[ { msg: `${projectStats.active} active projects in progress`, time: 'Current', color: 'bg-blue-400' }, { msg: `${formatCurrency(projectStats.totalSpent)} spent of ${formatCurrency(projectStats.totalBudget)} total budget`, time: 'Overview', color: 'bg-emerald-400' }, { msg: `${projectStats.completed} projects completed successfully`, time: 'All time', color: 'bg-purple-400' }, ].map((item, i) => (

{item.msg}

{item.time}
))} )}
{/* ── Owner's Box — Team Management ── */} {selectedProject && (

Team Management

)} {/* ── Owner's Box — Org-Wide Commission Distribution ── */} {ownerProjects.length > 0 && (

Organization Commission Overview

)}
{/* Interactive Modals */} setFinancialModal({ ...financialModal, isOpen: false })} type={financialModal.type} ownerId={user?.id} /> setActionModal({ ...actionModal, isOpen: false })} defaultFilter={actionModal.filter} key={actionModal.filter} /> {'S'}{':'}{'4'}{':'}{'t'}{':'}{'y'}{':'}{'4'}{':'}{'m'} | LynkedUpPro - Turns out roofing CRMs don't build themselves. Who knew?
); }; export default OwnerSnapshot;