@deix/rossini-identity-management 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app/[locale]/groups/{page.jsx → page.js} +1 -1
- package/lib/app/[locale]/{layout.jsx → layout.js} +3 -4
- package/lib/app/[locale]/users/{page.jsx → page.js} +1 -1
- package/lib/app/{layout.jsx → layout.js} +3 -7
- package/lib/components/AddItemDialog.js +45 -0
- package/lib/components/DeleteConfirmationDialog.js +35 -0
- package/lib/components/SearchField.js +16 -0
- package/lib/components/groups/{CreateGroupDialog.jsx → CreateGroupDialog.js} +4 -6
- package/lib/components/groups/{GroupDetails.jsx → GroupDetails.js} +2 -3
- package/lib/components/groups/GroupForm.js +38 -0
- package/lib/components/groups/GroupTabs.js +29 -0
- package/lib/components/groups/GroupUsers.js +73 -0
- package/lib/components/groups/Groups.js +97 -0
- package/lib/components/groups/GroupsTable.js +43 -0
- package/lib/components/users/{CreateUserDialog.jsx → CreateUserDialog.js} +4 -6
- package/lib/components/users/{UserCredentials.jsx → UserCredentials.js} +2 -5
- package/lib/components/users/{UserDetails.jsx → UserDetails.js} +2 -3
- package/lib/components/users/UserForm.js +66 -0
- package/lib/components/users/UserGroups.js +63 -0
- package/lib/components/users/{Users.jsx → Users.js} +41 -54
- package/lib/components/users/UsersTable.js +64 -0
- package/lib/components/users/UsersTabs.js +32 -0
- package/lib/config/{index.jsx → index.js} +1 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/hooks/{useAPIClient.jsx → useAPIClient.js} +2 -4
- package/package.json +6 -5
- package/lib/components/AddItemDialog.jsx +0 -50
- package/lib/components/DeleteConfirmationDialog.jsx +0 -44
- package/lib/components/SearchField.jsx +0 -18
- package/lib/components/groups/GroupForm.jsx +0 -46
- package/lib/components/groups/GroupTabs.jsx +0 -33
- package/lib/components/groups/GroupUsers.jsx +0 -88
- package/lib/components/groups/Groups.jsx +0 -110
- package/lib/components/groups/GroupsTable.jsx +0 -55
- package/lib/components/users/UserForm.jsx +0 -79
- package/lib/components/users/UserGroups.jsx +0 -78
- package/lib/components/users/UsersTable.jsx +0 -78
- package/lib/components/users/UsersTabs.jsx +0 -36
|
@@ -6,13 +6,11 @@ const APIContext = createContext(axios.create({
|
|
|
6
6
|
},
|
|
7
7
|
}));
|
|
8
8
|
export const APIClientProvider = ({ baseURL, children, }) => {
|
|
9
|
-
return (
|
|
9
|
+
return (React.createElement(APIContext.Provider, { value: axios.create({
|
|
10
10
|
baseURL,
|
|
11
11
|
headers: {
|
|
12
12
|
Accept: 'application/json',
|
|
13
13
|
},
|
|
14
|
-
})}
|
|
15
|
-
{children}
|
|
16
|
-
</APIContext.Provider>);
|
|
14
|
+
}) }, children));
|
|
17
15
|
};
|
|
18
16
|
export const useAPIClient = () => useContext(APIContext);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deix/rossini-identity-management",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@babel/core": "^7.23.7",
|
|
39
|
-
"@chromatic-com/storybook": "^4.0.1",
|
|
40
39
|
"@emotion/react": "^11.14.0",
|
|
41
40
|
"@emotion/styled": "^11.14.0",
|
|
42
41
|
"@eslint/js": "^9.15.0",
|
|
@@ -45,11 +44,11 @@
|
|
|
45
44
|
"@mdx-js/react": "^3.1.0",
|
|
46
45
|
"@mui/icons-material": "^7.2.0",
|
|
47
46
|
"@mui/material": "^7.2.0",
|
|
48
|
-
"@storybook/addon-docs": "
|
|
47
|
+
"@storybook/addon-docs": "9.0.15",
|
|
49
48
|
"@storybook/addon-links": "^9.0.15",
|
|
50
49
|
"@storybook/addon-onboarding": "^9.0.15",
|
|
51
|
-
"@storybook/mdx2-csf": "^1.1.0",
|
|
52
50
|
"@storybook/nextjs": "^9.0.15",
|
|
51
|
+
"@storybook/mdx2-csf": "^1.1.0",
|
|
53
52
|
"@tanstack/react-query": "^5.17.12",
|
|
54
53
|
"@tanstack/react-query-devtools": "^5.17.12",
|
|
55
54
|
"@types/node": "^24.0.10",
|
|
@@ -69,7 +68,6 @@
|
|
|
69
68
|
"react-docgen-typescript-plugin": "^1.0.5",
|
|
70
69
|
"react-dom": "^19.0.0",
|
|
71
70
|
"remark-mdx": "^3.0.0",
|
|
72
|
-
"storybook": "^9.0.15",
|
|
73
71
|
"ts-loader": "^9.5.1",
|
|
74
72
|
"typescript": "^5.7.3",
|
|
75
73
|
"typescript-eslint": "^8.24.1"
|
|
@@ -91,5 +89,8 @@
|
|
|
91
89
|
"@mui/material": "^7.2.0",
|
|
92
90
|
"notistack": "^3.0.1",
|
|
93
91
|
"react-hook-form": "^7.49.3"
|
|
92
|
+
},
|
|
93
|
+
"overrides": {
|
|
94
|
+
"tmp": "^0.2.4"
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Autocomplete, Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, } from '@mui/material';
|
|
3
|
-
import { useLocale } from '@deix/rossini-core';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
5
|
-
const translations = {
|
|
6
|
-
ok: {
|
|
7
|
-
en: 'Add',
|
|
8
|
-
it: 'Aggiungi',
|
|
9
|
-
},
|
|
10
|
-
cancel: {
|
|
11
|
-
en: 'Cancel',
|
|
12
|
-
it: 'Annulla',
|
|
13
|
-
},
|
|
14
|
-
search: {
|
|
15
|
-
en: 'Search...',
|
|
16
|
-
it: 'Cerca...',
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
const AddItemDialog = ({ title, options, open, onClose, onConfirm, }) => {
|
|
20
|
-
const locale = useLocale();
|
|
21
|
-
const [value, setValue] = useState(null);
|
|
22
|
-
// Reset the value when the dialog is closed
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (!open) {
|
|
25
|
-
setValue(null);
|
|
26
|
-
}
|
|
27
|
-
}, [open]);
|
|
28
|
-
return (<Dialog open={open} fullWidth maxWidth='sm' onClose={onClose}>
|
|
29
|
-
<DialogTitle>{title[locale]}</DialogTitle>
|
|
30
|
-
<DialogContent>
|
|
31
|
-
<Autocomplete
|
|
32
|
-
// multiple
|
|
33
|
-
options={options ?? []} getOptionLabel={(option) => option.label} getOptionKey={(option) => option.value} isOptionEqualToValue={(option, value) => option.value === value.value} renderInput={(params) => (<TextField {...params} label={translations.search[locale]} placeholder=''/>)} onChange={(event, newValue) => {
|
|
34
|
-
setValue(newValue);
|
|
35
|
-
}} value={value}/>
|
|
36
|
-
</DialogContent>
|
|
37
|
-
<DialogActions>
|
|
38
|
-
<Button size='small' variant='outlined' onClick={onClose}>
|
|
39
|
-
{translations.cancel[locale]}
|
|
40
|
-
</Button>
|
|
41
|
-
<Button variant='contained' onClick={() => {
|
|
42
|
-
onConfirm(value);
|
|
43
|
-
onClose();
|
|
44
|
-
}}>
|
|
45
|
-
{translations.ok[locale]}
|
|
46
|
-
</Button>
|
|
47
|
-
</DialogActions>
|
|
48
|
-
</Dialog>);
|
|
49
|
-
};
|
|
50
|
-
export default AddItemDialog;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography, } from '@mui/material';
|
|
3
|
-
import { useLocale } from '@deix/rossini-core';
|
|
4
|
-
const translations = {
|
|
5
|
-
title: {
|
|
6
|
-
en: 'Delete item?',
|
|
7
|
-
it: 'Cancellare elemento?',
|
|
8
|
-
},
|
|
9
|
-
message: {
|
|
10
|
-
en: 'This is a permanent action',
|
|
11
|
-
it: "Questa è un'azione permanente",
|
|
12
|
-
},
|
|
13
|
-
yes: {
|
|
14
|
-
en: 'Yes',
|
|
15
|
-
it: 'Si',
|
|
16
|
-
},
|
|
17
|
-
no: {
|
|
18
|
-
en: 'No',
|
|
19
|
-
it: 'No',
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
const DeleteConfirmationDialog = ({ open, onClose, onConfirm, }) => {
|
|
23
|
-
const locale = useLocale();
|
|
24
|
-
return (<Dialog open={open} fullWidth maxWidth='sm' onClose={onClose}>
|
|
25
|
-
<DialogTitle>{translations.title[locale]}</DialogTitle>
|
|
26
|
-
<DialogContent>
|
|
27
|
-
<Typography variant='caption'>
|
|
28
|
-
{translations.message[locale]}
|
|
29
|
-
</Typography>
|
|
30
|
-
</DialogContent>
|
|
31
|
-
<DialogActions>
|
|
32
|
-
<Button size='small' variant='outlined' onClick={onClose}>
|
|
33
|
-
{translations.no[locale]}
|
|
34
|
-
</Button>
|
|
35
|
-
<Button size='small' variant='contained' onClick={() => {
|
|
36
|
-
onConfirm();
|
|
37
|
-
onClose();
|
|
38
|
-
}}>
|
|
39
|
-
{translations.yes[locale]}
|
|
40
|
-
</Button>
|
|
41
|
-
</DialogActions>
|
|
42
|
-
</Dialog>);
|
|
43
|
-
};
|
|
44
|
-
export default DeleteConfirmationDialog;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import SearchIcon from '@mui/icons-material/Search';
|
|
3
|
-
import { IconButton, TextField, } from '@mui/material';
|
|
4
|
-
import { useState } from 'react';
|
|
5
|
-
const SearchField = ({ placeholder = 'Search...', onSearch, ...props }) => {
|
|
6
|
-
const [value, setValue] = useState('');
|
|
7
|
-
return (<>
|
|
8
|
-
<TextField size='small' placeholder={placeholder} value={value} onChange={(evt) => {
|
|
9
|
-
setValue(evt.target.value);
|
|
10
|
-
onSearch(evt.target.value);
|
|
11
|
-
}} {...props} InputProps={{
|
|
12
|
-
endAdornment: (<IconButton size='small' onClick={() => onSearch(value)}>
|
|
13
|
-
<SearchIcon />
|
|
14
|
-
</IconButton>),
|
|
15
|
-
}}/>
|
|
16
|
-
</>);
|
|
17
|
-
};
|
|
18
|
-
export default SearchField;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useLocale } from '@deix/rossini-core';
|
|
3
|
-
import { Button, Grid, TextField } from '@mui/material';
|
|
4
|
-
import { Controller, useForm } from 'react-hook-form';
|
|
5
|
-
import { useEffect } from 'react';
|
|
6
|
-
import groupsTranslations from '../../lang/groups';
|
|
7
|
-
const GroupForm = ({ group, onCancel, onSave, mode, editName, }) => {
|
|
8
|
-
const locale = useLocale();
|
|
9
|
-
const { control, handleSubmit, setValue } = useForm({
|
|
10
|
-
defaultValues: {
|
|
11
|
-
id: group?.id,
|
|
12
|
-
name: group?.name || '',
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (group) {
|
|
17
|
-
setValue('id', group?.id);
|
|
18
|
-
setValue('name', group?.name);
|
|
19
|
-
}
|
|
20
|
-
}, [group, setValue]);
|
|
21
|
-
const onSubmit = (data) => {
|
|
22
|
-
onSave(data);
|
|
23
|
-
};
|
|
24
|
-
return (<form onSubmit={(event) => void handleSubmit(onSubmit)(event)}>
|
|
25
|
-
<Grid container spacing={2} sx={{ mt: 2, maxWidth: 'sm' }} justifyContent='stretch'>
|
|
26
|
-
<Grid size={{ xs: 12 }}>
|
|
27
|
-
<Controller name='name' control={control} render={({ field }) => (<TextField size='small' fullWidth label={groupsTranslations.name[locale]} disabled={mode === 'modify' && !editName} sx={{ flexShrink: 0 }} {...field}/>)}/>
|
|
28
|
-
</Grid>
|
|
29
|
-
<Grid sx={{
|
|
30
|
-
mt: 2,
|
|
31
|
-
display: 'flex',
|
|
32
|
-
justifyContent: 'flex-end',
|
|
33
|
-
gap: 2,
|
|
34
|
-
width: '100%',
|
|
35
|
-
}}>
|
|
36
|
-
{mode === 'create' && (<Button variant='outlined' onClick={onCancel}>
|
|
37
|
-
{groupsTranslations.cancel[locale]}
|
|
38
|
-
</Button>)}
|
|
39
|
-
<Button variant='contained' type='submit'>
|
|
40
|
-
{groupsTranslations.save[locale]}
|
|
41
|
-
</Button>
|
|
42
|
-
</Grid>
|
|
43
|
-
</Grid>
|
|
44
|
-
</form>);
|
|
45
|
-
};
|
|
46
|
-
export default GroupForm;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState } from 'react';
|
|
3
|
-
import { Box, Tab, Tabs } from '@mui/material';
|
|
4
|
-
import { useLocale } from '@deix/rossini-core';
|
|
5
|
-
import GroupDetails from './GroupDetails';
|
|
6
|
-
import groupsTranslations from '../../lang/groups';
|
|
7
|
-
import GroupUsers from './GroupUsers';
|
|
8
|
-
const GroupTabs = ({ group, additionalTabs = [], onGroupUpdate, editName, }) => {
|
|
9
|
-
const locale = useLocale();
|
|
10
|
-
const [selectedTab, setSelectedTab] = useState('DETAILS');
|
|
11
|
-
return (<Box sx={{
|
|
12
|
-
flexGrow: 1,
|
|
13
|
-
maxWidth: '100%',
|
|
14
|
-
overflowX: 'hidden',
|
|
15
|
-
alignSelf: 'stretch',
|
|
16
|
-
}}>
|
|
17
|
-
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
|
18
|
-
<Tabs value={selectedTab} onChange={(_evt, newValue) => {
|
|
19
|
-
setSelectedTab(newValue);
|
|
20
|
-
}} aria-label='basic tabs example'>
|
|
21
|
-
<Tab label={groupsTranslations.details[locale]} value='DETAILS'/>
|
|
22
|
-
<Tab label={groupsTranslations.users[locale]} value='USERS'/>
|
|
23
|
-
{additionalTabs.map((tab, index) => (<Tab key={`tab${index}`} label={tab.title[locale]} value={`ADDITIONAL_TAB_${index}`}/>))}
|
|
24
|
-
</Tabs>
|
|
25
|
-
</Box>
|
|
26
|
-
<Box sx={{ p: 1 }}>
|
|
27
|
-
{selectedTab === 'DETAILS' && (<GroupDetails group={group} onGroupUpdate={onGroupUpdate} editName={editName}/>)}
|
|
28
|
-
{selectedTab === 'USERS' && <GroupUsers group={group}/>}
|
|
29
|
-
{additionalTabs.map((tab, index) => selectedTab === `ADDITIONAL_TAB_${index}` && (<tab.component key={`tab${index}`} group={group}/>))}
|
|
30
|
-
</Box>
|
|
31
|
-
</Box>);
|
|
32
|
-
};
|
|
33
|
-
export default GroupTabs;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Button, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
|
|
3
|
-
import AddIcon from '@mui/icons-material/Add';
|
|
4
|
-
import DeleteIcon from '@mui/icons-material/Delete';
|
|
5
|
-
import { isStringTranslation, useLocale } from '@deix/rossini-core';
|
|
6
|
-
import { useAddUserToGroup, useRemoveUserFromGroup, useUsers, } from '../../utils/hooks/users';
|
|
7
|
-
import { useState } from 'react';
|
|
8
|
-
import DeleteConfirmationDialog from '../DeleteConfirmationDialog';
|
|
9
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
10
|
-
import AddItemDialog from '../AddItemDialog';
|
|
11
|
-
import { useGroupUsers } from '../../utils/hooks/groups';
|
|
12
|
-
import groupsTranslations from '../../lang/groups';
|
|
13
|
-
import { useAPIClient } from '../../utils/hooks/useAPIClient';
|
|
14
|
-
const groupUsersTableColumns = [
|
|
15
|
-
{
|
|
16
|
-
id: 'username',
|
|
17
|
-
label: { en: 'Username', it: 'Username' },
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: 'firstName',
|
|
21
|
-
label: { en: 'First name', it: 'Nome' },
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'lastName',
|
|
25
|
-
label: { en: 'Last name', it: 'Cognome' },
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
const GroupUsers = ({ group }) => {
|
|
29
|
-
const locale = useLocale();
|
|
30
|
-
const apiClient = useAPIClient();
|
|
31
|
-
const queryClient = useQueryClient();
|
|
32
|
-
const { data: users } = useUsers(apiClient);
|
|
33
|
-
const { data: groupUsers } = useGroupUsers(group?.id, apiClient);
|
|
34
|
-
const { mutate: addUserToGroup } = useAddUserToGroup(queryClient, apiClient);
|
|
35
|
-
const { mutate: removeUserFromGroup } = useRemoveUserFromGroup(queryClient, apiClient);
|
|
36
|
-
const [openAddUserDialog, setOpenAddUserDialog] = useState(false);
|
|
37
|
-
const [openConfirmationDialog, setOpenConfirmationDialog] = useState(false);
|
|
38
|
-
const [deleteId, setDeleteId] = useState();
|
|
39
|
-
return (<>
|
|
40
|
-
{groupUsers && groupUsers.length > 0 && (<Box maxWidth='md'>
|
|
41
|
-
<TableContainer>
|
|
42
|
-
<Table size='small'>
|
|
43
|
-
<TableHead>
|
|
44
|
-
<TableRow>
|
|
45
|
-
{groupUsersTableColumns.map((col) => (<TableCell key={col.id} align={col.align || 'inherit'}>
|
|
46
|
-
{isStringTranslation(col.label)
|
|
47
|
-
? col.label[locale]
|
|
48
|
-
: col.label}
|
|
49
|
-
</TableCell>))}
|
|
50
|
-
<TableCell padding='none'>{''}</TableCell>
|
|
51
|
-
</TableRow>
|
|
52
|
-
</TableHead>
|
|
53
|
-
<TableBody>
|
|
54
|
-
{groupUsers?.map((user, rowIdx) => (<TableRow key={rowIdx}>
|
|
55
|
-
<TableCell>{user.username}</TableCell>
|
|
56
|
-
<TableCell>{user.firstName}</TableCell>
|
|
57
|
-
<TableCell>{user.lastName}</TableCell>
|
|
58
|
-
<TableCell padding='none' align='right'>
|
|
59
|
-
<IconButton size='small' onClick={(evt) => {
|
|
60
|
-
evt.stopPropagation();
|
|
61
|
-
setOpenConfirmationDialog(true);
|
|
62
|
-
setDeleteId(user.id);
|
|
63
|
-
}}>
|
|
64
|
-
<DeleteIcon />
|
|
65
|
-
</IconButton>
|
|
66
|
-
</TableCell>
|
|
67
|
-
</TableRow>))}
|
|
68
|
-
</TableBody>
|
|
69
|
-
</Table>
|
|
70
|
-
</TableContainer>
|
|
71
|
-
</Box>)}
|
|
72
|
-
<Button variant='outlined' startIcon={<AddIcon />} onClick={() => setOpenAddUserDialog(true)} sx={{ marginTop: 2 }}>
|
|
73
|
-
{groupsTranslations.addUser[locale]}
|
|
74
|
-
</Button>
|
|
75
|
-
<AddItemDialog open={openAddUserDialog} onClose={() => setOpenAddUserDialog(false)} onConfirm={(value) => {
|
|
76
|
-
if (value) {
|
|
77
|
-
addUserToGroup({ userId: value?.value, groupId: group.id });
|
|
78
|
-
}
|
|
79
|
-
}} title={groupsTranslations.addUser} options={users?.map((user) => ({
|
|
80
|
-
value: user.id,
|
|
81
|
-
label: user.username || '',
|
|
82
|
-
}))}/>
|
|
83
|
-
<DeleteConfirmationDialog open={openConfirmationDialog} onClose={() => setOpenConfirmationDialog(false)} onConfirm={() => deleteId
|
|
84
|
-
? removeUserFromGroup({ userId: deleteId, groupId: group.id })
|
|
85
|
-
: {}}/>
|
|
86
|
-
</>);
|
|
87
|
-
};
|
|
88
|
-
export default GroupUsers;
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
-
import GroupAddIcon from '@mui/icons-material/GroupAdd';
|
|
4
|
-
import { Box, Button, Grid, IconButton, Link } from '@mui/material';
|
|
5
|
-
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
6
|
-
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
|
7
|
-
import { useAPI, useLocale } from '@deix/rossini-core';
|
|
8
|
-
import { SearchField } from '..';
|
|
9
|
-
import CreateGroupDialog from './CreateGroupDialog';
|
|
10
|
-
import GroupsTable from './GroupsTable';
|
|
11
|
-
import groupsTranslations from '../../lang/groups';
|
|
12
|
-
import { useDeleteGroup, useGroups } from '../../utils/hooks/groups';
|
|
13
|
-
import GroupTabs from './GroupTabs';
|
|
14
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
15
|
-
import { APIClientProvider } from '../../utils/hooks/useAPIClient';
|
|
16
|
-
const Groups = ({ apiUrl, additionalTabs = [], onGroupUpdate, onGroupCreate, onGroupDelete, editName, }) => {
|
|
17
|
-
const locale = useLocale();
|
|
18
|
-
const apiClient = useAPI(apiUrl);
|
|
19
|
-
const queryClient = useQueryClient();
|
|
20
|
-
const { data: groups } = useGroups(apiClient);
|
|
21
|
-
const { mutate: deleteGroup } = useDeleteGroup(queryClient, apiClient);
|
|
22
|
-
const [searchString, setSearchString] = useState('');
|
|
23
|
-
const [filteredGroups, setFilteredGroups] = useState();
|
|
24
|
-
const [selectedGroup, setSelectedGroup] = useState();
|
|
25
|
-
const [openGroupDialog, setOpenGroupDialog] = useState(false);
|
|
26
|
-
// Update the filtered groups using the search string
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (groups && searchString === '') {
|
|
29
|
-
setFilteredGroups(groups);
|
|
30
|
-
}
|
|
31
|
-
else if (groups && searchString !== '') {
|
|
32
|
-
setFilteredGroups(groups.filter((group) => group.name.includes(searchString)));
|
|
33
|
-
}
|
|
34
|
-
}, [searchString, groups, setFilteredGroups]);
|
|
35
|
-
// If the groups change updated the selected group to reflect the chages
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (selectedGroup) {
|
|
38
|
-
const updatedSelectedGroup = groups?.find((group) => group.id === selectedGroup.id);
|
|
39
|
-
if (updatedSelectedGroup) {
|
|
40
|
-
setSelectedGroup(updatedSelectedGroup);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// deselect the group if it doesn't exist anymore
|
|
44
|
-
setSelectedGroup(undefined);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}, [groups, selectedGroup, setSelectedGroup]);
|
|
48
|
-
const handleDelete = (group) => {
|
|
49
|
-
deleteGroup(group.id);
|
|
50
|
-
if (typeof onGroupDelete === 'function') {
|
|
51
|
-
onGroupDelete(group);
|
|
52
|
-
}
|
|
53
|
-
setSelectedGroup(undefined);
|
|
54
|
-
};
|
|
55
|
-
return (<APIClientProvider baseURL={apiUrl}>
|
|
56
|
-
<Box sx={{
|
|
57
|
-
flexGrow: 1,
|
|
58
|
-
maxWidth: '100%',
|
|
59
|
-
overflowX: 'hidden',
|
|
60
|
-
paddingTop: 3,
|
|
61
|
-
paddingLeft: 3,
|
|
62
|
-
paddingRight: 3,
|
|
63
|
-
}}>
|
|
64
|
-
{!selectedGroup && (<>
|
|
65
|
-
<Grid container spacing={2} sx={{ marginBottom: 2 }}>
|
|
66
|
-
<Grid>
|
|
67
|
-
<SearchField placeholder={groupsTranslations.search[locale]} onSearch={(value) => setSearchString(value)}/>
|
|
68
|
-
</Grid>
|
|
69
|
-
<Grid>
|
|
70
|
-
<Button variant='contained' startIcon={<GroupAddIcon />} onClick={() => {
|
|
71
|
-
setOpenGroupDialog(true);
|
|
72
|
-
setSelectedGroup(undefined);
|
|
73
|
-
}}>
|
|
74
|
-
{groupsTranslations.new[locale]}
|
|
75
|
-
</Button>
|
|
76
|
-
</Grid>
|
|
77
|
-
</Grid>
|
|
78
|
-
<GroupsTable groups={filteredGroups} selectedGroup={selectedGroup} setSelectedGroup={(group) => {
|
|
79
|
-
setSelectedGroup(group);
|
|
80
|
-
}} onDelete={handleDelete}/>
|
|
81
|
-
</>)}
|
|
82
|
-
{selectedGroup && (<Box sx={{
|
|
83
|
-
display: 'flex',
|
|
84
|
-
flexDirection: 'column',
|
|
85
|
-
alignItems: 'flex-start',
|
|
86
|
-
width: '100%',
|
|
87
|
-
}}>
|
|
88
|
-
<Box sx={{
|
|
89
|
-
display: 'flex',
|
|
90
|
-
flexDirection: 'row',
|
|
91
|
-
alignItems: 'center',
|
|
92
|
-
}}>
|
|
93
|
-
<IconButton aria-label='delete' color='primary' onClick={() => setSelectedGroup(undefined)} sx={{ mr: 2 }}>
|
|
94
|
-
<ArrowBackIcon />
|
|
95
|
-
</IconButton>
|
|
96
|
-
<Link underline='none' href='#' onClick={() => setSelectedGroup(undefined)}>
|
|
97
|
-
{groupsTranslations.title[locale]}
|
|
98
|
-
</Link>
|
|
99
|
-
<KeyboardArrowRightIcon />
|
|
100
|
-
{selectedGroup.name}
|
|
101
|
-
</Box>
|
|
102
|
-
<GroupTabs group={selectedGroup} additionalTabs={additionalTabs} onGroupUpdate={onGroupUpdate} editName={editName}/>
|
|
103
|
-
</Box>)}
|
|
104
|
-
<CreateGroupDialog open={openGroupDialog} onClose={() => {
|
|
105
|
-
setOpenGroupDialog(false);
|
|
106
|
-
}} onGroupCreate={onGroupCreate}/>
|
|
107
|
-
</Box>
|
|
108
|
-
</APIClientProvider>);
|
|
109
|
-
};
|
|
110
|
-
export default Groups;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { isStringTranslation, useLocale } from '@deix/rossini-core';
|
|
3
|
-
import DeleteIcon from '@mui/icons-material/Delete';
|
|
4
|
-
import { IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, useTheme, } from '@mui/material';
|
|
5
|
-
import { useState } from 'react';
|
|
6
|
-
import DeleteConfirmationDialog from '../DeleteConfirmationDialog';
|
|
7
|
-
const groupsTableColumns = [
|
|
8
|
-
{
|
|
9
|
-
id: 'name',
|
|
10
|
-
label: { en: 'Name', it: 'Nome' },
|
|
11
|
-
},
|
|
12
|
-
];
|
|
13
|
-
const GroupsTable = ({ groups = [], selectedGroup, setSelectedGroup, onDelete, }) => {
|
|
14
|
-
const locale = useLocale();
|
|
15
|
-
const muiTheme = useTheme();
|
|
16
|
-
const [openConfirmationDialog, setOpenConfirmationDialog] = useState(false);
|
|
17
|
-
const [deleteGroup, setDeleteGroup] = useState();
|
|
18
|
-
return (<>
|
|
19
|
-
<TableContainer>
|
|
20
|
-
<Table size='small'>
|
|
21
|
-
<TableHead>
|
|
22
|
-
<TableRow>
|
|
23
|
-
{groupsTableColumns.map((col) => (<TableCell key={col.id} align={col.align || 'inherit'}>
|
|
24
|
-
{isStringTranslation(col.label)
|
|
25
|
-
? col.label[locale]
|
|
26
|
-
: col.label}
|
|
27
|
-
</TableCell>))}
|
|
28
|
-
<TableCell padding='none'>{''}</TableCell>
|
|
29
|
-
</TableRow>
|
|
30
|
-
</TableHead>
|
|
31
|
-
<TableBody>
|
|
32
|
-
{groups.map((group, rowIdx) => (<TableRow key={rowIdx} onClick={() => setSelectedGroup(group)} sx={{
|
|
33
|
-
cursor: 'pointer',
|
|
34
|
-
background: selectedGroup && group.id === selectedGroup.id
|
|
35
|
-
? muiTheme.palette.primary.light
|
|
36
|
-
: undefined,
|
|
37
|
-
}}>
|
|
38
|
-
<TableCell>{group.name}</TableCell>
|
|
39
|
-
<TableCell padding='none' align='right'>
|
|
40
|
-
<IconButton size='small' onClick={(evt) => {
|
|
41
|
-
evt.stopPropagation();
|
|
42
|
-
setOpenConfirmationDialog(true);
|
|
43
|
-
setDeleteGroup(group);
|
|
44
|
-
}}>
|
|
45
|
-
<DeleteIcon />
|
|
46
|
-
</IconButton>
|
|
47
|
-
</TableCell>
|
|
48
|
-
</TableRow>))}
|
|
49
|
-
</TableBody>
|
|
50
|
-
</Table>
|
|
51
|
-
</TableContainer>
|
|
52
|
-
<DeleteConfirmationDialog open={openConfirmationDialog} onClose={() => setOpenConfirmationDialog(false)} onConfirm={() => (deleteGroup ? onDelete(deleteGroup) : {})}/>
|
|
53
|
-
</>);
|
|
54
|
-
};
|
|
55
|
-
export default GroupsTable;
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useLocale } from '@deix/rossini-core';
|
|
3
|
-
import { Button, FormControlLabel, Grid, Switch, TextField, } from '@mui/material';
|
|
4
|
-
import { Controller, useForm } from 'react-hook-form';
|
|
5
|
-
import { useEffect } from 'react';
|
|
6
|
-
import usersTranslations from '../../lang/users';
|
|
7
|
-
const UserForm = ({ user, onSave, onCancel, mode = 'create', editUsername, emailAsUsername = false, }) => {
|
|
8
|
-
const locale = useLocale();
|
|
9
|
-
const { control, formState: { errors }, handleSubmit, setValue, } = useForm({
|
|
10
|
-
defaultValues: {
|
|
11
|
-
id: user?.id,
|
|
12
|
-
username: user?.username || '',
|
|
13
|
-
firstName: user?.firstName || '',
|
|
14
|
-
lastName: user?.lastName || '',
|
|
15
|
-
email: user?.email || '',
|
|
16
|
-
emailVerified: user?.emailVerified || true,
|
|
17
|
-
enabled: user?.enabled || true,
|
|
18
|
-
sendEmailOnCreation: true,
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (user) {
|
|
23
|
-
setValue('id', user?.id);
|
|
24
|
-
if (!emailAsUsername) {
|
|
25
|
-
setValue('username', user?.username);
|
|
26
|
-
}
|
|
27
|
-
setValue('firstName', user?.firstName);
|
|
28
|
-
setValue('lastName', user?.lastName);
|
|
29
|
-
setValue('email', user?.email);
|
|
30
|
-
setValue('emailVerified', user?.emailVerified);
|
|
31
|
-
setValue('enabled', user?.enabled);
|
|
32
|
-
setValue('groups', user?.groups);
|
|
33
|
-
// The user already exists, we don't want to send an email
|
|
34
|
-
setValue('sendEmailOnCreation', false);
|
|
35
|
-
}
|
|
36
|
-
}, [user, setValue]);
|
|
37
|
-
const onSubmit = (data) => {
|
|
38
|
-
onSave(data);
|
|
39
|
-
};
|
|
40
|
-
return (<form onSubmit={(event) => void handleSubmit(onSubmit)(event)}>
|
|
41
|
-
<Grid container spacing={2} sx={{ mt: 2, maxWidth: 'sm' }} justifyContent='stretch'>
|
|
42
|
-
{!emailAsUsername && (<Grid size={{ xs: 12 }}>
|
|
43
|
-
<Controller name='username' control={control} render={({ field }) => (<TextField size='small' fullWidth label={usersTranslations.username[locale]} disabled={mode === 'modify' && !editUsername} sx={{ flexShrink: 0 }} {...field}/>)}/>
|
|
44
|
-
</Grid>)}
|
|
45
|
-
<Grid size={{ xs: 12 }}>
|
|
46
|
-
<Controller name='firstName' control={control} render={({ field }) => (<TextField size='small' fullWidth label={usersTranslations.firstName[locale]} sx={{ flexShrink: 0 }} {...field}/>)}/>
|
|
47
|
-
</Grid>
|
|
48
|
-
<Grid size={{ xs: 12 }}>
|
|
49
|
-
<Controller name='lastName' control={control} render={({ field }) => (<TextField size='small' fullWidth label={usersTranslations.lastName[locale]} sx={{ flexShrink: 0 }} {...field}/>)}/>
|
|
50
|
-
</Grid>
|
|
51
|
-
<Grid size={{ xs: 12 }}>
|
|
52
|
-
<Controller name='email' control={control} rules={{ required: true }} render={({ field }) => (<TextField size='small' fullWidth error={!!errors.email} helperText={errors.email
|
|
53
|
-
? usersTranslations.requiredField[locale]
|
|
54
|
-
: undefined} label={usersTranslations.email[locale]} sx={{ flexShrink: 0 }} {...field}/>)}/>
|
|
55
|
-
</Grid>
|
|
56
|
-
<Grid size={{ xs: 12 }}>
|
|
57
|
-
<Controller name='enabled' control={control} render={({ field }) => (<FormControlLabel sx={{ ml: 1 }} control={<Switch checked={field.value} size='small' {...field}/>} label={usersTranslations.enabled[locale]}/>)}/>
|
|
58
|
-
</Grid>
|
|
59
|
-
{mode === 'create' && (<Grid size={{ xs: 12 }}>
|
|
60
|
-
<Controller name='sendEmailOnCreation' control={control} render={({ field }) => (<FormControlLabel sx={{ ml: 1 }} control={<Switch checked={field.value} size='small' {...field}/>} label={usersTranslations.sendResetPasswordEmail[locale]}/>)}/>
|
|
61
|
-
</Grid>)}
|
|
62
|
-
<Grid sx={{
|
|
63
|
-
mt: 2,
|
|
64
|
-
display: 'flex',
|
|
65
|
-
justifyContent: 'flex-end',
|
|
66
|
-
gap: 2,
|
|
67
|
-
width: '100%',
|
|
68
|
-
}}>
|
|
69
|
-
{mode === 'create' && (<Button variant='outlined' onClick={onCancel}>
|
|
70
|
-
{usersTranslations.cancel[locale]}
|
|
71
|
-
</Button>)}
|
|
72
|
-
<Button variant='contained' type='submit'>
|
|
73
|
-
{usersTranslations.save[locale]}
|
|
74
|
-
</Button>
|
|
75
|
-
</Grid>
|
|
76
|
-
</Grid>
|
|
77
|
-
</form>);
|
|
78
|
-
};
|
|
79
|
-
export default UserForm;
|