fix(close-ticket): adiciona segundos na formatacao e ajuste de tempo
All checks were successful
All checks were successful
- Corrige formatacao de tempo para exibir segundos (ex: 2m 04s) - Adiciona campo de segundos nos inputs de ajuste de tempo - Melhora espacamento entre secoes de tempo interno e externo 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f9deb408dc
commit
ce52a4393b
1 changed files with 79 additions and 16 deletions
|
|
@ -107,8 +107,10 @@ type CloseTicketDraft = {
|
|||
shouldAdjustTime: boolean
|
||||
internalHours: string
|
||||
internalMinutes: string
|
||||
internalSeconds: string
|
||||
externalHours: string
|
||||
externalMinutes: string
|
||||
externalSeconds: string
|
||||
adjustReason: string
|
||||
linkedReference: string
|
||||
reopenWindowDays: string
|
||||
|
|
@ -128,17 +130,22 @@ function applyTemplatePlaceholders(html: string, customerName?: string | null, a
|
|||
|
||||
const splitDuration = (ms: number) => {
|
||||
const safeMs = Number.isFinite(ms) && ms > 0 ? ms : 0
|
||||
const totalMinutes = Math.round(safeMs / 60000)
|
||||
const hours = Math.floor(totalMinutes / 60)
|
||||
const minutes = totalMinutes % 60
|
||||
return { hours, minutes }
|
||||
const totalSeconds = Math.floor(safeMs / 1000)
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
return { hours, minutes, seconds }
|
||||
}
|
||||
|
||||
const formatDurationLabel = (ms: number) => {
|
||||
const { hours, minutes } = splitDuration(ms)
|
||||
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}min`
|
||||
if (hours > 0) return `${hours}h`
|
||||
return `${minutes}min`
|
||||
const { hours, minutes, seconds } = splitDuration(ms)
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes.toString().padStart(2, "0")}m`
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`
|
||||
}
|
||||
return `${seconds}s`
|
||||
}
|
||||
|
||||
const STATUS_LABELS: Record<TicketStatus, string> = {
|
||||
|
|
@ -218,8 +225,10 @@ export function CloseTicketDialog({
|
|||
const [shouldAdjustTime, setShouldAdjustTime] = useState<boolean>(false)
|
||||
const [internalHours, setInternalHours] = useState<string>("0")
|
||||
const [internalMinutes, setInternalMinutes] = useState<string>("0")
|
||||
const [internalSeconds, setInternalSeconds] = useState<string>("0")
|
||||
const [externalHours, setExternalHours] = useState<string>("0")
|
||||
const [externalMinutes, setExternalMinutes] = useState<string>("0")
|
||||
const [externalSeconds, setExternalSeconds] = useState<string>("0")
|
||||
const [adjustReason, setAdjustReason] = useState<string>("")
|
||||
const enableAdjustment = Boolean(canAdjustTime && workSummary)
|
||||
const [linkedReference, setLinkedReference] = useState<string>("")
|
||||
|
|
@ -277,8 +286,10 @@ export function CloseTicketDialog({
|
|||
setAdjustReason("")
|
||||
setInternalHours("0")
|
||||
setInternalMinutes("0")
|
||||
setInternalSeconds("0")
|
||||
setExternalHours("0")
|
||||
setExternalMinutes("0")
|
||||
setExternalSeconds("0")
|
||||
setLinkedReference("")
|
||||
setLinkedTicketSelection(null)
|
||||
setLinkSuggestions([])
|
||||
|
|
@ -306,8 +317,10 @@ export function CloseTicketDialog({
|
|||
setShouldAdjustTime(Boolean(parsed.shouldAdjustTime))
|
||||
setInternalHours(parsed.internalHours ?? "0")
|
||||
setInternalMinutes(parsed.internalMinutes ?? "0")
|
||||
setInternalSeconds(parsed.internalSeconds ?? "0")
|
||||
setExternalHours(parsed.externalHours ?? "0")
|
||||
setExternalMinutes(parsed.externalMinutes ?? "0")
|
||||
setExternalSeconds(parsed.externalSeconds ?? "0")
|
||||
setAdjustReason(parsed.adjustReason ?? "")
|
||||
setLinkedReference(parsed.linkedReference ?? "")
|
||||
setLinkedTicketSelection(null)
|
||||
|
|
@ -332,8 +345,10 @@ export function CloseTicketDialog({
|
|||
shouldAdjustTime,
|
||||
internalHours,
|
||||
internalMinutes,
|
||||
internalSeconds,
|
||||
externalHours,
|
||||
externalMinutes,
|
||||
externalSeconds,
|
||||
adjustReason,
|
||||
linkedReference,
|
||||
reopenWindowDays,
|
||||
|
|
@ -348,8 +363,10 @@ export function CloseTicketDialog({
|
|||
draftStorageKey,
|
||||
externalHours,
|
||||
externalMinutes,
|
||||
externalSeconds,
|
||||
internalHours,
|
||||
internalMinutes,
|
||||
internalSeconds,
|
||||
linkedReference,
|
||||
message,
|
||||
reopenWindowDays,
|
||||
|
|
@ -392,8 +409,10 @@ export function CloseTicketDialog({
|
|||
const external = splitDuration(workSummary?.externalWorkedMs ?? 0)
|
||||
setInternalHours(internal.hours.toString())
|
||||
setInternalMinutes(internal.minutes.toString())
|
||||
setInternalSeconds(internal.seconds.toString())
|
||||
setExternalHours(external.hours.toString())
|
||||
setExternalMinutes(external.minutes.toString())
|
||||
setExternalSeconds(external.seconds.toString())
|
||||
}, [
|
||||
open,
|
||||
enableAdjustment,
|
||||
|
|
@ -595,6 +614,12 @@ export function CloseTicketDialog({
|
|||
toast.error("Os minutos internos devem estar entre 0 e 59.")
|
||||
return
|
||||
}
|
||||
const internalSecondsValue = parsePart(internalSeconds, "segundos internos")
|
||||
if (internalSecondsValue === null) return
|
||||
if (internalSecondsValue >= 60) {
|
||||
toast.error("Os segundos internos devem estar entre 0 e 59.")
|
||||
return
|
||||
}
|
||||
|
||||
const externalHoursValue = parsePart(externalHours, "horas externas")
|
||||
if (externalHoursValue === null) return
|
||||
|
|
@ -604,9 +629,15 @@ export function CloseTicketDialog({
|
|||
toast.error("Os minutos externos devem estar entre 0 e 59.")
|
||||
return
|
||||
}
|
||||
const externalSecondsValue = parsePart(externalSeconds, "segundos externos")
|
||||
if (externalSecondsValue === null) return
|
||||
if (externalSecondsValue >= 60) {
|
||||
toast.error("Os segundos externos devem estar entre 0 e 59.")
|
||||
return
|
||||
}
|
||||
|
||||
targetInternalMs = (internalHoursValue * 60 + internalMinutesValue) * 60000
|
||||
targetExternalMs = (externalHoursValue * 60 + externalMinutesValue) * 60000
|
||||
targetInternalMs = (internalHoursValue * 3600 + internalMinutesValue * 60 + internalSecondsValue) * 1000
|
||||
targetExternalMs = (externalHoursValue * 3600 + externalMinutesValue * 60 + externalSecondsValue) * 1000
|
||||
trimmedReason = adjustReason.trim()
|
||||
if (trimmedReason.length < 5) {
|
||||
toast.error("Descreva o motivo do ajuste (mínimo de 5 caracteres).")
|
||||
|
|
@ -716,10 +747,10 @@ export function CloseTicketDialog({
|
|||
</div>
|
||||
{shouldAdjustTime ? (
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">Tempo interno</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="adjust-internal-hours" className="text-xs text-neutral-600">
|
||||
Horas
|
||||
|
|
@ -751,12 +782,28 @@ export function CloseTicketDialog({
|
|||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="adjust-internal-seconds" className="text-xs text-neutral-600">
|
||||
Segundos
|
||||
</Label>
|
||||
<Input
|
||||
id="adjust-internal-seconds"
|
||||
type="number"
|
||||
min={0}
|
||||
max={59}
|
||||
step={1}
|
||||
inputMode="numeric"
|
||||
value={internalSeconds}
|
||||
onChange={(event) => setInternalSeconds(event.target.value)}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.internalWorkedMs ?? 0)}</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">Tempo externo</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="adjust-external-hours" className="text-xs text-neutral-600">
|
||||
Horas
|
||||
|
|
@ -788,6 +835,22 @@ export function CloseTicketDialog({
|
|||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="adjust-external-seconds" className="text-xs text-neutral-600">
|
||||
Segundos
|
||||
</Label>
|
||||
<Input
|
||||
id="adjust-external-seconds"
|
||||
type="number"
|
||||
min={0}
|
||||
max={59}
|
||||
step={1}
|
||||
inputMode="numeric"
|
||||
value={externalSeconds}
|
||||
onChange={(event) => setExternalSeconds(event.target.value)}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.externalWorkedMs ?? 0)}</p>
|
||||
</div>
|
||||
|
|
@ -805,7 +868,7 @@ export function CloseTicketDialog({
|
|||
disabled={isSubmitting}
|
||||
/>
|
||||
<p className="text-xs text-neutral-500">
|
||||
Registre o motivo para fins de auditoria interna. Informe valores em minutos quando menor que 1 hora.
|
||||
Registre o motivo para fins de auditoria interna.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue