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 shouldAdjustTime: boolean
internalHours: string internalHours: string
internalMinutes: string internalMinutes: string
internalSeconds: string
externalHours: string externalHours: string
externalMinutes: string externalMinutes: string
externalSeconds: string
adjustReason: string adjustReason: string
linkedReference: string linkedReference: string
reopenWindowDays: string reopenWindowDays: string
@ -128,17 +130,22 @@ function applyTemplatePlaceholders(html: string, customerName?: string | null, a
const splitDuration = (ms: number) => { const splitDuration = (ms: number) => {
const safeMs = Number.isFinite(ms) && ms > 0 ? ms : 0 const safeMs = Number.isFinite(ms) && ms > 0 ? ms : 0
const totalMinutes = Math.round(safeMs / 60000) const totalSeconds = Math.floor(safeMs / 1000)
const hours = Math.floor(totalMinutes / 60) const hours = Math.floor(totalSeconds / 3600)
const minutes = totalMinutes % 60 const minutes = Math.floor((totalSeconds % 3600) / 60)
return { hours, minutes } const seconds = totalSeconds % 60
return { hours, minutes, seconds }
} }
const formatDurationLabel = (ms: number) => { const formatDurationLabel = (ms: number) => {
const { hours, minutes } = splitDuration(ms) const { hours, minutes, seconds } = splitDuration(ms)
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}min` if (hours > 0) {
if (hours > 0) return `${hours}h` return `${hours}h ${minutes.toString().padStart(2, "0")}m`
return `${minutes}min` }
if (minutes > 0) {
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`
}
return `${seconds}s`
} }
const STATUS_LABELS: Record<TicketStatus, string> = { const STATUS_LABELS: Record<TicketStatus, string> = {
@ -218,8 +225,10 @@ export function CloseTicketDialog({
const [shouldAdjustTime, setShouldAdjustTime] = useState<boolean>(false) const [shouldAdjustTime, setShouldAdjustTime] = useState<boolean>(false)
const [internalHours, setInternalHours] = useState<string>("0") const [internalHours, setInternalHours] = useState<string>("0")
const [internalMinutes, setInternalMinutes] = useState<string>("0") const [internalMinutes, setInternalMinutes] = useState<string>("0")
const [internalSeconds, setInternalSeconds] = useState<string>("0")
const [externalHours, setExternalHours] = useState<string>("0") const [externalHours, setExternalHours] = useState<string>("0")
const [externalMinutes, setExternalMinutes] = useState<string>("0") const [externalMinutes, setExternalMinutes] = useState<string>("0")
const [externalSeconds, setExternalSeconds] = useState<string>("0")
const [adjustReason, setAdjustReason] = useState<string>("") const [adjustReason, setAdjustReason] = useState<string>("")
const enableAdjustment = Boolean(canAdjustTime && workSummary) const enableAdjustment = Boolean(canAdjustTime && workSummary)
const [linkedReference, setLinkedReference] = useState<string>("") const [linkedReference, setLinkedReference] = useState<string>("")
@ -277,8 +286,10 @@ export function CloseTicketDialog({
setAdjustReason("") setAdjustReason("")
setInternalHours("0") setInternalHours("0")
setInternalMinutes("0") setInternalMinutes("0")
setInternalSeconds("0")
setExternalHours("0") setExternalHours("0")
setExternalMinutes("0") setExternalMinutes("0")
setExternalSeconds("0")
setLinkedReference("") setLinkedReference("")
setLinkedTicketSelection(null) setLinkedTicketSelection(null)
setLinkSuggestions([]) setLinkSuggestions([])
@ -306,8 +317,10 @@ export function CloseTicketDialog({
setShouldAdjustTime(Boolean(parsed.shouldAdjustTime)) setShouldAdjustTime(Boolean(parsed.shouldAdjustTime))
setInternalHours(parsed.internalHours ?? "0") setInternalHours(parsed.internalHours ?? "0")
setInternalMinutes(parsed.internalMinutes ?? "0") setInternalMinutes(parsed.internalMinutes ?? "0")
setInternalSeconds(parsed.internalSeconds ?? "0")
setExternalHours(parsed.externalHours ?? "0") setExternalHours(parsed.externalHours ?? "0")
setExternalMinutes(parsed.externalMinutes ?? "0") setExternalMinutes(parsed.externalMinutes ?? "0")
setExternalSeconds(parsed.externalSeconds ?? "0")
setAdjustReason(parsed.adjustReason ?? "") setAdjustReason(parsed.adjustReason ?? "")
setLinkedReference(parsed.linkedReference ?? "") setLinkedReference(parsed.linkedReference ?? "")
setLinkedTicketSelection(null) setLinkedTicketSelection(null)
@ -332,8 +345,10 @@ export function CloseTicketDialog({
shouldAdjustTime, shouldAdjustTime,
internalHours, internalHours,
internalMinutes, internalMinutes,
internalSeconds,
externalHours, externalHours,
externalMinutes, externalMinutes,
externalSeconds,
adjustReason, adjustReason,
linkedReference, linkedReference,
reopenWindowDays, reopenWindowDays,
@ -348,8 +363,10 @@ export function CloseTicketDialog({
draftStorageKey, draftStorageKey,
externalHours, externalHours,
externalMinutes, externalMinutes,
externalSeconds,
internalHours, internalHours,
internalMinutes, internalMinutes,
internalSeconds,
linkedReference, linkedReference,
message, message,
reopenWindowDays, reopenWindowDays,
@ -392,8 +409,10 @@ export function CloseTicketDialog({
const external = splitDuration(workSummary?.externalWorkedMs ?? 0) const external = splitDuration(workSummary?.externalWorkedMs ?? 0)
setInternalHours(internal.hours.toString()) setInternalHours(internal.hours.toString())
setInternalMinutes(internal.minutes.toString()) setInternalMinutes(internal.minutes.toString())
setInternalSeconds(internal.seconds.toString())
setExternalHours(external.hours.toString()) setExternalHours(external.hours.toString())
setExternalMinutes(external.minutes.toString()) setExternalMinutes(external.minutes.toString())
setExternalSeconds(external.seconds.toString())
}, [ }, [
open, open,
enableAdjustment, enableAdjustment,
@ -595,6 +614,12 @@ export function CloseTicketDialog({
toast.error("Os minutos internos devem estar entre 0 e 59.") toast.error("Os minutos internos devem estar entre 0 e 59.")
return 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") const externalHoursValue = parsePart(externalHours, "horas externas")
if (externalHoursValue === null) return if (externalHoursValue === null) return
@ -604,9 +629,15 @@ export function CloseTicketDialog({
toast.error("Os minutos externos devem estar entre 0 e 59.") toast.error("Os minutos externos devem estar entre 0 e 59.")
return 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 targetInternalMs = (internalHoursValue * 3600 + internalMinutesValue * 60 + internalSecondsValue) * 1000
targetExternalMs = (externalHoursValue * 60 + externalMinutesValue) * 60000 targetExternalMs = (externalHoursValue * 3600 + externalMinutesValue * 60 + externalSecondsValue) * 1000
trimmedReason = adjustReason.trim() trimmedReason = adjustReason.trim()
if (trimmedReason.length < 5) { if (trimmedReason.length < 5) {
toast.error("Descreva o motivo do ajuste (mínimo de 5 caracteres).") toast.error("Descreva o motivo do ajuste (mínimo de 5 caracteres).")
@ -716,10 +747,10 @@ export function CloseTicketDialog({
</div> </div>
{shouldAdjustTime ? ( {shouldAdjustTime ? (
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-6 sm:grid-cols-2">
<div className="space-y-2"> <div className="space-y-3">
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">Tempo interno</p> <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"> <div className="space-y-1">
<Label htmlFor="adjust-internal-hours" className="text-xs text-neutral-600"> <Label htmlFor="adjust-internal-hours" className="text-xs text-neutral-600">
Horas Horas
@ -751,12 +782,28 @@ export function CloseTicketDialog({
disabled={isSubmitting} disabled={isSubmitting}
/> />
</div> </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> </div>
<p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.internalWorkedMs ?? 0)}</p> <p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.internalWorkedMs ?? 0)}</p>
</div> </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> <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"> <div className="space-y-1">
<Label htmlFor="adjust-external-hours" className="text-xs text-neutral-600"> <Label htmlFor="adjust-external-hours" className="text-xs text-neutral-600">
Horas Horas
@ -788,6 +835,22 @@ export function CloseTicketDialog({
disabled={isSubmitting} disabled={isSubmitting}
/> />
</div> </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> </div>
<p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.externalWorkedMs ?? 0)}</p> <p className="text-xs text-neutral-500">Atual: {formatDurationLabel(workSummary?.externalWorkedMs ?? 0)}</p>
</div> </div>
@ -805,7 +868,7 @@ export function CloseTicketDialog({
disabled={isSubmitting} disabled={isSubmitting}
/> />
<p className="text-xs text-neutral-500"> <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> </p>
</div> </div>
</div> </div>