From 1e653dc0ebf074705c645ac10fbaf6ad9a10ae35 Mon Sep 17 00:00:00 2001 From: Michael Taylor Date: Tue, 2 Apr 2024 13:22:59 -0400 Subject: [PATCH] feat: add glossay page --- .../api/cadt/v1/governance/governance.api.ts | 43 ++++++++++++++ src/renderer/api/cadt/v1/governance/index.ts | 1 + .../components/blocks/layout/LeftNav.tsx | 7 +++ .../blocks/tables/GlossaryTable.tsx | 46 +++++++++++++++ .../components/blocks/tables/index.ts | 3 +- src/renderer/pages/Glossary/GlossaryPage.tsx | 57 +++++++++++++++++++ src/renderer/pages/Glossary/index.ts | 1 + .../pages/ProjectsList/ProjectsList.tsx | 1 - src/renderer/pages/index.ts | 1 + src/renderer/routes/AppNavigator.tsx | 38 ++++--------- src/renderer/routes/route-constants.ts | 3 +- src/renderer/translations/tokens/en-US.json | 5 +- 12 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 src/renderer/api/cadt/v1/governance/governance.api.ts create mode 100644 src/renderer/api/cadt/v1/governance/index.ts create mode 100644 src/renderer/components/blocks/tables/GlossaryTable.tsx create mode 100644 src/renderer/pages/Glossary/GlossaryPage.tsx create mode 100644 src/renderer/pages/Glossary/index.ts diff --git a/src/renderer/api/cadt/v1/governance/governance.api.ts b/src/renderer/api/cadt/v1/governance/governance.api.ts new file mode 100644 index 00000000..39e458aa --- /dev/null +++ b/src/renderer/api/cadt/v1/governance/governance.api.ts @@ -0,0 +1,43 @@ +import { cadtApi } from '../'; +// @ts-ignore +import { BaseQueryResult } from '@reduxjs/toolkit/dist/query/baseQueryTypes'; + +const host: string = 'http://localhost:31310'; + +interface BaseQueryResult { + [key: string]: string[]; +} + +interface DescriptionItem { + header: string; + definition: string; +} + +interface GlossaryItem { + term: string; + description: DescriptionItem[]; +} + +const governanceApi = cadtApi.injectEndpoints({ + endpoints: (builder) => ({ + getGlossary: builder.query({ + query: () => ({ + url: `${host}/v1/governance/meta/glossary`, + method: 'GET', + }), + transformResponse: (baseQueryReturnValue: BaseQueryResult): GlossaryItem[] => { + return Object.entries(baseQueryReturnValue).map(([key, valueArray]) => { + // Map each value in the value array to a DescriptionItem by splitting by ";" to separate header and definition + const description: DescriptionItem[] = valueArray.map((item) => { + const [header, definition] = item.split(';').map((part) => part.trim()); + return { header, definition }; + }); + + return { term: key, description }; + }); + }, + }), + }), +}); + +export const { useGetGlossaryQuery } = governanceApi; diff --git a/src/renderer/api/cadt/v1/governance/index.ts b/src/renderer/api/cadt/v1/governance/index.ts new file mode 100644 index 00000000..ff6ea788 --- /dev/null +++ b/src/renderer/api/cadt/v1/governance/index.ts @@ -0,0 +1 @@ +export * from './governance.api'; \ No newline at end of file diff --git a/src/renderer/components/blocks/layout/LeftNav.tsx b/src/renderer/components/blocks/layout/LeftNav.tsx index 8917305e..e17d341d 100644 --- a/src/renderer/components/blocks/layout/LeftNav.tsx +++ b/src/renderer/components/blocks/layout/LeftNav.tsx @@ -64,6 +64,13 @@ const LeftNav = () => { > + navigate(ROUTES.GLOSSARY)} + > + + {/* Add more Sidebar.Item as needed */} diff --git a/src/renderer/components/blocks/tables/GlossaryTable.tsx b/src/renderer/components/blocks/tables/GlossaryTable.tsx new file mode 100644 index 00000000..cef1653d --- /dev/null +++ b/src/renderer/components/blocks/tables/GlossaryTable.tsx @@ -0,0 +1,46 @@ +import React, { useMemo } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { DataTable } from '@/components'; + +interface TableProps { + data: any[]; + isLoading: boolean; + order: string; + setOrder: (sort: string) => void; +} + +const GlossaryTable: React.FC = ({ data, isLoading, order, setOrder }) => { + const columns = useMemo( + () => [ + { + title: , + key: 'term', + }, + { + title: , + key: 'description', + render: (row: any) => { + return ( +
+ {row.description.map((item, index) => ( +
+
{item.header}
+
{item.definition}
+
+ ))} +
+ ); + }, + }, + ], + [], + ); + + return ( +
+ +
+ ); +}; + +export { GlossaryTable }; diff --git a/src/renderer/components/blocks/tables/index.ts b/src/renderer/components/blocks/tables/index.ts index 5574dbd8..0161d472 100644 --- a/src/renderer/components/blocks/tables/index.ts +++ b/src/renderer/components/blocks/tables/index.ts @@ -1,3 +1,4 @@ export * from './ProjectsListTable'; export * from './SkeletonTable'; -export * from './UnitsListTable'; \ No newline at end of file +export * from './UnitsListTable'; +export * from './GlossaryTable'; \ No newline at end of file diff --git a/src/renderer/pages/Glossary/GlossaryPage.tsx b/src/renderer/pages/Glossary/GlossaryPage.tsx new file mode 100644 index 00000000..6e570023 --- /dev/null +++ b/src/renderer/pages/Glossary/GlossaryPage.tsx @@ -0,0 +1,57 @@ +import React, { useMemo, useCallback } from 'react'; +import { useGetGlossaryQuery } from '@/api/cadt/v1/governance'; +import { SkeletonTable, GlossaryTable, SearchBox } from '@/components'; +import { useQueryParamState, useColumnOrderHandler } from '@/hooks'; +import { FormattedMessage } from 'react-intl'; +import { debounce } from 'lodash'; + +const GlossaryPage: React.FC = () => { + const { data: glossaryData, isLoading: glossaryLoading, error: glossaryError } = useGetGlossaryQuery(); + const [search, setSearch] = useQueryParamState('search', ''); // Fixed the state name to 'search' + const [order, setOrder] = useQueryParamState('order', undefined); + const handleSetOrder = useColumnOrderHandler(order, setOrder); + + // Memoized ordered data based on the `order` variable + const orderedData = useMemo(() => { + if (!order || !glossaryData) return glossaryData; + + const [orderBy, direction] = order.split(':'); + return [...glossaryData].sort((a, b) => { + if (a[orderBy] < b[orderBy]) return direction === 'ASC' ? -1 : 1; + if (a[orderBy] > b[orderBy]) return direction === 'ASC' ? 1 : -1; + return 0; + }); + }, [glossaryData, order]); + + // Filter the ordered data based on the search term + const filteredData = useMemo(() => { + if (!search.trim()) return orderedData; + return orderedData?.filter(item => item.term.toLowerCase().includes(search.toLowerCase())); + }, [orderedData, search]); + + const handleSearchChange = useCallback( + debounce((event: React.ChangeEvent) => { + setSearch(event.target.value); + }, 800), + [] // Removed dependencies on `setSearch` and `debounce` as they are not expected to change + ); + + if (glossaryLoading) { + return ; + } + + if (glossaryError) { + return ; + } + + return ( + <> +
+ +
+ + + ); +}; + +export { GlossaryPage }; diff --git a/src/renderer/pages/Glossary/index.ts b/src/renderer/pages/Glossary/index.ts new file mode 100644 index 00000000..46416595 --- /dev/null +++ b/src/renderer/pages/Glossary/index.ts @@ -0,0 +1 @@ +export * from './GlossaryPage' \ No newline at end of file diff --git a/src/renderer/pages/ProjectsList/ProjectsList.tsx b/src/renderer/pages/ProjectsList/ProjectsList.tsx index 60d9277d..21e2bcf1 100644 --- a/src/renderer/pages/ProjectsList/ProjectsList.tsx +++ b/src/renderer/pages/ProjectsList/ProjectsList.tsx @@ -44,7 +44,6 @@ const ProjectsList: React.FC = () => { [setSearch, debounce], ); - if (projectsLoading) { return ; } diff --git a/src/renderer/pages/index.ts b/src/renderer/pages/index.ts index 64381782..26df64e5 100644 --- a/src/renderer/pages/index.ts +++ b/src/renderer/pages/index.ts @@ -1,3 +1,4 @@ export * from './Error'; export * from './ProjectsList'; export * from './UnitsList'; +export * from './Glossary'; \ No newline at end of file diff --git a/src/renderer/routes/AppNavigator.tsx b/src/renderer/routes/AppNavigator.tsx index 26e2e8ba..42e17eb6 100644 --- a/src/renderer/routes/AppNavigator.tsx +++ b/src/renderer/routes/AppNavigator.tsx @@ -1,14 +1,8 @@ import React from 'react'; -import { - BrowserRouter as Router, - Routes, - Route, - Navigate, - redirect, -} from 'react-router-dom'; +import { BrowserRouter as Router, Routes, Route, Navigate, redirect } from 'react-router-dom'; import ROUTES from '@/routes/route-constants'; -import * as Pages from '@/pages' -import {Template} from "@/components"; +import * as Pages from '@/pages'; +import { Template } from '@/components'; const AppNavigator: React.FC = () => { return ( @@ -19,23 +13,13 @@ const AppNavigator: React.FC = () => { path="*(/+)" loader={({ params }) => redirect(params['*'] || '/')} /> - }> - } - /> - } - /> - } - /> - } - /> + }> + } /> + } /> + } /> + + } /> + } /> {/* app-wide blocking modals go below this comment*/} @@ -44,4 +28,4 @@ const AppNavigator: React.FC = () => { ); }; -export { AppNavigator }; \ No newline at end of file +export { AppNavigator }; diff --git a/src/renderer/routes/route-constants.ts b/src/renderer/routes/route-constants.ts index e3273dc6..fed68231 100644 --- a/src/renderer/routes/route-constants.ts +++ b/src/renderer/routes/route-constants.ts @@ -1,4 +1,5 @@ export default { PROJECTS_LIST: '/projects', - UNITS_LIST: '/units' + UNITS_LIST: '/units', + GLOSSARY: '/glossary', }; \ No newline at end of file diff --git a/src/renderer/translations/tokens/en-US.json b/src/renderer/translations/tokens/en-US.json index d0f0bde2..a64d42a2 100644 --- a/src/renderer/translations/tokens/en-US.json +++ b/src/renderer/translations/tokens/en-US.json @@ -28,5 +28,8 @@ "covered-by-ndc": "Covered By Ndc", "project-status": "Project Status", "unit-metric": "Unit Metric", - "validation-body": "Validation Body" + "validation-body": "Validation Body", + "glossary": "Glossary", + "term": "Term", + "description": "Description" } \ No newline at end of file