184 lines
5.4 KiB
TypeScript
184 lines
5.4 KiB
TypeScript
import { z } from "zod";
|
|
import { ticketSchema, ticketWithDetailsSchema } from "@/lib/schemas/ticket";
|
|
|
|
// Server shapes: datas como number (epoch ms) e alguns nullables
|
|
// Relaxamos email/urls no shape do servidor para evitar que payloads parciais quebrem o app.
|
|
const serverUserSchema = z.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
email: z.string().optional(),
|
|
avatarUrl: z.string().optional(),
|
|
teams: z.array(z.string()).optional(),
|
|
});
|
|
|
|
const serverTicketSchema = z.object({
|
|
id: z.string(),
|
|
reference: z.number(),
|
|
tenantId: z.string(),
|
|
subject: z.string(),
|
|
summary: z.string().optional().nullable(),
|
|
status: z.string(),
|
|
priority: z.string(),
|
|
channel: z.string(),
|
|
queue: z.string().nullable(),
|
|
requester: serverUserSchema,
|
|
assignee: serverUserSchema.nullable(),
|
|
slaPolicy: z.any().nullable().optional(),
|
|
dueAt: z.number().nullable().optional(),
|
|
firstResponseAt: z.number().nullable().optional(),
|
|
resolvedAt: z.number().nullable().optional(),
|
|
updatedAt: z.number(),
|
|
createdAt: z.number(),
|
|
tags: z.array(z.string()).default([]).optional(),
|
|
lastTimelineEntry: z.string().nullable().optional(),
|
|
metrics: z.any().nullable().optional(),
|
|
category: z
|
|
.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
})
|
|
.nullable()
|
|
.optional(),
|
|
subcategory: z
|
|
.object({
|
|
id: z.string(),
|
|
name: z.string(),
|
|
categoryId: z.string().optional(),
|
|
})
|
|
.nullable()
|
|
.optional(),
|
|
workSummary: z
|
|
.object({
|
|
totalWorkedMs: z.number(),
|
|
activeSession: z
|
|
.object({
|
|
id: z.string(),
|
|
agentId: z.string(),
|
|
startedAt: z.number(),
|
|
})
|
|
.nullable(),
|
|
})
|
|
.nullable()
|
|
.optional(),
|
|
});
|
|
|
|
const serverAttachmentSchema = z.object({
|
|
id: z.any(),
|
|
name: z.string(),
|
|
size: z.number().optional(),
|
|
url: z.string().url().optional(),
|
|
});
|
|
|
|
const serverCommentSchema = z.object({
|
|
id: z.string(),
|
|
author: serverUserSchema,
|
|
visibility: z.string(),
|
|
body: z.string(),
|
|
attachments: z.array(serverAttachmentSchema).default([]).optional(),
|
|
createdAt: z.number(),
|
|
updatedAt: z.number(),
|
|
});
|
|
|
|
const serverEventSchema = z.object({
|
|
id: z.string(),
|
|
type: z.string(),
|
|
payload: z.any().optional(),
|
|
createdAt: z.number(),
|
|
});
|
|
|
|
const serverCustomFieldValueSchema = z.object({
|
|
label: z.string(),
|
|
type: z.string(),
|
|
value: z.any().optional(),
|
|
displayValue: z.string().optional(),
|
|
});
|
|
|
|
const serverTicketWithDetailsSchema = serverTicketSchema.extend({
|
|
description: z.string().optional().nullable(),
|
|
customFields: z.record(z.string(), serverCustomFieldValueSchema).optional(),
|
|
timeline: z.array(serverEventSchema),
|
|
comments: z.array(serverCommentSchema),
|
|
});
|
|
|
|
export function mapTicketFromServer(input: unknown) {
|
|
const s = serverTicketSchema.parse(input);
|
|
const ui = {
|
|
...s,
|
|
category: s.category ?? undefined,
|
|
subcategory: s.subcategory ?? undefined,
|
|
lastTimelineEntry: s.lastTimelineEntry ?? undefined,
|
|
updatedAt: new Date(s.updatedAt),
|
|
createdAt: new Date(s.createdAt),
|
|
dueAt: s.dueAt ? new Date(s.dueAt) : null,
|
|
firstResponseAt: s.firstResponseAt ? new Date(s.firstResponseAt) : null,
|
|
resolvedAt: s.resolvedAt ? new Date(s.resolvedAt) : null,
|
|
workSummary: s.workSummary
|
|
? {
|
|
totalWorkedMs: s.workSummary.totalWorkedMs,
|
|
activeSession: s.workSummary.activeSession
|
|
? {
|
|
...s.workSummary.activeSession,
|
|
startedAt: new Date(s.workSummary.activeSession.startedAt),
|
|
}
|
|
: null,
|
|
}
|
|
: undefined,
|
|
};
|
|
return ui as unknown as z.infer<typeof ticketSchema>;
|
|
}
|
|
|
|
export function mapTicketsFromServerList(arr: unknown[]) {
|
|
return arr.map(mapTicketFromServer);
|
|
}
|
|
|
|
export function mapTicketWithDetailsFromServer(input: unknown) {
|
|
const s = serverTicketWithDetailsSchema.parse(input);
|
|
const customFields = Object.entries(s.customFields ?? {}).reduce<
|
|
Record<string, { label: string; type: string; value?: unknown; displayValue?: string }>
|
|
>(
|
|
(acc, [key, value]) => {
|
|
let parsedValue: unknown = value.value;
|
|
if (value.type === "date" && typeof value.value === "number") {
|
|
parsedValue = new Date(value.value);
|
|
}
|
|
acc[key] = {
|
|
label: value.label,
|
|
type: value.type,
|
|
value: parsedValue,
|
|
displayValue: value.displayValue,
|
|
};
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
const ui = {
|
|
...s,
|
|
customFields,
|
|
category: s.category ?? undefined,
|
|
subcategory: s.subcategory ?? undefined,
|
|
lastTimelineEntry: s.lastTimelineEntry ?? undefined,
|
|
updatedAt: new Date(s.updatedAt),
|
|
createdAt: new Date(s.createdAt),
|
|
dueAt: s.dueAt ? new Date(s.dueAt) : null,
|
|
firstResponseAt: s.firstResponseAt ? new Date(s.firstResponseAt) : null,
|
|
resolvedAt: s.resolvedAt ? new Date(s.resolvedAt) : null,
|
|
timeline: s.timeline.map((e) => ({ ...e, createdAt: new Date(e.createdAt) })),
|
|
comments: s.comments.map((c) => ({
|
|
...c,
|
|
createdAt: new Date(c.createdAt),
|
|
updatedAt: new Date(c.updatedAt),
|
|
})),
|
|
workSummary: s.workSummary
|
|
? {
|
|
totalWorkedMs: s.workSummary.totalWorkedMs,
|
|
activeSession: s.workSummary.activeSession
|
|
? {
|
|
...s.workSummary.activeSession,
|
|
startedAt: new Date(s.workSummary.activeSession.startedAt),
|
|
}
|
|
: null,
|
|
}
|
|
: undefined,
|
|
};
|
|
return ui as unknown as z.infer<typeof ticketWithDetailsSchema>;
|
|
}
|