|
| 1 | +# Making the Admin Panel Mobile-Friendly (Design & Implementation Plan) |
| 2 | + |
| 3 | +> **Status: proposed / not yet implemented.** This is a forward-looking plan for a |
| 4 | +> future development pass. It documents what breaks on small screens, the chosen |
| 5 | +> approach, and a concrete checklist so the work can be picked up and executed |
| 6 | +> confidently. Nothing here is wired up yet. |
| 7 | +
|
| 8 | +## Motivation |
| 9 | + |
| 10 | +The Admin Panel (the ⚙️ settings hub, [AdminPanel.jsx](../../client/src/components/AdminPanel.jsx)) |
| 11 | +is effectively unusable on a phone. It opens in a `maxWidth="lg"` MUI `Dialog` that never |
| 12 | +goes edge-to-edge, its 8-tab bar and nested sub-tab bars overflow horizontally, ~7 data |
| 13 | +tables don't reflow, and its dialogs/keypads keep desktop margins. Because HomeGlow is a |
| 14 | +touch-first app that people also administer from their phones, the settings surface should |
| 15 | +be first-class on narrow screens. |
| 16 | + |
| 17 | +**Goal:** the entire Admin Panel — shell, embedded tabs, and shared modals — is comfortable |
| 18 | +and native-feeling on a ~360px phone, with **zero visual change at ≥600px** (desktop/kiosk). |
| 19 | + |
| 20 | +## Constraints & current state (what the code looks like today) |
| 21 | + |
| 22 | +- **No responsive JS infrastructure exists.** There is no `useMediaQuery`, no `useTheme`, |
| 23 | + and **no `ThemeProvider`/`createTheme`** anywhere in `client/src`. MUI therefore uses its |
| 24 | + default breakpoints (`xs:0, sm:600, md:900, …`). `useTheme()`/`useMediaQuery` still work |
| 25 | + without a provider (they fall back to the default theme), so we can introduce them safely. |
| 26 | +- **The established house style for responsiveness** is breakpoint objects in `sx` and MUI |
| 27 | + Grid v2 `size={{ xs, sm }}`. Examples to mirror: |
| 28 | + - [app.jsx:~978](../../client/src/app.jsx) — `px: { xs: 3, sm: 5 }` |
| 29 | + - [AdminPanel.jsx:~2906](../../client/src/components/AdminPanel.jsx) — |
| 30 | + `flexDirection: { xs: 'column', sm: 'row' }` |
| 31 | +- **The forms are already fine.** Every `<Grid size={{ xs: 12, sm: 6 }}>` collapses to full |
| 32 | + width on mobile. Leave them alone. |
| 33 | + |
| 34 | +### What breaks on a ~360px screen (priority order) |
| 35 | + |
| 36 | +1. **Outer dialog** — [app.jsx:~996](../../client/src/app.jsx): `maxWidth="lg"` with **no** |
| 37 | + `fullScreen`/`fullWidth`; keeps default 32px side margins. The absolutely-positioned close |
| 38 | + `IconButton` ([app.jsx:~998](../../client/src/app.jsx)) can overlap the title/tabs. |
| 39 | +2. **Main Tabs bar** — [AdminPanel.jsx:~1782](../../client/src/components/AdminPanel.jsx): 8 |
| 40 | + text tabs, `variant="standard"` (not scrollable) → squash/overflow. Same for nested |
| 41 | + sub-tabs (Widgets sub-tabs ~1793; Chores sub-tabs ~2981). |
| 42 | +3. **Non-reflowing tables** (MUI `<Table>` never reflows): |
| 43 | + |
| 44 | + | File | Table | ~Line | Cols | |
| 45 | + | --- | --- | --- | --- | |
| 46 | + | AdminPanel.jsx | Tabs management | 2226 | 5 | |
| 47 | + | AdminPanel.jsx | Devices | 2329 | 4 | |
| 48 | + | AdminPanel.jsx | Users | 2769 | 6 (email = worst) | |
| 49 | + | AdminPanel.jsx | Per-user Chores modal | 3654 | 6 (crontab/description) | |
| 50 | + | ChoreSchedulesTab.jsx | Chore Definitions | 430 | 5 | |
| 51 | + | ChoreSchedulesTab.jsx | Schedules | 533 | **8 (widest in app)** | |
| 52 | + | ChoreHistoryTab.jsx | History | 91 | 5 | |
| 53 | + |
| 54 | +4. **Dialogs never go full-screen.** The `maxWidth="md"` **per-user Chores modal** |
| 55 | + ([AdminPanel.jsx:~3630](../../client/src/components/AdminPanel.jsx)) wrapping the 6-col |
| 56 | + table is the worst; also the 6 AdminPanel confirm dialogs, the 4 ChoreSchedulesTab dialogs, |
| 57 | + and the shared keypad/icon modals. |
| 58 | +5. **A few non-stacking flex rows / hard widths** — e.g. the Prize edit row |
| 59 | + ([AdminPanel.jsx:~3048-3060](../../client/src/components/AdminPanel.jsx)) with a fixed |
| 60 | + `width: 120` field, and hard `width: 120/140` fields elsewhere. |
| 61 | + |
| 62 | +## Chosen approach |
| 63 | + |
| 64 | +**Scope:** comprehensive — AdminPanel shell + embedded `ChoreSchedulesTab` / |
| 65 | +`ChoreHistoryTab` + shared modals (`PinModal`, `ClamValueModal`, `TabIconModal`). |
| 66 | +**Tables:** full card reflow on mobile (not just horizontal scroll). |
| 67 | + |
| 68 | +The strategy is two small reusable primitives plus three repeated patterns, so the change |
| 69 | +is uniform and low-risk rather than a per-screen rewrite. |
| 70 | + |
| 71 | +### Reusable primitives (build once) |
| 72 | + |
| 73 | +1. **`client/src/hooks/useIsMobile.js`** |
| 74 | + ```js |
| 75 | + import useMediaQuery from '@mui/material/useMediaQuery'; |
| 76 | + // Matches MUI's `sm` breakpoint (600px) without needing a ThemeProvider. |
| 77 | + export default function useIsMobile() { |
| 78 | + return useMediaQuery('(max-width:599.95px)'); |
| 79 | + } |
| 80 | + ``` |
| 81 | + One source of truth for the mobile cutoff, imported by `app.jsx`, `AdminPanel.jsx`, the |
| 82 | + embedded tab components, and the shared modals. |
| 83 | + |
| 84 | +2. **`client/src/utils/responsiveTable.js`** — a `stackableTableSx` object implementing the |
| 85 | + CSS "stacked card" pattern, spread into each `<Table sx={{ ...stackableTableSx }}>`: |
| 86 | + ```js |
| 87 | + export const stackableTableSx = { |
| 88 | + '@media (max-width:599.95px)': { |
| 89 | + '& thead': { display: 'none' }, |
| 90 | + '& tr': { |
| 91 | + display: 'block', |
| 92 | + mb: 1.5, |
| 93 | + border: '1px solid var(--card-border)', |
| 94 | + borderRadius: 2, |
| 95 | + p: 1, |
| 96 | + }, |
| 97 | + '& td': { |
| 98 | + display: 'flex', |
| 99 | + justifyContent: 'space-between', |
| 100 | + alignItems: 'center', |
| 101 | + gap: 2, |
| 102 | + border: 0, |
| 103 | + py: 0.75, |
| 104 | + '&::before': { |
| 105 | + content: 'attr(data-label)', |
| 106 | + fontWeight: 600, |
| 107 | + color: 'var(--text-secondary)', |
| 108 | + marginRight: '12px', |
| 109 | + }, |
| 110 | + }, |
| 111 | + }, |
| 112 | + }; |
| 113 | + ``` |
| 114 | + **Why this over a data-driven `<ResponsiveTable>` component:** the existing tables carry a |
| 115 | + lot of custom inline behavior (inline edit state, avatar upload, clam-modal triggers, |
| 116 | + visibility toggles, next-occurrence calc). This CSS pattern keeps **all existing cell JSX |
| 117 | + intact** — the only per-table change is adding `data-label="<header>"` to each body |
| 118 | + `<TableCell>` (MUI forwards `data-*` to the `<td>`). It's far less risky than rewriting |
| 119 | + seven tables. If a cell has no sensible label (e.g. the avatar/actions cell), omit |
| 120 | + `data-label` and it renders without a prefix. |
| 121 | + |
| 122 | +### Pattern A — Dialogs full-screen on mobile |
| 123 | + |
| 124 | +In each component: `const isMobile = useIsMobile();` then `fullScreen={isMobile}` (keep |
| 125 | +existing `fullWidth`) on **every** `<Dialog>`. |
| 126 | +- **Outer Admin dialog** ([app.jsx:~996](../../client/src/app.jsx)): add `fullScreen={isMobile}` |
| 127 | + + `fullWidth`; make `DialogContent` padding responsive (`p: { xs: 1.5, sm: 3 }`); prevent the |
| 128 | + absolute close button from overlapping by giving the AdminPanel title row |
| 129 | + `pr: { xs: 5, sm: 0 }` — or, for polish, render the close button in a small sticky top bar |
| 130 | + when `fullScreen`. |
| 131 | +- **AdminPanel.jsx**: the 6 dialogs (delete-tab, copy-device, rename-device, delete-device, |
| 132 | + delete-user, and the `md` per-user Chores modal ~3630). |
| 133 | +- **ChoreSchedulesTab.jsx**: the 4 dialogs (Chore, Delete-chore, Schedule form, Delete-schedule). |
| 134 | +- **Shared modals**: `PinModal.jsx`, `ClamValueModal.jsx`, `TabIconModal.jsx` — the keypads and |
| 135 | + icon grid benefit most; let `fullScreen` override their glass `slotProps.paper` borders on mobile. |
| 136 | + |
| 137 | +### Pattern B — Scrollable tabs |
| 138 | + |
| 139 | +Add `variant="scrollable" scrollButtons="auto" allowScrollButtonsMobile` to the main Admin |
| 140 | +tabs ([AdminPanel.jsx:~1782](../../client/src/components/AdminPanel.jsx)) and the nested |
| 141 | +sub-tab bars (~1793, ~2981). |
| 142 | + |
| 143 | +### Pattern C — Tables reflow to cards |
| 144 | + |
| 145 | +For each of the 7 tables in the table above: spread `stackableTableSx` into its `<Table>` and |
| 146 | +add `data-label="<header>"` to every body `<TableCell>`. Drop now-pointless fixed cell widths |
| 147 | +(`width={60}`, `width={120}`). |
| 148 | + |
| 149 | +### Targeted fixes |
| 150 | + |
| 151 | +- Prize edit row ([AdminPanel.jsx:~3048-3060](../../client/src/components/AdminPanel.jsx)): |
| 152 | + `flexDirection: { xs: 'column', sm: 'row' }` and Clam-Cost field `width: { xs: '100%', sm: 120 }`. |
| 153 | +- Hard-width fields (`width: 120/140`, e.g. clam value ~671) → `width: { xs: '100%', sm: N }`. |
| 154 | +- `SoundPicker.jsx` inline select `minWidth: 160` → `minWidth: { xs: 120, sm: 160 }` |
| 155 | + ([SoundPicker.jsx:~119](../../client/src/components/SoundPicker.jsx)). |
| 156 | + |
| 157 | +## Implementation checklist |
| 158 | + |
| 159 | +- [ ] Add `hooks/useIsMobile.js` and `utils/responsiveTable.js`. |
| 160 | +- [ ] `app.jsx`: outer dialog `fullScreen`/`fullWidth`, responsive `DialogContent` padding, |
| 161 | + fix close-button overlap. |
| 162 | +- [ ] AdminPanel.jsx: scrollable main + sub tabs; `fullScreen` on all 6 dialogs; `stackableTableSx` |
| 163 | + + `data-label` on all 4 tables; Prize-row + hard-width fixes. |
| 164 | +- [ ] ChoreSchedulesTab.jsx: `fullScreen` on all 4 dialogs; card reflow on both tables. |
| 165 | +- [ ] ChoreHistoryTab.jsx: card reflow on the history table. |
| 166 | +- [ ] PinModal / ClamValueModal / TabIconModal: `fullScreen` on mobile. |
| 167 | +- [ ] SoundPicker: responsive `minWidth`. |
| 168 | + |
| 169 | +## Verification |
| 170 | + |
| 171 | +1. `cd client && npm run build` (compile) and `npm test` (existing suites stay green; |
| 172 | + optionally add a `useIsMobile` unit test that mocks `window.matchMedia`). |
| 173 | +2. **Manual responsive pass — the real proof.** Run the app, open the ⚙️ Admin Panel, and in |
| 174 | + browser devtools device mode at **360px** walk every tab: |
| 175 | + - outer dialog is full-screen edge-to-edge; close button clears the title; |
| 176 | + - main + sub tab bars scroll horizontally; |
| 177 | + - each table renders as stacked labelled cards, with all buttons/toggles/inline-edits working; |
| 178 | + - every dialog (confirmations, per-user Chores, Schedule editor, PIN pad, Clam pad, Tab icon) |
| 179 | + fills the screen and is operable. |
| 180 | +3. **Regression check:** at ≥600px everything is pixel-identical to today. |
| 181 | +4. Verify light **and** dark themes; confirm touch targets stay comfortable (touch-first app). |
| 182 | + |
| 183 | +## Notes / open questions for the implementer |
| 184 | + |
| 185 | +- The **real validation is visual** at narrow width — build/tests can't confirm "looks right |
| 186 | + on a phone." Budget time for the devtools walk-through in both themes. |
| 187 | +- If a future refactor wants a proper data-driven `<ResponsiveTable>` component instead of the |
| 188 | + CSS pattern, that's a larger but cleaner option — deferred here to avoid destabilizing the |
| 189 | + tables' existing inline behavior. |
| 190 | +- No `ThemeProvider` is introduced; if one is ever added for theming, `useIsMobile` can switch |
| 191 | + to `useMediaQuery(theme.breakpoints.down('sm'))` for consistency. |
0 commit comments