Skip to content

Commit 9f84913

Browse files
committed
refactor(vendors): migrate vendor data fetching to server API and remove obsolete queries
1 parent a6902c6 commit 9f84913

19 files changed

Lines changed: 391 additions & 487 deletions

File tree

apps/app/src/app/(app)/[orgId]/vendors/(overview)/actions/get-vendors-action.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorDeleteCell.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@ import {
1010
AlertDialogTitle,
1111
} from '@comp/ui/alert-dialog';
1212
import { Button } from '@comp/ui/button';
13+
import type { Vendor } from '@/hooks/use-vendors';
1314
import { Trash2 } from 'lucide-react';
1415
import * as React from 'react';
1516
import { toast } from 'sonner';
1617
import { useSWRConfig } from 'swr';
17-
import type { GetVendorsResult } from '../data/queries';
18-
19-
type VendorRow = GetVendorsResult['data'][number];
2018

2119
interface VendorDeleteCellProps {
22-
vendor: VendorRow;
20+
vendor: Vendor;
2321
}
2422

2523
export const VendorDeleteCell: React.FC<VendorDeleteCellProps> = ({ vendor }) => {
@@ -38,7 +36,9 @@ export const VendorDeleteCell: React.FC<VendorDeleteCellProps> = ({ vendor }) =>
3836
toast.success(`Vendor "${vendor.name}" has been deleted.`);
3937
setIsRemoveAlertOpen(false);
4038
mutate(
41-
(key) => Array.isArray(key) && key[0] === 'vendors',
39+
(key) =>
40+
(Array.isArray(key) && key[0]?.includes('/v1/vendors')) ||
41+
(typeof key === 'string' && key.includes('/v1/vendors')),
4242
undefined,
4343
{ revalidate: true },
4444
);

apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorsTable.tsx

Lines changed: 45 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { OnboardingLoadingAnimation } from '@/components/onboarding-loading-animation';
44
import { useApi } from '@/hooks/use-api';
5+
import { useVendors, type Vendor } from '@/hooks/use-vendors';
56
import { VendorStatus } from '@/components/vendor-status';
67
import {
78
AlertDialog,
@@ -41,23 +42,27 @@ import {
4142
import { OverflowMenuVertical, Search, TrashCan } from '@trycompai/design-system/icons';
4243
import { ArrowDown, ArrowUp, ArrowUpDown, Loader2, UserIcon } from 'lucide-react';
4344
import { useRouter } from 'next/navigation';
44-
import { useCallback, useEffect, useMemo, useState } from 'react';
45+
import { useEffect, useMemo, useState } from 'react';
4546
import { toast } from 'sonner';
46-
import useSWR from 'swr';
47-
import { getVendorsAction, type GetVendorsActionInput } from '../actions/get-vendors-action';
48-
import type { GetAssigneesResult, GetVendorsResult } from '../data/queries';
49-
import type { GetVendorsSchema } from '../data/validations';
47+
import { useSWRConfig } from 'swr';
5048
import { useOnboardingStatus } from '../hooks/use-onboarding-status';
5149
import { VendorOnboardingProvider, useVendorOnboardingStatus } from './vendor-onboarding-context';
5250

53-
export type VendorRow = GetVendorsResult['data'][number] & {
51+
export type VendorRow = Vendor & {
5452
isPending?: boolean;
5553
isAssessing?: boolean;
5654
};
5755

58-
const callGetVendorsAction = getVendorsAction as unknown as (
59-
input: GetVendorsActionInput,
60-
) => Promise<GetVendorsResult>;
56+
type AssigneeMember = {
57+
id: string;
58+
role: string;
59+
user: {
60+
id: string;
61+
name: string | null;
62+
email: string;
63+
image: string | null;
64+
};
65+
};
6166

6267
const ACTIVE_STATUSES: Array<'pending' | 'processing' | 'created' | 'assessing'> = [
6368
'pending',
@@ -78,15 +83,13 @@ const CATEGORY_MAP: Record<string, string> = {
7883
};
7984

8085
interface VendorsTableProps {
81-
vendors: GetVendorsResult['data'];
82-
pageCount: number;
83-
assignees: GetAssigneesResult;
86+
vendors: Vendor[];
87+
assignees: AssigneeMember[];
8488
onboardingRunId?: string | null;
85-
searchParams: GetVendorsSchema;
8689
orgId: string;
8790
}
8891

89-
function VendorNameCell({ vendor, orgId }: { vendor: VendorRow; orgId: string }) {
92+
function VendorNameCell({ vendor }: { vendor: VendorRow }) {
9093
const onboardingStatus = useVendorOnboardingStatus();
9194
const status = onboardingStatus[vendor.id];
9295
const isPending = vendor.isPending || status === 'pending' || status === 'processing';
@@ -139,13 +142,13 @@ function VendorStatusCell({ vendor }: { vendor: VendorRow }) {
139142

140143
export function VendorsTable({
141144
vendors: initialVendors,
142-
pageCount: initialPageCount,
143145
assignees,
144146
onboardingRunId,
145147
orgId,
146148
}: VendorsTableProps) {
147149
const router = useRouter();
148150
const api = useApi();
151+
const { mutate: globalMutate } = useSWRConfig();
149152
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
150153
const [vendorToDelete, setVendorToDelete] = useState<VendorRow | null>(null);
151154
const [isDeleting, setIsDeleting] = useState(false);
@@ -165,44 +168,16 @@ export function VendorsTable({
165168
'vendors',
166169
);
167170

168-
// Build search params for API
169-
const currentSearchParams = useMemo<GetVendorsSchema>(() => {
170-
return {
171-
page,
172-
perPage,
173-
name: '',
174-
status: null,
175-
department: null,
176-
assigneeId: null,
177-
sort: [{ id: sort.id, desc: sort.desc }],
178-
filters: [],
179-
joinOperator: 'and',
180-
};
181-
}, [page, perPage, sort]);
182-
183-
// Create stable SWR key
184-
const swrKey = useMemo(() => {
185-
if (!orgId) return null;
186-
const key = JSON.stringify(currentSearchParams);
187-
return ['vendors', orgId, key] as const;
188-
}, [orgId, currentSearchParams]);
189-
190-
// Fetcher function for SWR
191-
const fetcher = useCallback(async () => {
192-
if (!orgId) return { data: [], pageCount: 0 };
193-
return await callGetVendorsAction({ orgId, searchParams: currentSearchParams });
194-
}, [orgId, currentSearchParams]);
195-
196-
// Use SWR to fetch vendors with polling for real-time updates
197-
const { data: vendorsData } = useSWR(swrKey, fetcher, {
198-
fallbackData: { data: initialVendors, pageCount: initialPageCount },
171+
// Use SWR hook for vendors with polling
172+
const { data: vendorsResponse } = useVendors({
173+
initialData: initialVendors,
199174
refreshInterval: isActive ? 1000 : 5000,
200-
revalidateOnFocus: false,
201-
revalidateOnReconnect: true,
202-
keepPreviousData: true,
203175
});
204176

205-
const vendors = vendorsData?.data || initialVendors;
177+
const vendors = useMemo(() => {
178+
const data = vendorsResponse?.data?.data;
179+
return Array.isArray(data) ? data : initialVendors;
180+
}, [vendorsResponse, initialVendors]);
206181

207182
// Check if all vendors are done assessing
208183
const allVendorsDoneAssessing = useMemo(() => {
@@ -276,8 +251,8 @@ export function VendorsTable({
276251
organizationId: orgId,
277252
assigneeId: null,
278253
assignee: null,
279-
createdAt: new Date(),
280-
updatedAt: new Date(),
254+
createdAt: new Date().toISOString(),
255+
updatedAt: new Date().toISOString(),
281256
isPending: true,
282257
}));
283258

@@ -298,8 +273,8 @@ export function VendorsTable({
298273
organizationId: orgId,
299274
assigneeId: null,
300275
assignee: null,
301-
createdAt: new Date(),
302-
updatedAt: new Date(),
276+
createdAt: new Date().toISOString(),
277+
updatedAt: new Date().toISOString(),
303278
isPending: true,
304279
}));
305280

@@ -330,24 +305,21 @@ export function VendorsTable({
330305
const comparison = (aValue as string).localeCompare(bValue as string);
331306
return sort.desc ? -comparison : comparison;
332307
}
333-
const comparison = new Date(aValue as Date).getTime() - new Date(bValue as Date).getTime();
308+
const comparison =
309+
new Date(aValue as string).getTime() - new Date(bValue as string).getTime();
334310
return sort.desc ? -comparison : comparison;
335311
});
336312

337313
return result;
338314
}, [mergedVendors, searchQuery, sort]);
339315

340316
// Calculate pageCount from filtered data and paginate
341-
// When searching locally, calculate pageCount from filtered data
342-
// When not searching, use server's pageCount (server handles pagination)
343-
const filteredPageCount = searchQuery
344-
? Math.max(1, Math.ceil(filteredAndSortedVendors.length / perPage))
345-
: Math.max(1, vendorsData?.pageCount ?? initialPageCount);
346-
347-
// When searching locally, slice the data for client-side pagination
348-
// When not searching, server returns the correct page, but slice to enforce perPage
349-
// (avoids extra rows from onboarding pending/temp vendors)
350-
const startIndex = searchQuery ? (page - 1) * perPage : 0;
317+
const filteredPageCount = Math.max(
318+
1,
319+
Math.ceil(filteredAndSortedVendors.length / perPage),
320+
);
321+
322+
const startIndex = (page - 1) * perPage;
351323
const paginatedVendors = filteredAndSortedVendors.slice(startIndex, startIndex + perPage);
352324

353325
// Keep page in bounds when pageCount changes
@@ -426,6 +398,13 @@ export function VendorsTable({
426398
toast.success('Vendor deleted successfully');
427399
setDeleteDialogOpen(false);
428400
setVendorToDelete(null);
401+
globalMutate(
402+
(key) =>
403+
(Array.isArray(key) && key[0]?.includes('/v1/vendors')) ||
404+
(typeof key === 'string' && key.includes('/v1/vendors')),
405+
undefined,
406+
{ revalidate: true },
407+
);
429408
} else {
430409
toast.error('Failed to delete vendor');
431410
}
@@ -555,7 +534,7 @@ export function VendorsTable({
555534
data-state={blocked ? 'disabled' : undefined}
556535
>
557536
<TableCell>
558-
<VendorNameCell vendor={vendor} orgId={orgId} />
537+
<VendorNameCell vendor={vendor} />
559538
</TableCell>
560539
<TableCell>
561540
<VendorStatusCell vendor={vendor} />

apps/app/src/app/(app)/[orgId]/vendors/(overview)/data/queries.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

apps/app/src/app/(app)/[orgId]/vendors/(overview)/data/validations.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)