fix(close-ticket): adiciona segundos na formatacao e ajuste de tempo
All checks were successful
CI/CD Web + Desktop / Detect changes (push) Successful in 5s
CI/CD Web + Desktop / Deploy (VPS Linux) (push) Successful in 3m15s
CI/CD Web + Desktop / Deploy Convex functions (push) Has been skipped
Quality Checks / Lint, Test and Build (push) Successful in 3m24s

- 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:
rever-tecnologia 2025-12-18 08:59:43 -03:00
parent f9deb408dc
commit ce52a4393b

View file

@ -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>