Fix admin cleanup hook order and lint setup

This commit is contained in:
Esdras Renan 2025-10-24 09:21:31 -03:00
parent 37c32149a6
commit c7aaa60d9a
4 changed files with 1886 additions and 31 deletions

View file

@ -61,9 +61,9 @@
"pdfkit": "^0.17.2", "pdfkit": "^0.17.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"react": "19.2.0", "react": "19.2.0",
"react-aria-components": "^1.4.0",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"react-hook-form": "^7.64.0", "react-hook-form": "^7.64.0",
"react-aria-components": "^1.4.0",
"recharts": "^2.15.4", "recharts": "^2.15.4",
"sanitize-html": "^2.17.0", "sanitize-html": "^2.17.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
@ -98,6 +98,7 @@
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"tw-animate-css": "^1.3.8", "tw-animate-css": "^1.3.8",
"typescript": "^5", "typescript": "^5",
"typescript-eslint": "^8.46.2",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.1" "vitest": "^4.0.1"
} }

1841
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,28 @@ export const runtime = "nodejs"
const MAX_RESULTS = 10 const MAX_RESULTS = 10
type TicketRecord = {
id: string | number
reference: string | number
subject?: string | null
status?: string | null
priority?: string | null
requester?: { name?: string | null } | null
assignee?: { name?: string | null } | null
company?: { name?: string | null } | null
updatedAt?: number | null
}
function isTicketRecord(value: unknown): value is TicketRecord {
if (!value || typeof value !== "object") {
return false
}
const record = value as Record<string, unknown>
const hasValidId = typeof record.id === "string" || typeof record.id === "number"
const hasValidReference = typeof record.reference === "string" || typeof record.reference === "number"
return hasValidId && hasValidReference
}
function normalizeRole(role?: string | null) { function normalizeRole(role?: string | null) {
return (role ?? "").toLowerCase() return (role ?? "").toLowerCase()
} }
@ -59,25 +81,15 @@ export async function GET(request: Request) {
} }
// Pesquisar pelos tickets visíveis ao viewer (assunto, resumo ou #referência) // Pesquisar pelos tickets visíveis ao viewer (assunto, resumo ou #referência)
let tickets: Array<{ let tickets: TicketRecord[] = []
id: string
reference: number
subject: string
status: string
priority: string
requester?: { name?: string | null } | null
assignee?: { name?: string | null } | null
company?: { name?: string | null } | null
updatedAt?: number | null
}> = []
try { try {
const res = (await client.query(api.tickets.list, { const res = await client.query(api.tickets.list, {
tenantId, tenantId,
viewerId: viewerId as unknown as Id<"users">, viewerId: viewerId as unknown as Id<"users">,
search: query, search: query,
limit: 40, limit: 40,
})) as any[] })
tickets = Array.isArray(res) ? res : [] tickets = Array.isArray(res) ? res.filter(isTicketRecord) : []
} catch (error) { } catch (error) {
console.error("[mentions] tickets.list failed", error) console.error("[mentions] tickets.list failed", error)
return NextResponse.json({ items: [] }, { status: 500 }) return NextResponse.json({ items: [] }, { status: 500 })

View file

@ -2,7 +2,7 @@
import Link from "next/link" import Link from "next/link"
import { useCallback, useEffect, useMemo, useState, useTransition } from "react" import { useCallback, useEffect, useMemo, useState, useTransition } from "react"
import { IconSearch, IconUserPlus, IconTrash } from "@tabler/icons-react" import { IconSearch, IconUserPlus, IconTrash, IconAlertTriangle } from "@tabler/icons-react"
import { toast } from "sonner" import { toast } from "sonner"
@ -273,21 +273,6 @@ export function AdminUsersManager({
return Array.from(unique) return Array.from(unique)
}, [normalizedRoles, viewerIsAdmin]) }, [normalizedRoles, viewerIsAdmin])
const buildKeepEmailSet = useCallback(() => {
const keep = new Set<string>()
DEFAULT_KEEP_EMAILS.forEach((email) => keep.add(email.toLowerCase()))
if (viewerEmail) {
keep.add(viewerEmail)
}
cleanupKeepEmails
.split(",")
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean)
.forEach((email) => keep.add(email))
return keep
}, [cleanupKeepEmails, viewerEmail])
const cleanupPreview = useMemo(() => Array.from(buildKeepEmailSet()).join(", "), [buildKeepEmailSet])
// Split users: team (admin/agent) and people (manager/collaborator); exclude machines // Split users: team (admin/agent) and people (manager/collaborator); exclude machines
const teamUsers = useMemo( const teamUsers = useMemo(
() => users.filter((user) => user.role !== "machine" && ["admin", "agent"].includes(coerceRole(user.role))), () => users.filter((user) => user.role !== "machine" && ["admin", "agent"].includes(coerceRole(user.role))),
@ -331,6 +316,22 @@ export function AdminUsersManager({
const [cleanupKeepEmails, setCleanupKeepEmails] = useState(DEFAULT_KEEP_EMAILS.join(", ")) const [cleanupKeepEmails, setCleanupKeepEmails] = useState(DEFAULT_KEEP_EMAILS.join(", "))
const [cleanupPending, setCleanupPending] = useState(false) const [cleanupPending, setCleanupPending] = useState(false)
const buildKeepEmailSet = useCallback(() => {
const keep = new Set<string>()
DEFAULT_KEEP_EMAILS.forEach((email) => keep.add(email.toLowerCase()))
if (viewerEmail) {
keep.add(viewerEmail)
}
cleanupKeepEmails
.split(",")
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean)
.forEach((email) => keep.add(email))
return keep
}, [cleanupKeepEmails, viewerEmail])
const cleanupPreview = useMemo(() => Array.from(buildKeepEmailSet()).join(", "), [buildKeepEmailSet])
// Máquinas (para listar vínculos por usuário) // Máquinas (para listar vínculos por usuário)
type MachinesListItem = { type MachinesListItem = {
id: string id: string