#Company shift dashboard
Module id: MOD-COMPANY-SHIFTS
Doc profile: capability
Company operators post and manage shifts, review applicants, browse contractors, approve time cards, and update company profile/settings on /company/shifts. This module owns the company dashboard page and is the sole implement owner for shared reusables Detailed job view, End job popup, and Row actions — contractor and time-tracking modules consume those components without forking them.
#Purpose
Hiring companies need one authenticated home to run the marketplace day-to-day: see open and closed shifts, publish new jobs with rates and headcount, open a shift for edit and applicant actions, start or end time tracking from a row, mark contractors Preferred / Do not Book / No show, approve logged time and mark payment send, and review dashboard/report metrics. Observable outcomes are persisted shifts / time_entries rows (and contractor-user list memberships that include the signed-in company user) under company-scoped RLS, with money and approval flags written only through server-authorized RPCs (MON-CLIENT-WRITES / LOG-CLIENT-WRITES).
#Scope boundaries
| In scope |
Out of scope |
Route /company/shifts under cabinet workspace; tab panels from nav |
Workspace chrome Header / SideBar (MOD-SHELL) |
| Filter Posted / Upcoming / Closed; Stop Hiring confirm persist; Closed filter-only |
Inventing a Close-the-job persist path (control unwired in export) |
| Shift list browse + open detail |
Contractor shift browse / apply (MOD-CONTRACTOR-SHIFTS) |
Post a New Job form + insert shifts + edge shift notify path |
Full chat thread UI (MOD-CHAT); only create-chat + navigate |
| Shared Detailed job view, Row actions, End job popup implement trees |
Stripe Connect / KYC / payouts onboarding (MON-MARKETPLACE) |
| Contractors tab Preferred / Do not Book / No show on contractor-user lists + message start |
Admin verification console (MOD-ADMIN) |
| Time-tracking approve + payments tab company send paths |
Recursive tracking approve backend rewrite (MOD-TIME-TRACKING / LOG-RECURSIVE) |
| Dashboard KPIs, reports sections, company profile save, light settings |
Inventing Connect onboarding or non-evidenced money rails |
#Actors
| Actor |
Behavior in this module |
| Company operator |
Signed-in profiles.role = company. Sees tab panels, posts/edits shifts, manages applicants, adds self to contractor Preferred / Do not Book / No show lists, approves time, views payments schedule, edits company profile. |
| Contractor (via shared reusables) |
Does not own this page, but Row actions / End job popup / Detailed job view are shared; contractor hosts import the same components. Contractor-user list fields hold which company users preferred/blocked them. |
| Session client |
Anon-key Supabase client for reads/list filters; money and approval mutations go through JWT RPCs / edge — never service-role in the browser. |
| Scheduler / edge |
Edge function shift (replaces API event shift) runs after post/edit when the shift date is after “now”, and fans out follow-on schedules when ≥1 contractor already has the shift on their list. |
#Capability composition
The company dashboard is one host route with mutually exclusive tab panels driven by URL nav values. Nested tabs=postjob and tabs=detail overlay the shifts list without adding sidebar items. Shared reusables mount from list rows and from other modules.
flowchart TB
subgraph host["Company shifts host"]
Dash["Dashboard KPIs"]
List["Company shifts list"]
Post["Post new job"]
Detail["Detailed job view"]
Contractors["Contractors browse"]
Time["Time-tracking approve"]
Pay["Payments tab"]
Profile["Profile / settings / reports"]
end
subgraph shared["Shared reusables"]
DV["Detailed job view"]
RA["Row actions"]
EJ["End job popup"]
end
List --> DV
List --> RA
RA --> EJ
Post -->|"insert shifts + edge shift"| List
Detail --> DV
Bubble evidence for panels and reusables is under Traceability / IR evidence.
#Primary journeys
Journeys are reconstructed from company dashboard workflows and shared reusable workflows (ButtonClicked → NewThing / ChangeThing / ScheduleAPIEvent / ChangePage / Show-Hide / SetCustomState). Where a step only opens another module’s full UI (chat thread body, Stripe Connect plugin), the step is marked out of scope.
#J1 — Enter company dashboard and switch tabs
- A signed-in company user opens
/company/shifts (with or without tab).
- The host renders under the cabinet workspace and stamps the page root; non-company or signed-out visitors are redirected or denied.
- URL
tab selects exactly one panel: empty/dashboard → dashboard; company_shifts → shifts list (+ Post a New Job); contractors → contractors; time_tracking → time-tracking; payments → payments; reports → reports; profile → profile; settings → settings.
- Nested
tabs=postjob or tabs=detail opens post/detail overlays without inventing extra sidebar entries.
- Session/role read failure shows a clear error or redirect — never a blank authenticated shell.
#J2 — Filter and browse company shifts
- On
tab=company_shifts, the operator sees Posted / Upcoming / Closed filters bound to shift_type (default filter custom state posted).
- The repeating list shows Job Title, Location, Assigned Contractor, Skillset, Required Hours, Date, and HR columns, or empty “No result found.”
- The list loads only shifts owned by the current company profile, filtered by
shift_type, with pagination and an explicit max page size.
- Stop Hiring opens Popup stop hiring; confirm persists
shift_type = upcoming and stop_hiring = true through server-authorized updates. Closed is filter-only (sets list custom state type_ = closed). Close the job is visible but has no ButtonClicked workflow in the export — do not invent a persist path.
- Opening a row / details control mounts the shared Detailed job view (and/or
tabs=detail).
- Load/filter/mutation failures show plain-language UI errors.
#J3 — Post a new job and publish to marketplace
- Operator chooses Post a New Job (
tab stays on company shifts; nested tabs=postjob) and fills the new-job form (title, date/time Wisconsin, hours, policies, contacts, seats needed, skill set, rates, experience).
- Post creates a
shifts row with shift_type = posted, company ownership, and the form field map from DATA-MODEL; Discard resets/hides without insert.
- After insert, when the new shift’s date is after current date/time, the app invokes edge function
shift (JWT; company or service scheduler roles) instead of a browser-trusted API secret.
- Edge
shift only continues the notify chain when at least one contractor user already lists that shift; it does not invent Stripe Connect/KYC.
- Invalid/empty parent group blocks Post; failures show inline errors.
#J4 — View, edit, and manage applicants on a shift
- Operator opens Detailed job view for a bound shift and sees transcribed labels (Location, Experience, Directions, Was posted at, Start Date/Time, Wisconsin time, Hours required, pay practice, contacts, policies, assigned contractors).
- Edit the job PATCHes shift fields through server-authorized paths;
company_rate / contractor_rate only via RPC.
- Stop hiring confirm sets
shift_type = upcoming and stop_hiring = true, then closes the dialog.
- Cancel contractor removes the user from assignee lists and from the contractor’s shifts membership after the 2-hour/4-hour pay note confirm.
- Contractor don’t show up adds the user to the no-show list and adjusts dependability counters server-side.
- Empty assignees show “There you’ll see assigned contractors for this shift”; mutation failures stay inline; Discard/Confirm reset groups.
#J5 — Row actions and end job popup
- Shared Row actions shows Details, Start tracking, and End (visibility respects closed-shift custom state).
- Details opens the shared Detailed job view for the bound shift — no second detail tree.
- Start tracking creates a
time_entries row (company_request false, contractor = current user, send false) via server insert/RPC, then navigates per source ChangePage.
- End shows End job popup; Cancel resets/hides without writes.
- Submit writes tracking notes, sets
send true, and computes paid_amount from logged hours/minutes × skill rate inside a server RPC (MON-CLIENT-WRITES).
- Postmark/notification connectors may be stubbed behind env with timeout; no new money rails.
#J6 — Browse contractors, Preferred, and message
- On
tab=contractors, operator searches and filters by Location and Skills over a paginated contractor list (empty “No result found.”).
- Preferred / Do not Book / No show up mutate the viewed contractor user’s
preferred_list_user / do_not_book_list_user / no_show_up_list_user by adding or removing the signed-in company user (CurrentUser) under RLS — not writes to the company operator’s own profile lists.
- Send a message creates a chat row visible to both users and navigates to
/chat (chat body remains MOD-CHAT).
- Back to all returns from contractor detail to the list; load/save errors are visible.
#J7 — Approve time cards and send payments
- On
tab=time_tracking, operator sees company-scoped time entries with column labels and empty “Nothing was found. Try again later!”.
- Approve / Multipal approval set company approval and/or sent-for-payment only via transactional server RPC.
- Logged hours/minutes edits recompute
paid_amount on the server from shift skill rates (same family as End job popup).
- On
tab=payments, operator sees Previous payments / Upcoming payment schedule headings, Unpaid status copy, and Multipal payment using the same server send path.
- Existing Stripe account display may be read-only; Connect plugin actions are not implemented here (MON-MARKETPLACE).
- Dialogs dismiss on success; failures show visible errors.
#J8 — Dashboard metrics, reports, profile, and settings
- On
tab=dashboard, operator sees KPI labels (Current open shifts, Percentage of open shifts, Placement rate, Total Cancellation rate, Time to fill shift, Repeat contractors, Average weekly cost) and empty “You have no shifts for today.” when appropriate.
- Metrics come from company-scoped queries (or honest zeros) — no fabricated Connect balances.
- Reports shows Cancellations/No shows, TradeForce Usage, Total budget for the month with empty “Nothing was found at the moment.” and paginated lists.
- Profile fields align with company onboarding where they overlap; Save PATCHes
profiles under RLS.
- Settings mounts only evidenced chrome — no undocumented Connect onboarding.
#Vertical slices (decompose)
| Item |
Product outcome |
| COSHIFT-HOST |
/company/shifts host, role gate, and exclusive tab panel switching (including empty → dashboard). |
| COSHIFT-LIST |
Posted/Upcoming/Closed filter list; Stop Hiring confirm persist; Closed filter-only; Close the job unwired; open detail. |
| COSHIFT-POST |
Post a New Job form, insert shifts, conditional edge shift when date > now. |
| COSHIFT-DETAIL |
Sole Detailed job view implement tree: view/edit, stop hiring, cancel / don’t-show applicants. |
| COSHIFT-ACTIONS |
Sole Row actions + End job popup trees: details, start tracking, end + paid RPC. |
| COSHIFT-CONTRACTORS |
Contractors tab search/filter; Preferred / Do not Book / No show on contractor-user lists; start chat navigate. |
| COSHIFT-APPROVE-PAY |
Time-tracking approve + payments tab send_paid RPCs. |
| COSHIFT-DASHBOARD-META |
Dashboard KPIs, reports, profile save, settings chrome. |
#Key UI states
| State |
When |
User sees |
Empty tab / dashboard |
URL nav empty or dashboard |
Dashboard KPI panel |
company_shifts |
tab=company_shifts |
Shift filters + list + Post a New Job |
tabs=postjob |
Nested post overlay |
G-New job post form |
tabs=detail |
Nested detail |
Detailed job view instance |
Filter posted / upcoming / closed |
List custom state type_ |
Matching shift rows or empty copy |
| Closed-shift row actions |
Closed-shifts custom state |
Constrained Details / Start tracking / End visibility |
| End job popup open |
End control |
Confirm copy, earlier/later ask, notes, Cancel/Submit |
| Preferred on contractor detail |
Preferred control |
Button label Preferred; list membership updates on contractor user |
| Approve / payment dialogs |
Approve or Multipal actions |
Note popup then success dismiss or error |
#Navigation contracts
| Label / trigger |
Behavior |
Empty tab |
Show dashboard (nav empty or dashboard) |
| Dashboard |
?tab=dashboard |
| Company shifts |
?tab=company_shifts |
| Post a New Job |
Keep company shifts; set tabs=postjob |
| Open detail |
Set tabs=detail and/or mount Detailed job view |
| Contractors |
?tab=contractors |
| Time-tracking |
?tab=time_tracking |
| Payments |
?tab=payments |
| Reports |
?tab=reports |
| Profile (SideBar Settings) |
?tab=profile |
| Settings (SideMenuCompany) |
?tab=settings |
| Send a message |
Create chat → /chat |
#Style and fidelity
Company dashboard uses the cabinet workspace outlet (.surface-card main). Transcribe visible copy and stamp data-bubble-id on migrated roots: page, panel groups (dashboard, company shifts, contractors, time track, payments, reports, profile, settings), Post a New Job, new-job form, Detailed job view, Row actions, End job popup, and primary list/RG roots. Shared tokens come from the app-level stylesheet that SHELL-LAYOUT (MOD-SHELL) delivers out of IR styles (ir/slices/styles____part_all.json — 49 styles, 8 color tokens, 1 font token); this cycle's module map has no separate design-system module, so do not defer a token to one. Preserve Wisconsin time copy on date/time fields. Prefer-control button copy is Preferred (not “Prefer”).
#Failure modes
| Case |
Expected behavior |
| Signed-out or non-company on host |
Redirect to / or role home; no company panels. |
| Shift list query / filter failure |
Plain-language error on list; no silent empty success. |
| Post validation (empty parent / invalid fields) |
Block insert; inline errors; stay on form. |
Edge shift unavailable / unauthorized |
User-visible failure after insert; do not pretend notify succeeded. |
| Stop hiring confirm / edit / cancel / don’t-show write failure |
Inline error; leave prior state unchanged. |
| Close the job clicked |
No persist in source (unwired); UI may no-op — do not invent shift_type/stop_hiring writes. |
| Closed filter |
Filter-only custom state; never ChangeThing the shift. |
| Start tracking / End submit / approve / send_paid RPC failure |
Popup or row error; map once at boundary; no client-only money flip. |
| Contractor list / Preferred / Do not Book / No show save failure |
Visible error; contractor-user lists unchanged. |
| Profile save failure |
Visible error on profile panel. |
| Stripe Connect plugin path |
Do not implement; read-only account fields only if evidenced. |
#Traceability
| Product concept |
Target (route / component / table) |
Bubble evidence |
| Company dashboard page |
/company/shifts, CompanyShiftsPage |
bTITd |
| Dashboard panel |
CompanyDashboardPanel |
bTKOL0, element_condition:bTKOL0:0 |
| Company shifts list |
CompanyShiftsList |
bTIcA, element_condition:bTIcA:0 |
| Post a New Job control |
PostNewJobButton |
bTIbh |
| New job form |
PostJobForm |
bTIkQ |
| Post insert workflow |
createShift + RLS/RPC |
bTJer1, bTJfJ1 |
| Schedule notify after post |
Edge shift |
bTLtQ, bTMBT, bTMBa |
| Schedule notify after edit |
Edge shift |
bTMBl, bTMBT |
| Shift type filters |
shift_type enum + list state |
shift_type, custom_state:bTIbn:type_ |
| Closed filter (filter-only) |
list custom state type_=closed |
bTIcH, bTJfl |
| Stop Hiring (opens popup) |
ShowElement / DisplayGroupData |
bTIdf, bTJft |
| Stop hiring confirm (list popup) |
shift_type=upcoming, stop_hiring |
bTJgL |
| Close the job (unwired) |
visible control; no workflow |
bTIdl |
| Detailed job view |
DetailedViewJob (sole tree) |
bTIgC, instance bTIfM |
| Edit the job |
PATCH shift fields / rate RPC |
bTJmD, bTJmQ |
| Stop hiring confirm (detail) |
shift_type=upcoming, stop_hiring |
bTJlT, bTJlV |
| Cancel contractor |
assignee + shifts_list remove |
bTJkk |
| Contractor don’t show up |
no-show list + counters |
bTJkG, bTJkM |
| Row actions |
ShiftRowActions (sole tree) |
bTHTn |
| Details / Start tracking / End |
row controls |
bTKCl, bTLdP/bTLdV, bTLej |
| End job popup |
EndJobPopup (sole tree) |
bTHkx, submit bTLcX/bTLcd |
| Contractors panel |
CompanyContractorsPanel |
bTJph |
| Preferred control |
Preferred button copy |
bTJyd0 |
| Preferred list mutate |
contractor user += CurrentUser |
bTJzT0, bTJzZ0 |
| Do not Book list mutate |
contractor user lists |
bTJzd0, bTJzj0 |
| No show up list mutate |
contractor user lists |
bTJzl0, bTJzr0 |
| Send a message |
NewThing chat + ChangePage |
bTJzA0, bTJzM0, bTJzS0 |
| Time-tracking panel |
CompanyTimeTrackingPanel |
bTKSh0 |
| Payments panel |
CompanyPaymentsPanel |
bTKea4 |
| Reports / profile / settings |
meta panels |
bTKky4, bTKiv4, bTKrz |
| Tab option values |
?tab= from nav |
nav |
| Nested post/detail |
?tabs= |
tabs (postjob, detail) |
| Shifts table |
public.shifts |
shifts |
| Time entries |
public.time_entries |
time_tracking1 |
| Preferred / do-not-book / no-show fields |
user list fields on contractor profile |
preferred_list_user, do_not_book_list_user, no_show_up_list_user |
| Custom rates (skill) |
rates / custom_rate reads |
custom_rate, rates |
Dead Bubble job type |
omit entity |
job (residual dead) |
Residual company type |
ownership via profiles + shifts.company_id |
company |
#Naming (idiomatic) — source → target
| Source (Bubble) |
Target |
Layer |
company_dashboard_shifts / bTITd |
/company/shifts |
route |
DetailedViewJob |
DetailedViewJob component |
ts |
EndJobPopup |
EndJobPopup |
ts |
RGaction |
ShiftRowActions |
ts |
shifts |
shifts |
sql |
job_title_text |
job_title |
sql |
hours_required_number |
hours_required |
sql |
date_date |
shift_date |
sql |
shift_type_option_shift_type |
shift_type |
sql/enum |
company_rates_number |
company_rate |
sql |
contractor_rates_number |
contractor_rate |
sql |
amount_of_needed_contractors_number |
seats_needed |
sql |
company_user |
company_id → profiles |
sql |
stop_hiring_boolean |
stop_hiring |
sql |
preferred_list_user |
contractor-user preferred companies list |
sql |
do_not_book_list_user |
contractor-user do-not-book companies list |
sql |
no_show_up_list_user |
contractor-user no-show companies list |
sql |
time_tracking1 |
time_entries |
sql |
paid___number |
paid_amount |
sql |
send_paid_boolean |
sent_for_payment |
sql |
approve_company_boolean |
company_approved (or DATA-MODEL name) |
sql |
logged__hours__number / logged__minuts__number |
logged_hours / logged_minutes |
sql |
API event shift (bTMBT) |
edge function shift |
api |
option nav db_values |
tab query string |
doc/route |
#IR evidence
#Module evidence roots
bTITd — company_dashboard_shifts page (/pages/bTIbd); tab panels, post/list/detail workflows
bTIgC — DetailedViewJob definition (/element_definitions/bTIgD); sole shared detail UI
bTHkx — EndJobPopup definition (/element_definitions/bTHky); sole end-shift popup
bTHTn — RGaction definition (/element_definitions/bTHTo); Details / Start tracking / End
bTMBT — API event shift (/api/bTMBU); contractor-count gate + ScheduleAPIEventOnList → tracking helper
Supporting segments: ir/slices/bTIbd____root.json, ir/slices/bTIbd__elements____remaining_children.json, ir/slices/bTIbd__elements__bTHQp__elements____remaining_children.json, ir/slices/bTIbd__workflows____part_A.json, ir/slices/edef_bTIgD____root.json, ir/slices/edef_bTHky____root.json, ir/slices/edef_bTHTo____root.json, user types shifts / job / company / custom_rate, option shift_type.
#Path-key ↔ inventory id aliases
| Inventory id |
Export path key |
bTITd |
/pages/bTIbd |
bTIgC |
/element_definitions/bTIgD |
bTHkx |
/element_definitions/bTHky |
bTHTn |
/element_definitions/bTHTo |
bTMBT |
/api/bTMBU |
#Panel visibility (URL nav)
| Panel |
Shows when |
| Dashboard |
nav equals dashboard or nav is empty |
| Company shifts |
nav equals company_shifts |
| Post a New Job button |
nav equals company_shifts |
| New job form |
tabs equals postjob |
| Contractors |
nav equals contractors |
| Time-tracking |
nav equals time_tracking |
| Payments |
nav equals payments |
| Reports |
nav equals reports |
| Profile |
nav equals profile |
| Settings |
nav equals settings |
#Shift type option set
| db_value |
Display |
posted |
posted |
upcoming |
upcoming |
closed |
closed |
List filter custom state type_ defaults to posted. Closed filter workflow bTJfl (trigger bTIcH) only SetCustomState type_=closed — it does not ChangeThing the shift.
#Stop Hiring vs Close the job (list)
- Stop Hiring (
bTIdf) → workflow bTJft: ShowElement + DisplayGroupData on Popup stop hiring (bTIeJ).
- Confirm ChangeThing action
bTJgL: shift_type_option_shift_type → upcoming, stop_hiring_boolean → true on ElementParent shift.
- Close the job (
bTIdl, path element bTIdn): button text “Close the job”; zero ButtonClicked workflows on page or element_definitions after sanitized export chase — treat as unwired UI, not a persist path.
- Decompose COSHIFT-LIST AC that claims Close the job persists is export-drift; implement follows this chase.
#Preferred / Do not Book / No show ownership
- Contractor detail group binds to URL param contractor (
bTJxD0 chain under bTJph).
- Preferred button (
bTJyd0) copy is “Preferred”; workflow bTJzT0 / action bTJzZ0 ChangeThing ElementParent: add CurrentUser to preferred_list_user, remove from do_not_book_list_user.
- Do not Book:
bTJzd0 / bTJzj0 — remove CurrentUser from preferred, add to do_not_book.
- No show:
bTJzl0 / bTJzr0 — add CurrentUser to no_show_up_list_user.
- Fields live on the viewed contractor user, not on the company operator’s profile.
#Post / edit → edge shift
- Post workflow ScheduleAPIEvent (
bTLtQ): schedules bTMBT when PreviousStep (NewThing) date_date greater_than Current Date/Time; passes new shift as parameter.
- Edit workflow ScheduleAPIEvent (
bTMBl): same API event after ChangeThing on the shift.
- API
shift (bTMBT): runs when Search count of contractor-role users whose shifts_list contains the parameter shift is ≥ 1; action ScheduleAPIEventOnList toward tracking helper (bTLsx family) with short delay.
- Target: JWT edge function
shift — never ship service role to the browser.
#Privacy / feature notes
shifts and time_tracking1 carry Bubble privacy roles (see DATA-MODEL / overview).
- Residual
company / job types: no separate migrate journeys; ownership via profiles.role=company and shifts.company_id.
- Money writes (rates,
paid_amount, sent_for_payment, approve flags) require server/RPC authority (MON-CLIENT-WRITES / LOG-CLIENT-WRITES).
- Stripe Connect / KYC remain MON-MARKETPLACE — out of this module’s implement scope.
#Decompose refs
tooling/cursor-impl-cycle/output/decompose/MOD-COMPANY-SHIFTS.json — items COSHIFT-HOST through COSHIFT-DASHBOARD-META.