2022-05-25 10:14:22 +02:00
|
|
|
import { useEffect, useMemo, useState, VFC } from 'react';
|
2022-05-06 12:21:31 +02:00
|
|
|
import { Link, useMediaQuery, useTheme } from '@mui/material';
|
2022-05-25 10:14:22 +02:00
|
|
|
import { Link as RouterLink, useSearchParams } from 'react-router-dom';
|
|
|
|
import { SortingRule, useGlobalFilter, useSortBy, useTable } from 'react-table';
|
2022-05-05 15:34:46 +02:00
|
|
|
import {
|
|
|
|
Table,
|
2022-05-06 12:21:31 +02:00
|
|
|
SortableTableHeader,
|
2022-05-05 15:34:46 +02:00
|
|
|
TableBody,
|
|
|
|
TableCell,
|
|
|
|
TableRow,
|
2022-05-06 12:21:31 +02:00
|
|
|
TablePlaceholder,
|
|
|
|
TableSearch,
|
|
|
|
} from 'component/common/Table';
|
2022-05-25 10:14:22 +02:00
|
|
|
import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
|
2022-05-05 15:34:46 +02:00
|
|
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
2022-05-25 10:14:22 +02:00
|
|
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
2022-05-20 10:29:23 +02:00
|
|
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
2022-05-13 14:51:22 +02:00
|
|
|
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
|
|
|
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
2022-05-05 15:34:46 +02:00
|
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
2022-05-09 14:38:12 +02:00
|
|
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
|
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
2022-05-13 14:51:22 +02:00
|
|
|
import { sortTypes } from 'utils/sortTypes';
|
2022-05-25 10:14:22 +02:00
|
|
|
import { useLocalStorage } from 'hooks/useLocalStorage';
|
|
|
|
import { FeatureSchema } from 'openapi';
|
|
|
|
import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton';
|
|
|
|
import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell';
|
2022-05-25 12:45:30 +02:00
|
|
|
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
|
2022-05-05 15:34:46 +02:00
|
|
|
|
2022-05-25 10:14:22 +02:00
|
|
|
const featuresPlaceholder: FeatureSchema[] = Array(15).fill({
|
|
|
|
name: 'Name of the feature',
|
|
|
|
description: 'Short description of the feature',
|
|
|
|
type: '-',
|
|
|
|
createdAt: new Date(2022, 1, 1),
|
|
|
|
project: 'projectID',
|
|
|
|
});
|
|
|
|
|
|
|
|
type PageQueryType = Partial<Record<'sort' | 'order' | 'search', string>>;
|
2022-05-05 15:34:46 +02:00
|
|
|
|
|
|
|
const columns = [
|
|
|
|
{
|
|
|
|
Header: 'Seen',
|
|
|
|
accessor: 'lastSeenAt',
|
|
|
|
Cell: FeatureSeenCell,
|
|
|
|
sortType: 'date',
|
2022-05-13 14:51:22 +02:00
|
|
|
align: 'center',
|
2022-05-05 15:34:46 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Header: 'Type',
|
|
|
|
accessor: 'type',
|
|
|
|
Cell: FeatureTypeCell,
|
2022-05-13 14:51:22 +02:00
|
|
|
align: 'center',
|
2022-05-05 15:34:46 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Header: 'Feature toggle name',
|
|
|
|
accessor: 'name',
|
2022-05-13 14:51:22 +02:00
|
|
|
maxWidth: 300,
|
|
|
|
width: '67%',
|
2022-05-25 12:45:30 +02:00
|
|
|
Cell: FeatureNameCell,
|
2022-05-05 15:34:46 +02:00
|
|
|
sortType: 'alphanumeric',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Header: 'Created on',
|
|
|
|
accessor: 'createdAt',
|
|
|
|
Cell: DateCell,
|
|
|
|
sortType: 'date',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Header: 'Project ID',
|
|
|
|
accessor: 'project',
|
|
|
|
Cell: ({ value }: { value: string }) => (
|
2022-05-20 10:29:23 +02:00
|
|
|
<LinkCell title={value} to={`/projects/${value}`} />
|
2022-05-05 15:34:46 +02:00
|
|
|
),
|
|
|
|
sortType: 'alphanumeric',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Header: 'State',
|
|
|
|
accessor: 'stale',
|
|
|
|
Cell: FeatureStaleCell,
|
|
|
|
sortType: 'boolean',
|
|
|
|
},
|
|
|
|
// Always hidden -- for search
|
|
|
|
{
|
|
|
|
accessor: 'description',
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2022-05-25 10:14:22 +02:00
|
|
|
const defaultSort: SortingRule<string> = { id: 'createdAt', desc: false };
|
|
|
|
|
|
|
|
export const FeatureToggleListTable: VFC = () => {
|
2022-05-05 15:34:46 +02:00
|
|
|
const theme = useTheme();
|
|
|
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
|
|
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
2022-05-25 10:14:22 +02:00
|
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
|
|
const [storedParams, setStoredParams] = useLocalStorage(
|
|
|
|
'FeatureToggleListTable:v1',
|
|
|
|
defaultSort
|
|
|
|
);
|
|
|
|
const { features = [], loading } = useFeatures();
|
|
|
|
const data = useMemo(
|
|
|
|
() =>
|
|
|
|
features?.length === 0 && loading ? featuresPlaceholder : features,
|
|
|
|
[features, loading]
|
2022-05-05 15:34:46 +02:00
|
|
|
);
|
|
|
|
|
2022-05-25 10:14:22 +02:00
|
|
|
const [initialState] = useState(() => ({
|
|
|
|
sortBy: [
|
|
|
|
{
|
|
|
|
id: searchParams.get('sort') || storedParams.id,
|
|
|
|
desc: searchParams.has('order')
|
|
|
|
? searchParams.get('order') === 'desc'
|
|
|
|
: storedParams.desc,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
hiddenColumns: ['description'],
|
|
|
|
globalFilter: searchParams.get('search') || '',
|
|
|
|
}));
|
|
|
|
|
2022-05-05 15:34:46 +02:00
|
|
|
const {
|
|
|
|
getTableProps,
|
|
|
|
getTableBodyProps,
|
|
|
|
headerGroups,
|
|
|
|
rows,
|
|
|
|
prepareRow,
|
2022-05-25 10:14:22 +02:00
|
|
|
state: { globalFilter, sortBy },
|
2022-05-05 15:34:46 +02:00
|
|
|
setGlobalFilter,
|
|
|
|
setHiddenColumns,
|
|
|
|
} = useTable(
|
|
|
|
{
|
2022-05-25 10:14:22 +02:00
|
|
|
// @ts-expect-error -- fix in react-table v8
|
2022-05-05 15:34:46 +02:00
|
|
|
columns,
|
2022-05-25 12:45:30 +02:00
|
|
|
// @ts-expect-error -- fix in react-table v8
|
2022-05-05 15:34:46 +02:00
|
|
|
data,
|
|
|
|
initialState,
|
|
|
|
sortTypes,
|
|
|
|
autoResetGlobalFilter: false,
|
2022-05-25 10:14:22 +02:00
|
|
|
autoResetSortBy: false,
|
2022-05-06 12:21:31 +02:00
|
|
|
disableSortRemove: true,
|
2022-05-25 10:14:22 +02:00
|
|
|
disableMultiSort: true,
|
2022-05-05 15:34:46 +02:00
|
|
|
},
|
|
|
|
useGlobalFilter,
|
|
|
|
useSortBy
|
|
|
|
);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isSmallScreen) {
|
2022-05-06 12:21:31 +02:00
|
|
|
setHiddenColumns([
|
|
|
|
'lastSeenAt',
|
|
|
|
'type',
|
|
|
|
'stale',
|
|
|
|
'description',
|
|
|
|
'createdAt',
|
|
|
|
]);
|
2022-05-05 15:34:46 +02:00
|
|
|
} else if (isMediumScreen) {
|
|
|
|
setHiddenColumns(['lastSeenAt', 'stale', 'description']);
|
|
|
|
} else {
|
|
|
|
setHiddenColumns(['description']);
|
|
|
|
}
|
|
|
|
}, [setHiddenColumns, isSmallScreen, isMediumScreen]);
|
|
|
|
|
2022-05-25 10:14:22 +02:00
|
|
|
useEffect(() => {
|
|
|
|
const tableState: PageQueryType = {};
|
|
|
|
tableState.sort = sortBy[0].id;
|
|
|
|
if (sortBy[0].desc) {
|
|
|
|
tableState.order = 'desc';
|
|
|
|
}
|
|
|
|
if (globalFilter) {
|
|
|
|
tableState.search = globalFilter;
|
|
|
|
}
|
|
|
|
|
|
|
|
setSearchParams(tableState, {
|
|
|
|
replace: true,
|
|
|
|
});
|
|
|
|
setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false });
|
|
|
|
}, [sortBy, globalFilter, setSearchParams, setStoredParams]);
|
|
|
|
|
2022-05-05 15:34:46 +02:00
|
|
|
return (
|
2022-05-09 14:38:12 +02:00
|
|
|
<PageContent
|
2022-05-25 10:14:22 +02:00
|
|
|
isLoading={loading}
|
2022-05-09 14:38:12 +02:00
|
|
|
header={
|
|
|
|
<PageHeader
|
|
|
|
title={`Feature toggles (${data.length})`}
|
|
|
|
actions={
|
|
|
|
<>
|
|
|
|
<TableSearch
|
|
|
|
initialValue={globalFilter}
|
|
|
|
onChange={setGlobalFilter}
|
|
|
|
/>
|
|
|
|
<PageHeader.Divider />
|
|
|
|
<Link
|
|
|
|
component={RouterLink}
|
|
|
|
to="/archive"
|
|
|
|
underline="always"
|
|
|
|
sx={{ marginRight: 3 }}
|
|
|
|
>
|
|
|
|
View archive
|
|
|
|
</Link>
|
|
|
|
<CreateFeatureButton
|
|
|
|
loading={false}
|
|
|
|
filter={{ query: '', project: 'default' }}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
}
|
2022-05-06 12:21:31 +02:00
|
|
|
/>
|
2022-05-09 14:38:12 +02:00
|
|
|
}
|
|
|
|
>
|
2022-05-05 15:34:46 +02:00
|
|
|
<SearchHighlightProvider value={globalFilter}>
|
|
|
|
<Table {...getTableProps()}>
|
2022-05-25 10:14:22 +02:00
|
|
|
{/* @ts-expect-error -- fix in react-table v8 */}
|
2022-05-05 15:34:46 +02:00
|
|
|
<SortableTableHeader headerGroups={headerGroups} />
|
|
|
|
<TableBody {...getTableBodyProps()}>
|
|
|
|
{rows.map(row => {
|
|
|
|
prepareRow(row);
|
|
|
|
return (
|
2022-05-09 12:01:12 +02:00
|
|
|
<TableRow hover {...row.getRowProps()}>
|
2022-05-05 15:34:46 +02:00
|
|
|
{row.cells.map(cell => (
|
|
|
|
<TableCell {...cell.getCellProps()}>
|
|
|
|
{cell.render('Cell')}
|
|
|
|
</TableCell>
|
|
|
|
))}
|
|
|
|
</TableRow>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</SearchHighlightProvider>
|
|
|
|
<ConditionallyRender
|
|
|
|
condition={rows.length === 0}
|
|
|
|
show={
|
2022-05-06 12:21:31 +02:00
|
|
|
<ConditionallyRender
|
|
|
|
condition={globalFilter?.length > 0}
|
|
|
|
show={
|
|
|
|
<TablePlaceholder>
|
|
|
|
No features or projects found matching “
|
|
|
|
{globalFilter}
|
|
|
|
”
|
|
|
|
</TablePlaceholder>
|
|
|
|
}
|
|
|
|
elseShow={
|
|
|
|
<TablePlaceholder>
|
|
|
|
No features available. Get started by adding a
|
|
|
|
new feature toggle.
|
|
|
|
</TablePlaceholder>
|
|
|
|
}
|
|
|
|
/>
|
2022-05-05 15:34:46 +02:00
|
|
|
}
|
|
|
|
/>
|
2022-05-09 14:38:12 +02:00
|
|
|
</PageContent>
|
2022-05-05 15:34:46 +02:00
|
|
|
);
|
|
|
|
};
|