Mobile View Proposal

Admin · Users — Mobile

Three frames at 375 × 667 (iPhone SE viewport). Mobile is a separate render path — UsersListMobile.jsx and UserEditMobile.jsx — switched at the route level by viewport width. Desktop components are untouched.

1 · List
Utilities

Admin · Users

Joshua Miller
joshuam@crsmechanical.net
Active
admin 2m ago
Brian Strehl
brian@crsmechanical.net
Active
cfo 3h ago
Mark Heffner
mark.h@crsmechanical.net
Active
pm 5h ago
Holly Ann Peters
holly@crsmechanical.net
Active
office 1d ago
Alice Reyes
alice@crsmechanical.net
Pending
pm never
Tony Ramirez
tony@crsmechanical.net
Deactivated
office

Cards replace the 6-column table. Each row collapses to: name + email (primary), role + last-login (secondary), single status badge top-right. Tap-to-edit; deactivated rows have inline "Reactivate".

2 · Edit
Brian Strehl

Edit User

Identity
name · email · qb id
Role + grants
cfo · 28 / 32 features

Changing the role here doesn't change grants. Use Reset to apply the template.

Always-on
1 of 1 granted
Projects
5 of 6 granted · 1 override
eva
wip Override
labor_forecast
ctc
job_status
job_editor
Field Ops
2 of 2 granted
Billing
3 of 3 granted
Admin
1 of 1 granted
Tap a section to expand and toggle individual feature grants. Overrides show inline.

The 9-group grid-cols-2 grant matrix collapses to accordions. Each section shows a "n of m granted · k override" summary so the admin sees deltas without expanding. Toggle switches replace checkboxes (bigger tap target). Sticky Save pinned to the bottom.

3 · Add User
Utilities

Admin · Users

Joshua Miller
Brian Strehl
Mark Heffner

Add User

Create a new user account. They'll sign in with their Microsoft work account.

Default access for pm
· dashboard · eva · wip · labor_forecast · ctc · job_status · job_editor · field_roster · bid_tracker · transaction_recoding · notifications · pto_balances

Mobile uses a bottom sheet (shadcn Sheet) instead of a centered Dialog — lower thumb-reach Create button, drag-to-dismiss handle, full-width inputs. The chip strip wraps naturally; QB list ID field moves into "Edit" later (rarely set at create time).

Implementation strategy

Mobile is a separate render path, not a responsive shrink of desktop. Two new files — UsersListMobile.jsx and UserEditMobile.jsx — render the views above. Existing UsersList.jsx + UserEdit.jsx stay untouched; only the route element wraps them in a viewport switch.

// main/src/pages/admin/index.jsx (NEW — thin switcher)
import useIsMobile from '@/core/useIsMobile' // window.innerWidth < 1024
import UsersList from './UsersList'
import UsersListMobile from './UsersListMobile'

export default function UsersListSwitcher() {
  return useIsMobile() ? <UsersListMobile /> : <UsersList />
}

The useIsMobile hook reads window.matchMedia('(max-width: 1023px)') and listens for changes — so a desktop user resizing their window past the breakpoint sees the appropriate view. Since desktop CRSApp is gated behind hidden lg:flex sidebar anyway, "mobile" here means anything < lg (1024px), matching the existing breakpoint convention.

Shared, unchanged:

Three new components:

The pattern is reusable for every other admin / settings / form-heavy page that gets the same mobile treatment later. The switcher is a dozen lines; the heavy lift is each page's mobile component.