@@ -2,14 +2,16 @@ import { Modal, statusColor } from "@/lib/components";
2
2
import { IoMdArrowDropdown } from "react-icons/io" ;
3
3
import { FaRegClock } from "react-icons/fa" ;
4
4
import { DatePickerFilter } from "./TimesheetFilterDate" ;
5
- import { useState } from "react" ;
5
+ import { FormEvent , useCallback , useState } from "react" ;
6
6
import { useTranslations } from "next-intl" ;
7
7
import { clsxm } from "@/app/utils" ;
8
8
import { Item , ManageOrMemberComponent , getNestedValue } from "@/lib/features/manual-time/manage-member-component" ;
9
9
import { useTeamTasks } from "@/app/hooks" ;
10
10
import { CustomSelect , TaskNameInfoDisplay } from "@/lib/features" ;
11
11
import { statusTable } from "./TimesheetAction" ;
12
12
import { TimesheetLog } from "@/app/interfaces" ;
13
+ import { secondsToTime } from "@/app/helpers" ;
14
+ import { useTimesheet } from "@/app/hooks/features/useTimesheet" ;
13
15
14
16
export interface IEditTaskModalProps {
15
17
isOpen : boolean ;
@@ -19,34 +21,43 @@ export interface IEditTaskModalProps {
19
21
export function EditTaskModal ( { isOpen, closeModal, dataTimesheet } : IEditTaskModalProps ) {
20
22
const { activeTeam } = useTeamTasks ( ) ;
21
23
const t = useTranslations ( ) ;
22
- // const [dateRange, setDateRange] = useState<{ from: Date | null }>({
23
- // from: new Date(),
24
- // });
25
- // const [endTime, setEndTime] = useState<string>('');
26
- // const [startTime, setStartTime] = useState<string>('');
27
- // const [isBillable, setIsBillable] = useState<boolean>(dataTimesheet.isBillable);
28
- // const [notes, setNotes] = useState('');
24
+ const { updateTimesheet } = useTimesheet ( { } )
29
25
30
- const [ dateRange , setDateRange ] = useState < { from : Date | null } > ( {
31
- from : dataTimesheet . timesheet ?. startedAt ? new Date ( dataTimesheet . timesheet . startedAt ) : new Date ( ) ,
26
+ const [ dateRange , setDateRange ] = useState < { date : Date | null } > ( {
27
+ date : dataTimesheet . timesheet ?. startedAt ? new Date ( dataTimesheet . timesheet . startedAt ) : new Date ( ) ,
32
28
} ) ;
33
- const [ endTime , setEndTime ] = useState < string > (
34
- dataTimesheet . timesheet ?. stoppedAt
35
- ? new Date ( dataTimesheet . timesheet . stoppedAt ) . toLocaleTimeString ( 'en-US' , { hour12 : false } ) . slice ( 0 , 5 )
36
- : ''
37
- ) ;
38
- const [ startTime , setStartTime ] = useState < string > (
39
- dataTimesheet . timesheet ?. startedAt
40
- ? new Date ( dataTimesheet . timesheet . startedAt ) . toLocaleTimeString ( 'en-US' , { hour12 : false } ) . slice ( 0 , 5 )
41
- : ''
42
- ) ;
43
- const [ isBillable , setIsBillable ] = useState < boolean > ( dataTimesheet . isBillable ) ;
44
- const [ notes , setNotes ] = useState < string > ( '' ) ;
29
+
30
+ const { h : hours , m : minutes } = secondsToTime ( dataTimesheet . timesheet . duration ) ;
31
+
32
+ const [ timeRange , setTimeRange ] = useState < { startTime : string ; endTime : string } > ( {
33
+ startTime : dataTimesheet . timesheet ?. startedAt
34
+ ? dataTimesheet . timesheet . startedAt . toString ( ) . slice ( 0 , 5 )
35
+ : '' ,
36
+ endTime : dataTimesheet . timesheet ?. stoppedAt
37
+ ? dataTimesheet . timesheet . stoppedAt . toString ( ) . slice ( 0 , 5 )
38
+ : '' ,
39
+ } ) ;
40
+
41
+ const updateTime = ( key : 'startTime' | 'endTime' , value : string ) => {
42
+ setTimeRange ( prevState => ( {
43
+ ...prevState ,
44
+ [ key ] : value ,
45
+ } ) ) ;
46
+ } ;
47
+ const [ timesheetData , setTimesheetData ] = useState ( {
48
+ isBillable : dataTimesheet . isBillable ,
49
+ projectId : dataTimesheet . project ?. id || '' ,
50
+ notes : dataTimesheet . description || '' ,
51
+ } ) ;
52
+
45
53
const memberItemsLists = {
46
54
Project : activeTeam ?. projects as [ ] ,
47
55
} ;
48
56
const handleSelectedValuesChange = ( values : { [ key : string ] : Item | null } ) => {
49
- // Handle value changes
57
+ setTimesheetData ( ( prev ) => ( {
58
+ ...prev ,
59
+ projectId : values [ 'Project' ] ?. id ,
60
+ } ) ) ;
50
61
} ;
51
62
const selectedValues = {
52
63
Teams : null ,
@@ -55,29 +66,74 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
55
66
// Handle field changes
56
67
} ;
57
68
69
+ const handleUpdateSubmit = useCallback ( async ( e : FormEvent < HTMLFormElement > ) => {
70
+ e . preventDefault ( ) ;
71
+ if ( ! timeRange . startTime || ! timeRange . endTime ) {
72
+ alert ( 'Please enter valid start and end times.' ) ;
73
+ return ;
74
+ }
75
+ if ( ! / ^ \d { 2 } : \d { 2 } $ / . test ( timeRange . startTime ) || ! / ^ \d { 2 } : \d { 2 } $ / . test ( timeRange . endTime ) ) {
76
+ alert ( 'Time format should be HH:MM.' ) ;
77
+ return ;
78
+ }
79
+
80
+ const baseDate = dateRange . date ?? new Date ( ) ;
81
+ const startedAt = new Date (
82
+ Date . UTC (
83
+ baseDate . getFullYear ( ) ,
84
+ baseDate . getMonth ( ) ,
85
+ baseDate . getDate ( ) ,
86
+ ...timeRange . startTime . split ( ':' ) . map ( Number )
87
+ )
88
+ ) ;
89
+ const stoppedAt = new Date (
90
+ Date . UTC (
91
+ baseDate . getFullYear ( ) ,
92
+ baseDate . getMonth ( ) ,
93
+ baseDate . getDate ( ) ,
94
+ ...timeRange . endTime . split ( ':' ) . map ( Number )
95
+ )
96
+ ) ;
97
+ await updateTimesheet ( {
98
+ id : dataTimesheet . timesheetId ,
99
+ isBillable : timesheetData . isBillable ,
100
+ employeeId : dataTimesheet . employeeId ,
101
+ logType : dataTimesheet . logType ,
102
+ source : dataTimesheet . source ,
103
+ startedAt : startedAt ,
104
+ stoppedAt : stoppedAt ,
105
+ tenantId : dataTimesheet . tenantId ,
106
+ organizationId : dataTimesheet . organizationId ,
107
+ description : timesheetData . notes ,
108
+ projectId : timesheetData . projectId ,
109
+ } ) ;
110
+ } , [ dateRange , timeRange , timesheetData , dataTimesheet , updateTimesheet ] ) ;
111
+
58
112
const fields = [
59
113
{
60
114
label : t ( 'sidebar.PROJECTS' ) ,
61
115
placeholder : 'Select a project' ,
62
116
isRequired : true ,
63
117
valueKey : 'id' ,
64
118
displayKey : 'name' ,
65
- element : 'Project'
119
+ element : 'Project' ,
120
+ defaultValue : dataTimesheet . project . name
121
+
66
122
} ,
67
123
] ;
68
124
69
125
const handleFromChange = ( fromDate : Date | null ) => {
70
- setDateRange ( ( prev ) => ( { ...prev , from : fromDate } ) ) ;
126
+ setDateRange ( ( prev ) => ( { ...prev , date : fromDate } ) ) ;
71
127
} ;
72
128
return (
73
129
< Modal
74
130
closeModal = { closeModal }
75
131
isOpen = { isOpen }
76
132
showCloseIcon
77
133
title = { 'Edit Task' }
78
- className = "bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:w-40 md: min-w-[30rem ] justify-start h-[auto]"
134
+ className = "bg-light--theme-light dark:bg-dark--theme-light p-5 rounded-xl w-full md:min-w-[32rem ] justify-start h-[auto]"
79
135
titleClass = "font-bold flex justify-start w-full" >
80
- < div className = "flex flex-col w-full" >
136
+ < form onSubmit = { handleUpdateSubmit } className = "flex flex-col w-full" >
81
137
< div className = "flex flex-col border-b border-b-slate-100 dark:border-b-gray-700" >
82
138
< TaskNameInfoDisplay
83
139
task = { dataTimesheet . task }
@@ -98,7 +154,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
98
154
< span className = "text-[#282048] dark:text-gray-500 " > { t ( 'dailyPlan.TASK_TIME' ) } </ span >
99
155
< div className = "flex items-center gap-x-2 " >
100
156
< FaRegClock className = "text-[#30B366]" />
101
- < span > 08:10h </ span >
157
+ < span > { hours } : { minutes } h </ span >
102
158
</ div >
103
159
</ div >
104
160
< div className = "flex items-center w-full" >
@@ -111,8 +167,11 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
111
167
aria-label = "Start time"
112
168
aria-describedby = "start-time-error"
113
169
type = "time"
114
- value = { startTime }
115
- onChange = { ( e ) => setStartTime ( e . target . value ) }
170
+ min = "00:00"
171
+ max = "23:59"
172
+ pattern = "[0-9]{2}:[0-9]{2}"
173
+ value = { timeRange . startTime }
174
+ onChange = { ( e ) => updateTime ( "startTime" , e . target . value ) }
116
175
className = "w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md"
117
176
required
118
177
/>
@@ -128,8 +187,8 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
128
187
aria-label = "End time"
129
188
aria-describedby = "end-time-error"
130
189
type = "time"
131
- value = { endTime }
132
- onChange = { ( e ) => setEndTime ( e . target . value ) }
190
+ value = { timeRange . endTime }
191
+ onChange = { ( e ) => updateTime ( 'endTime' , e . target . value ) }
133
192
className = "w-full p-1 border font-normal border-slate-300 dark:border-slate-600 dark:bg-dark--theme-light rounded-md"
134
193
required
135
194
/>
@@ -139,7 +198,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
139
198
< div >
140
199
< span className = "block text-[#282048] dark:text-gray-500 mr-2" > { t ( "manualTime.DATE" ) } </ span >
141
200
< DatePickerFilter
142
- date = { dateRange . from }
201
+ date = { dateRange . date }
143
202
setDate = { handleFromChange }
144
203
label = "Oct 01 2024"
145
204
/>
@@ -160,36 +219,45 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
160
219
< label className = "text-[#282048] dark:text-gray-500 mr-12 capitalize" > { t ( 'pages.timesheet.BILLABLE.BILLABLE' ) . toLowerCase ( ) } </ label >
161
220
< div className = "flex items-center gap-3" >
162
221
< ToggleButton
163
- isActive = { isBillable }
164
- onClick = { ( ) => setIsBillable ( true ) }
222
+ isActive = { timesheetData . isBillable }
223
+ onClick = { ( ) => setTimesheetData ( ( prev ) => ( {
224
+ ...prev ,
225
+ isBillable : true ,
226
+ } ) ) }
165
227
label = { t ( 'pages.timesheet.BILLABLE.YES' ) }
166
228
/>
167
229
< ToggleButton
168
- isActive = { ! isBillable }
169
- onClick = { ( ) => setIsBillable ( false ) }
230
+ isActive = { ! timesheetData . isBillable }
231
+ onClick = { ( ) => setTimesheetData ( ( prev ) => ( {
232
+ ...prev ,
233
+ isBillable : false ,
234
+ } ) ) }
170
235
label = { t ( 'pages.timesheet.BILLABLE.NO' ) }
171
236
/>
172
237
</ div >
173
238
</ div >
174
239
< div className = "w-full flex flex-col" >
175
240
< span className = "text-[#282048] dark:text-gray-400" > { t ( 'common.NOTES' ) } </ span >
176
241
< textarea
177
- value = { notes }
178
- onChange = { ( e ) => setNotes ( e . target . value ) }
242
+ value = { timesheetData . notes }
243
+ onChange = { ( e ) => setTimesheetData ( ( prev ) => ( {
244
+ ...prev ,
245
+ notes : e . target . value ,
246
+ } ) ) }
179
247
placeholder = "Insert notes here..."
180
248
className = { clsxm (
181
249
"bg-transparent focus:border-transparent focus:ring-2 focus:ring-transparent" ,
182
250
"placeholder-gray-300 placeholder:font-normal resize-none p-2 grow w-full" ,
183
251
"border border-gray-200 dark:border-slate-600 dark:bg-dark--theme-light rounded-md h-40 bg-[#FBB6500D]" ,
184
- notes . trim ( ) . length === 0 && "border-red-500"
252
+ timesheetData . notes . trim ( ) . length === 0 && "border-red-500"
185
253
) }
186
254
maxLength = { 120 }
187
255
minLength = { 0 }
188
256
aria-label = "Insert notes here"
189
257
required
190
258
/>
191
259
< div className = "text-sm text-[#282048] dark:text-gray-500 text-right" >
192
- { notes . length } /{ 120 }
260
+ { timesheetData . notes . length } /{ 120 }
193
261
</ div >
194
262
</ div >
195
263
< div className = "border-t border-t-gray-200 dark:border-t-gray-700 w-full" > </ div >
@@ -224,7 +292,7 @@ export function EditTaskModal({ isOpen, closeModal, dataTimesheet }: IEditTaskMo
224
292
</ div >
225
293
</ div >
226
294
</ div >
227
- </ div >
295
+ </ form >
228
296
229
297
</ Modal >
230
298
)
0 commit comments