[PUI] Mantine tree (#7357)

* Refactor part category tree

- New "NavigationTree" using native mantine components
- Make it generic, too

* Replace existing <StockLocationTree /> component

* Adjust API filtering for location tree endpoint

* Added playwright tests

* Update api filter classes

* Fix for DetailsImage

- Update to @mantine/core had changed the <AspectRatio> component

* fix for identifierString function

* Adjust playwright tests
This commit is contained in:
Oliver 2024-05-28 14:24:21 +10:00 committed by GitHub
parent c90ee43808
commit f3223c6d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 361 additions and 389 deletions

View File

@ -166,3 +166,5 @@ SEARCH_ORDER_FILTER_ALIAS = [
]
ORDER_FILTER = [rest_filters.DjangoFilterBackend, filters.OrderingFilter]
ORDER_FILTER_ALIAS = [rest_filters.DjangoFilterBackend, InvenTreeOrderingFilter]

View File

@ -26,6 +26,7 @@ from InvenTree.api import (
)
from InvenTree.filters import (
ORDER_FILTER,
ORDER_FILTER_ALIAS,
SEARCH_ORDER_FILTER,
SEARCH_ORDER_FILTER_ALIAS,
InvenTreeDateFilter,
@ -303,10 +304,12 @@ class CategoryTree(ListAPI):
queryset = PartCategory.objects.all()
serializer_class = part_serializers.CategoryTree
filter_backends = ORDER_FILTER
filter_backends = ORDER_FILTER_ALIAS
ordering_fields = ['level', 'name', 'subcategories']
ordering_field_aliases = {'level': ['level', 'name'], 'name': ['name', 'level']}
# Order by tree level (top levels first) and then name
ordering = ['level', 'name']

View File

@ -35,7 +35,7 @@ from InvenTree.api import (
MetadataView,
)
from InvenTree.filters import (
ORDER_FILTER,
ORDER_FILTER_ALIAS,
SEARCH_ORDER_FILTER,
SEARCH_ORDER_FILTER_ALIAS,
InvenTreeDateFilter,
@ -429,13 +429,15 @@ class StockLocationTree(ListAPI):
queryset = StockLocation.objects.all()
serializer_class = StockSerializers.LocationTreeSerializer
filter_backends = ORDER_FILTER
filter_backends = ORDER_FILTER_ALIAS
ordering_fields = ['level', 'name', 'sublocations']
# Order by tree level (top levels first) and then name
ordering = ['level', 'name']
ordering_field_aliases = {'level': ['level', 'name'], 'name': ['name', 'level']}
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for the StockLocationTree endpoint."""
queryset = super().get_queryset(*args, **kwargs)

View File

@ -27,7 +27,7 @@
"@lingui/core": "^4.10.0",
"@lingui/react": "^4.10.0",
"@mantine/carousel": "^7.8.0",
"@mantine/core": "^7.8.0",
"@mantine/core": "^7.10.0",
"@mantine/dates": "^7.8.0",
"@mantine/dropzone": "^7.8.0",
"@mantine/form": "^7.8.0",
@ -36,7 +36,6 @@
"@mantine/notifications": "^7.8.0",
"@mantine/spotlight": "^7.8.0",
"@mantine/vanilla-extract": "^7.8.0",
"@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^7.110.0",
"@tabler/icons-react": "^3.2.0",
"@tanstack/react-query": "^5.29.2",
@ -60,7 +59,7 @@
"react-router-dom": "^6.22.3",
"react-select": "^5.8.0",
"react-simplemde-editor": "^5.2.0",
"recharts": "^2.12.4",
"recharts": "2",
"styled-components": "^6.1.8",
"zustand": "^4.5.2"
},

View File

@ -355,7 +355,7 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
return (
<>
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1}>
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
<>
<ApiImage
src={img}

View File

@ -10,6 +10,7 @@ import { IconMenu2 } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { identifierString } from '../../functions/conversion';
import { navigateToLink } from '../../functions/navigation';
export type Breadcrumb = {
@ -47,7 +48,8 @@ export function BreadcrumbList({
<Group gap="xs">
{navCallback && (
<ActionIcon
key="nav-action"
key="nav-breadcrumb-action"
aria-label="nav-breadcrumb-action"
onClick={navCallback}
variant="transparent"
>
@ -59,6 +61,9 @@ export function BreadcrumbList({
return (
<Anchor
key={index}
aria-label={`breadcrumb-${index}-${identifierString(
breadcrumb.name
)}`}
onClick={(event: any) =>
breadcrumb.url &&
navigateToLink(breadcrumb.url, navigate, event)

View File

@ -0,0 +1,205 @@
import {
ActionIcon,
Anchor,
Divider,
Drawer,
Group,
LoadingOverlay,
RenderTreeNodePayload,
Space,
Stack,
Tree,
TreeNodeData,
useTree
} from '@mantine/core';
import {
IconChevronDown,
IconChevronRight,
IconPoint,
IconSitemap
} from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { apiUrl } from '../../states/ApiState';
import { StylishText } from '../items/StylishText';
/*
* A generic navigation tree component.
*/
export default function NavigationTree({
title,
opened,
onClose,
selectedId,
modelType,
endpoint
}: {
title: string;
opened: boolean;
onClose: () => void;
selectedId?: number | null;
modelType: ModelType;
endpoint: ApiEndpoints;
}) {
const navigate = useNavigate();
const treeState = useTree();
// Data query to fetch the tree data from server
const query = useQuery({
enabled: opened,
queryKey: [modelType, opened],
queryFn: async () =>
api
.get(apiUrl(endpoint), {
data: {
ordering: 'level'
}
})
.then((response) => response.data ?? [])
.catch((error) => {
console.error(`Error fetching ${modelType} tree`);
return [];
})
});
const follow = useCallback(
(node: TreeNodeData, event?: any) => {
const url = getDetailUrl(modelType, node.value);
if (event?.shiftKey || event?.ctrlKey) {
navigateToLink(url, navigate, event);
} else {
onClose();
navigate(url);
}
},
[modelType, navigate]
);
// Map returned query to a "tree" structure
const data: TreeNodeData[] = useMemo(() => {
/*
* Reconstruct the navigation tree from the provided data.
* It is required (and assumed) that the data is first sorted by level.
*/
let nodes: Record<number, any> = {};
let tree: TreeNodeData[] = [];
if (!query?.data?.length) {
return [];
}
for (let ii = 0; ii < query.data.length; ii++) {
let node = {
...query.data[ii],
children: [],
label: query.data[ii].name,
value: query.data[ii].pk.toString(),
selected: query.data[ii].pk === selectedId
};
const pk: number = node.pk;
const parent: number | null = node.parent;
if (!parent) {
// This is a top level node
tree.push(node);
} else {
// This is *not* a top level node, so the parent *must* already exist
nodes[parent]?.children.push(node);
}
// Finally, add this node
nodes[pk] = node;
if (pk === selectedId) {
// Expand all parents
let parent = nodes[node.parent];
while (parent) {
parent.expanded = true;
parent = nodes[parent.parent];
}
}
}
return tree;
}, [selectedId, query.data]);
const renderNode = useCallback(
(payload: RenderTreeNodePayload) => {
return (
<Group
justify="left"
key={payload.node.value}
wrap="nowrap"
onClick={() => {
if (payload.hasChildren) {
treeState.toggleExpanded(payload.node.value);
}
}}
>
<Space w={5 * payload.level} />
<ActionIcon
size="sm"
variant="transparent"
aria-label={`nav-tree-toggle-${payload.node.value}}`}
>
{payload.hasChildren ? (
payload.expanded ? (
<IconChevronDown />
) : (
<IconChevronRight />
)
) : (
<IconPoint />
)}
</ActionIcon>
<Anchor
onClick={(event: any) => follow(payload.node, event)}
aria-label={`nav-tree-item-${payload.node.value}`}
>
{payload.node.label}
</Anchor>
</Group>
);
},
[treeState]
);
return (
<Drawer
opened={opened}
size="md"
position="left"
onClose={onClose}
withCloseButton={true}
styles={{
header: {
width: '100%'
},
title: {
width: '100%'
}
}}
title={
<Group justify="left" p="ms" gap="md" wrap="nowrap">
<IconSitemap />
<StylishText size="lg">{title}</StylishText>
</Group>
}
>
<Stack gap="xs">
<Divider />
<LoadingOverlay visible={query.isFetching || query.isLoading} />
<Tree data={data} tree={treeState} renderNode={renderNode} />
</Stack>
</Drawer>
);
}

View File

@ -20,6 +20,7 @@ import {
useParams
} from 'react-router-dom';
import { identifierString } from '../../functions/conversion';
import { navigateToLink } from '../../functions/navigation';
import { useLocalState } from '../../states/LocalState';
import { Boundary } from '../Boundary';
@ -172,6 +173,9 @@ function BasePanelGroup({
<Tabs.Panel
key={panel.name}
value={panel.name}
aria-label={`nav-panel-${identifierString(
`${pageKey}-${panel.name}`
)}`}
p="sm"
style={{
overflowX: 'scroll',

View File

@ -1,176 +0,0 @@
import { t } from '@lingui/macro';
import {
Drawer,
Group,
LoadingOverlay,
Stack,
Text,
useMantineColorScheme
} from '@mantine/core';
import { ReactTree, ThemeSettings } from '@naisutech/react-tree';
import {
IconChevronDown,
IconChevronRight,
IconSitemap
} from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { apiUrl } from '../../states/ApiState';
import { theme, vars } from '../../theme';
import { StylishText } from '../items/StylishText';
export function PartCategoryTree({
opened,
onClose,
selectedCategory
}: {
opened: boolean;
onClose: () => void;
selectedCategory?: number | null;
}) {
const navigate = useNavigate();
const treeQuery = useQuery({
enabled: opened,
queryKey: ['part_category_tree', opened],
queryFn: async () =>
api
.get(apiUrl(ApiEndpoints.category_tree), {})
.then((response) =>
response.data.map((category: any) => {
return {
id: category.pk,
label: category.name,
parentId: category.parent,
children: category.subcategories
};
})
)
.catch((error) => {
console.error('Error fetching part category tree:', error);
return [];
}),
refetchOnMount: true
});
function renderNode({ node }: { node: any }) {
return (
<Group
justify="space-between"
key={node.id}
wrap="nowrap"
onClick={() => {
onClose();
navigate(`/part/category/${node.id}`);
}}
>
<Text>{node.label}</Text>
</Group>
);
}
function renderIcon({ node, open }: { node: any; open?: boolean }) {
if (node.children == 0) {
return undefined;
}
return open ? <IconChevronDown /> : <IconChevronRight />;
}
const { colorScheme } = useMantineColorScheme();
const themes: ThemeSettings = useMemo(() => {
const currentTheme =
colorScheme === 'dark'
? vars.colors.defaultColor
: vars.colors.primaryColors;
return {
dark: {
text: {
fontFamily: vars.fontFamily,
//fontSize: vars.fontSizes.md,
color: vars.colors.text
},
nodes: {
height: '2.5rem',
folder: {
selectedBgColor: currentTheme[4],
hoverBgColor: currentTheme[6]
},
leaf: {
selectedBgColor: currentTheme[4],
hoverBgColor: currentTheme[6]
},
icons: {
folderColor: currentTheme[3],
leafColor: currentTheme[3]
}
}
},
light: {
text: {
fontFamily: vars.fontFamily,
//fontSize: vars.fontSizes.md,
color: vars.colors.text
},
nodes: {
height: '2.5rem',
folder: {
selectedBgColor: currentTheme[4],
hoverBgColor: currentTheme[2]
},
leaf: {
selectedBgColor: currentTheme[4],
hoverBgColor: currentTheme[2]
},
icons: {
folderColor: currentTheme[8],
leafColor: currentTheme[6]
}
}
}
};
}, [theme]);
return (
<Drawer
opened={opened}
size="md"
position="left"
onClose={onClose}
withCloseButton={true}
styles={{
header: {
width: '100%'
},
title: {
width: '100%'
}
}}
title={
<Group justify="left" p="ms" gap="md" wrap="nowrap">
<IconSitemap />
<StylishText size="lg">{t`Part Categories`}</StylishText>
</Group>
}
>
<Stack gap="xs">
<LoadingOverlay visible={treeQuery.isFetching} />
<ReactTree
nodes={treeQuery.data ?? []}
RenderNode={renderNode}
RenderIcon={renderIcon}
defaultSelectedNodes={selectedCategory ? [selectedCategory] : []}
showEmptyItems={false}
theme={colorScheme}
themes={themes}
/>
</Stack>
</Drawer>
);
}

View File

@ -1,109 +0,0 @@
import { t } from '@lingui/macro';
import { Drawer, Group, LoadingOverlay, Stack, Text } from '@mantine/core';
import { ReactTree } from '@naisutech/react-tree';
import {
IconChevronDown,
IconChevronRight,
IconSitemap
} from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { apiUrl } from '../../states/ApiState';
import { StylishText } from '../items/StylishText';
export function StockLocationTree({
opened,
onClose,
selectedLocation
}: {
opened: boolean;
onClose: () => void;
selectedLocation?: number | null;
}) {
const navigate = useNavigate();
const treeQuery = useQuery({
enabled: opened,
queryKey: ['stock_location_tree', opened],
queryFn: async () =>
api
.get(apiUrl(ApiEndpoints.stock_location_tree), {})
.then((response) =>
response.data.map((location: any) => {
return {
id: location.pk,
label: location.name,
parentId: location.parent,
children: location.sublocations
};
})
)
.catch((error) => {
console.error('Error fetching stock location tree:', error);
return [];
}),
refetchOnMount: true
});
function renderNode({ node }: { node: any }) {
return (
<Group
justify="space-between"
key={node.id}
wrap="nowrap"
onClick={() => {
onClose();
navigate(`/stock/location/${node.id}`);
}}
>
<Text>{node.label}</Text>
</Group>
);
}
function renderIcon({ node, open }: { node: any; open?: boolean }) {
if (node.children == 0) {
return undefined;
}
return open ? <IconChevronDown /> : <IconChevronRight />;
}
return (
<Drawer
opened={opened}
size="md"
position="left"
onClose={onClose}
withCloseButton={true}
styles={{
header: {
width: '100%'
},
title: {
width: '100%'
}
}}
title={
<Group justify="left" wrap="nowrap" gap="md" p="md">
<IconSitemap />
<StylishText size="lg">{t`Stock Locations`}</StylishText>
</Group>
}
>
<Stack gap="xs">
<LoadingOverlay visible={treeQuery.isFetching} />
<ReactTree
nodes={treeQuery.data ?? []}
showEmptyItems={false}
RenderNode={renderNode}
RenderIcon={renderIcon}
defaultSelectedNodes={selectedLocation ? [selectedLocation] : []}
/>
</Stack>
</Drawer>
);
}

View File

@ -35,5 +35,8 @@ export function resolveItem(obj: any, path: string): any {
export function identifierString(value: string): string {
// Convert an input string e.g. "Hello World" into a string that can be used as an identifier, e.g. "hello-world"
value = value || '-';
return value.toLowerCase().replace(/[^a-z0-9]/g, '-');
}

View File

@ -18,9 +18,9 @@ import {
DeleteItemAction,
EditItemAction
} from '../../components/items/ActionDropdown';
import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
@ -277,12 +277,15 @@ export default function CategoryDetail({}: {}) {
{deleteCategory.modal}
<Stack gap="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PartCategoryTree
<NavigationTree
modelType={ModelType.partcategory}
title={t`Part Categories`}
endpoint={ApiEndpoints.category_tree}
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedCategory={category?.pk}
selectedId={category?.pk}
/>
<PageDetail
title={t`Part Category`}

View File

@ -1,6 +1,7 @@
import { t } from '@lingui/macro';
import {
Alert,
Divider,
Grid,
LoadingOverlay,
Skeleton,
@ -51,9 +52,9 @@ import {
UnlinkBarcodeAction,
ViewBarcodeAction
} from '../../components/items/ActionDropdown';
import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
@ -817,12 +818,15 @@ export default function PartDetail() {
{deletePart.modal}
<Stack gap="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PartCategoryTree
<NavigationTree
title={t`Part Categories`}
modelType={ModelType.partcategory}
endpoint={ApiEndpoints.category_tree}
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedCategory={part?.category}
selectedId={part?.category}
/>
<PageDetail
title={t`Part` + ': ' + part.full_name}

View File

@ -23,9 +23,9 @@ import {
UnlinkBarcodeAction,
ViewBarcodeAction
} from '../../components/items/ActionDropdown';
import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
@ -357,10 +357,13 @@ export default function Stock() {
{deleteLocation.modal}
<Stack>
<LoadingOverlay visible={instanceQuery.isFetching} />
<StockLocationTree
<NavigationTree
title={t`Stock Locations`}
modelType={ModelType.stocklocation}
endpoint={ApiEndpoints.stock_location_tree}
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedLocation={location?.pk}
selectedId={location?.pk}
/>
<PageDetail
title={t`Stock Items`}

View File

@ -31,9 +31,9 @@ import {
UnlinkBarcodeAction,
ViewBarcodeAction
} from '../../components/items/ActionDropdown';
import NavigationTree from '../../components/nav/NavigationTree';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { StatusRenderer } from '../../components/render/StatusRenderer';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
@ -539,10 +539,13 @@ export default function StockDetail() {
return (
<Stack>
<LoadingOverlay visible={instanceQuery.isFetching} />
<StockLocationTree
<NavigationTree
title={t`Stock Locations`}
modelType={ModelType.stocklocation}
endpoint={ApiEndpoints.stock_location_tree}
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedLocation={stockitem?.location}
selectedId={stockitem?.location}
/>
<PageDetail
title={t`Stock Item`}

View File

@ -199,7 +199,7 @@ test('PUI - Language / Color', async ({ page }) => {
await page.getByRole('menuitem', { name: 'Logout' }).click();
await page.getByRole('button', { name: 'Send me an email' }).click();
await page.getByRole('button').nth(3).click();
await page.getByLabel('Select language').click();
await page.getByLabel('Select language').first().click();
await page.getByRole('option', { name: 'German' }).click();
await page.waitForTimeout(200);

View File

@ -95,3 +95,20 @@ test('PUI - Purchasing', async ({ page }) => {
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('tab', { name: 'Details' }).waitFor();
});
test('PUI - Stock Location Tree', async ({ page }) => {
await doQuickLogin(page);
await page.goto(`${baseUrl}/stock/location/index/`);
await page.waitForURL('**/platform/stock/location/**');
await page.getByRole('tab', { name: 'Location Details' }).click();
await page.getByLabel('nav-breadcrumb-action').click();
await page.getByLabel('nav-tree-toggle-1}').click();
await page.getByLabel('nav-tree-item-2').click();
await page.getByLabel('breadcrumb-2-storage-room-a').waitFor();
await page.getByLabel('breadcrumb-1-factory').click();
await page.getByRole('cell', { name: 'Factory' }).first().waitFor();
});

View File

@ -316,13 +316,20 @@
"@babel/plugin-transform-modules-commonjs" "^7.24.1"
"@babel/plugin-transform-typescript" "^7.24.1"
"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.21.0":
version "7.24.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.24.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e"
integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15", "@babel/template@^7.24.0":
version "7.24.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50"
@ -524,13 +531,6 @@
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/is-prop-valid@^1.2.0":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337"
integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
@ -932,13 +932,21 @@
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
"@floating-ui/core@^1.0.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
version "1.6.2"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a"
integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==
dependencies:
"@floating-ui/utils" "^0.2.1"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/dom@^1.0.1", "@floating-ui/dom@^1.6.1":
"@floating-ui/dom@^1.0.0":
version "1.6.5"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9"
integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==
dependencies:
"@floating-ui/core" "^1.0.0"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/dom@^1.0.1":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
@ -946,26 +954,26 @@
"@floating-ui/core" "^1.0.0"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/react-dom@^2.0.0":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d"
integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==
"@floating-ui/react-dom@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff"
integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==
dependencies:
"@floating-ui/dom" "^1.6.1"
"@floating-ui/dom" "^1.0.0"
"@floating-ui/react@^0.26.9":
version "0.26.12"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.12.tgz#6908f774d8e3167d89b37fd83be975c7e5d8be99"
integrity sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==
version "0.26.16"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.16.tgz#3415a087f452165161c2d313d1d57e8142894679"
integrity sha512-HEf43zxZNAI/E781QIVpYSF3K2VH4TTYZpqecjdsFkjsaU1EbaWcM++kw0HXFffj7gDUcBFevX8s0rQGQpxkow==
dependencies:
"@floating-ui/react-dom" "^2.0.0"
"@floating-ui/react-dom" "^2.1.0"
"@floating-ui/utils" "^0.2.0"
tabbable "^6.0.0"
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@floating-ui/utils@^0.2.0":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5"
integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==
"@fortawesome/fontawesome-common-types@6.5.2":
version "6.5.2"
@ -1216,56 +1224,56 @@
resolved "https://registry.yarnpkg.com/@mantine/carousel/-/carousel-7.8.0.tgz#d49b3a57cebfc9c774467bd4c46df1005eb765f4"
integrity sha512-nxuLtZ4N4KJaayab5KuaffYhiVi4UaIjywkgQiNfTxABlgFdERNfo5if9sSMyisL0r3RUPJBfLkyi8A4fEC8GQ==
"@mantine/core@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-7.8.0.tgz#b4bbd82ea2f1a25f5fb3d11ae5583cf80ecd8383"
integrity sha512-19RKuNdJ/s8pZjy2w2rvTsl4ybi/XM6vf+Kc0WY7kpLFCvdG+/UxNi1MuJF8t2Zs0QSFeb/H5yZQNe0XPbegHw==
"@mantine/core@^7.10.0":
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-7.10.0.tgz#bfaafc92cf2346e5a6cbb49289f577ce3f7c05f7"
integrity sha512-hNqhdn/+4x8+FDWzR5fu1eMgnG1Mw4fZHw4WjIYjKrSv0NeKHY263RiesZz8RwcUQ8r7LlD95/2tUOMnKVTV5Q==
dependencies:
"@floating-ui/react" "^0.26.9"
clsx "2.1.0"
clsx "^2.1.1"
react-number-format "^5.3.1"
react-remove-scroll "^2.5.7"
react-textarea-autosize "8.5.3"
type-fest "^4.12.0"
"@mantine/dates@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-7.8.0.tgz#a8785030000487158e1bd23655ea26245bbf299a"
integrity sha512-9jjiYMwP3jQOpOLKkjhp9uf2BGhtEbOnOzyAlpLOS0CJJlYtB0tO6dJ3JaogrOZ/Yfee7ZUBgouCG5EkR4/qtQ==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-7.10.0.tgz#0c2a02883d5fb4a36b40a578b26ef5a697c333e5"
integrity sha512-LBBh1U/RzxFQKGA6sSYxbCwYEMoM5lNIhwofY6g8zOTAZuRQqo5FIWItmB9I9ltT+M2o75SADeP6ZBLi4ec8ZA==
dependencies:
clsx "2.1.0"
clsx "^2.1.1"
"@mantine/dropzone@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-7.8.0.tgz#f962c72adb585ddc7706dca680102728a52b2de7"
integrity sha512-rpNTR3NASvI3BnqhY5wg3BDhxkABT9UoZEGRrOGnS3YU7SYXg5rT9ch5Cm4iPwMNdwsyAIU6K2ii4wWk40dRpg==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-7.10.0.tgz#54283a22d1e848619a1659e38dd29852bff135e7"
integrity sha512-LFJjYvz0pSfKCSiVGLgAS94AazF2npK/ZYrr+Ax9/tdd1HgbxSd3B8SaPdGm1wOwZbpp8w0auyl3fZfqnDBG8w==
dependencies:
react-dropzone-esm "15.0.1"
"@mantine/form@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-7.8.0.tgz#3f16b2e0124c65286892ed50181d192ae03d988b"
integrity sha512-Qn3/69zGt/p3wyMwGz2V0+FbmvqC2/PvXaeyO0a4CnwhROeE7ObyCKXDcBmgapOSBHr/7wFvMeTDMaTMfe3DXw==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-7.10.0.tgz#3e8e3fb836948becb13b89412c74016b50bac3d3"
integrity sha512-ChAtqdQCAZrnH6iiCivumyMuMsev+tFWIgsCCgAmbP2sOyMtjbNtypKrcwBwI/PzAH9N4jSJlsmJsnRdXNeEkQ==
dependencies:
fast-deep-equal "^3.1.3"
klona "^2.0.6"
"@mantine/hooks@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.8.0.tgz#fc32e07746689459c4b049dc581d1dbda5545686"
integrity sha512-+70fkgjhVJeJ+nJqnburIM3UAsfvxat1Low9HMPobLbv64FIdB4Nzu5ct3qojNQ58r5sK01tg5UoFIJYslaVrg==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.10.0.tgz#10a259e204a8af29df6aeeb24090c1e2c6debca0"
integrity sha512-fnalwYS2WQEFS4wmhmAetDZ/VdJPLNeUXPX9t+S21o3p/dRTX1xhU2mS7yWaQUKM0hPD1TcujqXGlP2M2g/A9A==
"@mantine/modals@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-7.8.0.tgz#1960f34a7d0c45490465d61acf5da9a0c65610ca"
integrity sha512-/Kxquz8U7xau9PoqPi5Pjysfnq8b48VRAMp62B+SsPWNTH47R0F7dYRYpLQ2/0VU+OWGY0lOshxpgA61sZ3IPA==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-7.10.0.tgz#c08789491bfbfb1d432818e0fc4b2eac71fd480e"
integrity sha512-UVtmRpTBWDqcJjdv97IUYLduYcZBrqteyDwnspHT453iFZlvCglHUXYR+LvN5ExE+kxUe2IUXL/pEaIRTjwtKQ==
"@mantine/notifications@^7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.8.0.tgz#90b97ca3191951bffea5da6a6ce9e607daf37bf5"
integrity sha512-O7BnaCcwVg38fh+gSZ6GEsTFPPgJAiOTrRkOMXG+7pNqJT9YNa9KDZhiPZzn3WV4wexncjyK32a8gGSVtf+kdg==
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.10.0.tgz#aa638b8bb6c3d6bfb34d518a49ef8a8b6ab499e4"
integrity sha512-3a0mmM9Kr3nPP+8VHsIuly507nda6ciu2aB/xSxb7gFIKHw3GqSu77pxXa+5l4Y6AQKKvP9360K4KjH6+rOBWw==
dependencies:
"@mantine/store" "7.8.0"
"@mantine/store" "7.10.0"
react-transition-group "4.4.5"
"@mantine/spotlight@^7.8.0":
@ -1275,6 +1283,11 @@
dependencies:
"@mantine/store" "7.8.0"
"@mantine/store@7.10.0":
version "7.10.0"
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.10.0.tgz#68368c6ca5b75cfb331220e06a3235be753df055"
integrity sha512-B6AyUX0cA97/hI9v0att7eJJnQTcUG7zBlTdWhOsptBV5UoDNrzdv3DDWIFxrA8h+nhNKGBh6Dif5HWh1+QLeA==
"@mantine/store@7.8.0":
version "7.8.0"
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.8.0.tgz#d3ac70a96b71cbc0a4ef506bf751d8e290666d28"
@ -1292,15 +1305,6 @@
dependencies:
moo "^0.5.1"
"@naisutech/react-tree@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@naisutech/react-tree/-/react-tree-3.1.0.tgz#a83820425b53a1ec7a39804ff8bd9024f0a953f4"
integrity sha512-6p1l3ZIaTmbgiAf/mpFELvqwl51LDhr+09f7L+C27DBLWjtleezCMoUuiSLhrJgpixCPNL13PuI3q2yn+0AGvA==
dependencies:
"@emotion/is-prop-valid" "^1.2.0"
nanoid "^4.0.0"
react-draggable "^4.4.5"
"@playwright/test@^1.43.1":
version "1.43.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.1.tgz#16728a59eb8ce0f60472f98d8886d6cab0fa3e42"
@ -2114,16 +2118,21 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
clsx@2.1.0, clsx@^2.0.0, clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.0.0, clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
codemirror-spell-checker@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e"
@ -2361,9 +2370,9 @@ date-fns@^3.6.0:
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
dayjs@^1.11.10:
version "1.11.10"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
version "1.11.11"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e"
integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4:
version "4.3.4"
@ -3315,11 +3324,6 @@ nanoid@^3.3.6, nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanoid@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
node-preload@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301"
@ -3721,9 +3725,9 @@ react-remove-scroll-bar@^2.3.6:
tslib "^2.0.0"
react-remove-scroll@^2.5.7:
version "2.5.9"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz#6a38e7d46043abc2c6b0fb39db650b9f2e38be3e"
integrity sha512-bvHCLBrFfM2OgcrpPY2YW84sPdS2o2HKWJUf1xGyGLnSoEnOTOBpahIarjRuYtN0ryahCeP242yf+5TrBX/pZA==
version "2.5.10"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz#5fae456a23962af6d3c38ca1978bcfe0806c4061"
integrity sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==
dependencies:
react-remove-scroll-bar "^2.3.6"
react-style-singleton "^2.2.1"
@ -3843,10 +3847,10 @@ recharts-scale@^0.4.4:
dependencies:
decimal.js-light "^2.4.1"
recharts@^2.12.4:
version "2.12.5"
resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.5.tgz#b335eb66173317dccb3e126fce1d7ac5b3cee1e9"
integrity sha512-Cy+BkqrFIYTHJCyKHJEPvbHE2kVQEP6PKbOHJ8ztRGTAhvHuUnCwDaKVb13OwRFZ0QNUk1QvGTDdgWSMbuMtKw==
recharts@2:
version "2.12.7"
resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.7.tgz#c7f42f473a257ff88b43d88a92530930b5f9e773"
integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==
dependencies:
clsx "^2.0.0"
eventemitter3 "^4.0.1"
@ -4276,9 +4280,9 @@ type-fest@^0.8.0:
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^4.12.0:
version "4.15.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.15.0.tgz#21da206b89c15774cc718c4f2d693e13a1a14a43"
integrity sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==
version "4.18.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.18.3.tgz#5249f96e7c2c3f0f1561625f54050e343f1c8f68"
integrity sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==
typedarray-to-buffer@^3.1.5:
version "3.1.5"