Add requester filter and improve error messages
- Add requester filter to device tickets history page - Create listMachineRequesters query to list unique requesters - Add friendly API error formatting in desktop agent - Translate validation errors to user-friendly Portuguese messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bb82efa9d3
commit
cf31e78edb
3 changed files with 140 additions and 5 deletions
|
|
@ -1282,6 +1282,7 @@ export const listOpenTickets = query({
|
|||
type MachineTicketsHistoryFilter = {
|
||||
statusFilter: "all" | "open" | "resolved"
|
||||
priorityFilter: string | null
|
||||
requesterEmail: string | null
|
||||
from: number | null
|
||||
to: number | null
|
||||
}
|
||||
|
|
@ -1290,6 +1291,7 @@ type ListTicketsHistoryArgs = {
|
|||
machineId: Id<"machines">
|
||||
status?: "all" | "open" | "resolved"
|
||||
priority?: string
|
||||
requesterEmail?: string
|
||||
search?: string
|
||||
from?: number
|
||||
to?: number
|
||||
|
|
@ -1300,6 +1302,7 @@ type GetTicketsHistoryStatsArgs = {
|
|||
machineId: Id<"machines">
|
||||
status?: "all" | "open" | "resolved"
|
||||
priority?: string
|
||||
requesterEmail?: string
|
||||
search?: string
|
||||
from?: number
|
||||
to?: number
|
||||
|
|
@ -1343,6 +1346,13 @@ function createMachineTicketsQuery(
|
|||
return working
|
||||
}
|
||||
|
||||
function matchesRequesterEmail(ticket: Doc<"tickets">, requesterEmail: string | null): boolean {
|
||||
if (!requesterEmail) return true
|
||||
const requesterSnapshot = ticket.requesterSnapshot as { name?: string; email?: string } | undefined
|
||||
if (!requesterSnapshot?.email) return false
|
||||
return requesterSnapshot.email.toLowerCase() === requesterEmail.toLowerCase()
|
||||
}
|
||||
|
||||
function matchesTicketSearch(ticket: Doc<"tickets">, searchTerm: string): boolean {
|
||||
const normalized = searchTerm.trim().toLowerCase()
|
||||
if (!normalized) return true
|
||||
|
|
@ -1383,19 +1393,27 @@ export async function listTicketsHistoryHandler(ctx: QueryCtx, args: ListTickets
|
|||
|
||||
const normalizedStatusFilter = args.status ?? "all"
|
||||
const normalizedPriorityFilter = args.priority ? args.priority.toUpperCase() : null
|
||||
const normalizedRequesterEmail = args.requesterEmail?.trim().toLowerCase() ?? 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,
|
||||
requesterEmail: normalizedRequesterEmail,
|
||||
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
|
||||
let page = pageResult.page
|
||||
if (normalizedRequesterEmail) {
|
||||
page = page.filter((ticket) => matchesRequesterEmail(ticket, normalizedRequesterEmail))
|
||||
}
|
||||
if (searchTerm) {
|
||||
page = page.filter((ticket) => matchesTicketSearch(ticket, searchTerm))
|
||||
}
|
||||
const queueCache = new Map<string, Doc<"queues"> | null>()
|
||||
const items = await Promise.all(
|
||||
page.map(async (ticket) => {
|
||||
|
|
@ -1448,6 +1466,7 @@ export const listTicketsHistory = query({
|
|||
machineId: v.id("machines"),
|
||||
status: v.optional(v.union(v.literal("all"), v.literal("open"), v.literal("resolved"))),
|
||||
priority: v.optional(v.string()),
|
||||
requesterEmail: v.optional(v.string()),
|
||||
search: v.optional(v.string()),
|
||||
from: v.optional(v.number()),
|
||||
to: v.optional(v.number()),
|
||||
|
|
@ -1467,12 +1486,14 @@ export async function getTicketsHistoryStatsHandler(
|
|||
|
||||
const normalizedStatusFilter = args.status ?? "all"
|
||||
const normalizedPriorityFilter = args.priority ? args.priority.toUpperCase() : null
|
||||
const normalizedRequesterEmail = args.requesterEmail?.trim().toLowerCase() ?? 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,
|
||||
requesterEmail: normalizedRequesterEmail,
|
||||
from,
|
||||
to,
|
||||
}
|
||||
|
|
@ -1487,7 +1508,13 @@ export async function getTicketsHistoryStatsHandler(
|
|||
numItems: MACHINE_TICKETS_STATS_PAGE_SIZE,
|
||||
cursor,
|
||||
})
|
||||
const page = searchTerm ? pageResult.page.filter((ticket) => matchesTicketSearch(ticket, searchTerm)) : pageResult.page
|
||||
let page = pageResult.page
|
||||
if (normalizedRequesterEmail) {
|
||||
page = page.filter((ticket) => matchesRequesterEmail(ticket, normalizedRequesterEmail))
|
||||
}
|
||||
if (searchTerm) {
|
||||
page = page.filter((ticket) => matchesTicketSearch(ticket, searchTerm))
|
||||
}
|
||||
total += page.length
|
||||
for (const ticket of page) {
|
||||
if (OPEN_TICKET_STATUSES.has(normalizeStatus(ticket.status))) {
|
||||
|
|
@ -1513,6 +1540,7 @@ export const getTicketsHistoryStats = query({
|
|||
machineId: v.id("machines"),
|
||||
status: v.optional(v.union(v.literal("all"), v.literal("open"), v.literal("resolved"))),
|
||||
priority: v.optional(v.string()),
|
||||
requesterEmail: v.optional(v.string()),
|
||||
search: v.optional(v.string()),
|
||||
from: v.optional(v.number()),
|
||||
to: v.optional(v.number()),
|
||||
|
|
@ -1520,6 +1548,44 @@ export const getTicketsHistoryStats = query({
|
|||
handler: getTicketsHistoryStatsHandler,
|
||||
})
|
||||
|
||||
// Lista os solicitantes unicos que abriram tickets nesta maquina
|
||||
export const listMachineRequesters = query({
|
||||
args: {
|
||||
machineId: v.id("machines"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const machine = await ctx.db.get(args.machineId)
|
||||
if (!machine) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tickets = await ctx.db
|
||||
.query("tickets")
|
||||
.withIndex("by_tenant_machine", (q) => q.eq("tenantId", machine.tenantId).eq("machineId", args.machineId))
|
||||
.collect()
|
||||
|
||||
const requestersMap = new Map<string, { email: string; name: string | null }>()
|
||||
for (const ticket of tickets) {
|
||||
const snapshot = ticket.requesterSnapshot as { name?: string; email?: string } | undefined
|
||||
if (snapshot?.email) {
|
||||
const emailLower = snapshot.email.toLowerCase()
|
||||
if (!requestersMap.has(emailLower)) {
|
||||
requestersMap.set(emailLower, {
|
||||
email: snapshot.email,
|
||||
name: snapshot.name ?? null,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(requestersMap.values()).sort((a, b) => {
|
||||
const nameA = a.name ?? a.email
|
||||
const nameB = b.name ?? b.email
|
||||
return nameA.localeCompare(nameB)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export async function updatePersonaHandler(
|
||||
ctx: MutationCtx,
|
||||
args: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue