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,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DocumentTypeBadge,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
@@ -13,8 +14,12 @@ import { useTranslate } from "@probo/i18n";
|
||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
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 type { NodeOf } from "/types";
|
||||
|
||||
const documentsQuery = graphql`
|
||||
query DocumentLinkDialogQuery($organizationId: ID!) {
|
||||
@@ -26,6 +31,7 @@ const documentsQuery = graphql`
|
||||
node {
|
||||
id
|
||||
title
|
||||
documentType
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,8 +108,12 @@ function DocumentLinkDialogContent(props: Omit<Props, "children">) {
|
||||
);
|
||||
}
|
||||
|
||||
type Document = NodeOf<
|
||||
DocumentLinkDialogQuery$data["organization"]["documents"]
|
||||
>;
|
||||
|
||||
type RowProps = {
|
||||
document: { title: string; id: string };
|
||||
document: Document;
|
||||
linkedDocuments: Set<string>;
|
||||
disabled?: boolean;
|
||||
onLink: (documentId: string) => void;
|
||||
@@ -123,6 +133,7 @@ function DocumentRow(props: RowProps) {
|
||||
onClick={() => onClick(props.document.id)}
|
||||
>
|
||||
{props.document.title}
|
||||
<DocumentTypeBadge type={props.document.documentType} />
|
||||
<Button
|
||||
disabled={props.disabled}
|
||||
className="ml-auto"
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
IconChevronDown,
|
||||
IconTrashCan,
|
||||
DocumentVersionBadge,
|
||||
DocumentTypeBadge,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
|
||||
@@ -26,6 +27,7 @@ const linkedDocumentFragment = graphql`
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
documentType
|
||||
versions(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
@@ -117,6 +119,7 @@ export function LinkedDocumentsCard<Params>(props: Props<Params>) {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("Type")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
@@ -172,6 +175,9 @@ function DocumentRow(props: {
|
||||
{document.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<DocumentTypeBadge type={document.documentType} />
|
||||
</Td>
|
||||
<Td>
|
||||
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
|
||||
</Td>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<dfc2f49ef0ca7d3a3e62809aef61dfe6>>
|
||||
* @generated SignedSource<<ec5910871c59f229db361be238007976>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,6 +9,7 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||
export type DocumentLinkDialogQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
@@ -17,6 +18,7 @@ export type DocumentLinkDialogQuery$data = {
|
||||
readonly documents?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly documentType: DocumentType;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
};
|
||||
@@ -84,6 +86,13 @@ v4 = [
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -219,7 +228,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fa671324c6ab818f40ccf9f83ca9af8a",
|
||||
"cacheID": "0905fffe448540bf9c3d8abcbb5b7583",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -236,11 +245,11 @@ return {
|
||||
},
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<da0435f63c82413be14bb2bb444aad49>>
|
||||
* @generated SignedSource<<743c5a0c4d380bd05c0d8ccd7a664e58>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type DocumentStatus = "DRAFT" | "PUBLISHED";
|
||||
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type LinkedDocumentsCardFragment$data = {
|
||||
readonly createdAt: any;
|
||||
readonly documentType: DocumentType;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly versions: {
|
||||
@@ -59,6 +61,13 @@ return {
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
@@ -112,6 +121,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "907fbdc23df596e4f76e396a11878706";
|
||||
(node as any).hash = "952e653849ce0b1b693de8d7e3086e3f";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<64acca659bff60ca637937d933fc839f>>
|
||||
* @generated SignedSource<<9d5d654dbc062e8a2a82adeed4c0839e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -304,6 +304,13 @@ return {
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
@@ -392,12 +399,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "b10b304da4ff5b00e15b881ee486e8fe",
|
||||
"cacheID": "9af171f23ae160b6725af13fdebaa07a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"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")}
|
||||
/>
|
||||
<UserDropdownItem
|
||||
to="/settings"
|
||||
to="mailto:support@getprobo.com"
|
||||
icon={IconCircleQuestionmark}
|
||||
label={__("Help")}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<174d7c77d0d858ecc59261174b424af2>>
|
||||
* @generated SignedSource<<7d4a807c299030c97164951cbe8ef067>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -159,6 +159,13 @@ return {
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
@@ -234,12 +241,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9100dabaa4861920eab52dc47e551b06",
|
||||
"cacheID": "968bd0beeabab861ca610804640e5e11",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"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$key,
|
||||
} from "./__generated__/MeasuresPageFragment.graphql";
|
||||
import { groupBy, objectKeys, sprintf } from "@probo/helpers";
|
||||
import { useMemo, useRef, useState, type ChangeEventHandler } from "react";
|
||||
import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers";
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ChangeEventHandler,
|
||||
} from "react";
|
||||
import type { NodeOf } from "/types";
|
||||
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { useToggle } from "@probo/hooks";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Link, useParams } from "react-router";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
||||
@@ -170,13 +178,16 @@ type CategoryProps = {
|
||||
};
|
||||
|
||||
function Category(props: CategoryProps) {
|
||||
const params = useParams<{ categoryId?: string }>();
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
const categoryId = slugify(props.category);
|
||||
const [limit, setLimit] = useState<number | null>(4);
|
||||
const measures = useMemo(() => {
|
||||
return limit ? props.measures.slice(0, limit) : props.measures;
|
||||
}, [props.measures, 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 completedMeasures = props.measures.filter(
|
||||
(m) => m.state === "IMPLEMENTED"
|
||||
@@ -184,9 +195,9 @@ function Category(props: CategoryProps) {
|
||||
|
||||
return (
|
||||
<Card className="py-3 px-5">
|
||||
<div
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/measures/category/${categoryId}`}
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
onClick={toggleExpanded}
|
||||
>
|
||||
<h2 className="text-base font-medium">{props.category}</h2>
|
||||
<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>
|
||||
<ExpandComponent size={16} className="text-txt-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
{isExpanded && (
|
||||
<div className="mt-3">
|
||||
<Table className="bg-invert">
|
||||
@@ -245,6 +256,7 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
const { __ } = useTranslate();
|
||||
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
||||
const confirm = useConfirm();
|
||||
const organizationId = useOrganizationId();
|
||||
|
||||
const onDelete = () => {
|
||||
confirm(
|
||||
@@ -271,12 +283,12 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr>
|
||||
<Tr to={`/organizations/${organizationId}/measures/${props.measure.id}`}>
|
||||
<Td>{props.measure.name}</Td>
|
||||
<Td width={120}>
|
||||
<MeasureBadge state={props.measure.state} />
|
||||
</Td>
|
||||
<Td width={50} className="text-end">
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem icon={IconPencil}>{__("Edit")}</DropdownItem>
|
||||
<DropdownItem
|
||||
|
||||
@@ -24,7 +24,6 @@ import { usePageTitle } from "@probo/hooks";
|
||||
import { getRole } from "@probo/helpers";
|
||||
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Link } from "react-router";
|
||||
|
||||
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
|
||||
|
||||
@@ -99,14 +98,6 @@ function PeopleRow({
|
||||
<Td className="text-sm">{getRole(__, people.kind)}</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem asChild>
|
||||
<Link
|
||||
to={`/organizations/${organizationId}/people/${people.id}/tasks`}
|
||||
>
|
||||
<IconPencil size={16} />
|
||||
{__("Edit")}
|
||||
</Link>
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
|
||||
@@ -142,7 +142,6 @@ function CertificationInput({
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{search}
|
||||
<Combobox
|
||||
autoSelect
|
||||
resetValueOnHide
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import { lazy } from "react";
|
||||
import { Fragment, lazy } from "react";
|
||||
import { loadQuery } from "react-relay";
|
||||
import { RisksPageSkeleton } from "/components/skeletons/RisksPageSkeleton.tsx";
|
||||
import type { AppRoute } from "/routes.tsx";
|
||||
import { relayEnvironment } from "/providers/RelayProviders";
|
||||
import { measuresQuery } from "/hooks/graph/MeasureGraph";
|
||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||
|
||||
export const measureRoutes = [
|
||||
{
|
||||
path: "measures",
|
||||
fallback: RisksPageSkeleton,
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ organizationId }) =>
|
||||
loadQuery(relayEnvironment, measuresQuery, { organizationId }),
|
||||
Component: lazy(() => import("/pages/organizations/measures/MeasuresPage")),
|
||||
children: [
|
||||
{
|
||||
path: "category/:categoryId",
|
||||
Component: Fragment,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "measures/:measureId",
|
||||
fallback: PageSkeleton,
|
||||
Component: Fragment,
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export type NodeOf<T> = T extends
|
||||
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
||||
| undefined
|
||||
? U
|
||||
: never;
|
||||
export type NodeOf<T> =
|
||||
NonNullable<T> extends
|
||||
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
||||
| undefined
|
||||
? U
|
||||
: never;
|
||||
|
||||
export type ItemOf<T> = T extends (infer U)[] ? U : never;
|
||||
|
||||
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 { sprintf, faviconUrl } from "./string";
|
||||
export { sprintf, faviconUrl, slugify } from "./string";
|
||||
export {
|
||||
getTreatment,
|
||||
getRiskImpacts,
|
||||
@@ -13,3 +13,4 @@ export { getMeasureStateLabel, measureStates } from "./measure";
|
||||
export { getRole, getRoles, peopleRoles } from "./people";
|
||||
export { certificationCategoryLabel, certifications } from "./certifications";
|
||||
export { availableFrameworks } from "./frameworks";
|
||||
export { getDocumentTypeLabel, documentTypes } from "./documents";
|
||||
|
||||
@@ -20,3 +20,13 @@ export function faviconUrl(url?: string | null): string | 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) {
|
||||
const [hasDrawer, setDrawer] = useState(false);
|
||||
const layoutContext = useMemo(
|
||||
() => ({
|
||||
setDrawer,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<LayoutContext
|
||||
value={useMemo(
|
||||
() => ({
|
||||
setDrawer,
|
||||
}),
|
||||
[],
|
||||
)}
|
||||
>
|
||||
<LayoutContext value={layoutContext}>
|
||||
<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">
|
||||
<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 { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation";
|
||||
export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector";
|
||||
export { DocumentTypeBadge } from "./Molecules/Badge/DocumentTypeBadge";
|
||||
|
||||
// Hooks
|
||||
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";
|
||||
|
||||
Reference in New Issue
Block a user