import { Bars3Icon, BanknotesIcon, BellIcon, CalendarDaysIcon, ChatBubbleLeftEllipsisIcon, ChatBubbleLeftRightIcon, CheckCircleIcon, ChevronDownIcon, ChevronRightIcon, ClipboardDocumentCheckIcon, ClipboardDocumentListIcon, ClockIcon, CreditCardIcon, DocumentTextIcon, FolderOpenIcon, FunnelIcon, MagnifyingGlassIcon, PaperClipIcon, PencilIcon, PhoneArrowUpRightIcon, PhoneIcon, PlusIcon, Squares2X2Icon, UserPlusIcon, UserIcon, UsersIcon, EllipsisHorizontalIcon, EyeIcon, } from "@heroicons/react/24/outline"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import React, { useCallback, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { z } from "zod"; import FeatureGate from "../components/ui/FeatureGate"; import Modal from "../components/ui/Modal"; import PageHeader from "../components/ui/PageHeader"; import Pagination from "../components/ui/Pagination"; import SearchableSelect from "../components/ui/SearchableSelect"; import PatientRecordInfoForm from "../components/PatientRecordInfoForm"; import { GENDER_OPTS, MARITAL_OPTS, EDUCATION_OPTS, REFERRAL_OPTS, profileToFormValues, formValuesToPayload, } from "../lib/patientForm"; import type { ApiResponse, PaginatedResponse } from "../lib/api"; import { api } from "../lib/api"; import { useIssueInvoice } from "../hooks/useIssueInvoice"; import { numericField } from "../lib/forms"; import { formatDate, formatDateTime, formatNumber, formatRial, } from "../lib/utils"; import type { PatientAppointment, PatientRecord, PatientSession, ServiceItem, ServiceSection, } from "../types"; const sessionSchema = z.object({ visit_price_rials: z.coerce.number().min(0), base_insurance_discount_percent: z.coerce.number().min(0).max(100), supplementary_discount_percent: z.coerce.number().min(0).max(100), insurance_base_id: z.coerce.number().optional(), insurance_supplementary_id: z.coerce.number().optional(), payment_method: z.enum(["cash", "card", "insurance", "online", "pending"]), notes: z.string().optional(), }); type SessionFormData = z.infer; interface PricingInsurance { insurance_id: number; insurance_name: string; type: string; patient_share_rials: number | null; } interface InsurancePricing { free_visit_price_rials: number; insurances: PricingInsurance[]; } const PAYMENT_LABELS: Record = { cash: "نقدی", card: "کارت", insurance: "بیمه", online: "آنلاین", pending: "در انتظار", }; const APPT_STATUS_LABELS: Record = { pending: "در انتظار", confirmed: "تایید شده", completed: "انجام شده", cancelled_by_doctor: "لغو توسط پزشک", cancelled_by_user: "لغو توسط بیمار", no_show: "عدم مراجعه", expired: "منقضی", }; const APPT_STATUS_BADGE: Record = { pending: "amber", confirmed: "blue", completed: "green", cancelled_by_doctor: "red", cancelled_by_user: "red", no_show: "gray", expired: "gray", }; const APPT_ACTIVE_STATUSES = ["pending", "confirmed"]; const EMPTY_RECORDS: PatientRecord[] = []; const EMPTY_SESSIONS: PatientSession[] = []; const PATIENT_TABS = [ { key: "visits", label: "مراجعات", icon: ClipboardDocumentCheckIcon }, { key: "info", label: "اطلاعات پرونده", icon: DocumentTextIcon }, { key: "appointments", label: "نوبت ها", icon: CalendarDaysIcon }, { key: "payments", label: "پرداخت ها", icon: CreditCardIcon }, { key: "wallet", label: "کیف پول", icon: BanknotesIcon }, { key: "messages", label: "پیام ها", icon: ChatBubbleLeftRightIcon }, { key: "callcenter", label: "کال سنتر", icon: PhoneArrowUpRightIcon }, { key: "attach", label: "ضمیمه", icon: PaperClipIcon }, { key: "records", label: "پرونده پزشکی", icon: ClipboardDocumentListIcon }, ] as const; const PLACEHOLDER_TABS: Record = { records: "پرونده پزشکی", attach: "ضمیمه", callcenter: "کال سنتر", messages: "پیام‌ها", wallet: "کیف پول", }; function BannerInfoRow({ icon, label, value, }: { icon: React.ReactNode; label: string; value: React.ReactNode; }) { return (
{icon}
{label} {value}
); } function getPatientName(record?: PatientRecord | null) { return record?.user_name || "—"; } function getPatientPhone(record?: PatientRecord | null) { return record?.user_mobile || "—"; } function getPatientNationalCode(record?: PatientRecord | null) { return record?.user_national_code || null; } function calcFinalPrice( visitPrice: number, baseDiscount: number, suppDiscount: number, servicesTotal: number, ) { const afterBase = visitPrice * (1 - baseDiscount / 100); const afterSupp = afterBase * (1 - suppDiscount / 100); return Math.round(afterSupp) + servicesTotal; } function MyPatientsPageInner() { const qc = useQueryClient(); const navigate = useNavigate(); const [selectedRecord, setSelectedRecord] = useState( null, ); const [page, setPage] = useState(1); const [search, setSearch] = useState(""); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [detailTab, setDetailTab] = useState< | "info" | "visits" | "appointments" | "payments" | "records" | "attach" | "callcenter" | "messages" | "wallet" >("visits"); const [expandedVisit, setExpandedVisit] = useState(null); const [invoiceUuid, setInvoiceUuid] = useState(null); // «مشاهده فاکتور»: نبودِ invoice_uuid یعنی اول صادر شود (idempotent create + finalize). const issueInvoiceMut = useIssueInvoice(selectedRecord?.uuid); const viewInvoice = (s: PatientSession) => { if (s.invoice_uuid) { setExpandedVisit(null); setInvoiceUuid(s.invoice_uuid); return; } issueInvoiceMut.mutate(s.uuid, { onSuccess: (iv) => { setExpandedVisit(null); setInvoiceUuid(iv); }, onError: () => toast.error("صدور فاکتور ناموفق بود"), }); }; const [sessionPage, setSessionPage] = useState(1); const [sessionModal, setSessionModal] = useState(false); const [editSession, setEditSession] = useState(null); const form = useForm({ resolver: zodResolver(sessionSchema), defaultValues: { visit_price_rials: 0, base_insurance_discount_percent: 0, supplementary_discount_percent: 0, payment_method: "cash", }, }); const watchVisit = form.watch("visit_price_rials") ?? 0; const watchBase = form.watch("base_insurance_discount_percent") ?? 0; const watchSupp = form.watch("supplementary_discount_percent") ?? 0; const [selectedServices, setSelectedServices] = useState< { service_item_uuid: string; name: string; price_rials: number }[] >([]); const [sectionUuid, setSectionUuid] = useState(""); const [itemUuid, setItemUuid] = useState(""); const [baseInsuranceId, setBaseInsuranceId] = useState(""); const [suppInsuranceId, setSuppInsuranceId] = useState(""); const [createRecordOpen, setCreateRecordOpen] = useState(false); const [searchMobile, setSearchMobile] = useState(""); const [foundUser, setFoundUser] = useState<{ uuid: string; name: string | null; mobile: string; national_code?: string | null; } | null>(null); const [recordNationalCode, setRecordNationalCode] = useState(""); const [searchError, setSearchError] = useState(""); const [notFound, setNotFound] = useState(false); const [newPatientName, setNewPatientName] = useState(""); const mobileInputRef = useRef(null); const [editOpen, setEditOpen] = useState(false); const servicesTotal = selectedServices.reduce( (sum, s) => sum + s.price_rials, 0, ); const finalPrice = calcFinalPrice( Number(watchVisit), Number(watchBase), Number(watchSupp), servicesTotal, ); const limit = 20; const { data: recordsData, isLoading } = useQuery< PaginatedResponse >({ queryKey: ["patients", page, search], queryFn: () => api.get( `/api/v1/patients?page=${page}&limit=${limit}&search=${encodeURIComponent(search)}`, ), }); const { data: recordDetail } = useQuery>({ queryKey: ["patient-detail", selectedRecord?.uuid], queryFn: () => api.get(`/api/v1/patient/${selectedRecord!.uuid}`), enabled: !!selectedRecord, }); const patientProfile = (recordDetail?.data as PatientRecord | undefined)?.profile ?? null; // استان/شهر برای فرم «اطلاعات پرونده» (منبع: دامنهٔ Location؛ شهر وابسته به استان) const [editProvinceId, setEditProvinceId] = useState(null); const provincesQ = useQuery({ queryKey: ["provinces"], queryFn: () => api.get("/api/v1/provinces"), staleTime: 600_000, }); const citiesQ = useQuery({ queryKey: ["cities", editProvinceId], queryFn: () => api.get(`/api/v1/cities${editProvinceId ? `?province_id=${editProvinceId}` : ""}`), staleTime: 300_000, }); const locOpts = (raw: any) => (raw?.data?.data ?? raw?.data ?? []).map((x: any) => ({ value: Number(x.id), label: x.name })); const { data: sessionsData, isLoading: sessionsLoading } = useQuery< PaginatedResponse >({ queryKey: ["patient-sessions", selectedRecord?.uuid, sessionPage], queryFn: () => api.get( `/api/v1/patient/${selectedRecord!.uuid}/sessions?page=${sessionPage}&limit=20`, ), enabled: !!selectedRecord, }); const { data: appointmentsData, isLoading: appointmentsLoading } = useQuery< ApiResponse >({ queryKey: ["patient-appointments", selectedRecord?.uuid], queryFn: () => api.get(`/api/v1/patient/${selectedRecord!.uuid}/appointments`), enabled: !!selectedRecord, }); const { data: sectionsData } = useQuery>({ queryKey: ["service-sections"], queryFn: () => api.get("/api/v1/service-sections"), }); const { data: itemsData } = useQuery>({ queryKey: ["service-items-for-session", sectionUuid], queryFn: () => api.get(`/api/v1/service-items/${sectionUuid}`), enabled: !!sectionUuid, }); const { data: pricingData } = useQuery>({ queryKey: ["insurance-pricing"], queryFn: () => api.get("/api/v1/insurance-pricing"), enabled: !!selectedRecord, }); const { data: invoiceData } = useQuery>({ queryKey: ["invoice", invoiceUuid], queryFn: () => api.get(`/api/v1/billing/invoices/${invoiceUuid}`), enabled: !!invoiceUuid, }); const invoice = (invoiceData?.data as any)?.data ?? (invoiceData?.data as any) ?? null; const records = recordsData?.data ?? EMPTY_RECORDS; const sessions = sessionsData?.data ?? EMPTY_SESSIONS; const appointments = appointmentsData?.data ?? []; const totalRec = recordsData?.meta?.totalRecords ?? 0; const totalSes = sessionsData?.meta?.totalRecords ?? 0; const selectedPatientName = getPatientName(selectedRecord); const selectedPatientPhone = getPatientPhone(selectedRecord); // نوبت بعدی: نزدیک‌ترین نوبت آینده که لغو نشده const nowSec = Math.floor(Date.now() / 1000); const nextAppointment = appointments .filter((a) => a.starts_at >= nowSec && APPT_ACTIVE_STATUSES.includes(a.status)) .sort((a, b) => a.starts_at - b.starts_at)[0]; // برای بنر: پرونده «تکمیل نشده» اگر مراجعه‌ی تسویه‌نشده دارد const hasUnpaid = sessions.some((s) => s.is_paid === false || (s.patient_debt_rials ?? 0) > 0); const sectionOptions = (sectionsData?.data ?? []).map((s) => ({ value: s.uuid, label: s.name, })); const itemOptions = (itemsData?.data ?? []) .filter((i) => i.active) .map((i) => ({ value: i.uuid, label: `${i.name} — ${formatRial(i.price_rials)}`, })); const pricing = (pricingData?.data as InsurancePricing | undefined) ?? undefined; const freeVisitPrice = pricing?.free_visit_price_rials ?? 0; const baseInsuranceOptions = (pricing?.insurances ?? []) .filter((i) => i.type === "basic") .map((i) => ({ value: String(i.insurance_id), label: i.insurance_name })); const suppInsuranceOptions = (pricing?.insurances ?? []) .filter((i) => i.type === "supplementary") .map((i) => ({ value: String(i.insurance_id), label: i.insurance_name })); // درصد تخفیف معادلِ سهم بیمار بر اساس قیمت آزاد. share=null یعنی پوشش ندارد (۰٪). const shareToDiscountPercent = (insuranceId: string): number => { if (!insuranceId || freeVisitPrice <= 0) return 0; const ins = pricing?.insurances.find((i) => String(i.insurance_id) === insuranceId); if (!ins || ins.patient_share_rials == null) return 0; const covered = Math.max(0, freeVisitPrice - ins.patient_share_rials); return Math.round((covered / freeVisitPrice) * 1000) / 10; }; const applyBaseInsurance = (insuranceId: string) => { setBaseInsuranceId(insuranceId); form.setValue("insurance_base_id", insuranceId ? Number(insuranceId) : undefined); if (insuranceId && freeVisitPrice > 0) { form.setValue("visit_price_rials", freeVisitPrice); form.setValue("base_insurance_discount_percent", shareToDiscountPercent(insuranceId)); } }; const applySuppInsurance = (insuranceId: string) => { setSuppInsuranceId(insuranceId); form.setValue("insurance_supplementary_id", insuranceId ? Number(insuranceId) : undefined); if (insuranceId) { form.setValue("supplementary_discount_percent", shareToDiscountPercent(insuranceId)); } }; const openEditInfo = () => { setEditProvinceId(patientProfile?.province_id ?? null); setEditOpen(true); }; const updatePatientMut = useMutation({ mutationFn: (body: object) => api.patch(`/api/v1/patient/${selectedRecord!.uuid}`, body), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patient-detail", selectedRecord?.uuid] }); qc.invalidateQueries({ queryKey: ["patients"] }); setEditOpen(false); toast.success("اطلاعات بیمار به‌روزرسانی شد"); }, onError: (e: any) => { toast.error(e?.message || "خطا در به‌روزرسانی اطلاعات بیمار"); }, }); const settleSessionMut = useMutation({ mutationFn: (uuid: string) => api.patch(`/api/v1/session/${uuid}`, { payment_method: "cash" }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patient-sessions", selectedRecord?.uuid] }); toast.success("پرداخت ثبت شد"); }, onError: (e: any) => toast.error(e?.message || "خطا در ثبت پرداخت"), }); const createSessionMut = useMutation({ mutationFn: (body: object) => api.post(`/api/v1/patient/${selectedRecord!.uuid}/session`, body), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patient-sessions", selectedRecord?.uuid], }); setSessionModal(false); form.reset(); setSelectedServices([]); setBaseInsuranceId(""); setSuppInsuranceId(""); toast.success("مراجعه ثبت شد"); }, onError: (e: any) => toast.error(e.message), }); const updateSessionMut = useMutation({ mutationFn: ({ uuid, body }: { uuid: string; body: object }) => api.patch(`/api/v1/session/${uuid}`, body), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patient-sessions", selectedRecord?.uuid], }); setEditSession(null); toast.success("مراجعه ویرایش شد"); }, onError: (e: any) => toast.error(e.message), }); const searchUserMut = useMutation({ mutationFn: (mobile: string) => api.get( `/api/v1/patient/search-user?mobile=${encodeURIComponent(mobile)}`, ), onSuccess: (res: any) => { setFoundUser(res?.data); setRecordNationalCode(res?.data?.national_code ?? ""); setNotFound(false); setSearchError(""); }, onError: () => { setFoundUser(null); setNotFound(true); setSearchError(""); }, }); const createRecordMut = useMutation({ mutationFn: (body: Record) => api.post("/api/v1/patient", body), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patients"] }); setCreateRecordOpen(false); setSearchMobile(""); setFoundUser(null); setRecordNationalCode(""); setNotFound(false); setNewPatientName(""); setSearchError(""); toast.success("پرونده بیمار ایجاد شد"); }, onError: (e: any) => toast.error(e.message), }); const handleSearchMobile = () => { const digits = searchMobile.replace(/\D/g, ""); if (!/^09\d{9}$/.test(digits)) { setSearchError("شماره موبایل معتبر نیست"); return; } setSearchError(""); setFoundUser(null); setNotFound(false); searchUserMut.mutate(digits); }; const handleCreateRecord = () => { if (foundUser) { createRecordMut.mutate({ user_uuid: foundUser.uuid, national_code: recordNationalCode || undefined, }); } else if (notFound) { createRecordMut.mutate({ mobile: searchMobile.replace(/\D/g, ""), name: newPatientName.trim(), national_code: recordNationalCode || undefined, }); } }; const handleCreateRecordClose = () => { setCreateRecordOpen(false); setSearchMobile(""); setFoundUser(null); setRecordNationalCode(""); setNotFound(false); setNewPatientName(""); setSearchError(""); }; const handleAddService = () => { if (!itemUuid) return; const found = itemsData?.data?.find((i) => i.uuid === itemUuid); if ( !found || selectedServices.some((s) => s.service_item_uuid === found.uuid) ) return; setSelectedServices((p) => [ ...p, { service_item_uuid: found.uuid, name: found.name, price_rials: found.price_rials, }, ]); setItemUuid(""); }; const handleSubmitSession = form.handleSubmit((d) => { createSessionMut.mutate({ ...d, services: selectedServices.map((s) => ({ service_item_uuid: s.service_item_uuid, })), }); }); const handleSearch = useCallback((v: string) => { setSearch(v); setPage(1); }, []); if (!selectedRecord) { return ( <> setCreateRecordOpen(true)} > پرونده جدید } />
handleSearch(e.target.value)} placeholder="جستجو بر اساس نام، شماره موبایل یا کد ملی..." />
{records.length === 0 && !isLoading ? (
{search ? "بیماری یافت نشد" : "هنوز بیماری ثبت نشده است"}
{search ? "عبارت جستجو را تغییر دهید" : "پس از ثبت نوبت، پرونده بیمار به طور خودکار ایجاد می‌شود"}
) : ( <>
{records.map((r) => { const open = () => { setSelectedRecord(r); setSessionPage(1); }; return viewMode === "grid" ? : ; })}
)} {/* Modal ایجاد پرونده دستی */} } >
بیمار باید قبلاً در سیستم ثبت‌نام کرده باشد. شماره موبایل او را وارد و جستجو کنید.
{ const digits = e.target.value.replace( /\D/g, "", ); setSearchMobile(digits); setFoundUser(null); setSearchError(""); }} onKeyDown={(e) => { if (e.key === "Enter") handleSearchMobile(); }} />
{searchError && ( {searchError} )}
{foundUser && (
{(foundUser.name ?? "؟").charAt(0)}
{foundUser.name ?? "بدون نام"}
{foundUser.mobile}
یافت شد
)} {notFound && (
کاربری با این شماره یافت نشد. برای ثبت بیمار جدید، نام را وارد کنید.
setNewPatientName(e.target.value) } placeholder="مثال: محمد محمدی" autoFocus />
setRecordNationalCode( e.target.value.replace(/\D/g, ""), ) } placeholder="۱۰ رقم" />
)}
); } return ( <> } /> {/* بنر اطلاعات بیمار — مطابق فیگما */}
{/* راست: نام، شماره پرونده، برچسب‌ها */}
{selectedPatientName} {hasUnpaid ? ( تکمیل نشده ) : ( تکمیل شده )}
شماره پرونده: {fileNumber(selectedRecord)}
افزودن برچسب ها:
{/* وسط: شماره تماس و تاریخ تشکیل پرونده */}
} label="شماره تماس:" value={{selectedPatientPhone}} /> } label="تاریخ تشکیل پرونده:" value={formatDate(selectedRecord.created_at)} />
{/* چپ: نوبت بعدی و دکمه یادداشت */}
نوبت بعدی:{" "} {nextAppointment ? formatDate(nextAppointment.starts_at) : "—"}
{PATIENT_TABS.map(({ key, label, icon: Icon }) => { const active = detailTab === key; return ( ); })}
{detailTab === "info" && patientProfile && (
اطلاعات بیمار
{([ ["نام کامل", patientProfile.full_name], ["نام پدر", patientProfile.fathers_name], ["کد ملی", patientProfile.national_code], ["جنسیت", patientProfile.gender === "male" ? "مرد" : patientProfile.gender === "female" ? "زن" : patientProfile.gender], ["تاریخ تولد", patientProfile.date_of_birth ? formatDate(patientProfile.date_of_birth) : null], ["گروه خونی", patientProfile.blood_type], ["وضعیت تأهل", patientProfile.marital_status], ["مقطع تحصیلی", patientProfile.education], ["رشته تحصیلی", patientProfile.field_of_study], ["شغل", patientProfile.job], ["موبایل", patientProfile.mobile], ["تلفن منزل", patientProfile.home_phone], ["بیمه پایه", patientProfile.basic_insurance_name], ["بیمه تکمیلی", patientProfile.supplementary_insurance_name], ["کد پستی", patientProfile.postal_code], ["نحوه آشنایی", patientProfile.referral_source], ["آدرس", patientProfile.address], ["توضیحات", patientProfile.description], ] as [string, string | null | undefined][]).map(([label, value]) => (
{label}
{value ? value : ثبت نشده}
))}
)} setEditOpen(false)} > updatePatientMut.mutate(formValuesToPayload(v))} isSubmitting={updatePatientMut.isPending} onProvinceChange={setEditProvinceId} /> {detailTab === "visits" && ( sessionsLoading ? (
در حال بارگذاری…
) : ( <>
{sessions.length === 0 ? (
هنوز مراجعه‌ای برای این بیمار ثبت نشده است.
) : ( <>
{sessions.map((s) => ( setExpandedVisit(s.uuid)} /> ))}
{totalSes > 20 && (
)} )} ) )} {detailTab === "payments" && ( sessionsLoading ? (
در حال بارگذاری…
) : sessions.length === 0 ? (
پرداختی برای این بیمار ثبت نشده است.
) : (
{sessions.map((s) => { const debt = s.patient_debt_rials ?? 0; const desc = (s.services ?? []).map((x) => x.service_name).join("، ") || "ویزیت"; return ( ); })}
تاریخ شرح مبلغ مانده بدهی وضعیت
{formatDate(s.created_at)} {desc} {formatRial(s.final_price_rials)} 0 ? "var(--danger)" : "var(--success)" }}> {debt > 0 ? formatRial(debt) : "ندارد"} {s.is_paid ? پرداخت شده : در انتظار} {s.is_paid ? ( s.invoice_uuid && ( ) ) : ( )}
) )} {detailTab === "appointments" && ( appointmentsLoading ? (
در حال بارگذاری…
) : appointments.length === 0 ? (
نوبتی برای این بیمار ثبت نشده است.
) : (
{appointments.map((a) => ( ))}
تاریخ و ساعت پزشک خدمت مبلغ وضعیت
{formatDateTime(a.starts_at)} {a.doctor_name || "—"} {a.service_name || "—"} {a.price_rials != null ? formatRial(a.price_rials) : "—"} {APPT_STATUS_LABELS[a.status] ?? a.status}
) )} {PLACEHOLDER_TABS[detailTab] && (
{PLACEHOLDER_TABS[detailTab]}
این بخش به‌زودی اضافه می‌شود.
)} s.uuid === expandedVisit) ?? null} onClose={() => setExpandedVisit(null)} settling={settleSessionMut.isPending} onSettle={(uuid) => settleSessionMut.mutate(uuid)} onViewInvoice={viewInvoice} issuing={issueInvoiceMut.isPending} onEdit={(s) => { setExpandedVisit(null); setEditSession(s); }} /> setInvoiceUuid(null)}> {!invoice ? (
در حال بارگذاری…
) : (
وضعیت{invoice.status}
مبلغ کل{formatRial(invoice.total_rials ?? 0)}
سهم بیمه پایه{formatRial(invoice.base_insurance_rials ?? 0)}
سهم بیمه تکمیلی{formatRial(invoice.supplementary_rials ?? 0)}
سهم بیمار{formatRial(invoice.patient_rials ?? 0)}
{Array.isArray(invoice.items) && invoice.items.length > 0 && (
اقلام
{invoice.items.map((it: any, i: number) => (
{it.title} {formatRial(it.total_rials ?? 0)}
))}
)}
)}
{/* Modal مراجعه جدید */} setSessionModal(false)} title="ثبت مراجعه جدید" size="lg" >
بیمه و مبلغ ویزیت
applyBaseInsurance(v ? String(v) : "")} placeholder="بدون بیمه پایه" isClearable height={38} />
applySuppInsurance(v ? String(v) : "")} placeholder="بدون بیمه تکمیلی" isClearable height={38} />
پرداخت و یادداشت
({ value: v, label: l }))} value={form.watch("payment_method")} onChange={(v) => form.setValue("payment_method", v as "cash" | "card" | "insurance" | "online" | "pending", { shouldDirty: true })} height={38} />