"use client" import { useMemo, useState } from "react" import { useQuery } from "convex/react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { mapTicketsFromServerList } from "@/lib/mappers/ticket" import type { Ticket } from "@/lib/schemas/ticket" import { useAuth } from "@/lib/auth-client" import Link from "next/link" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty" import { Spinner } from "@/components/ui/spinner" import { Button } from "@/components/ui/button" import { PortalTicketCard } from "@/components/portal/portal-ticket-card" import { PortalTicketFilters, defaultPortalTicketFilters, type PortalTicketFiltersState, } from "@/components/portal/portal-ticket-filters" export function PortalTicketList() { const { convexUserId, session, machineContext } = useAuth() const viewerId = (convexUserId ?? machineContext?.assignedUserId ?? null) as Id<"users"> | null const ticketsRaw = useQuery( api.tickets.list, viewerId ? { tenantId: session?.user.tenantId ?? DEFAULT_TENANT_ID, viewerId, limit: 100, } : "skip" ) const tickets = useMemo(() => { if (!ticketsRaw) return [] return mapTicketsFromServerList((ticketsRaw as unknown[]) ?? []) }, [ticketsRaw]) const [filters, setFilters] = useState(defaultPortalTicketFilters) const queueOptions = useMemo(() => { const set = new Set() ;(tickets as Ticket[]).forEach((ticket) => { if (ticket.queue) { set.add(ticket.queue) } }) return Array.from(set).sort((a, b) => a.localeCompare(b, "pt-BR")) }, [tickets]) const companyOptions = useMemo(() => { const set = new Set() ;(tickets as Ticket[]).forEach((ticket) => { const name = ticket.company?.name if (name) set.add(name) }) return Array.from(set).sort((a, b) => a.localeCompare(b, "pt-BR")) }, [tickets]) const categoryOptions = useMemo(() => { const map = new Map() ;(tickets as Ticket[]).forEach((ticket) => { if (ticket.category?.id && ticket.category.name) { map.set(ticket.category.id, ticket.category.name) } }) return Array.from(map.entries()) .map(([id, name]) => ({ id, name })) .sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) }, [tickets]) const assigneeOptions = useMemo(() => { const map = new Map() ;(tickets as Ticket[]).forEach((ticket) => { if (ticket.assignee?.id && ticket.assignee.name) { map.set(ticket.assignee.id, ticket.assignee.name) } }) return Array.from(map.entries()) .map(([id, name]) => ({ id, name })) .sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) }, [tickets]) const lastResolvedNoCsat = useMemo(() => { const resolved = (tickets as Ticket[]) .filter((t) => t.status === "RESOLVED" && (t.csatScore == null)) .sort((a, b) => (b.resolvedAt?.getTime?.() ?? 0) - (a.resolvedAt?.getTime?.() ?? 0)) return resolved[0] ?? null }, [tickets]) const parseDateInput = (value: string | null, options?: { endOfDay?: boolean }) => { if (!value) return null const [year, month, day] = value.split("-").map((part) => Number(part)) if (!year || !month || !day) return null const date = new Date( year, month - 1, day, options?.endOfDay ? 23 : 0, options?.endOfDay ? 59 : 0, options?.endOfDay ? 59 : 0, options?.endOfDay ? 999 : 0 ) if (Number.isNaN(date.getTime())) return null return date } const filteredTickets = useMemo(() => { const fromDate = parseDateInput(filters.dateFrom) const toDate = parseDateInput(filters.dateTo, { endOfDay: true }) return (tickets as Ticket[]) .filter((ticket) => { if (filters.queue && ticket.queue !== filters.queue) return false if (filters.company && ticket.company?.name !== filters.company) return false if (filters.categoryId && ticket.category?.id !== filters.categoryId) return false if (filters.assigneeId && ticket.assignee?.id !== filters.assigneeId) return false const isResolved = ticket.status === "RESOLVED" if (filters.status === "active" && isResolved) return false if (filters.status === "resolved" && !isResolved) return false const createdAt = ticket.createdAt instanceof Date ? ticket.createdAt : new Date(ticket.createdAt) if (fromDate && createdAt < fromDate) return false if (toDate && createdAt > toDate) return false return true }) .sort((a, b) => { const aDate = a.createdAt instanceof Date ? a.createdAt.getTime() : new Date(a.createdAt).getTime() const bDate = b.createdAt instanceof Date ? b.createdAt.getTime() : new Date(b.createdAt).getTime() return filters.sort === "oldest" ? aDate - bDate : bDate - aDate }) }, [filters, tickets]) const handleFiltersChange = (partial: Partial) => { setFilters((prev) => ({ ...prev, ...partial })) } const handleResetFilters = () => { setFilters(defaultPortalTicketFilters) } const isLoading = Boolean(viewerId && ticketsRaw === undefined) if (isLoading) { return (
Carregando chamados...

Estamos buscando seus chamados mais recentes. Isso deve levar apenas alguns instantes.

) } if (!viewerId || !tickets.length) { return ( Meus chamados Nenhum ticket encontrado Ajuste os filtros ou crie um novo ticket.
) } return (

Meus chamados

Acompanhe seus tickets e veja as últimas atualizações.

{lastResolvedNoCsat ? (
Como foi seu último atendimento? Avalie o chamado #{lastResolvedNoCsat.reference}.{' '} Avaliar agora
) : null} {filteredTickets.length === 0 ? ( Nenhum ticket corresponde aos filtros

Ajuste as opções acima ou limpe os filtros para visualizar novamente.

) : (
{filteredTickets.map((ticket) => ( ))}
)}
) }