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:
+28
-10
@@ -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 & 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">
|
||||
|
||||
Reference in New Issue
Block a user