Session 2026-05-30: task detail page overhaul

- New TaskDetail replaces /tasks/:id (was v2 at /requests/:id/v2)
- Overview tab: R00 request info, Requested By/Date/Sign Count inline row, Notes, Sign Family, files, amendments
- Revisions tab: R01+ from submissions, newest first, same layout as overview, per-entry Amend button
- Comments tab: single-line input, post on Enter, delete own comments, Supabase task_comments table
- Folder tab: placeholder for file sharing
- Tab badges showing revision/comment counts
- Amend Request modal: drag-drop file zone, popupOverlayStyle/Surface, Save+Cancel buttons
- Request Revision modal for clients on approved/invoiced/paid tasks
- JSZip download-all with progress popup for submission files
- Upload progress popup for Add Task and amend file uploads
- FileAttachment drop zone: 8px radius, transparent bg, label style fix (no all-caps override)
- PageLoader on task detail load
- task_comments migration with RLS policies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-30 22:44:16 -04:00
parent 13ef1f4ded
commit 0b4705311b
28 changed files with 1491 additions and 1552 deletions
+28 -10
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import PageLoader from './PageLoader';
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
import { NavLink, Link, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
const ICONS = {
@@ -41,11 +41,12 @@ function TeamNav({ onNav }) {
const isCompaniesActive = location.pathname === '/company' && !location.search.includes('tab=users');
const isUsersActive = location.pathname === '/company' && location.search.includes('tab=users');
const isTasksActive = location.pathname === '/tasks' || location.pathname.startsWith('/requests/');
return (
<div className="sidebar-section">
{primaryLinks.map(({ to, label, icon }) => (
<NavLink key={to} to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NavLink key={to} to={to} onClick={onNav} className={to === '/tasks' ? () => `sidebar-link${isTasksActive ? ' active' : ''}` : ({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NI icon={icon} /><span className="nav-label">{label}</span>
</NavLink>
))}
@@ -67,6 +68,8 @@ function TeamNav({ onNav }) {
}
function ClientNav({ onNav }) {
const location = useLocation();
const isTasksActive = location.pathname === '/tasks' || location.pathname.startsWith('/requests/');
const links = [
{ to: '/dashboard', label: 'Dashboard', icon: ICONS.dashboard },
{ to: '/tasks', label: 'Tasks', icon: ICONS.requests },
@@ -77,7 +80,7 @@ function ClientNav({ onNav }) {
return (
<div className="sidebar-section">
{links.map(({ to, label, icon }) => (
<NavLink key={label} to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NavLink key={label} to={to} onClick={onNav} className={to === '/tasks' ? () => `sidebar-link${isTasksActive ? ' active' : ''}` : ({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NI icon={icon} /><span className="nav-label">{label}</span>
</NavLink>
))}
@@ -86,6 +89,8 @@ function ClientNav({ onNav }) {
}
function ExternalNav({ onNav }) {
const location = useLocation();
const isTasksActive = location.pathname === '/tasks' || location.pathname.startsWith('/requests/');
const links = [
{ to: '/dashboard', label: 'Dashboard', icon: ICONS.dashboard },
{ to: '/tasks', label: 'Tasks', icon: ICONS.requests },
@@ -106,7 +111,7 @@ function ExternalNav({ onNav }) {
<div className="sidebar-tools-label">Tools</div>
</>
)}
<NavLink to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NavLink to={to} onClick={onNav} className={to === '/tasks' ? () => `sidebar-link${isTasksActive ? ' active' : ''}` : ({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
<NI icon={icon} /><span className="nav-label">{label}</span>
</NavLink>
</div>
@@ -140,15 +145,16 @@ export default function Layout({ children }) {
const firstName = currentUser?.name?.split(' ')[0] || '';
const isProfileRoute = location.pathname === '/profile' || location.pathname.startsWith('/profile/');
const isFileSharingRoute = location.pathname === '/file-sharing';
const isRequestsRoute = location.pathname === '/tasks' || location.pathname === '/requests' || location.pathname === '/team/tasks';
const headerTitle = isProfileRoute ? 'Profile' : isFileSharingRoute ? 'File Sharing' : isRequestsRoute ? 'Tasks & Projects' : `Good ${timeOfDay}${firstName ? `, ${firstName}` : ''}`;
const isTaskDetailRoute = location.pathname.startsWith('/requests/');
const isRequestsRoute = location.pathname === '/tasks' || location.pathname === '/requests' || location.pathname === '/team/tasks' || isTaskDetailRoute;
const headerTitle = isProfileRoute ? 'Profile' : isFileSharingRoute ? 'File Sharing' : isRequestsRoute && !isTaskDetailRoute ? 'Tasks & Projects' : !isTaskDetailRoute ? `Good ${timeOfDay}${firstName ? `, ${firstName}` : ''}` : null;
const headerSubtitle = isProfileRoute
? 'Account details and security settings.'
: isFileSharingRoute
? 'Browse, share and manage files.'
: isRequestsRoute
: isRequestsRoute && !isTaskDetailRoute
? 'Browse and manage all tasks and projects.'
: "Here's what's happening today.";
: isTaskDetailRoute ? null : "Here's what's happening today.";
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
@@ -236,8 +242,20 @@ export default function Layout({ children }) {
<main className="main-content">
<div className="site-header">
<div>
<div className="site-header-greeting">{headerTitle}</div>
<div className="site-header-sub">{headerSubtitle}</div>
{isTaskDetailRoute ? (
<div>
<Link to="/tasks" className="dashboard-inline-link" style={{ display: 'inline-flex', alignItems: 'center', gap: 1, color: 'var(--text-muted)', textDecoration: 'none' }}>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ width: 24, height: 24, flexShrink: 0 }}><polyline points="10 4 6 8 10 12"/></svg>
<span style={{ fontSize: 24, fontWeight: 500, lineHeight: 1.2, letterSpacing: '-0.3px', position: 'relative', top: 2 }}>Tasks &amp; Projects</span>
</Link>
<div className="site-header-sub" style={{ paddingLeft: 25 }}>Return back to previous page</div>
</div>
) : (
<>
<div className="site-header-greeting">{headerTitle}</div>
<div className="site-header-sub">{headerSubtitle}</div>
</>
)}
</div>
<div className="site-header-right">
<div className="site-header-search-wrap">