update ui admin
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
@@ -17,15 +17,17 @@ import {
|
||||
Tooltip,
|
||||
Button,
|
||||
Stack,
|
||||
Select,
|
||||
alpha
|
||||
} from '@mui/material';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
|
||||
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
|
||||
import PersonOutlineIcon from '@mui/icons-material/PersonOutline';
|
||||
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
|
||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined';
|
||||
import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined';
|
||||
@@ -33,33 +35,41 @@ import AssignmentOutlinedIcon from '@mui/icons-material/AssignmentOutlined';
|
||||
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
||||
|
||||
import Logo from '@/components/Logo';
|
||||
import { locations } from '@/data/mock';
|
||||
import { useFilters } from '@/store/Filters';
|
||||
|
||||
const RED = '#C01227'; // brand accent (avatars, dots)
|
||||
const BAR = '#8E1F2A'; // muted deep-brick top bar (toned down from vivid #C01227)
|
||||
const RED = '#C01227'; // brand accent (reserved for attention: avatar, unread dots)
|
||||
|
||||
const INITIAL_NOTIFICATIONS = [
|
||||
{ id: 1, icon: Inventory2OutlinedIcon, title: 'New order #ORD-10482 placed', time: '2 min ago', to: '/orders', read: false },
|
||||
{ id: 2, icon: TwoWheelerOutlinedIcon, title: 'Rider Imran went online', time: '18 min ago', to: '/riders', read: false },
|
||||
{ id: 2, icon: TwoWheelerOutlinedIcon, title: 'Rider Imran went online', time: '18 min ago', to: '/fleet', read: false },
|
||||
{ id: 3, icon: PaymentsOutlinedIcon, title: 'Invoice INV-2041 marked paid', time: '1 hr ago', to: '/invoice', read: false },
|
||||
{ id: 4, icon: AssignmentOutlinedIcon, title: 'MileTruth AI re-optimized 41 routes', time: '3 hrs ago', to: '/dispatch', read: true }
|
||||
];
|
||||
|
||||
const MESSAGES = [
|
||||
{ id: 1, name: 'Priya Nair', text: 'Can we reroute the MG Road batch?', time: '5 min ago', initials: 'PN' },
|
||||
{ id: 2, name: 'Imran Khan', text: 'Reached the warehouse, loading now.', time: '22 min ago', initials: 'IK' },
|
||||
{ id: 3, name: 'Acme Logistics', text: 'Please confirm the revised pricing.', time: '2 hrs ago', initials: 'AL' }
|
||||
];
|
||||
|
||||
export default function Header({ onToggle }) {
|
||||
const navigate = useNavigate();
|
||||
const [account, setAccount] = useState(null);
|
||||
const [notifAnchor, setNotifAnchor] = useState(null);
|
||||
const [msgAnchor, setMsgAnchor] = useState(null);
|
||||
const [notifications, setNotifications] = useState(INITIAL_NOTIFICATIONS);
|
||||
const [search, setSearch] = useState('');
|
||||
const { location, setLocation } = useFilters(); // global location — single source of truth
|
||||
const searchRef = useRef(null);
|
||||
|
||||
const unread = notifications.filter((n) => !n.read).length;
|
||||
|
||||
// ⌘K / Ctrl+K focuses global search
|
||||
useEffect(() => {
|
||||
const onKey = (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
searchRef.current?.focus();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, []);
|
||||
|
||||
const openNotif = (e) => setNotifAnchor(e.currentTarget);
|
||||
const closeNotif = () => setNotifAnchor(null);
|
||||
const markAllRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||
@@ -79,76 +89,124 @@ export default function Header({ onToggle }) {
|
||||
<AppBar
|
||||
position="fixed"
|
||||
elevation={0}
|
||||
sx={{ bgcolor: BAR, color: '#fff', zIndex: (t) => t.zIndex.drawer + 1, boxShadow: '0 1px 0 rgba(0,0,0,0.06)' }}
|
||||
sx={{
|
||||
bgcolor: '#FFFFFF',
|
||||
color: 'grey.800',
|
||||
zIndex: (t) => t.zIndex.drawer + 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'grey.200'
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ minHeight: 64, px: { xs: 1.5, sm: 2.5 }, gap: 1 }}>
|
||||
<IconButton color="inherit" edge="start" onClick={onToggle} sx={{ mr: 0.5 }}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
{/* Brand wordmark — left side */}
|
||||
<Box
|
||||
onClick={() => navigate('/dashboard')}
|
||||
sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
||||
>
|
||||
<Logo onDark height={22} />
|
||||
{/* LEFT — hamburger + brand (equal-flex zone) */}
|
||||
<Box sx={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<IconButton edge="start" onClick={onToggle} sx={{ color: 'grey.700' }}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Box
|
||||
onClick={() => navigate('/dashboard')}
|
||||
sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer', flexShrink: 0 }}
|
||||
>
|
||||
<Logo height={24} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
{/* Search — moved to the right */}
|
||||
{/* CENTER — global search, the primary nav element (fixed width = stays truly centered) */}
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={submitSearch}
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'flex' },
|
||||
flexShrink: 0,
|
||||
alignItems: 'center',
|
||||
bgcolor: alpha('#fff', 0.16),
|
||||
borderRadius: 2,
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
width: { sm: 240, md: 320 },
|
||||
'&:hover': { bgcolor: alpha('#fff', 0.22) },
|
||||
'&:focus-within': { bgcolor: alpha('#fff', 0.26) }
|
||||
height: 46,
|
||||
px: 1.75,
|
||||
bgcolor: 'grey.50',
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.200',
|
||||
borderRadius: 2.5,
|
||||
width: { sm: 300, md: 460, lg: 560 },
|
||||
transition: 'all 0.15s ease',
|
||||
'&:hover': { borderColor: 'grey.300' },
|
||||
'&:focus-within': { borderColor: 'primary.main', bgcolor: '#fff', boxShadow: '0 0 0 3px rgba(192,18,39,0.08)' }
|
||||
}}
|
||||
>
|
||||
<SearchIcon sx={{ fontSize: 20, mr: 1, opacity: 0.9 }} />
|
||||
<SearchIcon sx={{ fontSize: 20, mr: 1, color: 'grey.500' }} />
|
||||
<InputBase
|
||||
inputRef={searchRef}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Search orders, riders, customers…"
|
||||
sx={{ color: '#fff', fontSize: '0.875rem', flex: 1, '&::placeholder': { color: '#fff' } }}
|
||||
inputProps={{ style: { color: '#fff' }, 'aria-label': 'search' }}
|
||||
placeholder="Search orders, shipments, riders, customers…"
|
||||
sx={{ color: 'grey.800', fontSize: '0.875rem', flex: 1 }}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: 'none', md: 'flex' },
|
||||
alignItems: 'center',
|
||||
gap: 0.25,
|
||||
px: 0.75,
|
||||
py: 0.4,
|
||||
ml: 1,
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.300',
|
||||
bgcolor: '#fff',
|
||||
color: 'grey.500',
|
||||
fontSize: '0.6875rem',
|
||||
fontWeight: 700,
|
||||
lineHeight: 1,
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
⌘K
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Tooltip title="Messages">
|
||||
<IconButton color="inherit" onClick={(e) => setMsgAnchor(e.currentTarget)}>
|
||||
<Badge badgeContent={MESSAGES.length} color="warning">
|
||||
<ChatBubbleOutlineIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Notifications">
|
||||
<IconButton color="inherit" onClick={openNotif}>
|
||||
<Badge badgeContent={unread} color="warning">
|
||||
<NotificationsNoneIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{/* RIGHT — location + notifications + profile (equal-flex zone, right-aligned) */}
|
||||
<Box sx={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.5 }}>
|
||||
<Select
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
size="small"
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
startAdornment={<PlaceOutlinedIcon sx={{ fontSize: 18, color: 'grey.500', mr: 0.75 }} />}
|
||||
sx={{
|
||||
display: { xs: 'none', md: 'flex' },
|
||||
minWidth: 168,
|
||||
bgcolor: 'grey.50',
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'grey.200' },
|
||||
'& .MuiSelect-select': { display: 'flex', alignItems: 'center', fontSize: '0.8125rem', fontWeight: 600, color: 'grey.700' }
|
||||
}}
|
||||
>
|
||||
<MenuItem value="all">All Locations</MenuItem>
|
||||
{locations.map((l) => (
|
||||
<MenuItem key={l} value={l}>{l}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Box
|
||||
onClick={(e) => setAccount(e.currentTarget)}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 1, ml: 0.5, cursor: 'pointer', py: 0.5, px: 0.5, borderRadius: 2, '&:hover': { bgcolor: alpha('#fff', 0.14) } }}
|
||||
>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: '#fff', color: RED, fontWeight: 700 }}>AD</Avatar>
|
||||
<Box sx={{ display: { xs: 'none', md: 'block' }, lineHeight: 1.1 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: '#fff', fontWeight: 600 }}>
|
||||
Aman Deshmukh
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: alpha('#fff', 0.8) }}>
|
||||
Operations Admin
|
||||
</Typography>
|
||||
<Tooltip title="Notifications">
|
||||
<IconButton onClick={openNotif} sx={{ color: 'grey.700' }}>
|
||||
<Badge badgeContent={unread} color="error">
|
||||
<NotificationsNoneIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Box
|
||||
onClick={(e) => setAccount(e.currentTarget)}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 1, cursor: 'pointer', py: 0.5, px: 0.5, borderRadius: 2, '&:hover': { bgcolor: 'grey.100' } }}
|
||||
>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: RED, color: '#fff', fontWeight: 700, fontSize: '0.8rem' }}>AD</Avatar>
|
||||
<Box sx={{ display: { xs: 'none', md: 'block' }, lineHeight: 1.1 }}>
|
||||
<Typography variant="subtitle2" sx={{ color: 'grey.800', fontWeight: 600 }}>
|
||||
Aman Deshmukh
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'grey.500' }}>
|
||||
Operations Admin
|
||||
</Typography>
|
||||
</Box>
|
||||
<KeyboardArrowDownIcon sx={{ fontSize: 18, color: 'grey.500', display: { xs: 'none', md: 'block' } }} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -180,7 +238,7 @@ export default function Header({ onToggle }) {
|
||||
return (
|
||||
<MenuItem key={n.id} onClick={() => onNotifClick(n)} sx={{ py: 1.25, whiteSpace: 'normal', alignItems: 'flex-start' }}>
|
||||
<ListItemIcon sx={{ mt: 0.25 }}>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: n.read ? 'grey.200' : alpha(RED, 0.12), color: RED }}>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: n.read ? 'grey.100' : alpha(RED, 0.12), color: n.read ? 'grey.500' : RED }}>
|
||||
<Icon fontSize="small" />
|
||||
</Avatar>
|
||||
</ListItemIcon>
|
||||
@@ -200,39 +258,6 @@ export default function Header({ onToggle }) {
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Messages dropdown */}
|
||||
<Menu
|
||||
anchorEl={msgAnchor}
|
||||
open={Boolean(msgAnchor)}
|
||||
onClose={() => setMsgAnchor(null)}
|
||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
PaperProps={{ sx: { mt: 1, width: 340, maxWidth: '90vw' } }}
|
||||
>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, px: 2, py: 1.25 }}>
|
||||
Messages
|
||||
</Typography>
|
||||
<Divider />
|
||||
{MESSAGES.map((m) => (
|
||||
<MenuItem key={m.id} onClick={() => setMsgAnchor(null)} sx={{ py: 1.25, whiteSpace: 'normal', alignItems: 'flex-start' }}>
|
||||
<ListItemIcon sx={{ mt: 0.25 }}>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: alpha(RED, 0.12), color: RED, fontWeight: 700, fontSize: '0.8rem' }}>
|
||||
{m.initials}
|
||||
</Avatar>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={m.name}
|
||||
secondary={m.text}
|
||||
primaryTypographyProps={{ fontSize: '0.875rem', fontWeight: 700 }}
|
||||
secondaryTypographyProps={{ fontSize: '0.8rem' }}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ ml: 1, mt: 0.5, flexShrink: 0 }}>
|
||||
{m.time}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
|
||||
{/* Account dropdown */}
|
||||
<Menu
|
||||
anchorEl={account}
|
||||
|
||||
Reference in New Issue
Block a user