import { useState, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import Layout from '../../components/Layout'; import StatusBadge from '../../components/StatusBadge'; import RequestForm from '../../components/RequestForm'; import { supabase } from '../../lib/supabase'; import { useAuth } from '../../context/AuthContext'; import { readPageCache, writePageCache } from '../../lib/pageCache'; import { withTimeout } from '../../lib/withTimeout'; import { getCurrentVersionForTask, getDeadlineSourceSubmission } from '../../lib/taskDeadlines'; import { fmtShortDate } from '../../lib/dates'; import { createInitialSubmissionForRequest, createTaskForRequest, findOrCreateProject } from '../../lib/requestSubmission'; import { createTaskFolder } from '../../lib/filebrowserFolders'; import SortTh from '../../components/SortTh'; import { useSortable } from '../../hooks/useSortable'; const CARD = { background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 8, backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)', }; const ListViewIcon = () => ( ); const GridViewIcon = () => ( ); const STATUS_TABS = [ { id: 'all', label: 'All' }, { id: 'not_started', label: 'Not Started' }, { id: 'in_progress', label: 'In Progress' }, { id: 'on_hold', label: 'On Hold' }, { id: 'client_review', label: 'In Review' }, { id: 'client_approved', label: 'Approved' }, { id: 'invoiced', label: 'Invoiced' }, { id: 'paid', label: 'Paid' }, ]; export default function TeamTasksPage() { const { currentUser } = useAuth(); const navigate = useNavigate(); const cached = readPageCache('team_requests'); const [projects, setProjects] = useState(() => cached?.projects || []); const [tasks, setTasks] = useState(() => cached?.tasks || []); const [submissions, setSubmissions] = useState(() => cached?.submissions || []); const [companies, setCompanies] = useState(() => cached?.companies || []); const [loading, setLoading] = useState(!cached); const [loadError, setLoadError] = useState(false); const [activeTab, setActiveTab] = useState('all'); const [viewMode, setViewMode] = useState(() => localStorage.getItem('requestsViewMode') || 'list'); const [filterCompany, setFilterCompany] = useState(''); const [filterProject, setFilterProject] = useState(''); const [showAddForm, setShowAddForm] = useState(false); const [addFormKey, setAddFormKey] = useState(0); const [addSaving, setAddSaving] = useState(false); const [addError, setAddError] = useState(''); const [addRequestKey, setAddRequestKey] = useState(() => crypto.randomUUID()); const { sortKey, sortDir, toggle, sort } = useSortable('submitted_at', 'desc'); const toggleView = () => setViewMode(v => { const n = v === 'list' ? 'grid' : 'list'; localStorage.setItem('requestsViewMode', n); return n; }); useEffect(() => { async function load() { try { const [{ data: subs }, { data: t }, { data: p }, { data: co }] = await withTimeout(Promise.all([ supabase.from('submissions').select('id, task_id, submitted_at, submitted_by, submitted_by_name, is_hot, service_type, deadline, version_number, type').order('submitted_at', { ascending: false }), supabase.from('tasks').select('id, title, status, current_version, project_id, assigned_name, assigned_to, invoiced, completed_at'), supabase.from('projects').select('id, name, status, company_id'), supabase.from('companies').select('id, name'), ]), 12000, 'Requests load'); setSubmissions(subs || []); setTasks(t || []); setProjects(p || []); setCompanies(co || []); writePageCache('team_requests', { submissions: subs || [], tasks: t || [], projects: p || [], companies: co || [] }); } catch { setLoadError(true); } finally { setLoading(false); } } load(); }, []); // eslint-disable-line react-hooks/exhaustive-deps const handleAddRequest = async (formData, _files, existingProjects) => { if (addSaving) return; setAddSaving(true); setAddError(''); try { const resolvedProject = await findOrCreateProject(formData.companyId, formData.project.trim(), existingProjects); if (!projects.some(p => p.id === resolvedProject.id)) setProjects(prev => [...prev, resolvedProject]); const { task } = await createTaskForRequest({ projectId: resolvedProject.id, title: formData.title.trim(), requestKey: addRequestKey }); if (!task) throw new Error('Failed to create task.'); const taskCompany = companies?.find(c => c.id === formData.companyId); createTaskFolder(taskCompany?.name, resolvedProject.name, formData.title.trim()).catch(() => {}); const { submission: sub } = await createInitialSubmissionForRequest({ taskId: task.id, requestKey: addRequestKey, isHot: formData.isHot, serviceType: formData.serviceType, deadline: formData.deadline, description: formData.description, submittedBy: formData.requestedBy, submittedByName: formData.requestedByName, }); if (!sub) throw new Error('Failed to create submission.'); const [{ data: newSubs }, { data: newTasks }] = await Promise.all([ supabase.from('submissions').select('id, task_id, submitted_at, submitted_by, submitted_by_name, is_hot, service_type, deadline, version_number, type').order('submitted_at', { ascending: false }), supabase.from('tasks').select('id, title, status, current_version, project_id, assigned_name, assigned_to, invoiced, completed_at'), ]); setSubmissions(newSubs || []); setTasks(newTasks || []); setShowAddForm(false); setAddFormKey(k => k + 1); setAddRequestKey(crypto.randomUUID()); } catch (err) { setAddError(err.message); } finally { setAddSaving(false); } }; if (loading) return

Loading...

; if (loadError) return (

Failed to load. Check your connection and try again.

); // ── Derived data ────────────────────────────────────────────────────── const latestGroups = 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); return { task, primary: deadlineSource, group: taskSubs.filter(s => s.version_number === currentVersion) }; }).filter(Boolean); const coProjects = filterCompany ? projects.filter(p => p.company_id === filterCompany) : []; const filtered = latestGroups.filter(({ task }) => { const project = projects.find(p => p.id === task?.project_id); if (filterCompany && project?.company_id !== filterCompany) return false; if (filterProject && task?.project_id !== filterProject) return false; return true; }).sort((a, b) => Math.max(...b.group.map(s => new Date(s.submitted_at).getTime())) - Math.max(...a.group.map(s => new Date(s.submitted_at).getTime())) ); const tabs = STATUS_TABS.map(t => ({ ...t, groups: t.id === 'all' ? filtered : filtered.filter(({ task }) => task?.status === t.id), })); const activeGroups = sort( tabs.find(t => t.id === activeTab)?.groups || [], ({ task, primary }, key) => { const project = projects.find(p => p.id === task?.project_id); const company = companies.find(co => co.id === project?.company_id); if (key === 'title') return task?.title || ''; if (key === 'client') return company?.name || ''; if (key === 'serviceType') return primary?.service_type || ''; if (key === 'deadline') return primary?.deadline || ''; if (key === 'completed_at') return task?.completed_at || ''; if (key === 'status') return task?.status || ''; if (key === 'submitted_at') return primary?.submitted_at || ''; return ''; } ); return ( {showAddForm && (
{ setShowAddForm(false); setAddFormKey(k => k + 1); setAddError(''); }} >
e.stopPropagation()} >
Add Request
{ setShowAddForm(false); setAddFormKey(k => k + 1); setAddError(''); }} saving={addSaving} error={addError} submitLabel="Add Request" />
)} {/* Filter bar */}
{companies.length > 0 && ( )} {coProjects.length > 0 && ( )}
{/* Content */} {submissions.length === 0 ? (
No requests yet.
) : filtered.length === 0 ? (
No matching requests.
) : activeGroups.length === 0 ? (
No {tabs.find(t => t.id === activeTab)?.label.toLowerCase()} requests.
) : viewMode === 'grid' ? (
{activeGroups.map(({ task, primary }) => { const project = projects.find(p => p.id === task?.project_id); const company = companies.find(co => co.id === project?.company_id); const svcType = submissions.find(s => s.task_id === task?.id && s.type === 'initial')?.service_type || primary?.service_type || '—'; return (
navigate(`/requests/${task.id}`)} style={{ ...CARD, padding: '14px 16px', cursor: 'pointer' }}>
{task?.title || '—'}
{project?.name || '—'}
{company &&
{company.name}
}
{svcType} {fmtShortDate(primary?.deadline)}
); })}
) : (
NameClientTypeDeadlineApprovedStatus {activeGroups.map(({ task, primary }) => { const project = projects.find(p => p.id === task?.project_id); const company = companies.find(co => co.id === project?.company_id); const svcType = submissions.find(s => s.task_id === task?.id && s.type === 'initial')?.service_type || primary?.service_type || '—'; return ( navigate(`/requests/${task.id}`)} style={{ cursor: 'pointer' }}> ); })}
{task?.title || '—'} {`R${String(primary.version_number ?? 0).padStart(2, '0')}`} {primary.is_hot && HOT}
{project?.name || '—'}
{company ? e.stopPropagation()}>{company.name} : No client} {svcType} {fmtShortDate(primary.deadline, '—')} {fmtShortDate(task?.completed_at)}
)}
); }