ui phase 1
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { AppIcon, AppLink } from '@/components';
|
||||
import { AdminPageHeader } from '@/components/admin';
|
||||
import { useAdminCapabilities } from '@/hooks';
|
||||
import { ROUTES } from '@/constants';
|
||||
|
||||
/**
|
||||
* Admin overview landing (f15) — the backoffice home. Renders one **console card** per worklist the current
|
||||
* principal may act on, derived from `useAdminCapabilities()` (a UI hint; the server still enforces every
|
||||
* command's role scope). A `support` admin sees verification/tickets/alerts; a `finance` admin sees
|
||||
* payouts/config; only a `super_admin` sees roles. Each card deep-links into its console.
|
||||
*/
|
||||
export default function AdminOverviewScreen() {
|
||||
const t = useTranslations('admin');
|
||||
const tNav = useTranslations('nav');
|
||||
const locale = useLocale();
|
||||
const caps = useAdminCapabilities();
|
||||
|
||||
// `key` doubles as the `nav` i18n key for the card label.
|
||||
const consoles: { key: string; route: string; icon: string; enabled: boolean }[] = [
|
||||
{ key: 'verification', route: ROUTES.ADMIN_VERIFICATION, icon: 'verification', enabled: caps.canVerify },
|
||||
{ key: 'tickets', route: ROUTES.ADMIN_TICKETS, icon: 'support', enabled: caps.canManageTickets },
|
||||
{ key: 'payouts', route: ROUTES.ADMIN_PAYOUTS, icon: 'earnings', enabled: caps.canPayout },
|
||||
{ key: 'reviews', route: ROUTES.ADMIN_REVIEWS, icon: 'moderation', enabled: caps.canModerate },
|
||||
{ key: 'config', route: ROUTES.ADMIN_CONFIG, icon: 'config', enabled: caps.canConfig },
|
||||
{ key: 'holidays', route: ROUTES.ADMIN_HOLIDAYS, icon: 'calendar', enabled: caps.canConfig },
|
||||
{ key: 'alerts', route: ROUTES.ADMIN_ALERTS, icon: 'alerts', enabled: caps.canManageAlerts },
|
||||
{ key: 'audit', route: ROUTES.ADMIN_AUDIT, icon: 'audit', enabled: caps.canViewAudit },
|
||||
{ key: 'partners', route: ROUTES.ADMIN_PARTNERS, icon: 'partners', enabled: caps.canManagePartners },
|
||||
{ key: 'roles', route: ROUTES.ADMIN_ROLES, icon: 'roles', enabled: caps.canManageRoles },
|
||||
].filter((c) => c.enabled);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<AdminPageHeader title={t('overview_title')} subtitle={t('overview_subtitle')} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: '1fr 1fr 1fr' },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{consoles.map((c) => (
|
||||
<AppLink
|
||||
key={c.key}
|
||||
to={`/${locale}${c.route}`}
|
||||
color="inherit"
|
||||
underline="none"
|
||||
sx={{ display: 'block', height: '100%' }}
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '100%',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: 1.5,
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
||||
'&:hover': { borderColor: 'primary.main', boxShadow: 3 },
|
||||
}}
|
||||
>
|
||||
<AppIcon icon={c.icon} size={32} color="var(--bal-primary)" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
|
||||
{tNav(c.key)}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</AppLink>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import SidebarShellSkeleton from '../_chrome/SidebarShellSkeleton';
|
||||
|
||||
export default function Loading() {
|
||||
return <SidebarShellSkeleton />;
|
||||
}
|
||||
@@ -1,81 +1,13 @@
|
||||
'use client';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { AppIcon, AppLink } from '@/components';
|
||||
import { AdminPageHeader } from '@/components/admin';
|
||||
import { useAdminCapabilities } from '@/hooks';
|
||||
import { ROUTES } from '@/constants';
|
||||
import type { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import AdminOverviewScreen from './AdminOverviewScreen';
|
||||
|
||||
/**
|
||||
* Admin overview landing (f15) — the backoffice home. Renders one **console card** per worklist the current
|
||||
* principal may act on, derived from `useAdminCapabilities()` (a UI hint; the server still enforces every
|
||||
* command's role scope). A `support` admin sees verification/tickets/alerts; a `finance` admin sees
|
||||
* payouts/config; only a `super_admin` sees roles. Each card deep-links into its console.
|
||||
*/
|
||||
export default function AdminOverviewPage() {
|
||||
const t = useTranslations('admin');
|
||||
const tNav = useTranslations('nav');
|
||||
const locale = useLocale();
|
||||
const caps = useAdminCapabilities();
|
||||
|
||||
// `key` doubles as the `nav` i18n key for the card label.
|
||||
const consoles: { key: string; route: string; icon: string; enabled: boolean }[] = [
|
||||
{ key: 'verification', route: ROUTES.ADMIN_VERIFICATION, icon: 'verification', enabled: caps.canVerify },
|
||||
{ key: 'tickets', route: ROUTES.ADMIN_TICKETS, icon: 'support', enabled: caps.canManageTickets },
|
||||
{ key: 'payouts', route: ROUTES.ADMIN_PAYOUTS, icon: 'earnings', enabled: caps.canPayout },
|
||||
{ key: 'reviews', route: ROUTES.ADMIN_REVIEWS, icon: 'moderation', enabled: caps.canModerate },
|
||||
{ key: 'config', route: ROUTES.ADMIN_CONFIG, icon: 'config', enabled: caps.canConfig },
|
||||
{ key: 'holidays', route: ROUTES.ADMIN_HOLIDAYS, icon: 'calendar', enabled: caps.canConfig },
|
||||
{ key: 'alerts', route: ROUTES.ADMIN_ALERTS, icon: 'alerts', enabled: caps.canManageAlerts },
|
||||
{ key: 'audit', route: ROUTES.ADMIN_AUDIT, icon: 'audit', enabled: caps.canViewAudit },
|
||||
{ key: 'partners', route: ROUTES.ADMIN_PARTNERS, icon: 'partners', enabled: caps.canManagePartners },
|
||||
{ key: 'roles', route: ROUTES.ADMIN_ROLES, icon: 'roles', enabled: caps.canManageRoles },
|
||||
].filter((c) => c.enabled);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<AdminPageHeader title={t('overview_title')} subtitle={t('overview_subtitle')} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: '1fr 1fr 1fr' },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{consoles.map((c) => (
|
||||
<AppLink
|
||||
key={c.key}
|
||||
to={`/${locale}${c.route}`}
|
||||
color="inherit"
|
||||
underline="none"
|
||||
sx={{ display: 'block', height: '100%' }}
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '100%',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: 1.5,
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
||||
'&:hover': { borderColor: 'primary.main', boxShadow: 3 },
|
||||
}}
|
||||
>
|
||||
<AppIcon icon={c.icon} size={32} color="var(--bal-primary)" />
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
|
||||
{tNav(c.key)}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</AppLink>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'admin' });
|
||||
return { title: t('overview_title') };
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return <AdminOverviewScreen />;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { AppButton, StatusChip } from '@/components';
|
||||
import { AppButton, Money, StatusChip } from '@/components';
|
||||
import type { StatusKind } from '@/components';
|
||||
import {
|
||||
AdminDataTable,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
import type { AdminTableColumn } from '@/components/admin';
|
||||
import { useAdminCapabilities } from '@/hooks';
|
||||
import { adminPayoutBatchPath } from '@/constants';
|
||||
import { formatIrrToToman, formatShamsiDate } from '@/utils';
|
||||
import { formatShamsiDate } from '@/utils';
|
||||
import { PAYOUTS_PAGE_SIZE } from '@/services/payouts/constants';
|
||||
import { usePayoutBatches, usePreviewPayoutBatch, useRunPayoutBatch } from '@/services/payouts';
|
||||
import type { PayoutBatchStatus, PayoutBatchSummary } from '@/services/payouts/types';
|
||||
@@ -63,7 +63,6 @@ const isoDate = (d: Date): string => d.toISOString().slice(0, 10);
|
||||
*/
|
||||
export default function AdminPayoutsPage() {
|
||||
const t = useTranslations('admin');
|
||||
const tCommon = useTranslations('common');
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const caps = useAdminCapabilities();
|
||||
@@ -92,7 +91,7 @@ export default function AdminPayoutsPage() {
|
||||
{
|
||||
key: 'total',
|
||||
header: t('payout_col_total'),
|
||||
render: (b) => `${formatIrrToToman(b.totalAmount, locale)} ${tCommon('currency_toman')}`,
|
||||
render: (b) => <Money amountIrr={b.totalAmount} size="sm" />,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
@@ -189,7 +188,6 @@ export default function AdminPayoutsPage() {
|
||||
*/
|
||||
function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClose: () => void }) {
|
||||
const t = useTranslations('admin');
|
||||
const tCommon = useTranslations('common');
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
@@ -304,9 +302,9 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
||||
) : null}
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||
{t('payout_col_gross')}: {formatIrrToToman(n.grossEarningsIrr, locale)} ·{' '}
|
||||
{t('payout_col_clawback')}: {formatIrrToToman(n.clawbackAppliedIrr, locale)} ·{' '}
|
||||
{t('payout_col_net')}: {formatIrrToToman(n.netAmountIrr, locale)} {tCommon('currency_toman')}
|
||||
{t('payout_col_gross')}: <Money amountIrr={n.grossEarningsIrr} size="sm" hideUnit /> ·{' '}
|
||||
{t('payout_col_clawback')}: <Money amountIrr={n.clawbackAppliedIrr} size="sm" hideUnit /> ·{' '}
|
||||
{t('payout_col_net')}: <Money amountIrr={n.netAmountIrr} size="sm" />
|
||||
</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user