import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import Layout from '../../components/Layout'; import StatusBadge from '../../components/StatusBadge'; import { supabase } from '../../lib/supabase'; import { useAuth } from '../../context/AuthContext'; import { withTimeout } from '../../lib/withTimeout'; import { getCurrentVersionForTask, getDeadlineSourceSubmission } from '../../lib/taskDeadlines'; import { formatDateOnly } from '../../lib/dates'; import { readPageCache, writePageCache } from '../../lib/pageCache'; export default function ExternalMyRequests() { const { currentUser } = useAuth(); const navigate = useNavigate(); const cacheKey = `ext-requests:${currentUser?.id}`; const cached = readPageCache(cacheKey, 3 * 60_000); const [projects, setProjects] = useState(() => cached?.projects || []); const [tasks, setTasks] = useState(() => cached?.tasks || []); const [submissions, setSubmissions] = useState(() => cached?.submissions || []); const [paidTaskIds, setPaidTaskIds] = useState(() => new Set(cached?.paidTaskIds || [])); const [loading, setLoading] = useState(() => !cached); const [error, setError] = useState(''); const [activeTab, setActiveTab] = useState('active'); const [filterProject, setFilterProject] = useState(''); const [filterRequester, setFilterRequester] = useState(''); useEffect(() => { async function load() { if (!currentUser?.id) { setLoading(false); return; } try { const [ { data: projectData, error: projectError }, { data: taskData, error: taskError }, { data: subData, error: subError }, { data: paidItems }, ] = await withTimeout( Promise.all([ supabase.from('projects').select('id, name, company_id').order('created_at', { ascending: false }), supabase.from('tasks').select('id, title, status, current_version, project_id, invoiced').order('submitted_at', { ascending: false }), supabase.from('submissions').select('task_id, submitted_by_name, version_number, deadline, is_hot, service_type, submitted_at').order('submitted_at', { ascending: false }), supabase .from('subcontractor_invoice_items') .select('task_id, invoice:subcontractor_invoices!inner(status)') .eq('subcontractor_invoices.status', 'paid'), ]), 15000, 'External requests load' ); if (projectError) throw projectError; if (taskError) throw taskError; if (subError) throw subError; const paid = new Set( (paidItems || []) .filter(item => item.invoice?.status === 'paid' && item.task_id) .map(item => item.task_id) ); setProjects(projectData || []); setTasks(taskData || []); setSubmissions(subData || []); setPaidTaskIds(paid); writePageCache(cacheKey, { projects: projectData || [], tasks: taskData || [], submissions: subData || [], paidTaskIds: [...paid] }); setError(''); } catch (err) { console.error('External requests load failed:', err); setError(err.message || 'Failed to load requests.'); } finally { setLoading(false); } } load(); }, [currentUser?.id]); if (loading) return

Loading...

; const isFullyClosedTask = (task) => task?.status === 'client_approved' && paidTaskIds.has(task.id); const latestTaskGroups = tasks.map(task => { const taskSubs = submissions.filter(s => s.task_id === task.id); const deadlineSource = getDeadlineSourceSubmission(task, taskSubs); if (!deadlineSource) return null; const currentVersion = getCurrentVersionForTask(task, taskSubs); const latestGroup = taskSubs.filter(s => s.version_number === currentVersion); return { task, primary: deadlineSource, group: latestGroup }; }).filter(Boolean); const projectNames = [...new Map( latestTaskGroups.map(({ task }) => { const p = projects.find(p => p.id === task.project_id); return p ? [p.id, p] : null; }).filter(Boolean) ).values()]; const requesterNames = [...new Set(submissions.map(s => s.submitted_by_name).filter(Boolean))].sort(); const filteredGroups = latestTaskGroups.filter(({ task, group }) => { if (filterProject && task.project_id !== filterProject) return false; if (filterRequester && !group.some(s => s.submitted_by_name === filterRequester)) return false; return true; }).sort((a, b) => { const aLatest = Math.max(...a.group.map(s => new Date(s.submitted_at).getTime())); const bLatest = Math.max(...b.group.map(s => new Date(s.submitted_at).getTime())); return bLatest - aLatest; }); const activeGroups = filteredGroups.filter(({ task }) => task?.status !== 'client_approved' && task?.status !== 'client_review'); const clientReviewGroups = filteredGroups.filter(({ task }) => task?.status === 'client_review'); const completedGroups = filteredGroups.filter(({ task }) => task?.status === 'client_approved' && !isFullyClosedTask(task)); const closedGroups = filteredGroups.filter(({ task }) => isFullyClosedTask(task)); const renderRow = ({ task, primary }) => { const project = projects.find(p => p.id === task?.project_id); const isCompleted = task?.status === 'client_approved'; const isFullyClosed = isFullyClosedTask(task); const revisionLabel = `R${String(primary.version_number ?? 0).padStart(2, '0')}`; const deadline = formatDateOnly(primary.deadline, 'Not specified'); return ( navigate(`/tasks/${task.id}`)} style={{ cursor: 'pointer' }}> {project?.name || 'No project'}
{task?.title || primary.service_type} {primary.is_hot ? Hot : null}
{revisionLabel} {primary.service_type || 'Request'} {deadline} {isFullyClosed ? Paid & Closed : isCompleted ? Completed : } ); }; const tabList = [ { id: 'active', label: 'Active', groups: activeGroups }, { id: 'client-review', label: 'Client Review', groups: clientReviewGroups }, { id: 'completed', label: 'Completed', groups: completedGroups }, { id: 'closed', label: 'Fully Closed', groups: closedGroups }, ]; const currentGroups = tabList.find(t => t.id === activeTab)?.groups || []; return (
Requests
All tasks in your assigned projects.
{(projectNames.length > 0 || requesterNames.length > 0) && (
{projectNames.length > 0 && (
Filter By Project
{projectNames.map(p => ( ))}
)} {requesterNames.length > 0 && (
Filter By Requester
{requesterNames.map(name => ( ))}
)}
)} {submissions.length === 0 ? (

No requests yet

Tasks will appear here once Fourge assigns you to a project.

) : filteredGroups.length === 0 ? (

No matching requests

Try clearing the current filters.

) : (
{tabList.map(tab => ( ))}
{currentGroups.length === 0 ? (

No {tabList.find(t => t.id === activeTab)?.label.toLowerCase()} requests

) : (
{currentGroups.map(group => renderRow(group))}
Project Name Revision Request Type Deadline Status
)}
)} {error &&
{error}
}
); }