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:
parent
e7cfb4c3c0
commit
0872beaba9
@ -31,7 +31,8 @@
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"batisteo.vscode-django",
|
||||
"eamodio.gitlens"
|
||||
"eamodio.gitlens",
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -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
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"quickfix.biome": "explicit"
|
||||
}
|
||||
}
|
40
biome.json
Normal file
40
biome.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"]
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"importOrder": ["<THIRD_PARTY_MODULES>", "^[./]"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true
|
||||
}
|
@ -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,
|
||||
};
|
@ -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'];
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 />} />;
|
||||
}
|
||||
|
@ -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'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 && (
|
||||
|
@ -17,7 +17,7 @@ export function EditButton({
|
||||
<ActionIcon
|
||||
onClick={() => setEditing()}
|
||||
disabled={disabled}
|
||||
variant="default"
|
||||
variant='default'
|
||||
>
|
||||
{editing ? saveIcon : <IconEdit />}
|
||||
</ActionIcon>
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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}{' '}
|
||||
|
@ -16,7 +16,7 @@ export function ScanButton() {
|
||||
innerProps: {}
|
||||
})
|
||||
}
|
||||
variant="transparent"
|
||||
variant='transparent'
|
||||
title={t`Open Barcode Scanner`}
|
||||
>
|
||||
<IconQrcode />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
@ -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>
|
||||
|
@ -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 />
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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';
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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 = {
|
||||
|
@ -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' />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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 = {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)}
|
||||
>
|
||||
|
@ -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'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>
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
/>
|
||||
|
@ -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} />
|
||||
)}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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`
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Group, Title, TitleProps } from '@mantine/core';
|
||||
import { Group, Title, type TitleProps } from '@mantine/core';
|
||||
|
||||
import { DocInfo } from './DocInfo';
|
||||
|
||||
|
@ -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' />;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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', '/']}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user