fix: align company data with machines

This commit is contained in:
Esdras Renan 2025-10-18 21:57:13 -03:00
parent 40e92cf2b9
commit 5de8b2bf7f
3 changed files with 124 additions and 22 deletions

View file

@ -641,3 +641,68 @@ export const importPrismaSnapshot = mutation({
} }
}, },
}) })
export const syncMachineCompanyReferences = mutation({
args: {
tenantId: v.optional(v.string()),
dryRun: v.optional(v.boolean()),
},
handler: async (ctx, { tenantId, dryRun }) => {
const effectiveDryRun = Boolean(dryRun)
const machines = tenantId && tenantId.trim().length > 0
? await ctx.db
.query("machines")
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
.collect()
: await ctx.db.query("machines").collect()
const slugCache = new Map<string, Id<"companies"> | null>()
const summary = {
total: machines.length,
updated: 0,
skippedMissingSlug: 0,
skippedMissingCompany: 0,
alreadyLinked: 0,
}
for (const machine of machines) {
const slug = machine.companySlug ?? null
if (!slug) {
summary.skippedMissingSlug += 1
continue
}
const cacheKey = `${machine.tenantId}::${slug}`
let companyId = slugCache.get(cacheKey)
if (companyId === undefined) {
const company = await ctx.db
.query("companies")
.withIndex("by_tenant_slug", (q) => q.eq("tenantId", machine.tenantId).eq("slug", slug))
.unique()
companyId = company?._id ?? null
slugCache.set(cacheKey, companyId)
}
if (!companyId) {
summary.skippedMissingCompany += 1
continue
}
if (machine.companyId === companyId) {
summary.alreadyLinked += 1
continue
}
if (!effectiveDryRun) {
await ctx.db.patch(machine._id, { companyId })
}
summary.updated += 1
}
return {
dryRun: effectiveDryRun,
...summary,
}
},
})

View file

@ -119,22 +119,59 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
const machinesByCompanyId = useMemo(() => { const machinesByCompanyId = useMemo(() => {
const map = new Map<string, MachineSummary[]>() const map = new Map<string, MachineSummary[]>()
;(machinesQuery ?? []).forEach((machine) => { ;(machinesQuery ?? []).forEach((machine) => {
if (!machine.companyId) return const keys = [
const list = map.get(machine.companyId) ?? [] machine.companyId ?? undefined,
list.push(machine) machine.companySlug ?? undefined,
map.set(machine.companyId, list) ].filter((key): key is string => Boolean(key))
if (keys.length === 0) {
return
}
keys.forEach((key) => {
const list = map.get(key)
if (list) {
list.push(machine)
} else {
map.set(key, [machine])
}
})
}) })
return map return map
}, [machinesQuery]) }, [machinesQuery])
const editingCompanyMachines = useMemo(() => { const getMachinesForCompany = useCallback(
if (!editingId) return [] (company: Company | null | undefined) => {
return machinesByCompanyId.get(editingId) ?? [] if (!company) return []
}, [machinesByCompanyId, editingId]) const keys = [company.id, company.slug].filter(Boolean)
const machinesDialogList = useMemo(() => { for (const key of keys) {
if (!machinesDialog) return [] const list = machinesByCompanyId.get(key)
return machinesByCompanyId.get(machinesDialog.companyId) ?? [] if (list && list.length > 0) {
}, [machinesByCompanyId, machinesDialog]) return list
}
}
return []
},
[machinesByCompanyId]
)
const editingCompany = useMemo(
() => (editingId ? companies.find((company) => company.id === editingId) ?? null : null),
[companies, editingId]
)
const editingCompanyMachines = useMemo(
() => getMachinesForCompany(editingCompany),
[getMachinesForCompany, editingCompany]
)
const machinesDialogCompany = useMemo(
() => (machinesDialog ? companies.find((company) => company.id === machinesDialog.companyId) ?? null : null),
[companies, machinesDialog]
)
const machinesDialogList = useMemo(
() => getMachinesForCompany(machinesDialogCompany),
[getMachinesForCompany, machinesDialogCompany]
)
const resetForm = () => setForm({}) const resetForm = () => setForm({})
@ -525,7 +562,7 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
<div className="space-y-4 rounded-xl border border-slate-200 bg-white p-4 shadow-sm"> <div className="space-y-4 rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
{hasCompanies ? ( {hasCompanies ? (
filteredCompanies.map((company) => { filteredCompanies.map((company) => {
const companyMachines = machinesByCompanyId.get(company.id) ?? [] const companyMachines = getMachinesForCompany(company)
const formattedPhone = formatPhoneDisplay(company.phone) const formattedPhone = formatPhoneDisplay(company.phone)
const alertInfo = lastAlerts[company.slug] ?? null const alertInfo = lastAlerts[company.slug] ?? null
const usagePct = alertInfo?.usagePct ?? 0 const usagePct = alertInfo?.usagePct ?? 0
@ -714,7 +751,7 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
? formatDistanceToNow(alertInfo.createdAt, { addSuffix: true, locale: ptBR }) ? formatDistanceToNow(alertInfo.createdAt, { addSuffix: true, locale: ptBR })
: null : null
const formattedPhone = formatPhoneDisplay(company.phone) const formattedPhone = formatPhoneDisplay(company.phone)
const companyMachines = machinesByCompanyId.get(company.id) ?? [] const companyMachines = getMachinesForCompany(company)
const machineCount = companyMachines.length const machineCount = companyMachines.length
return ( return (
<TableRow <TableRow

View file

@ -38,23 +38,23 @@ function TicketRow({ ticket, entering }: { ticket: Ticket; entering: boolean })
<Link <Link
href={`/tickets/${ticket.id}`} href={`/tickets/${ticket.id}`}
className={cn( className={cn(
"group block rounded-2xl border border-slate-200 bg-white/70 px-6 py-5 transition-all duration-300 hover:border-slate-300 hover:bg-white", "group relative block rounded-2xl border border-slate-200 bg-white/70 px-6 py-5 transition-all duration-300 hover:border-slate-300 hover:bg-white",
entering ? "recent-ticket-enter" : "" entering ? "recent-ticket-enter" : ""
)} )}
> >
<div className="absolute right-6 top-5 flex flex-col items-end gap-2 sm:flex-row sm:items-center">
<TicketStatusBadge status={ticket.status} className="h-8 px-3.5 text-sm" />
<TicketPriorityPill priority={ticket.priority} className="h-8 px-3.5 text-sm" />
</div>
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"> <div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="min-w-0 space-y-3"> <div className="min-w-0 space-y-3">
<div className="flex items-start justify-between gap-3"> <div className="flex items-start gap-3 pr-28 text-sm font-medium text-neutral-500 sm:pr-32">
<div className="flex flex-wrap items-center gap-2 text-sm font-medium text-neutral-500"> <div className="flex flex-wrap items-center gap-2">
<span className="text-xl font-bold text-neutral-900">#{ticket.reference}</span> <span className="text-xl font-bold text-neutral-900">#{ticket.reference}</span>
<span className="truncate text-neutral-500">{queueLabel}</span> <span className="truncate text-neutral-500">{queueLabel}</span>
</div> </div>
<div className="ml-auto flex items-start gap-2 text-right">
<TicketStatusBadge status={ticket.status} className="h-8 px-3.5 text-sm" />
<TicketPriorityPill priority={ticket.priority} className="h-8 px-3.5 text-sm" />
</div>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5 pr-12 sm:pr-20">
<span className="line-clamp-1 text-[20px] font-semibold text-neutral-900 transition-colors group-hover:text-neutral-700"> <span className="line-clamp-1 text-[20px] font-semibold text-neutral-900 transition-colors group-hover:text-neutral-700">
{ticket.subject} {ticket.subject}
</span> </span>