feat: adicionar construtor de dashboards e api de métricas

This commit is contained in:
Esdras Renan 2025-11-04 20:37:34 -03:00
parent c2acd65764
commit 741f1d7f9c
14 changed files with 4356 additions and 9 deletions

View file

@ -6,9 +6,9 @@ import type { Doc, Id } from "./_generated/dataModel";
import { requireStaff } from "./rbac";
import { getOfflineThresholdMs, getStaleThresholdMs } from "./machines";
type TicketStatusNormalized = "PENDING" | "AWAITING_ATTENDANCE" | "PAUSED" | "RESOLVED";
export type TicketStatusNormalized = "PENDING" | "AWAITING_ATTENDANCE" | "PAUSED" | "RESOLVED";
type QueryFilterBuilder = { lt: (field: unknown, value: number) => unknown; field: (name: string) => unknown };
const STATUS_NORMALIZE_MAP: Record<string, TicketStatusNormalized> = {
export const STATUS_NORMALIZE_MAP: Record<string, TicketStatusNormalized> = {
NEW: "PENDING",
PENDING: "PENDING",
OPEN: "AWAITING_ATTENDANCE",
@ -19,7 +19,7 @@ const STATUS_NORMALIZE_MAP: Record<string, TicketStatusNormalized> = {
CLOSED: "RESOLVED",
};
function normalizeStatus(status: string | null | undefined): TicketStatusNormalized {
export function normalizeStatus(status: string | null | undefined): TicketStatusNormalized {
if (!status) return "PENDING";
const normalized = STATUS_NORMALIZE_MAP[status.toUpperCase()];
return normalized ?? "PENDING";
@ -30,8 +30,8 @@ function average(values: number[]) {
return values.reduce((sum, value) => sum + value, 0) / values.length;
}
const OPEN_STATUSES = new Set<TicketStatusNormalized>(["PENDING", "AWAITING_ATTENDANCE", "PAUSED"]);
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
export const OPEN_STATUSES = new Set<TicketStatusNormalized>(["PENDING", "AWAITING_ATTENDANCE", "PAUSED"]);
export const ONE_DAY_MS = 24 * 60 * 60 * 1000;
function percentageChange(current: number, previous: number) {
if (previous === 0) {
@ -88,14 +88,14 @@ function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
async function fetchTickets(ctx: QueryCtx, tenantId: string) {
export async function fetchTickets(ctx: QueryCtx, tenantId: string) {
return ctx.db
.query("tickets")
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
.collect();
}
async function fetchScopedTickets(
export async function fetchScopedTickets(
ctx: QueryCtx,
tenantId: string,
viewer: Awaited<ReturnType<typeof requireStaff>>,
@ -114,7 +114,7 @@ async function fetchScopedTickets(
return fetchTickets(ctx, tenantId);
}
async function fetchScopedTicketsByCreatedRange(
export async function fetchScopedTicketsByCreatedRange(
ctx: QueryCtx,
tenantId: string,
viewer: Awaited<ReturnType<typeof requireStaff>>,
@ -179,7 +179,7 @@ async function fetchScopedTicketsByCreatedRange(
);
}
async function fetchScopedTicketsByResolvedRange(
export async function fetchScopedTicketsByResolvedRange(
ctx: QueryCtx,
tenantId: string,
viewer: Awaited<ReturnType<typeof requireStaff>>,