import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { TrashIcon, PencilIcon, MagnifyingGlassIcon, CurrencyDollarIcon } from '@heroicons/react/24/outline'; import { Link } from 'react-router-dom'; import { toast } from 'sonner'; import { api } from '../lib/api'; import { useUrlState, pageOf } from '../hooks/useUrlState'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; import type { Secretary, SecretaryPermissions } from '../types'; import { formatDate, formatNumber, maskMobile } from '../lib/utils'; import DataTable, { Column } from '../components/ui/DataTable'; import { ActiveBadge } from '../components/ui/StatusBadge'; import Pagination from '../components/ui/Pagination'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import Modal from '../components/ui/Modal'; import Switch from '../components/ui/Switch'; import { usePermissionCatalog, alignPermissions } from '../hooks/usePermissionCatalog'; import type { CatalogResource } from '../hooks/usePermissionCatalog'; const ACTION_COLUMNS = ['view', 'create', 'update', 'delete', 'cancel', 'update_status']; const ACTION_HEADERS = ['مشاهده', 'ایجاد', 'ویرایش', 'حذف', 'لغو', 'تغییر وضعیت']; function PermissionsMatrix({ permissions, onChange, resources, }: { permissions: SecretaryPermissions; onChange: (p: SecretaryPermissions) => void; resources: CatalogResource[]; }) { const toggle = (section: string, action: string) => { onChange({ ...permissions, [section]: { ...permissions[section], [action]: !permissions[section]?.[action] }, }); }; const allActions = ACTION_COLUMNS; const actionHeaders = ACTION_HEADERS; return (
{actionHeaders.map((h) => ( ))} {resources.map((resource) => { const available = new Set(resource.actions.map((a) => a.key)); return ( {allActions.map((action) => { if (!available.has(action)) { return ; } return ( ); })} ); })}
بخش{h}
{resource.label}
toggle(resource.key, action)} ariaLabel={`${resource.label} — ${ACTION_HEADERS[ACTION_COLUMNS.indexOf(action)]}`} />
); } export default function SecretariesPage() { const qc = useQueryClient(); // وضعیت لیست در URL می‌ماند تا «بازگشت» از صفحهٔ جزئیات، همین جستجو و صفحه را برگرداند. const [urlState, setUrlState] = useUrlState({ page: '1', search: '' }); const page = pageOf(urlState.page); const search = urlState.search; const setPage = (p: number) => setUrlState({ page: String(p) }); const setSearch = (v: string) => setUrlState({ search: v, page: '1' }); const [editTarget, setEditTarget] = useState(null); const [editPerms, setEditPerms] = useState({}); const catalog = usePermissionCatalog(); const [deleteTarget, setDeleteTarget] = useState(null); const limit = 15; const { data, isLoading } = useQuery({ queryKey: ['secretaries', page, search], queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); return api.get>(`/api/v1/admin/secretaries?${params}`); }, }); const updatePermsMutation = useMutation({ mutationFn: ({ uuid, permissions }: { uuid: string; permissions: SecretaryPermissions }) => api.patch>(`/api/v1/secretary/${uuid}`, { permissions }), onSuccess: () => { toast.success('دسترسی‌ها بروزرسانی شد'); setEditTarget(null); qc.invalidateQueries({ queryKey: ['secretaries'] }); }, onError: (err: Error) => toast.error(err.message), }); const deleteMutation = useMutation({ mutationFn: (s: Secretary) => api.delete>(`/api/v1/secretary/${s.uuid}`), onSuccess: () => { toast.success('منشی حذف شد'); setDeleteTarget(null); qc.invalidateQueries({ queryKey: ['secretaries'] }); }, onError: (err: Error) => toast.error(err.message), }); const openEdit = (s: Secretary) => { setEditTarget(s); // شکل را کاتالوگ می‌دهد؛ منبعی که در JSONِ ذخیره‌شده نیست خاموش نمایش داده می‌شود. setEditPerms(alignPermissions(s.permissions, catalog.resources)); }; const columns: Column[] = [ { key: 'user_name', header: 'نام', render: (s) => {s.user_name} }, { key: 'mobile_number', header: 'موبایل', render: (s) => {maskMobile(s.mobile_number)} }, { key: 'doctor_name', header: 'پزشک', render: (s) => s.doctor_name }, { key: 'is_active', header: 'وضعیت', render: (s) => }, { key: 'online_share_percent', header: 'سهم آنلاین', render: (s) => ( s.online_share_enabled ? {formatNumber(s.online_share_percent ?? 0)}٪ : ) }, { key: 'created_at', header: 'تاریخ ثبت', render: (s) => formatDate(s.created_at) }, ]; const items = data?.data ?? []; const total = data?.meta?.totalRecords ?? 0; return (

منشی‌ها

{total} منشی ثبت‌شده
{ setSearch(e.target.value); setPage(1); }} placeholder="جستجو بر اساس نام یا پزشک..." />
columns={columns} data={items} loading={isLoading} emptyMessage="هیچ منشی‌ای یافت نشد" actions={(sec) => ( <> )} />
setEditTarget(null)} footer={ <> } > {catalog.isLoading ? (

در حال بارگذاری فهرست دسترسی‌ها...

) : catalog.isError ? (

فهرست دسترسی‌ها خوانده نشد. صفحه را دوباره باز کنید.

) : ( )}
deleteTarget && deleteMutation.mutate(deleteTarget)} onCancel={() => setDeleteTarget(null)} />
); }