Add frameworks detail page
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
b6aec0b14a
commit
74c136f6c0
15
apps/console2/src/components/FrameworkLogo.tsx
Normal file
15
apps/console2/src/components/FrameworkLogo.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { availableFrameworks } from "@probo/helpers";
|
||||
import { Avatar } from "@probo/ui";
|
||||
|
||||
const availableLogos = new Map(
|
||||
availableFrameworks.map((framework) => [framework.name, framework.logo])
|
||||
);
|
||||
|
||||
export function FrameworkLogo({ name }: { name: string }) {
|
||||
const logo = availableLogos.get(name);
|
||||
return logo ? (
|
||||
<img src={logo} alt="" className="size-12" />
|
||||
) : (
|
||||
<Avatar name={name} size="l" className="size-12" />
|
||||
);
|
||||
}
|
||||
138
apps/console2/src/components/documents/DocumentLinkDialog.tsx
Normal file
138
apps/console2/src/components/documents/DocumentLinkDialog.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
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 { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
|
||||
const documentsQuery = graphql`
|
||||
query DocumentLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
documents(first: 100) @connection(key: "Organization__documents") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
connectionId: string;
|
||||
disabled?: boolean;
|
||||
linkedDocuments?: { id: string }[];
|
||||
onLink: (documentId: string) => void;
|
||||
onUnlink: (documentId: string) => void;
|
||||
};
|
||||
|
||||
export function DocumentLinkDialog({ children, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={children} title={__("Link documents")}>
|
||||
<DialogContent>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<DocumentLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentLinkDialogContent(props: Omit<Props, "children">) {
|
||||
const organizationId = useOrganizationId();
|
||||
const data = useLazyLoadQuery<DocumentLinkDialogQuery>(documentsQuery, {
|
||||
organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const documents =
|
||||
data.organization?.documents?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(() => {
|
||||
return new Set(props.linkedDocuments?.map((m) => m.id) ?? []);
|
||||
}, [props.linkedDocuments]);
|
||||
|
||||
const filteredDocuments = useMemo(() => {
|
||||
return documents.filter((document) =>
|
||||
document.title.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}, [documents, search]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 sticky top-0 relative py-4 bg-linear-to-b from-50% from-level-2 to-level-2/0 px-6">
|
||||
<Input
|
||||
icon={IconMagnifyingGlass}
|
||||
placeholder={__("Search documents...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredDocuments.map((document) => (
|
||||
<DocumentRow
|
||||
key={document.id}
|
||||
document={document}
|
||||
linkedDocuments={linkedIds}
|
||||
onLink={props.onLink}
|
||||
onUnlink={props.onUnlink}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
document: { title: string; id: string };
|
||||
linkedDocuments: Set<string>;
|
||||
disabled?: boolean;
|
||||
onLink: (documentId: string) => void;
|
||||
onUnlink: (documentId: string) => void;
|
||||
};
|
||||
|
||||
function DocumentRow(props: RowProps) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const isLinked = props.linkedDocuments.has(props.document.id);
|
||||
const onClick = isLinked ? props.onUnlink : props.onLink;
|
||||
const IconComponent = isLinked ? IconTrashCan : IconPlusLarge;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="py-4 flex items-center gap-4 hover:bg-subtle cursor-pointer px-6 w-full"
|
||||
onClick={() => onClick(props.document.id)}
|
||||
>
|
||||
{props.document.title}
|
||||
<Button
|
||||
disabled={props.disabled}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
asChild
|
||||
>
|
||||
<span>
|
||||
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
189
apps/console2/src/components/documents/LinkedDocumentsCard.tsx
Normal file
189
apps/console2/src/components/documents/LinkedDocumentsCard.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import {
|
||||
Card,
|
||||
IconPlusLarge,
|
||||
Button,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
IconChevronDown,
|
||||
IconTrashCan,
|
||||
DocumentVersionBadge,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useMemo, useState } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { DocumentLinkDialog } from "./DocumentLinkDialog";
|
||||
|
||||
const linkedDocumentFragment = graphql`
|
||||
fragment LinkedDocumentsCardFragment on Document {
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
versions(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Mutation<Params> = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
documentId: string;
|
||||
} & Params;
|
||||
connections: string[];
|
||||
};
|
||||
}) => void;
|
||||
|
||||
type Props<Params> = {
|
||||
// Documents linked to the element
|
||||
documents: (LinkedDocumentsCardFragment$key & { id: string })[];
|
||||
// Extra params to send to the mutation
|
||||
params: Params;
|
||||
// Disable (action when loading for instance)
|
||||
disabled?: boolean;
|
||||
// ID of the connection to update
|
||||
connectionId: string;
|
||||
// Mutation to attach a document (will receive {documentId, ...params})
|
||||
onAttach: Mutation<Params>;
|
||||
// Mutation to detach a document (will receive {documentId, ...params})
|
||||
onDetach: Mutation<Params>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reusable component that displays a list of linked documents
|
||||
*/
|
||||
export function LinkedDocumentsCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
const [limit, setLimit] = useState<number | null>(4);
|
||||
const documents = useMemo(() => {
|
||||
return limit ? props.documents.slice(0, limit) : props.documents;
|
||||
}, [props.documents, limit]);
|
||||
const showMoreButton = limit !== null && props.documents.length > limit;
|
||||
|
||||
const onAttach = (documentId: string) => {
|
||||
props.onAttach({
|
||||
variables: {
|
||||
input: {
|
||||
documentId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDetach = (documentId: string) => {
|
||||
props.onDetach({
|
||||
variables: {
|
||||
input: {
|
||||
documentId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padded className="space-y-[10px]">
|
||||
<div className="flex justify-between">
|
||||
<div className="text-lg font-semibold">{__("Documents")}</div>
|
||||
<DocumentLinkDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedDocuments={props.documents}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||
{__("Link document")}
|
||||
</Button>
|
||||
</DocumentLinkDialog>
|
||||
</div>
|
||||
{documents.length > 0 ? (
|
||||
<Table className="bg-invert">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow
|
||||
key={document.id}
|
||||
document={document}
|
||||
onClick={onDetach}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center text-sm text-txt-secondary">
|
||||
{__("No documents linked")}
|
||||
</div>
|
||||
)}
|
||||
{showMoreButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => setLimit(null)}
|
||||
className="mt-3 mx-auto"
|
||||
icon={IconChevronDown}
|
||||
>
|
||||
{sprintf(__("Show %s more"), props.documents.length - limit)}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentRow(props: {
|
||||
document: LinkedDocumentsCardFragment$key & { id: string };
|
||||
onClick: (documentId: string) => void;
|
||||
}) {
|
||||
const document = useFragment(linkedDocumentFragment, props.document);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/documents/${document.id}`}>
|
||||
<Td>
|
||||
<div className="flex gap-4 items-center">
|
||||
<img
|
||||
src="/document.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={36}
|
||||
className="border-4 border-highlight rounded box-content"
|
||||
/>
|
||||
{document.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => props.onClick(document.id)}
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
246
apps/console2/src/components/documents/__generated__/DocumentLinkDialogQuery.graphql.ts
generated
Normal file
246
apps/console2/src/components/documents/__generated__/DocumentLinkDialogQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* @generated SignedSource<<dfc2f49ef0ca7d3a3e62809aef61dfe6>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DocumentLinkDialogQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type DocumentLinkDialogQuery$data = {
|
||||
readonly organization: {
|
||||
readonly documents?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
export type DocumentLinkDialogQuery = {
|
||||
response: DocumentLinkDialogQuery$data;
|
||||
variables: DocumentLinkDialogQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
v5 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "documents",
|
||||
"args": null,
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__Organization__documents_connection",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "documents",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": "documents(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "Organization__documents",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documents"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fa671324c6ab818f40ccf9f83ca9af8a",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organization",
|
||||
"documents"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "70a09260215ae0f74ccf06a0c0872e75";
|
||||
|
||||
export default node;
|
||||
117
apps/console2/src/components/documents/__generated__/LinkedDocumentsCardFragment.graphql.ts
generated
Normal file
117
apps/console2/src/components/documents/__generated__/LinkedDocumentsCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @generated SignedSource<<da0435f63c82413be14bb2bb444aad49>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type DocumentStatus = "DRAFT" | "PUBLISHED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type LinkedDocumentsCardFragment$data = {
|
||||
readonly createdAt: any;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly versions: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly status: DocumentStatus;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "LinkedDocumentsCardFragment";
|
||||
};
|
||||
export type LinkedDocumentsCardFragment$key = {
|
||||
readonly " $data"?: LinkedDocumentsCardFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "LinkedDocumentsCardFragment",
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"type": "Document",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "907fbdc23df596e4f76e396a11878706";
|
||||
|
||||
export default node;
|
||||
170
apps/console2/src/components/measures/LinkedMeasuresCard.tsx
Normal file
170
apps/console2/src/components/measures/LinkedMeasuresCard.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import {
|
||||
Card,
|
||||
IconPlusLarge,
|
||||
Button,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
IconChevronDown,
|
||||
MeasureBadge,
|
||||
IconTrashCan,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { LinkedMeasuresCardFragment$key } from "./__generated__/LinkedMeasuresCardFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useMemo, useState } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { MeasureLinkDialog } from "./MeasureLinkDialog";
|
||||
|
||||
const linkedMeasureFragment = graphql`
|
||||
fragment LinkedMeasuresCardFragment on Measure {
|
||||
id
|
||||
name
|
||||
state
|
||||
}
|
||||
`;
|
||||
|
||||
type Mutation<Params> = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
measureId: string;
|
||||
} & Params;
|
||||
connections: string[];
|
||||
};
|
||||
}) => void;
|
||||
|
||||
type Props<Params> = {
|
||||
// Measures linked to the element
|
||||
measures: (LinkedMeasuresCardFragment$key & { id: string })[];
|
||||
// Extra params to send to the mutation
|
||||
params: Params;
|
||||
// Disable (action when loading for instance)
|
||||
disabled?: boolean;
|
||||
// ID of the connection to update
|
||||
connectionId: string;
|
||||
// Mutation to attach a measure (will receive {measureId, ...params})
|
||||
onAttach: Mutation<Params>;
|
||||
// Mutation to detach a measure (will receive {measureId, ...params})
|
||||
onDetach: Mutation<Params>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reusable component that displays a list of linked measures
|
||||
*/
|
||||
export function LinkedMeasuresCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
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 onAttach = (measureId: string) => {
|
||||
props.onAttach({
|
||||
variables: {
|
||||
input: {
|
||||
measureId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDetach = (measureId: string) => {
|
||||
props.onDetach({
|
||||
variables: {
|
||||
input: {
|
||||
measureId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padded className="space-y-[10px]">
|
||||
<div className="flex justify-between">
|
||||
<div className="text-lg font-semibold">{__("Measures")}</div>
|
||||
<MeasureLinkDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedMeasures={props.measures}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
</MeasureLinkDialog>
|
||||
</div>
|
||||
{measures.length > 0 ? (
|
||||
<Table className="bg-invert">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{measures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
onClick={onDetach}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center text-sm text-txt-secondary">
|
||||
{__("No measures linked")}
|
||||
</div>
|
||||
)}
|
||||
{showMoreButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => setLimit(null)}
|
||||
className="mt-3 mx-auto"
|
||||
icon={IconChevronDown}
|
||||
>
|
||||
{sprintf(__("Show %s more"), props.measures.length - limit)}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureRow(props: {
|
||||
measure: LinkedMeasuresCardFragment$key & { id: string };
|
||||
onClick: (measureId: string) => void;
|
||||
}) {
|
||||
const measure = useFragment(linkedMeasureFragment, props.measure);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/measures/${measure.id}`}>
|
||||
<Td>{measure.name}</Td>
|
||||
<Td>
|
||||
<MeasureBadge state={measure.state} />
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => props.onClick(measure.id)}
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
166
apps/console2/src/components/measures/MeasureLinkDialog.tsx
Normal file
166
apps/console2/src/components/measures/MeasureLinkDialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Option,
|
||||
Select,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
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 { MeasureLinkDialogQuery } from "./__generated__/MeasureLinkDialogQuery.graphql";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
|
||||
const measuresQuery = graphql`
|
||||
query MeasureLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
measures(first: 100) @connection(key: "Organization__measures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
state
|
||||
description
|
||||
category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
connectionId: string;
|
||||
disabled?: boolean;
|
||||
linkedMeasures?: { id: string }[];
|
||||
onLink: (measureId: string) => void;
|
||||
onUnlink: (measureId: string) => void;
|
||||
};
|
||||
|
||||
export function MeasureLinkDialog({ children, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={children} title={__("Link measures")}>
|
||||
<DialogContent>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<MeasureLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureLinkDialogContent(props: Omit<Props, "children">) {
|
||||
const organizationId = useOrganizationId();
|
||||
const data = useLazyLoadQuery<MeasureLinkDialogQuery>(measuresQuery, {
|
||||
organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const measures =
|
||||
data.organization?.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(() => {
|
||||
return new Set(props.linkedMeasures?.map((m) => m.id) ?? []);
|
||||
}, [props.linkedMeasures]);
|
||||
|
||||
const filteredMeasures = useMemo(() => {
|
||||
return measures.filter(
|
||||
(measure) =>
|
||||
(category === null || measure.category === category) &&
|
||||
(measure.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
measure.description?.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
}, [measures, search, category]);
|
||||
|
||||
const categories = useMemo(
|
||||
() => Array.from(new Set(measures.map((m) => m.category))),
|
||||
[measures]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 sticky top-0 relative py-4 bg-linear-to-b from-50% from-level-2 to-level-2/0 px-6">
|
||||
<Input
|
||||
icon={IconMagnifyingGlass}
|
||||
placeholder={__("Search measures...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<Select
|
||||
value={category ?? ""}
|
||||
placeholder={__("All categories")}
|
||||
onValueChange={setCategory}
|
||||
className="max-w-[180px]"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<Option key={category} value={category}>
|
||||
{category}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredMeasures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
linkedMeasures={linkedIds}
|
||||
onLink={props.onLink}
|
||||
onUnlink={props.onUnlink}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
measure: { name: string; category: string; id: string };
|
||||
linkedMeasures: Set<string>;
|
||||
disabled?: boolean;
|
||||
onLink: (measureId: string) => void;
|
||||
onUnlink: (measureId: string) => void;
|
||||
};
|
||||
|
||||
function MeasureRow(props: RowProps) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const isLinked = props.linkedMeasures.has(props.measure.id);
|
||||
const onClick = isLinked ? props.onUnlink : props.onLink;
|
||||
const IconComponent = isLinked ? IconTrashCan : IconPlusLarge;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="py-4 flex items-center gap-4 hover:bg-subtle cursor-pointer px-6 w-full"
|
||||
onClick={() => onClick(props.measure.id)}
|
||||
>
|
||||
{props.measure.name}
|
||||
<Badge variant="neutral">{props.measure.category}</Badge>
|
||||
<Button
|
||||
disabled={props.disabled}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
asChild
|
||||
>
|
||||
<span>
|
||||
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
59
apps/console2/src/components/measures/__generated__/LinkedMeasuresCardFragment.graphql.ts
generated
Normal file
59
apps/console2/src/components/measures/__generated__/LinkedMeasuresCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @generated SignedSource<<b7482b43ce0f4dfab470c35e806acf50>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type LinkedMeasuresCardFragment$data = {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
readonly " $fragmentType": "LinkedMeasuresCardFragment";
|
||||
};
|
||||
export type LinkedMeasuresCardFragment$key = {
|
||||
readonly " $data"?: LinkedMeasuresCardFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "LinkedMeasuresCardFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Measure",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "741a216c02732c1ff97b265ac8dbf39b";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6a9b2c4428ed2255dda81771894c0c6c>>
|
||||
* @generated SignedSource<<0b725ea8709fbf5da3f17545fcd2b8c4>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -88,6 +88,13 @@ v4 = [
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -102,13 +109,6 @@ v4 = [
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -244,7 +244,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "13eb493b76ecc90bc9dd27963b5dba84",
|
||||
"cacheID": "54fff8a0c07ebe02ef34f3bb35e7582e",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -261,11 +261,11 @@ return {
|
||||
},
|
||||
"name": "MeasureLinkDialogQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MeasureLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n measures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query MeasureLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n measures(first: 100) {\n edges {\n node {\n id\n name\n state\n description\n category\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "468d35ade53ae7655cd0fbab242b7d1a";
|
||||
(node as any).hash = "cf01fa3a766badd4f6150e1a544403ba";
|
||||
|
||||
export default node;
|
||||
@@ -1,48 +1,4 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Option,
|
||||
Select,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useLazyLoadQuery, useMutation } from "react-relay";
|
||||
import type {
|
||||
MeasureLinkDialogQuery,
|
||||
MeasureLinkDialogQuery$data,
|
||||
} from "./__generated__/MeasureLinkDialogQuery.graphql";
|
||||
import type { NodeOf } from "/types";
|
||||
|
||||
const measuresQuery = graphql`
|
||||
query MeasureLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
measures(first: 100) @connection(key: "Organization__measures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/*
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation MeasureLinkDialogCreateMutation(
|
||||
$input: CreateRiskMeasureMappingInput!
|
||||
@@ -72,132 +28,4 @@ export const detachMeasureMutation = graphql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
trigger: ReactNode;
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
riskId: string;
|
||||
linkedIds?: Set<string>;
|
||||
};
|
||||
|
||||
export function MeasureLinkDialog({ trigger, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={trigger} title={__("Manage Risk Measures")}>
|
||||
<DialogContent className="px-6">
|
||||
<p className="text-sm text-txt-secondary mt-6">
|
||||
{__("Link or unlink measures to manage this risk.")}
|
||||
</p>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<MeasureLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureLinkDialogContent(props: Omit<Props, "trigger">) {
|
||||
const data = useLazyLoadQuery<MeasureLinkDialogQuery>(measuresQuery, {
|
||||
organizationId: props.organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const measures =
|
||||
data.organization?.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
|
||||
const filteredMeasures = useMemo(() => {
|
||||
return measures.filter(
|
||||
(measure) =>
|
||||
(category === null || measure.category === category) &&
|
||||
(measure.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
measure.description?.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
}, [measures, search, category]);
|
||||
|
||||
const categories = useMemo(
|
||||
() => Array.from(new Set(measures.map((m) => m.category))),
|
||||
[measures]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 sticky top-0 relative py-4 bg-linear-to-b from-50% from-level-2 to-level-2/0">
|
||||
<Input
|
||||
icon={IconMagnifyingGlass}
|
||||
placeholder={__("Search measures...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<Select
|
||||
value={category ?? ""}
|
||||
placeholder={__("All categories")}
|
||||
onValueChange={setCategory}
|
||||
className="max-w-[180px]"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<Option key={category} value={category}>
|
||||
{category}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredMeasures.map((measure) => (
|
||||
<MeasureRow key={measure.id} measure={measure} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
measure: NodeOf<
|
||||
Required<MeasureLinkDialogQuery$data["organization"]>["measures"]
|
||||
>;
|
||||
} & Omit<Props, "trigger">;
|
||||
|
||||
function MeasureRow(props: RowProps) {
|
||||
const isLinked = props.linkedIds?.has(props.measure.id) ?? false;
|
||||
const { __ } = useTranslate();
|
||||
const [attachMeasure, isFetchingAttach] = useMutation(attachMeasureMutation);
|
||||
const [detachMeasure, isFetchingDetach] = useMutation(detachMeasureMutation);
|
||||
|
||||
const isFetching = isFetchingAttach || isFetchingDetach;
|
||||
|
||||
const onClick = () => {
|
||||
if (isFetching) {
|
||||
return;
|
||||
}
|
||||
const action = isLinked ? detachMeasure : attachMeasure;
|
||||
action({
|
||||
variables: {
|
||||
input: {
|
||||
riskId: props.riskId,
|
||||
measureId: props.measure.id,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="py-4 flex items-center gap-4 hover:bg-subtle cursor-pointer"
|
||||
onClick={onClick}
|
||||
>
|
||||
{props.measure.name}
|
||||
<Badge variant="neutral">{props.measure.category}</Badge>
|
||||
<Button
|
||||
disabled={isFetching}
|
||||
icon={isLinked ? IconTrashCan : IconPlusLarge}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
>
|
||||
{isLinked ? __("Unlink") : __("Link")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<043e8cc5beda1ac8949a7c02adcff4bf>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
export type CreateRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateRiskMeasureMappingInput;
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation$data = {
|
||||
readonly createRiskMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation = {
|
||||
response: MeasureLinkDialogCreateMutation$data;
|
||||
variables: MeasureLinkDialogCreateMutation$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,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "309f3c90d91577e2f1c95ea69848ce5a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation MeasureLinkDialogCreateMutation(\n $input: CreateRiskMeasureMappingInput!\n) {\n createRiskMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n name\n description\n category\n state\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "bd012f06e39d5a04f4c1a2d2026f497e";
|
||||
|
||||
export default node;
|
||||
@@ -70,3 +70,15 @@ export const useDeleteFrameworkMutation = (
|
||||
);
|
||||
}, [framework, connectionId, commitDelete]);
|
||||
};
|
||||
|
||||
export const frameworkNodeQuery = graphql`
|
||||
query FrameworkGraphNodeQuery($frameworkId: ID!) {
|
||||
node(id: $frameworkId) {
|
||||
... on Framework {
|
||||
id
|
||||
name
|
||||
...FrameworkDetailPageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
407
apps/console2/src/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql.ts
generated
Normal file
407
apps/console2/src/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* @generated SignedSource<<64acca659bff60ca637937d933fc839f>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworkGraphNodeQuery$variables = {
|
||||
frameworkId: string;
|
||||
};
|
||||
export type FrameworkGraphNodeQuery$data = {
|
||||
readonly node: {
|
||||
readonly id?: string;
|
||||
readonly name?: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworkDetailPageFragment">;
|
||||
};
|
||||
};
|
||||
export type FrameworkGraphNodeQuery = {
|
||||
response: FrameworkGraphNodeQuery$data;
|
||||
variables: FrameworkGraphNodeQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "frameworkId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "frameworkId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "FrameworkDetailPageFragment"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "ControlConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "controls",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "ControlEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Control",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "referenceId",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "measures",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": "measures(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "FrameworkDetailPage_measures",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measures"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "documents",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": "documents(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "FrameworkDetailPage_documents",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documents"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "controls(first:100)"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "b10b304da4ff5b00e15b881ee486e8fe",
|
||||
"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"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "69a32dc8fbcaefbbaeb02dd2b89752c4";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<18341e1d2a7f480ad73ef32506121c5e>>
|
||||
* @generated SignedSource<<dfb9e4b341ccc895fce60e2de5287ec1>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -108,14 +108,7 @@ v8 = {
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = [
|
||||
v9 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
@@ -195,7 +188,13 @@ return {
|
||||
(v4/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -240,7 +239,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v9/*: any*/),
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "measures",
|
||||
@@ -264,15 +263,6 @@ return {
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -336,7 +326,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v9/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "Risk__measures",
|
||||
@@ -353,12 +343,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fc9da3522dfacf8b5f887a0ebbd3e21e",
|
||||
"cacheID": "295ecc0e99f46b7790fe04db0deed1d8",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "RiskGraphNodeQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n }\n id\n }\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\n measures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n"
|
||||
"text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n }\n id\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\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}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import {
|
||||
useFragment,
|
||||
useMutation,
|
||||
usePreloadedQuery,
|
||||
type PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { ControlItem, PageHeader } from "@probo/ui";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
import { frameworkNodeQuery } from "/hooks/graph/FrameworkGraph";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
|
||||
import { useParams } from "react-router";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type { FrameworkGraphNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql";
|
||||
import type { FrameworkDetailPageFragment$key } from "./__generated__/FrameworkDetailPageFragment.graphql";
|
||||
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
|
||||
|
||||
const frameworkDetailFragment = graphql`
|
||||
fragment FrameworkDetailPageFragment on Framework {
|
||||
id
|
||||
name
|
||||
description
|
||||
controls(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referenceId
|
||||
name
|
||||
description
|
||||
measures(first: 100)
|
||||
@connection(key: "FrameworkDetailPage_measures") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
documents(first: 100)
|
||||
@connection(key: "FrameworkDetailPage_documents") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...LinkedDocumentsCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation FrameworkDetailPageAttachMutation(
|
||||
$input: CreateControlMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createControlMeasureMapping(input: $input) {
|
||||
measureEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const detachMeasureMutation = graphql`
|
||||
mutation FrameworkDetailPageDetachMutation(
|
||||
$input: DeleteControlMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteControlMeasureMapping(input: $input) {
|
||||
deletedMeasureId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachDocumentMutation = graphql`
|
||||
mutation FrameworkDetailPageAttachDocumentMutation(
|
||||
$input: CreateControlDocumentMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createControlDocumentMapping(input: $input) {
|
||||
documentEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedDocumentsCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const detachDocumentMutation = graphql`
|
||||
mutation FrameworkDetailPageDetachDocumentMutation(
|
||||
$input: DeleteControlDocumentMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteControlDocumentMapping(input: $input) {
|
||||
deletedDocumentId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphNodeQuery>;
|
||||
};
|
||||
|
||||
export default function FrameworkDetailPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { controlId } = useParams<{ controlId?: string }>();
|
||||
const organizationId = useOrganizationId();
|
||||
const data = usePreloadedQuery(frameworkNodeQuery, props.queryRef);
|
||||
const framework = useFragment<FrameworkDetailPageFragment$key>(
|
||||
frameworkDetailFragment,
|
||||
data.node
|
||||
);
|
||||
|
||||
const [detachMeasure, isDetachingMeasure] = useMutation(
|
||||
detachMeasureMutation
|
||||
);
|
||||
const [attachMeasure, isAttachingMeasure] = useMutation(
|
||||
attachMeasureMutation
|
||||
);
|
||||
const [detachDocument, isDetachingDocument] = useMutation(
|
||||
detachDocumentMutation
|
||||
);
|
||||
const [attachDocument, isAttachingDocument] = useMutation(
|
||||
attachDocumentMutation
|
||||
);
|
||||
|
||||
const controls = framework.controls.edges.map((edge) => edge.node);
|
||||
|
||||
const selectedControl = controlId
|
||||
? controls.find((control) => control.id === controlId)
|
||||
: controls[0];
|
||||
usePageTitle(`${framework.name} | ${selectedControl?.referenceId}`);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title={
|
||||
<>
|
||||
<FrameworkLogo {...framework} />
|
||||
{framework.name}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="text-lg font-semibold">
|
||||
{__("Requirement categories")}
|
||||
</div>
|
||||
<div className="divide-x divide-border-low grid grid-cols-[264px_1fr]">
|
||||
<div
|
||||
className="space-y-1 overflow-y-auto pr-6 mr-6 sticky top-0"
|
||||
style={{ maxHeight: "calc(100vh - 48px)" }}
|
||||
>
|
||||
{controls.map((control) => (
|
||||
<ControlItem
|
||||
key={control.id}
|
||||
id={control.referenceId}
|
||||
description={control.name ?? control.description}
|
||||
to={`/organizations/${organizationId}/frameworks/${framework.id}/${control.id}`}
|
||||
active={selectedControl?.id === control.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectedControl ? (
|
||||
<div className="space-y-6">
|
||||
<div className="text-xl font-medium px-[6px] py-[2px] border border-border-low rounded-lg w-max bg-active mb-3">
|
||||
{selectedControl.referenceId}
|
||||
</div>
|
||||
<div className="text-base">{selectedControl.name}</div>
|
||||
<LinkedMeasuresCard
|
||||
measures={
|
||||
selectedControl?.measures.edges.map((edge) => edge.node) ?? []
|
||||
}
|
||||
params={{ controlId: selectedControl.id }}
|
||||
connectionId={selectedControl.measures.__id!}
|
||||
onAttach={attachMeasure}
|
||||
onDetach={detachMeasure}
|
||||
disabled={isAttachingMeasure || isDetachingMeasure}
|
||||
/>
|
||||
<LinkedDocumentsCard
|
||||
documents={
|
||||
selectedControl?.documents.edges.map((edge) => edge.node) ?? []
|
||||
}
|
||||
params={{ controlId: selectedControl.id }}
|
||||
connectionId={selectedControl.documents.__id!}
|
||||
onAttach={attachDocument}
|
||||
onDetach={detachDocument}
|
||||
disabled={isAttachingDocument || isDetachingDocument}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-sm text-txt-secondary">
|
||||
{__("No control selected")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
ActionDropdown,
|
||||
Avatar,
|
||||
Card,
|
||||
DropdownItem,
|
||||
FileButton,
|
||||
@@ -27,8 +26,8 @@ import { Link } from "react-router";
|
||||
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { useState, type ChangeEventHandler } from "react";
|
||||
import { availableFrameworks } from "@probo/helpers";
|
||||
import { CreateFrameworkDialog } from "./dialogs/CreateFrameworkDialog";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphListQuery>;
|
||||
@@ -50,10 +49,6 @@ const importFrameworkMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const availableLogos = new Map(
|
||||
availableFrameworks.map((framework) => [framework.name, framework.logo])
|
||||
);
|
||||
|
||||
export default function FrameworksPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
usePageTitle(__("Frameworks"));
|
||||
@@ -182,15 +177,10 @@ function FrameworkCard(props: FrameworkCardProps) {
|
||||
props.connectionId
|
||||
);
|
||||
const { __ } = useTranslate();
|
||||
const logo = availableLogos.get(framework.name);
|
||||
return (
|
||||
<Card padded className="p-6 bg-white rounded shadow relative">
|
||||
<div className="flex justify-between mb-3">
|
||||
{logo ? (
|
||||
<img src={logo} alt="" className="size-12" />
|
||||
) : (
|
||||
<Avatar name={framework.name} size="l" className="size-12" />
|
||||
)}
|
||||
<FrameworkLogo {...framework} />
|
||||
<ActionDropdown className="z-10 relative">
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* @generated SignedSource<<174d7c77d0d858ecc59261174b424af2>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateControlDocumentMappingInput = {
|
||||
controlId: string;
|
||||
documentId: string;
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateControlDocumentMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation$data = {
|
||||
readonly createControlDocumentMapping: {
|
||||
readonly documentEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation = {
|
||||
response: FrameworkDetailPageAttachDocumentMutation$data;
|
||||
variables: FrameworkDetailPageAttachDocumentMutation$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": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "documentEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedDocumentsCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "documentEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documentEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9100dabaa4861920eab52dc47e551b06",
|
||||
"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"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "26c53db439feba2b2d65c3e3a9a973c7";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @generated SignedSource<<81d008fc4116a78a92e2d925f8d6daa7>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateControlMeasureMappingInput = {
|
||||
controlId: string;
|
||||
measureId: string;
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateControlMeasureMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation$data = {
|
||||
readonly createControlMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation = {
|
||||
response: FrameworkDetailPageAttachMutation$data;
|
||||
variables: FrameworkDetailPageAttachMutation$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": "FrameworkDetailPageAttachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageAttachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"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": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9fcb599cd78f3108d6d71aa571d7005d",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageAttachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageAttachMutation(\n $input: CreateControlMeasureMappingInput!\n) {\n createControlMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "d3834b8c2656d347e13189469894be61";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @generated SignedSource<<6f2850042c0413963bd82f90236fe220>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteControlDocumentMappingInput = {
|
||||
controlId: string;
|
||||
documentId: string;
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteControlDocumentMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation$data = {
|
||||
readonly deleteControlDocumentMapping: {
|
||||
readonly deletedDocumentId: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation = {
|
||||
response: FrameworkDetailPageDetachDocumentMutation$data;
|
||||
variables: FrameworkDetailPageDetachDocumentMutation$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": "deletedDocumentId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedDocumentId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "53618baa1678e3a1b496a08597bdd364",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageDetachDocumentMutation(\n $input: DeleteControlDocumentMappingInput!\n) {\n deleteControlDocumentMapping(input: $input) {\n deletedDocumentId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "233b182104744a67a21d21d60b6df67a";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @generated SignedSource<<ff996c0902be9e7b8c23c9dd3d0f3759>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteControlMeasureMappingInput = {
|
||||
controlId: string;
|
||||
measureId: string;
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteControlMeasureMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation$data = {
|
||||
readonly deleteControlMeasureMapping: {
|
||||
readonly deletedMeasureId: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation = {
|
||||
response: FrameworkDetailPageDetachMutation$data;
|
||||
variables: FrameworkDetailPageDetachMutation$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": "deletedMeasureId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedMeasureId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "0bede5dc29caa4e2aba9b0dd96e58014",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageDetachMutation(\n $input: DeleteControlMeasureMappingInput!\n) {\n deleteControlMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "96945bf8e909e7425af2297f7ee6ea0a";
|
||||
|
||||
export default node;
|
||||
288
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworkDetailPageFragment.graphql.ts
generated
Normal file
288
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworkDetailPageFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* @generated SignedSource<<15cc2290d512a0cdde5241edd045d3f5>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworkDetailPageFragment$data = {
|
||||
readonly controls: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly description: string;
|
||||
readonly documents: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
readonly measures: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly name: string;
|
||||
readonly referenceId: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly " $fragmentType": "FrameworkDetailPageFragment";
|
||||
};
|
||||
export type FrameworkDetailPageFragment$key = {
|
||||
readonly " $data"?: FrameworkDetailPageFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworkDetailPageFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": null
|
||||
},
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": {
|
||||
"connection": [
|
||||
(v0/*: any*/),
|
||||
(v0/*: any*/)
|
||||
]
|
||||
},
|
||||
"name": "FrameworkDetailPageFragment",
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
"concreteType": "ControlConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "controls",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "ControlEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Control",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "referenceId",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": "measures",
|
||||
"args": null,
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__FrameworkDetailPage_measures_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "documents",
|
||||
"args": null,
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__FrameworkDetailPage_documents_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedDocumentsCardFragment"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "controls(first:100)"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "15b523ca06d1a1e1a80379f6d51b92e2";
|
||||
|
||||
export default node;
|
||||
@@ -67,7 +67,6 @@ export function CreateFrameworkDialog(props: Props) {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
defaultOpen
|
||||
ref={props.ref}
|
||||
title={<Breadcrumb items={[__("Framework"), __("New Framework")]} />}
|
||||
>
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
import { graphql, useFragment, useMutation } from "react-relay";
|
||||
import type {
|
||||
RiskMeasuresTabFragment$data,
|
||||
RiskMeasuresTabFragment$key,
|
||||
} from "./__generated__/RiskMeasuresTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Button,
|
||||
IconPlusLarge,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Tr,
|
||||
IconTrashCan,
|
||||
} from "@probo/ui";
|
||||
import {
|
||||
detachMeasureMutation,
|
||||
MeasureLinkDialog,
|
||||
} from "/components/risks/MeasureLinkDialog";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type { RiskMeasuresTabFragment$key } from "./__generated__/RiskMeasuresTabFragment.graphql";
|
||||
import { useOutletContext } from "react-router";
|
||||
import type { NodeOf } from "/types";
|
||||
import { useMemo } from "react";
|
||||
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
|
||||
|
||||
const measuresFragment = graphql`
|
||||
fragment RiskMeasuresTabFragment on Risk {
|
||||
@@ -32,127 +11,60 @@ const measuresFragment = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
createdAt
|
||||
state
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation RiskMeasuresTabCreateMutation(
|
||||
$input: CreateRiskMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createRiskMeasureMapping(input: $input) {
|
||||
measureEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const detachMeasureMutation = graphql`
|
||||
mutation RiskMeasuresTabDetachMutation(
|
||||
$input: DeleteRiskMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteRiskMeasureMapping(input: $input) {
|
||||
deletedMeasureId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function RiskMeasuresTab() {
|
||||
const { risk } = useOutletContext<{
|
||||
risk: RiskMeasuresTabFragment$key & { id: string };
|
||||
}>();
|
||||
const data = useFragment(measuresFragment, risk);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const connectionId = data.measures.__id;
|
||||
const measures = data.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(
|
||||
() => new Set(measures.map((measure) => measure.id)),
|
||||
[measures]
|
||||
);
|
||||
|
||||
if (measures.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-txt-secondary text-center flex flex-col gap-4 items-center justify-center py-10">
|
||||
{__("No measures associated with this risk.")}
|
||||
<MeasureLinkDialog
|
||||
linkedIds={linkedIds}
|
||||
connectionId={connectionId}
|
||||
organizationId={organizationId}
|
||||
riskId={data.id}
|
||||
trigger={
|
||||
<Button icon={IconPlusLarge} variant="quaternary">
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const [detachMeasure, isDetaching] = useMutation(detachMeasureMutation);
|
||||
const [attachMeasure, isAttaching] = useMutation(attachMeasureMutation);
|
||||
const isLoading = isDetaching || isAttaching;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<MeasureLinkDialog
|
||||
linkedIds={linkedIds}
|
||||
connectionId={connectionId}
|
||||
organizationId={organizationId}
|
||||
riskId={data.id}
|
||||
trigger={
|
||||
<Button
|
||||
icon={IconPlusLarge}
|
||||
variant="primary"
|
||||
className="absolute -top-18 right-0"
|
||||
>
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Measure")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{measures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
riskId={data.id}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureRow({
|
||||
measure,
|
||||
riskId,
|
||||
connectionId,
|
||||
}: {
|
||||
measure: NodeOf<RiskMeasuresTabFragment$data["measures"]>;
|
||||
riskId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const [detachMeasure, isFetching] = useMutation(detachMeasureMutation);
|
||||
|
||||
const onClick = () => {
|
||||
detachMeasure({
|
||||
variables: {
|
||||
input: {
|
||||
riskId: riskId,
|
||||
measureId: measure.id,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Tr>
|
||||
<Td>{measure.name}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
disabled={isFetching}
|
||||
icon={IconTrashCan}
|
||||
className="ml-auto"
|
||||
variant="quaternary"
|
||||
onClick={onClick}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
<LinkedMeasuresCard
|
||||
disabled={isLoading}
|
||||
measures={measures}
|
||||
onAttach={attachMeasure}
|
||||
onDetach={detachMeasure}
|
||||
params={{ riskId: data.id }}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
202
apps/console2/src/pages/organizations/risks/tabs/__generated__/RiskMeasuresTabCreateMutation.graphql.ts
generated
Normal file
202
apps/console2/src/pages/organizations/risks/tabs/__generated__/RiskMeasuresTabCreateMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @generated SignedSource<<6e27a069ae5675b9c83e369566a91d01>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateRiskMeasureMappingInput;
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation$data = {
|
||||
readonly createRiskMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation = {
|
||||
response: RiskMeasuresTabCreateMutation$data;
|
||||
variables: RiskMeasuresTabCreateMutation$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": "RiskMeasuresTabCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "RiskMeasuresTabCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"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": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3b141d4117959b1f6efed67e8aa655cf",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "RiskMeasuresTabCreateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation RiskMeasuresTabCreateMutation(\n $input: CreateRiskMeasureMappingInput!\n) {\n createRiskMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "982d31266eda233518c48f1fd0cd2f46";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<327cdb825c0029c21d8812b782ecf14f>>
|
||||
* @generated SignedSource<<c34aab708f8713173639ae4b7aacdf10>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -13,18 +13,18 @@ export type DeleteRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation$variables = {
|
||||
export type RiskMeasuresTabDetachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteRiskMeasureMappingInput;
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation$data = {
|
||||
export type RiskMeasuresTabDetachMutation$data = {
|
||||
readonly deleteRiskMeasureMapping: {
|
||||
readonly deletedMeasureId: string;
|
||||
};
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation = {
|
||||
response: MeasureLinkDialogDetachMutation$data;
|
||||
variables: MeasureLinkDialogDetachMutation$variables;
|
||||
export type RiskMeasuresTabDetachMutation = {
|
||||
response: RiskMeasuresTabDetachMutation$data;
|
||||
variables: RiskMeasuresTabDetachMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
@@ -60,7 +60,7 @@ return {
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -85,7 +85,7 @@ return {
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -118,16 +118,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "8a54f580a892f60f24c65ec927c7340c",
|
||||
"cacheID": "81260f49b2f31c4a12f87b38bab7a683",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation MeasureLinkDialogDetachMutation(\n $input: DeleteRiskMeasureMappingInput!\n) {\n deleteRiskMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
"text": "mutation RiskMeasuresTabDetachMutation(\n $input: DeleteRiskMeasureMappingInput!\n) {\n deleteRiskMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e02a17e00e2c07881dcae17fef9513eb";
|
||||
(node as any).hash = "c5adc6d900f0da15ec3acf7db304d410";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<62ef6617862f528f26edaffe3adc3069>>
|
||||
* @generated SignedSource<<5a37b1ce3c0c17ce36fc3e1157eadf8e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type RiskMeasuresTabFragment$data = {
|
||||
readonly id: string;
|
||||
@@ -17,12 +16,8 @@ export type RiskMeasuresTabFragment$data = {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly createdAt: any;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -85,39 +80,9 @@ return {
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
@@ -185,6 +150,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e04c5962a40b48aade0d1e4a31f0c583";
|
||||
(node as any).hash = "e928c83f7fea74daa5663d6f80518419";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { loadQuery } from "react-relay";
|
||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||
import { relayEnvironment } from "/providers/RelayProviders";
|
||||
import { frameworksQuery } from "/hooks/graph/FrameworkGraph";
|
||||
import {
|
||||
frameworksQuery,
|
||||
frameworkNodeQuery,
|
||||
} from "/hooks/graph/FrameworkGraph";
|
||||
import type { AppRoute } from "/routes";
|
||||
import { lazy } from "react";
|
||||
|
||||
@@ -15,4 +18,13 @@ export const frameworkRoutes = [
|
||||
() => import("/pages/organizations/frameworks/FrameworksPage")
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "frameworks/:frameworkId/:controlId?",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ frameworkId }) =>
|
||||
loadQuery(relayEnvironment, frameworkNodeQuery, { frameworkId }),
|
||||
Component: lazy(
|
||||
() => import("/pages/organizations/frameworks/FrameworkDetailPage")
|
||||
),
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
27
packages/ui/src/Atoms/ControlItem/ControlItem.stories.tsx
Normal file
27
packages/ui/src/Atoms/ControlItem/ControlItem.stories.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ControlItem } from "./ControlItem";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
export default {
|
||||
title: "Atoms/ControlItem",
|
||||
component: ControlItem,
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof ControlItem>;
|
||||
|
||||
type Story = StoryObj<typeof ControlItem>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
id: "CC1.1",
|
||||
description:
|
||||
"The entity obtains privacy commitments from vendors and other third parties who have access to personal information to meet the entity’s objectives related to privacy. The entity assesses those parties’ compliance on a periodic and as-needed basis and takes corrective action, if necessary.",
|
||||
},
|
||||
render: (args) => (
|
||||
<div className="p-4 space-y-2" style={{ width: "240px" }}>
|
||||
<ControlItem {...args} />
|
||||
<ControlItem {...args} active />
|
||||
<ControlItem {...args} />
|
||||
<ControlItem {...args} />
|
||||
<ControlItem {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
49
packages/ui/src/Atoms/ControlItem/ControlItem.tsx
Normal file
49
packages/ui/src/Atoms/ControlItem/ControlItem.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { HTMLAttributes } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { tv } from "tailwind-variants";
|
||||
|
||||
type Props = {
|
||||
active?: boolean;
|
||||
id: string;
|
||||
description?: string;
|
||||
to: string;
|
||||
} & HTMLAttributes<HTMLAnchorElement>;
|
||||
|
||||
const classNames = tv({
|
||||
slots: {
|
||||
wrapper: "block p-4 space-y-[6px] rounded-xl cursor-pointer text-start",
|
||||
id: "px-[6px] py-[2px] text-base font-medium border border-border-low rounded-lg w-max",
|
||||
description: "text-sm text-txt-tertiary line-clamp-3",
|
||||
},
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
wrapper: "bg-tertiary-pressed",
|
||||
id: "bg-active",
|
||||
},
|
||||
false: {
|
||||
wrapper: "hover:bg-tertiary-hover",
|
||||
id: "bg-highlight",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
export function ControlItem({ active, id, description, to, ...props }: Props) {
|
||||
const {
|
||||
wrapper,
|
||||
id: idCls,
|
||||
description: descriptionCls,
|
||||
} = classNames({
|
||||
active,
|
||||
});
|
||||
return (
|
||||
<Link className={wrapper()} to={to} {...props}>
|
||||
<div className={idCls()}>{id}</div>
|
||||
<div className={descriptionCls()}>{description}</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ type Story = StoryObj<typeof Markdown>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: `# Siquis facies post
|
||||
content: `# Siquis facies post
|
||||
|
||||
## Venit Ianigenam egressus a tamen terra frater
|
||||
|
||||
|
||||
@@ -18,13 +18,6 @@ export const Default: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
|
||||
@@ -12,9 +12,6 @@ export default {
|
||||
type Story = StoryObj<typeof Combobox>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
open: true,
|
||||
},
|
||||
render: () => {
|
||||
const [items, setItems] = useState(["a", "b", "c"] as string[]);
|
||||
const onSearch = (query: string) => {
|
||||
@@ -22,10 +19,6 @@ export const Default: Story = {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
Nostrum labore repellat facere voluptatum, in voluptatem eaque
|
||||
quidem nam quod nesciunt repudiandae a illo non placeat nulla.
|
||||
Ratione ipsa at sint?
|
||||
<Combobox onSearch={onSearch}>
|
||||
{items.map((item) => (
|
||||
<ComboboxItem key={item}>{item}</ComboboxItem>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Button } from "../../Atoms/Button/Button";
|
||||
import { ConfirmDialog } from "./ConfirmDialog";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
export default {
|
||||
title: "Atoms/ConfirmDialog",
|
||||
component: ConfirmDialog,
|
||||
argTypes: {},
|
||||
} satisfies Meta<typeof ConfirmDialog>;
|
||||
|
||||
type Story = StoryObj<typeof ConfirmDialog>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
message:
|
||||
'This will permanently delete the risk "Demo". This action cannot be undone.',
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<ConfirmDialog
|
||||
message="Are you sure you want to delete this risk?"
|
||||
onConfirm={() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button variant="danger">Delete this</Button>
|
||||
</ConfirmDialog>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -15,7 +15,6 @@ export const Default: Story = {
|
||||
return (
|
||||
<Dialog
|
||||
{...args}
|
||||
onClose={() => {}}
|
||||
trigger={<Button>Open dialog</Button>}
|
||||
title="Edit profile"
|
||||
>
|
||||
|
||||
@@ -9,7 +9,9 @@ export function PageHeader({ title, description, children }: Props) {
|
||||
return (
|
||||
<div className="flex justify-between items-start w-full">
|
||||
<div className=" space-y-1">
|
||||
<h1 className="text-2xl ">{title}</h1>
|
||||
<h1 className="text-2xl flex gap-4 font-semibold items-center">
|
||||
{title}
|
||||
</h1>
|
||||
{description && (
|
||||
<p className="text-sm text-txt-secondary">{description}</p>
|
||||
)}
|
||||
|
||||
@@ -33,6 +33,7 @@ export { Table, Thead, Tr, Tbody, Td, Th } from "./Atoms/Table/Table";
|
||||
export { Tabs, TabLink } from "./Atoms/Tabs/Tabs";
|
||||
export { Markdown } from "./Atoms/Markdown/Markdown";
|
||||
export { Dropzone } from "./Atoms/Dropzone/Dropzone";
|
||||
export { ControlItem } from "./Atoms/ControlItem/ControlItem";
|
||||
|
||||
// Molecules
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user