feat(portal): enable ticket reopen and improve loading UX
This commit is contained in:
parent
8b905dc467
commit
50a80f5244
4 changed files with 40 additions and 10 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -60,3 +60,6 @@ Screenshot*.png
|
|||
*:\:Zone.Identifier
|
||||
# Infrastructure secrets
|
||||
.ci.env
|
||||
|
||||
# ferramentas externas
|
||||
rustdesk/
|
||||
|
|
|
|||
|
|
@ -276,6 +276,10 @@ export function PortalTicketDetail({ ticketId }: PortalTicketDetailProps) {
|
|||
)
|
||||
|
||||
const viewerId = convexUserId ?? null
|
||||
const viewerRole = role ?? ""
|
||||
const viewerEmailRaw = session?.user?.email ?? machineContext?.assignedUserEmail ?? null
|
||||
const viewerEmail = (viewerEmailRaw ?? "").trim().toLowerCase()
|
||||
|
||||
const rawReopenDeadline = ticket?.reopenDeadline ?? null
|
||||
|
||||
const DEFAULT_REOPEN_DAYS = 7
|
||||
|
|
@ -285,8 +289,19 @@ export function PortalTicketDetail({ ticketId }: PortalTicketDetailProps) {
|
|||
: fallbackClosedMs
|
||||
? fallbackClosedMs + DEFAULT_REOPEN_DAYS * 24 * 60 * 60 * 1000
|
||||
: null
|
||||
|
||||
const isRequesterById = Boolean(ticket?.requester?.id && viewerId && ticket.requester.id === viewerId)
|
||||
const isRequesterByEmail = Boolean(
|
||||
viewerEmail && ticket?.requester?.email && viewerEmail === ticket.requester.email.trim().toLowerCase()
|
||||
)
|
||||
const isRequester = isRequesterById || isRequesterByEmail
|
||||
|
||||
const reopenWindowActive = inferredDeadline ? inferredDeadline > Date.now() : true
|
||||
const canReopenTicket = !!ticket && ticket.status === "RESOLVED" && reopenWindowActive
|
||||
const canReopenTicket =
|
||||
!!ticket &&
|
||||
ticket.status === "RESOLVED" &&
|
||||
reopenWindowActive &&
|
||||
(isStaff || viewerRole === "manager" || isRequester)
|
||||
const reopenDeadlineLabel = useMemo(() => {
|
||||
const deadline = inferredDeadline ?? rawReopenDeadline
|
||||
if (!deadline) return null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useQuery } from "convex/react"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from "@/components/portal/portal-ticket-filters"
|
||||
|
||||
export function PortalTicketList() {
|
||||
const { convexUserId, session, machineContext, role, isLoading: authLoading, machineContextLoading } = useAuth()
|
||||
const { convexUserId, session, machineContext, role } = useAuth()
|
||||
|
||||
const viewerId = (convexUserId ?? machineContext?.assignedUserId ?? null) as Id<"users"> | null
|
||||
|
||||
|
|
@ -41,6 +41,15 @@ export function PortalTicketList() {
|
|||
return mapTicketsFromServerList((ticketsRaw as unknown[]) ?? [])
|
||||
}, [ticketsRaw])
|
||||
|
||||
const [initialLoadCompleted, setInitialLoadCompleted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialLoadCompleted) return
|
||||
if (!viewerId) return
|
||||
if (ticketsRaw === undefined) return
|
||||
setInitialLoadCompleted(true)
|
||||
}, [initialLoadCompleted, viewerId, ticketsRaw])
|
||||
|
||||
const [filters, setFilters] = useState<PortalTicketFiltersState>(defaultPortalTicketFilters)
|
||||
|
||||
// No app desktop, colaboradores e gestores não devem ver filtros avançados
|
||||
|
|
@ -145,13 +154,7 @@ export function PortalTicketList() {
|
|||
setFilters(defaultPortalTicketFilters)
|
||||
}
|
||||
|
||||
const hasAuthContext = Boolean(session || machineContext)
|
||||
const isLoading = Boolean(
|
||||
authLoading ||
|
||||
machineContextLoading ||
|
||||
(hasAuthContext && !viewerId) ||
|
||||
(viewerId && ticketsRaw === undefined)
|
||||
)
|
||||
const isLoading = !initialLoadCompleted
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@ const serverTicketSchema = z.object({
|
|||
dueAt: z.number().nullable().optional(),
|
||||
firstResponseAt: z.number().nullable().optional(),
|
||||
resolvedAt: z.number().nullable().optional(),
|
||||
reopenDeadline: z.number().nullable().optional(),
|
||||
reopenWindowDays: z.number().nullable().optional(),
|
||||
reopenedAt: z.number().nullable().optional(),
|
||||
updatedAt: z.number(),
|
||||
createdAt: z.number(),
|
||||
tags: z.array(z.string()).default([]).optional(),
|
||||
|
|
@ -258,6 +261,9 @@ export function mapTicketFromServer(input: unknown) {
|
|||
dueAt: s.dueAt ? new Date(s.dueAt) : null,
|
||||
firstResponseAt: s.firstResponseAt ? new Date(s.firstResponseAt) : null,
|
||||
resolvedAt: s.resolvedAt ? new Date(s.resolvedAt) : null,
|
||||
reopenDeadline: typeof s.reopenDeadline === "number" ? s.reopenDeadline : null,
|
||||
reopenWindowDays: typeof s.reopenWindowDays === "number" ? s.reopenWindowDays : null,
|
||||
reopenedAt: typeof s.reopenedAt === "number" ? s.reopenedAt : null,
|
||||
csatScore: typeof csatScore === "number" ? csatScore : null,
|
||||
csatMaxScore: typeof csatMaxScore === "number" ? csatMaxScore : null,
|
||||
csatComment: typeof csatComment === "string" && csatComment.trim().length > 0 ? csatComment.trim() : null,
|
||||
|
|
@ -356,6 +362,9 @@ export function mapTicketWithDetailsFromServer(input: unknown) {
|
|||
dueAt: base.dueAt ? new Date(base.dueAt) : null,
|
||||
firstResponseAt: base.firstResponseAt ? new Date(base.firstResponseAt) : null,
|
||||
resolvedAt: base.resolvedAt ? new Date(base.resolvedAt) : null,
|
||||
reopenDeadline: typeof base.reopenDeadline === "number" ? base.reopenDeadline : null,
|
||||
reopenWindowDays: typeof base.reopenWindowDays === "number" ? base.reopenWindowDays : null,
|
||||
reopenedAt: typeof base.reopenedAt === "number" ? base.reopenedAt : null,
|
||||
csatScore: typeof csatScore === "number" ? csatScore : null,
|
||||
csatMaxScore: typeof csatMaxScore === "number" ? csatMaxScore : null,
|
||||
csatComment: typeof csatComment === "string" && csatComment.trim().length > 0 ? csatComment.trim() : null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue