Skip to content

Commit 2c66f03

Browse files
committed
plan to implement mobile friendly admin panel design
1 parent 6417691 commit 2c66f03

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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

Comments
 (0)