adds sections as list tasks,log expenses. fees, notifications for sub contrcator
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
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 } from 'lucide-react';
|
||||
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 }) => {
|
||||
@@ -194,11 +196,18 @@ const SubDetailModal = ({ isOpen, onClose, title, type, tasks, invoices, clockIn
|
||||
|
||||
const SubContractorDashboard = () => {
|
||||
const { user } = useAuth();
|
||||
const { projects } = useMockStore();
|
||||
const { projects, subcontractorTasks, notifications, markNotificationRead } = useMockStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const subId = user?.id || 'sub_001';
|
||||
|
||||
// Aggregate Tasks
|
||||
// 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 }))
|
||||
@@ -208,6 +217,12 @@ const SubContractorDashboard = () => {
|
||||
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)
|
||||
@@ -308,58 +323,70 @@ const SubContractorDashboard = () => {
|
||||
</div>
|
||||
|
||||
{/* Main Task List */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
|
||||
<div className="lg:col-span-2">
|
||||
<SpotlightCard className="p-6 h-full">
|
||||
<SpotlightCard className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-lg font-bold text-zinc-900 dark:text-white">My Assignments</h2>
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-zinc-500">{myAssignments.length} task{myAssignments.length === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{myTasks.length > 0 ? myTasks.map((task, idx) => (
|
||||
<div key={idx} 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" onClick={() => handleTaskClick(task)}>
|
||||
{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 (
|
||||
<div
|
||||
key={task.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row justify-between md:items-center gap-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-3 rounded-lg ${task.status === 'completed' ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400' :
|
||||
task.status === 'in_progress' ? 'bg-blue-100 text-blue-600 dark:bg-blue-500/20 dark:text-blue-400' :
|
||||
'bg-zinc-100 text-zinc-600 dark:bg-white/10 dark:text-zinc-400'
|
||||
}`}>
|
||||
<div className="flex items-start gap-4 min-w-0">
|
||||
<div className={`p-3 rounded-lg shrink-0 ${statusTone}`}>
|
||||
<CheckSquare size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-zinc-900 dark:text-white">{task.name}</h4>
|
||||
<div className="flex items-center text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
||||
<MapPin size={12} className="mr-1" />
|
||||
{task.projectAddress}
|
||||
<div className="min-w-0">
|
||||
<h4 className="font-bold text-zinc-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors truncate">{task.title}</h4>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-zinc-500 dark:text-zinc-400 mt-1">
|
||||
<span className="flex items-center gap-1"><MapPin size={12} />{task.location}</span>
|
||||
<span className="font-mono">{task.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3 md:gap-4 shrink-0">
|
||||
<div className="text-right">
|
||||
<p className="text-xs font-mono font-medium text-zinc-500 mb-1">Due: {task.dueDate}</p>
|
||||
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${task.status === 'completed' ? 'bg-emerald-100 text-emerald-600' :
|
||||
task.status === 'in_progress' ? 'bg-blue-100 text-blue-600' : 'bg-amber-100 text-amber-600'
|
||||
}`}>
|
||||
{task.status.replace('_', ' ')}
|
||||
<p className={`text-xs font-mono font-medium mb-1 flex items-center justify-end gap-1 ${overdue ? 'text-red-500 font-bold' : 'text-zinc-500'}`}>
|
||||
{overdue && <AlertCircle size={11} />}
|
||||
Due: {task.dueDate}
|
||||
</p>
|
||||
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${statusTone}`}>
|
||||
{task.status}
|
||||
</span>
|
||||
</div>
|
||||
{task.status !== 'completed' && (
|
||||
<button className="px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold transition-colors"
|
||||
onClick={(e) => { e.stopPropagation(); handleTaskClick(task); }}>
|
||||
Update
|
||||
</button>
|
||||
)}
|
||||
<ChevronRight size={16} className="text-zinc-400 group-hover:text-blue-500 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)) : (
|
||||
<div className="text-center py-10 text-zinc-500"><p>No tasks assigned.</p></div>
|
||||
);
|
||||
}) : (
|
||||
<div className="text-center py-10 text-zinc-500"><p>No tasks assigned yet.</p></div>
|
||||
)}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
</div>
|
||||
|
||||
{/* Earnings Widget */}
|
||||
<div>
|
||||
{/* Right column: Earnings + Notifications */}
|
||||
<div className="space-y-6">
|
||||
<SpotlightCard className="p-6">
|
||||
<h2 className="text-lg font-bold text-zinc-900 dark:text-white mb-4">Earnings Tracker</h2>
|
||||
<div className="relative pt-4 pb-8">
|
||||
@@ -385,6 +412,12 @@ const SubContractorDashboard = () => {
|
||||
</button>
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
<NotificationsPanel
|
||||
notifications={myNotifications}
|
||||
onMarkAllRead={() => myNotifications.forEach(n => !n.isRead && markNotificationRead(n.id))}
|
||||
onNotificationClick={(n) => { if (!n.isRead) markNotificationRead(n.id); }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user