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) => (
{h} |
))}
{resources.map((resource) => {
const available = new Set(resource.actions.map((a) => a.key));
return (
| {resource.label} |
{allActions.map((action) => {
if (!available.has(action)) {
return — | ;
}
return (
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} منشی ثبتشده
columns={columns}
data={items}
loading={isLoading}
emptyMessage="هیچ منشیای یافت نشد"
actions={(sec) => (
<>
>
)}
/>
setEditTarget(null)}
footer={
<>
>
}
>
{catalog.isLoading ? (
در حال بارگذاری فهرست دسترسیها...
) : catalog.isError ? (
فهرست دسترسیها خوانده نشد. صفحه را دوباره باز کنید.
) : (
)}
deleteTarget && deleteMutation.mutate(deleteTarget)}
onCancel={() => setDeleteTarget(null)}
/>
);
}