fix: display correction amounts as h/m and replace decimal input with h:m fields
- Correction list now shows '13h 32m' instead of '13.65h', using formatDurationHoursMinutes (same formatter used everywhere else) - Sign shown as '−' (minus) for negative corrections instead of bare '-' - Correction input replaced with separate hours + minutes integer fields and a +/− toggle button, removing the awkward decimal entry
This commit is contained in:
@@ -60,7 +60,9 @@ function ClientTargetPanel({
|
|||||||
// Correction form state
|
// Correction form state
|
||||||
const [showCorrectionForm, setShowCorrectionForm] = useState(false);
|
const [showCorrectionForm, setShowCorrectionForm] = useState(false);
|
||||||
const [corrDate, setCorrDate] = useState('');
|
const [corrDate, setCorrDate] = useState('');
|
||||||
const [corrHours, setCorrHours] = useState('');
|
const [corrHoursInt, setCorrHoursInt] = useState('');
|
||||||
|
const [corrMins, setCorrMins] = useState('');
|
||||||
|
const [corrNegative, setCorrNegative] = useState(false);
|
||||||
const [corrDesc, setCorrDesc] = useState('');
|
const [corrDesc, setCorrDesc] = useState('');
|
||||||
const [corrError, setCorrError] = useState<string | null>(null);
|
const [corrError, setCorrError] = useState<string | null>(null);
|
||||||
const [corrSaving, setCorrSaving] = useState(false);
|
const [corrSaving, setCorrSaving] = useState(false);
|
||||||
@@ -152,9 +154,15 @@ function ClientTargetPanel({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setCorrError(null);
|
setCorrError(null);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
const hours = parseFloat(corrHours);
|
const h = parseInt(corrHoursInt || '0', 10);
|
||||||
if (isNaN(hours) || hours < -1000 || hours > 1000) {
|
const m = parseInt(corrMins || '0', 10);
|
||||||
setCorrError('Hours must be between -1000 and 1000');
|
if (isNaN(h) || isNaN(m) || h < 0 || m < 0 || m > 59 || (h === 0 && m === 0)) {
|
||||||
|
setCorrError('Enter a valid duration (at least 1 minute)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const totalHours = h + m / 60;
|
||||||
|
if (totalHours > 1000) {
|
||||||
|
setCorrError('Duration cannot exceed 1000 hours');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!corrDate) {
|
if (!corrDate) {
|
||||||
@@ -163,10 +171,15 @@ function ClientTargetPanel({
|
|||||||
}
|
}
|
||||||
setCorrSaving(true);
|
setCorrSaving(true);
|
||||||
try {
|
try {
|
||||||
|
const h = parseInt(corrHoursInt || '0', 10);
|
||||||
|
const m = parseInt(corrMins || '0', 10);
|
||||||
|
const hours = (h + m / 60) * (corrNegative ? -1 : 1);
|
||||||
const input: CreateCorrectionInput = { date: corrDate, hours, description: corrDesc || undefined };
|
const input: CreateCorrectionInput = { date: corrDate, hours, description: corrDesc || undefined };
|
||||||
await addCorrection.mutateAsync({ targetId: target.id, input });
|
await addCorrection.mutateAsync({ targetId: target.id, input });
|
||||||
setCorrDate('');
|
setCorrDate('');
|
||||||
setCorrHours('');
|
setCorrHoursInt('');
|
||||||
|
setCorrMins('');
|
||||||
|
setCorrNegative(false);
|
||||||
setCorrDesc('');
|
setCorrDesc('');
|
||||||
setShowCorrectionForm(false);
|
setShowCorrectionForm(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -359,7 +372,7 @@ function ClientTargetPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className={`text-xs font-semibold ${c.hours >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
<span className={`text-xs font-semibold ${c.hours >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||||
{c.hours >= 0 ? '+' : ''}{c.hours}h
|
{c.hours >= 0 ? '+' : '−'}{formatDurationHoursMinutes(Math.abs(c.hours) * 3600)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteCorrection(c.id)}
|
onClick={() => handleDeleteCorrection(c.id)}
|
||||||
@@ -385,17 +398,43 @@ function ClientTargetPanel({
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-24">
|
<div>
|
||||||
<label className="block text-xs text-gray-500 mb-0.5">Hours</label>
|
<label className="block text-xs text-gray-500 mb-0.5">Duration</label>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCorrNegative(v => !v)}
|
||||||
|
className={`text-xs px-1.5 py-1 rounded border font-mono font-bold transition-colors ${
|
||||||
|
corrNegative
|
||||||
|
? 'bg-red-50 border-red-300 text-red-600'
|
||||||
|
: 'bg-green-50 border-green-300 text-green-600'
|
||||||
|
}`}
|
||||||
|
title="Toggle positive / negative"
|
||||||
|
>
|
||||||
|
{corrNegative ? '−' : '+'}
|
||||||
|
</button>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={corrHours}
|
value={corrHoursInt}
|
||||||
onChange={e => setCorrHours(e.target.value)}
|
onChange={e => setCorrHoursInt(e.target.value)}
|
||||||
className="input text-xs py-1"
|
className="input text-xs py-1 w-14 text-center"
|
||||||
placeholder="+8 / -4"
|
placeholder="0h"
|
||||||
step="0.5"
|
min="0"
|
||||||
required
|
max="999"
|
||||||
|
step="1"
|
||||||
/>
|
/>
|
||||||
|
<span className="text-xs text-gray-400">:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={corrMins}
|
||||||
|
onChange={e => setCorrMins(e.target.value)}
|
||||||
|
className="input text-xs py-1 w-14 text-center"
|
||||||
|
placeholder="00m"
|
||||||
|
min="0"
|
||||||
|
max="59"
|
||||||
|
step="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user