chore: prep platform improvements
This commit is contained in:
parent
a62f3d5283
commit
c5ddd54a3e
24 changed files with 777 additions and 649 deletions
|
|
@ -3,8 +3,7 @@ import { mutation, query } from "./_generated/server";
|
|||
import { api } from "./_generated/api";
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server";
|
||||
import { ConvexError, v } from "convex/values";
|
||||
import { Id, type Doc, type DataModel } from "./_generated/dataModel";
|
||||
import type { NamedTableInfo, Query as ConvexQuery } from "convex/server";
|
||||
import { Id, type Doc } from "./_generated/dataModel";
|
||||
|
||||
import { requireAdmin, requireStaff, requireUser } from "./rbac";
|
||||
import {
|
||||
|
|
@ -477,13 +476,15 @@ async function fetchTemplateSummaries(ctx: AnyCtx, tenantId: string): Promise<Te
|
|||
async function fetchTicketFieldsByScopes(
|
||||
ctx: QueryCtx,
|
||||
tenantId: string,
|
||||
scopes: string[]
|
||||
scopes: string[],
|
||||
companyId: Id<"companies"> | null
|
||||
): Promise<TicketFieldScopeMap> {
|
||||
const uniqueScopes = Array.from(new Set(scopes.filter((scope) => Boolean(scope))));
|
||||
if (uniqueScopes.length === 0) {
|
||||
return new Map();
|
||||
}
|
||||
const scopeSet = new Set(uniqueScopes);
|
||||
const companyIdStr = companyId ? String(companyId) : null;
|
||||
const result: TicketFieldScopeMap = new Map();
|
||||
const allFields = await ctx.db
|
||||
.query("ticketFields")
|
||||
|
|
@ -495,6 +496,10 @@ async function fetchTicketFieldsByScopes(
|
|||
if (!scopeSet.has(scope)) {
|
||||
continue;
|
||||
}
|
||||
const fieldCompanyId = field.companyId ? String(field.companyId) : null;
|
||||
if (fieldCompanyId && (!companyIdStr || companyIdStr !== fieldCompanyId)) {
|
||||
continue;
|
||||
}
|
||||
const current = result.get(scope);
|
||||
if (current) {
|
||||
current.push(field);
|
||||
|
|
@ -634,6 +639,7 @@ async function ensureTicketFormDefaultsForTenant(ctx: MutationCtx, tenantId: str
|
|||
label: option.label,
|
||||
})),
|
||||
scope: template.key,
|
||||
companyId: field.companyId ?? undefined,
|
||||
order,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
|
@ -1319,9 +1325,6 @@ const MAX_FETCH_LIMIT = 1000;
|
|||
const FETCH_MULTIPLIER_NO_SEARCH = 3;
|
||||
const FETCH_MULTIPLIER_WITH_SEARCH = 5;
|
||||
|
||||
type TicketsTableInfo = NamedTableInfo<DataModel, "tickets">;
|
||||
type TicketsQueryBuilder = ConvexQuery<TicketsTableInfo>;
|
||||
|
||||
function clampTicketLimit(limit: number) {
|
||||
if (!Number.isFinite(limit)) return DEFAULT_TICKETS_LIST_LIMIT;
|
||||
return Math.max(MIN_TICKETS_LIST_LIMIT, Math.min(MAX_TICKETS_LIST_LIMIT, Math.floor(limit)));
|
||||
|
|
@ -1371,7 +1374,6 @@ export const list = query({
|
|||
const normalizedStatusFilter = args.status ? normalizeStatus(args.status) : null;
|
||||
const normalizedPriorityFilter = normalizePriorityFilter(args.priority);
|
||||
const prioritySet = normalizedPriorityFilter.length > 0 ? new Set(normalizedPriorityFilter) : null;
|
||||
const primaryPriorityFilter = normalizedPriorityFilter.length === 1 ? normalizedPriorityFilter[0] : null;
|
||||
const normalizedChannelFilter = args.channel ? args.channel.toUpperCase() : null;
|
||||
const searchTerm = args.search?.trim().toLowerCase() ?? null;
|
||||
|
||||
|
|
@ -1379,80 +1381,43 @@ export const list = query({
|
|||
const requestedLimit = clampTicketLimit(requestedLimitRaw);
|
||||
const fetchLimit = computeFetchLimit(requestedLimit, Boolean(searchTerm));
|
||||
|
||||
const applyQueryFilters = (query: TicketsQueryBuilder) => {
|
||||
let working = query;
|
||||
if (normalizedStatusFilter) {
|
||||
working = working.filter((q) => q.eq(q.field("status"), normalizedStatusFilter));
|
||||
}
|
||||
if (primaryPriorityFilter) {
|
||||
working = working.filter((q) => q.eq(q.field("priority"), primaryPriorityFilter));
|
||||
}
|
||||
if (normalizedChannelFilter) {
|
||||
working = working.filter((q) => q.eq(q.field("channel"), normalizedChannelFilter));
|
||||
}
|
||||
if (args.queueId) {
|
||||
working = working.filter((q) => q.eq(q.field("queueId"), args.queueId!));
|
||||
}
|
||||
if (args.assigneeId) {
|
||||
working = working.filter((q) => q.eq(q.field("assigneeId"), args.assigneeId!));
|
||||
}
|
||||
if (args.requesterId) {
|
||||
working = working.filter((q) => q.eq(q.field("requesterId"), args.requesterId!));
|
||||
}
|
||||
return working;
|
||||
};
|
||||
|
||||
let base: Doc<"tickets">[] = [];
|
||||
|
||||
if (role === "MANAGER") {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", args.tenantId).eq("companyId", user.companyId!))
|
||||
);
|
||||
const baseQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_company", (q) => q.eq("tenantId", args.tenantId).eq("companyId", user.companyId!));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
} else if (args.assigneeId) {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_assignee", (q) => q.eq("tenantId", args.tenantId).eq("assigneeId", args.assigneeId!))
|
||||
);
|
||||
const baseQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_assignee", (q) => q.eq("tenantId", args.tenantId).eq("assigneeId", args.assigneeId!));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
} else if (args.requesterId) {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_requester", (q) => q.eq("tenantId", args.tenantId).eq("requesterId", args.requesterId!))
|
||||
);
|
||||
const baseQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_requester", (q) => q.eq("tenantId", args.tenantId).eq("requesterId", args.requesterId!));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
} else if (args.queueId) {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_queue", (q) => q.eq("tenantId", args.tenantId).eq("queueId", args.queueId!))
|
||||
);
|
||||
const baseQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_queue", (q) => q.eq("tenantId", args.tenantId).eq("queueId", args.queueId!));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
} else if (normalizedStatusFilter) {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_status", (q) => q.eq("tenantId", args.tenantId).eq("status", normalizedStatusFilter))
|
||||
);
|
||||
const baseQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_status", (q) => q.eq("tenantId", args.tenantId).eq("status", normalizedStatusFilter));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
} else if (role === "COLLABORATOR") {
|
||||
const viewerEmail = user.email.trim().toLowerCase();
|
||||
const directQuery = applyQueryFilters(
|
||||
ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_requester", (q) => q.eq("tenantId", args.tenantId).eq("requesterId", viewerId))
|
||||
);
|
||||
const directQuery = ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_requester", (q) => q.eq("tenantId", args.tenantId).eq("requesterId", viewerId));
|
||||
const directTickets = await directQuery.order("desc").take(fetchLimit);
|
||||
|
||||
let combined = directTickets;
|
||||
if (directTickets.length < fetchLimit) {
|
||||
const fallbackQuery = applyQueryFilters(
|
||||
ctx.db.query("tickets").withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId))
|
||||
);
|
||||
const fallbackQuery = ctx.db.query("tickets").withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId));
|
||||
const fallbackRaw = await fallbackQuery.order("desc").take(fetchLimit);
|
||||
const fallbackMatches = fallbackRaw.filter((ticket) => {
|
||||
const snapshotEmail = (ticket.requesterSnapshot as { email?: string } | undefined)?.email;
|
||||
|
|
@ -1463,9 +1428,7 @@ export const list = query({
|
|||
}
|
||||
base = combined.slice(0, fetchLimit);
|
||||
} else {
|
||||
const baseQuery = applyQueryFilters(
|
||||
ctx.db.query("tickets").withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId))
|
||||
);
|
||||
const baseQuery = ctx.db.query("tickets").withIndex("by_tenant", (q) => q.eq("tenantId", args.tenantId));
|
||||
base = await baseQuery.order("desc").take(fetchLimit);
|
||||
}
|
||||
|
||||
|
|
@ -2829,7 +2792,7 @@ export const listTicketForms = query({
|
|||
const templates = await fetchTemplateSummaries(ctx, tenantId)
|
||||
|
||||
const scopes = templates.map((template) => template.key)
|
||||
const fieldsByScope = await fetchTicketFieldsByScopes(ctx, tenantId, scopes)
|
||||
const fieldsByScope = await fetchTicketFieldsByScopes(ctx, tenantId, scopes, viewerCompanyId)
|
||||
|
||||
const staffOverride = viewerRole === "ADMIN" || viewerRole === "AGENT"
|
||||
const settingsByTemplate = staffOverride
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue