From 1b76858c304d8cb35a7dc38eb5bc0f873124c0d0 Mon Sep 17 00:00:00 2001 From: Thiru-tenext Date: Sat, 6 Jun 2026 13:56:00 +0530 Subject: [PATCH] updated for the mobile view --- src/components/PageHeader.jsx | 2 +- src/pages/Dashboard.jsx | 24 +- src/pages/Settings.jsx | 13 +- src/pages/team/TeamUsers.jsx | 125 +++++--- src/pages/team/UserFormDialog.jsx | 7 +- src/pages/tenants/ClientFormDialog.jsx | 7 +- src/pages/tenants/Tenants.jsx | 418 ++++++++++++++----------- 7 files changed, 368 insertions(+), 228 deletions(-) diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx index 2cc3a61..0a616e9 100644 --- a/src/components/PageHeader.jsx +++ b/src/components/PageHeader.jsx @@ -36,7 +36,7 @@ export default function PageHeader({ title, breadcrumbs = [], action }) { )} - {action && {action}} + {action && {action}} ); } diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 3c95b0c..3446a83 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,8 +1,9 @@ import { useState, useEffect, useMemo } from 'react'; import { Grid, Card, Box, Stack, Typography, Button, Divider, LinearProgress, CircularProgress, Alert, - Table, TableBody, TableCell, TableHead, TableRow, TableContainer + Table, TableBody, TableCell, TableHead, TableRow, TableContainer, useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; import RefreshIcon from '@mui/icons-material/Refresh'; import ApartmentOutlinedIcon from '@mui/icons-material/ApartmentOutlined'; import FiberNewOutlinedIcon from '@mui/icons-material/FiberNewOutlined'; @@ -68,7 +69,7 @@ function Panel({ icon: Icon, title, action, color = 'primary', noPadding = false `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, transparent 100%)` }} > @@ -78,12 +79,14 @@ function Panel({ icon: Icon, title, action, color = 'primary', noPadding = false {title} {action} - {children} + {children} ); } export default function Dashboard() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const [clients, setClients] = useState([]); const [team, setTeam] = useState([]); const [loading, setLoading] = useState(true); @@ -161,6 +164,21 @@ export default function Dashboard() { {recent.length === 0 ? ( + ) : isMobile ? ( + }> + {recent.map((c) => ( + + + + {c.name} + + {[titleCase(c.businessType), c.city].filter(Boolean).join(' · ') || '—'} · {c.parcelVolume.toLocaleString('en-IN')} parcels + + + + + ))} + ) : ( diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx index c5253f5..c325fe0 100644 --- a/src/pages/Settings.jsx +++ b/src/pages/Settings.jsx @@ -71,7 +71,7 @@ function Section({ icon: Icon, title, subtitle, color = 'primary', danger = fals `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, ${theme.palette.background.paper} 72%)` }} > @@ -83,7 +83,7 @@ function Section({ icon: Icon, title, subtitle, color = 'primary', danger = fals {subtitle && {subtitle}} - {children} + {children} ); } @@ -155,10 +155,13 @@ export default function Settings() { title="Settings" breadcrumbs={[{ label: 'Settings' }]} action={ - + {dirty && } - - + + } /> diff --git a/src/pages/team/TeamUsers.jsx b/src/pages/team/TeamUsers.jsx index 1f23716..dc3e36d 100644 --- a/src/pages/team/TeamUsers.jsx +++ b/src/pages/team/TeamUsers.jsx @@ -2,9 +2,10 @@ import { useState, useMemo, useEffect } from 'react'; import { Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, Chip, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, - TablePagination, Typography, CircularProgress, Alert, Tooltip, + TablePagination, Typography, CircularProgress, Alert, Tooltip, Divider, useMediaQuery, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; import SearchIcon from '@mui/icons-material/Search'; import AddIcon from '@mui/icons-material/Add'; import RefreshIcon from '@mui/icons-material/Refresh'; @@ -94,7 +95,51 @@ function RoleCell({ role }) { ); } +// Mobile presentation of a user row — a self-contained card instead of a wide table row. +function UserCard({ row, index, onEdit, onDelete }) { + return ( + + + + + + + + + + + + {row.email ? ( + + {row.email} + + ) : No email} + + + + {row.phone ? ( + + {row.phone} + + ) : No phone} + + + + + + #{index} + + + + + + + ); +} + export default function TeamUsers() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -167,9 +212,9 @@ export default function TeamUsers() { title="App Users" breadcrumbs={[{ label: 'App Users' }]} action={ - - - + + + } /> @@ -177,7 +222,7 @@ export default function TeamUsers() { `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)` }} @@ -191,10 +236,10 @@ export default function TeamUsers() { - + { setSearch(e.target.value); setPage(0); }} - sx={{ minWidth: 300 }} + sx={{ width: { xs: '100%', md: 300 } }} InputProps={{ startAdornment: }} /> @@ -218,33 +263,37 @@ export default function TeamUsers() { {error && Retry}>{error}} - -
- - - S.No - User - Email - Phone - Role - Actions - - - - {loading ? ( - - - - + {loading ? ( + + ) : paged.length === 0 ? ( + + ) : isMobile ? ( + + {paged.map((row, i) => ( + setDialog({ open: true, mode: 'edit', initial: r })} + onDelete={(r) => setToDelete(r)} + /> + ))} + + ) : ( + +
+ + + S.No + User + Email + Phone + Role + Actions - ) : paged.length === 0 ? ( - - - - - - ) : ( - paged.map((row, i) => ( + + + {paged.map((row, i) => ( {page * rpp + i + 1} @@ -268,11 +317,11 @@ export default function TeamUsers() { setToDelete(row)}> - )) - )} - -
-
+ ))} + + + + )} setPage(p)} rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]} diff --git a/src/pages/team/UserFormDialog.jsx b/src/pages/team/UserFormDialog.jsx index 67bc237..bad3693 100644 --- a/src/pages/team/UserFormDialog.jsx +++ b/src/pages/team/UserFormDialog.jsx @@ -1,8 +1,9 @@ import { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Grid, TextField, - MenuItem, Alert, CircularProgress, IconButton + MenuItem, Alert, CircularProgress, IconButton, useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; import CloseIcon from '@mui/icons-material/Close'; import { createPoint, setPayload, COLLECTIONS } from '@/utils/qdrant'; @@ -14,6 +15,8 @@ const withValue = (opts, v) => (v && !opts.includes(v) ? [v, ...opts] : opts); export default function UserFormDialog({ open, mode, initial, onClose, onSaved }) { const isEdit = mode === 'edit'; + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const [form, setForm] = useState(EMPTY); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); @@ -56,7 +59,7 @@ export default function UserFormDialog({ open, mode, initial, onClose, onSaved } }; return ( - + {isEdit ? 'Edit Team User' : 'Add Team User'} diff --git a/src/pages/tenants/ClientFormDialog.jsx b/src/pages/tenants/ClientFormDialog.jsx index 7fb497c..e69e1c6 100644 --- a/src/pages/tenants/ClientFormDialog.jsx +++ b/src/pages/tenants/ClientFormDialog.jsx @@ -1,8 +1,9 @@ import { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Grid, TextField, - MenuItem, Box, Typography, Divider, Alert, CircularProgress, IconButton + MenuItem, Box, Typography, Divider, Alert, CircularProgress, IconButton, useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; import CloseIcon from '@mui/icons-material/Close'; import { createPoint, setPayload, COLLECTIONS } from '@/utils/qdrant'; @@ -23,6 +24,8 @@ const withValue = (opts, v) => (v && !opts.includes(v) ? [v, ...opts] : opts); export default function ClientFormDialog({ open, mode, initial, onClose, onSaved }) { const isEdit = mode === 'edit'; + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const [form, setForm] = useState(EMPTY); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); @@ -89,7 +92,7 @@ export default function ClientFormDialog({ open, mode, initial, onClose, onSaved }; return ( - + {isEdit ? 'Edit Client' : 'Add Client'} diff --git a/src/pages/tenants/Tenants.jsx b/src/pages/tenants/Tenants.jsx index da703c4..2e72084 100644 --- a/src/pages/tenants/Tenants.jsx +++ b/src/pages/tenants/Tenants.jsx @@ -4,8 +4,9 @@ import { Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, Grid, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, TablePagination, Typography, Collapse, CircularProgress, Alert, Tooltip, Chip, Divider, Link, - Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions + useMediaQuery, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; import SearchIcon from '@mui/icons-material/Search'; import AddIcon from '@mui/icons-material/Add'; import RefreshIcon from '@mui/icons-material/Refresh'; @@ -180,6 +181,202 @@ function Metric({ label, value, color = 'grey.800' }) { ); } +// The expandable detail panel — shared by the desktop table row and the mobile card. +function ClientDetail({ row, onEdit }) { + return ( + + `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)` + }} + > + + + + {row.name} + + + {row.clientId} + + + + + + + + + + + + + + + + + {row.clientId} + + + + + + + + + + {row.logisticsSegment ? ( + + {String(row.logisticsSegment).split(/[,/]/).map((seg) => seg.trim()).filter(Boolean).map((seg) => ( + + ))} + + ) : } + + + + + {(() => { + const stops = [row.transitFrom, ...String(row.transitTo || '').split(',')] + .map((s) => s.trim()).filter(Boolean); + if (!stops.length) return ; + return ( + + {stops.map((stop, idx) => ( + + {idx > 0 && } + + + ))} + + ); + })()} + + + + + + + + {[row.city, row.businessState].filter(Boolean).join(', ') || '—'} + + + + + + {row.surveyAddress || '—'} + + + + {row.surveyLat && row.surveyLng ? ( + + + + + + {[row.neighbourhood, row.city].filter(Boolean).join(', ') || 'Pinned location'} + + + + + + ) : } + + + + + + + + + + + {row.notes && ( + + + + + Notes + + {row.notes} + + + )} + + + + + + + ); +} + +// Mobile presentation of a client — summary card with an expandable detail panel. +function ClientCard({ row, onEdit, onDelete }) { + const [open, setOpen] = useState(false); + return ( + + + + + + {row.name} + {row.logicalId} + + + + + + + + {row.phone || '—'} + + + + {row.city || '—'}{row.businessState ? `, ${row.businessState}` : ''} + + + + } sx={{ mt: 1.75 }}> + + + + + + + + + + + + + + + + + + + + ); +} + function ClientRow({ row, index, onEdit, onDelete }) { const [open, setOpen] = useState(false); @@ -254,145 +451,7 @@ function ClientRow({ row, index, onEdit, onDelete }) { - - `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)` - }} - > - - - - {row.name} - - - {row.clientId} - - - - - - - - - - - - - - - - - {row.clientId} - - - - - - - - - - {row.logisticsSegment ? ( - - {String(row.logisticsSegment).split(/[,/]/).map((seg) => seg.trim()).filter(Boolean).map((seg) => ( - - ))} - - ) : } - - - - - {(() => { - const stops = [row.transitFrom, ...String(row.transitTo || '').split(',')] - .map((s) => s.trim()).filter(Boolean); - if (!stops.length) return ; - return ( - - {stops.map((stop, idx) => ( - - {idx > 0 && } - - - ))} - - ); - })()} - - - - - - - - {[row.city, row.businessState].filter(Boolean).join(', ') || '—'} - - - - - - {row.surveyAddress || '—'} - - - - {row.surveyLat && row.surveyLng ? ( - - - - - - {[row.neighbourhood, row.city].filter(Boolean).join(', ') || 'Pinned location'} - - - - - - ) : } - - - - - - - - - - - {row.notes && ( - - - - - Notes - - {row.notes} - - - )} - - - - - - + @@ -401,6 +460,8 @@ function ClientRow({ row, index, onEdit, onDelete }) { } export default function Tenants() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [clients, setClients] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -489,9 +550,9 @@ export default function Tenants() { title="Clients" breadcrumbs={[{ label: 'Clients' }]} action={ - - - + + + } /> @@ -523,7 +584,7 @@ export default function Tenants() { { setSearch(e.target.value); setPage(0); }} - sx={{ minWidth: 300 }} + sx={{ width: { xs: '100%', md: 300 } }} InputProps={{ startAdornment: }} /> @@ -547,35 +608,38 @@ export default function Tenants() { {error && Retry}>{error}} - - - - - - ID - Client - Contact - Location - Volume - Status - Actions - - - - {loading ? ( - - - - + {loading ? ( + + ) : paged.length === 0 ? ( + + ) : isMobile ? ( + + {paged.map((row) => ( + setDialog({ open: true, mode: 'edit', initial: r })} + onDelete={(r) => setToDelete(r)} + /> + ))} + + ) : ( + +
+ + + + ID + Client + Contact + Location + Volume + Status + Actions - ) : paged.length === 0 ? ( - - - - - - ) : ( - paged.map((row, i) => ( + + + {paged.map((row, i) => ( setDialog({ open: true, mode: 'edit', initial: r })} onDelete={(r) => setToDelete(r)} /> - )) - )} - -
-
+ ))} + + + + )} setPage(p)} rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]}