# 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