Ajusta placeholders, formulários e widgets

This commit is contained in:
Esdras Renan 2025-11-06 23:13:41 -03:00
parent 343f0c8c64
commit b94cea2f9a
33 changed files with 2122 additions and 462 deletions

View file

@ -174,10 +174,9 @@ export function TicketCustomFieldsList({ record, emptyMessage, className }: Tick
type TicketCustomFieldsSectionProps = {
ticket: TicketWithDetails
hidePreview?: boolean
}
export function TicketCustomFieldsSection({ ticket, hidePreview = false }: TicketCustomFieldsSectionProps) {
export function TicketCustomFieldsSection({ ticket }: TicketCustomFieldsSectionProps) {
const { convexUserId, role } = useAuth()
const canEdit = Boolean(convexUserId && (role === "admin" || role === "agent"))
@ -319,14 +318,10 @@ export function TicketCustomFieldsSection({ ticket, hidePreview = false }: Ticke
</Button>
) : null}
</div>
{hidePreview ? (
<p className="text-xs text-neutral-500">Visualize os valores no resumo principal.</p>
) : (
<TicketCustomFieldsList
record={ticket.customFields}
emptyMessage="Nenhum campo adicional preenchido neste chamado."
/>
)}
<TicketCustomFieldsList
record={ticket.customFields}
emptyMessage="Nenhum campo adicional preenchido neste chamado."
/>
<Dialog open={editorOpen} onOpenChange={setEditorOpen}>
<DialogContent className="max-w-3xl gap-4">

View file

@ -5,9 +5,9 @@ import { ptBR } from "date-fns/locale"
import type { TicketWithDetails } from "@/lib/schemas/ticket"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { getTicketStatusLabel, getTicketStatusSummaryTone } from "@/lib/ticket-status-style"
import { TicketCustomFieldsSection } from "@/components/tickets/ticket-custom-fields"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
import { TicketCustomFieldsSection } from "@/components/tickets/ticket-custom-fields"
interface TicketDetailsPanelProps {
ticket: TicketWithDetails
@ -53,19 +53,19 @@ export function TicketDetailsPanel({ ticket }: TicketDetailsPanelProps) {
const isAvulso = Boolean(ticket.company?.isAvulso)
const companyLabel = ticket.company?.name ?? (isAvulso ? "Cliente avulso" : "Sem empresa vinculada")
const summaryChips = useMemo(
() => [
const summaryChips = useMemo(() => {
const chips: Array<{ key: string; label: string; value: string; tone: SummaryTone }> = [
{
key: "queue",
label: "Fila",
value: ticket.queue ?? "Sem fila",
tone: ticket.queue ? ("default" as SummaryTone) : ("muted" as SummaryTone),
tone: ticket.queue ? "default" : "muted",
},
{
key: "company",
label: "Empresa",
value: companyLabel,
tone: isAvulso ? ("warning" as SummaryTone) : ("default" as SummaryTone),
tone: isAvulso ? "warning" : "default",
},
{
key: "status",
@ -83,11 +83,19 @@ export function TicketDetailsPanel({ ticket }: TicketDetailsPanelProps) {
key: "assignee",
label: "Responsável",
value: ticket.assignee?.name ?? "Não atribuído",
tone: ticket.assignee ? ("default" as SummaryTone) : ("muted" as SummaryTone),
tone: ticket.assignee ? "default" : "muted",
},
],
[companyLabel, isAvulso, ticket.assignee, ticket.priority, ticket.queue, ticket.status]
)
]
if (ticket.formTemplateLabel) {
chips.push({
key: "formTemplate",
label: "Tipo de solicitação",
value: ticket.formTemplateLabel,
tone: "info",
})
}
return chips
}, [companyLabel, isAvulso, ticket.assignee, ticket.formTemplateLabel, ticket.priority, ticket.queue, ticket.status])
const agentTotals = useMemo(() => {
const totals = ticket.workSummary?.perAgentTotals ?? []
@ -129,8 +137,6 @@ export function TicketDetailsPanel({ ticket }: TicketDetailsPanelProps) {
</div>
</section>
<TicketCustomFieldsSection ticket={ticket} hidePreview />
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="text-sm font-semibold text-neutral-900">SLA & métricas</h3>
@ -184,6 +190,8 @@ export function TicketDetailsPanel({ ticket }: TicketDetailsPanelProps) {
</div>
</section>
<TicketCustomFieldsSection ticket={ticket} />
<section className="space-y-3">
<h3 className="text-sm font-semibold text-neutral-900">Tempo por agente</h3>
{agentTotals.length > 0 ? (

View file

@ -27,7 +27,6 @@ import { Textarea } from "@/components/ui/textarea"
import { Spinner } from "@/components/ui/spinner"
import { useTicketCategories } from "@/hooks/use-ticket-categories"
import { useDefaultQueues } from "@/hooks/use-default-queues"
import { mapTicketCustomFields } from "@/lib/ticket-custom-fields"
import {
DropdownMenu,
DropdownMenuContent,
@ -214,7 +213,6 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
queuesEnabled ? { tenantId: ticket.tenantId, viewerId: convexUserId as Id<"users"> } : "skip"
)
const queues: TicketQueueSummary[] = Array.isArray(queuesResult) ? queuesResult : []
const customFieldEntries = useMemo(() => mapTicketCustomFields(ticket.customFields), [ticket.customFields])
const { categories, isLoading: categoriesLoading } = useTicketCategories(ticket.tenantId)
const workSummaryRemote = useQuery(
api.tickets.workSummary,
@ -1300,13 +1298,9 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
<div className="space-y-1">
<div className="flex flex-wrap items-center gap-2">
<h1 className="break-words text-2xl font-semibold text-neutral-900">{subject}</h1>
{ticket.formTemplate ? (
{ticket.formTemplateLabel || ticket.formTemplate ? (
<span className="inline-flex items-center rounded-full border border-sky-200 bg-sky-50 px-2.5 py-0.5 text-xs font-semibold text-sky-700">
{ticket.formTemplate === "admissao"
? "Admissão"
: ticket.formTemplate === "desligamento"
? "Desligamento"
: "Chamado"}
{ticket.formTemplateLabel ?? ticket.formTemplate}
</span>
) : null}
</div>
@ -1585,24 +1579,6 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
</div>
) : null}
</div>
<div className="mt-6 space-y-2">
<span className={sectionLabelClass}>Informações adicionais</span>
{customFieldEntries.length > 0 ? (
<div className="grid gap-3 sm:grid-cols-2">
{customFieldEntries.map((entry) => (
<div
key={entry.key}
className="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm"
>
<p className="text-xs font-semibold uppercase tracking-wide text-neutral-500">{entry.label}</p>
<p className="mt-1 text-sm font-semibold text-neutral-900">{entry.formattedValue}</p>
</div>
))}
</div>
) : (
<p className="text-xs text-neutral-500">Nenhum campo adicional preenchido para este chamado.</p>
)}
</div>
<Dialog open={pauseDialogOpen} onOpenChange={setPauseDialogOpen}>
<DialogContent>
<DialogHeader>

View file

@ -221,14 +221,10 @@ export function TicketsTable({ tickets, enteringIds }: TicketsTableProps) {
<span className="text-sm text-neutral-600 line-clamp-1 break-words">
{ticket.summary ?? "Sem resumo"}
</span>
{ticket.formTemplate ? (
{ticket.formTemplateLabel || ticket.formTemplate ? (
<div className="flex items-center gap-2">
<Badge className="rounded-full border border-sky-200 bg-sky-50 px-2.5 py-0.5 text-[11px] font-semibold text-sky-700">
{ticket.formTemplate === "admissao"
? "Admissão"
: ticket.formTemplate === "desligamento"
? "Desligamento"
: "Chamado"}
{ticket.formTemplateLabel ?? ticket.formTemplate}
</Badge>
</div>
) : null}