Skip to content

Commit b907a4f

Browse files
committed
Implementing issue 99 for moble friendly phase 1 with groundwork for phase 2 started.
1 parent dea581b commit b907a4f

11 files changed

Lines changed: 164 additions & 69 deletions

client/src/app.jsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { API_BASE_URL } from './utils/apiConfig.js';
1212
import { getDeviceApiBase } from './utils/deviceName.js';
1313
import { unlockAudio } from './utils/choreSound.js';
1414
import useChoreSoundScheduler from './hooks/useChoreSoundScheduler.js';
15+
import useIsMobile from './hooks/useIsMobile.js';
1516
import './index.css';
1617

1718
const loadAdminPanel = () => import('./components/AdminPanel.jsx');
@@ -218,6 +219,7 @@ const WidgetLoadingFallback = ({ label }) => (
218219

219220
const App = () => {
220221
const API_DEVICE_URL = getDeviceApiBase(API_BASE_URL);
222+
const isMobile = useIsMobile();
221223
const [theme, setTheme] = useState(readLocalTheme);
222224
const [themeMode, setThemeMode] = useState(() => readLocalThemeMode(readLocalTheme()));
223225
const [autoDarkModeSettings, setAutoDarkModeSettings] = useState(readLocalAutoDarkModeSettings);
@@ -993,8 +995,8 @@ const App = () => {
993995
)}
994996
</Box>
995997

996-
<Dialog open={showAdminPanel} onClose={toggleAdminPanel} maxWidth="lg">
997-
<DialogContent sx={{ position: 'relative' }}>
998+
<Dialog open={showAdminPanel} onClose={toggleAdminPanel} maxWidth="lg" fullScreen={isMobile}>
999+
<DialogContent sx={{ position: 'relative', '@media (max-width:599.95px)': { p: 1.5 } }}>
9981000
<IconButton
9991001
onClick={toggleAdminPanel}
10001002
sx={{
@@ -1023,7 +1025,10 @@ const App = () => {
10231025
</DialogContent>
10241026
</Dialog>
10251027

1026-
{/* Floating Dock TabBar */}
1028+
{/* Floating Dock TabBar. The dock renders above MUI dialogs, so hide it
1029+
while the Admin Panel is open full-screen on mobile — otherwise it
1030+
covers the bottom action buttons of the panel's dialogs. */}
1031+
{!(isMobile && showAdminPanel) && (
10271032
<TabBar
10281033
tabs={tabs}
10291034
activeTab={activeTab}
@@ -1046,6 +1051,7 @@ const App = () => {
10461051
/>
10471052
}
10481053
/>
1054+
)}
10491055

10501056
<Suspense fallback={null}>
10511057
<TabIconModal

client/src/components/AdminPanel.jsx

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ import TabIconModal from './TabIconModal';
7575
import GoogleAccountConnection from './GoogleAccountConnection';
7676
import ClamValueModal from './ClamValueModal';
7777
import SoundPicker from './SoundPicker';
78+
import useIsMobile from '../hooks/useIsMobile.js';
79+
import { stackableTableSx } from '../utils/responsiveTable.js';
7880

7981
const USERS_UPDATED_EVENT = 'homeglow:users-updated';
8082
const DEVICE_SETTINGS_UPDATED_EVENT = 'homeglow:device-settings-updated';
@@ -204,6 +206,7 @@ const buildTagUrl = (repository, tagName) => {
204206
const toShortCommit = (commitSha) => (commitSha ? commitSha.slice(0, 7) : 'Unknown');
205207

206208
const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
209+
const isMobile = useIsMobile();
207210
const [currentDeviceName, setCurrentDeviceName] = useState(() => getDeviceName());
208211
const API_DEVICE_URL = getDeviceApiBase(API_BASE_URL);
209212
const CORE_WIDGET_DEFAULT_SIZES = {
@@ -1775,11 +1778,18 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
17751778

17761779
return (
17771780
<Box sx={{ width: '100%', maxWidth: 1200, mx: 'auto' }}>
1778-
<Typography variant="h4" gutterBottom>
1781+
<Typography variant="h4" gutterBottom sx={{ pr: { xs: 5, sm: 0 } }}>
17791782
⚙️ Admin Panel
17801783
</Typography>
17811784

1782-
<Tabs value={activeTab} onChange={(e, newValue) => setActiveTab(newValue)} sx={{ mb: 3 }}>
1785+
<Tabs
1786+
value={activeTab}
1787+
onChange={(e, newValue) => setActiveTab(newValue)}
1788+
variant="scrollable"
1789+
scrollButtons="auto"
1790+
allowScrollButtonsMobile
1791+
sx={{ mb: 3 }}
1792+
>
17831793
{adminTabs.map((tab, index) => (
17841794
<Tab key={tab} label={tab} />
17851795
))}
@@ -1790,7 +1800,14 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
17901800
<Card>
17911801
<CardContent>
17921802
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
1793-
<Tabs value={widgetsSubTab} onChange={(_, v) => setWidgetsSubTab(v)} size="small">
1803+
<Tabs
1804+
value={widgetsSubTab}
1805+
onChange={(_, v) => setWidgetsSubTab(v)}
1806+
size="small"
1807+
variant="scrollable"
1808+
scrollButtons="auto"
1809+
allowScrollButtonsMobile
1810+
>
17941811
<Tab label="Widgets" />
17951812
<Tab label="Plugins" />
17961813
<Tab label="Tabs" />
@@ -2224,7 +2241,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
22242241
</Box>
22252242

22262243
<TableContainer component={Paper}>
2227-
<Table>
2244+
<Table sx={stackableTableSx}>
22282245
<TableHead>
22292246
<TableRow>
22302247
<TableCell width={60}>Order</TableCell>
@@ -2257,22 +2274,22 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
22572274
opacity: draggingTabNumber === tab.number ? 0.65 : 1,
22582275
}}
22592276
>
2260-
<TableCell>
2277+
<TableCell data-label="Order">
22612278
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
22622279
{!isHome && <DragIndicator fontSize="small" />}
22632280
<Chip label={tab.number} size="small" />
22642281
</Box>
22652282
</TableCell>
2266-
<TableCell>
2283+
<TableCell data-label="Label">
22672284
{tab.label}
22682285
{isHome && (
22692286
<Chip size="small" label="Home" color="primary" sx={{ ml: 1 }} />
22702287
)}
22712288
</TableCell>
2272-
<TableCell>
2289+
<TableCell data-label="Icon">
22732290
<Chip size="small" label={tab.icon} />
22742291
</TableCell>
2275-
<TableCell>
2292+
<TableCell data-label="Show Label">
22762293
<Switch
22772294
checked={Boolean(tab.show_label)}
22782295
onClick={(e) => e.stopPropagation()}
@@ -2327,7 +2344,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
23272344
</Box>
23282345

23292346
<TableContainer component={Paper}>
2330-
<Table>
2347+
<Table sx={stackableTableSx}>
23312348
<TableHead>
23322349
<TableRow>
23332350
<TableCell>Name</TableCell>
@@ -2341,18 +2358,18 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
23412358
const isCurrent = device.name === currentDeviceName;
23422359
return (
23432360
<TableRow key={device.name}>
2344-
<TableCell>
2361+
<TableCell data-label="Name">
23452362
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
23462363
{isCurrent && <Chip label="Current" color="primary" size="small" />}
23472364
<Typography variant="body2" sx={{ fontFamily: 'monospace' }}>
23482365
{device.name}
23492366
</Typography>
23502367
</Box>
23512368
</TableCell>
2352-
<TableCell>
2369+
<TableCell data-label="Last Updated">
23532370
{device.updateTime ? new Date(device.updateTime).toLocaleString() : 'Unknown'}
23542371
</TableCell>
2355-
<TableCell>
2372+
<TableCell data-label="Widgets">
23562373
<Chip label={Number(device.widgets) || 0} size="small" />
23572374
</TableCell>
23582375
<TableCell>
@@ -2767,7 +2784,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
27672784
</Box>
27682785

27692786
<TableContainer component={Paper}>
2770-
<Table>
2787+
<Table sx={stackableTableSx}>
27712788
<TableHead>
27722789
<TableRow>
27732790
<TableCell>Avatar</TableCell>
@@ -2799,7 +2816,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
27992816
</Button>
28002817
</Box>
28012818
</TableCell>
2802-
<TableCell>
2819+
<TableCell data-label="Username">
28032820
{editingUser?.id === user.id ? (
28042821
<TextField
28052822
value={editingUser.username}
@@ -2810,7 +2827,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
28102827
user.username
28112828
)}
28122829
</TableCell>
2813-
<TableCell>
2830+
<TableCell data-label="Email" sx={{ '@media (max-width:599.95px)': { wordBreak: 'break-all' } }}>
28142831
{editingUser?.id === user.id ? (
28152832
<TextField
28162833
value={editingUser.email}
@@ -2821,7 +2838,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
28212838
user.email
28222839
)}
28232840
</TableCell>
2824-
<TableCell>
2841+
<TableCell data-label="Clam Total">
28252842
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
28262843
<Chip
28272844
label={`${user.clam_total || 0} 🥟`}
@@ -2839,7 +2856,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
28392856
</Tooltip>
28402857
</Box>
28412858
</TableCell>
2842-
<TableCell>
2859+
<TableCell data-label="Chores">
28432860
<Button
28442861
variant="outlined"
28452862
size="small"
@@ -2978,7 +2995,14 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
29782995
</Box>
29792996

29802997
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
2981-
<Tabs value={choresSubTab} onChange={(_, v) => setChoresSubTab(v)} size="small">
2998+
<Tabs
2999+
value={choresSubTab}
3000+
onChange={(_, v) => setChoresSubTab(v)}
3001+
size="small"
3002+
variant="scrollable"
3003+
scrollButtons="auto"
3004+
allowScrollButtonsMobile
3005+
>
29823006
<Tab label="Chores" />
29833007
<Tab label="History" />
29843008
</Tabs>
@@ -3045,7 +3069,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
30453069
{prizes.map((prize) => (
30463070
<ListItem key={prize.id} sx={{ border: '1px solid var(--card-border)', borderRadius: 1, mb: 1 }}>
30473071
{editingPrize?.id === prize.id ? (
3048-
<Box sx={{ display: 'flex', gap: 2, width: '100%', alignItems: 'center' }}>
3072+
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 2, width: '100%', alignItems: { xs: 'stretch', sm: 'center' } }}>
30493073
<TextField
30503074
label="Prize Name"
30513075
value={editingPrize.name}
@@ -3057,7 +3081,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
30573081
type="number"
30583082
value={editingPrize.clam_cost}
30593083
onChange={(e) => setEditingPrize({ ...editingPrize, clam_cost: parseInt(e.target.value) || 0 })}
3060-
sx={{ width: 120 }}
3084+
sx={{ width: { xs: '100%', sm: 120 } }}
30613085
/>
30623086
<IconButton onClick={savePrize} color="primary">
30633087
<Save />
@@ -3459,6 +3483,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
34593483
onClose={() => setDeleteTabDialog({ open: false, tab: null })}
34603484
maxWidth="sm"
34613485
fullWidth
3486+
fullScreen={isMobile}
34623487
>
34633488
<DialogTitle>
34643489
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3489,6 +3514,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
34893514
onClose={() => setCopyDeviceDialog({ open: false, device: null })}
34903515
maxWidth="sm"
34913516
fullWidth
3517+
fullScreen={isMobile}
34923518
>
34933519
<DialogTitle>
34943520
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3525,6 +3551,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
35253551
onClose={() => setRenameDeviceDialog({ open: false, currentName: '', newName: '', error: '' })}
35263552
maxWidth="sm"
35273553
fullWidth
3554+
fullScreen={isMobile}
35283555
>
35293556
<DialogTitle>
35303557
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3559,6 +3586,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
35593586
onClose={() => setDeleteDeviceDialog({ open: false, device: null })}
35603587
maxWidth="sm"
35613588
fullWidth
3589+
fullScreen={isMobile}
35623590
>
35633591
<DialogTitle>
35643592
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3590,6 +3618,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
35903618
onClose={() => setDeleteUserDialog({ open: false, user: null })}
35913619
maxWidth="sm"
35923620
fullWidth
3621+
fullScreen={isMobile}
35933622
>
35943623
<DialogTitle>
35953624
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3632,6 +3661,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
36323661
onClose={closeChoreModal}
36333662
maxWidth="md"
36343663
fullWidth
3664+
fullScreen={isMobile}
36353665
>
36363666
<DialogTitle>
36373667
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -3652,7 +3682,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
36523682
</Typography>
36533683
) : (
36543684
<TableContainer component={Paper} sx={{ mt: 1 }}>
3655-
<Table>
3685+
<Table sx={stackableTableSx}>
36563686
<TableHead>
36573687
<TableRow>
36583688
<TableCell>Title</TableCell>
@@ -3666,29 +3696,29 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
36663696
<TableBody>
36673697
{choreModal.userChores.map((chore) => (
36683698
<TableRow key={chore.id}>
3669-
<TableCell>
3699+
<TableCell data-label="Title">
36703700
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>
36713701
{chore.title}
36723702
</Typography>
36733703
</TableCell>
3674-
<TableCell>
3704+
<TableCell data-label="Description">
36753705
<Typography variant="body2" color="text.secondary">
36763706
{chore.description || 'No description'}
36773707
</Typography>
36783708
</TableCell>
3679-
<TableCell>
3709+
<TableCell data-label="Schedule">
36803710
<Typography variant="body2" sx={{ fontFamily: 'monospace' }}>
36813711
{chore.crontab || 'One-time'}
36823712
</Typography>
36833713
</TableCell>
3684-
<TableCell>
3714+
<TableCell data-label="Visible">
36853715
<Chip
36863716
label={chore.visible ? 'Visible' : 'Hidden'}
36873717
color={chore.visible ? 'success' : 'default'}
36883718
size="small"
36893719
/>
36903720
</TableCell>
3691-
<TableCell>
3721+
<TableCell data-label="Clams">
36923722
{chore.clam_value > 0 ? (
36933723
<Chip
36943724
label={`${chore.clam_value} 🥟`}

client/src/components/ChoreHistoryTab.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { Delete, Refresh } from '@mui/icons-material';
1919
import axios from 'axios';
2020
import { API_BASE_URL } from '../utils/apiConfig.js';
21+
import { stackableTableSx } from '../utils/responsiveTable.js';
2122

2223
export default function ChoreHistoryTab() {
2324
const [history, setHistory] = useState([]);
@@ -89,7 +90,7 @@ export default function ChoreHistoryTab() {
8990
</Box>
9091
) : (
9192
<TableContainer component={Paper} variant="outlined">
92-
<Table size="small">
93+
<Table size="small" sx={stackableTableSx}>
9394
<TableHead>
9495
<TableRow>
9596
<TableCell sx={{ fontWeight: 600 }}>User</TableCell>
@@ -102,20 +103,20 @@ export default function ChoreHistoryTab() {
102103
<TableBody>
103104
{history.map((entry) => (
104105
<TableRow key={entry.id} hover>
105-
<TableCell>
106+
<TableCell data-label="User">
106107
<Typography variant="body2" sx={{ fontWeight: 500 }}>
107108
{entry.username || '—'}
108109
</Typography>
109110
</TableCell>
110-
<TableCell>
111+
<TableCell data-label="Title">
111112
<Typography variant="body2">
112113
{entry.title || <span style={{ color: 'rgba(0,0,0,0.4)', fontStyle: 'italic' }}>Unknown</span>}
113114
</Typography>
114115
</TableCell>
115-
<TableCell>
116+
<TableCell data-label="Date">
116117
<Typography variant="body2">{formatDate(entry.date)}</Typography>
117118
</TableCell>
118-
<TableCell>
119+
<TableCell data-label="Clams">
119120
<Chip
120121
label={`${entry.clam_value} 🥟`}
121122
size="small"

0 commit comments

Comments
 (0)