Skip to content

Commit 12dbe9f

Browse files
authored
add user profiles (#21)
GridAdmin can now display a list of profiles. We can: - add or delete a profile - edit a profile to define default LF parameters (this is a link on an existing element) - edit a user to select or remove its profile Signed-off-by: David BRAQUART <[email protected]>
1 parent 889ddee commit 12dbe9f

20 files changed

+1339
-35
lines changed

src/components/App/app-top-bar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
useState,
1515
} from 'react';
1616
import { capitalize, Tab, Tabs, useTheme } from '@mui/material';
17-
import { PeopleAlt } from '@mui/icons-material';
17+
import { ManageAccounts, PeopleAlt } from '@mui/icons-material';
1818
import { logout, TopBar } from '@gridsuite/commons-ui';
1919
import { useParameterState } from '../parameters';
2020
import {
@@ -47,6 +47,20 @@ const tabs = new Map<MainPaths, ReactElement>([
4747
))}
4848
/>,
4949
],
50+
[
51+
MainPaths.profiles,
52+
<Tab
53+
icon={<ManageAccounts />}
54+
label={<FormattedMessage id="appBar.tabs.profiles" />}
55+
href={`/${MainPaths.profiles}`}
56+
value={MainPaths.profiles}
57+
key={`tab-${MainPaths.profiles}`}
58+
iconPosition="start"
59+
LinkComponent={forwardRef((props, ref) => (
60+
<NavLink ref={ref} to={props.href} {...props} />
61+
))}
62+
/>,
63+
],
5064
]);
5165

5266
const AppTopBar: FunctionComponent = () => {

src/pages/common/paper-form.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { FunctionComponent } from 'react';
9+
import { Paper, PaperProps } from '@mui/material';
10+
11+
/*
12+
* <Paper> is defined in <Dialog> without generics, which default to `PaperProps => PaperProps<'div'>`,
13+
* so we must trick typescript check with a cast
14+
*/
15+
const PaperForm: FunctionComponent<
16+
PaperProps<'form'> & { untypedProps?: PaperProps }
17+
> = (props, context) => {
18+
const { untypedProps, ...formProps } = props;
19+
const othersProps = untypedProps as PaperProps<'form'>; //trust me ts
20+
return <Paper component="form" {...formProps} {...(othersProps ?? {})} />;
21+
};
22+
23+
export default PaperForm;

src/pages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
*/
77

88
export * from './users';
9+
export * from './profiles';
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { FunctionComponent, RefObject, useCallback } from 'react';
9+
import {
10+
Button,
11+
Dialog,
12+
DialogActions,
13+
DialogContent,
14+
DialogContentText,
15+
DialogTitle,
16+
InputAdornment,
17+
TextField,
18+
} from '@mui/material';
19+
import { FormattedMessage } from 'react-intl';
20+
import { Controller, useForm } from 'react-hook-form';
21+
import { ManageAccounts } from '@mui/icons-material';
22+
import { UserAdminSrv, UserProfile } from '../../services';
23+
import { useSnackMessage } from '@gridsuite/commons-ui';
24+
import { GridTableRef } from '../../components/Grid';
25+
import PaperForm from '../common/paper-form';
26+
27+
export interface AddProfileDialogProps {
28+
gridRef: RefObject<GridTableRef<UserProfile>>;
29+
open: boolean;
30+
setOpen: (open: boolean) => void;
31+
}
32+
33+
const AddProfileDialog: FunctionComponent<AddProfileDialogProps> = (props) => {
34+
const { snackError } = useSnackMessage();
35+
36+
const { handleSubmit, control, reset, clearErrors } = useForm<{
37+
name: string;
38+
}>({
39+
defaultValues: { name: '' }, //need default not undefined value for html input, else react error at runtime
40+
});
41+
42+
const addProfile = useCallback(
43+
(name: string) => {
44+
const profileData: UserProfile = {
45+
name: name,
46+
};
47+
UserAdminSrv.addProfile(profileData)
48+
.catch((error) =>
49+
snackError({
50+
messageTxt: error.message,
51+
headerId: 'profiles.table.error.add',
52+
})
53+
)
54+
.then(() => props.gridRef?.current?.context?.refresh?.());
55+
},
56+
[props.gridRef, snackError]
57+
);
58+
59+
const handleClose = useCallback(() => {
60+
props.setOpen(false);
61+
reset();
62+
clearErrors();
63+
}, [clearErrors, props, reset]);
64+
65+
const onSubmit = useCallback(
66+
(data: { name: string }) => {
67+
addProfile(data.name.trim());
68+
handleClose();
69+
},
70+
[addProfile, handleClose]
71+
);
72+
73+
return (
74+
<Dialog
75+
open={props.open}
76+
onClose={handleClose}
77+
PaperComponent={(props) => (
78+
<PaperForm
79+
untypedProps={props}
80+
onSubmit={handleSubmit(onSubmit)}
81+
/>
82+
)}
83+
>
84+
<DialogTitle>
85+
<FormattedMessage id="profiles.form.title" />
86+
</DialogTitle>
87+
<DialogContent>
88+
<DialogContentText>
89+
<FormattedMessage id="profiles.form.content" />
90+
</DialogContentText>
91+
<Controller
92+
name="name"
93+
control={control}
94+
rules={{ required: true, minLength: 1 }}
95+
render={({ field, fieldState, formState }) => (
96+
<TextField
97+
{...field}
98+
autoFocus
99+
required
100+
margin="dense"
101+
label={
102+
<FormattedMessage id="profiles.form.field.profilename.label" />
103+
}
104+
type="text"
105+
fullWidth
106+
variant="standard"
107+
inputMode="text"
108+
InputProps={{
109+
startAdornment: (
110+
<InputAdornment position="start">
111+
<ManageAccounts />
112+
</InputAdornment>
113+
),
114+
}}
115+
error={fieldState?.invalid}
116+
helperText={fieldState?.error?.message}
117+
/>
118+
)}
119+
/>
120+
</DialogContent>
121+
<DialogActions>
122+
<Button onClick={handleClose}>
123+
<FormattedMessage id="cancel" />
124+
</Button>
125+
<Button type="submit">
126+
<FormattedMessage id="ok" />
127+
</Button>
128+
</DialogActions>
129+
</Dialog>
130+
);
131+
};
132+
export default AddProfileDialog;

src/pages/profiles/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
export { default as Profiles } from './profiles-page';
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
// TODO: this file is going to be available soon in commons-ui
9+
10+
import { FunctionComponent } from 'react';
11+
import { FieldErrors, FormProvider } from 'react-hook-form';
12+
import { FormattedMessage } from 'react-intl';
13+
import {
14+
DialogActions,
15+
DialogContent,
16+
Grid,
17+
LinearProgress,
18+
Dialog,
19+
DialogTitle,
20+
} from '@mui/material';
21+
import { CancelButton, SubmitButton } from '@gridsuite/commons-ui';
22+
23+
interface ICustomMuiDialog {
24+
open: boolean;
25+
formSchema: any;
26+
formMethods: any;
27+
onClose: (event: React.MouseEvent) => void;
28+
onSave: (data: any) => void;
29+
onValidationError?: (errors: FieldErrors) => void;
30+
titleId: string;
31+
disabledSave?: boolean;
32+
removeOptional?: boolean;
33+
onCancel?: () => void;
34+
children: React.ReactNode;
35+
isDataFetching?: boolean;
36+
}
37+
38+
const styles = {
39+
dialogPaper: {
40+
'.MuiDialog-paper': {
41+
width: 'auto',
42+
minWidth: '800px',
43+
margin: 'auto',
44+
},
45+
},
46+
};
47+
48+
const CustomMuiDialog: FunctionComponent<ICustomMuiDialog> = ({
49+
open,
50+
formSchema,
51+
formMethods,
52+
onClose,
53+
onSave,
54+
isDataFetching = false,
55+
onValidationError,
56+
titleId,
57+
disabledSave,
58+
removeOptional = false,
59+
onCancel,
60+
children,
61+
}) => {
62+
const { handleSubmit } = formMethods;
63+
64+
const handleCancel = (event: React.MouseEvent) => {
65+
onCancel && onCancel();
66+
onClose(event);
67+
};
68+
69+
const handleClose = (event: React.MouseEvent, reason?: string) => {
70+
if (reason === 'backdropClick' && onCancel) {
71+
onCancel();
72+
}
73+
onClose(event);
74+
};
75+
76+
const handleValidate = (data: any) => {
77+
onSave(data);
78+
onClose(data);
79+
};
80+
81+
const handleValidationError = (errors: FieldErrors) => {
82+
onValidationError && onValidationError(errors);
83+
};
84+
85+
return (
86+
<FormProvider
87+
validationSchema={formSchema}
88+
{...formMethods}
89+
removeOptional={removeOptional}
90+
>
91+
<Dialog
92+
sx={styles.dialogPaper}
93+
open={open}
94+
onClose={handleClose}
95+
fullWidth
96+
>
97+
{isDataFetching && <LinearProgress />}
98+
<DialogTitle>
99+
<Grid item xs={11}>
100+
<FormattedMessage id={titleId} />
101+
</Grid>
102+
</DialogTitle>
103+
<DialogContent>{children}</DialogContent>
104+
<DialogActions>
105+
<CancelButton onClick={handleCancel} />
106+
<SubmitButton
107+
variant="outlined"
108+
disabled={disabledSave}
109+
onClick={handleSubmit(
110+
handleValidate,
111+
handleValidationError
112+
)}
113+
/>
114+
</DialogActions>
115+
</Dialog>
116+
</FormProvider>
117+
);
118+
};
119+
120+
export default CustomMuiDialog;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright (c) 2024, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
import { Typography, useTheme } from '@mui/material';
9+
import { useIntl } from 'react-intl';
10+
import { FunctionComponent } from 'react';
11+
12+
export interface LinkedPathDisplayProps {
13+
nameKey: string;
14+
value?: string;
15+
linkValidity?: boolean;
16+
}
17+
18+
const LinkedPathDisplay: FunctionComponent<LinkedPathDisplayProps> = (
19+
props
20+
) => {
21+
const intl = useIntl();
22+
const theme = useTheme();
23+
24+
return (
25+
<Typography
26+
sx={{
27+
fontWeight: 'bold',
28+
color:
29+
props.linkValidity === false
30+
? theme.palette.error.main
31+
: undefined,
32+
}}
33+
>
34+
{intl.formatMessage({
35+
id: props.nameKey,
36+
}) +
37+
' : ' +
38+
(props.value
39+
? props.value
40+
: intl.formatMessage({
41+
id:
42+
props.linkValidity === false
43+
? 'linked.path.display.invalidLink'
44+
: 'linked.path.display.noLink',
45+
}))}
46+
</Typography>
47+
);
48+
};
49+
50+
export default LinkedPathDisplay;

0 commit comments

Comments
 (0)