From 2a55f3d6802a06e5a39444748e4ffc97eeaf5db1 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 20 Nov 2023 14:53:13 +0530 Subject: [PATCH] feat: improve dashboard view user experience (#3654) * feat: improve dashboard view user experience * chore: dashboard ux is updated * feat: add inter font and set font family in theme configuration --------- Co-authored-by: Palash Gupta --- .../TableComponents/DeleteButton.tsx | 28 +++- .../src/container/ListOfDashboard/index.tsx | 131 ++++++++++++------ .../NewDashboard/DashboardSettings/index.tsx | 14 +- .../TopNav/DateTimeSelection/config.ts | 6 + frontend/src/container/TopNav/index.tsx | 20 ++- frontend/src/hooks/useDarkMode/index.tsx | 3 +- frontend/src/index.html.ejs | 4 +- 7 files changed, 148 insertions(+), 58 deletions(-) diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index b6d51445f8..f6ee603cc2 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,5 +1,5 @@ import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; -import { Modal, Tooltip } from 'antd'; +import { Modal, Tooltip, Typography } from 'antd'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { useDeleteDashboard } from 'hooks/dashboard/useDeleteDashboard'; import { useCallback } from 'react'; @@ -10,10 +10,22 @@ import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; import { USER_ROLES } from 'types/roles'; -import { Data } from '../index'; +import { Data } from '..'; import { TableLinkText } from './styles'; -function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element { +interface DeleteButtonProps { + createdBy: string; + name: string; + id: string; + isLocked: boolean; +} + +function DeleteButton({ + createdBy, + name, + id, + isLocked, +}: DeleteButtonProps): JSX.Element { const [modal, contextHolder] = Modal.useModal(); const { role, user } = useSelector((state) => state.app); const isAuthor = user?.email === createdBy; @@ -26,7 +38,13 @@ function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element { const openConfirmationDialog = useCallback((): void => { modal.confirm({ - title: 'Do you really want to delete this dashboard?', + title: ( + + Are you sure you want to delete the + {name} + dashboard? + + ), icon: , onOk() { deleteDashboardMutation.mutateAsync(undefined, { @@ -39,7 +57,7 @@ function DeleteButton({ id, createdBy, isLocked }: Data): JSX.Element { okButtonProps: { danger: true }, centered: true, }); - }, [modal, deleteDashboardMutation, queryClient]); + }, [modal, name, deleteDashboardMutation, queryClient]); const getDeleteTooltipContent = (): string => { if (isLocked) { diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index a310c5be63..c0db8379f7 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -1,11 +1,12 @@ import { PlusOutlined } from '@ant-design/icons'; import { Card, + Col, Dropdown, + Input, MenuProps, Row, TableColumnProps, - Typography, } from 'antd'; import { ItemType } from 'antd/es/menu/hooks/useItems'; import createDashboard from 'api/dashboard/create'; @@ -18,9 +19,9 @@ import DynamicColumnTable from 'components/ResizeTable/DynamicColumnTable'; import LabelColumn from 'components/TableRenderer/LabelColumn'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; -import SearchFilter from 'container/ListOfDashboard/SearchFilter'; import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; +import useDebouncedFn from 'hooks/useDebouncedFunction'; import history from 'lib/history'; import { Key, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,6 +39,8 @@ import DeleteButton from './TableComponents/DeleteButton'; import Name from './TableComponents/Name'; function ListOfAllDashboard(): JSX.Element { + const { Search } = Input; + const { data: dashboardListResponse = [], isLoading: isDashboardListLoading, @@ -59,12 +62,21 @@ function ListOfAllDashboard(): JSX.Element { ] = useState(false); const [uploadedGrafana, setUploadedGrafana] = useState(false); + const [isFilteringDashboards, setIsFilteringDashboards] = useState(false); - const [filteredDashboards, setFilteredDashboards] = useState(); + const [dashboards, setDashboards] = useState(); + + const sortDashboardsByCreatedAt = (dashboards: Dashboard[]): void => { + const sortedDashboards = dashboards.sort( + (a, b) => + new Date(b.created_at).getTime() - new Date(a.created_at).getTime(), + ); + setDashboards(sortedDashboards); + }; useEffect(() => { if (dashboardListResponse.length) { - setFilteredDashboards(dashboardListResponse); + sortDashboardsByCreatedAt(dashboardListResponse); } }, [dashboardListResponse]); @@ -150,7 +162,7 @@ function ListOfAllDashboard(): JSX.Element { }, [action]); const data: Data[] = - filteredDashboards?.map((e) => ({ + dashboards?.map((e) => ({ createdAt: e.created_at, description: e.data.description || '', id: e.uuid, @@ -255,41 +267,84 @@ function ListOfAllDashboard(): JSX.Element { [getMenuItems], ); + const searchArrayOfObjects = (searchValue: string): any[] => { + // Convert the searchValue to lowercase for case-insensitive search + const searchValueLowerCase = searchValue.toLowerCase(); + + // Use the filter method to find matching objects + return dashboardListResponse.filter((item: any) => { + // Convert each property value to lowercase for case-insensitive search + const itemValues = Object.values(item?.data).map((value: any) => + value.toString().toLowerCase(), + ); + + // Check if any property value contains the searchValue + return itemValues.some((value) => value.includes(searchValueLowerCase)); + }); + }; + + const handleSearch = useDebouncedFn((event: unknown): void => { + setIsFilteringDashboards(true); + const searchText = (event as React.BaseSyntheticEvent)?.target?.value || ''; + const filteredDashboards = searchArrayOfObjects(searchText); + setDashboards(filteredDashboards); + setIsFilteringDashboards(false); + }, 500); + const GetHeader = useMemo( () => ( - - Dashboard List - - - + + - {newDashboard && ( - - } - type="primary" - loading={newDashboardState.loading} - danger={newDashboardState.error} + + + + + + {newDashboard && ( + - {getText()} - - - )} - + } + type="primary" + loading={newDashboardState.loading} + danger={newDashboardState.error} + > + {getText()} + + + )} + + ), [ - newDashboard, + Search, isDashboardListLoading, + handleSearch, + isFilteringDashboards, + newDashboard, menu, newDashboardState.loading, newDashboardState.error, @@ -301,13 +356,6 @@ function ListOfAllDashboard(): JSX.Element { {GetHeader} - {!isDashboardListLoading && ( - - )} - }, - { label: 'Variables', key: 'variables', children: }, -]; - function DashboardSettingsContent(): JSX.Element { + const items = [ + { + label: 'General', + key: 'general', + children: , + }, + { label: 'Variables', key: 'variables', children: }, + ]; + return ; } diff --git a/frontend/src/container/TopNav/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts index 99e4e4f23f..7b51837cce 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelection/config.ts @@ -68,6 +68,12 @@ export const getOptions = (routes: string): Option[] => { return Options; }; +export const routesToHideBreadCrumbs = [ + ROUTES.SUPPORT, + ROUTES.ALL_DASHBOARD, + ROUTES.DASHBOARD, +]; + export const routesToSkip = [ ROUTES.SETTINGS, ROUTES.LIST_ALL_ALERT, diff --git a/frontend/src/container/TopNav/index.tsx b/frontend/src/container/TopNav/index.tsx index 38d163b3f8..6592faa569 100644 --- a/frontend/src/container/TopNav/index.tsx +++ b/frontend/src/container/TopNav/index.tsx @@ -6,7 +6,11 @@ import { matchPath, useHistory } from 'react-router-dom'; import NewExplorerCTA from '../NewExplorerCTA'; import ShowBreadcrumbs from './Breadcrumbs'; import DateTimeSelector from './DateTimeSelection'; -import { routesToDisable, routesToSkip } from './DateTimeSelection/config'; +import { + routesToDisable, + routesToHideBreadCrumbs, + routesToSkip, +} from './DateTimeSelection/config'; import { Container } from './styles'; function TopNav(): JSX.Element | null { @@ -20,6 +24,14 @@ function TopNav(): JSX.Element | null { [location.pathname], ); + const isRouteToHideBreadCrumbs = useMemo( + () => + routesToHideBreadCrumbs.some((route) => + matchPath(location.pathname, { path: route, exact: true }), + ), + [location.pathname], + ); + const isDisabled = useMemo( () => routesToDisable.some((route) => @@ -33,22 +45,20 @@ function TopNav(): JSX.Element | null { [location.pathname], ); - const hideBreadcrumbs = location.pathname === ROUTES.SUPPORT; - if (isSignUpPage || isDisabled) { return null; } return ( - {!hideBreadcrumbs && ( + {!isRouteToHideBreadCrumbs && ( )} {!isRouteToSkip && ( - + diff --git a/frontend/src/hooks/useDarkMode/index.tsx b/frontend/src/hooks/useDarkMode/index.tsx index 01e48cd0bf..069e08b2de 100644 --- a/frontend/src/hooks/useDarkMode/index.tsx +++ b/frontend/src/hooks/useDarkMode/index.tsx @@ -74,7 +74,8 @@ export const useThemeConfig = (): ThemeConfig => { borderRadiusLG: 2, borderRadiusSM: 2, borderRadiusXS: 2, - fontFamily: 'Open Sans', + fontFamily: 'Inter', + fontSize: 13, }, }; }; diff --git a/frontend/src/index.html.ejs b/frontend/src/index.html.ejs index 5351ea65af..d6f1afb64a 100644 --- a/frontend/src/index.html.ejs +++ b/frontend/src/index.html.ejs @@ -62,10 +62,12 @@ href="https://unpkg.com/uplot@1.6.26/dist/uPlot.min.css" /> + +