From 13ef1f4ded9b288d970d188e65a7697c4808ab5f Mon Sep 17 00:00:00 2001 From: Krao Hasanee Date: Sat, 30 May 2026 10:19:23 -0400 Subject: [PATCH] Session 2026-05-30: tasks/projects unification, code consolidation, file renames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unified Tasks page: 3 role render blocks → 1, normalized row shape, single renderRow/sort/tabs - Fixed role scoping: external = all tasks in member projects, client = all tasks in company projects - Fixed client bug: was scoped by submitted_by, now company→project→tasks - Aligned dashboard client scope to match tasks page - Hot tasks dashboard: now filters to active statuses only (not completed/invoiced/paid) - Removed Projects.jsx (dead), fixed /project/ → /projects/ links - Renamed all page files to match folder path (Team*, External*, Client* prefixes) - Renamed: RequestsPage→Tasks, Settings→Profile, CompaniesPage→Companies, ProjectDetailPage→ProjectDetail - Dropped dead fetches from Tasks page (invoices, invoice_items, subcontractor_invoice_items) Co-Authored-By: Claude Sonnet 4.6 --- api/filebrowser.js | 24 +- src/App.jsx | 58 +- src/components/PageLoader.jsx | 33 +- src/components/RequestForm.jsx | 42 +- src/components/StatusBadge.jsx | 2 +- src/index.css | 75 ++ src/lib/popupStyles.js | 31 + src/pages/BrandBook.jsx | 21 +- .../{CompaniesPage.jsx => Companies.jsx} | 2 +- src/pages/FileSharing.jsx | 144 +-- src/pages/{Settings.jsx => Profile.jsx} | 78 +- ...rojectDetailPage.jsx => ProjectDetail.jsx} | 0 src/pages/RequestsPage.jsx | 1000 ----------------- src/pages/Tasks.jsx | 678 +++++++++++ .../{MyInvoices.jsx => ClientMyInvoices.jsx} | 0 .../{NewProject.jsx => ClientNewProject.jsx} | 0 .../{NewRequest.jsx => ClientNewRequest.jsx} | 0 ...Create.jsx => ExternalMyInvoiceCreate.jsx} | 0 ...Detail.jsx => ExternalMyInvoiceDetail.jsx} | 0 ...{MyInvoices.jsx => ExternalMyInvoices.jsx} | 0 ...rders.jsx => ExternalMyPurchaseOrders.jsx} | 0 ...reateInvoice.jsx => TeamCreateInvoice.jsx} | 0 ...orPO.jsx => TeamCreateSubcontractorPO.jsx} | 0 src/pages/team/TeamDashboard.jsx | 215 ++-- ...ePasswords.jsx => TeamFourgePasswords.jsx} | 0 ...nvoiceDetail.jsx => TeamInvoiceDetail.jsx} | 0 .../team/{Invoices.jsx => TeamInvoices.jsx} | 5 +- ...iceDetail.jsx => TeamSubInvoiceDetail.jsx} | 0 ...tail.jsx => TeamSubcontractorPODetail.jsx} | 0 supabase/functions/fbq-proxy/index.ts | 27 +- 30 files changed, 1177 insertions(+), 1258 deletions(-) create mode 100644 src/lib/popupStyles.js rename src/pages/{CompaniesPage.jsx => Companies.jsx} (99%) rename src/pages/{Settings.jsx => Profile.jsx} (91%) rename src/pages/{ProjectDetailPage.jsx => ProjectDetail.jsx} (100%) delete mode 100644 src/pages/RequestsPage.jsx create mode 100644 src/pages/Tasks.jsx rename src/pages/client/{MyInvoices.jsx => ClientMyInvoices.jsx} (100%) rename src/pages/client/{NewProject.jsx => ClientNewProject.jsx} (100%) rename src/pages/client/{NewRequest.jsx => ClientNewRequest.jsx} (100%) rename src/pages/external/{MyInvoiceCreate.jsx => ExternalMyInvoiceCreate.jsx} (100%) rename src/pages/external/{MyInvoiceDetail.jsx => ExternalMyInvoiceDetail.jsx} (100%) rename src/pages/external/{MyInvoices.jsx => ExternalMyInvoices.jsx} (100%) rename src/pages/external/{MyPurchaseOrders.jsx => ExternalMyPurchaseOrders.jsx} (100%) rename src/pages/team/{CreateInvoice.jsx => TeamCreateInvoice.jsx} (100%) rename src/pages/team/{CreateSubcontractorPO.jsx => TeamCreateSubcontractorPO.jsx} (100%) rename src/pages/team/{FourgePasswords.jsx => TeamFourgePasswords.jsx} (100%) rename src/pages/team/{InvoiceDetail.jsx => TeamInvoiceDetail.jsx} (100%) rename src/pages/team/{Invoices.jsx => TeamInvoices.jsx} (98%) rename src/pages/team/{SubInvoiceDetail.jsx => TeamSubInvoiceDetail.jsx} (100%) rename src/pages/team/{SubcontractorPODetail.jsx => TeamSubcontractorPODetail.jsx} (100%) diff --git a/api/filebrowser.js b/api/filebrowser.js index a929073..f7ab8c2 100644 --- a/api/filebrowser.js +++ b/api/filebrowser.js @@ -1,6 +1,6 @@ import { createClient } from '@supabase/supabase-js'; -const FB_SOURCE = 'files'; +const FB_SOURCE = 'srv'; function json(res, status, body) { res.status(status).setHeader('Content-Type', 'application/json'); @@ -263,7 +263,8 @@ function toListResponse(vPath, entries, { readOnly = false } = {}) { export default async function handler(req, res) { try { - const authHeader = req.headers.authorization || ''; + const queryToken = String(req.query?.sb_access_token || req.body?.sb_access_token || '').trim(); + const authHeader = req.headers.authorization || (queryToken ? `Bearer ${queryToken}` : ''); if (!authHeader) return json(res, 401, { error: 'No authorization header' }); const auth = await requirePortalUser(authHeader); @@ -333,6 +334,25 @@ export default async function handler(req, res) { return json(res, 200, { url: downloadUrl, token }); } + if (req.method === 'GET' && action === 'download-blob') { + const resolved = toFbPath(); + if (resolved.virtual) return json(res, 400, { error: 'Cannot download virtual directory' }); + const token = getToken(config); + const downloadUrl = `${config.url}/api/resources/download?source=${FB_SOURCE}&file=${encodeURIComponent(resolved.fbPath)}&auth=${encodeURIComponent(token)}`; + const upstream = await fetch(downloadUrl); + if (!upstream.ok) { + const text = await upstream.text(); + return json(res, upstream.status, { error: text || `Download failed (${upstream.status})` }); + } + + res.status(upstream.status); + res.setHeader('Cache-Control', 'no-store'); + res.setHeader('Content-Type', upstream.headers.get('content-type') || 'application/octet-stream'); + res.setHeader('Content-Disposition', upstream.headers.get('content-disposition') || `attachment; filename="${basename(resolved.fbPath) || 'download'}"`); + const arrayBuffer = await upstream.arrayBuffer(); + return res.send(Buffer.from(arrayBuffer)); + } + if (req.method === 'POST' && action === 'upload-token') { const resolved = toFbPath(); if (resolved.virtual) return json(res, 400, { error: 'Cannot upload to virtual directory' }); diff --git a/src/App.jsx b/src/App.jsx index 5d4969d..0611517 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -39,31 +39,31 @@ class ChunkErrorBoundary extends Component { import Login from './pages/Login'; import PayInvoice from './pages/PayInvoice'; -const ProfilePage = lazy(() => import('./pages/Settings')); -const CompaniesPage = lazy(() => import('./pages/CompaniesPage')); +const ProfilePage = lazy(() => import('./pages/Profile')); +const CompaniesPage = lazy(() => import('./pages/Companies')); const CompanyDetail = lazy(() => import('./pages/CompanyDetail')); -const Invoices = lazy(() => import('./pages/team/Invoices')); +const TeamInvoices = lazy(() => import('./pages/team/TeamInvoices')); const RequestDetail = lazy(() => import('./pages/RequestDetail')); -const CreateInvoice = lazy(() => import('./pages/team/CreateInvoice')); -const CreateSubcontractorPO = lazy(() => import('./pages/team/CreateSubcontractorPO')); -const InvoiceDetail = lazy(() => import('./pages/team/InvoiceDetail')); -const SubcontractorPODetail = lazy(() => import('./pages/team/SubcontractorPODetail')); -const SubInvoiceDetail = lazy(() => import('./pages/team/SubInvoiceDetail')); +const TeamCreateInvoice = lazy(() => import('./pages/team/TeamCreateInvoice')); +const TeamCreateSubcontractorPO = lazy(() => import('./pages/team/TeamCreateSubcontractorPO')); +const TeamInvoiceDetail = lazy(() => import('./pages/team/TeamInvoiceDetail')); +const TeamSubcontractorPODetail = lazy(() => import('./pages/team/TeamSubcontractorPODetail')); +const TeamSubInvoiceDetail = lazy(() => import('./pages/team/TeamSubInvoiceDetail')); const SurveyMaker = lazy(() => import('./pages/SurveyMaker')); const BrandBook = lazy(() => import('./pages/BrandBook')); const Converters = lazy(() => import('./pages/Converters')); const FileSharing = lazy(() => import('./pages/FileSharing')); -const FourgePasswords = lazy(() => import('./pages/team/FourgePasswords')); -const MyPurchaseOrders = lazy(() => import('./pages/external/MyPurchaseOrders')); -const ExternalMyInvoices = lazy(() => import('./pages/external/MyInvoices')); -const ExternalMyInvoiceDetail = lazy(() => import('./pages/external/MyInvoiceDetail')); -const ExternalMyInvoiceCreate = lazy(() => import('./pages/external/MyInvoiceCreate')); -const ProjectDetailPage = lazy(() => import('./pages/ProjectDetailPage')); +const TeamFourgePasswords = lazy(() => import('./pages/team/TeamFourgePasswords')); +const ExternalMyPurchaseOrders = lazy(() => import('./pages/external/ExternalMyPurchaseOrders')); +const ExternalMyInvoices = lazy(() => import('./pages/external/ExternalMyInvoices')); +const ExternalMyInvoiceDetail = lazy(() => import('./pages/external/ExternalMyInvoiceDetail')); +const ExternalMyInvoiceCreate = lazy(() => import('./pages/external/ExternalMyInvoiceCreate')); +const ProjectDetailPage = lazy(() => import('./pages/ProjectDetail')); const TeamDashboard = lazy(() => import('./pages/team/TeamDashboard')); -const RequestsPage = lazy(() => import('./pages/RequestsPage')); -const MyInvoices = lazy(() => import('./pages/client/MyInvoices')); -const NewRequest = lazy(() => import('./pages/client/NewRequest')); -const NewProject = lazy(() => import('./pages/client/NewProject')); +const RequestsPage = lazy(() => import('./pages/Tasks')); +const ClientMyInvoices = lazy(() => import('./pages/client/ClientMyInvoices')); +const ClientNewRequest = lazy(() => import('./pages/client/ClientNewRequest')); +const ClientNewProject = lazy(() => import('./pages/client/ClientNewProject')); function RedirectProjectDetail() { const { id } = useParams(); @@ -103,21 +103,21 @@ export default function App() { } /> } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> } /> } /> } /> } /> - } /> + } /> } /> } /> - } /> + } /> } /> } /> } /> @@ -133,9 +133,9 @@ export default function App() { } /> } /> } /> - } /> - } /> - } /> + } /> + } /> + } /> } /> } /> diff --git a/src/components/PageLoader.jsx b/src/components/PageLoader.jsx index 94601ad..cf0a78a 100644 --- a/src/components/PageLoader.jsx +++ b/src/components/PageLoader.jsx @@ -1,32 +1,43 @@ -export default function PageLoader() { +export default function PageLoader({ label = 'Loading', progress = null, scope = 'page' }) { + const hasProgress = typeof progress === 'number' && Number.isFinite(progress); + const isContainerScope = scope === 'container'; return (
-
- Loading -
-
-
+
+ {label}
+ <> +
+
+
+ {hasProgress && ( +
{progress}%
+ )} +
); diff --git a/src/components/RequestForm.jsx b/src/components/RequestForm.jsx index a0b1c84..d002261 100644 --- a/src/components/RequestForm.jsx +++ b/src/components/RequestForm.jsx @@ -5,6 +5,9 @@ import FileAttachment from './FileAttachment'; import { addDaysToDateOnly, getTodayDateOnlyEST } from '../lib/dates'; const defaultDeadline = () => addDaysToDateOnly(getTodayDateOnlyEST(), 3); +const modalLabelStyle = { fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 6 }; +const modalInputStyle = { fontSize: 13, padding: '0 5px', textAlign: 'left', lineHeight: 1 }; +const modalTextAreaStyle = { ...modalInputStyle, minHeight: 100, padding: '8px 5px', lineHeight: 1.4 }; const emptyForm = (companyId = '') => ({ companyId, project: '', @@ -123,11 +126,12 @@ export default function RequestForm({
{showCompanySelect && (
- + setNewProjectName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }} autoFocus - style={{ flex: 1 }} + style={{ ...modalInputStyle, flex: 1 }} /> - - + +
) : ( - {allProjectNames.map(name => )} {(!showCompanySelect || companyId) && } @@ -162,20 +166,20 @@ export default function RequestForm({
- - {serviceTypes.map(s => )}
- - + +
-