@fluxo-dev/shell 0.0.8

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.
Files changed (66) hide show
  1. package/.babelrc +12 -0
  2. package/.eslintrc.json +18 -0
  3. package/README.md +7 -0
  4. package/jest.config.ts +11 -0
  5. package/package.json +4 -0
  6. package/project.json +43 -0
  7. package/src/index.ts +1 -0
  8. package/src/lib/app.module.css +1 -0
  9. package/src/lib/app.spec.tsx +17 -0
  10. package/src/lib/app.tsx +374 -0
  11. package/src/lib/components/error-page/404.tsx +16 -0
  12. package/src/lib/components/routes/main-routes.tsx +26 -0
  13. package/src/lib/layouts/blank/blank-layout.tsx +10 -0
  14. package/src/lib/layouts/main/app-bar.tsx +192 -0
  15. package/src/lib/layouts/main/app-layout.tsx +52 -0
  16. package/src/lib/layouts/main/content.tsx +10 -0
  17. package/src/lib/layouts/main/footer-bar.tsx +76 -0
  18. package/src/lib/layouts/main/navbar-item.module.css +49 -0
  19. package/src/lib/layouts/main/navbar-item.tsx +129 -0
  20. package/src/lib/layouts/main/navbar.tsx +38 -0
  21. package/src/lib/layouts/main/search-bar.tsx +44 -0
  22. package/src/lib/layouts/main/shell-spotlight.tsx +58 -0
  23. package/src/lib/models/app-jit.ts +6 -0
  24. package/src/lib/pages/general/fluxo-plugin-loader.tsx +22 -0
  25. package/src/lib/pages/login/account-confirmation.tsx +86 -0
  26. package/src/lib/pages/login/change-password.tsx +58 -0
  27. package/src/lib/pages/login/forgot-password.tsx +53 -0
  28. package/src/lib/pages/login/login-page.tsx +70 -0
  29. package/src/lib/pages/login/reset-password.tsx +91 -0
  30. package/src/lib/pages/settings/apps/add-jit-app.tsx +79 -0
  31. package/src/lib/pages/settings/apps/app-item.tsx +157 -0
  32. package/src/lib/pages/settings/apps/app-jit-item.tsx +105 -0
  33. package/src/lib/pages/settings/apps/apps-list.tsx +171 -0
  34. package/src/lib/pages/settings/apps/embedded-module-item.tsx +66 -0
  35. package/src/lib/pages/settings/apps/install-or-upgrade-app.tsx +105 -0
  36. package/src/lib/pages/settings/menu-designer/custom-node.module.css +34 -0
  37. package/src/lib/pages/settings/menu-designer/custom-node.tsx +160 -0
  38. package/src/lib/pages/settings/menu-designer/menu-create-form.tsx +109 -0
  39. package/src/lib/pages/settings/menu-designer/menu-designer.module.css +19 -0
  40. package/src/lib/pages/settings/menu-designer/menu-designer.tsx +463 -0
  41. package/src/lib/pages/settings/menu-designer/menu-item-edit.tsx +115 -0
  42. package/src/lib/pages/settings/menu-designer/placeholder.module.css +8 -0
  43. package/src/lib/pages/settings/menu-designer/placeholder.tsx +13 -0
  44. package/src/lib/pages/settings/security/components/domani-role/domain-role-form.tsx +67 -0
  45. package/src/lib/pages/settings/security/components/domani-role/domain-role-grid.tsx +139 -0
  46. package/src/lib/pages/settings/security/components/permission/create-custom-permission-form.tsx +64 -0
  47. package/src/lib/pages/settings/security/components/permission/create-scope-form.tsx +158 -0
  48. package/src/lib/pages/settings/security/components/permission/permission-grid.tsx +304 -0
  49. package/src/lib/pages/settings/security/components/permission/permission-navigator.tsx +320 -0
  50. package/src/lib/pages/settings/security/components/permission/update-permission.tsx +54 -0
  51. package/src/lib/pages/settings/security/components/route-rule/route-rule-create-form.tsx +59 -0
  52. package/src/lib/pages/settings/security/components/route-rule/route-rule-grid.tsx +229 -0
  53. package/src/lib/pages/settings/security/components/user/user-form.tsx +123 -0
  54. package/src/lib/pages/settings/security/components/user/users-grid.tsx +127 -0
  55. package/src/lib/pages/settings/settings.tsx +298 -0
  56. package/src/lib/pages/settings/token/token-row.tsx +69 -0
  57. package/src/lib/pages/settings/token/tokens.tsx +73 -0
  58. package/src/lib/pages/settings/ui/ui.tsx +149 -0
  59. package/src/lib/services/app-jit-manager.ts +92 -0
  60. package/src/lib/services/app-menu.service.ts +35 -0
  61. package/src/lib/utils/icon-factory.tsx +25 -0
  62. package/src/lib/utils/security/only-not-logged-route.tsx +17 -0
  63. package/src/lib/utils/security/protected-route.tsx +47 -0
  64. package/tsconfig.json +20 -0
  65. package/tsconfig.lib.json +24 -0
  66. package/tsconfig.spec.json +20 -0
@@ -0,0 +1,59 @@
1
+ import React, {useState} from "react";
2
+ import {FluxoClientSys, RouteRule} from "@fluxo-dev/api-client-base";
3
+ import {showNotification} from "@mantine/notifications";
4
+ import {Alert, Button, Divider, Group, Stack, TextInput} from "@mantine/core";
5
+ import {IconInfoCircle} from "@tabler/icons-react";
6
+
7
+ export interface RouteRuleCreateFormProps {
8
+ scope: string;
9
+ packageName: string;
10
+ onCanceled: () => void;
11
+ onCreated: () => void;
12
+ }
13
+
14
+ export function RouteRuleCreateForm(props: RouteRuleCreateFormProps) {
15
+ const [identifier, setIdentifier] = useState<string>('');
16
+
17
+ const createRouteRule = () => {
18
+ if(!identifier) return;
19
+
20
+ const command: RouteRule = {
21
+ scope: props.scope,
22
+ packageName: props.packageName,
23
+ route: identifier,
24
+ }
25
+ FluxoClientSys.Instance.security.routeRule.create(command)
26
+ .then(() => {
27
+ showNotification({
28
+ title: 'Regola creata',
29
+ message: `La regola ${props.scope}.${props.packageName}.${identifier} è stato creata con successo.`,
30
+ color: 'green'
31
+ })
32
+ props.onCreated();
33
+ })
34
+ .catch(() => {
35
+ showNotification({
36
+ title: 'Errore',
37
+ message: `Si è verificato un errore durante la creazione della regola ${props.scope}.${props.packageName}.${identifier}.`,
38
+ color: 'red'
39
+ })
40
+ });
41
+ }
42
+
43
+ return (
44
+ <Stack>
45
+ <TextInput label={"Identificatore"} placeholder={'Es: create'} required onChange={(v) => setIdentifier(v.target.value)} />
46
+
47
+ <p>Rotta: {`${props.scope}/${props.packageName}/${identifier}`}</p>
48
+ <Divider />
49
+ <Group justify={'right'}>
50
+ <Button variant={'subtle'} onClick={props.onCanceled}>Annulla</Button>
51
+ <Button
52
+ disabled={!identifier}
53
+ onClick={createRouteRule}>
54
+ Salva
55
+ </Button>
56
+ </Group>
57
+ </Stack>
58
+ )
59
+ }
@@ -0,0 +1,229 @@
1
+ import {ActionIcon, Button, Center, Divider, Grid, Group, Modal, Select, Stack, Table, Text} from "@mantine/core";
2
+ import {DataCard} from "@fluxo-dev/ui-components";
3
+ import React, {useEffect, useState} from "react";
4
+ import {FluxoClientSys, PermissionScope, RouteRule} from "@fluxo-dev/api-client-base";
5
+ import {IconCloudOff, IconLock, IconPlus, IconTrash} from "@tabler/icons-react";
6
+ import {PermissionNavigator} from "../permission/permission-navigator";
7
+ import {RouteRuleCreateForm} from "./route-rule-create-form";
8
+ import {showNotification} from "@mantine/notifications";
9
+
10
+ export function RouteRuleGrid() {
11
+
12
+ const [permissionScopes, setPermissionScopes] = useState<PermissionScope[]>([]);
13
+ const [permissionScopesLoaded, setPermissionScopesLoaded] = useState<boolean>(false);
14
+
15
+ const [currentScope, setCurrentScope] = useState<string | null>(null);
16
+ const [currentPackageName, setCurrentPackageName] = useState<string | null>(null);
17
+ const [routeRules, setRouteRules] = useState<RouteRule[]>([]);
18
+ const [permissionLoading, setPermissionLoading] = useState<boolean>(false);
19
+
20
+ const [currentRouteRule, setCurrentRouteRule] = useState<RouteRule | undefined>();
21
+ const [managePermissionModalOpened, setManagePermissionModalOpened] = useState<boolean>(false);
22
+
23
+ const [createRouteRuleModalOpened, setCreateRouteRuleModalOpened] = useState<boolean>(false);
24
+
25
+ useEffect(() => {
26
+ loadPermissionScopes();
27
+ }, [])
28
+
29
+ useEffect(() => {
30
+ if(currentScope && currentPackageName)
31
+ getRouteRulesForScope();
32
+ else
33
+ setRouteRules([]);
34
+ }, [currentScope, currentPackageName]);
35
+
36
+ const getRouteRulesForScope = () => {
37
+
38
+ if(!currentScope || !currentPackageName) {
39
+ return;
40
+ }
41
+
42
+ setPermissionLoading(true);
43
+
44
+ FluxoClientSys.Instance.security.routeRule.getRouteRulesForScope(currentScope, currentPackageName)
45
+ .then((res) => {
46
+ setRouteRules(res);
47
+ setPermissionLoading(false);
48
+ })
49
+ }
50
+
51
+ const loadPermissionScopes = () => {
52
+ FluxoClientSys.Instance.security.permission.getPermissionScopes()
53
+ .then((res) => {
54
+ setPermissionScopes(res);
55
+ setPermissionScopesLoaded(true);
56
+ })
57
+ }
58
+
59
+ const deleteRouteRule = (routeRule: RouteRule) => {
60
+ FluxoClientSys.Instance.security.routeRule.delete(routeRule.id!.toString())
61
+ .then(() => {
62
+ showNotification({
63
+ title: 'Regola eliminata',
64
+ message: `La regola ${routeRule.scope}.${routeRule.packageName}.${routeRule.route} è stato eliminata con successo.`,
65
+ color: 'green'
66
+ })
67
+ getRouteRulesForScope();
68
+ })
69
+ .catch(() => {
70
+ showNotification({
71
+ title: 'Errore',
72
+ message: `Si è verificato un errore durante l'eliminazione della regola ${routeRule.scope}.${routeRule.packageName}.${routeRule.route}.`,
73
+ color: 'red'
74
+ })
75
+ });
76
+ }
77
+
78
+ const getScopes = () => {
79
+ if(permissionScopes === undefined) return [];
80
+ return [...new Set(permissionScopes.map(a => a.scope))];
81
+ }
82
+
83
+ const getPackageNames = (scope: string | null) => {
84
+ if(permissionScopes === undefined || !scope) return [];
85
+ return [...new Set(permissionScopes.filter(a => a.scope === scope).map(a => a.packageName))];
86
+ }
87
+
88
+ return (
89
+ <DataCard title={'Regole sulle rotte'}>
90
+ <Stack>
91
+ <Grid>
92
+ <Grid.Col span={6}>
93
+ <Select
94
+ data={getScopes()}
95
+ searchable
96
+ placeholder={'Seleziona uno scope'}
97
+ value={currentScope}
98
+ label={'Scope'}
99
+ onChange={(v) => {
100
+ setCurrentScope(v)
101
+ setCurrentPackageName(null);
102
+ }} />
103
+ </Grid.Col>
104
+ <Grid.Col span={6}>
105
+ <Select
106
+ disabled={!currentScope}
107
+ searchable
108
+ placeholder={'Seleziona un package name'}
109
+ data={getPackageNames(currentScope)}
110
+ value={currentPackageName}
111
+ label={'Package name'}
112
+ onChange={(v) => {
113
+ setCurrentPackageName(v)
114
+ }} />
115
+ </Grid.Col>
116
+ </Grid>
117
+
118
+ <Divider />
119
+
120
+ {(currentScope && currentPackageName && !permissionLoading && routeRules.length) === 0 &&
121
+ <Stack>
122
+ <Group justify={'right'} align={'center'}>
123
+ <Button leftSection={<IconPlus size={18}/>} onClick={() => setCreateRouteRuleModalOpened(true)}>Nuova regola</Button>
124
+ </Group>
125
+ <Center>
126
+ <Text size={'md'}>Nessuna regola esistente.</Text>
127
+ </Center>
128
+ </Stack>
129
+ }
130
+
131
+ {(!currentScope || !currentPackageName) &&
132
+ <Stack align={'center'} mt={32} mb={32}>
133
+ <IconCloudOff color={'gray'} size={64} />
134
+ <Center>
135
+ <Text size={'md'}>Seleziona uno scope e un package name per visualizzare i permessi associati.</Text>
136
+ </Center>
137
+ </Stack>
138
+ }
139
+
140
+ {!permissionLoading && routeRules.length > 0 &&
141
+ <Stack>
142
+ <Group justify={'right'} align={'center'}>
143
+ <Button leftSection={<IconPlus size={18}/>} onClick={() => setCreateRouteRuleModalOpened(true)}>Nuova regola</Button>
144
+ </Group>
145
+ <Table highlightOnHover>
146
+ <Table.Thead>
147
+ <Table.Tr>
148
+ <Table.Th>Id</Table.Th>
149
+ <Table.Th>Scope</Table.Th>
150
+ <Table.Th>Rotta</Table.Th>
151
+ <Table.Th>Permessi</Table.Th>
152
+ <Table.Th style={{maxWidth: 80, width: 80}}>Azioni</Table.Th>
153
+ </Table.Tr>
154
+ </Table.Thead>
155
+ <Table.Tbody>
156
+ {routeRules.map((element, i) => {
157
+ return (
158
+ <Table.Tr key={i}>
159
+ <Table.Td>{element.id}</Table.Td>
160
+ <Table.Td>{`${element.scope}.${element.packageName}`}</Table.Td>
161
+ <Table.Td>{`/${element.scope}/${element.packageName}/${element.route}`}</Table.Td>
162
+ <Table.Td>{element.permissionsCount}</Table.Td>
163
+ <Table.Td style={{maxWidth: 130, width: 130}}>
164
+ <Group>
165
+ <ActionIcon onClick={() => {
166
+ setCurrentRouteRule(element);
167
+ setManagePermissionModalOpened(true);
168
+ }}>
169
+ <IconLock size={18} />
170
+ </ActionIcon>
171
+ <ActionIcon onClick={() => {
172
+ deleteRouteRule(element);
173
+ }}>
174
+ <IconTrash size={18} />
175
+ </ActionIcon>
176
+ </Group>
177
+
178
+ </Table.Td>
179
+ </Table.Tr>
180
+ )
181
+ })}
182
+ </Table.Tbody>
183
+ </Table>
184
+ </Stack>
185
+ }
186
+
187
+ </Stack>
188
+
189
+ {currentRouteRule &&
190
+ <Modal
191
+ size={'60%'}
192
+ centered
193
+ opened={managePermissionModalOpened}
194
+ onClose={() => {
195
+ getRouteRulesForScope();
196
+ setManagePermissionModalOpened(false);
197
+ setCurrentRouteRule(undefined);
198
+ }}
199
+ title={`Gestione permessi ${currentRouteRule.route}`}>
200
+ <PermissionNavigator
201
+ context={{type: 'route-rule', id: currentRouteRule.id!.toString()}}
202
+ onUpdate={() => {}}
203
+ />
204
+ </Modal>
205
+ }
206
+
207
+ <Modal
208
+ size={'60%'}
209
+ centered
210
+ opened={createRouteRuleModalOpened}
211
+ onClose={() => {
212
+ getRouteRulesForScope();
213
+ setCreateRouteRuleModalOpened(false);
214
+ }}
215
+ title={`Crea nuova regola`}>
216
+ <RouteRuleCreateForm
217
+ scope={currentScope!}
218
+ packageName={currentPackageName!}
219
+ onCreated={() => {
220
+ getRouteRulesForScope();
221
+ setCreateRouteRuleModalOpened(false);
222
+ }}
223
+ onCanceled={() => {setCreateRouteRuleModalOpened(false)}}
224
+ />
225
+ </Modal>
226
+
227
+ </DataCard>
228
+ )
229
+ }
@@ -0,0 +1,123 @@
1
+ import {DomainRole, FluxoClientSys, User, UserService} from "@fluxo-dev/api-client-base";
2
+ import { Button, MultiSelect, Stack, TextInput } from "@mantine/core";
3
+ import { useForm } from "@mantine/form";
4
+ import { showNotification } from "@mantine/notifications";
5
+ import { IconFilePlus } from '@tabler/icons-react';
6
+ import {useEffect, useState} from "react";
7
+
8
+ export interface UserFormProps {
9
+ user?: User,
10
+ onCreate: () => void
11
+ }
12
+ function UserForm(props: UserFormProps) {
13
+ const userService = new UserService();
14
+ const roles = [
15
+ { value: 'administrator', label: 'Amministratore' },
16
+ { value: 'user', label: 'Utente' }
17
+ ];
18
+
19
+ const [domainRoles, setDomainRoles] = useState<DomainRole[]>([]);
20
+
21
+ useEffect(() => {
22
+
23
+ FluxoClientSys.Instance.security.domainRole.find(999,0)
24
+ .then((res) => {
25
+ setDomainRoles(res.results);
26
+ });
27
+
28
+ }, []);
29
+
30
+ const generalForm = useForm({
31
+ initialValues: {
32
+ user: {
33
+ firstName: props.user?.userData?.FirstName ?? '',
34
+ lastName: props.user?.userData?.LastName ?? '',
35
+ email: props.user?.email ?? '',
36
+ userName: ''
37
+ },
38
+ assignToRoles: props.user?.roles ?? [],
39
+ assignToDomainRoles: props.user?.domainRoles.map(a => a.id!.toString()) ?? []
40
+ },
41
+ clearInputErrorOnChange: true,
42
+ validateInputOnChange: true,
43
+ validate: {
44
+ user: {
45
+ firstName: (value) => (!value ? 'Il nome è obbligatorio' : null),
46
+ lastName: (value) => (!value ? 'Il cognome è obbligatorio' : null),
47
+ email: (value) => (!value ? 'L\'email è obbligatoria' : null),
48
+ },
49
+ }
50
+ });
51
+
52
+ const create = (value: any) => {
53
+ value.user.userName = value.user.email;
54
+
55
+ const newUser = {
56
+ user: value.user,
57
+ assignToRoles: value.assignToRoles,
58
+ assignToDomainRoles: value.assignToDomainRoles.map((a: string) => parseInt(a))
59
+ };
60
+
61
+
62
+ (props.user ? userService.updateUser(props.user.id, newUser) : userService.createUser(newUser))
63
+ .then((res) => {
64
+ if (res.success) {
65
+ showNotification({
66
+ title: 'Operazione completata',
67
+ message: 'Aggiornamento completato',
68
+ color: 'green',
69
+ })
70
+ generalForm.reset();
71
+ generalForm.resetDirty();
72
+ if (props.onCreate)
73
+ props.onCreate();
74
+ } else {
75
+ showNotification({
76
+ title: 'Errore',
77
+ message: `Errore durante l'aggiornamento: ${res.errors.map(x => x.description).join(', ')}`,
78
+ color: 'red',
79
+ });
80
+ }
81
+ })
82
+ .catch((err) => {
83
+ showNotification({
84
+ title: 'Errore',
85
+ message: `Errore durante l'aggiornamento: ${err.message}`,
86
+ color: 'red',
87
+ });
88
+ })
89
+ }
90
+
91
+ return (
92
+ <form
93
+ onKeyDown={event => { if (event.which === 13) event.preventDefault() }}
94
+ onSubmit={generalForm.onSubmit(
95
+ (values, event) => {
96
+ event?.preventDefault();
97
+ create(values);
98
+ })}>
99
+ <Stack gap="xs">
100
+ <TextInput label={'Nome'} {...generalForm.getInputProps('user.firstName')} />
101
+ <TextInput label={'Cognome'} {...generalForm.getInputProps('user.lastName')} />
102
+ <TextInput label={'Email'} disabled={!!props.user} {...generalForm.getInputProps('user.email')} />
103
+ <MultiSelect
104
+ data={roles}
105
+ label="Tipo di accesso"
106
+ placeholder="Seleziona uno o più tipi di accesso"
107
+ {...generalForm.getInputProps('assignToRoles')}
108
+ />
109
+ <MultiSelect
110
+ data={domainRoles.map(x => ({value: x.id!.toString(), label: x.name}))}
111
+ label="Ruolo"
112
+ placeholder="Seleziona uno o più ruoli"
113
+ {...generalForm.getInputProps('assignToDomainRoles')}
114
+ />
115
+ </Stack >
116
+ <Stack align="flex-end" mt="md">
117
+ <Button type="submit" leftSection={<IconFilePlus />} ml='auto' disabled={!generalForm.isDirty()}>Salva</Button>
118
+ </Stack >
119
+ </form>
120
+ );
121
+ }
122
+
123
+ export default UserForm;
@@ -0,0 +1,127 @@
1
+ import React, {useEffect, useState} from "react";
2
+ import {FluxoClientSys, User} from "@fluxo-dev/api-client-base";
3
+ import {showNotification} from "@mantine/notifications";
4
+ import {ActionIcon, Badge, Menu, Modal, Stack, Table} from "@mantine/core";
5
+ import {DataCard} from "@fluxo-dev/ui-components";
6
+ import {IconDotsVertical, IconEdit, IconPlus, IconTrash} from "@tabler/icons-react";
7
+ import UserForm from "./user-form";
8
+
9
+ export function UsersGrid() {
10
+ const [users, setUsers] = useState<User[]>([]);
11
+
12
+ const [currentUser, setCurrentUser] = useState<User>();
13
+
14
+ const [newUserModalOpen, setNewUserModalOpen] = useState<boolean>(false);
15
+
16
+ useEffect(() => {
17
+ getAllUsers();
18
+ }, [])
19
+
20
+ const getAllUsers = () => {
21
+ FluxoClientSys.Instance.security.user.getAllUsers().then(res => {
22
+ setUsers(res);
23
+ })
24
+ }
25
+
26
+ const deletion = (id: string) => {
27
+ FluxoClientSys.Instance.security.user.deleteUser(id)
28
+ .then((res) => {
29
+ if (res.success) {
30
+ showNotification({
31
+ title: 'Cancellazione completata',
32
+ message: `L'utente è stato cancellato con successo`,
33
+ color: 'green',
34
+ });
35
+ } else {
36
+ showNotification({
37
+ title: 'Errore',
38
+ message: `Errore durante la cancellazione: ${res.errors.map(x => x.description).join(', ')}`,
39
+ color: 'red',
40
+ });
41
+ }
42
+ getAllUsers();
43
+ })
44
+ }
45
+
46
+ return (
47
+ <Stack>
48
+ <DataCard
49
+ title={'Utenti'}
50
+ actions={[
51
+ {icon: <IconPlus size={18}/>, label: 'Nuovo utente', onClick: () => setNewUserModalOpen(true)},
52
+ ]}>
53
+
54
+ <Table highlightOnHover>
55
+ <Table.Thead>
56
+ <Table.Tr>
57
+ <Table.Th>Nome</Table.Th>
58
+ <Table.Th>Cognome</Table.Th>
59
+ <Table.Th>Email</Table.Th>
60
+ <Table.Th>Tipo di accesso</Table.Th>
61
+ <Table.Th>Ruoli</Table.Th>
62
+ <Table.Th></Table.Th>
63
+ </Table.Tr>
64
+ </Table.Thead>
65
+ <Table.Tbody>
66
+ {
67
+ users.map((element, i) =>
68
+ <Table.Tr key={i}>
69
+ <Table.Td>{element.userData.FirstName}</Table.Td>
70
+ <Table.Td>{element.userData.LastName}</Table.Td>
71
+ <Table.Td>{element.email}</Table.Td>
72
+ <Table.Td>
73
+ <Stack gap={8}>
74
+ {element.roles.map((d) => {
75
+ return (
76
+ <Badge key={d} color={'orange'}>{d}</Badge>
77
+ )
78
+ })}
79
+ </Stack>
80
+ </Table.Td>
81
+ <Table.Td>
82
+ <Stack gap={8}>
83
+ {element.domainRoles.map((d) => {
84
+ return (
85
+ <Badge key={d.id}>{d.name}</Badge>
86
+ )
87
+ })}
88
+ </Stack>
89
+ </Table.Td>
90
+ <Table.Td align="right">
91
+ <Menu shadow="md" width={200}>
92
+ <Menu.Target>
93
+ <ActionIcon><IconDotsVertical size={18} /></ActionIcon>
94
+ </Menu.Target>
95
+
96
+ <Menu.Dropdown>
97
+ <Menu.Item onClick={() => {
98
+ setCurrentUser(element);
99
+ setNewUserModalOpen(true);
100
+ }} leftSection={<IconEdit size={14} />}>Modifica</Menu.Item>
101
+ <Menu.Item color={'red'} onClick={() => deletion(element.id)} leftSection={<IconTrash color={'red'} size={14} />}>Elimina</Menu.Item>
102
+ </Menu.Dropdown>
103
+ </Menu>
104
+ </Table.Td>
105
+ </Table.Tr>
106
+ )
107
+ }
108
+ </Table.Tbody>
109
+ </Table>
110
+ </DataCard>
111
+
112
+
113
+ <Modal centered opened={newUserModalOpen} onClose={() => {
114
+ setNewUserModalOpen(false);
115
+ setCurrentUser(undefined);
116
+ }} title={`${currentUser ? 'Modifica utente' : 'Crea utente'}`}>
117
+ <UserForm onCreate={() => {
118
+ setNewUserModalOpen(false);
119
+ getAllUsers();
120
+ setCurrentUser(undefined);
121
+ }} user={currentUser}/>
122
+ </Modal>
123
+
124
+
125
+ </Stack>
126
+ );
127
+ }