fix: harden machine session fallback and clean lint
This commit is contained in:
parent
2607ca5ce3
commit
846e575637
6 changed files with 60 additions and 32 deletions
|
|
@ -106,6 +106,53 @@ function normalizeTeams(teams?: string[] | null): string[] {
|
|||
return teams.map((team) => renameQueueString(team) ?? team);
|
||||
}
|
||||
|
||||
type RequesterFallbackContext = {
|
||||
ticketId?: Id<"tickets">;
|
||||
fallbackName?: string | null;
|
||||
fallbackEmail?: string | null;
|
||||
};
|
||||
|
||||
function buildRequesterSummary(
|
||||
requester: Doc<"users"> | null,
|
||||
requesterId: Id<"users">,
|
||||
context?: RequesterFallbackContext,
|
||||
) {
|
||||
if (requester) {
|
||||
return {
|
||||
id: requester._id,
|
||||
name: requester.name,
|
||||
email: requester.email,
|
||||
avatarUrl: requester.avatarUrl,
|
||||
teams: normalizeTeams(requester.teams),
|
||||
};
|
||||
}
|
||||
|
||||
const idString = String(requesterId);
|
||||
const fallbackName =
|
||||
typeof context?.fallbackName === "string" && context.fallbackName.trim().length > 0
|
||||
? context.fallbackName.trim()
|
||||
: "Solicitante não encontrado";
|
||||
const fallbackEmailCandidate =
|
||||
typeof context?.fallbackEmail === "string" && context.fallbackEmail.includes("@")
|
||||
? context.fallbackEmail
|
||||
: null;
|
||||
const fallbackEmail = fallbackEmailCandidate ?? `requester-${idString}@example.invalid`;
|
||||
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
const ticketInfo = context?.ticketId ? ` (ticket ${String(context.ticketId)})` : "";
|
||||
console.warn(
|
||||
`[tickets] requester ${idString} ausente ao hidratar resposta${ticketInfo}; usando placeholders.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: requesterId,
|
||||
name: fallbackName,
|
||||
email: fallbackEmail,
|
||||
teams: [],
|
||||
};
|
||||
}
|
||||
|
||||
type CustomFieldInput = {
|
||||
fieldId: Id<"ticketFields">;
|
||||
value: unknown;
|
||||
|
|
@ -348,13 +395,7 @@ export const list = query({
|
|||
channel: t.channel,
|
||||
queue: queueName,
|
||||
company: company ? { id: company._id, name: company.name, isAvulso: company.isAvulso ?? false } : null,
|
||||
requester: requester && {
|
||||
id: requester._id,
|
||||
name: requester.name,
|
||||
email: requester.email,
|
||||
avatarUrl: requester.avatarUrl,
|
||||
teams: normalizeTeams(requester.teams),
|
||||
},
|
||||
requester: buildRequesterSummary(requester, t.requesterId, { ticketId: t._id }),
|
||||
assignee: assignee
|
||||
? {
|
||||
id: assignee._id,
|
||||
|
|
@ -526,13 +567,7 @@ export const getById = query({
|
|||
channel: t.channel,
|
||||
queue: queueName,
|
||||
company: company ? { id: company._id, name: company.name, isAvulso: company.isAvulso ?? false } : null,
|
||||
requester: requester && {
|
||||
id: requester._id,
|
||||
name: requester.name,
|
||||
email: requester.email,
|
||||
avatarUrl: requester.avatarUrl,
|
||||
teams: normalizeTeams(requester.teams),
|
||||
},
|
||||
requester: buildRequesterSummary(requester, t.requesterId, { ticketId: t._id }),
|
||||
assignee: assignee
|
||||
? {
|
||||
id: assignee._id,
|
||||
|
|
@ -1434,13 +1469,7 @@ export const playNext = mutation({
|
|||
priority: chosen.priority,
|
||||
channel: chosen.channel,
|
||||
queue: queueName,
|
||||
requester: requester && {
|
||||
id: requester._id,
|
||||
name: requester.name,
|
||||
email: requester.email,
|
||||
avatarUrl: requester.avatarUrl,
|
||||
teams: normalizeTeams(requester.teams),
|
||||
},
|
||||
requester: buildRequesterSummary(requester, chosen.requesterId, { ticketId: chosen._id }),
|
||||
assignee: assignee
|
||||
? {
|
||||
id: assignee._id,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import { mutation, query } from "./_generated/server";
|
|||
import { ConvexError, v } from "convex/values";
|
||||
import { requireAdmin } from "./rbac";
|
||||
|
||||
// All roles that have staff-level access in some areas. Do NOT include COLLABORATOR here
|
||||
// to avoid leaking collaborators into staff pickers such as "responsável".
|
||||
const STAFF_ROLES = new Set(["ADMIN", "MANAGER", "AGENT"]);
|
||||
const INTERNAL_STAFF_ROLES = new Set(["ADMIN", "AGENT"]);
|
||||
|
||||
export const ensureUser = mutation({
|
||||
|
|
|
|||
|
|
@ -994,16 +994,13 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
|||
const metrics = machine?.metrics ?? null
|
||||
const metricsCapturedAt = useMemo(() => getMetricsTimestamp(metrics), [metrics])
|
||||
// Live refresh the relative time label every second when we have a capture timestamp
|
||||
const [relativeTick, setRelativeTick] = useState(0)
|
||||
const [, setRelativeTick] = useState(0)
|
||||
useEffect(() => {
|
||||
if (!metricsCapturedAt) return
|
||||
const id = setInterval(() => setRelativeTick((t) => t + 1), 1000)
|
||||
return () => clearInterval(id)
|
||||
}, [metricsCapturedAt])
|
||||
const lastUpdateRelative = useMemo(
|
||||
() => (metricsCapturedAt ? formatRelativeTime(metricsCapturedAt) : null),
|
||||
[metricsCapturedAt, relativeTick]
|
||||
)
|
||||
const lastUpdateRelative = metricsCapturedAt ? formatRelativeTime(metricsCapturedAt) : null
|
||||
const hardware = metadata?.hardware
|
||||
const network = metadata?.network ?? null
|
||||
const networkInterfaces = Array.isArray(network) ? network : null
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ function PortalCommentAttachmentCard({
|
|||
window.open(target, "_blank", "noopener,noreferrer")
|
||||
toast.error("Não foi possível baixar o anexo automaticamente.", { id: toastId })
|
||||
}
|
||||
}, [attachment.name, ensureUrl])
|
||||
}, [attachment.id, attachment.name, ensureUrl])
|
||||
|
||||
const resolvedUrl = url
|
||||
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ function CommentAttachmentCard({
|
|||
window.open(target, "_blank", "noopener,noreferrer")
|
||||
toast.error("Não foi possível baixar o anexo automaticamente.", { id: toastId })
|
||||
}
|
||||
}, [attachment.name, ensureUrl, url])
|
||||
}, [attachment.id, attachment.name, ensureUrl, url])
|
||||
|
||||
const name = attachment.name ?? ""
|
||||
const urlLooksImage = url ? /\.(png|jpe?g|gif|webp|svg)$/i.test(url) : false
|
||||
|
|
|
|||
|
|
@ -109,6 +109,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
// carregar o contexto — se a API responder 200, assumimos que há sessão válida
|
||||
// do lado do servidor e populamos o contexto para o restante do app.
|
||||
useEffect(() => {
|
||||
if (isPending) {
|
||||
setMachineContextLoading(true)
|
||||
return
|
||||
}
|
||||
|
||||
const shouldFetch = Boolean(session?.user?.role === "machine") || !session?.user
|
||||
if (!shouldFetch) {
|
||||
setMachineContext(null)
|
||||
|
|
@ -215,7 +220,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [session?.user])
|
||||
}, [session?.user, isPending])
|
||||
|
||||
// Poll machine session periodically to reflect admin changes (e.g., deactivation)
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue