mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: new tag types table (#1014)
* feat: new tag types table * fix: update loader text * fix: update header * fix: regenerator runtime * fix: update snapshot
This commit is contained in:
parent
91a825792e
commit
c073908027
@ -0,0 +1,36 @@
|
|||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import { UPDATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Add } from '@mui/icons-material';
|
||||||
|
|
||||||
|
export const AddTagTypeButton = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const smallScreen = useMediaQuery('(max-width:700px)');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={smallScreen}
|
||||||
|
show={
|
||||||
|
<PermissionIconButton
|
||||||
|
onClick={() => navigate('/tag-types/create')}
|
||||||
|
size="large"
|
||||||
|
permission={UPDATE_TAG_TYPE}
|
||||||
|
>
|
||||||
|
<Add />
|
||||||
|
</PermissionIconButton>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<PermissionButton
|
||||||
|
permission={UPDATE_TAG_TYPE}
|
||||||
|
onClick={() => navigate('/tag-types/create')}
|
||||||
|
>
|
||||||
|
New tag type
|
||||||
|
</PermissionButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,50 +0,0 @@
|
|||||||
.select {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: #757575;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textfield {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: var(--card-header-padding);
|
|
||||||
word-break: break-all;
|
|
||||||
border-bottom: var(--default-border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: var(--h1-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: var(--card-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
.formButtons {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagListItem {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagListItem * > a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagTypeContainer {
|
|
||||||
max-width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addTagTypeForm {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
@ -1,15 +1,16 @@
|
|||||||
import { useContext, useState } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
Button,
|
Table,
|
||||||
IconButton,
|
SortableTableHeader,
|
||||||
List,
|
TableBody,
|
||||||
ListItem,
|
TableCell,
|
||||||
ListItemIcon,
|
TableRow,
|
||||||
ListItemText,
|
TablePlaceholder,
|
||||||
Tooltip,
|
TableSearch,
|
||||||
} from '@mui/material';
|
} from 'component/common/Table';
|
||||||
import { Add, Delete, Edit, Label } from '@mui/icons-material';
|
import { Delete, Edit, Label } from '@mui/icons-material';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
@ -18,28 +19,152 @@ import {
|
|||||||
UPDATE_TAG_TYPE,
|
UPDATE_TAG_TYPE,
|
||||||
} from 'component/providers/AccessProvider/permissions';
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
|
||||||
import styles from './TagTypeList.module.scss';
|
|
||||||
import AccessContext from 'contexts/AccessContext';
|
|
||||||
import useTagTypesApi from 'hooks/api/actions/useTagTypesApi/useTagTypesApi';
|
import useTagTypesApi from 'hooks/api/actions/useTagTypesApi/useTagTypesApi';
|
||||||
import useTagTypes from 'hooks/api/getters/useTagTypes/useTagTypes';
|
import useTagTypes from 'hooks/api/getters/useTagTypes/useTagTypes';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { ITagType } from 'interfaces/tags';
|
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
|
||||||
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import { AddTagTypeButton } from './AddTagTypeButton/AddTagTypeButton';
|
||||||
|
|
||||||
export const TagTypeList = () => {
|
export const TagTypeList = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
|
||||||
const [deletion, setDeletion] = useState<{
|
const [deletion, setDeletion] = useState<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
}>({ open: false });
|
}>({ open: false });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const smallScreen = useMediaQuery('(max-width:700px)');
|
|
||||||
const { deleteTagType } = useTagTypesApi();
|
const { deleteTagType } = useTagTypesApi();
|
||||||
const { tagTypes, refetch } = useTagTypes();
|
const { tagTypes, refetch, loading } = useTagTypes();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const data = useMemo(() => {
|
||||||
|
if (loading) {
|
||||||
|
return Array(5).fill({
|
||||||
|
name: 'Tag type name',
|
||||||
|
description: 'Tag type description when loading',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagTypes.map(({ name, description }) => ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
}));
|
||||||
|
}, [tagTypes, loading]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'Icon',
|
||||||
|
Cell: () => (
|
||||||
|
<Box
|
||||||
|
data-loading
|
||||||
|
sx={{
|
||||||
|
pl: 2,
|
||||||
|
pr: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label color="disabled" />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
width: '90%',
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { name, description },
|
||||||
|
},
|
||||||
|
}: any) => {
|
||||||
|
return (
|
||||||
|
<LinkCell
|
||||||
|
data-loading
|
||||||
|
title={name}
|
||||||
|
subtitle={description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sortType: 'alphanumeric',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Actions',
|
||||||
|
id: 'Actions',
|
||||||
|
align: 'center',
|
||||||
|
Cell: ({ row: { original } }: any) => (
|
||||||
|
<Box
|
||||||
|
sx={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
<PermissionIconButton
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/tag-types/edit/${original.name}`)
|
||||||
|
}
|
||||||
|
permission={UPDATE_TAG_TYPE}
|
||||||
|
tooltipProps={{ title: 'Edit tag type' }}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</PermissionIconButton>
|
||||||
|
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={DELETE_TAG_TYPE}
|
||||||
|
tooltipProps={{ title: 'Delete tag type' }}
|
||||||
|
onClick={() =>
|
||||||
|
setDeletion({
|
||||||
|
open: true,
|
||||||
|
name: original.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</PermissionIconButton>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
width: 150,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'description',
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialState = useMemo(
|
||||||
|
() => ({
|
||||||
|
sortBy: [{ id: 'name', desc: false }],
|
||||||
|
hiddenColumns: ['description'],
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
prepareRow,
|
||||||
|
state: { globalFilter },
|
||||||
|
setGlobalFilter,
|
||||||
|
} = useTable(
|
||||||
|
{
|
||||||
|
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||||
|
data,
|
||||||
|
initialState,
|
||||||
|
sortTypes,
|
||||||
|
autoResetGlobalFilter: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
},
|
||||||
|
useGlobalFilter,
|
||||||
|
useSortBy
|
||||||
|
);
|
||||||
|
|
||||||
const deleteTag = async () => {
|
const deleteTag = async () => {
|
||||||
try {
|
try {
|
||||||
if (deletion.name) {
|
if (deletion.name) {
|
||||||
@ -57,100 +182,64 @@ export const TagTypeList = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let header = (
|
return (
|
||||||
|
<PageContent
|
||||||
|
isLoading={loading}
|
||||||
|
header={
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Tag Types"
|
title="Tag types"
|
||||||
actions={
|
actions={
|
||||||
<ConditionallyRender
|
<>
|
||||||
condition={hasAccess(UPDATE_TAG_TYPE)}
|
<TableSearch
|
||||||
show={
|
initialValue={globalFilter}
|
||||||
<ConditionallyRender
|
onChange={setGlobalFilter}
|
||||||
condition={smallScreen}
|
/>
|
||||||
show={
|
<PageHeader.Divider />
|
||||||
<Tooltip title="Add tag type" arrow>
|
<AddTagTypeButton />
|
||||||
<IconButton
|
</>
|
||||||
onClick={() =>
|
}
|
||||||
navigate('/tag-types/create')
|
/>
|
||||||
}
|
}
|
||||||
size="large"
|
|
||||||
>
|
>
|
||||||
<Add />
|
<SearchHighlightProvider value={globalFilter}>
|
||||||
</IconButton>
|
<Table {...getTableProps()}>
|
||||||
</Tooltip>
|
<SortableTableHeader headerGroups={headerGroups} />
|
||||||
|
<TableBody {...getTableBodyProps()}>
|
||||||
|
{rows.map(row => {
|
||||||
|
prepareRow(row);
|
||||||
|
return (
|
||||||
|
<TableRow hover {...row.getRowProps()}>
|
||||||
|
{row.cells.map(cell => (
|
||||||
|
<TableCell {...cell.getCellProps()}>
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</SearchHighlightProvider>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={rows.length === 0}
|
||||||
|
show={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={globalFilter?.length > 0}
|
||||||
|
show={
|
||||||
|
<TablePlaceholder>
|
||||||
|
No tags found matching “
|
||||||
|
{globalFilter}
|
||||||
|
”
|
||||||
|
</TablePlaceholder>
|
||||||
}
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<Button
|
<TablePlaceholder>
|
||||||
variant="contained"
|
No tags available. Get started by adding one.
|
||||||
color="primary"
|
</TablePlaceholder>
|
||||||
onClick={() =>
|
|
||||||
navigate('/tag-types/create')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
New tag type
|
|
||||||
</Button>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderTagType = (tagType: ITagType) => {
|
|
||||||
let link = (
|
|
||||||
<Link to={`/tag-types/edit/${tagType.name}`}>
|
|
||||||
<strong>{tagType.name}</strong>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
let deleteButton = (
|
|
||||||
<Tooltip title={`Delete ${tagType.name}`} arrow>
|
|
||||||
<IconButton
|
|
||||||
onClick={() =>
|
|
||||||
setDeletion({
|
|
||||||
open: true,
|
|
||||||
name: tagType.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Delete />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={`${tagType.name}`}
|
|
||||||
classes={{ root: styles.tagListItem }}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Label />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={link} secondary={tagType.description} />
|
|
||||||
<PermissionIconButton
|
|
||||||
permission={UPDATE_TAG_TYPE}
|
|
||||||
component={Link}
|
|
||||||
tooltipProps={{ title: 'Edit tag type' }}
|
|
||||||
to={`/tag-types/edit/${tagType.name}`}
|
|
||||||
>
|
|
||||||
<Edit className={styles.icon} />
|
|
||||||
</PermissionIconButton>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasAccess(DELETE_TAG_TYPE)}
|
|
||||||
show={deleteButton}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<PageContent header={header}>
|
|
||||||
<List>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={tagTypes.length > 0}
|
|
||||||
show={tagTypes.map(tagType => renderTagType(tagType))}
|
|
||||||
elseShow={<ListItem>No entries</ListItem>}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
<Dialogue
|
<Dialogue
|
||||||
title="Really delete Tag type?"
|
title="Really delete Tag type?"
|
||||||
open={deletion.open}
|
open={deletion.open}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
|||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
import 'regenerator-runtime';
|
||||||
|
|
||||||
process.env.TZ = 'UTC';
|
process.env.TZ = 'UTC';
|
||||||
|
Loading…
Reference in New Issue
Block a user