diff --git a/src/pages/client/MyRequests.jsx b/src/pages/client/MyRequests.jsx index 6e8abe3..71539af 100755 --- a/src/pages/client/MyRequests.jsx +++ b/src/pages/client/MyRequests.jsx @@ -10,7 +10,10 @@ export default function MyRequests() { const [projects, setProjects] = useState([]); const [tasks, setTasks] = useState([]); const [submissions, setSubmissions] = useState([]); + const [invoices, setInvoices] = useState([]); + const [invoiceItems, setInvoiceItems] = useState([]); const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState('active'); useEffect(() => { async function load() { @@ -38,6 +41,13 @@ export default function MyRequests() { .order('version_number'); setSubmissions(allSubs || []); + const [{ data: inv }, { data: itemRows }] = await Promise.all([ + supabase.from('invoices').select('id, status'), + supabase.from('invoice_items').select('task_id, invoice_id').in('task_id', myTaskIds), + ]); + setInvoices(inv || []); + setInvoiceItems(itemRows || []); + // Group tasks by project const projectMap = {}; (t || []).forEach(task => { @@ -54,6 +64,56 @@ export default function MyRequests() { if (loading) return

Loading...

; + const paidInvoiceIds = new Set(invoices.filter(invoice => invoice.status === 'paid').map(invoice => invoice.id)); + const paidTaskIds = new Set( + invoiceItems + .filter(item => item.task_id && paidInvoiceIds.has(item.invoice_id)) + .map(item => item.task_id) + ); + const isFullyClosedTask = (task) => task?.status === 'client_approved' && Boolean(task?.invoiced) && paidTaskIds.has(task.id); + const activeCount = tasks.filter(task => task.status !== 'client_approved').length; + const reviewCount = tasks.filter(task => task.status === 'client_review').length; + const completedCount = tasks.filter(task => task.status === 'client_approved' && !isFullyClosedTask(task)).length; + const fullyClosedCount = tasks.filter(task => isFullyClosedTask(task)).length; + const activeTasks = tasks.filter(task => task.status !== 'client_review' && task.status !== 'client_approved'); + const reviewTasks = tasks.filter(task => task.status === 'client_review'); + const completedTasks = tasks.filter(task => task.status === 'client_approved' && !isFullyClosedTask(task)); + const closedTasks = tasks.filter(task => isFullyClosedTask(task)); + const renderTaskRow = (task, showClosedStatus = false, isLast = false) => { + const taskSubs = submissions.filter(s => s.task_id === task.id); + const initialSub = taskSubs.find(s => s.type === 'initial') || taskSubs[0]; + const latestSub = taskSubs[taskSubs.length - 1]; + const hasRevision = taskSubs.length > 1 && latestSub?.submitted_by_name !== initialSub?.submitted_by_name; + return ( + +
+ + {task.title}{' '} + + {'R' + String(task.current_version || 0).padStart(2, '0')} + + +
+ Submitted by {initialSub?.submitted_by_name || 'Unknown'} + {hasRevision && ` · Last updated by ${latestSub.submitted_by_name}`} +
+
+
+ {showClosedStatus ? ( + Paid & Closed + ) : ( + + )} +
+ + ); + }; + return (
@@ -64,6 +124,29 @@ export default function MyRequests() { + New Request
+
+
+
{projects.length}
+
Projects
+
+
+
{activeCount}
+
Active Requests
+
+
+
0 ? 'var(--accent)' : undefined }}>{reviewCount}
+
Awaiting Review
+
+
+
{completedCount}
+
Completed
+
+
+
{fullyClosedCount}
+
Fully Closed
+
+
+ {projects.length === 0 ? (
📋
@@ -72,50 +155,76 @@ export default function MyRequests() { Submit Request
) : ( - projects.map(project => { - const projectTasks = tasks.filter(t => t.project?.id === project.id); - return ( -
-
-
-
{project.name}
-
- Started {new Date(project.created_at).toLocaleDateString()} · {projectTasks.length} job{projectTasks.length !== 1 ? 's' : ''} -
+
+
+
+ {[ + { id: 'active', label: 'Active', count: activeTasks.length }, + { id: 'client-review', label: 'Client Review', count: reviewTasks.length }, + { id: 'completed', label: 'Completed', count: completedTasks.length }, + { id: 'closed', label: 'Fully Closed', count: closedTasks.length }, + ].map((tab, index) => ( + + {index > 0 && |} + + + ))} +
+
+ + {[ + { id: 'active', emptyTitle: 'No active requests', tasks: activeTasks, closed: false }, + { id: 'client-review', emptyTitle: 'No requests in review', tasks: reviewTasks, closed: false }, + { id: 'completed', emptyTitle: 'No completed requests', tasks: completedTasks, closed: false }, + { id: 'closed', emptyTitle: 'No fully closed requests', tasks: closedTasks, closed: true }, + ].filter(section => section.id === activeTab).map(section => { + const sectionProjects = projects.filter(project => section.tasks.some(task => task.project?.id === project.id)); + + if (sectionProjects.length === 0) { + return ( +
+

{section.emptyTitle}

+ {section.closed &&

Requests move here once they are completed, invoiced, and paid.

}
- -
-
- {projectTasks.map(task => { - const taskSubs = submissions.filter(s => s.task_id === task.id); - const initialSub = taskSubs.find(s => s.type === 'initial') || taskSubs[0]; - const latestSub = taskSubs[taskSubs.length - 1]; - const hasRevision = taskSubs.length > 1 && latestSub?.submitted_by_name !== initialSub?.submitted_by_name; + ); + } + + return ( +
+ {sectionProjects.map(project => { + const projectTasks = section.tasks.filter(task => task.project?.id === project.id); return ( -
-
- - {task.title}{' '} - - {'v' + String(task.current_version || 0).padStart(2, '0')} - - -
- Submitted by {initialSub?.submitted_by_name || 'Unknown'} - {hasRevision && ` · Last updated by ${latestSub.submitted_by_name}`} -
+
+
+
{project.name}
-
- - Details +
+ {projectTasks.map((task, index) => renderTaskRow(task, section.closed, index === projectTasks.length - 1))}
); })}
-
- ); - }) + ); + })} +
)} );