From 526f0525b9772c3e30ed7237049c5f55b0e4b092 Mon Sep 17 00:00:00 2001 From: Kiryl Koniukh <49252228+Kasmadei@users.noreply.github.com> Date: Tue, 14 May 2024 20:59:01 +0200 Subject: [PATCH] Improvements for global select --- public/locales/cs/translation.json | 6 +- public/locales/en/translation.json | 6 +- src/components/appBar/AppBar.styles.tsx | 14 ++- src/components/appBar/AppBar.tsx | 98 ++++++++++--------- .../FaultTreeAndSystemOverviewCardsList.tsx | 6 +- .../list/FaultTreeAndSystemOverviewTable.tsx | 27 ++++- src/components/dialog/system/SystemDialog.tsx | 5 +- src/components/editor/faultTree/Editor.tsx | 4 +- src/components/editor/system/Editor.tsx | 4 +- src/components/navigation/Navigation.tsx | 6 +- src/contexts/AppBarContext.tsx | 87 ++++++++++++++++ src/contexts/AppBarTitleContext.tsx | 49 ---------- 12 files changed, 201 insertions(+), 111 deletions(-) create mode 100644 src/contexts/AppBarContext.tsx delete mode 100644 src/contexts/AppBarTitleContext.tsx diff --git a/public/locales/cs/translation.json b/public/locales/cs/translation.json index 1f2bde0d..5d76a222 100644 --- a/public/locales/cs/translation.json +++ b/public/locales/cs/translation.json @@ -71,7 +71,7 @@ }, "faultTreeOverviewTable": { "name": "Jméno", - "aircraftType": "Typ letadla", + "aircraftType": "Typ systému", "ata": "ATA", "calculatedFailureRate": "Vypočtená intenzita poruch", "fhaBasedFailureRate": "Intenzita poruch založená na FHA", @@ -92,5 +92,9 @@ }, "appBar": { "selectSystemPlaceholder": "Vyberte systém" + }, + "common": { + "defaultErrorMsg": "Došlo k neočekávané chybě", + "delete": "Smazat" } } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 8d96e181..9fd7206d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -71,7 +71,7 @@ }, "faultTreeOverviewTable": { "name": "Name", - "aircraftType": "Aircraft type", + "aircraftType": "System type", "ata": "ATA", "calculatedFailureRate": "Calculated failure rate", "fhaBasedFailureRate": "FHA based failure rate", @@ -92,5 +92,9 @@ }, "appBar": { "selectSystemPlaceholder": "Select system" + }, + "common": { + "defaultErrorMsg": "Unexpected error occurred", + "delete": "Delete" } } diff --git a/src/components/appBar/AppBar.styles.tsx b/src/components/appBar/AppBar.styles.tsx index 3772e2c0..c5d4e943 100644 --- a/src/components/appBar/AppBar.styles.tsx +++ b/src/components/appBar/AppBar.styles.tsx @@ -28,8 +28,8 @@ const useStyles = makeStyles()((theme: Theme) => ({ minWidth: 120, }, textfieldSelect: { + marginRight: 8, minWidth: 160, - marginRight: 16, "& .MuiSelect-icon": { color: theme.button.secondary, }, @@ -64,6 +64,18 @@ const useStyles = makeStyles()((theme: Theme) => ({ left: 12, pointerEvents: "none", }, + dropdownContainer: { + display: "flex", + flexDirection: "row", + alignItems: "center", + }, + tooltipContainer: { + height: 24, + width: 24, + }, + tooltip: { + cursor: "pointer", + }, })); // TODO jss-to-tss-react codemod: usages of this hook outside of this file will not be converted. diff --git a/src/components/appBar/AppBar.tsx b/src/components/appBar/AppBar.tsx index 4263672a..e1e3893c 100644 --- a/src/components/appBar/AppBar.tsx +++ b/src/components/appBar/AppBar.tsx @@ -1,10 +1,20 @@ -import Toolbar from "@mui/material/Toolbar"; -import IconButton from "@mui/material/IconButton"; -import Typography from "@mui/material/Typography"; import * as React from "react"; +import { useEffect } from "react"; import useStyles from "@components/appBar/AppBar.styles"; import { AccountCircle } from "@mui/icons-material"; -import { Menu, MenuItem, AppBar as MaterialAppBar, Box, FormControl, InputLabel, TextField } from "@mui/material"; +import { + Menu, + MenuItem, + AppBar as MaterialAppBar, + Box, + FormControl, + InputLabel, + TextField, + IconButton, + Typography, + Toolbar, + Tooltip, +} from "@mui/material"; import { FormEvent, useState } from "react"; import { useNavigate } from "react-router-dom"; import ChangePasswordDialog from "@components/dialog/password/ChangePasswordDialog"; @@ -14,11 +24,9 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useTranslation } from "react-i18next"; import LanguageIcon from "@mui/icons-material/Language"; import { PRIMARY_LANGUAGE, SECONDARY_LANGUAGE, SELECTED_LANGUAGE_KEY, SELECTED_SYSTEM } from "@utils/constants"; -import { useAppBarTitle } from "../../contexts/AppBarTitleContext"; -import * as systemService from "@services/systemService"; -import { System } from "@models/systemModel"; -import { SnackbarType, useSnackbar } from "@hooks/useSnackbar"; -import { axiosSource } from "@services/utils/axiosUtils"; +import { useAppBar } from "../../contexts/AppBarContext"; +import { useLocation } from "react-router-dom"; +import CancelIcon from "@mui/icons-material/Cancel"; interface Props { title: string; @@ -30,29 +38,20 @@ const AppBar = ({ title, showBackButton = false, topPanelHeight }: Props) => { const [loggedUser] = useLoggedUser(); const { classes } = useStyles(); const history = useNavigate(); - const { i18n } = useTranslation(); - const { t } = useTranslation(); - const { appBarTitle } = useAppBarTitle(); - const [showSnackbar] = useSnackbar(); + const { i18n, t } = useTranslation(); + const location = useLocation(); + const { appBarTitle, systemsList } = useAppBar(); const [anchorEl, setAnchorEl] = React.useState(null); const [selectedSystem, setSelectedSystem] = useState(() => sessionStorage.getItem(SELECTED_SYSTEM) || ""); - const [systems, setSystems] = useState([]); + const [changePasswordDialogOpen, setChangePasswordDialogOpen] = useState(false); const isMenuOpen = Boolean(anchorEl); - React.useEffect(() => { - const fetchSystems = async () => { - systemService - .findAll() - .then((value) => setSystems(value)) - .catch((reason) => showSnackbar(reason, SnackbarType.ERROR)); - }; - - fetchSystems(); - - return () => axiosSource.cancel("SystemsProvider - unmounting"); - }, []); + useEffect(() => { + const currentItem = sessionStorage.getItem(SELECTED_SYSTEM); + if (selectedSystem !== currentItem) setSelectedSystem(currentItem); + }, [location.pathname]); const handleProfileMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -100,8 +99,6 @@ const AppBar = ({ title, showBackButton = false, topPanelHeight }: Props) => { ); - const [changePasswordDialogOpen, setChangePasswordDialogOpen] = useState(false); - const toggleLanguage = () => { if (i18n.resolvedLanguage === PRIMARY_LANGUAGE) { i18n.changeLanguage(SECONDARY_LANGUAGE); @@ -138,22 +135,35 @@ const AppBar = ({ title, showBackButton = false, topPanelHeight }: Props) => { {!selectedSystem && ( {t("appBar.selectSystemPlaceholder")} )} - {systems && ( - - {systems.map((s, i) => { - return ( - - {s.name} - - ); - })} - + {systemsList && ( + + + {systemsList.map((s, i) => { + return ( + + {s.name} + + ); + })} + + + {selectedSystem && ( + setSelectedSystem("")} + > + + + )} + + )} diff --git a/src/components/dashboard/content/list/FaultTreeAndSystemOverviewCardsList.tsx b/src/components/dashboard/content/list/FaultTreeAndSystemOverviewCardsList.tsx index 19fded45..5a260c21 100644 --- a/src/components/dashboard/content/list/FaultTreeAndSystemOverviewCardsList.tsx +++ b/src/components/dashboard/content/list/FaultTreeAndSystemOverviewCardsList.tsx @@ -1,5 +1,5 @@ import React, { FC, useState } from "react"; -import { Box, Grid, Typography, Button } from "@mui/material"; +import { Box, Grid, Typography, Button, useTheme } from "@mui/material"; import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import useStyles from "./FaultTreeOverviewCardsList.styles"; import { useTranslation } from "react-i18next"; @@ -39,17 +39,19 @@ const FaultTreeAndSystemOverviewCardsList: FC = const { classes } = useStyles(); const { t } = useTranslation(); const navigate = useNavigate(); + const theme = useTheme(); const [hoveredIndex, setHoveredIndex] = useState(null); const modifiedSystemsList = getModifiedSystemsList(systems, selectedSystem); const Card: FC = ({ name, onRedirect, onOpenMenu, border, index }) => { + const bgColor = name === selectedSystem ? theme.sidePanel.colors.hover : "transparent"; return ( setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(null)} > diff --git a/src/components/dashboard/content/list/FaultTreeAndSystemOverviewTable.tsx b/src/components/dashboard/content/list/FaultTreeAndSystemOverviewTable.tsx index 6fe34588..add650c7 100644 --- a/src/components/dashboard/content/list/FaultTreeAndSystemOverviewTable.tsx +++ b/src/components/dashboard/content/list/FaultTreeAndSystemOverviewTable.tsx @@ -1,11 +1,11 @@ import React, { FC } from "react"; -import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Button } from "@mui/material"; +import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Button, useTheme } from "@mui/material"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import { useTranslation } from "react-i18next"; import useStyles from "./FaultTreeOverviewTable.styles"; import { FaultTree } from "@models/faultTreeModel"; import { useNavigate } from "react-router-dom"; -import { ROUTES } from "@utils/constants"; +import { ROUTES, SELECTED_SYSTEM } from "@utils/constants"; import { extractFragment } from "@services/utils/uriIdentifierUtils"; import { System } from "@models/systemModel"; import { getModifiedSystemsList, getModifiedFaultTreesList, formatDate } from "@utils/utils"; @@ -42,10 +42,18 @@ const FaultTreeAndSystemOverviewTable: FC = ({ const navigate = useNavigate(); const { classes } = useStyles(); const { t } = useTranslation(); + const theme = useTheme(); const modifiedSystemsList = getModifiedSystemsList(systems, selectedSystem); const modifiedFaultTreesList = getModifiedFaultTreesList(faultTrees, selectedSystem); + const redirectToPath = (routePath: string, systemName?: string) => { + if (systemName) { + sessionStorage.setItem(SELECTED_SYSTEM, systemName); + } + navigate(routePath); + }; + return ( @@ -91,7 +99,11 @@ const FaultTreeAndSystemOverviewTable: FC = ({ {/* */} - = ({ {systems && modifiedSystemsList.map((system, rowIndex) => { const routePath = ROUTES.SYSTEMS + `/${extractFragment(system.iri)}`; + const bgColor = system.name === selectedSystem ? theme.sidePanel.colors.hover : "transparent"; return ( - + {system.name} - { const { t } = useTranslation(); const [, addSystem] = useSystems(); + const { addSystemToList } = useAppBar(); const [processing, setIsProcessing] = useState(false); const useFormMethods = useForm({ resolver: yupResolver(schema) }); @@ -31,8 +33,9 @@ const SystemDialog = ({ open, handleCloseDialog }) => { const system = { name: values.systemName, } as System; - + // TODO: Add correct error handling await addSystem(system); + addSystemToList(system); setIsProcessing(false); handleCloseDialog(); diff --git a/src/components/editor/faultTree/Editor.tsx b/src/components/editor/faultTree/Editor.tsx index 92d53791..1b80e4a3 100644 --- a/src/components/editor/faultTree/Editor.tsx +++ b/src/components/editor/faultTree/Editor.tsx @@ -20,13 +20,13 @@ import { Rectangle } from "@models/utils/Rectangle"; import { JOINTJS_NODE_MODEL } from "@components/editor/faultTree/shapes/constants"; import { calculateCutSets } from "@services/faultTreeService"; import { FaultEventScenario } from "@models/faultEventScenario"; -import { useAppBarTitle } from "../../../contexts/AppBarTitleContext"; +import { useAppBar } from "../../../contexts/AppBarContext"; const Editor = () => { const history = useNavigate(); const [showSnackbar] = useSnackbar(); const [requestConfirmation] = useConfirmDialog(); - const { setAppBarTitle } = useAppBarTitle(); + const { setAppBarTitle } = useAppBar(); const [faultTree, refreshTree] = useCurrentFaultTree(); const [rootEvent, setRootEvent] = useState(); diff --git a/src/components/editor/system/Editor.tsx b/src/components/editor/system/Editor.tsx index 7e45aa25..0d28e211 100644 --- a/src/components/editor/system/Editor.tsx +++ b/src/components/editor/system/Editor.tsx @@ -13,12 +13,12 @@ import * as componentService from "@services/componentService"; import { SnackbarType, useSnackbar } from "@hooks/useSnackbar"; import * as joint from "jointjs"; import { FTABoundary } from "@components/editor/faultTree/shapes/shapesDefinitions"; -import { useAppBarTitle } from "../../../contexts/AppBarTitleContext"; +import { useAppBar } from "../../../contexts/AppBarContext"; const Editor = () => { const [requestConfirmation] = useConfirmDialog(); const [showSnackbar] = useSnackbar(); - const { setAppBarTitle } = useAppBarTitle(); + const { setAppBarTitle } = useAppBar(); const [system, addComponent, updateComponent, removeComponent, fetchSystem] = useCurrentSystem(); diff --git a/src/components/navigation/Navigation.tsx b/src/components/navigation/Navigation.tsx index a88c200c..cfe0394b 100644 --- a/src/components/navigation/Navigation.tsx +++ b/src/components/navigation/Navigation.tsx @@ -6,7 +6,7 @@ import { ROUTES } from "@utils/constants"; import AppBar from "../appBar/AppBar"; import { SIDE_PANEL_STATE_KEY } from "../../utils/constants"; import useStyles from "./Navigation.styles"; -import { AppBarTitleProvider } from "../../contexts/AppBarTitleContext"; +import { AppBarProvider } from "../../contexts/AppBarContext"; import DashboardContentProvider from "@hooks/DashboardContentProvider"; interface SideNavigationProps { @@ -61,7 +61,7 @@ const Navigation: FC = ({ children }) => { const mgLeft = shouldHideSidePanel ? 0 : isCollapsed ? SIDE_PANEL_COLLAPSED_WIDTH : SIDE_PANEL_WIDTH; return ( - + {!shouldHideSidePanel && ( @@ -87,7 +87,7 @@ const Navigation: FC = ({ children }) => { - + ); }; diff --git a/src/contexts/AppBarContext.tsx b/src/contexts/AppBarContext.tsx new file mode 100644 index 00000000..3bc79319 --- /dev/null +++ b/src/contexts/AppBarContext.tsx @@ -0,0 +1,87 @@ +import { ROUTES } from "@utils/constants"; +import React, { createContext, useState, useContext, ReactNode, useEffect } from "react"; +import { useLocation } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { System } from "@models/systemModel"; +import * as systemService from "@services/systemService"; +import { useSnackbar } from "@hooks/useSnackbar"; +import { axiosSource } from "@services/utils/axiosUtils"; + +interface AppBarTitleContextType { + appBarTitle: string; + setAppBarTitle: React.Dispatch>; + systemsList: System[]; + setSystemsList: React.Dispatch>; + addSystemToList: (system: System) => void; +} + +interface AppBarTitleProviderProps { + children: ReactNode; +} + +const categoryTitles = { + [ROUTES.SYSTEMS]: "categories.systems", + [ROUTES.FTA]: "categories.trees", + [ROUTES.FMEA]: "categories.worksheets", + [ROUTES.FHA]: "categories.tables", +}; + +const AppBarMainContext = createContext({ + appBarTitle: "", + setAppBarTitle: () => {}, + systemsList: [], + setSystemsList: () => {}, + addSystemToList: (system: System) => {}, +}); + +export const useAppBar = () => useContext(AppBarMainContext); + +export const AppBarProvider = ({ children }: AppBarTitleProviderProps) => { + const [appBarTitle, setAppBarTitle] = useState(""); + const [systemsList, setSystemsList] = useState([]); + const location = useLocation(); + const { t } = useTranslation(); + const [showSnackbar] = useSnackbar(); + const errorMessage = t("common.defaultErrorMsg"); + + useEffect(() => { + const title = getTitleFromPathname(location.pathname); + if (!title) setAppBarTitle(""); + else { + const translatedTitle = t(`${title}`); + setAppBarTitle(translatedTitle); + } + }, [location.pathname, t]); + + useEffect(() => { + const fetchSystems = async () => { + systemService + .findAll() + .then((value) => { + setSystemsList(value); + }) + .catch((reason) => showSnackbar(reason, errorMessage)); + }; + fetchSystems(); + + return () => axiosSource.cancel("SystemsProvider - unmounting"); + }, []); + + const getTitleFromPathname = (pathname: string): string => { + const parts = pathname.split("/"); + const lastPart = parts.pop(); + const title = categoryTitles[`/${lastPart}`]; + if (title) return title; + return ""; + }; + + const addSystemToList = (system: System) => { + setSystemsList((state) => [...state, system]); + }; + + return ( + + {children} + + ); +}; diff --git a/src/contexts/AppBarTitleContext.tsx b/src/contexts/AppBarTitleContext.tsx deleted file mode 100644 index e2227598..00000000 --- a/src/contexts/AppBarTitleContext.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { ROUTES } from "@utils/constants"; -import React, { createContext, useState, useContext, ReactNode, useEffect } from "react"; -import { useLocation } from "react-router-dom"; -import { useTranslation } from "react-i18next"; - -interface AppBarTitleContextType { - appBarTitle: string; - setAppBarTitle: React.Dispatch>; -} - -interface AppBarTitleProviderProps { - children: ReactNode; -} - -const categoryTitles = { - [ROUTES.SYSTEMS]: "categories.systems", - [ROUTES.FTA]: "categories.trees", - [ROUTES.FMEA]: "categories.worksheets", - [ROUTES.FHA]: "categories.tables", -}; - -const AppBarTitleContext = createContext({ appBarTitle: "", setAppBarTitle: () => {} }); - -export const useAppBarTitle = () => useContext(AppBarTitleContext); - -export const AppBarTitleProvider = ({ children }: AppBarTitleProviderProps) => { - const [appBarTitle, setAppBarTitle] = useState(""); - const location = useLocation(); - const { t } = useTranslation(); - - useEffect(() => { - const title = getTitleFromPathname(location.pathname); - if (!title) setAppBarTitle(""); - else { - const translatedTitle = t(`${title}`); - setAppBarTitle(translatedTitle); - } - }, [location.pathname, t]); - - const getTitleFromPathname = (pathname: string): string => { - const parts = pathname.split("/"); - const lastPart = parts.pop(); - const title = categoryTitles[`/${lastPart}`]; - if (title) return title; - return ""; - }; - - return {children}; -};