From 3d0537484cf692fe23c656fc6cd5e90122d46d39 Mon Sep 17 00:00:00 2001 From: EdiWeeks <121399761+EdiWeeks@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:52:43 +0000 Subject: [PATCH 1/2] fix: Allow any minute value (0-59) in time entry modal Previously minutes were restricted to 15-minute increments (step=15, max=45). Now users can enter any minute value from 0-59, enabling precise time logging like 1h 45m. Closes #177 Co-Authored-By: Claude Opus 4.6 --- src/components/timesheet/TimeEntryModal.tsx | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/timesheet/TimeEntryModal.tsx b/src/components/timesheet/TimeEntryModal.tsx index a57e638..b81ff23 100644 --- a/src/components/timesheet/TimeEntryModal.tsx +++ b/src/components/timesheet/TimeEntryModal.tsx @@ -33,14 +33,6 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP const [taskId, setTaskId] = useState(''); const [hours, setHours] = useState(''); const [minutes, setMinutes] = useState(''); - - // When hours is 24, minutes must be 0 - const handleHoursChange = (value: string) => { - setHours(value); - if (parseInt(value) >= 24) { - setMinutes('0'); - } - }; const [notes, setNotes] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [extensionInstalled, setExtensionInstalled] = useState(null); @@ -128,8 +120,8 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP setTaskId(task?.id || ''); const h = Math.floor(entry.hours); const m = Math.round((entry.hours - h) * 60); - setHours(h.toString()); - setMinutes(m.toString()); + setHours(h > 0 ? h.toString() : ''); + setMinutes(m > 0 ? m.toString() : ''); setNotes(entry.notes || ''); } else { // New entry - use matching customer option value @@ -341,15 +333,17 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP min="0" max="24" value={hours} - onChange={(e) => handleHoursChange(e.target.value)} + onChange={(e) => { + setHours(e.target.value); + if (parseInt(e.target.value) >= 24) setMinutes('0'); + }} placeholder="0" /> setMinutes(e.target.value)} placeholder="0" From 8c039d884d8a18b05657124870be21561b178780 Mon Sep 17 00:00:00 2001 From: EdiWeeks <121399761+EdiWeeks@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:05:26 +0000 Subject: [PATCH 2/2] fix: Address PR review - clamp inputs and fix floating-point rounding - Use totalMinutes approach to avoid Math.round rounding to 60 minutes - Clamp hours (0-24) and minutes (0-59) in onChange handlers - Validate/clamp values in handleSubmit before computing totalHours Co-Authored-By: Claude Opus 4.6 --- src/components/timesheet/TimeEntryModal.tsx | 29 ++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/components/timesheet/TimeEntryModal.tsx b/src/components/timesheet/TimeEntryModal.tsx index b81ff23..753a10d 100644 --- a/src/components/timesheet/TimeEntryModal.tsx +++ b/src/components/timesheet/TimeEntryModal.tsx @@ -118,8 +118,9 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP // Find task by code and use its id const task = project?.tasks.find((t) => t.code === entry.taskId); setTaskId(task?.id || ''); - const h = Math.floor(entry.hours); - const m = Math.round((entry.hours - h) * 60); + const totalMinutes = Math.round(entry.hours * 60); + const h = Math.floor(totalMinutes / 60); + const m = totalMinutes % 60; setHours(h > 0 ? h.toString() : ''); setMinutes(m > 0 ? m.toString() : ''); setNotes(entry.notes || ''); @@ -175,7 +176,9 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP e.preventDefault(); if (!date || !projectId || !taskId || isSubmitting) return; - const totalHours = (parseInt(hours) || 0) + (parseInt(minutes) || 0) / 60; + const h = Math.max(0, Math.min(24, parseInt(hours) || 0)); + const m = h >= 24 ? 0 : Math.max(0, Math.min(59, parseInt(minutes) || 0)); + const totalHours = h + m / 60; if (totalHours <= 0) return; const project = projects.find((p) => p.id === projectId); @@ -334,8 +337,14 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP max="24" value={hours} onChange={(e) => { - setHours(e.target.value); - if (parseInt(e.target.value) >= 24) setMinutes('0'); + const v = e.target.value; + if (v === '') { + setHours(''); + return; + } + const n = Math.max(0, Math.min(24, parseInt(v) || 0)); + setHours(n.toString()); + if (n >= 24) setMinutes('0'); }} placeholder="0" /> @@ -345,7 +354,15 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP min="0" max="59" value={minutes} - onChange={(e) => setMinutes(e.target.value)} + onChange={(e) => { + const v = e.target.value; + if (v === '') { + setMinutes(''); + return; + } + const n = Math.max(0, Math.min(59, parseInt(v) || 0)); + setMinutes(n.toString()); + }} placeholder="0" disabled={parseInt(hours) >= 24} />