ui phase 1

This commit is contained in:
hamid
2026-07-17 17:10:39 +03:30
parent f1cba6cf74
commit 370c1beefa
151 changed files with 4254 additions and 1840 deletions
@@ -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>
))}