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>
This commit is contained in:
@@ -15,6 +15,15 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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
|
||||
@@ -66,12 +75,28 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- **`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`
|
||||
@@ -112,7 +137,7 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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, file sharing, etc.):
|
||||
- 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
|
||||
@@ -135,8 +160,6 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- row vertical spacing via cell padding: typically `5px`
|
||||
- Hot Tasks column widths:
|
||||
- check `10%`, task `40%`, requested by `35%`, due by `15%`
|
||||
- Client Highlight column widths:
|
||||
- icon `5%`, company `22%`, contact `23%`, projects `13%`, open `13%`, outstanding `12%`, paid `12%`
|
||||
- Sorting rule:
|
||||
- Every visible data column header must be sortable.
|
||||
- Use clickable header controls (`SortTh`) with ascending/descending indicator.
|
||||
@@ -173,57 +196,58 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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.5) File Sharing
|
||||
- Main split: `grid-template-columns: minmax(0, 1fr) minmax(0, 3fr)` for an effective `25 / 75` layout, `gap: 24`
|
||||
- Left stacked cards (`Pinned`, `Navigation`): `gap: 24`
|
||||
- Canonical folder structure:
|
||||
- team and subcontractor profile folders live at `Team/<User Name>`
|
||||
- company folders live at `Clients/<Company Name>`
|
||||
- project folders live at `Clients/<Company Name>/Projects/<Project Name>`
|
||||
- every project auto-creates `00 Project Files`
|
||||
- task folders live at `Clients/<Company Name>/Projects/<Project Name>/<Task Name>`
|
||||
- every task auto-creates `Request Info`, `Working Files`, and `Old Books`
|
||||
- request submission files are copied into `Request Info/R00`, `R01`, and higher revision folders
|
||||
- renaming a team user, subcontractor user, company, project, or task must rename the matching filesystem folder
|
||||
- folder repair/backfill is additive only: create missing folders, leave existing folders untouched
|
||||
- request-info backfill must skip files that already exist and must not overwrite added working files or user-managed files
|
||||
- deleting a task in the portal moves its task folder into `Archive/Clients/<Company Name>/Projects/<Project Name>/<Task Name>` instead of deleting it
|
||||
- deleting a project in the portal moves its project folder into `Archive/Clients/<Company Name>/Projects/<Project Name>` instead of deleting it
|
||||
- orphan reconciliation may move folders into archive when they no longer map to portal records
|
||||
- orphan team folders move to `Archive/Team/<Folder Name>` when no team or subcontractor profile matches that folder name
|
||||
- orphan project folders move to `Archive/Clients/<Company Name>/Projects/<Project Name>` when no portal project matches that folder name under that company
|
||||
- orphan reconciliation does not remove company roots or overwrite archived content; it merge-moves into archive
|
||||
- Access/home rules:
|
||||
- team members have full file access
|
||||
- client users with exactly one tied company use that company folder as home: `/Clients/<Company Name>`
|
||||
- client users tied to multiple companies use `/Clients` as home and only see company folders they are tied to
|
||||
- `/Clients` acts as a virtual parent for multi-company client users and must not expose unrelated company folders
|
||||
- subcontractor users use virtual home `/`
|
||||
- subcontractor virtual home shows `Team` plus only the project folders they are assigned to
|
||||
- subcontractor users skip all other root folders and must not see unrelated client or internal roots
|
||||
- subcontractor `Team` maps to the real shared `Team` folder
|
||||
- each subcontractor project folder is a virtual root that maps to the real client project path `/Clients/<Company>/Projects/<Project>`
|
||||
- Right card header:
|
||||
- title label is `File Browser`
|
||||
- drag/drop note sits directly under the title and uses subtext sizing: `12px`
|
||||
- current file path uses the same `11px` uppercase label treatment as the title row family
|
||||
- toolbar does not show `+ Folder` or `Upload` buttons
|
||||
- upload happens by dragging and dropping files or folders into the right card
|
||||
- delete action uses the same geometry as the profile edit button (`top: 18px`, `right: 21px`, `8px` radius, `22px` height, `12px` text, `0 12px` padding) but uses danger styling with red hover/fill
|
||||
- Right card table:
|
||||
- uses dashboard table styling (`table-layout: fixed`, transparent header row, `10px` uppercase headers, `13px/12px` body text)
|
||||
- card padding matches dashboard widget shell (`18px 21px`)
|
||||
- clickable text inside the table highlights in accent; rows do not use block hover fill
|
||||
- body cells are borderless like dashboard card tables
|
||||
- body cell padding matches dashboard tables exactly: `5px`
|
||||
- visible headers (`Name`, `Size`, `Modified`) use shared sortable header controls
|
||||
- file/folder icon footprint is compact so row height stays on the same cadence as dashboard tables
|
||||
- alternating rows use a very subtle theme-aware tint for scanability
|
||||
- Navigation tree:
|
||||
- `Home` aligns to the first arrow column
|
||||
- connector line runs through the arrow center column
|
||||
- pinned folders are user-specific and must not be shared across users on the same device/browser session
|
||||
- hover is text-only accent color, no row fill
|
||||
|
||||
## 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
|
||||
@@ -250,6 +274,22 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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%`
|
||||
|
||||
@@ -261,7 +301,7 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
|
||||
## 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 and file-sharing loading states, use the same widget shell as dashboard cards:
|
||||
- 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`
|
||||
@@ -289,6 +329,13 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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`).
|
||||
@@ -329,7 +376,7 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- fast `160ms`
|
||||
- base `220ms`
|
||||
- easing `cubic-bezier(0.22, 1, 0.36, 1)`
|
||||
- Dropdown animation: `ui-fade-up` from `translateY(4px)` + opacity 0 -> 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.
|
||||
@@ -341,10 +388,6 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- Hover and active must be visually distinct:
|
||||
- hover uses stronger temporary contrast (`bg` + thin border),
|
||||
- active remains persistent selected-state background.
|
||||
- File Sharing left navigation exception:
|
||||
- `Pinned` + `Navigation` tree rows use text-only hover highlight (`color: var(--accent)`).
|
||||
- Do not apply row background fill for hover in this section.
|
||||
|
||||
## 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`
|
||||
@@ -357,7 +400,7 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
- 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 `html`, not `body`
|
||||
- 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:
|
||||
@@ -366,9 +409,351 @@ This is the single source of truth for dashboard/profile visual structure and UI
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user