Add document type field

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Jonathan
2025-06-02 20:40:38 +02:00
committed by Sacha Al Himdani
parent 7a65ec8672
commit 7d2d26aab5
20 changed files with 166 additions and 253 deletions

View File

@@ -3,6 +3,7 @@ import {
Dialog,
DialogContent,
DialogFooter,
DocumentTypeBadge,
IconMagnifyingGlass,
IconPlusLarge,
IconTrashCan,
@@ -13,8 +14,12 @@ import { useTranslate } from "@probo/i18n";
import { Suspense, useMemo, useState, type ReactNode } from "react";
import { graphql } from "relay-runtime";
import { useLazyLoadQuery } from "react-relay";
import type { DocumentLinkDialogQuery } from "./__generated__/DocumentLinkDialogQuery.graphql";
import type {
DocumentLinkDialogQuery,
DocumentLinkDialogQuery$data,
} from "./__generated__/DocumentLinkDialogQuery.graphql";
import { useOrganizationId } from "/hooks/useOrganizationId";
import type { NodeOf } from "/types";
const documentsQuery = graphql`
query DocumentLinkDialogQuery($organizationId: ID!) {
@@ -26,6 +31,7 @@ const documentsQuery = graphql`
node {
id
title
documentType
}
}
}
@@ -102,8 +108,12 @@ function DocumentLinkDialogContent(props: Omit<Props, "children">) {
);
}
type Document = NodeOf<
DocumentLinkDialogQuery$data["organization"]["documents"]
>;
type RowProps = {
document: { title: string; id: string };
document: Document;
linkedDocuments: Set<string>;
disabled?: boolean;
onLink: (documentId: string) => void;
@@ -123,6 +133,7 @@ function DocumentRow(props: RowProps) {
onClick={() => onClick(props.document.id)}
>
{props.document.title}
<DocumentTypeBadge type={props.document.documentType} />
<Button
disabled={props.disabled}
className="ml-auto"

View File

@@ -12,6 +12,7 @@ import {
IconChevronDown,
IconTrashCan,
DocumentVersionBadge,
DocumentTypeBadge,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
@@ -26,6 +27,7 @@ const linkedDocumentFragment = graphql`
id
title
createdAt
documentType
versions(first: 1) {
edges {
node {
@@ -117,6 +119,7 @@ export function LinkedDocumentsCard<Params>(props: Props<Params>) {
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Type")}</Th>
<Th>{__("State")}</Th>
<Th></Th>
</Tr>
@@ -172,6 +175,9 @@ function DocumentRow(props: {
{document.title}
</div>
</Td>
<Td>
<DocumentTypeBadge type={document.documentType} />
</Td>
<Td>
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
</Td>

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<dfc2f49ef0ca7d3a3e62809aef61dfe6>>
* @generated SignedSource<<ec5910871c59f229db361be238007976>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,6 +9,7 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
export type DocumentLinkDialogQuery$variables = {
organizationId: string;
};
@@ -17,6 +18,7 @@ export type DocumentLinkDialogQuery$data = {
readonly documents?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
};
@@ -84,6 +86,13 @@ v4 = [
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
@@ -219,7 +228,7 @@ return {
]
},
"params": {
"cacheID": "fa671324c6ab818f40ccf9f83ca9af8a",
"cacheID": "0905fffe448540bf9c3d8abcbb5b7583",
"id": null,
"metadata": {
"connection": [
@@ -236,11 +245,11 @@ return {
},
"name": "DocumentLinkDialogQuery",
"operationKind": "query",
"text": "query DocumentLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n documents(first: 100) {\n edges {\n node {\n id\n title\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query DocumentLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n documents(first: 100) {\n edges {\n node {\n id\n title\n documentType\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "70a09260215ae0f74ccf06a0c0872e75";
(node as any).hash = "6994e268de74f2102f48dc3e8156a797";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<da0435f63c82413be14bb2bb444aad49>>
* @generated SignedSource<<743c5a0c4d380bd05c0d8ccd7a664e58>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,9 +10,11 @@
import { ReaderFragment } from 'relay-runtime';
export type DocumentStatus = "DRAFT" | "PUBLISHED";
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
import { FragmentRefs } from "relay-runtime";
export type LinkedDocumentsCardFragment$data = {
readonly createdAt: any;
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
readonly versions: {
@@ -59,6 +61,13 @@ return {
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
{
"alias": null,
"args": [
@@ -112,6 +121,6 @@ return {
};
})();
(node as any).hash = "907fbdc23df596e4f76e396a11878706";
(node as any).hash = "952e653849ce0b1b693de8d7e3086e3f";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<64acca659bff60ca637937d933fc839f>>
* @generated SignedSource<<9d5d654dbc062e8a2a82adeed4c0839e>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -304,6 +304,13 @@ return {
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
{
"alias": null,
"args": [
@@ -392,12 +399,12 @@ return {
]
},
"params": {
"cacheID": "b10b304da4ff5b00e15b881ee486e8fe",
"cacheID": "9af171f23ae160b6725af13fdebaa07a",
"id": null,
"metadata": {},
"name": "FrameworkGraphNodeQuery",
"operationKind": "query",
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
}
};
})();

View File

@@ -140,7 +140,7 @@ function UserDropdown() {
label={__("Settings")}
/>
<UserDropdownItem
to="/settings"
to="mailto:support@getprobo.com"
icon={IconCircleQuestionmark}
label={__("Help")}
/>

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<174d7c77d0d858ecc59261174b424af2>>
* @generated SignedSource<<7d4a807c299030c97164951cbe8ef067>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -159,6 +159,13 @@ return {
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
{
"alias": null,
"args": [
@@ -234,12 +241,12 @@ return {
]
},
"params": {
"cacheID": "9100dabaa4861920eab52dc47e551b06",
"cacheID": "968bd0beeabab861ca610804640e5e11",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageAttachDocumentMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
}
};
})();

View File

@@ -1,203 +0,0 @@
/**
* @generated SignedSource<<a91c1ee7ea4934d7d6e524f32462832a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CreateFrameworkInput = {
description: string;
name: string;
organizationId: string;
};
export type CreateFrameworkDialogMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateFrameworkInput;
};
export type CreateFrameworkDialogMutation$data = {
readonly createFramework: {
readonly frameworkEdge: {
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
};
};
};
};
export type CreateFrameworkDialogMutation = {
response: CreateFrameworkDialogMutation$data;
variables: CreateFrameworkDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "CreateFrameworkDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateFrameworkPayload",
"kind": "LinkedField",
"name": "createFramework",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FrameworkEdge",
"kind": "LinkedField",
"name": "frameworkEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "FrameworksPageCardFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "CreateFrameworkDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateFrameworkPayload",
"kind": "LinkedField",
"name": "createFramework",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FrameworkEdge",
"kind": "LinkedField",
"name": "frameworkEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "frameworkEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "d08bc5a14bbf8d131029da890bdab850",
"id": null,
"metadata": {},
"name": "CreateFrameworkDialogMutation",
"operationKind": "mutation",
"text": "mutation CreateFrameworkDialogMutation(\n $input: CreateFrameworkInput!\n) {\n createFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
}
};
})();
(node as any).hash = "957d00afa55875c9a6879292e8fa7bb1";
export default node;

View File

@@ -37,12 +37,20 @@ import type {
MeasuresPageFragment$data,
MeasuresPageFragment$key,
} from "./__generated__/MeasuresPageFragment.graphql";
import { groupBy, objectKeys, sprintf } from "@probo/helpers";
import { useMemo, useRef, useState, type ChangeEventHandler } from "react";
import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers";
import {
useEffect,
useMemo,
useRef,
useState,
type ChangeEventHandler,
} from "react";
import type { NodeOf } from "/types";
import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useToggle } from "@probo/hooks";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { Link, useParams } from "react-router";
type Props = {
queryRef: PreloadedQuery<MeasureGraphListQuery>;
@@ -170,13 +178,16 @@ type CategoryProps = {
};
function Category(props: CategoryProps) {
const params = useParams<{ categoryId?: string }>();
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const categoryId = slugify(props.category);
const [limit, setLimit] = useState<number | null>(4);
const measures = useMemo(() => {
return limit ? props.measures.slice(0, limit) : props.measures;
}, [props.measures, limit]);
const showMoreButton = limit !== null && props.measures.length > limit;
const [isExpanded, toggleExpanded] = useToggle(false);
const isExpanded = categoryId === params.categoryId;
const ExpandComponent = isExpanded ? IconChevronUp : IconChevronDown;
const completedMeasures = props.measures.filter(
(m) => m.state === "IMPLEMENTED"
@@ -184,9 +195,9 @@ function Category(props: CategoryProps) {
return (
<Card className="py-3 px-5">
<div
<Link
to={`/organizations/${organizationId}/measures/category/${categoryId}`}
className="flex items-center justify-between cursor-pointer"
onClick={toggleExpanded}
>
<h2 className="text-base font-medium">{props.category}</h2>
<div className="flex items-center gap-3 text-sm text-txt-secondary">
@@ -199,7 +210,7 @@ function Category(props: CategoryProps) {
<span className="text-border-low">|</span>
<ExpandComponent size={16} className="text-txt-secondary" />
</div>
</div>
</Link>
{isExpanded && (
<div className="mt-3">
<Table className="bg-invert">
@@ -245,6 +256,7 @@ function MeasureRow(props: MeasureRowProps) {
const { __ } = useTranslate();
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
const confirm = useConfirm();
const organizationId = useOrganizationId();
const onDelete = () => {
confirm(
@@ -271,12 +283,12 @@ function MeasureRow(props: MeasureRowProps) {
return (
<>
<Tr>
<Tr to={`/organizations/${organizationId}/measures/${props.measure.id}`}>
<Td>{props.measure.name}</Td>
<Td width={120}>
<MeasureBadge state={props.measure.state} />
</Td>
<Td width={50} className="text-end">
<Td noLink width={50} className="text-end">
<ActionDropdown>
<DropdownItem icon={IconPencil}>{__("Edit")}</DropdownItem>
<DropdownItem

View File

@@ -24,7 +24,6 @@ import { usePageTitle } from "@probo/hooks";
import { getRole } from "@probo/helpers";
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { Link } from "react-router";
type People = NodeOf<PeopleGraphPaginatedFragment$data["peoples"]>;
@@ -99,14 +98,6 @@ function PeopleRow({
<Td className="text-sm">{getRole(__, people.kind)}</Td>
<Td noLink width={50} className="text-end">
<ActionDropdown>
<DropdownItem asChild>
<Link
to={`/organizations/${organizationId}/people/${people.id}/tasks`}
>
<IconPencil size={16} />
{__("Edit")}
</Link>
</DropdownItem>
<DropdownItem
icon={IconTrashCan}
variant="danger"

View File

@@ -142,7 +142,6 @@ function CertificationInput({
return (
<div className="flex items-center gap-2">
{search}
<Combobox
autoSelect
resetValueOnHide

View File

@@ -1,16 +1,27 @@
import { lazy } from "react";
import { Fragment, lazy } from "react";
import { loadQuery } from "react-relay";
import { RisksPageSkeleton } from "/components/skeletons/RisksPageSkeleton.tsx";
import type { AppRoute } from "/routes.tsx";
import { relayEnvironment } from "/providers/RelayProviders";
import { measuresQuery } from "/hooks/graph/MeasureGraph";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
export const measureRoutes = [
{
path: "measures",
fallback: RisksPageSkeleton,
fallback: PageSkeleton,
queryLoader: ({ organizationId }) =>
loadQuery(relayEnvironment, measuresQuery, { organizationId }),
Component: lazy(() => import("/pages/organizations/measures/MeasuresPage")),
children: [
{
path: "category/:categoryId",
Component: Fragment,
},
],
},
{
path: "measures/:measureId",
fallback: PageSkeleton,
Component: Fragment,
},
] satisfies AppRoute[];

View File

@@ -1,7 +1,8 @@
export type NodeOf<T> = T extends
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
| undefined
? U
: never;
export type NodeOf<T> =
NonNullable<T> extends
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
| undefined
? U
: never;
export type ItemOf<T> = T extends (infer U)[] ? U : never;

View File

@@ -0,0 +1,14 @@
type Translator = (s: string) => string;
export const documentTypes = ["OTHER", "ISMS", "POLICY"] as const;
export function getDocumentTypeLabel(__: Translator, type: string) {
switch (type) {
case "OTHER":
return __("Other");
case "ISMS":
return __("ISMS");
case "POLICY":
return __("Policy");
}
}

View File

@@ -1,5 +1,5 @@
export { objectKeys, objectEntries } from "./object";
export { sprintf, faviconUrl } from "./string";
export { sprintf, faviconUrl, slugify } from "./string";
export {
getTreatment,
getRiskImpacts,
@@ -13,3 +13,4 @@ export { getMeasureStateLabel, measureStates } from "./measure";
export { getRole, getRoles, peopleRoles } from "./people";
export { certificationCategoryLabel, certifications } from "./certifications";
export { availableFrameworks } from "./frameworks";
export { getDocumentTypeLabel, documentTypes } from "./documents";

View File

@@ -20,3 +20,13 @@ export function faviconUrl(url?: string | null): string | null {
return null;
}
}
export function slugify(str: string): string {
return str
.normalize("NFD") // split an accented letter in the base letter and the acent
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
.toLowerCase()
.trim()
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
.replace(/\s+/g, "-");
}

View File

@@ -25,15 +25,14 @@ const LayoutContext = createContext({
export function Layout({ header, sidebar, children }: Props) {
const [hasDrawer, setDrawer] = useState(false);
const layoutContext = useMemo(
() => ({
setDrawer,
}),
[],
);
return (
<LayoutContext
value={useMemo(
() => ({
setDrawer,
}),
[],
)}
>
<LayoutContext value={layoutContext}>
<div className="text-txt-primary bg-level-0">
<header className="absolute z-2 left-0 right-0 px-4 flex items-center border-b border-border-solid h-12 bg-level-1">
<Logo className="w-12 h-5" />

View File

@@ -0,0 +1,16 @@
import { DocumentTypeBadge } from "./DocumentTypeBadge";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "Molecules/Badges/DocumentTypeBadge",
component: DocumentTypeBadge,
argTypes: {},
} satisfies Meta<typeof DocumentTypeBadge>;
type Story = StoryObj<typeof DocumentTypeBadge>;
export const Default: Story = {
args: {
type: "OTHER",
},
};

View File

@@ -0,0 +1,12 @@
import { Badge } from "../../Atoms/Badge/Badge";
import { getDocumentTypeLabel } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
type Props = {
type: string;
};
export function DocumentTypeBadge({ type }: Props) {
const { __ } = useTranslate();
return <Badge variant="neutral">{getDocumentTypeLabel(__, type)}</Badge>;
}

View File

@@ -61,6 +61,7 @@ export { FileButton } from "./Molecules/FileButton/FileButton";
export { MeasureBadge } from "./Molecules/Badge/MeasureBadge";
export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation";
export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector";
export { DocumentTypeBadge } from "./Molecules/Badge/DocumentTypeBadge";
// Hooks
export { useToast, Toasts } from "./Atoms/Toasts/Toasts";