Staff Entity Detail Page Redesign — Design
This is the design / rationale / wireframe doc. For the canonical contract — invariants, scope, component shapes, content maps, sheet behavior, permissions, tables, MG ID system, action endpoints — see spec.md. When the two disagree, spec.md wins.
Drift notes (resolved by spec.md, not yet rewritten here):
- Entity scope is all of
apps/web/src/app/(app)/staff/, not just the original 6.Reservationis one detail page that branches onReservationType(MONTHLY/HOURLY/TOUR), not a Lockout-only page.- User now has an MG ID (
USR{seq}) — schema change is a backend prereq.- MG ID format is dashless (
RSV22,AST42), notRSV-22/AST-42as wireframes show.- Read-only / financial entities (Payment, Charge, Ledger, Lock, Studio Reference) use the same shell with no edit affordances.
- Sheets anchor right on desktop, bottom on mobile with drag handle + swipe-down dismiss.
- Save flow is invalidate-and-refetch; dirty-state guard fires on every dismiss vector; last-write-wins concurrency with
Updated byinSystem.- Every detail page gets an Activity section (audit-log powered,
Load morepaginated) as the last main-column block.- Per-row
...menus come from a single entity-boundrowActions(entity, viewerRoles)function shared by detail-page and list-page tables.- "Inline-edit affordances" phrase removed from the contract — sheets + actions menu only.
View in Stripe →uses a single sharedstripeDashboardUrl(resource, id)helper (single platform account; only mode varies).- Actions whose backend endpoints don't exist (Pause billing, Send payment link, Refund, Resend receipt, Convert to lockout, Duplicate, Export resources) are stripped from launch menus and tracked in spec's Future actions backlog.
Problem Statement
All staff entity detail pages (reservations/lockouts, users, locations, resources, assets, organizations) share the same poor UX/UI pattern:
- Mixed edit/display modes - Forms mix editable HeroUI input fields with read-only
LabeledValuedisplays, making it impossible to scan what's changeable - No visual hierarchy - Everything dumped into flat tabs with no grouping or structure
- Typography chaos - Inconsistent label styles (
LabeledValueuses uppercase 12px labels, HeroUI inputs use floating labels, helper text scattered as standalone<p>tags) - No design system - Each form evolved organically with different patterns
- Massive form components - Single forms handle both create and edit modes, reaching 1700 lines (LockoutForm)
Current Architecture
All staff entity pages follow this pattern:
- Server component (
[id]/page.tsx) fetches data - Client component (
EditEntityPage.tsx) handles mutations - Form component (
EntityForm.tsx) renders React Hook Form with Zod validation StaffFormLayoutwraps forms with tabs (edit mode) or stacked cards (create mode)- HeroUI components (Input, Select, Autocomplete, Textarea) for form fields
LabeledValuecomponent for read-only fields, mixed inconsistently with form inputsFormSectionhas 3 variants (default, card, collapsible) but edit mode always uses default (basically nothing)- System tab auto-generated by StaffFormLayout, but some forms also define their own system fields
Current form files and sizes:
apps/web/src/components/organisms/forms/LockoutForm.tsx(~1700 lines)apps/web/src/components/organisms/forms/ResourceForm.tsx(~1060 lines)apps/web/src/components/organisms/forms/LocationForm.tsx(~890 lines)apps/web/src/components/organisms/forms/AssetForm.tsx(~750 lines)apps/web/src/components/organisms/forms/UserForm.tsx(~440 lines)apps/web/src/components/organisms/forms/OrganizationForm.tsx(~180 lines)
Supporting files:
apps/web/src/components/molecules/forms/StaffFormLayout.tsx- shared layout wrapperapps/web/src/components/molecules/forms/FormSection.tsx- section containerapps/web/src/components/atoms/forms/LabeledValue.tsx- read-only displayapps/web/src/components/molecules/forms/FormFieldGroup.tsx- side-by-side field layout
Solution: View/Edit Separation Pattern
Design Inspiration
Inspired by Stripe's dashboard pattern, adapted for Metrognome's simpler needs:
What Stripe does:
- Detail pages are read-only display
- Primary CTA button in header ("Edit product", "Update subscription") opens a drawer/sheet
- Actions menu (
...) for less common operations (cancel, pause, etc.) - Two-column layout: main column for activity/tables, sidebar for details/metadata
- Small edit pencil (
✎) on sidebar sections for focused edits - Sidebar collapses below main content on mobile
How we adapt it:
- Same view/edit separation and sheet pattern
- Same two-column layout logic
- Simpler than Stripe (we don't have API logs, events, tax calculations)
- Our entities have less activity data but still enough for tables (payments, reservations, placements)
- No need to replicate Stripe's visual language - just the UX pattern
Column Content Logic
Main column = "What's happening / what's related"
- Tables of related data (payments, reservations, resources)
- Activity and history
- Lists with add/remove (images, access codes, directions)
- Things you scan through or work with
Sidebar = "What it is / who it belongs to"
- Entity properties grouped into sections
- Related entity links (customer, location)
- IDs, dates, status info
- Each section optionally editable via
✎button
On mobile, sidebar content stacks below main content (single column).
Core Principles
- Display separated from edit - Detail surfaces render values as labels-and-text only; no form inputs mixed with display fields. Edits route through sheets, the actions menu, or designated inline-edit affordances.
- Primary edit button -
[Edit X]in header row opens sheet with core editable fields - Section edit buttons - Small
✎on sidebar sections for metadata edits - Actions menu -
[...]for rare/dangerous actions (cancel, delete) - Two-column layout - Main column for activity/lists, sidebar for details
- Create via sheets - List pages have
[+ Add X]button that opens create sheet
Layout Pattern
Header
┌─────────────────────────────────────────────────────────────────────────────┐
│ {EntityType} › {MG_ID} [Edit {X}] [...] │
│ │
│ {Entity Name} ● {Status} │
│ {Subtitle} │
│ │
├───────────────────────────────────────────────────┬─────────────────────────┤
│ │ │
│ MAIN COLUMN │ SIDEBAR │
│ │ │
└───────────────────────────────────────────────────┴─────────────────────────┘
- Row 1: Breadcrumb (with MG ID) + primary action button + actions dropdown (right-aligned)
- Row 2: Entity name + status badge inline
- Row 3: Subtitle (contextual info)
- Status badge appears immediately after entity name (like Stripe), NOT on the right side competing with buttons
MG ID
MG ID is the staff shorthand used in Slack messages, verbal communication, etc. Displayed prominently in breadcrumb.
| Entity | MG ID Format | Example | Source |
|---|---|---|---|
| Reservation | RSV{seq} |
RSV-22 | Global sequence |
| Location | {TypeCode}{seq} |
MG7 | Location type code + local seq |
| Resource | {locMgId}-{TypeCode}{seq} |
MG7-SMO248 | Location MG ID + resource type code + local seq |
| Asset | AST{seq} |
AST42 | Global sequence |
| User | (no MG ID) | — | — |
MG ID service: apps/api/src/services/shared/MgIdService.ts
Type codes: packages/shared-constants/src/mg-id.ts
Location type codes: MG (Metrognome), VNU (Venue), RTL (Retail), CMP (Competitor) Resource type codes: SMO (Studio Monthly), SHR (Studio Hourly), EQP (Equipment), STO (Storage), PRK (Parking), MBX (Mailbox), AMN (Amenity)
Header Examples
Lockout:
Reservations › RSV-22 [Edit Lockout] [...]
Studio 248 ● Active
TZTR TZTING · MG7 Cully
Location:
Locations › MG7 [Edit Location] [...]
Cully ● Active
Portland, OR
Resource:
Locations › MG7 › Resources › MG7-SMO248 [Edit Resource] [...]
Studio 248 ● Active
Monthly · MG7 Cully
User:
Users › [Edit User] [...]
Aaron Hogan ● Approved
aaron@example.com
Wireframes
Lockout Detail Page
┌─────────────────────────────────────────────────────────────────────────────┐
│ Reservations › RSV-22 [Edit Lockout] [...] │
│ │
│ Studio 248 ● Active │
│ TZTR TZTING · MG7 Cully │
│ │
│ Started Next invoice │
│ Jan 15 $421.95 on Feb 1 │
│ │
├───────────────────────────────────────────────────┬─────────────────────────┤
│ │ │
│ Payments │ Details ✎ │
│ │ │
│ Date Amount Status Invoice │ Customer │
│ ────────────────────────────────────────────── │ TZTR TZTING → │
│ Jan 15 $421.95 ● Paid inv_xxx ... │ │
│ │ Resource │
│ │ Studio 248 → │
│ Transfers │ │
│ │ Location │
│ Date Amount Status Account │ MG7 Cully → │
│ ────────────────────────────────────────────── │ │
│ Jan 16 $380.00 ● Complete Cully ... │ ───────────────────── │
│ │ │
│ │ Subscription │
│ Access Codes [+] │ │
│ │ Stripe Sub │
│ ┌──────────────────────────────────────────┐ │ sub_xxx → │
│ │ 123456 Dynamic PIN Active ... │ │ │
│ └──────────────────────────────────────────┘ │ Billing Anchor │
│ ┌──────────────────────────────────────────┐ │ 1st of month │
│ │ 789012 Static Active ... │ │ │
│ └──────────────────────────────────────────┘ │ Monthly Total │
│ │ $421.95 │
│ │ $411 + $10.95 ins. │
│ │ │
│ │ Insurance │
│ │ $5,000 Coverage │
│ │ │
│ │ Promo Code │
│ │ — │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ Notes ✎ │
│ │ │
│ │ Admin notes for this │
│ │ lockout │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ System │
│ │ │
│ │ ID │
│ │ LK-CU-248-T2T ⧉ │
│ │ │
│ │ Created │
│ │ Jan 15, 2:34 PM │
│ │ │
│ │ Updated │
│ │ Jan 28, 9:12 AM │
│ │ │
└───────────────────────────────────────────────────┴─────────────────────────┘
Location Detail Page
┌─────────────────────────────────────────────────────────────────────────────┐
│ Locations › MG7 [Edit Location] [...] │
│ │
│ Cully ● Active │
│ Portland, OR │
│ │
├───────────────────────────────────────────────────┬─────────────────────────┤
│ │ │
│ Resources [+] │ Details ✎ │
│ │ │
│ Name Type Status Price │ Type │
│ ────────────────────────────────────────────── │ Metrognome │
│ Studio 101 Monthly Active $285 ... │ │
│ Studio 102 Monthly Active $325 ... │ Opened │
│ Studio 201 Hourly Active $28/hr ... │ Mar 15, 2024 │
│ Drum Room Hourly Active $35/hr ... │ │
│ │ Slug │
│ View all 24 resources → │ cully │
│ │ │
│ │ Description │
│ Access Gates [+] │ Premier rehearsal │
│ │ facility in NE │
│ Device ID Scope Status │ Portland... │
│ ────────────────────────────────────────────── │ │
│ GATE-001 Location Online ... │ ───────────────────── │
│ GATE-002 Studio 248 Online ... │ │
│ │ Address ✎ │
│ │ │
│ Images [+] │ 4530 NE Cully Blvd │
│ │ Portland, OR 97218 │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ │ │ │ │ │ │ │ │ │ Timezone │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ America/Los_Angeles │
│ │ │
│ │ ───────────────────── │
│ Directions [+] │ │
│ │ Contact ✎ │
│ ┌──────────────────────────────────────────┐ │ │
│ │ From I-84: Take exit 5... ... │ │ Phone │
│ └──────────────────────────────────────────┘ │ (503) 555-1234 │
│ │ │
│ │ Email │
│ │ cully@metrognome.com │
│ │ │
│ │ Manager │
│ │ Sarah Johnson → │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ Pricing ✎ │
│ │ │
│ │ Monthly Rate │
│ │ $10.00/sq ft │
│ │ │
│ │ Hourly Rate │
│ │ $2.50/sq ft │
│ │ │
│ │ Insurance │
│ │ Available, optional │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ Integrations ✎ │
│ │ │
│ │ Stripe Account │
│ │ acct_xxx → │
│ │ │
│ │ UniFi Group │
│ │ group_xxx │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ System │
│ │ ID loc_xxx ⧉ │
│ │ Created Jan 10, 2024 │
│ │ │
└───────────────────────────────────────────────────┴─────────────────────────┘
Resource Detail Page
┌─────────────────────────────────────────────────────────────────────────────┐
│ Locations › MG7 › Resources › MG7-SMO248 [Edit Resource] [...] │
│ │
│ Studio 248 ● Active │
│ Monthly · MG7 Cully │
│ │
├───────────────────────────────────────────────────┬─────────────────────────┤
│ │ │
│ Reservations │ Details ✎ │
│ │ │
│ Customer Type Start Status │ Type │
│ ────────────────────────────────────────────── │ Monthly │
│ TZTR TZTING Lockout Jan 15 Active │ │
│ │ Status │
│ │ ● Active │
│ Asset Placements [+] │ │
│ │ Availability │
│ Asset Category Placed │ Occupied │
│ ────────────────────────────────────────────── │ │
│ Fender Twin Amplifier Jan 10 ... │ Description │
│ SM58 (x2) Microphone Jan 10 ... │ Professional monthly │
│ │ rehearsal studio... │
│ │ │
│ Resource Groups │ ───────────────────── │
│ │ │
│ ┌──────────────────────────────────────────┐ │ Location │
│ │ Yellow Wing Floor 2 ... │ │ MG7 Cully → │
│ └──────────────────────────────────────────┘ │ │
│ │ ───────────────────── │
│ │ │
│ Images [+] │ Physical ✎ │
│ │ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ Dimensions │
│ │ │ │ │ │ │ │ │ │ 12' × 14' × 10' │
│ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ Max Occupancy │
│ │ 4 people │
│ │ │
│ │ Floor / Wing │
│ │ 2 / Yellow │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ Pricing ✎ │
│ │ │
│ │ Card Price │
│ │ $411.00/mo │
│ │ │
│ │ ACH Price │
│ │ $395.00/mo │
│ │ │
│ │ Rate per Sq Ft │
│ │ $2.45 │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ Notes ✎ │
│ │ Good natural light, │
│ │ corner unit │
│ │ │
│ │ ───────────────────── │
│ │ │
│ │ System │
│ │ ID res_xxx ⧉ │
│ │ MG ID CU-248 │
│ │ │
└───────────────────────────────────────────────────┴─────────────────────────┘
Edit Sheet (Vaul drawer from right)
┌─────────────────────────────┐
│ │
│ Edit Lockout ✕ │
│ │
│ Studio │
│ ─────────────────────── │
│ │
│ Location │
│ ┌─────────────────────┐ │
│ │ MG7 Cully ▾ │ │
│ └─────────────────────┘ │
│ │
│ Resource │
│ ┌─────────────────────┐ │
│ │ Studio 248 ▾ │ │
│ └─────────────────────┘ │
│ │
│ Pricing │
│ ─────────────────────── │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Card $ │ │ ACH $ │ │
│ │ 411.00 │ │ │ │
│ └──────────┘ └──────────┘ │
│ Leave blank for default │
│ │
│ Insurance │
│ ─────────────────────── │
│ │
│ ┌─────────────────────┐ │
│ │ $5,000 Coverage ▾ │ │
│ └─────────────────────┘ │
│ │
│ When to apply │
│ ─────────────────────── │
│ │
│ ● Immediately │
│ ○ Next billing cycle │
│ │
│ ┌─────────────────────────┐│
│ │ No changes to preview ││
│ └─────────────────────────┘│
│ │
│ ┌────────┐ ┌────────────┐ │
│ │ Cancel │ │ Update │ │
│ └────────┘ └────────────┘ │
│ │
└─────────────────────────────┘
Section Edit Sheet (small/focused)
┌─────────────────────────────┐
│ │
│ Edit Notes ✕ │
│ │
│ ┌─────────────────────────┐│
│ │ Admin notes for this ││
│ │ lockout ││
│ │ ││
│ │ ││
│ └─────────────────────────┘│
│ Internal notes, staff only │
│ │
│ ┌────────┐ ┌────────────┐ │
│ │ Cancel │ │ Save │ │
│ └────────┘ └────────────┘ │
│ │
└─────────────────────────────┘
Actions Menu Examples
Lockout: User: Location:
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ Pause billing │ │ Send password reset │ │ Duplicate location │
│ Send payment link │ │ Impersonate user │ │ Export resources │
│ View in Stripe → │ │ View in Stripe → │ │ View in Stripe → │
│ ───────────────────── │ │ ───────────────────── │ │ ───────────────────── │
│ Cancel subscription │ │ Delete user │ │ Deactivate location │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
Create Flow
List pages have a [+ Add X] button that opens a create sheet:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Resources [+ Add Resource] │
├─────────────────────────────────────────────────────────────────────────────┤
│ Name Location Type Status Price │
│ ───────────────────────────────────────────────────────────────────────── │
│ Studio 101 MG7 Cully Monthly Active $285 │
│ ... │
└─────────────────────────────────────────────────────────────────────────────┘
Create sheet uses the same EditSheet component. On save: POST to API → close sheet → redirect to new entity detail page.
Content Architecture by Entity
Lockout (Reservation - STUDIO_MONTHLY)
Main Column: | Section | Content | |---------|---------| | Payments | Table: date, amount, status, invoice link, row actions | | Transfers | Table: date, amount, status, account | | Access Codes | Table: code, type (dynamic/static), status. Can have MULTIPLE codes (front gate, wing, studio door) |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Details | Customer (link), Resource (link), Location (link) | Via primary edit | | Subscription | Stripe Sub (link), Billing Anchor, Monthly Total (with insurance breakdown), Insurance, Promo Code | Via primary edit | | Notes | Admin notes | ✎ section edit | | System | ID, MG ID, Created, Updated | Read-only |
Primary Edit Sheet fields: Location, Resource, Card Price, ACH Price, Insurance, Timing (immediate vs next cycle), with subscription preview/proration
Actions Menu: Pause billing, Send payment link, View in Stripe, Cancel subscription
Important domain notes:
- Insurance is part of the subscription/billing, NOT the studio itself
- From lockout detail, you edit lockout properties (assignment, pricing). To edit the studio itself (name, dimensions), go to the resource detail page
- Access codes are per-reservation, not per-resource. A lockout can include codes for front gate, wing door, and studio door
User
Main Column: | Section | Content | |---------|---------| | Reservations | Table: name, location, type, status | | Transactions | Table: date, description, amount | | Credit Balances | Table: location, balance | | Access Codes | Table: code, location, resource, status | | Waitlist | Table: entries or empty state |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Profile | Name, Email, Phone, Bio | ✎ section edit | | Status | Approved, Organization | ✎ section edit | | Billing | Stripe Customer (link), Stripe Balance | ✎ section edit | | Notes | Admin notes | ✎ section edit | | System | ID, Auth ID, Created, Last Login | Read-only |
Primary Edit Sheet: Name, Email (read-only), Phone, Bio, Approved status
Actions Menu: Send password reset, Impersonate, View in Stripe, Delete user
Location
Main Column: | Section | Content | |---------|---------| | Resources | Table: name, type, status, price, row actions | | Access Gates | Table: device ID, scope, status | | Images | Grid with add/remove | | Directions | List with add/remove |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Details | Type (immutable), Opened, Slug, Description | ✎ section edit | | Address | Street, City, State, ZIP, Timezone, Coordinates, Parking | ✎ section edit | | Contact | Phone, Email, Website, Manager (link) | ✎ section edit | | Pricing | Monthly Rate, Hourly Rate, Peak Multiplier, Insurance settings | ✎ section edit | | Integrations | Stripe Account (link), UniFi Group | ✎ section edit | | System | ID, MG ID, Created, Updated | Read-only |
Primary Edit Sheet: Name, Status, Description, Notes, Slug, Opened date
Actions Menu: Duplicate location, Export resources, View in Stripe, Deactivate
Resource
Main Column: | Section | Content | |---------|---------| | Reservations | Table: customer, type, start, status | | Asset Placements | Table: asset, category, placed date | | Resource Groups | List of group memberships | | Images | Grid with add/remove |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Details | Type (immutable), Status, Availability, Description, Featured | Via primary edit | | Location | Location (link), Acuity Calendar | Via primary edit | | Physical | Dimensions, Area, Max Occupancy, Floor, Wing | ✎ section edit | | Pricing | Card Price, ACH Price, Rate per Sq Ft, Peak settings | ✎ section edit | | Notes | Admin notes | ✎ section edit | | System | ID, MG ID, Created, Updated | Read-only |
Primary Edit Sheet: Name, Status, Description, Availability, Featured, Acuity Calendar ID
Actions Menu: Duplicate resource, View reservations, Delete resource
Asset
Main Column: | Section | Content | |---------|---------| | Placement History | Table: location, scope, placed date, removed date | | Component Installations | Table: component, installed date (if applicable) |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Details | Category (immutable), Subcategory, Status, Condition, Consumable, Description | Via primary edit | | Equipment | Manufacturer, Model, Serial Number | ✎ section edit | | Purchase | Date, Price, Vendor, Order Number | ✎ section edit | | Warranty | Expiration, Notes | ✎ section edit | | System | ID, MG ID, Created, Updated | Read-only |
Primary Edit Sheet: Name, Status, Condition, Description, Consumable flag
Actions Menu: Duplicate asset, Delete asset
Organization
Main Column: | Section | Content | |---------|---------| | Members | Table: users with this email domain | | Credit Balances | Table: location, balance |
Sidebar: | Section | Fields | Editable? | |---------|--------|-----------| | Details | Name, Email Domain | Via primary edit | | Owner | Owner (link) | ✎ section edit | | System | ID, Created, Updated | Read-only |
Primary Edit Sheet: Name, Email Domain
Actions Menu: Delete organization
Component Architecture
New Components
apps/web/src/components/
├── molecules/
│ ├── detail/
│ │ ├── DetailLayout.tsx # Two-column responsive layout
│ │ ├── DetailHeader.tsx # Breadcrumb, title, status, CTAs
│ │ ├── DetailSection.tsx # Sidebar section card with optional ✎
│ │ ├── DetailTable.tsx # Main column table with row actions
│ │ ├── DisplayField.tsx # Label + value for sidebar
│ │ ├── DisplayGrid.tsx # Grid of DisplayFields
│ │ └── index.ts # Barrel export
│ └── sheets/
│ └── EditSheet.tsx # Vaul drawer wrapper (create + edit)
├── organisms/
│ ├── detail/
│ │ ├── LockoutDetail.tsx # Lockout detail page content
│ │ ├── UserDetail.tsx
│ │ ├── LocationDetail.tsx
│ │ ├── ResourceDetail.tsx
│ │ ├── AssetDetail.tsx
│ │ └── OrganizationDetail.tsx
│ └── sheets/
│ ├── resource/
│ │ ├── ResourceSheet.tsx # Create + primary edit
│ │ ├── EditResourcePhysicalSheet.tsx
│ │ ├── EditResourcePricingSheet.tsx
│ │ └── EditResourceNotesSheet.tsx
│ ├── lockout/
│ │ ├── LockoutSheet.tsx # Create + primary edit (subscription)
│ │ └── EditLockoutNotesSheet.tsx
│ ├── location/
│ │ ├── LocationSheet.tsx # Create + primary edit
│ │ ├── EditLocationAddressSheet.tsx
│ │ ├── EditLocationContactSheet.tsx
│ │ ├── EditLocationPricingSheet.tsx
│ │ └── EditLocationIntegrationsSheet.tsx
│ ├── user/
│ │ ├── UserSheet.tsx # Create + primary edit
│ │ ├── EditUserProfileSheet.tsx
│ │ ├── EditUserStatusSheet.tsx
│ │ ├── EditUserBillingSheet.tsx
│ │ └── EditUserNotesSheet.tsx
│ ├── asset/
│ │ ├── AssetSheet.tsx
│ │ ├── EditAssetEquipmentSheet.tsx
│ │ ├── EditAssetPurchaseSheet.tsx
│ │ └── EditAssetWarrantySheet.tsx
│ └── organization/
│ ├── OrganizationSheet.tsx
│ └── EditOrganizationOwnerSheet.tsx
Component Specifications
DetailLayout
- Props:
children(main column),sidebar(sidebar content) - Two-column grid on desktop (main ~65%, sidebar ~35%)
- Single column on mobile (main content first, then sidebar below)
- Handles responsive breakpoint
DetailHeader
- Props:
breadcrumbs,title,subtitle,status,primaryAction,actionsMenu - Row 1: Breadcrumb (includes MG ID) + primary action button + actions dropdown (right-aligned)
- Row 2: Title + status badge inline (e.g., "Studio 248 ● Active")
- Row 3: Subtitle (contextual info - customer, location, etc.)
DetailSection
- Props:
title,onEdit?,children - Card with title and optional edit pencil button
- Used in sidebar for property groups
DetailTable
- Props:
title,columns,data,onAdd?,emptyMessage,viewAllHref? - Consistent table styling
- Row actions via
...menu - Optional
[+]add button in header - "View all →" link if data is paginated/truncated
DisplayField
- Props:
label,value,href?,copyable?,description? - Clean label + value display
- Optional link styling (for entity references)
- Optional copy button (for IDs, subscription IDs)
DisplayGrid
- Props:
children(DisplayField components) - Arranges DisplayFields in consistent spacing
EditSheet
- Props:
title,isOpen,onClose,onSave,isLoading,children - Vaul drawer from right side
- Header with title + close button
- Scrollable content area for form fields
- Sticky footer with Cancel + Save/Update/Create buttons
- Used for both create and edit flows (different title and button labels)
Implementation Phases
Phase 1: Foundation Components
Scope: Build reusable detail components
Tasks:
- Create
DetailLayoutcomponent with responsive two-column grid - Create
DetailHeadercomponent with breadcrumb, title, status, actions - Create
DetailSectioncomponent for sidebar cards - Create
DetailTablecomponent for main column tables - Create
DisplayFieldandDisplayGridcomponents - Create
EditSheetwrapper using Vaul (already installed:vaul: ^1.1.2)
Files to create:
apps/web/src/components/molecules/detail/DetailLayout.tsxapps/web/src/components/molecules/detail/DetailHeader.tsxapps/web/src/components/molecules/detail/DetailSection.tsxapps/web/src/components/molecules/detail/DetailTable.tsxapps/web/src/components/molecules/detail/DisplayField.tsxapps/web/src/components/molecules/detail/DisplayGrid.tsxapps/web/src/components/molecules/detail/index.tsapps/web/src/components/molecules/sheets/EditSheet.tsx
Phase 2: Prototype on Resource
Scope: Build complete Resource detail page + create flow as reference implementation
Why Resource: Has good variety of content types (tables, images, sidebar sections with edit buttons) without the subscription/billing complexity of Lockout
Tasks:
- Create
ResourceDetail.tsxdisplay component - Create
ResourceSheet.tsxfor primary edit and create (shared component with mode prop) - Create
EditResourcePhysicalSheet.tsxfor physical properties section - Create
EditResourcePricingSheet.tsxfor pricing section - Create
EditResourceNotesSheet.tsxfor notes section - Wire up data fetching and mutations
- Update detail page
[id]/page.tsx - Update list page to use create sheet instead of
/newroute - Test responsive behavior (two-column → single column)
Files to create/modify:
apps/web/src/components/organisms/detail/ResourceDetail.tsx(create)apps/web/src/components/organisms/sheets/resource/ResourceSheet.tsx(create)apps/web/src/components/organisms/sheets/resource/EditResourcePhysicalSheet.tsx(create)apps/web/src/components/organisms/sheets/resource/EditResourcePricingSheet.tsx(create)apps/web/src/components/organisms/sheets/resource/EditResourceNotesSheet.tsx(create)apps/web/src/app/(app)/staff/resources/[id]/page.tsx(modify)apps/web/src/app/(app)/staff/resources/[id]/EditReservationPage.tsx(replace)apps/web/src/app/(app)/staff/resources/page.tsx(modify list page for create sheet)apps/web/src/app/(app)/staff/resources/new/(remove after migration)
Phase 3: Location
Scope: Apply pattern to Location
Tasks:
- Create
LocationDetail.tsx - Create
LocationSheet.tsx(create + primary edit) - Create section edit sheets: address, contact, pricing, integrations
- Update list page for create sheet, remove
/newroute - Wire up data and mutations
- Test
Phase 4: User
Scope: Apply pattern to User (more tables in main column)
Tasks:
- Create
UserDetail.tsx - Create
UserSheet.tsx(create + primary edit) - Create section edit sheets: profile, status, billing, notes
- Update list page for create sheet, remove
/newroute - Wire up data and mutations
- Test
Phase 5: Lockout
Scope: Apply pattern to Lockout (most complex due to subscription logic)
Tasks:
- Create
LockoutDetail.tsx - Create
LockoutSheet.tsx(create + primary edit with subscription logic) - Create section edit sheets: notes
- Handle subscription preview/proration logic in primary sheet
- Update list page for create sheet, remove
/newroute - Wire up existing subscription metadata endpoints
- Test
Phase 6: Remaining Entities
Scope: Asset and Organization
Tasks:
- Create
AssetDetail.tsx,AssetSheet.tsx, and section sheets - Create
OrganizationDetail.tsx,OrganizationSheet.tsx, and section sheets - Update list pages for create sheets, remove
/newroutes - Test
Phase 7: Cleanup
Scope: Remove old code
Tasks:
- Remove old form components (
LockoutForm.tsx,LocationForm.tsx,ResourceForm.tsx,UserForm.tsx,AssetForm.tsx,OrganizationForm.tsx) - Remove old
/newpage routes - Remove unused
StaffFormLayout.tsx,FormSection.tsxif no longer needed - Update any shared navigation/links
- Final testing across all entities
- Mobile responsive testing
Migration Strategy
- Replace in place - Update existing routes directly (no parallel routes)
- Create flows also use sheets -
[+ Add X]on list pages opens create sheet, redirects to detail on save - Reuse form logic - Sheets reuse existing Zod schemas and field components (selectors, inputs, etc.)
- Incremental rollout - Deploy one entity at a time, each phase is independently shippable
Verification Plan
Per-Entity Testing
- Navigate to entity list → click entity → verify detail page renders correctly
- Verify two-column layout on desktop, single column on mobile
- Verify MG ID appears in breadcrumb
- Click primary edit button → verify sheet opens with correct data pre-filled
- Make changes → save → verify data updates on page
- Click section edit buttons (✎) → verify focused sheet opens
- Test actions menu items
- Verify all links work (breadcrumb, related entities in sidebar, table row links)
- Test empty states (no reservations, no placements, etc.)
Create Flow Testing
- Navigate to entity list page
- Click
[+ Add X]button → verify create sheet opens - Fill required fields → save → verify redirects to new entity detail page
- Verify new entity appears in list
Cross-Entity Testing
- Navigate between entities via links (user → reservation → resource → location)
- Verify consistent styling and behavior across all entity types
- Test on mobile viewport
Regression Testing
- Run existing test suites
Decisions Log
| Decision | Choice | Rationale |
|---|---|---|
| Vaul for sheets | ✅ Already installed (vaul: ^1.1.2) |
— |
| Create flow | Sheet-based (same as edit) | Consistency across all flows |
| URL structure | Replace in place | Simpler, no parallel route cleanup |
| Column layout | Two-column (main + sidebar) | Enough activity data to justify (payments, reservations, placements, access codes) |
| Status badge position | Inline with entity name | Don't compete with header buttons |
| Prototype entity | Resource | Good variety without subscription complexity |
| Insurance grouping | Part of subscription, not studio | Insurance is a billing concern, not a physical property |
Existing Files Reference
Form Files (to be removed in Phase 7)
apps/web/src/components/organisms/forms/LockoutForm.tsxapps/web/src/components/organisms/forms/LocationForm.tsxapps/web/src/components/organisms/forms/ResourceForm.tsxapps/web/src/components/organisms/forms/UserForm.tsxapps/web/src/components/organisms/forms/AssetForm.tsxapps/web/src/components/organisms/forms/OrganizationForm.tsx
Layout Files (to be removed/replaced in Phase 7)
apps/web/src/components/molecules/forms/StaffFormLayout.tsxapps/web/src/components/molecules/forms/FormSection.tsxapps/web/src/components/atoms/forms/LabeledValue.tsx
Reusable Field Components (keep and use in sheets)
apps/web/src/components/molecules/forms/LocationSelector.tsxapps/web/src/components/molecules/forms/ResourceSelector.tsxapps/web/src/components/molecules/forms/UserSelector.tsxapps/web/src/components/molecules/forms/InsuranceSelector.tsxapps/web/src/components/molecules/forms/CouponSelector.tsxapps/web/src/components/molecules/forms/AccessCodeSelector.tsxapps/web/src/components/molecules/forms/RecipientInput.tsxapps/web/src/components/molecules/forms/AddressAutocomplete.tsxapps/web/src/components/molecules/forms/AssetSelector.tsxapps/web/src/components/molecules/forms/FormFieldGroup.tsxapps/web/src/components/molecules/forms/PeakHours.tsx
Data Hooks (to wire up main column tables)
apps/web/src/hooks/api/useReservations.tsapps/web/src/hooks/api/useTransactions.tsapps/web/src/hooks/api/useAccessCodes.tsapps/web/src/hooks/api/useAssetPlacements.tsapps/web/src/hooks/api/useResources.tsapps/web/src/hooks/api/useLocations.ts
MG ID System
apps/api/src/services/shared/MgIdService.ts- Allocates sequences, builds MG IDspackages/shared-constants/src/mg-id.ts- Type codes (MG, SMO, SHR, etc.)
Related API Endpoints
GET /reservations/[id]- Reservation + location + resource + user + paymentsGET /reservations/[id]/subscription-metadata- Subscription health, transfer infoGET /reservations/[id]/access-codes- Dynamic/static access codesPOST /lockouts/[id]/preview-transfer- Preview transfer/proration calculationGET /resources/[id]- Resource detailsGET /resources?locationId=X- Resources at locationGET /access-gates?locationId=X- Access gates at locationGET /asset-placements?resourceId=X- Asset placementsGET /users/[id]- User detailsGET /users/[id]/stripe-balance- Stripe customer balance