Skip to content

Commit 042fd7d

Browse files
committed
Release 2.0.2
1 parent 67c4eab commit 042fd7d

File tree

2 files changed

+117
-93
lines changed

2 files changed

+117
-93
lines changed

ai-harvest-timesheet/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ai-harvest-timesheet",
33
"private": true,
4-
"version": "2.0.1",
4+
"version": "2.0.2",
55
"description": "Automate time logging in Harvest based on Git commit history",
66
"author": "Suraj Adsul",
77
"main": "dist-electron/main.js",

ai-harvest-timesheet/src/components/timeEntry/HourEditor.tsx

Lines changed: 116 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import {
55
IconButton,
66
Tooltip,
77
Typography,
8-
Stack,
98
} from '@mui/material';
109
import EditIcon from '@mui/icons-material/Edit';
1110
import CheckIcon from '@mui/icons-material/Check';
1211
import CloseIcon from '@mui/icons-material/Close';
1312
import RestoreIcon from '@mui/icons-material/Restore';
14-
import { formatHours } from '../../utils/timeUtils';
13+
import { formatHours, decimalToHoursAndMinutes, hoursAndMinutesToDecimal } from '../../utils/timeUtils';
1514

1615
interface 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

Comments
 (0)