diff --git a/src/pages/TaskDetail.jsx b/src/pages/TaskDetail.jsx index d7233d6..b132524 100644 --- a/src/pages/TaskDetail.jsx +++ b/src/pages/TaskDetail.jsx @@ -13,6 +13,7 @@ import { sendEmail } from '../lib/email'; import { uploadFilesToRequestInfo, safeName } from '../lib/filebrowserFolders'; import { useRefetchOnFocus } from '../hooks/useRefetchOnFocus'; import { useRealtimeSubscription } from '../hooks/useRealtimeSubscription'; +import FileAttachment from '../components/FileAttachment'; const ACTION_LABEL = { task_created: 'created this task', task_started: 'started this task', task_on_hold: 'placed task on hold', @@ -855,19 +856,7 @@ export default function TaskDetail() { />
- - setRevisionFiles(prev => [...prev, ...Array.from(e.target.files)])} /> - - {revisionFiles.length > 0 && ( -
- {revisionFiles.map((f, i) => ( -
- {f.name} - -
- ))} -
- )} +
diff --git a/src/pages/team/TeamDashboard.jsx b/src/pages/team/TeamDashboard.jsx index f87022d..965be44 100644 --- a/src/pages/team/TeamDashboard.jsx +++ b/src/pages/team/TeamDashboard.jsx @@ -530,7 +530,7 @@ function ClientHighlightCard({ highlights }) { ); } -function TeamPerformanceCard({ tasks, profiles }) { +function TeamPerformanceCard({ tasks, profiles, deliveries }) { const navigate = useNavigate(); const profileMap = useMemo(() => { const m = new Map(); @@ -538,40 +538,43 @@ function TeamPerformanceCard({ tasks, profiles }) { return m; }, [profiles]); const doneStatuses = ['client_approved', 'invoiced', 'paid']; + const toEST = (dateStr) => new Date(new Date(dateStr).toLocaleString('en-US', { timeZone: 'America/New_York' })); + const monthsWithData = useMemo(() => new Set( + (deliveries || []).filter(d => d.submission?.task).map(d => { + const dt = toEST(d.sent_at); + return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}`; + }) + ), [deliveries]); // eslint-disable-line react-hooks/exhaustive-deps const allMonthOpts = useMemo(() => { const opts = []; - const now = new Date(); + const now = toEST(new Date().toISOString()); for (let i = 0; i < 6; i++) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1); opts.push({ value: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`, label: i === 0 ? 'This Month' : d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) }); } return opts; }, []); - const monthsWithData = useMemo(() => new Set( - (tasks || []).filter(t => doneStatuses.includes(t.status) && t.completed_at).map(t => { - const d = new Date(t.completed_at); - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`; - }) - ), [tasks]); // eslint-disable-line react-hooks/exhaustive-deps const monthOpts = allMonthOpts.filter(o => monthsWithData.has(o.value)); const [monthKey, setMonthKey] = useState(() => monthOpts[0]?.value || allMonthOpts[0].value); const { people, totalDone } = useMemo(() => { const [year, month] = monthKey.split('-').map(Number); const map = new Map(); - (tasks || []).forEach(t => { - if (!doneStatuses.includes(t.status) || !t.completed_at) return; - const d = new Date(t.completed_at); - if (d.getFullYear() !== year || d.getMonth() + 1 !== month) return; - if (!t.assigned_name) return; - const entry = map.get(t.assigned_name) || { name: t.assigned_name, id: t.assigned_to || null, newCount: 0, revCount: 0 }; - if ((t.current_version || 0) === 0) entry.newCount += 1; + (deliveries || []).forEach(d => { + const task = d.submission?.task; + if (!task) return; + const dt = toEST(d.sent_at); + if (dt.getFullYear() !== year || dt.getMonth() + 1 !== month) return; + const name = d.sent_by || task.assigned_name; + if (!name) return; + const entry = map.get(name) || { name, id: task.assigned_to || null, newCount: 0, revCount: 0 }; + if ((d.version_number || 0) === 0) entry.newCount += 1; else entry.revCount += 1; - map.set(t.assigned_name, entry); + map.set(name, entry); }); const people = [...map.values()].map(p => ({ ...p, done: p.newCount + p.revCount })).sort((a, b) => b.done - a.done); return { people, totalDone: people.reduce((s, p) => s + p.done, 0) }; - }, [tasks, monthKey]); // eslint-disable-line react-hooks/exhaustive-deps + }, [deliveries, monthKey]); // eslint-disable-line react-hooks/exhaustive-deps return (
@@ -584,7 +587,7 @@ function TeamPerformanceCard({ tasks, profiles }) {
- {people.slice(0, 5).length === 0 ? ( + {people.length === 0 ? (
No completed tasks this month
) : (
@@ -643,6 +646,7 @@ export default function TeamDashboard() { const [teamInvoices, setTeamInvoices] = useState(() => cached?.teamInvoices || []); const [teamExpenses, setTeamExpenses] = useState([]); const [myInvoices, setMyInvoices] = useState([]); + const [perfDeliveries, setPerfDeliveries] = useState([]); const [loading, setLoading] = useState(!cached); useEffect(() => { @@ -731,7 +735,7 @@ export default function TeamDashboard() { ? supabase.from('expenses').select('amount') : Promise.resolve({ data: [] }); - const [{ data: t }, { data: p }, { data: subs }, { data: profiles }, { data: activity }, { data: cos }, { data: memRows }, { data: invs }, { data: exps }] = await withTimeout(Promise.all([ + const [{ data: t }, { data: p }, { data: subs }, { data: profiles }, { data: activity }, { data: cos }, { data: memRows }, { data: invs }, { data: exps }, { data: delivs }] = await withTimeout(Promise.all([ tasksQuery, projectsQuery, submissionsQuery, @@ -741,6 +745,7 @@ export default function TeamDashboard() { supabase.from('company_members').select('company_id, profile_id'), invoicesPromise, expensesPromise, + supabase.from('deliveries').select('sent_by, sent_at, version_number, submission:submissions!inner(task:tasks!inner(assigned_to, assigned_name))').gte('sent_at', cutoffStr), ]), 30000, 'Dashboard load'); if (cancelled) return; @@ -768,6 +773,7 @@ export default function TeamDashboard() { setTeamInvoices(isTeam ? (invs || []) : []); setMyInvoices(!isTeam ? (invs || []) : []); setTeamExpenses(exps || []); + setPerfDeliveries(delivs || []); if (isTeam) { writePageCache(CACHE_KEY, { @@ -871,7 +877,7 @@ export default function TeamDashboard() {
- +
{isTeam && (