Add document type field
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
7a65ec8672
commit
7d2d26aab5
@@ -3,6 +3,7 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
|
DocumentTypeBadge,
|
||||||
IconMagnifyingGlass,
|
IconMagnifyingGlass,
|
||||||
IconPlusLarge,
|
IconPlusLarge,
|
||||||
IconTrashCan,
|
IconTrashCan,
|
||||||
@@ -13,8 +14,12 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
import type { DocumentLinkDialogQuery } from "./__generated__/DocumentLinkDialogQuery.graphql";
|
import type {
|
||||||
|
DocumentLinkDialogQuery,
|
||||||
|
DocumentLinkDialogQuery$data,
|
||||||
|
} from "./__generated__/DocumentLinkDialogQuery.graphql";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import type { NodeOf } from "/types";
|
||||||
|
|
||||||
const documentsQuery = graphql`
|
const documentsQuery = graphql`
|
||||||
query DocumentLinkDialogQuery($organizationId: ID!) {
|
query DocumentLinkDialogQuery($organizationId: ID!) {
|
||||||
@@ -26,6 +31,7 @@ const documentsQuery = graphql`
|
|||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
documentType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,8 +108,12 @@ function DocumentLinkDialogContent(props: Omit<Props, "children">) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Document = NodeOf<
|
||||||
|
DocumentLinkDialogQuery$data["organization"]["documents"]
|
||||||
|
>;
|
||||||
|
|
||||||
type RowProps = {
|
type RowProps = {
|
||||||
document: { title: string; id: string };
|
document: Document;
|
||||||
linkedDocuments: Set<string>;
|
linkedDocuments: Set<string>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onLink: (documentId: string) => void;
|
onLink: (documentId: string) => void;
|
||||||
@@ -123,6 +133,7 @@ function DocumentRow(props: RowProps) {
|
|||||||
onClick={() => onClick(props.document.id)}
|
onClick={() => onClick(props.document.id)}
|
||||||
>
|
>
|
||||||
{props.document.title}
|
{props.document.title}
|
||||||
|
<DocumentTypeBadge type={props.document.documentType} />
|
||||||
<Button
|
<Button
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconTrashCan,
|
IconTrashCan,
|
||||||
DocumentVersionBadge,
|
DocumentVersionBadge,
|
||||||
|
DocumentTypeBadge,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
|
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
|
||||||
@@ -26,6 +27,7 @@ const linkedDocumentFragment = graphql`
|
|||||||
id
|
id
|
||||||
title
|
title
|
||||||
createdAt
|
createdAt
|
||||||
|
documentType
|
||||||
versions(first: 1) {
|
versions(first: 1) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@@ -117,6 +119,7 @@ export function LinkedDocumentsCard<Params>(props: Props<Params>) {
|
|||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>{__("Name")}</Th>
|
<Th>{__("Name")}</Th>
|
||||||
|
<Th>{__("Type")}</Th>
|
||||||
<Th>{__("State")}</Th>
|
<Th>{__("State")}</Th>
|
||||||
<Th></Th>
|
<Th></Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
@@ -172,6 +175,9 @@ function DocumentRow(props: {
|
|||||||
{document.title}
|
{document.title}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<DocumentTypeBadge type={document.documentType} />
|
||||||
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
|
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<dfc2f49ef0ca7d3a3e62809aef61dfe6>>
|
* @generated SignedSource<<ec5910871c59f229db361be238007976>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||||
export type DocumentLinkDialogQuery$variables = {
|
export type DocumentLinkDialogQuery$variables = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
};
|
};
|
||||||
@@ -17,6 +18,7 @@ export type DocumentLinkDialogQuery$data = {
|
|||||||
readonly documents?: {
|
readonly documents?: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
|
readonly documentType: DocumentType;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
};
|
};
|
||||||
@@ -84,6 +86,13 @@ v4 = [
|
|||||||
"name": "title",
|
"name": "title",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "documentType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
(v3/*: any*/)
|
(v3/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -219,7 +228,7 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "fa671324c6ab818f40ccf9f83ca9af8a",
|
"cacheID": "0905fffe448540bf9c3d8abcbb5b7583",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"connection": [
|
"connection": [
|
||||||
@@ -236,11 +245,11 @@ return {
|
|||||||
},
|
},
|
||||||
"name": "DocumentLinkDialogQuery",
|
"name": "DocumentLinkDialogQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query DocumentLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n documents(first: 100) {\n edges {\n node {\n id\n title\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
"text": "query DocumentLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n documents(first: 100) {\n edges {\n node {\n id\n title\n documentType\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "70a09260215ae0f74ccf06a0c0872e75";
|
(node as any).hash = "6994e268de74f2102f48dc3e8156a797";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<da0435f63c82413be14bb2bb444aad49>>
|
* @generated SignedSource<<743c5a0c4d380bd05c0d8ccd7a664e58>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -10,9 +10,11 @@
|
|||||||
|
|
||||||
import { ReaderFragment } from 'relay-runtime';
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
export type DocumentStatus = "DRAFT" | "PUBLISHED";
|
export type DocumentStatus = "DRAFT" | "PUBLISHED";
|
||||||
|
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||||
import { FragmentRefs } from "relay-runtime";
|
import { FragmentRefs } from "relay-runtime";
|
||||||
export type LinkedDocumentsCardFragment$data = {
|
export type LinkedDocumentsCardFragment$data = {
|
||||||
readonly createdAt: any;
|
readonly createdAt: any;
|
||||||
|
readonly documentType: DocumentType;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
readonly versions: {
|
readonly versions: {
|
||||||
@@ -59,6 +61,13 @@ return {
|
|||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "documentType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": [
|
"args": [
|
||||||
@@ -112,6 +121,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "907fbdc23df596e4f76e396a11878706";
|
(node as any).hash = "952e653849ce0b1b693de8d7e3086e3f";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<64acca659bff60ca637937d933fc839f>>
|
* @generated SignedSource<<9d5d654dbc062e8a2a82adeed4c0839e>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -304,6 +304,13 @@ return {
|
|||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "documentType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": [
|
"args": [
|
||||||
@@ -392,12 +399,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "b10b304da4ff5b00e15b881ee486e8fe",
|
"cacheID": "9af171f23ae160b6725af13fdebaa07a",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "FrameworkGraphNodeQuery",
|
"name": "FrameworkGraphNodeQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ function UserDropdown() {
|
|||||||
label={__("Settings")}
|
label={__("Settings")}
|
||||||
/>
|
/>
|
||||||
<UserDropdownItem
|
<UserDropdownItem
|
||||||
to="/settings"
|
to="mailto:support@getprobo.com"
|
||||||
icon={IconCircleQuestionmark}
|
icon={IconCircleQuestionmark}
|
||||||
label={__("Help")}
|
label={__("Help")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<174d7c77d0d858ecc59261174b424af2>>
|
* @generated SignedSource<<7d4a807c299030c97164951cbe8ef067>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -159,6 +159,13 @@ return {
|
|||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "documentType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": [
|
"args": [
|
||||||
@@ -234,12 +241,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "9100dabaa4861920eab52dc47e551b06",
|
"cacheID": "968bd0beeabab861ca610804640e5e11",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "FrameworkDetailPageAttachDocumentMutation",
|
"name": "FrameworkDetailPageAttachDocumentMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
|
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
/**
|
|
||||||
* @generated SignedSource<<a91c1ee7ea4934d7d6e524f32462832a>>
|
|
||||||
* @lightSyntaxTransform
|
|
||||||
* @nogrep
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
|
||||||
import { FragmentRefs } from "relay-runtime";
|
|
||||||
export type CreateFrameworkInput = {
|
|
||||||
description: string;
|
|
||||||
name: string;
|
|
||||||
organizationId: string;
|
|
||||||
};
|
|
||||||
export type CreateFrameworkDialogMutation$variables = {
|
|
||||||
connections: ReadonlyArray<string>;
|
|
||||||
input: CreateFrameworkInput;
|
|
||||||
};
|
|
||||||
export type CreateFrameworkDialogMutation$data = {
|
|
||||||
readonly createFramework: {
|
|
||||||
readonly frameworkEdge: {
|
|
||||||
readonly node: {
|
|
||||||
readonly id: string;
|
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export type CreateFrameworkDialogMutation = {
|
|
||||||
response: CreateFrameworkDialogMutation$data;
|
|
||||||
variables: CreateFrameworkDialogMutation$variables;
|
|
||||||
};
|
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
|
||||||
var v0 = {
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "connections"
|
|
||||||
},
|
|
||||||
v1 = {
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "input"
|
|
||||||
},
|
|
||||||
v2 = [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "input",
|
|
||||||
"variableName": "input"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v3 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "id",
|
|
||||||
"storageKey": null
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
"fragment": {
|
|
||||||
"argumentDefinitions": [
|
|
||||||
(v0/*: any*/),
|
|
||||||
(v1/*: any*/)
|
|
||||||
],
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "CreateFrameworkDialogMutation",
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": (v2/*: any*/),
|
|
||||||
"concreteType": "CreateFrameworkPayload",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "createFramework",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "FrameworkEdge",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "frameworkEdge",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "Framework",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v3/*: any*/),
|
|
||||||
{
|
|
||||||
"args": null,
|
|
||||||
"kind": "FragmentSpread",
|
|
||||||
"name": "FrameworksPageCardFragment"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "Mutation",
|
|
||||||
"abstractKey": null
|
|
||||||
},
|
|
||||||
"kind": "Request",
|
|
||||||
"operation": {
|
|
||||||
"argumentDefinitions": [
|
|
||||||
(v1/*: any*/),
|
|
||||||
(v0/*: any*/)
|
|
||||||
],
|
|
||||||
"kind": "Operation",
|
|
||||||
"name": "CreateFrameworkDialogMutation",
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": (v2/*: any*/),
|
|
||||||
"concreteType": "CreateFrameworkPayload",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "createFramework",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "FrameworkEdge",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "frameworkEdge",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "Framework",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v3/*: any*/),
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "name",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "description",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"filters": null,
|
|
||||||
"handle": "prependEdge",
|
|
||||||
"key": "",
|
|
||||||
"kind": "LinkedHandle",
|
|
||||||
"name": "frameworkEdge",
|
|
||||||
"handleArgs": [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "connections",
|
|
||||||
"variableName": "connections"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"cacheID": "d08bc5a14bbf8d131029da890bdab850",
|
|
||||||
"id": null,
|
|
||||||
"metadata": {},
|
|
||||||
"name": "CreateFrameworkDialogMutation",
|
|
||||||
"operationKind": "mutation",
|
|
||||||
"text": "mutation CreateFrameworkDialogMutation(\n $input: CreateFrameworkInput!\n) {\n createFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
(node as any).hash = "957d00afa55875c9a6879292e8fa7bb1";
|
|
||||||
|
|
||||||
export default node;
|
|
||||||
@@ -37,12 +37,20 @@ import type {
|
|||||||
MeasuresPageFragment$data,
|
MeasuresPageFragment$data,
|
||||||
MeasuresPageFragment$key,
|
MeasuresPageFragment$key,
|
||||||
} from "./__generated__/MeasuresPageFragment.graphql";
|
} from "./__generated__/MeasuresPageFragment.graphql";
|
||||||
import { groupBy, objectKeys, sprintf } from "@probo/helpers";
|
import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers";
|
||||||
import { useMemo, useRef, useState, type ChangeEventHandler } from "react";
|
import {
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type ChangeEventHandler,
|
||||||
|
} from "react";
|
||||||
import type { NodeOf } from "/types";
|
import type { NodeOf } from "/types";
|
||||||
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
|
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { useToggle } from "@probo/hooks";
|
import { useToggle } from "@probo/hooks";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { Link, useParams } from "react-router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
||||||
@@ -170,13 +178,16 @@ type CategoryProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function Category(props: CategoryProps) {
|
function Category(props: CategoryProps) {
|
||||||
|
const params = useParams<{ categoryId?: string }>();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const categoryId = slugify(props.category);
|
||||||
const [limit, setLimit] = useState<number | null>(4);
|
const [limit, setLimit] = useState<number | null>(4);
|
||||||
const measures = useMemo(() => {
|
const measures = useMemo(() => {
|
||||||
return limit ? props.measures.slice(0, limit) : props.measures;
|
return limit ? props.measures.slice(0, limit) : props.measures;
|
||||||
}, [props.measures, limit]);
|
}, [props.measures, limit]);
|
||||||
const showMoreButton = limit !== null && props.measures.length > limit;
|
const showMoreButton = limit !== null && props.measures.length > limit;
|
||||||
const [isExpanded, toggleExpanded] = useToggle(false);
|
const isExpanded = categoryId === params.categoryId;
|
||||||
const ExpandComponent = isExpanded ? IconChevronUp : IconChevronDown;
|
const ExpandComponent = isExpanded ? IconChevronUp : IconChevronDown;
|
||||||
const completedMeasures = props.measures.filter(
|
const completedMeasures = props.measures.filter(
|
||||||
(m) => m.state === "IMPLEMENTED"
|
(m) => m.state === "IMPLEMENTED"
|
||||||
@@ -184,9 +195,9 @@ function Category(props: CategoryProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="py-3 px-5">
|
<Card className="py-3 px-5">
|
||||||
<div
|
<Link
|
||||||
|
to={`/organizations/${organizationId}/measures/category/${categoryId}`}
|
||||||
className="flex items-center justify-between cursor-pointer"
|
className="flex items-center justify-between cursor-pointer"
|
||||||
onClick={toggleExpanded}
|
|
||||||
>
|
>
|
||||||
<h2 className="text-base font-medium">{props.category}</h2>
|
<h2 className="text-base font-medium">{props.category}</h2>
|
||||||
<div className="flex items-center gap-3 text-sm text-txt-secondary">
|
<div className="flex items-center gap-3 text-sm text-txt-secondary">
|
||||||
@@ -199,7 +210,7 @@ function Category(props: CategoryProps) {
|
|||||||
<span className="text-border-low">|</span>
|
<span className="text-border-low">|</span>
|
||||||
<ExpandComponent size={16} className="text-txt-secondary" />
|
<ExpandComponent size={16} className="text-txt-secondary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Table className="bg-invert">
|
<Table className="bg-invert">
|
||||||
@@ -245,6 +256,7 @@ function MeasureRow(props: MeasureRowProps) {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
|
||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
confirm(
|
confirm(
|
||||||
@@ -271,12 +283,12 @@ function MeasureRow(props: MeasureRowProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tr>
|
<Tr to={`/organizations/${organizationId}/measures/${props.measure.id}`}>
|
||||||
<Td>{props.measure.name}</Td>
|
<Td>{props.measure.name}</Td>
|
||||||
<Td width={120}>
|
<Td width={120}>
|
||||||
<MeasureBadge state={props.measure.state} />
|
<MeasureBadge state={props.measure.state} />
|
||||||
</Td>
|
</Td>
|
||||||
<Td width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem icon={IconPencil}>{__("Edit")}</DropdownItem>
|
<DropdownItem icon={IconPencil}>{__("Edit")}</DropdownItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { usePageTitle } from "@probo/hooks";
|
|||||||
import { getRole } from "@probo/helpers";
|
import { getRole } from "@probo/helpers";
|
||||||
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { Link } from "react-router";
|
|
||||||
|
|
||||||
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
|
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
|
||||||
|
|
||||||
@@ -99,14 +98,6 @@ function PeopleRow({
|
|||||||
<Td className="text-sm">{getRole(__, people.kind)}</Td>
|
<Td className="text-sm">{getRole(__, people.kind)}</Td>
|
||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem asChild>
|
|
||||||
<Link
|
|
||||||
to={`/organizations/${organizationId}/people/${people.id}/tasks`}
|
|
||||||
>
|
|
||||||
<IconPencil size={16} />
|
|
||||||
{__("Edit")}
|
|
||||||
</Link>
|
|
||||||
</DropdownItem>
|
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ function CertificationInput({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{search}
|
|
||||||
<Combobox
|
<Combobox
|
||||||
autoSelect
|
autoSelect
|
||||||
resetValueOnHide
|
resetValueOnHide
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
import { lazy } from "react";
|
import { Fragment, lazy } from "react";
|
||||||
import { loadQuery } from "react-relay";
|
import { loadQuery } from "react-relay";
|
||||||
import { RisksPageSkeleton } from "/components/skeletons/RisksPageSkeleton.tsx";
|
|
||||||
import type { AppRoute } from "/routes.tsx";
|
import type { AppRoute } from "/routes.tsx";
|
||||||
import { relayEnvironment } from "/providers/RelayProviders";
|
import { relayEnvironment } from "/providers/RelayProviders";
|
||||||
import { measuresQuery } from "/hooks/graph/MeasureGraph";
|
import { measuresQuery } from "/hooks/graph/MeasureGraph";
|
||||||
|
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||||
|
|
||||||
export const measureRoutes = [
|
export const measureRoutes = [
|
||||||
{
|
{
|
||||||
path: "measures",
|
path: "measures",
|
||||||
fallback: RisksPageSkeleton,
|
fallback: PageSkeleton,
|
||||||
queryLoader: ({ organizationId }) =>
|
queryLoader: ({ organizationId }) =>
|
||||||
loadQuery(relayEnvironment, measuresQuery, { organizationId }),
|
loadQuery(relayEnvironment, measuresQuery, { organizationId }),
|
||||||
Component: lazy(() => import("/pages/organizations/measures/MeasuresPage")),
|
Component: lazy(() => import("/pages/organizations/measures/MeasuresPage")),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "category/:categoryId",
|
||||||
|
Component: Fragment,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "measures/:measureId",
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
Component: Fragment,
|
||||||
},
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type NodeOf<T> = T extends
|
export type NodeOf<T> =
|
||||||
|
NonNullable<T> extends
|
||||||
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
||||||
| undefined
|
| undefined
|
||||||
? U
|
? U
|
||||||
|
|||||||
14
packages/helpers/src/documents.ts
Normal file
14
packages/helpers/src/documents.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
type Translator = (s: string) => string;
|
||||||
|
|
||||||
|
export const documentTypes = ["OTHER", "ISMS", "POLICY"] as const;
|
||||||
|
|
||||||
|
export function getDocumentTypeLabel(__: Translator, type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case "OTHER":
|
||||||
|
return __("Other");
|
||||||
|
case "ISMS":
|
||||||
|
return __("ISMS");
|
||||||
|
case "POLICY":
|
||||||
|
return __("Policy");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export { objectKeys, objectEntries } from "./object";
|
export { objectKeys, objectEntries } from "./object";
|
||||||
export { sprintf, faviconUrl } from "./string";
|
export { sprintf, faviconUrl, slugify } from "./string";
|
||||||
export {
|
export {
|
||||||
getTreatment,
|
getTreatment,
|
||||||
getRiskImpacts,
|
getRiskImpacts,
|
||||||
@@ -13,3 +13,4 @@ export { getMeasureStateLabel, measureStates } from "./measure";
|
|||||||
export { getRole, getRoles, peopleRoles } from "./people";
|
export { getRole, getRoles, peopleRoles } from "./people";
|
||||||
export { certificationCategoryLabel, certifications } from "./certifications";
|
export { certificationCategoryLabel, certifications } from "./certifications";
|
||||||
export { availableFrameworks } from "./frameworks";
|
export { availableFrameworks } from "./frameworks";
|
||||||
|
export { getDocumentTypeLabel, documentTypes } from "./documents";
|
||||||
|
|||||||
@@ -20,3 +20,13 @@ export function faviconUrl(url?: string | null): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function slugify(str: string): string {
|
||||||
|
return str
|
||||||
|
.normalize("NFD") // split an accented letter in the base letter and the acent
|
||||||
|
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
|
||||||
|
.replace(/\s+/g, "-");
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ const LayoutContext = createContext({
|
|||||||
|
|
||||||
export function Layout({ header, sidebar, children }: Props) {
|
export function Layout({ header, sidebar, children }: Props) {
|
||||||
const [hasDrawer, setDrawer] = useState(false);
|
const [hasDrawer, setDrawer] = useState(false);
|
||||||
return (
|
const layoutContext = useMemo(
|
||||||
<LayoutContext
|
|
||||||
value={useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
setDrawer,
|
setDrawer,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
)}
|
);
|
||||||
>
|
return (
|
||||||
|
<LayoutContext value={layoutContext}>
|
||||||
<div className="text-txt-primary bg-level-0">
|
<div className="text-txt-primary bg-level-0">
|
||||||
<header className="absolute z-2 left-0 right-0 px-4 flex items-center border-b border-border-solid h-12 bg-level-1">
|
<header className="absolute z-2 left-0 right-0 px-4 flex items-center border-b border-border-solid h-12 bg-level-1">
|
||||||
<Logo className="w-12 h-5" />
|
<Logo className="w-12 h-5" />
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { DocumentTypeBadge } from "./DocumentTypeBadge";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Molecules/Badges/DocumentTypeBadge",
|
||||||
|
component: DocumentTypeBadge,
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof DocumentTypeBadge>;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof DocumentTypeBadge>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
type: "OTHER",
|
||||||
|
},
|
||||||
|
};
|
||||||
12
packages/ui/src/Molecules/Badge/DocumentTypeBadge.tsx
Normal file
12
packages/ui/src/Molecules/Badge/DocumentTypeBadge.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Badge } from "../../Atoms/Badge/Badge";
|
||||||
|
import { getDocumentTypeLabel } from "@probo/helpers";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DocumentTypeBadge({ type }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
return <Badge variant="neutral">{getDocumentTypeLabel(__, type)}</Badge>;
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ export { FileButton } from "./Molecules/FileButton/FileButton";
|
|||||||
export { MeasureBadge } from "./Molecules/Badge/MeasureBadge";
|
export { MeasureBadge } from "./Molecules/Badge/MeasureBadge";
|
||||||
export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation";
|
export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation";
|
||||||
export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector";
|
export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector";
|
||||||
|
export { DocumentTypeBadge } from "./Molecules/Badge/DocumentTypeBadge";
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";
|
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";
|
||||||
|
|||||||
Reference in New Issue
Block a user