added the api for the real data

This commit is contained in:
2026-07-04 17:08:16 +05:30
parent 200831d19c
commit e6ad37b20f
22 changed files with 2854 additions and 1061 deletions

View File

@@ -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>

View File

@@ -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