diff --git a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx index c7a3977d9b..53e9a4aad1 100644 --- a/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx +++ b/frontend/src/component/executiveDashboard/FlagsProjectChart/FlagsProjectChart.tsx @@ -1,12 +1,7 @@ -import { useMemo, type VFC } from 'react'; +import { type VFC } from 'react'; import 'chartjs-adapter-date-fns'; -import { useTheme } from '@mui/material'; -import { - ExecutiveSummarySchema, - ExecutiveSummarySchemaProjectFlagTrendsItem, -} from 'openapi'; +import { ExecutiveSummarySchema } from 'openapi'; import { LineChart } from '../LineChart/LineChart'; -import { getRandomColor } from '../executive-dashboard-utils'; import { useProjectChartData } from '../useProjectChartData'; interface IFlagsProjectChartProps { diff --git a/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx b/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx index 08acf7f210..b54989a185 100644 --- a/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx +++ b/frontend/src/component/executiveDashboard/ProjectHealthChart/ProjectHealthChart.tsx @@ -1,12 +1,7 @@ -import { useMemo, type VFC } from 'react'; +import { type VFC } from 'react'; import 'chartjs-adapter-date-fns'; -import { useTheme } from '@mui/material'; -import { - ExecutiveSummarySchema, - ExecutiveSummarySchemaProjectFlagTrendsItem, -} from 'openapi'; +import { ExecutiveSummarySchema } from 'openapi'; import { LineChart } from '../LineChart/LineChart'; -import { getRandomColor } from '../executive-dashboard-utils'; import { useProjectChartData } from '../useProjectChartData'; interface IFlagsProjectChartProps { diff --git a/frontend/src/component/executiveDashboard/TimeToProductionChart/TimeToProductionChart.tsx b/frontend/src/component/executiveDashboard/TimeToProductionChart/TimeToProductionChart.tsx index f1f33be090..5c3ec80cff 100644 --- a/frontend/src/component/executiveDashboard/TimeToProductionChart/TimeToProductionChart.tsx +++ b/frontend/src/component/executiveDashboard/TimeToProductionChart/TimeToProductionChart.tsx @@ -1,12 +1,7 @@ -import { useMemo, type VFC } from 'react'; +import { type VFC } from 'react'; import 'chartjs-adapter-date-fns'; -import { useTheme } from '@mui/material'; -import { - ExecutiveSummarySchema, - ExecutiveSummarySchemaProjectFlagTrendsItem, -} from 'openapi'; +import { ExecutiveSummarySchema } from 'openapi'; import { LineChart } from '../LineChart/LineChart'; -import { getRandomColor } from '../executive-dashboard-utils'; import { useProjectChartData } from '../useProjectChartData'; interface IFlagsProjectChartProps { diff --git a/frontend/src/component/feedbackNew/FeedbackList.tsx b/frontend/src/component/feedbackNew/FeedbackList.tsx new file mode 100644 index 0000000000..94315566d5 --- /dev/null +++ b/frontend/src/component/feedbackNew/FeedbackList.tsx @@ -0,0 +1,161 @@ +import useFeedbackPosted from 'hooks/api/getters/useFeedbackPosted/useFeedbackPosted'; +import { VirtualizedTable } from 'component/common/Table'; +import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; +import { useFlexLayout, useSortBy, useTable } from 'react-table'; +import { sortTypes } from 'utils/sortTypes'; +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Search } from 'component/common/Search/Search'; +import { useMediaQuery } from '@mui/material'; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { useSearch } from 'hooks/useSearch'; +import theme from 'themes/theme'; +import { useState } from 'react'; +import { FeedbackSchema } from '../../openapi'; + +interface IFeedbackSchemaCellProps { + value?: string | null; // FIXME: proper type + row: { original: FeedbackSchema }; +} + +export const FeedbackList = () => { + const { feedback } = useFeedbackPosted(); + + const [searchValue, setSearchValue] = useState(''); + + const columns = [ + { + Header: 'Category', + accessor: 'category', + Cell: ({ + row: { original: feedback }, + }: IFeedbackSchemaCellProps) => ( + {feedback.category} + ), + searchable: true, + }, + { + Header: 'UserType', + accessor: 'userType', + Cell: ({ + row: { original: feedback }, + }: IFeedbackSchemaCellProps) => ( + {feedback.userType} + ), + searchable: true, + }, + { + Header: 'DifficultyScore', + accessor: 'difficultyScore', + Cell: ({ + row: { original: feedback }, + }: IFeedbackSchemaCellProps) => ( + {feedback.difficultyScore} + ), + }, + { + Header: 'Positive', + accessor: 'positive', + minWidth: 100, + Cell: ({ + row: { original: feedback }, + }: IFeedbackSchemaCellProps) => ( + {feedback.positive} + ), + disableSortBy: true, + searchable: true, + }, + { + Header: 'Areas for improvement', + accessor: 'areasForImprovement', + minWidth: 100, + Cell: ({ + row: { original: feedback }, + }: IFeedbackSchemaCellProps) => ( + {feedback.areasForImprovement} + ), + disableSortBy: true, + searchable: true, + }, + { + Header: 'Created at', + accessor: 'createdAt', + Cell: DateCell, + }, + ]; + + const { data, getSearchText } = useSearch(columns, searchValue, feedback); + + const { headerGroups, rows, prepareRow } = useTable( + { + columns: columns as any, + data, + initialState: { + sortBy: [ + { + id: 'createdAt', + desc: true, + }, + ], + }, + sortTypes, + autoResetHiddenColumns: false, + autoResetSortBy: false, + disableSortRemove: true, + disableMultiSort: true, + defaultColumn: { + Cell: TextCell, + }, + }, + useSortBy, + useFlexLayout, + ); + + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + + return ( + + + + + + } + /> + + } + > + + } + /> + + } + > + + + + + ); +}; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index 6110b83b6a..434e846304 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -254,6 +254,14 @@ exports[`returns all baseRoutes 1`] = ` "title": "Environments", "type": "protected", }, + { + "component": [Function], + "flag": "featureSearchFeedbackPosting", + "menu": {}, + "path": "/feedback", + "title": "Feedback", + "type": "protected", + }, { "component": [Function], "menu": {}, diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 3315f8aaaf..2b4a6e2792 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -46,6 +46,7 @@ import { ViewIntegration } from 'component/integrations/ViewIntegration/ViewInte import { ApplicationList } from '../application/ApplicationList/ApplicationList'; import { AddonRedirect } from 'component/integrations/AddonRedirect/AddonRedirect'; import { ExecutiveDashboard } from 'component/executiveDashboard/ExecutiveDashboard'; +import { FeedbackList } from '../feedbackNew/FeedbackList'; export const routes: IRoute[] = [ // Splash @@ -266,6 +267,14 @@ export const routes: IRoute[] = [ flag: EEA, menu: { mobile: true, advanced: true }, }, + { + path: '/feedback', + title: 'Feedback', + component: FeedbackList, + type: 'protected', + flag: 'featureSearchFeedbackPosting', + menu: {}, + }, // Tags { diff --git a/frontend/src/hooks/api/getters/useFeedbackPosted/useFeedbackPosted.ts b/frontend/src/hooks/api/getters/useFeedbackPosted/useFeedbackPosted.ts new file mode 100644 index 0000000000..1362fd67be --- /dev/null +++ b/frontend/src/hooks/api/getters/useFeedbackPosted/useFeedbackPosted.ts @@ -0,0 +1,38 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useState, useEffect } from 'react'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { FeedbackListSchema } from '../../../../openapi'; + +const KEY = `api/admin/feedback`; +const path = formatApiPath(KEY); + +const useFeedbackPosted = (options: SWRConfiguration = {}) => { + const fetcher = () => { + return fetch(path, { + method: 'GET', + }) + .then(handleErrorResponses('FeedbackPosted')) + .then((res) => res.json()); + }; + + const { data, error } = useSWR(KEY, fetcher, options); + const [loading, setLoading] = useState(!error && !data); + + const refetchFeedback = () => { + mutate(KEY); + }; + + useEffect(() => { + setLoading(!error && !data); + }, [data, error]); + + return { + feedback: data || [], + error, + loading, + refetchFeedback, + }; +}; + +export default useFeedbackPosted; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 04cf9988de..4de5bd9201 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -79,6 +79,7 @@ export type UiFlags = { feedbackComments?: Variant; displayUpgradeEdgeBanner?: boolean; showInactiveUsers?: boolean; + featureSearchFeedbackPosting?: boolean; }; export interface IVersionInfo { diff --git a/src/server-dev.ts b/src/server-dev.ts index 7cedcb8278..309d0e50c5 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -46,6 +46,7 @@ process.nextTick(async () => { celebrateUnleash: true, increaseUnleashWidth: true, newStrategyConfigurationFeedback: true, + featureSearchFeedbackPosting: true, extendedUsageMetricsUI: true, executiveDashboard: true, },