-
Active Projects
+
Active Clients
{profileStats.activeProjects}
diff --git a/src/pages/ProjectDetail.jsx b/src/pages/ProjectDetail.jsx
index 9ee287d..65b46bf 100644
--- a/src/pages/ProjectDetail.jsx
+++ b/src/pages/ProjectDetail.jsx
@@ -5,6 +5,7 @@ import PageLoader from '../components/PageLoader';
import ProfileAvatar from '../components/ProfileAvatar';
import StatusBadge from '../components/StatusBadge';
import SortTh from '../components/SortTh';
+import FilterDropdown from '../components/FilterDropdown';
import { supabase } from '../lib/supabase';
import { useAuth } from '../context/AuthContext';
import { logActivity } from '../lib/activityLog';
@@ -49,7 +50,11 @@ export default function ProjectDetailPage() {
const [loading, setLoading] = useState(true);
const [submissions, setSubmissions] = useState([]);
const [deliveries, setDeliveries] = useState([]);
- const [activeTab, setActiveTab] = useState('all');
+ const [activeTab, setActiveTab] = useState('tasks');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const [filterRevision, setFilterRevision] = useState('all');
+ const [filterType, setFilterType] = useState('all');
+ const [filterAssigned, setFilterAssigned] = useState('all');
const [editingName, setEditingName] = useState(false);
const [nameVal, setNameVal] = useState('');
@@ -227,7 +232,7 @@ export default function ProjectDetailPage() {
};
if (loading) return
;
- if (!project) return
Project not found.
;
+ if (!project) return
Client not found.
;
const rows = tasks.map(task => {
const derived = getTaskDerivedState(task, submissions, deliveries);
@@ -254,21 +259,51 @@ export default function ProjectDetailPage() {
const doneStatuses = new Set(['client_approved', 'invoiced', 'paid']);
const taskTabs = [
- { id: 'all', label: 'All Tasks' },
- { id: 'new_requests', label: 'New Requests' },
- { id: 'revisions', label: 'Revisions' },
- { id: 'in_progress', label: 'In Progress' },
- { id: 'on_hold', label: 'On Hold' },
- { id: 'client_review', label: 'In Review' },
- { id: 'completed', label: 'Completed' },
+ { id: 'tasks', label: 'Tasks' },
+ { id: 'completed', label: 'Completed' },
...(isTeam ? [{ id: 'members', label: 'Subcontractors' }] : []),
];
- const visibleRows = activeTab === 'all' ? sortedRows
- : activeTab === 'completed' ? sortedRows.filter(r => doneStatuses.has(r.status))
- : activeTab === 'new_requests' ? sortedRows.filter(r => r.status === 'not_started' && Number(r.version || 0) === 0)
- : activeTab === 'revisions' ? sortedRows.filter(r => r.status === 'not_started' && Number(r.version || 0) >= 1)
- : activeTab === 'members' ? []
- : sortedRows.filter(r => r.status === activeTab);
+ const tabBaseRows = activeTab === 'completed'
+ ? sortedRows.filter(r => doneStatuses.has(r.status))
+ : sortedRows.filter(r => !doneStatuses.has(r.status));
+ let visibleRows = tabBaseRows;
+ if (filterStatus !== 'all') visibleRows = visibleRows.filter(r => r.status === filterStatus);
+ if (filterRevision === 'new') visibleRows = visibleRows.filter(r => Number(r.version || 0) === 0);
+ else if (filterRevision === 'revision') visibleRows = visibleRows.filter(r => Number(r.version || 0) >= 1);
+ if (filterType !== 'all') visibleRows = visibleRows.filter(r => (r.serviceType || '') === filterType);
+ if (filterAssigned === 'unassigned') visibleRows = visibleRows.filter(r => !r.assignedTo);
+ else if (filterAssigned !== 'all') visibleRows = visibleRows.filter(r => r.assignedTo === filterAssigned);
+
+ const STATUS_FILTER_OPTIONS = activeTab === 'completed'
+ ? [
+ { value: 'all', label: 'All Statuses' },
+ { value: 'client_approved', label: 'Approved' },
+ { value: 'invoiced', label: 'Invoiced' },
+ { value: 'paid', label: 'Paid' },
+ ]
+ : [
+ { value: 'all', label: 'All Statuses' },
+ { value: 'not_started', label: 'Not Started' },
+ { value: 'in_progress', label: 'In Progress' },
+ { value: 'on_hold', label: 'On Hold' },
+ { value: 'client_review', label: 'In Review' },
+ ];
+ const REVISION_FILTER_OPTIONS = [
+ { value: 'all', label: 'All' },
+ { value: 'new', label: 'New (R00)' },
+ { value: 'revision', label: 'Revisions (R1+)' },
+ ];
+ const TYPE_FILTER_OPTIONS = [
+ { value: 'all', label: 'All Types' },
+ ...[...new Set(tabBaseRows.map(r => r.serviceType).filter(Boolean))].sort((a, b) => a.localeCompare(b)).map(t => ({ value: t, label: t })),
+ ];
+ const ASSIGNED_FILTER_OPTIONS = [
+ { value: 'all', label: 'All Assignees' },
+ { value: 'unassigned', label: 'Unassigned' },
+ ...[...new Map(tabBaseRows.filter(r => r.assignedTo).map(r => [r.assignedTo, r.assignedName || '—'])).entries()]
+ .sort((a, b) => a[1].localeCompare(b[1]))
+ .map(([id, name]) => ({ value: id, label: name })),
+ ];
const fmtDate = (d) => d ? new Date(d).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '—';
@@ -345,13 +380,8 @@ export default function ProjectDetailPage() {
{(() => {
const tabCounts = {
- all: rows.length,
- new_requests: rows.filter(r => r.status === 'not_started' && Number(r.version || 0) === 0).length,
- revisions: rows.filter(r => r.status === 'not_started' && Number(r.version || 0) >= 1).length,
- in_progress: rows.filter(r => r.status === 'in_progress').length,
- on_hold: rows.filter(r => r.status === 'on_hold').length,
- client_review: rows.filter(r => r.status === 'client_review').length,
- completed: rows.filter(r => ['client_approved','invoiced','paid'].includes(r.status)).length,
+ tasks: rows.filter(r => !doneStatuses.has(r.status)).length,
+ completed: rows.filter(r => doneStatuses.has(r.status)).length,
members: members.filter(m => m.profile?.role === 'external').length,
};
return (
@@ -360,7 +390,7 @@ export default function ProjectDetailPage() {
const count = tabCounts[tab.id] ?? 0;
const active = activeTab === tab.id;
return (
-