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
|
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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue