updates on the ui changes regarding the padding and margins on the all pages such as dashboard clients surveys pricing matrix and booking more
This commit is contained in:
@@ -41,7 +41,7 @@ function Panel({ icon: Icon, title, action, color = 'primary', noPadding = false
|
|||||||
<Stack
|
<Stack
|
||||||
direction="row" spacing={1.5} alignItems="center"
|
direction="row" spacing={1.5} alignItems="center"
|
||||||
sx={{
|
sx={{
|
||||||
px: { xs: 2, sm: 3 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
px: { xs: 2, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
background: (theme) => `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, transparent 100%)`
|
background: (theme) => `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, transparent 100%)`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -124,9 +124,7 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
{error && <Alert severity="error" sx={{ mb: 2.5 }} action={<Button color="inherit" size="small" onClick={load}>Retry</Button>}>{error}</Alert>}
|
{error && <Alert severity="error" sx={{ mb: 2.5 }} action={<Button color="inherit" size="small" onClick={load}>Retry</Button>}>{error}</Alert>}
|
||||||
|
|
||||||
|
<Grid container spacing={2.5}>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Total Clients" value={stats.total} icon={ApartmentOutlinedIcon} color="primary" caption="All registered" /></Grid>
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Total Clients" value={stats.total} icon={ApartmentOutlinedIcon} color="primary" caption="All registered" /></Grid>
|
||||||
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="New Clients" value={stats.newCount} icon={FiberNewOutlinedIcon} color="primary" caption="Awaiting onboarding" /></Grid>
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="New Clients" value={stats.newCount} icon={FiberNewOutlinedIcon} color="primary" caption="Awaiting onboarding" /></Grid>
|
||||||
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Total Parcel Volume" value={stats.parcels.toLocaleString('en-IN')} icon={Inventory2OutlinedIcon} color="primary" caption="Across all clients" /></Grid>
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Total Parcel Volume" value={stats.parcels.toLocaleString('en-IN')} icon={Inventory2OutlinedIcon} color="primary" caption="Across all clients" /></Grid>
|
||||||
@@ -200,7 +198,7 @@ export default function Dashboard() {
|
|||||||
{byType.length === 0 ? (
|
{byType.length === 0 ? (
|
||||||
<EmptyState title="No data" />
|
<EmptyState title="No data" />
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={2.25}>
|
<Stack spacing={2}>
|
||||||
{byType.map(([type, count], i) => (
|
{byType.map(([type, count], i) => (
|
||||||
<Box key={type}>
|
<Box key={type}>
|
||||||
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 0.75 }}>
|
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 0.75 }}>
|
||||||
@@ -231,7 +229,7 @@ export default function Dashboard() {
|
|||||||
) : (
|
) : (
|
||||||
<Stack divider={<Divider />} spacing={0}>
|
<Stack divider={<Divider />} spacing={0}>
|
||||||
{team.slice(0, 6).map((u) => (
|
{team.slice(0, 6).map((u) => (
|
||||||
<Stack key={u.id} direction="row" spacing={1.5} alignItems="center" sx={{ py: 1.25 }}>
|
<Stack key={u.id} direction="row" spacing={1.5} alignItems="center" sx={{ py: 1.5 }}>
|
||||||
<UserAvatar name={u.name} size={36} />
|
<UserAvatar name={u.name} size={36} />
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>{u.name}</Typography>
|
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>{u.name}</Typography>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function Section({ icon: Icon, title, subtitle, color = 'primary', danger = fals
|
|||||||
<Stack
|
<Stack
|
||||||
direction="row" spacing={1.75} alignItems="center"
|
direction="row" spacing={1.75} alignItems="center"
|
||||||
sx={{
|
sx={{
|
||||||
px: { xs: 2, sm: 3 }, py: 2.25, borderBottom: 1, borderColor: 'divider',
|
px: { xs: 2, sm: 3 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
background: (theme) => `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, ${theme.palette.background.paper} 72%)`
|
background: (theme) => `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, ${theme.palette.background.paper} 72%)`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -170,7 +170,7 @@ export default function Settings() {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
<Stack spacing={2.5} sx={{ position: { md: 'sticky' }, top: { md: 88 } }}>
|
<Stack spacing={2.5} sx={{ position: { md: 'sticky' }, top: { md: 88 } }}>
|
||||||
<Card sx={{ p: 1.5 }}>
|
<Card sx={{ p: 2 }}>
|
||||||
<Typography variant="overline" sx={{ px: 1, color: 'text.secondary', fontWeight: 700, letterSpacing: 0.6 }}>Preferences</Typography>
|
<Typography variant="overline" sx={{ px: 1, color: 'text.secondary', fontWeight: 700, letterSpacing: 0.6 }}>Preferences</Typography>
|
||||||
<Stack spacing={0.25} sx={{ mt: 0.5 }}>
|
<Stack spacing={0.25} sx={{ mt: 0.5 }}>
|
||||||
{NAV.map((item, i) => {
|
{NAV.map((item, i) => {
|
||||||
@@ -182,7 +182,7 @@ export default function Settings() {
|
|||||||
direction="row" spacing={1.5} alignItems="center"
|
direction="row" spacing={1.5} alignItems="center"
|
||||||
onClick={() => setTab(i)}
|
onClick={() => setTab(i)}
|
||||||
sx={{
|
sx={{
|
||||||
px: 1.5, py: 1.25, borderRadius: 2, cursor: 'pointer', position: 'relative',
|
px: 1.5, py: 1.5, borderRadius: 2, cursor: 'pointer', position: 'relative',
|
||||||
bgcolor: active ? 'primary.lighter' : 'transparent',
|
bgcolor: active ? 'primary.lighter' : 'transparent',
|
||||||
transition: 'background-color .15s',
|
transition: 'background-color .15s',
|
||||||
'&:hover': { bgcolor: active ? 'primary.lighter' : 'grey.50' },
|
'&:hover': { bgcolor: active ? 'primary.lighter' : 'grey.50' },
|
||||||
@@ -203,7 +203,7 @@ export default function Settings() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card sx={{ p: 2.5, bgcolor: 'primary.lighter', borderColor: 'primary.100' }}>
|
<Card sx={{ p: 2, bgcolor: 'primary.lighter', borderColor: 'primary.100' }}>
|
||||||
<Stack spacing={1.25}>
|
<Stack spacing={1.25}>
|
||||||
<Box sx={{ width: 38, height: 38, borderRadius: 2, bgcolor: 'primary.main', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<Box sx={{ width: 38, height: 38, borderRadius: 2, bgcolor: 'primary.main', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<HelpOutlineRoundedIcon fontSize="small" />
|
<HelpOutlineRoundedIcon fontSize="small" />
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ function BookingFormDialog({ open, onClose, onSave, clients, surveys }) {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{formData.parcels.map((parcel, idx) => (
|
{formData.parcels.map((parcel, idx) => (
|
||||||
<Grid container spacing={1.5} key={idx} sx={{ mb: 2, alignItems: 'center' }}>
|
<Grid container spacing={2} key={idx} sx={{ mb: 2, alignItems: 'center' }}>
|
||||||
<Grid item xs={12} sm={3}>
|
<Grid item xs={12} sm={3}>
|
||||||
<TextField fullWidth label="Category (e.g. Box)" value={parcel.itemcategory} onChange={handleParcelChange(idx, 'itemcategory')} size="small" />
|
<TextField fullWidth label="Category (e.g. Box)" value={parcel.itemcategory} onChange={handleParcelChange(idx, 'itemcategory')} size="small" />
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -334,7 +334,7 @@ function BookingFormDialog({ open, onClose, onSave, clients, surveys }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mt: 2, mb: 1 }}>
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 1 }}>
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: 'primary.main' }}>Price & Insurance</Typography>
|
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: 'primary.main' }}>Price & Insurance</Typography>
|
||||||
<Button size="small" variant="outlined" onClick={handleCheckPrice}>Check Price Estimate</Button>
|
<Button size="small" variant="outlined" onClick={handleCheckPrice}>Check Price Estimate</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -402,7 +402,7 @@ function BookingFormDialog({ open, onClose, onSave, clients, surveys }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2 }}>
|
<DialogActions sx={{ px: 3, py: 2 }}>
|
||||||
<Button onClick={onClose} disabled={saving}>Cancel</Button>
|
<Button onClick={onClose} disabled={saving}>Cancel</Button>
|
||||||
<Button variant="contained" onClick={handleSave} disabled={saving || !formData.customer_phone} sx={{ bgcolor: 'primary.main', '&:hover': { bgcolor: 'primary.dark' } }}>
|
<Button variant="contained" onClick={handleSave} disabled={saving || !formData.customer_phone} sx={{ bgcolor: 'primary.main', '&:hover': { bgcolor: 'primary.dark' } }}>
|
||||||
{saving ? 'Creating...' : 'Create Booking'}
|
{saving ? 'Creating...' : 'Create Booking'}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined
|
|||||||
import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
|
import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||||
|
import PriceCheckOutlinedIcon from '@mui/icons-material/PriceCheckOutlined';
|
||||||
import PageHeader from '@/components/PageHeader';
|
import PageHeader from '@/components/PageHeader';
|
||||||
import { fetchUsers } from '@/utils/apiClient';
|
import { fetchUsers } from '@/utils/apiClient';
|
||||||
|
|
||||||
@@ -197,7 +198,7 @@ function PricingFormDialog({ open, onClose, onSave, initialData, isCell = false,
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2, bgcolor: 'grey.50' }}>
|
<DialogActions sx={{ px: 3, py: 2 }}>
|
||||||
<Button onClick={onClose} disabled={saving} color="inherit">Cancel</Button>
|
<Button onClick={onClose} disabled={saving} color="inherit">Cancel</Button>
|
||||||
<Button onClick={handleSave} disabled={saving} variant="contained">
|
<Button onClick={handleSave} disabled={saving} variant="contained">
|
||||||
{saving ? 'Saving...' : 'Save Rate'}
|
{saving ? 'Saving...' : 'Save Rate'}
|
||||||
@@ -313,7 +314,23 @@ export default function Pricing() {
|
|||||||
|
|
||||||
{/* Control Panel */}
|
{/* Control Panel */}
|
||||||
<Card sx={{ overflow: 'hidden' }}>
|
<Card sx={{ overflow: 'hidden' }}>
|
||||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: { xs: 2, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
|
display: 'flex', alignItems: 'center', gap: 1.5,
|
||||||
|
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)`
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: 40, height: 40, borderRadius: 2, bgcolor: 'primary.lighter', color: 'primary.main', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<PriceCheckOutlinedIcon fontSize="small" />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: 'grey.800', lineHeight: 1.2 }}>Pricing Matrix</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">Manage carrier rates by weight slab and zone</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Search weight slabs…"
|
placeholder="Search weight slabs…"
|
||||||
@@ -418,8 +435,8 @@ export default function Pricing() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Collapse in={locationsOpen} timeout="auto" unmountOnExit>
|
<Collapse in={locationsOpen} timeout="auto" unmountOnExit>
|
||||||
<Box sx={{ p: 2, pt: 0, borderTop: '1px dashed', borderColor: 'grey.200', bgcolor: 'grey.50' }}>
|
<Box sx={{ p: 2, borderTop: '1px dashed', borderColor: 'grey.200', bgcolor: 'grey.50' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5, mt: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
|
||||||
<PlaceOutlinedIcon sx={{ color: 'primary.main', fontSize: 20, mr: 1 }} />
|
<PlaceOutlinedIcon sx={{ color: 'primary.main', fontSize: 20, mr: 1 }} />
|
||||||
<Typography variant="caption" sx={{ color: 'grey.600', fontWeight: 700, letterSpacing: 1 }}>ALL SERVICED AREAS:</Typography>
|
<Typography variant="caption" sx={{ color: 'grey.600', fontWeight: 700, letterSpacing: 1 }}>ALL SERVICED AREAS:</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function SectionCard({ icon: Icon, title, children }) {
|
|||||||
<Icon sx={{ fontSize: 18, color: 'primary.main' }} />
|
<Icon sx={{ fontSize: 18, color: 'primary.main' }} />
|
||||||
<Typography variant="overline" sx={{ fontWeight: 700, color: 'grey.800', letterSpacing: 0.6, lineHeight: 1 }}>{title}</Typography>
|
<Typography variant="overline" sx={{ fontWeight: 700, color: 'grey.800', letterSpacing: 0.6, lineHeight: 1 }}>{title}</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack spacing={2} sx={{ p: 2 }}>{children}</Stack>
|
<Stack spacing={1.75} sx={{ p: 2 }}>{children}</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -371,7 +371,7 @@ function SurveyFormDialog({ open, onClose, onSave, initialData }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 2, bgcolor: 'grey.50' }}>
|
<DialogActions sx={{ px: 3, py: 2 }}>
|
||||||
<Button onClick={onClose} disabled={saving} color="inherit">Cancel</Button>
|
<Button onClick={onClose} disabled={saving} color="inherit">Cancel</Button>
|
||||||
<Button onClick={handleSave} disabled={saving} variant="contained">
|
<Button onClick={handleSave} disabled={saving} variant="contained">
|
||||||
{saving ? 'Saving...' : 'Save Record'}
|
{saving ? 'Saving...' : 'Save Record'}
|
||||||
@@ -388,7 +388,7 @@ function BranchCard({ row, onEdit, onDelete, users }) {
|
|||||||
const updater = users.find(u => u.id === row.updated_by);
|
const updater = users.find(u => u.id === row.updated_by);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1.5, py: 1.25, borderRadius: 2, border: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', transition: 'border-color 0.2s ease, box-shadow 0.2s ease', '&:hover': { borderColor: 'primary.main', boxShadow: '0 4px 12px rgba(192, 18, 39, 0.05)' } }}>
|
<Box sx={{ p: 2, borderRadius: 2, border: '1px solid', borderColor: 'divider', bgcolor: 'background.paper', transition: 'border-color 0.2s ease, box-shadow 0.2s ease', '&:hover': { borderColor: 'primary.main', boxShadow: '0 4px 12px rgba(192, 18, 39, 0.05)' } }}>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={2}
|
spacing={2}
|
||||||
@@ -457,7 +457,7 @@ function BranchCard({ row, onEdit, onDelete, users }) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
<Box sx={{ mt: 2.5, pt: 2.5, borderTop: '1px dashed', borderColor: 'divider' }}>
|
<Box sx={{ mt: 2, pt: 2, borderTop: '1px dashed', borderColor: 'divider' }}>
|
||||||
<Grid container spacing={2} alignItems="stretch">
|
<Grid container spacing={2} alignItems="stretch">
|
||||||
<Grid item xs={12} md={4}>
|
<Grid item xs={12} md={4}>
|
||||||
<SectionCard icon={StorefrontOutlinedIcon} title="ENQUIRY DETAILS">
|
<SectionCard icon={StorefrontOutlinedIcon} title="ENQUIRY DETAILS">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, Chip, Link,
|
Card, Stack, Button, TextField, InputAdornment, Box, Tabs, Tab, Chip, Link,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton,
|
||||||
TablePagination, Typography, CircularProgress, Alert, Tooltip, Divider, useMediaQuery,
|
TablePagination, Typography, CircularProgress, Alert, Tooltip, Divider, useMediaQuery,
|
||||||
Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions
|
Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Grid
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
@@ -21,6 +21,7 @@ import ManageAccountsOutlinedIcon from '@mui/icons-material/ManageAccountsOutlin
|
|||||||
import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
|
import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
|
||||||
|
|
||||||
import PageHeader from '@/components/PageHeader';
|
import PageHeader from '@/components/PageHeader';
|
||||||
|
import StatCard from '@/components/StatCard';
|
||||||
import StatusChip from '@/components/StatusChip';
|
import StatusChip from '@/components/StatusChip';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
import UserAvatar from '@/components/UserAvatar';
|
import UserAvatar from '@/components/UserAvatar';
|
||||||
@@ -85,7 +86,7 @@ function RoleCell({ role }) {
|
|||||||
// Mobile presentation of a user row — a self-contained card instead of a wide table row.
|
// Mobile presentation of a user row — a self-contained card instead of a wide table row.
|
||||||
function UserCard({ row, index, onEdit, onDelete }) {
|
function UserCard({ row, index, onEdit, onDelete }) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1.75, borderRadius: 3, border: 1, borderColor: 'divider', bgcolor: 'background.paper' }}>
|
<Box sx={{ p: 2, borderRadius: 3, border: 1, borderColor: 'divider', bgcolor: 'background.paper' }}>
|
||||||
<Stack direction="row" spacing={1.25} alignItems="flex-start">
|
<Stack direction="row" spacing={1.25} alignItems="flex-start">
|
||||||
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
<UserIdentity name={row.name} email={row.email} role={row.role} />
|
<UserIdentity name={row.name} email={row.email} role={row.role} />
|
||||||
@@ -165,6 +166,15 @@ export default function TeamUsers() {
|
|||||||
return c;
|
return c;
|
||||||
}, [users]);
|
}, [users]);
|
||||||
|
|
||||||
|
const roleCounts = useMemo(() => {
|
||||||
|
const c = { admin: 0, manager: 0, rep: 0, support: 0 };
|
||||||
|
users.forEach((u) => {
|
||||||
|
const role = String(u.role || '').toLowerCase();
|
||||||
|
if (role in c) c[role] += 1;
|
||||||
|
});
|
||||||
|
return c;
|
||||||
|
}, [users]);
|
||||||
|
|
||||||
const filtered = useMemo(
|
const filtered = useMemo(
|
||||||
() =>
|
() =>
|
||||||
users.filter((u) => {
|
users.filter((u) => {
|
||||||
@@ -206,10 +216,17 @@ export default function TeamUsers() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Grid container spacing={2.5} sx={{ mb: 3 }}>
|
||||||
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Total Users" value={users.length} icon={GroupsOutlinedIcon} color="primary" caption="All console members" /></Grid>
|
||||||
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Admins" value={roleCounts.admin} icon={AdminPanelSettingsOutlinedIcon} color="primary" caption="Full access" /></Grid>
|
||||||
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Managers" value={roleCounts.manager} icon={ManageAccountsOutlinedIcon} color="primary" caption="Team oversight" /></Grid>
|
||||||
|
<Grid item xs={12} sm={6} lg={3}><StatCard accent title="Reps & Support" value={roleCounts.rep + roleCounts.support} icon={SupportAgentOutlinedIcon} color="primary" caption="Frontline staff" /></Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Card sx={{ overflow: 'hidden' }}>
|
<Card sx={{ overflow: 'hidden' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: { xs: 1.75, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
px: { xs: 2, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
display: 'flex', alignItems: 'center', gap: 1.5,
|
display: 'flex', alignItems: 'center', gap: 1.5,
|
||||||
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)`
|
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)`
|
||||||
}}
|
}}
|
||||||
@@ -223,7 +240,7 @@ export default function TeamUsers() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: { xs: 1.5, sm: 2 } }} alignItems={{ md: 'center' }}>
|
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
|
||||||
<TextField
|
<TextField
|
||||||
size="small" placeholder="Search by name, email, phone…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }}
|
size="small" placeholder="Search by name, email, phone…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }}
|
||||||
sx={{ width: { xs: '100%', md: 300 } }}
|
sx={{ width: { xs: '100%', md: 300 } }}
|
||||||
@@ -255,7 +272,7 @@ export default function TeamUsers() {
|
|||||||
) : paged.length === 0 ? (
|
) : paged.length === 0 ? (
|
||||||
<EmptyState title="No team users found" caption="Try a different role or search term, or add a user." />
|
<EmptyState title="No team users found" caption="Try a different role or search term, or add a user." />
|
||||||
) : isMobile ? (
|
) : isMobile ? (
|
||||||
<Stack spacing={1.25} sx={{ p: { xs: 1.5, sm: 2 } }}>
|
<Stack spacing={1.5} sx={{ p: 2 }}>
|
||||||
{paged.map((row, i) => (
|
{paged.map((row, i) => (
|
||||||
<UserCard
|
<UserCard
|
||||||
key={row.id}
|
key={row.id}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ function ClientDetail({ row, onEdit }) {
|
|||||||
alignItems={{ xs: 'flex-start', sm: 'center' }}
|
alignItems={{ xs: 'flex-start', sm: 'center' }}
|
||||||
spacing={1.5}
|
spacing={1.5}
|
||||||
sx={{
|
sx={{
|
||||||
px: 2.5, py: 1.75, borderBottom: 1, borderColor: 'divider',
|
px: { xs: 2, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)`
|
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}88 0%, ${theme.palette.background.paper} 75%)`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -528,7 +528,7 @@ export default function Tenants() {
|
|||||||
<Card sx={{ overflow: 'hidden' }}>
|
<Card sx={{ overflow: 'hidden' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
px: 2.5, py: 2, borderBottom: 1, borderColor: 'divider',
|
px: { xs: 2, sm: 2.5 }, py: 2, borderBottom: 1, borderColor: 'divider',
|
||||||
display: 'flex', alignItems: 'center', gap: 1.5,
|
display: 'flex', alignItems: 'center', gap: 1.5,
|
||||||
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)`
|
background: (theme) => `linear-gradient(90deg, ${theme.palette.primary.lighter}66 0%, ${theme.palette.background.paper} 70%)`
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user