diff --git a/src/pages/ProjectDetail.jsx b/src/pages/ProjectDetail.jsx index f79077d..1d1cd87 100644 --- a/src/pages/ProjectDetail.jsx +++ b/src/pages/ProjectDetail.jsx @@ -17,7 +17,6 @@ import { fmtShortDate } from '../lib/dates'; import { popupOverlayStyle } from '../lib/popupStyles'; import { useLiveRefresh } from '../hooks/useLiveRefresh'; import { getTaskDerivedState } from '../lib/taskVersions'; -import { buildInvoiceStatusByKey, deriveVersionStatus } from '../lib/invoiceVersionRules'; import { TASK_TABLE_TH_STYLE, TASK_TABLE_TD_BASE, @@ -50,7 +49,6 @@ export default function ProjectDetailPage() { const [loading, setLoading] = useState(true); const [submissions, setSubmissions] = useState([]); const [deliveries, setDeliveries] = useState([]); - const [invoiceItems, setInvoiceItems] = useState([]); const [activeTab, setActiveTab] = useState('all'); const [editingName, setEditingName] = useState(false); @@ -113,14 +111,6 @@ export default function ProjectDetailPage() { const { data: act, error: actErr } = await supabase.from('activity_log').select('id, created_at, actor_name, action, task_title').eq('project_id', id).order('created_at', { ascending: false }).limit(50); if (actErr) console.error('activity_log fetch:', actErr); setActivityLog((act || []).filter(e => ['task_started', 'task_on_hold', 'task_approved'].includes(e.action))); - if (isTeam && taskIds.length > 0) { - const { data: invItems } = await supabase - .from('invoice_items') - .select('task_id, description, invoice:invoices(status)') - .in('task_id', taskIds) - .not('task_id', 'is', null); - setInvoiceItems(invItems || []); - } if (isTeam) { const { data: ext } = await supabase.from('profiles').select('id, name, avatar_url, email').eq('role', 'external').order('name'); setExtProfs(ext || []); @@ -239,21 +229,15 @@ export default function ProjectDetailPage() { if (loading) return ; if (!project) return

Project not found.

; - const invoiceStatusByKey = buildInvoiceStatusByKey(invoiceItems); - const rows = tasks.flatMap(task => { + const rows = tasks.map(task => { const derived = getTaskDerivedState(task, submissions, deliveries); - const versionSet = new Set(derived.taskSubs.map(s => Number(s.version_number ?? 0))); - versionSet.add(derived.currentVersion); - return [...versionSet].sort((a, b) => a - b).map(v => ({ - id: task.id, - rowKey: `${task.id}:${v}`, - title: task.title, - status: deriveVersionStatus(task, v, invoiceStatusByKey), - version: v, + return { + id: task.id, rowKey: task.id, title: task.title, status: task.status, + version: derived.currentVersion, assignedName: task.assigned_name || null, assigneeAvatar: task.assignee?.avatar_url || null, assignedTo: task.assigned_to || null, serviceType: derived.serviceType, deadline: derived.deadline, isHot: derived.isHot, submittedAt: derived.latestActivityAt ? new Date(derived.latestActivityAt).toISOString() : (task.submitted_at || ''), - })); + }; }); const sortedRows = sort(rows, (r, key) => { @@ -319,7 +303,7 @@ export default function ProjectDetailPage() { {(() => { - const approved = tasks.filter(t => t.status === 'client_approved').length; + const approved = tasks.filter(t => ['client_approved','invoiced','paid'].includes(t.status)).length; const pct = tasks.length > 0 ? Math.round((approved / tasks.length) * 100) : 0; return (
diff --git a/src/pages/Tasks.jsx b/src/pages/Tasks.jsx index d144948..e9b0664 100644 --- a/src/pages/Tasks.jsx +++ b/src/pages/Tasks.jsx @@ -21,7 +21,6 @@ import { popupOverlayStyle } from '../lib/popupStyles'; import { useLiveRefresh } from '../hooks/useLiveRefresh'; import { resolveScopedWorkIds } from '../lib/workScope'; import { mergeSubmissionDisplayNames } from '../lib/submissionDisplay'; -import { buildInvoiceStatusByKey, deriveVersionStatus } from '../lib/invoiceVersionRules'; import { TASK_TABLE_TH_STYLE, TASK_TABLE_TD_BASE, @@ -207,7 +206,6 @@ export default function RequestsPage() { const [tasks, setTasks] = useState(() => teamCached?.tasks || extCached?.tasks || []); const [submissions, setSubmissions] = useState(() => teamCached?.submissions || extCached?.submissions || []); const [deliveries, setDeliveries] = useState([]); - const [invoiceItems, setInvoiceItems] = useState([]); const [companies, setCompanies] = useState(() => teamCached?.companies || []); const [loading, setLoading] = useState(() => { if (isTeam) return !teamCached; @@ -323,16 +321,6 @@ export default function RequestsPage() { setDeliveries(deliveryRows); setCompanies(co || []); - // Fetch invoice items for per-version status derivation (team only) - if (isTeam && (t || []).length > 0) { - const taskIdsList = (t || []).map(tk => tk.id); - const { data: invItems } = await supabase - .from('invoice_items') - .select('task_id, description, invoice:invoices(status)') - .in('task_id', taskIdsList) - .not('task_id', 'is', null); - setInvoiceItems(invItems || []); - } if (isTeam) { writePageCache('team_requests', { submissions: hydratedSubs, tasks: t || [], projects: p || [], companies: co || [] }); @@ -542,9 +530,7 @@ export default function RequestsPage() { return companies.slice().sort((a, b) => (a.name || '').localeCompare(b.name || '')); }, [isClient, companies, currentUser]); // eslint-disable-line react-hooks/exhaustive-deps - const invoiceStatusByKey = useMemo(() => buildInvoiceStatusByKey(invoiceItems), [invoiceItems]); - - // Normalize tasks → one row per version for independent tracking/billing + // Normalize all tasks to a common row shape for unified table render const allRows = useMemo(() => { if (isClient) { return tasks.map(task => { @@ -558,27 +544,19 @@ export default function RequestsPage() { }; }); } - return tasks.flatMap(task => { + return tasks.map(task => { const derived = getTaskDerivedState(task, submissions, deliveries); - if (derived.visibleTaskSubs.length === 0 || !derived.deadlineSource) return []; - // One row per version that has a submission, always include current version - const versionSet = new Set(derived.taskSubs.map(s => Number(s.version_number ?? 0))); - versionSet.add(derived.currentVersion); - return [...versionSet].sort((a, b) => a - b).map(v => ({ - id: task.id, - rowKey: `${task.id}:${v}`, - title: task.title, - status: deriveVersionStatus(task, v, invoiceStatusByKey), - projectId: task.project_id, - serviceType: derived.serviceType, - deadline: derived.deadline, - version: v, + if (derived.visibleTaskSubs.length === 0 || !derived.deadlineSource) return null; + return { + id: task.id, rowKey: task.id, title: task.title, status: task.status, projectId: task.project_id, + serviceType: derived.serviceType, deadline: derived.deadline, + version: derived.currentVersion, isHot: derived.isHot, assignedName: task.assigned_name || null, assigneeAvatar: task.assignee?.avatar_url || null, assignedTo: task.assigned_to || null, submittedAt: derived.latestActivityAt, - })); - }); - }, [tasks, submissions, deliveries, invoiceStatusByKey, isClient]); // eslint-disable-line react-hooks/exhaustive-deps + }; + }).filter(Boolean); + }, [tasks, submissions, deliveries, isClient]); // eslint-disable-line react-hooks/exhaustive-deps const filteredRows = useMemo(() => { return allRows