mirror of
https://github.com/Telecominfraproject/wlan-cloud-owprov-ui.git
synced 2025-11-02 03:27:56 +00:00
[WIFI-12575] Theme improvements
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
@@ -15,7 +15,6 @@
|
|||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"ignorePatterns": ["build/", "dist/"],
|
"ignorePatterns": ["build/", "dist/"],
|
||||||
"plugins": ["import", "react", "@typescript-eslint", "prettier"],
|
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
"plugin:import/warnings",
|
"plugin:import/warnings",
|
||||||
"plugin:import/typescript"
|
"plugin:import/typescript"
|
||||||
],
|
],
|
||||||
|
"plugins": ["import", "react", "@typescript-eslint", "prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/extensions": [
|
"import/extensions": [
|
||||||
"error",
|
"error",
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
],
|
],
|
||||||
"max-len": ["error", { "code": 150 }],
|
"max-len": ["error", { "code": 150 }],
|
||||||
"@typescript-eslint/ban-ts-comment": ["off"],
|
"@typescript-eslint/ban-ts-comment": ["off"],
|
||||||
|
"import/prefer-default-export": ["off"],
|
||||||
"react/prop-types": ["warn"],
|
"react/prop-types": ["warn"],
|
||||||
"react/require-default-props": "off",
|
"react/require-default-props": "off",
|
||||||
"react/jsx-props-no-spreading": ["off"],
|
"react/jsx-props-no-spreading": ["off"],
|
||||||
|
|||||||
8277
package-lock.json
generated
8277
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wlan-cloud-owprov-ui",
|
"name": "wlan-cloud-owprov-ui",
|
||||||
"version": "2.10.0(2)",
|
"version": "2.10.0(7)",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -14,73 +14,73 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^2.0.11",
|
"@chakra-ui/anatomy": "^2.1.1",
|
||||||
|
"@chakra-ui/icons": "^2.0.18",
|
||||||
"@chakra-ui/react": "^2.3.6",
|
"@chakra-ui/react": "^2.3.6",
|
||||||
"@chakra-ui/theme-tools": "^2.0.12",
|
"@chakra-ui/theme-tools": "^2.0.12",
|
||||||
"@chakra-ui/utils": "^2.0.11",
|
"@chakra-ui/utils": "^2.0.14",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.4",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@fontsource/inter": "^4.5.14",
|
"@fontsource/inter": "^4.5.15",
|
||||||
|
"@hello-pangea/dnd": "^16.2.0",
|
||||||
"@nivo/circle-packing": "^0.80.0",
|
"@nivo/circle-packing": "^0.80.0",
|
||||||
"@nivo/core": "^0.80.0",
|
"@nivo/core": "^0.80.0",
|
||||||
|
"@phosphor-icons/react": "^2.0.8",
|
||||||
"@react-spring/web": "^9.5.5",
|
"@react-spring/web": "^9.5.5",
|
||||||
"axios": "^1.1.3",
|
"@tanstack/react-query": "^4.29.3",
|
||||||
|
"@tanstack/react-table": "^8.8.5",
|
||||||
|
"axios": "^1.3.5",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"chakra-react-select": "^4.3.0",
|
"chakra-react-select": "^4.6.0",
|
||||||
"cronstrue": "2.14.0",
|
"cronstrue": "2.26.0",
|
||||||
"currency-codes": "^2.1.0",
|
"currency-codes": "^2.1.0",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"framer-motion": "^6.3.6",
|
"framer-motion": "^10.12.3",
|
||||||
"i18next": "^22.0.0",
|
"i18next": "^22.4.14",
|
||||||
"i18next-browser-languagedetector": "^6.1.8",
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
"i18next-http-backend": "^1.4.4",
|
"i18next-http-backend": "^2.2.0",
|
||||||
"libphonenumber-js": "^1.10.14",
|
"libphonenumber-js": "^1.10.28",
|
||||||
"papaparse": "^5.3.2",
|
"lodash.debounce": "^4.0.8",
|
||||||
"phosphor-react": "^1.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-app-polyfill": "^3.0.0",
|
"react-app-polyfill": "^3.0.0",
|
||||||
"react-country-flag": "^3.0.2",
|
"react-country-flag": "^3.1.0",
|
||||||
"react-csv": "^2.2.2",
|
"react-csv": "^2.2.2",
|
||||||
"react-datepicker": "^4.8.0",
|
"react-datepicker": "^4.11.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.1",
|
||||||
"react-flow-renderer": "^10.3.17",
|
"react-flow-renderer": "^10.3.17",
|
||||||
"react-full-screen": "^1.1.1",
|
"react-full-screen": "^1.1.1",
|
||||||
"react-i18next": "^11.18.6",
|
"react-i18next": "^12.2.0",
|
||||||
"react-masonry-css": "^1.0.16",
|
"react-masonry-css": "^1.0.16",
|
||||||
"react-papaparse": "^4.1.0",
|
"react-papaparse": "^4.1.0",
|
||||||
"@tanstack/react-query": "^4.12.0",
|
"react-router-dom": "^6.10.0",
|
||||||
"react-router-dom": "^6.4.2",
|
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"react-tooltip": "^4.4.2",
|
"react-virtualized-auto-sizer": "^1.0.15",
|
||||||
"react-virtualized-auto-sizer": "^1.0.7",
|
"react-window": "^1.8.9",
|
||||||
"react-window": "^1.8.8",
|
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^3.1.8",
|
"typescript": "^5.0.4",
|
||||||
"typescript": "^4.8.4",
|
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
|
"vite": "^4.2.2",
|
||||||
"yup": "^0.32.11",
|
"yup": "^0.32.11",
|
||||||
"zustand": "^4.1.2"
|
"zustand": "^4.3.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.2",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/react": "^18.0.21",
|
"@types/node": "^18.15.11",
|
||||||
|
"@types/react": "^18.0.37",
|
||||||
"@types/react-csv": "^1.1.3",
|
"@types/react-csv": "^1.1.3",
|
||||||
"@types/react-dom": "^18.0.6",
|
|
||||||
"@types/react-table": "^7.7.12",
|
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
|
||||||
"@types/react-datepicker": "4.8.0",
|
"@types/react-datepicker": "4.8.0",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/react-table": "^7.7.14",
|
||||||
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^9.0.1",
|
||||||
"eslint": "8.25.0",
|
"@vitejs/plugin-react": "^3.1.0",
|
||||||
"vite-tsconfig-paths": "^3.5.1",
|
"eslint": "8.38.0",
|
||||||
"lint-staged": "^13.0.3",
|
|
||||||
"@vitejs/plugin-react": "^2.1.0",
|
|
||||||
"vite-plugin-pwa": "^0.13.1",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
|
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
|
||||||
@@ -92,7 +92,10 @@
|
|||||||
"eslint-plugin-no-inline-styles": "^1.0.5",
|
"eslint-plugin-no-inline-styles": "^1.0.5",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.31.10",
|
"eslint-plugin-react": "^7.31.10",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0"
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"lint-staged": "^13.2.1",
|
||||||
|
"prettier": "^2.8.7",
|
||||||
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
24
src/@tanstack.react-table.d.ts
vendored
Normal file
24
src/@tanstack.react-table.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { BoxProps } from '@chakra-ui/react';
|
||||||
|
import '@tanstack/react-table';
|
||||||
|
|
||||||
|
declare module '@tanstack/table-core' {
|
||||||
|
interface ColumnMeta<TData extends RowData, TValue> {
|
||||||
|
stopPropagation?: boolean;
|
||||||
|
alwaysShow?: boolean;
|
||||||
|
anchored?: boolean;
|
||||||
|
hasPopover?: boolean;
|
||||||
|
customMaxWidth?: string;
|
||||||
|
customMinWidth?: string;
|
||||||
|
customWidth?: string;
|
||||||
|
isMonospace?: boolean;
|
||||||
|
isCentered?: boolean;
|
||||||
|
columnSelectorOptions?: {
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
headerOptions?: {
|
||||||
|
tooltip?: string;
|
||||||
|
};
|
||||||
|
headerStyleProps?: BoxProps;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { Warning } from 'phosphor-react';
|
import { Warning } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ThemeProps } from 'models/Theme';
|
import { ThemeProps } from 'models/Theme';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Tooltip } from '@chakra-ui/react';
|
import { IconButton, Tooltip } from '@chakra-ui/react';
|
||||||
import { X } from 'phosphor-react';
|
import { X } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint, LayoutProps, SpaceProps } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint, LayoutProps, SpaceProps } from '@chakra-ui/react';
|
||||||
import { Plus } from 'phosphor-react';
|
import { Plus } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props extends LayoutProps, SpaceProps {
|
interface Props extends LayoutProps, SpaceProps {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { IconButton, Button, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { Pen } from 'phosphor-react';
|
import { Pen } from '@phosphor-icons/react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { ArrowsClockwise } from 'phosphor-react';
|
import { ArrowsClockwise } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { FloppyDisk } from 'phosphor-react';
|
import { FloppyDisk } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
interface Props extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { ArrowRight, FloppyDisk } from 'phosphor-react';
|
import { ArrowRight, FloppyDisk } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint, useDisclosure } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint, useDisclosure } from '@chakra-ui/react';
|
||||||
import { Pencil, X } from 'phosphor-react';
|
import { Pencil, X } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ConfirmCloseAlert from 'components/Modals/Actions/ConfirmCloseAlert';
|
import ConfirmCloseAlert from 'components/Modals/Actions/ConfirmCloseAlert';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
|
||||||
import { Warning } from 'phosphor-react';
|
import { Warning } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ThemeProps } from 'models/Theme';
|
import { ThemeProps } from 'models/Theme';
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
|
import { Box, FlexProps, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
|
||||||
|
|
||||||
interface CardBodyProps extends LayoutProps, SpaceProps {
|
interface CardBodyProps extends LayoutProps, SpaceProps, FlexProps {
|
||||||
variant?: string;
|
variant?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const CardBody = ({ variant, children, ...props }: CardBodyProps) => {
|
const CardBody: React.FC<CardBodyProps> = ({ variant, children, ...props }) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const styles = useStyleConfig('CardBody', { variant });
|
const styles = useStyleConfig('CardBody', { variant });
|
||||||
// Pass the computed styles into the `__css` prop
|
// Pass the computed styles into the `__css` prop
|
||||||
|
|||||||
@@ -1,26 +1,60 @@
|
|||||||
import React from 'react';
|
import React, { DOMAttributes } from 'react';
|
||||||
import { Box, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
|
import {
|
||||||
|
BackgroundProps,
|
||||||
|
Box,
|
||||||
|
EffectProps,
|
||||||
|
InteractivityProps,
|
||||||
|
LayoutProps,
|
||||||
|
PositionProps,
|
||||||
|
SpaceProps,
|
||||||
|
useColorModeValue,
|
||||||
|
useStyleConfig,
|
||||||
|
useToken,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
interface Props extends LayoutProps, SpaceProps {
|
interface CardHeaderProps
|
||||||
variant?: string;
|
extends LayoutProps,
|
||||||
|
SpaceProps,
|
||||||
|
BackgroundProps,
|
||||||
|
InteractivityProps,
|
||||||
|
PositionProps,
|
||||||
|
EffectProps,
|
||||||
|
DOMAttributes<HTMLDivElement> {
|
||||||
|
variant?: 'panel' | 'unstyled';
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
headerStyle?: {
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps = {
|
const CardHeader = ({
|
||||||
variant: undefined,
|
variant,
|
||||||
};
|
children,
|
||||||
|
icon,
|
||||||
|
headerStyle = {
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
...rest
|
||||||
|
}: CardHeaderProps) => {
|
||||||
|
const iconBgcolor = useToken('colors', [`${headerStyle?.color}.500`, `${headerStyle?.color}.300`]);
|
||||||
|
const bgColor = useToken('colors', [`${headerStyle?.color}.50`, `${headerStyle?.color}.700`]);
|
||||||
|
const iconColor = useColorModeValue(iconBgcolor[0], iconBgcolor[1]);
|
||||||
|
const headerBgColor = useColorModeValue(bgColor[0], bgColor[1]);
|
||||||
|
|
||||||
const CardHeader = ({ variant, children, ...rest }: Props) => {
|
|
||||||
// @ts-ignore
|
|
||||||
const styles = useStyleConfig('CardHeader', { variant });
|
const styles = useStyleConfig('CardHeader', { variant });
|
||||||
|
|
||||||
// Pass the computed styles into the `__css` prop
|
// Pass the computed styles into the `__css` prop
|
||||||
return (
|
return (
|
||||||
<Box __css={styles} {...rest}>
|
<Box __css={styles} bgColor={variant === 'unstyled' ? undefined : headerBgColor} {...rest}>
|
||||||
|
{icon ? (
|
||||||
|
<Box mr={2} color={headerStyle ? iconColor : undefined} bgColor="unset">
|
||||||
|
{icon}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CardHeader.defaultProps = defaultProps;
|
export default React.memo(CardHeader);
|
||||||
|
|
||||||
export default CardHeader;
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FunnelSimple } from 'phosphor-react';
|
import { FunnelSimple } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { useAuth } from 'contexts/AuthProvider';
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import EditOverrideForm from './EditForm';
|
import EditOverrideForm from './EditForm';
|
||||||
import { ConfigurationOverride } from 'hooks/Network/ConfigurationOverride';
|
import { ConfigurationOverride } from 'hooks/Network/ConfigurationOverride';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Plus } from 'phosphor-react';
|
import { Plus } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CreateConfigurationOverrideForm from './CreateForm';
|
import CreateConfigurationOverrideForm from './CreateForm';
|
||||||
import { useAuth } from 'contexts/AuthProvider';
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Pen } from 'phosphor-react';
|
import { Pen } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ACCEPTED_CONFIGURATION_OVERRIDES, ConfigurationOverride } from 'hooks/Network/ConfigurationOverride';
|
import { ACCEPTED_CONFIGURATION_OVERRIDES, ConfigurationOverride } from 'hooks/Network/ConfigurationOverride';
|
||||||
import useFastField from 'hooks/useFastField';
|
import useFastField from 'hooks/useFastField';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Box,
|
Box,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ArrowDown, ArrowUp, Plus, Trash } from 'phosphor-react';
|
import { ArrowDown, ArrowUp, Plus, Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseButton from 'components/Buttons/CloseButton';
|
import CloseButton from 'components/Buttons/CloseButton';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { areRrmParamsValid } from './helper';
|
import { areRrmParamsValid } from './helper';
|
||||||
|
|||||||
64
src/components/DataGrid/CellRow.tsx
Normal file
64
src/components/DataGrid/CellRow.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Td, Tr } from '@chakra-ui/react';
|
||||||
|
import { Row, flexRender } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
export type DataGridCellRowProps<TValue extends object> = {
|
||||||
|
row: Row<TValue>;
|
||||||
|
onRowClick: ((row: TValue) => (() => void) | undefined) | undefined;
|
||||||
|
rowStyle: {
|
||||||
|
hoveredRowBg: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataGridCellRow = <TValue extends object>({
|
||||||
|
row,
|
||||||
|
rowStyle: { hoveredRowBg },
|
||||||
|
onRowClick,
|
||||||
|
}: DataGridCellRowProps<TValue>) => {
|
||||||
|
const onClick = onRowClick ? onRowClick(row.original) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
key={row.id}
|
||||||
|
_hover={{
|
||||||
|
backgroundColor: hoveredRowBg,
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
borderRight="1px solid gray"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<Td
|
||||||
|
px={1}
|
||||||
|
key={cell.id}
|
||||||
|
textOverflow="ellipsis"
|
||||||
|
overflow="hidden"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
minWidth={cell.column.columnDef.meta?.customMinWidth ?? undefined}
|
||||||
|
maxWidth={cell.column.columnDef.meta?.customMaxWidth ?? undefined}
|
||||||
|
width={cell.column.columnDef.meta?.customWidth}
|
||||||
|
textAlign={cell.column.columnDef.meta?.isCentered ? 'center' : undefined}
|
||||||
|
fontFamily={
|
||||||
|
cell.column.columnDef.meta?.isMonospace
|
||||||
|
? 'Inter, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={
|
||||||
|
cell.column.columnDef.meta?.stopPropagation || (cell.column.id === 'actions' && onClick)
|
||||||
|
? (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
cursor={
|
||||||
|
!cell.column.columnDef.meta?.stopPropagation && cell.column.id !== 'actions' && onClick
|
||||||
|
? 'pointer'
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
border="0.5px solid gray"
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</Td>
|
||||||
|
))}
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
54
src/components/DataGrid/DataGridColumnPicker.tsx
Normal file
54
src/components/DataGrid/DataGridColumnPicker.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Checkbox, IconButton, Menu, MenuButton, MenuItem, MenuList, Tooltip } from '@chakra-ui/react';
|
||||||
|
import { FunnelSimple } from '@phosphor-icons/react';
|
||||||
|
import { VisibilityState } from '@tanstack/react-table';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { DataGridColumn } from './useDataGrid';
|
||||||
|
|
||||||
|
export type DataGridColumnPickerProps<TValue extends object> = {
|
||||||
|
columns: DataGridColumn<TValue>[];
|
||||||
|
columnVisibility: VisibilityState;
|
||||||
|
toggleVisibility: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataGridColumnPicker = <TValue extends object>({
|
||||||
|
columns,
|
||||||
|
columnVisibility,
|
||||||
|
toggleVisibility,
|
||||||
|
}: DataGridColumnPickerProps<TValue>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Menu closeOnSelect={false} isLazy>
|
||||||
|
<Tooltip label={t('common.columns')} hasArrow>
|
||||||
|
<MenuButton as={IconButton} icon={<FunnelSimple />} />
|
||||||
|
</Tooltip>
|
||||||
|
<MenuList maxH="200px" overflowY="auto">
|
||||||
|
{columns
|
||||||
|
.filter((col) => col.id && col.header)
|
||||||
|
.map((column) => {
|
||||||
|
const handleClick =
|
||||||
|
column.id !== undefined ? () => toggleVisibility(column.id as unknown as string) : undefined;
|
||||||
|
const id = column.id ?? uuid();
|
||||||
|
let label = column.header?.toString() ?? 'Unrecognized column';
|
||||||
|
if (column.meta?.columnSelectorOptions?.label) label = column.meta.columnSelectorOptions.label;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={id}
|
||||||
|
as={Checkbox}
|
||||||
|
isChecked={columnVisibility[id] === undefined || columnVisibility[id]}
|
||||||
|
onChange={column.meta?.alwaysShow ? undefined : handleClick}
|
||||||
|
isDisabled={column.meta?.alwaysShow}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
45
src/components/DataGrid/HeaderRow.tsx
Normal file
45
src/components/DataGrid/HeaderRow.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Flex, Th, Tooltip, Tr } from '@chakra-ui/react';
|
||||||
|
import { HeaderGroup, flexRender } from '@tanstack/react-table';
|
||||||
|
import { DataGridSortIcon } from './SortIcon';
|
||||||
|
|
||||||
|
export type DataGridHeaderRowProps<TValue extends object> = {
|
||||||
|
headerGroup: HeaderGroup<TValue>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataGridHeaderRow = <TValue extends object>({ headerGroup }: DataGridHeaderRowProps<TValue>) => (
|
||||||
|
<Tr p={0} borderRight="1px solid gray">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<Th
|
||||||
|
color="gray.400"
|
||||||
|
key={header.id}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
minWidth={header.column.columnDef.meta?.customMinWidth ?? undefined}
|
||||||
|
maxWidth={header.column.columnDef.meta?.customMaxWidth ?? undefined}
|
||||||
|
width={header.column.columnDef.meta?.customWidth}
|
||||||
|
fontSize="sm"
|
||||||
|
onClick={header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined}
|
||||||
|
cursor={header.column.getCanSort() ? 'pointer' : undefined}
|
||||||
|
border="0.5px solid gray"
|
||||||
|
px={1}
|
||||||
|
>
|
||||||
|
<Flex display="flex" alignItems="center">
|
||||||
|
{header.isPlaceholder ? null : (
|
||||||
|
<Tooltip label={header.column.columnDef.meta?.headerOptions?.tooltip}>
|
||||||
|
<Box
|
||||||
|
overflow="hidden"
|
||||||
|
whiteSpace="nowrap"
|
||||||
|
alignContent="center"
|
||||||
|
width="100%"
|
||||||
|
{...header.column.columnDef.meta?.headerStyleProps}
|
||||||
|
>
|
||||||
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<DataGridSortIcon sortInfo={header.column.getIsSorted()} canSort={header.column.getCanSort()} />
|
||||||
|
</Flex>
|
||||||
|
</Th>
|
||||||
|
))}
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
124
src/components/DataGrid/Input.tsx
Normal file
124
src/components/DataGrid/Input.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
Select,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Table } from '@tanstack/react-table';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { useContainerDimensions } from 'hooks/useContainerDimensions';
|
||||||
|
|
||||||
|
type Props<T extends object> = {
|
||||||
|
table: Table<T>;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DataGridControls = <T extends object>({ table, isDisabled }: Props<T>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { ref, dimensions } = useContainerDimensions({ precision: 100 });
|
||||||
|
const isCompact = dimensions.width !== 0 && dimensions.width <= 800;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex ref={ref} justifyContent="space-between" m={4} alignItems="center">
|
||||||
|
<Flex>
|
||||||
|
<Tooltip label={t('table.first_page')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Go to first page"
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
isDisabled={isDisabled || !table.getCanPreviousPage()}
|
||||||
|
icon={<ArrowLeftIcon h={3} w={3} />}
|
||||||
|
mr={4}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={t('table.previous_page')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Previous page"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
isDisabled={isDisabled || !table.getCanPreviousPage()}
|
||||||
|
icon={<ChevronLeftIcon h={6} w={6} />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex alignItems="center">
|
||||||
|
{isCompact ? null : (
|
||||||
|
<>
|
||||||
|
<Text flexShrink={0} mr={8}>
|
||||||
|
{t('table.page')}{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{table.getState().pagination.pageIndex + 1}
|
||||||
|
</Text>{' '}
|
||||||
|
{t('common.of')}{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{table.getPageCount()}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text flexShrink={0}>{t('table.go_to_page')}</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={table.getPageCount()}
|
||||||
|
onChange={(_, numberValue) => {
|
||||||
|
const newPage = numberValue ? numberValue - 1 : 0;
|
||||||
|
table.setPageIndex(newPage);
|
||||||
|
}}
|
||||||
|
value={table.getState().pagination.pageIndex + 1}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={table.getState().pagination.pageSize}
|
||||||
|
onChange={(e) => {
|
||||||
|
table.setPageSize(Number(e.target.value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[10, 20, 30, 40, 50].map((opt) => (
|
||||||
|
<option key={uuid()} value={opt}>
|
||||||
|
{t('common.show')} {opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex>
|
||||||
|
<Tooltip label={t('table.next_page')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Go to next page"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
isDisabled={isDisabled || !table.getCanNextPage()}
|
||||||
|
icon={<ChevronRightIcon h={6} w={6} />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={t('table.last_page')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Go to last page"
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
isDisabled={isDisabled || !table.getCanNextPage()}
|
||||||
|
icon={<ArrowRightIcon h={3} w={3} />}
|
||||||
|
ml={4}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataGridControls;
|
||||||
23
src/components/DataGrid/SortIcon.tsx
Normal file
23
src/components/DataGrid/SortIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Icon } from '@chakra-ui/react';
|
||||||
|
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||||
|
import { SortDirection } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
export type DataGridSortIconProps = {
|
||||||
|
sortInfo: false | SortDirection;
|
||||||
|
canSort: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataGridSortIcon = ({ sortInfo, canSort }: DataGridSortIconProps) => {
|
||||||
|
if (canSort) {
|
||||||
|
if (sortInfo) {
|
||||||
|
return sortInfo === 'desc' ? (
|
||||||
|
<Icon ml={1} boxSize={3} as={ArrowDown} />
|
||||||
|
) : (
|
||||||
|
<Icon ml={1} boxSize={3} as={ArrowUp} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <Icon ml={1} boxSize={3} as={Circle} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Icon, Text, Tooltip, useColorModeValue } from '@chakra-ui/react';
|
||||||
|
import { Draggable } from '@hello-pangea/dnd';
|
||||||
|
import { ArrowsDownUp, Lock } from '@phosphor-icons/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataGridColumn } from '../../useDataGrid';
|
||||||
|
|
||||||
|
type Props<TValue> = {
|
||||||
|
draggableId: string;
|
||||||
|
index: number;
|
||||||
|
column: DataGridColumn<TValue>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DraggableColumn = <TValue extends object>({ draggableId, index, column }: Props<TValue>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isDraggingBackground = useColorModeValue('blue.100', 'blue.600');
|
||||||
|
const notDraggingBackground = useColorModeValue('gray.50', 'gray.700');
|
||||||
|
|
||||||
|
let label = column.header?.toString() ?? 'Unrecognized column';
|
||||||
|
if (column.meta?.columnSelectorOptions?.label) label = column.meta.columnSelectorOptions.label;
|
||||||
|
|
||||||
|
const tooltipLabel = () => {
|
||||||
|
if (column.meta?.anchored) return t('table.drag_locked');
|
||||||
|
if (column.meta?.alwaysShow) return t('table.drag_always_show');
|
||||||
|
|
||||||
|
return t('table.drag_explanation');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable draggableId={draggableId} index={index} isDragDisabled={column.meta?.anchored}>
|
||||||
|
{(itemProvided, itemSnapshot) => (
|
||||||
|
<Tooltip label={tooltipLabel()}>
|
||||||
|
<Box
|
||||||
|
ref={itemProvided.innerRef}
|
||||||
|
{...itemProvided.draggableProps}
|
||||||
|
{...itemProvided.dragHandleProps}
|
||||||
|
display="flex"
|
||||||
|
backgroundColor={itemSnapshot.isDragging ? isDraggingBackground : notDraggingBackground}
|
||||||
|
px={6}
|
||||||
|
py={2}
|
||||||
|
my={2}
|
||||||
|
borderRadius={15}
|
||||||
|
cursor={column.meta?.anchored ? 'not-allowed' : undefined}
|
||||||
|
>
|
||||||
|
<Icon as={column.meta?.anchored ? Lock : ArrowsDownUp} boxSize={5} ml={0.5} mr={2} my="auto" />
|
||||||
|
<Text my="auto">{label}</Text>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DraggableColumn;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||||
|
import { Droppable } from '@hello-pangea/dnd';
|
||||||
|
import { DataGridColumn } from '../../useDataGrid';
|
||||||
|
import DraggableColumn from './DraggableColumn';
|
||||||
|
|
||||||
|
type Props<TValue> = {
|
||||||
|
items: string[];
|
||||||
|
columns: DataGridColumn<TValue>[];
|
||||||
|
droppableId: string;
|
||||||
|
isDropDisabled?: boolean;
|
||||||
|
};
|
||||||
|
const DroppableBox = <TValue extends object>({ items, columns, droppableId, isDropDisabled }: Props<TValue>) => {
|
||||||
|
const notDraggingBackground = useColorModeValue('gray.200', 'gray.600');
|
||||||
|
const isDraggingOverBackground = useColorModeValue('blue.300', 'blue.500');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Droppable droppableId={droppableId} direction="vertical" isCombineEnabled={false} isDropDisabled={isDropDisabled}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<Box
|
||||||
|
ref={provided.innerRef}
|
||||||
|
backgroundColor={snapshot.isDraggingOver ? isDraggingOverBackground : notDraggingBackground}
|
||||||
|
padding={2}
|
||||||
|
borderRadius={15}
|
||||||
|
>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
const found = columns.find((col) => col.id === item);
|
||||||
|
return found ? <DraggableColumn key={item} draggableId={item} index={index} column={found} /> : null;
|
||||||
|
})}
|
||||||
|
{provided.placeholder}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(DroppableBox);
|
||||||
129
src/components/DataGrid/TableSettingsModal/DragDrop/index.tsx
Normal file
129
src/components/DataGrid/TableSettingsModal/DragDrop/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||||
|
import { DragDropContext, DragStart, DropResult } from '@hello-pangea/dnd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataGridColumn, UseDataGridReturn } from '../../useDataGrid';
|
||||||
|
import DroppableBox from './DroppableBox';
|
||||||
|
|
||||||
|
const reorder = (list: string[], startIndex: number, endIndex: number) => {
|
||||||
|
const result = Array.from(list);
|
||||||
|
const [removed] = result.splice(startIndex, 1);
|
||||||
|
if (removed) {
|
||||||
|
result.splice(endIndex, 0, removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShownColumns = <TValue extends object>(columns: DataGridColumn<TValue>[], columnOrder: string[]) => {
|
||||||
|
const order = [...columnOrder];
|
||||||
|
|
||||||
|
for (const col of columns) {
|
||||||
|
if (!order.includes(col.id)) {
|
||||||
|
order.push(col.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return order;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props<TValue extends object> = {
|
||||||
|
controller: UseDataGridReturn;
|
||||||
|
shownColumns: DataGridColumn<TValue>[];
|
||||||
|
hiddenColumns: DataGridColumn<TValue>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableDragDrop = <TValue extends object>({ controller, shownColumns, hiddenColumns }: Props<TValue>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [shownOrder, setShowOrder] = React.useState(getShownColumns(shownColumns, controller.columnOrder));
|
||||||
|
const [hiddenOrder, setHiddenOrder] = React.useState(hiddenColumns.map((col) => col.id));
|
||||||
|
const [currentDraggingColumn, setCurrentDraggingColumn] = React.useState<DataGridColumn<TValue>>();
|
||||||
|
|
||||||
|
const handleDragStart = React.useCallback(
|
||||||
|
(start: DragStart) => {
|
||||||
|
const foundColumn =
|
||||||
|
shownColumns.find(({ id }) => id === start.draggableId) ??
|
||||||
|
hiddenColumns.find(({ id }) => id === start.draggableId);
|
||||||
|
setCurrentDraggingColumn(foundColumn);
|
||||||
|
},
|
||||||
|
[shownColumns, hiddenColumns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const minimumIndex = React.useMemo(() => {
|
||||||
|
let index = 0;
|
||||||
|
for (const [i, col] of shownColumns.entries()) {
|
||||||
|
if (col.meta?.anchored) {
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index + 1;
|
||||||
|
}, [shownColumns]);
|
||||||
|
|
||||||
|
const handleDragEnd = React.useCallback(
|
||||||
|
(result: DropResult) => {
|
||||||
|
const { source, destination, draggableId } = result;
|
||||||
|
|
||||||
|
if (destination === null) return;
|
||||||
|
|
||||||
|
if (source.droppableId === destination.droppableId) {
|
||||||
|
const newOrder = reorder(shownOrder, source.index, Math.max(destination.index, minimumIndex));
|
||||||
|
if (destination.droppableId === 'displayed-columns') {
|
||||||
|
controller.setColumnOrder(newOrder);
|
||||||
|
setShowOrder(newOrder);
|
||||||
|
} else setHiddenOrder(newOrder);
|
||||||
|
}
|
||||||
|
// This means we are moving from displayed to hidden
|
||||||
|
else if (source.droppableId === 'displayed-columns') {
|
||||||
|
// Toggle the column visibility in user preferences
|
||||||
|
const results = controller.hideColumn(draggableId);
|
||||||
|
if (results) {
|
||||||
|
setHiddenOrder([...results.hiddenColumns]);
|
||||||
|
setShowOrder([...results.columnOrder]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This means we are moving from hidden to displayed
|
||||||
|
else if (source.droppableId === 'hidden-columns') {
|
||||||
|
const newOrder = Array.from(shownOrder);
|
||||||
|
newOrder.splice(Math.max(destination.index, minimumIndex), 0, draggableId);
|
||||||
|
const results = controller.unhideColumn(draggableId, newOrder);
|
||||||
|
if (results) {
|
||||||
|
setHiddenOrder(results.hiddenColumns);
|
||||||
|
setShowOrder([...results.columnOrder]);
|
||||||
|
setHiddenOrder([...results.hiddenColumns]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentDraggingColumn(undefined);
|
||||||
|
},
|
||||||
|
[shownColumns, hiddenColumns, controller.hideColumn, controller.unhideColumn, minimumIndex],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading size="md">{t('table.columns')}</Heading>
|
||||||
|
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||||
|
<Flex mt={4}>
|
||||||
|
<Box w="50%" mr={2}>
|
||||||
|
<Heading size="sm" mb={4}>
|
||||||
|
Visible ({shownOrder.length})
|
||||||
|
</Heading>
|
||||||
|
<DroppableBox droppableId="displayed-columns" items={shownOrder} columns={shownColumns} />
|
||||||
|
</Box>
|
||||||
|
<Box ml={2} w="50%">
|
||||||
|
<Heading size="sm" mb={4}>
|
||||||
|
Hidden ({hiddenColumns.length})
|
||||||
|
</Heading>
|
||||||
|
<DroppableBox
|
||||||
|
droppableId="hidden-columns"
|
||||||
|
items={hiddenOrder}
|
||||||
|
columns={hiddenColumns}
|
||||||
|
isDropDisabled={currentDraggingColumn?.meta?.alwaysShow}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</DragDropContext>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableDragDrop;
|
||||||
57
src/components/DataGrid/TableSettingsModal/index.tsx
Normal file
57
src/components/DataGrid/TableSettingsModal/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { SettingsIcon } from '@chakra-ui/icons';
|
||||||
|
import { Box, IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { ClockCounterClockwise } from '@phosphor-icons/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataGridColumn, UseDataGridReturn } from '../useDataGrid';
|
||||||
|
import TableDragDrop from './DragDrop';
|
||||||
|
import { Modal } from 'components/Modals/Modal';
|
||||||
|
|
||||||
|
type Props<TValue extends object> = {
|
||||||
|
controller: UseDataGridReturn;
|
||||||
|
columns: DataGridColumn<TValue>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableSettingsModal = <TValue extends object>({ controller, columns }: Props<TValue>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const modalProps = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip label={t('table.preferences')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('table.preferences')}
|
||||||
|
icon={<SettingsIcon weight="bold" />}
|
||||||
|
onClick={modalProps.onOpen}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Modal
|
||||||
|
title={t('table.preferences')}
|
||||||
|
topRightButtons={
|
||||||
|
<Tooltip label={t('table.reset')}>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('table.reset')}
|
||||||
|
icon={<ClockCounterClockwise size={20} />}
|
||||||
|
onClick={controller.resetPreferences}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
options={{
|
||||||
|
modalSize: 'md',
|
||||||
|
maxWidth: { sm: '600px', md: '600px', lg: '600px', xl: '600px' },
|
||||||
|
}}
|
||||||
|
{...modalProps}
|
||||||
|
>
|
||||||
|
<Box w="100%">
|
||||||
|
<TableDragDrop<TValue>
|
||||||
|
shownColumns={columns.filter((col) => controller.columnVisibility[col.id] !== false)}
|
||||||
|
hiddenColumns={columns.filter((col) => controller.columnVisibility[col.id] === false)}
|
||||||
|
controller={controller}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TableSettingsModal);
|
||||||
251
src/components/DataGrid/index.tsx
Normal file
251
src/components/DataGrid/index.tsx
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
HStack,
|
||||||
|
Heading,
|
||||||
|
LayoutProps,
|
||||||
|
Spacer,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
Tbody,
|
||||||
|
Thead,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DataGridCellRow } from './CellRow';
|
||||||
|
import { DataGridHeaderRow } from './HeaderRow';
|
||||||
|
import DataGridControls from './Input';
|
||||||
|
import TableSettingsModal from './TableSettingsModal';
|
||||||
|
import { DataGridColumn, UseDataGridReturn } from './useDataGrid';
|
||||||
|
import RefreshButton from 'components/Buttons/RefreshButton';
|
||||||
|
import Card from 'components/Card';
|
||||||
|
import CardBody from 'components/Card/CardBody';
|
||||||
|
import CardHeader from 'components/Card/CardHeader';
|
||||||
|
import LoadingOverlay from 'components/LoadingOverlay';
|
||||||
|
|
||||||
|
export type ColumnOptions = {
|
||||||
|
isSortable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataGridOptions<TValue extends object> = {
|
||||||
|
count?: number;
|
||||||
|
isFullScreen?: boolean;
|
||||||
|
isHidingControls?: boolean;
|
||||||
|
isManual?: boolean;
|
||||||
|
minimumHeight?: LayoutProps['minH'];
|
||||||
|
onRowClick?: (row: TValue) => (() => void) | undefined;
|
||||||
|
refetch?: () => void;
|
||||||
|
showAsCard?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataGridProps<TValue extends object> = {
|
||||||
|
controller: UseDataGridReturn;
|
||||||
|
columns: DataGridColumn<TValue>[];
|
||||||
|
header: {
|
||||||
|
title: string;
|
||||||
|
objectListed: string;
|
||||||
|
leftContent?: React.ReactNode;
|
||||||
|
addButton?: React.ReactNode;
|
||||||
|
otherButtons?: React.ReactNode;
|
||||||
|
};
|
||||||
|
data?: TValue[];
|
||||||
|
isLoading?: boolean;
|
||||||
|
options?: DataGridOptions<TValue>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataGrid = <TValue extends object>({
|
||||||
|
controller,
|
||||||
|
columns,
|
||||||
|
header,
|
||||||
|
data = [],
|
||||||
|
options = {},
|
||||||
|
isLoading = false,
|
||||||
|
}: DataGridProps<TValue>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Table Styling
|
||||||
|
*/
|
||||||
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
|
const hoveredRowBg = useColorModeValue('gray.100', 'gray.600');
|
||||||
|
|
||||||
|
const minimumHeight: LayoutProps['minH'] = React.useMemo(() => {
|
||||||
|
if (options.isFullScreen) {
|
||||||
|
return { base: 'calc(100vh - 360px)', md: 'calc(100vh - 288px)' };
|
||||||
|
}
|
||||||
|
return options.minimumHeight ?? '300px';
|
||||||
|
}, [options.isFullScreen, options.minimumHeight]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Table Options
|
||||||
|
*/
|
||||||
|
const onRowClick = React.useMemo(() => options.onRowClick, [options.onRowClick]);
|
||||||
|
|
||||||
|
const pagination = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
pageIndex: controller.pageInfo.pageIndex,
|
||||||
|
pageSize: controller.pageInfo.pageSize,
|
||||||
|
}),
|
||||||
|
[controller.pageInfo.pageIndex, controller.pageInfo.pageSize],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pageCount = React.useMemo(() => {
|
||||||
|
if (options.isManual && options.count) {
|
||||||
|
return Math.ceil(options.count / pagination.pageSize);
|
||||||
|
}
|
||||||
|
return Math.ceil((data?.length ?? 0) / pagination.pageSize);
|
||||||
|
}, [options.count, options.isManual, data?.length, pagination.pageSize]);
|
||||||
|
|
||||||
|
const tableOptions = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
pageCount: pageCount > 0 ? pageCount : 1,
|
||||||
|
initialState: { sorting: controller.sortBy, pagination },
|
||||||
|
manualPagination: options.isManual,
|
||||||
|
manualSorting: options.isManual,
|
||||||
|
autoResetPageIndex: false,
|
||||||
|
}),
|
||||||
|
[options.isManual, controller.sortBy, pageCount],
|
||||||
|
);
|
||||||
|
|
||||||
|
const orderedColumns = React.useMemo(() => {
|
||||||
|
const order = controller.columnOrder.filter((id) => columns.find((col) => col.id === id));
|
||||||
|
if (order.length !== columns.length) {
|
||||||
|
for (const col of columns) {
|
||||||
|
if (!order.includes(col.id)) {
|
||||||
|
order.push(col.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns.slice().sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
||||||
|
}, [columns, controller.columnOrder]);
|
||||||
|
|
||||||
|
const table = useReactTable<TValue>({
|
||||||
|
// react-table base functions
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
|
||||||
|
// Table State
|
||||||
|
data,
|
||||||
|
columns: orderedColumns,
|
||||||
|
state: {
|
||||||
|
sorting: controller.sortBy,
|
||||||
|
columnVisibility: controller.columnVisibility,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change Handlers
|
||||||
|
onSortingChange: controller.setSortBy,
|
||||||
|
onPaginationChange: controller.onPaginationChange,
|
||||||
|
|
||||||
|
// debugTable: true,
|
||||||
|
|
||||||
|
// Table Options
|
||||||
|
...tableOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading && data.length === 0) {
|
||||||
|
return (
|
||||||
|
<Center>
|
||||||
|
<Spinner size="xl" />
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.showAsCard ? (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<Heading size="md" my="auto" mr={2}>
|
||||||
|
{header.title}
|
||||||
|
</Heading>
|
||||||
|
{header.leftContent}
|
||||||
|
<Spacer />
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{header.otherButtons}
|
||||||
|
{header.addButton}
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||||
|
}
|
||||||
|
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||||
|
</HStack>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody display="flex" flexDirection="column">
|
||||||
|
<LoadingOverlay isLoading={isLoading}>
|
||||||
|
<TableContainer minH={minimumHeight}>
|
||||||
|
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px">
|
||||||
|
<Thead>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||||
|
))}
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<DataGridCellRow<TValue> key={row.id} row={row} onRowClick={onRowClick} rowStyle={{ hoveredRowBg }} />
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{data?.length === 0 ? (
|
||||||
|
<Center mt={8}>
|
||||||
|
<Heading size="md">
|
||||||
|
{header.objectListed
|
||||||
|
? t('common.no_obj_found', { obj: header.objectListed })
|
||||||
|
: t('common.empty_list')}
|
||||||
|
</Heading>
|
||||||
|
</Center>
|
||||||
|
) : null}
|
||||||
|
</TableContainer>
|
||||||
|
</LoadingOverlay>
|
||||||
|
{!options.isHidingControls ? <DataGridControls table={table} isDisabled={isLoading} /> : null}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<Box w="100%">
|
||||||
|
<Flex mb={2}>
|
||||||
|
<Heading size="md" my="auto" mr={2}>
|
||||||
|
{header.title}
|
||||||
|
</Heading>
|
||||||
|
{header.leftContent}
|
||||||
|
<Spacer />
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{header.otherButtons}
|
||||||
|
{header.addButton}
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
<TableSettingsModal<TValue> controller={controller} columns={columns} />
|
||||||
|
}
|
||||||
|
{options.refetch ? <RefreshButton onClick={options.refetch} isCompact isFetching={isLoading} /> : null}
|
||||||
|
</HStack>
|
||||||
|
</Flex>
|
||||||
|
<LoadingOverlay isLoading={isLoading}>
|
||||||
|
<TableContainer minH={minimumHeight}>
|
||||||
|
<Table size="small" variant="simple" textColor={textColor} w="100%" fontSize="14px">
|
||||||
|
<Thead>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<DataGridHeaderRow<TValue> key={headerGroup.id} headerGroup={headerGroup} />
|
||||||
|
))}
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<DataGridCellRow<TValue> key={row.id} row={row} onRowClick={onRowClick} rowStyle={{ hoveredRowBg }} />
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{data?.length === 0 ? (
|
||||||
|
<Center mt={8}>
|
||||||
|
<Heading size="md">
|
||||||
|
{header.objectListed ? t('common.no_obj_found', { obj: header.objectListed }) : t('common.empty_list')}
|
||||||
|
</Heading>
|
||||||
|
</Center>
|
||||||
|
) : null}
|
||||||
|
</TableContainer>
|
||||||
|
</LoadingOverlay>
|
||||||
|
{!options.isHidingControls ? <DataGridControls table={table} isDisabled={isLoading} /> : null}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
195
src/components/DataGrid/useDataGrid.ts
Normal file
195
src/components/DataGrid/useDataGrid.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ColumnDef, PaginationState, SortingColumnDef, SortingState, VisibilityState } from '@tanstack/react-table';
|
||||||
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
|
|
||||||
|
const getDefaultSettings = (settings?: string) => {
|
||||||
|
let limit = 10;
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
const savedSizeSetting = localStorage.getItem(settings);
|
||||||
|
if (savedSizeSetting) {
|
||||||
|
try {
|
||||||
|
limit = parseInt(savedSizeSetting, 10);
|
||||||
|
} catch (e) {
|
||||||
|
limit = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedPageSetting = localStorage.getItem(`${settings}.page`);
|
||||||
|
if (savedPageSetting) {
|
||||||
|
try {
|
||||||
|
index = parseInt(savedPageSetting, 10);
|
||||||
|
} catch (e) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pageSize: limit,
|
||||||
|
pageIndex: index,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSavedColumnOrder = (defaultValue: string[], settings?: string) => {
|
||||||
|
if (settings) {
|
||||||
|
const savedOrderSetting = localStorage.getItem(`${settings}.order`);
|
||||||
|
if (savedOrderSetting) {
|
||||||
|
try {
|
||||||
|
const savedOrder = JSON.parse(savedOrderSetting);
|
||||||
|
return savedOrder.length > 0 ? savedOrder : defaultValue;
|
||||||
|
} catch (e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataGridColumn<T> = ColumnDef<T> & SortingColumnDef<T> & { id: string };
|
||||||
|
|
||||||
|
export type UseDataGridProps = {
|
||||||
|
tableSettingsId: string;
|
||||||
|
defaultOrder: string[];
|
||||||
|
defaultSortBy?: SortingState;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDataGrid = ({ tableSettingsId, defaultSortBy, defaultOrder }: UseDataGridProps) => {
|
||||||
|
const orderSetting = `${tableSettingsId}.order`;
|
||||||
|
const hiddenColumnSetting = `${tableSettingsId}.hiddenColumns`;
|
||||||
|
const pageSetting = `${tableSettingsId}.page`;
|
||||||
|
const { getPref, setPref, setPrefs, deletePref } = useAuth();
|
||||||
|
const [sortBy, setSortBy] = React.useState<SortingState>(defaultSortBy ?? []);
|
||||||
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||||
|
const [columnOrder, setColumnOrder] = React.useState<string[]>(
|
||||||
|
getSavedColumnOrder(defaultOrder ?? [], tableSettingsId),
|
||||||
|
);
|
||||||
|
const [pageInfo, setPageInfo] = React.useState<PaginationState>(getDefaultSettings(tableSettingsId));
|
||||||
|
|
||||||
|
const setNewColumnOrder = React.useCallback(
|
||||||
|
(newOrder: string[]) => {
|
||||||
|
setColumnOrder(newOrder);
|
||||||
|
if (tableSettingsId) {
|
||||||
|
localStorage.setItem(orderSetting, JSON.stringify(newOrder));
|
||||||
|
setPref({ preference: orderSetting, value: newOrder.join(',') });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setPref],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetPreferences = React.useCallback(async () => {
|
||||||
|
if (tableSettingsId) {
|
||||||
|
localStorage.removeItem(orderSetting);
|
||||||
|
localStorage.removeItem(hiddenColumnSetting);
|
||||||
|
await deletePref([orderSetting, hiddenColumnSetting]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setColumnOrder(defaultOrder ?? []);
|
||||||
|
setColumnVisibility({});
|
||||||
|
}, [deletePref]);
|
||||||
|
|
||||||
|
const hideColumn = React.useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
const newVisibility = { ...columnVisibility };
|
||||||
|
newVisibility[id] = false;
|
||||||
|
let hiddenColumnsArray = Object.entries(newVisibility)
|
||||||
|
.filter(([, value]) => !value)
|
||||||
|
.map(([key]) => key);
|
||||||
|
hiddenColumnsArray = [...new Set(hiddenColumnsArray)]; // Remove duplicates
|
||||||
|
|
||||||
|
// New column order without hidden columns
|
||||||
|
let filteredColumnOrder = columnOrder.filter((columnId) => !hiddenColumnsArray.includes(columnId));
|
||||||
|
filteredColumnOrder = [...new Set(filteredColumnOrder)]; // Remove duplicates
|
||||||
|
|
||||||
|
setPrefs([
|
||||||
|
{ tag: hiddenColumnSetting, value: hiddenColumnsArray.join(',') },
|
||||||
|
{ tag: orderSetting, value: filteredColumnOrder.join(',') },
|
||||||
|
]);
|
||||||
|
setColumnVisibility({ ...newVisibility });
|
||||||
|
setColumnOrder(filteredColumnOrder);
|
||||||
|
localStorage.setItem(orderSetting, JSON.stringify(filteredColumnOrder));
|
||||||
|
localStorage.setItem(hiddenColumnSetting, hiddenColumnsArray.join(','));
|
||||||
|
|
||||||
|
return {
|
||||||
|
hiddenColumns: hiddenColumnsArray,
|
||||||
|
columnOrder: filteredColumnOrder,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[columnOrder, columnVisibility, setPrefs],
|
||||||
|
);
|
||||||
|
|
||||||
|
const unhideColumn = React.useCallback(
|
||||||
|
(id: string, newOrder: string[]) => {
|
||||||
|
const newVisibility = { ...columnVisibility };
|
||||||
|
newVisibility[id] = true;
|
||||||
|
let hiddenColumnsArray = Object.entries(newVisibility)
|
||||||
|
.filter(([, value]) => !value)
|
||||||
|
.map(([key]) => key);
|
||||||
|
hiddenColumnsArray = [...new Set(hiddenColumnsArray)]; // Remove duplicates
|
||||||
|
|
||||||
|
const newColumnOrder = [...new Set(newOrder)]; // Remove duplicates
|
||||||
|
|
||||||
|
setPrefs([
|
||||||
|
{ tag: hiddenColumnSetting, value: hiddenColumnsArray.join(',') },
|
||||||
|
{ tag: orderSetting, value: newColumnOrder.join(',') },
|
||||||
|
]);
|
||||||
|
setColumnVisibility({ ...newVisibility });
|
||||||
|
setColumnOrder(newColumnOrder);
|
||||||
|
localStorage.setItem(orderSetting, JSON.stringify(newColumnOrder));
|
||||||
|
localStorage.setItem(hiddenColumnSetting, hiddenColumnsArray.join(','));
|
||||||
|
|
||||||
|
return {
|
||||||
|
hiddenColumns: hiddenColumnsArray,
|
||||||
|
columnOrder: newColumnOrder,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[columnOrder, columnVisibility, setPrefs],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const savedPrefs = getPref(hiddenColumnSetting);
|
||||||
|
|
||||||
|
if (savedPrefs) {
|
||||||
|
const savedHiddenColumns = savedPrefs.split(',');
|
||||||
|
setColumnVisibility(savedHiddenColumns.reduce((acc, curr) => ({ ...acc, [curr]: false }), {}));
|
||||||
|
} else {
|
||||||
|
setColumnVisibility({});
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedOrderSetting = getPref(orderSetting);
|
||||||
|
|
||||||
|
if (savedOrderSetting) {
|
||||||
|
const savedHiddenColumns = savedOrderSetting.split(',');
|
||||||
|
setColumnOrder(savedHiddenColumns);
|
||||||
|
}
|
||||||
|
}, [tableSettingsId]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (tableSettingsId) {
|
||||||
|
localStorage.setItem(pageSetting, String(pageInfo.pageIndex));
|
||||||
|
if (tableSettingsId) localStorage.setItem(`${tableSettingsId}`, String(pageInfo.pageSize));
|
||||||
|
}
|
||||||
|
}, [pageInfo.pageIndex, pageInfo.pageSize]);
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => ({
|
||||||
|
tableSettingsId,
|
||||||
|
pageInfo,
|
||||||
|
sortBy,
|
||||||
|
setSortBy,
|
||||||
|
columnOrder,
|
||||||
|
setColumnOrder: setNewColumnOrder,
|
||||||
|
hideColumn,
|
||||||
|
unhideColumn,
|
||||||
|
columnVisibility,
|
||||||
|
setColumnVisibility,
|
||||||
|
onPaginationChange: setPageInfo,
|
||||||
|
resetPreferences,
|
||||||
|
}),
|
||||||
|
[pageInfo, hideColumn, unhideColumn, columnVisibility, sortBy, columnOrder, setNewColumnOrder],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UseDataGridReturn = ReturnType<typeof useDataGrid>;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from '@chakra-ui/react';
|
import { Icon } from '@chakra-ui/react';
|
||||||
import { ArrowDown, ArrowUp, Circle } from 'phosphor-react';
|
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isSorted: boolean;
|
isSorted: boolean;
|
||||||
@@ -12,13 +12,7 @@ const defaultProps = {
|
|||||||
isSortedDesc: false,
|
isSortedDesc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortIcon = (
|
const SortIcon = ({ isSorted, isSortedDesc, canSort }: Props) => {
|
||||||
{
|
|
||||||
isSorted,
|
|
||||||
isSortedDesc,
|
|
||||||
canSort
|
|
||||||
}: Props
|
|
||||||
) => {
|
|
||||||
if (canSort) {
|
if (canSort) {
|
||||||
if (isSorted) {
|
if (isSorted) {
|
||||||
return isSortedDesc ? <Icon pt={2} h={5} w={5} as={ArrowDown} /> : <Icon pt={2} h={5} w={5} as={ArrowUp} />;
|
return isSortedDesc ? <Icon pt={2} h={5} w={5} as={ArrowDown} /> : <Icon pt={2} h={5} w={5} as={ArrowUp} />;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { UploadSimple } from 'phosphor-react';
|
import { UploadSimple } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { IconButton, Input, InputGroup, InputRightElement, Tooltip } from '@chakra-ui/react';
|
import { IconButton, Input, InputGroup, InputRightElement, Tooltip } from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { Trash } from 'phosphor-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import CloseButton from 'components/Buttons/CloseButton';
|
import CloseButton from 'components/Buttons/CloseButton';
|
||||||
import SaveButton from 'components/Buttons/SaveButton';
|
import SaveButton from 'components/Buttons/SaveButton';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
PopoverHeader,
|
PopoverHeader,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Question } from 'phosphor-react';
|
import { Question } from '@phosphor-icons/react';
|
||||||
|
|
||||||
export type InfoPopoverProps = {
|
export type InfoPopoverProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ReactCountryFlag from 'react-country-flag';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const iconStyle = { width: '24px', height: '24px', borderRadius: '20px' };
|
const iconStyle = { width: '24px', height: '24px', borderRadius: '20px' };
|
||||||
|
|
||||||
const LanguageSwitcher = () => {
|
const LanguageSwitcher = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
@@ -32,7 +33,14 @@ const LanguageSwitcher = () => {
|
|||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Tooltip label={t('common.language')}>
|
<Tooltip label={t('common.language')}>
|
||||||
<MenuButton background="transparent" as={IconButton} aria-label="Commands" icon={languageIcon} size="sm" />
|
<MenuButton
|
||||||
|
background="transparent"
|
||||||
|
variant="ghost"
|
||||||
|
as={IconButton}
|
||||||
|
aria-label="Commands"
|
||||||
|
icon={languageIcon}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem onClick={changeLanguage('de')}>Deutsche</MenuItem>
|
<MenuItem onClick={changeLanguage('de')}>Deutsche</MenuItem>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Flex, HStack, ModalHeader as Header, Spacer } from '@chakra-ui/react';
|
import { HStack, ModalHeader as Header, Spacer, useColorModeValue } from '@chakra-ui/react';
|
||||||
|
|
||||||
interface ModalHeaderProps {
|
interface ModalHeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -7,17 +7,21 @@ interface ModalHeaderProps {
|
|||||||
right: React.ReactNode;
|
right: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalHeader: React.FC<ModalHeaderProps> = ({ title, left, right }) => (
|
const ModalHeader: React.FC<ModalHeaderProps> = ({ title, left, right }) => {
|
||||||
<Header>
|
const bg = useColorModeValue('blue.50', 'blue.700');
|
||||||
<Flex justifyContent="center" alignItems="center" maxW="100%" px={1}>
|
|
||||||
|
return (
|
||||||
|
<Header bg={bg}>
|
||||||
{title}
|
{title}
|
||||||
<HStack spacing={2} ml={2}>
|
{left ? (
|
||||||
{left ?? null}
|
<HStack spacing={2} ml={2}>
|
||||||
</HStack>
|
{left}
|
||||||
|
</HStack>
|
||||||
|
) : null}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{right}
|
{right}
|
||||||
</Flex>
|
</Header>
|
||||||
</Header>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default ModalHeader;
|
export default ModalHeader;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
import { Flex, IconButton, Tooltip } from '@chakra-ui/react';
|
||||||
import { MagnifyingGlass } from 'phosphor-react';
|
import { MagnifyingGlass } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
@@ -10,11 +10,7 @@ interface Props {
|
|||||||
subscribers: Subscriber[];
|
subscribers: Subscriber[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubscriberSearchDisplayTable = (
|
const SubscriberSearchDisplayTable = ({ subscribers }: Props) => {
|
||||||
{
|
|
||||||
subscribers
|
|
||||||
}: Props
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { MagnifyingGlass } from 'phosphor-react';
|
import { MagnifyingGlass } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SubscriberSearchDisplayTable from './Table';
|
import SubscriberSearchDisplayTable from './Table';
|
||||||
import CloseButton from 'components/Buttons/CloseButton';
|
import CloseButton from 'components/Buttons/CloseButton';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Modal, ModalOverlay, ModalContent, ModalBody, Center, Spinner } from '@chakra-ui/react';
|
import { Modal, ModalOverlay, ModalContent, ModalBody, Center, Spinner } from '@chakra-ui/react';
|
||||||
import { ArrowLeft, Download, Gauge } from 'phosphor-react';
|
import { ArrowLeft, Download, Gauge } from '@phosphor-icons/react';
|
||||||
import { CSVLink } from 'react-csv';
|
import { CSVLink } from 'react-csv';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import WifiScanForm from './Form';
|
import WifiScanForm from './Form';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from '@chakra-ui/react';
|
import { Icon } from '@chakra-ui/react';
|
||||||
import { ArrowDown, ArrowUp, Circle } from 'phosphor-react';
|
import { ArrowDown, ArrowUp, Circle } from '@phosphor-icons/react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isSorted: boolean;
|
isSorted: boolean;
|
||||||
@@ -12,13 +12,7 @@ const defaultProps = {
|
|||||||
isSortedDesc: false,
|
isSortedDesc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortIcon = (
|
const SortIcon = ({ isSorted, isSortedDesc, canSort }: Props) => {
|
||||||
{
|
|
||||||
isSorted,
|
|
||||||
isSortedDesc,
|
|
||||||
canSort
|
|
||||||
}: Props
|
|
||||||
) => {
|
|
||||||
if (canSort) {
|
if (canSort) {
|
||||||
if (isSorted) {
|
if (isSorted) {
|
||||||
return isSortedDesc ? <Icon pt={2} h={5} w={5} as={ArrowDown} /> : <Icon pt={2} h={5} w={5} as={ArrowUp} />;
|
return isSortedDesc ? <Icon pt={2} h={5} w={5} as={ArrowDown} /> : <Icon pt={2} h={5} w={5} as={ArrowUp} />;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InfoIcon } from '@chakra-ui/icons';
|
import { InfoIcon } from '@chakra-ui/icons';
|
||||||
import { Heading, IconButton, LayoutProps, LightMode, SpaceProps, Spacer, Tooltip } from '@chakra-ui/react';
|
import { Heading, IconButton, LayoutProps, LightMode, SpaceProps, Spacer, Tooltip } from '@chakra-ui/react';
|
||||||
import { MagnifyingGlass } from 'phosphor-react';
|
import { MagnifyingGlass } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Card from 'components/Card';
|
import Card from 'components/Card';
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@ const SimpleStatDisplay = ({
|
|||||||
onClick={openModal}
|
onClick={openModal}
|
||||||
cursor={openModal ? 'pointer' : ''}
|
cursor={openModal ? 'pointer' : ''}
|
||||||
className="tile-shadow-animate"
|
className="tile-shadow-animate"
|
||||||
|
p={4}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{title !== '' && (
|
{title !== '' && (
|
||||||
@@ -48,7 +49,6 @@ const SimpleStatDisplay = ({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label={t('common.view_details')}
|
aria-label={t('common.view_details')}
|
||||||
size="sm"
|
size="sm"
|
||||||
colorScheme="purple"
|
|
||||||
icon={<MagnifyingGlass height={20} width={20} />}
|
icon={<MagnifyingGlass height={20} width={20} />}
|
||||||
ml={2}
|
ml={2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip } from '@chakra-ui/react';
|
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip } from '@chakra-ui/react';
|
||||||
import { Wrench } from 'phosphor-react';
|
import { Wrench } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useBlinkDevice, useGetDeviceRtty, useRebootDevice } from 'hooks/Network/GatewayDevices';
|
import { useBlinkDevice, useGetDeviceRtty, useRebootDevice } from 'hooks/Network/GatewayDevices';
|
||||||
import useMutationResult from 'hooks/useMutationResult';
|
import useMutationResult from 'hooks/useMutationResult';
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { Trash } from 'phosphor-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDeleteConfiguration } from 'hooks/Network/Configurations';
|
import { useDeleteConfiguration } from 'hooks/Network/Configurations';
|
||||||
import { Configuration } from 'models/Configuration';
|
import { Configuration } from 'models/Configuration';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { Lock, Plus, Trash } from 'phosphor-react';
|
import { Lock, Plus, Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip } from '@chakra-ui/react';
|
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Spinner, Tooltip } from '@chakra-ui/react';
|
||||||
import { Wrench } from 'phosphor-react';
|
import { Wrench } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useBlinkDevice, useGetDeviceRtty, useRebootDevice } from 'hooks/Network/GatewayDevices';
|
import { useBlinkDevice, useGetDeviceRtty, useRebootDevice } from 'hooks/Network/GatewayDevices';
|
||||||
import useMutationResult from 'hooks/useMutationResult';
|
import useMutationResult from 'hooks/useMutationResult';
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { ArrowSquareOut, PaperPlaneTilt, Trash } from 'phosphor-react';
|
import { ArrowSquareOut, PaperPlaneTilt, Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import DeviceActionDropdown from './ActionDropdown';
|
import DeviceActionDropdown from './ActionDropdown';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useDisclosure, Modal, ModalOverlay, ModalContent, ModalBody, Tooltip, IconButton } from '@chakra-ui/react';
|
import { useDisclosure, Modal, ModalOverlay, ModalContent, ModalBody, Tooltip, IconButton } from '@chakra-ui/react';
|
||||||
import { UploadSimple } from 'phosphor-react';
|
import { UploadSimple } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Flex, IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
import { Flex, IconButton, Tooltip, useToast } from '@chakra-ui/react';
|
||||||
import { Plus, Trash } from 'phosphor-react';
|
import { Plus, Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { MagnifyingGlass, Trash } from 'phosphor-react';
|
import { MagnifyingGlass, Trash } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export const AuthProvider = ({ token, children }: AuthProviderProps) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPref = ({ preference, value }: { preference: string; value: string }) => {
|
const setPref = async ({ preference, value }: { preference: string; value: string }) => {
|
||||||
let updated = false;
|
let updated = false;
|
||||||
if (preferences) {
|
if (preferences) {
|
||||||
const newPreferences: Preference[] = preferences.map((pref: Preference) => {
|
const newPreferences: Preference[] = preferences.map((pref: Preference) => {
|
||||||
@@ -102,15 +102,41 @@ export const AuthProvider = ({ token, children }: AuthProviderProps) => {
|
|||||||
|
|
||||||
if (!updated) newPreferences.push({ tag: preference, value });
|
if (!updated) newPreferences.push({ tag: preference, value });
|
||||||
|
|
||||||
updatePreferences.mutateAsync(newPreferences);
|
await updatePreferences.mutateAsync(newPreferences);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletePref = (preference: string) => {
|
const setPrefs = async (preferencesToUpdate: Preference[]) => {
|
||||||
if (preferences) {
|
if (preferences) {
|
||||||
const newPreferences: Preference[] = preferences.filter((pref: Preference) => pref.tag !== preference);
|
const updatedPreferences: string[] = [];
|
||||||
|
const newPreferences = preferences.map((pref: Preference) => {
|
||||||
|
const preferenceToUpdate = preferencesToUpdate.find(
|
||||||
|
(prefToUpdate: Preference) => prefToUpdate.tag === pref.tag,
|
||||||
|
);
|
||||||
|
if (preferenceToUpdate) {
|
||||||
|
updatedPreferences.push(pref.tag);
|
||||||
|
return { tag: pref.tag, value: preferenceToUpdate.value };
|
||||||
|
}
|
||||||
|
return pref;
|
||||||
|
});
|
||||||
|
|
||||||
updatePreferences.mutateAsync(newPreferences);
|
for (const preferenceToUpdate of preferencesToUpdate) {
|
||||||
|
if (!updatedPreferences.includes(preferenceToUpdate.tag)) {
|
||||||
|
newPreferences.push(preferenceToUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updatePreferences.mutateAsync(newPreferences);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePref = async (preference: string | string[]) => {
|
||||||
|
if (preferences) {
|
||||||
|
const newPreferences: Preference[] = preferences.filter((pref: Preference) =>
|
||||||
|
typeof preference === 'string' ? pref.tag !== preference : !preference.includes(pref.tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
await updatePreferences.mutateAsync(newPreferences);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,6 +172,7 @@ export const AuthProvider = ({ token, children }: AuthProviderProps) => {
|
|||||||
ref,
|
ref,
|
||||||
getPref,
|
getPref,
|
||||||
setPref,
|
setPref,
|
||||||
|
setPrefs,
|
||||||
deletePref,
|
deletePref,
|
||||||
endpoints,
|
endpoints,
|
||||||
configurationDescriptions,
|
configurationDescriptions,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { Preference } from 'models/Preference';
|
||||||
import { User } from 'models/User';
|
import { User } from 'models/User';
|
||||||
import { axiosProv } from 'utils/axiosInstances';
|
import { axiosProv } from 'utils/axiosInstances';
|
||||||
|
|
||||||
@@ -26,7 +27,8 @@ export interface AuthProviderReturn {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
getPref: (preference: string) => string | null;
|
getPref: (preference: string) => string | null;
|
||||||
setPref: ({ preference, value }: { preference: string; value: string }) => void;
|
setPref: ({ preference, value }: { preference: string; value: string }) => void;
|
||||||
deletePref: (preference: string) => void;
|
setPrefs: (preferencesToUpdate: Preference[]) => void;
|
||||||
|
deletePref: (preference: string | string[]) => void;
|
||||||
ref: React.MutableRefObject<undefined>;
|
ref: React.MutableRefObject<undefined>;
|
||||||
endpoints: { [key: string]: string } | null;
|
endpoints: { [key: string]: string } | null;
|
||||||
configurationDescriptions: Record<string, unknown>;
|
configurationDescriptions: Record<string, unknown>;
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { X } from '@phosphor-icons/react';
|
||||||
import { TOptions } from 'i18next';
|
import { TOptions } from 'i18next';
|
||||||
import { X } from 'phosphor-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { ProvisioningVenueNotificationMessage } from '../../utils';
|
import { ProvisioningVenueNotificationMessage } from '../../utils';
|
||||||
|
|||||||
54
src/hooks/useContainerDimensions.ts
Normal file
54
src/hooks/useContainerDimensions.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const roundToNearest = (num: number, precision: number) => {
|
||||||
|
const factor = 1 / precision;
|
||||||
|
return Math.round(num * factor) / factor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UseContainerDimensionsProps = {
|
||||||
|
precision?: 10 | 100 | 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useContainerDimensions = ({ precision }: UseContainerDimensionsProps) => {
|
||||||
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
|
const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const getDimensions = () => ({
|
||||||
|
width: (ref && ref.current?.offsetWidth) || 0,
|
||||||
|
height: (ref && ref.current?.offsetHeight) || 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const { width, height } = getDimensions();
|
||||||
|
if (!precision) {
|
||||||
|
if (dimensions.width !== width && dimensions.height !== height) setDimensions({ width, height });
|
||||||
|
} else {
|
||||||
|
const newDimensions = { width, height };
|
||||||
|
newDimensions.width = roundToNearest(newDimensions.width, precision);
|
||||||
|
newDimensions.height = roundToNearest(newDimensions.height, precision);
|
||||||
|
if (newDimensions.width !== dimensions.width || newDimensions.height !== dimensions.height) {
|
||||||
|
setDimensions(newDimensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ref.current) {
|
||||||
|
handleResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, [ref, dimensions]);
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => ({
|
||||||
|
dimensions,
|
||||||
|
ref,
|
||||||
|
}),
|
||||||
|
[dimensions.height, dimensions.width],
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ i18next
|
|||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
|
returnNull: false,
|
||||||
// debug: process.env.NODE_ENV === "development",
|
// debug: process.env.NODE_ENV === "development",
|
||||||
});
|
});
|
||||||
export default i18next;
|
export default i18next;
|
||||||
|
|||||||
7
src/i18next.d.ts
vendored
Normal file
7
src/i18next.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import 'i18next';
|
||||||
|
|
||||||
|
declare module 'i18next' {
|
||||||
|
interface CustomTypeOptions {
|
||||||
|
returnNull: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
Portal,
|
Portal,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ArrowCircleLeft, MapTrifold } from 'phosphor-react';
|
import { ArrowCircleLeft } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from 'contexts/AuthProvider';
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
@@ -73,7 +73,6 @@ export const Navbar = ({ toggleSidebar, activeRoute, languageSwitcher }: NavbarP
|
|||||||
};
|
};
|
||||||
|
|
||||||
const goToProfile = () => navigate('/account');
|
const goToProfile = () => navigate('/account');
|
||||||
const goToMap = () => navigate('/map');
|
|
||||||
|
|
||||||
window.addEventListener('scroll', changeNavbar);
|
window.addEventListener('scroll', changeNavbar);
|
||||||
|
|
||||||
@@ -98,14 +97,22 @@ export const Navbar = ({ toggleSidebar, activeRoute, languageSwitcher }: NavbarP
|
|||||||
ps="12px"
|
ps="12px"
|
||||||
pt="8px"
|
pt="8px"
|
||||||
top="15px"
|
top="15px"
|
||||||
w={isCompact ? '100%' : 'calc(100vw - 256px)'}
|
border={scrolled ? '0.5px solid' : undefined}
|
||||||
|
w={isCompact ? '100%' : 'calc(100% - 254px)'}
|
||||||
>
|
>
|
||||||
<Flex w="100%" flexDirection="row" alignItems="center">
|
<Flex
|
||||||
|
w="100%"
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
justifyItems="center"
|
||||||
|
alignContent="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
{isCompact && <HamburgerIcon w="24px" h="24px" onClick={toggleSidebar} mr={10} mt={1} />}
|
{isCompact && <HamburgerIcon w="24px" h="24px" onClick={toggleSidebar} mr={10} mt={1} />}
|
||||||
<Heading>{activeRoute}</Heading>
|
<Heading size="lg">{activeRoute}</Heading>
|
||||||
<Tooltip label={t('common.go_back')}>
|
<Tooltip label={t('common.go_back')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
mt={2}
|
mt={1}
|
||||||
ml={4}
|
ml={4}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
aria-label={t('common.go_back')}
|
aria-label={t('common.go_back')}
|
||||||
@@ -116,14 +123,6 @@ export const Navbar = ({ toggleSidebar, activeRoute, languageSwitcher }: NavbarP
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Box ms="auto" w={{ base: 'unset' }}>
|
<Box ms="auto" w={{ base: 'unset' }}>
|
||||||
<Flex alignItems="center" flexDirection="row">
|
<Flex alignItems="center" flexDirection="row">
|
||||||
<Tooltip hasArrow label={t('common.go_to_map')}>
|
|
||||||
<IconButton
|
|
||||||
aria-label={t('common.go_to_map')}
|
|
||||||
variant="ghost"
|
|
||||||
icon={<MapTrifold size={24} />}
|
|
||||||
onClick={goToMap}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip hasArrow label={t('common.theme')}>
|
<Tooltip hasArrow label={t('common.theme')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={t('common.theme')}
|
aria-label={t('common.theme')}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Flex, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react';
|
import { Button, Flex, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react';
|
||||||
import { ArrowCircleRight } from 'phosphor-react';
|
import { ArrowCircleRight } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import EntityPopover from './EntityPopover';
|
import EntityPopover from './EntityPopover';
|
||||||
import IconBox from 'components/IconBox';
|
import IconBox from 'components/IconBox';
|
||||||
@@ -21,7 +21,7 @@ const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
const inactiveArrowColor = useColorModeValue('var(--chakra-colors-gray-600)', 'var(--chakra-colors-gray-200)');
|
const inactiveArrowColor = useColorModeValue('var(--chakra-colors-gray-600)', 'var(--chakra-colors-gray-200)');
|
||||||
const activeTextColor = useColorModeValue('gray.700', 'white');
|
const activeTextColor = useColorModeValue('gray.700', 'white');
|
||||||
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
||||||
const inactiveIconColor = useColorModeValue('gray.100', 'gray.600');
|
const hoverBg = useColorModeValue('blue.100', 'blue.800');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityPopover isOpen={isOpen} onClose={onClose} toggleSidebar={toggleSidebar}>
|
<EntityPopover isOpen={isOpen} onClose={onClose} toggleSidebar={toggleSidebar}>
|
||||||
@@ -35,7 +35,7 @@ const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
bg="transparent"
|
bg="transparent"
|
||||||
transition={variantChange}
|
transition={variantChange}
|
||||||
mx="auto"
|
mx="auto"
|
||||||
ps="10px"
|
px={1}
|
||||||
py="12px"
|
py="12px"
|
||||||
borderRadius="15px"
|
borderRadius="15px"
|
||||||
w="100%"
|
w="100%"
|
||||||
@@ -47,13 +47,17 @@ const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
_focus={{
|
_focus={{
|
||||||
boxShadow: '0px 7px 11px rgba(0, 0, 0, 0.04)',
|
boxShadow: '0px 7px 11px rgba(0, 0, 0, 0.04)',
|
||||||
}}
|
}}
|
||||||
|
_hover={{
|
||||||
|
bg: hoverBg,
|
||||||
|
}}
|
||||||
|
borderWidth="0px"
|
||||||
rightIcon={<ArrowCircleRight size={24} color={activeArrowColor} />}
|
rightIcon={<ArrowCircleRight size={24} color={activeArrowColor} />}
|
||||||
>
|
>
|
||||||
<Flex>
|
<Flex alignItems="center" w="100%">
|
||||||
<IconBox bg="blue.300" color="white" h="42px" w="42px" me="12px" transition={variantChange}>
|
<IconBox color="blue.300" h="30px" w="30px" me="6px" transition={variantChange} fontWeight="bold">
|
||||||
{route.icon(true)}
|
{route.icon(false)}
|
||||||
</IconBox>
|
</IconBox>
|
||||||
<Text color={activeTextColor} my="auto" fontSize="lg">
|
<Text color={activeTextColor} fontSize="md" fontWeight="bold">
|
||||||
{t(route.name)}
|
{t(route.name)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -67,7 +71,7 @@ const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
bg="transparent"
|
bg="transparent"
|
||||||
mx="auto"
|
mx="auto"
|
||||||
py="12px"
|
py="12px"
|
||||||
ps="10px"
|
ps={1}
|
||||||
borderRadius="15px"
|
borderRadius="15px"
|
||||||
w="100%"
|
w="100%"
|
||||||
_active={{
|
_active={{
|
||||||
@@ -78,13 +82,17 @@ const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
_focus={{
|
_focus={{
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
}}
|
}}
|
||||||
|
_hover={{
|
||||||
|
bg: hoverBg,
|
||||||
|
}}
|
||||||
|
borderWidth="0px"
|
||||||
rightIcon={<ArrowCircleRight size={20} color={inactiveArrowColor} />}
|
rightIcon={<ArrowCircleRight size={20} color={inactiveArrowColor} />}
|
||||||
>
|
>
|
||||||
<Flex>
|
<Flex alignItems="center" w="100%">
|
||||||
<IconBox bg={inactiveIconColor} color="blue.300" h="34px" w="34px" me="12px" transition={variantChange}>
|
<IconBox color="blue.300" h="30px" w="30px" me="6px" transition={variantChange} fontWeight="bold">
|
||||||
{route.icon(false)}
|
{route.icon(false)}
|
||||||
</IconBox>
|
</IconBox>
|
||||||
<Text color={inactiveTextColor} my="auto" fontSize="sm">
|
<Text color={inactiveTextColor} fontSize="md" fontWeight="bold">
|
||||||
{t(route.name)}
|
{t(route.name)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FocusableElement } from '@chakra-ui/utils';
|
import { FocusableElement } from '@chakra-ui/utils';
|
||||||
import { TreeStructure, Buildings, X } from 'phosphor-react';
|
import { TreeStructure, Buildings, X } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@@ -119,8 +119,9 @@ const EntityPopover = ({ isOpen, onClose, children, toggleSidebar }: Props) => {
|
|||||||
const initRef = React.useRef<HTMLButtonElement>();
|
const initRef = React.useRef<HTMLButtonElement>();
|
||||||
|
|
||||||
const goTo = useCallback(
|
const goTo = useCallback(
|
||||||
(id: string, type: string) => {
|
(id, type) => {
|
||||||
navigate(`/${type}/${id}`);
|
navigate(`/${type}/${id}`);
|
||||||
|
onClose();
|
||||||
if (breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md') toggleSidebar();
|
if (breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md') toggleSidebar();
|
||||||
},
|
},
|
||||||
[breakpoint],
|
[breakpoint],
|
||||||
|
|||||||
@@ -1,38 +1,15 @@
|
|||||||
/* eslint-disable import/prefer-default-export */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Flex, Text, useColorModeValue } from '@chakra-ui/react';
|
import { AccordionButton, AccordionItem, Flex, Text, useColorModeValue } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import IconBox from 'components/IconBox';
|
import IconBox from 'components/IconBox';
|
||||||
import { Route } from 'models/Routes';
|
import { SingleRoute } from 'models/Routes';
|
||||||
|
|
||||||
const variantChange = '0.2s linear';
|
const variantChange = '0.2s linear';
|
||||||
|
|
||||||
const commonStyle = {
|
|
||||||
boxSize: 'initial',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
alignItems: 'center',
|
|
||||||
transition: variantChange,
|
|
||||||
bg: 'transparent',
|
|
||||||
ps: '6px',
|
|
||||||
py: '12px',
|
|
||||||
pe: '4px',
|
|
||||||
w: '100%',
|
|
||||||
borderRadius: '15px',
|
|
||||||
_active: {
|
|
||||||
bg: 'inherit',
|
|
||||||
transform: 'none',
|
|
||||||
borderColor: 'transparent',
|
|
||||||
},
|
|
||||||
_focus: {
|
|
||||||
boxShadow: '0px 7px 11px rgba(0, 0, 0, 0.04)',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
route: Route;
|
route: SingleRoute;
|
||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,42 +17,64 @@ export const NavLinkButton = ({ isActive, route, toggleSidebar }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeTextColor = useColorModeValue('gray.700', 'white');
|
const activeTextColor = useColorModeValue('gray.700', 'white');
|
||||||
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
||||||
const inactiveIconColor = useColorModeValue('gray.100', 'gray.600');
|
const activeBg = useColorModeValue('blue.50', 'blue.900');
|
||||||
|
const hoverBg = useColorModeValue('blue.100', 'blue.800');
|
||||||
|
|
||||||
if (route.navButton) {
|
if (route.navButton) {
|
||||||
return route.navButton(isActive, toggleSidebar, route) as JSX.Element;
|
return route.navButton(isActive, toggleSidebar, route) as JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavLink to={route.path.replace(':id', '0')} key={uuid()} style={{ width: '100%' }}>
|
<NavLink to={route.path.replace(':id', '0')} style={{ width: '100%' }}>
|
||||||
{isActive ? (
|
{isActive ? (
|
||||||
<Button {...commonStyle} boxShadow="none">
|
<AccordionItem w="152px" borderTop="0px" borderBottom="0px">
|
||||||
<Flex>
|
<AccordionButton
|
||||||
<IconBox bg="blue.300" color="white" h="38px" w="38px" me="6px" transition={variantChange}>
|
px={1}
|
||||||
{route.icon(true)}
|
h={{
|
||||||
</IconBox>
|
md: '40px',
|
||||||
<Text color={activeTextColor} my="auto" fontSize="md">
|
lg: '50px',
|
||||||
{t(route.name)}
|
}}
|
||||||
</Text>
|
borderRadius="15px"
|
||||||
</Flex>
|
w="100%"
|
||||||
</Button>
|
bg={activeBg}
|
||||||
|
_hover={{
|
||||||
|
bg: hoverBg,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex alignItems="center" w="100%">
|
||||||
|
<IconBox color="blue.300" h="30px" w="30px" me="6px" transition={variantChange} fontWeight="bold">
|
||||||
|
{route.icon(false)}
|
||||||
|
</IconBox>
|
||||||
|
<Text color={activeTextColor} fontSize="md" fontWeight="bold">
|
||||||
|
{t(route.name)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</AccordionButton>
|
||||||
|
</AccordionItem>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<AccordionItem w="152px" borderTop="0px" borderBottom="0px">
|
||||||
{...commonStyle}
|
<AccordionButton
|
||||||
ps="6px"
|
px={1}
|
||||||
_focus={{
|
h={{
|
||||||
boxShadow: 'none',
|
md: '40px',
|
||||||
}}
|
lg: '50px',
|
||||||
>
|
}}
|
||||||
<Flex>
|
borderRadius="15px"
|
||||||
<IconBox bg={inactiveIconColor} color="blue.300" h="34px" w="34px" me="6px" transition={variantChange}>
|
w="100%"
|
||||||
{route.icon(false)}
|
_hover={{
|
||||||
</IconBox>
|
bg: hoverBg,
|
||||||
<Text color={inactiveTextColor} my="auto" fontSize="sm">
|
}}
|
||||||
{t(route.name)}
|
>
|
||||||
</Text>
|
<Flex alignItems="center" w="100%">
|
||||||
</Flex>
|
<IconBox color="blue.300" h="30px" w="30px" me="6px" transition={variantChange} fontWeight="bold">
|
||||||
</Button>
|
{route.icon(false)}
|
||||||
|
</IconBox>
|
||||||
|
<Text color={inactiveTextColor} fontSize="md" fontWeight="bold">
|
||||||
|
{t(route.name)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</AccordionButton>
|
||||||
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|||||||
39
src/layout/Sidebar/NestedNavButton/SubNavigationButton.tsx
Normal file
39
src/layout/Sidebar/NestedNavButton/SubNavigationButton.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Button, useColorModeValue } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { SubRoute } from 'models/Routes';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isActive: (path: string) => boolean;
|
||||||
|
route: SubRoute;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubNavigationButton = ({ isActive, route }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const activeTextColor = useColorModeValue('gray.700', 'white');
|
||||||
|
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
||||||
|
const activeBg = useColorModeValue('blue.50', 'blue.900');
|
||||||
|
const hoverBg = useColorModeValue('blue.100', 'blue.800');
|
||||||
|
|
||||||
|
const isCurrentlyActive = isActive(route.path);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink to={route.path.replace(':id', '0')} style={{ width: '100%' }}>
|
||||||
|
<Button
|
||||||
|
w="100%"
|
||||||
|
justifyContent="left"
|
||||||
|
color={isCurrentlyActive ? activeTextColor : inactiveTextColor}
|
||||||
|
bg={isCurrentlyActive ? activeBg : 'transparent'}
|
||||||
|
_hover={{
|
||||||
|
bg: hoverBg,
|
||||||
|
}}
|
||||||
|
border="none"
|
||||||
|
>
|
||||||
|
{t(route.name)}
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubNavigationButton;
|
||||||
64
src/layout/Sidebar/NestedNavButton/index.tsx
Normal file
64
src/layout/Sidebar/NestedNavButton/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
AccordionButton,
|
||||||
|
AccordionIcon,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionPanel,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
useColorModeValue,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import SubNavigationButton from './SubNavigationButton';
|
||||||
|
import IconBox from 'components/IconBox';
|
||||||
|
import { RouteGroup } from 'models/Routes';
|
||||||
|
|
||||||
|
const variantChange = '0.2s linear';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isActive: (path: string) => boolean;
|
||||||
|
route: RouteGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NestedNavButton = ({ isActive, route }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const inactiveTextColor = useColorModeValue('gray.600', 'gray.200');
|
||||||
|
const hoverBg = useColorModeValue('blue.100', 'blue.800');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionItem w="152px" borderTop="0px" borderBottom="0px">
|
||||||
|
<AccordionButton
|
||||||
|
px={1}
|
||||||
|
h={{
|
||||||
|
md: '40px',
|
||||||
|
lg: '50px',
|
||||||
|
}}
|
||||||
|
_hover={{
|
||||||
|
bg: hoverBg,
|
||||||
|
}}
|
||||||
|
borderRadius="15px"
|
||||||
|
w="100%"
|
||||||
|
>
|
||||||
|
<Flex alignItems="center" w="100%">
|
||||||
|
<IconBox color="blue.300" h="30px" w="30px" me="6px" transition={variantChange} fontWeight="bold">
|
||||||
|
{route.icon(false)}
|
||||||
|
</IconBox>
|
||||||
|
<Text size="md" fontWeight="bold" color={inactiveTextColor}>
|
||||||
|
{typeof route.name === 'string' ? t(route.name) : route.name(t)}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel pl="18px" paddingEnd={0} pr="-18px">
|
||||||
|
<Box pl={1} pr={-1} borderLeft="1px solid #63b3ed">
|
||||||
|
{route.children.map((subRoute) => (
|
||||||
|
<SubNavigationButton key={subRoute.path} route={subRoute} isActive={isActive} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NestedNavButton;
|
||||||
@@ -12,11 +12,12 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
useBreakpoint,
|
useBreakpoint,
|
||||||
VStack,
|
VStack,
|
||||||
|
Accordion,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { NavLinkButton } from './NavLinkButton';
|
import { NavLinkButton } from './NavLinkButton';
|
||||||
|
import NestedNavButton from './NestedNavButton';
|
||||||
import { useAuth } from 'contexts/AuthProvider';
|
import { useAuth } from 'contexts/AuthProvider';
|
||||||
import { Route } from 'models/Routes';
|
import { Route } from 'models/Routes';
|
||||||
|
|
||||||
@@ -60,14 +61,25 @@ export const Sidebar = ({ routes, isOpen, toggle, logo, version, topNav, childre
|
|||||||
const sidebarContent = React.useMemo(
|
const sidebarContent = React.useMemo(
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
<VStack spacing={2} alignItems="start" w="100%" px={4}>
|
<Accordion allowToggle>
|
||||||
{topNav ? topNav(isRouteActive, toggle) : null}
|
<VStack spacing={2} alignItems="start" w="100%" px={4}>
|
||||||
{routes
|
{topNav ? topNav(isRouteActive, toggle) : null}
|
||||||
.filter(({ hidden, authorized }) => !hidden && authorized.includes(user?.userRole ?? ''))
|
{routes
|
||||||
.map((route) => (
|
.filter(({ hidden, authorized }) => !hidden && authorized.includes(user?.userRole ?? ''))
|
||||||
<NavLinkButton key={uuid()} isActive={isRouteActive(route.path)} route={route} toggleSidebar={toggle} />
|
.map((route) =>
|
||||||
))}
|
route.children ? (
|
||||||
</VStack>
|
<NestedNavButton key={route.id} isActive={isRouteActive} route={route} />
|
||||||
|
) : (
|
||||||
|
<NavLinkButton
|
||||||
|
key={route.id}
|
||||||
|
isActive={isRouteActive(route.path)}
|
||||||
|
route={route}
|
||||||
|
toggleSidebar={toggle}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</Accordion>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Box mb={2}>{children}</Box>
|
<Box mb={2}>{children}</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@@ -117,7 +129,8 @@ export const Sidebar = ({ routes, isOpen, toggle, logo, version, topNav, childre
|
|||||||
h="calc(100vh - 32px)"
|
h="calc(100vh - 32px)"
|
||||||
my="16px"
|
my="16px"
|
||||||
ml="16px"
|
ml="16px"
|
||||||
borderRadius="16px"
|
borderRadius="15px"
|
||||||
|
border="0.5px solid"
|
||||||
>
|
>
|
||||||
{brand}
|
{brand}
|
||||||
<Flex direction="column" h="calc(100vh - 160px)" alignItems="center" overflowY="auto">
|
<Flex direction="column" h="calc(100vh - 160px)" alignItems="center" overflowY="auto">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useBoolean, useBreakpoint, useColorMode } from '@chakra-ui/react';
|
import { useBoolean, useColorMode } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@@ -9,31 +9,69 @@ import { Sidebar } from './Sidebar';
|
|||||||
import darkLogo from 'assets/Logo_Dark_Mode.svg';
|
import darkLogo from 'assets/Logo_Dark_Mode.svg';
|
||||||
import lightLogo from 'assets/Logo_Light_Mode.svg';
|
import lightLogo from 'assets/Logo_Light_Mode.svg';
|
||||||
import LanguageSwitcher from 'components/LanguageSwitcher';
|
import LanguageSwitcher from 'components/LanguageSwitcher';
|
||||||
import { Route as RouteType } from 'models/Routes';
|
import { RouteName } from 'models/Routes';
|
||||||
import NotFoundPage from 'pages/NotFound';
|
import NotFoundPage from 'pages/NotFound';
|
||||||
import routes from 'router/routes';
|
import routes from 'router/routes';
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { colorMode } = useColorMode();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const breakpoint = useBreakpoint('xl');
|
const { colorMode } = useColorMode();
|
||||||
const [isSidebarOpen, { toggle: toggleSidebar }] = useBoolean(breakpoint !== 'base' && breakpoint !== 'sm');
|
const [isSidebarOpen, { toggle: toggleSidebar }] = useBoolean(false);
|
||||||
document.documentElement.dir = 'ltr';
|
document.documentElement.dir = 'ltr';
|
||||||
|
|
||||||
const activeRoute = React.useMemo(() => {
|
const activeRoute = React.useMemo(() => {
|
||||||
const route = routes.find(
|
let name: RouteName = '';
|
||||||
(r) => r.path === location.pathname || location.pathname.split('/')[1] === r.path.split('/')[1],
|
for (const route of routes) {
|
||||||
);
|
if (!route.children && route.path === location.pathname) {
|
||||||
|
name = route.navName ?? route.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (route.path?.includes('/:')) {
|
||||||
|
const routePath = route.path.split('/:')[0];
|
||||||
|
const currPath = location.pathname.split('/');
|
||||||
|
if (routePath && location.pathname.startsWith(routePath) && currPath.length === 3) {
|
||||||
|
name = route.navName ?? route.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (route.children) {
|
||||||
|
for (const child of route.children) {
|
||||||
|
if (child.path === location.pathname) {
|
||||||
|
name = child.navName ?? child.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (route) return route.navName ? t(route.navName) : t(route.name);
|
if (typeof name === 'function') return name(t);
|
||||||
|
|
||||||
return '';
|
if (name.includes('PATH')) {
|
||||||
|
name = location.pathname.split('/')[location.pathname.split('/').length - 1] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.includes('RAW-')) name.replace('RAW-', '');
|
||||||
|
|
||||||
|
return t(name);
|
||||||
}, [t, location.pathname]);
|
}, [t, location.pathname]);
|
||||||
|
|
||||||
const getRoutes = (r: RouteType[]) =>
|
const routeInstances = React.useMemo(() => {
|
||||||
// @ts-ignore
|
const instances = [];
|
||||||
r.map((route: RouteType) => <Route path={route.path} element={<route.component />} key={uuid()} />);
|
|
||||||
|
for (const route of routes) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (!route.children) instances.push(<Route path={route.path} element={<route.component />} key={route.id} />);
|
||||||
|
else {
|
||||||
|
for (const child of route.children) {
|
||||||
|
// @ts-ignore
|
||||||
|
instances.push(<Route path={child.path} element={<child.component />} key={child.id} />);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -57,9 +95,7 @@ const Layout = () => {
|
|||||||
/>
|
/>
|
||||||
<Navbar toggleSidebar={toggleSidebar} languageSwitcher={<LanguageSwitcher />} activeRoute={activeRoute} />
|
<Navbar toggleSidebar={toggleSidebar} languageSwitcher={<LanguageSwitcher />} activeRoute={activeRoute} />
|
||||||
<PageContainer waitForUser>
|
<PageContainer waitForUser>
|
||||||
<Routes>
|
<Routes>{[...routeInstances, <Route path="*" element={<NotFoundPage />} key={uuid()} />]}</Routes>
|
||||||
{[...getRoutes(routes as RouteType[]), <Route path="*" element={<NotFoundPage />} key={uuid()} />]}
|
|
||||||
</Routes>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
48
src/models/Inventory.ts
Normal file
48
src/models/Inventory.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Note } from './Note';
|
||||||
|
|
||||||
|
export type InventoryTagApiResponse = {
|
||||||
|
contact: string;
|
||||||
|
created: number;
|
||||||
|
description: string;
|
||||||
|
devClass: string;
|
||||||
|
deviceConfiguration: string;
|
||||||
|
deviceRules: {
|
||||||
|
firmwareUpgrade: 'inherit' | 'on' | 'off';
|
||||||
|
rcOnly: 'inherit' | 'on' | 'off';
|
||||||
|
rrm: 'inherit' | 'on' | 'off';
|
||||||
|
};
|
||||||
|
deviceType: string;
|
||||||
|
entity: string;
|
||||||
|
extendedInfo?: {
|
||||||
|
venue?: {
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
entity?: {
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
deviceConfiguration?: {
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
geoCode: string;
|
||||||
|
id: string;
|
||||||
|
locale: string;
|
||||||
|
location: string;
|
||||||
|
managementPolicy: string;
|
||||||
|
modified: number;
|
||||||
|
name: string;
|
||||||
|
notes: Note[];
|
||||||
|
qrCode: string;
|
||||||
|
realMacAddress: string;
|
||||||
|
serialNumber: string;
|
||||||
|
state: string;
|
||||||
|
subscriber: string;
|
||||||
|
tags: string[];
|
||||||
|
venue: string;
|
||||||
|
};
|
||||||
@@ -1,14 +1,53 @@
|
|||||||
import { ReactNode } from 'react';
|
import React, { LazyExoticComponent } from 'react';
|
||||||
|
|
||||||
export type Route = {
|
export type RouteName = string | ((t: (s: string) => string) => string);
|
||||||
|
|
||||||
|
export type SubRoute = {
|
||||||
|
id: string;
|
||||||
authorized: string[];
|
authorized: string[];
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
name: RouteName;
|
||||||
navName?: string;
|
component: React.ReactElement | LazyExoticComponent<React.ComponentType<unknown>>;
|
||||||
icon: (active: boolean) => ReactNode;
|
navName?: RouteName;
|
||||||
navButton?: (isActive: boolean, toggleSidebar: () => void, route: Route) => React.ReactNode;
|
hidden?: boolean;
|
||||||
|
icon?: undefined;
|
||||||
|
navButton?: undefined;
|
||||||
|
isEntity?: undefined;
|
||||||
|
isCustom?: undefined;
|
||||||
|
children?: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RouteGroup = {
|
||||||
|
id: string;
|
||||||
|
authorized: string[];
|
||||||
|
name: RouteName;
|
||||||
|
icon: (active: boolean) => React.ReactElement;
|
||||||
|
children: SubRoute[];
|
||||||
|
hidden?: boolean;
|
||||||
|
path?: undefined;
|
||||||
|
navName?: undefined;
|
||||||
|
navButton?: undefined;
|
||||||
|
isEntity?: undefined;
|
||||||
|
isCustom?: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SingleRoute = {
|
||||||
|
id: string;
|
||||||
|
authorized: string[];
|
||||||
|
path: string;
|
||||||
|
name: RouteName;
|
||||||
|
navName?: RouteName;
|
||||||
|
icon: (active: boolean) => React.ReactElement;
|
||||||
|
navButton?: (
|
||||||
|
isActive: boolean,
|
||||||
|
toggleSidebar: () => void,
|
||||||
|
route: Route,
|
||||||
|
) => React.ReactElement | LazyExoticComponent<React.ComponentType<unknown>>;
|
||||||
isEntity?: boolean;
|
isEntity?: boolean;
|
||||||
component: unknown;
|
component: React.ReactElement | LazyExoticComponent<React.ComponentType<unknown>>;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
isCustom?: boolean;
|
isCustom?: boolean;
|
||||||
|
children?: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Route = SingleRoute | RouteGroup;
|
||||||
|
|||||||
@@ -1,13 +1,42 @@
|
|||||||
import { Note } from './Note';
|
import { Note } from './Note';
|
||||||
|
|
||||||
export interface User {
|
export type UserRole =
|
||||||
name: string;
|
| 'root'
|
||||||
|
| 'admin'
|
||||||
|
| 'subscriber'
|
||||||
|
| 'partner'
|
||||||
|
| 'csr'
|
||||||
|
| 'system'
|
||||||
|
| 'installer'
|
||||||
|
| 'noc'
|
||||||
|
| 'accounting';
|
||||||
|
|
||||||
|
export type User = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
blackListed: boolean;
|
||||||
|
creationDate: number;
|
||||||
|
currentLoginURI: string;
|
||||||
|
currentPassword: string;
|
||||||
description: string;
|
description: string;
|
||||||
currentPassword?: string;
|
|
||||||
id: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
userRole: string;
|
id: string;
|
||||||
|
lastEmailCheck: number;
|
||||||
|
lastLogin: number;
|
||||||
|
lastPasswordChange: number;
|
||||||
|
lastPasswords: string[];
|
||||||
|
locale: string;
|
||||||
|
location: string;
|
||||||
|
modified: number;
|
||||||
|
name: string;
|
||||||
|
notes: Note[];
|
||||||
|
oauthType: string;
|
||||||
|
oauthUserInfo: string;
|
||||||
|
owner: string;
|
||||||
|
securityPolicy: string;
|
||||||
|
securityPolicyChange: number;
|
||||||
|
signingUp: string;
|
||||||
|
suspended: boolean;
|
||||||
|
userRole: UserRole;
|
||||||
userTypeProprietaryInfo: {
|
userTypeProprietaryInfo: {
|
||||||
authenticatorSecret: string;
|
authenticatorSecret: string;
|
||||||
mfa: {
|
mfa: {
|
||||||
@@ -16,6 +45,9 @@ export interface User {
|
|||||||
};
|
};
|
||||||
mobiles: { number: string }[];
|
mobiles: { number: string }[];
|
||||||
};
|
};
|
||||||
suspended: boolean;
|
validated: boolean;
|
||||||
notes: Note[];
|
validationDate: number;
|
||||||
}
|
validationEmail: string;
|
||||||
|
validationURI: string;
|
||||||
|
waitingForEmailCheck: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
useBoolean,
|
useBoolean,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Flask } from 'phosphor-react';
|
import { Flask } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SaveButton from 'components/Buttons/SaveButton';
|
import SaveButton from 'components/Buttons/SaveButton';
|
||||||
import { Modal } from 'components/Modals/Modal';
|
import { Modal } from 'components/Modals/Modal';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
useBoolean,
|
useBoolean,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { WarningCircle } from 'phosphor-react';
|
import { WarningCircle } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
||||||
import { UploadSimple } from 'phosphor-react';
|
import { UploadSimple } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ImportConfigurationModal from './Modal';
|
import ImportConfigurationModal from './Modal';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay, Box } from '@chakra-ui/react';
|
import { useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay, Box } from '@chakra-ui/react';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { Plus } from 'phosphor-react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CREATE_INTERFACE_SCHEMA, SINGLE_INTERFACE_SCHEMA } from './interfacesConstants';
|
import { CREATE_INTERFACE_SCHEMA, SINGLE_INTERFACE_SCHEMA } from './interfacesConstants';
|
||||||
import CloseButton from 'components/Buttons/CloseButton';
|
import CloseButton from 'components/Buttons/CloseButton';
|
||||||
|
import CreateButton from 'components/Buttons/CreateButton';
|
||||||
import SaveButton from 'components/Buttons/SaveButton';
|
import SaveButton from 'components/Buttons/SaveButton';
|
||||||
import SelectField from 'components/FormFields/SelectField';
|
import SelectField from 'components/FormFields/SelectField';
|
||||||
import StringField from 'components/FormFields/StringField';
|
import StringField from 'components/FormFields/StringField';
|
||||||
@@ -35,17 +35,14 @@ const CreateInterfaceButton = ({ editing, arrayHelpers: { push: pushInterface },
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<CreateButton
|
||||||
colorScheme="blue"
|
label={t('configurations.add_interface')}
|
||||||
type="button"
|
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
rightIcon={<Plus size={20} />}
|
isCompact={arrLength !== 0}
|
||||||
hidden={!editing}
|
hidden={!editing}
|
||||||
|
size="lg"
|
||||||
borderRadius={0}
|
borderRadius={0}
|
||||||
minWidth={24}
|
/>
|
||||||
>
|
|
||||||
{t('configurations.add_interface')}
|
|
||||||
</Button>
|
|
||||||
<Modal onClose={onClose} isOpen={isOpen} size="sm" scrollBehavior="inside">
|
<Modal onClose={onClose} isOpen={isOpen} size="sm" scrollBehavior="inside">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Button, Heading, useColorModeValue, useMultiStyleConfig, useTab } from '@chakra-ui/react';
|
import { Tab, useColorMode, useMultiStyleConfig, useTab } from '@chakra-ui/react';
|
||||||
import useFastField from 'hooks/useFastField';
|
import useFastField from 'hooks/useFastField';
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const InterfaceTab = React.forwardRef((
|
const InterfaceTab: React.FC<{ index: number }> = React.forwardRef(({ index, ...props }, ref) => {
|
||||||
{
|
|
||||||
index,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
index: number
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const { value } = useFastField({ name: `configuration[${index}]` });
|
const { value } = useFastField({ name: `configuration[${index}]` });
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
const bgColorSelected = useColorModeValue('gray.100', 'gray.800');
|
const isLight = colorMode === 'light';
|
||||||
const bgColorUnSelected = useColorModeValue('white', 'gray.700');
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const tabProps = useTab({ ...props, ref });
|
const tabProps = useTab({ ...props, ref });
|
||||||
const isSelected = !!tabProps['aria-selected'];
|
|
||||||
|
|
||||||
// 2. Hook into the Tabs `size`, `variant`, props
|
|
||||||
const styles = useMultiStyleConfig('Tabs', tabProps);
|
const styles = useMultiStyleConfig('Tabs', tabProps);
|
||||||
|
|
||||||
const name = useMemo(() => {
|
const name = useMemo(() => {
|
||||||
@@ -32,16 +20,15 @@ const InterfaceTab = React.forwardRef((
|
|||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Tab
|
||||||
__css={styles.tab}
|
_selected={{
|
||||||
{...tabProps}
|
// @ts-ignore
|
||||||
bgColor={isSelected ? bgColorSelected : bgColorUnSelected}
|
...styles.tab?._selected,
|
||||||
_focus={{ outline: 'none !important' }}
|
borderBottomColor: isLight ? 'gray.100' : 'gray.800',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Heading size="md">
|
{name} ({value?.role})
|
||||||
{name} ({value?.role})
|
</Tab>
|
||||||
</Heading>
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Center, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
import { Box, Center, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CreateInterfaceButton from './CreateInterfaceButton';
|
import CreateInterfaceButton from './CreateInterfaceButton';
|
||||||
import InterfaceTab from './InterfaceTab';
|
import InterfaceTab from './InterfaceTab';
|
||||||
@@ -42,48 +42,38 @@ const Interfaces = ({ editing, arrayHelpers, interfacesLength }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Card variant="widget">
|
||||||
index={tabIndex}
|
<CardBody display="block">
|
||||||
onChange={handleTabsChange}
|
<Box display="unset" position="unset" w="100%">
|
||||||
isLazy
|
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" isLazy w="100%">
|
||||||
variant="enclosed-colored"
|
<Box overflowX="auto" overflowY="auto" pt={1} h="56px">
|
||||||
w="100%"
|
<TabList mt={0}>
|
||||||
px={0}
|
{Array(interfacesLength)
|
||||||
colorScheme="blue"
|
.fill(1)
|
||||||
>
|
.map((el, i) => (
|
||||||
<TabList
|
<InterfaceTab key={i} index={i} />
|
||||||
w="100%"
|
))}
|
||||||
overflowX="auto"
|
<CreateInterfaceButton
|
||||||
style={{
|
editing={editing}
|
||||||
overflowY: 'hidden',
|
arrayHelpers={arrayHelpers}
|
||||||
}}
|
setTabIndex={setTabIndex}
|
||||||
>
|
arrLength={interfacesLength}
|
||||||
{Array(interfacesLength)
|
/>
|
||||||
.fill(1)
|
</TabList>
|
||||||
.map((el, i) => (
|
</Box>
|
||||||
<InterfaceTab key={i} index={i} />
|
<TabPanels w="100%">
|
||||||
))}
|
{Array(interfacesLength)
|
||||||
<CreateInterfaceButton
|
.fill(1)
|
||||||
editing={editing}
|
.map((el, i) => (
|
||||||
arrayHelpers={arrayHelpers}
|
<TabPanel key={i}>
|
||||||
setTabIndex={setTabIndex}
|
<SingleInterface index={i} remove={handleRemove} editing={editing} />
|
||||||
arrLength={interfacesLength}
|
</TabPanel>
|
||||||
/>
|
))}
|
||||||
</TabList>
|
</TabPanels>
|
||||||
<Card variant="widget" mb={4} borderRadius={0}>
|
</Tabs>
|
||||||
<CardBody display="unset">
|
</Box>
|
||||||
<TabPanels>
|
</CardBody>
|
||||||
{Array(interfacesLength)
|
</Card>
|
||||||
.fill(1)
|
|
||||||
.map((el, i) => (
|
|
||||||
<TabPanel overflowX="auto" px={0} key={i}>
|
|
||||||
<SingleInterface index={i} remove={handleRemove} editing={editing} />
|
|
||||||
</TabPanel>
|
|
||||||
))}
|
|
||||||
</TabPanels>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</Tabs>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Center } from '@chakra-ui/react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { INTERFACE_SSID_SCHEMA } from '../../interfacesConstants';
|
import { INTERFACE_SSID_SCHEMA } from '../../interfacesConstants';
|
||||||
@@ -22,9 +21,14 @@ const CreateSsidButton = ({ editing, pushSsid, setTabIndex, arrLength }) => {
|
|||||||
if (!editing) return null;
|
if (!editing) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center>
|
<CreateButton
|
||||||
<CreateButton label={t('configurations.add_ssid')} onClick={createSsid} borderRadius={0} />
|
label={t('configurations.add_ssid')}
|
||||||
</Center>
|
onClick={createSsid}
|
||||||
|
borderRadius={0}
|
||||||
|
mt="auto"
|
||||||
|
isCompact={arrLength !== 0}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Heading, SimpleGrid, Spacer } from '@chakra-ui/react';
|
import { Box, Flex, Heading, SimpleGrid, Spacer } from '@chakra-ui/react';
|
||||||
import { getIn, useFormikContext } from 'formik';
|
import { getIn, useFormikContext } from 'formik';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,9 +9,7 @@ import Encryption from './Encryption';
|
|||||||
import LockedSsid from './LockedSsid';
|
import LockedSsid from './LockedSsid';
|
||||||
import PassPoint from './PassPoint';
|
import PassPoint from './PassPoint';
|
||||||
import DeleteButton from 'components/Buttons/DeleteButton';
|
import DeleteButton from 'components/Buttons/DeleteButton';
|
||||||
import Card from 'components/Card';
|
|
||||||
import CardBody from 'components/Card/CardBody';
|
import CardBody from 'components/Card/CardBody';
|
||||||
import CardHeader from 'components/Card/CardHeader';
|
|
||||||
import ConfigurationResourcePicker from 'components/CustomFields/ConfigurationResourcePicker';
|
import ConfigurationResourcePicker from 'components/CustomFields/ConfigurationResourcePicker';
|
||||||
import MultiSelectField from 'components/FormFields/MultiSelectField';
|
import MultiSelectField from 'components/FormFields/MultiSelectField';
|
||||||
import SelectField from 'components/FormFields/SelectField';
|
import SelectField from 'components/FormFields/SelectField';
|
||||||
@@ -39,8 +37,8 @@ const SingleSsid = ({ editing, index, namePrefix, remove }) => {
|
|||||||
}, [getIn(values, `${namePrefix}`)]);
|
}, [getIn(values, `${namePrefix}`)]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card mb={4} borderRadius={0}>
|
<>
|
||||||
<CardHeader flex="auto">
|
<Flex px={4} py={2}>
|
||||||
<Heading size="md" mr={2} pt={2}>
|
<Heading size="md" mr={2} pt={2}>
|
||||||
#{index}
|
#{index}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -52,7 +50,7 @@ const SingleSsid = ({ editing, index, namePrefix, remove }) => {
|
|||||||
/>
|
/>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<DeleteButton isDisabled={!editing} onClick={removeSsid} label={t('configurations.delete_ssid')} />
|
<DeleteButton isDisabled={!editing} onClick={removeSsid} label={t('configurations.delete_ssid')} />
|
||||||
</CardHeader>
|
</Flex>
|
||||||
<CardBody display="unset">
|
<CardBody display="unset">
|
||||||
{isUsingCustomRadius ? (
|
{isUsingCustomRadius ? (
|
||||||
<>
|
<>
|
||||||
@@ -113,7 +111,7 @@ const SingleSsid = ({ editing, index, namePrefix, remove }) => {
|
|||||||
<LockedSsid variableBlockId={getIn(values, `${namePrefix}.__variableBlock`)[0]} />
|
<LockedSsid variableBlockId={getIn(values, `${namePrefix}.__variableBlock`)[0]} />
|
||||||
)}
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +1,52 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Button, Heading, useColorModeValue, useMultiStyleConfig, useTab } from '@chakra-ui/react';
|
import { Tab } from '@chakra-ui/react';
|
||||||
import { useGetResource } from 'hooks/Network/Resources';
|
import { useGetResource } from 'hooks/Network/Resources';
|
||||||
import useFastField from 'hooks/useFastField';
|
import useFastField from 'hooks/useFastField';
|
||||||
|
|
||||||
const SsidTab = React.forwardRef(// eslint-disable-next-line react/prop-types
|
const SsidTab: React.FC<{ index: number; interIndex: number }> = React.forwardRef(
|
||||||
(
|
// eslint-disable-next-line react/prop-types
|
||||||
{
|
({ index, interIndex }) => {
|
||||||
index,
|
const { value } = useFastField({ name: `configuration[${interIndex}].ssids[${index}]` });
|
||||||
interIndex,
|
const { value: wifiBands } = useFastField({ name: `configuration[${interIndex}].ssids[${index}].wifi-bands` });
|
||||||
...props
|
const { data: resource } = useGetResource({
|
||||||
}: {
|
id: value?.__variableBlock,
|
||||||
index: number
|
enabled: value?.__variableBlock !== undefined,
|
||||||
interIndex: number
|
});
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const { value } = useFastField({ name: `configuration[${interIndex}].ssids[${index}]` });
|
|
||||||
const { value: wifiBands } = useFastField({ name: `configuration[${interIndex}].ssids[${index}].wifi-bands` });
|
|
||||||
const { data: resource } = useGetResource({
|
|
||||||
id: value?.__variableBlock,
|
|
||||||
enabled: value?.__variableBlock !== undefined,
|
|
||||||
});
|
|
||||||
const bgColorSelected = useColorModeValue('white', 'gray.700');
|
|
||||||
const bgColorUnSelected = useColorModeValue('gray.100', 'gray.800');
|
|
||||||
|
|
||||||
// @ts-ignore
|
const name = useMemo(() => {
|
||||||
const tabProps = useTab({ ...props, ref });
|
if (value?.name) {
|
||||||
const isSelected = !!tabProps['aria-selected'];
|
return value.name.length <= 12 ? value.name : `${value.name.substring(0, 9)}...`;
|
||||||
|
|
||||||
const styles = useMultiStyleConfig('Tabs', tabProps);
|
|
||||||
|
|
||||||
const name = useMemo(() => {
|
|
||||||
if (value?.name) {
|
|
||||||
return value.name.length <= 15 ? value.name : `${value.name.substring(0, 12)}...`;
|
|
||||||
}
|
|
||||||
if (resource?.variables && resource?.variables[0]?.value) {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(resource?.variables[0]?.value);
|
|
||||||
return json.name.length <= 15 ? json.name : `${json.name.substring(0, 12)}...`;
|
|
||||||
} catch (e) {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
if (resource?.variables && resource?.variables[0]?.value) {
|
||||||
|
try {
|
||||||
return '';
|
const json = JSON.parse(resource?.variables[0]?.value);
|
||||||
}, [value, resource]);
|
return json.name.length <= 12 ? json.name : `${json.name.substring(0, 9)}...`;
|
||||||
|
} catch (e) {
|
||||||
const bands = useMemo(() => {
|
return '';
|
||||||
if (wifiBands) return wifiBands;
|
}
|
||||||
if (resource?.variables && resource?.variables[0]?.value) {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(resource?.variables[0]?.value);
|
|
||||||
return json['wifi-bands'];
|
|
||||||
} catch (e) {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return undefined;
|
return '';
|
||||||
}, [wifiBands, resource]);
|
}, [value, resource]);
|
||||||
return (
|
|
||||||
// @ts-ignore
|
const bands = useMemo(() => {
|
||||||
(<Button
|
if (wifiBands) return wifiBands;
|
||||||
__css={styles.tab}
|
if (resource?.variables && resource?.variables[0]?.value) {
|
||||||
{...tabProps}
|
try {
|
||||||
bgColor={isSelected ? bgColorSelected : bgColorUnSelected}
|
const json = JSON.parse(resource?.variables[0]?.value);
|
||||||
_focus={{ outline: 'none !important' }}
|
return json['wifi-bands'];
|
||||||
>
|
} catch (e) {
|
||||||
<Heading size="md">
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [wifiBands, resource]);
|
||||||
|
return (
|
||||||
|
<Tab>
|
||||||
{name} ({bands?.join(', ')})
|
{name} ({bands?.join(', ')})
|
||||||
</Heading>
|
</Tab>
|
||||||
</Button>)
|
);
|
||||||
);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
export default React.memo(SsidTab);
|
export default React.memo(SsidTab);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Center, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
import { Box, Center, Heading, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CreateSsidButton from './CreateSsidButton';
|
import CreateSsidButton from './CreateSsidButton';
|
||||||
import SingleSsid from './SingleSsid';
|
import SingleSsid from './SingleSsid';
|
||||||
import SsidTab from './SsidTab';
|
import SsidTab from './SsidTab';
|
||||||
|
import Card from 'components/Card';
|
||||||
|
import CardBody from 'components/Card/CardBody';
|
||||||
|
import CardHeader from 'components/Card/CardHeader';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
editing: PropTypes.bool.isRequired,
|
editing: PropTypes.bool.isRequired,
|
||||||
@@ -31,46 +34,52 @@ const SsidList = ({ editing, index, arrayHelpers, ssidsLength }) => {
|
|||||||
if (ssidsLength === 0) {
|
if (ssidsLength === 0) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
<CreateSsidButton editing={editing} pushSsid={arrayHelpers.push} />
|
<CreateSsidButton editing={editing} pushSsid={arrayHelpers.push} arrLength={0} />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tabs index={tabIndex} onChange={handleTabsChange} isLazy variant="enclosed-colored" w="100%" colorScheme="blue">
|
<Card>
|
||||||
<TabList
|
<CardHeader mb={0}>
|
||||||
w="100%"
|
<Heading size="md">SSIDs</Heading>
|
||||||
overflowX="auto"
|
</CardHeader>
|
||||||
style={{
|
<CardBody display="block">
|
||||||
overflowY: 'hidden',
|
<Box display="unset" position="unset" w="100%">
|
||||||
}}
|
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" isLazy w="100%">
|
||||||
>
|
<Box overflowX="auto" overflowY="auto" pt={1} h="56px">
|
||||||
{Array(ssidsLength)
|
<TabList mt={0}>
|
||||||
.fill(1)
|
{Array(ssidsLength)
|
||||||
.map((el, i) => (
|
.fill(1)
|
||||||
<SsidTab key={i} index={i} interIndex={index} />
|
.map((el, i) => (
|
||||||
))}
|
<SsidTab key={i} index={i} interIndex={index} />
|
||||||
<CreateSsidButton
|
))}
|
||||||
editing={editing}
|
<CreateSsidButton
|
||||||
pushSsid={arrayHelpers.push}
|
editing={editing}
|
||||||
setTabIndex={setTabIndex}
|
pushSsid={arrayHelpers.push}
|
||||||
arrLength={ssidsLength}
|
setTabIndex={setTabIndex}
|
||||||
/>
|
arrLength={ssidsLength}
|
||||||
</TabList>
|
/>
|
||||||
<TabPanels>
|
</TabList>
|
||||||
{Array(ssidsLength)
|
</Box>
|
||||||
.fill(1)
|
|
||||||
.map((el, i) => (
|
<TabPanels w="100%">
|
||||||
<TabPanel overflowX="auto" p={0} key={i}>
|
{Array(ssidsLength)
|
||||||
<SingleSsid
|
.fill(1)
|
||||||
index={i}
|
.map((el, i) => (
|
||||||
namePrefix={`configuration[${index}].ssids[${i}]`}
|
<TabPanel key={i}>
|
||||||
remove={handleRemove}
|
<SingleSsid
|
||||||
editing={editing}
|
index={i}
|
||||||
/>
|
namePrefix={`configuration[${index}].ssids[${i}]`}
|
||||||
</TabPanel>
|
remove={handleRemove}
|
||||||
))}
|
editing={editing}
|
||||||
</TabPanels>
|
/>
|
||||||
</Tabs>
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Flex, Heading, SimpleGrid, Spacer } from '@chakra-ui/react';
|
import { Box, Flex, Heading, SimpleGrid, Spacer } from '@chakra-ui/react';
|
||||||
import { FieldArray } from 'formik';
|
import { FieldArray } from 'formik';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Captive from './Captive';
|
import Captive from './Captive';
|
||||||
@@ -36,7 +36,7 @@ const SingleInterface: React.FC<Props> = ({ editing, index, remove }) => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<Box w="100%">
|
||||||
<Flex>
|
<Flex>
|
||||||
<div>
|
<div>
|
||||||
<Heading size="md" borderBottom="1px solid">
|
<Heading size="md" borderBottom="1px solid">
|
||||||
@@ -155,7 +155,7 @@ const SingleInterface: React.FC<Props> = ({ editing, index, remove }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FieldArray>
|
</FieldArray>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { Plus } from '@phosphor-icons/react';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Plus } from 'phosphor-react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SINGLE_RADIO_SCHEMA } from './radiosConstants';
|
import { SINGLE_RADIO_SCHEMA } from './radiosConstants';
|
||||||
import CloseButton from 'components/Buttons/CloseButton';
|
import CloseButton from 'components/Buttons/CloseButton';
|
||||||
|
import CreateButton from 'components/Buttons/CreateButton';
|
||||||
import ModalHeader from 'components/Modals/ModalHeader';
|
import ModalHeader from 'components/Modals/ModalHeader';
|
||||||
import { useGetAllResources } from 'hooks/Network/Resources';
|
import { useGetAllResources } from 'hooks/Network/Resources';
|
||||||
|
|
||||||
@@ -72,16 +73,14 @@ const RadioPicker = ({ editing, radios, arrayHelpers: { push: pushRadio }, setTa
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<CreateButton
|
||||||
colorScheme="blue"
|
label={t('configurations.add_radio')}
|
||||||
type="button"
|
size="lg"
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
rightIcon={<Plus size={20} />}
|
isCompact={radios.length > 0}
|
||||||
hidden={!editing}
|
hidden={!editing}
|
||||||
borderRadius={0}
|
borderRadius={0}
|
||||||
>
|
/>
|
||||||
{t('configurations.add_radio')}
|
|
||||||
</Button>
|
|
||||||
<Modal onClose={onClose} isOpen={isOpen} size="sm" scrollBehavior="inside">
|
<Modal onClose={onClose} isOpen={isOpen} size="sm" scrollBehavior="inside">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Button, Heading, useColorModeValue, useMultiStyleConfig, useTab } from '@chakra-ui/react';
|
import { Tab, useColorMode, useMultiStyleConfig, useTab } from '@chakra-ui/react';
|
||||||
import { useGetResource } from 'hooks/Network/Resources';
|
import { useGetResource } from 'hooks/Network/Resources';
|
||||||
import useFastField from 'hooks/useFastField';
|
import useFastField from 'hooks/useFastField';
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
const RadioTab = React.forwardRef((
|
const RadioTab: React.FC<{ index: number }> = React.forwardRef(({ index, ...props }, ref) => {
|
||||||
{
|
|
||||||
index,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
index: number
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const { value } = useFastField({ name: `configuration[${index}]` });
|
const { value } = useFastField({ name: `configuration[${index}]` });
|
||||||
const { data: resource } = useGetResource({
|
const { data: resource } = useGetResource({
|
||||||
id: value?.__variableBlock,
|
id: value?.__variableBlock,
|
||||||
enabled: value?.__variableBlock !== undefined,
|
enabled: value?.__variableBlock !== undefined,
|
||||||
});
|
});
|
||||||
const bgColorSelected = useColorModeValue('gray.100', 'gray.800');
|
const { colorMode } = useColorMode();
|
||||||
const bgColorUnSelected = useColorModeValue('white', 'gray.700');
|
const isLight = colorMode === 'light';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const tabProps = useTab({ ...props, ref });
|
const tabProps = useTab({ ...props, ref });
|
||||||
const isSelected = !!tabProps['aria-selected'];
|
|
||||||
|
|
||||||
// 2. Hook into the Tabs `size`, `variant`, props
|
// 2. Hook into the Tabs `size`, `variant`, props
|
||||||
const styles = useMultiStyleConfig('Tabs', tabProps);
|
const styles = useMultiStyleConfig('Tabs', tabProps);
|
||||||
@@ -44,15 +35,15 @@ const RadioTab = React.forwardRef((
|
|||||||
return '';
|
return '';
|
||||||
}, [value, resource]);
|
}, [value, resource]);
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
<Tab
|
||||||
(<Button
|
_selected={{
|
||||||
__css={styles.tab}
|
// @ts-ignore
|
||||||
{...tabProps}
|
...styles.tab?._selected,
|
||||||
bgColor={isSelected ? bgColorSelected : bgColorUnSelected}
|
borderBottomColor: isLight ? 'gray.100' : 'gray.800',
|
||||||
_focus={{ outline: 'none !important' }}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size="md">{name} Radio</Heading>
|
{name} Radio
|
||||||
</Button>)
|
</Tab>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { Center, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
import { Box, Center, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import RadioPicker from './RadioPicker';
|
import RadioPicker from './RadioPicker';
|
||||||
@@ -66,37 +66,27 @@ const Radios = ({ editing, arrayHelpers, radioBands, radioBandsLength }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Card variant="widget">
|
||||||
index={tabIndex}
|
<CardBody display="block">
|
||||||
onChange={handleTabsChange}
|
<Box display="unset" position="unset" w="100%">
|
||||||
isLazy
|
<Tabs index={tabIndex} onChange={handleTabsChange} variant="enclosed" isLazy w="100%">
|
||||||
variant="enclosed-colored"
|
<Box overflowX="auto" overflowY="auto" pt={1} h="56px">
|
||||||
colorScheme="blue"
|
<TabList mt={0}>
|
||||||
w="100%"
|
{tabs}
|
||||||
px={0}
|
<RadioPicker
|
||||||
>
|
radios={radioBands}
|
||||||
<TabList
|
editing={editing}
|
||||||
w="100%"
|
arrayHelpers={arrayHelpers}
|
||||||
overflowX="auto"
|
setTabIndex={setTabIndex}
|
||||||
style={{
|
arrLength={radioBandsLength}
|
||||||
overflowY: 'hidden',
|
/>
|
||||||
}}
|
</TabList>
|
||||||
>
|
</Box>
|
||||||
{tabs}
|
<TabPanels>{panels}</TabPanels>
|
||||||
<RadioPicker
|
</Tabs>
|
||||||
radios={radioBands}
|
</Box>
|
||||||
editing={editing}
|
</CardBody>
|
||||||
arrayHelpers={arrayHelpers}
|
</Card>
|
||||||
setTabIndex={setTabIndex}
|
|
||||||
arrLength={radioBandsLength}
|
|
||||||
/>
|
|
||||||
</TabList>
|
|
||||||
<Card variant="widget" mb={4} borderRadius={0}>
|
|
||||||
<CardBody display="unset">
|
|
||||||
<TabPanels>{panels}</TabPanels>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</Tabs>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Flex, Heading, IconButton, SimpleGrid, Spacer, Tooltip } from '@chakra-ui/react';
|
import { Flex, Heading, IconButton, SimpleGrid, Spacer, Tooltip } from '@chakra-ui/react';
|
||||||
import { Trash } from 'phosphor-react';
|
import { Trash } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SERVICES_CLASSIFIER_DNS_SCHEMA, SERVICES_CLASSIFIER_PORTS_SCHEMA } from '../servicesConstants';
|
import { SERVICES_CLASSIFIER_DNS_SCHEMA, SERVICES_CLASSIFIER_PORTS_SCHEMA } from '../servicesConstants';
|
||||||
import Card from 'components/Card';
|
import Card from 'components/Card';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Flex, Heading, IconButton, Tooltip } from '@chakra-ui/react';
|
import { Box, Flex, Heading, IconButton, Tooltip } from '@chakra-ui/react';
|
||||||
import { Plus } from 'phosphor-react';
|
import { Plus } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ClassifierField } from './ClassifierField';
|
import { ClassifierField } from './ClassifierField';
|
||||||
import useFastField from 'hooks/useFastField';
|
import useFastField from 'hooks/useFastField';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
||||||
import { CheckCircle, WarningOctagon } from 'phosphor-react';
|
import { CheckCircle, WarningOctagon } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
||||||
import { CheckCircle, WarningOctagon } from 'phosphor-react';
|
import { CheckCircle, WarningOctagon } from '@phosphor-icons/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useDisclosure, Modal, ModalBody, ModalContent, ModalOverlay } from '@chakra-ui/react';
|
||||||
import { ArrowsOut } from 'phosphor-react';
|
import { ArrowsOut } from '@phosphor-icons/react';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import useInterfacesJsonDisplay from './useInterfacesJsonDisplay';
|
import useInterfacesJsonDisplay from './useInterfacesJsonDisplay';
|
||||||
|
|||||||
@@ -353,10 +353,8 @@ const ConfigurationSectionsCard = ({ configId, editing, setSections, label, onDe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card px={label ? 0 : undefined}>
|
<Card px={label ? 0 : undefined}>
|
||||||
<CardHeader mb="10px" display="flex">
|
<CardHeader>
|
||||||
<Box pt={1}>
|
<Heading size="md">{label ?? configuration?.name}</Heading>
|
||||||
<Heading size="md">{label ?? configuration?.name}</Heading>
|
|
||||||
</Box>
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Box>
|
<Box>
|
||||||
<ViewConfigWarningsModal
|
<ViewConfigWarningsModal
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ const ConfigurationCard = ({ id }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card mb={4}>
|
<Card mb={4}>
|
||||||
<CardHeader mb="10px" display="flex">
|
<CardHeader>
|
||||||
<Box pt={1}>
|
<Box pt={1}>
|
||||||
<Heading size="md">{configuration?.name}</Heading>
|
<Heading size="md">{configuration?.name}</Heading>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
AlertDescription,
|
AlertDescription,
|
||||||
AlertIcon,
|
AlertIcon,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
Center,
|
Center,
|
||||||
Heading,
|
Heading,
|
||||||
Select,
|
Select,
|
||||||
@@ -18,11 +17,11 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { FloppyDisk } from 'phosphor-react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { Modal } from '../../../../components/Modals/Modal';
|
import SaveButton from 'components/Buttons/SaveButton';
|
||||||
import LoadingOverlay from 'components/LoadingOverlay';
|
import LoadingOverlay from 'components/LoadingOverlay';
|
||||||
|
import { Modal } from 'components/Modals/Modal';
|
||||||
import { useGetSystemLogLevelNames, useGetSystemLogLevels, useUpdateSystemLogLevels } from 'hooks/Network/System';
|
import { useGetSystemLogLevelNames, useGetSystemLogLevels, useUpdateSystemLogLevels } from 'hooks/Network/System';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -138,15 +137,7 @@ const SystemLoggingModal = ({ modalProps, endpoint, token }: Props) => {
|
|||||||
maxWidth: { sm: '600px', md: '600px', lg: '600px', xl: '600px' },
|
maxWidth: { sm: '600px', md: '600px', lg: '600px', xl: '600px' },
|
||||||
}}
|
}}
|
||||||
topRightButtons={
|
topRightButtons={
|
||||||
<Button
|
<SaveButton onClick={onUpdate} isDisabled={newLevels.length === 0} isLoading={updateLevels.isLoading} />
|
||||||
colorScheme="blue"
|
|
||||||
rightIcon={<FloppyDisk size={20} />}
|
|
||||||
onClick={onUpdate}
|
|
||||||
isDisabled={newLevels.length === 0}
|
|
||||||
isLoading={updateLevels.isLoading}
|
|
||||||
>
|
|
||||||
{t('system.update_levels')} {newLevels.length > 0 ? newLevels.length : ''}
|
|
||||||
</Button>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
import { IconButton, Tooltip, useDisclosure } from '@chakra-ui/react';
|
||||||
import { Article } from 'phosphor-react';
|
import { Article } from '@phosphor-icons/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SystemLoggingModal from './Modal';
|
import SystemLoggingModal from './Modal';
|
||||||
import { EndpointApiResponse } from 'hooks/Network/Endpoints';
|
import { EndpointApiResponse } from 'hooks/Network/Endpoints';
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user