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 && (