import { CheckCircleIcon, IdentificationIcon, NoSymbolIcon, PencilIcon, PhoneIcon, PlusIcon, } from "@heroicons/react/24/outline"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { Link } from "react-router-dom"; import { toast } from "sonner"; import { z } from "zod"; import ConfirmDialog from "../components/ui/ConfirmDialog"; import DataTable, { type Column } from "../components/ui/DataTable"; import Modal from "../components/ui/Modal"; import PageHeader from "../components/ui/PageHeader"; import { ActiveBadge } from "../components/ui/StatusBadge"; import { useSubscription } from "../hooks/useSubscription"; import type { ApiResponse } from "../lib/api"; import { api } from "../lib/api"; import { formatDate, maskMobile } from "../lib/utils"; import { useAuthStore } from "../stores/authStore"; import type { Secretary, SecretaryPermissions } from "../types"; // ── Default permissions & labels ────────────────────────────────────────── const DEFAULT_PERMISSIONS: SecretaryPermissions = { appointments: { view: true, create: false, cancel: false, update_status: false, }, addresses: { view: true, create: false, update: false, delete: false }, clinic_info: { view: true, update: false }, insurances: { view: true, create: false, update: false, delete: false }, }; type PermSection = keyof SecretaryPermissions; const PERMISSION_LABELS: Record< PermSection, { label: string; actions: { key: string; label: string }[] } > = { appointments: { label: "نوبت‌ها", actions: [ { key: "view", label: "مشاهده" }, { key: "create", label: "ایجاد" }, { key: "cancel", label: "لغو" }, { key: "update_status", label: "تغییر وضعیت" }, ], }, addresses: { label: "آدرس‌ها", actions: [ { key: "view", label: "مشاهده" }, { key: "create", label: "ایجاد" }, { key: "update", label: "ویرایش" }, { key: "delete", label: "حذف" }, ], }, clinic_info: { label: "اطلاعات کلینیک", actions: [ { key: "view", label: "مشاهده" }, { key: "update", label: "ویرایش" }, ], }, insurances: { label: "بیمه‌ها", actions: [ { key: "view", label: "مشاهده" }, { key: "create", label: "ایجاد" }, { key: "update", label: "ویرایش" }, { key: "delete", label: "حذف" }, ], }, }; const ALL_ACTIONS = [ "view", "create", "update", "delete", "cancel", "update_status", ]; const ACTION_HEADERS = [ "مشاهده", "ایجاد", "ویرایش", "حذف", "لغو", "تغییر وضعیت", ]; function PermissionsMatrix({ permissions, onChange, }: { permissions: SecretaryPermissions; onChange: (p: SecretaryPermissions) => void; }) { const toggle = (section: PermSection, action: string) => { const cur = (permissions[section] as Record)[action]; onChange({ ...permissions, [section]: { ...(permissions[section] as Record), [action]: !cur, }, }); }; return (
{ACTION_HEADERS.map((h) => ( ))} {(Object.keys(PERMISSION_LABELS) as PermSection[]).map( (section, idx) => { const config = PERMISSION_LABELS[section]; const sectionPrm = permissions[section] as Record< string, boolean >; return ( {ALL_ACTIONS.map((action) => { const ac = config.actions.find( (a) => a.key === action, ); if (!ac) return ( ); return ( ); })} ); }, )}
بخش {h}
{config.label} toggle(section, action) } style={{ width: 16, height: 16, accentColor: "var(--primary)", cursor: "pointer", }} />
); } // ── Create form schema ───────────────────────────────────────────────────── const createSchema = z.object({ name: z.string().min(2, "نام الزامی است"), mobile_number: z.string().regex(/^09[0-9]{9}$/, "شماره موبایل معتبر نیست"), }); type CreateForm = z.infer; interface ClinicDoctor { uuid: string; name: string; } // ── Main component ───────────────────────────────────────────────────────── export default function MySecretariesPage() { const qc = useQueryClient(); const { doctorUuid, dbUuid, primaryRole } = useAuthStore(); const isClinic = primaryRole === "clinic"; const { maxSecretaries } = useSubscription(); // for clinic: selected doctor to add secretary for const [selectedDoctorUuid, setSelectedDoctorUuid] = useState(""); const activeDoctorUuid = isClinic ? selectedDoctorUuid : (doctorUuid ?? ""); const [createOpen, setCreateOpen] = useState(false); const [editTarget, setEditTarget] = useState(null); const [editPerms, setEditPerms] = useState(DEFAULT_PERMISSIONS); const [deleteTarget, setDeleteTarget] = useState(null); // clinic: load clinic's doctors const { data: clinicDoctorsData, isLoading: clinicDoctorsLoading } = useQuery>({ queryKey: ["clinic-doctors", dbUuid], queryFn: () => api.get(`/api/v1/clinic/doctor-list/${dbUuid}`), enabled: isClinic && !!dbUuid, }); const clinicDoctors: ClinicDoctor[] = clinicDoctorsData?.data?.data ?? []; // clinic: load ALL secretaries across all its doctors const { data: clinicSecrData, isLoading: clinicSecrLoading } = useQuery< ApiResponse >({ queryKey: ["my-secretaries-clinic", dbUuid], queryFn: () => api.get(`/api/v1/secretaries/clinic/${dbUuid}`), enabled: isClinic && !!dbUuid, }); // doctor: load secretaries for the doctor const { data: doctorSecrData, isLoading: doctorSecrLoading } = useQuery< ApiResponse >({ queryKey: ["my-secretaries", activeDoctorUuid], queryFn: () => api.get(`/api/v1/secretaries/${activeDoctorUuid}`), enabled: !isClinic && !!activeDoctorUuid, }); const secretaries = isClinic ? (clinicSecrData?.data ?? []) : (doctorSecrData?.data ?? []); const activeSecretaryCount = secretaries.filter((s) => s.is_active).length; const isLoading = isClinic ? clinicSecrLoading : doctorSecrLoading; const createForm = useForm({ resolver: zodResolver(createSchema), }); const createMutation = useMutation({ mutationFn: (body: CreateForm) => api.post("/api/v1/secretary", { ...body, doctor_uuid: activeDoctorUuid, }), onSuccess: () => { toast.success("منشی اضافه شد"); setCreateOpen(false); createForm.reset(); qc.invalidateQueries({ queryKey: ["my-secretaries"] }); qc.invalidateQueries({ queryKey: ["my-secretaries-clinic"] }); }, onError: (e: any) => toast.error(e.message), }); const updatePermsMutation = useMutation({ mutationFn: ({ uuid, permissions, }: { uuid: string; permissions: SecretaryPermissions; }) => api.patch(`/api/v1/secretary/${uuid}`, { permissions: { version: 1, resources: permissions }, }), onSuccess: () => { toast.success("دسترسی‌ها بروزرسانی شد"); setEditTarget(null); qc.invalidateQueries({ queryKey: ["my-secretaries"] }); qc.invalidateQueries({ queryKey: ["my-secretaries-clinic"] }); }, onError: (e: any) => toast.error(e.message), }); const toggleActiveMutation = useMutation({ mutationFn: ({ uuid, active }: { uuid: string; active: boolean }) => api.patch(`/api/v1/secretary/${uuid}`, { active }), onSuccess: (_, { active }) => { toast.success(active ? "منشی فعال شد" : "منشی غیرفعال شد"); setDeleteTarget(null); qc.invalidateQueries({ queryKey: ["my-secretaries"] }); qc.invalidateQueries({ queryKey: ["my-secretaries-clinic"] }); }, onError: (e: any) => toast.error(e.message), }); const openEdit = (s: Secretary) => { setEditTarget(s); setEditPerms(s.permissions ?? DEFAULT_PERMISSIONS); }; const handleCreateOpen = () => { if (isClinic && !selectedDoctorUuid) { toast.error("ابتدا یک پزشک را انتخاب کنید"); return; } setCreateOpen(true); }; const selectedDoctorName = clinicDoctors.find((d) => d.uuid === selectedDoctorUuid)?.name ?? ""; // for clinic: show doctor column in the table const clinicColumns: Column[] = isClinic ? [ { key: "doctor_name", header: "پزشک", render: (s) => ( {s.doctor_name} ), }, ] : []; const allColumns: Column[] = [ { key: "user_name", header: "منشی", render: (s) => (
{(s.user_name ?? "?").charAt(0)}
{s.user_name}
), }, ...clinicColumns, { key: "mobile_number", header: "موبایل", render: (s) => (
{maskMobile(s.mobile_number)}
), }, { key: "is_active", header: "وضعیت", render: (s) => , }, { key: "created_at", header: "تاریخ ثبت", render: (s) => ( {formatDate(Number(s.created_at))} ), }, { key: "uuid", header: "عملیات", render: (s) => (
), }, ]; return ( <> = maxSecretaries ? ( ارتقاء پنل برای افزودن منشی ) : ( ) } /> {/* کلینیک: انتخاب پزشک برای افزودن منشی */} {isClinic && (
{clinicDoctorsLoading ? (

در حال بارگذاری...

) : clinicDoctors.length === 0 ? (

هیچ پزشکی در این کلینیک تعریف نشده است. ابتدا پزشک اضافه کنید.

) : ( )}
)} {!isClinic && !doctorUuid ? (
پروفایل پزشک یافت نشد
) : (
{secretaries.length === 0 && !isLoading ? (
هنوز منشی‌ای اضافه نشده
{isClinic ? "برای افزودن منشی، ابتدا یک پزشک را از لیست بالا انتخاب کنید" : "منشی می‌تواند نوبت‌ها و اطلاعات کلینیک را مدیریت کند"}
{!isClinic && ( )}
) : ( )}
)} {/* Modal افزودن منشی */} { setCreateOpen(false); createForm.reset(); }} title="افزودن منشی جدید" size="sm" footer={ <> } > {isClinic && selectedDoctorName && (
منشی برای {selectedDoctorName} ثبت می‌شود
)}
createMutation.mutate(d), )} >
{createForm.formState.errors.name && ( {createForm.formState.errors.name.message} )}
{ const digits = e.target.value.replace( /\D/g, "", ); createForm.setValue("mobile_number", digits, { shouldValidate: createForm.formState.isSubmitted, }); }} style={ createForm.formState.errors.mobile_number ? { borderColor: "var(--danger)" } : undefined } /> {createForm.formState.errors.mobile_number && ( { createForm.formState.errors.mobile_number .message } )}

شماره باید با ۰۹ شروع شده و ۱۱ رقم باشد. اگر کاربری با این شماره در سیستم وجود داشته باشد، به عنوان منشی اضافه می‌شود.

{/* Modal ویرایش دسترسی‌ها */} setEditTarget(null)} title={`دسترسی‌های ${editTarget?.user_name ?? ""}`} size="lg" footer={ <> } > {/* Confirm تغییر وضعیت منشی */} deleteTarget && toggleActiveMutation.mutate({ uuid: deleteTarget.uuid, active: !deleteTarget.is_active, }) } onCancel={() => setDeleteTarget(null)} /> ); }