feat: labels ui

This commit is contained in:
Ezequiel Bergamaschi 2021-03-01 23:59:38 -05:00
parent b4fd0fb40e
commit 1a3e299c71
12 changed files with 247 additions and 51 deletions

2
.nvmrc
View File

@ -1 +1 @@
v14.15.5 v14.15.4

View File

@ -19,7 +19,6 @@ export type Label = {
id: number; id: number;
title: string; title: string;
color: string; color: string;
iconName: string;
}; };
export type Role = 'owner' | 'editor' | 'viewer'; export type Role = 'owner' | 'editor' | 'viewer';
@ -28,7 +27,7 @@ export type MapInfo = {
id: number; id: number;
starred: boolean; starred: boolean;
title: string; title: string;
labels: number[]; labels: Label[];
createdBy: string; createdBy: string;
creationTime: string; creationTime: string;
lastModificationBy: string; lastModificationBy: string;

View File

@ -10,6 +10,24 @@ import Client, {
} from '..'; } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
const label1: Label = {
id: 1,
title: 'label 1',
color: 'black',
}
const label2: Label = {
id: 2,
title: 'label 2',
color: 'green',
}
const label3: Label = {
id: 3,
title: 'label 3',
color: 'red',
}
class MockClient implements Client { class MockClient implements Client {
private maps: MapInfo[] = []; private maps: MapInfo[] = [];
private labels: Label[] = []; private labels: Label[] = [];
@ -21,7 +39,7 @@ class MockClient implements Client {
id: number, id: number,
starred: boolean, starred: boolean,
title: string, title: string,
labels: number[], labels: Label[],
creator: string, creator: string,
creationTime: string, creationTime: string,
modifiedByUser: string, modifiedByUser: string,
@ -63,7 +81,7 @@ class MockClient implements Client {
11, 11,
false, false,
'El Mapa3', 'El Mapa3',
[1, 2, 3], [label1, label2],
'Paulo3', 'Paulo3',
'2008-06-02T00:00:00Z', '2008-06-02T00:00:00Z',
'Berna', 'Berna',
@ -76,7 +94,7 @@ class MockClient implements Client {
12, 12,
false, false,
'El Mapa3', 'El Mapa3',
[1, 2, 3], [label2, label3],
'Paulo3', 'Paulo3',
'2008-06-02T00:00:00Z', '2008-06-02T00:00:00Z',
'Berna', 'Berna',
@ -87,10 +105,7 @@ class MockClient implements Client {
), ),
]; ];
this.labels = [ this.labels = [label1, label2, label3];
{ id: 1, title: 'Red Label', iconName: '', color: 'red' },
{ id: 2, title: 'Blue Label', iconName: '', color: 'blue' },
];
} }
deleteMapPermission(id: number, email: string): Promise<void> { deleteMapPermission(id: number, email: string): Promise<void> {
let perm = this.permissionsByMap.get(id) || []; let perm = this.permissionsByMap.get(id) || [];

View File

@ -219,20 +219,18 @@ const MapsPage = (): ReactElement => {
</div> </div>
<List component="nav"> <List component="nav">
{filterButtons.map((buttonInfo) => { {filterButtons.map(buttonInfo => {
return ( return (<StyleListItem
<StyleListItem
icon={buttonInfo.icon} icon={buttonInfo.icon}
label={buttonInfo.label} label={buttonInfo.label}
filter={buttonInfo.filter} filter={buttonInfo.filter}
active={filter} active={filter}
onClick={handleMenuClick} onClick={handleMenuClick}
onDelete={handleLabelDelete} onDelete={handleLabelDelete}
key={`${buttonInfo.filter.type}:${(buttonInfo.filter as LabelFilter).label key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
}`} />)
/> }
); )}
})}
</List> </List>
<div <div

View File

@ -0,0 +1,71 @@
import React from 'react';
import Popover from '@material-ui/core/Popover';
import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
import { FormattedMessage, useIntl } from 'react-intl';
import { Label } from '../../../../classes/client';
import { LabelSelector } from '../label-selector';
type AddLabelButtonTypes = {
onChange?: (label: Label) => void;
};
export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactElement {
console.log(onChange);
const intl = useIntl();
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? 'add-label-popover' : undefined;
return (
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'map.tooltip-add',
defaultMessage: 'Add label to selected',
})}
>
<>
<Button
color="primary"
size="medium"
variant="outlined"
type="button"
style={{ marginLeft: '10px' }}
disableElevation={true}
startIcon={<LabelTwoTone />}
onClick={handleClick}
>
<FormattedMessage id="action.label" defaultMessage="Add Label" />
</Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<LabelSelector />
</Popover>
</>
</Tooltip>
);
}

View File

@ -4,7 +4,7 @@ import { useStyles } from './styled';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../redux/clientSlice'; import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useMutation, useQuery, useQueryClient } from 'react-query';
import Client, { ErrorInfo, MapInfo } from '../../../classes/client'; import Client, {ErrorInfo, Label, MapInfo} from '../../../classes/client';
import ActionChooser, { ActionType } from '../action-chooser'; import ActionChooser, { ActionType } from '../action-chooser';
import ActionDispatcher from '../action-dispatcher'; import ActionDispatcher from '../action-dispatcher';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -28,14 +28,15 @@ import Button from '@material-ui/core/Button';
import InputBase from '@material-ui/core/InputBase'; import InputBase from '@material-ui/core/InputBase';
import Link from '@material-ui/core/Link'; import Link from '@material-ui/core/Link';
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined'; import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded'; import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
import SearchIcon from '@material-ui/icons/Search'; import SearchIcon from '@material-ui/icons/Search';
import { AddLabelButton } from './add-label-button';
// Load fromNow pluggin // Load fromNow pluggin
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import {LabelsCell} from './labels-cell';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
@ -55,8 +56,8 @@ function getComparator<Key extends keyof any>(
order: Order, order: Order,
orderBy: Key orderBy: Key
): ( ): (
a: { [key in Key]: number | string | boolean | number[] | undefined }, a: { [key in Key]: number | string | boolean | Label[] | undefined },
b: { [key in Key]: number | string | number[] | boolean } b: { [key in Key]: number | string | Label[] | boolean }
) => number { ) => number {
return order === 'desc' return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy) ? (a, b) => descendingComparator(a, b, orderBy)
@ -220,7 +221,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
break; break;
case 'label': case 'label':
result = result =
!mapInfo.labels || mapInfo.labels.includes((filter as LabelFilter).label.id); !mapInfo.labels || mapInfo.labels.some((label) => label.id === (filter as LabelFilter).label.id)
break; break;
case 'public': case 'public':
result = mapInfo.isPublic; result = mapInfo.isPublic;
@ -412,24 +413,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
</Tooltip> </Tooltip>
)} )}
{selected.length > 0 && ( {selected.length > 0 && <AddLabelButton/>}
<Tooltip arrow={true} title={intl.formatMessage({ id: 'map.tooltip-add', defaultMessage: 'Add label to selected' })}>
<Button
color="primary"
size="medium"
variant="outlined"
type="button"
style={{ marginLeft: '10px' }}
disableElevation={true}
startIcon={<LabelTwoTone />}
>
<FormattedMessage
id="action.label"
defaultMessage="Add Label"
/>
</Button>
</Tooltip>
)}
</div> </div>
<div className={classes.toolbarListActions}> <div className={classes.toolbarListActions}>
@ -558,7 +542,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell className={classes.bodyCell}></TableCell> <TableCell className={classes.bodyCell}>
<LabelsCell labels={row.labels} />
</TableCell>
<TableCell className={classes.bodyCell}> <TableCell className={classes.bodyCell}>
{row.createdBy} {row.createdBy}

View File

@ -0,0 +1,58 @@
import React from 'react';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Divider from '@material-ui/core/Divider';
import AddIcon from '@material-ui/icons/Add';
import Checkbox from '@material-ui/core/Checkbox';
import Container from '@material-ui/core/Container';
import { Label as LabelComponent } from '../label';
import Client, { Label, ErrorInfo } from '../../../../classes/client';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { activeInstance } from '../../../../redux/clientSlice';
import { StyledButton } from './styled';
export function LabelSelector(): React.ReactElement {
const client: Client = useSelector(activeInstance);
const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => client.fetchLabels());
const [state, setState] = React.useState(labels.reduce((acc, label) => {
acc[label.id] = false //label.checked;
return acc;
}, {}),);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setState({ ...state, [event.target.id]: event.target.checked });
};
return (
<Container>
<FormGroup>
{labels.map(({ id, title, color}) => (
<FormControlLabel
key={id}
control={
<Checkbox
id={`${id}`}
checked={state[id]}
onChange={handleChange}
name={title}
color="primary"
/>
}
label={<LabelComponent name={title} color={color} />}
/>
))}
<Divider />
<StyledButton
color="primary"
startIcon={<AddIcon />}
>
{/* i18n */}
Add new label
</StyledButton>
</FormGroup>
</Container>
);
}

View File

@ -0,0 +1,6 @@
import styled from 'styled-components';
import Button from '@material-ui/core/Button';
export const StyledButton = styled(Button)`
margin: 4px;
`;

View File

@ -0,0 +1,13 @@
import React from 'react';
import { Color, StyledLabel, Name } from './styled';
type Props = { name: string, color: string };
export function Label({ name, color }: Props): React.ReactElement<Props> {
return (
<StyledLabel>
<Color color={color} />
<Name>{name}</Name>
</StyledLabel>
);
}

View File

@ -0,0 +1,23 @@
import styled, { css } from 'styled-components';
const SIZE = 20;
export const Color = styled.div`
width: ${SIZE}px;
height: ${SIZE}px;
border-radius: ${SIZE * 0.25}px;
border: 1px solid black;
margin: 1px ${SIZE * 0.5}px 1px 0px;
${props => props.color && css`
background-color: ${props.color};
`}
`;
export const StyledLabel = styled.div`
display: flex;
flex-direction: row;
`;
export const Name = styled.div`
flex: 1;
`;

View File

@ -0,0 +1,27 @@
import React from 'react';
import Chip from '@material-ui/core/Chip';
import LabelIcon from '@material-ui/icons/Label';
import {Label} from '../../../../classes/client';
type Props = {
labels: Label[],
};
export function LabelsCell({ labels }: Props): React.ReactElement<Props> {
return (
<>
{labels.map(label => (
<Chip
key={label.id}
size="small"
icon={<LabelIcon />}
label={label.title}
clickable
color="primary"
style={{ backgroundColor: label.color }}
onDelete={() => {return 1;}}
/>
))}
</>
);
}

View File

@ -69,9 +69,9 @@ const theme = createMuiTheme({
contrastText: '#FFFFFF', contrastText: '#FFFFFF',
}, },
secondary: { secondary: {
light: '#FFFFFF', light: '#a19f9f',
main: '#FFFFFF', main: '#5a5a5a',
dark: '#FFFFFF', dark: '#000000',
contrastText: '#FFFFFF', contrastText: '#FFFFFF',
}, },
}, },