@@ -5,12 +5,16 @@ import { useDepartments } from '@/src/features/admin/hooks/useDepartments';
55import { useDoctors } from '../hooks/useDoctors' ;
66import { useAvailableSlots } from '../hooks/useAvailableSlots' ;
77import { useBookAppointment } from '../hooks/useBookAppointment' ;
8+
9+ import DoctorCard from './DoctorCard' ;
810import SlotSelector from './SlotSelector' ;
11+ import BookingStepper from './BookingStepper' ;
912
1013export default function BookingForm ( ) {
14+
15+ const [ date , setDate ] = useState ( '' ) ;
1116 const [ departmentId , setDepartmentId ] = useState ( '' ) ;
1217 const [ doctorId , setDoctorId ] = useState ( '' ) ;
13- const [ date , setDate ] = useState ( '' ) ;
1418 const [ selectedSlot , setSelectedSlot ] = useState < string | null > ( null ) ;
1519
1620 const { data : departments } = useDepartments ( ) ;
@@ -20,128 +24,178 @@ export default function BookingForm() {
2024
2125 const mutation = useBookAppointment ( ) ;
2226
23- function handleBook ( ) {
24- if ( ! selectedSlot || ! doctorId || ! date ) return ;
27+ let step = 1 ;
28+ if ( date ) step = 2 ;
29+ if ( departmentId ) step = 3 ;
30+ if ( doctorId ) step = 4 ;
31+
32+ function handleBook ( ) {
33+
34+ if ( ! selectedSlot || ! doctorId || ! date ) return ;
35+
36+ const fullDateTime = `${ date } T${ selectedSlot } :00` ;
2537
26- // Combine date + time into ISO timestamp
27- const fullDateTime = `${ date } T${ selectedSlot } :00` ;
38+ mutation . mutate ( {
39+ doctorId,
40+ appointmentTime : fullDateTime ,
41+ durationMinutes : 15 ,
42+ } ) ;
2843
29- mutation . mutate ( {
30- doctorId,
31- appointmentTime : fullDateTime ,
32- durationMinutes : 15 ,
33- } ) ;
34- }
44+ }
3545
3646 return (
37- < div className = "space-y-6 max-w-xl bg-white p-6 rounded-xl shadow-md" >
38-
39- < h2 className = "text-xl font-semibold" >
40- Book Appointment
41- </ h2 >
42-
43- { /* Department */ }
44- < div >
45- < label className = "block mb-1 font-medium" >
46- Department
47- </ label >
48-
49- < select
50- className = "border p-2 w-full rounded-lg"
51- value = { departmentId }
52- onChange = { ( e ) => {
53- setDepartmentId ( e . target . value ) ;
54- setDoctorId ( '' ) ;
55- setSelectedSlot ( null ) ;
56- } }
57- >
58- < option value = "" > Select Department</ option >
59-
60- { departments ?. map ( dep => (
61- < option key = { dep . id } value = { dep . id } >
62- { dep . name }
63- </ option >
64- ) ) }
65- </ select >
66- </ div >
6747
68- { /* Doctor */ }
69- < div >
70- < label className = "block mb-1 font-medium" >
71- Doctor
72- </ label >
73-
74- < select
75- className = "border p-2 w-full rounded-lg"
76- value = { doctorId }
77- onChange = { ( e ) => {
78- setDoctorId ( e . target . value ) ;
79- setSelectedSlot ( null ) ;
80- } }
81- disabled = { ! departmentId }
82- >
83- < option value = "" > Select Doctor</ option >
84-
85- { doctors ?. map ( doc => (
86- < option key = { doc . id } value = { doc . id } >
87- { doc . name } — { doc . job_title }
88- </ option >
89- ) ) }
90- </ select >
91- </ div >
48+ < div className = "w-full" >
49+
50+ { /* STEP INDICATOR */ }
51+
52+ < BookingStepper step = { step } />
53+
54+ { /* MAIN LAYOUT */ }
55+
56+ < div className = "grid grid-cols-1 lg:grid-cols-3 gap-10" >
57+
58+ { /* LEFT PANEL */ }
59+
60+ < div className = "bg-slate-50 border rounded-2xl p-6 space-y-6" >
61+
62+ < h2 className = "font-semibold text-lg" >
63+ Appointment Details
64+ </ h2 >
65+
66+ { /* Date */ }
67+
68+ < div >
69+
70+ < label className = "block mb-1 font-medium" >
71+ Select Date
72+ </ label >
73+
74+ < input
75+ type = "date"
76+ className = "border p-3 w-full rounded-lg"
77+ value = { date }
78+ onChange = { ( e ) => {
79+ setDate ( e . target . value ) ;
80+ setDoctorId ( '' ) ;
81+ setSelectedSlot ( null ) ;
82+ } }
83+ min = { new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] }
84+ />
85+
86+ </ div >
87+
88+ { /* Department */ }
89+
90+ < div >
91+
92+ < label className = "block mb-1 font-medium" >
93+ Choose Department
94+ </ label >
95+
96+ < select
97+ className = "border p-3 w-full rounded-lg"
98+ value = { departmentId }
99+ onChange = { ( e ) => {
100+ setDepartmentId ( e . target . value ) ;
101+ setDoctorId ( '' ) ;
102+ setSelectedSlot ( null ) ;
103+ } }
104+ disabled = { ! date }
105+ >
106+
107+ < option value = "" >
108+ Select Department
109+ </ option >
110+
111+ { departments ?. map ( dep => (
112+ < option key = { dep . id } value = { dep . id } >
113+ { dep . name }
114+ </ option >
115+ ) ) }
116+
117+ </ select >
118+
119+ </ div >
120+
121+ </ div >
122+
123+ { /* RIGHT PANEL */ }
124+
125+ < div className = "lg:col-span-2 space-y-8" >
126+
127+ { /* DOCTOR SECTION */ }
128+
129+ < div className = "bg-white border rounded-2xl p-6" >
130+
131+ < h3 className = "font-semibold mb-4" >
132+ Select Doctor
133+ </ h3 >
134+
135+ < div className = "grid md:grid-cols-2 gap-4" >
136+
137+ { doctors ?. map ( ( doc ) => (
138+
139+ < DoctorCard
140+ key = { doc . id }
141+ doctor = { doc }
142+ selected = { doctorId === doc . id }
143+ onSelect = { ( id ) => {
144+ setDoctorId ( id ) ;
145+ setSelectedSlot ( null ) ;
146+ } }
147+ />
148+
149+ ) ) }
150+
151+ </ div >
152+
153+ </ div >
154+
155+ { /* SLOT SECTION */ }
156+
157+ < div className = "bg-white border rounded-2xl p-6" >
158+
159+ < h3 className = "font-semibold mb-4" >
160+ Available Time Slots
161+ </ h3 >
162+
163+ { isLoading && (
164+ < p className = "text-sm text-gray-500" >
165+ Loading slots...
166+ </ p >
167+ ) }
168+
169+ { doctorId && (
170+ < SlotSelector
171+ slots = { slots }
172+ selected = { selectedSlot }
173+ onSelect = { setSelectedSlot }
174+ />
175+ ) }
176+
177+ </ div >
178+
179+ { /* BOOK BUTTON */ }
180+
181+ < button
182+ onClick = { handleBook }
183+ disabled = { ! selectedSlot || mutation . isPending }
184+ className = "bg-blue-600 hover:bg-blue-700 text-white p-4 rounded-xl w-full transition font-semibold"
185+ >
186+
187+ { mutation . isPending
188+ ? 'Booking Appointment...'
189+ : 'Confirm Appointment' }
190+
191+ </ button >
192+
193+ </ div >
92194
93- { /* Date */ }
94- < div >
95- < label className = "block mb-1 font-medium" >
96- Date
97- </ label >
98-
99- < input
100- type = "date"
101- className = "border p-2 w-full rounded-lg"
102- value = { date }
103- onChange = { ( e ) => {
104- setDate ( e . target . value ) ;
105- setSelectedSlot ( null ) ;
106- } }
107- disabled = { ! doctorId }
108- min = { new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] }
109- />
110195 </ div >
111196
112- { /* Slots */ }
113- { isLoading && < p > Loading available slots...</ p > }
114-
115- { slots . length > 0 && (
116- < SlotSelector
117- slots = { slots }
118- selected = { selectedSlot }
119- onSelect = { setSelectedSlot }
120- />
121- ) }
122-
123- { /* Button */ }
124- < button
125- onClick = { handleBook }
126- disabled = { ! selectedSlot || mutation . isPending }
127- className = "bg-blue-600 hover:bg-blue-700 text-white p-3 rounded-lg w-full transition"
128- >
129- { mutation . isPending ? 'Booking...' : 'Book Appointment' }
130- </ button >
131-
132- { /* Errors */ }
133- { mutation . isError && (
134- < p className = "text-red-500 text-sm" >
135- { ( mutation . error as any ) ?. response ?. data ?. error }
136- </ p >
137- ) }
138-
139- { /* Success */ }
140- { mutation . isSuccess && (
141- < p className = "text-green-600 text-sm" >
142- Appointment booked successfully!
143- </ p >
144- ) }
145197 </ div >
198+
146199 ) ;
200+
147201}
0 commit comments