diff --git a/.prettierignore b/.prettierignore
index 9020bbc..305ef68 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -3,3 +3,7 @@ build
dist
node_modules
.github
+docker-entrypoint.d
+helm
+.dockerignore
+Dockerfile
diff --git a/package-lock.json b/package-lock.json
index 5eb048c..cb56ba9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "wlan-cloud-owprov-ui",
- "version": "2.9.0(18)",
+ "version": "2.10.0(2)",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "wlan-cloud-owprov-ui",
- "version": "2.9.0(18)",
+ "version": "2.10.0(2)",
"license": "ISC",
"dependencies": {
"@chakra-ui/icons": "^2.0.11",
diff --git a/package.json b/package.json
index f71331a..804df2a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wlan-cloud-owprov-ui",
- "version": "2.9.0(18)",
+ "version": "2.10.0(2)",
"description": "",
"main": "index.tsx",
"scripts": {
diff --git a/src/components/Card/CardHeader.tsx b/src/components/Card/CardHeader.tsx
index 3d7f8f2..26deecb 100644
--- a/src/components/Card/CardHeader.tsx
+++ b/src/components/Card/CardHeader.tsx
@@ -1,8 +1,7 @@
import React from 'react';
-import { Box, useStyleConfig } from '@chakra-ui/react';
-import { ThemeProps } from 'models/Theme';
+import { Box, LayoutProps, SpaceProps, useStyleConfig } from '@chakra-ui/react';
-interface Props extends ThemeProps {
+interface Props extends LayoutProps, SpaceProps {
variant?: string;
children: React.ReactNode;
}
@@ -11,13 +10,7 @@ const defaultProps = {
variant: undefined,
};
-const CardHeader = (
- {
- variant,
- children,
- ...rest
- }: Props
-) => {
+const CardHeader = ({ variant, children, ...rest }: Props) => {
// @ts-ignore
const styles = useStyleConfig('CardHeader', { variant });
// Pass the computed styles into the `__css` prop
diff --git a/src/components/CreateObjectsForms/LocationPickerCreator/index.jsx b/src/components/CreateObjectsForms/LocationPickerCreator/index.jsx
index 99c649b..3e1af69 100644
--- a/src/components/CreateObjectsForms/LocationPickerCreator/index.jsx
+++ b/src/components/CreateObjectsForms/LocationPickerCreator/index.jsx
@@ -97,7 +97,7 @@ const LocationPickerCreator = ({ locationName, createLocationName, editing, isMo
{ value: 'CREATE_NEW', label: getCreateLabel() },
...getOptions(),
]}
- w={256}
+ w="unset"
/>
{location === 'CREATE_NEW' && newLocation && !isModal &&
}
{location === 'CREATE_NEW' && isModal && (
diff --git a/src/components/Tables/VenueTable/CreateVenueModal/index.jsx b/src/components/Tables/VenueTable/CreateVenueModal/index.jsx
index 9b2b33c..e2f4cba 100644
--- a/src/components/Tables/VenueTable/CreateVenueModal/index.jsx
+++ b/src/components/Tables/VenueTable/CreateVenueModal/index.jsx
@@ -4,27 +4,25 @@ import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import CreateVenueForm from './Form';
import CloseButton from 'components/Buttons/CloseButton';
-import CreateButton from 'components/Buttons/CreateButton';
import SaveButton from 'components/Buttons/SaveButton';
import ConfirmCloseAlert from 'components/Modals/Actions/ConfirmCloseAlert';
import ModalHeader from 'components/Modals/ModalHeader';
import useFormRef from 'hooks/useFormRef';
const propTypes = {
- isDisabled: PropTypes.bool,
parentId: PropTypes.string,
entityId: PropTypes.string,
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
};
const defaultProps = {
- isDisabled: false,
parentId: '',
entityId: '',
};
-const CreateVenueModal = ({ parentId, entityId, isDisabled }) => {
+const CreateVenueModal = ({ isOpen, onClose, parentId, entityId }) => {
const { t } = useTranslation();
- const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen: showConfirm, onOpen: openConfirm, onClose: closeConfirm } = useDisclosure();
const { form, formRef } = useFormRef();
@@ -36,37 +34,35 @@ const CreateVenueModal = ({ parentId, entityId, isDisabled }) => {
};
return (
- <>
-
-
-
-
-
-
-
- >
- }
+
+
+
+
+
+
+ >
+ }
+ />
+
+
-
-
-
-
-
-
- >
+
+
+
+
);
};
diff --git a/src/hooks/Network/Analytics.ts b/src/hooks/Network/Analytics.ts
index 5d0ca0c..a3800f6 100644
--- a/src/hooks/Network/Analytics.ts
+++ b/src/hooks/Network/Analytics.ts
@@ -1,248 +1,16 @@
import { useToast } from '@chakra-ui/react';
-import { useMutation, useQuery } from '@tanstack/react-query';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
+import {
+ AnalyticsBoardApiResponse,
+ AnalyticsBoardDevicesApiResponse,
+ AnalyticsClientLifecycleApiResponse,
+ AnalyticsTimePointsApiResponse,
+} from 'models/Analytics';
import { AxiosError } from 'models/Axios';
-import { Note } from 'models/Note';
import { PageInfo, SortInfo } from 'models/Table';
import { axiosAnalytics } from 'utils/axiosInstances';
-export type AnalyticsBoardDevice = {
- associations_2g: number;
- associations_5g: number;
- associations_6g: number;
- boardId: string;
- connected: boolean;
- connectionIp: string;
- deviceType: string;
- health: number;
- lastConnection: number;
- lastContact: number;
- lastDisconnection: number;
- lastFirmware: string;
- lastFirmwareUpdate: number;
- lastHealth: number;
- lastPing: number;
- lastState: number;
- locale: string;
- memory: number;
- pings: number;
- serialNumber: string;
- states: number;
- type: string;
- uptime: number;
-};
-
-export type AnalyticsBoardDevicesApiResponse = {
- devices: AnalyticsBoardDevice[];
-};
-
-export type AnalyticsBoardApiResponse = {
- created: number;
- description: string;
- id: string;
- modified: number;
- name: string;
- notes: Note[];
- tags: string[];
- venueList: {
- description: string;
- id: string;
- interval: number;
- monitorSubVenues: boolean;
- name: string;
- retention: number;
- };
-};
-
-export type AnalyticsClientLifecycleApiResponse = {
- ack_signal: number;
- ack_signal_avg: number;
- active_ms: number;
- bssid: string;
- busy_ms: number;
- channel: number;
- channel_width: number;
- connected: number;
- inactive: number;
- ipv4: string;
- ipv6: string;
- mode: string;
- noise: number;
- receive_ms: number;
- rssi: number;
- rx_bitrate: number;
- rx_bytes: number;
- rx_chwidth: number;
- rx_duration: number;
- rx_mcs: number;
- rx_nss: number;
- rx_packets: number;
- rx_vht: boolean;
- ssid: string;
- station_id: string;
- timestamp: number;
- tx_bitrate: number;
- tx_bytes: number;
- tx_chwidth: number;
- tx_duration: number;
- tx_mcs: number;
- tx_nss: number;
- tx_packets: number;
- tx_power: number;
- tx_retries: number;
- tx_vht: boolean;
- venue_id: string;
-};
-
-export type AnalyticsApData = {
- collisions: number;
- multicast: number;
- rx_bytes: number;
- rx_bytes_bw: number;
- rx_bytes_delta: number;
- rx_dropped: number;
- rx_dropped_delta: number;
- rx_dropped_pct: number;
- rx_errors: number;
- rx_errors_delta: number;
- rx_errors_pct: number;
- rx_packets: number;
- rx_packets_bw: number;
- rx_packets_delta: number;
- tx_bytes: number;
- tx_bytes_bw: number;
- tx_bytes_delta: number;
- tx_dropped: number;
- tx_dropped_delta: number;
- tx_dropped_pct: number;
- tx_errors: number;
- tx_errors_delta: number;
- tx_errors_pct: number;
- tx_packets: number;
- tx_packets_bw: number;
- tx_packets_delta: number;
-};
-
-export type AnalyticsRadioData = {
- active_ms: number;
- active_pct: number;
- band: number;
- busy_ms: number;
- busy_pct: number;
- channel: number;
- channel_width: number;
- noise: number;
- receive_ms: number;
- receive_pct: number;
- temperature: number;
- transmit_ms: number;
- transmit_pct: number;
- tx_power: number;
-};
-
-export type AnalyticsAssociationData = {
- connected: number;
- inactive: number;
- rssi: number;
- rx_bytes: number;
- rx_bytes_bw: number;
- rx_bytes_delta: number;
- rx_packets: number;
- rx_packets_bw: number;
- rx_packets_delta: number;
- rx_rate: {
- bitrate: number;
- chwidth: number;
- ht: boolean;
- mcs: number;
- nss: number;
- sgi: boolean;
- };
- station: string;
- tx_bytes: number;
- tx_bytes_bw: number;
- tx_bytes_delta: number;
- tx_duration: number;
- tx_duration_delta: number;
- tx_duration_pct: number;
- tx_failed: number;
- tx_failed_delta: number;
- tx_failed_pct: number;
- tx_packets: number;
- tx_packets_bw: number;
- tx_packets_delta: number;
- tx_rate: {
- bitrate: number;
- chwidth: number;
- ht: boolean;
- mcs: number;
- nss: number;
- sgi: boolean;
- };
- tx_retries: number;
- tx_retries_delta: number;
- tx_retries_pct: number;
-};
-
-export type AnalyticsSsidData = {
- associations: AnalyticsAssociationData[];
- band: 2;
- bssid: string;
- channel: number;
- mode: string;
- rx_bytes_bw: {
- avg: number;
- max: number;
- min: number;
- };
- rx_packets_bw: {
- avg: number;
- max: number;
- min: number;
- };
- ssid: string;
- tx_bytes_bw: {
- avg: number;
- max: number;
- min: number;
- };
- tx_duration_pct: {
- avg: number;
- max: number;
- min: number;
- };
- tx_failed_pct: {
- avg: number;
- max: number;
- min: number;
- };
- tx_packets_bw: {
- avg: number;
- max: number;
- min: number;
- };
- tx_retries_pct: {
- avg: number;
- max: number;
- min: number;
- };
-};
-
-export type AnalyticsTimePointApiResponse = {
- ap_data: AnalyticsApData;
- boardId: string;
- device_info: AnalyticsBoardDevice;
- id: string;
- radio_data: AnalyticsRadioData[];
- serialNumber: string;
- ssid_data: AnalyticsSsidData[];
- timestamp: number;
-};
-
-export type AnalyticsTimePointsApiResponse = {
- points: AnalyticsTimePointApiResponse[][];
-};
-
export const useGetAnalyticsBoard = ({ id }: { id?: string }) => {
const { t } = useTranslation();
const toast = useToast();
@@ -344,26 +112,6 @@ export const useGetClientLifecycleTableSpecs = () =>
},
);
-const getPartialClients = async (venueId: string, offset: number) =>
- axiosAnalytics
- .get(`wifiClientHistory?macsOnly=true&venue=${venueId}&limit=500&offset=${offset}`)
- .then(({ data }) => data.entries as string[]);
-
-export const getAllClients = async (venueId: string) => {
- const allClients: string[] = [];
- let continueFirmware = true;
- let offset = 0;
- while (continueFirmware) {
- // eslint-disable-next-line no-await-in-loop
- const newClients = await getPartialClients(venueId, offset);
- if (newClients === null || newClients.length === 0 || newClients.length < 500 || offset >= 50000)
- continueFirmware = false;
- allClients.push(...newClients);
- offset += 500;
- }
- return allClients;
-};
-
export const useGetClientLifecycleCount = ({
venueId,
mac,
@@ -497,9 +245,31 @@ export const useGetAnalyticsBoardTimepoints = ({
);
};
-export const useCreateAnalyticsBoard = () => useMutation((newBoard) => axiosAnalytics.post('board/0', newBoard));
+export const useCreateAnalyticsBoard = () =>
+ useMutation((newBoard: unknown) => axiosAnalytics.post('board/0', newBoard));
-export const useUpdateAnalyticsBoard = () =>
- useMutation((newBoard: { id: string }) => axiosAnalytics.put(`board/${newBoard.id}`, newBoard));
+export const useUpdateAnalyticsBoard = ({ id }: { id: string }) => {
+ const queryClient = useQueryClient();
-export const useDeleteAnalyticsBoard = () => useMutation((id) => axiosAnalytics.delete(`board/${id}`, {}));
+ return useMutation(
+ (newBoard: {
+ name: string;
+ venueList: [
+ {
+ id: string;
+ name: string;
+ retention: number;
+ interval: number;
+ monitorSubVenues: boolean;
+ },
+ ];
+ }) => axiosAnalytics.put(`board/${id}`, newBoard),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(['get-board', id]);
+ },
+ },
+ );
+};
+
+export const useDeleteAnalyticsBoard = () => useMutation((id: string) => axiosAnalytics.delete(`board/${id}`, {}));
diff --git a/src/hooks/Network/Entity.ts b/src/hooks/Network/Entity.ts
index 338b18e..8d2a779 100644
--- a/src/hooks/Network/Entity.ts
+++ b/src/hooks/Network/Entity.ts
@@ -1,9 +1,9 @@
import { useToast } from '@chakra-ui/react';
-import { useMutation, useQuery } from '@tanstack/react-query';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
-import useDefaultPage from 'hooks/useDefaultPage';
+import { Entity } from '../../models/Entity';
+import useDefaultPage from '../useDefaultPage';
import { AxiosError } from 'models/Axios';
-import { Entity } from 'models/Entity';
import { axiosProv, axiosSec } from 'utils/axiosInstances';
export const useGetEntityTree = () => {
@@ -151,7 +151,19 @@ export const useCreateEntity = (isRoot = false) =>
axiosProv.post(`entity/${isRoot ? '0000-0000-0000' : 0}`, newEnt).then(({ data }) => data as Entity),
);
-export const useUpdateEntity = ({ id }: { id: string }) =>
- useMutation((newEnt) => axiosProv.put(`entity/${id}`, newEnt).then(({ data }) => data as Entity));
+export const useUpdateEntity = ({ id }: { id: string }) => {
+ const queryClient = useQueryClient();
+
+ return useMutation(
+ (newEnt: Partial) => axiosProv.put(`entity/${id}`, newEnt).then(({ data }) => data as Entity),
+ {
+ onSuccess: (data) => {
+ queryClient.invalidateQueries(['get-entity-tree']);
+ queryClient.invalidateQueries(['get-entities']);
+ queryClient.setQueryData(['get-entity', id], data);
+ },
+ },
+ );
+};
export const useDeleteEntity = () => useMutation((id: string) => axiosProv.delete(`entity/${id}`));
diff --git a/src/hooks/Network/Resources.ts b/src/hooks/Network/Resources.ts
index 7659dc7..4910182 100644
--- a/src/hooks/Network/Resources.ts
+++ b/src/hooks/Network/Resources.ts
@@ -177,4 +177,4 @@ export const useGetResource = ({ id, enabled }: { id: string; enabled: boolean }
export const useCreateResource = () => useMutation((newResource: unknown) => axiosProv.post('variable/0', newResource));
export const useUpdateResource = (id: string) =>
useMutation((resource: unknown) => axiosProv.put(`variable/${id}`, resource));
-export const useDeleteResource = () => useMutation((id) => axiosProv.delete(`variable/${id}`, {}));
+export const useDeleteResource = () => useMutation((id: string) => axiosProv.delete(`variable/${id}`, {}));
diff --git a/src/hooks/Network/Venues.ts b/src/hooks/Network/Venues.ts
index 7592c12..edfe927 100644
--- a/src/hooks/Network/Venues.ts
+++ b/src/hooks/Network/Venues.ts
@@ -4,30 +4,9 @@ import { useTranslation } from 'react-i18next';
import { v4 as uuid } from 'uuid';
import useDefaultPage from '../useDefaultPage';
import { AxiosError } from 'models/Axios';
-import { DeviceRules } from 'models/Basic';
-import { Note } from 'models/Note';
+import { VenueApiResponse } from 'models/Venue';
import { axiosProv } from 'utils/axiosInstances';
-export interface VenueApiResponse {
- id: string;
- name: string;
- description: string;
- parent: string;
- devices: string[];
- children: string[];
- contacts: string[];
- entity: string;
- boards: string[];
- created: number;
- modified: number;
- configurations: string[];
- notes: Note[];
- variables: string[];
- location: string;
- sourceIP: string[];
- deviceRules: DeviceRules;
-}
-
const getVenuesBatch = async (limit: number, offset: number) =>
axiosProv
.get(`venue?withExtendedInfo=true&offset=${offset}&limit=${limit}`)
diff --git a/src/index.css b/src/index.css
index 8647090..d9f1f2c 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,7 +2,8 @@
display: -webkit-box;
display: -ms-flexbox;
display: flex;
- margin-left: -30px;
+ margin-left: -20px;
+ margin-bottom: -20px;
width: auto;
}
.my-masonry-grid_column {
diff --git a/src/layout/Containers/PanelContainer.tsx b/src/layout/Containers/PanelContainer.tsx
deleted file mode 100644
index a8b985f..0000000
--- a/src/layout/Containers/PanelContainer.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { ReactNode } from 'react';
-import { Box, useStyleConfig } from '@chakra-ui/react';
-
-interface Props {
- children: ReactNode;
-}
-
-const PanelContainer = (
- {
- children
- }: Props
-) => {
- const styles = useStyleConfig('PanelContainer');
- // Pass the computed styles into the `__css` prop
- return {children};
-};
-
-export default PanelContainer;
diff --git a/src/layout/Containers/PanelContent.tsx b/src/layout/Containers/PanelContent.tsx
deleted file mode 100644
index 302fdb1..0000000
--- a/src/layout/Containers/PanelContent.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { ReactNode } from 'react';
-import { Box, useStyleConfig } from '@chakra-ui/react';
-
-interface Props {
- children: ReactNode;
-}
-
-const PanelContent = (
- {
- children
- }: Props
-) => {
- const styles = useStyleConfig('PanelContent');
-
- return {children};
-};
-
-export default PanelContent;
diff --git a/src/layout/MainPanel.tsx b/src/layout/MainPanel.tsx
deleted file mode 100644
index cf245c7..0000000
--- a/src/layout/MainPanel.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import { Box, LayoutProps, useStyleConfig } from '@chakra-ui/react';
-
-interface Props extends LayoutProps {
- children: React.ReactNode;
-}
-
-const MainPanel = (
- {
- children,
- ...props
- }: Props
-) => {
- const styles = useStyleConfig('MainPanel');
-
- return (
-
- {children}
-
- );
-};
-
-export default MainPanel;
diff --git a/src/layout/Navbar/index.tsx b/src/layout/Navbar/index.tsx
index 56242f2..fb75119 100644
--- a/src/layout/Navbar/index.tsx
+++ b/src/layout/Navbar/index.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { ChevronDownIcon, HamburgerIcon, MoonIcon, SunIcon } from '@chakra-ui/icons';
+import { HamburgerIcon, MoonIcon, SunIcon } from '@chakra-ui/icons';
import {
Box,
Flex,
@@ -12,73 +12,55 @@ import {
MenuList,
Heading,
HStack,
- VStack,
Text,
IconButton,
Tooltip,
+ useBreakpoint,
+ Portal,
} from '@chakra-ui/react';
import { ArrowCircleLeft, MapTrifold } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
-import { useLocation, useNavigate } from 'react-router-dom';
-import LanguageSwitcher from 'components/LanguageSwitcher';
+import { useNavigate } from 'react-router-dom';
import { useAuth } from 'contexts/AuthProvider';
-import routes from 'router/routes';
-import { uppercaseFirstLetter } from 'utils/stringHelper';
-interface Props {
- secondary: boolean;
- isSidebarOpen: boolean;
+export type NavbarProps = {
toggleSidebar: () => void;
-}
+ activeRoute?: string;
+ languageSwitcher?: React.ReactNode;
+};
-const Navbar = ({ secondary, toggleSidebar, isSidebarOpen }: Props) => {
+export const Navbar = ({ toggleSidebar, activeRoute, languageSwitcher }: NavbarProps) => {
const { t } = useTranslation();
- const location = useLocation();
const navigate = useNavigate();
const [scrolled, setScrolled] = useState(false);
+ const breakpoint = useBreakpoint();
const { colorMode, toggleColorMode } = useColorMode();
const { logout, user, avatar } = useAuth();
- const getActiveRoute = () => {
- const route = routes.find(
- (r) => r.path === location.pathname || location.pathname.split('/')[1] === r.path.split('/')[1],
- );
- if (route) return route.navName ?? route.name;
+ const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
- return '';
- };
-
- // Style variables
- let navbarPosition: 'absolute' | 'fixed' = 'absolute';
- let navbarFilter = 'none';
- let navbarBackdrop = 'blur(21px)';
- let navbarShadow = 'none';
- let navbarBg = 'none';
- let navbarBorder = 'transparent';
- let secondaryMargin = '0px';
-
- // Values if scrolled
- const scrolledNavbarShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
- const scrolledNavbarBg = useColorModeValue(
+ const boxShadow = useColorModeValue('0px 7px 23px rgba(0, 0, 0, 0.05)', 'none');
+ const bg = useColorModeValue(
'linear-gradient(112.83deg, rgba(255, 255, 255, 0.82) 0%, rgba(255, 255, 255, 0.8) 110.84%)',
'linear-gradient(112.83deg, rgba(255, 255, 255, 0.21) 0%, rgba(255, 255, 255, 0) 110.84%)',
);
- const scrolledNavbarBorder = useColorModeValue('#FFFFFF', 'rgba(255, 255, 255, 0.31)');
- const scrolledNavbarFilter = useColorModeValue('none', 'drop-shadow(0px 7px 23px rgba(0, 0, 0, 0.05))');
-
- if (scrolled === true) {
- navbarPosition = 'fixed';
- navbarShadow = scrolledNavbarShadow;
- navbarBg = scrolledNavbarBg;
- navbarBorder = scrolledNavbarBorder;
- navbarFilter = scrolledNavbarFilter;
- }
-
- if (secondary) {
- navbarBackdrop = 'none';
- navbarPosition = 'absolute';
- secondaryMargin = '22px';
- }
+ const borderColor = useColorModeValue('#FFFFFF', 'rgba(255, 255, 255, 0.31)');
+ const filter = useColorModeValue('none', 'drop-shadow(0px 7px 23px rgba(0, 0, 0, 0.05))');
+ const scrollDependentStyles = scrolled
+ ? ({
+ position: 'fixed',
+ boxShadow,
+ bg,
+ borderColor,
+ filter,
+ } as const)
+ : ({
+ position: 'absolute',
+ filter: 'none',
+ boxShadow: 'none',
+ bg: 'none',
+ borderColor: 'transparent',
+ } as const);
const goBack = () => navigate(-1);
@@ -96,103 +78,86 @@ const Navbar = ({ secondary, toggleSidebar, isSidebarOpen }: Props) => {
window.addEventListener('scroll', changeNavbar);
return (
-
-
-
- {t(getActiveRoute())}
-
- }
- />
-
-
-
-
- }
- onClick={goToMap}
- />
-
-
- : }
- onClick={toggleColorMode}
- />
-
-
-
-
+
+
+
+ {isCompact && }
+ {activeRoute}
+
+ }
+ />
+
+
+
+
+ }
+ onClick={goToMap}
+ />
+
+
+ : }
+ onClick={toggleColorMode}
+ />
+
+ {languageSwitcher}
+
-
-
-
-
+
+
+
+
-
+
);
};
-
-export default Navbar;
diff --git a/src/layout/PageContainer/index.tsx b/src/layout/PageContainer/index.tsx
new file mode 100644
index 0000000..485bd61
--- /dev/null
+++ b/src/layout/PageContainer/index.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { Box, Center, Flex, Spinner, useBreakpoint } from '@chakra-ui/react';
+import { useAuth } from 'contexts/AuthProvider';
+
+export type PageContainerProps = {
+ waitForUser: boolean;
+ children: React.ReactNode;
+};
+
+export const PageContainer = ({ waitForUser, children }: PageContainerProps) => {
+ const { isUserLoaded } = useAuth();
+ const breakpoint = useBreakpoint('xl');
+ const isCompact = breakpoint === 'base' || breakpoint === 'sm' || breakpoint === 'md';
+
+ return (
+
+
+
+
+
+
+ }
+ >
+ {waitForUser && !isUserLoaded ? (
+
+
+
+ ) : (
+ children
+ )}
+
+
+
+
+ );
+};
diff --git a/src/layout/Sidebar/CreateLinks.tsx b/src/layout/Sidebar/CreateLinks.tsx
deleted file mode 100644
index 2295380..0000000
--- a/src/layout/Sidebar/CreateLinks.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { v4 as uuid } from 'uuid';
-import EntityNavButton from './EntityNavButton';
-import NavLinkButton from './NavLinkButton';
-import { Route } from 'models/Routes';
-
-const createLinks = (
- routes: Route[],
- activeRoute: (path: string, otherRoute: string | undefined) => string,
- role: string,
- toggleSidebar = () => {},
-) =>
- routes.map((route) =>
- route.isEntity ? (
-
- ) : (
-
- ),
- );
-
-export default createLinks;
diff --git a/src/layout/Sidebar/EntityNavButton.tsx b/src/layout/Sidebar/EntityNavButton.tsx
index 319f3a8..e4e24d1 100644
--- a/src/layout/Sidebar/EntityNavButton.tsx
+++ b/src/layout/Sidebar/EntityNavButton.tsx
@@ -9,20 +9,12 @@ import { Route } from 'models/Routes';
const variantChange = '0.2s linear';
interface Props {
- activeRoute: (path: string, otherRoute: string | undefined) => string;
+ isActive: boolean;
route: Route;
- role: string;
toggleSidebar: () => void;
}
-const EntityNavButton = (
- {
- activeRoute,
- route,
- role,
- toggleSidebar
- }: Props
-) => {
+const EntityNavButton = ({ isActive, route, toggleSidebar }: Props) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const activeArrowColor = useColorModeValue('var(--chakra-colors-gray-700)', 'white');
@@ -33,17 +25,15 @@ const EntityNavButton = (
return (
- {activeRoute(route.path, '/venue/:id') === 'active' ? (
+ {isActive ? (
+
+ }
+ onClick={onOpen}
+ isDisabled={!data}
+ colorScheme="teal"
+ />
+
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueDashboard/index.jsx b/src/pages/VenuePage/Layout/AnalyticsCard/VenueDashboard/index.jsx
similarity index 76%
rename from src/pages/VenuePage/VenueChildrenCard/VenueDashboard/index.jsx
rename to src/pages/VenuePage/Layout/AnalyticsCard/VenueDashboard/index.jsx
index 1db4749..8fd3817 100644
--- a/src/pages/VenuePage/VenueChildrenCard/VenueDashboard/index.jsx
+++ b/src/pages/VenuePage/Layout/AnalyticsCard/VenueDashboard/index.jsx
@@ -1,22 +1,12 @@
import React, { useEffect, useMemo, useState } from 'react';
-import {
- Alert,
- AlertDescription,
- AlertIcon,
- AlertTitle,
- Box,
- Center,
- Flex,
- Heading,
- Spacer,
- Spinner,
- useDisclosure,
-} from '@chakra-ui/react';
+import { Box, Center, Heading, Spacer, Spinner, useDisclosure } from '@chakra-ui/react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import VenueAnalyticsHeader from './Header';
import VenueDashboardTableModal from './TableModal';
import RefreshButton from 'components/Buttons/RefreshButton';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
import LoadingOverlay from 'components/LoadingOverlay';
import { useGetAnalyticsBoardDevices } from 'hooks/Network/Analytics';
@@ -28,7 +18,7 @@ const VenueDashboard = ({ boardId }) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure(false);
const [tableOptions, setTableOptions] = useState(null);
- const { data: devices, isFetching, refetch, error } = useGetAnalyticsBoardDevices({ id: boardId });
+ const { data: devices, isFetching, refetch } = useGetAnalyticsBoardDevices({ id: boardId });
const handleRefreshClick = () => {
refetch();
@@ -124,45 +114,34 @@ const VenueDashboard = ({ boardId }) => {
if (!isOpen) setTableOptions(null);
}, [isOpen]);
- if (error)
- return (
-
-
-
-
- {t('common.error')}
-
- {error.response?.status === 404 ? t('analytics.missing_board') : error.response?.data?.ErrorDescription}
-
-
-
-
- );
-
return !devices ? (
-
+
) : (
-
-
-
-
- {parsedData?.totalDevices} {t('devices.title')}
-
-
-
-
-
-
-
-
+ <>
+
+
+ {parsedData?.totalDevices} {t('devices.title')}
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
};
diff --git a/src/pages/VenuePage/Layout/AnalyticsCard/ViewAnalyticsSettingsModal.tsx b/src/pages/VenuePage/Layout/AnalyticsCard/ViewAnalyticsSettingsModal.tsx
new file mode 100644
index 0000000..9fd8729
--- /dev/null
+++ b/src/pages/VenuePage/Layout/AnalyticsCard/ViewAnalyticsSettingsModal.tsx
@@ -0,0 +1,196 @@
+import * as React from 'react';
+import { Box, VStack, useBoolean, useDisclosure, useToast } from '@chakra-ui/react';
+import { Form, Formik } from 'formik';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuid } from 'uuid';
+import * as Yup from 'yup';
+import SaveButton from 'components/Buttons/SaveButton';
+import ToggleEditButton from 'components/Buttons/ToggleEditButton';
+import NumberField from 'components/FormFields/NumberField';
+import StringField from 'components/FormFields/StringField';
+import ToggleField from 'components/FormFields/ToggleField';
+import ConfirmCloseAlert from 'components/Modals/Actions/ConfirmCloseAlert';
+import { Modal } from 'components/Modals/Modal';
+import { testObjectName } from 'constants/formTests';
+import { useGetAnalyticsBoard, useUpdateAnalyticsBoard } from 'hooks/Network/Analytics';
+import useFormRef from 'hooks/useFormRef';
+import { AxiosError } from 'models/Axios';
+
+type Props = {
+ venueId: string;
+ boardId: string;
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+const ViewAnalyticsSettingsModal = ({ boardId, venueId, isOpen, onClose }: Props) => {
+ const { t } = useTranslation();
+ const toast = useToast();
+ const getBoard = useGetAnalyticsBoard({
+ id: boardId,
+ });
+ const updateAnalytics = useUpdateAnalyticsBoard({ id: boardId });
+ const [formKey, setFormKey] = React.useState(uuid());
+ const { isOpen: isConfirmOpen, onOpen: openConfirm, onClose: closeConfirm } = useDisclosure();
+ const [editing, { toggle, off }] = useBoolean();
+ const { form, formRef } = useFormRef<{
+ name: string;
+ interval: number;
+ retention: number;
+ monitorSubVenues: boolean;
+ }>();
+ const closeModal = () => (form.dirty ? openConfirm() : onClose());
+
+ const Schema = Yup.object()
+ .shape({
+ name: Yup.string().required(t('form.required')).test('len', t('common.name_error'), testObjectName),
+ interval: Yup.number().required(t('form.required')).moreThan(0).integer(),
+ retention: Yup.number().required(t('form.required')).moreThan(0).integer(),
+ })
+ .nullable()
+ .default(undefined);
+
+ const data = getBoard.data?.venueList[0];
+
+ React.useEffect(() => {
+ setFormKey(uuid());
+ }, [editing, getBoard.data]);
+
+ return (
+ <>
+
+
+
+ >
+ }
+ options={{
+ modalSize: 'sm',
+ }}
+ >
+
+ {data ? (
+ {
+ updateAnalytics.mutateAsync(
+ {
+ name,
+ venueList: [
+ {
+ id: venueId,
+ name,
+ retention,
+ interval,
+ monitorSubVenues,
+ },
+ ],
+ },
+ {
+ onSuccess: () => {
+ setSubmitting(false);
+ toast({
+ id: 'venue-update-success',
+ title: t('common.success'),
+ description: t('crud.success_update_obj', {
+ obj: t('venues.one'),
+ }),
+ status: 'success',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ resetForm();
+ off();
+ onClose();
+ },
+ onError: (e) => {
+ toast({
+ id: uuid(),
+ title: t('common.error'),
+ description: t('crud.error_update_obj', {
+ obj: t('venues.one'),
+ e: (e as AxiosError)?.response?.data?.ErrorDescription,
+ }),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ setSubmitting(false);
+ },
+ },
+ );
+ }}
+ >
+
+
+ ) : null}
+
+
+ {
+ onClose();
+ closeConfirm();
+ }}
+ cancel={closeConfirm}
+ />
+ >
+ );
+};
+
+export default ViewAnalyticsSettingsModal;
diff --git a/src/pages/VenuePage/Layout/AnalyticsCard/index.tsx b/src/pages/VenuePage/Layout/AnalyticsCard/index.tsx
new file mode 100644
index 0000000..1abf41e
--- /dev/null
+++ b/src/pages/VenuePage/Layout/AnalyticsCard/index.tsx
@@ -0,0 +1,172 @@
+import * as React from 'react';
+import {
+ Alert,
+ AlertDescription,
+ AlertIcon,
+ AlertTitle,
+ Box,
+ Center,
+ Heading,
+ IconButton,
+ Spacer,
+ Spinner,
+ Tab,
+ TabList,
+ TabPanel,
+ TabPanels,
+ Tabs,
+ Tooltip,
+ useDisclosure,
+} from '@chakra-ui/react';
+import { MagnifyingGlass } from 'phosphor-react';
+import { useTranslation } from 'react-i18next';
+import LiveView from './LiveView';
+import StartAnalyticsModal from './StartAnalyticsModal';
+import StopMonitoringButton from './StopMonitoringButton';
+import VenueClientLifecycle from './VenueClientLifecycle';
+import VenueDashboard from './VenueDashboard';
+import ViewAnalyticsSettingsModal from './ViewAnalyticsSettingsModal';
+import Card from 'components/Card';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import { useGetAnalyticsBoardDevices } from 'hooks/Network/Analytics';
+import { useGetVenue } from 'hooks/Network/Venues';
+import { AxiosError } from 'models/Axios';
+
+type Props = {
+ id: string;
+};
+
+const VenueAnalyticsCard = ({ id }: Props) => {
+ const { t } = useTranslation();
+ const { isOpen: isCreateOpen, onOpen: onCreateOpen, onClose: onCreateClose } = useDisclosure();
+ const { isOpen: isViewOpen, onOpen: onViewOpen, onClose: onViewClose } = useDisclosure();
+ const getVenue = useGetVenue({ id });
+ const boardId = getVenue.data?.boards[0];
+ const getDashboard = useGetAnalyticsBoardDevices({ id: boardId });
+
+ const body = React.useMemo(() => {
+ if (!boardId)
+ return (
+
+
+ {t('analytics.title')}
+
+
+
+
+
+
+ {t('analytics.no_board')}
+ {t('analytics.no_board_description')}
+
+
+
+
+
+ );
+
+ if (getDashboard.error || getDashboard.isLoading || !getVenue.data)
+ return (
+
+
+ {t('analytics.title')}
+
+
+ {getDashboard.error ? (
+
+
+
+ {t('common.error')}
+
+ {getDashboard.error.response?.status === 404
+ ? t('analytics.missing_board')
+ : (getDashboard.error as AxiosError).response?.data?.ErrorDescription}
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ );
+
+ return (
+
+
+
+
+ {t('analytics.dashboard')}
+ {t('analytics.live_view')}
+ {t('analytics.client_lifecycle')}
+
+
+
+ }
+ h="41px"
+ borderTopLeftRadius={0}
+ borderBottomRadius="0px"
+ colorScheme="blue"
+ onClick={onViewOpen}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }, [boardId, getDashboard, getVenue]);
+
+ return (
+
+ {body}
+
+
+ );
+};
+
+export default VenueAnalyticsCard;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/Actions.jsx b/src/pages/VenuePage/Layout/ConfigurationCard/ResourceActions.tsx
similarity index 82%
rename from src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/Actions.jsx
rename to src/pages/VenuePage/Layout/ConfigurationCard/ResourceActions.tsx
index 73c451b..1a3fcb2 100644
--- a/src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/Actions.jsx
+++ b/src/pages/VenuePage/Layout/ConfigurationCard/ResourceActions.tsx
@@ -18,23 +18,19 @@ import {
useToast,
} from '@chakra-ui/react';
import { MagnifyingGlass, Trash } from 'phosphor-react';
-import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { v4 as uuid } from 'uuid';
import { useDeleteResource } from 'hooks/Network/Resources';
+import { AxiosError } from 'models/Axios';
+import { Resource } from 'models/Resource';
-const propTypes = {
- cell: PropTypes.shape({
- original: PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- }).isRequired,
- }).isRequired,
- refreshTable: PropTypes.func.isRequired,
- openEditModal: PropTypes.func.isRequired,
+type Props = {
+ resource: Resource;
+ refreshTable: () => void;
+ openEditModal: (resource: Resource) => void;
};
-const Actions = ({ cell: { original: resource }, refreshTable, openEditModal }) => {
+const EntityResourceActions = ({ resource, refreshTable, openEditModal }: Props) => {
const { t } = useTranslation();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -63,7 +59,7 @@ const Actions = ({ cell: { original: resource }, refreshTable, openEditModal })
title: t('common.error'),
description: t('crud.error_delete_obj', {
obj: resource.name,
- e: e?.response?.data?.ErrorDescription,
+ e: (e as AxiosError)?.response?.data?.ErrorDescription,
}),
status: 'error',
duration: 5000,
@@ -80,7 +76,7 @@ const Actions = ({ cell: { original: resource }, refreshTable, openEditModal })
- } size="sm" />
+ } size="sm" />
@@ -105,6 +101,7 @@ const Actions = ({ cell: { original: resource }, refreshTable, openEditModal })
}
@@ -116,6 +113,4 @@ const Actions = ({ cell: { original: resource }, refreshTable, openEditModal })
);
};
-Actions.propTypes = propTypes;
-
-export default Actions;
+export default EntityResourceActions;
diff --git a/src/pages/VenuePage/Layout/ConfigurationCard/VenueConfigurations.tsx b/src/pages/VenuePage/Layout/ConfigurationCard/VenueConfigurations.tsx
new file mode 100644
index 0000000..2249585
--- /dev/null
+++ b/src/pages/VenuePage/Layout/ConfigurationCard/VenueConfigurations.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import { Box, HStack, IconButton, Spacer, Tooltip } from '@chakra-ui/react';
+import { MagnifyingGlass } from 'phosphor-react';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import ConfigurationsTable from 'components/Tables/ConfigurationTable';
+import CreateConfigurationModal from 'components/Tables/ConfigurationTable/CreateConfigurationModal';
+import DeleteConfigurationButton from 'components/Tables/ConfigurationTable/DeleteConfigurationButton';
+import { useGetVenue } from 'hooks/Network/Venues';
+import { Configuration } from 'models/Configuration';
+
+type Props = {
+ id: string;
+};
+
+const VenueConfigurations = ({ id }: Props) => {
+ const { t } = useTranslation();
+ const getVenue = useGetVenue({ id });
+ const navigate = useNavigate();
+
+ const handleGoToPage = (configId: string) => () => navigate(`/configuration/${configId}`);
+
+ const actions = React.useCallback(
+ (cell: { row: { original: Configuration } }) => (
+
+
+
+ }
+ size="sm"
+ onClick={handleGoToPage(cell.row.original.id)}
+ />
+
+
+ ),
+ [t],
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default VenueConfigurations;
diff --git a/src/pages/VenuePage/Layout/ConfigurationCard/VenueResources.tsx b/src/pages/VenuePage/Layout/ConfigurationCard/VenueResources.tsx
new file mode 100644
index 0000000..3355255
--- /dev/null
+++ b/src/pages/VenuePage/Layout/ConfigurationCard/VenueResources.tsx
@@ -0,0 +1,67 @@
+import * as React from 'react';
+import { Box, Spacer, useDisclosure } from '@chakra-ui/react';
+import { useQueryClient } from '@tanstack/react-query';
+import EntityResourceActions from './ResourceActions';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import CreateResourceModal from 'components/Modals/Resources/CreateModal';
+import EditResourceModal from 'components/Modals/Resources/EditModal';
+import ResourcesTable from 'components/Tables/ResourceTable';
+import { useGetVenue } from 'hooks/Network/Venues';
+import { Resource } from 'models/Resource';
+
+type Props = {
+ id: string;
+};
+
+const VenueResources = ({ id }: Props) => {
+ const queryClient = useQueryClient();
+ const [resource, setResource] = React.useState();
+ const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
+ const getVenue = useGetVenue({ id });
+
+ const refreshTable = () => {
+ queryClient.invalidateQueries(['get-resources-with-select']);
+ };
+
+ const openEditModal = (openedResource: Resource) => () => {
+ setResource(openedResource);
+ openEdit();
+ };
+ const openDetailsModalFromTable = (openedResource: Resource) => {
+ setResource(openedResource);
+ openEdit();
+ };
+
+ const actions = React.useCallback(
+ (cell: { row: { original: Resource } }) => (
+
+ ),
+ [],
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default VenueResources;
diff --git a/src/pages/VenuePage/Layout/ConfigurationCard/index.tsx b/src/pages/VenuePage/Layout/ConfigurationCard/index.tsx
new file mode 100644
index 0000000..5382c9b
--- /dev/null
+++ b/src/pages/VenuePage/Layout/ConfigurationCard/index.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
+import { useTranslation } from 'react-i18next';
+import VenueConfigurations from './VenueConfigurations';
+import VenueResources from './VenueResources';
+import Card from 'components/Card';
+import CardHeader from 'components/Card/CardHeader';
+
+type Props = {
+ id: string;
+};
+
+const VenueConfigurationCard = ({ id }: Props) => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+ {t('configurations.title')}
+ {t('resources.title')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VenueConfigurationCard;
diff --git a/src/pages/VenuePage/Layout/ContactActions.tsx b/src/pages/VenuePage/Layout/ContactActions.tsx
new file mode 100644
index 0000000..da8a9e6
--- /dev/null
+++ b/src/pages/VenuePage/Layout/ContactActions.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import {
+ Box,
+ Button,
+ Center,
+ Flex,
+ IconButton,
+ Popover,
+ PopoverArrow,
+ PopoverBody,
+ PopoverCloseButton,
+ PopoverContent,
+ PopoverFooter,
+ PopoverHeader,
+ PopoverTrigger,
+ Tooltip,
+ useDisclosure,
+ useToast,
+} from '@chakra-ui/react';
+import { useMutation } from '@tanstack/react-query';
+import { MagnifyingGlass, Trash } from 'phosphor-react';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuid } from 'uuid';
+import { AxiosError } from 'models/Axios';
+import { ContactObj } from 'models/Contact';
+import { axiosProv } from 'utils/axiosInstances';
+
+const deleteApi = async (id: string) => axiosProv.delete(`/contact/${id}`).then(() => true);
+
+type Props = {
+ contact: ContactObj;
+ refreshEntity: () => void;
+ openEditModal: (contact: ContactObj) => void;
+};
+
+const ContactActions = ({ contact, refreshEntity, openEditModal }: Props) => {
+ const { t } = useTranslation();
+ const toast = useToast();
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const deleteConfig = useMutation(() => deleteApi(contact.id), {
+ onSuccess: () => {
+ onClose();
+ refreshEntity();
+ toast({
+ id: `contact-delete-success${uuid()}`,
+ title: t('common.success'),
+ description: t('crud.success_delete_obj', {
+ obj: contact.name,
+ }),
+ status: 'success',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ },
+ onError: (e) => {
+ toast({
+ id: 'contact-delete-error',
+ title: t('common.error'),
+ description: t('crud.error_delete_obj', {
+ obj: contact.name,
+ e: (e as AxiosError)?.response?.data?.ErrorDescription,
+ }),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ },
+ });
+
+ const handleDeleteClick = () => deleteConfig.mutateAsync();
+ const handleOpenEdit = () => openEditModal(contact);
+
+ return (
+
+
+
+
+
+ } size="sm" />
+
+
+
+
+
+
+
+ {t('crud.delete')} {contact.name}
+
+ {t('crud.delete_confirm', { obj: t('contacts.one') })}
+
+
+
+ {t('common.cancel')}
+
+
+ Yes
+
+
+
+
+
+
+ }
+ size="sm"
+ onClick={handleOpenEdit}
+ />
+
+
+ );
+};
+
+export default ContactActions;
diff --git a/src/pages/EntityPage/EntityChildrenCard/EntityDeviceTableWrapper/Actions.tsx b/src/pages/VenuePage/Layout/InventoryCard/Actions.tsx
similarity index 94%
rename from src/pages/EntityPage/EntityChildrenCard/EntityDeviceTableWrapper/Actions.tsx
rename to src/pages/VenuePage/Layout/InventoryCard/Actions.tsx
index fc57db3..0340e29 100644
--- a/src/pages/EntityPage/EntityChildrenCard/EntityDeviceTableWrapper/Actions.tsx
+++ b/src/pages/VenuePage/Layout/InventoryCard/Actions.tsx
@@ -32,16 +32,14 @@ interface Props {
onOpenUpgradeModal: (serialNumber: string) => void;
}
-const Actions = (
- {
- cell: { original: tag },
- refreshEntity,
- openEditModal,
- onOpenScan,
- onOpenFactoryReset,
- onOpenUpgradeModal
- }: Props
-) => {
+const VenueInventoryActions = ({
+ cell: { original: tag },
+ refreshEntity,
+ openEditModal,
+ onOpenScan,
+ onOpenFactoryReset,
+ onOpenUpgradeModal,
+}: Props) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const { data: gwUi } = useGetGatewayUi();
@@ -115,4 +113,4 @@ const Actions = (
);
};
-export default Actions;
+export default VenueInventoryActions;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueDeviceTableWrapper/index.tsx b/src/pages/VenuePage/Layout/InventoryCard/index.tsx
similarity index 63%
rename from src/pages/VenuePage/VenueChildrenCard/VenueDeviceTableWrapper/index.tsx
rename to src/pages/VenuePage/Layout/InventoryCard/index.tsx
index cd876cc..1583eeb 100644
--- a/src/pages/VenuePage/VenueChildrenCard/VenueDeviceTableWrapper/index.tsx
+++ b/src/pages/VenuePage/Layout/InventoryCard/index.tsx
@@ -1,9 +1,10 @@
-import React, { useCallback, useState } from 'react';
-import { Flex, Heading, Spacer, useDisclosure } from '@chakra-ui/react';
+import * as React from 'react';
+import { Box, Heading, Spacer, useDisclosure } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
-import { v4 as uuid } from 'uuid';
-import Actions from './Actions';
+import VenueInventoryActions from './Actions';
+import Card from 'components/Card';
+import CardHeader from 'components/Card/CardHeader';
import FactoryResetModal from 'components/Modals/SubscriberDevice/FactoryResetModal';
import FirmwareUpgradeModal from 'components/Modals/SubscriberDevice/FirmwareUpgradeModal';
import WifiScanModal from 'components/Modals/SubscriberDevice/WifiScanModal';
@@ -13,24 +14,24 @@ import CreateTagModal from 'components/Tables/InventoryTable/CreateTagModal';
import EditTagModal from 'components/Tables/InventoryTable/EditTagModal';
import ImportDeviceCsvModal from 'components/Tables/InventoryTable/ImportDeviceCsvModal';
import { usePushConfig } from 'hooks/Network/Inventory';
+import { useGetVenue } from 'hooks/Network/Venues';
import { Device } from 'models/Device';
-import { Venue } from 'models/Venue';
-interface Props {
- venue?: Venue;
-}
+type Props = {
+ id: string;
+};
-const VenueDeviceTableWrapper = ({ venue = undefined }: Props) => {
+const VenueInventoryCard = ({ id }: Props) => {
const { t } = useTranslation();
+ const getVenue = useGetVenue({ id });
const queryClient = useQueryClient();
- const [tag, setTag] = useState(undefined);
- const [serialNumber, setSerialNumber] = useState('');
- const [refreshId, setRefreshId] = useState(0);
+ const [tag, setTag] = React.useState(undefined);
+ const [serialNumber, setSerialNumber] = React.useState('');
+ const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
+ const { isOpen: isPushOpen, onOpen: openPush, onClose: closePush } = useDisclosure();
const scanModalProps = useDisclosure();
const resetModalProps = useDisclosure();
const upgradeModalProps = useDisclosure();
- const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
- const { isOpen: isPushOpen, onOpen: openPush, onClose: closePush } = useDisclosure();
const pushConfiguration = usePushConfig({ onSuccess: () => openPush() });
const onOpenScan = (serial: string) => {
@@ -50,40 +51,43 @@ const VenueDeviceTableWrapper = ({ venue = undefined }: Props) => {
openEdit();
};
- const refreshEntity = () => queryClient.invalidateQueries(['get-venue', venue?.id ?? '']);
-
- const refetchTags = () => setRefreshId(refreshId + 1);
-
- const actions = useCallback(
- (cell) => (
- (
+
),
- [refreshId],
+ [],
);
+ const refetchTags = React.useCallback(() => {
+ getVenue.refetch();
+ queryClient.invalidateQueries(['get-inventory-with-select']);
+ }, []);
+
return (
- <>
-
- {t('devices.title')}
+
+
+
+ {t('inventory.title')}
+
-
-
-
-
+
+
+
+
+
+
{
- >
+
);
};
-export default VenueDeviceTableWrapper;
+export default VenueInventoryCard;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/Actions.tsx b/src/pages/VenuePage/Layout/UseContactExistingModal/Actions.tsx
similarity index 81%
rename from src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/Actions.tsx
rename to src/pages/VenuePage/Layout/UseContactExistingModal/Actions.tsx
index 808179f..9ef6a12 100644
--- a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/Actions.tsx
+++ b/src/pages/VenuePage/Layout/UseContactExistingModal/Actions.tsx
@@ -2,23 +2,17 @@ import React from 'react';
import { Flex, IconButton, Tooltip } from '@chakra-ui/react';
import { Plus } from 'phosphor-react';
import { useTranslation } from 'react-i18next';
-import { Contact } from 'models/Contact';
+import { ContactObj } from 'models/Contact';
interface Props {
cell: {
- original: Contact;
+ original: ContactObj;
};
claimContact: (contactId: string) => void;
isDisabled: boolean;
}
-const Actions = (
- {
- cell: { original: contact },
- claimContact,
- isDisabled
- }: Props
-) => {
+const Actions: React.FC = ({ cell: { original: contact }, claimContact, isDisabled }) => {
const { t } = useTranslation();
const handleOpenEdit = () => claimContact(contact.id);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/index.tsx b/src/pages/VenuePage/Layout/UseContactExistingModal/index.tsx
similarity index 87%
rename from src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/index.tsx
rename to src/pages/VenuePage/Layout/UseContactExistingModal/index.tsx
index 7fd7ac3..a2e479a 100644
--- a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/UseExistingModal/index.tsx
+++ b/src/pages/VenuePage/Layout/UseContactExistingModal/index.tsx
@@ -8,24 +8,25 @@ import CloseButton from 'components/Buttons/CloseButton';
import ModalHeader from 'components/Modals/ModalHeader';
import ContactTable from 'components/Tables/ContactTable';
import { useGetEntity } from 'hooks/Network/Entity';
-import { Venue } from 'models/Venue';
+import { ContactObj } from 'models/Contact';
+import { VenueApiResponse } from 'models/Venue';
interface Props {
- venue: Venue;
+ venue?: VenueApiResponse;
onAssignContact: (contactId: string) => void;
}
const UseExistingContactModal = ({ onAssignContact, venue }: Props) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
- const { data: entity } = useGetEntity({ id: venue.entity });
+ const { data: entity } = useGetEntity({ id: venue?.entity });
const claimContact = async (id: string) => {
await onAssignContact(id);
onClose();
};
const actions = useCallback(
- (cell) => (
+ (cell: { row: { original: ContactObj } }) => (
{
select={entity?.contacts ?? []}
actions={actions}
ignoredColumns={['entity', 'venue']}
- disabledIds={venue.contacts}
+ disabledIds={venue?.contacts ?? []}
/>
diff --git a/src/pages/VenuePage/Layout/VenueContacts.tsx b/src/pages/VenuePage/Layout/VenueContacts.tsx
new file mode 100644
index 0000000..464029f
--- /dev/null
+++ b/src/pages/VenuePage/Layout/VenueContacts.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import { Box, Heading, Spacer, useDisclosure } from '@chakra-ui/react';
+import { useQueryClient } from '@tanstack/react-query';
+import { useTranslation } from 'react-i18next';
+import ContactActions from './ContactActions';
+import UseExistingContactModal from './UseContactExistingModal';
+import Card from 'components/Card';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import ContactTable from 'components/Tables/ContactTable';
+import CreateContactModal from 'components/Tables/ContactTable/CreateContactModal';
+import EditContactModal from 'components/Tables/ContactTable/EditContactModal';
+import { useAddVenueContact, useGetVenue } from 'hooks/Network/Venues';
+import { ContactObj } from 'models/Contact';
+
+type Props = {
+ id: string;
+};
+
+const VenueContacts = ({ id }: Props) => {
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+ const getVenue = useGetVenue({ id });
+ const [contact, setContact] = React.useState();
+ const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
+ const { mutateAsync: addContact } = useAddVenueContact({ id, originalContacts: getVenue.data?.contacts });
+
+ const openEditModal = (newContact: ContactObj) => {
+ setContact(newContact);
+ openEdit();
+ };
+
+ const refetchContacts = () => {
+ queryClient.invalidateQueries(['get-contacts-select']);
+ };
+
+ const onContactCreate = async (newContactId: string) => {
+ await addContact(newContactId);
+ refetchContacts();
+ };
+
+ const actions = React.useCallback(
+ (cell: { row: { original: ContactObj } }) => (
+
+ ),
+ [],
+ );
+
+ return (
+
+
+ {t('contacts.other')}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VenueContacts;
diff --git a/src/pages/VenuePage/Layout/VenueDetails.tsx b/src/pages/VenuePage/Layout/VenueDetails.tsx
new file mode 100644
index 0000000..21627e8
--- /dev/null
+++ b/src/pages/VenuePage/Layout/VenueDetails.tsx
@@ -0,0 +1,177 @@
+import * as React from 'react';
+import {
+ Box,
+ Center,
+ FormControl,
+ FormLabel,
+ Grid,
+ GridItem,
+ HStack,
+ Heading,
+ SimpleGrid,
+ Spacer,
+ Spinner,
+ useBoolean,
+ useToast,
+} from '@chakra-ui/react';
+import { Form, Formik } from 'formik';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuid } from 'uuid';
+import SaveButton from 'components/Buttons/SaveButton';
+import ToggleEditButton from 'components/Buttons/ToggleEditButton';
+import Card from 'components/Card';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import LocationPickerCreator from 'components/CreateObjectsForms/LocationPickerCreator';
+import DeviceRulesField from 'components/CustomFields/DeviceRulesField';
+import IpDetectionModalField from 'components/CustomFields/IpDetectionModalField';
+import FormattedDate from 'components/FormattedDate';
+import StringField from 'components/FormFields/StringField';
+import { VenueSchema } from 'constants/formSchemas';
+import { useGetVenue, useUpdateVenue } from 'hooks/Network/Venues';
+import useFormRef from 'hooks/useFormRef';
+import { AxiosError } from 'models/Axios';
+import { VenueApiResponse } from 'models/Venue';
+
+type Props = {
+ id: string;
+};
+
+const VenueDetails = ({ id }: Props) => {
+ const { t } = useTranslation();
+ const toast = useToast();
+ const [formKey, setFormKey] = React.useState(uuid());
+ const getVenue = useGetVenue({ id });
+ const [editing, setEditing] = useBoolean();
+ const { form, formRef } = useFormRef();
+ const updateVenue = useUpdateVenue({ id });
+
+ React.useEffect(() => {
+ setFormKey(uuid());
+ }, [editing]);
+
+ return (
+
+
+
+ {t('common.details')}
+
+
+
+
+
+
+
+
+
+
+ {getVenue.data ? (
+
+ updateVenue.mutateAsync(
+ {
+ params: {
+ name,
+ description,
+ deviceRules,
+ sourceIP,
+ location: location === 'CREATE_NEW' ? undefined : location,
+ },
+ createObjects: __createLocation ? { objects: [{ location: __createLocation }] } : undefined,
+ },
+ {
+ onSuccess: () => {
+ setSubmitting(false);
+ toast({
+ id: 'venue-update-success',
+ title: t('common.success'),
+ description: t('crud.success_update_obj', {
+ obj: t('venues.one'),
+ }),
+ status: 'success',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ resetForm();
+ setEditing.off();
+ },
+ onError: (e) => {
+ toast({
+ id: uuid(),
+ title: t('common.error'),
+ description: t('crud.error_update_obj', {
+ obj: t('venues.one'),
+ e: (e as AxiosError)?.response?.data?.ErrorDescription,
+ }),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ setSubmitting(false);
+ },
+ },
+ )
+ }
+ >
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+ );
+};
+
+export default VenueDetails;
diff --git a/src/pages/VenuePage/Layout/VenueNotes.tsx b/src/pages/VenuePage/Layout/VenueNotes.tsx
new file mode 100644
index 0000000..06a9de1
--- /dev/null
+++ b/src/pages/VenuePage/Layout/VenueNotes.tsx
@@ -0,0 +1,175 @@
+import * as React from 'react';
+import {
+ Box,
+ Button,
+ Center,
+ Heading,
+ IconButton,
+ Popover,
+ PopoverArrow,
+ PopoverBody,
+ PopoverCloseButton,
+ PopoverContent,
+ PopoverHeader,
+ PopoverTrigger,
+ Spacer,
+ Text,
+ Textarea,
+ useBreakpoint,
+ useToast,
+} from '@chakra-ui/react';
+import { Plus } from 'phosphor-react';
+import { useTranslation } from 'react-i18next';
+import Card from 'components/Card';
+import CardBody from 'components/Card/CardBody';
+import CardHeader from 'components/Card/CardHeader';
+import DataTable from 'components/DataTable';
+import FormattedDate from 'components/FormattedDate';
+import { useGetVenue, useUpdateVenue } from 'hooks/Network/Venues';
+import { Note } from 'models/Note';
+import { Column } from 'models/Table';
+
+const VenueNotes = ({ id }: { id: string }) => {
+ const { t } = useTranslation();
+ const getVenue = useGetVenue({ id });
+ const [newNote, setNewNote] = React.useState('');
+ const updateVenue = useUpdateVenue({ id });
+ const toast = useToast();
+ const breakpoint = useBreakpoint();
+
+ const onNoteChange = React.useCallback((e: React.ChangeEvent) => {
+ setNewNote(e.target.value);
+ }, []);
+
+ const onNoteSubmit = React.useCallback(
+ (onClose: () => void) => () => {
+ updateVenue.mutateAsync(
+ {
+ params: { id, notes: [{ note: newNote, created: 0 }] },
+ },
+ {
+ onSuccess: () => {
+ toast({
+ id: 'entity-update-success',
+ title: t('common.success'),
+ description: t('venues.update_success'),
+ status: 'success',
+ duration: 5000,
+ isClosable: true,
+ position: 'top-right',
+ });
+ onClose();
+ setNewNote('');
+ },
+ },
+ );
+ },
+ [newNote],
+ );
+
+ const notes = React.useMemo(
+ () => getVenue.data?.notes?.sort(({ created: a }, { created: b }) => b - a) ?? [],
+ [getVenue.data, getVenue.data?.notes],
+ );
+
+ const dateCell = React.useCallback((created: number) => , []);
+ const noteCell = React.useCallback(
+ (note: string) => (
+
+ {note}
+
+ ),
+ [],
+ );
+
+ const columns: Column[] = React.useMemo(
+ () => [
+ {
+ id: 'created',
+ Header: t('common.date'),
+ Footer: '',
+ accessor: 'created',
+ Cell: ({ cell }: { cell: { row: { original: { created: number } } } }) => dateCell(cell.row.original.created),
+ customWidth: '150px',
+ },
+ {
+ id: 'note',
+ Header: t('common.note'),
+ Cell: ({ cell }: { cell: { row: { original: { note: string } } } }) => noteCell(cell.row.original.note),
+ Footer: '',
+ accessor: 'note',
+ },
+ {
+ id: 'by',
+ Header: t('common.by'),
+ Footer: '',
+ accessor: 'createdBy',
+ customWidth: '200px',
+ },
+ ],
+ [dateCell],
+ );
+ return (
+
+
+
+ {t('common.notes')}
+
+
+
+ {({ onClose }) => (
+ <>
+
+ }
+ colorScheme="blue"
+ />
+
+
+
+
+ {t('profile.add_new_note')}
+
+
+
+
+
+
+ {t('crud.add')}
+
+
+
+
+ >
+ )}
+
+
+
+
+ []}
+ data={notes}
+ obj={t('common.notes')}
+ sortBy={[
+ {
+ id: 'created',
+ desc: true,
+ },
+ ]}
+ minHeight="200px"
+ hideControls
+ showAllRows
+ />
+
+
+
+ );
+};
+
+export default VenueNotes;
diff --git a/src/pages/VenuePage/Layout/index.tsx b/src/pages/VenuePage/Layout/index.tsx
new file mode 100644
index 0000000..9889a79
--- /dev/null
+++ b/src/pages/VenuePage/Layout/index.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import Masonry from 'react-masonry-css';
+import VenueAnalyticsCard from './AnalyticsCard';
+import VenueConfigurationCard from './ConfigurationCard';
+import VenueInventoryCard from './InventoryCard';
+import VenueContactsCard from './VenueContacts';
+import VenueDetails from './VenueDetails';
+import VenueNotes from './VenueNotes';
+import { axiosAnalytics, axiosSec } from 'utils/axiosInstances';
+
+type Props = {
+ id: string;
+};
+
+const VenuePageLayout = ({ id }: Props) => {
+ const isAnalyticsAvailable = axiosSec.defaults.baseURL !== axiosAnalytics.defaults.baseURL;
+
+ return (
+
+
+ {isAnalyticsAvailable && }
+
+
+
+
+
+ );
+};
+
+export default VenuePageLayout;
diff --git a/src/pages/VenuePage/VenueCard/Form.jsx b/src/pages/VenuePage/VenueCard/Form.jsx
deleted file mode 100644
index 767407c..0000000
--- a/src/pages/VenuePage/VenueCard/Form.jsx
+++ /dev/null
@@ -1,344 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { useToast, Tabs, TabList, TabPanels, TabPanel, Tab, SimpleGrid, Box } from '@chakra-ui/react';
-import { useQueryClient } from '@tanstack/react-query';
-import { Formik, Field, Form } from 'formik';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { v4 as uuid } from 'uuid';
-import VenueAnalytics from './VenueAnalytics';
-import LocationPickerCreator from 'components/CreateObjectsForms/LocationPickerCreator';
-import DeviceRulesField from 'components/CustomFields/DeviceRulesField';
-import IpDetectionModalField from 'components/CustomFields/IpDetectionModalField';
-import NotesTable from 'components/CustomFields/NotesTable';
-import FormattedDate from 'components/FormattedDate';
-import StringField from 'components/FormFields/StringField';
-import { VenueSchema } from 'constants/formSchemas';
-import { EntityShape } from 'constants/propShapes';
-import { useAuth } from 'contexts/AuthProvider';
-import { useCreateAnalyticsBoard, useDeleteAnalyticsBoard, useUpdateAnalyticsBoard } from 'hooks/Network/Analytics';
-import { useUpdateVenue } from 'hooks/Network/Venues';
-
-const propTypes = {
- editing: PropTypes.bool.isRequired,
- venue: PropTypes.shape(EntityShape).isRequired,
- formRef: PropTypes.instanceOf(Object).isRequired,
- stopEditing: PropTypes.func.isRequired,
- board: PropTypes.shape({
- name: PropTypes.string.isRequired,
- venueList: PropTypes.arrayOf(
- PropTypes.shape({
- interval: PropTypes.number.isRequired,
- retention: PropTypes.number.isRequired,
- monitorSubVenues: PropTypes.bool.isRequired,
- }),
- ).isRequired,
- }),
-};
-
-const defaultProps = {
- board: null,
-};
-
-const EditVenueForm = ({ editing, venue, formRef, stopEditing, board }) => {
- const { t } = useTranslation();
- const toast = useToast();
- const { endpoints } = useAuth();
- const [tabIndex, setTabIndex] = useState(0);
- const [formKey, setFormKey] = useState(uuid());
- const queryClient = useQueryClient();
- const updateVenue = useUpdateVenue({ id: venue.id });
- const createAnalytics = useCreateAnalyticsBoard();
- const updateAnalytics = useUpdateAnalyticsBoard();
- const deleteAnalytics = useDeleteAnalyticsBoard();
-
- const handleTabsChange = (index) => {
- setTabIndex(index);
- };
-
- useEffect(() => {
- setFormKey(uuid());
- }, [editing]);
-
- return (
- {
- const updateVenueWithInfo = (boards) =>
- updateVenue.mutateAsync(
- {
- params: {
- name,
- description,
- deviceRules,
- sourceIP,
- location: location === 'CREATE_NEW' ? undefined : location,
- boards,
- notes: notes.filter((note) => note.isNew),
- },
- createObjects: __createLocation ? { objects: [{ location: __createLocation }] } : undefined,
- },
- {
- onSuccess: ({ data }) => {
- setSubmitting(false);
- toast({
- id: 'venue-update-success',
- title: t('common.success'),
- description: t('crud.success_update_obj', {
- obj: t('venues.one'),
- }),
- status: 'success',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- queryClient.setQueryData(['get-venue', venue.id], data);
- queryClient.invalidateQueries(['get-entity-tree']);
- queryClient.invalidateQueries(['get-all-locations']);
- resetForm();
- stopEditing();
- },
- onError: (e) => {
- toast({
- id: uuid(),
- title: t('common.error'),
- description: t('crud.error_update_obj', {
- obj: t('venues.one'),
- e: e?.response?.data?.ErrorDescription,
- }),
- status: 'error',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- setSubmitting(false);
- },
- },
- );
-
- if (__BOARD) {
- if (venue.boards.length > 0 && venue.boards[0] !== '') {
- updateAnalytics.mutateAsync(
- {
- name: __BOARD.name,
- venueList: [
- {
- id: venue.id,
- name,
- retention: __BOARD.retention,
- interval: __BOARD.interval,
- monitorSubVenues: __BOARD.monitorSubVenues,
- },
- ],
- id: venue.boards[0],
- },
- {
- onSuccess: () => {
- updateVenueWithInfo();
- queryClient.invalidateQueries(['get-board', venue.boards[0]]);
- },
- onError: (e) => {
- if (e?.response?.status === 404) {
- createAnalytics.mutateAsync(
- {
- name: __BOARD.name,
- venueList: [
- {
- id: venue.id,
- name,
- retention: __BOARD.retention,
- interval: __BOARD.interval,
- monitorSubVenues: __BOARD.monitorSubVenues,
- },
- ],
- },
- {
- onSuccess: ({ data: boardData }) => {
- if (boardData && boardData.id && boardData.id.length > 0) updateVenueWithInfo([boardData.id]);
- },
- onError: (createError) => {
- toast({
- id: uuid(),
- title: t('common.error'),
- description: t('crud.error_create_obj', {
- obj: t('analytics.board'),
- e: createError?.response?.data?.ErrorDescription,
- }),
- status: 'error',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- setSubmitting(false);
- },
- },
- );
- } else {
- toast({
- id: uuid(),
- title: t('common.error'),
- description: t('crud.error_update_obj', {
- obj: t('analytics.board'),
- e: e?.response?.data?.ErrorDescription,
- }),
- status: 'error',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- setSubmitting(false);
- }
- },
- },
- );
- } else {
- createAnalytics.mutateAsync(
- {
- name: __BOARD.name,
- venueList: [
- {
- id: venue.id,
- name,
- retention: __BOARD.retention,
- interval: __BOARD.interval,
- monitorSubVenues: __BOARD.monitorSubVenues,
- },
- ],
- },
- {
- onSuccess: ({ data: boardData }) => {
- if (boardData && boardData.id && boardData.id.length > 0) updateVenueWithInfo([boardData.id]);
- },
- onError: (e) => {
- toast({
- id: uuid(),
- title: t('common.error'),
- description: t('crud.error_create_obj', {
- obj: t('analytics.board'),
- e: e?.response?.data?.ErrorDescription,
- }),
- status: 'error',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- setSubmitting(false);
- },
- },
- );
- }
- } else if (venue.boards.length > 0) {
- deleteAnalytics.mutateAsync(venue.boards[0], {
- onSuccess: () => updateVenueWithInfo([]),
- onError: (e) => {
- toast({
- id: uuid(),
- title: t('common.error'),
- description: t('crud.error_delete_obj', {
- obj: t('analytics.board'),
- e: e?.response?.data?.ErrorDescription,
- }),
- status: 'error',
- duration: 5000,
- isClosable: true,
- position: 'top-right',
- });
- setSubmitting(false);
- },
- });
- } else {
- updateVenueWithInfo();
- }
- }}
- >
- {({ errors, touched, setFieldValue }) => (
-
-
- {t('common.main')}
- {t('common.notes')}
- {endpoints.owanalytics && {t('analytics.title')}}
-
-
-
-
-
-
-
- {({ field }) => }
-
-
- {endpoints.owanalytics && (
-
-
-
- )}
-
-
- )}
-
- );
-};
-
-EditVenueForm.propTypes = propTypes;
-EditVenueForm.defaultProps = defaultProps;
-
-export default EditVenueForm;
diff --git a/src/pages/VenuePage/VenueCard/VenueAnalytics/index.jsx b/src/pages/VenuePage/VenueCard/VenueAnalytics/index.jsx
deleted file mode 100644
index 62a06e9..0000000
--- a/src/pages/VenuePage/VenueCard/VenueAnalytics/index.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { useField } from 'formik';
-import PropTypes from 'prop-types';
-import AnalyticsBoardForm from 'components/CustomFields/AnalyticsBoardForm';
-
-const propTypes = {
- editing: PropTypes.bool.isRequired,
- venueName: PropTypes.string.isRequired,
-};
-
-const VenueAnalytics = ({ editing, venueName }) => {
- const [{ value: boards }] = useField('boards');
-
- return 0 ? boards[0] : ''} editing={editing} objName={venueName} />;
-};
-
-VenueAnalytics.propTypes = propTypes;
-export default React.memo(VenueAnalytics);
diff --git a/src/pages/VenuePage/VenueCard/index.tsx b/src/pages/VenuePage/VenueCard/index.tsx
deleted file mode 100644
index 3a71bc6..0000000
--- a/src/pages/VenuePage/VenueCard/index.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import { Box, Center, Heading, Spacer, Spinner, useBoolean } from '@chakra-ui/react';
-import VenueActions from './Actions';
-import DeleteVenuePopover from './DeleteVenuePopover';
-import EditVenueForm from './Form';
-import RefreshButton from 'components/Buttons/RefreshButton';
-import SaveButton from 'components/Buttons/SaveButton';
-import ToggleEditButton from 'components/Buttons/ToggleEditButton';
-import Card from 'components/Card';
-import CardBody from 'components/Card/CardBody';
-import CardHeader from 'components/Card/CardHeader';
-import LoadingOverlay from 'components/LoadingOverlay';
-import { useAuth } from 'contexts/AuthProvider';
-import { useGetAnalyticsBoard } from 'hooks/Network/Analytics';
-import { useGetVenue } from 'hooks/Network/Venues';
-import useFormRef from 'hooks/useFormRef';
-
-const VenueCard = ({ id }: { id: string }) => {
- const { endpoints } = useAuth();
- const [editing, setEditing] = useBoolean();
- const { data: venue, refetch, isFetching } = useGetVenue({ id });
- const { data: board, isFetching: isFetchingBoard } = useGetAnalyticsBoard({
- id: endpoints?.owanalytics && venue?.boards.length > 0 ? venue.boards[0] : null,
- });
- const { form, formRef } = useFormRef();
-
- return (
-
-
-
- {venue?.name}
-
-
-
-
-
-
-
-
-
-
-
- {(!venue && isFetching) || (!board && isFetchingBoard) ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- );
-};
-
-export default React.memo(VenueCard);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/Actions.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/Actions.jsx
deleted file mode 100644
index 022b8d1..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/Actions.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-import { Flex, IconButton, Tooltip } from '@chakra-ui/react';
-import { MagnifyingGlass } from 'phosphor-react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-
-const propTypes = {
- cell: PropTypes.shape({
- original: PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- }).isRequired,
- }).isRequired,
-};
-
-const Actions = ({ cell: { original: venue } }) => {
- const { t } = useTranslation();
- const navigate = useNavigate();
-
- const handleGoToPage = () => navigate(`/venue/${venue.id}`);
-
- return (
-
-
- } size="sm" onClick={handleGoToPage} />
-
-
- );
-};
-
-Actions.propTypes = propTypes;
-
-export default Actions;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/index.jsx
deleted file mode 100644
index 4bfd330..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueChildrenTableWrapper/index.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, { useCallback } from 'react';
-import { Box } from '@chakra-ui/react';
-import PropTypes from 'prop-types';
-import { v4 as uuid } from 'uuid';
-import Actions from './Actions';
-import VenueTable from 'components/Tables/VenueTable';
-import CreateVenueModal from 'components/Tables/VenueTable/CreateVenueModal';
-import { EntityShape } from 'constants/propShapes';
-
-const propTypes = {
- venue: PropTypes.shape(EntityShape),
-};
-
-const defaultProps = {
- venue: null,
-};
-
-const VenueChildrenTableWrapper = ({ venue }) => {
- const actions = useCallback((cell) => , []);
-
- return (
- <>
-
-
-
-
- >
- );
-};
-VenueChildrenTableWrapper.propTypes = propTypes;
-VenueChildrenTableWrapper.defaultProps = defaultProps;
-export default VenueChildrenTableWrapper;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueClientLifecycle/index.tsx b/src/pages/VenuePage/VenueChildrenCard/VenueClientLifecycle/index.tsx
deleted file mode 100644
index dd5b1e5..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueClientLifecycle/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { useState } from 'react';
-import { Box } from '@chakra-ui/react';
-import MacSearchBar from './MacSearchBar';
-import ClientLifecyleTable from './Table';
-import LoadingOverlay from 'components/LoadingOverlay';
-import { getAllClients } from 'hooks/Network/Analytics';
-import useDatePickers from 'hooks/useDatePickers';
-import { getHoursAgo } from 'utils/dateFormatting';
-
-interface Props {
- venueId: string;
-}
-
-const VenueClientLifecycle = ({ venueId }: Props) => {
- const [macs, setMacs] = useState();
- const [mac, setMac] = useState();
- const { start, end, timepickers, refreshId } = useDatePickers({ defaultStart: getHoursAgo(5 * 24) });
-
- const getMacs = React.useCallback(async () => {
- try {
- const newMacs = await getAllClients(venueId);
- return newMacs;
- } catch (e) {
- return undefined;
- }
- }, [venueId]);
-
- React.useEffect(() => {
- getMacs().then((res) => setMacs(res));
- }, [getMacs]);
-
- return (
-
-
- }
- />
-
-
- );
-};
-
-export default VenueClientLifecycle;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueConfigurationsTableWrapper/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueConfigurationsTableWrapper/index.jsx
deleted file mode 100644
index 2c6bc66..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueConfigurationsTableWrapper/index.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { Box, useDisclosure } from '@chakra-ui/react';
-import { useQueryClient } from '@tanstack/react-query';
-import PropTypes from 'prop-types';
-import { v4 as uuid } from 'uuid';
-import Actions from './Actions';
-import ConfigurationInUseModal from 'components/Modals/Configuration/ConfigurationInUseModal';
-import ConfigurationsTable from 'components/Tables/ConfigurationTable';
-import ConfigurationViewAffectedModal from 'components/Tables/ConfigurationTable/ConfigurationViewAffectedModal';
-import CreateConfigurationModal from 'components/Tables/ConfigurationTable/CreateConfigurationModal';
-import { EntityShape } from 'constants/propShapes';
-
-const propTypes = {
- venue: PropTypes.shape(EntityShape),
-};
-const defaultProps = {
- venue: null,
-};
-
-const VenueConfigurationsTableWrapper = ({ venue }) => {
- const [config, setConfig] = useState(null);
- const queryClient = useQueryClient();
- const { isOpen: isInUseOpen, onOpen: openInUse, onClose: closeInUse } = useDisclosure();
- const { isOpen: isAffectedOpen, onOpen: openAffected, onClose: closeAffected } = useDisclosure();
- const openInUseModal = (newConf) => {
- setConfig(newConf);
- openInUse();
- };
- const openAffectedModal = (newConf) => {
- setConfig(newConf);
- openAffected();
- };
-
- const actions = useCallback(
- (cell) => (
-
- ),
- [],
- );
-
- const refresh = () => queryClient.invalidateQueries(['get-venue', venue.id]);
- return (
- <>
-
-
-
-
-
-
- >
- );
-};
-VenueConfigurationsTableWrapper.propTypes = propTypes;
-VenueConfigurationsTableWrapper.defaultProps = defaultProps;
-export default VenueConfigurationsTableWrapper;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/Actions.tsx b/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/Actions.tsx
deleted file mode 100644
index dae361d..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/Actions.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import React from 'react';
-import {
- Box,
- Button,
- Center,
- Flex,
- IconButton,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverCloseButton,
- PopoverContent,
- PopoverFooter,
- PopoverHeader,
- PopoverTrigger,
- Tooltip,
- useDisclosure,
-} from '@chakra-ui/react';
-import { MagnifyingGlass, Trash } from 'phosphor-react';
-import { useTranslation } from 'react-i18next';
-import { useRemoveVenueContact } from 'hooks/Network/Venues';
-import { Contact } from 'models/Contact';
-
-interface Props {
- cell: {
- original: Contact;
- };
- refreshEntity: () => void;
- openEditModal: (contact: Contact) => void;
- originalContacts: string[];
- venueId: string;
-}
-
-const Actions = (
- {
- cell: { original: contact },
- refreshEntity,
- openEditModal,
- originalContacts,
- venueId
- }: Props
-) => {
- const { t } = useTranslation();
- const { isOpen, onOpen, onClose } = useDisclosure();
- const { mutateAsync: removeContactClaim, isLoading: isRemoving } = useRemoveVenueContact({
- id: venueId,
- originalContacts,
- refresh: refreshEntity,
- });
-
- const handleDeleteClick = () => {
- removeContactClaim(contact.id);
- };
- const handleOpenEdit = () => openEditModal(contact);
-
- return (
-
-
-
-
-
- } size="sm" />
-
-
-
-
-
-
-
- {t('common.remove')} {contact.name}
-
-
- {t('venues.confirm_remove_contact')}
-
-
-
-
- {t('common.cancel')}
-
-
- Yes
-
-
-
-
-
-
- }
- size="sm"
- onClick={handleOpenEdit}
- />
-
-
- );
-};
-
-export default Actions;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/index.tsx b/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/index.tsx
deleted file mode 100644
index 1f691fa..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueContactTableWrapper/index.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { Box, useDisclosure } from '@chakra-ui/react';
-import { useQueryClient } from '@tanstack/react-query';
-import { v4 as uuid } from 'uuid';
-import Actions from './Actions';
-import UseExistingContactModal from './UseExistingModal';
-import ContactTable from 'components/Tables/ContactTable';
-import CreateContactModal from 'components/Tables/ContactTable/CreateContactModal';
-import EditContactModal from 'components/Tables/ContactTable/EditContactModal';
-import { useAddVenueContact } from 'hooks/Network/Venues';
-import { Contact } from 'models/Contact';
-import { Venue } from 'models/Venue';
-
-interface Props {
- venue?: Venue;
-}
-
-const VenueContactTableWrapper = ({ venue }: Props) => {
- const queryClient = useQueryClient();
- const [contact, setContact] = useState(undefined);
- const [refreshId, setRefreshId] = useState(0);
- const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
- const { mutateAsync: addContact } = useAddVenueContact({ id: venue?.id ?? '', originalContacts: venue?.contacts });
-
- const openEditModal = (newContact: Contact) => {
- setContact(newContact);
- openEdit();
- };
-
- const refreshEntity = () => queryClient.invalidateQueries(['get-venue', venue?.id ?? '']);
-
- const refetchContacts = () => {
- setRefreshId(refreshId + 1);
- refreshEntity();
- };
-
- const onContactCreate = async (newContactId: string) => {
- await addContact(newContactId);
- refetchContacts();
- };
-
- const actions = useCallback(
- (cell) => (
-
- ),
- [refreshId, venue, addContact],
- );
-
- return (
- <>
-
- {venue && (
- <>
-
-
- >
- )}
-
-
-
- >
- );
-};
-
-export default VenueContactTableWrapper;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleComponent/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleComponent/index.jsx
deleted file mode 100644
index e0f1824..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleComponent/index.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React, { useMemo } from 'react';
-import PropTypes from 'prop-types';
-import AssociationCircle from './AssociationCircle';
-import DeviceCircle from './DeviceCircle';
-import RadioCircle from './RadioCircle';
-import SsidCircle from './SsidCircle';
-import VenueCircle from './VenueCircle';
-
-const propTypes = {
- node: PropTypes.instanceOf(Object).isRequired,
- style: PropTypes.instanceOf(Object).isRequired,
- onClick: PropTypes.func.isRequired,
-};
-
-const CircleComponent = ({ node, style, onClick }) => {
- const handleClicks = useMemo(
- () => ({
- onClick: (e) => {
- onClick(node, e);
- },
- }),
- [onClick, node],
- );
- if (node.data.type === 'association')
- return ;
- if (node.data.type === 'ssid') return ;
- if (node.data.type === 'radio') return ;
- if (node.data.type === 'device') return ;
- if (node.data.type === 'venue') return ;
- return null;
-};
-
-CircleComponent.propTypes = propTypes;
-export default CircleComponent;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleLabel.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleLabel.jsx
deleted file mode 100644
index 71ee947..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/CircleLabel.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import { animated } from '@react-spring/web';
-import PropTypes from 'prop-types';
-
-const propTypes = {
- label: PropTypes.string.isRequired,
- node: PropTypes.shape({
- id: PropTypes.string.isRequired,
- }).isRequired,
- style: PropTypes.shape({
- x: PropTypes.instanceOf(Object).isRequired,
- y: PropTypes.instanceOf(Object).isRequired,
- textColor: PropTypes.instanceOf(Object).isRequired,
- opacity: PropTypes.instanceOf(Object).isRequired,
- }).isRequired,
-};
-
-const CircleLabel = ({ label, node, style }) => (
-
- {label.split('/')[0]}
-
-);
-
-CircleLabel.propTypes = propTypes;
-export default React.memo(CircleLabel);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/InfoButton.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/InfoButton.jsx
deleted file mode 100644
index 38ba54b..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/InfoButton.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import {
- Box,
- Heading,
- IconButton,
- Popover,
- PopoverArrow,
- PopoverBody,
- PopoverCloseButton,
- PopoverContent,
- PopoverHeader,
- PopoverTrigger,
- Portal,
-} from '@chakra-ui/react';
-import { Question } from 'phosphor-react';
-import { useTranslation } from 'react-i18next';
-import { useCircleGraph } from 'contexts/CircleGraphProvider';
-
-const CirclePackInfoButton = () => {
- const { t } = useTranslation();
- const { popoverRef } = useCircleGraph();
-
- return (
-
-
-
- } size="sm" colorScheme="blue" />
-
-
-
-
-
- {t('analytics.live_view_help')}
-
- {t('analytics.live_view_explanation_one')}
-
- {t('analytics.live_view_explanation_two')}
-
- {t('analytics.live_view_explanation_three')}
-
- {t('analytics.live_view_explanation_four')}
-
-
- {t('analytics.live_view_explanation_five')}
-
-
-
-
-
-
- );
-};
-
-export default React.memo(CirclePackInfoButton);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/index.jsx
deleted file mode 100644
index fdd1a78..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/CirclePack/index.jsx
+++ /dev/null
@@ -1,329 +0,0 @@
-import React, { useEffect, useMemo, useState } from 'react';
-import { Box, Center, Heading, useColorMode } from '@chakra-ui/react';
-import { ResponsiveCirclePacking } from '@nivo/circle-packing';
-import { patternLinesDef } from '@nivo/core';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import { v4 as uuid } from 'uuid';
-import CircleComponent from './CircleComponent';
-import CircleLabel from './CircleLabel';
-import CirclePackInfoButton from './InfoButton';
-import CirclePackSlider from './Slider';
-import { useCircleGraph } from 'contexts/CircleGraphProvider';
-import { getScaledArray } from 'utils/arrayHelpers';
-import { errorColor, getBlendedColor, successColor, warningColor } from 'utils/colors';
-import { parseDbm } from 'utils/stringHelper';
-
-const theme = {
- labels: {
- text: {
- background: 'black',
- },
- background: 'black',
- },
-};
-const getFill = [
- {
- match: (d) => d.data.type === 'association' && d.data.details.rssi >= -45,
- id: 'assoc_success',
- },
- {
- match: (d) => d.data.type === 'association' && d.data.details.rssi >= -60,
- id: 'assoc_warning',
- },
- {
- match: (d) => d.data.type === 'association' && d.data.details.rssi < -60,
- id: 'assoc_danger',
- },
-];
-
-const getLabelsFilter = (label) => label.node.height === 0;
-
-const labelTextColor = {
- from: 'color',
- modifiers: [['darker', 4]],
-};
-
-const margin = { top: 20, right: 20, bottom: 20, left: 20 };
-
-const propTypes = {
- timepoints: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired,
- handle: PropTypes.shape({
- enter: PropTypes.func.isRequired,
- active: PropTypes.bool.isRequired,
- }).isRequired,
- venue: PropTypes.instanceOf(Object).isRequired,
-};
-
-const CirclePack = ({ timepoints, handle, venue }) => {
- const { t } = useTranslation();
- const { popoverRef } = useCircleGraph();
- const { colorMode } = useColorMode();
- const [pointIndex, setPointIndex] = useState(Math.max(timepoints.length - 1, 0));
- const [zoomedId, setZoomedId] = useState(null);
-
- const data = useMemo(() => {
- if (!timepoints || timepoints.length === 0 || !timepoints[pointIndex]) return null;
-
- const root = {
- name: venue.name,
- details: {
- avgHealth: 0,
- },
- type: 'venue',
- children: [],
- scale: 1,
- };
-
- let totalHealth = 0;
- const allBandwidth = [];
-
- for (const {
- device_info: deviceInfo,
- ssid_data: ssidData = [],
- radio_data: radioData = [],
- ap_data: apData,
- } of timepoints[pointIndex]) {
- totalHealth += deviceInfo.health;
-
- const finalDevice = {
- name: `${deviceInfo.serialNumber}/device/${uuid()}`,
- type: 'device',
- details: {
- deviceInfo,
- ssidData,
- apData,
- },
- scale: 1,
- children: [],
- };
-
- if (deviceInfo.health >= 90) {
- finalDevice.details.color = successColor(colorMode);
- finalDevice.details.tagColor = 'green';
- } else if (deviceInfo.health >= 70) {
- finalDevice.details.color = warningColor(colorMode);
- finalDevice.details.tagColor = 'yellow';
- } else {
- finalDevice.details.color = errorColor(colorMode);
- finalDevice.details.tagColor = 'red';
- }
-
- const radioChannelIndex = {};
-
- for (const [i, { band, transmit_pct: transmitPct, ...radioDetails }] of radioData.entries()) {
- let tagColor = 'green';
- if (transmitPct > 30) tagColor = 'yellow';
- else if (transmitPct > 50) tagColor = 'red';
- const finalRadio = {
- name: `${band}/radio/${uuid()}`,
- type: 'radio',
- details: {
- band,
- transmitPct,
- ...radioDetails,
- color: getBlendedColor('#0ba057', '#FD3049', transmitPct / 100),
- tagColor,
- },
- children: [],
- };
- radioChannelIndex[band] = i;
- finalDevice.children.push(finalRadio);
- }
-
- for (const { ssid, associations, ...ssidDetails } of ssidData) {
- const finalSsid = {
- name: `${ssid}/ssid/${uuid()}`,
- type: 'ssid',
- details: {
- ssid,
- associations,
- ...ssidDetails,
- },
- children: [],
- scale: 1,
- };
-
- let totalRssi = 0;
- for (const { station, rssi, ...associationDetails } of associations) {
- const bw = associationDetails.tx_bytes_bw + associationDetails.rx_bytes_bw;
- allBandwidth.push(bw);
-
- const finalAssociation = {
- name: `${station}/assoc/${uuid()}`,
- type: 'association',
- details: {
- station,
- rssi: parseDbm(rssi),
- ...associationDetails,
- },
- totalBw: bw,
- scale: 1,
- };
-
- if (rssi >= -45) {
- finalAssociation.details.color = successColor(colorMode);
- finalAssociation.details.tagColor = 'green';
- } else if (rssi >= -60) {
- finalAssociation.details.color = warningColor(colorMode);
- finalAssociation.details.tagColor = 'yellow';
- } else {
- finalAssociation.details.color = errorColor(colorMode);
- finalAssociation.details.tagColor = 'red';
- }
-
- totalRssi += rssi;
- finalSsid.children.push(finalAssociation);
- }
- finalSsid.details.avgRssi =
- associations.length === 0 ? undefined : parseDbm(Math.floor(totalRssi / Math.max(associations.length, 1)));
- if (associations.length === 0 || finalSsid.details.avgRssi >= -45) {
- finalSsid.details.color = successColor(colorMode);
- finalSsid.details.tagColor = 'green';
- } else if (finalSsid.details.avgRssi >= -60) {
- finalSsid.details.color = warningColor(colorMode);
- finalSsid.details.tagColor = 'yellow';
- } else {
- finalSsid.details.color = errorColor(colorMode);
- finalSsid.details.tagColor = 'red';
- }
- finalDevice.children[radioChannelIndex[ssidDetails.band]].children.push(finalSsid);
- }
- root.children.push(finalDevice);
- }
-
- if (allBandwidth.length > 0) {
- const scaledArray = getScaledArray(allBandwidth, 1, 30);
- const bandwidthObj = {};
- for (const [i, bw] of allBandwidth.entries()) {
- bandwidthObj[bw] = scaledArray[i];
- }
-
- for (const [deviceIndex, device] of root.children.entries()) {
- for (const [radioIndex, radio] of device.children.entries()) {
- for (const [ssidIndex, ssid] of radio.children.entries()) {
- for (const [assocIndex, assoc] of ssid.children.entries()) {
- root.children[deviceIndex].children[radioIndex].children[ssidIndex].children[assocIndex].scale =
- bandwidthObj[assoc.totalBw];
- }
- }
- }
- }
- }
-
- root.details.avgHealth = Math.floor(totalHealth / Math.max(timepoints[pointIndex].length, 1));
- if (root.details.avgHealth >= 90) {
- root.details.color = successColor(colorMode);
- root.details.tagColor = 'green';
- } else if (root.details.avgHealth >= 70) {
- root.details.color = warningColor(colorMode);
- root.details.tagColor = 'yellow';
- } else {
- root.details.color = errorColor(colorMode);
- root.details.tagColor = 'red';
- }
- root.color = '#31e88a';
-
- return root;
- }, [timepoints, pointIndex, colorMode]);
-
- const shapeDefs = useMemo(
- () => [
- patternLinesDef(
- 'assoc_success',
- colorMode === 'light'
- ? {
- rotation: -45,
- color: 'var(--chakra-colors-success-400)',
- background: 'var(--chakra-colors-success-600)',
- }
- : {
- rotation: -45,
- color: 'var(--chakra-colors-success-400)',
- background: 'var(--chakra-colors-success-600)',
- },
- ),
- patternLinesDef(
- 'assoc_warning',
- colorMode === 'light'
- ? {
- rotation: -45,
- color: 'var(--chakra-colors-warning-100)',
- background: 'var(--chakra-colors-warning-400)',
- }
- : {
- rotation: -45,
- color: 'var(--chakra-colors-warning-100)',
- background: 'var(--chakra-colors-warning-400)',
- },
- ),
- patternLinesDef(
- 'assoc_danger',
- colorMode === 'light'
- ? {
- rotation: -45,
- color: 'var(--chakra-colors-danger-200)',
- background: 'var(--chakra-colors-danger-400)',
- }
- : {
- rotation: -45,
- color: 'var(--chakra-colors-danger-200)',
- background: 'var(--chakra-colors-danger-400)',
- },
- ),
- ],
- [colorMode],
- );
-
- const handleNodeClick = React.useCallback(
- (node) => {
- setZoomedId(zoomedId === node.id ? null : node.id);
- },
- [zoomedId],
- );
-
- useEffect(() => {
- setPointIndex(timepoints.length - 1);
- }, [timepoints]);
-
- return (
-
- {timepoints.length > 0 && }
-
- {data === null ? (
-
- {t('common.no_records_found')}
-
- ) : (
- <>
-
-
- >
- )}
-
-
- );
-};
-
-CirclePack.propTypes = propTypes;
-export default React.memo(CirclePack);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/ExpandButton.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/ExpandButton.jsx
deleted file mode 100644
index 7a8bc44..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/ExpandButton.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import { Button, IconButton, Tooltip, useBreakpoint } from '@chakra-ui/react';
-import { ArrowsIn, ArrowsOut } from 'phosphor-react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-
-const propTypes = {
- isDisabled: PropTypes.bool.isRequired,
- handle: PropTypes.shape({
- enter: PropTypes.func.isRequired,
- exit: PropTypes.func.isRequired,
- active: PropTypes.bool.isRequired,
- }).isRequired,
-};
-
-const CirclePackExpandButton = ({ isDisabled, handle }) => {
- const { t } = useTranslation();
- const breakpoint = useBreakpoint();
-
- const handleClick = () => (handle.active ? handle.exit() : handle.enter());
- const icon = () => (handle.active ? : );
-
- if (breakpoint !== 'base' && breakpoint !== 'sm') {
- return (
-
- {handle.active ? t('common.exit_fullscreen') : t('common.fullscreen')}
-
- );
- }
- return (
-
-
-
- );
-};
-
-CirclePackExpandButton.propTypes = propTypes;
-export default React.memo(CirclePackExpandButton);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/TimePickers.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/TimePickers.jsx
deleted file mode 100644
index 5692e83..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/TimePickers.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { Flex, Heading } from '@chakra-ui/react';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
-import DateTimePicker from 'components/DatePickers/DateTimePicker';
-
-const propTypes = {
- start: PropTypes.instanceOf(Date).isRequired,
- end: PropTypes.instanceOf(Date).isRequired,
- setStart: PropTypes.func.isRequired,
- setEnd: PropTypes.func.isRequired,
- isDisabled: PropTypes.bool.isRequired,
-};
-
-const CirclePackTimePickers = ({ start, end, setStart, setEnd, isDisabled }) => {
- const { t } = useTranslation();
-
- return (
-
-
-
- {t('common.start')}:
-
-
-
-
-
- {t('common.end')}:
-
-
-
-
- );
-};
-
-CirclePackTimePickers.propTypes = propTypes;
-export default React.memo(CirclePackTimePickers);
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/index.jsx
deleted file mode 100644
index 6c6f917..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueLiveView/index.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import React, { useState } from 'react';
-import {
- Alert,
- AlertDescription,
- AlertIcon,
- AlertTitle,
- Box,
- Center,
- Flex,
- Spacer,
- Spinner,
- useColorModeValue,
-} from '@chakra-ui/react';
-import PropTypes from 'prop-types';
-import { FullScreen, useFullScreenHandle } from 'react-full-screen';
-import { useTranslation } from 'react-i18next';
-import CirclePack from './CirclePack';
-import ExpandButton from './ExpandButton';
-import CirclePackTimePickers from './TimePickers';
-import RefreshButton from 'components/Buttons/RefreshButton';
-import LoadingOverlay from 'components/LoadingOverlay';
-import { CircleGraphProvider } from 'contexts/CircleGraphProvider';
-import { useGetAnalyticsBoardTimepoints } from 'hooks/Network/Analytics';
-import { getHoursAgo } from 'utils/dateFormatting';
-
-const propTypes = {
- boardId: PropTypes.string.isRequired,
- venue: PropTypes.instanceOf(Object).isRequired,
-};
-
-const VenueLiveView = ({ boardId, venue }) => {
- const { t } = useTranslation();
- const handle = useFullScreenHandle();
- const color = useColorModeValue('gray.50', 'gray.800');
- const [startTime, setStartTime] = useState(getHoursAgo(1));
- const [endTime, setEndTime] = useState(new Date());
- const {
- data: timepoints,
- isFetching,
- refetch,
- error,
- } = useGetAnalyticsBoardTimepoints({ id: boardId, startTime, endTime });
-
- if (error)
- return (
-
-
-
-
- {t('common.error')}
-
- {error.response?.status === 404 ? t('analytics.missing_board') : error.response?.data?.ErrorDescription}
-
-
-
-
- );
-
- return !timepoints ? (
-
-
-
- ) : (
-
-
-
-
-
-
-
-
-
-
-
- {timepoints && }
-
-
-
-
-
- );
-};
-
-VenueLiveView.propTypes = propTypes;
-export default VenueLiveView;
diff --git a/src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/index.jsx b/src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/index.jsx
deleted file mode 100644
index e922de9..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/VenueResourcesTableWrapper/index.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { Box, useDisclosure } from '@chakra-ui/react';
-import { useQueryClient } from '@tanstack/react-query';
-import PropTypes from 'prop-types';
-import { v4 as uuid } from 'uuid';
-import Actions from './Actions';
-import CreateResourceModal from 'components/Modals/Resources/CreateModal';
-import EditResourceModal from 'components/Modals/Resources/EditModal';
-import ResourceTable from 'components/Tables/ResourceTable';
-import { EntityShape } from 'constants/propShapes';
-
-const propTypes = {
- venue: PropTypes.shape(EntityShape).isRequired,
-};
-
-const VenueResourcesTableWrapper = ({ venue }) => {
- const queryClient = useQueryClient();
- const [resource, setResource] = useState(null);
- const [refreshId, setRefreshId] = useState(0);
- const { isOpen: isEditOpen, onOpen: openEdit, onClose: closeEdit } = useDisclosure();
-
- const openEditModal = (newResource) => {
- setResource(newResource);
- openEdit();
- };
-
- const refreshEntity = () => queryClient.invalidateQueries(['get-venue', venue.id]);
-
- const refreshTable = () => {
- setRefreshId(refreshId + 1);
- };
-
- const actions = useCallback(
- (cell) => ,
- [refreshId],
- );
-
- return (
- <>
-
-
-
-
-
- >
- );
-};
-
-VenueResourcesTableWrapper.propTypes = propTypes;
-export default VenueResourcesTableWrapper;
diff --git a/src/pages/VenuePage/VenueChildrenCard/index.tsx b/src/pages/VenuePage/VenueChildrenCard/index.tsx
deleted file mode 100644
index fc35fb8..0000000
--- a/src/pages/VenuePage/VenueChildrenCard/index.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import React, { useMemo } from 'react';
-import { Center, Spinner, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
-import { useTranslation } from 'react-i18next';
-import VenueChildrenTableWrapper from './VenueChildrenTableWrapper';
-import VenueClientLifecycle from './VenueClientLifecycle';
-import VenueConfigurationsTableWrapper from './VenueConfigurationsTableWrapper';
-import VenueContactTableWrapper from './VenueContactTableWrapper';
-import VenueDashboard from './VenueDashboard';
-import VenueDeviceTableWrapper from './VenueDeviceTableWrapper';
-import VenueLiveView from './VenueLiveView';
-import VenueResourcesTableWrapper from './VenueResourcesTableWrapper';
-import Card from 'components/Card';
-import CardBody from 'components/Card/CardBody';
-import LoadingOverlay from 'components/LoadingOverlay';
-import { useAuth } from 'contexts/AuthProvider';
-import { useGetVenue } from 'hooks/Network/Venues';
-
-const getDefaultIndex = (hasAnalytics: boolean, id: string) => {
- localStorage.getItem('venue.lastActiveIndex');
- const index = parseInt(localStorage.getItem(`venue.${id}.lastActiveIndex`) || '0', 10);
- if (hasAnalytics) {
- return index >= 0 && index <= 7 ? index : 0;
- }
-
- if (index >= 0 && index <= 4) return index;
- return 0;
-};
-
-const VenueChildrenCard = ({ id }: { id: string }) => {
- const { t } = useTranslation();
- const { endpoints } = useAuth();
- const { data: venue, isFetching } = useGetVenue({ id });
- const analyticsActive =
- endpoints?.owanalytics !== undefined &&
- venue?.boards !== undefined &&
- venue?.boards.length > 0 &&
- venue?.boards[0] !== undefined &&
- venue?.boards[0].length > 0;
- const [tabIndex, setTabIndex] = React.useState(getDefaultIndex(analyticsActive, id));
-
- const onTabChange = (index: number) => {
- setTabIndex(index);
- localStorage.setItem(`venue.${id}.lastActiveIndex`, index.toString());
- };
-
- React.useEffect(() => {
- setTabIndex(getDefaultIndex(analyticsActive, id));
- }, [analyticsActive]);
-
- const panels = useMemo(() => {
- if (analyticsActive && venue?.boards[0] !== undefined) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }, [endpoints, venue]);
-
- return (
-
-
-
-
- {analyticsActive && (
- <>
- {t('analytics.dashboard')}
- {t('analytics.live_view')}
- {t('analytics.client_lifecycle')}
- >
- )}
- {t('venues.subvenues')}
- {t('configurations.title')}
- {t('inventory.title')}
- {t('contacts.other')}
- {t('resources.title')}
-
- {!venue && isFetching ? (
-
-
-
- ) : (
- {panels}
- )}
-
-
-
- );
-};
-
-export default VenueChildrenCard;
diff --git a/src/pages/VenuePage/VenueDropdown.tsx b/src/pages/VenuePage/VenueDropdown.tsx
new file mode 100644
index 0000000..5b7813e
--- /dev/null
+++ b/src/pages/VenuePage/VenueDropdown.tsx
@@ -0,0 +1,79 @@
+import * as React from 'react';
+import {
+ Button,
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuDivider,
+ MenuItem,
+ MenuList,
+ Tooltip,
+ useBreakpoint,
+ useDisclosure,
+} from '@chakra-ui/react';
+import { Buildings } from 'phosphor-react';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+import CreateVenueModal from 'components/Tables/VenueTable/CreateVenueModal';
+import { useGetSelectVenues, useGetVenue } from 'hooks/Network/Venues';
+import { Entity } from 'models/Entity';
+
+type Props = {
+ id: string;
+};
+
+const VenueDropdown = ({ id }: Props) => {
+ const { t } = useTranslation();
+ const breakpoint = useBreakpoint();
+ const navigate = useNavigate();
+ const getVenue = useGetVenue({ id });
+ const getChildren = useGetSelectVenues({ select: getVenue.data?.children ?? [] });
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
+ const goToVenue = (venueId: string) => () => navigate(`/venue/${venueId}`);
+
+ const amount = getVenue.data?.children.length ?? 0;
+
+ const isCompact = breakpoint === 'base' || breakpoint === 'sm';
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default VenueDropdown;
diff --git a/src/pages/VenuePage/VenueCard/VenueFirmwareUpgradeModal.tsx b/src/pages/VenuePage/VenueFirmwareUpgradeModal.tsx
similarity index 100%
rename from src/pages/VenuePage/VenueCard/VenueFirmwareUpgradeModal.tsx
rename to src/pages/VenuePage/VenueFirmwareUpgradeModal.tsx
diff --git a/src/pages/VenuePage/VenueHeader.tsx b/src/pages/VenuePage/VenueHeader.tsx
new file mode 100644
index 0000000..26adf19
--- /dev/null
+++ b/src/pages/VenuePage/VenueHeader.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+import { HStack, Heading, Icon, Spacer } from '@chakra-ui/react';
+import { Buildings } from 'phosphor-react';
+import VenueActions from './Actions';
+import DeleteVenuePopover from './DeleteVenuePopover';
+import VenueDropdown from './VenueDropdown';
+import RefreshButton from 'components/Buttons/RefreshButton';
+import Card from 'components/Card';
+import CardHeader from 'components/Card/CardHeader';
+import { useGetVenue } from 'hooks/Network/Venues';
+
+type Props = {
+ id: string;
+};
+
+const VenuePageHeader = ({ id }: Props) => {
+ const getVenue = useGetVenue({ id });
+
+ return (
+
+
+
+
+
+ {getVenue.data?.name}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VenuePageHeader;
diff --git a/src/pages/VenuePage/index.tsx b/src/pages/VenuePage/index.tsx
index b5930ac..0a72b55 100644
--- a/src/pages/VenuePage/index.tsx
+++ b/src/pages/VenuePage/index.tsx
@@ -1,12 +1,9 @@
import React from 'react';
-import { Flex } from '@chakra-ui/react';
import { useParams } from 'react-router-dom';
-import VenueCard from './VenueCard';
-import VenueChildrenCard from './VenueChildrenCard';
-import { useAuth } from 'contexts/AuthProvider';
+import VenuePageLayout from './Layout';
+import VenuePageHeader from './VenueHeader';
const VenuePage = ({ idToUse }: { idToUse?: string }) => {
- const { isUserLoaded } = useAuth();
const { id } = useParams();
const entityIdToUse = React.useMemo(() => {
@@ -20,16 +17,12 @@ const VenuePage = ({ idToUse }: { idToUse?: string }) => {
return undefined;
}, [idToUse, id]);
- return (
-
- {isUserLoaded && entityIdToUse !== undefined && (
- <>
-
-
- >
- )}
-
- );
+ return entityIdToUse ? (
+ <>
+
+
+ >
+ ) : null;
};
export default VenuePage;
diff --git a/src/router/index.tsx b/src/router/index.tsx
index dfbd97c..ea39a65 100644
--- a/src/router/index.tsx
+++ b/src/router/index.tsx
@@ -7,7 +7,7 @@ import { useAuth } from 'contexts/AuthProvider';
const Layout = React.lazy(() => import('layout'));
const Login = React.lazy(() => import('pages/LoginPage'));
-const Router = () => {
+const Router: React.FC = () => {
const { token } = useAuth();
return (
diff --git a/src/router/routes.tsx b/src/router/routes.tsx
index 9218063..d62e0b4 100644
--- a/src/router/routes.tsx
+++ b/src/router/routes.tsx
@@ -1,14 +1,15 @@
import React from 'react';
import { Icon } from '@chakra-ui/react';
import { Info, ListBullets, Storefront, Tag, TreeStructure, UsersThree } from 'phosphor-react';
+import EntityNavButton from 'layout/Sidebar/EntityNavButton';
import { Route } from 'models/Routes';
-const AccountPage = React.lazy(() => import('pages/Profile'));
const ConfigurationPage = React.lazy(() => import('pages/ConfigurationPage'));
const EntityPage = React.lazy(() => import('pages/EntityPage'));
const InventoryPage = React.lazy(() => import('pages/InventoryPage'));
-const MapPage = React.lazy(() => import('pages/MapPage'));
const NotificationsPage = React.lazy(() => import('pages/Notifications'));
+const MapPage = React.lazy(() => import('pages/MapPage'));
+const ProfilePage = React.lazy(() => import('pages/Profile'));
const OperatorPage = React.lazy(() => import('pages/OperatorPage'));
const OperatorsPage = React.lazy(() => import('pages/OperatorsPage'));
const SubscriberPage = React.lazy(() => import('pages/SubscriberPage'));
@@ -25,6 +26,9 @@ const routes: Route[] = [
icon: (active: boolean) => (
),
+ navButton: (isActive: boolean, toggleSidebar: () => void, route: Route) => (
+
+ ),
isEntity: true,
component: EntityPage,
},
@@ -93,7 +97,7 @@ const routes: Route[] = [
icon: (active: boolean) => (
),
- component: AccountPage,
+ component: ProfilePage,
},
{
hidden: true,
diff --git a/src/theme/additions/layout/MainPanel.ts b/src/theme/additions/layout/MainPanel.ts
deleted file mode 100644
index 12535a1..0000000
--- a/src/theme/additions/layout/MainPanel.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-const MainPanel = {
- baseStyle: {
- float: 'right',
- maxWidth: '100%',
- overflow: 'auto',
- position: 'relative',
- maxHeight: '100%',
- transition: 'all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)',
- transitionDuration: '.2s, .2s, .35s',
- transitionProperty: 'top, bottom, width',
- transitionTimingFunction: 'linear, linear, ease',
- },
- variants: {
- main: () => ({
- float: 'right',
- }),
- },
- defaultProps: {
- variant: 'main',
- },
-};
-
-export default {
- components: {
- MainPanel,
- },
-};
diff --git a/src/theme/additions/layout/PanelContainer.ts b/src/theme/additions/layout/PanelContainer.ts
deleted file mode 100644
index aea81e6..0000000
--- a/src/theme/additions/layout/PanelContainer.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const PanelContainer = {
- baseStyle: {
- p: '30px 10px 0px',
- minHeight: 'calc(100vh - 123px)',
- },
-};
-
-export default {
- components: {
- PanelContainer,
- },
-};
diff --git a/src/theme/additions/layout/PanelContent.ts b/src/theme/additions/layout/PanelContent.ts
deleted file mode 100644
index 8f1acb4..0000000
--- a/src/theme/additions/layout/PanelContent.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-const PanelContent = {
- baseStyle: {
- ms: 'auto',
- me: 'auto',
- ps: '15px',
- pe: '15px',
- },
-};
-
-export default {
- components: {
- PanelContent,
- },
-};
diff --git a/src/theme/components/alert.ts b/src/theme/components/alert.ts
index 659d074..3e192f9 100644
--- a/src/theme/components/alert.ts
+++ b/src/theme/components/alert.ts
@@ -2,7 +2,7 @@ export default {
components: {
Alert: {
baseStyle: {
- borderRadius: '10px',
+ borderRadius: '15px',
},
},
},
diff --git a/src/theme/theme.ts b/src/theme/theme.ts
index 9ccd330..09e3bb9 100644
--- a/src/theme/theme.ts
+++ b/src/theme/theme.ts
@@ -1,10 +1,7 @@
-import { Tooltip, extendTheme } from '@chakra-ui/react';
+import { extendTheme, Tooltip, type ThemeConfig } from '@chakra-ui/react';
import CardComponent from './additions/card/Card';
import CardBodyComponent from './additions/card/CardBody';
import CardHeaderComponent from './additions/card/CardHeader';
-import MainPanelComponent from './additions/layout/MainPanel';
-import PanelContainerComponent from './additions/layout/PanelContainer';
-import PanelContentComponent from './additions/layout/PanelContent';
import alertStyles from './components/alert';
import badgeStyles from './components/badge';
import buttonStyles from './components/button';
@@ -32,9 +29,6 @@ const theme = extendTheme({
Card: CardComponent.components.Card,
CardBody: CardBodyComponent.components.CardBody,
CardHeader: CardHeaderComponent.components.CardHeader,
- MainPanel: MainPanelComponent.components.MainPanel,
- PanelContent: PanelContentComponent.components.PanelContent,
- PanelContainer: PanelContainerComponent.components.PanelContainer,
},
});
diff --git a/src/utils/colors.ts b/src/utils/colors.ts
index 53ec7f4..2627743 100644
--- a/src/utils/colors.ts
+++ b/src/utils/colors.ts
@@ -5,9 +5,9 @@ export const warningColor = (colorMode = 'light') =>
export const errorColor = (colorMode = 'light') =>
colorMode === 'light' ? 'var(--chakra-colors-danger-400)' : 'var(--chakra-colors-danger-400)';
-const mix = (start: number, end: number, percent: number) => start + percent * (end - start);
+const getMixedColors = (start: number, end: number, percent: number) => start + percent * (end - start);
-const generateHex = (red: number, green: number, blue: number) => {
+const getHexFromRGB = (red: number, green: number, blue: number) => {
let r = red.toString(16);
let g = green.toString(16);
let b = blue.toString(16);
@@ -25,22 +25,23 @@ const generateHex = (red: number, green: number, blue: number) => {
return `#${r}${g}${b}`;
};
-export const getBlendedColor = (
- color1: [string, string, string, string, string, string, string],
- color2: [string, string, string, string, string, string, string],
- percent: number,
-) => {
- const red1 = parseInt(color1[1] + color1[2], 16);
- const green1 = parseInt(color1[3] + color1[4], 16);
- const blue1 = parseInt(color1[5] + color1[6], 16);
+const getColorChar = (str: string, index: number) => str[index] ?? '0';
- const red2 = parseInt(color2[1] + color2[2], 16);
- const green2 = parseInt(color2[3] + color2[4], 16);
- const blue2 = parseInt(color2[5] + color2[6], 16);
+export const getBlendedColor = (color1: string, color2: string, percent: number) => {
+ if (color1.length >= 7 && color2.length >= 7) {
+ const red1 = parseInt(getColorChar(color1, 1) + getColorChar(color1, 2), 16);
+ const green1 = parseInt(getColorChar(color1, 3) + getColorChar(color1, 4), 16);
+ const blue1 = parseInt(getColorChar(color1, 5) + getColorChar(color1, 6), 16);
- const red = Math.round(mix(red1, red2, percent));
- const green = Math.round(mix(green1, green2, percent));
- const blue = Math.round(mix(blue1, blue2, percent));
+ const red2 = parseInt(getColorChar(color2, 1) + getColorChar(color2, 2), 16);
+ const green2 = parseInt(getColorChar(color2, 3) + getColorChar(color2, 4), 16);
+ const blue2 = parseInt(getColorChar(color2, 5) + getColorChar(color2, 6), 16);
- return generateHex(red, green, blue);
+ const red = Math.round(getMixedColors(red1, red2, percent));
+ const green = Math.round(getMixedColors(green1, green2, percent));
+ const blue = Math.round(getMixedColors(blue1, blue2, percent));
+
+ return getHexFromRGB(red, green, blue);
+ }
+ return color1;
};