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
))}{owner ? `${owner.companyName} — ${owner.portfolioSize} projects in portfolio` : "Here's your business snapshot for today."}
Per project (in $k)
Distribution
Last 12 months (in $k)
{item.action} {item.details && — {item.details}}
{item.date} · {item.projectType}{item.msg}
{item.time}