diff --git a/src/pages/subcontractor/SubContractorDashboard.jsx b/src/pages/subcontractor/SubContractorDashboard.jsx index a681de1..84e4adb 100644 --- a/src/pages/subcontractor/SubContractorDashboard.jsx +++ b/src/pages/subcontractor/SubContractorDashboard.jsx @@ -5,7 +5,7 @@ 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 { CheckSquare, Clock, Wallet, MapPin, Camera, X, Calendar, DollarSign, ChevronRight, AlertCircle, ArrowDown, ArrowUp, FileText, Receipt } from 'lucide-react'; import TaskDetailsModal from '../../components/contractor/TaskDetailsModal'; import FinancialSummaryModal from '../../components/contractor/FinancialSummaryModal'; import { NotificationsPanel } from '../../components/subcontractor/NotificationsPanel'; @@ -239,6 +239,56 @@ const SubContractorDashboard = () => { { date: '2026-02-14', project: '2612 Dunwick Dr — Interior Renovation', clockIn: '7:30 AM', clockOut: '5:00 PM', hours: '9.5' }, ], []); + // Project history & payment tracking + const [historyFilter, setHistoryFilter] = useState('all'); // all | Paid | Requested | Pending + const [historySort, setHistorySort] = useState('desc'); // desc = newest first + const [noteModal, setNoteModal] = useState(null); // { title, body, project, requestedAt, receivedAt, status } + + const projectById = useMemo(() => { + const m = new Map(); + for (const p of projects) m.set(p.id, p); + return m; + }, [projects]); + + const paymentHistoryRows = useMemo(() => { + const items = myAssignments.map(t => { + const project = t.projectId ? projectById.get(t.projectId) : null; + const requestedAt = t.paymentRequestedAt || null; + const receivedAt = t.paidAt || null; + const status = t.paymentStatus === 'Paid' + ? 'Paid' + : (requestedAt ? 'Requested' : 'Pending'); + return { + id: t.id, + projectId: t.projectId || null, + projectName: project ? (project.projectType || project.address) : 'Standalone Task', + projectAddress: project?.address || t.location || '', + requestedAt, + receivedAt, + note: t.title || '', + description: t.description || '', + status, + }; + }); + const filtered = historyFilter === 'all' ? items : items.filter(r => r.status === historyFilter); + const sorted = [...filtered].sort((a, b) => { + const av = a.requestedAt || ''; + const bv = b.requestedAt || ''; + if (av === bv) return 0; + if (!av) return 1; // missing requested dates sink to bottom + if (!bv) return -1; + return historySort === 'desc' ? (bv > av ? 1 : -1) : (av > bv ? 1 : -1); + }); + return sorted; + }, [myAssignments, projectById, historyFilter, historySort]); + + const historyCounts = useMemo(() => ({ + all: myAssignments.length, + Paid: myAssignments.filter(t => t.paymentStatus === 'Paid').length, + Requested: myAssignments.filter(t => t.paymentStatus !== 'Paid' && t.paymentRequestedAt).length, + Pending: myAssignments.filter(t => !t.paymentRequestedAt).length, + }), [myAssignments]); + // Modal states const [detailModal, setDetailModal] = useState({ isOpen: false, type: null, title: '' }); const [selectedTask, setSelectedTask] = useState(null); @@ -324,7 +374,7 @@ const SubContractorDashboard = () => { {/* Main Task List */}
-
+

My Assignments

@@ -383,6 +433,151 @@ const SubContractorDashboard = () => { )}
+ + {/* Project History & Payment Tracking */} + +
+
+

+ + Project History & Payments +

+

+ Track payment requests and receipts across every project you've worked on. +

+
+
+ {[ + { key: 'all', label: 'All', tone: 'bg-zinc-100 dark:bg-white/10 text-zinc-700 dark:text-zinc-200' }, + { key: 'Paid', label: 'Paid', tone: 'bg-emerald-100 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-400' }, + { key: 'Requested', label: 'Requested', tone: 'bg-blue-100 dark:bg-blue-500/15 text-blue-700 dark:text-blue-400' }, + { key: 'Pending', label: 'Pending', tone: 'bg-amber-100 dark:bg-amber-500/15 text-amber-700 dark:text-amber-400' }, + ].map(p => { + const active = historyFilter === p.key; + return ( + + ); + })} + +
+
+ + {paymentHistoryRows.length === 0 ? ( +
+ +

No project history yet.

+

+ {historyFilter === 'all' ? 'Tasks assigned to you will appear here.' : `No projects with status "${historyFilter}".`} +

+
+ ) : ( +
+ + + + + + + + + + + + + {paymentHistoryRows.map(row => { + const unpaid = row.status !== 'Paid'; + const statusTone = { + Paid: 'bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-400 dark:border-emerald-500/20', + Requested: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-500/15 dark:text-blue-400 dark:border-blue-500/20', + Pending: 'bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-500/15 dark:text-amber-400 dark:border-amber-500/20', + }[row.status] || 'bg-zinc-100 text-zinc-600 border-zinc-200 dark:bg-white/5 dark:text-zinc-400 dark:border-white/10'; + + return ( + navigate(`/subcontractor/tasks/${row.id}`)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate(`/subcontractor/tasks/${row.id}`); }} + className={`border-b border-zinc-100 dark:border-white/[0.03] hover:bg-zinc-50 dark:hover:bg-white/[0.02] transition-colors cursor-pointer group ${unpaid ? 'border-l-2 border-l-amber-400/60 dark:border-l-amber-500/40' : ''}`} + > + + + + + + + + ); + })} + +
Project IDProject Name + + ReceivedNoteStatus
+ + {row.projectId || '—'} + + +
+
+ {row.projectName} +
+ {row.projectAddress && ( +
+ {row.projectAddress} +
+ )} +
+
+ + {row.requestedAt ? new Date(row.requestedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : '—'} + + + + {row.receivedAt ? new Date(row.receivedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : 'Not received'} + + + + + + {row.status} + +
+
+ )} +
{/* Right column: Earnings + Notifications */} @@ -421,6 +616,64 @@ const SubContractorDashboard = () => {
+ {/* Note detail modal */} + {noteModal && createPortal( +
+
setNoteModal(null)} /> +
+
+

Note Detail

+ +
+
+
+
Project
+
{noteModal.projectName}
+ {noteModal.projectAddress && ( +
{noteModal.projectAddress}
+ )} +
+
+
+
Requested
+
+ {noteModal.requestedAt ? new Date(noteModal.requestedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : '—'} +
+
+
+
Received
+
+ {noteModal.receivedAt ? new Date(noteModal.receivedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : 'Not received'} +
+
+
+
+
Note
+
{noteModal.note || '—'}
+ {noteModal.description && ( +

{noteModal.description}

+ )} +
+
+
+ + +
+
+
, + document.body + )} + {/* Modals */} setIsTaskModalOpen(false)} task={selectedTask} onUpdate={handleTaskUpdate} /> setIsFinancialModalOpen(false)} role="SUBCONTRACTOR" data={financialData} />