ui phase 10
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
import { FunctionComponent, PropsWithChildren, useMemo } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { NotificationBell } from '@/components/notifications';
|
||||
import { ProfileSummary } from '@/components';
|
||||
import { ROUTES } from '@/constants';
|
||||
import { useAdminCapabilities } from '@/hooks';
|
||||
@@ -13,8 +12,11 @@ import TopBarAndSideBarLayout from './TopBarAndSideBarLayout';
|
||||
* Admin / backoffice shell — the desktop ops console (f15). The sidebar is **sectioned**
|
||||
* (اعتماد/مالی/پشتیبانی/سیستم) and **role-gated**: each console appears only when the current
|
||||
* admin role can act on it (`useAdminCapabilities`), unchanged from before — grouping never adds,
|
||||
* removes, or loosens a gate. The TopBar carries a page title, the notification bell (widened to
|
||||
* the `'admin'` role), and a compact identity chip showing the admin's fine-grained role.
|
||||
* removes, or loosens a gate. The TopBar carries a page title and a compact identity chip showing
|
||||
* the admin's fine-grained role. **No notification bell** (ui-phase-10): admin notifications have no
|
||||
* real feed yet (phase 11 owns that decision) and the bell was the only entry into the dead
|
||||
* `admin/notifications` placeholder — re-add it once phase 11 ships a feed, reusing
|
||||
* `NotificationBellPopover` (already exported for that handoff).
|
||||
* @layout AdminLayout
|
||||
*/
|
||||
const AdminLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
@@ -50,7 +52,6 @@ const AdminLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<TopBarAndSideBarLayout
|
||||
sidebarItems={sidebarItems}
|
||||
headerActions={<NotificationBell role="admin" />}
|
||||
identity={
|
||||
me && primaryRoleCode ? (
|
||||
<ProfileSummary
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
import { FunctionComponent, PropsWithChildren, useMemo } from 'react';
|
||||
import { Box, Stack, Tab, Tabs } from '@mui/material';
|
||||
import { Badge, Box, Stack, Tab, Tabs } from '@mui/material';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { AppIcon, AppIconButton, ErrorBoundary } from '@/components';
|
||||
import { NotificationBell } from '@/components/notifications';
|
||||
import { CONTENT_MAX_WIDTH } from '@/components/config';
|
||||
import { ROUTES } from '@/constants';
|
||||
import { useSupportUnreadTotal } from '@/services/tickets';
|
||||
import { LinkToPage } from '@/utils';
|
||||
import { usePathname, useRouter } from '@/i18n/navigation';
|
||||
import { TopBar, BottomBar } from './components';
|
||||
@@ -50,18 +51,27 @@ const CustomerHeader: FunctionComponent<{ bottomNavItems: Array<LinkToPage> }> =
|
||||
const router = useRouter();
|
||||
const title = useRouteTitle();
|
||||
const onRootTab = isCustomerRootTab(pathname);
|
||||
const supportUnreadTotal = useSupportUnreadTotal();
|
||||
|
||||
return (
|
||||
<TopBar
|
||||
align={onRootTab ? 'center' : 'start'}
|
||||
startNode={
|
||||
onRootTab ? (
|
||||
<AppIconButton
|
||||
icon="support"
|
||||
color="inherit"
|
||||
title={t('support')}
|
||||
onClick={() => router.push(ROUTES.SUPPORT_TICKETS)}
|
||||
/>
|
||||
<Badge
|
||||
badgeContent={supportUnreadTotal ?? 0}
|
||||
max={99}
|
||||
overlap="circular"
|
||||
invisible={!supportUnreadTotal}
|
||||
sx={{ '& .MuiBadge-badge': { bgcolor: 'var(--bal-error)', color: 'var(--bal-error-contrast)' } }}
|
||||
>
|
||||
<AppIconButton
|
||||
icon="support"
|
||||
color="inherit"
|
||||
title={t('support')}
|
||||
onClick={() => router.push(ROUTES.SUPPORT_TICKETS)}
|
||||
/>
|
||||
</Badge>
|
||||
) : (
|
||||
<AppIconButton icon="back" title={tc('back')} onClick={() => router.back()} />
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ROUTES } from '@/constants';
|
||||
import { LinkToPage } from '@/utils';
|
||||
import { useMe } from '@/services/auth';
|
||||
import { useNurseProfile } from '@/services/profiles';
|
||||
import { useSupportUnreadTotal } from '@/services/tickets';
|
||||
import { useVerificationStatus } from '@/services/verification';
|
||||
import { ownBadgeState } from '@/services/verification/types';
|
||||
import TopBarAndSideBarLayout from './TopBarAndSideBarLayout';
|
||||
@@ -29,6 +30,7 @@ const NurseLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
const { data: me } = useMe();
|
||||
const { data: nurseProfile } = useNurseProfile();
|
||||
const { data: verification } = useVerificationStatus();
|
||||
const supportUnreadTotal = useSupportUnreadTotal();
|
||||
|
||||
const identityLoading = !me;
|
||||
const displayName = me ? [me.firstName, me.lastName].filter(Boolean).join(' ').trim() || me.phone : '';
|
||||
@@ -48,9 +50,15 @@ const NurseLayout: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||
{ title: t('verification'), path: ROUTES.NURSE_VERIFICATION, icon: 'verification', group: groupProfession },
|
||||
{ title: t('earnings'), path: ROUTES.NURSE_EARNINGS, icon: 'earnings', group: groupFinance },
|
||||
{ title: t('bank'), path: ROUTES.NURSE_BANK, icon: 'bank', group: groupFinance },
|
||||
{ title: t('support'), path: ROUTES.NURSE_SUPPORT_TICKETS, icon: 'support', group: groupSupport },
|
||||
{
|
||||
title: t('support'),
|
||||
path: ROUTES.NURSE_SUPPORT_TICKETS,
|
||||
icon: 'support',
|
||||
group: groupSupport,
|
||||
badgeCount: supportUnreadTotal ?? undefined,
|
||||
},
|
||||
];
|
||||
}, [t]);
|
||||
}, [t, supportUnreadTotal]);
|
||||
|
||||
const mobileTabs = useMemo(
|
||||
(): Array<LinkToPage> => [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import { FunctionComponent, MouseEventHandler } from 'react';
|
||||
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { Badge, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { AppIcon } from '@/components';
|
||||
import { Link } from '@/i18n/navigation';
|
||||
import { LinkToPage } from '@/utils';
|
||||
@@ -14,13 +14,36 @@ interface Props extends LinkToPage {
|
||||
* Renders a single SideBar navigation item over the locale-aware `Link` (`@/i18n/navigation`) —
|
||||
* `href` is the unprefixed `ROUTES.*` path; the wrapper adds the active locale, so a click is one
|
||||
* navigation with no middleware redirect hop. `selected` is computed by the caller (`SideBarNavList`)
|
||||
* via the shared `matchActivePath` helper.
|
||||
* via the shared `matchActivePath` helper. `badgeCount` renders a small unread dot on the icon (the
|
||||
* nurse support entry, §3.1) — omitted entirely when falsy, never a bare "0".
|
||||
* @component SideBarNavItem
|
||||
*/
|
||||
const SideBarNavItem: FunctionComponent<Props> = ({ icon, path, selected = false, subtitle, title, onClick }) => {
|
||||
const SideBarNavItem: FunctionComponent<Props> = ({
|
||||
icon,
|
||||
path,
|
||||
selected = false,
|
||||
subtitle,
|
||||
title,
|
||||
onClick,
|
||||
badgeCount,
|
||||
}) => {
|
||||
const iconNode = icon && <AppIcon icon={icon} />;
|
||||
return (
|
||||
<ListItemButton component={Link} href={path ?? '#'} selected={selected} onClick={onClick}>
|
||||
<ListItemIcon>{icon && <AppIcon icon={icon} />}</ListItemIcon>
|
||||
<ListItemIcon>
|
||||
{badgeCount ? (
|
||||
<Badge
|
||||
badgeContent={badgeCount}
|
||||
max={99}
|
||||
overlap="circular"
|
||||
sx={{ '& .MuiBadge-badge': { bgcolor: 'var(--bal-error)', color: 'var(--bal-error-contrast)' } }}
|
||||
>
|
||||
{iconNode}
|
||||
</Badge>
|
||||
) : (
|
||||
iconNode
|
||||
)}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={title} secondary={subtitle} />
|
||||
</ListItemButton>
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ const SideBarNavList: FunctionComponent<Props> = ({ items, showIcons, onClick })
|
||||
subtitle={item.subtitle}
|
||||
selected={item.path === activePath}
|
||||
onClick={onClick}
|
||||
badgeCount={item.badgeCount}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user