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