feat: dispositivos e ajustes de csat e relatórios
This commit is contained in:
parent
25d2a9b062
commit
e0ef66555d
86 changed files with 5811 additions and 992 deletions
|
|
@ -103,7 +103,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
}, [session?.user])
|
||||
|
||||
// Sempre tenta obter o contexto da máquina.
|
||||
// Sempre tenta obter o contexto da dispositivo.
|
||||
// 1) Se a sessão Better Auth indicar role "machine", buscamos normalmente.
|
||||
// 2) Se a sessão vier nula (alguns ambientes WebView), ainda assim tentamos
|
||||
// carregar o contexto — se a API responder 200, assumimos que há sessão válida
|
||||
|
|
@ -303,7 +303,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ensureUser, session?.user?.email, session?.user?.tenantId, session?.user?.role, convexUserId])
|
||||
|
||||
// Se não houver sessão mas tivermos contexto de máquina, tratamos como "machine"
|
||||
// Se não houver sessão mas tivermos contexto de dispositivo, tratamos como "machine"
|
||||
const baseRole = session?.user?.role ? session.user.role.toLowerCase() : (machineContext ? "machine" : null)
|
||||
const personaRole = session?.user?.machinePersona ? session.user.machinePersona.toLowerCase() : null
|
||||
const normalizedRole =
|
||||
|
|
|
|||
67
src/lib/device-inventory-columns.ts
Normal file
67
src/lib/device-inventory-columns.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
export type DeviceInventoryColumnMetadata = {
|
||||
key: string
|
||||
label: string
|
||||
width: number
|
||||
default?: boolean
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type DeviceInventoryColumnConfig = {
|
||||
key: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
export const DEVICE_INVENTORY_COLUMN_METADATA: DeviceInventoryColumnMetadata[] = [
|
||||
{ key: "displayName", label: "Dispositivo", width: 26, default: true },
|
||||
{ key: "hostname", label: "Hostname", width: 24, default: true },
|
||||
{ key: "deviceType", label: "Tipo", width: 14, default: true },
|
||||
{ key: "devicePlatform", label: "Plataforma", width: 18, default: true },
|
||||
{ key: "company", label: "Empresa", width: 26, default: true },
|
||||
{ key: "status", label: "Status", width: 16, default: true },
|
||||
{ key: "persona", label: "Persona", width: 16, default: true },
|
||||
{ key: "active", label: "Ativo", width: 10, default: true },
|
||||
{ key: "lastHeartbeat", label: "Último heartbeat", width: 20, default: true },
|
||||
{ key: "assignedUser", label: "Responsável", width: 24, default: true },
|
||||
{ key: "assignedEmail", label: "E-mail responsável", width: 26, default: true },
|
||||
{ key: "linkedUsers", label: "Usuários vinculados", width: 28, default: true },
|
||||
{ key: "authEmail", label: "E-mail autenticado", width: 26, default: true },
|
||||
{ key: "osName", label: "Sistema operacional", width: 20, default: true },
|
||||
{ key: "osVersion", label: "Versão SO", width: 18, default: true },
|
||||
{ key: "architecture", label: "Arquitetura", width: 14, default: true },
|
||||
{ key: "hardwareVendor", label: "Fabricante", width: 20, default: true },
|
||||
{ key: "hardwareModel", label: "Modelo", width: 22, default: true },
|
||||
{ key: "hardwareSerial", label: "Serial hardware", width: 24, default: true },
|
||||
{ key: "cpu", label: "Processador", width: 22, default: true },
|
||||
{ key: "physicalCores", label: "Cores físicas", width: 14, default: true },
|
||||
{ key: "logicalCores", label: "Cores lógicas", width: 14, default: true },
|
||||
{ key: "memoryGiB", label: "Memória (GiB)", width: 20, default: true },
|
||||
{ key: "gpus", label: "GPUs", width: 24, default: true },
|
||||
{ key: "labels", label: "Labels", width: 24, default: true },
|
||||
{ key: "macs", label: "MACs", width: 24, default: true },
|
||||
{ key: "serials", label: "Seriais", width: 24, default: true },
|
||||
{ key: "primaryIp", label: "IP principal", width: 18, default: true },
|
||||
{ key: "publicIp", label: "IP público", width: 18, default: true },
|
||||
{ key: "registeredBy", label: "Registrado via", width: 18, default: true },
|
||||
{ key: "tokenExpiresAt", label: "Token expira em", width: 20, default: true },
|
||||
{ key: "tokenLastUsedAt", label: "Token último uso", width: 20, default: true },
|
||||
{ key: "tokenUsageCount", label: "Uso do token", width: 14, default: true },
|
||||
{ key: "createdAt", label: "Criado em", width: 20, default: true },
|
||||
{ key: "updatedAt", label: "Atualizado em", width: 20, default: true },
|
||||
{ key: "softwareCount", label: "Softwares instalados", width: 20, default: true },
|
||||
{ key: "osBuild", label: "Build SO", width: 18, default: true },
|
||||
{ key: "osLicense", label: "Licença ativada", width: 18, default: true },
|
||||
{ key: "osExperience", label: "Experiência SO", width: 18, default: true },
|
||||
{ key: "domain", label: "Domínio", width: 18, default: true },
|
||||
{ key: "workgroup", label: "Grupo de trabalho", width: 20, default: true },
|
||||
{ key: "deviceName", label: "Nome do dispositivo", width: 24, default: true },
|
||||
{ key: "boardSerial", label: "Serial placa-mãe", width: 24, default: true },
|
||||
{ key: "collaboratorName", label: "Colaborador (nome)", width: 24, default: true },
|
||||
{ key: "collaboratorEmail", label: "Colaborador (e-mail)", width: 26, default: true },
|
||||
{ key: "remoteAccessCount", label: "Acessos remotos", width: 18, default: true },
|
||||
{ key: "fleetId", label: "Fleet ID", width: 18, default: true },
|
||||
{ key: "fleetTeam", label: "Equipe Fleet", width: 18, default: true },
|
||||
{ key: "fleetUpdatedAt", label: "Fleet atualizado em", width: 20, default: true },
|
||||
{ key: "managementMode", label: "Modo de gestão", width: 20, default: false },
|
||||
]
|
||||
|
||||
export type DeviceInventoryColumnKey = (typeof DEVICE_INVENTORY_COLUMN_METADATA)[number]["key"]
|
||||
|
|
@ -65,6 +65,11 @@ const serverTicketSchema = z.object({
|
|||
tags: z.array(z.string()).default([]).optional(),
|
||||
lastTimelineEntry: z.string().nullable().optional(),
|
||||
metrics: z.any().nullable().optional(),
|
||||
csatScore: z.number().nullable().optional(),
|
||||
csatMaxScore: z.number().nullable().optional(),
|
||||
csatComment: z.string().nullable().optional(),
|
||||
csatRatedAt: z.number().nullable().optional(),
|
||||
csatRatedBy: z.string().nullable().optional(),
|
||||
category: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
|
|
@ -154,9 +159,17 @@ const serverTicketWithDetailsSchema = serverTicketSchema.extend({
|
|||
});
|
||||
|
||||
export function mapTicketFromServer(input: unknown) {
|
||||
const s = serverTicketSchema.parse(input);
|
||||
const {
|
||||
csatScore,
|
||||
csatMaxScore,
|
||||
csatComment,
|
||||
csatRatedAt,
|
||||
csatRatedBy,
|
||||
...base
|
||||
} = serverTicketSchema.parse(input);
|
||||
const s = { csatScore, csatMaxScore, csatComment, csatRatedAt, csatRatedBy, ...base };
|
||||
const ui = {
|
||||
...s,
|
||||
...base,
|
||||
status: normalizeTicketStatus(s.status),
|
||||
company: s.company
|
||||
? { id: s.company.id, name: s.company.name, isAvulso: s.company.isAvulso ?? false }
|
||||
|
|
@ -179,6 +192,11 @@ export function mapTicketFromServer(input: unknown) {
|
|||
dueAt: s.dueAt ? new Date(s.dueAt) : null,
|
||||
firstResponseAt: s.firstResponseAt ? new Date(s.firstResponseAt) : null,
|
||||
resolvedAt: s.resolvedAt ? new Date(s.resolvedAt) : null,
|
||||
csatScore: typeof csatScore === "number" ? csatScore : null,
|
||||
csatMaxScore: typeof csatMaxScore === "number" ? csatMaxScore : null,
|
||||
csatComment: typeof csatComment === "string" && csatComment.trim().length > 0 ? csatComment.trim() : null,
|
||||
csatRatedAt: csatRatedAt ? new Date(csatRatedAt) : null,
|
||||
csatRatedBy: csatRatedBy ?? null,
|
||||
workSummary: s.workSummary
|
||||
? {
|
||||
totalWorkedMs: s.workSummary.totalWorkedMs,
|
||||
|
|
@ -211,7 +229,15 @@ export function mapTicketsFromServerList(arr: unknown[]) {
|
|||
}
|
||||
|
||||
export function mapTicketWithDetailsFromServer(input: unknown) {
|
||||
const s = serverTicketWithDetailsSchema.parse(input);
|
||||
const {
|
||||
csatScore,
|
||||
csatMaxScore,
|
||||
csatComment,
|
||||
csatRatedAt,
|
||||
csatRatedBy,
|
||||
...base
|
||||
} = serverTicketWithDetailsSchema.parse(input);
|
||||
const s = { csatScore, csatMaxScore, csatComment, csatRatedAt, csatRatedBy, ...base };
|
||||
const customFields = Object.entries(s.customFields ?? {}).reduce<
|
||||
Record<string, { label: string; type: string; value?: unknown; displayValue?: string }>
|
||||
>(
|
||||
|
|
@ -231,47 +257,52 @@ export function mapTicketWithDetailsFromServer(input: unknown) {
|
|||
{}
|
||||
);
|
||||
const ui = {
|
||||
...s,
|
||||
...base,
|
||||
customFields,
|
||||
status: normalizeTicketStatus(s.status),
|
||||
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,
|
||||
company: s.company ? { id: s.company.id, name: s.company.name, isAvulso: s.company.isAvulso ?? false } : undefined,
|
||||
machine: s.machine
|
||||
status: normalizeTicketStatus(base.status),
|
||||
category: base.category ?? undefined,
|
||||
subcategory: base.subcategory ?? undefined,
|
||||
lastTimelineEntry: base.lastTimelineEntry ?? undefined,
|
||||
updatedAt: new Date(base.updatedAt),
|
||||
createdAt: new Date(base.createdAt),
|
||||
dueAt: base.dueAt ? new Date(base.dueAt) : null,
|
||||
firstResponseAt: base.firstResponseAt ? new Date(base.firstResponseAt) : null,
|
||||
resolvedAt: base.resolvedAt ? new Date(base.resolvedAt) : null,
|
||||
csatScore: typeof csatScore === "number" ? csatScore : null,
|
||||
csatMaxScore: typeof csatMaxScore === "number" ? csatMaxScore : null,
|
||||
csatComment: typeof csatComment === "string" && csatComment.trim().length > 0 ? csatComment.trim() : null,
|
||||
csatRatedAt: csatRatedAt ? new Date(csatRatedAt) : null,
|
||||
csatRatedBy: csatRatedBy ?? null,
|
||||
company: base.company ? { id: base.company.id, name: base.company.name, isAvulso: base.company.isAvulso ?? false } : undefined,
|
||||
machine: base.machine
|
||||
? {
|
||||
id: s.machine.id ?? null,
|
||||
hostname: s.machine.hostname ?? null,
|
||||
persona: s.machine.persona ?? null,
|
||||
assignedUserName: s.machine.assignedUserName ?? null,
|
||||
assignedUserEmail: s.machine.assignedUserEmail ?? null,
|
||||
status: s.machine.status ?? null,
|
||||
id: base.machine.id ?? null,
|
||||
hostname: base.machine.hostname ?? null,
|
||||
persona: base.machine.persona ?? null,
|
||||
assignedUserName: base.machine.assignedUserName ?? null,
|
||||
assignedUserEmail: base.machine.assignedUserEmail ?? null,
|
||||
status: base.machine.status ?? null,
|
||||
}
|
||||
: null,
|
||||
timeline: s.timeline.map((e) => ({ ...e, createdAt: new Date(e.createdAt) })),
|
||||
comments: s.comments.map((c) => ({
|
||||
timeline: base.timeline.map((e) => ({ ...e, createdAt: new Date(e.createdAt) })),
|
||||
comments: base.comments.map((c) => ({
|
||||
...c,
|
||||
createdAt: new Date(c.createdAt),
|
||||
updatedAt: new Date(c.updatedAt),
|
||||
})),
|
||||
workSummary: s.workSummary
|
||||
workSummary: base.workSummary
|
||||
? {
|
||||
totalWorkedMs: s.workSummary.totalWorkedMs,
|
||||
internalWorkedMs: s.workSummary.internalWorkedMs ?? 0,
|
||||
externalWorkedMs: s.workSummary.externalWorkedMs ?? 0,
|
||||
serverNow: s.workSummary.serverNow,
|
||||
activeSession: s.workSummary.activeSession
|
||||
totalWorkedMs: base.workSummary.totalWorkedMs,
|
||||
internalWorkedMs: base.workSummary.internalWorkedMs ?? 0,
|
||||
externalWorkedMs: base.workSummary.externalWorkedMs ?? 0,
|
||||
serverNow: base.workSummary.serverNow,
|
||||
activeSession: base.workSummary.activeSession
|
||||
? {
|
||||
...s.workSummary.activeSession,
|
||||
startedAt: new Date(s.workSummary.activeSession.startedAt),
|
||||
...base.workSummary.activeSession,
|
||||
startedAt: new Date(base.workSummary.activeSession.startedAt),
|
||||
}
|
||||
: null,
|
||||
perAgentTotals: (s.workSummary.perAgentTotals ?? []).map((item) => ({
|
||||
perAgentTotals: (base.workSummary.perAgentTotals ?? []).map((item) => ({
|
||||
agentId: item.agentId,
|
||||
agentName: item.agentName ?? null,
|
||||
agentEmail: item.agentEmail ?? null,
|
||||
|
|
|
|||
|
|
@ -139,17 +139,30 @@ export const ticketSchema = z.object({
|
|||
.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(),
|
||||
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(),
|
||||
relatedTicketIds: z.array(z.string()).optional(),
|
||||
resolvedWithTicketId: z.string().nullable().optional(),
|
||||
reopenDeadline: z.number().nullable().optional(),
|
||||
reopenWindowDays: z.number().nullable().optional(),
|
||||
reopenedAt: z.number().nullable().optional(),
|
||||
reopenedBy: z.string().nullable().optional(),
|
||||
chatEnabled: z.boolean().optional(),
|
||||
formTemplate: z.string().nullable().optional(),
|
||||
csatScore: z.number().nullable().optional(),
|
||||
csatMaxScore: z.number().nullable().optional(),
|
||||
csatComment: z.string().nullable().optional(),
|
||||
csatRatedAt: z.coerce.date().nullable().optional(),
|
||||
csatRatedBy: z.string().nullable().optional(),
|
||||
category: ticketCategorySummarySchema.nullable().optional(),
|
||||
subcategory: ticketSubcategorySummarySchema.nullable().optional(),
|
||||
workSummary: z
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue