Session 2026-05-31: tasks table overhaul, FileBrowser redesign, folder path fix

- Tasks table: added R#, Assigned To (avatar), Priority (HOT/NO) columns; removed Status column; sortable columns; adjustable widths
- FileBrowser: full visual rewrite to match FileSharing page (table layout, colored ext badges, SVG folder icon, sortable columns, multi-select, context menu, drag-drop)
- TaskDetail folder tab: fix path with safeName(), rootPath set to project folder level
- filebrowserFolders: export safeName for reuse

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-31 11:02:40 -04:00
parent 0b4705311b
commit 5b3060e190
12 changed files with 724 additions and 2393 deletions
+27 -11
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { useParams, Link } from 'react-router-dom';
import Layout from '../components/Layout';
import PageLoader from '../components/PageLoader';
import FileBrowser from '../components/FileBrowser';
import StatusBadge from '../components/StatusBadge';
import { useAuth } from '../context/AuthContext';
import { supabase } from '../lib/supabase';
@@ -9,12 +10,13 @@ import { logActivity } from '../lib/activityLog';
import JSZip from 'jszip';
import { popupOverlayStyle, popupSurfaceStyle } from '../lib/popupStyles';
import { sendEmail } from '../lib/email';
import { uploadFilesToRequestInfo } from '../lib/filebrowserFolders';
import { uploadFilesToRequestInfo, safeName } from '../lib/filebrowserFolders';
const ACTION_LABEL = {
task_created: 'created this task', task_started: 'started this task', task_on_hold: 'placed task on hold',
task_resumed: 'resumed this task', task_submitted: 'submitted this task', task_approved: 'approved this task',
revision_requested: 'rejected this task', request_submitted: 'submitted this request',
task_released: 'released this task',
};
const ACTION_ICON = {
@@ -23,6 +25,7 @@ const ACTION_ICON = {
task_submitted: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: 'M12 19V5M5 12l7-7 7 7' },
task_approved: { bg: 'rgba(74,222,128,0.15)', color: '#4ade80', path: 'M4 13l5 5L20 7' },
task_on_hold: { bg: 'rgba(239,68,68,0.15)', color: '#ef4444', path: 'M6 4h4v16H6zM14 4h4v16h-4z' },
task_released: { bg: 'rgba(239,68,68,0.15)', color: '#ef4444', path: 'M12 5v14M5 12h14' },
task_created: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: 'M12 5v14M5 12h14' },
revision_requested: { bg: 'rgba(245,158,11,0.15)', color: '#f59e0b', path: 'M4 12a8 8 0 018-8v0a8 8 0 018 8' },
request_submitted: { bg: 'rgba(167,139,250,0.15)', color: '#a78bfa', path: 'M9 12h6M9 8h6M5 3h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2z' },
@@ -292,6 +295,7 @@ export default function TaskDetail() {
const handleSendToReview = () => updateStatus('client_review', {}, 'task_submitted');
const handleClientApprove = () => updateStatus('client_approved', { completed_at: new Date().toISOString() }, 'task_approved');
const handleClientReject = () => updateStatus('not_started', { current_version: (task.current_version || 0) + 1, assigned_to: null, assigned_name: null }, 'revision_requested');
const handleRelease = () => updateStatus('not_started', { assigned_to: null, assigned_name: null }, 'task_released');
const handleSubmitRevision = async () => {
setRevisionSaving(true);
@@ -408,19 +412,23 @@ export default function TaskDetail() {
{isTeamOrSub && status === 'not_started' && (
<button className="btn btn-outline" style={{ fontSize: 11, padding: '4px 12px' }} disabled={statusSaving} onClick={handleStart}>Start Task</button>
)}
{isTeamOrSub && status === 'in_progress' && (
{isTeamOrSub && status === 'in_progress' && task.assigned_to === currentUser?.id && (
<>
<button className="btn btn-outline" style={{ fontSize: 11, padding: '4px 12px' }} disabled={statusSaving} onClick={handleOnHold}>Place on Hold</button>
<button className="btn btn-outline" style={{ fontSize: 12, padding: '4px 12px', color: 'var(--accent)', borderColor: 'var(--accent)' }} disabled={statusSaving} onClick={handleSendToReview}>Place in Review</button>
<button className="btn btn-outline" disabled={statusSaving} onClick={handleRelease}>Release</button>
<button className="btn btn-outline" disabled={statusSaving} onClick={handleOnHold}>Place on Hold</button>
<button className="btn btn-primary" disabled={statusSaving} onClick={handleSendToReview}>Place in Review</button>
</>
)}
{isTeamOrSub && status === 'on_hold' && (
<button className="btn btn-outline" style={{ fontSize: 11, padding: '4px 12px' }} disabled={statusSaving} onClick={handleResume}>Resume</button>
{isTeamOrSub && status === 'on_hold' && task.assigned_to === currentUser?.id && (
<>
<button className="btn btn-outline" disabled={statusSaving} onClick={handleRelease}>Release</button>
<button className="btn btn-outline" disabled={statusSaving} onClick={handleResume}>Resume</button>
</>
)}
{isClient && status === 'client_review' && (
<>
<button className="btn btn-outline" style={{ fontSize: 12, padding: '4px 12px', color: 'var(--accent)', borderColor: 'var(--accent)' }} disabled={statusSaving} onClick={handleClientApprove}>Approve</button>
<button className="btn btn-danger" style={{ fontSize: 11, padding: '4px 12px' }} disabled={statusSaving} onClick={handleClientReject}>Reject</button>
<button className="btn btn-primary" disabled={statusSaving} onClick={handleClientApprove}>Approve</button>
<button className="btn btn-danger" disabled={statusSaving} onClick={handleClientReject}>Reject</button>
</>
)}
{isClient && ['client_approved', 'invoiced', 'paid'].includes(status) && (
@@ -494,8 +502,8 @@ export default function TaskDetail() {
);
})}
</div>
<div className="card" style={{ ...CARD, flex: 1, minHeight: 0, overflowY: 'auto' }}>
<div style={{ fontSize: 13 }}>
<div className="card" style={{ ...CARD, flex: 1, minHeight: 0, overflowY: activeTab === 'Folder' ? 'hidden' : 'auto', padding: activeTab === 'Folder' ? 0 : CARD.padding, ...(activeTab === 'Folder' ? { display: 'flex', flexDirection: 'column' } : {}) }}>
<div style={{ fontSize: 13, ...(activeTab === 'Folder' ? { flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 } : {}) }}>
{activeTab === 'Overview' && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 32 }}>
@@ -645,7 +653,15 @@ export default function TaskDetail() {
}
</div>
)}
{activeTab === 'Folder' && <div style={{ color: 'var(--text-muted)' }}>Folder view coming soon.</div>}
{activeTab === 'Folder' && company?.name && project?.name && (
<FileBrowser
initialPath={`/Clients/${safeName(company.name)}/Projects/${safeName(project.name)}/${safeName(task.title)}`}
rootPath={`/Clients/${safeName(company.name)}/Projects/${safeName(project.name)}`}
/>
)}
{activeTab === 'Folder' && (!company?.name || !project?.name) && (
<div style={{ color: 'var(--text-muted)' }}>No folder available task needs a project and company.</div>
)}
</div>
</div>
</div>