diff --git a/Relatório personalizadoCLIENTES_Inventario_Estações.xlsx b/Relatório personalizadoCLIENTES_Inventario_Estações.xlsx new file mode 100644 index 0000000..38653d2 Binary files /dev/null and b/Relatório personalizadoCLIENTES_Inventario_Estações.xlsx differ diff --git a/convex/machines.ts b/convex/machines.ts index ac8c5a7..d19caec 100644 --- a/convex/machines.ts +++ b/convex/machines.ts @@ -1,7 +1,8 @@ // ci: trigger convex functions deploy (no-op) import { mutation, query } from "./_generated/server" import { api } from "./_generated/api" -import { ConvexError, v } from "convex/values" +import { paginationOptsValidator } from "convex/server" +import { ConvexError, v, Infer } from "convex/values" import { sha256 } from "@noble/hashes/sha256" import { randomBytes } from "@noble/hashes/utils" import type { Doc, Id } from "./_generated/dataModel" @@ -12,6 +13,8 @@ const DEFAULT_TENANT_ID = "tenant-atlas" const DEFAULT_TOKEN_TTL_MS = 1000 * 60 * 60 * 24 * 30 // 30 dias const ALLOWED_MACHINE_PERSONAS = new Set(["collaborator", "manager"]) const DEFAULT_OFFLINE_THRESHOLD_MS = 10 * 60 * 1000 +const OPEN_TICKET_STATUSES = new Set(["PENDING", "AWAITING_ATTENDANCE", "PAUSED"]) +const MACHINE_TICKETS_STATS_PAGE_SIZE = 200 type NormalizedIdentifiers = { macs: string[] @@ -876,123 +879,128 @@ export const listByTenant = query({ }, }) +export async function getByIdHandler( + ctx: QueryCtx, + args: { id: Id<"machines">; includeMetadata?: boolean } +) { + const includeMetadata = Boolean(args.includeMetadata) + const now = Date.now() + + const machine = await ctx.db.get(args.id) + if (!machine) return null + + const companyFromId = machine.companyId ? await ctx.db.get(machine.companyId) : null + const machineSlug = machine.companySlug ?? null + let companyFromSlug: typeof companyFromId | null = null + if (!companyFromId && machineSlug) { + companyFromSlug = await ctx.db + .query("companies") + .withIndex("by_tenant_slug", (q) => q.eq("tenantId", machine.tenantId).eq("slug", machineSlug)) + .unique() + } + const resolvedCompany = companyFromId ?? companyFromSlug + + const activeToken = await findActiveMachineToken(ctx, machine._id, now) + + const offlineThresholdMs = getOfflineThresholdMs() + const staleThresholdMs = getStaleThresholdMs(offlineThresholdMs) + const manualStatus = (machine.status ?? "").toLowerCase() + let derivedStatus: string + if (machine.isActive === false) { + derivedStatus = "deactivated" + } else if (["maintenance", "blocked"].includes(manualStatus)) { + derivedStatus = manualStatus + } else if (machine.lastHeartbeatAt) { + const age = now - machine.lastHeartbeatAt + if (age <= offlineThresholdMs) { + derivedStatus = "online" + } else if (age <= staleThresholdMs) { + derivedStatus = "offline" + } else { + derivedStatus = "stale" + } + } else { + derivedStatus = machine.status ?? "unknown" + } + + const meta = includeMetadata ? (machine.metadata ?? null) : null + let metrics: Record | null = null + let inventory: Record | null = null + let postureAlerts: Array> | null = null + let lastPostureAt: number | null = null + if (meta && typeof meta === "object") { + const metaRecord = meta as Record + if (metaRecord.metrics && typeof metaRecord.metrics === "object") { + metrics = metaRecord.metrics as Record + } + if (metaRecord.inventory && typeof metaRecord.inventory === "object") { + inventory = metaRecord.inventory as Record + } + if (Array.isArray(metaRecord.postureAlerts)) { + postureAlerts = metaRecord.postureAlerts as Array> + } + if (typeof metaRecord.lastPostureAt === "number") { + lastPostureAt = metaRecord.lastPostureAt as number + } + } + + const linkedUserIds = machine.linkedUserIds ?? [] + const linkedUsers = await Promise.all( + linkedUserIds.map(async (id) => { + const u = await ctx.db.get(id) + if (!u) return null + return { id: u._id, email: u.email, name: u.name } + }) + ).then((arr) => arr.filter(Boolean) as Array<{ id: string; email: string; name: string }>) + + return { + id: machine._id, + tenantId: machine.tenantId, + hostname: machine.hostname, + companyId: machine.companyId ?? null, + companySlug: machine.companySlug ?? resolvedCompany?.slug ?? null, + companyName: resolvedCompany?.name ?? null, + osName: machine.osName, + osVersion: machine.osVersion ?? null, + architecture: machine.architecture ?? null, + macAddresses: machine.macAddresses, + serialNumbers: machine.serialNumbers, + authUserId: machine.authUserId ?? null, + authEmail: machine.authEmail ?? null, + persona: machine.persona ?? null, + assignedUserId: machine.assignedUserId ?? null, + assignedUserEmail: machine.assignedUserEmail ?? null, + assignedUserName: machine.assignedUserName ?? null, + assignedUserRole: machine.assignedUserRole ?? null, + linkedUsers, + status: derivedStatus, + isActive: machine.isActive ?? true, + lastHeartbeatAt: machine.lastHeartbeatAt ?? null, + heartbeatAgeMs: machine.lastHeartbeatAt ? now - machine.lastHeartbeatAt : null, + registeredBy: machine.registeredBy ?? null, + createdAt: machine.createdAt, + updatedAt: machine.updatedAt, + token: activeToken + ? { + expiresAt: activeToken.expiresAt, + lastUsedAt: activeToken.lastUsedAt ?? null, + usageCount: activeToken.usageCount ?? 0, + } + : null, + metrics, + inventory, + postureAlerts, + lastPostureAt, + remoteAccess: machine.remoteAccess ?? null, + } +} + export const getById = query({ args: { id: v.id("machines"), includeMetadata: v.optional(v.boolean()), }, - handler: async (ctx, args) => { - const includeMetadata = Boolean(args.includeMetadata) - const now = Date.now() - - const machine = await ctx.db.get(args.id) - if (!machine) return null - - const companyFromId = machine.companyId ? await ctx.db.get(machine.companyId) : null - const machineSlug = machine.companySlug ?? null - let companyFromSlug: typeof companyFromId | null = null - if (!companyFromId && machineSlug) { - companyFromSlug = await ctx.db - .query("companies") - .withIndex("by_tenant_slug", (q) => q.eq("tenantId", machine.tenantId).eq("slug", machineSlug)) - .unique() - } - const resolvedCompany = companyFromId ?? companyFromSlug - - const activeToken = await findActiveMachineToken(ctx, machine._id, now) - - const offlineThresholdMs = getOfflineThresholdMs() - const staleThresholdMs = getStaleThresholdMs(offlineThresholdMs) - const manualStatus = (machine.status ?? "").toLowerCase() - let derivedStatus: string - if (machine.isActive === false) { - derivedStatus = "deactivated" - } else if (["maintenance", "blocked"].includes(manualStatus)) { - derivedStatus = manualStatus - } else if (machine.lastHeartbeatAt) { - const age = now - machine.lastHeartbeatAt - if (age <= offlineThresholdMs) { - derivedStatus = "online" - } else if (age <= staleThresholdMs) { - derivedStatus = "offline" - } else { - derivedStatus = "stale" - } - } else { - derivedStatus = machine.status ?? "unknown" - } - - const meta = includeMetadata ? (machine.metadata ?? null) : null - let metrics: Record | null = null - let inventory: Record | null = null - let postureAlerts: Array> | null = null - let lastPostureAt: number | null = null - if (meta && typeof meta === "object") { - const metaRecord = meta as Record - if (metaRecord.metrics && typeof metaRecord.metrics === "object") { - metrics = metaRecord.metrics as Record - } - if (metaRecord.inventory && typeof metaRecord.inventory === "object") { - inventory = metaRecord.inventory as Record - } - if (Array.isArray(metaRecord.postureAlerts)) { - postureAlerts = metaRecord.postureAlerts as Array> - } - if (typeof metaRecord.lastPostureAt === "number") { - lastPostureAt = metaRecord.lastPostureAt as number - } - } - - const linkedUserIds = machine.linkedUserIds ?? [] - const linkedUsers = await Promise.all( - linkedUserIds.map(async (id) => { - const u = await ctx.db.get(id) - if (!u) return null - return { id: u._id, email: u.email, name: u.name } - }) - ).then((arr) => arr.filter(Boolean) as Array<{ id: string; email: string; name: string }>) - - return { - id: machine._id, - tenantId: machine.tenantId, - hostname: machine.hostname, - companyId: machine.companyId ?? null, - companySlug: machine.companySlug ?? resolvedCompany?.slug ?? null, - companyName: resolvedCompany?.name ?? null, - osName: machine.osName, - osVersion: machine.osVersion ?? null, - architecture: machine.architecture ?? null, - macAddresses: machine.macAddresses, - serialNumbers: machine.serialNumbers, - authUserId: machine.authUserId ?? null, - authEmail: machine.authEmail ?? null, - persona: machine.persona ?? null, - assignedUserId: machine.assignedUserId ?? null, - assignedUserEmail: machine.assignedUserEmail ?? null, - assignedUserName: machine.assignedUserName ?? null, - assignedUserRole: machine.assignedUserRole ?? null, - linkedUsers, - status: derivedStatus, - isActive: machine.isActive ?? true, - lastHeartbeatAt: machine.lastHeartbeatAt ?? null, - heartbeatAgeMs: machine.lastHeartbeatAt ? now - machine.lastHeartbeatAt : null, - registeredBy: machine.registeredBy ?? null, - createdAt: machine.createdAt, - updatedAt: machine.updatedAt, - token: activeToken - ? { - expiresAt: activeToken.expiresAt, - lastUsedAt: activeToken.lastUsedAt ?? null, - usageCount: activeToken.usageCount ?? 0, - } - : null, - metrics, - inventory, - postureAlerts, - lastPostureAt, - remoteAccess: machine.remoteAccess ?? null, - } - }, + handler: getByIdHandler, }) export const listAlerts = query({ @@ -1029,7 +1037,7 @@ export const listOpenTickets = query({ handler: async (ctx, { machineId, limit }) => { const machine = await ctx.db.get(machineId) if (!machine) { - return [] + return { totalOpen: 0, hasMore: false, tickets: [] } } const takeLimit = Math.max(1, Math.min(limit ?? 10, 50)) const candidates = await ctx.db @@ -1041,31 +1049,392 @@ export const listOpenTickets = query({ const openTickets = candidates .filter((ticket) => normalizeStatus(ticket.status) !== "RESOLVED") .sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)) - .slice(0, takeLimit) + const totalOpen = openTickets.length + const limited = openTickets.slice(0, takeLimit) - return openTickets.map((ticket) => ({ - id: ticket._id, - reference: ticket.reference, - subject: ticket.subject, - status: normalizeStatus(ticket.status), - priority: ticket.priority ?? "MEDIUM", - updatedAt: ticket.updatedAt, - createdAt: ticket.createdAt, - assignee: ticket.assigneeSnapshot - ? { - name: (ticket.assigneeSnapshot as { name?: string })?.name ?? null, - email: (ticket.assigneeSnapshot as { email?: string })?.email ?? null, - } - : null, - machine: { - id: String(ticket.machineId ?? machineId), - hostname: - ((ticket.machineSnapshot as { hostname?: string } | undefined)?.hostname ?? machine.hostname ?? null), - }, - })) + return { + totalOpen, + hasMore: totalOpen > takeLimit, + tickets: limited.map((ticket) => ({ + id: ticket._id, + reference: ticket.reference, + subject: ticket.subject, + status: normalizeStatus(ticket.status), + priority: ticket.priority ?? "MEDIUM", + updatedAt: ticket.updatedAt, + createdAt: ticket.createdAt, + assignee: ticket.assigneeSnapshot + ? { + name: (ticket.assigneeSnapshot as { name?: string })?.name ?? null, + email: (ticket.assigneeSnapshot as { email?: string })?.email ?? null, + } + : null, + machine: { + id: String(ticket.machineId ?? machineId), + hostname: + ((ticket.machineSnapshot as { hostname?: string } | undefined)?.hostname ?? machine.hostname ?? null), + }, + })), + } }, }) +type MachineTicketsHistoryFilter = { + statusFilter: "all" | "open" | "resolved" + priorityFilter: string | null + from: number | null + to: number | null +} + +type ListTicketsHistoryArgs = { + machineId: Id<"machines"> + status?: "all" | "open" | "resolved" + priority?: string + search?: string + from?: number + to?: number + paginationOpts: Infer +} + +type GetTicketsHistoryStatsArgs = { + machineId: Id<"machines"> + status?: "all" | "open" | "resolved" + priority?: string + search?: string + from?: number + to?: number +} + +function createMachineTicketsQuery( + ctx: QueryCtx, + machine: Doc<"machines">, + machineId: Id<"machines">, + filters: MachineTicketsHistoryFilter +) { + let working = ctx.db + .query("tickets") + .withIndex("by_tenant_machine", (q) => q.eq("tenantId", machine.tenantId).eq("machineId", machineId)) + .order("desc") + + if (filters.statusFilter === "open") { + working = working.filter((q) => + q.or( + q.eq(q.field("status"), "PENDING"), + q.eq(q.field("status"), "AWAITING_ATTENDANCE"), + q.eq(q.field("status"), "PAUSED") + ) + ) + } else if (filters.statusFilter === "resolved") { + working = working.filter((q) => q.eq(q.field("status"), "RESOLVED")) + } + + if (filters.priorityFilter) { + working = working.filter((q) => q.eq(q.field("priority"), filters.priorityFilter)) + } + + if (typeof filters.from === "number") { + working = working.filter((q) => q.gte(q.field("updatedAt"), filters.from!)) + } + + if (typeof filters.to === "number") { + working = working.filter((q) => q.lte(q.field("updatedAt"), filters.to!)) + } + + return working +} + +function matchesTicketSearch(ticket: Doc<"tickets">, searchTerm: string): boolean { + const normalized = searchTerm.trim().toLowerCase() + if (!normalized) return true + + const subject = ticket.subject.toLowerCase() + if (subject.includes(normalized)) return true + + const summary = typeof ticket.summary === "string" ? ticket.summary.toLowerCase() : "" + if (summary.includes(normalized)) return true + + const reference = `#${ticket.reference}`.toLowerCase() + if (reference.includes(normalized)) return true + + const requesterSnapshot = ticket.requesterSnapshot as { name?: string; email?: string } | undefined + if (requesterSnapshot) { + if (requesterSnapshot.name?.toLowerCase().includes(normalized)) return true + if (requesterSnapshot.email?.toLowerCase().includes(normalized)) return true + } + + const assigneeSnapshot = ticket.assigneeSnapshot as { name?: string; email?: string } | undefined + if (assigneeSnapshot) { + if (assigneeSnapshot.name?.toLowerCase().includes(normalized)) return true + if (assigneeSnapshot.email?.toLowerCase().includes(normalized)) return true + } + + return false +} + +export async function listTicketsHistoryHandler(ctx: QueryCtx, args: ListTicketsHistoryArgs) { + const machine = await ctx.db.get(args.machineId) + if (!machine) { + return { + page: [], + isDone: true, + continueCursor: args.paginationOpts.cursor ?? "", + } + } + + const normalizedStatusFilter = args.status ?? "all" + const normalizedPriorityFilter = args.priority ? args.priority.toUpperCase() : null + const searchTerm = args.search?.trim().toLowerCase() ?? null + const from = typeof args.from === "number" ? args.from : null + const to = typeof args.to === "number" ? args.to : null + const filters: MachineTicketsHistoryFilter = { + statusFilter: normalizedStatusFilter, + priorityFilter: normalizedPriorityFilter, + from, + to, + } + + const pageResult = await createMachineTicketsQuery(ctx, machine, args.machineId, filters).paginate(args.paginationOpts) + + const page = searchTerm ? pageResult.page.filter((ticket) => matchesTicketSearch(ticket, searchTerm)) : pageResult.page + const queueCache = new Map | null>() + const items = await Promise.all( + page.map(async (ticket) => { + let queueName: string | null = null + if (ticket.queueId) { + const key = String(ticket.queueId) + if (!queueCache.has(key)) { + queueCache.set(key, (await ctx.db.get(ticket.queueId)) as Doc<"queues"> | null) + } + queueName = queueCache.get(key)?.name ?? null + } + const requesterSnapshot = ticket.requesterSnapshot as { name?: string; email?: string } | undefined + const assigneeSnapshot = ticket.assigneeSnapshot as { name?: string; email?: string } | undefined + return { + id: ticket._id, + reference: ticket.reference, + subject: ticket.subject, + status: normalizeStatus(ticket.status), + priority: (ticket.priority ?? "MEDIUM").toString().toUpperCase(), + updatedAt: ticket.updatedAt ?? ticket.createdAt ?? 0, + createdAt: ticket.createdAt ?? 0, + queue: queueName, + requester: requesterSnapshot + ? { + name: requesterSnapshot.name ?? null, + email: requesterSnapshot.email ?? null, + } + : null, + assignee: assigneeSnapshot + ? { + name: assigneeSnapshot.name ?? null, + email: assigneeSnapshot.email ?? null, + } + : null, + } + }) + ) + + return { + page: items, + isDone: pageResult.isDone, + continueCursor: pageResult.continueCursor, + splitCursor: pageResult.splitCursor ?? undefined, + pageStatus: pageResult.pageStatus ?? undefined, + } +} + +export const listTicketsHistory = query({ + args: { + machineId: v.id("machines"), + status: v.optional(v.union(v.literal("all"), v.literal("open"), v.literal("resolved"))), + priority: v.optional(v.string()), + search: v.optional(v.string()), + from: v.optional(v.number()), + to: v.optional(v.number()), + paginationOpts: paginationOptsValidator, + }, + handler: listTicketsHistoryHandler, +}) + +export async function getTicketsHistoryStatsHandler( + ctx: QueryCtx, + args: GetTicketsHistoryStatsArgs +) { + const machine = await ctx.db.get(args.machineId) + if (!machine) { + return { total: 0, openCount: 0, resolvedCount: 0 } + } + + const normalizedStatusFilter = args.status ?? "all" + const normalizedPriorityFilter = args.priority ? args.priority.toUpperCase() : null + const searchTerm = args.search?.trim().toLowerCase() ?? "" + const from = typeof args.from === "number" ? args.from : null + const to = typeof args.to === "number" ? args.to : null + const filters: MachineTicketsHistoryFilter = { + statusFilter: normalizedStatusFilter, + priorityFilter: normalizedPriorityFilter, + from, + to, + } + + let cursor: string | null = null + let total = 0 + let openCount = 0 + let done = false + + while (!done) { + const pageResult = await createMachineTicketsQuery(ctx, machine, args.machineId, filters).paginate({ + numItems: MACHINE_TICKETS_STATS_PAGE_SIZE, + cursor, + }) + const page = searchTerm ? pageResult.page.filter((ticket) => matchesTicketSearch(ticket, searchTerm)) : pageResult.page + total += page.length + for (const ticket of page) { + if (OPEN_TICKET_STATUSES.has(normalizeStatus(ticket.status))) { + openCount += 1 + } + } + done = pageResult.isDone + cursor = pageResult.continueCursor ?? null + if (!cursor) { + done = true + } + } + + return { + total, + openCount, + resolvedCount: total - openCount, + } +} + +export const getTicketsHistoryStats = query({ + args: { + machineId: v.id("machines"), + status: v.optional(v.union(v.literal("all"), v.literal("open"), v.literal("resolved"))), + priority: v.optional(v.string()), + search: v.optional(v.string()), + from: v.optional(v.number()), + to: v.optional(v.number()), + }, + handler: getTicketsHistoryStatsHandler, +}) + +export async function updatePersonaHandler( + ctx: MutationCtx, + args: { + machineId: Id<"machines"> + persona?: string | null + assignedUserId?: Id<"users"> + assignedUserEmail?: string | null + assignedUserName?: string | null + assignedUserRole?: string | null + } +) { + const machine = await ctx.db.get(args.machineId) + if (!machine) { + throw new ConvexError("Máquina não encontrada") + } + + let nextPersona = machine.persona ?? undefined + const personaProvided = args.persona !== undefined + if (args.persona !== undefined) { + const trimmed = (args.persona ?? "").trim().toLowerCase() + if (!trimmed) { + nextPersona = undefined + } else if (!ALLOWED_MACHINE_PERSONAS.has(trimmed)) { + throw new ConvexError("Perfil inválido para a máquina") + } else { + nextPersona = trimmed + } + } + + let nextAssignedUserId = machine.assignedUserId ?? undefined + if (args.assignedUserId !== undefined) { + nextAssignedUserId = args.assignedUserId + } + + let nextAssignedEmail = machine.assignedUserEmail ?? undefined + if (args.assignedUserEmail !== undefined) { + const trimmedEmail = (args.assignedUserEmail ?? "").trim().toLowerCase() + nextAssignedEmail = trimmedEmail || undefined + } + + let nextAssignedName = machine.assignedUserName ?? undefined + if (args.assignedUserName !== undefined) { + const trimmedName = (args.assignedUserName ?? "").trim() + nextAssignedName = trimmedName || undefined + } + + let nextAssignedRole = machine.assignedUserRole ?? undefined + if (args.assignedUserRole !== undefined) { + const trimmedRole = (args.assignedUserRole ?? "").trim().toUpperCase() + nextAssignedRole = trimmedRole || undefined + } + + if (personaProvided && !nextPersona) { + nextAssignedUserId = undefined + nextAssignedEmail = undefined + nextAssignedName = undefined + nextAssignedRole = undefined + } + + if (nextPersona && !nextAssignedUserId) { + throw new ConvexError("Associe um usuário ao definir a persona da máquina") + } + + if (nextPersona && nextAssignedUserId) { + const assignedUser = await ctx.db.get(nextAssignedUserId) + if (!assignedUser) { + throw new ConvexError("Usuário vinculado não encontrado") + } + if (assignedUser.tenantId !== machine.tenantId) { + throw new ConvexError("Usuário vinculado pertence a outro tenant") + } + } + + let nextMetadata = machine.metadata + if (nextPersona) { + const collaboratorMeta = { + email: nextAssignedEmail ?? null, + name: nextAssignedName ?? null, + role: nextPersona, + } + nextMetadata = mergeMetadata(machine.metadata, { collaborator: collaboratorMeta }) + } + + const patch: Record = { + persona: nextPersona, + assignedUserId: nextPersona ? nextAssignedUserId : undefined, + assignedUserEmail: nextPersona ? nextAssignedEmail : undefined, + assignedUserName: nextPersona ? nextAssignedName : undefined, + assignedUserRole: nextPersona ? nextAssignedRole : undefined, + updatedAt: Date.now(), + } + if (nextMetadata !== machine.metadata) { + patch.metadata = nextMetadata + } + + if (personaProvided) { + patch.persona = nextPersona + } + + if (nextPersona) { + patch.assignedUserId = nextAssignedUserId + patch.assignedUserEmail = nextAssignedEmail + patch.assignedUserName = nextAssignedName + patch.assignedUserRole = nextAssignedRole + } else if (personaProvided) { + patch.assignedUserId = undefined + patch.assignedUserEmail = undefined + patch.assignedUserName = undefined + patch.assignedUserRole = undefined + } + + await ctx.db.patch(machine._id, patch) + return { ok: true, persona: nextPersona ?? null } +} + export const updatePersona = mutation({ args: { machineId: v.id("machines"), @@ -1075,110 +1444,7 @@ export const updatePersona = mutation({ assignedUserName: v.optional(v.string()), assignedUserRole: v.optional(v.string()), }, - handler: async (ctx, args) => { - const machine = await ctx.db.get(args.machineId) - if (!machine) { - throw new ConvexError("Máquina não encontrada") - } - - let nextPersona = machine.persona ?? undefined - const personaProvided = args.persona !== undefined - if (args.persona !== undefined) { - const trimmed = args.persona.trim().toLowerCase() - if (!trimmed) { - nextPersona = undefined - } else if (!ALLOWED_MACHINE_PERSONAS.has(trimmed)) { - throw new ConvexError("Perfil inválido para a máquina") - } else { - nextPersona = trimmed - } - } - - let nextAssignedUserId = machine.assignedUserId ?? undefined - if (args.assignedUserId !== undefined) { - nextAssignedUserId = args.assignedUserId - } - - let nextAssignedEmail = machine.assignedUserEmail ?? undefined - if (args.assignedUserEmail !== undefined) { - const trimmedEmail = args.assignedUserEmail.trim().toLowerCase() - nextAssignedEmail = trimmedEmail || undefined - } - - let nextAssignedName = machine.assignedUserName ?? undefined - if (args.assignedUserName !== undefined) { - const trimmedName = args.assignedUserName.trim() - nextAssignedName = trimmedName || undefined - } - - let nextAssignedRole = machine.assignedUserRole ?? undefined - if (args.assignedUserRole !== undefined) { - const trimmedRole = args.assignedUserRole.trim().toUpperCase() - nextAssignedRole = trimmedRole || undefined - } - - if (personaProvided && !nextPersona) { - nextAssignedUserId = undefined - nextAssignedEmail = undefined - nextAssignedName = undefined - nextAssignedRole = undefined - } - - if (nextPersona && !nextAssignedUserId) { - throw new ConvexError("Associe um usuário ao definir a persona da máquina") - } - - if (nextPersona && nextAssignedUserId) { - const assignedUser = await ctx.db.get(nextAssignedUserId) - if (!assignedUser) { - throw new ConvexError("Usuário vinculado não encontrado") - } - if (assignedUser.tenantId !== machine.tenantId) { - throw new ConvexError("Usuário vinculado pertence a outro tenant") - } - } - - let nextMetadata = machine.metadata - if (nextPersona) { - const collaboratorMeta = { - email: nextAssignedEmail ?? null, - name: nextAssignedName ?? null, - role: nextPersona, - } - nextMetadata = mergeMetadata(machine.metadata, { collaborator: collaboratorMeta }) - } - - const patch: Record = { - persona: nextPersona, - assignedUserId: nextPersona ? nextAssignedUserId : undefined, - assignedUserEmail: nextPersona ? nextAssignedEmail : undefined, - assignedUserName: nextPersona ? nextAssignedName : undefined, - assignedUserRole: nextPersona ? nextAssignedRole : undefined, - updatedAt: Date.now(), - } - if (nextMetadata !== machine.metadata) { - patch.metadata = nextMetadata - } - - if (personaProvided) { - patch.persona = nextPersona - } - - if (nextPersona) { - patch.assignedUserId = nextAssignedUserId - patch.assignedUserEmail = nextAssignedEmail - patch.assignedUserName = nextAssignedName - patch.assignedUserRole = nextAssignedRole - } else if (personaProvided) { - patch.assignedUserId = undefined - patch.assignedUserEmail = undefined - patch.assignedUserName = undefined - patch.assignedUserRole = undefined - } - - await ctx.db.patch(machine._id, patch) - return { ok: true, persona: nextPersona ?? null } - }, + handler: updatePersonaHandler, }) export const getContext = query({ diff --git a/convex/reports.ts b/convex/reports.ts index f718ec1..6dcfc4f 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -195,397 +195,474 @@ function formatDateKey(timestamp: number) { return `${year}-${month}-${day}`; } +export async function slaOverviewHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + let tickets = await fetchScopedTickets(ctx, tenantId, viewer); + if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) + // Optional range filter (createdAt) for reporting purposes, similar ao backlog/csat + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; + const end = new Date(); + end.setUTCHours(0, 0, 0, 0); + const endMs = end.getTime() + ONE_DAY_MS; + const startMs = endMs - days * ONE_DAY_MS; + const inRange = tickets.filter((t) => t.createdAt >= startMs && t.createdAt < endMs); + const queues = await fetchQueues(ctx, tenantId); + + const now = Date.now(); + const openTickets = inRange.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); + const resolvedTickets = inRange.filter((ticket) => { + const status = normalizeStatus(ticket.status); + return status === "RESOLVED"; + }); + const overdueTickets = openTickets.filter((ticket) => ticket.dueAt && ticket.dueAt < now); + + const firstResponseTimes = inRange + .filter((ticket) => ticket.firstResponseAt) + .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); + const resolutionTimes = resolvedTickets + .filter((ticket) => ticket.resolvedAt) + .map((ticket) => (ticket.resolvedAt! - ticket.createdAt) / 60000); + + const queueBreakdown = queues.map((queue) => { + const count = openTickets.filter((ticket) => ticket.queueId === queue._id).length; + return { + id: queue._id, + name: queue.name, + open: count, + }; + }); + + return { + totals: { + total: inRange.length, + open: openTickets.length, + resolved: resolvedTickets.length, + overdue: overdueTickets.length, + }, + response: { + averageFirstResponseMinutes: average(firstResponseTimes), + responsesRegistered: firstResponseTimes.length, + }, + resolution: { + averageResolutionMinutes: average(resolutionTimes), + resolvedCount: resolutionTimes.length, + }, + queueBreakdown, + rangeDays: days, + }; +} + export const slaOverview = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - let tickets = await fetchScopedTickets(ctx, tenantId, viewer); - if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) - // Optional range filter (createdAt) for reporting purposes, similar ao backlog/csat - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; - const end = new Date(); - end.setUTCHours(0, 0, 0, 0); - const endMs = end.getTime() + ONE_DAY_MS; - const startMs = endMs - days * ONE_DAY_MS; - const inRange = tickets.filter((t) => t.createdAt >= startMs && t.createdAt < endMs); - const queues = await fetchQueues(ctx, tenantId); - - const now = Date.now(); - const openTickets = inRange.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); - const resolvedTickets = inRange.filter((ticket) => { - const status = normalizeStatus(ticket.status); - return status === "RESOLVED"; - }); - const overdueTickets = openTickets.filter((ticket) => ticket.dueAt && ticket.dueAt < now); - - const firstResponseTimes = inRange - .filter((ticket) => ticket.firstResponseAt) - .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); - const resolutionTimes = resolvedTickets - .filter((ticket) => ticket.resolvedAt) - .map((ticket) => (ticket.resolvedAt! - ticket.createdAt) / 60000); - - const queueBreakdown = queues.map((queue) => { - const count = openTickets.filter((ticket) => ticket.queueId === queue._id).length; - return { - id: queue._id, - name: queue.name, - open: count, - }; - }); - - return { - totals: { - total: inRange.length, - open: openTickets.length, - resolved: resolvedTickets.length, - overdue: overdueTickets.length, - }, - response: { - averageFirstResponseMinutes: average(firstResponseTimes), - responsesRegistered: firstResponseTimes.length, - }, - resolution: { - averageResolutionMinutes: average(resolutionTimes), - resolvedCount: resolutionTimes.length, - }, - queueBreakdown, - rangeDays: days, - }; - }, + handler: slaOverviewHandler, }); +export async function csatOverviewHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + let tickets = await fetchScopedTickets(ctx, tenantId, viewer); + if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) + const surveysAll = await collectCsatSurveys(ctx, tickets); + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; + const end = new Date(); + end.setUTCHours(0, 0, 0, 0); + const endMs = end.getTime() + ONE_DAY_MS; + const startMs = endMs - days * ONE_DAY_MS; + const surveys = surveysAll.filter((s) => s.receivedAt >= startMs && s.receivedAt < endMs); + + const averageScore = average(surveys.map((item) => item.score)); + const distribution = [1, 2, 3, 4, 5].map((score) => ({ + score, + total: surveys.filter((item) => item.score === score).length, + })); + + return { + totalSurveys: surveys.length, + averageScore, + distribution, + recent: surveys + .slice() + .sort((a, b) => b.receivedAt - a.receivedAt) + .slice(0, 10) + .map((item) => ({ + ticketId: item.ticketId, + reference: item.reference, + score: item.score, + receivedAt: item.receivedAt, + })), + rangeDays: days, + }; +} + export const csatOverview = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - let tickets = await fetchScopedTickets(ctx, tenantId, viewer); - if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) - const surveysAll = await collectCsatSurveys(ctx, tickets); - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; - const end = new Date(); - end.setUTCHours(0, 0, 0, 0); - const endMs = end.getTime() + ONE_DAY_MS; - const startMs = endMs - days * ONE_DAY_MS; - const surveys = surveysAll.filter((s) => s.receivedAt >= startMs && s.receivedAt < endMs); - - const averageScore = average(surveys.map((item) => item.score)); - const distribution = [1, 2, 3, 4, 5].map((score) => ({ - score, - total: surveys.filter((item) => item.score === score).length, - })); - - return { - totalSurveys: surveys.length, - averageScore, - distribution, - recent: surveys - .slice() - .sort((a, b) => b.receivedAt - a.receivedAt) - .slice(0, 10) - .map((item) => ({ - ticketId: item.ticketId, - reference: item.reference, - score: item.score, - receivedAt: item.receivedAt, - })), - rangeDays: days, - }; - }, + handler: csatOverviewHandler, }); +export async function openedResolvedByDayHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + let tickets = await fetchScopedTickets(ctx, tenantId, viewer); + if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) + + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; + const end = new Date(); + end.setUTCHours(0, 0, 0, 0); + const endMs = end.getTime() + ONE_DAY_MS; + const startMs = endMs - days * ONE_DAY_MS; + + const opened: Record = {} + const resolved: Record = {} + + for (let i = days - 1; i >= 0; i--) { + const d = new Date(endMs - (i + 1) * ONE_DAY_MS) + const key = formatDateKey(d.getTime()) + opened[key] = 0 + resolved[key] = 0 + } + + for (const t of tickets) { + if (t.createdAt >= startMs && t.createdAt < endMs) { + const key = formatDateKey(t.createdAt) + opened[key] = (opened[key] ?? 0) + 1 + } + if (t.resolvedAt && t.resolvedAt >= startMs && t.resolvedAt < endMs) { + const key = formatDateKey(t.resolvedAt) + resolved[key] = (resolved[key] ?? 0) + 1 + } + } + + const series: Array<{ date: string; opened: number; resolved: number }> = [] + for (let i = days - 1; i >= 0; i--) { + const d = new Date(endMs - (i + 1) * ONE_DAY_MS) + const key = formatDateKey(d.getTime()) + series.push({ date: key, opened: opened[key] ?? 0, resolved: resolved[key] ?? 0 }) + } + + return { rangeDays: days, series } +} + export const openedResolvedByDay = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - let tickets = await fetchScopedTickets(ctx, tenantId, viewer); - if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) - - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; - const end = new Date(); - end.setUTCHours(0, 0, 0, 0); - const endMs = end.getTime() + ONE_DAY_MS; - const startMs = endMs - days * ONE_DAY_MS; - - const opened: Record = {} - const resolved: Record = {} - - // pre-fill buckets - for (let i = days - 1; i >= 0; i--) { - const d = new Date(endMs - (i + 1) * ONE_DAY_MS) - const key = formatDateKey(d.getTime()) - opened[key] = 0 - resolved[key] = 0 - } - - for (const t of tickets) { - if (t.createdAt >= startMs && t.createdAt < endMs) { - const key = formatDateKey(t.createdAt) - opened[key] = (opened[key] ?? 0) + 1 - } - if (t.resolvedAt && t.resolvedAt >= startMs && t.resolvedAt < endMs) { - const key = formatDateKey(t.resolvedAt) - resolved[key] = (resolved[key] ?? 0) + 1 - } - } - - const series: Array<{ date: string; opened: number; resolved: number }> = [] - for (let i = days - 1; i >= 0; i--) { - const d = new Date(endMs - (i + 1) * ONE_DAY_MS) - const key = formatDateKey(d.getTime()) - series.push({ date: key, opened: opened[key] ?? 0, resolved: resolved[key] ?? 0 }) - } - - return { rangeDays: days, series } - }, + handler: openedResolvedByDayHandler, }) +export async function backlogOverviewHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + // Optional range filter (createdAt) for reporting purposes + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; + const end = new Date(); + end.setUTCHours(0, 0, 0, 0); + const endMs = end.getTime() + ONE_DAY_MS; + const startMs = endMs - days * ONE_DAY_MS; + const inRange = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId); + + const statusCounts = inRange.reduce>((acc, ticket) => { + const status = normalizeStatus(ticket.status); + acc[status] = (acc[status] ?? 0) + 1; + return acc; + }, {} as Record); + + const priorityCounts = inRange.reduce>((acc, ticket) => { + acc[ticket.priority] = (acc[ticket.priority] ?? 0) + 1; + return acc; + }, {}); + + const openTickets = inRange.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); + + const queueMap = new Map(); + for (const ticket of openTickets) { + const queueId = ticket.queueId ? ticket.queueId : "sem-fila"; + const current = queueMap.get(queueId) ?? { name: queueId === "sem-fila" ? "Sem fila" : "", count: 0 }; + current.count += 1; + queueMap.set(queueId, current); + } + + const queues = await fetchQueues(ctx, tenantId); + + for (const queue of queues) { + const entry = queueMap.get(queue._id) ?? { name: queue.name, count: 0 }; + entry.name = queue.name; + queueMap.set(queue._id, entry); + } + + return { + rangeDays: days, + statusCounts, + priorityCounts, + queueCounts: Array.from(queueMap.entries()).map(([id, data]) => ({ + id, + name: data.name, + total: data.count, + })), + totalOpen: openTickets.length, + }; +} + export const backlogOverview = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - // Optional range filter (createdAt) for reporting purposes - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; - const end = new Date(); - end.setUTCHours(0, 0, 0, 0); - const endMs = end.getTime() + ONE_DAY_MS; - const startMs = endMs - days * ONE_DAY_MS; - const inRange = await fetchScopedTicketsByCreatedRange(ctx, tenantId, viewer, startMs, endMs, companyId); - - const statusCounts = inRange.reduce>((acc, ticket) => { - const status = normalizeStatus(ticket.status); - acc[status] = (acc[status] ?? 0) + 1; - return acc; - }, {} as Record); - - const priorityCounts = inRange.reduce>((acc, ticket) => { - acc[ticket.priority] = (acc[ticket.priority] ?? 0) + 1; - return acc; - }, {}); - - const openTickets = inRange.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); - - const queueMap = new Map(); - for (const ticket of openTickets) { - const queueId = ticket.queueId ? ticket.queueId : "sem-fila"; - const current = queueMap.get(queueId) ?? { name: queueId === "sem-fila" ? "Sem fila" : "", count: 0 }; - current.count += 1; - queueMap.set(queueId, current); - } - - const queues = await fetchQueues(ctx, tenantId); - - for (const queue of queues) { - const entry = queueMap.get(queue._id) ?? { name: queue.name, count: 0 }; - entry.name = queue.name; - queueMap.set(queue._id, entry); - } - - return { - rangeDays: days, - statusCounts, - priorityCounts, - queueCounts: Array.from(queueMap.entries()).map(([id, data]) => ({ - id, - name: data.name, - total: data.count, - })), - totalOpen: openTickets.length, - }; - }, + handler: backlogOverviewHandler, }); // Touch to ensure CI convex_deploy runs and that agentProductivity is deployed +export async function agentProductivityHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId) + let tickets = await fetchScopedTickets(ctx, tenantId, viewer) + if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) + + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 + const end = new Date() + end.setUTCHours(0, 0, 0, 0) + const endMs = end.getTime() + ONE_DAY_MS + const startMs = endMs - days * ONE_DAY_MS + + const inRange = tickets.filter((t) => t.createdAt >= startMs && t.createdAt < endMs) + type Acc = { + agentId: Id<"users"> + name: string | null + email: string | null + open: number + resolved: number + avgFirstResponseMinValues: number[] + avgResolutionMinValues: number[] + workedMs: number + } + const map = new Map() + + for (const t of inRange) { + const assigneeId = t.assigneeId ?? null + if (!assigneeId) continue + let acc = map.get(assigneeId) + if (!acc) { + const user = await ctx.db.get(assigneeId) + acc = { + agentId: assigneeId, + name: user?.name ?? null, + email: user?.email ?? null, + open: 0, + resolved: 0, + avgFirstResponseMinValues: [], + avgResolutionMinValues: [], + workedMs: 0, + } + map.set(assigneeId, acc) + } + const status = normalizeStatus(t.status) + if (OPEN_STATUSES.has(status)) acc.open += 1 + if (status === "RESOLVED") acc.resolved += 1 + if (t.firstResponseAt) acc.avgFirstResponseMinValues.push((t.firstResponseAt - t.createdAt) / 60000) + if (t.resolvedAt) acc.avgResolutionMinValues.push((t.resolvedAt - t.createdAt) / 60000) + } + + for (const [agentId, acc] of map) { + const sessions = await ctx.db + .query("ticketWorkSessions") + .withIndex("by_agent", (q) => q.eq("agentId", agentId as Id<"users">)) + .collect() + let total = 0 + for (const s of sessions) { + const started = s.startedAt + const ended = s.stoppedAt ?? s.startedAt + if (ended < startMs || started >= endMs) continue + total += s.durationMs ?? Math.max(0, (s.stoppedAt ?? Date.now()) - s.startedAt) + } + acc.workedMs = total + } + + const items = Array.from(map.values()).map((acc) => ({ + agentId: acc.agentId, + name: acc.name, + email: acc.email, + open: acc.open, + resolved: acc.resolved, + avgFirstResponseMinutes: average(acc.avgFirstResponseMinValues), + avgResolutionMinutes: average(acc.avgResolutionMinValues), + workedHours: Math.round((acc.workedMs / 3600000) * 100) / 100, + })) + items.sort((a, b) => b.resolved - a.resolved) + return { rangeDays: days, items } +} + export const agentProductivity = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()), companyId: v.optional(v.id("companies")) }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId) - let tickets = await fetchScopedTickets(ctx, tenantId, viewer) - if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) - - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 - const end = new Date() - end.setUTCHours(0, 0, 0, 0) - const endMs = end.getTime() + ONE_DAY_MS - const startMs = endMs - days * ONE_DAY_MS - - const inRange = tickets.filter((t) => t.createdAt >= startMs && t.createdAt < endMs) - type Acc = { - agentId: Id<"users"> - name: string | null - email: string | null - open: number - resolved: number - avgFirstResponseMinValues: number[] - avgResolutionMinValues: number[] - workedMs: number - } - const map = new Map() - - for (const t of inRange) { - const assigneeId = t.assigneeId ?? null - if (!assigneeId) continue - let acc = map.get(assigneeId) - if (!acc) { - const user = await ctx.db.get(assigneeId) - acc = { - agentId: assigneeId, - name: user?.name ?? null, - email: user?.email ?? null, - open: 0, - resolved: 0, - avgFirstResponseMinValues: [], - avgResolutionMinValues: [], - workedMs: 0, - } - map.set(assigneeId, acc) - } - const status = normalizeStatus(t.status) - if (OPEN_STATUSES.has(status)) acc.open += 1 - if (status === 'RESOLVED') acc.resolved += 1 - if (t.firstResponseAt) acc.avgFirstResponseMinValues.push((t.firstResponseAt - t.createdAt) / 60000) - if (t.resolvedAt) acc.avgResolutionMinValues.push((t.resolvedAt - t.createdAt) / 60000) - } - - // Sum work sessions by agent - for (const [agentId, acc] of map) { - const sessions = await ctx.db - .query('ticketWorkSessions') - .withIndex('by_agent', (q) => q.eq('agentId', agentId as Id<'users'>)) - .collect() - let total = 0 - for (const s of sessions) { - const started = s.startedAt - const ended = s.stoppedAt ?? s.startedAt - if (ended < startMs || started >= endMs) continue - total += s.durationMs ?? Math.max(0, (s.stoppedAt ?? Date.now()) - s.startedAt) - } - acc.workedMs = total - } - - const items = Array.from(map.values()).map((acc) => ({ - agentId: acc.agentId, - name: acc.name, - email: acc.email, - open: acc.open, - resolved: acc.resolved, - avgFirstResponseMinutes: average(acc.avgFirstResponseMinValues), - avgResolutionMinutes: average(acc.avgResolutionMinValues), - workedHours: Math.round((acc.workedMs / 3600000) * 100) / 100, - })) - // sort by resolved desc - items.sort((a, b) => b.resolved - a.resolved) - return { rangeDays: days, items } - } + handler: agentProductivityHandler, }) +export async function dashboardOverviewHandler( + ctx: QueryCtx, + { tenantId, viewerId }: { tenantId: string; viewerId: Id<"users"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + const tickets = await fetchScopedTickets(ctx, tenantId, viewer); + const now = Date.now(); + + const lastDayStart = now - ONE_DAY_MS; + const previousDayStart = now - 2 * ONE_DAY_MS; + + const newTickets = tickets.filter((ticket) => ticket.createdAt >= lastDayStart); + const previousTickets = tickets.filter( + (ticket) => ticket.createdAt >= previousDayStart && ticket.createdAt < lastDayStart + ); + + const trend = percentageChange(newTickets.length, previousTickets.length); + + const inProgressCurrent = tickets.filter((ticket) => { + if (!ticket.firstResponseAt) return false; + const status = normalizeStatus(ticket.status); + if (status === "RESOLVED") return false; + return !ticket.resolvedAt; + }); + + const inProgressPrevious = tickets.filter((ticket) => { + if (!ticket.firstResponseAt || ticket.firstResponseAt >= lastDayStart) return false; + if (ticket.resolvedAt && ticket.resolvedAt < lastDayStart) return false; + const status = normalizeStatus(ticket.status); + return status !== "RESOLVED" || !ticket.resolvedAt; + }); + + const inProgressTrend = percentageChange(inProgressCurrent.length, inProgressPrevious.length); + + const lastWindowStart = now - 7 * ONE_DAY_MS; + const previousWindowStart = now - 14 * ONE_DAY_MS; + + const firstResponseWindow = tickets + .filter( + (ticket) => + ticket.createdAt >= lastWindowStart && + ticket.createdAt < now && + ticket.firstResponseAt + ) + .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); + const firstResponsePrevious = tickets + .filter( + (ticket) => + ticket.createdAt >= previousWindowStart && + ticket.createdAt < lastWindowStart && + ticket.firstResponseAt + ) + .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); + + const averageWindow = average(firstResponseWindow); + const averagePrevious = average(firstResponsePrevious); + const deltaMinutes = + averageWindow !== null && averagePrevious !== null ? averageWindow - averagePrevious : null; + + const awaitingTickets = tickets.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); + const atRiskTickets = awaitingTickets.filter((ticket) => ticket.dueAt && ticket.dueAt < now); + + const resolvedLastWindow = tickets.filter( + (ticket) => ticket.resolvedAt && ticket.resolvedAt >= lastWindowStart && ticket.resolvedAt < now + ); + const resolvedPreviousWindow = tickets.filter( + (ticket) => + ticket.resolvedAt && + ticket.resolvedAt >= previousWindowStart && + ticket.resolvedAt < lastWindowStart + ); + const resolutionRate = tickets.length > 0 ? (resolvedLastWindow.length / tickets.length) * 100 : null; + const resolutionDelta = + resolvedPreviousWindow.length > 0 + ? ((resolvedLastWindow.length - resolvedPreviousWindow.length) / resolvedPreviousWindow.length) * 100 + : null; + + return { + newTickets: { + last24h: newTickets.length, + previous24h: previousTickets.length, + trendPercentage: trend, + }, + inProgress: { + current: inProgressCurrent.length, + previousSnapshot: inProgressPrevious.length, + trendPercentage: inProgressTrend, + }, + firstResponse: { + averageMinutes: averageWindow, + previousAverageMinutes: averagePrevious, + deltaMinutes, + responsesCount: firstResponseWindow.length, + }, + awaitingAction: { + total: awaitingTickets.length, + atRisk: atRiskTickets.length, + }, + resolution: { + resolvedLast7d: resolvedLastWindow.length, + previousResolved: resolvedPreviousWindow.length, + rate: resolutionRate, + deltaPercentage: resolutionDelta, + }, + }; +} + export const dashboardOverview = query({ args: { tenantId: v.string(), viewerId: v.id("users") }, - handler: async (ctx, { tenantId, viewerId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - const tickets = await fetchScopedTickets(ctx, tenantId, viewer); - const now = Date.now(); - - const lastDayStart = now - ONE_DAY_MS; - const previousDayStart = now - 2 * ONE_DAY_MS; - - const newTickets = tickets.filter((ticket) => ticket.createdAt >= lastDayStart); - const previousTickets = tickets.filter( - (ticket) => ticket.createdAt >= previousDayStart && ticket.createdAt < lastDayStart - ); - - const trend = percentageChange(newTickets.length, previousTickets.length); - - const inProgressCurrent = tickets.filter((ticket) => { - if (!ticket.firstResponseAt) return false; - const status = normalizeStatus(ticket.status); - if (status === "RESOLVED") return false; - return !ticket.resolvedAt; - }); - - const inProgressPrevious = tickets.filter((ticket) => { - if (!ticket.firstResponseAt || ticket.firstResponseAt >= lastDayStart) return false; - if (ticket.resolvedAt && ticket.resolvedAt < lastDayStart) return false; - const status = normalizeStatus(ticket.status); - return status !== "RESOLVED" || !ticket.resolvedAt; - }); - - const inProgressTrend = percentageChange(inProgressCurrent.length, inProgressPrevious.length); - - const lastWindowStart = now - 7 * ONE_DAY_MS; - const previousWindowStart = now - 14 * ONE_DAY_MS; - - const firstResponseWindow = tickets - .filter( - (ticket) => - ticket.createdAt >= lastWindowStart && - ticket.createdAt < now && - ticket.firstResponseAt - ) - .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); - const firstResponsePrevious = tickets - .filter( - (ticket) => - ticket.createdAt >= previousWindowStart && - ticket.createdAt < lastWindowStart && - ticket.firstResponseAt - ) - .map((ticket) => (ticket.firstResponseAt! - ticket.createdAt) / 60000); - - const averageWindow = average(firstResponseWindow); - const averagePrevious = average(firstResponsePrevious); - const deltaMinutes = - averageWindow !== null && averagePrevious !== null ? averageWindow - averagePrevious : null; - - const awaitingTickets = tickets.filter((ticket) => OPEN_STATUSES.has(normalizeStatus(ticket.status))); - const atRiskTickets = awaitingTickets.filter((ticket) => ticket.dueAt && ticket.dueAt < now); - - const resolvedLastWindow = tickets.filter( - (ticket) => ticket.resolvedAt && ticket.resolvedAt >= lastWindowStart && ticket.resolvedAt < now - ); - const resolvedPreviousWindow = tickets.filter( - (ticket) => - ticket.resolvedAt && - ticket.resolvedAt >= previousWindowStart && - ticket.resolvedAt < lastWindowStart - ); - const resolutionRate = tickets.length > 0 ? (resolvedLastWindow.length / tickets.length) * 100 : null; - const resolutionDelta = - resolvedPreviousWindow.length > 0 - ? ((resolvedLastWindow.length - resolvedPreviousWindow.length) / resolvedPreviousWindow.length) * 100 - : null; - - return { - newTickets: { - last24h: newTickets.length, - previous24h: previousTickets.length, - trendPercentage: trend, - }, - inProgress: { - current: inProgressCurrent.length, - previousSnapshot: inProgressPrevious.length, - trendPercentage: inProgressTrend, - }, - firstResponse: { - averageMinutes: averageWindow, - previousAverageMinutes: averagePrevious, - deltaMinutes, - responsesCount: firstResponseWindow.length, - }, - awaitingAction: { - total: awaitingTickets.length, - atRisk: atRiskTickets.length, - }, - resolution: { - resolvedLast7d: resolvedLastWindow.length, - previousResolved: resolvedPreviousWindow.length, - rate: resolutionRate, - deltaPercentage: resolutionDelta, - }, - }; - }, + handler: dashboardOverviewHandler, }); +export async function ticketsByChannelHandler( + ctx: QueryCtx, + { tenantId, viewerId, range, companyId }: { tenantId: string; viewerId: Id<"users">; range?: string; companyId?: Id<"companies"> } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId); + let tickets = await fetchScopedTickets(ctx, tenantId, viewer); + if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; + + const end = new Date(); + end.setUTCHours(0, 0, 0, 0); + const endMs = end.getTime() + ONE_DAY_MS; + const startMs = endMs - days * ONE_DAY_MS; + + const timeline = new Map>(); + for (let ts = startMs; ts < endMs; ts += ONE_DAY_MS) { + timeline.set(formatDateKey(ts), new Map()); + } + + const channels = new Set(); + + for (const ticket of tickets) { + if (ticket.createdAt < startMs || ticket.createdAt >= endMs) continue; + const dateKey = formatDateKey(ticket.createdAt); + const channelKey = ticket.channel ?? "OUTRO"; + channels.add(channelKey); + const dayMap = timeline.get(dateKey) ?? new Map(); + dayMap.set(channelKey, (dayMap.get(channelKey) ?? 0) + 1); + timeline.set(dateKey, dayMap); + } + + const sortedChannels = Array.from(channels).sort(); + + const points = Array.from(timeline.entries()) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([date, map]) => { + const values: Record = {}; + for (const channel of sortedChannels) { + values[channel] = map.get(channel) ?? 0; + } + return { date, values }; + }); + + return { + rangeDays: days, + channels: sortedChannels, + points, + }; +} + export const ticketsByChannel = query({ args: { tenantId: v.string(), @@ -593,184 +670,147 @@ export const ticketsByChannel = query({ range: v.optional(v.string()), companyId: v.optional(v.id("companies")), }, - handler: async (ctx, { tenantId, viewerId, range, companyId }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId); - let tickets = await fetchScopedTickets(ctx, tenantId, viewer); - if (companyId) tickets = tickets.filter((t) => t.companyId === companyId) - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90; - - const end = new Date(); - end.setUTCHours(0, 0, 0, 0); - const endMs = end.getTime() + ONE_DAY_MS; - const startMs = endMs - days * ONE_DAY_MS; - - const timeline = new Map>(); - for (let ts = startMs; ts < endMs; ts += ONE_DAY_MS) { - timeline.set(formatDateKey(ts), new Map()); - } - - const channels = new Set(); - - for (const ticket of tickets) { - if (ticket.createdAt < startMs || ticket.createdAt >= endMs) continue; - const dateKey = formatDateKey(ticket.createdAt); - const channelKey = ticket.channel ?? "OUTRO"; - channels.add(channelKey); - const dayMap = timeline.get(dateKey) ?? new Map(); - dayMap.set(channelKey, (dayMap.get(channelKey) ?? 0) + 1); - timeline.set(dateKey, dayMap); - } - - const sortedChannels = Array.from(channels).sort(); - - const points = Array.from(timeline.entries()) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([date, map]) => { - const values: Record = {}; - for (const channel of sortedChannels) { - values[channel] = map.get(channel) ?? 0; - } - return { date, values }; - }); - - return { - rangeDays: days, - channels: sortedChannels, - points, - }; - }, + handler: ticketsByChannelHandler, }); +export async function hoursByClientHandler( + ctx: QueryCtx, + { tenantId, viewerId, range }: { tenantId: string; viewerId: Id<"users">; range?: string } +) { + const viewer = await requireStaff(ctx, viewerId, tenantId) + const tickets = await fetchScopedTickets(ctx, tenantId, viewer) + + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 + const end = new Date() + end.setUTCHours(0, 0, 0, 0) + const endMs = end.getTime() + ONE_DAY_MS + const startMs = endMs - days * ONE_DAY_MS + + type Acc = { + companyId: Id<"companies"> + name: string + isAvulso: boolean + internalMs: number + externalMs: number + totalMs: number + contractedHoursPerMonth?: number | null + } + const map = new Map() + + for (const t of tickets) { + if (t.updatedAt < startMs || t.updatedAt >= endMs) continue + const companyId = t.companyId ?? null + if (!companyId) continue + + let acc = map.get(companyId) + if (!acc) { + const company = await ctx.db.get(companyId) + acc = { + companyId, + name: company?.name ?? "Sem empresa", + isAvulso: Boolean(company?.isAvulso ?? false), + internalMs: 0, + externalMs: 0, + totalMs: 0, + contractedHoursPerMonth: company?.contractedHoursPerMonth ?? null, + } + map.set(companyId, acc) + } + const internal = t.internalWorkedMs ?? 0 + const external = t.externalWorkedMs ?? 0 + acc.internalMs += internal + acc.externalMs += external + acc.totalMs += internal + external + } + + const items = Array.from(map.values()).sort((a, b) => b.totalMs - a.totalMs) + return { + rangeDays: days, + items: items.map((i) => ({ + companyId: i.companyId, + name: i.name, + isAvulso: i.isAvulso, + internalMs: i.internalMs, + externalMs: i.externalMs, + totalMs: i.totalMs, + contractedHoursPerMonth: i.contractedHoursPerMonth ?? null, + })), + } +} + export const hoursByClient = query({ args: { tenantId: v.string(), viewerId: v.id("users"), range: v.optional(v.string()) }, - handler: async (ctx, { tenantId, viewerId, range }) => { - const viewer = await requireStaff(ctx, viewerId, tenantId) - const tickets = await fetchScopedTickets(ctx, tenantId, viewer) - - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 - const end = new Date() - end.setUTCHours(0, 0, 0, 0) - const endMs = end.getTime() + ONE_DAY_MS - const startMs = endMs - days * ONE_DAY_MS - - // Accumulate by company - type Acc = { - companyId: Id<"companies"> - name: string - isAvulso: boolean - internalMs: number - externalMs: number - totalMs: number - contractedHoursPerMonth?: number | null - } - const map = new Map() - - for (const t of tickets) { - // only consider tickets updated in range as a proxy for recent work - if (t.updatedAt < startMs || t.updatedAt >= endMs) continue - const companyId = t.companyId ?? null - if (!companyId) continue - - let acc = map.get(companyId) - if (!acc) { - const company = await ctx.db.get(companyId) - acc = { - companyId, - name: company?.name ?? "Sem empresa", - isAvulso: Boolean(company?.isAvulso ?? false), - internalMs: 0, - externalMs: 0, - totalMs: 0, - contractedHoursPerMonth: company?.contractedHoursPerMonth ?? null, - } - map.set(companyId, acc) - } - const internal = t.internalWorkedMs ?? 0 - const external = t.externalWorkedMs ?? 0 - acc.internalMs += internal - acc.externalMs += external - acc.totalMs += internal + external - } - - const items = Array.from(map.values()).sort((a, b) => b.totalMs - a.totalMs) - return { - rangeDays: days, - items: items.map((i) => ({ - companyId: i.companyId, - name: i.name, - isAvulso: i.isAvulso, - internalMs: i.internalMs, - externalMs: i.externalMs, - totalMs: i.totalMs, - contractedHoursPerMonth: i.contractedHoursPerMonth ?? null, - })), - } - }, + handler: hoursByClientHandler, }) // Internal variant used by scheduled jobs: skips viewer scoping and aggregates for the whole tenant +export async function hoursByClientInternalHandler( + ctx: QueryCtx, + { tenantId, range }: { tenantId: string; range?: string } +) { + const tickets = await fetchTickets(ctx, tenantId) + + const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 + const end = new Date() + end.setUTCHours(0, 0, 0, 0) + const endMs = end.getTime() + ONE_DAY_MS + const startMs = endMs - days * ONE_DAY_MS + + type Acc = { + companyId: Id<"companies"> + name: string + isAvulso: boolean + internalMs: number + externalMs: number + totalMs: number + contractedHoursPerMonth?: number | null + } + const map = new Map() + + for (const t of tickets) { + if (t.updatedAt < startMs || t.updatedAt >= endMs) continue + const companyId = t.companyId ?? null + if (!companyId) continue + + let acc = map.get(companyId) + if (!acc) { + const company = await ctx.db.get(companyId) + acc = { + companyId, + name: company?.name ?? "Sem empresa", + isAvulso: Boolean(company?.isAvulso ?? false), + internalMs: 0, + externalMs: 0, + totalMs: 0, + contractedHoursPerMonth: company?.contractedHoursPerMonth ?? null, + } + map.set(companyId, acc) + } + const internal = t.internalWorkedMs ?? 0 + const external = t.externalWorkedMs ?? 0 + acc.internalMs += internal + acc.externalMs += external + acc.totalMs += internal + external + } + + const items = Array.from(map.values()).sort((a, b) => b.totalMs - a.totalMs) + return { + rangeDays: days, + items: items.map((i) => ({ + companyId: i.companyId, + name: i.name, + isAvulso: i.isAvulso, + internalMs: i.internalMs, + externalMs: i.externalMs, + totalMs: i.totalMs, + contractedHoursPerMonth: i.contractedHoursPerMonth ?? null, + })), + } +} + export const hoursByClientInternal = query({ args: { tenantId: v.string(), range: v.optional(v.string()) }, - handler: async (ctx, { tenantId, range }) => { - const tickets = await fetchTickets(ctx, tenantId) - - const days = range === "7d" ? 7 : range === "30d" ? 30 : 90 - const end = new Date() - end.setUTCHours(0, 0, 0, 0) - const endMs = end.getTime() + ONE_DAY_MS - const startMs = endMs - days * ONE_DAY_MS - - type Acc = { - companyId: Id<"companies"> - name: string - isAvulso: boolean - internalMs: number - externalMs: number - totalMs: number - contractedHoursPerMonth?: number | null - } - const map = new Map() - - for (const t of tickets) { - if (t.updatedAt < startMs || t.updatedAt >= endMs) continue - const companyId = t.companyId ?? null - if (!companyId) continue - - let acc = map.get(companyId) - if (!acc) { - const company = await ctx.db.get(companyId) - acc = { - companyId, - name: company?.name ?? "Sem empresa", - isAvulso: Boolean(company?.isAvulso ?? false), - internalMs: 0, - externalMs: 0, - totalMs: 0, - contractedHoursPerMonth: company?.contractedHoursPerMonth ?? null, - } - map.set(companyId, acc) - } - const internal = t.internalWorkedMs ?? 0 - const external = t.externalWorkedMs ?? 0 - acc.internalMs += internal - acc.externalMs += external - acc.totalMs += internal + external - } - - const items = Array.from(map.values()).sort((a, b) => b.totalMs - a.totalMs) - return { - rangeDays: days, - items: items.map((i) => ({ - companyId: i.companyId, - name: i.name, - isAvulso: i.isAvulso, - internalMs: i.internalMs, - externalMs: i.externalMs, - totalMs: i.totalMs, - contractedHoursPerMonth: i.contractedHoursPerMonth ?? null, - })), - } - }, + handler: hoursByClientInternalHandler, }) diff --git a/docs/admin/admin-inventory-ui.md b/docs/admin/admin-inventory-ui.md index 7cebada..5f6757e 100644 --- a/docs/admin/admin-inventory-ui.md +++ b/docs/admin/admin-inventory-ui.md @@ -24,6 +24,20 @@ A página Admin > Máquinas agora exibe um inventário detalhado e pesquisável ## Exportação - Exportar CSV de softwares ou serviços diretamente da seção detalhada (quando disponíveis). +- Exportar planilha XLSX completa (`/admin/machines/:id/inventory.xlsx`). A partir de 31/10/2025 a planilha contém: + - **Resumo**: data de geração, filtros aplicados, contagem por status e total de acessos remotos/alertas. + - **Inventário**: colunas principais exibidas na UI (status, persona, hardware, token, build/licença do SO, domínio, colaborador, Fleet, etc.). + - **Vínculos**: usuários associados à máquina. + - **Softwares**: lista deduplicada (nome + versão + origem/publisher). A coluna “Softwares instalados” no inventário bate com o total desta aba. + - **Partições**: nome/mount/FS/capacidade/livre, convertendo unidades (ex.: 447 GB → bytes). + - **Discos físicos**: modelo, tamanho, interface, tipo e serial de cada drive. + - **Rede**: interfaces com MAC/IP de todas as fontes (agente, Fleet). + - **Acessos remotos**: TeamViewer/AnyDesk/etc. com notas, URL, última verificação e metadados brutos. + - **Serviços**: serviços coletados (Windows/Linux) com nome, display name e status. + - **Alertas**: postura recente (tipo, mensagem, severidade, criado em). + - **Métricas**: CPU/Memória/Disco/GPU com timestamp coletado. + - **Labels**: tags aplicadas à máquina. + - **Sistema**: visão categorizada (Sistema, Dispositivo, Hardware, Acesso, Token, Fleet) contendo build, licença, domínio, fabricante, serial, colaborador, contagem de acessos, etc. ## Notas - Os dados vêm de duas fontes: diff --git a/machine-inventory-plc-est-025 (2).xlsx b/machine-inventory-plc-est-025 (2).xlsx new file mode 100644 index 0000000..6b2b933 Binary files /dev/null and b/machine-inventory-plc-est-025 (2).xlsx differ diff --git a/package.json b/package.json index b466cd1..ee4c948 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "date-fns": "^4.1.0", "dotenv": "^16.4.5", "lucide-react": "^0.544.0", - "next": "^16.0.0", + "next": "^16.0.1", "next-themes": "^0.4.6", "pdfkit": "^0.17.2", "postcss": "^8.5.6", @@ -88,7 +88,7 @@ "better-sqlite3": "^12.4.1", "cross-env": "^10.1.0", "eslint": "^9", - "eslint-config-next": "^16.0.0", + "eslint-config-next": "^16.0.1", "eslint-plugin-react-hooks": "^5.0.0", "jsdom": "^27.0.1", "playwright": "^1.56.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7fb930..9df4324 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ importers: version: 3.6.5 better-auth: specifier: ^1.3.26 - version: 1.3.26(next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.3.26(next@16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -123,8 +123,8 @@ importers: specifier: ^0.544.0 version: 0.544.0(react@19.2.0) next: - specifier: ^16.0.0 - version: 16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^16.0.1 + version: 16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -163,7 +163,7 @@ importers: version: 6.3.7 unicornstudio-react: specifier: ^1.4.31 - version: 1.4.31(next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 1.4.31(next@16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -214,8 +214,8 @@ importers: specifier: ^9 version: 9.37.0(jiti@2.6.1) eslint-config-next: - specifier: ^16.0.0 - version: 16.0.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + specifier: ^16.0.1 + version: 16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-hooks: specifier: ^5.0.0 version: 5.2.0(eslint@9.37.0(jiti@2.6.1)) @@ -843,21 +843,6 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@formatjs/ecma402-abstract@2.3.6': - resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} - - '@formatjs/fast-memoize@2.2.7': - resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - - '@formatjs/icu-messageformat-parser@2.11.4': - resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - - '@formatjs/icu-skeleton-parser@1.8.16': - resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} - - '@formatjs/intl-localematcher@0.6.2': - resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@hexagon/base64@1.1.28': resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} @@ -1008,18 +993,6 @@ packages: cpu: [x64] os: [win32] - '@internationalized/date@3.10.0': - resolution: {integrity: sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==} - - '@internationalized/message@3.1.8': - resolution: {integrity: sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==} - - '@internationalized/number@3.6.5': - resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} - - '@internationalized/string@3.2.7': - resolution: {integrity: sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1046,56 +1019,56 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.0.0': - resolution: {integrity: sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA==} + '@next/env@16.0.1': + resolution: {integrity: sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw==} - '@next/eslint-plugin-next@16.0.0': - resolution: {integrity: sha512-IB7RzmmtrPOrpAgEBR1PIQPD0yea5lggh5cq54m51jHjjljU80Ia+czfxJYMlSDl1DPvpzb8S9TalCc0VMo9Hw==} + '@next/eslint-plugin-next@16.0.1': + resolution: {integrity: sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==} - '@next/swc-darwin-arm64@16.0.0': - resolution: {integrity: sha512-/CntqDCnk5w2qIwMiF0a9r6+9qunZzFmU0cBX4T82LOflE72zzH6gnOjCwUXYKOBlQi8OpP/rMj8cBIr18x4TA==} + '@next/swc-darwin-arm64@16.0.1': + resolution: {integrity: sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.0': - resolution: {integrity: sha512-hB4GZnJGKa8m4efvTGNyii6qs76vTNl+3dKHTCAUaksN6KjYy4iEO3Q5ira405NW2PKb3EcqWiRaL9DrYJfMHg==} + '@next/swc-darwin-x64@16.0.1': + resolution: {integrity: sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.0': - resolution: {integrity: sha512-E2IHMdE+C1k+nUgndM13/BY/iJY9KGCphCftMh7SXWcaQqExq/pJU/1Hgn8n/tFwSoLoYC/yUghOv97tAsIxqg==} + '@next/swc-linux-arm64-gnu@16.0.1': + resolution: {integrity: sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.0': - resolution: {integrity: sha512-xzgl7c7BVk4+7PDWldU+On2nlwnGgFqJ1siWp3/8S0KBBLCjonB6zwJYPtl4MUY7YZJrzzumdUpUoquu5zk8vg==} + '@next/swc-linux-arm64-musl@16.0.1': + resolution: {integrity: sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.0': - resolution: {integrity: sha512-sdyOg4cbiCw7YUr0F/7ya42oiVBXLD21EYkSwN+PhE4csJH4MSXUsYyslliiiBwkM+KsuQH/y9wuxVz6s7Nstg==} + '@next/swc-linux-x64-gnu@16.0.1': + resolution: {integrity: sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.0': - resolution: {integrity: sha512-IAXv3OBYqVaNOgyd3kxR4L3msuhmSy1bcchPHxDOjypG33i2yDWvGBwFD94OuuTjjTt/7cuIKtAmoOOml6kfbg==} + '@next/swc-linux-x64-musl@16.0.1': + resolution: {integrity: sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.0': - resolution: {integrity: sha512-bmo3ncIJKUS9PWK1JD9pEVv0yuvp1KPuOsyJTHXTv8KDrEmgV/K+U0C75rl9rhIaODcS7JEb6/7eJhdwXI0XmA==} + '@next/swc-win32-arm64-msvc@16.0.1': + resolution: {integrity: sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.0': - resolution: {integrity: sha512-O1cJbT+lZp+cTjYyZGiDwsOjO3UHHzSqobkPNipdlnnuPb1swfcuY6r3p8dsKU4hAIEO4cO67ZCfVVH/M1ETXA==} + '@next/swc-win32-x64-msvc@16.0.1': + resolution: {integrity: sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1681,297 +1654,6 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-aria/autocomplete@3.0.0-rc.3': - resolution: {integrity: sha512-vemf7h3hvIDk3MxiiPryysfYgJDg8R72X46dRIeg0+cXKYxjPYou64/DTucSV2z5J6RC5JalINu0jIDaLhEILw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/breadcrumbs@3.5.29': - resolution: {integrity: sha512-rKS0dryllaZJqrr3f/EAf2liz8CBEfmL5XACj+Z1TAig6GIYe1QuA3BtkX0cV9OkMugXdX8e3cbA7nD10ORRqg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/button@3.14.2': - resolution: {integrity: sha512-VbLIA+Kd6f/MDjd+TJBUg2+vNDw66pnvsj2E4RLomjI9dfBuN7d+Yo2UnsqKVyhePjCUZ6xxa2yDuD63IOSIYA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/calendar@3.9.2': - resolution: {integrity: sha512-uSLxLgOPRnEU4Jg59lAhUVA+uDx/55NBg4lpfsP2ynazyiJ5LCXmYceJi+VuOqMml7d9W0dB87OldOeLdIxYVA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/checkbox@3.16.2': - resolution: {integrity: sha512-29Mj9ZqXioJ0bcMnNGooHztnTau5pikZqX3qCRj5bYR3by/ZFFavYoMroh9F7s/MbFm/tsKX+Sf02lYFEdXRjA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/collections@3.0.0': - resolution: {integrity: sha512-vCFztpsl1AYjQn3lH7CwzYiiRAGfnm7+EXaXIt7yS4O6YC8C3FfOBf3jdxcFjE5u8CEfiL4X+4ABkfio10nneg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/color@3.1.2': - resolution: {integrity: sha512-jCC+Q7rAQGLQBkHjkPAeDuGYuMbc4neifjlNRiyZ9as1z4gg63H8MteoWYYk6K4vCKKxSixgt8MfI29XWMOWPQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/combobox@3.14.0': - resolution: {integrity: sha512-z4ro0Hma//p4nL2IJx5iUa7NwxeXbzSoZ0se5uTYjG1rUUMszg+wqQh/AQoL+eiULn7rs18JY9wwNbVIkRNKWA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/datepicker@3.15.2': - resolution: {integrity: sha512-th078hyNqPf4P2K10su/y32zPDjs3lOYVdHvsL9/+5K1dnTvLHCK5vgUyLuyn8FchhF7cmHV49D+LZVv65PEpQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/dialog@3.5.31': - resolution: {integrity: sha512-inxQMyrzX0UBW9Mhraq0nZ4HjHdygQvllzloT1E/RlDd61lr3RbmJR6pLsrbKOTtSvDIBJpCso1xEdHCFNmA0Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/disclosure@3.1.0': - resolution: {integrity: sha512-5996BeBpnj+yKXYysz+UuhFQxGFPvaZZ3zNBd052wz/i+TVFVGSqqYJ6cwZyO1AfBR8zOT0ZIiK4EC3ETwSvtQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/dnd@3.11.3': - resolution: {integrity: sha512-MyTziciik1Owz3rqDghu0K3ZtTFvmj/R2ZsLDwbU9N4hKqGX/BKnrI8SytTn8RDqVv5LmA/GhApLngiupTAsXw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/focus@3.21.2': - resolution: {integrity: sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/form@3.1.2': - resolution: {integrity: sha512-R3i7L7Ci61PqZQvOrnL9xJeWEbh28UkTVgkj72EvBBn39y4h7ReH++0stv7rRs8p5ozETSKezBbGfu4UsBewWw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/grid@3.14.5': - resolution: {integrity: sha512-XHw6rgjlTqc85e3zjsWo3U0EVwjN5MOYtrolCKc/lc2ItNdcY3OlMhpsU9+6jHwg/U3VCSWkGvwAz9hg7krd8Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/gridlist@3.14.1': - resolution: {integrity: sha512-keS03Am07aOn7RuNaRsMOyh0jscyhDn95asCVy4lxhl9A9TFk1Jw0o2L6q6cWRj1gFiKeacj/otG5H8ZKQQ2Wg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/i18n@3.12.13': - resolution: {integrity: sha512-YTM2BPg0v1RvmP8keHenJBmlx8FXUKsdYIEX7x6QWRd1hKlcDwphfjzvt0InX9wiLiPHsT5EoBTpuUk8SXc0Mg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/interactions@3.25.6': - resolution: {integrity: sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/label@3.7.22': - resolution: {integrity: sha512-jLquJeA5ZNqDT64UpTc9XJ7kQYltUlNcgxZ37/v4mHe0UZ7QohCKdKQhXHONb0h2jjNUpp2HOZI8J9++jOpzxA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/landmark@3.0.7': - resolution: {integrity: sha512-t8c610b8hPLS6Vwv+rbuSyljZosI1s5+Tosfa0Fk4q7d+Ex6Yj7hLfUFy59GxZAufhUYfGX396fT0gPqAbU1tg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/link@3.8.6': - resolution: {integrity: sha512-7F7UDJnwbU9IjfoAdl6f3Hho5/WB7rwcydUOjUux0p7YVWh/fTjIFjfAGyIir7MJhPapun1D0t97QQ3+8jXVcg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/listbox@3.15.0': - resolution: {integrity: sha512-Ub1Wu79R9sgxM7h4HeEdjOgOKDHwduvYcnDqsSddGXgpkL8ADjsy2YUQ0hHY5VnzA4BxK36bLp4mzSna8Qvj1w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/live-announcer@3.4.4': - resolution: {integrity: sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==} - - '@react-aria/menu@3.19.3': - resolution: {integrity: sha512-52fh8y8b2776R2VrfZPpUBJYC9oTP7XDy+zZuZTxPEd7Ywk0JNUl5F92y6ru22yPkS13sdhrNM/Op+V/KulmAg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/meter@3.4.27': - resolution: {integrity: sha512-andOOdJkgRJF9vBi5VWRmFodK+GT+5X1lLeNUmb4qOX8/MVfX/RbK72LDeIhd7xC7rSCFHj3WvZ198rK4q0k3w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/numberfield@3.12.2': - resolution: {integrity: sha512-M2b+z0HIXiXpGAWOQkO2kpIjaLNUXJ5Q3/GMa3Fkr+B1piFX0VuOynYrtddKVrmXCe+r5t+XcGb0KS29uqv7nQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/overlays@3.30.0': - resolution: {integrity: sha512-UpjqSjYZx5FAhceWCRVsW6fX1sEwya1fQ/TKkL53FAlLFR8QKuoKqFlmiL43YUFTcGK3UdEOy3cWTleLQwdSmQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/progress@3.4.27': - resolution: {integrity: sha512-0OA1shs1575g1zmO8+rWozdbTnxThFFhOfuoL1m7UV5Dley6FHpueoKB1ECv7B+Qm4dQt6DoEqLg7wsbbQDhmg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/radio@3.12.2': - resolution: {integrity: sha512-I11f6I90neCh56rT/6ieAs3XyDKvEfbj/QmbU5cX3p+SJpRRPN0vxQi5D1hkh0uxDpeClxygSr31NmZsd4sqfg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/searchfield@3.8.9': - resolution: {integrity: sha512-Yt2pj8Wb5/XsUr2T0DQqFv+DlFpzzWIWnNr9cJATUcWV/xw6ok7YFEg9+7EHtBmsCQxFFJtock1QfZzBw6qLtQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/select@3.17.0': - resolution: {integrity: sha512-q5ZuyAn5jSOeI0Ys99951TaGcF4O7u1SSBVxPMwVVXOU8ZhToCNx+WG3n/JDYHEjqdo7sbsVRaPA7LkBzBGf5w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/selection@3.26.0': - resolution: {integrity: sha512-ZBH3EfWZ+RfhTj01dH8L17uT7iNbXWS8u77/fUpHgtrm0pwNVhx0TYVnLU1YpazQ/3WVpvWhmBB8sWwD1FlD/g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/separator@3.4.13': - resolution: {integrity: sha512-0NlcrdBfQbcjWEXdHl3+uSY1272n2ljT1gWL2RIf6aQsQWTZ0gz0rTgRHy0MTXN+y+tICItUERJT4vmTLtIzVg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/slider@3.8.2': - resolution: {integrity: sha512-6KyUGaVzRE4xAz1LKHbNh1q5wzxe58pdTHFSnxNe6nk1SCoHw7NfI4h2s2m6LgJ0megFxsT0Ir8aHaFyyxmbgg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/spinbutton@3.6.19': - resolution: {integrity: sha512-xOIXegDpts9t3RSHdIN0iYQpdts0FZ3LbpYJIYVvdEHo9OpDS+ElnDzCGtwZLguvZlwc5s1LAKuKopDUsAEMkw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/ssr@3.9.10': - resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} - engines: {node: '>= 12'} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/switch@3.7.8': - resolution: {integrity: sha512-AfsUq1/YiuoprhcBUD9vDPyWaigAwctQNW1fMb8dROL+i/12B+Zekj8Ml+jbU69/kIVtfL0Jl7/0Bo9KK3X0xQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/table@3.17.8': - resolution: {integrity: sha512-bXiZoxTMbsqUJsYDhHPzKc3jw0HFJ/xMsJ49a0f7mp5r9zACxNLeIU0wJ4Uvx37dnYOHKzGliG+rj5l4sph7MA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tabs@3.10.8': - resolution: {integrity: sha512-sPPJyTyoAqsBh76JinBAxStOcbjZvyWFYKpJ9Uqw+XT0ObshAPPFSGeh8DiQemPs02RwJdrfARPMhyqiX8t59A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tag@3.7.2': - resolution: {integrity: sha512-JV679P5r4DftbqyNBRt7Nw9mP7dxaKPfikjyQuvUoEOa06wBLbM/hU9RJUPRvqK+Un6lgBDAmXD9NNf4N2xpdw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/textfield@3.18.2': - resolution: {integrity: sha512-G+lM8VYSor6g9Yptc6hLZ6BF+0cq0pYol1z6wdQUQgJN8tg4HPtzq75lsZtlCSIznL3amgRAxJtd0dUrsAnvaQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toast@3.0.8': - resolution: {integrity: sha512-rfJIms6AkMyQ7ZgKrMZgGfPwGcB/t1JoEwbc1PAmXcAvFI/hzF6YF7ZFDXiq38ucFsP9PnHmbXIzM9w4ccl18A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toggle@3.12.2': - resolution: {integrity: sha512-g25XLYqJuJpt0/YoYz2Rab8ax+hBfbssllcEFh0v0jiwfk2gwTWfRU9KAZUvxIqbV8Nm8EBmrYychDpDcvW1kw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/toolbar@3.0.0-beta.21': - resolution: {integrity: sha512-yRCk/GD8g+BhdDgxd3I0a0c8Ni4Wyo6ERzfSoBkPkwQ4X2E2nkopmraM9D0fXw4UcIr4bnmvADzkHXtBN0XrBg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tooltip@3.8.8': - resolution: {integrity: sha512-CmHUqtXtFWmG4AHMEr9hIVex+oscK6xcM2V47gq9ijNInxe3M6UBu/dBdkgGP/jYv9N7tzCAjTR8nNIHQXwvWw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/tree@3.1.4': - resolution: {integrity: sha512-6pbFeN0dAsCOrFGUKU39CNjft20zCAjLfMqfkRWisL+JkUHI2nq6odUJF5jJTsU1C+1951+3oFOmVxPX+K+akQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/utils@3.31.0': - resolution: {integrity: sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/virtualizer@4.1.10': - resolution: {integrity: sha512-s0xOFh602ybTWuDrV/i6fV7Pz7vYghsY7F/RpYL/5IX9qCZ5C1FWFePpVktQAZghnd3ljH8hS8DULPeDfVLCrg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/visually-hidden@3.8.28': - resolution: {integrity: sha512-KRRjbVVob2CeBidF24dzufMxBveEUtUu7IM+hpdZKB+gxVROoh4XRLPv9SFmaH89Z7D9To3QoykVZoWD0lan6Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-pdf/fns@3.1.2': resolution: {integrity: sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==} @@ -2015,161 +1697,6 @@ packages: '@react-pdf/types@2.9.1': resolution: {integrity: sha512-5GoCgG0G5NMgpPuHbKG2xcVRQt7+E5pg3IyzVIIozKG3nLcnsXW4zy25vG1ZBQA0jmo39q34au/sOnL/0d1A4w==} - '@react-stately/autocomplete@3.0.0-beta.3': - resolution: {integrity: sha512-YfP/TrvkOCp6j7oqpZxJSvmSeXn+XtbKSOiBOuo+m2zCIhW2ncThmDB9uAUOkpmikDv/LkGKni40RQE8USdGdA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/calendar@3.9.0': - resolution: {integrity: sha512-U5Nf2kx9gDhJRxdDUm5gjfyUlt/uUfOvM1vDW2UA62cA6+2k2cavMLc2wNlXOb/twFtl6p0joYKHG7T4xnEFkg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/checkbox@3.7.2': - resolution: {integrity: sha512-j1ycUVz5JmqhaL6mDZgDNZqBilOB8PBW096sDPFaTtuYreDx2HOd1igxiIvwlvPESZwsJP7FVM3mYnaoXtpKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/collections@3.12.8': - resolution: {integrity: sha512-AceJYLLXt1Y2XIcOPi6LEJSs4G/ubeYW3LqOCQbhfIgMaNqKfQMIfagDnPeJX9FVmPFSlgoCBxb1pTJW2vjCAQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/color@3.9.2': - resolution: {integrity: sha512-F+6Do8W3yu/4n7MpzZtbXwVukcLTFYYDIUtpoR+Jl52UmAr9Hf1CQgkyTI2azv1ZMzj1mVrTBhpBL0q27kFZig==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/combobox@3.12.0': - resolution: {integrity: sha512-A6q9R/7cEa/qoQsBkdslXWvD7ztNLLQ9AhBhVN9QvzrmrH5B4ymUwcTU8lWl22ykH7RRwfonLeLXJL4C+/L2oQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/data@3.14.1': - resolution: {integrity: sha512-lDNc4gZ6kVZcrABeeQZPTTnP+1ykNylSvFzAC/Hq1fs8+s54xLRvoENWIyG+yK19N9TIGEoA0AOFG8PoAun43g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/datepicker@3.15.2': - resolution: {integrity: sha512-S5GL+W37chvV8knv9v0JRv0L6hKo732qqabCCHXzOpYxkLIkV4f/y3cHdEzFWzpZ0O0Gkg7WgeYo160xOdBKYg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/disclosure@3.0.8': - resolution: {integrity: sha512-/Ce/Z76y85eSBZiemfU/uEyXkBBa1RdfLRaKD13rnfUV7/nS3ae1VtNlsXgmwQjWv2pmAiSuEKYMbZfVL7q/lQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/dnd@3.7.1': - resolution: {integrity: sha512-O1JBJ4HI1rVNKuoa5NXiC5FCrCEkr9KVBoKNlTZU8/cnQselhbEsUfMglAakO2EuwIaM1tIXoNF5J/N5P+6lTA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/flags@3.1.2': - resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} - - '@react-stately/form@3.2.2': - resolution: {integrity: sha512-soAheOd7oaTO6eNs6LXnfn0tTqvOoe3zN9FvtIhhrErKz9XPc5sUmh3QWwR45+zKbitOi1HOjfA/gifKhZcfWw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/grid@3.11.6': - resolution: {integrity: sha512-vWPAkzpeTIsrurHfMubzMuqEw7vKzFhIJeEK5sEcLunyr1rlADwTzeWrHNbPMl66NAIAi70Dr1yNq+kahQyvMA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/layout@4.5.1': - resolution: {integrity: sha512-Zk92HM6a8KFdyPzslhLCOmrrsvJ28+vFBisgiKMwVhe96cWlax1m9i4ktmO43xaUpSZkn06DRD/2k0d1x+Uwjw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/list@3.13.1': - resolution: {integrity: sha512-eHaoauh21twbcl0kkwULhVJ+CzYcy1jUjMikNVMHOQdhr4WIBdExf7PmSgKHKqsSPhpGg6IpTCY2dUX3RycjDg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/menu@3.9.8': - resolution: {integrity: sha512-bo0NOhofnTHLESiYfsSSw6gyXiPVJJ0UlN2igUXtJk5PmyhWjFzUzTzcnd7B028OB0si9w3LIWM3stqz5271Eg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/numberfield@3.10.2': - resolution: {integrity: sha512-jlKVFYaH3RX5KvQ7a+SAMQuPccZCzxLkeYkBE64u1Zvi7YhJ8hkTMHG/fmZMbk1rHlseE2wfBdk0Rlya3MvoNQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/overlays@3.6.20': - resolution: {integrity: sha512-YAIe+uI8GUXX8F/0Pzr53YeC5c/bjqbzDFlV8NKfdlCPa6+Jp4B/IlYVjIooBj9+94QvbQdjylegvYWK/iPwlg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/radio@3.11.2': - resolution: {integrity: sha512-UM7L6AW+k8edhSBUEPZAqiWNRNadfOKK7BrCXyBiG79zTz0zPcXRR+N+gzkDn7EMSawDeyK1SHYUuoSltTactg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/searchfield@3.5.16': - resolution: {integrity: sha512-MRfqT1lZ24r94GuFNcGJXsfijZoWjSMySCT60T6NXtbOzVPuAF3K+pL70Rayq/EWLJjS2NPHND11VTs0VdcE0Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/select@3.8.0': - resolution: {integrity: sha512-A721nlt0DSCDit0wKvhcrXFTG5Vv1qkEVkeKvobmETZy6piKvwh0aaN8iQno5AFuZaj1iOZeNjZ/20TsDJR/4A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/selection@3.20.6': - resolution: {integrity: sha512-a0bjuP2pJYPKEiedz2Us1W1aSz0iHRuyeQEdBOyL6Z6VUa6hIMq9H60kvseir2T85cOa4QggizuRV7mcO6bU5w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/slider@3.7.2': - resolution: {integrity: sha512-EVBHUdUYwj++XqAEiQg2fGi8Reccznba0uyQ3gPejF0pAc390Q/J5aqiTEDfiCM7uJ6WHxTM6lcCqHQBISk2dQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/table@3.15.1': - resolution: {integrity: sha512-MhMAgE/LgAzHcAn1P3p/nQErzJ6DiixSJ1AOt2JlnAKEb5YJg4ATKWCb2IjBLwywt9ZCzfm3KMUzkctZqAoxwA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tabs@3.8.6': - resolution: {integrity: sha512-9RYxmgjVIxUpIsGKPIF7uRoHWOEz8muwaYiStCVeyiYBPmarvZoIYtTXcwSMN/vEs7heVN5uGCL6/bfdY4+WiA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/toast@3.1.2': - resolution: {integrity: sha512-HiInm7bck32khFBHZThTQaAF6e6/qm57F4mYRWdTq8IVeGDzpkbUYibnLxRhk0UZ5ybc6me+nqqPkG/lVmM42Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/toggle@3.9.2': - resolution: {integrity: sha512-dOxs9wrVXHUmA7lc8l+N9NbTJMAaXcYsnNGsMwfXIXQ3rdq+IjWGNYJ52UmNQyRYFcg0jrzRrU16TyGbNjOdNQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tooltip@3.5.8': - resolution: {integrity: sha512-gkcUx2ROhCiGNAYd2BaTejakXUUNLPnnoJ5+V/mN480pN+OrO8/2V9pqb/IQmpqxLsso93zkM3A4wFHHLBBmPQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/tree@3.9.3': - resolution: {integrity: sha512-ZngG79nLFxE/GYmpwX6E/Rma2MMkzdoJPRI3iWk3dgqnGMMzpPnUp/cvjDsU3UHF7xDVusC5BT6pjWN0uxCIFQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/utils@3.10.8': - resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/virtualizer@4.4.4': - resolution: {integrity: sha512-ri8giqXSZOrznZDCCOE4U36wSkOhy+hrFK7yo/YVcpxTqqp3d3eisfKMqbDsgqBW+XTHycTU/xeAf0u9NqrfpQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@react-three/fiber@9.3.0': resolution: {integrity: sha512-myPe3YL/C8+Eq939/4qIVEPBW/uxV0iiUbmjfwrs9sGKYDG8ib8Dz3Okq7BQt8P+0k4igedONbjXMQy84aDFmQ==} peerDependencies: @@ -2195,146 +1722,6 @@ packages: react-native: optional: true - '@react-types/autocomplete@3.0.0-alpha.35': - resolution: {integrity: sha512-Wv5eU4WixfJ4M+fqvJUQqliWPbw7/VldRlgoJhqAlPwlNyLlHYwv5tlA64AySDXHGcSMIbzcS38LaHm44wt0AQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/breadcrumbs@3.7.17': - resolution: {integrity: sha512-IhvVTcfli5o/UDlGACXxjlor2afGlMQA8pNR3faH0bBUay1Fmm3IWktVw9Xwmk+KraV2RTAg9e+E6p8DOQZfiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/button@3.14.1': - resolution: {integrity: sha512-D8C4IEwKB7zEtiWYVJ3WE/5HDcWlze9mLWQ5hfsBfpePyWCgO3bT/+wjb/7pJvcAocrkXo90QrMm85LcpBtrpg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/calendar@3.8.0': - resolution: {integrity: sha512-ZDZgfZgbz1ydWOFs1mH7QFfX3ioJrmb3Y/lkoubQE0HWXLZzyYNvhhKyFJRS1QJ40IofLSBHriwbQb/tsUnGlw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/checkbox@3.10.2': - resolution: {integrity: sha512-ktPkl6ZfIdGS1tIaGSU/2S5Agf2NvXI9qAgtdMDNva0oLyAZ4RLQb6WecPvofw1J7YKXu0VA5Mu7nlX+FM2weQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/color@3.1.2': - resolution: {integrity: sha512-NP0TAY3j4tlMztOp/bBfMlPwC9AQKTjSiTFmc2oQNkx5M4sl3QpPqFPosdt7jZ8M4nItvfCWZrlZGjST4SB83A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/combobox@3.13.9': - resolution: {integrity: sha512-G6GmLbzVkLW6VScxPAr/RtliEyPhBClfYaIllK1IZv+Z42SVnOpKzhnoe79BpmiFqy1AaC3+LjZX783mrsHCwA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/datepicker@3.13.2': - resolution: {integrity: sha512-+M6UZxJnejYY8kz0spbY/hP08QJ5rsZ3aNarRQQHc48xV2oelFLX5MhAqizfLEsvyfb0JYrhWoh4z1xZtAmYCg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/dialog@3.5.22': - resolution: {integrity: sha512-smSvzOcqKE196rWk0oqJDnz+ox5JM5+OT0PmmJXiUD4q7P5g32O6W5Bg7hMIFUI9clBtngo8kLaX2iMg+GqAzg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/form@3.7.16': - resolution: {integrity: sha512-Sb7KJoWEaQ/e4XIY+xRbjKvbP1luome98ZXevpD+zVSyGjEcfIroebizP6K1yMHCWP/043xH6GUkgEqWPoVGjg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/grid@3.3.6': - resolution: {integrity: sha512-vIZJlYTii2n1We9nAugXwM2wpcpsC6JigJFBd6vGhStRdRWRoU4yv1Gc98Usbx0FQ/J7GLVIgeG8+1VMTKBdxw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/link@3.6.5': - resolution: {integrity: sha512-+I2s3XWBEvLrzts0GnNeA84mUkwo+a7kLUWoaJkW0TOBDG7my95HFYxF9WnqKye7NgpOkCqz4s3oW96xPdIniQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/listbox@3.7.4': - resolution: {integrity: sha512-p4YEpTl/VQGrqVE8GIfqTS5LkT5jtjDTbVeZgrkPnX/fiPhsfbTPiZ6g0FNap4+aOGJFGEEZUv2q4vx+rCORww==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/menu@3.10.5': - resolution: {integrity: sha512-HBTrKll2hm0VKJNM4ubIv1L9MNo8JuOnm2G3M+wXvb6EYIyDNxxJkhjsqsGpUXJdAOSkacHBDcNh2HsZABNX4A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/meter@3.4.13': - resolution: {integrity: sha512-EiarfbpHcvmeyXvXcr6XLaHkNHuGc4g7fBVEiDPwssFJKKfbUzqnnknDxPjyspqUVRcXC08CokS98J1jYobqDg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/numberfield@3.8.15': - resolution: {integrity: sha512-97r92D23GKCOjGIGMeW9nt+/KlfM3GeWH39Czcmd2/D5y3k6z4j0avbsfx2OttCtJszrnENjw3GraYGYI2KosQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/overlays@3.9.2': - resolution: {integrity: sha512-Q0cRPcBGzNGmC8dBuHyoPR7N3057KTS5g+vZfQ53k8WwmilXBtemFJPLsogJbspuewQ/QJ3o2HYsp2pne7/iNw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/progress@3.5.16': - resolution: {integrity: sha512-I9tSdCFfvQ7gHJtm90VAKgwdTWXQgVNvLRStEc0z9h+bXBxdvZb+QuiRPERChwFQ9VkK4p4rDqaFo69nDqWkpw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/radio@3.9.2': - resolution: {integrity: sha512-3UcJXu37JrTkRyP4GJPDBU7NmDTInrEdOe+bVzA1j4EegzdkJmLBkLg5cLDAbpiEHB+xIsvbJdx6dxeMuc+H3g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/searchfield@3.6.6': - resolution: {integrity: sha512-cl3itr/fk7wbIQc2Gz5Ie8aVeUmPjVX/mRGS5/EXlmzycAKNYTvqf2mlxwObLndtLISmt7IgNjRRhbUUDI8Ang==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/select@3.11.0': - resolution: {integrity: sha512-SzIsMFVPCbXE1Z1TLfpdfiwJ1xnIkcL1/CjGilmUKkNk5uT7rYX1xCJqWCjXI0vAU1xM4Qn+T3n8de4fw6HRBg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/shared@3.32.1': - resolution: {integrity: sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/slider@3.8.2': - resolution: {integrity: sha512-MQYZP76OEOYe7/yA2To+Dl0LNb0cKKnvh5JtvNvDnAvEprn1RuLiay8Oi/rTtXmc2KmBa4VdTcsXsmkbbkeN2Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/switch@3.5.15': - resolution: {integrity: sha512-r/ouGWQmIeHyYSP1e5luET+oiR7N7cLrAlWsrAfYRWHxqXOSNQloQnZJ3PLHrKFT02fsrQhx2rHaK2LfKeyN3A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/table@3.13.4': - resolution: {integrity: sha512-I/DYiZQl6aNbMmjk90J9SOhkzVDZvyA3Vn3wMWCiajkMNjvubFhTfda5DDf2SgFP5l0Yh6TGGH5XumRv9LqL5Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/tabs@3.3.19': - resolution: {integrity: sha512-fE+qI43yR5pAMpeqPxGqQq9jDHXEPqXskuxNHERMW0PYMdPyem2Cw6goc5F4qeZO3Hf6uPZgHkvJz2OAq7TbBw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/textfield@3.12.6': - resolution: {integrity: sha512-hpEVKE+M3uUkTjw2WrX1NrH/B3rqDJFUa+ViNK2eVranLY4ZwFqbqaYXSzHupOF3ecSjJJv2C103JrwFvx6TPQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/tooltip@3.4.21': - resolution: {integrity: sha512-ugGHOZU6WbOdeTdbjnaEc+Ms7/WhsUCg+T3PCOIeOT9FG02Ce189yJ/+hd7oqL/tVwIhEMYJIqSCgSELFox+QA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} @@ -3814,8 +3201,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.0.0: - resolution: {integrity: sha512-DWKT1YAO9ex2rK0/EeiPpKU++ghTiG59z6m08/ReLRECOYIaEv17maSCYT8zmFQLwIrY5lhJ+iaJPQdT4sJd4g==} + eslint-config-next@16.0.1: + resolution: {integrity: sha512-wNuHw5gNOxwLUvpg0cu6IL0crrVC9hAwdS/7UwleNkwyaMiWIOAwf8yzXVqBBzL3c9A7jVRngJxjoSpPP1aEhg==} peerDependencies: eslint: '>=9.0.0' typescript: '>=3.3.1' @@ -4210,9 +3597,6 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - intl-messageformat@10.7.18: - resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -4606,8 +3990,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.0.0: - resolution: {integrity: sha512-nYohiNdxGu4OmBzggxy9rczmjIGI+TpR5vbKTsE1HqYwNm1B+YSiugSrFguX6omMOKnDHAmBPY4+8TNJk0Idyg==} + next@16.0.1: + resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -4915,10 +4299,6 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-aria-components@1.13.0: {} - - react-aria@3.44.0: {} - react-dom@19.2.0: resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: @@ -4972,11 +4352,6 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-stately@3.42.0: - resolution: {integrity: sha512-lYt2o1dd6dK8Bb4GRh08RG/2u64bSA1cqtRqtw4jEMgxC7Q17RFcIumBbChErndSdLzafEG/UBwV6shOfig6yw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -6186,32 +5561,6 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@formatjs/ecma402-abstract@2.3.6': - dependencies: - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.2 - decimal.js: 10.6.0 - tslib: 2.8.1 - - '@formatjs/fast-memoize@2.2.7': - dependencies: - tslib: 2.8.1 - - '@formatjs/icu-messageformat-parser@2.11.4': - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/icu-skeleton-parser': 1.8.16 - tslib: 2.8.1 - - '@formatjs/icu-skeleton-parser@1.8.16': - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - tslib: 2.8.1 - - '@formatjs/intl-localematcher@0.6.2': - dependencies: - tslib: 2.8.1 - '@hexagon/base64@1.1.28': {} '@hookform/resolvers@3.10.0(react-hook-form@7.64.0(react@19.2.0))': @@ -6318,23 +5667,6 @@ snapshots: '@img/sharp-win32-x64@0.34.4': optional: true - '@internationalized/date@3.10.0': - dependencies: - '@swc/helpers': 0.5.15 - - '@internationalized/message@3.1.8': - dependencies: - '@swc/helpers': 0.5.15 - intl-messageformat: 10.7.18 - - '@internationalized/number@3.6.5': - dependencies: - '@swc/helpers': 0.5.15 - - '@internationalized/string@3.2.7': - dependencies: - '@swc/helpers': 0.5.15 - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -6367,34 +5699,34 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.0.0': {} + '@next/env@16.0.1': {} - '@next/eslint-plugin-next@16.0.0': + '@next/eslint-plugin-next@16.0.1': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.0.0': + '@next/swc-darwin-arm64@16.0.1': optional: true - '@next/swc-darwin-x64@16.0.0': + '@next/swc-darwin-x64@16.0.1': optional: true - '@next/swc-linux-arm64-gnu@16.0.0': + '@next/swc-linux-arm64-gnu@16.0.1': optional: true - '@next/swc-linux-arm64-musl@16.0.0': + '@next/swc-linux-arm64-musl@16.0.1': optional: true - '@next/swc-linux-x64-gnu@16.0.0': + '@next/swc-linux-x64-gnu@16.0.1': optional: true - '@next/swc-linux-x64-musl@16.0.0': + '@next/swc-linux-x64-musl@16.0.1': optional: true - '@next/swc-win32-arm64-msvc@16.0.0': + '@next/swc-win32-arm64-msvc@16.0.1': optional: true - '@next/swc-win32-x64-msvc@16.0.0': + '@next/swc-win32-x64-msvc@16.0.1': optional: true '@noble/ciphers@2.0.1': {} @@ -7164,638 +6496,6 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-aria/autocomplete@3.0.0-rc.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/combobox': 3.14.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/listbox': 3.15.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/searchfield': 3.8.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.0) - '@react-stately/combobox': 3.12.0(react@19.2.0) - '@react-types/autocomplete': 3.0.0-alpha.35(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/breadcrumbs@3.5.29(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/link': 3.8.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/breadcrumbs': 3.7.17(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/button@3.14.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/toggle': 3.9.2(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/calendar@3.9.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/calendar': 3.9.0(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/calendar': 3.8.0(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/checkbox@3.16.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/toggle': 3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/checkbox': 3.7.2(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/toggle': 3.9.2(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/collections@3.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) - - '@react-aria/color@3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/numberfield': 3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/slider': 3.8.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/color': 3.9.2(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-types/color': 3.1.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/combobox@3.14.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/listbox': 3.15.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/menu': 3.19.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/combobox': 3.12.0(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/combobox': 3.13.9(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/datepicker@3.15.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/form': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/datepicker': 3.15.2(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/calendar': 3.8.0(react@19.2.0) - '@react-types/datepicker': 3.13.2(react@19.2.0) - '@react-types/dialog': 3.5.22(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/dialog@3.5.31(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/dialog': 3.5.22(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/disclosure@3.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/disclosure': 3.0.8(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/dnd@3.11.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@internationalized/string': 3.2.7 - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/dnd': 3.7.1(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/focus@3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/form@3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/grid@3.14.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/grid': 3.11.6(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/gridlist@3.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/grid': 3.14.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-stately/tree': 3.9.3(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/i18n@3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@internationalized/message': 3.1.8 - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/interactions@3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/flags': 3.1.2 - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/label@3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/landmark@3.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) - - '@react-aria/link@3.8.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/link': 3.6.5(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/listbox@3.15.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-types/listbox': 3.7.4(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/live-announcer@3.4.4': - dependencies: - '@swc/helpers': 0.5.15 - - '@react-aria/menu@3.19.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/menu': 3.9.8(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/tree': 3.9.3(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/menu': 3.10.5(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/meter@3.4.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/progress': 3.4.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/meter': 3.4.13(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/numberfield@3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/spinbutton': 3.6.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/numberfield': 3.10.2(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/numberfield': 3.8.15(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/overlays@3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/overlays': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/progress@3.4.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/progress': 3.5.16(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/radio@3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/form': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/radio': 3.11.2(react@19.2.0) - '@react-types/radio': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/searchfield@3.8.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/searchfield': 3.5.16(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/searchfield': 3.6.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/select@3.17.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/listbox': 3.15.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/menu': 3.19.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/select': 3.8.0(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/select': 3.11.0(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/selection@3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/separator@3.4.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/slider@3.8.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/slider': 3.7.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/slider': 3.8.2(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/spinbutton@3.6.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/ssr@3.9.10(react@19.2.0)': - dependencies: - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-aria/switch@3.7.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/toggle': 3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/toggle': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/switch': 3.5.15(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/table@3.17.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/grid': 3.14.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/flags': 3.1.2 - '@react-stately/table': 3.15.1(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/table': 3.13.4(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/tabs@3.10.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/tabs': 3.8.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/tabs': 3.3.19(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/tag@3.7.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/gridlist': 3.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/textfield@3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/form': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/textfield': 3.12.6(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/toast@3.0.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/landmark': 3.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/toast': 3.1.2(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/toggle@3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/toggle': 3.9.2(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/toolbar@3.0.0-beta.21(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/tooltip@3.8.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/tooltip': 3.5.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/tooltip': 3.4.21(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/tree@3.1.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/gridlist': 3.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/tree': 3.9.3(react@19.2.0) - '@react-types/button': 3.14.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/utils@3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-stately/flags': 3.1.2 - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - clsx: 2.1.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/virtualizer@4.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-aria/visually-hidden@3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - '@react-pdf/fns@3.1.2': {} '@react-pdf/font@4.0.3': @@ -7897,263 +6597,6 @@ snapshots: '@react-pdf/primitives': 4.1.1 '@react-pdf/stylesheet': 6.1.1 - '@react-stately/autocomplete@3.0.0-beta.3(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/calendar@3.9.0(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/calendar': 3.8.0(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/checkbox@3.7.2(react@19.2.0)': - dependencies: - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/collections@3.12.8(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/color@3.9.2(react@19.2.0)': - dependencies: - '@internationalized/number': 3.6.5 - '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/numberfield': 3.10.2(react@19.2.0) - '@react-stately/slider': 3.7.2(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/color': 3.1.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/combobox@3.12.0(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/combobox': 3.13.9(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/data@3.14.1(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/datepicker@3.15.2(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@internationalized/string': 3.2.7 - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/datepicker': 3.13.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/disclosure@3.0.8(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/dnd@3.7.1(react@19.2.0)': - dependencies: - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/flags@3.1.2': - dependencies: - '@swc/helpers': 0.5.15 - - '@react-stately/form@3.2.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/grid@3.11.6(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/layout@4.5.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/table': 3.15.1(react@19.2.0) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/table': 3.13.4(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - '@react-stately/list@3.13.1(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/menu@3.9.8(react@19.2.0)': - dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-types/menu': 3.10.5(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/numberfield@3.10.2(react@19.2.0)': - dependencies: - '@internationalized/number': 3.6.5 - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/numberfield': 3.8.15(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/overlays@3.6.20(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/overlays': 3.9.2(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/radio@3.11.2(react@19.2.0)': - dependencies: - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/radio': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/searchfield@3.5.16(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/searchfield': 3.6.6(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/select@3.8.0(react@19.2.0)': - dependencies: - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/select': 3.11.0(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/selection@3.20.6(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/slider@3.7.2(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/slider': 3.8.2(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/table@3.15.1(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/flags': 3.1.2 - '@react-stately/grid': 3.11.6(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/table': 3.13.4(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/tabs@3.8.6(react@19.2.0)': - dependencies: - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/tabs': 3.3.19(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/toast@3.1.2(react@19.2.0)': - dependencies: - '@swc/helpers': 0.5.15 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) - - '@react-stately/toggle@3.9.2(react@19.2.0)': - dependencies: - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/checkbox': 3.10.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/tooltip@3.5.8(react@19.2.0)': - dependencies: - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-types/tooltip': 3.4.21(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/tree@3.9.3(react@19.2.0)': - dependencies: - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/utils@3.10.8(react@19.2.0)': - dependencies: - '@swc/helpers': 0.5.15 - react: 19.2.0 - - '@react-stately/virtualizer@4.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@swc/helpers': 0.5.15 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - '@react-three/fiber@9.3.0(@types/react@18.3.26)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.180.0)': dependencies: '@babel/runtime': 7.28.4 @@ -8176,158 +6619,6 @@ snapshots: - '@types/react' - immer - '@react-types/autocomplete@3.0.0-alpha.35(react@19.2.0)': - dependencies: - '@react-types/combobox': 3.13.9(react@19.2.0) - '@react-types/searchfield': 3.6.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/breadcrumbs@3.7.17(react@19.2.0)': - dependencies: - '@react-types/link': 3.6.5(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/button@3.14.1(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/calendar@3.8.0(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/checkbox@3.10.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/color@3.1.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/slider': 3.8.2(react@19.2.0) - react: 19.2.0 - - '@react-types/combobox@3.13.9(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/datepicker@3.13.2(react@19.2.0)': - dependencies: - '@internationalized/date': 3.10.0 - '@react-types/calendar': 3.8.0(react@19.2.0) - '@react-types/overlays': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/dialog@3.5.22(react@19.2.0)': - dependencies: - '@react-types/overlays': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/form@3.7.16(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/grid@3.3.6(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/link@3.6.5(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/listbox@3.7.4(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/menu@3.10.5(react@19.2.0)': - dependencies: - '@react-types/overlays': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/meter@3.4.13(react@19.2.0)': - dependencies: - '@react-types/progress': 3.5.16(react@19.2.0) - react: 19.2.0 - - '@react-types/numberfield@3.8.15(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/overlays@3.9.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/progress@3.5.16(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/radio@3.9.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/searchfield@3.6.6(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/textfield': 3.12.6(react@19.2.0) - react: 19.2.0 - - '@react-types/select@3.11.0(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/shared@3.32.1(react@19.2.0)': - dependencies: - react: 19.2.0 - - '@react-types/slider@3.8.2(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/switch@3.5.15(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/table@3.13.4(react@19.2.0)': - dependencies: - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/tabs@3.3.19(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/textfield@3.12.6(react@19.2.0)': - dependencies: - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - - '@react-types/tooltip@3.4.21(react@19.2.0)': - dependencies: - '@react-types/overlays': 3.9.2(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - '@remirror/core-constants@3.0.0': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -9323,7 +7614,7 @@ snapshots: baseline-browser-mapping@2.8.16: {} - better-auth@1.3.26(next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + better-auth@1.3.26(next@16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@better-auth/core': 1.3.26 '@better-auth/utils': 0.3.0 @@ -9339,7 +7630,7 @@ snapshots: nanostores: 1.0.1 zod: 4.1.11 optionalDependencies: - next: 16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next: 16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) @@ -9866,9 +8157,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.0.0(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.0.1(@typescript-eslint/parser@8.46.2(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 16.0.0 + '@next/eslint-plugin-next': 16.0.1 eslint: 9.37.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.37.0(jiti@2.6.1)) @@ -10345,13 +8636,6 @@ snapshots: internmap@2.0.3: {} - intl-messageformat@10.7.18: - dependencies: - '@formatjs/ecma402-abstract': 2.3.6 - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/icu-messageformat-parser': 2.11.4 - tslib: 2.8.1 - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -10721,9 +9005,9 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@next/env': 16.0.0 + '@next/env': 16.0.1 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001747 postcss: 8.4.31 @@ -10731,14 +9015,14 @@ snapshots: react-dom: 19.2.0(react@19.2.0) styled-jsx: 5.1.6(react@19.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.0 - '@next/swc-darwin-x64': 16.0.0 - '@next/swc-linux-arm64-gnu': 16.0.0 - '@next/swc-linux-arm64-musl': 16.0.0 - '@next/swc-linux-x64-gnu': 16.0.0 - '@next/swc-linux-x64-musl': 16.0.0 - '@next/swc-win32-arm64-msvc': 16.0.0 - '@next/swc-win32-x64-msvc': 16.0.0 + '@next/swc-darwin-arm64': 16.0.1 + '@next/swc-darwin-x64': 16.0.1 + '@next/swc-linux-arm64-gnu': 16.0.1 + '@next/swc-linux-arm64-musl': 16.0.1 + '@next/swc-linux-x64-gnu': 16.0.1 + '@next/swc-linux-x64-musl': 16.0.1 + '@next/swc-win32-arm64-msvc': 16.0.1 + '@next/swc-win32-x64-msvc': 16.0.1 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' @@ -11093,87 +9377,6 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-aria-components@1.13.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): - dependencies: - '@internationalized/date': 3.10.0 - '@internationalized/string': 3.2.7 - '@react-aria/autocomplete': 3.0.0-rc.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/collections': 3.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/dnd': 3.11.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/live-announcer': 3.4.4 - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/toolbar': 3.0.0-beta.21(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/virtualizer': 4.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/autocomplete': 3.0.0-beta.3(react@19.2.0) - '@react-stately/layout': 4.5.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/table': 3.15.1(react@19.2.0) - '@react-stately/utils': 3.10.8(react@19.2.0) - '@react-stately/virtualizer': 4.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/form': 3.7.16(react@19.2.0) - '@react-types/grid': 3.3.6(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - '@react-types/table': 3.13.4(react@19.2.0) - '@swc/helpers': 0.5.15 - client-only: 0.0.1 - react: 19.2.0 - react-aria: 3.44.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react-dom: 19.2.0(react@19.2.0) - react-stately: 3.42.0(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) - - react-aria@3.44.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): - dependencies: - '@internationalized/string': 3.2.7 - '@react-aria/breadcrumbs': 3.5.29(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/button': 3.14.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/calendar': 3.9.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/checkbox': 3.16.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/color': 3.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/combobox': 3.14.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/datepicker': 3.15.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/dialog': 3.5.31(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/disclosure': 3.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/dnd': 3.11.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/focus': 3.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/gridlist': 3.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/i18n': 3.12.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/interactions': 3.25.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/label': 3.7.22(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/landmark': 3.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/link': 3.8.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/listbox': 3.15.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/menu': 3.19.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/meter': 3.4.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/numberfield': 3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/overlays': 3.30.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/progress': 3.4.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/radio': 3.12.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/searchfield': 3.8.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/select': 3.17.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/selection': 3.26.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/separator': 3.4.13(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/slider': 3.8.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/ssr': 3.9.10(react@19.2.0) - '@react-aria/switch': 3.7.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/table': 3.17.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/tabs': 3.10.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/tag': 3.7.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/textfield': 3.18.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/toast': 3.0.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/tooltip': 3.8.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/tree': 3.1.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/utils': 3.31.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-aria/visually-hidden': 3.8.28(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-dom@19.2.0(react@19.2.0): dependencies: react: 19.2.0 @@ -11221,36 +9424,6 @@ snapshots: react-dom: 19.2.0(react@19.2.0) react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react-stately@3.42.0(react@19.2.0): - dependencies: - '@react-stately/calendar': 3.9.0(react@19.2.0) - '@react-stately/checkbox': 3.7.2(react@19.2.0) - '@react-stately/collections': 3.12.8(react@19.2.0) - '@react-stately/color': 3.9.2(react@19.2.0) - '@react-stately/combobox': 3.12.0(react@19.2.0) - '@react-stately/data': 3.14.1(react@19.2.0) - '@react-stately/datepicker': 3.15.2(react@19.2.0) - '@react-stately/disclosure': 3.0.8(react@19.2.0) - '@react-stately/dnd': 3.7.1(react@19.2.0) - '@react-stately/form': 3.2.2(react@19.2.0) - '@react-stately/list': 3.13.1(react@19.2.0) - '@react-stately/menu': 3.9.8(react@19.2.0) - '@react-stately/numberfield': 3.10.2(react@19.2.0) - '@react-stately/overlays': 3.6.20(react@19.2.0) - '@react-stately/radio': 3.11.2(react@19.2.0) - '@react-stately/searchfield': 3.5.16(react@19.2.0) - '@react-stately/select': 3.8.0(react@19.2.0) - '@react-stately/selection': 3.20.6(react@19.2.0) - '@react-stately/slider': 3.7.2(react@19.2.0) - '@react-stately/table': 3.15.1(react@19.2.0) - '@react-stately/tabs': 3.8.6(react@19.2.0) - '@react-stately/toast': 3.1.2(react@19.2.0) - '@react-stately/toggle': 3.9.2(react@19.2.0) - '@react-stately/tooltip': 3.5.8(react@19.2.0) - '@react-stately/tree': 3.9.3(react@19.2.0) - '@react-types/shared': 3.32.1(react@19.2.0) - react: 19.2.0 - react-style-singleton@2.2.3(@types/react@18.3.26)(react@19.2.0): dependencies: get-nonce: 1.0.1 @@ -11844,12 +10017,12 @@ snapshots: pako: 0.2.9 tiny-inflate: 1.0.3 - unicornstudio-react@1.4.31(next@16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + unicornstudio-react@1.4.31(next@16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) optionalDependencies: - next: 16.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next: 16.0.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) unrs-resolver@1.11.1: dependencies: diff --git a/src/app/admin/machines/[id]/tickets/page.tsx b/src/app/admin/machines/[id]/tickets/page.tsx new file mode 100644 index 0000000..3f2b49f --- /dev/null +++ b/src/app/admin/machines/[id]/tickets/page.tsx @@ -0,0 +1,26 @@ +import { AppShell } from "@/components/app-shell" +import { SiteHeader } from "@/components/site-header" +import { DEFAULT_TENANT_ID } from "@/lib/constants" +import { MachineBreadcrumbs } from "@/components/admin/machines/machine-breadcrumbs.client" +import { MachineTicketsHistoryClient } from "@/components/admin/machines/machine-tickets-history.client" + +export const runtime = "nodejs" +export const dynamic = "force-dynamic" + +export default async function AdminMachineTicketsPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = await params + + return ( + }> +
+ + +
+
+ ) +} diff --git a/src/components/admin/machines/admin-machines-overview.tsx b/src/components/admin/machines/admin-machines-overview.tsx index 79b1b66..1db5d02 100644 --- a/src/components/admin/machines/admin-machines-overview.tsx +++ b/src/components/admin/machines/admin-machines-overview.tsx @@ -102,6 +102,12 @@ type MachineTicketSummary = { assignee: { name: string | null; email: string | null } | null } +type MachineOpenTicketsSummary = { + totalOpen: number + hasMore: boolean + tickets: MachineTicketSummary[] +} + type DetailLineProps = { label: string @@ -1454,9 +1460,14 @@ export function MachineDetails({ machine }: MachineDetailsProps) { const machineAlertsHistory = alertsHistory ?? [] const openTickets = useQuery( machine ? api.machines.listOpenTickets : "skip", - machine ? { machineId: machine.id as Id<"machines">, limit: 8 } : ("skip" as const) - ) as MachineTicketSummary[] | undefined - const machineTickets = openTickets ?? [] + machine ? { machineId: machine.id as Id<"machines">, limit: 6 } : ("skip" as const) + ) as MachineOpenTicketsSummary | undefined + const machineTickets = openTickets?.tickets ?? [] + const totalOpenTickets = openTickets?.totalOpen ?? machineTickets.length + const displayLimit = 3 + const displayedMachineTickets = machineTickets.slice(0, displayLimit) + const hasAdditionalOpenTickets = totalOpenTickets > displayedMachineTickets.length + const machineTicketsHref = machine ? `/admin/machines/${machine.id}/tickets` : null const metadata = machine?.inventory ?? null const metrics = machine?.metrics ?? null const metricsCapturedAt = useMemo(() => getMetricsTimestamp(metrics), [metrics]) @@ -2356,45 +2367,65 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
-
-

Tickets abertos por esta máquina

- {machineTickets.length === 0 ? ( -

Nenhum chamado em aberto registrado diretamente por esta máquina.

+
+
+

Tickets abertos por esta máquina

+ {machineTicketsHref ? ( + + Ver todos + + ) : null} +
+ {totalOpenTickets === 0 ? ( +

+ Nenhum chamado em aberto registrado diretamente por esta máquina. +

) : ( -
    - {machineTickets.map((ticket) => { - const priorityMeta = getTicketPriorityMeta(ticket.priority) - return ( -
  • - -
    -

    - #{ticket.reference} · {ticket.subject} -

    -

    - Atualizado {formatRelativeTime(new Date(ticket.updatedAt))} -

    -
    -
    - - {priorityMeta.label} - - -
    - -
  • - ) - })} -
+
+ {hasAdditionalOpenTickets ? ( +

+ Mostrando últimos {Math.min(displayLimit, totalOpenTickets)} de {totalOpenTickets} chamados + em aberto +

+ ) : null} +
    + {displayedMachineTickets.map((ticket) => { + const priorityMeta = getTicketPriorityMeta(ticket.priority) + return ( +
  • + +
    +

    + #{ticket.reference} · {ticket.subject} +

    +

    + Atualizado {formatRelativeTime(new Date(ticket.updatedAt))} +

    +
    +
    + + {priorityMeta.label} + + +
    + +
  • + ) + })} +
+
)}
- {machineTickets.length} + {totalOpenTickets}
diff --git a/src/components/admin/machines/machine-breadcrumbs.client.tsx b/src/components/admin/machines/machine-breadcrumbs.client.tsx index 58cc13e..6666681 100644 --- a/src/components/admin/machines/machine-breadcrumbs.client.tsx +++ b/src/components/admin/machines/machine-breadcrumbs.client.tsx @@ -7,7 +7,19 @@ import type { Id } from "@/convex/_generated/dataModel" import { api } from "@/convex/_generated/api" import { useAuth } from "@/lib/auth-client" -export function MachineBreadcrumbs({ tenantId: _tenantId, machineId }: { tenantId: string; machineId: string }) { +type BreadcrumbSegment = { + label: string + href?: string | null +} + +type MachineBreadcrumbsProps = { + tenantId: string + machineId: string + machineHref?: string | null + extra?: BreadcrumbSegment[] +} + +export function MachineBreadcrumbs({ tenantId: _tenantId, machineId, machineHref, extra }: MachineBreadcrumbsProps) { const { convexUserId } = useAuth() const queryArgs = machineId && convexUserId ? ({ id: machineId as Id<"machines">, includeMetadata: false } as const) @@ -15,15 +27,36 @@ export function MachineBreadcrumbs({ tenantId: _tenantId, machineId }: { tenantI const item = useQuery(api.machines.getById, queryArgs) const hostname = useMemo(() => item?.hostname ?? "Detalhe", [item]) + const segments = useMemo(() => { + const trail: BreadcrumbSegment[] = [ + { label: "Máquinas", href: "/admin/machines" }, + { label: hostname, href: machineHref ?? undefined }, + ] + if (Array.isArray(extra) && extra.length > 0) { + trail.push(...extra.filter((segment): segment is BreadcrumbSegment => Boolean(segment?.label))) + } + return trail + }, [hostname, machineHref, extra]) return ( ) diff --git a/src/components/admin/machines/machine-tickets-history.client.tsx b/src/components/admin/machines/machine-tickets-history.client.tsx new file mode 100644 index 0000000..6af2e9f --- /dev/null +++ b/src/components/admin/machines/machine-tickets-history.client.tsx @@ -0,0 +1,439 @@ +"use client" + +import { useEffect, useMemo, useState } from "react" +import Link from "next/link" +import { usePaginatedQuery, useQuery } from "convex/react" +import { format, formatDistanceToNowStrict } from "date-fns" +import { ptBR } from "date-fns/locale" + +import type { Id } from "@/convex/_generated/dataModel" +import { api } from "@/convex/_generated/api" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { cn } from "@/lib/utils" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Spinner } from "@/components/ui/spinner" +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty" +import { TicketStatusBadge } from "@/components/tickets/status-badge" +import type { TicketPriority, TicketStatus } from "@/lib/schemas/ticket" + +type MachineTicketHistoryItem = { + id: string + reference: number + subject: string + status: TicketStatus + priority: TicketPriority | string + updatedAt: number + createdAt: number + queue: string | null + requester: { name: string | null; email: string | null } | null + assignee: { name: string | null; email: string | null } | null +} + +type MachineTicketsHistoryArgs = { + machineId: Id<"machines"> + status?: "open" | "resolved" + priority?: string + search?: string + from?: number + to?: number +} + +type MachineTicketsHistoryStats = { + total: number + openCount: number + resolvedCount: number +} + +type PeriodPreset = "7d" | "30d" | "90d" | "year" | "all" | "custom" + +function startOfDayMs(date: Date) { + const copy = new Date(date) + copy.setHours(0, 0, 0, 0) + return copy.getTime() +} + +function endOfDayMs(date: Date) { + const copy = new Date(date) + copy.setHours(23, 59, 59, 999) + return copy.getTime() +} + +function parseDateInput(value: string) { + if (!value) return null + const parsed = new Date(`${value}T00:00:00`) + if (Number.isNaN(parsed.getTime())) { + return null + } + return parsed +} + +function computeRange(preset: PeriodPreset, customFrom: string, customTo: string) { + if (preset === "all") { + return { from: null, to: null } + } + if (preset === "custom") { + const fromDate = parseDateInput(customFrom) + const toDate = parseDateInput(customTo) + return { + from: fromDate ? startOfDayMs(fromDate) : null, + to: toDate ? endOfDayMs(toDate) : null, + } + } + + const now = new Date() + const end = endOfDayMs(now) + const start = new Date(now) + + switch (preset) { + case "7d": + start.setDate(start.getDate() - 6) + break + case "30d": + start.setDate(start.getDate() - 29) + break + case "90d": + start.setDate(start.getDate() - 89) + break + case "year": + start.setMonth(0, 1) + start.setHours(0, 0, 0, 0) + break + default: + break + } + + return { from: startOfDayMs(start), to: end } +} + +function formatRelativeTime(timestamp?: number | null) { + if (!timestamp || timestamp <= 0) return "—" + return formatDistanceToNowStrict(timestamp, { addSuffix: true, locale: ptBR }) +} + +function formatAbsoluteTime(timestamp?: number | null) { + if (!timestamp || timestamp <= 0) return "—" + return format(timestamp, "dd/MM/yyyy HH:mm", { locale: ptBR }) +} + +function getPriorityMeta(priority: TicketPriority | string | null | undefined) { + const normalized = (priority ?? "MEDIUM").toString().toUpperCase() + switch (normalized) { + case "LOW": + return { label: "Baixa", badgeClass: "bg-emerald-100 text-emerald-700 border border-emerald-200" } + case "MEDIUM": + return { label: "Média", badgeClass: "bg-sky-100 text-sky-700 border border-sky-200" } + case "HIGH": + return { label: "Alta", badgeClass: "bg-amber-100 text-amber-700 border border-amber-200" } + case "URGENT": + return { label: "Urgente", badgeClass: "bg-rose-100 text-rose-700 border border-rose-200" } + default: + return { label: normalized, badgeClass: "bg-slate-100 text-slate-700 border border-slate-200" } + } +} + +export function MachineTicketsHistoryClient({ tenantId: _tenantId, machineId }: { tenantId: string; machineId: string }) { + const [statusFilter, setStatusFilter] = useState<"all" | "open" | "resolved">("all") + const [priorityFilter, setPriorityFilter] = useState("ALL") + const [periodPreset, setPeriodPreset] = useState("90d") + const [customFrom, setCustomFrom] = useState("") + const [customTo, setCustomTo] = useState("") + const [searchValue, setSearchValue] = useState("") + const [debouncedSearch, setDebouncedSearch] = useState("") + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearch(searchValue.trim()) + }, 300) + return () => clearTimeout(timer) + }, [searchValue]) + + useEffect(() => { + if (periodPreset !== "custom") { + setCustomFrom("") + setCustomTo("") + } + }, [periodPreset]) + + const range = useMemo(() => computeRange(periodPreset, customFrom, customTo), [periodPreset, customFrom, customTo]) + + const queryArgs = useMemo(() => { + const args: MachineTicketsHistoryArgs = { + machineId: machineId as Id<"machines">, + } + if (statusFilter !== "all") { + args.status = statusFilter + } + if (priorityFilter !== "ALL") { + args.priority = priorityFilter + } + if (debouncedSearch) { + args.search = debouncedSearch + } + if (range.from !== null) { + args.from = range.from + } + if (range.to !== null) { + args.to = range.to + } + return args + }, [debouncedSearch, machineId, priorityFilter, range.from, range.to, statusFilter]) + + const { results: tickets, status: paginationStatus, loadMore } = usePaginatedQuery( + api.machines.listTicketsHistory, + queryArgs, + { initialNumItems: 25 } + ) + + const stats = useQuery(api.machines.getTicketsHistoryStats, queryArgs) as MachineTicketsHistoryStats | undefined + const totalTickets = stats?.total ?? 0 + const openTickets = stats?.openCount ?? 0 + const resolvedTickets = stats?.resolvedCount ?? 0 + + const isLoadingFirstPage = paginationStatus === "LoadingFirstPage" + const isLoadingMore = paginationStatus === "LoadingMore" + const canLoadMore = paginationStatus === "CanLoadMore" + + const resetFilters = () => { + setStatusFilter("all") + setPriorityFilter("ALL") + setPeriodPreset("90d") + setCustomFrom("") + setCustomTo("") + setSearchValue("") + setDebouncedSearch("") + } + + return ( +
+
+
+

Chamados no período

+

+ {stats ? ( + totalTickets + ) : ( + + Atualizando... + + )} +

+
+
+

Em aberto

+

{stats ? openTickets : "—"}

+
+
+

Resolvidos

+

{stats ? resolvedTickets : "—"}

+
+
+ +
+
+
+ setSearchValue(event.target.value)} + placeholder="Buscar por assunto, #ID, solicitante ou responsável" + className="sm:max-w-sm" + /> + + +
+
+ + {periodPreset === "custom" ? ( +
+ setCustomFrom(event.target.value)} + className="sm:w-[160px]" + placeholder="Início" + /> + setCustomTo(event.target.value)} + className="sm:w-[160px]" + placeholder="Fim" + /> +
+ ) : null} + +
+
+
+ +
+ {isLoadingFirstPage ? ( +
+ + Carregando chamados... +
+ ) : tickets.length === 0 ? ( + + + Nenhum chamado encontrado + + Ajuste os filtros ou expanda o período para visualizar o histórico de chamados desta máquina. + + + + + + + ) : ( + <> +
+ + + + + Ticket + + + Status + + + Prioridade + + + Última atualização + + + Responsável + + + + + {tickets.map((ticket) => { + const priorityMeta = getPriorityMeta(ticket.priority) + const requesterLabel = ticket.requester?.name ?? ticket.requester?.email ?? "Solicitante não informado" + const assigneeLabel = ticket.assignee?.name ?? ticket.assignee?.email ?? "Sem responsável" + const updatedLabel = formatRelativeTime(ticket.updatedAt) + const updatedAbsolute = formatAbsoluteTime(ticket.updatedAt) + return ( + + +
+ + #{ticket.reference} · {ticket.subject} + +
+ {requesterLabel} + {ticket.queue ? ( + + {ticket.queue} + + ) : null} + Aberto em {formatAbsoluteTime(ticket.createdAt)} +
+
+
+ + + + + + {priorityMeta.label} + + + +
+ {updatedLabel} + {updatedAbsolute} +
+
+ +
+ {assigneeLabel} + {ticket.assignee?.email ? ( + {ticket.assignee.email} + ) : null} +
+
+
+ ) + })} +
+
+
+
+ {canLoadMore || isLoadingMore ? ( + + ) : ( + + Todos os chamados filtrados foram exibidos. + + )} +
+ + )} +
+
+ ) +} diff --git a/src/components/tickets/close-ticket-dialog.tsx b/src/components/tickets/close-ticket-dialog.tsx index db40780..59d2183 100644 --- a/src/components/tickets/close-ticket-dialog.tsx +++ b/src/components/tickets/close-ticket-dialog.tsx @@ -303,7 +303,7 @@ export function CloseTicketDialog({ return ( - + Encerrar ticket diff --git a/src/components/ui/searchable-combobox.tsx b/src/components/ui/searchable-combobox.tsx index 80a8fb8..0c7d417 100644 --- a/src/components/ui/searchable-combobox.tsx +++ b/src/components/ui/searchable-combobox.tsx @@ -93,7 +93,7 @@ export function SearchableCombobox({ aria-expanded={open} disabled={disabled} className={cn( - "flex h-9 w-full items-center justify-between rounded-full border border-input bg-background px-3 text-sm font-medium text-foreground shadow-sm transition focus-visible:ring-2 focus-visible:ring-ring", + "flex min-h-[42px] w-full items-center justify-between rounded-full border border-input bg-background px-3 py-2 text-sm font-medium text-foreground shadow-sm transition focus-visible:ring-2 focus-visible:ring-ring", className, )} > @@ -171,4 +171,3 @@ export function SearchableCombobox({ ) } - diff --git a/tests/machines.getById.test.ts b/tests/machines.getById.test.ts index 08bfeeb..b60d374 100644 --- a/tests/machines.getById.test.ts +++ b/tests/machines.getById.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from "vitest" import type { Doc, Id } from "../convex/_generated/dataModel" -import { getById } from "../convex/machines" +import { getByIdHandler } from "../convex/machines" const FIXED_NOW = 1_706_071_200_000 @@ -65,8 +65,8 @@ describe("convex.machines.getById", () => { })), } - const ctx = { db } as unknown as Parameters[0] - const result = await getById(ctx, { id: machine._id, includeMetadata: true }) + const ctx = { db } as unknown as Parameters[0] + const result = await getByIdHandler(ctx, { id: machine._id, includeMetadata: true }) expect(result).toBeTruthy() expect(result?.metrics).toBeTruthy() @@ -75,4 +75,3 @@ describe("convex.machines.getById", () => { expect(result?.token).toBeTruthy() }) }) - diff --git a/tests/machines.listTicketsHistory.test.ts b/tests/machines.listTicketsHistory.test.ts new file mode 100644 index 0000000..41221e5 --- /dev/null +++ b/tests/machines.listTicketsHistory.test.ts @@ -0,0 +1,255 @@ +import { describe, expect, it, vi } from "vitest" + +import type { Doc, Id } from "../convex/_generated/dataModel" +import { getTicketsHistoryStatsHandler, listTicketsHistoryHandler } from "../convex/machines" + +const MACHINE_ID = "machine_1" as Id<"machines"> +const TENANT_ID = "tenant-1" + +function buildMachine(overrides: Partial> = {}): Doc<"machines"> { + const machine: Record = { + _id: MACHINE_ID, + tenantId: TENANT_ID, + hostname: "desktop-01", + macAddresses: [], + serialNumbers: [], + fingerprint: "fp", + isActive: true, + lastHeartbeatAt: Date.now(), + createdAt: Date.now() - 10_000, + updatedAt: Date.now() - 5_000, + linkedUserIds: [], + remoteAccess: null, + } + return { ...(machine as Doc<"machines">), ...overrides } +} + +function buildTicket(overrides: Partial> = {}): Doc<"tickets"> { + const base: Record = { + _id: "ticket_base" as Id<"tickets">, + tenantId: TENANT_ID, + reference: 42600, + subject: "Generic ticket", + summary: "", + status: "PENDING", + priority: "MEDIUM", + channel: "EMAIL", + queueId: undefined, + requesterId: "user_req" as Id<"users">, + requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] }, + assigneeId: "user_assignee" as Id<"users">, + assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] }, + companyId: undefined, + companySnapshot: undefined, + machineId: MACHINE_ID, + machineSnapshot: undefined, + working: false, + dueAt: undefined, + firstResponseAt: undefined, + resolvedAt: undefined, + closedAt: undefined, + updatedAt: Date.now(), + createdAt: Date.now() - 2000, + tags: [], + customFields: [], + totalWorkedMs: 0, + internalWorkedMs: 0, + externalWorkedMs: 0, + activeSessionId: undefined, + } + return { ...(base as Doc<"tickets">), ...overrides } +} + +type PaginateHandler = (options: { cursor: string | null; numItems: number }) => Promise<{ + page: Doc<"tickets">[] + isDone: boolean + continueCursor: string +}> + +function createCtx({ + machine = buildMachine(), + queues = new Map>(), + paginate, +}: { + machine?: Doc<"machines"> + queues?: Map> + paginate: PaginateHandler +}) { + const createFilterBuilder = () => { + const builder: Record typeof builder> = {} + builder.eq = () => builder + builder.gte = () => builder + builder.lte = () => builder + builder.or = () => builder + return builder + } + + return { + db: { + get: vi.fn(async (id: Id<"machines"> | Id<"queues">) => { + if (machine && id === machine._id) return machine + const queue = queues.get(String(id)) + if (queue) return queue + return null + }), + query: vi.fn(() => { + const chain = { + filter: vi.fn((cb?: (builder: ReturnType) => unknown) => { + cb?.(createFilterBuilder()) + return chain + }), + paginate: vi.fn((options: { cursor: string | null; numItems: number }) => paginate(options)), + } + + return { + withIndex: vi.fn((_indexName: string, cb?: (builder: ReturnType) => unknown) => { + cb?.(createFilterBuilder()) + return { + order: vi.fn(() => chain), + } + }), + } + }), + }, + } as unknown as Parameters[0] +} + +describe("convex.machines.listTicketsHistory", () => { + it("maps tickets metadata and resolves queue names", async () => { + const machine = buildMachine() + const ticket = buildTicket({ + _id: "ticket_1" as Id<"tickets">, + subject: "Printer offline", + priority: "HIGH", + status: "PENDING", + queueId: "queue_1" as Id<"queues">, + updatedAt: 170000, + createdAt: 160000, + }) + + const paginate = vi.fn(async () => ({ + page: [ticket], + isDone: false, + continueCursor: "cursor-next", + })) + + const queues = new Map>([ + ["queue_1", { _id: "queue_1" as Id<"queues">, name: "Atendimento", tenantId: TENANT_ID } as Doc<"queues">], + ]) + + const ctx = createCtx({ machine, queues, paginate }) + + const result = await listTicketsHistoryHandler(ctx, { + machineId: machine._id, + paginationOpts: { numItems: 25, cursor: null }, + }) + + expect(paginate).toHaveBeenCalledWith({ numItems: 25, cursor: null }) + expect(result.page).toHaveLength(1) + expect(result.page[0]).toMatchObject({ + id: "ticket_1", + subject: "Printer offline", + priority: "HIGH", + status: "PENDING", + queue: "Atendimento", + }) + expect(result.continueCursor).toBe("cursor-next") + }) + + it("applies search filtering over paginated results", async () => { + const machine = buildMachine() + const ticketMatches = buildTicket({ + _id: "ticket_match" as Id<"tickets">, + reference: 44321, + subject: "Notebook com tela quebrada", + requesterSnapshot: { name: "Carla", email: "carla@example.com", avatarUrl: undefined, teams: [] }, + }) + const ticketIgnored = buildTicket({ + _id: "ticket_other" as Id<"tickets">, + subject: "Troca de teclado", + requesterSnapshot: { name: "Roberto", email: "roberto@example.com", avatarUrl: undefined, teams: [] }, + }) + + const paginate = vi.fn(async () => ({ + page: [ticketMatches, ticketIgnored], + isDone: true, + continueCursor: "", + })) + + const ctx = createCtx({ machine, paginate }) + + const result = await listTicketsHistoryHandler(ctx, { + machineId: machine._id, + search: "notebook", + paginationOpts: { numItems: 50, cursor: null }, + }) + + expect(result.page).toHaveLength(1) + expect(result.page[0].id).toBe("ticket_match") + expect(result.isDone).toBe(true) + }) +}) + +describe("convex.machines.getTicketsHistoryStats", () => { + it("aggregates totals across multiple pages respecting open status", async () => { + const machine = buildMachine() + const firstPageTicket = buildTicket({ + _id: "ticket_open" as Id<"tickets">, + status: "AWAITING_ATTENDANCE", + }) + const secondPageTicket = buildTicket({ + _id: "ticket_resolved" as Id<"tickets">, + status: "RESOLVED", + }) + + const paginate = vi.fn(async ({ cursor }: { cursor: string | null }) => { + if (!cursor) { + return { page: [firstPageTicket], isDone: false, continueCursor: "cursor-1" } + } + return { page: [secondPageTicket], isDone: true, continueCursor: "" } + }) + + const ctx = createCtx({ machine, paginate }) + + const stats = await getTicketsHistoryStatsHandler( + ctx as unknown as Parameters[0], + { + machineId: machine._id, + } + ) + + expect(stats).toEqual({ total: 2, openCount: 1, resolvedCount: 1 }) + expect(paginate).toHaveBeenCalledTimes(2) + }) + + it("filters results by search term when aggregating", async () => { + const machine = buildMachine() + const matchingTicket = buildTicket({ + _id: "ticket_search" as Id<"tickets">, + subject: "Notebook com lentidão", + }) + const nonMatchingTicket = buildTicket({ + _id: "ticket_ignored" as Id<"tickets">, + subject: "Impressora parada", + }) + + const paginate = vi.fn(async ({ cursor }: { cursor: string | null }) => { + if (!cursor) { + return { page: [matchingTicket, nonMatchingTicket], isDone: true, continueCursor: "" } + } + return { page: [], isDone: true, continueCursor: "" } + }) + + const ctx = createCtx({ machine, paginate }) + + const stats = await getTicketsHistoryStatsHandler( + ctx as unknown as Parameters[0], + { + machineId: machine._id, + search: "notebook", + } + ) + + expect(stats).toEqual({ total: 1, openCount: 1, resolvedCount: 0 }) + }) +}) diff --git a/tests/machines.updatePersona.test.ts b/tests/machines.updatePersona.test.ts index 7aa3dfa..9758677 100644 --- a/tests/machines.updatePersona.test.ts +++ b/tests/machines.updatePersona.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" -import { updatePersona } from "../convex/machines" +import { updatePersonaHandler } from "../convex/machines" import type { Doc, Id } from "../convex/_generated/dataModel" const FIXED_NOW = 1_706_071_200_000 @@ -63,9 +63,9 @@ describe("convex.machines.updatePersona", () => { return null }) - const ctx = { db: { get, patch } } as unknown as Parameters[0] + const ctx = { db: { get, patch } } as unknown as Parameters[0] - const result = await updatePersona(ctx, { machineId: machine._id, persona: "" }) + const result = await updatePersonaHandler(ctx, { machineId: machine._id, persona: "" }) expect(result).toEqual({ ok: true, persona: null }) expect(patch).toHaveBeenCalledTimes(1) @@ -92,10 +92,10 @@ describe("convex.machines.updatePersona", () => { return null }) - const ctx = { db: { get, patch } } as unknown as Parameters[0] + const ctx = { db: { get, patch } } as unknown as Parameters[0] await expect( - updatePersona(ctx, { + updatePersonaHandler(ctx, { machineId: machine._id, persona: "collaborator", assignedUserId: missingUserId, diff --git a/tests/reports.productivity-dashboard.test.ts b/tests/reports.productivity-dashboard.test.ts new file mode 100644 index 0000000..a4f9ef6 --- /dev/null +++ b/tests/reports.productivity-dashboard.test.ts @@ -0,0 +1,267 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" + +vi.mock("../convex/rbac", () => ({ + requireStaff: vi.fn(), +})) + +import type { Doc, Id } from "../convex/_generated/dataModel" +import { + agentProductivityHandler, + dashboardOverviewHandler, + hoursByClientInternalHandler, +} from "../convex/reports" +import { requireStaff } from "../convex/rbac" +import { createReportsCtx } from "./utils/report-test-helpers" + +const TENANT_ID = "tenant-1" +const VIEWER_ID = "user-agent" as Id<"users"> + +function buildTicket(overrides: Partial>): Doc<"tickets"> { + const base: Record = { + _id: "ticket_base" as Id<"tickets">, + tenantId: TENANT_ID, + reference: 50000, + subject: "Chamado", + summary: null, + status: "PENDING", + priority: "MEDIUM", + channel: "EMAIL", + queueId: undefined, + requesterId: "user_req" as Id<"users">, + requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] }, + assigneeId: "user_assignee" as Id<"users">, + assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] }, + companyId: undefined, + companySnapshot: undefined, + machineId: undefined, + machineSnapshot: undefined, + working: false, + dueAt: undefined, + firstResponseAt: undefined, + resolvedAt: undefined, + closedAt: undefined, + updatedAt: Date.now(), + createdAt: Date.now(), + tags: [], + customFields: [], + totalWorkedMs: 0, + internalWorkedMs: 0, + externalWorkedMs: 0, + activeSessionId: undefined, + } + return { ...(base as Doc<"tickets">), ...overrides } +} + +function buildCompany(overrides: Partial>): Doc<"companies"> { + const base: Record = { + _id: "company_base" as Id<"companies">, + tenantId: TENANT_ID, + name: "Empresa", + slug: "empresa", + createdAt: Date.now(), + updatedAt: Date.now(), + isAvulso: false, + contractedHoursPerMonth: 40, + } + return { ...(base as Doc<"companies">), ...overrides } +} + +describe("convex.reports.agentProductivity", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 6, 10, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("aggregates per-agent metrics including work sessions", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const agentA = { _id: "agent_a" as Id<"users">, name: "Ana", email: "ana@example.com" } as Doc<"users"> + const agentB = { _id: "agent_b" as Id<"users">, name: "Bruno", email: "bruno@example.com" } as Doc<"users"> + + const tickets = [ + buildTicket({ + _id: "ticket_open" as Id<"tickets">, + assigneeId: agentA._id, + createdAt: Date.UTC(2024, 6, 9, 9, 0, 0), + status: "PENDING", + firstResponseAt: Date.UTC(2024, 6, 9, 9, 30, 0), + internalWorkedMs: 45 * 60 * 1000, + }), + buildTicket({ + _id: "ticket_resolved" as Id<"tickets">, + assigneeId: agentA._id, + createdAt: Date.UTC(2024, 6, 8, 10, 0, 0), + firstResponseAt: Date.UTC(2024, 6, 8, 10, 20, 0), + resolvedAt: Date.UTC(2024, 6, 8, 12, 0, 0), + status: "RESOLVED", + }), + buildTicket({ + _id: "ticket_old" as Id<"tickets">, + assigneeId: agentB._id, + createdAt: Date.UTC(2024, 5, 20, 10, 0, 0), + status: "RESOLVED", + }), + ] + + const sessionsMap = new Map; startedAt: number; stoppedAt?: number; durationMs?: number }>>([ + [ + String(agentA._id), + [ + { agentId: agentA._id, startedAt: Date.UTC(2024, 6, 9, 9, 0, 0), stoppedAt: Date.UTC(2024, 6, 9, 10, 0, 0) }, + { agentId: agentA._id, startedAt: Date.UTC(2024, 6, 8, 11, 0, 0), durationMs: 30 * 60 * 1000 }, + ], + ], + ]) + + const ctx = createReportsCtx({ + tickets, + users: new Map>([ + [String(agentA._id), agentA], + [String(agentB._id), agentB], + ]), + ticketWorkSessionsByAgent: sessionsMap, + }) as Parameters[0] + + const result = await agentProductivityHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.items).toHaveLength(1) + expect(result.items[0]).toMatchObject({ + agentId: agentA._id, + open: 1, + resolved: 1, + avgFirstResponseMinutes: 25, + }) + expect(result.items[0]?.workedHours).toBeCloseTo(1.5, 1) + }) +}) + +describe("convex.reports.dashboardOverview", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 6, 15, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("returns trend metrics for new, in-progress and resolution data", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const tickets = [ + buildTicket({ + _id: "ticket_new" as Id<"tickets">, + createdAt: Date.UTC(2024, 6, 15, 8, 0, 0), + firstResponseAt: Date.UTC(2024, 6, 15, 8, 30, 0), + status: "PENDING", + }), + buildTicket({ + _id: "ticket_resolved_recent" as Id<"tickets">, + createdAt: Date.UTC(2024, 6, 8, 9, 0, 0), + firstResponseAt: Date.UTC(2024, 6, 8, 9, 15, 0), + resolvedAt: Date.UTC(2024, 6, 13, 12, 0, 0), + status: "RESOLVED", + }), + buildTicket({ + _id: "ticket_prev" as Id<"tickets">, + createdAt: Date.UTC(2024, 6, 13, 9, 0, 0), + firstResponseAt: Date.UTC(2024, 6, 13, 9, 45, 0), + status: "PAUSED", + dueAt: Date.UTC(2024, 6, 14, 9, 0, 0), + }), + buildTicket({ + _id: "ticket_prev_resolved" as Id<"tickets">, + createdAt: Date.UTC(2024, 6, 5, 9, 0, 0), + firstResponseAt: Date.UTC(2024, 6, 5, 9, 10, 0), + resolvedAt: Date.UTC(2024, 6, 7, 10, 0, 0), + status: "RESOLVED", + }), + ] + + const ctx = createReportsCtx({ tickets }) as Parameters[0] + + const result = await dashboardOverviewHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + }) + + expect(result.newTickets.last24h).toBe(1) + expect(result.inProgress.current).toBe(2) + expect(result.awaitingAction.total).toBe(2) + expect(result.resolution.resolvedLast7d).toBe(1) + }) +}) + +describe("convex.reports.hoursByClientInternal", () => { + const FIXED_NOW = Date.UTC(2024, 7, 1, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("sums internal and external hours per company", async () => { + const companyA = buildCompany({ _id: "company_a" as Id<"companies">, name: "Empresa A" }) + const companyB = buildCompany({ _id: "company_b" as Id<"companies">, name: "Empresa B", isAvulso: true }) + + const tickets = [ + buildTicket({ + _id: "ticket_a" as Id<"tickets">, + companyId: companyA._id, + updatedAt: Date.UTC(2024, 6, 30, 10, 0, 0), + internalWorkedMs: 2 * 3600000, + externalWorkedMs: 3600000, + }), + buildTicket({ + _id: "ticket_b" as Id<"tickets">, + companyId: companyB._id, + updatedAt: Date.UTC(2024, 6, 29, 14, 0, 0), + internalWorkedMs: 0, + externalWorkedMs: 2 * 3600000, + }), + ] + + const ctx = createReportsCtx({ + tickets, + companies: new Map([ + [String(companyA._id), companyA], + [String(companyB._id), companyB], + ]), + }) as Parameters[0] + + const result = await hoursByClientInternalHandler(ctx, { tenantId: TENANT_ID, range: "7d" }) + + expect(result.rangeDays).toBe(7) + expect(result.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ companyId: companyA._id, internalMs: 2 * 3600000, externalMs: 3600000 }), + expect.objectContaining({ companyId: companyB._id, internalMs: 0, externalMs: 2 * 3600000 }), + ]) + ) + }) +}) diff --git a/tests/reports.sla-backlog.test.ts b/tests/reports.sla-backlog.test.ts new file mode 100644 index 0000000..440859c --- /dev/null +++ b/tests/reports.sla-backlog.test.ts @@ -0,0 +1,199 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" + +vi.mock("../convex/rbac", () => ({ + requireStaff: vi.fn(), +})) + +import type { Doc, Id } from "../convex/_generated/dataModel" +import { backlogOverviewHandler, slaOverviewHandler } from "../convex/reports" +import { requireStaff } from "../convex/rbac" +import { createReportsCtx } from "./utils/report-test-helpers" + +const TENANT_ID = "tenant-1" +const VIEWER_ID = "user-staff" as Id<"users"> + +function buildTicket(overrides: Partial>): Doc<"tickets"> { + const base: Record = { + _id: "ticket_base" as Id<"tickets">, + tenantId: TENANT_ID, + reference: 50000, + subject: "Chamado", + summary: null, + status: "PENDING", + priority: "MEDIUM", + channel: "EMAIL", + queueId: undefined, + requesterId: "user_req" as Id<"users">, + requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] }, + assigneeId: "user_agent" as Id<"users">, + assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] }, + companyId: undefined, + companySnapshot: undefined, + machineId: undefined, + machineSnapshot: undefined, + working: false, + dueAt: undefined, + firstResponseAt: undefined, + resolvedAt: undefined, + closedAt: undefined, + updatedAt: Date.now(), + createdAt: Date.now(), + tags: [], + customFields: [], + totalWorkedMs: 0, + internalWorkedMs: 0, + externalWorkedMs: 0, + activeSessionId: undefined, + } + return { ...(base as Doc<"tickets">), ...overrides } +} + +function buildQueue(overrides: Partial>): Doc<"queues"> { + const base: Record = { + _id: "queue_base" as Id<"queues">, + tenantId: TENANT_ID, + name: "Suporte", + slug: "suporte", + createdAt: Date.now(), + updatedAt: Date.now(), + } + return { ...(base as Doc<"queues">), ...overrides } +} + +describe("convex.reports.slaOverview", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 4, 8, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("summarizes totals, averages and queue breakdown", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const queue = buildQueue({ + _id: "queue_1" as Id<"queues">, + name: "Suporte Nível 1", + }) + + const tickets = [ + buildTicket({ + _id: "ticket_open" as Id<"tickets">, + status: "PENDING", + queueId: queue._id, + createdAt: Date.UTC(2024, 4, 7, 9, 0, 0), + dueAt: Date.UTC(2024, 4, 7, 11, 0, 0), + }), + buildTicket({ + _id: "ticket_resolved" as Id<"tickets">, + status: "RESOLVED", + queueId: queue._id, + createdAt: Date.UTC(2024, 4, 6, 8, 0, 0), + firstResponseAt: Date.UTC(2024, 4, 6, 8, 30, 0), + resolvedAt: Date.UTC(2024, 4, 6, 10, 0, 0), + }), + ] + + const ctx = createReportsCtx({ tickets, queues: [queue] }) as Parameters[0] + + const result = await slaOverviewHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.totals).toEqual({ total: 2, open: 1, resolved: 1, overdue: 1 }) + expect(result.response).toEqual({ averageFirstResponseMinutes: 30, responsesRegistered: 1 }) + expect(result.resolution).toEqual({ averageResolutionMinutes: 120, resolvedCount: 1 }) + expect(result.queueBreakdown).toEqual([{ id: queue._id, name: queue.name, open: 1 }]) + }) +}) + +describe("convex.reports.backlogOverview", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 6, 1, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("aggregates status, priority and queue counts for open tickets", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const queueA = buildQueue({ _id: "queue_a" as Id<"queues">, name: "Atendimento" }) + const queueB = buildQueue({ _id: "queue_b" as Id<"queues">, name: "Infraestrutura" }) + + const tickets = [ + buildTicket({ + _id: "ticket_pending" as Id<"tickets">, + status: "PENDING", + priority: "HIGH", + queueId: queueA._id, + createdAt: Date.UTC(2024, 5, 28, 10, 0, 0), + }), + buildTicket({ + _id: "ticket_paused" as Id<"tickets">, + status: "PAUSED", + priority: "MEDIUM", + queueId: queueB._id, + createdAt: Date.UTC(2024, 5, 29, 15, 0, 0), + }), + buildTicket({ + _id: "ticket_resolved" as Id<"tickets">, + status: "RESOLVED", + priority: "URGENT", + queueId: undefined, + createdAt: Date.UTC(2024, 5, 27, 9, 0, 0), + }), + ] + + const ctx = createReportsCtx({ + tickets, + createdRangeTickets: tickets, + queues: [queueA, queueB], + }) as Parameters[0] + + const result = await backlogOverviewHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.statusCounts).toEqual({ + PENDING: 1, + PAUSED: 1, + RESOLVED: 1, + }) + expect(result.priorityCounts).toEqual({ + HIGH: 1, + MEDIUM: 1, + URGENT: 1, + }) + expect(result.totalOpen).toBe(2) + expect(result.queueCounts).toHaveLength(2) + expect(result.queueCounts).toEqual( + expect.arrayContaining([ + { id: "queue_a", name: queueA.name, total: 1 }, + { id: "queue_b", name: queueB.name, total: 1 }, + ]) + ) + }) +}) diff --git a/tests/reports.ticketsByChannel.test.ts b/tests/reports.ticketsByChannel.test.ts new file mode 100644 index 0000000..70f8987 --- /dev/null +++ b/tests/reports.ticketsByChannel.test.ts @@ -0,0 +1,111 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" + +vi.mock("../convex/rbac", () => ({ + requireStaff: vi.fn(), +})) + +import type { Doc, Id } from "../convex/_generated/dataModel" +import { ticketsByChannelHandler } from "../convex/reports" +import { requireStaff } from "../convex/rbac" +import { createReportsCtx } from "./utils/report-test-helpers" + +const TENANT_ID = "tenant-1" +const VIEWER_ID = "user-viewer" as Id<"users"> + +function buildTicket(overrides: Partial>): Doc<"tickets"> { + const base: Record = { + _id: "ticket_base" as Id<"tickets">, + tenantId: TENANT_ID, + reference: 50000, + subject: "Chamado", + summary: null, + status: "PENDING", + priority: "MEDIUM", + channel: "EMAIL", + queueId: undefined, + requesterId: "user_req" as Id<"users">, + requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] }, + assigneeId: "user_assignee" as Id<"users">, + assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] }, + companyId: undefined, + companySnapshot: undefined, + machineId: undefined, + machineSnapshot: undefined, + working: false, + dueAt: undefined, + firstResponseAt: undefined, + resolvedAt: undefined, + closedAt: undefined, + updatedAt: Date.now(), + createdAt: Date.now(), + tags: [], + customFields: [], + totalWorkedMs: 0, + internalWorkedMs: 0, + externalWorkedMs: 0, + activeSessionId: undefined, + } + return { ...(base as Doc<"tickets">), ...overrides } +} + +describe("convex.reports.ticketsByChannel", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 4, 8, 12, 0, 0) // 8 May 2024 12:00 UTC + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("builds timeline grouped by channel within the requested range", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const tickets = [ + buildTicket({ + _id: "ticket_email" as Id<"tickets">, + channel: "EMAIL", + createdAt: Date.UTC(2024, 4, 7, 10, 0, 0), + }), + buildTicket({ + _id: "ticket_chat" as Id<"tickets">, + channel: "CHAT", + createdAt: Date.UTC(2024, 4, 7, 12, 0, 0), + }), + buildTicket({ + _id: "ticket_other" as Id<"tickets">, + channel: undefined, + createdAt: Date.UTC(2024, 4, 6, 9, 0, 0), + }), + buildTicket({ + _id: "ticket_outside" as Id<"tickets">, + createdAt: Date.UTC(2024, 3, 25, 12, 0, 0), // outside 7-day window + }), + ] + + const ctx = createReportsCtx({ tickets }) as Parameters[0] + + const result = await ticketsByChannelHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.channels).toEqual(["CHAT", "EMAIL", "OUTRO"]) + + const may06 = result.points.find((point) => point.date === "2024-05-06") + const may07 = result.points.find((point) => point.date === "2024-05-07") + + expect(may06?.values).toEqual({ CHAT: 0, EMAIL: 0, OUTRO: 1 }) + expect(may07?.values).toEqual({ CHAT: 1, EMAIL: 1, OUTRO: 0 }) + expect(may06).toBeTruthy() + expect(may07).toBeTruthy() + }) +}) diff --git a/tests/reports.timeline-hours.test.ts b/tests/reports.timeline-hours.test.ts new file mode 100644 index 0000000..fc69ae8 --- /dev/null +++ b/tests/reports.timeline-hours.test.ts @@ -0,0 +1,260 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest" + +vi.mock("../convex/rbac", () => ({ + requireStaff: vi.fn(), +})) + +import type { Doc, Id } from "../convex/_generated/dataModel" +import { + csatOverviewHandler, + hoursByClientHandler, + openedResolvedByDayHandler, +} from "../convex/reports" +import { requireStaff } from "../convex/rbac" +import { createReportsCtx } from "./utils/report-test-helpers" + +const TENANT_ID = "tenant-1" +const VIEWER_ID = "user-reports" as Id<"users"> + +function buildTicket(overrides: Partial>): Doc<"tickets"> { + const base: Record = { + _id: "ticket_base" as Id<"tickets">, + tenantId: TENANT_ID, + reference: 50000, + subject: "Chamado", + summary: null, + status: "PENDING", + priority: "MEDIUM", + channel: "EMAIL", + queueId: undefined, + requesterId: "user_req" as Id<"users">, + requesterSnapshot: { name: "Alice", email: "alice@example.com", avatarUrl: undefined, teams: [] }, + assigneeId: "user_assignee" as Id<"users">, + assigneeSnapshot: { name: "Bob", email: "bob@example.com", avatarUrl: undefined, teams: [] }, + companyId: undefined, + companySnapshot: undefined, + machineId: undefined, + machineSnapshot: undefined, + working: false, + dueAt: undefined, + firstResponseAt: undefined, + resolvedAt: undefined, + closedAt: undefined, + updatedAt: Date.now(), + createdAt: Date.now(), + tags: [], + customFields: [], + totalWorkedMs: 0, + internalWorkedMs: 0, + externalWorkedMs: 0, + activeSessionId: undefined, + } + return { ...(base as Doc<"tickets">), ...overrides } +} + +function buildCompany(overrides: Partial>): Doc<"companies"> { + const base: Record = { + _id: "company_base" as Id<"companies">, + tenantId: TENANT_ID, + name: "Empresa", + slug: "empresa", + createdAt: Date.now(), + updatedAt: Date.now(), + isAvulso: false, + contractedHoursPerMonth: 40, + } + return { ...(base as Doc<"companies">), ...overrides } +} + +describe("convex.reports.openedResolvedByDay", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 4, 15, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("counts opened and resolved tickets per day within the range", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const tickets = [ + buildTicket({ + _id: "ticket_open" as Id<"tickets">, + createdAt: Date.UTC(2024, 4, 13, 9, 0, 0), + status: "PENDING", + }), + buildTicket({ + _id: "ticket_resolved" as Id<"tickets">, + createdAt: Date.UTC(2024, 4, 12, 10, 0, 0), + resolvedAt: Date.UTC(2024, 4, 14, 8, 0, 0), + status: "RESOLVED", + }), + buildTicket({ + _id: "ticket_old" as Id<"tickets">, + createdAt: Date.UTC(2024, 3, 30, 10, 0, 0), + status: "PENDING", + }), + ] + + const ctx = createReportsCtx({ tickets }) as Parameters[0] + + const result = await openedResolvedByDayHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + const opened13 = result.series.find((point) => point.date === "2024-05-13") + const resolved14 = result.series.find((point) => point.date === "2024-05-14") + + expect(result.rangeDays).toBe(7) + expect(opened13).toMatchObject({ opened: 1, resolved: 0 }) + expect(resolved14).toMatchObject({ opened: 0, resolved: 1 }) + }) +}) + +describe("convex.reports.csatOverview", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 4, 20, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("summarizes survey averages and distribution", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const ticketA = buildTicket({ + _id: "ticket_a" as Id<"tickets">, + createdAt: Date.UTC(2024, 4, 18, 9, 0, 0), + }) + const ticketB = buildTicket({ + _id: "ticket_b" as Id<"tickets">, + createdAt: Date.UTC(2024, 4, 17, 15, 0, 0), + }) + + const eventsByTicket = new Map>([ + ["ticket_a", [{ type: "CSAT_RECEIVED", payload: { score: 5 }, createdAt: Date.UTC(2024, 4, 19, 8, 0, 0) }]], + [ + "ticket_b", + [ + { type: "CSAT_RECEIVED", payload: { score: 3 }, createdAt: Date.UTC(2024, 4, 18, 14, 0, 0) }, + { type: "COMMENT", payload: {}, createdAt: Date.UTC(2024, 4, 18, 15, 0, 0) }, + ], + ], + ]) + + const ctx = createReportsCtx({ tickets: [ticketA, ticketB], ticketEventsByTicket: eventsByTicket }) as Parameters[0] + + const result = await csatOverviewHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.totalSurveys).toBe(2) + expect(result.averageScore).toBe(4) + expect(result.distribution.find((entry) => entry.score === 5)?.total).toBe(1) + expect(result.distribution.find((entry) => entry.score === 3)?.total).toBe(1) + expect(result.recent[0]?.ticketId).toBe("ticket_a") + }) +}) + +describe("convex.reports.hoursByClient", () => { + const requireStaffMock = vi.mocked(requireStaff) + const FIXED_NOW = Date.UTC(2024, 5, 5, 12, 0, 0) + + beforeAll(() => { + vi.useFakeTimers() + vi.setSystemTime(FIXED_NOW) + }) + + afterAll(() => { + vi.useRealTimers() + }) + + it("aggregates worked hours by company", async () => { + requireStaffMock.mockResolvedValue({ + role: "ADMIN", + user: { companyId: undefined }, + } as unknown as Awaited>) + + const companyA = buildCompany({ _id: "company_a" as Id<"companies">, name: "Empresa A", contractedHoursPerMonth: 60 }) + const companyB = buildCompany({ _id: "company_b" as Id<"companies">, name: "Empresa B", isAvulso: true }) + + const tickets = [ + buildTicket({ + _id: "ticket_a" as Id<"tickets">, + companyId: companyA._id, + updatedAt: Date.UTC(2024, 5, 4, 10, 0, 0), + internalWorkedMs: 3 * 3600000, + externalWorkedMs: 1 * 3600000, + }), + buildTicket({ + _id: "ticket_b" as Id<"tickets">, + companyId: companyB._id, + updatedAt: Date.UTC(2024, 5, 3, 15, 0, 0), + internalWorkedMs: 3600000, + externalWorkedMs: 2 * 3600000, + }), + buildTicket({ + _id: "ticket_old" as Id<"tickets">, + companyId: companyA._id, + updatedAt: Date.UTC(2024, 4, 20, 12, 0, 0), + internalWorkedMs: 5 * 3600000, + externalWorkedMs: 0, + }), + ] + + const companies = new Map>([ + [String(companyA._id), companyA], + [String(companyB._id), companyB], + ]) + + const ctx = createReportsCtx({ tickets, companies }) as Parameters[0] + + const result = await hoursByClientHandler(ctx, { + tenantId: TENANT_ID, + viewerId: VIEWER_ID, + range: "7d", + }) + + expect(result.rangeDays).toBe(7) + expect(result.items).toHaveLength(2) + expect(result.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + companyId: companyA._id, + internalMs: 3 * 3600000, + externalMs: 3600000, + totalMs: 4 * 3600000, + contractedHoursPerMonth: 60, + }), + expect.objectContaining({ + companyId: companyB._id, + internalMs: 3600000, + externalMs: 2 * 3600000, + totalMs: 3 * 3600000, + isAvulso: true, + }), + ]) + ) + }) +}) diff --git a/tests/utils/report-test-helpers.ts b/tests/utils/report-test-helpers.ts new file mode 100644 index 0000000..eaf7c82 --- /dev/null +++ b/tests/utils/report-test-helpers.ts @@ -0,0 +1,127 @@ +import { vi } from "vitest" + +import type { Doc, Id } from "../../convex/_generated/dataModel" + +type ReportsCtxOptions = { + tickets?: Doc<"tickets">[] + createdRangeTickets?: Doc<"tickets">[] + queues?: Doc<"queues">[] + companies?: Map> + users?: Map> + ticketEventsByTicket?: Map> + ticketWorkSessionsByAgent?: Map; startedAt: number; stoppedAt?: number; durationMs?: number }>> +} + +const noopFilterBuilder = { + lt: () => noopFilterBuilder, + field: () => "createdAt", +} + +const noopIndexBuilder = { + eq: () => noopIndexBuilder, + gte: () => noopIndexBuilder, +} + +function ticketsChain(collection: Doc<"tickets">[]) { + const chain = { + filter: vi.fn((cb?: (builder: typeof noopFilterBuilder) => unknown) => { + cb?.(noopFilterBuilder) + return chain + }), + order: vi.fn(() => chain), + collect: vi.fn(async () => collection), + } + return chain +} + +export function createReportsCtx({ + tickets = [], + createdRangeTickets = tickets, + queues = [], + companies = new Map>(), + users = new Map>(), + ticketEventsByTicket = new Map>(), + ticketWorkSessionsByAgent = new Map; startedAt: number; stoppedAt?: number; durationMs?: number }>>(), +}: ReportsCtxOptions = {}) { + const db = { + get: vi.fn(async (id: Id<"companies"> | Id<"users">) => { + const company = companies.get(String(id)) + if (company) return company + const user = users.get(String(id)) + if (user) return user + return null + }), + query: vi.fn((table: string) => { + if (table === "tickets") { + return { + withIndex: vi.fn((indexName: string, cb?: (builder: typeof noopIndexBuilder) => unknown) => { + cb?.(noopIndexBuilder) + const collection = + indexName.includes("created") || indexName.includes("tenant_company_created") + ? createdRangeTickets + : tickets + return ticketsChain(collection) + }), + collect: vi.fn(async () => tickets), + } + } + + if (table === "queues") { + return { + withIndex: vi.fn((_indexName: string, cb?: (builder: typeof noopIndexBuilder) => unknown) => { + cb?.(noopIndexBuilder) + return { + collect: vi.fn(async () => queues), + } + }), + collect: vi.fn(async () => queues), + } + } + + if (table === "ticketEvents") { + return { + withIndex: vi.fn((_indexName: string, cb?: (builder: { eq: (field: unknown, value: unknown) => unknown }) => unknown) => { + let ticketId: string | null = null + const builder = { + eq: (_field: unknown, value: unknown) => { + ticketId = String(value) + return builder + }, + } + cb?.(builder as { eq: (field: unknown, value: unknown) => unknown }) + return { + collect: vi.fn(async () => (ticketId ? ticketEventsByTicket.get(ticketId) ?? [] : [])), + } + }), + } + } + + if (table === "ticketWorkSessions") { + return { + withIndex: vi.fn((_indexName: string, cb?: (builder: { eq: (field: unknown, value: unknown) => unknown }) => unknown) => { + let agentId: string | null = null + const builder = { + eq: (_field: unknown, value: unknown) => { + agentId = String(value) + return builder + }, + } + cb?.(builder as { eq: (field: unknown, value: unknown) => unknown }) + return { + collect: vi.fn(async () => (agentId ? ticketWorkSessionsByAgent.get(agentId) ?? [] : [])), + } + }), + } + } + + return { + withIndex: vi.fn(() => ({ + collect: vi.fn(async () => []), + })), + collect: vi.fn(async () => []), + } + }), + } + + return { db } as unknown +}