From 0d6ac3666ef15710733d83fd4d662d1af5f0adb4 Mon Sep 17 00:00:00 2001 From: Krao Hasanee Date: Wed, 15 Jul 2026 15:14:57 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20revert=20leftover=20Project=E2=86=92Clie?= =?UTF-8?q?nt=20mislabels,=20drop=20tracked=20.env.backfill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename in 91045a1 was only partially reverted by later commits, leaving ~20 UI spots calling the project entity "Client" while table headers already said "Project". Reverted all of them plus the /clients/:id route (now /projects/:id canonical again, nothing was live yet). Also: stop tracking .env.backfill (had a live admin password/token committed - rotate those credentials), remove stale pre-redesign layout.md superseded by REDESIGN-LAYOUT2.md. --- .env.backfill | 35 - .gitignore | 1 + REDESIGN-LAYOUT2.md | 1 + layout.md | 759 ------------------- src/App.jsx | 8 +- src/components/Layout.jsx | 4 +- src/components/RequestForm.jsx | 4 +- src/pages/Companies.jsx | 2 +- src/pages/CompanyDetail.jsx | 16 +- src/pages/Profile.jsx | 2 +- src/pages/ProjectDetail.jsx | 4 +- src/pages/TaskDetail.jsx | 4 +- src/pages/Tasks.jsx | 10 +- src/pages/team/TeamCreateSubcontractorPO.jsx | 6 +- src/pages/team/TeamDashboard.jsx | 2 +- src/pages/team/TeamInvoices.jsx | 4 +- src/pages/team/TeamReports.jsx | 8 +- src/pages/team/TeamSubcontractorPODetail.jsx | 6 +- 18 files changed, 42 insertions(+), 834 deletions(-) delete mode 100644 .env.backfill delete mode 100644 layout.md diff --git a/.env.backfill b/.env.backfill deleted file mode 100644 index 1229cdb..0000000 --- a/.env.backfill +++ /dev/null @@ -1,35 +0,0 @@ -# Created by Vercel CLI -FILEBROWSER_ADMIN_PASS="ChloeH092524#" -FILEBROWSER_ADMIN_USER="admin" -FILEBROWSER_SUBS_ROOT="/fourgebranding/team" -FILEBROWSER_TEAM_ROOT="/fourgebranding" -FILEBROWSER_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJGaWxlQnJvd3NlciBRdWFudHVtIiwiZXhwIjo2NTc1NDk4NDMzLCJpYXQiOjE3NzkwNzAxMjYsImJlbG9uZ3NUbyI6MSwiUGVybWlzc2lvbnMiOnsiYXBpIjp0cnVlLCJhZG1pbiI6dHJ1ZSwibW9kaWZ5Ijp0cnVlLCJzaGFyZSI6dHJ1ZSwicmVhbHRpbWUiOnRydWUsImRlbGV0ZSI6dHJ1ZSwiY3JlYXRlIjp0cnVlLCJkb3dubG9hZCI6dHJ1ZX19.sQzImZQMlvbKpDWdnN9ksehmkNG8wy6SpnjgZ1uFC2c" -FILEBROWSER_URL="https://fourgebranding.krao.us" -NX_DAEMON="false" -PASSWORD_VAULT_KEY="" -SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZxZmx4eHF2ZW5uaHZvZXl3cmR3Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3NDA0NzEzNCwiZXhwIjoyMDg5NjIzMTM0fQ.OPI-XtZDI0x83Lu5HaUl-YZx2EFAjFtHDivKx1_DXxA" -SUPABASE_WEBHOOK_SECRET="c76769359f53e7fb920776dd895f38b243cfdab4e2f07740" -TURBO_CACHE="remote:rw" -TURBO_DOWNLOAD_LOCAL_ENABLED="true" -TURBO_REMOTE_ONLY="true" -TURBO_RUN_SUMMARY="true" -VERCEL="1" -VERCEL_ENV="production" -VERCEL_GIT_COMMIT_AUTHOR_LOGIN="" -VERCEL_GIT_COMMIT_AUTHOR_NAME="" -VERCEL_GIT_COMMIT_MESSAGE="" -VERCEL_GIT_COMMIT_REF="" -VERCEL_GIT_COMMIT_SHA="" -VERCEL_GIT_PREVIOUS_SHA="" -VERCEL_GIT_PROVIDER="" -VERCEL_GIT_PULL_REQUEST_ID="" -VERCEL_GIT_REPO_ID="" -VERCEL_GIT_REPO_OWNER="" -VERCEL_GIT_REPO_SLUG="" -VERCEL_OIDC_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1yay00MzAyZWMxYjY3MGY0OGE5OGFkNjFkYWRlNGEyM2JlNyJ9.eyJpc3MiOiJodHRwczovL29pZGMudmVyY2VsLmNvbS9rcmFvIiwic3ViIjoib3duZXI6a3Jhbzpwcm9qZWN0OmZvdXJnZS1wb3J0YWw6ZW52aXJvbm1lbnQ6ZGV2ZWxvcG1lbnQiLCJzY29wZSI6Im93bmVyOmtyYW86cHJvamVjdDpmb3VyZ2UtcG9ydGFsOmVudmlyb25tZW50OmRldmVsb3BtZW50IiwiYXVkIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2tyYW8iLCJvd25lciI6ImtyYW8iLCJvd25lcl9pZCI6InRlYW1fR01WaUJ3N2xWYVpjVG9ST21RelFaeExyIiwicHJvamVjdCI6ImZvdXJnZS1wb3J0YWwiLCJwcm9qZWN0X2lkIjoicHJqX0RtUjdoZXhHdWoydTFiaHUzUlRpaGNVQWFwb0giLCJlbnZpcm9ubWVudCI6ImRldmVsb3BtZW50IiwicGxhbiI6ImhvYmJ5IiwidXNlcl9pZCI6InR3Q3dFWkI2MHZFelpma1pjWnREQ0VFbSIsImNsaWVudF9pZCI6ImNsX0hZeU9QQk50Rk1mSGhhVW45TDRRUGZUWno2VFA0N2JwIiwibmJmIjoxNzc5MDc0MTYyLCJpYXQiOjE3NzkwNzQxNjIsImV4cCI6MTc3OTExNzM2Mn0.Exb_7yIDR4iFQUEjRm2EpGRNHYug3Ixb7kcrga2Wj4DMsVY9Cm7AnC7wwe87e5SkA_qaKzVw6jR0w_obBAXPcZYQdnGhE4uAwz5LX4fVDTys-jBAzCp6AMNLmjnCjFBMfsB1UkO7g7OX7z9rhkvhLa8HVrt47Ulg5f5BN6E571ob23hkJJFP9fz5NzSzq-jXb-cyXfkcjQAWdU0Lw-NpuaoeUNr-VZhIc17rfQ6gwT-57UWi47Yikvn4bElVHAYT_UVtqPcph3LN9UNPRCtOwh0VQxzi-RiamW3maEmCdxsDv88DwY9Xj_jn79I_MIcmOUBvQGLjArkj-cxvFEwgqg" -VERCEL_TARGET_ENV="production" -VERCEL_TOKEN="" -VERCEL_URL="" -VITE_MAPBOX_TOKEN="pk.eyJ1Ijoia3Jhb2ZvdXJnZSIsImEiOiJjbW9hdHlyM2EwYW84MnBwemx1ZjRqYzY2In0.afcwBOBqUBnJqn9zIvZShQ" -VITE_SUPABASE_ANON_KEY="sb_publishable_qNNIKtnu1dUIVKelq9aYYQ_TfHgzhyR" -VITE_SUPABASE_URL="https://fqflxxqvennhvoeywrdw.supabase.co" diff --git a/.gitignore b/.gitignore index 3b62bc5..a2fd78f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env.backfill # Editor directories and files .vscode/* diff --git a/REDESIGN-LAYOUT2.md b/REDESIGN-LAYOUT2.md index 22fb041..689575a 100644 --- a/REDESIGN-LAYOUT2.md +++ b/REDESIGN-LAYOUT2.md @@ -191,3 +191,4 @@ Legend: ⬜ todo Β· πŸ”„ in progress Β· βœ… done - 2026-06-23 β€” Popups single-source: `--popup-bg` now = `var(--card-bg)` (dark); stripped 8 inline `background: var(--card-bg)` overrides on `popupSurfaceStyle` so loader/modals/menus all use the token. Light stays opaque white. - 2026-06-23 β€” Dashboard reflow: row2 = To Do (fluid) + Calendar (280px); row3 = Activity / In-Progress / Team Perf. New `.dash-row-todo` / `.dash-row-trio` classes; responsive (trio 2-up ≀1200, all stack ≀768). Retired bottomCardsHeight viewport-fill hack. Mobile-friendly is now a standing requirement for Layout2. - 2026-06-23 β€” Perf sweep: root cause = ~400–770ms/query server cold-start (compute tier, infra). Code fixes: added FK/filter index migration (pushed to DB); removed redundant client/external scope waterfall in Tasks + Dashboard (rely on RLS, verified `has_company_access`/`project_members` parity); embedded submitter-profiles + deliveries into the Tasks submissions query (βˆ’2 round trips all roles). Test client/external dashboards on 5173. +- 2026-07-15 β€” Naming cleanup: the 91045a1 Projectβ†’Client rename was only partially walked back by later commits, leaving the project entity mislabeled "Client" in ~20 spots (Add Project modal, filters, PO/report/dashboard columns, etc.) while table headers already said "Project". Confirmed with user: **Company** = the customer org (e.g. "Public Storage"), **Project** = a job within that company β€” "Client" was a leftover label, not a third entity. Reverted all mislabeled text to "Project"; reverted the `/clients/:id` route back to canonical `/projects/:id` (nothing deployed to prod yet, so no live URLs broke). Also: removed stale pre-redesign `layout.md` (superseded by this doc), stopped tracking `.env.backfill` (had a live admin password/token committed β€” rotate those credentials separately). diff --git a/layout.md b/layout.md deleted file mode 100644 index 2fd64e6..0000000 --- a/layout.md +++ /dev/null @@ -1,759 +0,0 @@ -# Fourge Portal Layout System - -This is the single source of truth for dashboard/profile visual structure and UI geometry. - -## 1) Global Frame -- Viewport app shell: `height: 100vh`, `overflow: hidden` -- Main content gutter: `24px` all sides -- Sidebar: `width: 76px`, `top: 24px`, `left: 24px`, `height: calc(100vh - 48px)`, `border-radius: 8px` -- Main wrapper offset from sidebar: `margin-left: 100px` -- Page rhythm unit: `24px` (header spacing, card gaps, section gaps) - -## 2) Theme + Background -- Background ownership is `body` via `background: var(--bg)`. -- Dark base token `--bg` is full gradient: - - radial glow + vertical dark gradient. -- Light base token `--bg` is full gradient: - - gray radial glow + white vertical gradient. -- A faint ambient grid may sit above the base gradient but behind all app content: - - use a fixed `body::before` layer with very low-contrast line color - - keep the static grid a touch more visible than before, but still quiet - - any animation should be tiny spark nodes that travel on the grid lines, not wide screen sweeps - - use several small sparks across the page with varied directions so the motion feels distributed rather than like one falling line - - avoid additional decorative shell/menu entrance animations so the sparks are the only ambient page motion - - keep animation slow and drifting, not pulsing or flashy - - keep the grid masked/faded so it is strongest near the top and softer lower in the page -- Auth/login screens use the same shared `body` background and spark effect as the app shell; auth surfaces should stay transparent around the card so the global background remains visible. -- Do not use `html` theme-gradient scripting for Safari chrome behavior. - -## 3) Tokens -- Accent: `#F5A523` -- Card bg dark: `rgba(255,255,255,0.02)` -- Card bg light: `rgba(0,0,0,0.02)` -- Sidebar/menu shell uses the same transparent card background token as cards -- Secondary card tone dark: `rgba(255,255,255,0.08)` -- Secondary card tone light: `rgba(0,0,0,0.08)` -- Border dark: `rgba(245,165,35,0.15)` -- Border light: `rgba(0,0,0,0.1)` -- Text primary dark/light: `#ffffff / #0d0d0d` -- Text secondary dark/light: `#a8a8a8 / rgba(0,0,0,0.6)` -- Text muted dark/light: `#666666 / rgba(0,0,0,0.38)` - -## 4) Typography -- Font family: `Fourge`, then `-apple-system`, `BlinkMacSystemFont`, `'Segoe UI'`, `sans-serif` -- Base font size: `14px` -- Header title: `28px`, `500`, `line-height: 1.2` -- Header subtitle: `13px` -- Widget title: `11px`, `500`, uppercase, `letter-spacing: 0.8px` -- Section header (new standard): `18px`, `500`, Title Case, `letter-spacing: 0.2px`, `line-height: 1.1` -- Body table text: `12px/13px` by column importance - -## 5) Card System -- Default widget shell: - - `background: var(--card-bg)` - - `border: 1px solid var(--border)` - - `border-radius: 8px` - - `padding: 18px 21px` - - `backdrop-filter: blur(12px)` + `-webkit-backdrop-filter` -- Card width is not an intrinsic card token. - - Width is owned by the page/grid/container the card lives in. - - Cards should fill the slot provided by that page layout unless a page-level rule explicitly fixes a track. -- Compact card radius (legacy generic `.card`): `4px` (do not use for new dashboard widgets) - -## 6) Header + Top Right Controls -- Site header: `padding-top: 24px`, `padding-bottom: 24px` -- Right control row: - - Search icon button: `32x32` - - Search button to theme toggle space: `7px` (`search-wrap margin-right`) - - Theme toggle: `32x32` - - Theme toggle to avatar: `14px` (`avatar-wrap margin-left`) - - Avatar button: `49x49`, circle, `2px` inner ring + `2px` accent outline - -## 6.5) Section Control Bars (Tabs + Actions) -- For page-level card controls (ex: Tasks/Projects, Finances tabs): - - container uses: `display: flex`, `align-items: center`, `gap: 4`, `margin-bottom: 10`, `flex-shrink: 0` - - **`margin-bottom: 10` is the site-wide standard gap between any tab/control bar and the card(s) below β€” use this everywhere, no exceptions** - - tab bar row must always be `min-height: var(--btn-height)` so the gap to the card never shifts when action buttons appear/disappear - - tabs stay on the left in source order - - when a tab has a count, use the same inline badge pattern as task detail tabs: - - `margin-left: 5` - - `font-size: 12` - - `font-weight: 600` - - `background: var(--card-bg-2)` - - `border: 1px solid var(--border)` - - `border-radius: 10px` - - `padding: 3px 8px` - - active tab count uses `var(--accent)` text; inactive uses `var(--text-muted)` - - action buttons group sits on the right using: `margin-left: auto`, `display: flex`, `align-items: center`, `gap: 8` - - do not use hardcoded spacer blocks (`width` filler divs) to force alignment - - icon-only filter/action buttons share the same row and align vertically with add buttons - - filter button wrapper: `
` β€” `display: flex` prevents block stretching that misaligns the button vertically - - filter button: `className="btn btn-outline"` + `style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}`, icon only (no label), funnel SVG `` at `13Γ—13`; dropdown uses `site-header-avatar-menu` + `site-header-avatar-item` classes - - when the control bar uses a multi-column grid (to align with split-card layouts below), add `align-items: center` to the grid and `min-height: var(--btn-height)` to every column so row height is stable across all tab states - - Tasks/Projects page tab rules: - - default landing tab is `New Requests` - - keep `All Tasks`, `In Progress`, `On Hold`, `In Review`, and `Completed` - - replace the old `To Do` tab with: - - `New Requests` for `not_started` rows on `R00` - - `Revisions` for `not_started` rows on `R01+` - - the project table tabs on the right also use the same count badge pattern - -## 7) Dashboard Grids (Team) -- Stat row: `grid-template-columns: 1fr 1fr 1fr 1.5fr`, `gap: 24`, `margin-bottom: 0` -- Row 2: `grid-template-columns: 1fr 1fr 280px`, `gap: 24`, `margin-top: 24` - - Order: Recent Activity (left), Tasks In Progress (center), Calendar (right, fixed width) -- Row 3: `grid-template-columns: 1fr 1fr`, `gap: 24`, `margin-top: 24` -- Row 4 full-width: `margin-top: 24` - -## 8) Stat Cards -- Card min height: `120px` -- Internal row gap: `21px` -- Label/value/sub spacing: - - Label: `margin-bottom: 5px` - - Value: `30px`, `400`, `letter-spacing: -0.5`, `line-height: 1.1` - - Sub: `12px`, `margin-top: 5px` -- Icon badge: `27x27`, circle -- Icon glyph: `13x13` - -## 9) Calendar -- Card uses widget shell -- Header-to-grid gap: `14px` -- Weekday label: `10px`, `600`, `letter-spacing: 0.5` -- Day cell button: `28x28`, circular -- Day number: `12px` -- Today style: bg `#F5A523`, text `#0d0d0d`, `700` -- Dots: up to 3, each `3x3`, gap `2` -- Popover: - - Anchored left of cell: `right: calc(100% + 8px)`, vertical centered - - `width: 210px`, `padding: 10px 12px`, `border-radius: 8px` - - shadow `0 12px 32px rgba(0,0,0,0.45)` - - row dot `6x6`, row text `12px` - -## 10) Activity + Performance Rows -- Visible rows target: 5 -- Row layout: `display:flex`, `align-items:center`, `gap:10px` -- Row spacing: `margin-top: 10px` from second row onward -- Name text: `13px` -- Meta/date text: `11px` -- Progress track: `height: 4px`, `radius: 2px` -- Percentage width slot: `min-width: 28px` -- Empty states in all cards (dashboard, profile, tasks, projects, etc.): - - any `No ...` message is centered in the card body (`display:flex`, `align-items:center`, `justify-content:center`) - - use shared class treatment (`.card-empty-center`) for consistency - - avoid top-offset-only placement for empty text - -## 11) Tables -- General table layout in dashboard cards: `table-layout: fixed`, `border-collapse: collapse` -- Column widths are table-specific, not universal tokens. - - Widths belong to the individual table/view that defines them. - - Reuse widths only when the same table pattern is intentionally repeated. -- Header cells: - - `font-size: 10px`, `font-weight: 500`, uppercase, `letter-spacing: 0.6px` - - bottom spacing: `padding-bottom: 12px` - - sticky behavior for scrollable tables: - - table headers stay fixed while body scrolls - - use shared sticky-head treatment for all app tables (`position: sticky; top: 0`) - - table scrollbars are visually hidden for table scroll containers; wheel/trackpad scrolling remains active -- Body cells: - - primary text: `13px` - - secondary/metrics text: `12px` - - row vertical spacing via cell padding: typically `5px` -- Hot Tasks column widths: - - check `10%`, task `40%`, requested by `35%`, due by `15%` -- Sorting rule: - - Every visible data column header must be sortable. - - Use clickable header controls (`SortTh`) with ascending/descending indicator. - - Exclude only non-data utility/action columns (checkbox-only, icon-only status marker, action buttons). - -## 12) Profile Page -- Container: full available content width, column, `gap: 24` -- Top row: `grid-template-columns: 60fr 40fr`, `gap: 24` -- At `<=1200px`: top row stacks to one column -- Main profile card uses widget shell -- Profile card width is determined by the profile page grid, not by the card itself. -- Internal card layout: - - row `gap: 20px` - - portrait max `140x140`, circle - - portrait aligns flush in the row without extra wrapper padding/side space - - detail grid `140px 1fr`, `row-gap: 8`, `column-gap: 12`, `margin-top: 14` - - profile name/title: `18px`, `500`, `line-height: 1.2` - - company subtitle: `13px`, secondary text - - contact/detail rows: `13px`, primary text - - social row `margin-top: 14`, `gap: 8` - - company line under title is always visible; fallback display is `β€”` when no company is assigned - - right-side meta labels (`Member Since`, `Role`) use widget-title sizing: `11px`, `500`, uppercase, `letter-spacing: 0.8px` - - right-side meta values use body sizing: `13px` - - self-only edit button: `position: absolute`, `top: 18px`, `right: 21px` (aligns to card padding), `border-radius: 8px` (matches card), `height: 22px`, `font-size: 12px`, `padding: 0 12px` - - default outline button hover/focus fills with accent gold and uses dark text -- Right calendar card shows only tasks/events assigned to the viewed profile user -- Profile page left column includes a `Tasks In Progress` card above `Recent Activity` -- Profile `Tasks In Progress` follows the dashboard table/card pattern: - - same widget shell and header treatment - - same show-all behavior (5 visible by default) - - profile-specific filter is tasks assigned to the viewed user with statuses `in_progress`, `on_hold`, or `client_review` - - columns are `Task` and `Status` -- Profile activity feed action text pattern: `Task started`, `Task submitted`, `Task approved`, `Task rejected` (sentence title case) -- Activity feed includes: - - actions performed by the viewed user - - `Task approved` and `Task rejected` entries for tasks assigned to that viewed user (even if actioned by someone else) - -## 12.25) Shared Portrait Logic -- Shared profile/avatar rendering must use one common resolver path instead of page-specific inline `` fallbacks. -- Canonical shared component: `ProfileAvatar` -- Portrait source resolution order: - - explicit `avatarUrl` prop when already resolved - - `profile.avatar_url` - - profile lookup by `profileId` - - profile lookup by normalized display `name` as a fallback when only sender/actor name exists in derived dashboard data - - initials fallback when no profile image is available -- Shared portrait behavior: - - image uses `object-fit: cover` - - shape is always circular (`border-radius: 50%`) - - fallback initials use accent background with dark text - - layout/header/task/project/dashboard surfaces should reuse the shared portrait resolver instead of hand-rolling image + initials logic - -## 12.3) Team Dashboard Data Logic -- Team dashboard is shared across roles, but card data is role-filtered by scoped tasks/projects/companies. -- Focus refresh + realtime refresh should be wired through the shared live-refresh path, not duplicated per page. -- Scoped company/project/task IDs should be resolved through the shared scope resolver for client/external views. -- Tall dashboard list cards should use viewport-driven height with internal scroll instead of hard-coded row counts: - - measure the lower dashboard row against the current window height and size the cards to the remaining viewport space - - do not use a generic fixed tall-card height that can force whole-page scrolling on shorter windows - - do not apply a lower clamp that stops the card from continuing to compress with the window - - scrolling stays inside the card body, not on the full page layout -- `Team Performance` card logic: - - source data is `deliveries` joined to `submissions` and `tasks` - - each delivery row must include `submission.task_id` plus nested task assignee info - - non-team roles must filter deliveries back down to their scoped task IDs before calculating performance rows - - month dropdown options are derived only from months that actually contain scoped delivery data - - initial render must use the first valid month with data; card must not render empty until the dropdown is manually changed - - performer identity resolves first from `task.assigned_to`, then falls back to normalized `sent_by` / display name matching against profiles - - avatar in `Team Performance` follows shared portrait resolution rules above - - counts: - - `version_number === 0` => `new` - - `version_number > 0` => `revision` - - progress bar percentage is performer completed count divided by total completed count in the active month -- Dashboard lower-left task card logic: - - team view remains `Hot Tasks` - - client view remains `Tasks Ready For Review` - - external/subcontractor view is `My Tasks` - - subcontractor `My Tasks` must source directly from scoped tasks assigned to the current subcontractor, not from `is_hot` submission flags - - subcontractor `My Tasks` should include only active assigned tasks: - - `not_started` - - `in_progress` - - `client_review` - - `on_hold` - - subcontractor `My Tasks` should exclude completed/closed states such as: - - `client_approved` - - `invoiced` - - `paid` - - subcontractor `My Tasks` rows should show task, project, status, and due date - -## 13) Radius + Geometry Rules -- Dashboard/profile widgets: `8px` radius -- Sidebar: `8px` radius -- Standard app buttons use the `Edit Profile` geometry: - - geometry is tokenized globally in CSS vars and must be reused (no per-page hardcoded button geometry): - - `--btn-height` - - `--btn-padding-x` - - `--btn-radius` - - `--btn-font-size` - - `--btn-font-weight` - - `--btn-letter-spacing` - - `--btn-line-height` - - height: `22px` - - horizontal padding: `0 12px` - - border-radius: `8px` - - font-size: `11px` - - font-weight: `500` - - letter-spacing: `0.8px` - - text-transform: uppercase - - line-height: `1` - - label color follows theme text roles: - - dark mode: primary button labels use dark text on accent fill; outline labels use primary text on transparent surface - - light mode: outline labels use light-theme primary text; danger labels use danger text - - outline button border uses the same `var(--border)` token as card outlines unless a button role explicitly overrides it - - keep existing button role styling intact (`outline`, `primary`, `danger`); geometry changes, not role colors - - popup/modal footer action rows are a no-wrap horizontal row: - - `display: flex` - - `justify-content: flex-end` - - `gap: 8px` - - `flex-wrap: nowrap` - - popup/modal action buttons use a stable minimum width so loading labels do not shove adjacent buttons underneath: - - `min-width: 108px` - - center content horizontally - - request/add-task popups, review/revision/reject/amend popups, and other form submit dialogs should keep the save/submit/cancel buttons on one line while the submit label changes to `Saving...` / `Submitting...` - - status action buttons that change the task state must update the page immediately and lock while the save is in flight so users cannot double-press: - - flip local UI state optimistically - - disable related status buttons until request finishes - - if save fails, roll back local state and show error -- route/page loading should continue using the shared `PageLoader` until page data is actually ready -- avoid plain page-level `Loading...` text fallbacks after navigation, or the popup can disappear while the route is still fetching - - pages with longer initial fetches should pass their route loading state into shared layout/page shells so the popup stays up until all first-load data for that screen is ready -- Inputs/dropdowns may use their own control radius unless a page-specific rule says otherwise -- Circular elements (avatar/day/icon badges): `50%` - -## 14) Z-Index Stack -- Sidebar: `200` -- Header dropdowns/tooltips: `300` -- Calendar hover popover: `1002` within card context (`card can be 1001 active`) -- Modal overlay: `1200` - -## 14.5) Loading Popup -- Use shared `PageLoader` component for loading overlays instead of page-specific popup implementations. -- Loading popups/overlays, including shared site-wide page loaders, use the same widget shell as dashboard cards: - - `background: var(--popup-bg)` - - `border: 1px solid var(--border)` - - `border-radius: 8px` - - `padding: 18px 21px` - - `backdrop-filter: blur(12px)` + `-webkit-backdrop-filter` -- Popup surface is theme-aware: - - dark mode: deep translucent dark surface - - light mode: bright translucent white surface -- Loading overlay scrim is theme-aware: - - dark mode: `rgba(0,0,0,0.58)` - - light mode: soft light scrim via `--overlay-scrim` -- Loading title uses widget header treatment: `11px`, `500`, uppercase, `letter-spacing: 0.8px`, secondary text -- Progress track: - - height: `4px` - - radius: `2px` - - track color: secondary card tone (`var(--card-bg-2)`) - - progress fill: accent -- Progress/meta text under the bar uses subtext sizing (`12px`) and secondary text - -## 14.6) Modal Form Fields -- Modal forms that follow `Edit Profile` styling (including `Add Task`/`New Task`) share the same field typography and theme tokens: - - modal surface transparency matches `Edit Profile` card shell (`background: var(--card-bg)`), not a denser popup-specific override - - field labels: `11px`, `500`, uppercase, `letter-spacing: 0.8px`, color `var(--text-secondary)` - - input/select text: `13px`, color `var(--text-primary)`, left aligned - - textarea text: `13px`, color `var(--text-primary)` - - field surfaces and borders use global theme tokens (`var(--card-bg-2)` + `var(--border)`) for dark/light parity - - modal form action buttons use the standard outline geometry/style (`btn btn-outline`) unless a page explicitly defines a different role -- Finance modal numeric fields (expense amount, invoice money inputs, line-item quantity/rate fields) follow the same shell system instead of custom oversized styles: - - amount/currency field outer shell: `min-height: 42px`, `border-radius: 6px`, `padding: 0 12px`, `background: var(--card-bg-2)`, `border: 1px solid var(--border)` - - currency symbol is separate from the numeric input and uses secondary text - - amount input text: `22px`, `500`, `line-height: 1.1`, right aligned, `font-variant-numeric: tabular-nums` - - line-item numeric inputs keep the normal field feel: `min-height: 32px`, `13px` text - - quantity is centered; rates/prices/totals are right aligned with tabular numerals - - do not use giant red standalone number inputs; red is reserved for negative/danger/read-only emphasis, not default editable money entry - -## 14.7) Status Tags -- Shared status tags use `StatusBadge` (`.badge.badge-status`). -- Rule: any workflow/state value (`not_started`, `in_progress`, `on_hold`, `client_review`, `client_approved`, `invoiced`, `paid`, `active`, `completed`, etc.) must render through `StatusBadge` only. -- Do not use raw `span.badge badge-*` for statuses. -- Base badge geometry: - - `display: inline-flex` - - `align-items: center` - - `gap: 4px` - - `padding: 3px 10px` - - `border-radius: 4px` - - `font-size: 11px` - - `font-weight: 400` - - `letter-spacing: 0.3px` - - `white-space: nowrap` -- Status badge geometry override (`.badge-status`): - - `display: inline-flex` - - `align-items: center` - - `justify-content: center` - - `min-width: 78px` - - `height: 20px` - - `padding: 0 8px` - - `font-size: 10px` - - `line-height: 1.05` - - `box-sizing: border-box` - - `padding-top: 1px` (optical vertical-centering correction for current font metrics) -- Status colors are variant classes (`.badge-not_started`, `.badge-in_progress`, etc.) and are theme-aware. -- Non-status chips (example: invoice line-item type `Initial`/`Revision`, or service-type labels) may use `.badge` variants directly. -- When a non-status chip should visually align with status pills, add `.badge-status` to match geometry. -- Exception: compact urgent tag (`.badge-needs_revision`) uses tighter geometry: - - `min-width: 28px` - - `padding: 3px 4px` - - `border-radius: 4px` - - fixed red treatment in both dark/light themes. - -## 15) Motion -- Motion vars: - - fast `160ms` - - base `220ms` - - easing `cubic-bezier(0.22, 1, 0.36, 1)` -- Dropdown/menu entrance animation should be minimal; avoid decorative motion when it competes with the shared background spark effect. - -## 17) Hover Interaction Contract -- Sidebar, header icon buttons, dropdown items, and avatar menu items must show a visible hover surface before click. -- Single hover source-of-truth block controls these elements. -- Dark hover surface baseline: `#1f1f1f`. -- Light hover surface baseline: `rgba(0,0,0,0.08)`. -- Nav icon opacity must lift from muted to full on hover (`opacity: 1`). -- Clickable text links use text-only highlight on hover/focus (`color: var(--accent)`), not whole-row/background fill. -- Hover and active must be visually distinct: - - hover uses stronger temporary contrast (`bg` + thin border), - - active remains persistent selected-state background. -## 17.5) Link Interaction Standard -- Use shared link classes only; do not hand-roll page-specific link hover styles: - - table/text links inside cards and tables: `table-link` - - inline action links/buttons in cards/feeds: `dashboard-inline-link` -- Hover behavior for both classes: - - color changes to accent gold (`var(--accent)`) - - no underline on hover/focus - - cursor remains pointer -- For table-heavy rows where hit targets are tight, row hover may also promote link color to accent; still keep text-only link treatment (no full-row fill just for links). -- Do not rely on inline style color overrides for hover behavior; if a variant is needed, add/extend a shared class in `index.css`. - -## 16) Non-Negotiable Implementation Rules -- Keep gradient backgrounds on `body`, not `html` -- Keep widget shell values (`18px 21px`, `8px`, blur 12, border token) consistent -- Maintain global `24px` spacing rhythm for page/frame/grid gaps -- Keep team dashboard card order: - 1. Open Tasks - 2. Active Projects - 3. Net Profit - 4. Revenue (wide) -- Keep row 2 order: Recent Activity (left), Tasks In Progress (center), Calendar (right fixed at `280px`) -- Keep the lower dashboard row shared across all three roles: - 1. role-specific task list card - 2. Team Performance -- Do not add an extra team-only `Client Highlight` card under the shared dashboard rows - -## 17) Team Reports -- Team-only tools nav includes `Reports`. -- Reports page is a finance-adjacent audit surface, but separate from the invoice page. -- Primary report source is task-version billing units, not lump tasks: - - `R00` is its own row - - each real revision version (`R01`, `R02`, and so on) is its own row -- Report rows must derive from the same version-unit billing logic as invoice creation: - - initial/new rows map to task-level client invoice units - - revision rows map to submission-level client invoice units - - hidden review-shadow submissions must not create report rows -- Minimum columns: - - company - - project - - task name - - `R##` - - type (`New` / `Revision`) - - version workflow status - - billing status tag (`Not Invoiced`, `Invoiced`, `Paid`) -- Reports page must support filtering by: - - company - - project -- Reports page must support PDF export of the currently filtered rows. - -## 18) Invoice Rules -- New work (`R00`) is billed at the new-service rate. -- Revision `R01` is free. -- Revisions `R02+` are billed incrementally: each uninvoiced revision entry contributes exactly `1x` revision charge. -- Previously invoiced revisions are excluded; later invoices only charge newly uninvoiced revisions (e.g. if `R02` was already invoiced, at `R04` only `R03` + `R04` are billed). -- Billing is version-unit based, not task-lump based: - - `R00` is one unit - - `R01` is one unit - - `R02+` are separate units -- Team -> client invoice availability: - - a task/version can appear only if it is client approved - - it must not have been invoiced to the client before - - already invoiced or paid client invoice units must not appear again - - revision availability must be sourced per version submission, not blocked just because the task's `R00` was already invoiced - - invoice add-item UI should show a visible type chip for each unit: - - `New` for `R00` - - `Revision` for `R01+` - - version availability is tied to completed version state, not only the task's current active state: - - if a newer version is now active (`not_started`, `in_progress`, etc.), older completed uninvoiced versions must still appear - - example: if `R02` is now active, uninvoiced `R01` may still appear while `R02` must stay off the invoice list until it is completed/approved - - team invoice popup and standalone team invoice page must both use the same shared version-eligibility source, not separate page-local rules - - clicking a team invoice from the `/finances` invoice list should open the same shared invoice-detail popup content used by the standalone team invoice detail route, not a separate simplified modal - - creating a new team invoice should also open that same shared invoice-detail popup after save -- Subcontractor -> Fourge invoice availability: - - a task/version can appear only if that subcontractor actually worked/delivered that version - - it must be client approved - - it must not have been invoiced by that subcontractor to Fourge before - - already invoiced or paid subcontractor invoice units must not appear again -- Client invoice history and subcontractor invoice history are separate streams: - - the same task/version may exist in client billing and subcontractor billing - - duplicate prevention happens within each stream separately - -## 18.0) Request And Task Lifecycle -- A task starts as a request created by `team` or `client`. -- The initial request is version `R00`. -- `R00` request data lives on the initial `submission` row. -- Visible `R##` labels on task lists/cards/detail must use the task's true current version: - - derive from `max(tasks.current_version, submission.version_number)` - - do not derive the visible `R##` from the deadline-bearing submission row - - deadline/request metadata may come from the deadline source submission, but the displayed revision number must still reflect the true current version -- Client and team can amend the current request without creating a new visible revision item: - - amendment stays tied to the same active version - - amendment does not increment `R##` -- Team or subcontractor works the task and places it in review. -- Review submissions belong in the `Submissions` tab and may happen multiple times for the same `R##`. -- Client decision on a review: - - `Approve`: - - task moves to `client_approved` - - task remains on the approved version - - `Reject`: - - task returns to `in_progress` - - same assigned worker stays on the task - - `R##` does not increment by rejection alone - - rejected reason is stored as `Rejected Notes` tied to that same version -- A real revision request creates the next visible version: - - `R00` -> `R01` - - `R01` -> `R02` - - etc. - - when a revision request creates the next `R##`, the task keeps the same last assignee instead of clearing assignment - - status resets to `not_started`, but the assignee remains attached so the work comes back to the same person by default -- Revision requests are separate from rejection notes: - - revision request = new visible request/version item - - rejection note = side note on the current version -- Re-review after rejection must still appear in `Submissions` without creating a fake new visible request/revision row. - -## 18.1) Task File Placement Rules -- Company and project folder chain: - - when a new company is created by team, the portal ensures a matching server folder exists: - - `/Clients/{Company}` - - when a company name is changed, the company folder is renamed to match - - when a new subcontractor is created by team, the portal ensures a matching server folder exists: - - `{FILEBROWSER_SUBS_ROOT}/{Subcontractor Name}` - - when a subcontractor name is changed, that subcontractor folder is renamed to match - - when a new project is created, the portal ensures a matching server folder exists: - - `/Clients/{Company}/Projects/{Project}` - - inside each new project folder, the portal also pre-creates: - - `00 Project Files` - - when a project name is changed, the project folder is renamed to match - - when a new task request is created, the portal ensures a matching server folder exists: - - `/Clients/{Company}/Projects/{Project}/{Task}` - - inside each new task folder, the portal also pre-creates: - - `Old Books` - - `Working Files` - - `Survey` - - folder creation now works via FileBrowser API using: - - a valid production `FILEBROWSER_TOKEN` - - FileBrowser source `srv` - - folder sync uses FileBrowser only for server folder creation - - this does not restore the old file-sharing page or task folder tab -- Initial task request files: - - created by `team` or `client` - - attach to the initial request in the `Overview` tab - - stored on the initial `submission` - - file records live in `submission_files` - - request file flow now works in this order: - - ensure the matching task folder exists under `/Clients/{Company}/Projects/{Project}/{Task}` - - upload each attached file into Supabase bucket `submissions` - - store the file row in `submission_files` - - then mirror that same saved file from Supabase into the server-side `Survey` folder - - initial request files are stored in Supabase under the task's `Survey` path for portal access - - storage path format: - - `{taskId}/Survey/{timestamp}_{originalFileName}` - - initial request files are also mirrored into the server-side task folder: - - `/Clients/{Company}/Projects/{Project}/{Task}/Survey` - - the mirror now copies from the saved Supabase storage object on the server side instead of re-uploading the browser file body - - multiple attached request files are processed one by one through that same mirror flow and should all land in the same `Survey` folder - - the live working behavior is: - - request attachments still show inside the portal from Supabase - - the same attachments also appear in the matching server `Survey` folder - - for `Brand Book` requests, sign details are stored directly on the `submission` row - - sign detail payload lives in `submissions.signs` - - old `submission_signs` rows are migrated into `submissions.signs` and no longer used by the portal - - sign info in the `Overview` tab renders as plain text rows with no inset background card -- Revision request files: - - created by `team` or `client` - - attach to the specific revision entry in the `Revisions` tab - - stored on the revision `submission` - - file records live in `submission_files` -- Review submission files: - - created by `team` or `subcontractor` - - attach to the specific submitted review entry in the `Submissions` tab - - stored on the `delivery` - - file records live in `delivery_files` -- Client rejection notes: - - when client rejects a task in review, it must not create a new request/revision item - - rejection opens a note box - - rejection returns the task to `in_progress` - - rejection keeps the same assigned worker on the task - - rejection does not increment the `R##` version by itself - - note is tied to the exact version being rejected - - `R00` rejection note shows beside the `R00` request info - - `R01` rejection note shows beside the `R01` request info - - `R02` rejection note shows beside the `R02` request info - - label should read `Rejected Notes` - - these notes are separate from normal comments and should not inflate the visible comments count - -## 18.2) Task Notification Rules -- New request: - - when a new request is created, it emails: - - all team members - - this applies from client request flows and team request flows -- In review: - - when a task is placed in review, it emails: - - the client - - all team members - - the assigned user -- On hold: - - when a task is placed on hold, it emails: - - the client - - all team members - - the assigned user -- Amend request: - - when a request is amended, it emails: - - the client - - all team members - - the assigned user -- Rejected: - - when a client rejects a task, it emails: - - the client - - all team members - - the assigned user - - rejected task email uses the shared task-status layout only - - do not also send a separate legacy revision email -- Approved: - - when a client approves a task, it emails: - - the client - - all team members - - the assigned user -- Revision request: - - when a revision request is created, it emails: - - the client - - all team members - - the assigned user - - revision request should send one shared task-status email only - - do not also send any older legacy revision email - -## 19) Finance Flow -- Finance is role-split, not one shared page across all users: - - team finance hub: `/finances` - - client invoice view: `/client-invoices` - - subcontractor invoice view: `/subs-invoices` - - subcontractor PO view: `/my-purchase-orders` -- Finance is shared in concept, but not one universal page component yet: - - team sees the full finance hub - - client sees receive/view/pay invoice flow only - - subcontractor sees invoice-Fourge and PO-review flow only - -### 19.1) Team -> Client Invoice Flow -- Team creates invoice for the client. -- Team invoice availability is version-unit based: - - `R00` can bill as new work - - `R01` is tracked but free - - `R02+` can bill as revision units -- A client invoice unit may appear only if: - - it is client approved - - it has not already been invoiced to the client -- Team sends invoice to the client. -- Invoice send email goes to: - - the client recipient - - all team members -- Client does not create invoices back to Fourge in the portal. -- Client only receives, views, pays, and downloads invoice records. -- Client payment can happen: - - through Stripe / portal pay flow - - outside the portal, then team marks the invoice paid manually -- Once paid: - - invoice status is `paid` - - branded paid/receipt email goes to: - - the client recipient - - all team members - - receipt can be generated/downloaded/sent if needed - - paid client invoices must not be deletable by client or team -- Team invoice detail remains the control point for: - - send / resend invoice - - mark paid - - generate invoice PDF - - generate/send receipt - -### 19.2) Client Finance Role -- Clients only see invoices relevant to their company access. -- Clients can: - - view invoices - - filter invoices by company when tied to multiple companies - - download invoice PDFs -- Clients cannot: - - create invoices to Fourge - - manage expenses - - manage subcontractor invoices - - manage POs - -### 19.3) Subcontractor -> Fourge Invoice Flow -- Subcontractor creates invoice to bill Fourge for completed work. -- Subcontractor billing is version-based, not task-lump based: - - `R00` is one billable unit - - `R01` is one version unit but free - - `R02+` are version units and billable -- Subcontractor invoice create page builds available rows from actual delivered versions tied to that subcontractor's assigned tasks. -- Only versions the subcontractor actually delivered should appear as available invoice rows. -- Subcontractor invoice availability must dedupe per `task + version`. -- If a subcontractor invoice already includes a specific version, that same version must not show up again in later subcontractor invoices. -- A subcontractor task/version is available only when: - - it is client approved - - that subcontractor delivered/worked that version - - it has not already been invoiced to Fourge by that subcontractor -- Version availability follows the same older-completed-version rule as team client invoices: - - if a newer version is now active (`not_started`, `in_progress`, etc.), older completed uninvoiced versions must still appear - - example: if `R02` is now active, uninvoiced `R01` may still appear while `R02` must stay off the subcontractor invoice list until it is completed/approved - - subcontractor invoice create page must use that same shared version-eligibility source as the team invoice flows -- That invoice appears to team finance for review/payment. -- Team marks subcontractor invoices `paid`. -- Subcontractors do not normally mark their own invoices paid. -- Once paid: - - subcontractor sees paid status - - branded paid/receipt email goes to: - - that subcontractor - - all team members - - receipt can be generated/downloaded/sent if needed -- Client invoicing and subcontractor invoicing are separate streams: - - team can invoice the client for a task/version - - subcontractor can invoice Fourge for that same task/version -- Client invoice state must not block subcontractor invoice availability by itself. -- Subcontractor invoice state only blocks the same subcontractor task/version from being billed again. -- Subcontractor finance page `/subs-invoices` stays separate from the team finance hub: - - no chart on this page - - top row uses four stat cards only: completed new tasks, completed revisions, invoiced, paid - - invoice history sits below in one full-width card with no secondary right-side companion card - - the `+ Invoice` action uses the same shared popup shell/button treatment as the team finance invoice modal - - subcontractor invoice modal keeps subcontractor-specific fields and billing rules, but visually follows the shared finance popup pattern - - clicking a subcontractor invoice from the list should open the shared invoice detail in a popup, matching the team-side subcontractor invoice interaction - - after a subcontractor creates an invoice, the portal should open that same popup detail view instead of routing away to a separate detail page - - subcontractor invoice popup layout should match the team-side subcontractor invoice popup layout - - subcontractor invoice popup action row should include: - - `Download Invoice` - - `Submit to Team` when still draft - - `Download Receipt` when paid - - subcontractor invoice popup line-item table should include: - - `Work` (`New` or `Revision`) - - `R#` (`R00`, `R01`, and so on) - - `Description` - - `Type` (task service type such as `Brand Book`, `Sign Family`, and so on) - - `Qty` - - `Unit Price` - - `Amount` - - subcontractor invoice detail uses one shared layout across team and subcontractor roles, with role-specific actions loaded into the same detail shell - - shared subcontractor invoice detail pattern: - - uses the shared site header chevron/back pattern from task detail, not a separate page-local header block - - content starts immediately under that shared header using the same task-detail rhythm, with no separate action bar floating above the first card row - - header title styling follows the global site header spec: - - `28px` - - `500` - - `var(--text-primary)` - - `line-height: 1.2` - - header subtitle styling follows the global site header spec: - - `13px` - - `var(--text-muted)` - - two top summary cards are the first content row, using the same `24px` gap rhythm as task detail - - role-specific invoice actions sit inside the top-right summary card header instead of on a separate row - - summary and section labels use widget-title styling: - - `11px` - - `500` - - uppercase - - `letter-spacing: 0.8px` - - `var(--text-secondary)` - - line items live in one full-width card with sticky table header and total footer - - notes card sits below line items when notes exist - -### 19.4) Team -> Subcontractor PO Flow -- Team creates purchase order for subcontractor work. -- PO can optionally be tied to a project and task-backed line items. -- PO email sending is currently disabled. -- Subcontractor can approve the PO from their portal view. -- PO approval is separate from subcontractor invoice payment. -- A PO being approved does not itself mean an invoice has been paid. - -### 19.5) Simple Mental Model -- Fourge -> Client = invoice out -- Client -> Fourge = payment in -- Subcontractor -> Fourge = invoice in -- Fourge -> Subcontractor = payment out -- Fourge -> Subcontractor before payment = PO / work authorization diff --git a/src/App.jsx b/src/App.jsx index 7e108b3..51d5b20 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -64,12 +64,12 @@ const ClientMyInvoices = lazy(() => import('./pages/client/ClientMyInvoices')); function RedirectProjectDetail() { const { id } = useParams(); - return ; + return ; } function RedirectClientDetail() { const { id } = useParams(); - return ; + return ; } function RedirectToTask() { @@ -104,8 +104,8 @@ export default function App() { } /> } /> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 708a0af..10ca7ff 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -334,9 +334,9 @@ export default function Layout({ children, loading = false }) { {!hasResults &&
No results for "{searchQuery}"
} {searchResults.projects.length > 0 && ( <> -
Clients
+
Projects
{searchResults.projects.map(p => ( -
{ navigate(`/clients/${p.id}`); setSearchQuery(''); setSearchOpen(false); }}> +
{ navigate(`/projects/${p.id}`); setSearchQuery(''); setSearchOpen(false); }}> {ICONS.projects} {p.name}
diff --git a/src/components/RequestForm.jsx b/src/components/RequestForm.jsx index b664d6d..901a311 100644 --- a/src/components/RequestForm.jsx +++ b/src/components/RequestForm.jsx @@ -242,12 +242,12 @@ export default function RequestForm({
) :
}
- + {lockedFields.includes('project') ? ( ) : isTypingProject ? (
- setNewProjectName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }} autoFocus style={{ ...modalInputStyle, flex: 1 }} /> + setNewProjectName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }} autoFocus style={{ ...modalInputStyle, flex: 1 }} />
diff --git a/src/pages/Companies.jsx b/src/pages/Companies.jsx index 45473c7..1246e5c 100644 --- a/src/pages/Companies.jsx +++ b/src/pages/Companies.jsx @@ -239,7 +239,7 @@ function TeamCompanies() { )}
{companies.length === 0 ? ( -
No clients
+
No companies
) : (
diff --git a/src/pages/CompanyDetail.jsx b/src/pages/CompanyDetail.jsx index 0bf7ab7..b357ae4 100644 --- a/src/pages/CompanyDetail.jsx +++ b/src/pages/CompanyDetail.jsx @@ -154,7 +154,7 @@ export default function CompanyDetail() { }; const handleDeleteCompany = async () => { - if (!window.confirm(`Delete "${company.name}"? This will permanently delete all clients, jobs, files, and data. This cannot be undone.`)) return; + if (!window.confirm(`Delete "${company.name}"? This will permanently delete all projects, jobs, files, and data. This cannot be undone.`)) return; await deleteCompanyData(id); navigate('/company'); }; @@ -309,7 +309,7 @@ export default function CompanyDetail() {
πŸ“
{projects.length}
-
Clients
+
Projects
⚑
@@ -477,16 +477,16 @@ export default function CompanyDetail() {
{isTeam &&
} {showNewProject && (
-
New Client
+
New Project
- + No clients
+
No projects
) : (
{projects.map(project => { @@ -513,7 +513,7 @@ export default function CompanyDetail() { const done = projectTasks.filter(t => t.status === 'client_approved').length; return (
- +
{project.name}
@@ -529,7 +529,7 @@ export default function CompanyDetail() { type="button" onClick={() => handleDeleteProject(project)} style={{ background: 'none', border: 'none', borderLeft: '1px solid var(--border)', color: 'var(--danger, var(--danger-strong))', cursor: 'pointer', fontSize: 16, padding: '0 14px', alignSelf: 'stretch', display: 'flex', alignItems: 'center' }} - title="Delete client" + title="Delete project" >βœ•}
); diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index a65e3db..fa5e777 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -392,7 +392,7 @@ export default function ProfilePage() { const ACTION_LABEL = { task_created: 'Task created', task_started: 'Task started', task_on_hold: 'Task put on hold', task_resumed: 'Task resumed', task_submitted: 'Task submitted', task_approved: 'Task approved', - project_created: 'Client created', request_submitted: 'Request submitted', revision_requested: 'Task rejected', + project_created: 'Project created', request_submitted: 'Request submitted', revision_requested: 'Task rejected', }; const toSentenceTitle = (value = '') => { diff --git a/src/pages/ProjectDetail.jsx b/src/pages/ProjectDetail.jsx index ba19046..74501db 100644 --- a/src/pages/ProjectDetail.jsx +++ b/src/pages/ProjectDetail.jsx @@ -232,7 +232,7 @@ export default function ProjectDetailPage() { }; if (loading) return ; - if (!project) return

Client not found.

; + if (!project) return

Project not found.

; const rows = tasks.map(task => { const derived = getTaskDerivedState(task, submissions, deliveries); @@ -506,7 +506,7 @@ export default function ProjectDetailPage() {
diff --git a/src/pages/TaskDetail.jsx b/src/pages/TaskDetail.jsx index 05a514e..cf2243d 100644 --- a/src/pages/TaskDetail.jsx +++ b/src/pages/TaskDetail.jsx @@ -757,9 +757,9 @@ export default function TaskDetail() {
-
Client
+
Project
{project - ? {project.name} + ? {project.name} :
β€”
}
diff --git a/src/pages/Tasks.jsx b/src/pages/Tasks.jsx index 2be0a9d..721c2e7 100644 --- a/src/pages/Tasks.jsx +++ b/src/pages/Tasks.jsx @@ -435,7 +435,7 @@ export default function RequestsPage() { setAddProjectSaving(true); setAddProjectError(''); try { const name = addProjectForm.name.trim(); - if (!name) throw new Error('Client name required.'); + if (!name) throw new Error('Project name required.'); if (!addProjectForm.companyId) throw new Error('Company required.'); const { data: newProject, error: insertError } = await supabase .from('projects').insert({ name, company_id: addProjectForm.companyId, status: 'active' }) @@ -530,7 +530,7 @@ export default function RequestsPage() { ]; const PROJECT_FILTER_OPTIONS = [ - { value: '', label: 'All Clients' }, + { value: '', label: 'All Projects' }, ...projects .filter(p => !filterCompany || p.company_id === filterCompany) .slice() @@ -609,7 +609,7 @@ export default function RequestsPage() { {row.title || 'β€”'}Task - Client + ProjectStatusDue By diff --git a/src/pages/team/TeamInvoices.jsx b/src/pages/team/TeamInvoices.jsx index fb7f207..f256dce 100644 --- a/src/pages/team/TeamInvoices.jsx +++ b/src/pages/team/TeamInvoices.jsx @@ -187,13 +187,13 @@ const invNewItem = (description = '', unit_price = '', quantity = 1, task_id = n ({ id: crypto.randomUUID(), description, unit_price, quantity, task_id, submission_id }); const invBuildNewItemDescription = (task) => { - const projectName = task.project?.name || 'No Client'; + const projectName = task.project?.name || 'No Project'; const taskTitle = task.title || task.service_type || 'Untitled'; return `${projectName} β€’ ${taskTitle}`; }; const invBuildRevisionItemDescription = (revision) => { - const projectName = revision.task?.project?.name || 'No Client'; + const projectName = revision.task?.project?.name || 'No Project'; const taskTitle = revision.task?.title || revision.service_type || 'Revision'; const versionLabel = 'R' + String(revision.version_number || 0).padStart(2, '0'); return `${projectName} β€’ ${taskTitle} β€’ Revision ${versionLabel}`; diff --git a/src/pages/team/TeamReports.jsx b/src/pages/team/TeamReports.jsx index 1ea2725..31cf2d7 100644 --- a/src/pages/team/TeamReports.jsx +++ b/src/pages/team/TeamReports.jsx @@ -278,7 +278,7 @@ export default function TeamReports() { autoTable(doc, { startY: 78, - head: [['Company', 'Client', 'Task', 'Version', 'Type', 'Version Status', 'Client Billing', 'Sub Billing']], + head: [['Company', 'Project', 'Task', 'Version', 'Type', 'Version Status', 'Client Billing', 'Sub Billing']], body: sortedRows.map((row) => [ row.company_name, row.project_name, @@ -329,9 +329,9 @@ export default function TeamReports() {
-
Client
+
Project
Company - Client + ProjectTask NameR#Type diff --git a/src/pages/team/TeamSubcontractorPODetail.jsx b/src/pages/team/TeamSubcontractorPODetail.jsx index d3b024f..923b86f 100644 --- a/src/pages/team/TeamSubcontractorPODetail.jsx +++ b/src/pages/team/TeamSubcontractorPODetail.jsx @@ -158,7 +158,7 @@ export default function SubcontractorPODetail() {

{new Date(po.date).toLocaleDateString()}

{po.due_date ? new Date(po.due_date).toLocaleDateString() : 'β€”'}

{po.terms || 'Net 15'}

-

{po.project?.name || 'No client'}

+

{po.project?.name || 'No project'}

{po.project?.company?.name || 'β€”'}

${Number(po.amount).toFixed(2)}

{po.paid_at &&

{new Date(po.paid_at).toLocaleDateString()}

} @@ -172,7 +172,7 @@ export default function SubcontractorPODetail() {
Name Email - Clients + Projects
- {project ? {project.name} : 'β€”'} + {project ? {project.name} : 'β€”'} {(() => { @@ -656,7 +656,7 @@ export default function RequestsPage() {
{ setShowAddProjectForm(false); setAddProjectForm({ name: '', companyId: '' }); setAddProjectError(''); }}>
e.stopPropagation()}>
-
Add Client
+
Add Project
@@ -667,7 +667,7 @@ export default function RequestsPage() {
- + setAddProjectForm(f => ({ ...f, name: e.target.value }))} required autoFocus style={TASK_MODAL_INPUT_STYLE} />
{addProjectError &&
{addProjectError}
} diff --git a/src/pages/team/TeamCreateSubcontractorPO.jsx b/src/pages/team/TeamCreateSubcontractorPO.jsx index fbabd5d..01256c4 100644 --- a/src/pages/team/TeamCreateSubcontractorPO.jsx +++ b/src/pages/team/TeamCreateSubcontractorPO.jsx @@ -208,9 +208,9 @@ export default function CreateSubcontractorPO() {
- +
- Client + ProjectTaskDescriptionAmount @@ -181,7 +181,7 @@ export default function SubcontractorPODetail() { {tableItems.map(item => ( - +
{po.project?.name || 'No client'}{po.project?.name || 'No project'} {item.task?.title || 'β€”'} {item.description} ${Number(item.amount).toFixed(2)}