'use client'; import { useState } from 'react'; import { useLocale, useTranslations } from 'next-intl'; import { useSnackbar } from 'notistack'; import { Box, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, MenuItem, Skeleton, Stack, Switch, TextField, } from '@mui/material'; import { AppButton } from '@/components'; import { AdminDataTable, AdminEmptyState, AdminErrorState, AdminPageHeader, type AdminTableColumn } from '@/components/admin'; import { formatShamsiDate } from '@/utils'; import { useAdminCapabilities } from '@/hooks'; import type { Holiday, HolidayInput, HolidayType } from '@/services/admin/types'; import { useHolidays, useUpsertHoliday } from '@/services/admin'; const HOLIDAY_TYPES: readonly HolidayType[] = ['official', 'religious', 'national']; /** * Iranian-holiday calendar manager (f15). Lists `iranian_holidays`, each with its Shamsi date, name, type, * and an `is_bank_closed` flag — the flag that shifts payout scheduling (the copy surfaces that consequence). * The client only maintains the calendar the **server** uses for the next-business-day shift; it never * computes the shift itself (phase §5). */ export default function AdminHolidaysPage() { const t = useTranslations('admin'); const locale = useLocale(); const caps = useAdminCapabilities(); const holidays = useHolidays({}, 1); const [editing, setEditing] = useState(null); const columns: AdminTableColumn[] = [ { key: 'date', header: t('hol_col_date'), render: (h) => formatShamsiDate(h.holidayDate, locale) }, { key: 'name', header: t('hol_col_name'), render: (h) => h.nameFa }, { key: 'type', header: t('hol_col_type'), render: (h) => }, { key: 'bank', header: t('hol_col_bank'), render: (h) => ( ), }, ...(caps.canConfig ? [ { key: 'actions', header: '', render: (h: Holiday) => ( setEditing(h)}> {t('cfg_edit')} ), } as AdminTableColumn, ] : []), ]; return ( setEditing('new')}> {t('hol_add')} ) : undefined } /> {holidays.isLoading ? ( ) : holidays.isError ? ( holidays.refetch()} /> ) : (holidays.data?.items.length ?? 0) === 0 ? ( ) : ( h.id} ariaLabel={t('hol_title')} /> )} {editing ? ( setEditing(null)} /> ) : null} ); } const TODAY_ISO = ''; // seeded below via state default so no Date at module load function HolidayDialog({ holiday, onClose }: { holiday: Holiday | null; onClose: () => void }) { const t = useTranslations('admin'); const { enqueueSnackbar } = useSnackbar(); const upsert = useUpsertHoliday(); const [form, setForm] = useState({ holidayDate: holiday?.holidayDate?.slice(0, 10) ?? TODAY_ISO, nameFa: holiday?.nameFa ?? '', type: holiday?.type ?? 'official', isBankClosed: holiday?.isBankClosed ?? true, }); const valid = form.holidayDate.length > 0 && form.nameFa.trim().length > 0; const onSave = () => { if (!valid) return; upsert.mutate( { ...form, nameFa: form.nameFa.trim() }, { onSuccess: () => { enqueueSnackbar(t('hol_saved'), { variant: 'success' }); onClose(); }, }, ); }; return ( {holiday ? t('hol_edit') : t('hol_add')} setForm((f) => ({ ...f, holidayDate: e.target.value }))} disabled={!!holiday} slotProps={{ inputLabel: { shrink: true } }} /> setForm((f) => ({ ...f, nameFa: e.target.value }))} /> setForm((f) => ({ ...f, type: e.target.value as HolidayType }))} > {HOLIDAY_TYPES.map((ty) => ( {t(`htype_${ty}`)} ))} setForm((f) => ({ ...f, isBankClosed: e.target.checked }))} />} label={t('hol_bank_hint')} /> {t('cancel')} {upsert.isPending ? t('saving') : t('save')} ); }