Update vendor compliance report UI

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-03-10 14:51:32 +01:00
parent 40e71489d8
commit 66a7d6c3e8
10 changed files with 379 additions and 383 deletions

View File

@@ -1,237 +0,0 @@
/**
* @generated SignedSource<<2f95db3fc1f72db6d38bed52fb1e89bd>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type ProfileState = "ACTIVE" | "INACTIVE";
export type TrustCenterAccessState = "ACTIVE" | "INACTIVE";
export type TrustCenterDocumentAccessStatus = "GRANTED" | "REJECTED" | "REQUESTED" | "REVOKED";
export type UpdateTrustCenterAccessInput = {
documents?: ReadonlyArray<TrustCenterDocumentAccessInput> | null | undefined;
id: string;
name?: string | null | undefined;
reports?: ReadonlyArray<TrustCenterDocumentAccessInput> | null | undefined;
state?: TrustCenterAccessState | null | undefined;
trustCenterFiles?: ReadonlyArray<TrustCenterDocumentAccessInput> | null | undefined;
};
export type TrustCenterDocumentAccessInput = {
id: string;
status: TrustCenterDocumentAccessStatus;
};
export type CompliancePageAccessListItemUpdateMutation$variables = {
input: UpdateTrustCenterAccessInput;
};
export type CompliancePageAccessListItemUpdateMutation$data = {
readonly updateTrustCenterAccess: {
readonly trustCenterAccess: {
readonly activeCount: number;
readonly createdAt: string;
readonly id: string;
readonly pendingRequestCount: number;
readonly profile: {
readonly emailAddress: string;
readonly fullName: string;
readonly state: ProfileState;
};
readonly updatedAt: string;
};
};
};
export type CompliancePageAccessListItemUpdateMutation = {
response: CompliancePageAccessListItemUpdateMutation$data;
variables: CompliancePageAccessListItemUpdateMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageAccessListItemUpdateMutation",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "UpdateTrustCenterAccessPayload",
"kind": "LinkedField",
"name": "updateTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Profile",
"kind": "LinkedField",
"name": "profile",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageAccessListItemUpdateMutation",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "UpdateTrustCenterAccessPayload",
"kind": "LinkedField",
"name": "updateTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Profile",
"kind": "LinkedField",
"name": "profile",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "f8d08c05c3a5c935c68ca7ef509cf376",
"id": null,
"metadata": {},
"name": "CompliancePageAccessListItemUpdateMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageAccessListItemUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n createdAt\n updatedAt\n pendingRequestCount\n activeCount\n profile {\n fullName\n emailAddress\n state\n id\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "2912ca422f9edc3806a7e1ca6cb21028";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<8f9e37a76c35d572a81006be1c54b685>>
* @generated SignedSource<<1fe3ef3e802ded7a9cea3f34400b708b>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -275,6 +275,13 @@ return {
"name": "size",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "downloadUrl",
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
@@ -368,12 +375,12 @@ return {
]
},
"params": {
"cacheID": "0301f89298e5a1d39beac8f822095c6d",
"cacheID": "dd0036455bf80553156a719888535131",
"id": null,
"metadata": {},
"name": "ComplianceReportListQuery",
"operationKind": "query",
"text": "query ComplianceReportListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorComplianceReportOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorComplianceTabFragment_16fISc\n id\n }\n}\n\nfragment VendorComplianceTabFragment_16fISc on Vendor {\n complianceReports(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n}\n"
"text": "query ComplianceReportListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorComplianceReportOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorComplianceTabFragment_16fISc\n id\n }\n}\n\nfragment VendorComplianceTabFragment_16fISc on Vendor {\n complianceReports(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n downloadUrl\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<24418ddf18ae4992b58f744247037b46>>
* @generated SignedSource<<9a5547b22e159fc1957a20e8d2da18ac>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -410,6 +410,13 @@ return {
"name": "size",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "downloadUrl",
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
@@ -555,12 +562,12 @@ return {
]
},
"params": {
"cacheID": "b5d6ce081b43c94793be27d4fc87e39d",
"cacheID": "978d7d03ee8e39843f87574eb6cb64bf",
"id": null,
"metadata": {},
"name": "ImportAssessmentDialogMutation",
"operationKind": "mutation",
"text": "mutation ImportAssessmentDialogMutation(\n $input: AssessVendorInput!\n) {\n assessVendor(input: $input) {\n vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorRiskAssessmentTabFragment\n }\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 createdAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n category\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n countries\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
"text": "mutation ImportAssessmentDialogMutation(\n $input: AssessVendorInput!\n) {\n assessVendor(input: $input) {\n vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorRiskAssessmentTabFragment\n }\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n downloadUrl\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 createdAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n category\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n countries\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7234ce37ee1c29a4b4546b9f9de471c8>>
* @generated SignedSource<<1a8f618e333bfd588ba06578d908bb6b>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type UploadVendorComplianceReportInput = {
file: any;
reportDate: string;
@@ -17,23 +16,32 @@ export type UploadVendorComplianceReportInput = {
validUntil?: string | null | undefined;
vendorId: string;
};
export type VendorComplianceTabUploadReportMutation$variables = {
export type UploadComplianceReportDialogMutation$variables = {
connections: ReadonlyArray<string>;
input: UploadVendorComplianceReportInput;
};
export type VendorComplianceTabUploadReportMutation$data = {
export type UploadComplianceReportDialogMutation$data = {
readonly uploadVendorComplianceReport: {
readonly vendorComplianceReportEdge: {
readonly node: {
readonly canDelete: boolean;
readonly file: {
readonly downloadUrl: string;
readonly fileName: string;
readonly mimeType: string;
readonly size: number;
} | null | undefined;
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment_report">;
readonly reportDate: string;
readonly reportName: string;
readonly validUntil: string | null | undefined;
};
};
};
};
export type VendorComplianceTabUploadReportMutation = {
response: VendorComplianceTabUploadReportMutation$data;
variables: VendorComplianceTabUploadReportMutation$variables;
export type UploadComplianceReportDialogMutation = {
response: UploadComplianceReportDialogMutation$data;
variables: UploadComplianceReportDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -60,6 +68,68 @@ v3 = {
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportName",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportDate",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "validUntil",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileName",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "mimeType",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "size",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "downloadUrl",
"storageKey": null
},
v11 = {
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:vendor-compliance-report:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:vendor-compliance-report:delete\")"
};
return {
"fragment": {
@@ -69,7 +139,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "VendorComplianceTabUploadReportMutation",
"name": "UploadComplianceReportDialogMutation",
"selections": [
{
"alias": null,
@@ -96,11 +166,25 @@ return {
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "FragmentSpread",
"name": "VendorComplianceTabFragment_report"
}
"concreteType": "File",
"kind": "LinkedField",
"name": "file",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/)
],
"storageKey": null
},
(v11/*: any*/)
],
"storageKey": null
}
@@ -121,7 +205,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "VendorComplianceTabUploadReportMutation",
"name": "UploadComplianceReportDialogMutation",
"selections": [
{
"alias": null,
@@ -148,27 +232,9 @@ return {
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "validUntil",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportName",
"storageKey": null
},
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
@@ -177,44 +243,15 @@ return {
"name": "file",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "mimeType",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "size",
"storageKey": null
},
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:vendor-compliance-report:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:vendor-compliance-report:delete\")"
}
(v11/*: any*/)
],
"storageKey": null
}
@@ -243,16 +280,16 @@ return {
]
},
"params": {
"cacheID": "404f46e6eabb0fbe406f06ca861414fe",
"cacheID": "b48652c18d3b6acde303491746764697",
"id": null,
"metadata": {},
"name": "VendorComplianceTabUploadReportMutation",
"name": "UploadComplianceReportDialogMutation",
"operationKind": "mutation",
"text": "mutation VendorComplianceTabUploadReportMutation(\n $input: UploadVendorComplianceReportInput!\n) {\n uploadVendorComplianceReport(input: $input) {\n vendorComplianceReportEdge {\n node {\n id\n ...VendorComplianceTabFragment_report\n }\n }\n }\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n file {\n fileName\n mimeType\n size\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n}\n"
"text": "mutation UploadComplianceReportDialogMutation(\n $input: UploadVendorComplianceReportInput!\n) {\n uploadVendorComplianceReport(input: $input) {\n vendorComplianceReportEdge {\n node {\n id\n reportName\n reportDate\n validUntil\n file {\n fileName\n mimeType\n size\n downloadUrl\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "4ed863a250b0686340056f4f77eb8ab8";
(node as any).hash = "8311149c4e46ed2f01cbd19d34bc3a38";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<ecb2f1b1e2f7614af9a86484fa5940bf>>
* @generated SignedSource<<9c8e97bf0858bdac532273c8a3620731>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,6 +13,7 @@ import { FragmentRefs } from "relay-runtime";
export type VendorComplianceTabFragment_report$data = {
readonly canDelete: boolean;
readonly file: {
readonly downloadUrl: string;
readonly fileName: string;
readonly mimeType: string;
readonly size: number;
@@ -90,6 +91,13 @@ const node: ReaderFragment = {
"kind": "ScalarField",
"name": "size",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "downloadUrl",
"storageKey": null
}
],
"storageKey": null
@@ -112,6 +120,6 @@ const node: ReaderFragment = {
"abstractKey": null
};
(node as any).hash = "7676d7b6379940339458e259707c222e";
(node as any).hash = "9d29871990edb54a36915fab64eaea3a";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<94e31e9e1e26ee4a548dc90b4f13b9ca>>
* @generated SignedSource<<40b7d19556f7d3d0a1e9afe937273625>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -630,6 +630,13 @@ return {
"name": "size",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "downloadUrl",
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
@@ -1023,12 +1030,12 @@ return {
]
},
"params": {
"cacheID": "2e729f8d534ac8b2c3ed86175ef62da7",
"cacheID": "2479b0f9eeb44a3a540cb155212eff24",
"id": null,
"metadata": {},
"name": "VendorGraphNodeQuery",
"operationKind": "query",
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n id\n ... on Vendor {\n snapshotId\n name\n websiteUrl\n canAssess: permission(action: \"core:vendor:assess\")\n canUpdate: permission(action: \"core:vendor:update\")\n canDelete: permission(action: \"core:vendor:delete\")\n canUploadComplianceReport: permission(action: \"core:vendor-compliance-report:upload\")\n canCreateRiskAssessment: permission(action: \"core:vendor-risk-assessment:create\")\n canCreateContact: permission(action: \"core:vendor-contact:create\")\n canCreateService: permission(action: \"core:vendor-service:create\")\n canUploadBAA: permission(action: \"core:vendor-business-associate-agreement:upload\")\n canUploadDPA: permission(action: \"core:vendor-data-privacy-agreement:upload\")\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorContactsTabFragment\n ...VendorServicesTabFragment\n ...VendorRiskAssessmentTabFragment\n ...VendorOverviewTabBusinessAssociateAgreementFragment\n ...VendorOverviewTabDataPrivacyAgreementFragment\n }\n }\n viewer {\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n}\n\nfragment VendorContactsTabFragment on Vendor {\n contacts(first: 50) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:vendor-contact:update\")\n canDelete: permission(action: \"core:vendor-contact:delete\")\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 canUpdate: permission(action: \"core:vendor-contact:update\")\n canDelete: permission(action: \"core:vendor-contact:delete\")\n}\n\nfragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {\n businessAssociateAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n canUpdate: permission(action: \"core:vendor-business-associate-agreement:update\")\n canDelete: permission(action: \"core:vendor-business-associate-agreement:delete\")\n }\n}\n\nfragment VendorOverviewTabDataPrivacyAgreementFragment on Vendor {\n dataPrivacyAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n canUpdate: permission(action: \"core:vendor-data-privacy-agreement:update\")\n canDelete: permission(action: \"core:vendor-data-privacy-agreement:delete\")\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 createdAt\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 canUpdate: permission(action: \"core:vendor-service:update\")\n canDelete: permission(action: \"core:vendor-service:delete\")\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 canUpdate: permission(action: \"core:vendor-service:update\")\n canDelete: permission(action: \"core:vendor-service:delete\")\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n category\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n countries\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n id\n ... on Vendor {\n snapshotId\n name\n websiteUrl\n canAssess: permission(action: \"core:vendor:assess\")\n canUpdate: permission(action: \"core:vendor:update\")\n canDelete: permission(action: \"core:vendor:delete\")\n canUploadComplianceReport: permission(action: \"core:vendor-compliance-report:upload\")\n canCreateRiskAssessment: permission(action: \"core:vendor-risk-assessment:create\")\n canCreateContact: permission(action: \"core:vendor-contact:create\")\n canCreateService: permission(action: \"core:vendor-service:create\")\n canUploadBAA: permission(action: \"core:vendor-business-associate-agreement:upload\")\n canUploadDPA: permission(action: \"core:vendor-data-privacy-agreement:upload\")\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorContactsTabFragment\n ...VendorServicesTabFragment\n ...VendorRiskAssessmentTabFragment\n ...VendorOverviewTabBusinessAssociateAgreementFragment\n ...VendorOverviewTabDataPrivacyAgreementFragment\n }\n }\n viewer {\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\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 file {\n fileName\n mimeType\n size\n downloadUrl\n id\n }\n canDelete: permission(action: \"core:vendor-compliance-report:delete\")\n}\n\nfragment VendorContactsTabFragment on Vendor {\n contacts(first: 50) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:vendor-contact:update\")\n canDelete: permission(action: \"core:vendor-contact:delete\")\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 canUpdate: permission(action: \"core:vendor-contact:update\")\n canDelete: permission(action: \"core:vendor-contact:delete\")\n}\n\nfragment VendorOverviewTabBusinessAssociateAgreementFragment on Vendor {\n businessAssociateAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n canUpdate: permission(action: \"core:vendor-business-associate-agreement:update\")\n canDelete: permission(action: \"core:vendor-business-associate-agreement:delete\")\n }\n}\n\nfragment VendorOverviewTabDataPrivacyAgreementFragment on Vendor {\n dataPrivacyAgreement {\n id\n fileName\n fileUrl\n validFrom\n validUntil\n createdAt\n canUpdate: permission(action: \"core:vendor-data-privacy-agreement:update\")\n canDelete: permission(action: \"core:vendor-data-privacy-agreement:delete\")\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 createdAt\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 canUpdate: permission(action: \"core:vendor-service:update\")\n canDelete: permission(action: \"core:vendor-service:delete\")\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 canUpdate: permission(action: \"core:vendor-service:update\")\n canDelete: permission(action: \"core:vendor-service:delete\")\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n category\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n countries\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
}
};
})();

View File

@@ -0,0 +1,175 @@
import { formatDatetime, todayAsDateInput } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
Dropzone,
Field,
Input,
Spinner,
useDialogRef,
} from "@probo/ui";
import { useState } from "react";
import { graphql } from "react-relay";
import { z } from "zod";
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
const uploadComplianceReportMutation = graphql`
mutation UploadComplianceReportDialogMutation(
$input: UploadVendorComplianceReportInput!
$connections: [ID!]!
) {
uploadVendorComplianceReport(input: $input) {
vendorComplianceReportEdge @appendEdge(connections: $connections) {
node {
id
reportName
reportDate
validUntil
file {
fileName
mimeType
size
downloadUrl
}
canDelete: permission(action: "core:vendor-compliance-report:delete")
}
}
}
}
`;
const schema = z.object({
reportDate: z.string().min(1, "Report date is required"),
validUntil: z.string().optional(),
});
type Props = {
children: React.ReactNode;
vendorId: string;
connectionId: string;
onSuccess?: () => void;
};
export function UploadComplianceReportDialog({
children,
vendorId,
connectionId,
onSuccess,
}: Props) {
const { __ } = useTranslate();
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const ref = useDialogRef();
const {
register,
handleSubmit,
formState: { isSubmitting },
reset,
} = useFormWithSchema(schema, {
defaultValues: {
reportDate: todayAsDateInput(),
validUntil: "",
},
});
const [mutate] = useMutationWithToasts(uploadComplianceReportMutation, {
successMessage: __("Compliance report uploaded successfully"),
errorMessage: __("Failed to upload compliance report"),
});
const handleDrop = (files: File[]) => {
if (files.length > 0) {
setUploadedFile(files[0]);
}
};
const onSubmit = async (data: z.infer<typeof schema>) => {
if (!uploadedFile) {
return;
}
await mutate({
variables: {
connections: [connectionId],
input: {
vendorId,
reportName: uploadedFile.name,
reportDate: `${data.reportDate}T00:00:00Z`,
validUntil: formatDatetime(data.validUntil),
file: null,
},
},
uploadables: {
"input.file": uploadedFile,
},
});
reset();
setUploadedFile(null);
onSuccess?.();
ref.current?.close();
};
const handleClose = () => {
reset();
setUploadedFile(null);
};
return (
<Dialog
title={__("Upload Compliance Report")}
ref={ref}
trigger={children}
className="max-w-lg"
onClose={handleClose}
>
<form onSubmit={e => void handleSubmit(onSubmit)(e)}>
<DialogContent padded className="space-y-4">
<Dropzone
description={__("Only PDF files up to 10MB are allowed")}
isUploading={isSubmitting}
onDrop={handleDrop}
accept={{
"application/pdf": [".pdf"],
}}
maxSize={10}
/>
{uploadedFile && (
<div className="p-3 bg-tertiary-subtle rounded-lg">
<p className="text-sm font-medium">
{__("Selected file")}
:
</p>
<p className="text-sm text-txt-secondary">{uploadedFile.name}</p>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<Field label={__("Report date")} required>
<Input {...register("reportDate")} type="date" required />
</Field>
<Field label={__("Valid until")}>
<Input {...register("validUntil")} type="date" />
</Field>
</div>
</DialogContent>
<DialogFooter>
<Button
type="submit"
disabled={isSubmitting || !uploadedFile}
icon={isSubmitting ? Spinner : undefined}
>
{__("Upload")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}

View File

@@ -1,11 +1,14 @@
import { fileSize, formatDate, sprintf } from "@probo/helpers";
import { downloadFile, fileSize, formatDate, sprintf } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import {
ActionDropdown,
Button,
DropdownItem,
Dropzone,
IconArrowDown,
IconPlusLarge,
IconTrashCan,
PageHeader,
Tbody,
Td,
Th,
@@ -14,7 +17,7 @@ import {
useConfirm,
} from "@probo/ui";
import type { ComponentProps } from "react";
import { useFragment, useMutation, useRefetchableFragment } from "react-relay";
import { useFragment, useRefetchableFragment } from "react-relay";
import { useOutletContext, useParams } from "react-router";
import { graphql } from "relay-runtime";
@@ -22,8 +25,10 @@ import type { ComplianceReportListQuery } from "#/__generated__/core/ComplianceR
import type { VendorComplianceTabFragment$key } from "#/__generated__/core/VendorComplianceTabFragment.graphql";
import type { VendorComplianceTabFragment_report$key } from "#/__generated__/core/VendorComplianceTabFragment_report.graphql";
import type { VendorGraphNodeQuery$data } from "#/__generated__/core/VendorGraphNodeQuery.graphql";
import { SortableTable, SortableTh } from "#/components/SortableTable";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
import { UploadComplianceReportDialog } from "../dialogs/UploadComplianceReportDialog";
export const complianceReportsFragment = graphql`
fragment VendorComplianceTabFragment on Vendor
@@ -64,27 +69,12 @@ const complianceReportFragment = graphql`
fileName
mimeType
size
downloadUrl
}
canDelete: permission(action: "core:vendor-compliance-report:delete")
}
`;
const uploadReportMutation = graphql`
mutation VendorComplianceTabUploadReportMutation(
$input: UploadVendorComplianceReportInput!
$connections: [ID!]!
) {
uploadVendorComplianceReport(input: $input) {
vendorComplianceReportEdge @appendEdge(connections: $connections) {
node {
id
...VendorComplianceTabFragment_report
}
}
}
}
`;
const deleteReportMutation = graphql`
mutation VendorComplianceTabDeleteReportMutation(
$input: DeleteVendorComplianceReportInput!
@@ -109,45 +99,24 @@ export default function VendorComplianceTab() {
const { __ } = useTranslate();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const canDeleteSomeReport = reports.some(({ canDelete }) => canDelete);
usePageTitle(vendor.name + " - " + __("Compliance reports"));
const [mutate, isMutating] = useMutation(uploadReportMutation);
const handleDrop = (files: File[]) => {
const reportDate = new Date().toISOString();
for (const file of files) {
mutate({
variables: {
connections: [connectionId],
input: {
vendorId: vendor.id,
reportDate,
reportName: file.name,
file: null,
},
},
uploadables: {
"input.file": file,
},
});
}
};
return (
<div className="space-y-6">
{!isSnapshotMode && vendor.canUploadComplianceReport && (
<Dropzone
description={__("Only PDF files up to 10MB are allowed")}
isUploading={isMutating}
onDrop={handleDrop}
accept={{
"application/pdf": [".pdf"],
}}
maxSize={10}
/>
)}
<PageHeader
title={__("Compliance reports")}
description={__("Track vendor compliance certifications and reports.")}
>
{!isSnapshotMode && vendor.canUploadComplianceReport && (
<UploadComplianceReportDialog
vendorId={vendor.id}
connectionId={connectionId}
>
<Button icon={IconPlusLarge}>{__("Add report")}</Button>
</UploadComplianceReportDialog>
)}
</PageHeader>
<SortableTable
refetch={refetch as ComponentProps<typeof SortableTable>["refetch"]}
>
@@ -157,7 +126,7 @@ export default function VendorComplianceTab() {
<SortableTh field="REPORT_DATE">{__("Report date")}</SortableTh>
<Th>{__("Valid until")}</Th>
<Th>{__("File size")}</Th>
{!isSnapshotMode && canDeleteSomeReport && <Th>{__("Actions")}</Th>}
{!isSnapshotMode && reports.length > 0 && <Th>{__("Actions")}</Th>}
</Tr>
</Thead>
<Tbody>
@@ -221,16 +190,30 @@ function ReportRow(props: ReportRowProps) {
<Td>{formatDate(report.reportDate)}</Td>
<Td>{formatDate(report.validUntil)}</Td>
<Td>{fileSize(__, report.file?.size ?? 0)}</Td>
{!props.isSnapshotMode && report.canDelete && (
{!props.isSnapshotMode && (
<Td width={50} className="text-end">
<ActionDropdown>
<DropdownItem
icon={IconTrashCan}
onClick={handleDelete}
variant="danger"
>
{__("Delete")}
</DropdownItem>
{report.file?.downloadUrl && (
<DropdownItem
icon={IconArrowDown}
onClick={() =>
downloadFile(
report.file!.downloadUrl,
report.file!.fileName,
)}
>
{__("Download")}
</DropdownItem>
)}
{report.canDelete && (
<DropdownItem
icon={IconTrashCan}
onClick={handleDelete}
variant="danger"
>
{__("Delete")}
</DropdownItem>
)}
</ActionDropdown>
</Td>
)}