ui phase 11

This commit is contained in:
hamid
2026-07-19 19:19:44 +03:30
parent b4b8c9ea79
commit 87fa4cd497
74 changed files with 3115 additions and 506 deletions
@@ -17,7 +17,7 @@ import {
TextField,
Typography,
} from '@mui/material';
import { AppButton, Money, StatusChip } from '@/components';
import { AppButton, JalaliDateField, 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 { formatShamsiDate } from '@/utils';
import { formatNumber, 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';
@@ -52,8 +52,17 @@ const BATCH_STATUSES: readonly PayoutBatchStatus[] = [
'failed',
];
/** UTC ISO date (`YYYY-MM-DD`) — the wire shape for the batch window. */
const isoDate = (d: Date): string => d.toISOString().slice(0, 10);
/**
* ISO date (`YYYY-MM-DD`) — the wire shape for the batch window. Formats using the browser's **local**
* date fields, never `toISOString()` (which converts to UTC first): near Tehran local midnight that would
* silently roll the date back/forward a day from the admin's actual wall-clock date.
*/
const isoDate = (d: Date): string => {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
/**
* Admin payout-batch dashboard (f15) — the reconciliation list of weekly `nurse_payout_batches` and the
@@ -173,7 +182,7 @@ export default function AdminPayoutsPage() {
onNext={() => setPage((p) => Math.min(pageCount, p + 1))}
prevLabel={t('prev_page')}
nextLabel={t('next_page')}
indicator={t('page_indicator', { page })}
indicator={t('page_indicator', { page, total: pageCount })}
/>
{previewOpen ? <PreviewBatchDialog canPayout={caps.canPayout} onClose={() => setPreviewOpen(false)} /> : null}
@@ -230,22 +239,13 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
<DialogTitle sx={{ fontWeight: 800 }}>{t('payout_preview_title')}</DialogTitle>
<DialogContent>
<Stack direction="row" sx={{ gap: 1.5, mt: 1, flexWrap: 'wrap', alignItems: 'center' }}>
<TextField
type="date"
<JalaliDateField
size="small"
label={t('payout_period_start')}
value={periodStart}
onChange={(e) => setPeriodStart(e.target.value)}
slotProps={{ inputLabel: { shrink: true } }}
/>
<TextField
type="date"
size="small"
label={t('payout_period_end')}
value={periodEnd}
onChange={(e) => setPeriodEnd(e.target.value)}
slotProps={{ inputLabel: { shrink: true } }}
onChange={setPeriodStart}
/>
<JalaliDateField size="small" label={t('payout_period_end')} value={periodEnd} onChange={setPeriodEnd} />
<AppButton
variant="outlined"
color="primary"
@@ -361,12 +361,41 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
<ConfirmDialog
open={runConfirmOpen}
title={t('payout_run_confirm_title')}
body={t('payout_run_confirm_body')}
body={
result ? (
<Stack sx={{ gap: 1.5, mt: 0.5 }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{t('payout_run_summary_intro')}
</Typography>
<Money amountIrr={result.totalNetIrr} size="lg" tone="emphasis" />
<Stack sx={{ gap: 0.5 }}>
<Stack direction="row" sx={{ justifyContent: 'space-between', gap: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{t('payout_run_count_label')}
</Typography>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{formatNumber(result.eligible.length, locale)}
</Typography>
</Stack>
<Stack direction="row" sx={{ justifyContent: 'space-between', gap: 1 }}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{t('payout_col_processing')}
</Typography>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{formatShamsiDate(result.processingDate, locale)}
</Typography>
</Stack>
</Stack>
</Stack>
) : null
}
confirmLabel={t('payout_run')}
cancelLabel={t('cancel')}
onConfirm={onRunConfirm}
onClose={() => setRunConfirmOpen(false)}
loading={run.isPending}
requireTypedConfirmation={result ? ['تایید', result.totalNetIrr] : []}
typedConfirmationLabel={t('payout_run_type_to_confirm')}
/>
</Dialog>
);