feat: project detail overhaul, realtime updates, avatar RLS fix

- ProjectDetail: full redesign — icon meta row, completion progress bar, main contact card, activity feed, subcontractor management modal with multi-select
- ProjectDetail: task table matches Tasks.jsx (R#, assigned avatar, priority, type, deadline, status) with status tabs + counts
- Realtime: useRefetchOnFocus + useRealtimeSubscription hooks wired to Tasks, TaskDetail, ProjectDetail, TeamDashboard
- AuthContext: live profile updates via Supabase realtime channel
- Tasks/ProjectDetail: assignee avatar join added for all roles (client, external, team)
- RLS: allow all authenticated users to read profiles (fixes avatar display across roles)
- RequestForm: lockedFields prop for pre-filled read-only company/project fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-31 16:55:58 -04:00
parent 35d92d2176
commit 70ad8d0cef
8 changed files with 546 additions and 203 deletions
+11 -3
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, useRef } from 'react';
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import Layout from '../../components/Layout';
import SortTh from '../../components/SortTh';
@@ -8,6 +8,8 @@ import { readPageCache, writePageCache } from '../../lib/pageCache';
import { withTimeout } from '../../lib/withTimeout';
import { getDeadlineSourceSubmission } from '../../lib/taskDeadlines';
import { useSortable } from '../../hooks/useSortable';
import { useRefetchOnFocus } from '../../hooks/useRefetchOnFocus';
import { useRealtimeSubscription } from '../../hooks/useRealtimeSubscription';
// ─── Helpers ──────────────────────────────────────────────────────────────
@@ -625,6 +627,12 @@ export default function TeamDashboard() {
const isTeam = currentUser?.role === 'team';
const isClient = currentUser?.role === 'client';
const isExternal = currentUser?.role === 'external';
const [refreshKey, setRefreshKey] = useState(0);
const refresh = useCallback(() => setRefreshKey(k => k + 1), []);
useRefetchOnFocus(refresh);
useRealtimeSubscription(['tasks', 'projects', 'submissions', 'activity_log', 'profiles'], refresh);
const cached = isTeam ? readPageCache(CACHE_KEY, 5 * 60_000) : null;
const [tasks, setTasks] = useState(() => cached?.tasks || []);
@@ -679,7 +687,7 @@ export default function TeamDashboard() {
}
const tasksQuery = supabase.from('tasks')
.select('id, title, status, current_version, project_id, assigned_name, assigned_to, completed_at')
.select('id, title, status, current_version, project_id, assigned_name, assigned_to, completed_at, assignee:profiles!assigned_to(avatar_url)')
.gte('submitted_at', cutoffStr)
.order('submitted_at', { ascending: false });
if (isClient) {
@@ -784,7 +792,7 @@ export default function TeamDashboard() {
loadDashboard();
return () => { cancelled = true; };
}, [isTeam, isClient, isExternal, currentUser?.id, currentUser?.company_id, currentUser?.companies]); // eslint-disable-line react-hooks/exhaustive-deps
}, [isTeam, isClient, isExternal, currentUser?.id, currentUser?.company_id, currentUser?.companies, refreshKey]); // eslint-disable-line react-hooks/exhaustive-deps
const teamHighlights = useMemo(() =>
isTeam ? buildClientHighlights(allCompanies, projects, tasks, clientProfiles, companyMemberships, teamInvoices)