1
1
import { useState } from "react" ;
2
- import { ChevronLeftIcon , ChevronRightIcon } from "../icon-components" ;
2
+ import {
3
+ ChevronLeftIcon ,
4
+ ChevronRightIcon ,
5
+ ClockIcon ,
6
+ } from "../icon-components" ;
3
7
import { Button } from "../ui/Button" ;
4
8
import { Modal } from "../ui/Modal" ;
5
9
import {
@@ -21,6 +25,7 @@ export default function DateTimePicker({
21
25
initialStartTime = "13:00" ,
22
26
initialEndTime = "18:00" ,
23
27
} : DateTimePickerProps ) {
28
+ const today = new Date ( ) ;
24
29
const [ currentDate , setCurrentDate ] = useState ( new Date ( ) ) ;
25
30
26
31
// Selected date range
@@ -31,15 +36,8 @@ export default function DateTimePicker({
31
36
endTime : initialEndTime ,
32
37
} ) ;
33
38
34
- // Selection mode (start or end date)
35
- const [ selectionMode , setSelectionMode ] = useState < "start" | "end" | null > (
36
- null
37
- ) ;
38
-
39
39
// Time picker state
40
- const [ timePickerOpen , setTimePickerOpen ] = useState < "start" | "end" | null > (
41
- null
42
- ) ;
40
+ const [ timePickerOpen , setTimePickerOpen ] = useState < "end" | null > ( null ) ;
43
41
44
42
// Navigate to previous month
45
43
const previousMonth = ( ) => {
@@ -63,50 +61,25 @@ export default function DateTimePicker({
63
61
const handleDateClick = ( day : number , month : number , year : number ) => {
64
62
const clickedDate = new Date ( year , month , day ) ;
65
63
66
- if (
67
- ! dateRange . startDate ||
68
- selectionMode === "start" ||
69
- ( dateRange . startDate && dateRange . endDate )
70
- ) {
71
- // Start new selection
72
- setDateRange ( {
73
- ...dateRange ,
74
- startDate : clickedDate ,
75
- endDate : null ,
76
- } ) ;
77
- setSelectionMode ( "end" ) ;
78
- } else {
79
- // Complete selection
80
- if ( clickedDate < dateRange . startDate ) {
81
- // If clicked date is before start date, swap them
82
- setDateRange ( {
83
- ...dateRange ,
84
- startDate : clickedDate ,
85
- endDate : dateRange . startDate ,
86
- } ) ;
87
- } else {
88
- setDateRange ( {
89
- ...dateRange ,
90
- endDate : clickedDate ,
91
- } ) ;
92
- }
93
- setSelectionMode ( null ) ;
64
+ if ( clickedDate <= today ) {
65
+ return ;
94
66
}
67
+
68
+ setDateRange ( {
69
+ ...dateRange ,
70
+ endDate : clickedDate ,
71
+ } ) ;
95
72
} ;
96
73
97
74
// Handle time selection
98
- const handleTimeChange = (
99
- type : "start" | "end" ,
100
- hours : number ,
101
- minutes : number
102
- ) => {
75
+ const handleTimeChange = ( hours : number , minutes : number ) => {
103
76
const formattedHours = hours . toString ( ) . padStart ( 2 , "0" ) ;
104
77
const formattedMinutes = minutes . toString ( ) . padStart ( 2 , "0" ) ;
105
78
const timeString = `${ formattedHours } :${ formattedMinutes } ` ;
106
79
107
80
setDateRange ( ( prev ) => ( {
108
81
...prev ,
109
- [ type === "start" ? "startTime" : " endTime" ] : timeString ,
82
+ endTime : timeString ,
110
83
} ) ) ;
111
84
} ;
112
85
@@ -192,6 +165,17 @@ export default function DateTimePicker({
192
165
return date . getTime ( ) === compareDate . getTime ( ) ;
193
166
} ;
194
167
168
+ // Check if a date should be disabled (current date or past dates)
169
+ const isDateDisabled = (
170
+ day : number ,
171
+ month : number ,
172
+ year : number
173
+ ) : boolean => {
174
+ const date = new Date ( year , month , day ) ;
175
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
176
+ return date <= today ;
177
+ } ;
178
+
195
179
// Get current month and year for display
196
180
const currentMonthName = new Intl . DateTimeFormat ( "en-US" , {
197
181
month : "long" ,
@@ -208,8 +192,10 @@ export default function DateTimePicker({
208
192
{ /* Month navigation */ }
209
193
< div className = "flex items-center justify-between mb-6" >
210
194
< button
195
+ type = "button"
211
196
onClick = { previousMonth }
212
- className = "p-2 rounded-full"
197
+ className = "p-2 rounded-full disabled:opacity-25"
198
+ disabled = { currentDate . getMonth ( ) === today . getMonth ( ) }
213
199
aria-label = "Previous month"
214
200
>
215
201
< ChevronLeftIcon />
@@ -218,6 +204,7 @@ export default function DateTimePicker({
218
204
{ currentMonthName } { currentYear }
219
205
</ h2 >
220
206
< button
207
+ type = "button"
221
208
onClick = { nextMonth }
222
209
className = "p-2 rounded-full"
223
210
aria-label = "Next month"
@@ -229,50 +216,43 @@ export default function DateTimePicker({
229
216
{ /* Date range inputs */ }
230
217
< div className = "flex items-center mb-6" >
231
218
< div className = "flex-1" >
232
- < button
233
- className = "w-full border border-gray-300 rounded-lg p-4 bg-transparent text-left"
234
- onClick = { ( ) => setSelectionMode ( "start" ) }
235
- >
236
- < div className = "text-gray-900 text-sm" >
237
- { dateRange . startDate ? formatDate ( dateRange . startDate ) : "" }
219
+ < div className = "w-full border border-gray-300 rounded-lg p-4 bg-gray-100 text-left cursor-not-allowed" >
220
+ < div className = "text-gray-500 text-sm" >
221
+ { formatDate ( dateRange . startDate ) }
238
222
</ div >
239
- </ button >
223
+ </ div >
240
224
</ div >
241
225
< div className = "mx-2 text-gray-400" > —</ div >
242
226
< div className = "flex-1" >
243
227
< button
228
+ type = "button"
244
229
className = "w-full border border-gray-300 rounded-lg p-4 bg-transparent text-left"
245
- onClick = { ( ) => dateRange . startDate && setSelectionMode ( "end" ) }
246
230
disabled = { ! dateRange . startDate }
247
231
>
248
232
< div className = "text-gray-900 text-sm" >
249
- { dateRange . endDate ? formatDate ( dateRange . endDate ) : "" }
233
+ { dateRange . endDate
234
+ ? formatDate ( dateRange . endDate )
235
+ : "Select end date" }
250
236
</ div >
251
237
</ button >
252
238
</ div >
253
239
</ div >
254
240
255
241
{ /* Time range inputs */ }
256
242
< div className = "flex items-center mb-8" >
257
- < TimePicker
258
- time = { dateRange . startTime }
259
- isOpen = { timePickerOpen === "start" }
260
- onTimeChange = { ( hours , minutes ) =>
261
- handleTimeChange ( "start" , hours , minutes )
262
- }
263
- onToggle = { ( ) =>
264
- setTimePickerOpen ( ( prev ) => ( prev === "start" ? null : "start" ) )
265
- }
266
- />
243
+ < div className = "flex-1" >
244
+ < div className = "w-full border border-gray-300 rounded-lg p-4 bg-gray-100 text-left cursor-not-allowed flex items-center gap-2" >
245
+ < ClockIcon />
246
+ < div className = "text-gray-500 text-sm" > { dateRange . startTime } </ div >
247
+ </ div >
248
+ </ div >
267
249
268
250
< div className = "mx-2 text-gray-400" > —</ div >
269
251
270
252
< TimePicker
271
253
time = { dateRange . endTime }
272
254
isOpen = { timePickerOpen === "end" }
273
- onTimeChange = { ( hours , minutes ) =>
274
- handleTimeChange ( "end" , hours , minutes )
275
- }
255
+ onTimeChange = { ( hours , minutes ) => handleTimeChange ( hours , minutes ) }
276
256
onToggle = { ( ) =>
277
257
setTimePickerOpen ( ( prev ) => ( prev === "end" ? null : "end" ) )
278
258
}
@@ -320,6 +300,11 @@ export default function DateTimePicker({
320
300
) }
321
301
isFirstInWeek = { dayIndex === 0 }
322
302
isLastInWeek = { dayIndex === 6 }
303
+ isDisabled = { isDateDisabled (
304
+ dateObj . day ,
305
+ dateObj . month ,
306
+ dateObj . year
307
+ ) }
323
308
onClick = { handleDateClick }
324
309
/>
325
310
) ) }
@@ -331,16 +316,18 @@ export default function DateTimePicker({
331
316
{ /* Footer */ }
332
317
< div className = "flex border-t border-gray-300 justify-evenly items-center py-4" >
333
318
< Button
319
+ type = "button"
334
320
variant = "outline"
335
321
className = "w-5/12"
336
322
onClick = { ( ) => onOpenChange ( false ) }
337
323
>
338
324
Cancel
339
325
</ Button >
340
326
< Button
341
- className = "w-5/12"
327
+ type = "button"
328
+ className = "w-5/12 disabled:opacity-50"
342
329
onClick = { handleApply }
343
- disabled = { ! dateRange . startDate || ! dateRange . endDate }
330
+ disabled = { ! dateRange . endDate }
344
331
>
345
332
Apply
346
333
</ Button >
0 commit comments