Files
fourge-portal/layout.md
T
Krao Hasanee 04e0911e9f fix: crash bugs in TeamInvoices + faster load
Bug fixes:
- TeamInvoices: add useAuth import/currentUser (invoice-create crash)
- TeamInvoices: setChartYear -> setExportYear (year-dropdown crash)
- TeamDashboard: drop redundant setState-in-effect (cascading renders)
- Companies: delete dead _UnusedClientCompanies (illegal hook calls)
- annotate intentional empty PDF-fallback catches

Load speed:
- preconnect/dns-prefetch to Supabase origin
- lazy-load heic-to in Converters: page chunk 2737KB -> 9KB
- split recharts into its own 'charts' vendor chunk

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:59:11 -04:00

760 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: `<div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>``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 `<path d="M2 4h12M4 8h8M6 12h4" />` 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 `<img>` 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