From 7de21d83693b322c4414fa2ce26ee52e8a45f360 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 16 Jan 2026 21:32:54 -0500 Subject: [PATCH] refactor(vendors): improve vendor deduplication strategy in VendorsTable --- .../(overview)/components/VendorsTable.tsx | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx index dcadbd67f..e96dd6887 100644 --- a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx +++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx @@ -245,8 +245,12 @@ export function VendorsTable({ }, [vendors, itemsInfo, itemStatuses, orgId, isActive, onboardingRunId]); const dedupedVendors = useMemo(() => { + // SAFE deduplication strategy: + // 1. Show ALL real DB vendors (no deduplication) - user data must not be hidden + // 2. Hide placeholders if a real vendor with same normalized name exists + // 3. Deduplicate placeholders against each other (show only one per name) + // Normalize vendor name for deduplication - strips parenthetical suffixes - // e.g., "Fanta (cool)" and "Fanta" are treated as the same vendor const normalizeVendorName = (name: string): string => { return name .toLowerCase() @@ -254,50 +258,34 @@ export function VendorsTable({ .trim(); }; - // Rank vendors for deduplication - higher rank wins - // Rank 3: assessed (completed) - // Rank 2: actively being assessed (via isAssessing flag OR metadata status) - // Rank 1: pending placeholder - // Rank 0: not assessed and not processing - const getRank = (vendor: VendorRow) => { - if (vendor.status === 'assessed') return 3; - // Check both isAssessing flag and metadata status for active assessment - const metadataStatus = itemStatuses[vendor.id]; - if (vendor.isAssessing || metadataStatus === 'assessing' || metadataStatus === 'processing') { - return 2; - } - if (vendor.isPending) return 1; - return 0; - }; + // Separate real DB vendors from placeholders + const realVendors = mergedVendors.filter((v) => !v.isPending); + const placeholders = mergedVendors.filter((v) => v.isPending); - const map = new Map(); - mergedVendors.forEach((vendor) => { - const nameKey = normalizeVendorName(vendor.name); - const existing = map.get(nameKey); - if (!existing) { - map.set(nameKey, vendor); - return; - } + // Build a set of normalized names from real vendors + const realVendorNames = new Set(realVendors.map((v) => normalizeVendorName(v.name))); - const currentRank = getRank(vendor); - const existingRank = getRank(existing); + // Deduplicate placeholders: keep only one per name, and only if no real vendor exists + const placeholderMap = new Map(); + placeholders.forEach((placeholder) => { + const nameKey = normalizeVendorName(placeholder.name); - if (currentRank > existingRank) { - map.set(nameKey, vendor); + // Skip if a real vendor with this name already exists + if (realVendorNames.has(nameKey)) { return; } - if (currentRank === existingRank) { - const existingUpdatedAt = new Date(existing.updatedAt).getTime(); - const currentUpdatedAt = new Date(vendor.updatedAt).getTime(); - if (currentUpdatedAt > existingUpdatedAt) { - map.set(nameKey, vendor); - } + // Keep the first placeholder for each name (or replace if needed) + const existing = placeholderMap.get(nameKey); + if (!existing) { + placeholderMap.set(nameKey, placeholder); } + // If multiple placeholders with same name, keep the first one (no ranking needed) }); - return Array.from(map.values()); - }, [mergedVendors, itemStatuses]); + // Return all real vendors + deduplicated placeholders + return [...realVendors, ...Array.from(placeholderMap.values())]; + }, [mergedVendors]); const columns = useMemo[]>(() => getColumns(orgId), [orgId]);