2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-26 18:46:44 +00:00

[PUI] Switch linting to biome (#8317)

* bump pre-commit

* add biome

* autofixes

* use number functions

* fix string usage

* use specific variable definition

* fix missing translations

* reduce alerts

* add missing keys

* fix index creation

* fix more strings

* fix types

* fix function

* add missing keys

* fiy array access

* fix string functions

* do not redefine var

* extend exlcusions

* reduce unnecessary operators

* simplify request

* use number functions

* fix missing translation

* add missing type

* fix filter

* use newer func

* remove unused fragment

* fix confusing assigment

* pass children as elements

* add missing translation

* fix imports

* fix import

* auto-fix problems

* add autfix for unused imports

* fix SAST error

* fix useSelfClosingElements

* fix useTemplate

* add codespell exception

* Update pui_printing.spec.ts

* Update pui_printing.spec.ts

* add vscode defaults
This commit is contained in:
Matthias Mair 2024-11-12 01:03:08 +01:00 committed by GitHub
parent e7cfb4c3c0
commit 0872beaba9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
308 changed files with 2635 additions and 2550 deletions

View File

@ -31,7 +31,8 @@
"ms-python.python",
"ms-python.vscode-pylance",
"batisteo.vscode-django",
"eamodio.gitlens"
"eamodio.gitlens",
"biomejs.biome"
]
}
},

View File

@ -69,26 +69,12 @@ repos:
pyproject.toml |
src/frontend/vite.config.ts |
)$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v4.0.0-alpha.8"
hooks:
- id: prettier
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
additional_dependencies:
- "prettier@^2.4.1"
- "@trivago/prettier-plugin-sort-imports"
- repo: https://github.com/pre-commit/mirrors-eslint
rev: "v9.12.0"
hooks:
- id: eslint
additional_dependencies:
- eslint@^8.41.0
- eslint-config-google@^0.14.0
- eslint-plugin-react@6.10.3
- babel-eslint@6.1.2
- "@typescript-eslint/eslint-plugin@latest"
- "@typescript-eslint/parser"
files: ^src/frontend/.*\.(js|jsx|ts|tsx)$
- repo: https://github.com/biomejs/pre-commit
rev: "v0.5.0"
hooks:
- id: biome-check
additional_dependencies: ["@biomejs/biome@1.9.4"]
files: ^src/frontend/.*\.(js|ts|tsx)$
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.0
hooks:

5
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"recommendations": [
"biomejs.biome"
]
}

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit"
}
}

40
biome.json Normal file
View File

@ -0,0 +1,40 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"trailingCommas": "none",
"indentStyle": "space"
}
},
"linter": {
"rules": {
"suspicious" : {
"noExplicitAny": "off",
"noDoubleEquals": "off",
"noArrayIndexKey": "off",
"useDefaultSwitchClauseLast": "off"
},
"style": {
"noUselessElse": "off",
"noNonNullAssertion": "off",
"noParameterAssign": "off"
}, "correctness":{
"useExhaustiveDependencies": "off",
"useJsxKeyInIterable": "off",
"noUnsafeOptionalChaining": "off",
"noSwitchDeclarations": "off",
"noUnusedImports":"error"
}, "complexity": {
"noBannedTypes": "off",
"noExtraBooleanCast": "off",
"noForEach": "off",
"noUselessSwitchCase": "off",
"useLiteralKeys":"off"
}, "performance": {
"noDelete":"off"
}
}
}
}

View File

@ -106,4 +106,4 @@ known_django="django"
sections=["FUTURE","STDLIB","DJANGO","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
[tool.codespell]
ignore-words-list = ["assertIn","SME","intoto"]
ignore-words-list = ["assertIn","SME","intoto","fitH"]

View File

@ -1,9 +0,0 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"importOrder": ["<THIRD_PARTY_MODULES>", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}

View File

@ -1,7 +0,0 @@
/* eslint-env node */
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
};

View File

@ -23,7 +23,7 @@ export function setApiDefaults() {
api.defaults.xsrfHeaderName = 'X-CSRFToken';
if (token) {
api.defaults.headers['Authorization'] = `Token ${token}`;
api.defaults.headers.Authorization = `Token ${token}`;
} else {
delete api.defaults.headers['Authorization'];
}

View File

@ -1,15 +1,15 @@
import { t } from '@lingui/macro';
import { Alert } from '@mantine/core';
import { ErrorBoundary, FallbackRender } from '@sentry/react';
import { ErrorBoundary, type FallbackRender } from '@sentry/react';
import { IconExclamationCircle } from '@tabler/icons-react';
import { ReactNode, useCallback } from 'react';
import { type ReactNode, useCallback } from 'react';
function DefaultFallback({ title }: Readonly<{ title: string }>): ReactNode {
return (
<Alert
color="red"
color='red'
icon={<IconExclamationCircle />}
title={t`Error rendering component` + `: ${title}`}
title={`${t`Error rendering component`}: ${title}`}
>
{t`An error occurred while rendering this component. Refer to the console for more information.`}
</Alert>

View File

@ -1,5 +1,10 @@
import { ActionIcon, FloatingPosition, Group, Tooltip } from '@mantine/core';
import { ReactNode } from 'react';
import {
ActionIcon,
type FloatingPosition,
Group,
Tooltip
} from '@mantine/core';
import type { ReactNode } from 'react';
import { identifierString } from '../../functions/conversion';
@ -46,7 +51,7 @@ export function ActionButton(props: ActionButtonProps) {
}}
variant={props.variant ?? 'transparent'}
>
<Group gap="xs" wrap="nowrap">
<Group gap='xs' wrap='nowrap'>
{props.icon}
</Group>
</ActionIcon>

View File

@ -1,10 +1,10 @@
import { IconPlus } from '@tabler/icons-react';
import { ActionButton, ActionButtonProps } from './ActionButton';
import { ActionButton, type ActionButtonProps } from './ActionButton';
/**
* A generic icon button which is used to add or create a new item
*/
export function AddItemButton(props: Readonly<ActionButtonProps>) {
return <ActionButton {...props} color="green" icon={<IconPlus />} />;
return <ActionButton {...props} color='green' icon={<IconPlus />} />;
}

View File

@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
import { IconUserStar } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { ModelType } from '../../enums/ModelType';
import type { ModelType } from '../../enums/ModelType';
import { useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { useUserState } from '../../states/UserState';
@ -78,14 +78,14 @@ export default function AdminButton(props: Readonly<AdminButtonProps>) {
return (
<ActionButton
icon={<IconUserStar />}
color="blue"
size="lg"
radius="sm"
variant="filled"
color='blue'
size='lg'
radius='sm'
variant='filled'
tooltip={t`Open in admin interface`}
hidden={!enabled}
onClick={openAdmin}
tooltipAlignment="bottom"
tooltipAlignment='bottom'
/>
);
}

View File

@ -16,16 +16,16 @@ export function ButtonMenu({
tooltip?: string;
}>) {
return (
<Menu shadow="xs">
<Menu shadow='xs'>
<Menu.Target>
<ActionIcon variant="default">
<ActionIcon variant='default'>
<Tooltip label={tooltip}>{icon}</Tooltip>
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
{label && <Menu.Label>{label}</Menu.Label>}
{actions.map((action, i) => (
<Menu.Item key={i}>{action}</Menu.Item>
<Menu.Item key={`${i}-${action}`}>{action}</Menu.Item>
))}
</Menu.Dropdown>
</Menu>

View File

@ -3,7 +3,7 @@ import {
ActionIcon,
Button,
CopyButton as MantineCopyButton,
MantineSize,
type MantineSize,
Text,
Tooltip
} from '@mantine/core';
@ -30,13 +30,13 @@ export function CopyButton({
<ButtonComponent
color={copied ? 'teal' : 'gray'}
onClick={copy}
variant="transparent"
variant='transparent'
size={size ?? 'sm'}
>
{copied ? (
<InvenTreeIcon icon="check" />
<InvenTreeIcon icon='check' />
) : (
<InvenTreeIcon icon="copy" />
<InvenTreeIcon icon='copy' />
)}
{content}
{label && (

View File

@ -17,7 +17,7 @@ export function EditButton({
<ActionIcon
onClick={() => setEditing()}
disabled={disabled}
variant="default"
variant='default'
>
{editing ? saveIcon : <IconEdit />}
</ActionIcon>

View File

@ -1,6 +1,6 @@
import { Button, Tooltip } from '@mantine/core';
import { InvenTreeIcon, InvenTreeIconType } from '../../functions/icons';
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
/**
* A "primary action" button for display on a page detail, (for example)
@ -25,12 +25,12 @@ export default function PrimaryActionButton({
}
return (
<Tooltip label={tooltip ?? title} position="bottom" hidden={!tooltip}>
<Tooltip label={tooltip ?? title} position='bottom' hidden={!tooltip}>
<Button
leftSection={icon && <InvenTreeIcon icon={icon} />}
color={color}
radius="sm"
p="xs"
radius='sm'
p='xs'
onClick={onClick}
>
{title}

View File

@ -6,13 +6,13 @@ import { useMemo, useState } from 'react';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import type { ModelType } from '../../enums/ModelType';
import { extractAvailableFields } from '../../functions/forms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { apiUrl } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { useUserSettingsState } from '../../states/SettingsState';
import { ApiFormFieldSet } from '../forms/fields/ApiFormField';
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
import { ActionDropdown } from '../items/ActionDropdown';
export function PrintingActions({
@ -57,11 +57,11 @@ export function PrintingActions({
});
const labelFields: ApiFormFieldSet = useMemo(() => {
let fields: ApiFormFieldSet = printingFields.data || {};
const fields: ApiFormFieldSet = printingFields.data || {};
// Override field values
fields['template'] = {
...fields['template'],
fields.template = {
...fields.template,
filters: {
enabled: true,
model_type: modelType,
@ -69,8 +69,8 @@ export function PrintingActions({
}
};
fields['items'] = {
...fields['items'],
fields.items = {
...fields.items,
value: items,
hidden: true
};

View File

@ -13,10 +13,10 @@ export default function RemoveRowButton({
return (
<ActionButton
onClick={onClick}
icon={<InvenTreeIcon icon="square_x" />}
icon={<InvenTreeIcon icon='square_x' />}
tooltip={tooltip}
tooltipAlignment="top"
color="red"
tooltipAlignment='top'
color='red'
/>
);
}

View File

@ -17,7 +17,7 @@ import {
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { apiUrl } from '../../states/ApiState';
import { Provider } from '../../states/states';
import type { Provider } from '../../states/states';
const brandIcons: { [key: string]: JSX.Element } = {
google: <IconBrandGoogle />,
@ -51,8 +51,8 @@ export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
return (
<Button
leftSection={getBrandIcon(provider)}
radius="xl"
component="a"
radius='xl'
component='a'
onClick={login}
>
{provider.display_name}{' '}

View File

@ -16,7 +16,7 @@ export function ScanButton() {
innerProps: {}
})
}
variant="transparent"
variant='transparent'
title={t`Open Barcode Scanner`}
>
<IconQrcode />

View File

@ -11,7 +11,7 @@ import { IconChevronDown } from '@tabler/icons-react';
import { useEffect, useMemo, useState } from 'react';
import { identifierString } from '../../functions/conversion';
import { TablerIconType } from '../../functions/icons';
import type { TablerIconType } from '../../functions/icons';
import * as classes from './SplitButton.css';
interface SplitButtonOption {
@ -58,7 +58,7 @@ export function SplitButton({
const theme = useMantineTheme();
return (
<Group wrap="nowrap" style={{ gap: 0 }}>
<Group wrap='nowrap' style={{ gap: 0 }}>
<Button
onClick={currentOption?.onClick}
disabled={loading ? false : currentOption?.disabled}
@ -70,12 +70,12 @@ export function SplitButton({
</Button>
<Menu
transitionProps={{ transition: 'pop' }}
position="bottom-end"
position='bottom-end'
withinPortal
>
<Menu.Target>
<ActionIcon
variant="filled"
variant='filled'
color={theme.primaryColor}
size={36}
className={classes.icon}
@ -99,7 +99,7 @@ export function SplitButton({
disabled={option.disabled}
leftSection={<option.icon />}
>
<Tooltip label={option.tooltip} position="right">
<Tooltip label={option.tooltip} position='right'>
<Text>{option.name}</Text>
</Tooltip>
</Menu.Item>

View File

@ -12,8 +12,8 @@ export function SpotlightButton() {
<ActionIcon
onClick={() => firstSpotlight.open()}
title={t`Open spotlight`}
variant="transparent"
aria-label="open-spotlight"
variant='transparent'
aria-label='open-spotlight'
>
<IconCommand />
</ActionIcon>

View File

@ -19,9 +19,9 @@ export function PassFailButton({
return (
<Badge
color={v ? 'lime.5' : 'red.6'}
variant="filled"
radius="lg"
size="sm"
variant='filled'
radius='lg'
size='sm'
style={{ maxWidth: '50px' }}
>
{v ? pass : fail}

View File

@ -3,12 +3,12 @@ import { Alert, Card, Center, Divider, Loader, Text } from '@mantine/core';
import { useDisclosure, useHotkeys } from '@mantine/hooks';
import { IconInfoCircle } from '@tabler/icons-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Layout, Responsive, WidthProvider } from 'react-grid-layout';
import { type Layout, Responsive, WidthProvider } from 'react-grid-layout';
import { useDashboardItems } from '../../hooks/UseDashboardItems';
import { useUserState } from '../../states/UserState';
import DashboardMenu from './DashboardMenu';
import DashboardWidget, { DashboardWidgetProps } from './DashboardWidget';
import DashboardWidget, { type DashboardWidgetProps } from './DashboardWidget';
import DashboardWidgetDrawer from './DashboardWidgetDrawer';
const ReactGridLayout = WidthProvider(Responsive);
@ -17,7 +17,7 @@ const ReactGridLayout = WidthProvider(Responsive);
* Save the dashboard layout to local storage
*/
function saveDashboardLayout(layouts: any, userId: number | undefined): void {
let reducedLayouts: any = {};
const reducedLayouts: any = {};
// Reduce the layouts to exclude default attributes from the dataset
Object.keys(layouts).forEach((key) => {
@ -93,7 +93,7 @@ function loadDashboardWidgets(userId: number | undefined): string[] {
}
}
export default function DashboardLayout({}: {}) {
export default function DashboardLayout() {
const user = useUserState();
// Dashboard layout definition
@ -141,7 +141,7 @@ export default function DashboardLayout({}: {}) {
*/
const addWidget = useCallback(
(widget: string) => {
let newWidget = availableWidgets.items.find(
const newWidget = availableWidgets.items.find(
(wid) => wid.label === widget
);
@ -150,7 +150,7 @@ export default function DashboardLayout({}: {}) {
}
// Update the layouts to include the new widget (and enforce initial size)
let _layouts: any = { ...layouts };
const _layouts: any = { ...layouts };
Object.keys(_layouts).forEach((key) => {
_layouts[key] = updateLayoutForWidget(_layouts[key], widgets, true);
@ -170,7 +170,7 @@ export default function DashboardLayout({}: {}) {
setWidgets(widgets.filter((item) => item.label !== widget));
// Remove the widget from the layout
let _layouts: any = { ...layouts };
const _layouts: any = { ...layouts };
Object.keys(_layouts).forEach((key) => {
_layouts[key] = _layouts[key].filter(
@ -188,7 +188,7 @@ export default function DashboardLayout({}: {}) {
(layout: any[], widgets: any[], overrideSize: boolean) => {
return layout.map((item: Layout): Layout => {
// Find the matching widget
let widget = widgets.find(
const widget = widgets.find(
(widget: DashboardWidgetProps) => widget.label === item.i
);
@ -275,14 +275,14 @@ export default function DashboardLayout({}: {}) {
editing={editing}
removing={removing}
/>
<Divider p="xs" />
<Divider p='xs' />
{layouts && loaded && availableWidgets.loaded ? (
<>
{widgetLabels.length == 0 ? (
<Center>
<Card shadow="xs" padding="xl" style={{ width: '100%' }}>
<Card shadow='xs' padding='xl' style={{ width: '100%' }}>
<Alert
color="blue"
color='blue'
title={t`No Widgets Selected`}
icon={<IconInfoCircle />}
>
@ -292,7 +292,7 @@ export default function DashboardLayout({}: {}) {
</Center>
) : (
<ReactGridLayout
className="dashboard-layout"
className='dashboard-layout'
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
rowHeight={64}
@ -320,7 +320,7 @@ export default function DashboardLayout({}: {}) {
</>
) : (
<Center>
<Loader size="xl" />
<Loader size='xl' />
</Center>
)}
</>

View File

@ -45,42 +45,42 @@ export default function DashboardMenu({
const username = user.username();
return (
<StylishText size="lg">{`${instanceName} - ${username}`}</StylishText>
<StylishText size='lg'>{`${instanceName} - ${username}`}</StylishText>
);
}, [user, instanceName]);
return (
<Paper p="sm" shadow="xs">
<Group justify="space-between" wrap="nowrap">
<Paper p='sm' shadow='xs'>
<Group justify='space-between' wrap='nowrap'>
{title}
<Group justify="right" wrap="nowrap">
<Group justify='right' wrap='nowrap'>
{(editing || removing) && (
<Tooltip label={t`Accept Layout`} onClick={onAcceptLayout}>
<ActionIcon
aria-label={'dashboard-accept-layout'}
color="green"
variant="transparent"
color='green'
variant='transparent'
>
<IconCircleCheck />
</ActionIcon>
</Tooltip>
)}
<Menu
shadow="md"
shadow='md'
width={200}
openDelay={100}
closeDelay={400}
position="bottom-end"
position='bottom-end'
>
<Menu.Target>
<Indicator
color="red"
position="bottom-start"
color='red'
position='bottom-start'
processing
disabled={!editing}
>
<ActionIcon variant="transparent" aria-label="dashboard-menu">
<ActionIcon variant='transparent' aria-label='dashboard-menu'>
<IconDotsVertical />
</ActionIcon>
</Indicator>
@ -93,7 +93,7 @@ export default function DashboardMenu({
{!editing && !removing && (
<Menu.Item
leftSection={<IconLayout2 color="blue" size={14} />}
leftSection={<IconLayout2 color='blue' size={14} />}
onClick={onStartEdit}
>
<Trans>Edit Layout</Trans>
@ -102,7 +102,7 @@ export default function DashboardMenu({
{!editing && !removing && (
<Menu.Item
leftSection={<IconLayoutGridAdd color="green" size={14} />}
leftSection={<IconLayoutGridAdd color='green' size={14} />}
onClick={onAddWidget}
>
<Trans>Add Widget</Trans>
@ -111,7 +111,7 @@ export default function DashboardMenu({
{!editing && !removing && (
<Menu.Item
leftSection={<IconLayoutGridRemove color="red" size={14} />}
leftSection={<IconLayoutGridRemove color='red' size={14} />}
onClick={onStartRemove}
>
<Trans>Remove Widgets</Trans>
@ -120,7 +120,7 @@ export default function DashboardMenu({
{(editing || removing) && (
<Menu.Item
leftSection={<IconCircleCheck color="green" size={14} />}
leftSection={<IconCircleCheck color='green' size={14} />}
onClick={onAcceptLayout}
>
<Trans>Accept Layout</Trans>

View File

@ -43,7 +43,7 @@ export default function DashboardWidget({
// TODO: Add button to remove widget (if "editing")
return (
<Paper withBorder key={item.label} shadow="sm" p="xs">
<Paper withBorder key={item.label} shadow='sm' p='xs'>
<Boundary label={`dashboard-widget-${item.label}`}>
<Box
key={`dashboard-widget-${item.label}`}
@ -58,17 +58,17 @@ export default function DashboardWidget({
{item.render()}
</Box>
{removing && (
<Overlay color="black" opacity={0.7} zIndex={1000}>
<Overlay color='black' opacity={0.7} zIndex={1000}>
{removing && (
<Group justify="right">
<Group justify='right'>
<Tooltip
label={t`Remove this widget from the dashboard`}
position="bottom"
position='bottom'
>
<ActionIcon
aria-label={`remove-dashboard-item-${item.label}`}
variant="filled"
color="red"
variant='filled'
color='red'
onClick={onRemove}
>
<IconX />

View File

@ -49,7 +49,7 @@ export default function DashboardWidgetDrawer({
// Filter widgets based on search text
const filteredWidgets = useMemo(() => {
let words = filterText.trim().toLowerCase().split(' ');
const words = filterText.trim().toLowerCase().split(' ');
return unusedWidgets.filter((widget) => {
return words.every((word) =>
@ -60,28 +60,28 @@ export default function DashboardWidgetDrawer({
return (
<Drawer
position="right"
size="50%"
position='right'
size='50%'
opened={opened}
onClose={onClose}
title={
<Group justify="space-between" wrap="nowrap">
<StylishText size="lg">Add Dashboard Widgets</StylishText>
<Group justify='space-between' wrap='nowrap'>
<StylishText size='lg'>Add Dashboard Widgets</StylishText>
</Group>
}
>
<Stack gap="xs">
<Stack gap='xs'>
<Divider />
<TextInput
aria-label="dashboard-widgets-filter-input"
aria-label='dashboard-widgets-filter-input'
placeholder={t`Filter dashboard widgets`}
value={filter}
onChange={(event) => setFilter(event.currentTarget.value)}
rightSection={
filter && (
<IconBackspace
aria-label="dashboard-widgets-filter-clear"
color="red"
aria-label='dashboard-widgets-filter-clear'
color='red'
onClick={() => setFilter('')}
/>
)
@ -94,18 +94,18 @@ export default function DashboardWidgetDrawer({
<Table.Tr key={widget.label}>
<Table.Td>
<Tooltip
position="left"
position='left'
label={t`Add this widget to the dashboard`}
>
<ActionIcon
aria-label={`add-widget-${widget.label}`}
variant="transparent"
color="green"
variant='transparent'
color='green'
onClick={() => {
onAddWidget(widget.label);
}}
>
<IconLayoutGridAdd></IconLayoutGridAdd>
<IconLayoutGridAdd />
</ActionIcon>
</Tooltip>
</Table.Td>
@ -113,14 +113,14 @@ export default function DashboardWidgetDrawer({
<Text>{widget.title}</Text>
</Table.Td>
<Table.Td>
<Text size="sm">{widget.description}</Text>
<Text size='sm'>{widget.description}</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
{unusedWidgets.length === 0 && (
<Alert color="blue" title={t`No Widgets Available`}>
<Alert color='blue' title={t`No Widgets Available`}>
<Text>{t`There are no more widgets available for the dashboard`}</Text>
</Alert>
)}

View File

@ -1,7 +1,7 @@
import { t } from '@lingui/macro';
import { ModelType } from '../../enums/ModelType';
import { DashboardWidgetProps } from './DashboardWidget';
import type { DashboardWidgetProps } from './DashboardWidget';
import ColorToggleDashboardWidget from './widgets/ColorToggleWidget';
import GetStartedWidget from './widgets/GetStartedWidget';
import LanguageSelectDashboardWidget from './widgets/LanguageSelectWidget';

View File

@ -3,12 +3,12 @@ import { Group } from '@mantine/core';
import { ColorToggle } from '../../items/ColorToggle';
import { StylishText } from '../../items/StylishText';
import { DashboardWidgetProps } from '../DashboardWidget';
import type { DashboardWidgetProps } from '../DashboardWidget';
function ColorToggleWidget(title: string) {
return (
<Group justify="space-between" wrap="nowrap">
<StylishText size="lg">{title}</StylishText>
<Group justify='space-between' wrap='nowrap'>
<StylishText size='lg'>{title}</StylishText>
<ColorToggle />
</Group>
);

View File

@ -4,7 +4,7 @@ import { useMemo } from 'react';
import { DocumentationLinks } from '../../../defaults/links';
import { GettingStartedCarousel } from '../../items/GettingStartedCarousel';
import { MenuLinkItem } from '../../items/MenuLinks';
import type { MenuLinkItem } from '../../items/MenuLinks';
import { StylishText } from '../../items/StylishText';
export default function GetStartedWidget() {
@ -12,7 +12,7 @@ export default function GetStartedWidget() {
return (
<Stack>
<StylishText size="xl">{t`Getting Started`}</StylishText>
<StylishText size='xl'>{t`Getting Started`}</StylishText>
<GettingStartedCarousel items={docLinks} />
</Stack>
);

View File

@ -3,12 +3,12 @@ import { Stack } from '@mantine/core';
import { LanguageSelect } from '../../items/LanguageSelect';
import { StylishText } from '../../items/StylishText';
import { DashboardWidgetProps } from '../DashboardWidget';
import type { DashboardWidgetProps } from '../DashboardWidget';
function LanguageSelectWidget(title: string) {
return (
<Stack gap="xs">
<StylishText size="lg">{title}</StylishText>
<Stack gap='xs'>
<StylishText size='lg'>{title}</StylishText>
<LanguageSelect width={140} />
</Stack>
);

View File

@ -4,7 +4,6 @@ import {
Alert,
Anchor,
Container,
Group,
ScrollArea,
Stack,
Table,
@ -28,13 +27,13 @@ import { StylishText } from '../../items/StylishText';
function NewsLink({ item }: { item: any }) {
let link: string = item.link;
if (link && link.startsWith('/')) {
link = 'https://inventree.org' + link;
if (link?.startsWith('/')) {
link = `https://inventree.org${link}`;
}
if (link) {
return (
<Anchor href={link} target="_blank">
<Anchor href={link} target='_blank'>
{item.title}
</Anchor>
);
@ -60,9 +59,9 @@ function NewsItem({
<Table.Td>
<Tooltip label={t`Mark as read`}>
<ActionIcon
size="sm"
color="green"
variant="transparent"
size='sm'
color='green'
variant='transparent'
onClick={() => onMarkRead(item.pk)}
>
<IconMailCheck />
@ -112,7 +111,7 @@ export default function NewsWidget() {
if (!user.isSuperuser()) {
return (
<Alert color="red" title={t`Requires Superuser`}>
<Alert color='red' title={t`Requires Superuser`}>
<Text>{t`This widget requires superuser permissions`}</Text>
</Alert>
);
@ -120,7 +119,7 @@ export default function NewsWidget() {
return (
<Stack>
<StylishText size="xl">{t`News Updates`}</StylishText>
<StylishText size='xl'>{t`News Updates`}</StylishText>
<ScrollArea h={400}>
<Container>
<Table>
@ -130,7 +129,7 @@ export default function NewsWidget() {
<NewsItem key={item.pk} item={item} onMarkRead={markRead} />
))
) : (
<Alert color="green" title={t`No News`}>
<Alert color='green' title={t`No News`}>
<Text>{t`There are no unread news items`}</Text>
</Alert>
)}

View File

@ -1,29 +1,21 @@
import {
ActionIcon,
Card,
Group,
Loader,
Skeleton,
Space,
Stack,
Text
} from '@mantine/core';
import { ActionIcon, Group, Loader } from '@mantine/core';
import { IconExternalLink } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { on } from 'events';
import { ReactNode, useCallback } from 'react';
import { type ReactNode, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../../App';
import { ModelType } from '../../../enums/ModelType';
import { identifierString } from '../../../functions/conversion';
import { InvenTreeIcon, InvenTreeIconType } from '../../../functions/icons';
import type { ModelType } from '../../../enums/ModelType';
import {
InvenTreeIcon,
type InvenTreeIconType
} from '../../../functions/icons';
import { navigateToLink } from '../../../functions/navigation';
import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { StylishText } from '../../items/StylishText';
import { ModelInformationDict } from '../../render/ModelType';
import { DashboardWidgetProps } from '../DashboardWidget';
import type { DashboardWidgetProps } from '../DashboardWidget';
/**
* A simple dashboard widget for displaying the number of results for a particular query
@ -81,18 +73,18 @@ function QueryCountWidget({
// TODO: Improve visual styling
return (
<Group gap="xs" wrap="nowrap">
<Group gap='xs' wrap='nowrap'>
<InvenTreeIcon icon={icon ?? modelProperties.icon} />
<Group gap="xs" wrap="nowrap" justify="space-between">
<StylishText size="md">{title}</StylishText>
<Group gap="xs" wrap="nowrap" justify="right">
<Group gap='xs' wrap='nowrap' justify='space-between'>
<StylishText size='md'>{title}</StylishText>
<Group gap='xs' wrap='nowrap' justify='right'>
{query.isFetching ? (
<Loader size="sm" />
<Loader size='sm' />
) : (
<StylishText size="sm">{query.data?.count ?? '-'}</StylishText>
<StylishText size='sm'>{query.data?.count ?? '-'}</StylishText>
)}
{modelProperties?.url_overview && (
<ActionIcon size="sm" variant="transparent" onClick={onFollowLink}>
<ActionIcon size='sm' variant='transparent' onClick={onFollowLink}>
<IconExternalLink />
</ActionIcon>
)}

View File

@ -11,14 +11,14 @@ import {
} from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import { getValueAtPath } from 'mantine-datatable';
import { ReactNode, useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { InvenTreeIcon, InvenTreeIconType } from '../../functions/icons';
import type { ModelType } from '../../enums/ModelType';
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { apiUrl } from '../../states/ApiState';
@ -30,22 +30,21 @@ import { StylishText } from '../items/StylishText';
import { getModelInfo } from '../render/ModelType';
import { StatusRenderer } from '../render/StatusRenderer';
export type DetailsField =
| {
hidden?: boolean;
icon?: InvenTreeIconType;
name: string;
label?: string;
badge?: BadgeType;
copy?: boolean;
value_formatter?: () => ValueFormatterReturn;
} & (
| StringDetailField
| BooleanField
| LinkDetailField
| ProgressBarField
| StatusField
);
export type DetailsField = {
hidden?: boolean;
icon?: InvenTreeIconType;
name: string;
label?: string;
badge?: BadgeType;
copy?: boolean;
value_formatter?: () => ValueFormatterReturn;
} & (
| StringDetailField
| BooleanField
| LinkDetailField
| ProgressBarField
| StatusField
);
type BadgeType = 'owner' | 'user' | 'group';
type ValueFormatterReturn = string | number | null | React.ReactNode;
@ -104,7 +103,7 @@ function NameBadge({
const { data } = useQuery({
queryKey: ['badge', type, pk],
queryFn: async () => {
let path: string = '';
let path = '';
switch (type) {
case 'owner':
@ -141,7 +140,7 @@ function NameBadge({
const settings = useGlobalSettingsState();
if (!data || data.isLoading || data.isFetching) {
return <Skeleton height={12} radius="md" />;
return <Skeleton height={12} radius='md' />;
}
// Rendering a user's rame for the badge
@ -162,10 +161,10 @@ function NameBadge({
}
return (
<Group wrap="nowrap" gap="sm" justify="right">
<Group wrap='nowrap' gap='sm' justify='right'>
<Badge
color="dark"
variant="filled"
color='dark'
variant='filled'
style={{ display: 'flex', alignItems: 'center' }}
>
{data?.name ?? _render_name()}
@ -176,7 +175,7 @@ function NameBadge({
}
function DateValue(props: Readonly<FieldProps>) {
return <Text size="sm">{formatDate(props.field_value?.toString())}</Text>;
return <Text size='sm'>{formatDate(props.field_value?.toString())}</Text>;
}
/**
@ -185,7 +184,7 @@ function DateValue(props: Readonly<FieldProps>) {
* If user is defined, a badge is rendered in addition to main value
*/
function TableStringValue(props: Readonly<FieldProps>) {
let value = props?.field_value;
const value = props?.field_value;
let renderedValue = null;
@ -194,19 +193,19 @@ function TableStringValue(props: Readonly<FieldProps>) {
} else if (props?.field_data?.value_formatter) {
renderedValue = props.field_data.value_formatter();
} else if (value === null || value === undefined) {
renderedValue = <Text size="sm">'---'</Text>;
renderedValue = <Text size='sm'>'---'</Text>;
} else {
renderedValue = <Text size="sm">{value.toString()}</Text>;
renderedValue = <Text size='sm'>{value.toString()}</Text>;
}
return (
<Group wrap="nowrap" gap="xs" justify="space-apart">
<Group wrap="nowrap" gap="xs" justify="left">
<Group wrap='nowrap' gap='xs' justify='space-apart'>
<Group wrap='nowrap' gap='xs' justify='left'>
{renderedValue}
{props.field_data.unit == true && <Text size="xs">{props.unit}</Text>}
{props.field_data.unit && <Text size='xs'>{props.unit}</Text>}
</Group>
{props.field_data.user && (
<NameBadge pk={props.field_data?.user} type="user" />
<NameBadge pk={props.field_data?.user} type='user' />
)}
</Group>
);
@ -265,7 +264,7 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
);
if (!data || data.isLoading || data.isFetching) {
return <Skeleton height={12} radius="md" />;
return <Skeleton height={12} radius='md' />;
}
if (props.field_data.external) {
@ -277,7 +276,7 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
>
<span style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
<Text>{props.field_value}</Text>
<InvenTreeIcon icon="external" iconProps={{ size: 15 }} />
<InvenTreeIcon icon='external' iconProps={{ size: 15 }} />
</span>
</Anchor>
);
@ -304,7 +303,7 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
return (
<>
{make_link ? (
<Anchor href="#" onClick={handleLinkClick}>
<Anchor href='#' onClick={handleLinkClick}>
<Text>{value}</Text>
</Anchor>
) : (
@ -316,7 +315,7 @@ function TableAnchorValue(props: Readonly<FieldProps>) {
function ProgressBarValue(props: Readonly<FieldProps>) {
if (props.field_data.total <= 0) {
return <Text size="sm">{props.field_data.progress}</Text>;
return <Text size='sm'>{props.field_data.progress}</Text>;
}
return (
@ -404,10 +403,10 @@ export function DetailsTable({
title?: string;
}>) {
return (
<Paper p="xs" withBorder radius="xs">
<Stack gap="xs">
{title && <StylishText size="lg">{title}</StylishText>}
<Table striped verticalSpacing={5} horizontalSpacing="sm">
<Paper p='xs' withBorder radius='xs'>
<Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>}
<Table striped verticalSpacing={5} horizontalSpacing='sm'>
<Table.Tbody>
{fields
.filter((field: DetailsField) => !field.hidden)

View File

@ -13,7 +13,7 @@ export default function DetailsBadge(props: Readonly<DetailsBadgeProps>) {
}
return (
<Badge color={props.color} variant="filled" size={props.size ?? 'lg'}>
<Badge color={props.color} variant='filled' size={props.size ?? 'lg'}>
{props.label}
</Badge>
);

View File

@ -10,13 +10,17 @@ import {
rem,
useMantineColorScheme
} from '@mantine/core';
import { Dropzone, FileWithPath, IMAGE_MIME_TYPE } from '@mantine/dropzone';
import {
Dropzone,
type FileWithPath,
IMAGE_MIME_TYPE
} from '@mantine/dropzone';
import { useHover } from '@mantine/hooks';
import { modals } from '@mantine/modals';
import { useMemo, useState } from 'react';
import { api } from '../../App';
import { UserRoles } from '../../enums/Roles';
import type { UserRoles } from '../../enums/Roles';
import { cancelEvent } from '../../functions/events';
import { InvenTreeIcon } from '../../functions/icons';
import { useEditApiFormModal } from '../../hooks/UseForm';
@ -66,7 +70,7 @@ const backup_image = '/static/img/blank_image.png';
*/
const removeModal = (apiPath: string, setImage: (image: string) => void) =>
modals.openConfirmModal({
title: <StylishText size="xl">{t`Remove Image`}</StylishText>,
title: <StylishText size='xl'>{t`Remove Image`}</StylishText>,
children: (
<Text>
<Trans>Remove the associated image from this item?</Trans>
@ -95,12 +99,12 @@ function UploadModal({
// Components to show in the Dropzone when no file is selected
const noFileIdle = (
<Group>
<InvenTreeIcon icon="photo" iconProps={{ size: '3.2rem', stroke: 1.5 }} />
<InvenTreeIcon icon='photo' iconProps={{ size: '3.2rem', stroke: 1.5 }} />
<div>
<Text size="xl" inline>
<Text size='xl' inline>
<Trans>Drag and drop to upload</Trans>
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
<Text size='sm' c='dimmed' inline mt={7}>
<Trans>Click to select file(s)</Trans>
</Text>
</div>
@ -126,16 +130,16 @@ function UploadModal({
<Image
src={imageUrl}
onLoad={() => URL.revokeObjectURL(imageUrl)}
radius="sm"
radius='sm'
height={75}
fit="contain"
fit='contain'
style={{ flexBasis: '40%' }}
/>
<div style={{ flexBasis: '60%' }}>
<Text size="xl" inline style={{ wordBreak: 'break-all' }}>
<Text size='xl' inline style={{ wordBreak: 'break-all' }}>
{file.name}
</Text>
<Text size="sm" c="dimmed" inline mt={7}>
<Text size='sm' c='dimmed' inline mt={7}>
{size.toFixed(2)} MB
</Text>
</div>
@ -178,13 +182,13 @@ function UploadModal({
loading={uploading}
>
<Group
justify="center"
gap="xl"
justify='center'
gap='xl'
style={{ minHeight: rem(140), pointerEvents: 'none' }}
>
<Dropzone.Accept>
<InvenTreeIcon
icon="upload"
icon='upload'
iconProps={{
size: '3.2rem',
stroke: 1.5,
@ -194,7 +198,7 @@ function UploadModal({
</Dropzone.Accept>
<Dropzone.Reject>
<InvenTreeIcon
icon="reject"
icon='reject'
iconProps={{
size: '3.2rem',
stroke: 1.5,
@ -223,7 +227,7 @@ function UploadModal({
}}
>
<Button
variant="outline"
variant='outline'
disabled={!currentFile}
onClick={() => setCurrentFile(null)}
>
@ -266,26 +270,26 @@ function ImageActionButtons({
<>
{visible && (
<Group
gap="xs"
gap='xs'
style={{ zIndex: 2, position: 'absolute', top: '10px', left: '10px' }}
>
{actions.selectExisting && (
<ActionButton
icon={
<InvenTreeIcon
icon="select_image"
icon='select_image'
iconProps={{ color: 'white' }}
/>
}
tooltip={t`Select from existing images`}
variant="outline"
size="lg"
tooltipAlignment="top"
variant='outline'
size='lg'
tooltipAlignment='top'
onClick={(event: any) => {
cancelEvent(event);
modals.open({
title: <StylishText size="xl">{t`Select Image`}</StylishText>,
title: <StylishText size='xl'>{t`Select Image`}</StylishText>,
size: 'xxl',
children: <PartThumbTable pk={pk} setImage={setImage} />
});
@ -297,14 +301,14 @@ function ImageActionButtons({
<ActionButton
icon={
<InvenTreeIcon
icon="download"
icon='download'
iconProps={{ color: 'white' }}
/>
}
tooltip={t`Download remote image`}
variant="outline"
size="lg"
tooltipAlignment="top"
variant='outline'
size='lg'
tooltipAlignment='top'
onClick={(event: any) => {
cancelEvent(event);
downloadImage();
@ -314,16 +318,16 @@ function ImageActionButtons({
{actions.uploadFile && (
<ActionButton
icon={
<InvenTreeIcon icon="upload" iconProps={{ color: 'white' }} />
<InvenTreeIcon icon='upload' iconProps={{ color: 'white' }} />
}
tooltip={t`Upload new image`}
variant="outline"
size="lg"
tooltipAlignment="top"
variant='outline'
size='lg'
tooltipAlignment='top'
onClick={(event: any) => {
cancelEvent(event);
modals.open({
title: <StylishText size="xl">{t`Upload Image`}</StylishText>,
title: <StylishText size='xl'>{t`Upload Image`}</StylishText>,
children: (
<UploadModal apiPath={apiPath} setImage={setImage} />
)
@ -334,12 +338,12 @@ function ImageActionButtons({
{actions.deleteFile && hasImage && (
<ActionButton
icon={
<InvenTreeIcon icon="delete" iconProps={{ color: 'red' }} />
<InvenTreeIcon icon='delete' iconProps={{ color: 'red' }} />
}
tooltip={t`Delete image`}
variant="outline"
size="lg"
tooltipAlignment="top"
variant='outline'
size='lg'
tooltipAlignment='top'
onClick={(event: any) => {
cancelEvent(event);
removeModal(apiPath, setImage);
@ -363,7 +367,7 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
// Sets a new image, and triggers upstream instance refresh
const setAndRefresh = (image: string) => {
setImg(image);
props.refresh && props.refresh();
props.refresh?.();
};
const permissions = useUserState();
@ -403,7 +407,7 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
return (
<>
{downloadImage.modal}
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
<AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos='relative'>
<>
<ApiImage
src={img}
@ -414,12 +418,12 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
{permissions.hasChangeRole(props.appRole) &&
hasOverlay &&
hovered && (
<Overlay color="black" opacity={0.8} onClick={expandImage}>
<Overlay color='black' opacity={0.8} onClick={expandImage}>
<ImageActionButtons
visible={hovered}
actions={props.imageActions}
apiPath={props.apiPath}
hasImage={props.src ? true : false}
hasImage={!!props.src}
pk={props.pk}
setImage={setAndRefresh}
downloadImage={downloadImage.open}

View File

@ -1,10 +1,10 @@
import { Paper, SimpleGrid } from '@mantine/core';
import React from 'react';
import type React from 'react';
export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) {
return (
<Paper p="xs">
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
<Paper p='xs'>
<SimpleGrid cols={2} spacing='xs' verticalSpacing='xs'>
{props.children}
</SimpleGrid>
</Paper>

View File

@ -2,14 +2,14 @@ import { t } from '@lingui/macro';
import { notifications } from '@mantine/notifications';
import { useQuery } from '@tanstack/react-query';
import DOMPurify from 'dompurify';
import EasyMDE, { default as SimpleMde } from 'easymde';
import EasyMDE, { type default as SimpleMde } from 'easymde';
import 'easymde/dist/easymde.min.css';
import { useCallback, useEffect, useMemo, useState } from 'react';
import SimpleMDE from 'react-simplemde-editor';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import type { ModelType } from '../../enums/ModelType';
import { apiUrl } from '../../states/ApiState';
import { ModelInformationDict } from '../render/ModelType';
@ -126,7 +126,7 @@ export default function NotesEditor({
.catch((error) => {
notifications.hide('notes');
let msg =
const msg =
error?.response?.data?.non_field_errors[0] ??
t`Failed to save notes`;
@ -142,7 +142,7 @@ export default function NotesEditor({
);
const editorOptions: SimpleMde.Options = useMemo(() => {
let icons: any[] = [];
const icons: any[] = [];
if (editing) {
icons.push({
@ -201,13 +201,14 @@ export default function NotesEditor({
useEffect(() => {
if (mdeInstance) {
let previewMode = !(editable && editing);
const previewMode = !(editable && editing);
mdeInstance.codemirror?.setOption('readOnly', previewMode);
// Ensure the preview mode is toggled if required
if (mdeInstance.isPreviewActive() != previewMode) {
let sibling = mdeInstance?.codemirror.getWrapperElement()?.nextSibling;
const sibling =
mdeInstance?.codemirror.getWrapperElement()?.nextSibling;
if (sibling != null) {
EasyMDE.togglePreview(mdeInstance);

View File

@ -9,7 +9,7 @@ import {
useState
} from 'react';
import { EditorComponent } from '../TemplateEditor';
import type { EditorComponent } from '../TemplateEditor';
type Tag = {
label: string;
@ -88,8 +88,8 @@ Returns: <small>${tag.returns}</small>`;
const tooltips = hoverTooltip((view, pos, side) => {
// extract the word at the current hover position into the variable text
let { from, to, text } = view.state.doc.lineAt(pos);
let start = pos,
end = pos;
let start = pos;
let end = pos;
while (start > from && /\w/.test(text[start - from - 1])) start--;
while (end < to && /\w/.test(text[end - from])) end++;
if ((start == pos && side < 0) || (end == pos && side > 0)) return null;
@ -152,7 +152,7 @@ export const CodeEditorComponent: EditorComponent = forwardRef((props, ref) => {
<div
style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }}
ref={editor}
></div>
/>
</div>
);
});

View File

@ -1,7 +1,7 @@
import { t } from '@lingui/macro';
import { IconCode } from '@tabler/icons-react';
import { Editor } from '../TemplateEditor';
import type { Editor } from '../TemplateEditor';
import { CodeEditorComponent } from './CodeEditor';
export const CodeEditor: Editor = {

View File

@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { api } from '../../../../App';
import { PreviewAreaComponent } from '../TemplateEditor';
import type { PreviewAreaComponent } from '../TemplateEditor';
export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
(props, ref) => {
@ -56,13 +56,13 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
});
}
let pdf = new Blob([preview.data], {
const pdf = new Blob([preview.data], {
type: preview.headers['content-type']
});
let srcUrl = URL.createObjectURL(pdf);
const srcUrl = URL.createObjectURL(pdf);
setPdfUrl(srcUrl + '#view=fitH');
setPdfUrl(`${srcUrl}#view=fitH`);
}
}));
@ -82,7 +82,7 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
</div>
)}
{pdfUrl && (
<iframe src={pdfUrl} width="100%" height="100%" title="PDF Preview" />
<iframe src={pdfUrl} width='100%' height='100%' title='PDF Preview' />
)}
</>
);

View File

@ -1,7 +1,7 @@
import { t } from '@lingui/macro';
import { IconFileTypePdf } from '@tabler/icons-react';
import { PreviewArea } from '../TemplateEditor';
import type { PreviewArea } from '../TemplateEditor';
import { PdfPreviewComponent } from './PdfPreview';
export const PdfPreview: PreviewArea = {

View File

@ -21,19 +21,14 @@ import {
IconRefresh
} from '@tabler/icons-react';
import Split from '@uiw/react-split';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
import type React from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { api } from '../../../App';
import { ModelType } from '../../../enums/ModelType';
import { TablerIconType } from '../../../functions/icons';
import type { TablerIconType } from '../../../functions/icons';
import { apiUrl } from '../../../states/ApiState';
import { TemplateI } from '../../../tables/settings/TemplateTable';
import type { TemplateI } from '../../../tables/settings/TemplateTable';
import { Boundary } from '../../Boundary';
import { SplitButton } from '../../buttons/SplitButton';
import { StandaloneField } from '../../forms/StandaloneField';
@ -166,13 +161,13 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
}, [editorValue]);
const updatePreview = useCallback(
async (confirmed: boolean, saveTemplate: boolean = true) => {
async (confirmed: boolean, saveTemplate = true) => {
if (!confirmed) {
openConfirmModal({
title: t`Save & Reload Preview`,
children: (
<Alert
color="yellow"
color='yellow'
icon={<IconAlertTriangle />}
title={t`Are you sure you want to Save & Reload the preview?`}
>
@ -254,7 +249,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
}, [previewApiUrl, templateFilters]);
return (
<Boundary label="TemplateEditor">
<Boundary label='TemplateEditor'>
<Stack style={{ height: '100%', flex: '1' }}>
<Split style={{ gap: '10px' }}>
<Tabs
@ -277,18 +272,18 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
<Tabs.Tab
key={Editor.key}
value={Editor.key}
leftSection={Editor.icon && <Editor.icon size="0.8rem" />}
leftSection={Editor.icon && <Editor.icon size='0.8rem' />}
>
{Editor.name}
</Tabs.Tab>
);
})}
<Group justify="right" style={{ flex: '1' }} wrap="nowrap">
<Group justify='right' style={{ flex: '1' }} wrap='nowrap'>
<SplitButton
loading={isPreviewLoading}
defaultSelected="preview_save"
name="preview-options"
defaultSelected='preview_save'
name='preview-options'
options={[
{
key: 'preview',
@ -342,7 +337,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
key={PreviewArea.key}
value={PreviewArea.key}
leftSection={
PreviewArea.icon && <PreviewArea.icon size="0.8rem" />
PreviewArea.icon && <PreviewArea.icon size='0.8rem' />
}
>
{PreviewArea.name}
@ -392,7 +387,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
<PreviewArea.component ref={previewRef} />
{errorOverlay && (
<Overlay color="red" center blur={0.2}>
<Overlay color='red' center blur={0.2}>
<CloseButton
onClick={() => setErrorOverlay(null)}
style={{
@ -401,13 +396,13 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
right: '10px',
color: '#fff'
}}
variant="filled"
variant='filled'
/>
<Alert
color="red"
color='red'
icon={<IconExclamationCircle />}
title={t`Error rendering template`}
mx="10px"
mx='10px'
>
<Code>{errorOverlay}</Code>
</Alert>

View File

@ -31,20 +31,20 @@ export default function ErrorPage({
return (
<LanguageContext>
<Center>
<Container w="md" miw={400}>
<Card withBorder shadow="xs" padding="xl" radius="sm">
<Card.Section p="lg">
<Group gap="xs">
<ActionIcon color="red" variant="transparent" size="xl">
<Container w='md' miw={400}>
<Card withBorder shadow='xs' padding='xl' radius='sm'>
<Card.Section p='lg'>
<Group gap='xs'>
<ActionIcon color='red' variant='transparent' size='xl'>
<IconExclamationCircle />
</ActionIcon>
<Text size="xl">{title}</Text>
<Text size='xl'>{title}</Text>
</Group>
</Card.Section>
<Divider />
<Card.Section p="lg">
<Stack gap="md">
<Text size="lg">{message}</Text>
<Card.Section p='lg'>
<Stack gap='md'>
<Text size='lg'>{message}</Text>
{status && (
<Text>
<Trans>Status Code</Trans>: {status}
@ -53,11 +53,11 @@ export default function ErrorPage({
</Stack>
</Card.Section>
<Divider />
<Card.Section p="lg">
<Card.Section p='lg'>
<Center>
<Button
variant="outline"
color="green"
variant='outline'
color='green'
onClick={() => navigate('/')}
>
<Trans>Return to the index page</Trans>

View File

@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
import {
Alert,
Button,
DefaultMantineColor,
type DefaultMantineColor,
Divider,
Group,
LoadingOverlay,
@ -15,19 +15,19 @@ import { notifications } from '@mantine/notifications';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
FieldValues,
type FieldValues,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
type SubmitErrorHandler,
type SubmitHandler,
useForm
} from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { api, queryClient } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
import {
NestedDict,
type NestedDict,
constructField,
constructFormUrl,
extractAvailableFields,
@ -38,13 +38,13 @@ import {
showTimeoutNotification
} from '../../functions/notifications';
import { getDetailUrl } from '../../functions/urls';
import { TableState } from '../../hooks/UseTable';
import { PathParams } from '../../states/ApiState';
import type { TableState } from '../../hooks/UseTable';
import type { PathParams } from '../../states/ApiState';
import { Boundary } from '../Boundary';
import {
ApiFormField,
ApiFormFieldSet,
ApiFormFieldType
type ApiFormFieldSet,
type ApiFormFieldType
} from './fields/ApiFormField';
export interface ApiFormAction {
@ -137,7 +137,7 @@ export function OptionsApiForm({
props.pathParams
],
queryFn: async () => {
let response = await api.options(url);
const response = await api.options(url);
let fields: Record<string, ApiFormFieldType> | null = {};
if (!props.ignorePermissionCheck) {
fields = extractAvailableFields(response, props.method);
@ -173,7 +173,7 @@ export function OptionsApiForm({
});
// If the user has specified initial data, use that value here
let value = _props?.initialData?.[k];
const value = _props?.initialData?.[k];
if (value) {
_props.fields[k].value = value;
@ -212,7 +212,7 @@ export function ApiForm({
);
const defaultValues: FieldValues = useMemo(() => {
let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => {
const defaultValuesMap = mapFields(fields ?? {}, (_path, field) => {
return field.value ?? field.default ?? undefined;
});
@ -306,9 +306,9 @@ export function ApiForm({
});
useEffect(() => {
let _fields: any = props.fields || {};
let _initialData: any = props.initialData || {};
let _fetchedData: any = initialDataQuery.data || {};
const _fields: any = props.fields || {};
const _initialData: any = props.initialData || {};
const _fetchedData: any = initialDataQuery.data || {};
for (const k of Object.keys(_fields)) {
// Ensure default values override initial field spec
@ -391,7 +391,7 @@ export function ApiForm({
const submitForm: SubmitHandler<FieldValues> = async (data) => {
setNonFieldErrors([]);
let method = props.method?.toLowerCase() ?? 'get';
const method = props.method?.toLowerCase() ?? 'get';
let hasFiles = false;
@ -400,13 +400,13 @@ export function ApiForm({
data = props.processFormData(data);
}
let jsonData = { ...data };
let formData = new FormData();
const jsonData = { ...data };
const formData = new FormData();
Object.keys(data).forEach((key: string) => {
let value: any = data[key];
let field_type = fields[key]?.field_type;
let exclude = fields[key]?.exclude;
const field_type = fields[key]?.field_type;
const exclude = fields[key]?.exclude;
if (field_type == 'file upload' && !!value) {
hasFiles = true;
@ -457,7 +457,7 @@ export function ApiForm({
navigate(getDetailUrl(props.modelType, response.data?.pk));
} else if (props.table) {
// If we want to automatically update or reload a linked table
let pk_field = props.pk_field ?? 'pk';
const pk_field = props.pk_field ?? 'pk';
if (props.pk && response?.data[pk_field]) {
props.table.updateRecord(response.data);
@ -499,8 +499,8 @@ export function ApiForm({
const path = _path ? `${_path}.${k}` : k;
// Determine if field "k" is valid (exists and is visible)
let field = fields[k];
let valid = field && !field.hidden;
const field = fields[k];
const valid = field && !field.hidden;
if (!valid || k === 'non_field_errors' || k === '__all__') {
if (Array.isArray(v)) {
@ -574,11 +574,11 @@ export function ApiForm({
<Paper mah={'65vh'} style={{ overflowY: 'auto' }}>
<div>
{/* Form Fields */}
<Stack gap="sm">
<Stack gap='sm'>
{(!isValid || nonFieldErrors.length > 0) && (
<Alert radius="sm" color="red" title={t`Form Error`}>
<Alert radius='sm' color='red' title={t`Form Error`}>
{nonFieldErrors.length > 0 ? (
<Stack gap="xs">
<Stack gap='xs'>
{nonFieldErrors.map((message) => (
<Text key={message}>{message}</Text>
))}
@ -591,19 +591,19 @@ export function ApiForm({
<Boundary label={`ApiForm-${id}-PreFormContent`}>
{props.preFormContent}
{props.preFormSuccess && (
<Alert color="green" radius="sm">
<Alert color='green' radius='sm'>
{props.preFormSuccess}
</Alert>
)}
{props.preFormWarning && (
<Alert color="orange" radius="sm">
<Alert color='orange' radius='sm'>
{props.preFormWarning}
</Alert>
)}
</Boundary>
<Boundary label={`ApiForm-${id}-FormContent`}>
<FormProvider {...form}>
<Stack gap="xs">
<Stack gap='xs'>
{Object.entries(fields).map(([fieldName, field]) => {
return (
<ApiFormField
@ -638,13 +638,13 @@ export function ApiForm({
{/* Footer with Action Buttons */}
<Divider />
<div>
<Group justify="right">
<Group justify='right'>
{props.actions?.map((action, i) => (
<Button
key={i}
key={`${i}-${action.text}`}
onClick={action.onClick}
variant={action.variant ?? 'outline'}
radius="sm"
radius='sm'
color={action.color}
>
{action.text}
@ -652,8 +652,8 @@ export function ApiForm({
))}
<Button
onClick={form.handleSubmit(submitForm, onFormError)}
variant="filled"
radius="sm"
variant='filled'
radius='sm'
color={props.submitColor ?? 'green'}
disabled={isLoading || (props.fetchInitialData && !isDirty)}
>

View File

@ -88,7 +88,7 @@ export function AuthenticationForm() {
<>
{auth_settings?.sso_enabled === true ? (
<>
<Group grow mb="md" mt="md">
<Group grow mb='md' mt='md'>
{auth_settings.providers.map((provider) => (
<SsoButton provider={provider} key={provider.id} />
))}
@ -96,8 +96,8 @@ export function AuthenticationForm() {
<Divider
label={t`Or continue with other methods`}
labelPosition="center"
my="lg"
labelPosition='center'
my='lg'
/>
</>
) : null}
@ -117,12 +117,12 @@ export function AuthenticationForm() {
{...classicForm.getInputProps('password')}
/>
{auth_settings?.password_forgotten_enabled === true && (
<Group justify="space-between" mt="0">
<Group justify='space-between' mt='0'>
<Anchor
component="button"
type="button"
c="dimmed"
size="xs"
component='button'
type='button'
c='dimmed'
size='xs'
onClick={() => navigate('/reset-password')}
>
<Trans>Reset password</Trans>
@ -136,18 +136,18 @@ export function AuthenticationForm() {
required
label={t`Email`}
description={t`We will send you a link to login - if you are registered`}
placeholder="email@example.org"
placeholder='email@example.org'
{...simpleForm.getInputProps('email')}
/>
</Stack>
)}
<Group justify="space-between" mt="xl">
<Group justify='space-between' mt='xl'>
<Anchor
component="button"
type="button"
c="dimmed"
size="xs"
component='button'
type='button'
c='dimmed'
size='xs'
onClick={() => setMode.toggle()}
>
{classicLoginMode ? (
@ -156,9 +156,9 @@ export function AuthenticationForm() {
<Trans>Use username and password</Trans>
)}
</Anchor>
<Button type="submit" disabled={isLoggingIn} onClick={handleLogin}>
<Button type='submit' disabled={isLoggingIn} onClick={handleLogin}>
{isLoggingIn ? (
<Loader size="sm" />
<Loader size='sm' />
) : (
<>
{classicLoginMode ? (
@ -235,7 +235,7 @@ export function RegistrationForm() {
required
label={t`Email`}
description={t`This will be used for a confirmation`}
placeholder="email@example.org"
placeholder='email@example.org'
{...registrationForm.getInputProps('email')}
/>
<PasswordInput
@ -252,9 +252,9 @@ export function RegistrationForm() {
/>
</Stack>
<Group justify="space-between" mt="xl">
<Group justify='space-between' mt='xl'>
<Button
type="submit"
type='submit'
disabled={isRegistering}
onClick={handleRegistration}
fullWidth
@ -265,10 +265,10 @@ export function RegistrationForm() {
</form>
)}
{both_reg_enabled && (
<Divider label={t`Or use SSO`} labelPosition="center" my="lg" />
<Divider label={t`Or use SSO`} labelPosition='center' my='lg' />
)}
{auth_settings?.sso_registration === true && (
<Group grow mb="md" mt="md">
<Group grow mb='md' mt='md'>
{auth_settings.providers.map((provider) => (
<SsoButton provider={provider} key={provider.id} />
))}
@ -293,15 +293,15 @@ export function ModeSelector({
if (registration_enabled === false) return null;
return (
<Text ta="center" size={'xs'} mt={'md'}>
<Text ta='center' size={'xs'} mt={'md'}>
{loginMode ? (
<>
<Trans>Don&apos;t have an account?</Trans>{' '}
<Anchor
component="button"
type="button"
c="dimmed"
size="xs"
component='button'
type='button'
c='dimmed'
size='xs'
onClick={() => setMode.close()}
>
<Trans>Register</Trans>
@ -309,10 +309,10 @@ export function ModeSelector({
</>
) : (
<Anchor
component="button"
type="button"
c="dimmed"
size="xs"
component='button'
type='button'
c='dimmed'
size='xs'
onClick={() => setMode.open()}
>
<Trans>Go back to login</Trans>

View File

@ -12,7 +12,7 @@ import { useForm } from '@mantine/form';
import { randomId } from '@mantine/hooks';
import { IconSquarePlus, IconTrash } from '@tabler/icons-react';
import { HostList } from '../../states/states';
import type { HostList } from '../../states/states';
export function HostOptionsForm({
data,
@ -29,7 +29,7 @@ export function HostOptionsForm({
}
const fields = Object.entries(form.values).map(([key]) => (
<Group key={key} mt="xs">
<Group key={key} mt='xs'>
{form.values[key] !== undefined && (
<>
<TextInput
@ -45,11 +45,11 @@ export function HostOptionsForm({
{...form.getInputProps(`${key}.name`)}
/>
<ActionIcon
color="red"
color='red'
onClick={() => {
deleteItem(key);
}}
variant="default"
variant='default'
>
<IconTrash />
</ActionIcon>
@ -60,23 +60,23 @@ export function HostOptionsForm({
return (
<form onSubmit={form.onSubmit(saveOptions)}>
<Box style={{ maxWidth: 500 }} mx="auto">
<Box style={{ maxWidth: 500 }} mx='auto'>
{fields.length > 0 ? (
<Group mb="xs">
<Text fw={500} size="sm" style={{ flex: 1 }}>
<Group mb='xs'>
<Text fw={500} size='sm' style={{ flex: 1 }}>
<Trans>Host</Trans>
</Text>
<Text fw={500} size="sm" style={{ flex: 1 }}>
<Text fw={500} size='sm' style={{ flex: 1 }}>
<Trans>Name</Trans>
</Text>
</Group>
) : (
<Text c="dimmed" ta="center">
<Text c='dimmed' ta='center'>
<Trans>No one here...</Trans>
</Text>
)}
{fields}
<Group mt="md">
<Group mt='md'>
<Button
onClick={() =>
form.setFieldValue(`${randomId()}`, { name: '', host: '' })
@ -86,7 +86,7 @@ export function HostOptionsForm({
<Trans>Add Host</Trans>
</Button>
<Space style={{ flex: 1 }} />
<Button type="submit">
<Button type='submit'>
<Trans>Save</Trans>
</Button>
</Group>

View File

@ -5,7 +5,7 @@ import { IconCheck } from '@tabler/icons-react';
import { useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
import { HostList } from '../../states/states';
import type { HostList } from '../../states/states';
import { EditButton } from '../buttons/EditButton';
import { HostOptionsForm } from './HostOptionsForm';

View File

@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { ApiFormField, ApiFormFieldType } from './fields/ApiFormField';
import { ApiFormField, type ApiFormFieldType } from './fields/ApiFormField';
export function StandaloneField({
fieldDefinition,

View File

@ -1,11 +1,11 @@
import { t } from '@lingui/macro';
import { Alert, FileInput, NumberInput, Stack, Switch } from '@mantine/core';
import { UseFormReturnType } from '@mantine/form';
import type { UseFormReturnType } from '@mantine/form';
import { useId } from '@mantine/hooks';
import { ReactNode, useCallback, useEffect, useMemo } from 'react';
import { Control, FieldValues, useController } from 'react-hook-form';
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
import { type Control, type FieldValues, useController } from 'react-hook-form';
import { ModelType } from '../../../enums/ModelType';
import type { ModelType } from '../../../enums/ModelType';
import { isTrue } from '../../../functions/conversion';
import { ChoiceField } from './ChoiceField';
import DateField from './DateField';
@ -167,15 +167,16 @@ export function ApiFormField({
// Callback helper when form value changes
const onChange = useCallback(
(value: any) => {
let rtnValue = value;
// Allow for custom value adjustments (per field)
if (definition.adjustValue) {
value = definition.adjustValue(value);
rtnValue = definition.adjustValue(value);
}
field.onChange(value);
field.onChange(rtnValue);
// Run custom callback for this field
definition.onValueChange?.(value);
definition.onValueChange?.(rtnValue);
},
[fieldName, definition]
);
@ -186,18 +187,18 @@ export function ApiFormField({
switch (definition.field_type) {
case 'integer':
val = parseInt(value) ?? '';
val = Number.parseInt(value) ?? '';
break;
case 'decimal':
case 'float':
case 'number':
val = parseFloat(value) ?? '';
val = Number.parseFloat(value) ?? '';
break;
default:
break;
}
if (isNaN(val) || !isFinite(val)) {
if (Number.isNaN(val) || !Number.isFinite(val)) {
val = '';
}
@ -246,8 +247,8 @@ export function ApiFormField({
ref={ref}
id={fieldId}
aria-label={`boolean-field-${fieldName}`}
radius="lg"
size="sm"
radius='lg'
size='sm'
error={error?.message}
onChange={(event) => onChange(event.currentTarget.checked)}
/>
@ -264,7 +265,7 @@ export function ApiFormField({
return (
<NumberInput
{...reducedDefinition}
radius="sm"
radius='sm'
ref={field.ref}
id={fieldId}
aria-label={`number-field-${field.name}`}
@ -289,7 +290,7 @@ export function ApiFormField({
{...reducedDefinition}
id={fieldId}
ref={field.ref}
radius="sm"
radius='sm'
value={value}
error={error?.message}
onChange={(payload: File | null) => onChange(payload)}
@ -325,7 +326,7 @@ export function ApiFormField({
);
default:
return (
<Alert color="red" title={t`Error`}>
<Alert color='red' title={t`Error`}>
Invalid field type for field '{fieldName}': '
{fieldDefinition.field_type}'
</Alert>

View File

@ -1,9 +1,9 @@
import { Select } from '@mantine/core';
import { useId } from '@mantine/hooks';
import { useCallback, useMemo } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
import { ApiFormFieldType } from './ApiFormField';
import type { ApiFormFieldType } from './ApiFormField';
/**
* Render a 'select' field for selecting from a list of choices
@ -28,7 +28,7 @@ export function ChoiceField({
// Build a set of choices for the field
const choices: any[] = useMemo(() => {
let choices = definition.choices ?? [];
const choices = definition.choices ?? [];
// TODO: Allow provision of custom render function also
@ -64,7 +64,7 @@ export function ChoiceField({
id={fieldId}
aria-label={`choice-field-${field.name}`}
error={error?.message}
radius="sm"
radius='sm'
{...field}
onChange={onChange}
data={choices}

View File

@ -2,9 +2,9 @@ import { DateInput } from '@mantine/dates';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { useCallback, useId, useMemo } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
import { ApiFormFieldType } from './ApiFormField';
import type { ApiFormFieldType } from './ApiFormField';
dayjs.extend(customParseFormat);
@ -47,7 +47,7 @@ export default function DateField({
}
// Ensure that the date is valid
if (dv instanceof Date && !isNaN(dv.getTime())) {
if (dv instanceof Date && !Number.isNaN(dv.getTime())) {
return dv;
} else {
return null;
@ -58,7 +58,7 @@ export default function DateField({
<DateInput
id={fieldId}
aria-label={`date-field-${field.name}`}
radius="sm"
radius='sm'
ref={field.ref}
type={undefined}
error={error?.message}

View File

@ -1,5 +1,9 @@
import { useEffect, useMemo } from 'react';
import { Control, FieldValues, useFormContext } from 'react-hook-form';
import {
type Control,
type FieldValues,
useFormContext
} from 'react-hook-form';
import { api } from '../../../App';
import {
@ -8,8 +12,8 @@ import {
} from '../../../functions/forms';
import {
ApiFormField,
ApiFormFieldSet,
ApiFormFieldType
type ApiFormFieldSet,
type ApiFormFieldType
} from './ApiFormField';
export function DependentField({

View File

@ -3,7 +3,7 @@ import {
Box,
CloseButton,
Combobox,
ComboboxStore,
type ComboboxStore,
Group,
Input,
InputBase,
@ -17,12 +17,12 @@ import { useDebouncedValue, useElementSize } from '@mantine/hooks';
import { IconX } from '@tabler/icons-react';
import Fuse from 'fuse.js';
import { startTransition, useEffect, useMemo, useRef, useState } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
import { FixedSizeGrid as Grid } from 'react-window';
import { useIconState } from '../../../states/IconState';
import { ApiIcon } from '../../items/ApiIcon';
import { ApiFormFieldType } from './ApiFormField';
import type { ApiFormFieldType } from './ApiFormField';
export default function IconField({
controller,
@ -52,13 +52,13 @@ export default function IconField({
required={definition.required}
error={error?.message}
ref={field.ref}
component="button"
type="button"
component='button'
type='button'
pointer
rightSection={
value !== null && !definition.required ? (
<CloseButton
size="sm"
size='sm'
onMouseDown={(e) => e.preventDefault()}
onClick={() => field.onChange(null)}
/>
@ -70,9 +70,9 @@ export default function IconField({
rightSectionPointerEvents={value === null ? 'none' : 'all'}
>
{field.value ? (
<Group gap="xs">
<Group gap='xs'>
<ApiIcon name={field.value} />
<Text size="sm" c="dimmed">
<Text size='sm' c='dimmed'>
{field.value}
</Text>
</Group>
@ -209,7 +209,7 @@ function ComboboxDropdown({
placeholder={t`Search...`}
rightSection={
searchValue && !definition.required ? (
<IconX size="1rem" onClick={() => setSearchValue('')} />
<IconX size='1rem' onClick={() => setSearchValue('')} />
) : null
}
flex={1}
@ -233,7 +233,7 @@ function ComboboxDropdown({
/>
</Group>
<Text size="sm" c="dimmed" ta="center" mt={-4}>
<Text size='sm' c='dimmed' ta='center' mt={-4}>
<Trans>{filteredIcons.length} icons</Trans>
</Text>

View File

@ -1,10 +1,10 @@
import { Accordion, Divider, Stack, Text } from '@mantine/core';
import { Control, FieldValues } from 'react-hook-form';
import type { Control, FieldValues } from 'react-hook-form';
import {
ApiFormField,
ApiFormFieldSet,
ApiFormFieldType
type ApiFormFieldSet,
type ApiFormFieldType
} from './ApiFormField';
export function NestedObjectField({
@ -21,14 +21,14 @@ export function NestedObjectField({
setFields?: React.Dispatch<React.SetStateAction<ApiFormFieldSet>>;
}>) {
return (
<Accordion defaultValue={'OpenByDefault'} variant="contained">
<Accordion defaultValue={'OpenByDefault'} variant='contained'>
<Accordion.Item value={'OpenByDefault'}>
<Accordion.Control icon={definition.icon}>
<Text>{definition.label}</Text>
</Accordion.Control>
<Accordion.Panel>
<Divider style={{ marginTop: '-10px', marginBottom: '10px' }} />
<Stack gap="xs">
<Stack gap='xs'>
{Object.entries(definition.children ?? {}).map(
([childFieldName, field]) => (
<ApiFormField

View File

@ -9,8 +9,8 @@ import { useDebouncedValue, useId } from '@mantine/hooks';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
FieldValues,
UseControllerReturn,
type FieldValues,
type UseControllerReturn,
useFormContext
} from 'react-hook-form';
import Select from 'react-select';
@ -18,7 +18,7 @@ import Select from 'react-select';
import { api } from '../../../App';
import { vars } from '../../../theme';
import { RenderInstance } from '../../render/Instance';
import { ApiFormFieldType } from './ApiFormField';
import type { ApiFormFieldType } from './ApiFormField';
/**
* Render a 'select' field for searching the database against a particular model type
@ -71,8 +71,8 @@ export function RelatedModelField({
}
api.get(url).then((response) => {
let pk_field = definition.pk_field ?? 'pk';
if (response.data && response.data[pk_field]) {
const pk_field = definition.pk_field ?? 'pk';
if (response.data?.[pk_field]) {
const value = {
value: response.data[pk_field],
data: response.data
@ -138,7 +138,7 @@ export function RelatedModelField({
setFilters(_filters);
}
let params = {
const params = {
..._filters,
search: searchText,
offset: offset,
@ -158,8 +158,8 @@ export function RelatedModelField({
const results = response.data?.results ?? response.data ?? [];
results.forEach((item: any) => {
let pk_field = definition.pk_field ?? 'pk';
let pk = item[pk_field];
const pk_field = definition.pk_field ?? 'pk';
const pk = item[pk_field];
if (pk && !alreadyPresentPks.includes(pk)) {
values.push({
@ -201,7 +201,7 @@ export function RelatedModelField({
// Update form values when the selected value changes
const onChange = useCallback(
(value: any) => {
let _pk = value?.value ?? null;
const _pk = value?.value ?? null;
field.onChange(_pk);
setPk(_pk);
@ -230,7 +230,7 @@ export function RelatedModelField({
return null;
}
let _data = [...data, initialData];
const _data = [...data, initialData];
return _data.find((item) => item.value === pk);
}, [pk, data]);
@ -316,11 +316,11 @@ export function RelatedModelField({
isClearable={!definition.required}
isDisabled={definition.disabled}
isSearchable={true}
placeholder={definition.placeholder || t`Search` + `...`}
loadingMessage={() => t`Loading` + `...`}
placeholder={definition.placeholder || `${t`Search`}...`}
loadingMessage={() => `${t`Loading`}...`}
menuPortalTarget={document.body}
noOptionsMessage={() => t`No results found`}
menuPosition="fixed"
menuPosition='fixed'
styles={{ menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) }}
formatOptionLabel={(option: any) => formatOption(option)}
theme={(theme) => {

View File

@ -1,13 +1,13 @@
import { Trans, t } from '@lingui/macro';
import { Alert, Container, Group, Stack, Table, Text } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react';
import { ReactNode, useCallback, useEffect, useMemo } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
import { identifierString } from '../../../functions/conversion';
import { InvenTreeIcon } from '../../../functions/icons';
import { StandaloneField } from '../StandaloneField';
import { ApiFormFieldType } from './ApiFormField';
import type { ApiFormFieldType } from './ApiFormField';
export interface TableFieldRowProps {
item: any;
@ -38,10 +38,10 @@ function TableFieldRow({
// Table fields require render function
if (!definition.modelRenderer) {
return (
<Table.Tr key="table-row-no-renderer">
<Table.Tr key='table-row-no-renderer'>
<Table.Td colSpan={definition.headers?.length}>
<Alert color="red" title={t`Error`} icon={<IconExclamationCircle />}>
{`modelRenderer entry required for tables`}
<Alert color='red' title={t`Error`} icon={<IconExclamationCircle />}>
{t`modelRenderer entry required for tables`}
</Alert>
</Table.Td>
</Table.Tr>
@ -67,13 +67,13 @@ export function TableFieldErrorWrapper({
errorKey: string;
children: ReactNode;
}) {
const msg = props?.rowErrors && props.rowErrors[errorKey];
const msg = props?.rowErrors?.[errorKey];
return (
<Stack gap="xs">
<Stack gap='xs'>
{children}
{msg && (
<Text size="xs" c="red">
<Text size='xs' c='red'>
{msg.message}
</Text>
)}
@ -151,7 +151,7 @@ export function TableField({
);
})
) : (
<Table.Tr key="table-row-no-entries">
<Table.Tr key='table-row-no-entries'>
<Table.Td
style={{ textAlign: 'center' }}
colSpan={definition.headers?.length}
@ -163,7 +163,7 @@ export function TableField({
gap: '5px'
}}
>
<InvenTreeIcon icon="info" />
<InvenTreeIcon icon='info' />
<Trans>No entries available</Trans>
</span>
</Table.Td>
@ -215,9 +215,9 @@ export function TableFieldExtraRow({
visible && (
<Table.Tr>
<Table.Td colSpan={10}>
<Group grow preventGrowOverflow={false} justify="flex-apart" p="xs">
<Container flex={0} p="xs">
<InvenTreeIcon icon="downright" />
<Group grow preventGrowOverflow={false} justify='flex-apart' p='xs'>
<Container flex={0} p='xs'>
<InvenTreeIcon icon='downright' />
</Container>
<StandaloneField
fieldDefinition={field}

View File

@ -2,7 +2,7 @@ import { TextInput } from '@mantine/core';
import { useDebouncedValue } from '@mantine/hooks';
import { IconX } from '@tabler/icons-react';
import { useCallback, useEffect, useId, useState } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
/*
* Custom implementation of the mantine <TextInput> component,
@ -57,7 +57,7 @@ export default function TextField({
type={definition.field_type}
value={rawText || ''}
error={error?.message}
radius="sm"
radius='sm'
onChange={(event) => onTextChange(event.currentTarget.value)}
onBlur={(event) => {
if (event.currentTarget.value != value) {
@ -67,7 +67,7 @@ export default function TextField({
onKeyDown={(event) => onKeyDown(event.code)}
rightSection={
value && !definition.required ? (
<IconX size="1rem" color="red" onClick={() => onTextChange('')} />
<IconX size='1rem' color='red' onClick={() => onTextChange('')} />
) : null
}
/>

View File

@ -3,7 +3,7 @@
*
* Image caching is handled automagically by the browsers cache
*/
import { Image, ImageProps, Skeleton, Stack } from '@mantine/core';
import { Image, type ImageProps, Skeleton, Stack } from '@mantine/core';
import { useMemo } from 'react';
import { useLocalState } from '../../states/LocalState';
@ -25,7 +25,7 @@ export function ApiImage(props: Readonly<ApiImageProps>) {
return (
<Stack>
{imageUrl ? (
<Image {...props} src={imageUrl} fit="contain" />
<Image {...props} src={imageUrl} fit='contain' />
) : (
<Skeleton h={props?.h ?? props.w} w={props?.w ?? props.h} />
)}

View File

@ -1,6 +1,6 @@
import { t } from '@lingui/macro';
import { Anchor, Group } from '@mantine/core';
import { ReactNode, useMemo } from 'react';
import { type ReactNode, useMemo } from 'react';
import { ApiImage } from './ApiImage';
@ -27,7 +27,7 @@ export function Thumbnail({
const inner = useMemo(() => {
if (link) {
return (
<Anchor href={link} target="_blank">
<Anchor href={link} target='_blank'>
{text}
</Anchor>
);
@ -37,13 +37,13 @@ export function Thumbnail({
}, [link, text]);
return (
<Group align={align ?? 'left'} gap="xs" wrap="nowrap">
<Group align={align ?? 'left'} gap='xs' wrap='nowrap'>
<ApiImage
src={src || backup_image}
aria-label={alt}
w={size}
fit="contain"
radius="xs"
fit='contain'
radius='xs'
style={{ maxHeight: size }}
/>
{inner}

View File

@ -7,7 +7,7 @@ import {
IconCircleDashedCheck,
IconExclamationCircle
} from '@tabler/icons-react';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
@ -16,20 +16,20 @@ import {
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { ImportSessionState } from '../../hooks/UseImportSession';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { TableColumn } from '../../tables/Column';
import { TableFilter } from '../../tables/Filter';
import type { TableColumn } from '../../tables/Column';
import type { TableFilter } from '../../tables/Filter';
import { InvenTreeTable } from '../../tables/InvenTreeTable';
import {
RowAction,
type RowAction,
RowDeleteAction,
RowEditAction
} from '../../tables/RowActions';
import { ActionButton } from '../buttons/ActionButton';
import { YesNoButton } from '../buttons/YesNoButton';
import { ApiFormFieldSet } from '../forms/fields/ApiFormField';
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
import { ProgressBar } from '../items/ProgressBar';
import { RenderRemoteInstance } from '../render/Instance';
@ -63,7 +63,7 @@ function ImporterDataCell({
}, [row.errors, column.field]);
const cellValue: ReactNode = useMemo(() => {
let field_def = session.availableFields[column.field];
const field_def = session.availableFields[column.field];
if (!row?.data) {
return '-';
@ -88,7 +88,7 @@ function ImporterDataCell({
break;
}
let value = row.data ? row.data[column.field] ?? '' : '';
let value = row.data ? (row.data[column.field] ?? '') : '';
if (!value) {
value = '-';
@ -105,18 +105,18 @@ function ImporterDataCell({
return (
<HoverCard disabled={cellValid} openDelay={100} closeDelay={100}>
<HoverCard.Target>
<Group grow justify="apart" onClick={onRowEdit}>
<Group grow justify='apart' onClick={onRowEdit}>
<Group grow style={{ flex: 1 }}>
<Text size="xs" c={cellValid ? undefined : 'red'}>
<Text size='xs' c={cellValid ? undefined : 'red'}>
{cellValue}
</Text>
</Group>
</Group>
</HoverCard.Target>
<HoverCard.Dropdown>
<Stack gap="xs">
<Stack gap='xs'>
{cellErrors.map((error: string) => (
<Text size="xs" c="red" key={error}>
<Text size='xs' c='red' key={error}>
{error}
</Text>
))}
@ -136,11 +136,11 @@ export default function ImporterDataSelector({
const [selectedFieldNames, setSelectedFieldNames] = useState<string[]>([]);
const selectedFields: ApiFormFieldSet = useMemo(() => {
let fields: ApiFormFieldSet = {};
const fields: ApiFormFieldSet = {};
for (let field of selectedFieldNames) {
for (const field of selectedFieldNames) {
// Find the field definition in session.availableFields
let fieldDef = session.availableFields[field];
const fieldDef = session.availableFields[field];
if (fieldDef) {
// Construct field filters based on session field filters
let filters = fieldDef.filters ?? {};
@ -243,7 +243,7 @@ export default function ImporterDataSelector({
return [];
}
let errors: string[] = [];
const errors: string[] = [];
for (const k of Object.keys(row.errors)) {
if (row.errors[k]) {
@ -261,7 +261,7 @@ export default function ImporterDataSelector({
}, []);
const columns: TableColumn[] = useMemo(() => {
let columns: TableColumn[] = [
const columns: TableColumn[] = [
{
accessor: 'row_index',
title: t`Row`,
@ -269,22 +269,22 @@ export default function ImporterDataSelector({
switchable: false,
render: (row: any) => {
return (
<Group justify="left" gap="xs">
<Text size="sm">{row.row_index}</Text>
{row.complete && <IconCircleCheck color="green" size={16} />}
<Group justify='left' gap='xs'>
<Text size='sm'>{row.row_index}</Text>
{row.complete && <IconCircleCheck color='green' size={16} />}
{!row.complete && row.valid && (
<IconCircleDashedCheck color="blue" size={16} />
<IconCircleDashedCheck color='blue' size={16} />
)}
{!row.complete && !row.valid && (
<HoverCard openDelay={50} closeDelay={100}>
<HoverCard.Target>
<IconExclamationCircle color="red" size={16} />
<IconExclamationCircle color='red' size={16} />
</HoverCard.Target>
<HoverCard.Dropdown>
<Stack gap="xs">
<Stack gap='xs'>
<Text>{t`Row contains errors`}:</Text>
{rowErrors(row).map((error: string) => (
<Text size="sm" c="red" key={error}>
<Text size='sm' c='red' key={error}>
{error}
</Text>
))}
@ -377,10 +377,10 @@ export default function ImporterDataSelector({
return [
<ActionButton
key="import-selected-rows"
key='import-selected-rows'
disabled={!canImport}
icon={<IconArrowRight />}
color="green"
color='green'
tooltip={t`Import selected rows`}
onClick={() => {
importData(table.selectedRecords.map((row: any) => row.pk));
@ -393,10 +393,10 @@ export default function ImporterDataSelector({
<>
{editRow.modal}
{deleteRow.modal}
<Stack gap="xs">
<Paper shadow="xs" p="xs">
<Group grow justify="apart">
<Text size="lg">{t`Processing Data`}</Text>
<Stack gap='xs'>
<Paper shadow='xs' p='xs'>
<Group grow justify='apart'>
<Text size='lg'>{t`Processing Data`}</Text>
<Space />
<ProgressBar
maximum={session.rowCount}

View File

@ -15,10 +15,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ImportSessionState } from '../../hooks/UseImportSession';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import { apiUrl } from '../../states/ApiState';
import { StandaloneField } from '../forms/StandaloneField';
import { ApiFormFieldType } from '../forms/fields/ApiFormField';
import type { ApiFormFieldType } from '../forms/fields/ApiFormField';
function ImporterColumn({
column,
@ -81,7 +81,7 @@ function ImporterDefaultField({
const onChange = useCallback(
(value: any) => {
// Update the default value for the field
let defaults = {
const defaults = {
...session.fieldDefaults,
[fieldName]: value
};
@ -133,19 +133,19 @@ function ImporterColumnTableRow({
return (
<Table.Tr key={column.label ?? column.field}>
<Table.Td>
<Group gap="xs">
<Group gap='xs'>
<Text fw={column.required ? 700 : undefined}>
{column.label ?? column.field}
</Text>
{column.required && (
<Text c="red" fw={700}>
<Text c='red' fw={700}>
*
</Text>
)}
</Group>
</Table.Td>
<Table.Td>
<Text size="sm">{column.description}</Text>
<Text size='sm'>{column.description}</Text>
</Table.Td>
<Table.Td>
<ImporterColumn column={column} options={options} />
@ -193,12 +193,12 @@ export default function ImporterColumnSelector({
}, [session.availableColumns]);
return (
<Stack gap="xs">
<Paper shadow="xs" p="xs">
<Group grow justify="apart">
<Text size="lg">{t`Mapping data columns to database fields`}</Text>
<Stack gap='xs'>
<Paper shadow='xs' p='xs'>
<Group grow justify='apart'>
<Text size='lg'>{t`Mapping data columns to database fields`}</Text>
<Space />
<Button color="green" variant="filled" onClick={acceptMapping}>
<Button color='green' variant='filled' onClick={acceptMapping}>
<Group>
<IconCheck />
{t`Accept Column Mapping`}
@ -207,7 +207,7 @@ export default function ImporterColumnSelector({
</Group>
</Paper>
{errorMessage && (
<Alert color="red" title={t`Error`}>
<Alert color='red' title={t`Error`}>
<Text>{errorMessage}</Text>
</Alert>
)}

View File

@ -14,7 +14,7 @@ import {
Text
} from '@mantine/core';
import { IconCheck } from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react';
import { type ReactNode, useMemo } from 'react';
import { ModelType } from '../../enums/ModelType';
import { useImportSession } from '../../hooks/UseImportSession';
@ -41,7 +41,7 @@ function ImportDrawerStepper({
onStepClick={undefined}
allowNextStepsSelect={false}
iconSize={20}
size="xs"
size='xs'
>
<Stepper.Step label={t`Upload File`} />
<Stepper.Step label={t`Map Columns`} />
@ -100,24 +100,24 @@ export default function ImporterDrawer({
return <ImporterDataSelector session={session} />;
case importSessionStatus.COMPLETE:
return (
<Stack gap="xs">
<Stack gap='xs'>
<Alert
color="green"
color='green'
title={t`Import Complete`}
icon={<IconCheck />}
>
{t`Data has been imported successfully`}
</Alert>
<Button color="blue" onClick={onClose}>{t`Close`}</Button>
<Button color='blue' onClick={onClose}>{t`Close`}</Button>
</Stack>
);
default:
return (
<Stack gap="xs">
<Alert color="red" title={t`Unknown Status`} icon={<IconCheck />}>
<Stack gap='xs'>
<Alert color='red' title={t`Unknown Status`} icon={<IconCheck />}>
{t`Import session has unknown status`}: {session.status}
</Alert>
<Button color="red" onClick={onClose}>{t`Close`}</Button>
<Button color='red' onClick={onClose}>{t`Close`}</Button>
</Stack>
);
}
@ -125,15 +125,15 @@ export default function ImporterDrawer({
const title: ReactNode = useMemo(() => {
return (
<Stack gap="xs" style={{ width: '100%' }}>
<Stack gap='xs' style={{ width: '100%' }}>
<Group
gap="xs"
wrap="nowrap"
justify="space-apart"
gap='xs'
wrap='nowrap'
justify='space-apart'
grow
preventGrowOverflow={false}
>
<StylishText size="lg">
<StylishText size='lg'>
{session.sessionData?.statusText ?? t`Importing Data`}
</StylishText>
<ImportDrawerStepper currentStep={currentStep} />
@ -146,8 +146,8 @@ export default function ImporterDrawer({
return (
<Drawer
position="bottom"
size="80%"
position='bottom'
size='80%'
title={title}
opened={opened}
onClose={onClose}
@ -163,9 +163,9 @@ export default function ImporterDrawer({
}
}}
>
<Stack gap="xs">
<Stack gap='xs'>
<LoadingOverlay visible={session.sessionQuery.isFetching} />
<Paper p="md">{session.sessionQuery.isFetching || widget}</Paper>
<Paper p='md'>{session.sessionQuery.isFetching || widget}</Paper>
</Stack>
</Drawer>
);

View File

@ -4,7 +4,7 @@ import { useInterval } from '@mantine/hooks';
import { useEffect } from 'react';
import { ModelType } from '../../enums/ModelType';
import { ImportSessionState } from '../../hooks/UseImportSession';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import useStatusCodes from '../../hooks/UseStatusCodes';
import { StylishText } from '../items/StylishText';
@ -32,10 +32,10 @@ export default function ImporterImportProgress({
return (
<Center>
<Container>
<Stack gap="xs">
<StylishText size="lg">{t`Importing Records`}</StylishText>
<Stack gap='xs'>
<StylishText size='lg'>{t`Importing Records`}</StylishText>
<Loader />
<Text size="lg">
<Text size='lg'>
{t`Imported Rows`}: {session.sessionData.row_count}
</Text>
</Stack>

View File

@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
import {
Button,
Indicator,
IndicatorProps,
type IndicatorProps,
Menu,
Tooltip
} from '@mantine/core';
@ -17,9 +17,9 @@ import {
IconTrash,
IconUnlink
} from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react';
import { type ReactNode, useMemo } from 'react';
import { ModelType } from '../../enums/ModelType';
import type { ModelType } from '../../enums/ModelType';
import { identifierString } from '../../functions/conversion';
import { InvenTreeIcon } from '../../functions/icons';
import { InvenTreeQRCode, QRCodeLink, QRCodeUnlink } from './QRCode';
@ -67,17 +67,17 @@ export function ActionDropdown({
}, [tooltip]);
return !hidden && hasActions ? (
<Menu position="bottom-end" key={menuName}>
<Menu position='bottom-end' key={menuName}>
<Indicator disabled={!indicatorProps} {...indicatorProps?.indicator}>
<Menu.Target>
<Tooltip label={tooltip} hidden={!tooltip} position="bottom">
<Tooltip label={tooltip} hidden={!tooltip} position='bottom'>
<Button
radius="sm"
radius='sm'
variant={noindicator ? 'transparent' : 'light'}
disabled={disabled}
aria-label={menuName}
p="0"
size="sm"
p='0'
size='sm'
rightSection={
noindicator ? null : <IconChevronDown stroke={1.5} />
}
@ -102,7 +102,7 @@ export function ActionDropdown({
<Tooltip
label={action.tooltip}
hidden={!action.tooltip}
position="left"
position='left'
>
<Menu.Item
aria-label={id}
@ -232,7 +232,7 @@ function GeneralBarcodeAction({
export function EditItemAction(props: ActionDropdownItem): ActionDropdownItem {
return {
...props,
icon: <IconEdit color="blue" />,
icon: <IconEdit color='blue' />,
name: t`Edit`,
tooltip: props.tooltip ?? t`Edit item`
};
@ -244,7 +244,7 @@ export function DeleteItemAction(
): ActionDropdownItem {
return {
...props,
icon: <IconTrash color="red" />,
icon: <IconTrash color='red' />,
name: t`Delete`,
tooltip: props.tooltip ?? t`Delete item`
};
@ -253,7 +253,7 @@ export function DeleteItemAction(
export function HoldItemAction(props: ActionDropdownItem): ActionDropdownItem {
return {
...props,
icon: <InvenTreeIcon icon="hold" iconProps={{ color: 'orange' }} />,
icon: <InvenTreeIcon icon='hold' iconProps={{ color: 'orange' }} />,
name: t`Hold`,
tooltip: props.tooltip ?? t`Hold`
};
@ -264,7 +264,7 @@ export function CancelItemAction(
): ActionDropdownItem {
return {
...props,
icon: <InvenTreeIcon icon="cancel" iconProps={{ color: 'red' }} />,
icon: <InvenTreeIcon icon='cancel' iconProps={{ color: 'red' }} />,
name: t`Cancel`,
tooltip: props.tooltip ?? t`Cancel`
};
@ -276,7 +276,7 @@ export function DuplicateItemAction(
): ActionDropdownItem {
return {
...props,
icon: <IconCopy color="green" />,
icon: <IconCopy color='green' />,
name: t`Duplicate`,
tooltip: props.tooltip ?? t`Duplicate item`
};

View File

@ -9,9 +9,9 @@ type ApiIconProps = {
export const ApiIcon = ({ name: _name, size = 22 }: ApiIconProps) => {
const [iconPackage, name, variant] = _name.split(':');
const icon = useIconState(
(s) => s.packagesMap[iconPackage]?.['icons'][name]?.['variants'][variant]
(s) => s.packagesMap[iconPackage]?.icons[name]?.variants[variant]
);
const unicode = icon ? String.fromCodePoint(parseInt(icon, 16)) : '';
const unicode = icon ? String.fromCodePoint(Number.parseInt(icon, 16)) : '';
return (
<i

View File

@ -9,7 +9,7 @@ import {
IconLink,
IconPhoto
} from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react';
import { type ReactNode, useMemo } from 'react';
import { useLocalState } from '../../states/LocalState';
@ -18,7 +18,7 @@ import { useLocalState } from '../../states/LocalState';
*/
export function attachmentIcon(attachment: string): ReactNode {
const sz = 18;
let suffix = attachment.split('.').pop()?.toLowerCase() ?? '';
const suffix = attachment.split('.').pop()?.toLowerCase() ?? '';
switch (suffix) {
case 'pdf':
return <IconFileTypePdf size={sz} />;
@ -59,7 +59,7 @@ export function AttachmentLink({
attachment: string;
external?: boolean;
}>): ReactNode {
let text = external ? attachment : attachment.split('/').pop();
const text = external ? attachment : attachment.split('/').pop();
const host = useLocalState((s) => s.host);
@ -72,9 +72,9 @@ export function AttachmentLink({
}, [host, attachment, external]);
return (
<Group justify="left" gap="sm" wrap="nowrap">
<Group justify='left' gap='sm' wrap='nowrap'>
{external ? <IconLink /> : attachmentIcon(attachment)}
<Anchor href={url} target="_blank" rel="noopener noreferrer">
<Anchor href={url} target='_blank' rel='noopener noreferrer'>
{text}
</Anchor>
</Group>

View File

@ -1,7 +1,8 @@
import { t } from '@lingui/macro';
import { ActionIcon, Box, Button, Divider, TextInput } from '@mantine/core';
import { IconQrcode } from '@tabler/icons-react';
import React, { useState } from 'react';
import type React from 'react';
import { useState } from 'react';
import { InputImageBarcode } from '../../pages/Index/Scan';
@ -47,10 +48,10 @@ export function BarcodeInput({
<IconQrcode />
</ActionIcon>
}
w="100%"
w='100%'
/>
{onAction ? (
<Button color="green" onClick={onAction} mt="lg" fullWidth>
<Button color='green' onClick={onAction} mt='lg' fullWidth>
{actionText}
</Button>
) : null}

View File

@ -7,15 +7,15 @@ export function ColorToggle() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
return (
<Group justify="center">
<Group justify='center'>
<ActionIcon
onClick={toggleColorScheme}
size="lg"
size='lg'
style={{
color:
colorScheme === 'dark' ? vars.colors.yellow[4] : vars.colors.blue[6]
}}
variant="transparent"
variant='transparent'
>
{colorScheme === 'dark' ? <IconSun /> : <IconMoonStars />}
</ActionIcon>

View File

@ -17,15 +17,15 @@ export function StatisticItem({
isLoading: boolean;
}>) {
return (
<Paper withBorder p="xs" key={id} pos="relative">
<Paper withBorder p='xs' key={id} pos='relative'>
<LoadingOverlay visible={isLoading} overlayProps={{ blur: 2 }} />
<Group justify="space-between">
<Text size="xs" c="dimmed" className={classes.dashboardItemTitle}>
<Group justify='space-between'>
<Text size='xs' c='dimmed' className={classes.dashboardItemTitle}>
{data.title}
</Text>
</Group>
<Group align="flex-end" gap="xs" mt={25}>
<Group align='flex-end' gap='xs' mt={25}>
<Text className={classes.dashboardItemValue}>{data.value}</Text>
</Group>
</Paper>

View File

@ -1,6 +1,6 @@
import { IconInfoCircle } from '@tabler/icons-react';
import { BaseDocProps, DocTooltip } from './DocTooltip';
import { type BaseDocProps, DocTooltip } from './DocTooltip';
interface DocInfoProps extends BaseDocProps {
size?: number;

View File

@ -24,7 +24,7 @@ export function DocTooltip({
}: Readonly<DocTooltipProps>) {
return (
<HoverCard
shadow="md"
shadow='md'
openDelay={200}
closeDelay={200}
withinPortal={true}
@ -63,7 +63,7 @@ function ConstBody({
useEffect(() => {
if (ref.current == null) return;
let height = ref.current['clientHeight'];
const height = ref.current['clientHeight'];
if (height > 250) {
setHeight(250);
} else {
@ -78,7 +78,7 @@ function ConstBody({
<ScrollArea h={height} mah={250}>
<div ref={ref}>
{detail && (
<Text size="xs" c="dimmed">
<Text size='xs' c='dimmed'>
{detail}
</Text>
)}
@ -87,7 +87,7 @@ function ConstBody({
</ScrollArea>
)}
{link && (
<Anchor href={link} target="_blank">
<Anchor href={link} target='_blank'>
<Text size={'sm'}>
<Trans>Read More</Trans>
</Text>

View File

@ -9,7 +9,7 @@ export function ErrorItem({
return (
<>
<Alert color="red" title={t`An error occurred`}>
<Alert color='red' title={t`An error occurred`}>
<Text>{error_message}</Text>
</Alert>
</>

View File

@ -3,19 +3,19 @@ import { Carousel } from '@mantine/carousel';
import { Anchor, Button, Paper, Text } from '@mantine/core';
import * as classes from './GettingStartedCarousel.css';
import { MenuLinkItem } from './MenuLinks';
import type { MenuLinkItem } from './MenuLinks';
import { StylishText } from './StylishText';
function StartedCard({ title, description, link }: MenuLinkItem) {
return (
<Paper shadow="md" p="xl" radius="md" className={classes.card}>
<Paper shadow='md' p='xl' radius='md' className={classes.card}>
<div>
<StylishText size="md">{title}</StylishText>
<Text size="sm" className={classes.category} lineClamp={2}>
<StylishText size='md'>{title}</StylishText>
<Text size='sm' className={classes.category} lineClamp={2}>
{description}
</Text>
</div>
<Anchor href={link} target="_blank">
<Anchor href={link} target='_blank'>
<Button>
<Trans>Read More</Trans>
</Button>
@ -40,7 +40,7 @@ export function GettingStartedCarousel({
slideSize={{ base: '100%', sm: '50%', md: '33.333333%' }}
slideGap={{ base: 0, sm: 'md' }}
slidesToScroll={3}
align="start"
align='start'
loop
>
{slides}

View File

@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro';
import { Code, Flex, Group, Text } from '@mantine/core';
import { Link, To } from 'react-router-dom';
import { Link, type To } from 'react-router-dom';
import { YesNoButton } from '../buttons/YesNoButton';
import { DetailDrawerLink } from '../nav/DetailDrawer';
@ -43,8 +43,8 @@ export function InfoItem({
}
return (
<Group justify="space-between">
<Text fz="sm" fw={700}>
<Group justify='space-between'>
<Text fz='sm' fw={700}>
{name}:
</Text>
<Flex>

View File

@ -10,7 +10,7 @@ export const InvenTreeLogoHomeButton = forwardRef<HTMLDivElement>(
return (
<div ref={ref} {...props}>
<NavLink to={'/'}>
<ActionIcon size={28} variant="transparent">
<ActionIcon size={28} variant='transparent'>
<InvenTreeLogo />
</ActionIcon>
</NavLink>

View File

@ -37,7 +37,7 @@ export function LanguageSelect({ width = 80 }: Readonly<{ width?: number }>) {
value={value}
onChange={setValue}
searchable
aria-label="Select language"
aria-label='Select language'
/>
);
}

View File

@ -9,17 +9,17 @@ export function LanguageToggle() {
return (
<Group
justify="center"
justify='center'
style={{
border: open === true ? `1px dashed ` : ``,
border: open === true ? '1px dashed' : '',
margin: open === true ? 2 : 12,
padding: open === true ? 8 : 0
}}
>
<ActionIcon
onClick={() => toggle.toggle()}
size="lg"
variant="transparent"
size='lg'
variant='transparent'
>
<IconLanguage />
</ActionIcon>

View File

@ -8,11 +8,10 @@ import {
Tooltip,
UnstyledButton
} from '@mantine/core';
import { IconLink } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { InvenTreeIcon, InvenTreeIconType } from '../../functions/icons';
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
import { navigateToLink } from '../../functions/navigation';
import { StylishText } from './StylishText';
@ -50,9 +49,9 @@ export function MenuLinks({
return (
<>
<Stack gap="xs">
<Stack gap='xs'>
<Divider />
<StylishText size="md">{title}</StylishText>
<StylishText size='md'>{title}</StylishText>
<Divider />
<SimpleGrid cols={2} spacing={0} p={3}>
{visibleLinks.map((item) => (
@ -63,7 +62,7 @@ export function MenuLinks({
>
{item.link && item.external ? (
<Anchor href={item.link}>
<Group wrap="nowrap">
<Group wrap='nowrap'>
{item.external && (
<InvenTreeIcon
icon={item.icon ?? 'link'}
@ -87,7 +86,7 @@ export function MenuLinks({
}
}}
>
<Group wrap="nowrap">
<Group wrap='nowrap'>
{item.icon && (
<InvenTreeIcon
icon={item.icon}

View File

@ -13,7 +13,7 @@ export function PlaceholderPill() {
withArrow
label={t`This feature/button/site is a placeholder for a feature that is not implemented, only partial or intended for testing.`}
>
<Badge color="teal" variant="outline">
<Badge color='teal' variant='outline'>
<Trans>PLH</Trans>
</Badge>
</Tooltip>
@ -27,11 +27,11 @@ export function PlaceholderPanel() {
return (
<Stack>
<Alert
color="teal"
color='teal'
title={t`This panel is a placeholder.`}
icon={<IconInfoCircle />}
>
<Text c="gray">This panel has not yet been implemented</Text>
<Text c='gray'>This panel has not yet been implemented</Text>
</Alert>
</Stack>
);

View File

@ -15,8 +15,8 @@ export type ProgressBarProps = {
*/
export function ProgressBar(props: Readonly<ProgressBarProps>) {
const progress = useMemo(() => {
let maximum = props.maximum ?? 100;
let value = Math.max(props.value, 0);
const maximum = props.maximum ?? 100;
const value = Math.max(props.value, 0);
if (maximum == 0) {
return 0;
@ -28,7 +28,7 @@ export function ProgressBar(props: Readonly<ProgressBarProps>) {
return (
<Stack gap={2} style={{ flexGrow: 1, minWidth: '100px' }}>
{props.progressLabel && (
<Text ta="center" size="xs">
<Text ta='center' size='xs'>
{props.value} / {props.maximum}
</Text>
)}
@ -36,7 +36,7 @@ export function ProgressBar(props: Readonly<ProgressBarProps>) {
value={progress}
color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
size={props.size ?? 'md'}
radius="sm"
radius='sm'
/>
</Stack>
);

View File

@ -21,7 +21,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { apiUrl } from '../../states/ApiState';
import { useGlobalSettingsState } from '../../states/SettingsState';
import { CopyButton } from '../buttons/CopyButton';
import { QrCodeType } from './ActionDropdown';
import type { QrCodeType } from './ActionDropdown';
import { BarcodeInput } from './BarcodeInput';
type QRCodeProps = {
@ -46,7 +46,7 @@ export const QRCode = ({ data, ecl = 'Q', margin = 1 }: QRCodeProps) => {
return (
<Box>
{qrCode ? (
<Image src={qrCode} alt="QR Code" />
<Image src={qrCode} alt='QR Code' />
) : (
<Skeleton height={500} />
)}
@ -97,7 +97,7 @@ export const InvenTreeQRCode = ({
return (
<Stack>
{mdl_prop.hash ? (
<Alert variant="outline" color="red" title={t`Custom barcode`}>
<Alert variant='outline' color='red' title={t`Custom barcode`}>
<Trans>
A custom barcode is registered for this item. The shown code is not
that custom barcode.
@ -110,11 +110,11 @@ export const InvenTreeQRCode = ({
{data && settings.getSetting('BARCODE_SHOW_TEXT', 'false') && (
<Group
justify={showEclSelector ? 'space-between' : 'center'}
align="flex-start"
align='flex-start'
px={16}
>
<Stack gap={4} pt={2}>
<Text size="sm" fw={500}>
<Text size='sm' fw={500}>
<Trans>Barcode Data:</Trans>
</Text>
<Group>
@ -189,7 +189,7 @@ export const QRCodeUnlink = ({ mdl_prop }: { mdl_prop: QrCodeType }) => {
<Text>
<Trans>This will remove the link to the associated barcode</Trans>
</Text>
<Button color="red" onClick={unlinkBarcode}>
<Button color='red' onClick={unlinkBarcode}>
<Trans>Unlink Barcode</Trans>
</Button>
</Box>

View File

@ -10,7 +10,7 @@ export function StylishText({
size?: string;
}>) {
return (
<Text size={size} className={classes.signText} variant="gradient">
<Text size={size} className={classes.signText} variant='gradient'>
{children}
</Text>
);

View File

@ -1,4 +1,4 @@
import { Group, Title, TitleProps } from '@mantine/core';
import { Group, Title, type TitleProps } from '@mantine/core';
import { DocInfo } from './DocInfo';

View File

@ -1,5 +1,5 @@
import { IconAlertCircle } from '@tabler/icons-react';
export function UnavailableIndicator() {
return <IconAlertCircle size={18} color="red" />;
return <IconAlertCircle size={18} color='red' />;
}

View File

@ -8,10 +8,9 @@ import {
Space,
Stack,
Table,
Text,
Title
Text
} from '@mantine/core';
import { ContextModalProps } from '@mantine/modals';
import type { ContextModalProps } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
import { api } from '../../App';
@ -51,22 +50,18 @@ export function AboutInvenTreeModal({
queryFn: () => api.get(apiUrl(ApiEndpoints.version)).then((res) => res.data)
});
function fillTable(
lookup: AboutLookupRef[],
data: any,
alwaysLink: boolean = false
) {
function fillTable(lookup: AboutLookupRef[], data: any, alwaysLink = false) {
return lookup.map((map: AboutLookupRef, idx) => (
<Table.Tr key={idx}>
<Table.Td>{map.title}</Table.Td>
<Table.Td>
<Group justify="space-between" gap="xs">
<Group justify='space-between' gap='xs'>
{alwaysLink ? (
<Anchor href={data[map.ref]} target="_blank">
<Anchor href={data[map.ref]} target='_blank'>
{data[map.ref]}
</Anchor>
) : map.link ? (
<Anchor href={map.link} target="_blank">
<Anchor href={map.link} target='_blank'>
{data[map.ref]}
</Anchor>
) : (
@ -96,20 +91,20 @@ export function AboutInvenTreeModal({
return (
<Stack>
<Divider />
<Group justify="space-between" wrap="nowrap">
<StylishText size="lg">
<Group justify='space-between' wrap='nowrap'>
<StylishText size='lg'>
<Trans>Version Information</Trans>
</StylishText>
{data.dev ? (
<Badge color="blue">
<Badge color='blue'>
<Trans>Development Version</Trans>
</Badge>
) : data.up_to_date ? (
<Badge color="green">
<Badge color='green'>
<Trans>Up to Date</Trans>
</Badge>
) : (
<Badge color="teal">
<Badge color='teal'>
<Trans>Update Available</Trans>
</Badge>
)}
@ -162,7 +157,7 @@ export function AboutInvenTreeModal({
</Table.Tbody>
</Table>
<Divider />
<StylishText size="lg">
<StylishText size='lg'>
<Trans>Links</Trans>
</StylishText>
<Table striped>
@ -181,7 +176,7 @@ export function AboutInvenTreeModal({
</Table.Tbody>
</Table>
<Divider />
<Group justify="space-between">
<Group justify='space-between'>
<CopyButton value={copyval} label={t`Copy version information`} />
<Space />
<Button

View File

@ -19,17 +19,17 @@ import { apiUrl } from '../../states/ApiState';
export function LicenceView(entries: any[]) {
return (
<Stack gap="xs">
<Stack gap='xs'>
<Divider />
{entries?.length > 0 ? (
<Accordion variant="contained" defaultValue="-">
<Accordion variant='contained' defaultValue='-'>
{entries?.map((entry: any, index: number) => (
<Accordion.Item
key={entry.name + entry.license + entry.version}
value={`entry-${index}`}
>
<Accordion.Control>
<Group justify="space-between" grow>
<Group justify='space-between' grow>
<Text>{entry.name}</Text>
<Text>{entry.license}</Text>
<Space />
@ -75,7 +75,7 @@ export function LicenseModal() {
}, [packageKeys]);
return (
<Stack gap="xs">
<Stack gap='xs'>
<Divider />
<LoadingOverlay visible={isFetching} />
{isFetching && (
@ -84,7 +84,7 @@ export function LicenseModal() {
</Text>
)}
{isError ? (
<Alert color="red" title={t`Error`}>
<Alert color='red' title={t`Error`}>
<Text>
<Trans>Failed to fetch license information</Trans>
</Text>

View File

@ -1,7 +1,7 @@
import { Trans, t } from '@lingui/macro';
import { Button, ScrollArea, Stack, Text } from '@mantine/core';
import { useListState } from '@mantine/hooks';
import { ContextModalProps } from '@mantine/modals';
import type { ContextModalProps } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
import { api } from '../../App';
@ -32,14 +32,14 @@ export function QrCodeModal({
}
return (
<Stack gap="xs">
<Stack gap='xs'>
<BarcodeInput onScan={onScanAction} />
{values.length == 0 ? (
<Text c={'grey'}>
<Trans>No scans yet!</Trans>
</Text>
) : (
<ScrollArea style={{ height: 200 }} type="auto" offsetScrollbars>
<ScrollArea style={{ height: 200 }} type='auto' offsetScrollbars>
{values.map((value, index) => (
<div key={`${index}-${value}`}>{value}</div>
))}
@ -47,8 +47,8 @@ export function QrCodeModal({
)}
<Button
fullWidth
mt="md"
color="red"
mt='md'
color='red'
onClick={() => {
// stopScanning();
context.closeModal(id);

View File

@ -1,14 +1,6 @@
import { Trans } from '@lingui/macro';
import {
Badge,
Button,
Divider,
Group,
Stack,
Table,
Title
} from '@mantine/core';
import { ContextModalProps } from '@mantine/modals';
import { Badge, Button, Divider, Group, Stack, Table } from '@mantine/core';
import type { ContextModalProps } from '@mantine/modals';
import { useServerApiState } from '../../states/ApiState';
import { OnlyStaff } from '../items/OnlyStaff';
@ -23,7 +15,7 @@ export function ServerInfoModal({
return (
<Stack>
<Divider />
<StylishText size="lg">
<StylishText size='lg'>
<Trans>Server</Trans>
</StylishText>
<Table striped>
@ -110,7 +102,7 @@ export function ServerInfoModal({
<Trans>Background Worker</Trans>
</Table.Td>
<Table.Td>
<Badge color="red">
<Badge color='red'>
<Trans>Background worker not running</Trans>
</Badge>
</Table.Td>
@ -122,7 +114,7 @@ export function ServerInfoModal({
<Trans>Email Settings</Trans>
</Table.Td>
<Table.Td>
<Badge color="red">
<Badge color='red'>
<Trans>Email settings not configured</Trans>
</Badge>
</Table.Td>
@ -131,7 +123,7 @@ export function ServerInfoModal({
</Table.Tbody>
</Table>
<Divider />
<Group justify="right">
<Group justify='right'>
<Button
onClick={() => {
context.closeModal(id);

View File

@ -45,23 +45,23 @@ export function BreadcrumbList({
}, [breadcrumbs]);
return (
<Paper p="7" radius="xs" shadow="xs">
<Group gap="xs">
<Paper p='7' radius='xs' shadow='xs'>
<Group gap='xs'>
{navCallback && (
<ActionIcon
key="nav-breadcrumb-action"
aria-label="nav-breadcrumb-action"
key='nav-breadcrumb-action'
aria-label='nav-breadcrumb-action'
onClick={navCallback}
variant="transparent"
variant='transparent'
>
<IconMenu2 />
</ActionIcon>
)}
<Breadcrumbs key="breadcrumbs" separator=">">
<Breadcrumbs key='breadcrumbs' separator='>'>
{elements.map((breadcrumb, index) => {
return (
<Anchor
key={index}
key={`${index}-${breadcrumb.name}`}
aria-label={`breadcrumb-${index}-${identifierString(
breadcrumb.name
)}`}
@ -72,7 +72,7 @@ export function BreadcrumbList({
>
<Group gap={4}>
{breadcrumb.icon}
<Text size="sm">{breadcrumb.name}</Text>
<Text size='sm'>{breadcrumb.name}</Text>
</Group>
</Anchor>
);

View File

@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react';
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
import type { To } from 'react-router-dom';
import { UiSizeType } from '../../defaults/formatters';
import type { UiSizeType } from '../../defaults/formatters';
import { useLocalState } from '../../states/LocalState';
import * as classes from './DetailDrawer.css';
@ -57,7 +57,7 @@ function DetailDrawerComponent({
<Group>
{detailDrawerStack > 0 && (
<ActionIcon
variant="outline"
variant='outline'
onClick={() => {
navigate(-1);
addDetailDrawer(-1);
@ -66,7 +66,7 @@ function DetailDrawerComponent({
<IconChevronLeft />
</ActionIcon>
)}
<Text size="xl" fw={600} variant="gradient">
<Text size='xl' fw={600} variant='gradient'>
{title}
</Text>
</Group>
@ -83,7 +83,7 @@ function DetailDrawerComponent({
export function DetailDrawer(props: Readonly<DrawerProps>) {
return (
<Routes>
<Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
<Route path=':id?/' element={<DetailDrawerComponent {...props} />} />
</Routes>
);
}

View File

@ -2,7 +2,7 @@ import { ActionIcon, Container, Group, Indicator, Tabs } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconBell, IconSearch } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { ReactNode, useEffect, useMemo, useState } from 'react';
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useMatch, useNavigate } from 'react-router-dom';
import { api } from '../../App';
@ -61,7 +61,7 @@ export function Header() {
limit: 1
}
};
let response = await api
const response = await api
.get(apiUrl(ApiEndpoints.notifications_list), params)
.catch(() => {
return null;
@ -99,8 +99,8 @@ export function Header() {
closeNotificationDrawer();
}}
/>
<Container className={classes.layoutHeaderSection} size="100%">
<Group justify="space-between">
<Container className={classes.layoutHeaderSection} size='100%'>
<Group justify='space-between'>
<Group>
<NavHoverMenu openDrawer={openNavDrawer} />
<NavTabs />
@ -108,25 +108,25 @@ export function Header() {
<Group>
<ActionIcon
onClick={openSearchDrawer}
variant="transparent"
aria-label="open-search"
variant='transparent'
aria-label='open-search'
>
<IconSearch />
</ActionIcon>
<SpotlightButton />
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
<Indicator
radius="lg"
size="18"
radius='lg'
size='18'
label={notificationCount}
color="red"
color='red'
disabled={notificationCount <= 0}
inline
>
<ActionIcon
onClick={openNotificationDrawer}
variant="transparent"
aria-label="open-notifications"
variant='transparent'
aria-label='open-notifications'
>
<IconBell />
</ActionIcon>
@ -146,7 +146,7 @@ function NavTabs() {
const tabValue = match?.params.tabName;
const tabs: ReactNode[] = useMemo(() => {
let _tabs: ReactNode[] = [];
const _tabs: ReactNode[] = [];
mainNavTabs.forEach((tab) => {
if (tab.role && !user.hasViewRole(tab.role)) {
@ -171,7 +171,7 @@ function NavTabs() {
return (
<Tabs
defaultValue="home"
defaultValue='home'
classNames={{
root: classes.tabs,
list: classes.tabsList,

View File

@ -19,7 +19,7 @@ export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
if (!isLoggedIn()) {
return (
<Navigate
to="/logged-in"
to='/logged-in'
state={{
redirectUrl: location.pathname,
queryParams: location.search,
@ -58,22 +58,22 @@ export default function LayoutComponent() {
return (
<ProtectedRoute>
<Flex direction="column" mih="100vh">
<Flex direction='column' mih='100vh'>
<Header />
<Container className={classes.layoutContent} size="100%">
<Container className={classes.layoutContent} size='100%'>
<Boundary label={'layout'}>
<Outlet />
</Boundary>
{/* </ErrorBoundary> */}
</Container>
<Space h="xl" />
<Space h='xl' />
<Footer />
<Spotlight
actions={actions}
store={firstStore}
highlightQuery
searchProps={{
leftSection: <IconSearch size="1.2rem" />,
leftSection: <IconSearch size='1.2rem' />,
placeholder: t`Search...`
}}
shortcut={['mod + K', '/']}

View File

@ -32,12 +32,12 @@ export function MainMenu() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
return (
<Menu width={260} position="bottom-end">
<Menu width={260} position='bottom-end'>
<Menu.Target>
<UnstyledButton className={classes.layoutHeaderUser}>
<Group gap={7}>
{username() ? (
<Text fw={500} size="sm" style={{ lineHeight: 1 }} mr={3}>
<Text fw={500} size='sm' style={{ lineHeight: 1 }} mr={3}>
{username()}
</Text>
) : (
@ -54,7 +54,7 @@ export function MainMenu() {
<Menu.Item
leftSection={<IconUserCog />}
component={Link}
to="/settings/user"
to='/settings/user'
>
<Trans>Account Settings</Trans>
</Menu.Item>
@ -62,7 +62,7 @@ export function MainMenu() {
<Menu.Item
leftSection={<IconSettings />}
component={Link}
to="/settings/system"
to='/settings/system'
>
<Trans>System Settings</Trans>
</Menu.Item>
@ -81,7 +81,7 @@ export function MainMenu() {
<Menu.Item
leftSection={<IconUserBolt />}
component={Link}
to="/settings/admin"
to='/settings/admin'
>
<Trans>Admin Center</Trans>
</Menu.Item>

View File

@ -3,12 +3,12 @@ import { UnstyledButton } from '@mantine/core';
import { InvenTreeLogo } from '../items/InvenTreeLogo';
export function NavHoverMenu({
openDrawer: openDrawer
openDrawer
}: Readonly<{
openDrawer: () => void;
}>) {
return (
<UnstyledButton onClick={() => openDrawer()} aria-label="navigation-menu">
<UnstyledButton onClick={() => openDrawer()} aria-label='navigation-menu'>
<InvenTreeLogo />
</UnstyledButton>
);

View File

@ -18,7 +18,7 @@ import * as classes from '../../main.css';
import { useGlobalSettingsState } from '../../states/SettingsState';
import { useUserState } from '../../states/UserState';
import { InvenTreeLogo } from '../items/InvenTreeLogo';
import { MenuLinkItem, MenuLinks } from '../items/MenuLinks';
import { type MenuLinkItem, MenuLinks } from '../items/MenuLinks';
import { StylishText } from '../items/StylishText';
// TODO @matmair #1: implement plugin loading and menu item generation see #5269
@ -35,7 +35,7 @@ export function NavigationDrawer({
<Drawer
opened={opened}
onClose={close}
size="lg"
size='lg'
withCloseButton={false}
classNames={{
body: classes.navigationDrawer
@ -161,14 +161,14 @@ function DrawerContent({ closeFunc }: { closeFunc?: () => void }) {
const menuItemsAbout: MenuLinkItem[] = useMemo(() => AboutLinks(), []);
return (
<Flex direction="column" mih="100vh" p={16}>
<Group wrap="nowrap">
<Flex direction='column' mih='100vh' p={16}>
<Group wrap='nowrap'>
<InvenTreeLogo />
<StylishText size="xl">{title}</StylishText>
<StylishText size='xl'>{title}</StylishText>
</Group>
<Space h="xs" />
<Space h='xs' />
<Container className={classes.layoutContent} p={0}>
<ScrollArea h={scrollHeight} type="always" offsetScrollbars>
<ScrollArea h={scrollHeight} type='always' offsetScrollbars>
<MenuLinks
title={t`Navigation`}
links={menuItemsNavigate}
@ -184,7 +184,7 @@ function DrawerContent({ closeFunc }: { closeFunc?: () => void }) {
links={menuItemsAction}
beforeClick={closeFunc}
/>
<Space h="md" />
<Space h='md' />
{plugins.length > 0 ? (
<>
<MenuLinks
@ -199,13 +199,13 @@ function DrawerContent({ closeFunc }: { closeFunc?: () => void }) {
</ScrollArea>
</Container>
<div ref={ref}>
<Space h="md" />
<Space h='md' />
<MenuLinks
title={t`Documentation`}
links={menuItemsDocumentation}
beforeClick={closeFunc}
/>
<Space h="md" />
<Space h='md' />
<MenuLinks
title={t`About`}
links={menuItemsAbout}

View File

@ -5,11 +5,11 @@ import {
Drawer,
Group,
LoadingOverlay,
RenderTreeNodePayload,
type RenderTreeNodePayload,
Space,
Stack,
Tree,
TreeNodeData,
type TreeNodeData,
useTree
} from '@mantine/core';
import {
@ -22,8 +22,8 @@ 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 type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { apiUrl } from '../../states/ApiState';
@ -89,19 +89,19 @@ export default function NavigationTree({
* It is required (and assumed) that the data is first sorted by level.
*/
let nodes: Record<number, any> = {};
let tree: TreeNodeData[] = [];
const nodes: Record<number, any> = {};
const tree: TreeNodeData[] = [];
if (!query?.data?.length) {
return [];
}
for (let ii = 0; ii < query.data.length; ii++) {
let node = {
const node = {
...query.data[ii],
children: [],
label: (
<Group gap="xs">
<Group gap='xs'>
<ApiIcon name={query.data[ii].icon} />
{query.data[ii].name}
</Group>
@ -141,9 +141,9 @@ export default function NavigationTree({
(payload: RenderTreeNodePayload) => {
return (
<Group
justify="left"
justify='left'
key={payload.node.value}
wrap="nowrap"
wrap='nowrap'
onClick={() => {
if (payload.hasChildren) {
treeState.toggleExpanded(payload.node.value);
@ -152,8 +152,8 @@ export default function NavigationTree({
>
<Space w={5 * payload.level} />
<ActionIcon
size="sm"
variant="transparent"
size='sm'
variant='transparent'
aria-label={`nav-tree-toggle-${payload.node.value}}`}
>
{payload.hasChildren ? (
@ -179,8 +179,8 @@ export default function NavigationTree({
return (
<Drawer
opened={opened}
size="md"
position="left"
size='md'
position='left'
onClose={onClose}
withCloseButton={true}
styles={{
@ -192,13 +192,13 @@ export default function NavigationTree({
}
}}
title={
<Group justify="left" p="ms" gap="md" wrap="nowrap">
<Group justify='left' p='ms' gap='md' wrap='nowrap'>
<IconSitemap />
<StylishText size="lg">{title}</StylishText>
<StylishText size='lg'>{title}</StylishText>
</Group>
}
>
<Stack gap="xs">
<Stack gap='xs'>
<Divider />
<LoadingOverlay visible={query.isFetching || query.isLoading} />
<Tree data={data} tree={treeState} renderNode={renderNode} />

View File

@ -20,7 +20,7 @@ import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import type { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { base_url } from '../../main';
@ -44,12 +44,12 @@ function NotificationEntry({
let link = notification.target?.link;
let model_type = notification.target?.model_type;
let model_id = notification.target?.model_id;
const model_type = notification.target?.model_type;
const model_id = notification.target?.model_id;
// If a valid model type is provided, that overrides the specified link
if (model_type as ModelType) {
let model_info = ModelInformationDict[model_type as ModelType];
const model_info = ModelInformationDict[model_type as ModelType];
if (model_info?.url_detail && model_id) {
link = getDetailUrl(model_type as ModelType, model_id);
} else if (model_info?.url_overview) {
@ -58,18 +58,18 @@ function NotificationEntry({
}
return (
<Paper p="xs" shadow="xs">
<Group justify="space-between" wrap="nowrap">
<Paper p='xs' shadow='xs'>
<Group justify='space-between' wrap='nowrap'>
<Tooltip
label={notification.message}
position="bottom-end"
position='bottom-end'
hidden={!notification.message}
>
<Stack gap={2}>
<Anchor
href={link ? `/${base_url}${link}` : '#'}
underline="hover"
target="_blank"
underline='hover'
target='_blank'
onClick={(event: any) => {
if (link) {
// Mark the notification as read
@ -81,13 +81,13 @@ function NotificationEntry({
}
}}
>
<Text size="sm">{notification.name}</Text>
<Text size='sm'>{notification.name}</Text>
</Anchor>
<Text size="xs">{notification.age_human}</Text>
<Text size='xs'>{notification.age_human}</Text>
</Stack>
</Tooltip>
<Tooltip label={t`Mark as read`} position="bottom-end">
<ActionIcon variant="transparent" onClick={onRead}>
<Tooltip label={t`Mark as read`} position='bottom-end'>
<ActionIcon variant='transparent' onClick={onRead}>
<IconBellCheck />
</ActionIcon>
</Tooltip>
@ -162,8 +162,8 @@ export function NotificationDrawer({
return (
<Drawer
opened={opened}
size="md"
position="right"
size='md'
position='right'
onClose={onClose}
withCloseButton={false}
styles={{
@ -175,12 +175,12 @@ export function NotificationDrawer({
}
}}
title={
<Group justify="space-between" wrap="nowrap">
<StylishText size="lg">{t`Notifications`}</StylishText>
<Group justify="end" wrap="nowrap">
<Group justify='space-between' wrap='nowrap'>
<StylishText size='lg'>{t`Notifications`}</StylishText>
<Group justify='end' wrap='nowrap'>
<Tooltip label={t`Mark all as read`}>
<ActionIcon
variant="transparent"
variant='transparent'
onClick={() => {
markAllAsRead();
}}
@ -194,7 +194,7 @@ export function NotificationDrawer({
onClose();
navigateToLink('/notifications/unread', navigate, event);
}}
variant="transparent"
variant='transparent'
>
<IconArrowRight />
</ActionIcon>
@ -203,12 +203,12 @@ export function NotificationDrawer({
</Group>
}
>
<Boundary label="NotificationDrawer">
<Stack gap="xs">
<Boundary label='NotificationDrawer'>
<Stack gap='xs'>
<Divider />
{!hasNotifications && (
<Alert color="green">
<Text size="sm">{t`You have no unread notifications.`}</Text>
<Alert color='green'>
<Text size='sm'>{t`You have no unread notifications.`}</Text>
</Alert>
)}
{hasNotifications &&
@ -221,7 +221,7 @@ export function NotificationDrawer({
))}
{notificationQuery.isFetching && (
<Center>
<Loader size="sm" />
<Loader size='sm' />
</Center>
)}
</Stack>

View File

@ -1,10 +1,10 @@
import { Group, Paper, Space, Stack, Text } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks';
import { Fragment, ReactNode } from 'react';
import { Fragment, type ReactNode } from 'react';
import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText';
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList';
interface PageDetailInterface {
title?: string;
@ -51,26 +51,26 @@ export function PageDetail({
]);
return (
<Stack gap="xs">
<Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList
navCallback={breadcrumbAction}
breadcrumbs={breadcrumbs}
/>
)}
<Paper p="xs" radius="xs" shadow="xs">
<Stack gap="xs">
<Group justify="space-between" wrap="nowrap">
<Group justify="left" wrap="nowrap">
<Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'>
<Group justify='space-between' wrap='nowrap'>
<Group justify='left' wrap='nowrap'>
{imageUrl && (
<ApiImage src={imageUrl} radius="sm" mah={42} maw={42} />
<ApiImage src={imageUrl} radius='sm' mah={42} maw={42} />
)}
<Stack gap="xs">
{title && <StylishText size="lg">{title}</StylishText>}
<Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>}
{subtitle && (
<Group gap="xs">
<Group gap='xs'>
{icon}
<Text size="md" truncate>
<Text size='md' truncate>
{subtitle}
</Text>
</Group>
@ -79,14 +79,14 @@ export function PageDetail({
</Group>
<Space />
{detail}
<Group justify="right" gap="xs" wrap="nowrap">
<Group justify='right' gap='xs' wrap='nowrap'>
{badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment>
))}
</Group>
<Space />
{actions && (
<Group gap={5} justify="right">
<Group gap={5} justify='right'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))}

Some files were not shown because too many files have changed in this diff Show More