added the api for the real data
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
@@ -32,23 +32,36 @@ import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
|
||||
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
||||
import AcUnitIcon from '@mui/icons-material/AcUnit';
|
||||
import TwoWheelerIcon from '@mui/icons-material/TwoWheeler';
|
||||
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
|
||||
|
||||
import Logo from '@/components/Logo';
|
||||
import { getStaff, getHubContext, clearSession } from '@/auth/session';
|
||||
import { getNotifications, markNotificationRead } from '@/api/hub';
|
||||
|
||||
const RED = '#C01227';
|
||||
|
||||
const INITIAL_NOTIFICATIONS = [
|
||||
{ id: 1, icon: LocalShippingOutlinedIcon, title: 'Linehaul MH-04-AX-8822 from Mumbai Hub arrived', time: '5 min ago', to: '/inbound', read: false },
|
||||
{ id: 2, icon: QrCodeScannerIcon, title: 'Manifest #MNF-2940 sorted & sealed', time: '15 min ago', to: '/dispatch', read: false },
|
||||
{ id: 3, icon: TwoWheelerIcon, title: 'Last-mile Miler Deepak went online', time: '30 min ago', to: '/dispatch', read: false },
|
||||
{ id: 4, icon: AcUnitIcon, title: 'Cold Storage Zone C temperature stabilized at 4.2°C', time: '1 hr ago', to: '/inventory', read: true }
|
||||
];
|
||||
// Map a notification `type` to an icon component (real API sends type, not an icon).
|
||||
const NOTIF_ICON = {
|
||||
exception: WarningAmberIcon,
|
||||
inbound: LocalShippingOutlinedIcon,
|
||||
dispatch: LocalShippingOutlinedIcon,
|
||||
warning: WarningAmberIcon,
|
||||
alert: NotificationsActiveIcon
|
||||
};
|
||||
|
||||
// Build initials from a display name — falls back to a sensible default.
|
||||
const toInitials = (name) =>
|
||||
(name || '')
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
.map((w) => w[0])
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase() || 'HB';
|
||||
|
||||
const MESSAGES = [
|
||||
{ id: 1, name: 'Devendra (Gate Supervisor)', text: 'Jaipur vehicle is backing into Bay 4 now.', time: '3 min ago', initials: 'DS' },
|
||||
@@ -56,57 +69,6 @@ const MESSAGES = [
|
||||
{ id: 3, name: 'Neha (Last-mile Dispatcher)', text: 'We need 2 more milers for South Delhi route.', time: '45 min ago', initials: 'ND' }
|
||||
];
|
||||
|
||||
const NOTIFICATION_DETAILS = {
|
||||
1: {
|
||||
title: 'Linehaul Arrival: Mumbai Hub',
|
||||
desc: 'Linehaul vehicle MH-04-AX-8822 has checked in at gate terminal Bay 4. Manifest includes 480 mixed corporate shipments.',
|
||||
stats: [
|
||||
{ label: 'Carrier Plate', value: 'MH-04-AX-8822' },
|
||||
{ label: 'Origin Node', value: 'Mumbai Hub (BOM-02)' },
|
||||
{ label: 'Inbound Sorters', value: 'Allocated (Zone A & C)' },
|
||||
{ label: 'Cold Chain items', value: '18 items binned' }
|
||||
],
|
||||
actionText: 'Go to Inbound Station',
|
||||
to: '/inbound'
|
||||
},
|
||||
2: {
|
||||
title: 'Sealed Outbound Manifest #MNF-2940',
|
||||
desc: 'Sorting manifest #MNF-2940 has been closed, sealed, and audited by supervisor Suresh. Packages are loaded in transit cart.',
|
||||
stats: [
|
||||
{ label: 'Manifest ID', value: 'MNF-2940' },
|
||||
{ label: 'Route Zone', value: 'West Delhi (Dwarka)' },
|
||||
{ label: 'Packages Count', value: '42 items loaded' },
|
||||
{ label: 'Seal ID Code', value: 'SEAL-DEL-98402' }
|
||||
],
|
||||
actionText: 'Go to Outbound Dispatch',
|
||||
to: '/dispatch'
|
||||
},
|
||||
3: {
|
||||
title: 'Miler Deepak Sharma Online',
|
||||
desc: 'Last-mile EV miler Deepak Sharma logged into transit terminal app. Courier battery capacity 98% (Fully Charged).',
|
||||
stats: [
|
||||
{ label: 'Miler Name', value: 'Deepak Sharma' },
|
||||
{ label: 'Vehicle Class', value: 'EV Two-wheeler' },
|
||||
{ label: 'Capacity Limit', value: '35 kg' },
|
||||
{ label: 'Assigned Route', value: 'South Delhi (Saket)' }
|
||||
],
|
||||
actionText: 'Go to Outbound Dispatch',
|
||||
to: '/dispatch'
|
||||
},
|
||||
4: {
|
||||
title: 'Cold Storage Compliance Logged',
|
||||
desc: 'Sensor Zone C logged audit snapshot at 12:00 PM. Climate check parameters successfully verified at 4.2°C.',
|
||||
stats: [
|
||||
{ label: 'Chamber Area', value: 'Cold Zone Chamber C' },
|
||||
{ label: 'Sensor Reading', value: '4.2°C (Target: 2-8°C)' },
|
||||
{ label: 'Log ID', value: 'LOG-C-98248102' },
|
||||
{ label: 'Status', value: 'Optimal Compliance' }
|
||||
],
|
||||
actionText: 'Go to Storage & Bins',
|
||||
to: '/inventory'
|
||||
}
|
||||
};
|
||||
|
||||
const INITIAL_CHATS = {
|
||||
1: {
|
||||
name: 'Devendra (Gate Supervisor)',
|
||||
@@ -139,6 +101,16 @@ const INITIAL_CHATS = {
|
||||
|
||||
export default function Header({ onToggle }) {
|
||||
const navigate = useNavigate();
|
||||
const staff = getStaff();
|
||||
const hub = getHubContext();
|
||||
const staffName = staff.displayname || 'Hub Staff';
|
||||
const hubName = hub.hubname || 'Doormile Hub';
|
||||
|
||||
const handleLogout = () => {
|
||||
clearSession();
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const [account, setAccount] = useState(null);
|
||||
const [notifAnchor, setNotifAnchor] = useState(null);
|
||||
const [msgAnchor, setMsgAnchor] = useState(null);
|
||||
@@ -149,22 +121,53 @@ export default function Header({ onToggle }) {
|
||||
const [chats, setChats] = useState(INITIAL_CHATS);
|
||||
const [typedMessage, setTypedMessage] = useState('');
|
||||
|
||||
const [notifications, setNotifications] = useState(INITIAL_NOTIFICATIONS);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const unread = notifications.filter((n) => !n.read).length;
|
||||
|
||||
const openNotif = (e) => setNotifAnchor(e.currentTarget);
|
||||
// Load real notifications from the API (map `type` → an icon component).
|
||||
const loadNotifications = useCallback(async () => {
|
||||
try {
|
||||
const res = await getNotifications();
|
||||
setNotifications(
|
||||
(res?.data || []).map((n) => ({
|
||||
id: n.id,
|
||||
title: n.title,
|
||||
time: n.time,
|
||||
read: Boolean(n.read),
|
||||
type: n.type,
|
||||
icon: NOTIF_ICON[n.type] || NotificationsNoneIcon
|
||||
}))
|
||||
);
|
||||
} catch {
|
||||
// Non-fatal: leave the bell empty if it can't load.
|
||||
setNotifications([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadNotifications();
|
||||
const t = setInterval(loadNotifications, 60000); // refresh every 60s
|
||||
return () => clearInterval(t);
|
||||
}, [loadNotifications]);
|
||||
|
||||
const openNotif = (e) => { setNotifAnchor(e.currentTarget); loadNotifications(); };
|
||||
const closeNotif = () => setNotifAnchor(null);
|
||||
const markAllRead = () => setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||
|
||||
const onNotifClick = (n) => {
|
||||
|
||||
const markAllRead = async () => {
|
||||
const unreadIds = notifications.filter((n) => !n.read).map((n) => n.id);
|
||||
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||
await Promise.allSettled(unreadIds.map((id) => markNotificationRead(id)));
|
||||
};
|
||||
|
||||
const onNotifClick = async (n) => {
|
||||
setNotifications((prev) => prev.map((x) => (x.id === n.id ? { ...x, read: true } : x)));
|
||||
closeNotif();
|
||||
if (NOTIFICATION_DETAILS[n.id]) {
|
||||
setSelectedNotif(NOTIFICATION_DETAILS[n.id]);
|
||||
} else {
|
||||
navigate(n.to);
|
||||
try {
|
||||
await markNotificationRead(n.id);
|
||||
} catch {
|
||||
/* best effort */
|
||||
}
|
||||
};
|
||||
|
||||
@@ -283,13 +286,13 @@ export default function Header({ onToggle }) {
|
||||
'&:hover': { bgcolor: 'grey.100' }
|
||||
}}
|
||||
>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: RED, color: '#fff', fontWeight: 700 }}>RK</Avatar>
|
||||
<Avatar sx={{ width: 34, height: 34, bgcolor: RED, color: '#fff', fontWeight: 700 }}>{toInitials(staffName)}</Avatar>
|
||||
<Box sx={{ display: { xs: 'none', md: 'block' }, lineHeight: 1.1 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
|
||||
Rajesh Kumar
|
||||
{staffName}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||
Delhi Hub Manager
|
||||
{hubName}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -380,7 +383,7 @@ export default function Header({ onToggle }) {
|
||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||
PaperProps={{ sx: { mt: 1, minWidth: 200 } }}
|
||||
>
|
||||
<MenuItem onClick={() => { setAccount(null); navigate('/login'); }} sx={{ color: 'error.main' }}>
|
||||
<MenuItem onClick={() => { setAccount(null); handleLogout(); }} sx={{ color: 'error.main' }}>
|
||||
<ListItemIcon><LogoutIcon fontSize="small" color="error" /></ListItemIcon>
|
||||
Logout
|
||||
</MenuItem>
|
||||
|
||||
@@ -19,6 +19,7 @@ import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord';
|
||||
|
||||
import navItems from '@/menu/navItems';
|
||||
import Logo from '@/components/Logo';
|
||||
import { isDoormileStaff } from '@/auth/session';
|
||||
|
||||
export const DRAWER_WIDTH = 240;
|
||||
export const MINI_WIDTH = 72;
|
||||
@@ -97,6 +98,17 @@ export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const expanded = open || isMobile;
|
||||
const doormile = isDoormileStaff();
|
||||
|
||||
// Partner accounts don't see Doormile-only groups/items (e.g. Hub Settings).
|
||||
const groups = useMemo(
|
||||
() =>
|
||||
navItems
|
||||
.filter((g) => !g.doormileOnly || doormile)
|
||||
.map((g) => ({ ...g, items: g.items.filter((i) => !i.doormileOnly || doormile) }))
|
||||
.filter((g) => g.items.length > 0),
|
||||
[doormile]
|
||||
);
|
||||
|
||||
const isActive = (url) => !!(url && location.pathname.startsWith(url));
|
||||
|
||||
@@ -156,7 +168,7 @@ export default function Sidebar({ open, mobileOpen, onMobileClose, isMobile }) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{navItems.map((grp) => (
|
||||
{groups.map((grp) => (
|
||||
<Box key={grp.group} sx={{ mt: 2.5 }}>
|
||||
{expanded && (
|
||||
<Typography
|
||||
|
||||
Reference in New Issue
Block a user