Add vendor snapshots
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -89,11 +89,11 @@ export const useDeleteVendor = (
|
||||
export const vendorConnectionKey = "VendorsPage_vendors";
|
||||
|
||||
export const vendorsQuery = graphql`
|
||||
query VendorGraphListQuery($organizationId: ID!) {
|
||||
query VendorGraphListQuery($organizationId: ID!, $snapshotId: ID) {
|
||||
node(id: $organizationId) {
|
||||
... on Organization {
|
||||
id
|
||||
...VendorGraphPaginatedFragment
|
||||
...VendorGraphPaginatedFragment @arguments(snapshotId: $snapshotId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,7 @@ export const paginatedVendorsFragment = graphql`
|
||||
after: { type: "CursorKey", defaultValue: null }
|
||||
before: { type: "CursorKey", defaultValue: null }
|
||||
last: { type: "Int", defaultValue: null }
|
||||
snapshotId: { type: "ID", defaultValue: null }
|
||||
) {
|
||||
vendors(
|
||||
first: $first
|
||||
@@ -115,11 +116,13 @@ export const paginatedVendorsFragment = graphql`
|
||||
last: $last
|
||||
before: $before
|
||||
orderBy: $order
|
||||
) @connection(key: "VendorsListQuery_vendors") {
|
||||
filter: { snapshotId: $snapshotId }
|
||||
) @connection(key: "VendorsListQuery_vendors", filters: ["filter"]) {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
snapshotId
|
||||
name
|
||||
websiteUrl
|
||||
updatedAt
|
||||
@@ -148,6 +151,7 @@ export const vendorNodeQuery = graphql`
|
||||
node(id: $vendorId) {
|
||||
... on Vendor {
|
||||
id
|
||||
snapshotId
|
||||
name
|
||||
websiteUrl
|
||||
...useVendorFormFragment
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<b748739bef19b5c39b7c352f35332865>>
|
||||
* @generated SignedSource<<8b1614888dac560deff871232f91995d>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -12,6 +12,7 @@ import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type VendorGraphListQuery$variables = {
|
||||
organizationId: string;
|
||||
snapshotId?: string | null | undefined;
|
||||
};
|
||||
export type VendorGraphListQuery$data = {
|
||||
readonly node: {
|
||||
@@ -30,6 +31,11 @@ var v0 = [
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
},
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "snapshotId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
@@ -46,14 +52,26 @@ v2 = {
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
v3 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "snapshotId",
|
||||
"variableName": "snapshotId"
|
||||
}
|
||||
],
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
v5 = [
|
||||
{
|
||||
"fields": (v3/*: any*/),
|
||||
"kind": "ObjectValue",
|
||||
"name": "filter"
|
||||
},
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
@@ -80,7 +98,7 @@ return {
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"args": (v3/*: any*/),
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorGraphPaginatedFragment"
|
||||
}
|
||||
@@ -109,14 +127,14 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: any*/),
|
||||
"args": (v5/*: any*/),
|
||||
"concreteType": "VendorConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "vendors",
|
||||
@@ -139,6 +157,13 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "snapshotId",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -236,7 +261,7 @@ return {
|
||||
],
|
||||
"storageKey": "riskAssessments(first:1,orderBy:{\"direction\":\"DESC\",\"field\":\"ASSESSED_AT\"})"
|
||||
},
|
||||
(v3/*: any*/)
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -302,13 +327,13 @@ return {
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:50)"
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: any*/),
|
||||
"args": (v5/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
"filter"
|
||||
],
|
||||
"handle": "connection",
|
||||
"key": "VendorsListQuery_vendors",
|
||||
@@ -325,16 +350,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "e2eade63dcf3d7ea071463a2e5993293",
|
||||
"cacheID": "4b19f6f4be47c38d8b68601ec95315a6",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorGraphListQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query VendorGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n ...VendorGraphPaginatedFragment\n }\n id\n }\n}\n\nfragment VendorGraphPaginatedFragment on Organization {\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
|
||||
"text": "query VendorGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n ...VendorGraphPaginatedFragment_3iomuz\n }\n id\n }\n}\n\nfragment VendorGraphPaginatedFragment_3iomuz on Organization {\n vendors(first: 50, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3bb12a4d7a49090154bb56951e69f4aa";
|
||||
(node as any).hash = "3034ee812f89d0596f3f6d3094c1dfc8";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<46bd8a339fe6fd05256df0b1f806e0bd>>
|
||||
* @generated SignedSource<<c3a7e9de9c7f072bbca3ad552e5860cd>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -18,6 +18,7 @@ export type VendorGraphNodeQuery$data = {
|
||||
readonly node: {
|
||||
readonly id?: string;
|
||||
readonly name?: string;
|
||||
readonly snapshotId?: string | null | undefined;
|
||||
readonly websiteUrl?: string | null | undefined;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment" | "VendorContactsTabFragment" | "VendorOverviewTabBusinessAssociateAgreementFragment" | "VendorOverviewTabDataPrivacyAgreementFragment" | "VendorRiskAssessmentTabFragment" | "VendorServicesTabFragment" | "useVendorFormFragment">;
|
||||
};
|
||||
@@ -63,20 +64,27 @@ v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"name": "snapshotId",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = [
|
||||
v7 = [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
v7 = {
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
@@ -89,80 +97,80 @@ v7 = {
|
||||
"kind": "LinkedField",
|
||||
"name": "people",
|
||||
"plural": false,
|
||||
"selections": (v6/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"selections": (v7/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = [
|
||||
v11 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
v11 = {
|
||||
v12 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "validUntil",
|
||||
"storageKey": null
|
||||
},
|
||||
v12 = {
|
||||
v13 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fileUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v13 = {
|
||||
v14 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v14 = {
|
||||
v15 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v15 = {
|
||||
v16 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
},
|
||||
v16 = {
|
||||
v17 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasPreviousPage",
|
||||
"storageKey": null
|
||||
},
|
||||
v17 = {
|
||||
v18 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "startCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v18 = {
|
||||
v19 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
@@ -170,14 +178,14 @@ v18 = {
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v14/*: any*/),
|
||||
(v15/*: any*/),
|
||||
(v16/*: any*/),
|
||||
(v17/*: any*/)
|
||||
(v17/*: any*/),
|
||||
(v18/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v19 = {
|
||||
v20 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
@@ -189,31 +197,31 @@ v19 = {
|
||||
}
|
||||
]
|
||||
},
|
||||
v20 = [
|
||||
v21 = [
|
||||
"orderBy"
|
||||
],
|
||||
v21 = {
|
||||
v22 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
v22 = {
|
||||
v23 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v23 = {
|
||||
v24 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "updatedAt",
|
||||
"storageKey": null
|
||||
},
|
||||
v24 = [
|
||||
v25 = [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
@@ -222,7 +230,7 @@ v24 = [
|
||||
"name": "fileName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -230,8 +238,8 @@ v24 = [
|
||||
"name": "validFrom",
|
||||
"storageKey": null
|
||||
},
|
||||
(v11/*: any*/),
|
||||
(v22/*: any*/)
|
||||
(v12/*: any*/),
|
||||
(v23/*: any*/)
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
@@ -257,6 +265,7 @@ return {
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
@@ -315,7 +324,7 @@ return {
|
||||
"name": "user",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v7/*: any*/)
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
@@ -343,14 +352,15 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v10/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -428,7 +438,7 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "businessOwner",
|
||||
"plural": false,
|
||||
"selections": (v6/*: any*/),
|
||||
"selections": (v7/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -438,12 +448,12 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "securityOwner",
|
||||
"plural": false,
|
||||
"selections": (v6/*: any*/),
|
||||
"selections": (v7/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "VendorComplianceReportConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "complianceReports",
|
||||
@@ -473,7 +483,7 @@ return {
|
||||
"name": "reportDate",
|
||||
"storageKey": null
|
||||
},
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -481,7 +491,7 @@ return {
|
||||
"name": "reportName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -489,23 +499,23 @@ return {
|
||||
"name": "fileSize",
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/)
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v13/*: any*/)
|
||||
(v14/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v18/*: any*/),
|
||||
(v19/*: any*/)
|
||||
(v19/*: any*/),
|
||||
(v20/*: any*/)
|
||||
],
|
||||
"storageKey": "complianceReports(first:50)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"filters": (v20/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"filters": (v21/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "VendorComplianceTabFragment_complianceReports",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -513,7 +523,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "VendorContactConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "contacts",
|
||||
@@ -536,7 +546,7 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v21/*: any*/),
|
||||
(v22/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -558,25 +568,25 @@ return {
|
||||
"name": "role",
|
||||
"storageKey": null
|
||||
},
|
||||
(v22/*: any*/),
|
||||
(v23/*: any*/),
|
||||
(v8/*: any*/)
|
||||
(v24/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v13/*: any*/)
|
||||
(v14/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v18/*: any*/),
|
||||
(v19/*: any*/)
|
||||
(v19/*: any*/),
|
||||
(v20/*: any*/)
|
||||
],
|
||||
"storageKey": "contacts(first:50)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"filters": (v20/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"filters": (v21/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "VendorContactsTabFragment_contacts",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -584,7 +594,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "VendorServiceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "services",
|
||||
@@ -607,27 +617,27 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v22/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v23/*: any*/),
|
||||
(v8/*: any*/)
|
||||
(v24/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v13/*: any*/)
|
||||
(v14/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v18/*: any*/),
|
||||
(v19/*: any*/)
|
||||
(v19/*: any*/),
|
||||
(v20/*: any*/)
|
||||
],
|
||||
"storageKey": "services(first:50)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"filters": (v20/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"filters": (v21/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "VendorServicesTabFragment_services",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -635,7 +645,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"concreteType": "VendorRiskAssessmentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "riskAssessments",
|
||||
@@ -674,7 +684,7 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v21/*: any*/)
|
||||
(v22/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -706,11 +716,11 @@ return {
|
||||
"name": "notes",
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/)
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v13/*: any*/)
|
||||
(v14/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -722,21 +732,21 @@ return {
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v15/*: any*/),
|
||||
(v14/*: any*/),
|
||||
(v16/*: any*/),
|
||||
(v17/*: any*/)
|
||||
(v15/*: any*/),
|
||||
(v17/*: any*/),
|
||||
(v18/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v19/*: any*/)
|
||||
(v20/*: any*/)
|
||||
],
|
||||
"storageKey": "riskAssessments(first:50)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"filters": (v20/*: any*/),
|
||||
"args": (v11/*: any*/),
|
||||
"filters": (v21/*: any*/),
|
||||
"handle": "connection",
|
||||
"key": "VendorRiskAssessmentTabFragment_riskAssessments",
|
||||
"kind": "LinkedHandle",
|
||||
@@ -749,7 +759,7 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "businessAssociateAgreement",
|
||||
"plural": false,
|
||||
"selections": (v24/*: any*/),
|
||||
"selections": (v25/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -759,7 +769,7 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "dataPrivacyAgreement",
|
||||
"plural": false,
|
||||
"selections": (v24/*: any*/),
|
||||
"selections": (v25/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
@@ -785,7 +795,7 @@ return {
|
||||
"name": "user",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v7/*: any*/),
|
||||
(v8/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -797,16 +807,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "7e1a7455861aea37eee2d799d8385586",
|
||||
"cacheID": "f0b506b57e84959deb0b1e3fa0481897",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorGraphNodeQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n $organizationId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorContactsTabFragment\n ...VendorServicesTabFragment\n ...VendorRiskAssessmentTabFragment\n ...VendorOverviewTabBusinessAssociateAgreementFragment\n ...VendorOverviewTabDataPrivacyAgreementFragment\n }\n id\n }\n viewer {\n user {\n people(organizationId: $organizationId) {\n id\n }\n id\n }\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n\nfragment VendorContactsTabFragment on Vendor {\n contacts(first: 50) {\n edges {\n node {\n id\n ...VendorContactsTabFragment_contact\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorContactsTabFragment_contact on VendorContact {\n id\n fullName\n email\n phone\n role\n createdAt\n updatedAt\n}\n\nfragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {\n businessAssociateAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n }\n}\n\nfragment VendorOverviewTabDataPrivacyAgreementFragment on Vendor {\n dataPrivacyAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n }\n}\n\nfragment VendorRiskAssessmentTabFragment on Vendor {\n id\n riskAssessments(first: 50) {\n edges {\n node {\n id\n ...VendorRiskAssessmentTabFragment_assessment\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment {\n id\n assessedAt\n assessedBy {\n id\n fullName\n }\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n\nfragment VendorServicesTabFragment on Vendor {\n services(first: 50) {\n edges {\n node {\n id\n ...VendorServicesTabFragment_service\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorServicesTabFragment_service on VendorService {\n id\n name\n description\n createdAt\n updatedAt\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n $organizationId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n snapshotId\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorContactsTabFragment\n ...VendorServicesTabFragment\n ...VendorRiskAssessmentTabFragment\n ...VendorOverviewTabBusinessAssociateAgreementFragment\n ...VendorOverviewTabDataPrivacyAgreementFragment\n }\n id\n }\n viewer {\n user {\n people(organizationId: $organizationId) {\n id\n }\n id\n }\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n\nfragment VendorContactsTabFragment on Vendor {\n contacts(first: 50) {\n edges {\n node {\n id\n ...VendorContactsTabFragment_contact\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorContactsTabFragment_contact on VendorContact {\n id\n fullName\n email\n phone\n role\n createdAt\n updatedAt\n}\n\nfragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {\n businessAssociateAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n }\n}\n\nfragment VendorOverviewTabDataPrivacyAgreementFragment on Vendor {\n dataPrivacyAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n }\n}\n\nfragment VendorRiskAssessmentTabFragment on Vendor {\n id\n riskAssessments(first: 50) {\n edges {\n node {\n id\n ...VendorRiskAssessmentTabFragment_assessment\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment {\n id\n assessedAt\n assessedBy {\n id\n fullName\n }\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n\nfragment VendorServicesTabFragment on Vendor {\n services(first: 50) {\n edges {\n node {\n id\n ...VendorServicesTabFragment_service\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorServicesTabFragment_service on VendorService {\n id\n name\n description\n createdAt\n updatedAt\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "1eac0bd4bc5eccf51ea024d27f348e14";
|
||||
(node as any).hash = "4af64d92bd85441554a2c6ad8d2ccd77";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<93638f7eeb1634fc19aaaa95cae0c7b8>>
|
||||
* @generated SignedSource<<b6975a3a740bdf7e2c9914fc64770638>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -31,6 +31,7 @@ export type VendorGraphPaginatedFragment$data = {
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly snapshotId: string | null | undefined;
|
||||
readonly updatedAt: any;
|
||||
readonly websiteUrl: string | null | undefined;
|
||||
};
|
||||
@@ -82,6 +83,11 @@ return {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "order"
|
||||
},
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "snapshotId"
|
||||
}
|
||||
],
|
||||
"kind": "Fragment",
|
||||
@@ -122,9 +128,15 @@ return {
|
||||
"alias": "vendors",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "orderBy",
|
||||
"variableName": "order"
|
||||
"fields": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "snapshotId",
|
||||
"variableName": "snapshotId"
|
||||
}
|
||||
],
|
||||
"kind": "ObjectValue",
|
||||
"name": "filter"
|
||||
}
|
||||
],
|
||||
"concreteType": "VendorConnection",
|
||||
@@ -149,6 +161,13 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "snapshotId",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -327,6 +346,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "1d2bf7c29e77d60d0a955a62b98c85ab";
|
||||
(node as any).hash = "578772ed7c83ae174d43b0be95a02fd2";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<bfe74e681960002105d6995d91548400>>
|
||||
* @generated SignedSource<<f98b10bd4f5fb03b55b46e2554480590>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -23,6 +23,7 @@ export type VendorsListQuery$variables = {
|
||||
id: string;
|
||||
last?: number | null | undefined;
|
||||
order?: VendorOrder | null | undefined;
|
||||
snapshotId?: string | null | undefined;
|
||||
};
|
||||
export type VendorsListQuery$data = {
|
||||
readonly node: {
|
||||
@@ -65,52 +66,69 @@ v5 = {
|
||||
"kind": "LocalArgument",
|
||||
"name": "order"
|
||||
},
|
||||
v6 = [
|
||||
v6 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "snapshotId"
|
||||
},
|
||||
v7 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "id"
|
||||
}
|
||||
],
|
||||
v7 = {
|
||||
v8 = {
|
||||
"kind": "Variable",
|
||||
"name": "after",
|
||||
"variableName": "after"
|
||||
},
|
||||
v8 = {
|
||||
v9 = {
|
||||
"kind": "Variable",
|
||||
"name": "before",
|
||||
"variableName": "before"
|
||||
},
|
||||
v9 = {
|
||||
v10 = {
|
||||
"kind": "Variable",
|
||||
"name": "first",
|
||||
"variableName": "first"
|
||||
},
|
||||
v10 = {
|
||||
v11 = {
|
||||
"kind": "Variable",
|
||||
"name": "last",
|
||||
"variableName": "last"
|
||||
},
|
||||
v11 = {
|
||||
v12 = {
|
||||
"kind": "Variable",
|
||||
"name": "snapshotId",
|
||||
"variableName": "snapshotId"
|
||||
},
|
||||
v13 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v12 = {
|
||||
v14 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v13 = [
|
||||
(v7/*: any*/),
|
||||
v15 = [
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"fields": [
|
||||
(v12/*: any*/)
|
||||
],
|
||||
"kind": "ObjectValue",
|
||||
"name": "filter"
|
||||
},
|
||||
(v10/*: any*/),
|
||||
(v11/*: any*/),
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "orderBy",
|
||||
@@ -125,7 +143,8 @@ return {
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/)
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
@@ -133,7 +152,7 @@ return {
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
@@ -141,15 +160,16 @@ return {
|
||||
"selections": [
|
||||
{
|
||||
"args": [
|
||||
(v7/*: any*/),
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v11/*: any*/),
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "order",
|
||||
"variableName": "order"
|
||||
}
|
||||
},
|
||||
(v12/*: any*/)
|
||||
],
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorGraphPaginatedFragment"
|
||||
@@ -169,6 +189,7 @@ return {
|
||||
(v2/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
@@ -176,20 +197,20 @@ return {
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
(v14/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v13/*: any*/),
|
||||
"args": (v15/*: any*/),
|
||||
"concreteType": "VendorConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "vendors",
|
||||
@@ -211,7 +232,14 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v12/*: any*/),
|
||||
(v14/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "snapshotId",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -271,7 +299,7 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v12/*: any*/),
|
||||
(v14/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -309,7 +337,7 @@ return {
|
||||
],
|
||||
"storageKey": "riskAssessments(first:1,orderBy:{\"direction\":\"DESC\",\"field\":\"ASSESSED_AT\"})"
|
||||
},
|
||||
(v11/*: any*/)
|
||||
(v13/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
@@ -379,9 +407,9 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v13/*: any*/),
|
||||
"args": (v15/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
"filter"
|
||||
],
|
||||
"handle": "connection",
|
||||
"key": "VendorsListQuery_vendors",
|
||||
@@ -398,16 +426,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "70fb3d49e78996ae4cefbaa8f5605c2c",
|
||||
"cacheID": "6570f4601f6dea5b7fe738d851a7f1ba",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorsListQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query VendorsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorGraphPaginatedFragment_16fISc\n id\n }\n}\n\nfragment VendorGraphPaginatedFragment_16fISc on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
|
||||
"text": "query VendorsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorOrder = null\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorGraphPaginatedFragment_25MC8O\n id\n }\n}\n\nfragment VendorGraphPaginatedFragment_25MC8O on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "1d2bf7c29e77d60d0a955a62b98c85ab";
|
||||
(node as any).hash = "578772ed7c83ae174d43b0be95a02fd2";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -192,6 +192,7 @@ export default function AssetDetailsPage(props: Props) {
|
||||
organizationId={organizationId}
|
||||
control={control}
|
||||
name="vendorIds"
|
||||
selectedVendors={vendors}
|
||||
label={__("Vendors")}
|
||||
disabled={isSnapshotMode}
|
||||
/>
|
||||
|
||||
@@ -23,11 +23,12 @@ import {
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Outlet } from "react-router";
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import { Outlet, useParams } from "react-router";
|
||||
import { faviconUrl, validateSnapshotConsistency } from "@probo/helpers";
|
||||
import { ImportAssessmentDialog } from "./dialogs/ImportAssessmentDialog";
|
||||
import { complianceReportsFragment } from "./tabs/VendorComplianceTab";
|
||||
import type { VendorComplianceTabFragment$key } from "./tabs/__generated__/VendorComplianceTabFragment.graphql";
|
||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<VendorGraphNodeQuery>;
|
||||
@@ -38,6 +39,14 @@ export default function VendorDetailPage(props: Props) {
|
||||
const vendor = data.node;
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
if (!vendor) {
|
||||
return <div>{__("Vendor not found")}</div>;
|
||||
}
|
||||
|
||||
validateSnapshotConsistency(vendor, snapshotId);
|
||||
const deleteVendor = useDeleteVendor(
|
||||
vendor,
|
||||
ConnectionHandler.getConnectionID(organizationId, vendorConnectionKey)
|
||||
@@ -48,13 +57,22 @@ export default function VendorDetailPage(props: Props) {
|
||||
vendor as VendorComplianceTabFragment$key
|
||||
).complianceReports.edges.length;
|
||||
|
||||
const vendorsUrl = isSnapshotMode && snapshotId
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/vendors`
|
||||
: `/organizations/${organizationId}/vendors`;
|
||||
|
||||
const baseVendorUrl = isSnapshotMode && snapshotId
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/vendors/${vendor.id}`
|
||||
: `/organizations/${organizationId}/vendors/${vendor.id}`;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{snapshotId && <SnapshotBanner snapshotId={snapshotId} />}
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: __("Vendors"),
|
||||
to: `/organizations/${organizationId}/vendors`,
|
||||
to: vendorsUrl,
|
||||
},
|
||||
{
|
||||
label: vendor.name ?? "",
|
||||
@@ -72,54 +90,44 @@ export default function VendorDetailPage(props: Props) {
|
||||
)}
|
||||
<div className="text-2xl">{vendor.name}</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<ImportAssessmentDialog vendorId={vendor.id!}>
|
||||
<Button icon={IconPageTextLine} variant="secondary">
|
||||
{__("Assessment From Website")}
|
||||
</Button>
|
||||
</ImportAssessmentDialog>
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={deleteVendor}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
{!isSnapshotMode && (
|
||||
<div className="flex gap-2 items-center">
|
||||
<ImportAssessmentDialog vendorId={vendor.id!}>
|
||||
<Button icon={IconPageTextLine} variant="secondary">
|
||||
{__("Assessment From Website")}
|
||||
</Button>
|
||||
</ImportAssessmentDialog>
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={deleteVendor}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Tabs>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/overview`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/overview`}>
|
||||
{__("Overview")}
|
||||
</TabLink>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/certifications`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/certifications`}>
|
||||
{__("Certifications")}
|
||||
</TabLink>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/compliance`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/compliance`}>
|
||||
{__("Compliance reports")}
|
||||
{reportsCount > 0 && <TabBadge>{reportsCount}</TabBadge>}
|
||||
</TabLink>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/risks`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/risks`}>
|
||||
{__("Risk Assessment")}
|
||||
</TabLink>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/contacts`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/contacts`}>
|
||||
{__("Contacts")}
|
||||
</TabLink>
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/vendors/${vendor.id}/services`}
|
||||
>
|
||||
<TabLink to={`${baseVendorUrl}/services`}>
|
||||
{__("Services")}
|
||||
</TabLink>
|
||||
</Tabs>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { useParams } from "react-router";
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import type { NodeOf } from "/types";
|
||||
import { CreateVendorDialog } from "./dialogs/CreateVendorDialog";
|
||||
@@ -35,6 +36,7 @@ import type {
|
||||
VendorGraphPaginatedFragment$key,
|
||||
} from "/hooks/graph/__generated__/VendorGraphPaginatedFragment.graphql";
|
||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||
|
||||
type Vendor = NodeOf<VendorGraphPaginatedFragment$data["vendors"]>;
|
||||
|
||||
@@ -45,6 +47,8 @@ type Props = {
|
||||
export default function VendorsPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
const data = usePreloadedQuery(vendorsQuery, props.queryRef);
|
||||
const pagination = usePaginationFragment(
|
||||
@@ -59,18 +63,21 @@ export default function VendorsPage(props: Props) {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{snapshotId && <SnapshotBanner snapshotId={snapshotId} />}
|
||||
<PageHeader
|
||||
title={__("Vendors")}
|
||||
description={__(
|
||||
"Vendors are third-party services that your company uses. Add them to keep track of their risk and compliance status."
|
||||
)}
|
||||
>
|
||||
<CreateVendorDialog
|
||||
connection={connectionId}
|
||||
organizationId={organizationId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||
</CreateVendorDialog>
|
||||
{!isSnapshotMode && (
|
||||
<CreateVendorDialog
|
||||
connection={connectionId}
|
||||
organizationId={organizationId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||
</CreateVendorDialog>
|
||||
)}
|
||||
</PageHeader>
|
||||
<SortableTable {...pagination}>
|
||||
<Thead>
|
||||
@@ -106,14 +113,20 @@ function VendorRow({
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
const { __, dateFormat } = useTranslate();
|
||||
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
||||
|
||||
const deleteVendor = useDeleteVendor(vendor, connectionId);
|
||||
|
||||
const vendorUrl = isSnapshotMode && snapshotId
|
||||
? `/organizations/${organizationId}/snapshots/${snapshotId}/vendors/${vendor.id}/overview`
|
||||
: `/organizations/${organizationId}/vendors/${vendor.id}/overview`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr to={`/organizations/${organizationId}/vendors/${vendor.id}/overview`}>
|
||||
<Tr to={vendorUrl}>
|
||||
<Td>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar name={vendor.name} src={faviconUrl(vendor.websiteUrl)} />
|
||||
@@ -136,15 +149,17 @@ function VendorRow({
|
||||
<RiskBadge level={latestAssessment?.businessImpact ?? "NONE"} />
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
onClick={deleteVendor}
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
{!isSnapshotMode && (
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
onClick={deleteVendor}
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useOutletContext, useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { VendorComplianceTabFragment$key } from "./__generated__/VendorComplianceTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -98,6 +98,8 @@ export default function VendorComplianceTab() {
|
||||
const connectionId = data.complianceReports.__id;
|
||||
const reports = data.complianceReports.edges.map((edge) => edge.node);
|
||||
const { __ } = useTranslate();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
usePageTitle(vendor.name + " - " + __("Compliance reports"));
|
||||
|
||||
@@ -125,15 +127,17 @@ export default function VendorComplianceTab() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Dropzone
|
||||
description={__("Only PDF files up to 10MB are allowed")}
|
||||
isUploading={isMutating}
|
||||
onDrop={handleDrop}
|
||||
accept={{
|
||||
"application/pdf": [".pdf"],
|
||||
}}
|
||||
maxSize={10}
|
||||
/>
|
||||
{!isSnapshotMode && (
|
||||
<Dropzone
|
||||
description={__("Only PDF files up to 10MB are allowed")}
|
||||
isUploading={isMutating}
|
||||
onDrop={handleDrop}
|
||||
accept={{
|
||||
"application/pdf": [".pdf"],
|
||||
}}
|
||||
maxSize={10}
|
||||
/>
|
||||
)}
|
||||
<SortableTable refetch={refetch}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
@@ -141,7 +145,7 @@ export default function VendorComplianceTab() {
|
||||
<SortableTh field="REPORT_DATE">{__("Report date")}</SortableTh>
|
||||
<Th>{__("Valid until")}</Th>
|
||||
<Th>{__("File size")}</Th>
|
||||
<Th>{__("Actions")}</Th>
|
||||
{!isSnapshotMode && <Th>{__("Actions")}</Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -150,6 +154,7 @@ export default function VendorComplianceTab() {
|
||||
key={report.id}
|
||||
reportKey={report}
|
||||
connectionId={connectionId}
|
||||
isSnapshotMode={isSnapshotMode}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
@@ -161,6 +166,7 @@ export default function VendorComplianceTab() {
|
||||
type ReportRowProps = {
|
||||
reportKey: VendorComplianceTabFragment_report$key;
|
||||
connectionId: string;
|
||||
isSnapshotMode: boolean;
|
||||
};
|
||||
|
||||
function ReportRow(props: ReportRowProps) {
|
||||
@@ -204,17 +210,19 @@ function ReportRow(props: ReportRowProps) {
|
||||
<Td>{dateFormat(report.reportDate)}</Td>
|
||||
<Td>{dateFormat(report.validUntil)}</Td>
|
||||
<Td>{fileSize(__, report.fileSize)}</Td>
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
{!props.isSnapshotMode && (
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useOutletContext, useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { VendorContactsTabFragment$key } from "./__generated__/VendorContactsTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -89,6 +89,8 @@ export default function VendorContactsTab() {
|
||||
const connectionId = data.contacts.__id;
|
||||
const contacts = data.contacts.edges.map((edge) => edge.node);
|
||||
const { __ } = useTranslate();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
const [editingContact, setEditingContact] = useState<{
|
||||
id: string;
|
||||
fullName?: string | null;
|
||||
@@ -105,12 +107,14 @@ export default function VendorContactsTab() {
|
||||
title={__("Contacts")}
|
||||
description={__("Manage vendor contacts and their information.")}
|
||||
>
|
||||
<CreateContactDialog
|
||||
vendorId={vendor.id}
|
||||
connectionId={connectionId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add contact")}</Button>
|
||||
</CreateContactDialog>
|
||||
{!isSnapshotMode && (
|
||||
<CreateContactDialog
|
||||
vendorId={vendor.id}
|
||||
connectionId={connectionId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add contact")}</Button>
|
||||
</CreateContactDialog>
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
<SortableTable refetch={refetch}>
|
||||
@@ -120,7 +124,7 @@ export default function VendorContactsTab() {
|
||||
<SortableTh field="EMAIL">{__("Email")}</SortableTh>
|
||||
<Th>{__("Phone")}</Th>
|
||||
<Th>{__("Role")}</Th>
|
||||
<Th>{__("Actions")}</Th>
|
||||
{!isSnapshotMode && <Th>{__("Actions")}</Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -130,12 +134,13 @@ export default function VendorContactsTab() {
|
||||
contactKey={contact}
|
||||
connectionId={connectionId}
|
||||
onEdit={setEditingContact}
|
||||
isSnapshotMode={isSnapshotMode}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</SortableTable>
|
||||
|
||||
{editingContact && (
|
||||
{editingContact && !isSnapshotMode && (
|
||||
<EditContactDialog
|
||||
contactId={editingContact.id}
|
||||
contact={editingContact}
|
||||
@@ -156,6 +161,7 @@ type ContactRowProps = {
|
||||
phone?: string | null;
|
||||
role?: string | null;
|
||||
}) => void;
|
||||
isSnapshotMode: boolean;
|
||||
};
|
||||
|
||||
function ContactRow(props: ContactRowProps) {
|
||||
@@ -220,29 +226,31 @@ function ContactRow(props: ContactRowProps) {
|
||||
)}
|
||||
</Td>
|
||||
<Td>{contact.role || __("—")}</Td>
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => props.onEdit({
|
||||
id: contact.id,
|
||||
fullName: contact.fullName,
|
||||
email: contact.email,
|
||||
phone: contact.phone,
|
||||
role: contact.role,
|
||||
})}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
{!props.isSnapshotMode && (
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => props.onEdit({
|
||||
id: contact.id,
|
||||
fullName: contact.fullName,
|
||||
email: contact.email,
|
||||
phone: contact.phone,
|
||||
role: contact.role,
|
||||
})}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useVendorForm } from "/hooks/forms/useVendorForm";
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useOutletContext, useParams } from "react-router";
|
||||
import { Button, Card, Field, Input, IconPlusLarge, IconTrashCan, IconPencil } from "@probo/ui";
|
||||
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
@@ -55,6 +55,8 @@ export default function VendorOverviewTab() {
|
||||
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -98,7 +100,7 @@ export default function VendorOverviewTab() {
|
||||
usePageTitle(vendor.name + " - " + __("Overview"));
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-12">
|
||||
<form onSubmit={isSnapshotMode ? undefined : handleSubmit} className="space-y-12">
|
||||
{/* Vendor Details */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-base font-medium">{__("Vendor details")}</h2>
|
||||
@@ -108,30 +110,35 @@ export default function VendorOverviewTab() {
|
||||
label={__("Name")}
|
||||
type="text"
|
||||
error={errors.name?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
<Field
|
||||
{...register("description")}
|
||||
label={__("Description")}
|
||||
type="textarea"
|
||||
error={errors.description?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
<Field
|
||||
{...register("legalName")}
|
||||
label={__("Legal name")}
|
||||
type="text"
|
||||
error={errors.legalName?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
<Field
|
||||
{...register("headquarterAddress")}
|
||||
label={__("Headquarter address")}
|
||||
type="textarea"
|
||||
error={errors.headquarterAddress?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
<Field
|
||||
{...register("websiteUrl")}
|
||||
label={__("Website URL")}
|
||||
type="text"
|
||||
error={errors.websiteUrl?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -146,6 +153,7 @@ export default function VendorOverviewTab() {
|
||||
name="businessOwnerId"
|
||||
label={__("Business owner")}
|
||||
error={errors.businessOwnerId?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
<PeopleSelectField
|
||||
organizationId={organizationId}
|
||||
@@ -153,6 +161,7 @@ export default function VendorOverviewTab() {
|
||||
name="securityOwnerId"
|
||||
label={__("Security owner")}
|
||||
error={errors.securityOwnerId?.message}
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -180,6 +189,7 @@ export default function VendorOverviewTab() {
|
||||
type="text"
|
||||
placeholder="https://..."
|
||||
variant="ghost"
|
||||
disabled={isSubmitting || isSnapshotMode}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -220,33 +230,39 @@ export default function VendorOverviewTab() {
|
||||
>
|
||||
{__("Download PDF")}
|
||||
</Button>
|
||||
<EditBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
agreement={{
|
||||
validFrom: businessAssociateAgreement.validFrom,
|
||||
validUntil: businessAssociateAgreement.validUntil,
|
||||
}}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconPencil} />
|
||||
</EditBusinessAssociateAgreementDialog>
|
||||
<DeleteBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
fileName={businessAssociateAgreement.fileName}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconTrashCan} />
|
||||
</DeleteBusinessAssociateAgreementDialog>
|
||||
{!isSnapshotMode && (
|
||||
<>
|
||||
<EditBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
agreement={{
|
||||
validFrom: businessAssociateAgreement.validFrom,
|
||||
validUntil: businessAssociateAgreement.validUntil,
|
||||
}}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconPencil} />
|
||||
</EditBusinessAssociateAgreementDialog>
|
||||
<DeleteBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
fileName={businessAssociateAgreement.fileName}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconTrashCan} />
|
||||
</DeleteBusinessAssociateAgreementDialog>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<UploadBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="secondary" icon={IconPlusLarge}>
|
||||
{__("Upload")}
|
||||
</Button>
|
||||
</UploadBusinessAssociateAgreementDialog>
|
||||
!isSnapshotMode && (
|
||||
<UploadBusinessAssociateAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="secondary" icon={IconPlusLarge}>
|
||||
{__("Upload")}
|
||||
</Button>
|
||||
</UploadBusinessAssociateAgreementDialog>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -281,26 +297,30 @@ export default function VendorOverviewTab() {
|
||||
>
|
||||
{__("Download PDF")}
|
||||
</Button>
|
||||
<EditDataPrivacyAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
agreement={{
|
||||
validFrom: dataPrivacyAgreement.validFrom,
|
||||
validUntil: dataPrivacyAgreement.validUntil,
|
||||
}}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconPencil} />
|
||||
</EditDataPrivacyAgreementDialog>
|
||||
<DeleteDataPrivacyAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
fileName={dataPrivacyAgreement.fileName}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconTrashCan} />
|
||||
</DeleteDataPrivacyAgreementDialog>
|
||||
{!isSnapshotMode && (
|
||||
<>
|
||||
<EditDataPrivacyAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
agreement={{
|
||||
validFrom: dataPrivacyAgreement.validFrom,
|
||||
validUntil: dataPrivacyAgreement.validUntil,
|
||||
}}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconPencil} />
|
||||
</EditDataPrivacyAgreementDialog>
|
||||
<DeleteDataPrivacyAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
fileName={dataPrivacyAgreement.fileName}
|
||||
onSuccess={() => window.location.reload()}
|
||||
>
|
||||
<Button variant="quaternary" icon={IconTrashCan} />
|
||||
</DeleteDataPrivacyAgreementDialog>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
!isSnapshotMode && (
|
||||
<UploadDataPrivacyAgreementDialog
|
||||
vendorId={vendor.id}
|
||||
onSuccess={() => window.location.reload()}
|
||||
@@ -309,7 +329,7 @@ export default function VendorOverviewTab() {
|
||||
{__("Upload")}
|
||||
</Button>
|
||||
</UploadDataPrivacyAgreementDialog>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,11 +337,13 @@ export default function VendorOverviewTab() {
|
||||
</div>
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{__("Update vendor")}
|
||||
</Button>
|
||||
</div>
|
||||
{!isSnapshotMode && (
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{__("Update vendor")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useOutletContext, useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { VendorRiskAssessmentTabFragment$key } from "./__generated__/VendorRiskAssessmentTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -82,6 +82,8 @@ export default function VendorRiskAssessmentTab() {
|
||||
);
|
||||
const assessments = data.riskAssessments.edges.map((edge) => edge.node);
|
||||
const { __ } = useTranslate();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
const [expanded, setExpanded] = useState<string | null>(null);
|
||||
|
||||
usePageTitle(vendor.name + " - " + __("Risk Assessments"));
|
||||
@@ -90,15 +92,17 @@ export default function VendorRiskAssessmentTab() {
|
||||
return (
|
||||
<div className="text-center text-sm py-6 text-txt-secondary flex flex-col items-center gap-2">
|
||||
{__("No risk assessments found")}
|
||||
<CreateRiskAssessmentDialog
|
||||
vendorId={vendor.id}
|
||||
connection={data.riskAssessments.__id}
|
||||
peopleId={peopleId}
|
||||
>
|
||||
<Button icon={IconPlusLarge} variant="secondary">
|
||||
{__("Add Risk Assessment")}
|
||||
</Button>
|
||||
</CreateRiskAssessmentDialog>
|
||||
{!isSnapshotMode && (
|
||||
<CreateRiskAssessmentDialog
|
||||
vendorId={vendor.id}
|
||||
connection={data.riskAssessments.__id}
|
||||
peopleId={peopleId}
|
||||
>
|
||||
<Button icon={IconPlusLarge} variant="secondary">
|
||||
{__("Add Risk Assessment")}
|
||||
</Button>
|
||||
</CreateRiskAssessmentDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,15 +121,17 @@ export default function VendorRiskAssessmentTab() {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<CreateRiskAssessmentDialog
|
||||
vendorId={vendor.id}
|
||||
connection={data.riskAssessments.__id}
|
||||
peopleId={peopleId}
|
||||
>
|
||||
<TrButton colspan={5} onClick={() => {}}>
|
||||
{__("Add Risk Assessment")}
|
||||
</TrButton>
|
||||
</CreateRiskAssessmentDialog>
|
||||
{!isSnapshotMode && (
|
||||
<CreateRiskAssessmentDialog
|
||||
vendorId={vendor.id}
|
||||
connection={data.riskAssessments.__id}
|
||||
peopleId={peopleId}
|
||||
>
|
||||
<TrButton colspan={5} onClick={() => {}}>
|
||||
{__("Add Risk Assessment")}
|
||||
</TrButton>
|
||||
</CreateRiskAssessmentDialog>
|
||||
)}
|
||||
{assessments.map((assessment) => (
|
||||
<AssessmentRow
|
||||
key={assessment.id}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useOutletContext } from "react-router";
|
||||
import { useOutletContext, useParams } from "react-router";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { VendorServicesTabFragment$key } from "./__generated__/VendorServicesTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
@@ -87,6 +87,8 @@ export default function VendorServicesTab() {
|
||||
const connectionId = data.services.__id;
|
||||
const services = data.services.edges.map((edge) => edge.node);
|
||||
const { __ } = useTranslate();
|
||||
const { snapshotId } = useParams<{ snapshotId?: string }>();
|
||||
const isSnapshotMode = Boolean(snapshotId);
|
||||
const [editingService, setEditingService] = useState<{
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -101,12 +103,14 @@ export default function VendorServicesTab() {
|
||||
title={__("Services")}
|
||||
description={__("Manage services provided by this vendor.")}
|
||||
>
|
||||
<CreateServiceDialog
|
||||
vendorId={vendor.id}
|
||||
connectionId={connectionId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add service")}</Button>
|
||||
</CreateServiceDialog>
|
||||
{!isSnapshotMode && (
|
||||
<CreateServiceDialog
|
||||
vendorId={vendor.id}
|
||||
connectionId={connectionId}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add service")}</Button>
|
||||
</CreateServiceDialog>
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
<SortableTable refetch={refetch}>
|
||||
@@ -114,7 +118,7 @@ export default function VendorServicesTab() {
|
||||
<Tr>
|
||||
<SortableTh field="NAME">{__("Name")}</SortableTh>
|
||||
<Th>{__("Description")}</Th>
|
||||
<Th>{__("Actions")}</Th>
|
||||
{!isSnapshotMode && <Th>{__("Actions")}</Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -124,12 +128,13 @@ export default function VendorServicesTab() {
|
||||
serviceKey={service}
|
||||
connectionId={connectionId}
|
||||
onEdit={setEditingService}
|
||||
isSnapshotMode={isSnapshotMode}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</SortableTable>
|
||||
|
||||
{editingService && (
|
||||
{editingService && !isSnapshotMode && (
|
||||
<EditServiceDialog
|
||||
serviceId={editingService.id}
|
||||
service={editingService}
|
||||
@@ -148,6 +153,7 @@ type ServiceRowProps = {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
}) => void;
|
||||
isSnapshotMode: boolean;
|
||||
};
|
||||
|
||||
function ServiceRow(props: ServiceRowProps) {
|
||||
@@ -188,27 +194,29 @@ function ServiceRow(props: ServiceRowProps) {
|
||||
<Tr>
|
||||
<Td>{service.name}</Td>
|
||||
<Td>{service.description || __("—")}</Td>
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => props.onEdit({
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
description: service.description,
|
||||
})}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
{!props.isSnapshotMode && (
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => props.onEdit({
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
description: service.description,
|
||||
})}
|
||||
>
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,20 @@ export const vendorRoutes = [
|
||||
path: "vendors",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ organizationId }) =>
|
||||
loadQuery(relayEnvironment, vendorsQuery, { organizationId }),
|
||||
loadQuery(relayEnvironment, vendorsQuery, {
|
||||
organizationId,
|
||||
snapshotId: null
|
||||
}),
|
||||
Component: lazy(() => import("/pages/organizations/vendors/VendorsPage")),
|
||||
},
|
||||
{
|
||||
path: "snapshots/:snapshotId/vendors",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ organizationId, snapshotId }) =>
|
||||
loadQuery(relayEnvironment, vendorsQuery, {
|
||||
organizationId,
|
||||
snapshotId
|
||||
}),
|
||||
Component: lazy(() => import("/pages/organizations/vendors/VendorsPage")),
|
||||
},
|
||||
{
|
||||
@@ -79,4 +92,69 @@ export const vendorRoutes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "snapshots/:snapshotId/vendors/:vendorId",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ vendorId, organizationId }) =>
|
||||
loadQuery(relayEnvironment, vendorNodeQuery, {
|
||||
vendorId,
|
||||
organizationId,
|
||||
}),
|
||||
Component: lazy(
|
||||
() => import("../pages/organizations/vendors/VendorDetailPage")
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: "overview",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() => import("../pages/organizations/vendors/tabs/VendorOverviewTab")
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "certifications",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() =>
|
||||
import(
|
||||
"../pages/organizations/vendors/tabs/VendorCertificationsTab"
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "compliance",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() =>
|
||||
import("../pages/organizations/vendors/tabs/VendorComplianceTab")
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "risks",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() =>
|
||||
import(
|
||||
"../pages/organizations/vendors/tabs/VendorRiskAssessmentTab"
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "contacts",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() =>
|
||||
import("../pages/organizations/vendors/tabs/VendorContactsTab")
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "services",
|
||||
fallback: LinkCardSkeleton,
|
||||
Component: lazy(
|
||||
() =>
|
||||
import("../pages/organizations/vendors/tabs/VendorServicesTab")
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
Reference in New Issue
Block a user