@@ -493,7 +493,14 @@ function SessionEditor({ session, onChange, onRemove, addresses, serviceMode = f
// ── Weekly Schedule Tab ────────────────────────────────────────────────────
export function WeeklyScheduleTab ( { doctorUuid , addresses , readOnly = false } : { doctorUuid : string ; addresses : AddressData [ ] ; readOnly? : boolean } ) {
/**
* هر درخواست تنظیمات نوبتدهی باید محیطش را حمل کند: بدون clinic_uuid یعنی مطب
* شخصی پزشک، و با آن یعنی همان پزشک داخل آن کلینیک. این دو دادهی جدا دارند.
*/
const withClinic = ( url : string , clinicUuid? : string | null ) : string = >
clinicUuid ? ` ${ url } ${ url . includes ( '?' ) ? '&' : '?' } clinic_uuid= ${ encodeURIComponent ( clinicUuid ) } ` : url ;
export function WeeklyScheduleTab ( { doctorUuid , clinicUuid , addresses , readOnly = false } : { doctorUuid : string ; clinicUuid? : string | null ; addresses : AddressData [ ] ; readOnly? : boolean } ) {
const qc = useQueryClient ( ) ;
const [ scheduleMap , setScheduleMap ] = useState < NewScheduleMap > ( EMPTY_NEW_SCHEDULE ) ;
const [ scheduleUuid , setScheduleUuid ] = useState < string | null > ( null ) ;
@@ -504,8 +511,8 @@ export function WeeklyScheduleTab({ doctorUuid, addresses, readOnly = false }: {
const [ confirmMode , setConfirmMode ] = useState ( false ) ;
const scheduleQ = useQuery ( {
queryKey : [ 'doctor-schedule' , doctorUuid ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( ` /api/v1/appointment-settings/weekly-schedule/ ${ doctorUuid } ` ) ,
queryKey : [ 'doctor-schedule' , doctorUuid , clinicUuid ? ? null ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( withClinic ( ` /api/v1/appointment-settings/weekly-schedule/ ${ doctorUuid } ` , clinicUuid ) ) ,
staleTime : 0 ,
retry : ( count , err ) = > ! ( err instanceof ApiError && err . status === 404 ) ,
} ) ;
@@ -550,15 +557,15 @@ export function WeeklyScheduleTab({ doctorUuid, addresses, readOnly = false }: {
if ( hasAnyOverlap ) throw new Error ( 'تداخل زمانی در برنامه وجود دارد' ) ;
if ( missingLocation ) throw new Error ( 'مکان مطب برای همه بازههای فعال الزامی است' ) ;
return scheduleUuid
? api . patch < ApiResponse < any > > ( ` /api/v1/appointment-settings/weekly-schedule/ ${ doctorUuid } ` , { schedule : scheduleMap , meta } )
: api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/weekly-schedule' , { doctor_uuid : doctorUuid , schedule : scheduleMap , meta } ) ;
? api . patch < ApiResponse < any > > ( ` /api/v1/appointment-settings/weekly-schedule/ ${ doctorUuid } ` , { schedule : scheduleMap , meta , clinic_uuid : clinicUuid ? ? null } )
: api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/weekly-schedule' , { doctor_uuid : doctorUuid , schedule : scheduleMap , meta , clinic_uuid : clinicUuid ? ? null } ) ;
} ,
onSuccess : ( res ) = > {
const d : WeeklyScheduleData = res ? . data ? . data ? ? res ? . data ;
if ( d ? . uuid && ! scheduleUuid ) setScheduleUuid ( d . uuid ) ;
setModeLocked ( true ) ; // پس از ثبت، نوع نوبتدهی قفل میشود
toast . success ( 'برنامه هفتگی ذخیره شد' ) ;
qc . invalidateQueries ( { queryKey : [ 'doctor-schedule' , doctorUuid ] } ) ;
qc . invalidateQueries ( { queryKey : [ 'doctor-schedule' , doctorUuid , clinicUuid ? ? null ] } ) ;
} ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
} ) ;
@@ -712,7 +719,7 @@ export function WeeklyScheduleTab({ doctorUuid, addresses, readOnly = false }: {
< span className = "text-sm text-slate-600 dark:text-slate-400" > د ق ی ق ه < / span >
< / div >
< p className = "text-xs text-slate-400 dark:text-slate-500 leading-relaxed" >
م د ت ه ر ن و ب ت ا ز « م د ت س ر و ی س » ا ن ت خ ا ب ش د ه ت ع ی ی ن م ی ش و د . ل ا ز م ا س ت ح د ا ق ل ی ک س ر و ی س ب ا « ن م ا ی ش د ر ن و ب ت د ه ی » د ر ب خ ش < span className = "font-medium" > س ر و ی س ه ا < / span > ت ع ر ی ف ک ن ی د ، و گ ر ن ه ذ خ ی ر ه ن م ی ش و د .
م د ت ه ر ن و ب ت ا ز « م د ت س ر و ی س » ا ن ت خ ا ب ش د ه ت ع ی ی ن م ی ش و د . ل ا ز م ا س ت ح د ا ق ل ی ک س ر و ی س ب ا « ن م ا ی ش د ر ن و ب ت د ه ی » د ر ب خ ش < span className = "font-medium" > { clinicUuid ? 'سرویسهای کلینیک' : 'سرویسها' } < / span > ت ع ر ی ف ک ن ی د ، و گ ر ن ه ذ خ ی ر ه ن م ی ش و د .
< / p >
< / >
) : (
@@ -875,9 +882,9 @@ export function WeeklyScheduleTab({ doctorUuid, addresses, readOnly = false }: {
// ── Date Override Modal ────────────────────────────────────────────────────
function DateOverrideModal ( { open , onClose , existing , doctorUuid , onSaved , addresses } : {
function DateOverrideModal ( { open , onClose , existing , doctorUuid , clinicUuid , onSaved , addresses } : {
open : boolean ; onClose : ( ) = > void ;
existing : DateOverrideData | null ; doctorUuid : string ;
existing : DateOverrideData | null ; doctorUuid : string ; clinicUuid? : string | null ;
onSaved : ( ) = > void ; addresses : AddressData [ ] ;
} ) {
const [ dateStr , setDateStr ] = useState ( '' ) ;
@@ -917,7 +924,7 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, onSaved, addre
const body = { date : dateStr , active , reason : reason || undefined , custom_slots : active ? slots : [ ] } ;
if ( existing )
return api . patch < ApiResponse < any > > ( ` /api/v1/appointment-settings/date-override/ ${ existing . uuid } ` , body ) ;
return api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/date-override' , { . . . body , doctor_uuid : doctorUuid } ) ;
return api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/date-override' , { . . . body , doctor_uuid : doctorUuid , clinic_uuid : clinicUuid ? ? null } ) ;
} ,
onSuccess : ( ) = > { toast . success ( existing ? 'ویرایش شد' : 'تاریخ خاص اضافه شد' ) ; onSaved ( ) ; onClose ( ) ; } ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
@@ -1015,15 +1022,15 @@ function DateOverrideModal({ open, onClose, existing, doctorUuid, onSaved, addre
// ── Date Overrides Tab ─────────────────────────────────────────────────────
function DateOverridesTab ( { doctorUuid , addresses , readOnly = false } : { doctorUuid : string ; addresses : AddressData [ ] ; readOnly? : boolean } ) {
function DateOverridesTab ( { doctorUuid , clinicUuid , addresses , readOnly = false } : { doctorUuid : string ; clinicUuid? : string | null ; addresses : AddressData [ ] ; readOnly? : boolean } ) {
const qc = useQueryClient ( ) ;
const [ modalOpen , setModalOpen ] = useState ( false ) ;
const [ editing , setEditing ] = useState < DateOverrideData | null > ( null ) ;
const [ deletingUuid , setDeletingUuid ] = useState < string | null > ( null ) ;
const listQ = useQuery ( {
queryKey : [ 'doctor-overrides' , doctorUuid ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( ` /api/v1/appointment-settings/date-override/list/ ${ doctorUuid } ` ) ,
queryKey : [ 'doctor-overrides' , doctorUuid , clinicUuid ? ? null ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( withClinic ( ` /api/v1/appointment-settings/date-override/list/ ${ doctorUuid } ` , clinicUuid ) ) ,
staleTime : 0 ,
} ) ;
const overrides : DateOverrideData [ ] = useMemo (
@@ -1032,7 +1039,7 @@ function DateOverridesTab({ doctorUuid, addresses, readOnly = false }: { doctorU
const deleteMut = useMutation ( {
mutationFn : ( uuid : string ) = > api . delete < ApiResponse < any > > ( ` /api/v1/appointment-settings/date-override/ ${ uuid } ` ) ,
onSuccess : ( ) = > { toast . success ( 'حذف شد' ) ; setDeletingUuid ( null ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-overrides' , doctorUuid ] } ) ; } ,
onSuccess : ( ) = > { toast . success ( 'حذف شد' ) ; setDeletingUuid ( null ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-overrides' , doctorUuid , clinicUuid ? ? null ] } ) ; } ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
} ) ;
@@ -1103,8 +1110,8 @@ function DateOverridesTab({ doctorUuid, addresses, readOnly = false }: { doctorU
< / div >
) }
< DateOverrideModal open = { modalOpen } onClose = { ( ) = > { setModalOpen ( false ) ; setEditing ( null ) ; } }
existing = { editing } doctorUuid = { doctorUuid } addresses = { addresses }
onSaved = { ( ) = > qc . invalidateQueries ( { queryKey : [ 'doctor-overrides' , doctorUuid ] } ) } / >
existing = { editing } doctorUuid = { doctorUuid } clinicUuid = { clinicUuid } addresses = { addresses }
onSaved = { ( ) = > qc . invalidateQueries ( { queryKey : [ 'doctor-overrides' , doctorUuid , clinicUuid ? ? null ] } ) } / >
< ConfirmDialog open = { ! ! deletingUuid } title = "حذف تاریخ خاص" message = "آیا از حذف این تاریخ خاص اطمینان دارید؟"
confirmLabel = "بله، حذف کن" danger loading = { deleteMut . isPending }
onConfirm = { ( ) = > deletingUuid && deleteMut . mutate ( deletingUuid ) } onCancel = { ( ) = > setDeletingUuid ( null ) } / >
@@ -1114,9 +1121,9 @@ function DateOverridesTab({ doctorUuid, addresses, readOnly = false }: { doctorU
// ── Holiday Modal ──────────────────────────────────────────────────────────
function HolidayModal ( { open , onClose , existing , doctorUuid , onSaved } : {
function HolidayModal ( { open , onClose , existing , doctorUuid , clinicUuid , onSaved } : {
open : boolean ; onClose : ( ) = > void ;
existing : HolidayData | null ; doctorUuid : string ;
existing : HolidayData | null ; doctorUuid : string ; clinicUuid? : string | null ;
onSaved : ( ) = > void ;
} ) {
const [ startDate , setStartDate ] = useState ( '' ) ;
@@ -1143,7 +1150,7 @@ function HolidayModal({ open, onClose, existing, doctorUuid, onSaved }: {
const body = { start_date : startDate , end_date : endDate , reason : reason || undefined } ;
if ( existing )
return api . patch < ApiResponse < any > > ( ` /api/v1/appointment-settings/holidays/ ${ existing . uuid } ` , body ) ;
return api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/holidays' , { . . . body , doctor_uuid : doctorUuid } ) ;
return api . post < ApiResponse < any > > ( '/api/v1/appointment-settings/holidays' , { . . . body , doctor_uuid : doctorUuid , clinic_uuid : clinicUuid ? ? null } ) ;
} ,
onSuccess : ( ) = > { toast . success ( existing ? 'تعطیلات ویرایش شد' : 'تعطیلات اضافه شد' ) ; onSaved ( ) ; onClose ( ) ; } ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
@@ -1187,15 +1194,15 @@ function HolidayModal({ open, onClose, existing, doctorUuid, onSaved }: {
// ── Holidays Tab ───────────────────────────────────────────────────────────
function HolidaysTab ( { doctorUuid , readOnly = false } : { doctorUuid : string ; readOnly? : boolean } ) {
function HolidaysTab ( { doctorUuid , clinicUuid , readOnly = false } : { doctorUuid : string ; clinicUuid? : string | null ; readOnly? : boolean } ) {
const qc = useQueryClient ( ) ;
const [ modalOpen , setModalOpen ] = useState ( false ) ;
const [ editing , setEditing ] = useState < HolidayData | null > ( null ) ;
const [ deletingUuid , setDeletingUuid ] = useState < string | null > ( null ) ;
const listQ = useQuery ( {
queryKey : [ 'doctor-holidays' , doctorUuid ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( ` /api/v1/appointment-settings/holidays/list/ ${ doctorUuid } ` ) ,
queryKey : [ 'doctor-holidays' , doctorUuid , clinicUuid ? ? null ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( withClinic ( ` /api/v1/appointment-settings/holidays/list/ ${ doctorUuid } ` , clinicUuid ) ) ,
staleTime : 0 ,
} ) ;
const holidays : HolidayData [ ] = useMemo (
@@ -1204,13 +1211,13 @@ function HolidaysTab({ doctorUuid, readOnly = false }: { doctorUuid: string; rea
const deleteMut = useMutation ( {
mutationFn : ( uuid : string ) = > api . delete < ApiResponse < any > > ( ` /api/v1/appointment-settings/holidays/ ${ uuid } ` ) ,
onSuccess : ( ) = > { toast . success ( 'حذف شد' ) ; setDeletingUuid ( null ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid ] } ) ; } ,
onSuccess : ( ) = > { toast . success ( 'حذف شد' ) ; setDeletingUuid ( null ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid , clinicUuid ? ? null ] } ) ; } ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
} ) ;
const toggleMut = useMutation ( {
mutationFn : ( h : HolidayData ) = > api . patch < ApiResponse < any > > ( ` /api/v1/appointment-settings/holidays/ ${ h . uuid } ` , { active : ! h . active } ) ,
onSuccess : ( ) = > { toast . success ( 'وضعیت بروز شد' ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid ] } ) ; } ,
onSuccess : ( ) = > { toast . success ( 'وضعیت بروز شد' ) ; qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid , clinicUuid ? ? null ] } ) ; } ,
onError : ( e : Error ) = > toast . error ( e . message ) ,
} ) ;
@@ -1284,8 +1291,8 @@ function HolidaysTab({ doctorUuid, readOnly = false }: { doctorUuid: string; rea
< / div >
) }
< HolidayModal open = { modalOpen } onClose = { ( ) = > { setModalOpen ( false ) ; setEditing ( null ) ; } }
existing = { editing } doctorUuid = { doctorUuid }
onSaved = { ( ) = > qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid ] } ) } / >
existing = { editing } doctorUuid = { doctorUuid } clinicUuid = { clinicUuid }
onSaved = { ( ) = > qc . invalidateQueries ( { queryKey : [ 'doctor-holidays' , doctorUuid , clinicUuid ? ? null ] } ) } / >
< ConfirmDialog open = { ! ! deletingUuid } title = "حذف تعطیلات" message = "آیا از حذف این تعطیلات اطمینان دارید؟"
confirmLabel = "بله، حذف کن" danger loading = { deleteMut . isPending }
onConfirm = { ( ) = > deletingUuid && deleteMut . mutate ( deletingUuid ) } onCancel = { ( ) = > setDeletingUuid ( null ) } / >
@@ -1301,12 +1308,12 @@ const SCHEDULE_TABS = [
{ id : 'holidays' as const , label : 'تعطیلات' } ,
] ;
export function ScheduleSection ( { doctorUuid , readOnly = false } : { doctorUuid : string ; readOnly? : boolean } ) {
export function ScheduleSection ( { doctorUuid , clinicUuid , readOnly = false } : { doctorUuid : string ; clinicUuid? : string | null ; readOnly? : boolean } ) {
const [ tab , setTab ] = useState < 'weekly' | 'overrides' | 'holidays' > ( 'weekly' ) ;
const locationsQ = useQuery ( {
queryKey : [ 'available-locations' , doctorUuid ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( ` /api/v1/appointment-settings/available-locations/ ${ doctorUuid } ` ) ,
queryKey : [ 'available-locations' , doctorUuid , clinicUuid ? ? null ] ,
queryFn : ( ) = > api . get < ApiResponse < any > > ( withClinic ( ` /api/v1/appointment-settings/available-locations/ ${ doctorUuid } ` , clinicUuid ) ) ,
enabled : ! ! doctorUuid ,
staleTime : 30_000 ,
} ) ;
@@ -1327,9 +1334,9 @@ export function ScheduleSection({ doctorUuid, readOnly = false }: { doctorUuid:
< / button >
) ) }
< / div >
{ tab === 'weekly' && < WeeklyScheduleTab doctorUuid = { doctorUuid } addresses = { availableLocations } readOnly = { readOnly } / > }
{ tab === 'overrides' && < DateOverridesTab doctorUuid = { doctorUuid } addresses = { availableLocations } readOnly = { readOnly } / > }
{ tab === 'holidays' && < HolidaysTab doctorUuid = { doctorUuid } readOnly = { readOnly } / > }
{ tab === 'weekly' && < WeeklyScheduleTab doctorUuid = { doctorUuid } clinicUuid = { clinicUuid } addresses = { availableLocations } readOnly = { readOnly } / > }
{ tab === 'overrides' && < DateOverridesTab doctorUuid = { doctorUuid } clinicUuid = { clinicUuid } addresses = { availableLocations } readOnly = { readOnly } / > }
{ tab === 'holidays' && < HolidaysTab doctorUuid = { doctorUuid } clinicUuid = { clinicUuid } readOnly = { readOnly } / > }
< / div >
) ;
}