diff --git a/package.json b/package.json index 2054aff..0e9137d 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,9 @@ "eslint-config-react-app": "^7.0.1", "eslint-plugin-prettier": "^4.2.1", "jest-fetch-mock": "^3.0.3", - "prettier": "^2.2.0", - "ra-test": "^3.19.12" + "prettier": "^2.2.0" }, "dependencies": { - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.14.19", "@mui/material": "^5.14.8", "@mui/styles": "5.14.10", @@ -34,7 +31,7 @@ "ra-language-german": "^3.13.4", "ra-language-italian": "^3.13.1", "react": "^17.0.0", - "react-admin": "^3.19.12", + "react-admin": "^4.16.9", "react-dom": "^17.0.2", "react-scripts": "^5.0.1" }, diff --git a/src/App.js b/src/App.js index 3d6c4b1..58cdb22 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ import React from "react"; -import { Admin, Resource, resolveBrowserLocale } from "react-admin"; +import { + Admin, + CustomRoutes, + Resource, + resolveBrowserLocale, +} from "react-admin"; import polyglotI18nProvider from "ra-i18n-polyglot"; import authProvider from "./synapse/authProvider"; import dataProvider from "./synapse/dataProvider"; @@ -50,10 +55,10 @@ const App = () => ( authProvider={authProvider} dataProvider={dataProvider} i18nProvider={i18nProvider} - customRoutes={[ - , - ]} > + + } /> + { + const record = useRecordContext(rest); + const src = get(record, source)?.toString(); + return ; +}; + +export default AvatarField; diff --git a/src/components/LoginPage.js b/src/components/LoginPage.js index e6fb16f..33eb6be 100644 --- a/src/components/LoginPage.js +++ b/src/components/LoginPage.js @@ -1,19 +1,21 @@ import React, { useState, useEffect } from "react"; import { fetchUtils, + Form, FormDataConsumer, Notification, + required, useLogin, useNotify, - useLocale, - useSetLocale, + useLocaleState, useTranslate, PasswordInput, TextInput, } from "react-admin"; -import { Form, useForm } from "react-final-form"; +import { useFormContext } from "react-hook-form"; import { Avatar, + Box, Button, Card, CardActions, @@ -21,66 +23,64 @@ import { MenuItem, Select, TextField, + Typography, } from "@mui/material"; -import { makeStyles } from "@material-ui/core/styles"; +import { styled } from "@mui/material/styles"; import LockIcon from "@mui/icons-material/Lock"; -const useStyles = makeStyles(theme => ({ - main: { - display: "flex", - flexDirection: "column", - minHeight: "calc(100vh - 1em)", - alignItems: "center", - justifyContent: "flex-start", - background: "url(./images/floating-cogs.svg)", - backgroundColor: "#f9f9f9", - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - }, - card: { +const FormBox = styled(Box)(({ theme }) => ({ + display: "flex", + flexDirection: "column", + minHeight: "calc(100vh - 1em)", + alignItems: "center", + justifyContent: "flex-start", + background: "url(./images/floating-cogs.svg)", + backgroundColor: "#f9f9f9", + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + + [`& .card`]: { minWidth: "30em", marginTop: "6em", marginBottom: "6em", }, - avatar: { + [`& .avatar`]: { margin: "1em", display: "flex", justifyContent: "center", }, - icon: { - backgroundColor: theme.palette.secondary.main, + [`& .icon`]: { + backgroundColor: theme.palette.grey[500], }, - hint: { + [`& .hint`]: { marginTop: "1em", display: "flex", justifyContent: "center", - color: theme.palette.grey[500], + color: theme.palette.grey[600], }, - form: { + [`& .form`]: { padding: "0 1em 1em 1em", }, - input: { + [`& .input`]: { marginTop: "1em", }, - actions: { + [`& .actions`]: { padding: "0 1em 1em 1em", }, - serverVersion: { - color: "#9e9e9e", + [`& .serverVersion`]: { + color: theme.palette.grey[500], fontFamily: "Roboto, Helvetica, Arial, sans-serif", marginBottom: "1em", marginLeft: "0.5em", }, })); -const LoginPage = ({ theme }) => { - const classes = useStyles({ theme }); +const LoginPage = () => { const login = useLogin(); const notify = useNotify(); const [loading, setLoading] = useState(false); const [supportPassAuth, setSupportPassAuth] = useState(true); - var locale = useLocale(); - const setLocale = useSetLocale(); + const [locale, setLocale] = useLocaleState(); const translate = useTranslate(); const base_url = localStorage.getItem("base_url"); const cfg_base_url = process.env.REACT_APP_SERVER; @@ -135,28 +135,16 @@ const LoginPage = ({ theme }) => { /> ); - const validate = values => { - const errors = {}; - if (!values.username) { - errors.username = translate("ra.validation.required"); - } - if (!values.password) { - errors.password = translate("ra.validation.required"); - } - if (!values.base_url) { - errors.base_url = translate("ra.validation.required"); + const validateBaseUrl = value => { + if (!value.match(/^(http|https):\/\//)) { + return translate("synapseadmin.auth.protocol_error"); + } else if ( + !value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/) + ) { + return translate("synapseadmin.auth.url_error"); } else { - if (!values.base_url.match(/^(http|https):\/\//)) { - errors.base_url = translate("synapseadmin.auth.protocol_error"); - } else if ( - !values.base_url.match( - /^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/ - ) - ) { - errors.base_url = translate("synapseadmin.auth.url_error"); - } + return undefined; } - return errors; }; const handleSubmit = auth => { @@ -191,7 +179,7 @@ const LoginPage = ({ theme }) => { }; const UserData = ({ formData }) => { - const form = useForm(); + const form = useFormContext(); const [serverVersion, setServerVersion] = useState(""); const handleUsernameChange = _ => { @@ -204,11 +192,11 @@ const LoginPage = ({ theme }) => { fetchUtils .fetchJson(wellKnownUrl, { method: "GET" }) .then(({ json }) => { - form.change("base_url", json["m.homeserver"].base_url); + form.setValue("base_url", json["m.homeserver"].base_url); }) .catch(_ => { // if there is no .well-known entry, try the home server name - form.change("base_url", `https://${home_server}`); + form.setValue("base_url", `https://${home_server}`); }); } }; @@ -265,8 +253,8 @@ const LoginPage = ({ theme }) => { ); return ( -
-
+ <> + { onBlur={handleUsernameChange} resettable fullWidth + className="input" + validate={required()} /> -
-
+ + { disabled={loading || !supportPassAuth} resettable fullWidth + className="input" + validate={required()} /> -
-
+ + { disabled={cfg_base_url || loading} resettable fullWidth + className="input" + validate={[required(), validateBaseUrl]} /> -
-
{serverVersion}
-
+ + {serverVersion} + ); }; return (
( - -
- -
- - - -
-
- {translate("synapseadmin.auth.welcome")} -
-
-
- -
- - {formDataProps => } - -
- - - - -
- -
-
- )} - /> + mode="onTouched" + > + + + + + + + + {translate("synapseadmin.auth.welcome")} + + + + {formDataProps => } + + + + + + + + + + ); }; diff --git a/src/components/LoginPage.test.js b/src/components/LoginPage.test.js index 00c1267..a336e2d 100644 --- a/src/components/LoginPage.test.js +++ b/src/components/LoginPage.test.js @@ -1,14 +1,14 @@ import React from "react"; import { render } from "@testing-library/react"; -import { TestContext } from "ra-test"; +import { AdminContext } from "react-admin"; import LoginPage from "./LoginPage"; describe("LoginForm", () => { it("renders", () => { render( - + - + ); }); }); diff --git a/src/components/RoomDirectory.js b/src/components/RoomDirectory.js index d381d8f..aac42df 100644 --- a/src/components/RoomDirectory.js +++ b/src/components/RoomDirectory.js @@ -1,26 +1,28 @@ import React, { Fragment } from "react"; -import { Avatar, Chip } from "@mui/material"; -import { connect } from "react-redux"; import FolderSharedIcon from "@mui/icons-material/FolderShared"; import { BooleanField, BulkDeleteButton, Button, - Datagrid, + DatagridConfigurable, + ExportButton, DeleteButton, - Filter, List, NumberField, Pagination, + SelectColumnsButton, TextField, + TopToolbar, useCreate, - useMutation, + useListContext, useNotify, useTranslate, useRecordContext, useRefresh, useUnselectAll, } from "react-admin"; +import { useMutation } from "react-query"; +import AvatarField from "./AvatarField"; const RoomDirectoryPagination = props => ( @@ -59,26 +61,23 @@ export const RoomDirectoryBulkDeleteButton = props => ( /> ); -export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => { +export const RoomDirectoryBulkSaveButton = () => { + const { selectedIds } = useListContext(); const notify = useNotify(); const refresh = useRefresh(); const unselectAll = useUnselectAll(); - const [createMany, { loading }] = useMutation(); + const { createMany, isloading } = useMutation(); const handleSend = values => { createMany( + ["room_directory", "createMany", { ids: selectedIds, data: {} }], { - type: "createMany", - resource: "room_directory", - payload: { ids: selectedIds, data: {} }, - }, - { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.room_directory.action.send_success"); unselectAll("rooms"); refresh(); }, - onFailure: error => + onError: error => notify("resources.room_directory.action.send_failure", { type: "error", }), @@ -90,30 +89,29 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => { ); }; -export const RoomDirectorySaveButton = props => { +export const RoomDirectorySaveButton = () => { const record = useRecordContext(); const notify = useNotify(); const refresh = useRefresh(); - const [create, { loading }] = useCreate("room_directory"); + const [create, { isloading }] = useCreate(); const handleSend = values => { create( + "room_directory", + { data: { id: record.id } }, { - payload: { data: { id: record.id } }, - }, - { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.room_directory.action.send_success"); refresh(); }, - onFailure: error => + onError: error => notify("resources.room_directory.action.send_failure", { type: "error", }), @@ -125,127 +123,78 @@ export const RoomDirectorySaveButton = props => { ); }; -const RoomDirectoryBulkActionButtons = props => ( +const RoomDirectoryBulkActionButtons = () => ( - + ); -const AvatarField = ({ source, className, record = {} }) => ( - +const RoomDirectoryListActions = () => ( + + + + ); -const RoomDirectoryFilter = ({ ...props }) => { - const translate = useTranslate(); - return ( - - - - - - ); -}; - -export const FilterableRoomDirectoryList = ({ - roomDirectoryFilters, - dispatch, - ...props -}) => { - const filter = roomDirectoryFilters; - const roomIdFilter = filter && filter.room_id ? true : false; - const topicFilter = filter && filter.topic ? true : false; - const canonicalAliasFilter = filter && filter.canonical_alias ? true : false; - - return ( - } +export const RoomDirectoryList = () => ( + } + perPage={100} + actions={} + > + "/rooms/" + id + "/show"} bulkActionButtons={} - filters={} - perPage={100} + omit={["room_id", "canonical_alias", "topic"]} > - "/rooms/" + id + "/show"}> - - - {roomIdFilter && ( - - )} - {canonicalAliasFilter && ( - - )} - {topicFilter && ( - - )} - - - - - - ); -}; - -function mapStateToProps(state) { - return { - roomDirectoryFilters: - state.admin.resources.room_directory.list.params.displayedFilters, - }; -} - -export const RoomDirectoryList = connect(mapStateToProps)( - FilterableRoomDirectoryList + + + + + + + + + + ); diff --git a/src/components/ServerNotices.js b/src/components/ServerNotices.js index 88d67be..631a849 100644 --- a/src/components/ServerNotices.js +++ b/src/components/ServerNotices.js @@ -7,12 +7,13 @@ import { Toolbar, required, useCreate, - useMutation, + useListContext, useNotify, useRecordContext, useTranslate, useUnselectAll, } from "react-admin"; +import { useMutation } from "react-query"; import MessageIcon from "@mui/icons-material/Message"; import IconCancel from "@mui/icons-material/Cancel"; import { @@ -48,7 +49,6 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => { } - submitOnEnter={false} redirect={false} save={onSend} > @@ -67,11 +67,11 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => { ); }; -export const ServerNoticeButton = props => { +export const ServerNoticeButton = () => { const record = useRecordContext(); const [open, setOpen] = useState(false); const notify = useNotify(); - const [create, { loading }] = useCreate("servernotices"); + const [create, { isloading }] = useCreate("servernotices"); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); @@ -84,7 +84,7 @@ export const ServerNoticeButton = props => { notify("resources.servernotices.action.send_success"); handleDialogClose(); }, - onFailure: () => + onError: () => notify("resources.servernotices.action.send_failure", { type: "error", }), @@ -97,7 +97,7 @@ export const ServerNoticeButton = props => { @@ -110,29 +110,26 @@ export const ServerNoticeButton = props => { ); }; -export const ServerNoticeBulkButton = ({ selectedIds }) => { +export const ServerNoticeBulkButton = () => { + const { selectedIds } = useListContext(); const [open, setOpen] = useState(false); const notify = useNotify(); const unselectAll = useUnselectAll(); - const [createMany, { loading }] = useMutation(); + const { createMany, isloading } = useMutation(); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); const handleSend = values => { createMany( + ["servernotices", "createMany", { ids: selectedIds, data: values }], { - type: "createMany", - resource: "servernotices", - payload: { ids: selectedIds, data: values }, - }, - { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.servernotices.action.send_success"); unselectAll("users"); handleDialogClose(); }, - onFailure: error => + onError: error => notify("resources.servernotices.action.send_failure", { type: "error", }), @@ -145,7 +142,7 @@ export const ServerNoticeBulkButton = ({ selectedIds }) => { diff --git a/src/components/destinations.js b/src/components/destinations.js index c8c9f8d..1a734a9 100644 --- a/src/components/destinations.js +++ b/src/components/destinations.js @@ -37,11 +37,11 @@ const date_format = { second: "2-digit", }; -const destinationRowStyle = (record, index) => ({ +const destinationRowSx = (record, _index) => ({ backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white", }); -const DestinationFilter = ({ ...props }) => { +const DestinationFilter = props => { return ( @@ -71,7 +71,7 @@ export const DestinationReconnectButton = props => { }); refresh(); }, - onFailure: () => { + onError: () => { notify("ra.message.error", { type: "error" }); }, } @@ -112,11 +112,11 @@ export const DestinationList = props => { filters={} pagination={} sort={{ field: "destination", order: "ASC" }} - bulkActionButtons={false} > `${basePath}/${id}/show/rooms`} + rowSx={destinationRowSx} + rowClick={(id, _resource, _record) => `${id}/show/rooms`} + bulkActionButtons={false} > @@ -160,7 +160,7 @@ export const DestinationShow = props => { > `/rooms/${id}/show`} + rowClick={(id, resource, record) => `/rooms/${id}/show`} > ({ - deleteButton: { - color: theme.palette.error.main, - "&:hover": { - backgroundColor: alpha(theme.palette.error.main, 0.12), - // Reset on mouse devices - "@media (hover: none)": { - backgroundColor: "transparent", - }, - }, - }, - }), - { name: "RaDeleteDeviceButton" } -); +import { alpha, useTheme } from "@mui/material/styles"; export const DeviceRemoveButton = props => { + const theme = useTheme(); const record = useRecordContext(); - const classes = useStyles(props); const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); @@ -63,7 +45,16 @@ export const DeviceRemoveButton = props => { diff --git a/src/components/media.js b/src/components/media.js index 09a1377..5c2f361 100644 --- a/src/components/media.js +++ b/src/components/media.js @@ -1,7 +1,4 @@ import React, { Fragment, useState } from "react"; -import classnames from "classnames"; -import { alpha } from "@mui/material/styles"; -import { makeStyles } from "@material-ui/core/styles"; import { BooleanInput, Button, @@ -30,22 +27,7 @@ import { import IconCancel from "@mui/icons-material/Cancel"; import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; - -const useStyles = makeStyles( - theme => ({ - deleteButton: { - color: theme.palette.error.main, - "&:hover": { - backgroundColor: alpha(theme.palette.error.main, 0.12), - // Reset on mouse devices - "@media (hover: none)": { - backgroundColor: "transparent", - }, - }, - }, - }), - { name: "RaDeleteDeviceButton" } -); +import { alpha, useTheme } from "@mui/material/styles"; const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { const translate = useTranslate(); @@ -81,7 +63,6 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { } - submitOnEnter={false} redirect={false} save={onSend} > @@ -113,10 +94,10 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { }; export const DeleteMediaButton = props => { - const classes = useStyles(props); + const theme = useTheme(); const [open, setOpen] = useState(false); const notify = useNotify(); - const [deleteOne, { loading }] = useDelete("delete_media"); + const [deleteOne, { isLoading }] = useDelete("delete_media"); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); @@ -129,7 +110,7 @@ export const DeleteMediaButton = props => { notify("resources.delete_media.action.send_success"); handleDialogClose(); }, - onFailure: () => + onError: () => notify("resources.delete_media.action.send_failure", { type: "error", }), @@ -142,8 +123,17 @@ export const DeleteMediaButton = props => { @@ -174,7 +164,7 @@ export const ProtectMediaButton = props => { notify("resources.protect_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.protect_media.action.send_failure", { type: "error", }), @@ -190,7 +180,7 @@ export const ProtectMediaButton = props => { notify("resources.protect_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.protect_media.action.send_failure", { type: "error", }), @@ -270,7 +260,7 @@ export const QuarantineMediaButton = props => { notify("resources.quarantine_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.quarantine_media.action.send_failure", { type: "error", }), @@ -286,7 +276,7 @@ export const QuarantineMediaButton = props => { notify("resources.quarantine_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.quarantine_media.action.send_failure", { type: "error", }), diff --git a/src/components/rooms.js b/src/components/rooms.js index ecdbe74..eff2f16 100644 --- a/src/components/rooms.js +++ b/src/components/rooms.js @@ -1,18 +1,21 @@ import React, { Fragment } from "react"; -import { connect } from "react-redux"; import { BooleanField, BulkDeleteButton, DateField, Datagrid, + DatagridConfigurable, DeleteButton, + ExportButton, Filter, + FunctionField, List, NumberField, Pagination, ReferenceField, ReferenceManyField, SearchInput, + SelectColumnsButton, SelectField, Show, Tab, @@ -22,9 +25,7 @@ import { useRecordContext, useTranslate, } from "react-admin"; -import get from "lodash/get"; -import PropTypes from "prop-types"; -import { Tooltip, Typography, Chip } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; import FastForwardIcon from "@mui/icons-material/FastForward"; import HttpsIcon from "@mui/icons-material/Https"; @@ -54,32 +55,6 @@ const RoomPagination = props => ( ); -const EncryptionField = ({ source, record = {}, emptyText }) => { - const translate = useTranslate(); - const value = get(record, source); - let ariaLabel = value === false ? "ra.boolean.false" : "ra.boolean.true"; - - if (value === false || value === true) { - return ( - - - {value === true ? ( - - ) : ( - - )} - - - ); - } - - return ( - - {emptyText} - - ); -}; - const RoomTitle = props => { const record = useRecordContext(); const translate = useTranslate(); @@ -95,7 +70,7 @@ const RoomTitle = props => { ); }; -const RoomShowActions = ({ basePath, data, resource }) => { +const RoomShowActions = ({ data, resource }) => { var roomDirectoryStatus = ""; if (data) { roomDirectoryStatus = data.public; @@ -110,7 +85,6 @@ const RoomShowActions = ({ basePath, data, resource }) => { )} { > "/users/" + id} + rowClick={(id, resource, record) => "/users/" + id} > { ); }; -const RoomBulkActionButtons = props => ( +const RoomBulkActionButtons = () => ( - - + + ( ); -const RoomFilter = ({ ...props }) => { - const translate = useTranslate(); - return ( - - - - - - - - ); -}; +const RoomFilter = props => ( + + + +); -const RoomNameField = props => { - const { source } = props; - const record = useRecordContext(); - return ( - {record[source] || record["canonical_alias"] || record["id"]} - ); -}; +const RoomListActions = () => ( + + + + +); -RoomNameField.propTypes = { - label: PropTypes.string, - record: PropTypes.object, - source: PropTypes.string.isRequired, -}; - -const FilterableRoomList = ({ roomFilters, dispatch, ...props }) => { - const filter = roomFilters; - const localMembersFilter = - filter && filter.joined_local_members ? true : false; - const stateEventsFilter = filter && filter.state_events ? true : false; - const versionFilter = filter && filter.version ? true : false; - const federateableFilter = filter && filter.federatable ? true : false; +export const RoomList = () => { + const theme = useTheme(); return ( } sort={{ field: "name", order: "ASC" }} filters={} - bulkActionButtons={} + actions={} > - - } + omit={[ + "joined_local_members", + "state_events", + "version", + "federatable", + ]} + > + } + sx={{ + [`& [data-testid="true"]`]: { color: theme.palette.success.main }, + [`& [data-testid="false"]`]: { color: theme.palette.error.main }, + }} + /> + + record["name"] || record["canonical_alias"] || record["id"] + } /> - - {localMembersFilter && } - {stateEventsFilter && } - {versionFilter && } - {federateableFilter && } + + + + - + ); }; - -function mapStateToProps(state) { - return { - roomFilters: state.admin.resources.rooms.list.params.displayedFilters, - }; -} - -export const RoomList = connect(mapStateToProps)(FilterableRoomList); diff --git a/src/components/statistics.js b/src/components/statistics.js index 74e6b70..603cba1 100644 --- a/src/components/statistics.js +++ b/src/components/statistics.js @@ -65,9 +65,11 @@ export const UserMediaStatsList = props => { filters={} pagination={} sort={{ field: "media_length", order: "DESC" }} - bulkActionButtons={false} > - "/users/" + id + "/media"}> + "/users/" + id + "/media"} + bulkActionButtons={false} + > { - return { - pathname: "/import_users", - }; -}; - const choices_medium = [ { id: "email", name: "resources.users.email" }, { id: "msisdn", name: "resources.users.msisdn" }, @@ -88,7 +82,6 @@ const UserListActions = ({ filterValues, permanentFilter, hasCreate, // you can hide CreateButton if hasCreate = false - basePath, selectedIds, onUnselectItems, showFilter, @@ -106,7 +99,7 @@ const UserListActions = ({ filterValues, context: "button", })} - + {/* Add your custom actions */} - @@ -156,10 +149,6 @@ const UserBulkActionButtons = props => ( ); -const AvatarField = ({ source, record = {}, sx }) => ( - -); - export const UserList = props => { return ( { filterDefaultValues={{ guests: true, deactivated: false }} sort={{ field: "name", order: "ASC" }} actions={} - bulkActionButtons={} pagination={} > - + }> ( - + ); @@ -288,7 +276,6 @@ export const UserCreate = props => ( source="user_type" choices={choices_type} translateChoice={false} - allowEmpty={true} resettable /> @@ -354,7 +341,6 @@ export const UserEdit = props => { source="user_type" choices={choices_type} translateChoice={false} - allowEmpty={true} resettable /> @@ -498,7 +484,7 @@ export const UserEdit = props => { > "/rooms/" + id + "/show"} + rowClick={(id, resource, record) => "/rooms/" + id + "/show"} >