WIP: Share dialog with mock support

This commit is contained in:
Paulo Gustavo Veiga 2021-02-19 17:37:55 -08:00
parent c440e2426c
commit 2ae76dc08d
16 changed files with 291 additions and 74 deletions

View File

@ -22,6 +22,8 @@ export type Label = {
iconName: string;
}
export type Role = 'owner' | 'editor' | 'viewer';
export type MapInfo = {
id: number;
starred: boolean;
@ -33,7 +35,7 @@ export type MapInfo = {
lastModificationTime: string;
description: string;
isPublic: boolean;
role: 'owner' | 'editor' | 'viewer'
role: Role;
}
export type ChangeHistory = {
@ -64,6 +66,12 @@ export type AccountInfo = {
locale: Locale;
}
export type Permission = {
name?: string;
email: string;
role: Role;
}
interface Client {
deleteAccount(): Promise<void>
importMap(model: ImportMapInfo): Promise<number>
@ -72,11 +80,14 @@ interface Client {
deleteMap(id: number): Promise<void>;
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>;
fetchAllMaps(): Promise<MapInfo[]>;
fetchMapPermissions(id: number): Promise<Permission[]>;
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void>;
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number>;
updateAccountLanguage(locale: LocaleCode): Promise<void>;
updateAccountPassword(pasword: string): Promise<void>;
updateAccountInfo(firstname: string,lastname: string): Promise<void>;
updateAccountInfo(firstname: string, lastname: string): Promise<void>;
updateStarred(id: number, starred: boolean): Promise<void>;
updateMapToPublic(id: number, starred: boolean): Promise<void>;

View File

@ -1,8 +1,10 @@
import Client, { AccountInfo, BasicMapInfo, ChangeHistory, ImportMapInfo, Label, MapInfo, NewUser } from '..';
import Client, { AccountInfo, BasicMapInfo, ChangeHistory, ImportMapInfo, Label, MapInfo, NewUser, Permission } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n';
class MockClient implements Client {
private maps: MapInfo[] = [];
private labels: Label[] = [];
private permissionsByMap: Map<number, Permission[]> = new Map();
constructor() {
@ -22,6 +24,7 @@ class MockClient implements Client {
): MapInfo {
return { id, title, labels, createdBy: creator, creationTime, lastModificationBy: modifiedByUser, lastModificationTime: modifiedTime, starred, description, isPublic, role };
}
this.maps = [
createMapInfo(1, true, "El Mapa", [], "Paulo", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", true, 'owner'),
createMapInfo(11, false, "El Mapa3", [1, 2, 3], "Paulo3", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", false, 'editor'),
@ -32,7 +35,28 @@ class MockClient implements Client {
{ id: 1, title: "Red Label", iconName: "", color: 'red' },
{ id: 2, title: "Blue Label", iconName: "", color: 'blue' }
];
}
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
let perm = this.permissionsByMap.get(id) || [];
perm = perm.concat(permissions);
this.permissionsByMap.set(id, perm);
console.log(`Message ${message}`)
return Promise.resolve();
}
fetchMapPermissions(id: number): Promise<Permission[]> {
let perm = this.permissionsByMap.get(id);
if (!perm) {
perm = [{
name: 'Cosme Sharing',
email: 'pepe@gmail.com',
role: 'editor'
}];
this.permissionsByMap.set(id, perm);
}
return Promise.resolve(perm);
}
deleteAccount(): Promise<void> {

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory, AccountInfo, ImportMapInfo } from '..';
import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory, AccountInfo, ImportMapInfo, Permission } from '..';
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n';
export default class RestClient implements Client {
@ -10,6 +10,15 @@ export default class RestClient implements Client {
this.baseUrl = baseUrl;
this.sessionExpired = sessionExpired;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
throw new Error('Method not implemented.');
}
fetchMapPermissions(id: number): Promise<Permission[]> {
throw new Error('Method not implemented.' + id);
}
deleteAccount(): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(this.baseUrl + `/c/restful/account`,

View File

@ -24,7 +24,7 @@ const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.Reac
</p>
</div>);
}
return result ? result : null;
return result || null;
}
const LoginError = () => {

View File

@ -32,7 +32,7 @@ const AccountMenu = (): React.ReactElement => {
const account = fetchAccount();
return (
<span>
<Tooltip title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}>
<Tooltip arrow={true} title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}>
<IconButton
onClick={handleMenu}>
<AccountCircle fontSize="large" style={{ color: 'black' }} />

View File

@ -5,6 +5,7 @@ import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTit
import GlobalError from "../../../form/global-error";
import DialogContentText from "@material-ui/core/DialogContentText";
import Button from "@material-ui/core/Button";
import { PaperProps } from "@material-ui/core/Paper";
export type DialogProps = {
onClose: () => void;
@ -18,10 +19,11 @@ export type DialogProps = {
submitButton?: string;
actionUrl?: string;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
PaperProps?: Partial<PaperProps>;
}
const BaseDialog = (props: DialogProps): React.ReactElement => {
const { onClose, onSubmit, maxWidth = 'sm' } = props;
const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@ -31,13 +33,13 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
}
const description = props.description ? (<DialogContentText>{props.description}</DialogContentText>) : null;
return (
<div>
<StyledDialog
open={true}
onClose={onClose}
maxWidth={maxWidth}
PaperProps={PaperProps}
fullWidth={true}>
<form autoComplete="off" onSubmit={handleOnSubmit}>
<StyledDialogTitle>

View File

@ -2,7 +2,7 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client from "../../../../classes/client";
import Client, { ErrorInfo } from "../../../../classes/client";
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from "..";
import BaseDialog from "../base-dialog";
@ -14,10 +14,14 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id),
{
onSuccess: () => handleOnMutationSuccess(onClose, queryClient)
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error: ErrorInfo) => {
setError(error);
}
}
);
@ -35,6 +39,7 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
return (
<div>
<BaseDialog
error={error}
onClose={handleOnClose} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}
submitButton={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}>

View File

@ -21,7 +21,10 @@ const DeleteMultiselectDialog = ({ onClose, mapsId }: DeleteMultiselectDialogPro
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids),
{
onSuccess: () => handleOnMutationSuccess(onClose, queryClient)
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
}
);

View File

@ -1,15 +1,16 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client from "../../../../classes/client";
import Client, { ErrorInfo, Permission } from "../../../../classes/client";
import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from "..";
import { SimpleDialogProps } from "..";
import BaseDialog from "../base-dialog";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import IconButton from "@material-ui/core/IconButton";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import DeleteIcon from '@material-ui/icons/Delete';
import Paper from "@material-ui/core/Paper";
@ -19,16 +20,41 @@ import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
import Typography from "@material-ui/core/Typography";
import { useStyles } from "./style";
import RoleIcon from "../../role-icon";
type ShareModel = {
emails: string,
role: 'editor' | 'viewer',
message: string
}
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id),
const mutation = useMutation(
(model: ShareModel) => {
const emails = model.emails.split("'");
const permissions = emails.map((email) => { return { email: email, role: model.role } });
return client.addMapPermissions(mapId, model.message, permissions);
},
{
onSuccess: () => handleOnMutationSuccess(onClose, queryClient)
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
}
}
);
@ -36,65 +62,117 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
onClose();
};
const handleOnSubmit = (): void => {
mutation.mutate(mapId);
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value });
}
// Fetch map model to be rendered ...
const handleOnClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation();
mutation.mutate(model);
};
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(`perm-${mapId}`, () => {
return client.fetchMapPermissions(mapId);
});
return (
<div>
<BaseDialog
onClose={handleOnClose} onSubmit={handleOnSubmit}
onClose={handleOnClose}
title={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share with people" })}
description={intl.formatMessage({ id: "share.delete-description", defaultMessage: "Collaboration " })}
submitButton={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share" })}
maxWidth="md">
description={intl.formatMessage({ id: "share.delete-description", defaultMessage: "Invite people to collaborate with you on the creation of your midnmap. They will be notified by email. " })}
PaperProps={{ classes: { root: classes.paper } }}
error={error}
>
<div style={{ padding: '10px 10px', background: '#f9f9f9' }}>
<TextField id="email" style={{ width: '300px' }} size="small" type="text" variant="outlined" placeholder="Add collaboratos's emails seperated by commas" label="Email" />
<Select
value='edit'
<div className={classes.actionContainer}>
<TextField
id="emails"
name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined"
placeholder="Add collaboration email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Select
variant="outlined"
onChange={handleOnChange}
value={model.role}
name="role"
style={{ margin: '0px 10px' }}
>
<MenuItem value='edit'>Can Edit</MenuItem>
<MenuItem value='view'>Can View</MenuItem>
<MenuItem value='editor'><FormattedMessage id="share.can-edit" defaultMessage="Can edit" /></MenuItem>
<MenuItem value='viewer'><FormattedMessage id="share.can-view" defaultMessage="Can view" /></MenuItem>
</Select>
<FormControlLabel
value="start"
onChange={(event, value) => { setShowMessage(value) }}
style={{ fontSize: "5px" }}
control={<Checkbox color="primary" />}
label={<FormattedMessage id="share.add-message" defaultMessage="Add message" />}
label={<Typography variant="subtitle2"><FormattedMessage id="share.add-message" defaultMessage="Add message" /></Typography>}
labelPlacement="end"
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnClick}>
<FormattedMessage id="share.add-button" defaultMessage="Add " />
</Button>
{showMessage &&
<TextField
multiline
rows={3}
rowsMax={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
placeholder="Include a customize message to ..."
/>
<Button
color="primary"
variant="contained" disableElevation={true}><FormattedMessage id="share.add-button" defaultMessage="Add " /></Button>
}
</div>
<Paper elevation={1} style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined">
<List>
{[.4, 5, 7, 7, 8, 9, 100, 1, 2, 3].map((value) => {
const labelId = `checkbox-list-label-${value}`;
{!isLoading &&
<Paper elevation={1} className={classes.listPaper} variant="outlined">
<List>
{permissions && permissions.map((permission) => {
return (
<ListItem key={permission.email} role={undefined} dense button>
<ListItemText id={`${permission.name}<${permission.email}>`} primary={permission.email} />
return (
<ListItem key={value} role={undefined} dense button>
<ListItemText id={labelId} primary={`Line item ${value + 1}`} />
<ListItemSecondaryAction>
<IconButton edge="end">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</Paper>
<RoleIcon role={permission.role} />
<ListItemSecondaryAction>
<IconButton edge="end">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</Paper>
}
</BaseDialog>
</div >
);
}
export default ShareDialog;

View File

@ -0,0 +1,27 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
export const useStyles = makeStyles(() =>
createStyles({
actionContainer: {
padding: '10px 0px',
border: '1px solid rgba(0, 0, 0, 0.12)',
borderRadius: '8px 8px 0px 0px',
textAlign: "center"
},
textArea:
{
width: '730px',
margin: '5px 0px',
padding: '10px'
},
listPaper: {
maxHeight: 200,
overflowY: 'scroll',
},
paper: {
width: "850px",
minWidth: "850px"
}
}),
);

View File

@ -1,5 +1,5 @@
import React from "react";
import { FormattedMessage } from "react-intl";
import { FormattedMessage, useIntl } from "react-intl";
import Help from "@material-ui/icons/Help";
import PolicyOutlined from "@material-ui/icons/PolicyOutlined";
@ -11,10 +11,12 @@ import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Link from "@material-ui/core/Link";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import Tooltip from "@material-ui/core/Tooltip";
const HelpMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const intl = useIntl();
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
@ -26,11 +28,14 @@ const HelpMenu = (): React.ReactElement => {
return (
<span>
<IconButton
aria-haspopup="true"
onClick={handleMenu}>
<Help />
</IconButton>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })}>
<IconButton
aria-haspopup="true"
onClick={handleMenu}>
<Help />
</IconButton>
</Tooltip>
<Menu id="appbar-profile"
anchorEl={anchorEl}
keepMounted

View File

@ -74,7 +74,10 @@ const MapsPage = (): ReactElement => {
const mutation = useMutation(
(id: number) => client.deleteLabel(id),
{
onSuccess: () => queryClient.invalidateQueries('labels')
onSuccess: () => queryClient.invalidateQueries('labels'),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
}
);
@ -87,8 +90,8 @@ const MapsPage = (): ReactElement => {
mutation.mutate(id);
};
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => {
return await client.fetchLabels();
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => {
return client.fetchLabels();
});
const labels: Label[] = data ? data : [];
@ -140,7 +143,7 @@ const MapsPage = (): ReactElement => {
elevation={0}>
<Toolbar>
<Tooltip title={intl.formatMessage({ id: 'maps.create-tooltip', defaultMessage: 'Create a New Map' })}>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.create-tooltip', defaultMessage: 'Create a New Map' })}>
<Button color="primary"
size="medium"
variant="contained"
@ -153,7 +156,7 @@ const MapsPage = (): ReactElement => {
</Button>
</Tooltip>
<Tooltip title={intl.formatMessage({ id: 'maps.import-desc', defaultMessage: 'Import from other tools' })}>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.import-desc', defaultMessage: 'Import from other tools' })}>
<Button
color="primary"
size="medium"

View File

@ -32,6 +32,9 @@ const LanguageMenu = (): React.ReactElement => {
onSuccess: () => {
queryClient.invalidateQueries('account')
handleClose();
},
onError: (error) => {
console.error(`Unexpected error ${error}`)
}
}
);
@ -52,7 +55,7 @@ const LanguageMenu = (): React.ReactElement => {
const accountInfo = fetchAccount();
return (
<span>
<Tooltip title={intl.formatMessage({ id: 'language.change', defaultMessage: 'Change Language' })}>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'language.change', defaultMessage: 'Change Language' })}>
<Button
size="small"
variant="outlined"

View File

@ -196,7 +196,7 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
}
}
export const MapsList = (props: MapsListProps):React.ReactElement => {
export const MapsList = (props: MapsListProps): React.ReactElement => {
const classes = useStyles();
const [order, setOrder] = React.useState<Order>('asc');
const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
@ -225,8 +225,8 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
}, [props.filter.type, (props.filter as LabelFilter).label]);
const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', async () => {
return await client.fetchAllMaps();
const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return client.fetchAllMaps();
});
const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : [];
@ -346,7 +346,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
<div className={classes.toolbarActions}>
{selected.length > 0 &&
<Tooltip title="Delete selected">
<Tooltip arrow={true} title="Delete selected">
<Button
color="primary"
size="medium"
@ -362,7 +362,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
}
{selected.length > 0 &&
<Tooltip title="Add label to selected">
<Tooltip arrow={true} title="Add label to selected">
<Button
color="primary"
size="medium"
@ -456,7 +456,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
<TableCell
padding="checkbox"
className={classes.bodyCell}>
<Tooltip title="Starred">
<Tooltip arrow={true} title="Starred">
<IconButton aria-label="Starred" size="small" onClick={(e) => handleStarred(e, row.id)}>
<StarRateRoundedIcon color="action" style={{ color: row.starred ? 'yellow' : 'gray' }} />
</IconButton>
@ -464,7 +464,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell>
<TableCell className={classes.bodyCell}>
<Tooltip title="Open for edition" placement="bottom-start">
<Tooltip arrow={true} title="Open for edition" placement="bottom-start">
<Link href={`/c/maps/${row.id}/edit`} color="textPrimary" underline="always" onClick={(e) => e.stopPropagation()}>
{row.title}
</Link>
@ -480,7 +480,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell>
<TableCell className={classes.bodyCell}>
<Tooltip title={
<Tooltip arrow={true} title={
`Modified by ${row.lastModificationBy} on ${dayjs(row.lastModificationTime).format("lll")}`
} placement="bottom-start">
<span>{dayjs(row.lastModificationTime).fromNow()}</span>
@ -488,7 +488,7 @@ export const MapsList = (props: MapsListProps):React.ReactElement => {
</TableCell>
<TableCell className={classes.bodyCell}>
<Tooltip title={intl.formatMessage({ id: 'map.more-actions', defaultMessage: 'More Actions' })}>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'map.more-actions', defaultMessage: 'More Actions' })}>
<IconButton aria-label="Others" size="small" onClick={handleActionClick(row.id)}>
<MoreHorizIcon color="action" />
</IconButton>

View File

@ -0,0 +1,47 @@
import React from "react";
import Tooltip from "@material-ui/core/Tooltip";
import PersonSharpIcon from '@material-ui/icons/PersonSharp';
import EditSharpIcon from '@material-ui/icons/EditSharp';
import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp';
import { FormattedMessage } from "react-intl";
import { Role } from "../../../classes/client";
type RoleIconProps = {
role: Role;
}
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
return (
<span>
{role == 'owner' &&
<Tooltip
title={<FormattedMessage id="role.owner" defaultMessage="Onwer" />}
arrow={true}
>
<PersonSharpIcon />
</Tooltip>
}
{ role == 'editor' &&
<Tooltip
title={<FormattedMessage id="role.editor" defaultMessage="Editor" />}
arrow={true}>
<EditSharpIcon />
</Tooltip>
}
{role == 'viewer' &&
<Tooltip
title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />}
arrow={true}>
<VisibilitySharpIcon />
</Tooltip>
}
</span>)
};
export default RoleIcon;

View File

@ -76,7 +76,7 @@ export const fetchMapById = (id: number): MapLoadResult => {
});
const result = data?.find(m => m.id == id);
const map = result ? result : null;
const map = result || null;
return { isLoading: isLoading, error: error, map: map };
}