From a651d56c8c15c3d5b2aa133df5d620a36072c181 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Tue, 16 Jun 2026 15:38:09 +0000 Subject: [PATCH] Refactor third parties frontend to page arborescence Mirror the risks refactor (c78a713): colocate routes.ts, split the detail layout query so each child route owns its Loader + Page, rename tabs/*Tab to resource folders with *Page, move dialogs into _components/, and extract ThirdPartyRow with its own fragment. Remove outlet context data passing and deprecated loaderFromQueryLoader. Delete the monolithic ThirdPartyGraph hook, colocating each GraphQL operation with its consumer: the create mutation in CreateThirdPartyDialog (now useMutation + useToast) and the third-party list queries in ThirdPartiesCell and ThirdPartiesMultiSelectField (now useQueryLoader + usePreloadedQuery). Signed-off-by: Sacha Al Himdani --- .../form/ThirdPartiesMultiSelectField.tsx | 74 +++-- .../src/components/table/ThirdPartiesCell.tsx | 35 ++- .../src/hooks/graph/ThirdPartyGraph.ts | 258 ----------------- .../third-parties/ThirdPartiesPage.tsx | 199 ++++++------- .../third-parties/ThirdPartiesPageLoader.tsx | 42 +++ ...ailPage.tsx => ThirdPartyDetailLayout.tsx} | 182 +++++++++--- .../ThirdPartyDetailLayoutLoader.tsx | 44 +++ .../AddChildThirdPartyDialog.tsx | 6 +- .../CommonThirdPartyCombobox.tsx | 2 +- .../CreateContactDialog.tsx | 43 ++- .../CreateRiskAssessmentDialog.tsx | 53 +++- .../CreateServiceDialog.tsx | 43 ++- .../CreateThirdPartyDialog.tsx | 59 +++- ...DeleteBusinessAssociateAgreementDialog.tsx | 55 +++- .../DeleteDataPrivacyAgreementDialog.tsx | 55 +++- .../EditBusinessAssociateAgreementDialog.tsx | 57 +++- .../EditContactDialog.tsx | 68 +++-- .../EditDataPrivacyAgreementDialog.tsx | 57 +++- .../EditServiceDialog.tsx | 68 +++-- .../PublishThirdPartyListDialog.tsx | 0 .../_components/ThirdPartyRow.tsx | 157 ++++++++++ ...UploadBusinessAssociateAgreementDialog.tsx | 64 +++-- .../UploadComplianceReportDialog.tsx | 64 +++-- .../UploadDataPrivacyAgreementDialog.tsx | 64 +++-- .../VettingDialog.tsx | 4 +- .../ThirdPartyCertificationsPage.tsx} | 56 ++-- .../ThirdPartyCertificationsPageLoader.tsx | 44 +++ .../compliance/ThirdPartyCompliancePage.tsx | 143 ++++++++++ .../ThirdPartyCompliancePageLoader.tsx | 44 +++ .../ThirdPartyComplianceReportRow.tsx | 153 ++++++++++ .../contacts/ThirdPartyContactsPage.tsx | 170 +++++++++++ .../contacts/ThirdPartyContactsPageLoader.tsx | 44 +++ .../_components/ThirdPartyContactRow.tsx | 164 +++++++++++ .../measures/ThirdPartyMeasuresPage.tsx | 72 ++--- .../measures/ThirdPartyMeasuresPageLoader.tsx | 44 +++ .../ThirdPartyOverviewPage.tsx} | 70 +++-- .../overview/ThirdPartyOverviewPageLoader.tsx | 44 +++ .../risks/ThirdPartyRiskAssessmentPage.tsx | 172 +++++++++++ .../ThirdPartyRiskAssessmentPageLoader.tsx | 44 +++ .../ThirdPartyRiskAssessmentRow.tsx | 96 +++++++ .../organizations/third-parties/routes.ts | 84 ++++++ .../services/ThirdPartyServicesPage.tsx | 167 +++++++++++ .../services/ThirdPartyServicesPageLoader.tsx | 44 +++ .../_components/ThirdPartyServiceRow.tsx | 144 ++++++++++ .../tabs/ThirdPartyComplianceTab.tsx | 229 --------------- .../tabs/ThirdPartyContactsTab.tsx | 269 ------------------ .../tabs/ThirdPartyRiskAssessmentTab.tsx | 226 --------------- .../tabs/ThirdPartyServicesTab.tsx | 237 --------------- .../ThirdPartyThirdPartiesPage.tsx | 2 +- apps/console/src/routes.tsx | 2 +- apps/console/src/routes/thirdPartyRoutes.ts | 117 -------- 51 files changed, 2851 insertions(+), 1783 deletions(-) delete mode 100644 apps/console/src/hooks/graph/ThirdPartyGraph.ts create mode 100644 apps/console/src/pages/organizations/third-parties/ThirdPartiesPageLoader.tsx rename apps/console/src/pages/organizations/third-parties/{ThirdPartyDetailPage.tsx => ThirdPartyDetailLayout.tsx} (58%) create mode 100644 apps/console/src/pages/organizations/third-parties/ThirdPartyDetailLayoutLoader.tsx rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/AddChildThirdPartyDialog.tsx (94%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/CommonThirdPartyCombobox.tsx (98%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/CreateContactDialog.tsx (79%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/CreateRiskAssessmentDialog.tsx (75%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/CreateServiceDialog.tsx (74%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/CreateThirdPartyDialog.tsx (67%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/DeleteBusinessAssociateAgreementDialog.tsx (63%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/DeleteDataPrivacyAgreementDialog.tsx (64%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/EditBusinessAssociateAgreementDialog.tsx (71%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/EditContactDialog.tsx (69%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/EditDataPrivacyAgreementDialog.tsx (71%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/EditServiceDialog.tsx (63%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/PublishThirdPartyListDialog.tsx (100%) create mode 100644 apps/console/src/pages/organizations/third-parties/_components/ThirdPartyRow.tsx rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/UploadBusinessAssociateAgreementDialog.tsx (75%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/UploadComplianceReportDialog.tsx (74%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/UploadDataPrivacyAgreementDialog.tsx (75%) rename apps/console/src/pages/organizations/third-parties/{dialogs => _components}/VettingDialog.tsx (97%) rename apps/console/src/pages/organizations/third-parties/{tabs/ThirdPartyCertificationsTab.tsx => certifications/ThirdPartyCertificationsPage.tsx} (81%) create mode 100644 apps/console/src/pages/organizations/third-parties/certifications/ThirdPartyCertificationsPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/compliance/ThirdPartyCompliancePage.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/compliance/ThirdPartyCompliancePageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/compliance/_components/ThirdPartyComplianceReportRow.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/contacts/ThirdPartyContactsPage.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/contacts/ThirdPartyContactsPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/contacts/_components/ThirdPartyContactRow.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/measures/ThirdPartyMeasuresPageLoader.tsx rename apps/console/src/pages/organizations/third-parties/{tabs/ThirdPartyOverviewTab.tsx => overview/ThirdPartyOverviewPage.tsx} (86%) create mode 100644 apps/console/src/pages/organizations/third-parties/overview/ThirdPartyOverviewPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/risks/ThirdPartyRiskAssessmentPage.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/risks/ThirdPartyRiskAssessmentPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/risks/_components/ThirdPartyRiskAssessmentRow.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/routes.ts create mode 100644 apps/console/src/pages/organizations/third-parties/services/ThirdPartyServicesPage.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/services/ThirdPartyServicesPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/third-parties/services/_components/ThirdPartyServiceRow.tsx delete mode 100644 apps/console/src/pages/organizations/third-parties/tabs/ThirdPartyComplianceTab.tsx delete mode 100644 apps/console/src/pages/organizations/third-parties/tabs/ThirdPartyContactsTab.tsx delete mode 100644 apps/console/src/pages/organizations/third-parties/tabs/ThirdPartyRiskAssessmentTab.tsx delete mode 100644 apps/console/src/pages/organizations/third-parties/tabs/ThirdPartyServicesTab.tsx delete mode 100644 apps/console/src/routes/thirdPartyRoutes.ts diff --git a/apps/console/src/components/form/ThirdPartiesMultiSelectField.tsx b/apps/console/src/components/form/ThirdPartiesMultiSelectField.tsx index 581d00f60..88260e2a4 100644 --- a/apps/console/src/components/form/ThirdPartiesMultiSelectField.tsx +++ b/apps/console/src/components/form/ThirdPartiesMultiSelectField.tsx @@ -15,16 +15,38 @@ import { faviconUrl } from "@probo/helpers"; import { useTranslate } from "@probo/i18n"; import { Avatar, Badge, Button, Field, IconCrossLargeX, Option, Select } from "@probo/ui"; -import { type ComponentProps, Suspense, useState } from "react"; +import { type ComponentProps, Suspense, useEffect, useState } from "react"; import { type Control, Controller, type FieldValues, type Path } from "react-hook-form"; +import { type PreloadedQuery, usePreloadedQuery, useQueryLoader } from "react-relay"; +import { graphql } from "relay-runtime"; -import { useThirdParties } from "#/hooks/graph/ThirdPartyGraph"; +import type { ThirdPartiesMultiSelectFieldQuery } from "#/__generated__/core/ThirdPartiesMultiSelectFieldQuery.graphql"; + +const thirdPartiesQuery = graphql` + query ThirdPartiesMultiSelectFieldQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + ... on Organization { + thirdParties( + first: 100 + orderBy: { direction: ASC, field: NAME } + ) { + edges { + node { + id + name + websiteUrl + } + } + } + } + } + } +`; type ThirdParty = { id: string; name: string; websiteUrl: string | null | undefined; - level?: number; }; type Props = { @@ -42,29 +64,47 @@ export function ThirdPartiesMultiSelectField) { + const [queryRef, loadQuery] + = useQueryLoader(thirdPartiesQuery); + + useEffect(() => { + loadQuery({ organizationId }, { fetchPolicy: "network-only" }); + }, [loadQuery, organizationId]); + + const loadingState = ( +