feat: add queue summary widget and layout fixes

This commit is contained in:
Esdras Renan 2025-11-06 17:05:31 -03:00
parent f7976e2c39
commit a542846313
12 changed files with 350 additions and 45 deletions

View file

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

View file

@ -129,7 +129,7 @@ export function TicketDetailsPanel({ ticket }: TicketDetailsPanelProps) {
</div>
</section>
<TicketCustomFieldsSection ticket={ticket} />
<TicketCustomFieldsSection ticket={ticket} hidePreview />
<section className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-2">

View file

@ -16,13 +16,14 @@ interface TicketQueueSummaryProps {
export function TicketQueueSummaryCards({ queues }: TicketQueueSummaryProps) {
const { convexUserId, isStaff } = useAuth()
const enabled = Boolean(isStaff && convexUserId)
const shouldFetch = Boolean(!queues && enabled)
const fromServer = useQuery(
api.queues.summary,
enabled ? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> } : "skip"
shouldFetch ? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> } : "skip"
) as TicketQueueSummary[] | undefined
const data: TicketQueueSummary[] = queues ?? fromServer ?? []
if (!queues && fromServer === undefined) {
if (!queues && shouldFetch && fromServer === undefined) {
return (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 3 }).map((_, index) => (

View file

@ -27,6 +27,7 @@ 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,
@ -213,6 +214,7 @@ 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,
@ -1583,6 +1585,24 @@ 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>