fix: align company data with machines
This commit is contained in:
parent
40e92cf2b9
commit
5de8b2bf7f
3 changed files with 124 additions and 22 deletions
|
|
@ -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,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
machine.companySlug ?? undefined,
|
||||||
|
].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)
|
list.push(machine)
|
||||||
map.set(machine.companyId, list)
|
} 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
|
||||||
|
|
|
||||||
|
|
@ -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="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
<div className="absolute right-6 top-5 flex flex-col items-end gap-2 sm:flex-row sm:items-center">
|
||||||
<div className="min-w-0 space-y-3">
|
|
||||||
<div className="flex items-start justify-between gap-3">
|
|
||||||
<div className="flex flex-wrap items-center gap-2 text-sm font-medium text-neutral-500">
|
|
||||||
<span className="text-xl font-bold text-neutral-900">#{ticket.reference}</span>
|
|
||||||
<span className="truncate text-neutral-500">{queueLabel}</span>
|
|
||||||
</div>
|
|
||||||
<div className="ml-auto flex items-start gap-2 text-right">
|
|
||||||
<TicketStatusBadge status={ticket.status} className="h-8 px-3.5 text-sm" />
|
<TicketStatusBadge status={ticket.status} className="h-8 px-3.5 text-sm" />
|
||||||
<TicketPriorityPill priority={ticket.priority} className="h-8 px-3.5 text-sm" />
|
<TicketPriorityPill priority={ticket.priority} className="h-8 px-3.5 text-sm" />
|
||||||
</div>
|
</div>
|
||||||
|
<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="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">
|
||||||
|
<span className="text-xl font-bold text-neutral-900">#{ticket.reference}</span>
|
||||||
|
<span className="truncate text-neutral-500">{queueLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
</div>
|
||||||
|
<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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue