perf(connections): collapse pricing N+1 into a single bulk query

The /connections page was firing one listClinicPricing call per approved
counterpart inside Promise.all. That meant a lab with 10 clinics paid 10
sequential Appwrite roundtrips on every load, and worse, every time the
ClinicPricingDialog saved a row revalidatePath('/connections') ran the
whole fan-out again — saving a single discount felt like the request had
hung.

Replaced the per-peer query with listAllPricingForLab /
listAllPricingForClinic (single Query.equal on the side-specific column,
limit 500) and group the result into a Map client-side. One roundtrip
regardless of how many connections you have.

Also flipped the audit-log calls in setClinicPricingAction /
clearClinicPricingAction from 'await logAudit(...)' to 'void logAudit(...)'
— audit is best-effort by design and never blocks the user-facing
mutation; awaiting it doubled the perceived latency for nothing.
This commit is contained in:
kovakmedya
2026-05-21 22:45:58 +03:00
parent 90abb398fa
commit 48361792f0
3 changed files with 43 additions and 26 deletions
+15 -23
View File
@@ -1,7 +1,10 @@
import { redirect } from "next/navigation";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { listClinicPricing } from "@/lib/appwrite/clinic-pricing-queries";
import {
listAllPricingForClinic,
listAllPricingForLab,
} from "@/lib/appwrite/clinic-pricing-queries";
import {
listApprovedConnections,
listPendingInbound,
@@ -37,29 +40,18 @@ export default async function ConnectionsPage() {
listPendingOutbound(ctx.tenantId, ctx.user.id),
]);
// Lab side: load pricing rules per approved clinic so the dialog can
// surface existing rules. Clinic side leaves this empty — it's display-only
// for them (see ConnectionsTable).
// Single bulk query for pricing then group client-side. Avoids N+1 across
// approved counterparts (was the main reason the save dialog felt slow —
// every revalidatePath of /connections fanned out one query per peer).
const allPricing = isLab
? await listAllPricingForLab(ctx.tenantId)
: await listAllPricingForClinic(ctx.tenantId);
const pricingByCounterpart = new Map<string, ClinicPricing[]>();
if (isLab) {
const pricingLists = await Promise.all(
approved.map((c) =>
listClinicPricing(ctx.tenantId, c.clinicTenantId).then(
(rows) => [c.clinicTenantId, rows] as const,
),
),
);
for (const [id, rows] of pricingLists) pricingByCounterpart.set(id, rows);
} else {
// Clinic side: only its own pricing rules from each lab, read-only
const pricingLists = await Promise.all(
approved.map((c) =>
listClinicPricing(c.labTenantId, ctx.tenantId).then(
(rows) => [c.labTenantId, rows] as const,
),
),
);
for (const [id, rows] of pricingLists) pricingByCounterpart.set(id, rows);
for (const row of allPricing) {
const key = isLab ? row.clinicTenantId : row.labTenantId;
const bucket = pricingByCounterpart.get(key);
if (bucket) bucket.push(row);
else pricingByCounterpart.set(key, [row]);
}
const pricingObject: Record<string, ClinicPricing[]> = Object.fromEntries(
pricingByCounterpart,