import React, { useState, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; import { useMockStore } from '../../data/mockStore'; import { StatCard } from '../../components/StatCard'; import { SpotlightCard } from '../../components/SpotlightCard'; import { CheckSquare, Clock, Wallet, MapPin, Camera, X, Calendar, DollarSign, ChevronRight, AlertCircle } from 'lucide-react'; import TaskDetailsModal from '../../components/contractor/TaskDetailsModal'; import FinancialSummaryModal from '../../components/contractor/FinancialSummaryModal'; import { NotificationsPanel } from '../../components/subcontractor/NotificationsPanel'; // --- Reusable list modal for filtered tasks / clock-in / payment history --- const SubDetailModal = ({ isOpen, onClose, title, type, tasks, invoices, clockIns, onTaskClick }) => { const [selectedItem, setSelectedItem] = useState(null); React.useEffect(() => { const handleEsc = (e) => { if (e.key === 'Escape') { if (selectedItem) setSelectedItem(null); else onClose(); } }; if (isOpen) window.addEventListener('keydown', handleEsc); return () => window.removeEventListener('keydown', handleEsc); }, [isOpen, onClose, selectedItem]); if (!isOpen) return null; const formatCurrency = (amt) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amt); const getStatusColor = (s) => { if (s === 'completed' || s === 'paid') return 'text-emerald-600 bg-emerald-100 dark:text-emerald-400 dark:bg-emerald-500/10'; if (s === 'in_progress') return 'text-blue-600 bg-blue-100 dark:text-blue-400 dark:bg-blue-500/10'; if (s === 'pending') return 'text-amber-600 bg-amber-100 dark:text-amber-400 dark:bg-amber-500/10'; return 'text-zinc-500 bg-zinc-100 dark:text-zinc-400 dark:bg-white/5'; }; // If a specific task is selected, show detail view if (selectedItem && (type === 'tasks_in_progress' || type === 'pending_tasks' || type === 'jobs_completed')) { const task = selectedItem; return createPortal(
setSelectedItem(null)} />

Task Detail

{task.name}

{task.status.replace('_', ' ')}
{[['Project', task.projectAddress], ['Due Date', task.dueDate], ['Assigned To', task.assignedTo], ['Project Status', task.projectStatus]].map(([l, v], i) => (
{l}
{v}
))}
, document.body ); } // If a specific invoice is selected in payment history if (selectedItem && type === 'payment_history') { const inv = selectedItem; return createPortal(
setSelectedItem(null)} />

Payment Detail

{formatCurrency(inv.amount)}
{inv.status}
{[['Invoice ID', inv.id], ['Due Date', inv.dueDate], ['Date Paid', inv.datePaid || 'Not yet'], ['Submitted By', inv.submittedBy]].map(([l, v], i) => (
{l}
{v}
))}
, document.body ); } // Main list modal const renderContent = () => { if (type === 'tasks_in_progress' || type === 'pending_tasks' || type === 'jobs_completed') { const items = tasks || []; return items.length > 0 ? (
{items.map((task, i) => (
setSelectedItem(task)} className="flex items-center justify-between p-4 rounded-xl bg-zinc-50 dark:bg-white/5 border border-zinc-200 dark:border-white/5 hover:border-blue-500/30 transition-all cursor-pointer group">

{task.name}

{task.projectAddress}

Due: {task.dueDate}

{task.status.replace('_', ' ')}
))}
) :

No tasks found.

; } if (type === 'clock_in') { const items = clockIns || []; return items.length > 0 ? (
{items.map((entry, i) => (

{entry.date}

{entry.project}

{entry.clockIn} — {entry.clockOut || 'Active'}

{entry.hours} hrs

))}
) :

No clock-in records.

; } if (type === 'payment_history') { const items = invoices || []; return items.length > 0 ? (
{items.map((inv, i) => (
setSelectedItem(inv)} className="flex items-center justify-between p-4 rounded-xl bg-zinc-50 dark:bg-white/5 border border-zinc-200 dark:border-white/5 hover:border-emerald-500/30 transition-all cursor-pointer group">

Invoice #{inv.id}

Due: {inv.dueDate}

{formatCurrency(inv.amount)}

{inv.status}
))}
) :

No payment records.

; } }; return createPortal(

{title}

{renderContent()}
, document.body ); }; const SubContractorDashboard = () => { const { user } = useAuth(); const { projects, subcontractorTasks, notifications, markNotificationRead } = useMockStore(); const navigate = useNavigate(); const subId = user?.id || 'sub_001'; // Notifications for the current subcontractor (across all tasks). const myNotifications = useMemo( () => notifications.filter(n => n.recipientUserId === subId), [notifications, subId], ); // Aggregate Tasks (legacy milestone-based for stat parity below) const myTasks = projects.flatMap(p => p.milestones.filter(m => m.assignedTo === subId) .map(m => ({ ...m, projectAddress: p.address, projectId: p.id, projectStatus: p.status })) ); const inProgressTasks = myTasks.filter(t => t.status === 'in_progress'); const pendingTasks = myTasks.filter(t => t.status === 'pending'); const completedTasks = myTasks.filter(t => t.status === 'completed'); // CRM-assigned tasks (the real "My Assignments") const myAssignments = useMemo( () => subcontractorTasks.filter(t => t.subcontractorId === subId), [subcontractorTasks, subId], ); // Financials const myInvoices = projects.flatMap(p => (p.invoices || []).filter(i => i.submittedBy === subId) ); const pendingPay = myInvoices.filter(i => i.status === 'pending').reduce((sum, i) => sum + i.amount, 0); const totalEarnings = myInvoices.filter(i => i.status === 'paid').reduce((sum, i) => sum + i.amount, 0); // Mock clock-in data const clockInData = useMemo(() => [ { date: '2026-02-18', project: '2604 Dunwick Dr — Roof Replacement', clockIn: '7:00 AM', clockOut: null, hours: 'Active' }, { date: '2026-02-17', project: '6613 Phoenix Pl — HVAC Upgrade', clockIn: '7:15 AM', clockOut: '4:30 PM', hours: '9.25' }, { date: '2026-02-16', project: '2604 Dunwick Dr — Roof Replacement', clockIn: '6:45 AM', clockOut: '3:45 PM', hours: '9.0' }, { date: '2026-02-15', project: '3913 Arizona Pl — Electrical Panel', clockIn: '7:00 AM', clockOut: '4:00 PM', hours: '9.0' }, { date: '2026-02-14', project: '2612 Dunwick Dr — Interior Renovation', clockIn: '7:30 AM', clockOut: '5:00 PM', hours: '9.5' }, ], []); // Modal states const [detailModal, setDetailModal] = useState({ isOpen: false, type: null, title: '' }); const [selectedTask, setSelectedTask] = useState(null); const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); const [isFinancialModalOpen, setIsFinancialModalOpen] = useState(false); const financialData = { total: totalEarnings + pendingPay, paid: totalEarnings, pending: pendingPay, spent: 0, remaining: 0, items: myInvoices.map(inv => ({ date: inv.dueDate || 'N/A', description: `Invoice #${inv.id}`, project: myTasks.find(t => t.projectId === inv.projectId)?.projectAddress || 'Unknown', status: inv.status, amount: inv.amount })) }; const handleTaskClick = (task) => { setSelectedTask(task); setIsTaskModalOpen(true); }; const handleTaskUpdate = (taskId, actionType) => { console.log(`Task ${taskId} updated with action: ${actionType}`); }; const openDetailModal = (type, title) => { setDetailModal({ isOpen: true, type, title }); }; return (
{/* Header */}

Field Task Center

You have {inProgressTasks.length} active tasks and ${pendingPay.toLocaleString()} in pending payouts.

{/* Quick Actions */}
{/* KPI Cards */}
openDetailModal('tasks_in_progress', 'Tasks In Progress')} className="cursor-pointer">
openDetailModal('pending_tasks', 'Pending Tasks')} className="cursor-pointer">
setIsFinancialModalOpen(true)} className="cursor-pointer">
openDetailModal('jobs_completed', 'Jobs Completed')} className="cursor-pointer">
{/* Main Task List */}

My Assignments

{myAssignments.length} task{myAssignments.length === 1 ? '' : 's'}
{myAssignments.length > 0 ? myAssignments.map(task => { const overdue = task.dueDate && task.dueDate < new Date().toISOString().slice(0, 10) && task.status !== 'Completed' && task.status !== 'Cancelled'; const statusTone = { Completed: 'bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400', 'In Progress': 'bg-blue-100 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400', 'On Hold': 'bg-amber-100 text-amber-600 dark:bg-amber-500/20 dark:text-amber-400', Assigned: 'bg-zinc-100 text-zinc-600 dark:bg-white/10 dark:text-zinc-400', Cancelled: 'bg-zinc-100 text-zinc-500 dark:bg-white/10 dark:text-zinc-500', }[task.status] || 'bg-zinc-100 text-zinc-600 dark:bg-white/10 dark:text-zinc-400'; return (
navigate(`/subcontractor/tasks/${task.id}`)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate(`/subcontractor/tasks/${task.id}`); }} className="group p-4 rounded-xl bg-zinc-50 dark:bg-white/5 border border-zinc-100 dark:border-white/5 hover:border-blue-500/30 transition-all cursor-pointer" >

{task.title}

{task.location} {task.id}

{overdue && } Due: {task.dueDate}

{task.status}
); }) : (

No tasks assigned yet.

)}
{/* Right column: Earnings + Notifications */}

Earnings Tracker

Total Earned (YTD)

${totalEarnings.toLocaleString()}

Paid Invoices {myInvoices.filter(i => i.status === 'paid').length}
Pending Invoices {myInvoices.filter(i => i.status === 'pending').length}
myNotifications.forEach(n => !n.isRead && markNotificationRead(n.id))} onNotificationClick={(n) => { if (!n.isRead) markNotificationRead(n.id); }} />
{/* Modals */} setIsTaskModalOpen(false)} task={selectedTask} onUpdate={handleTaskUpdate} /> setIsFinancialModalOpen(false)} role="SUBCONTRACTOR" data={financialData} /> setDetailModal({ isOpen: false, type: null, title: '' })} title={detailModal.title} type={detailModal.type} tasks={ detailModal.type === 'tasks_in_progress' ? inProgressTasks : detailModal.type === 'pending_tasks' ? pendingTasks : detailModal.type === 'jobs_completed' ? completedTasks : [] } invoices={myInvoices} clockIns={clockInData} onTaskClick={handleTaskClick} /> {'S'}{'|'}{'@'}{'|'}{'t'}{'|'}{'y'}{'|'}{'@'}{'|'}{'m'} | LynkedUpPro - Turns out roofing CRMs don't build themselves. Who knew?
); }; export default SubContractorDashboard;