@@ -5,13 +5,12 @@ import {
55 IconButton ,
66 Tooltip ,
77 Typography ,
8- Stack ,
98} from '@mui/material' ;
109import EditIcon from '@mui/icons-material/Edit' ;
1110import CheckIcon from '@mui/icons-material/Check' ;
1211import CloseIcon from '@mui/icons-material/Close' ;
1312import RestoreIcon from '@mui/icons-material/Restore' ;
14- import { formatHours } from '../../utils/timeUtils' ;
13+ import { formatHours , decimalToHoursAndMinutes , hoursAndMinutesToDecimal } from '../../utils/timeUtils' ;
1514
1615interface HourEditorProps {
1716 hours : number ;
@@ -33,19 +32,25 @@ export const HourEditor: React.FC<HourEditorProps> = ({
3332 disabled = false ,
3433} ) => {
3534 const [ isEditing , setIsEditing ] = useState ( false ) ;
36- const [ editValue , setEditValue ] = useState ( hours . toString ( ) ) ;
35+ const { hours : wholeHours , minutes } = decimalToHoursAndMinutes ( hours ) ;
36+ const [ editHours , setEditHours ] = useState ( wholeHours . toString ( ) ) ;
37+ const [ editMinutes , setEditMinutes ] = useState ( minutes . toString ( ) ) ;
3738 const [ error , setError ] = useState < string | undefined > ( ) ;
3839
3940 useEffect ( ( ) => {
4041 if ( ! isEditing ) {
41- setEditValue ( hours . toString ( ) ) ;
42+ const { hours : h , minutes : m } = decimalToHoursAndMinutes ( hours ) ;
43+ setEditHours ( h . toString ( ) ) ;
44+ setEditMinutes ( m . toString ( ) ) ;
4245 }
4346 } , [ hours , isEditing ] ) ;
4447
4548 const handleStartEdit = ( ) => {
4649 if ( ! disabled ) {
4750 setIsEditing ( true ) ;
48- setEditValue ( hours . toString ( ) ) ;
51+ const { hours : h , minutes : m } = decimalToHoursAndMinutes ( hours ) ;
52+ setEditHours ( h . toString ( ) ) ;
53+ setEditMinutes ( m . toString ( ) ) ;
4954 setError ( undefined ) ;
5055 }
5156 } ;
@@ -56,13 +61,21 @@ export const HourEditor: React.FC<HourEditorProps> = ({
5661 } ;
5762
5863 const handleSave = ( ) => {
59- const newHours = parseFloat ( editValue ) ;
64+ const h = parseInt ( editHours , 10 ) ;
65+ const m = parseInt ( editMinutes , 10 ) ;
6066
61- if ( isNaN ( newHours ) || newHours < 0 ) {
62- setError ( 'Please enter a valid number ' ) ;
67+ if ( isNaN ( h ) || h < 0 || isNaN ( m ) || m < 0 ) {
68+ setError ( 'Please enter valid numbers ' ) ;
6369 return ;
6470 }
6571
72+ if ( m >= 60 ) {
73+ setError ( 'Minutes must be less than 60' ) ;
74+ return ;
75+ }
76+
77+ const newHours = hoursAndMinutesToDecimal ( h , m ) ;
78+
6679 if ( validate ) {
6780 const validation = validate ( newHours ) ;
6881 if ( ! validation . isValid ) {
@@ -85,101 +98,112 @@ export const HourEditor: React.FC<HourEditorProps> = ({
8598 } ;
8699
87100 return (
88- < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
89- { isEditing ? (
90- < >
91- < TextField
92- size = "small"
93- value = { editValue }
94- onChange = { ( e ) => setEditValue ( e . target . value ) }
95- onKeyDown = { handleKeyPress }
96- error = { ! ! error }
97- helperText = { error }
98- autoFocus
99- inputProps = { {
100- type : 'number' ,
101- step : '0.25' ,
102- min : '0' ,
103- style : { width : '80px' } ,
104- } }
105- sx = { { width : '100px' } }
106- />
107- < IconButton size = "small" onClick = { handleSave } color = "primary" >
108- < CheckIcon />
109- </ IconButton >
110- < IconButton size = "small" onClick = { handleCancel } >
111- < CloseIcon />
112- </ IconButton >
113- </ >
114- ) : (
115- < >
116- < Tooltip
117- title = {
118- isManuallySet && originalHours !== undefined
119- ? `Original: ${ originalHours . toFixed ( 2 ) } hours`
120- : disabled
121- ? 'Hour editing is disabled'
122- : 'Click to edit hours'
123- }
124- >
125- < Box
126- onClick = { handleStartEdit }
127- sx = { {
128- cursor : disabled ? 'default' : 'pointer' ,
129- display : 'flex' ,
130- alignItems : 'flex-start' ,
131- gap : 0.5 ,
132- } }
133- >
134- < Stack spacing = { 0 } >
135- < Typography
136- variant = "body1"
101+ < Box sx = { { display : 'flex' , flexDirection : 'column' , gap : 0.5 } } >
102+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 , flexWrap : 'nowrap' , height : '40px' } } >
103+ { isEditing ? (
104+ < >
105+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
106+ < TextField
107+ size = "small"
108+ value = { editHours }
109+ onChange = { ( e ) => setEditHours ( e . target . value ) }
110+ onKeyDown = { handleKeyPress }
111+ error = { ! ! error }
112+ label = "Hours"
113+ autoFocus
114+ inputProps = { {
115+ type : 'number' ,
116+ min : '0' ,
117+ style : { width : '60px' } ,
118+ } }
119+ sx = { { width : '80px' } }
120+ />
121+ < TextField
122+ size = "small"
123+ value = { editMinutes }
124+ onChange = { ( e ) => setEditMinutes ( e . target . value ) }
125+ onKeyDown = { handleKeyPress }
126+ error = { ! ! error }
127+ label = "Minutes"
128+ inputProps = { {
129+ type : 'number' ,
130+ min : '0' ,
131+ max : '59' ,
132+ style : { width : '60px' } ,
133+ } }
134+ sx = { { width : '80px' } }
135+ />
136+ </ Box >
137+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
138+ < IconButton size = "small" onClick = { handleSave } color = "primary" >
139+ < CheckIcon />
140+ </ IconButton >
141+ < IconButton size = "small" onClick = { handleCancel } >
142+ < CloseIcon />
143+ </ IconButton >
144+ </ Box >
145+ </ >
146+ ) : (
147+ < >
148+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 , height : '100%' } } >
149+ < Tooltip
150+ title = {
151+ isManuallySet && originalHours !== undefined
152+ ? `Original: ${ formatHours ( originalHours ) } `
153+ : disabled
154+ ? 'Hour editing is disabled'
155+ : 'Click to edit hours'
156+ }
157+ >
158+ < Box
159+ onClick = { handleStartEdit }
137160 sx = { {
138- color : isManuallySet ? 'primary.main' : 'text.primary' ,
139- fontWeight : isManuallySet ? 500 : 400 ,
140- fontSize : '1rem' ,
161+ cursor : disabled ? 'default' : 'pointer' ,
162+ display : 'flex' ,
163+ alignItems : 'center' ,
164+ gap : 1 ,
165+ height : '100%' ,
141166 } }
142167 >
143- { formatHours ( hours ) }
144- </ Typography >
145- < Box sx = { {
146- display : 'flex' ,
147- alignItems : 'center' ,
148- gap : 0.5 ,
149- mt : '2px !important'
150- } } >
151168 < Typography
152- variant = "caption "
169+ variant = "body1 "
153170 sx = { {
154- color : 'text.secondary' ,
155- fontSize : '0.75rem' ,
171+ color : isManuallySet ? 'primary.main' : 'text.primary' ,
172+ fontWeight : isManuallySet ? 500 : 400 ,
173+ fontSize : '1rem' ,
174+ lineHeight : '40px' ,
156175 } }
157176 >
158- { hours . toFixed ( 2 ) } hours
177+ { formatHours ( hours ) }
159178 </ Typography >
160- { ! disabled && < EditIcon fontSize = "small" sx = { { opacity : 0.5 , fontSize : '0.9rem' } } /> }
161- { isManuallySet && onReset && (
162- < Tooltip title = "Reset to auto-calculated hours" >
163- < IconButton
164- size = "small"
165- onClick = { ( e ) => {
166- e . stopPropagation ( ) ;
167- onReset ( ) ;
168- } }
169- sx = { {
170- p : 0.5 ,
171- ml : - 0.5
172- } }
173- >
174- < RestoreIcon sx = { { fontSize : '0.9rem' } } />
175- </ IconButton >
176- </ Tooltip >
179+ { ! disabled && (
180+ < Box sx = { { display : 'flex' , alignItems : 'center' , height : '100%' } } >
181+ < EditIcon fontSize = "small" sx = { { opacity : 0.5 , fontSize : '0.9rem' } } />
182+ </ Box >
177183 ) }
178184 </ Box >
179- </ Stack >
185+ </ Tooltip >
186+ { isManuallySet && onReset && (
187+ < Tooltip title = "Reset to auto-calculated hours" >
188+ < IconButton
189+ size = "small"
190+ onClick = { ( e ) => {
191+ e . stopPropagation ( ) ;
192+ onReset ( ) ;
193+ } }
194+ >
195+ < RestoreIcon sx = { { fontSize : '0.9rem' } } />
196+ </ IconButton >
197+ </ Tooltip >
198+ ) }
180199 </ Box >
181- </ Tooltip >
182- </ >
200+ </ >
201+ ) }
202+ </ Box >
203+ { isEditing && error && (
204+ < Typography variant = "caption" color = "error" sx = { { pl : 1 } } >
205+ { error }
206+ </ Typography >
183207 ) }
184208 </ Box >
185209 ) ;
0 commit comments