feat: núcleo de tickets com Convex (CRUD, play, comentários com anexos) + auth placeholder; docs em AGENTS.md; toasts e updates otimistas; mapeadores Zod; refinos PT-BR e layout do painel de detalhes

This commit is contained in:
esdrasrenan 2025-10-04 00:31:44 -03:00
parent 2230590e57
commit 27b103cb46
97 changed files with 15117 additions and 15715 deletions

View file

@ -1,124 +1,124 @@
import { z } from "zod"
export const ticketStatusSchema = z.enum([
"NEW",
"OPEN",
"PENDING",
"ON_HOLD",
"RESOLVED",
"CLOSED",
])
export type TicketStatus = z.infer<typeof ticketStatusSchema>
export const ticketPrioritySchema = z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"])
export type TicketPriority = z.infer<typeof ticketPrioritySchema>
export const ticketChannelSchema = z.enum([
"EMAIL",
"WHATSAPP",
"CHAT",
"PHONE",
"API",
"MANUAL",
])
export type TicketChannel = z.infer<typeof ticketChannelSchema>
export const commentVisibilitySchema = z.enum(["PUBLIC", "INTERNAL"])
export type CommentVisibility = z.infer<typeof commentVisibilitySchema>
export const userSummarySchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
avatarUrl: z.string().url().optional(),
teams: z.array(z.string()).default([]),
})
export type UserSummary = z.infer<typeof userSummarySchema>
export const ticketCommentSchema = z.object({
id: z.string(),
author: userSummarySchema,
visibility: commentVisibilitySchema,
body: z.string(),
attachments: z
.array(
z.object({
id: z.string(),
name: z.string(),
size: z.number().optional(),
url: z.string().url().optional(),
})
)
.default([]),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
})
export type TicketComment = z.infer<typeof ticketCommentSchema>
export const ticketEventSchema = z.object({
id: z.string(),
type: z.string(),
payload: z.record(z.any()).optional(),
createdAt: z.coerce.date(),
})
export type TicketEvent = z.infer<typeof ticketEventSchema>
export const ticketSchema = z.object({
id: z.string(),
reference: z.number(),
tenantId: z.string(),
subject: z.string(),
summary: z.string().optional(),
status: ticketStatusSchema,
priority: ticketPrioritySchema,
channel: ticketChannelSchema,
queue: z.string().nullable(),
requester: userSummarySchema,
assignee: userSummarySchema.nullable(),
slaPolicy: z
.object({
id: z.string(),
name: z.string(),
targetMinutesToFirstResponse: z.number().nullable(),
targetMinutesToResolution: z.number().nullable(),
})
.nullable(),
dueAt: z.coerce.date().nullable(),
firstResponseAt: z.coerce.date().nullable(),
resolvedAt: z.coerce.date().nullable(),
updatedAt: z.coerce.date(),
createdAt: z.coerce.date(),
tags: z.array(z.string()).default([]),
lastTimelineEntry: z.string().optional(),
metrics: z
.object({
timeWaitingMinutes: z.number().nullable(),
timeOpenedMinutes: z.number().nullable(),
})
.nullable(),
})
export type Ticket = z.infer<typeof ticketSchema>
export const ticketWithDetailsSchema = ticketSchema.extend({
description: z.string().optional(),
customFields: z.record(z.any()).default({}),
timeline: z.array(ticketEventSchema),
comments: z.array(ticketCommentSchema),
})
export type TicketWithDetails = z.infer<typeof ticketWithDetailsSchema>
export const ticketQueueSummarySchema = z.object({
id: z.string(),
name: z.string(),
pending: z.number(),
waiting: z.number(),
breached: z.number(),
})
export type TicketQueueSummary = z.infer<typeof ticketQueueSummarySchema>
export const ticketPlayContextSchema = z.object({
queue: ticketQueueSummarySchema,
nextTicket: ticketSchema.nullable(),
})
export type TicketPlayContext = z.infer<typeof ticketPlayContextSchema>
import { z } from "zod"
export const ticketStatusSchema = z.enum([
"NEW",
"OPEN",
"PENDING",
"ON_HOLD",
"RESOLVED",
"CLOSED",
])
export type TicketStatus = z.infer<typeof ticketStatusSchema>
export const ticketPrioritySchema = z.enum(["LOW", "MEDIUM", "HIGH", "URGENT"])
export type TicketPriority = z.infer<typeof ticketPrioritySchema>
export const ticketChannelSchema = z.enum([
"EMAIL",
"WHATSAPP",
"CHAT",
"PHONE",
"API",
"MANUAL",
])
export type TicketChannel = z.infer<typeof ticketChannelSchema>
export const commentVisibilitySchema = z.enum(["PUBLIC", "INTERNAL"])
export type CommentVisibility = z.infer<typeof commentVisibilitySchema>
export const userSummarySchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
avatarUrl: z.string().url().optional(),
teams: z.array(z.string()).default([]),
})
export type UserSummary = z.infer<typeof userSummarySchema>
export const ticketCommentSchema = z.object({
id: z.string(),
author: userSummarySchema,
visibility: commentVisibilitySchema,
body: z.string(),
attachments: z
.array(
z.object({
id: z.string(),
name: z.string(),
size: z.number().optional(),
url: z.string().url().optional(),
})
)
.default([]),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
})
export type TicketComment = z.infer<typeof ticketCommentSchema>
export const ticketEventSchema = z.object({
id: z.string(),
type: z.string(),
payload: z.record(z.any()).optional(),
createdAt: z.coerce.date(),
})
export type TicketEvent = z.infer<typeof ticketEventSchema>
export const ticketSchema = z.object({
id: z.string(),
reference: z.number(),
tenantId: z.string(),
subject: z.string(),
summary: z.string().optional(),
status: ticketStatusSchema,
priority: ticketPrioritySchema,
channel: ticketChannelSchema,
queue: z.string().nullable(),
requester: userSummarySchema,
assignee: userSummarySchema.nullable(),
slaPolicy: z
.object({
id: z.string(),
name: z.string(),
targetMinutesToFirstResponse: z.number().nullable(),
targetMinutesToResolution: z.number().nullable(),
})
.nullable(),
dueAt: z.coerce.date().nullable(),
firstResponseAt: z.coerce.date().nullable(),
resolvedAt: z.coerce.date().nullable(),
updatedAt: z.coerce.date(),
createdAt: z.coerce.date(),
tags: z.array(z.string()).default([]),
lastTimelineEntry: z.string().optional(),
metrics: z
.object({
timeWaitingMinutes: z.number().nullable(),
timeOpenedMinutes: z.number().nullable(),
})
.nullable(),
})
export type Ticket = z.infer<typeof ticketSchema>
export const ticketWithDetailsSchema = ticketSchema.extend({
description: z.string().optional(),
customFields: z.record(z.any()).default({}),
timeline: z.array(ticketEventSchema),
comments: z.array(ticketCommentSchema),
})
export type TicketWithDetails = z.infer<typeof ticketWithDetailsSchema>
export const ticketQueueSummarySchema = z.object({
id: z.string(),
name: z.string(),
pending: z.number(),
waiting: z.number(),
breached: z.number(),
})
export type TicketQueueSummary = z.infer<typeof ticketQueueSummarySchema>
export const ticketPlayContextSchema = z.object({
queue: ticketQueueSummarySchema,
nextTicket: ticketSchema.nullable(),
})
export type TicketPlayContext = z.infer<typeof ticketPlayContextSchema>