@truedat/auth 8.14.0 → 8.14.2
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/package.json +4 -4
- package/src/groups/components/EditGroup.js +64 -20
- package/src/groups/components/Group.js +39 -41
- package/src/groups/components/GroupCard.js +51 -54
- package/src/groups/components/GroupCards.js +60 -25
- package/src/groups/components/GroupDeleteModal.js +98 -0
- package/src/groups/components/GroupDropdown.js +92 -0
- package/src/groups/components/GroupForm.js +165 -84
- package/src/groups/components/GroupFormModal.js +137 -0
- package/src/groups/components/GroupInfoModal.js +114 -0
- package/src/groups/components/GroupMemberPicker.js +364 -0
- package/src/groups/components/GroupRouteModal.js +46 -0
- package/src/groups/components/GroupUsers.js +31 -14
- package/src/groups/components/GroupUsersTable.js +136 -83
- package/src/groups/components/NewGroup.js +38 -10
- package/src/groups/components/__tests__/EditGroup.spec.js +105 -0
- package/src/groups/components/__tests__/Group.spec.js +29 -47
- package/src/groups/components/__tests__/GroupCard.spec.js +8 -20
- package/src/groups/components/__tests__/GroupDropdown.spec.js +27 -0
- package/src/groups/components/__tests__/GroupForm.spec.js +32 -5
- package/src/groups/components/__tests__/GroupMemberPicker.spec.js +378 -0
- package/src/groups/components/__tests__/GroupModals.spec.js +210 -0
- package/src/groups/components/__tests__/GroupUsers.spec.js +17 -2
- package/src/groups/components/__tests__/GroupUsersTable.spec.js +34 -2
- package/src/groups/components/__tests__/__snapshots__/Group.spec.js.snap +1 -0
- package/src/groups/components/__tests__/__snapshots__/GroupCard.spec.js.snap +32 -30
- package/src/groups/components/__tests__/__snapshots__/GroupCards.spec.js.snap +90 -77
- package/src/groups/components/__tests__/__snapshots__/GroupUsers.spec.js.snap +75 -11
- package/src/groups/components/__tests__/__snapshots__/GroupUsersTable.spec.js.snap +70 -13
- package/src/groups/components/index.js +0 -4
- package/src/groups/hooks/__tests__/useGroupOperation.spec.js +167 -0
- package/src/groups/hooks/__tests__/useGroups.spec.js +90 -0
- package/src/groups/hooks/useGroupOperation.js +83 -0
- package/src/groups/hooks/useGroups.js +64 -0
- package/src/groups/reducers/index.js +0 -4
- package/src/groups/routines.js +0 -7
- package/src/groups/sagas/index.js +2 -24
- package/src/hooks/useRoles.js +6 -3
- package/src/styles/Groups.less +1009 -0
- package/src/users/components/AdminUserRoutes.js +1 -14
- package/src/users/components/GroupUserCrumbs.js +7 -12
- package/src/users/components/UserRoutes.js +12 -35
- package/src/users/components/UsersAndGroups.js +3 -6
- package/src/users/components/__tests__/GroupUserCrumbs.spec.js +26 -30
- package/src/users/components/__tests__/UserRoutes.spec.js +4 -6
- package/src/users/components/__tests__/UsersAndGroups.spec.js +5 -7
- package/src/users/components/__tests__/__snapshots__/UserRoutes.spec.js.snap +9 -55
- package/src/groups/components/GroupLoader.js +0 -27
- package/src/groups/components/GroupsLoader.js +0 -30
- package/src/groups/components/__tests__/GroupLoader.spec.js +0 -71
- package/src/groups/components/__tests__/__snapshots__/GroupLoader.spec.js.snap +0 -11
- package/src/groups/reducers/__tests__/groupRedirect.spec.js +0 -51
- package/src/groups/reducers/__tests__/groupUsers.spec.js +0 -60
- package/src/groups/reducers/group.js +0 -56
- package/src/groups/reducers/groupRedirect.js +0 -20
- package/src/groups/reducers/groupUsers.js +0 -16
- package/src/groups/reducers/groups.js +0 -29
- package/src/groups/sagas/__tests__/deleteGroup.spec.js +0 -93
- package/src/groups/sagas/__tests__/deleteGroupUser.spec.js +0 -80
- package/src/groups/sagas/createGroup.js +0 -27
- package/src/groups/sagas/deleteGroup.js +0 -31
- package/src/groups/sagas/deleteGroupUser.js +0 -35
- package/src/groups/sagas/fetchGroup.js +0 -30
- package/src/groups/sagas/fetchGroups.js +0 -26
- package/src/groups/sagas/updateGroup.js +0 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/auth",
|
|
3
|
-
"version": "8.14.
|
|
3
|
+
"version": "8.14.2",
|
|
4
4
|
"description": "Truedat Web Auth",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
56
56
|
"@testing-library/user-event": "^14.6.1",
|
|
57
|
-
"@truedat/test": "8.14.
|
|
57
|
+
"@truedat/test": "8.14.2",
|
|
58
58
|
"identity-obj-proxy": "^3.0.0",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@apollo/client": "^3.13.8",
|
|
64
|
-
"@truedat/core": "8.14.
|
|
64
|
+
"@truedat/core": "8.14.2",
|
|
65
65
|
"auth0-js": "^10.2.0",
|
|
66
66
|
"axios": "^1.20.0",
|
|
67
67
|
"graphql": "^16.11.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"swr": "^2.3.3"
|
|
91
91
|
},
|
|
92
92
|
"overrides": {},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "3814d239579315d817ba0e3467771e6a128e5788"
|
|
94
94
|
}
|
|
@@ -1,14 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import PropTypes from "prop-types";
|
|
1
|
+
import { useNavigate, useParams } from "react-router";
|
|
3
2
|
import { Header, Icon, Container, Segment } from "semantic-ui-react";
|
|
4
|
-
import { connect } from "react-redux";
|
|
5
3
|
import { FormattedMessage } from "react-intl";
|
|
6
|
-
import {
|
|
4
|
+
import { GROUPS } from "@truedat/core/routes";
|
|
5
|
+
import { useGroup, useGroupUsers } from "../hooks/useGroups";
|
|
6
|
+
import { useGroupOperation } from "../hooks/useGroupOperation";
|
|
7
7
|
import GroupBreadcrumbs from "./GroupBreadcrumbs";
|
|
8
8
|
import GroupForm from "./GroupForm";
|
|
9
9
|
|
|
10
|
-
const EditGroup = (
|
|
11
|
-
|
|
10
|
+
const EditGroup = () => {
|
|
11
|
+
const { id } = useParams();
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
const {
|
|
14
|
+
group,
|
|
15
|
+
members,
|
|
16
|
+
hasData: hasGroup,
|
|
17
|
+
loading,
|
|
18
|
+
error,
|
|
19
|
+
retry,
|
|
20
|
+
} = useGroup(id);
|
|
21
|
+
const {
|
|
22
|
+
users,
|
|
23
|
+
hasData: hasUsers,
|
|
24
|
+
loading: usersLoading,
|
|
25
|
+
error: usersError,
|
|
26
|
+
retry: retryUsers,
|
|
27
|
+
} = useGroupUsers();
|
|
28
|
+
const { submit, pending, failed } = useGroupOperation("update", id, () =>
|
|
29
|
+
navigate(GROUPS),
|
|
30
|
+
);
|
|
31
|
+
if ((loading && !hasGroup) || (usersLoading && !hasUsers))
|
|
32
|
+
return <p role="status">Cargando grupo…</p>;
|
|
33
|
+
if (!hasGroup || !hasUsers || !group)
|
|
34
|
+
return (
|
|
35
|
+
<div role="alert">
|
|
36
|
+
No se ha podido cargar el grupo o sus usuarios.
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => Promise.allSettled([retry(), retryUsers()])}
|
|
40
|
+
>
|
|
41
|
+
Reintentar
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
return (
|
|
12
46
|
<>
|
|
13
47
|
<GroupBreadcrumbs name="groups.actions.edit" />
|
|
14
48
|
<Container text as={Segment}>
|
|
@@ -18,23 +52,33 @@ const EditGroup = ({ updateGroup, group, groupUsers }) =>
|
|
|
18
52
|
<FormattedMessage id="groups.actions.edit" />
|
|
19
53
|
</Header.Content>
|
|
20
54
|
</Header>
|
|
21
|
-
{
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
{(error || usersError) && (
|
|
56
|
+
<div role="alert">
|
|
57
|
+
No se ha podido actualizar el grupo o sus usuarios.
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={() => Promise.allSettled([retry(), retryUsers()])}
|
|
61
|
+
>
|
|
62
|
+
Reintentar
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
{failed && (
|
|
67
|
+
<p role="alert">
|
|
68
|
+
No se ha podido guardar el grupo. Inténtalo de nuevo.
|
|
69
|
+
</p>
|
|
27
70
|
)}
|
|
71
|
+
<GroupForm
|
|
72
|
+
key={group.id}
|
|
73
|
+
onSubmit={(values) => submit({ ...values, id: group.id })}
|
|
74
|
+
group={group}
|
|
75
|
+
groupUsers={members}
|
|
76
|
+
users={users}
|
|
77
|
+
isSubmitting={pending}
|
|
78
|
+
/>
|
|
28
79
|
</Container>
|
|
29
80
|
</>
|
|
30
81
|
);
|
|
31
|
-
|
|
32
|
-
EditGroup.propTypes = {
|
|
33
|
-
group: PropTypes.object,
|
|
34
|
-
groupUsers: PropTypes.array,
|
|
35
|
-
updateGroup: PropTypes.func,
|
|
36
82
|
};
|
|
37
83
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export default connect(mapStateToProps, { updateGroup })(EditGroup);
|
|
84
|
+
export default EditGroup;
|
|
@@ -1,50 +1,59 @@
|
|
|
1
1
|
import PropTypes from "prop-types";
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
import { useNavigate, useParams } from "react-router";
|
|
3
4
|
import { Header, Button, Icon, Grid } from "semantic-ui-react";
|
|
4
5
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
|
-
import
|
|
6
|
+
import { GROUPS } from "@truedat/core/routes";
|
|
6
7
|
import GroupActions from "@truedat/core/components/GroupActions";
|
|
7
|
-
import {
|
|
8
|
+
import { useGroup } from "../hooks/useGroups";
|
|
9
|
+
import GroupDeleteModal from "./GroupDeleteModal";
|
|
8
10
|
|
|
9
|
-
const ModalDelete = ({ group
|
|
11
|
+
const ModalDelete = ({ group }) => {
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const onDeleted = useCallback(() => navigate(GROUPS), [navigate]);
|
|
10
15
|
return (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
<>
|
|
17
|
+
<Button
|
|
18
|
+
type="button"
|
|
19
|
+
icon={<Icon name="trash alternate outline" color="red" />}
|
|
20
|
+
content={<FormattedMessage id="actions.delete" />}
|
|
21
|
+
onClick={() => setOpen(true)}
|
|
22
|
+
/>
|
|
23
|
+
{open && (
|
|
24
|
+
<GroupDeleteModal
|
|
25
|
+
group={group}
|
|
26
|
+
onClose={() => setOpen(false)}
|
|
27
|
+
onDeleted={onDeleted}
|
|
17
28
|
/>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
<FormattedMessage id="group.actions.delete.confirmation.header" />
|
|
21
|
-
}
|
|
22
|
-
content={
|
|
23
|
-
<FormattedMessage
|
|
24
|
-
id="group.actions.delete.confirmation.content"
|
|
25
|
-
values={{ name: <i>{name}</i> }}
|
|
26
|
-
/>
|
|
27
|
-
}
|
|
28
|
-
onConfirm={() => deleteGroup({ id })}
|
|
29
|
-
/>
|
|
29
|
+
)}
|
|
30
|
+
</>
|
|
30
31
|
);
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
ModalDelete.propTypes = {
|
|
34
|
-
group: PropTypes.object,
|
|
35
|
-
deleteGroup: PropTypes.func,
|
|
36
|
-
};
|
|
34
|
+
ModalDelete.propTypes = { group: PropTypes.object.isRequired };
|
|
37
35
|
|
|
38
|
-
export const Group = (
|
|
36
|
+
export const Group = () => {
|
|
37
|
+
const { id } = useParams();
|
|
38
|
+
const { group, loading, error, retry } = useGroup(id);
|
|
39
39
|
const { formatMessage } = useIntl();
|
|
40
|
-
if (
|
|
40
|
+
if (loading) return <p role="status">Cargando grupo…</p>;
|
|
41
|
+
if (error || !group) {
|
|
42
|
+
return (
|
|
43
|
+
<div role="alert">
|
|
44
|
+
No se ha podido cargar el grupo.
|
|
45
|
+
<Button type="button" onClick={() => retry().catch(() => {})}>
|
|
46
|
+
Reintentar
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
41
51
|
const actions = [
|
|
42
52
|
{
|
|
43
53
|
key: "delete",
|
|
44
54
|
icon: "trash alternate outline",
|
|
45
55
|
value: "delete",
|
|
46
56
|
as: ModalDelete,
|
|
47
|
-
deleteGroup,
|
|
48
57
|
group,
|
|
49
58
|
selected: false,
|
|
50
59
|
active: false,
|
|
@@ -78,15 +87,4 @@ export const Group = ({ group, groupLoading, deleteGroup }) => {
|
|
|
78
87
|
);
|
|
79
88
|
};
|
|
80
89
|
|
|
81
|
-
|
|
82
|
-
group: PropTypes.object,
|
|
83
|
-
groupLoading: PropTypes.bool,
|
|
84
|
-
deleteGroup: PropTypes.func,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const mapStateToProps = (state) => ({
|
|
88
|
-
group: state.group,
|
|
89
|
-
groupLoading: state.groupLoading,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
export default connect(mapStateToProps, { deleteGroup })(Group);
|
|
90
|
+
export default Group;
|
|
@@ -1,61 +1,58 @@
|
|
|
1
1
|
import PropTypes from "prop-types";
|
|
2
|
-
import {
|
|
3
|
-
import { Button, Card, Icon } from "semantic-ui-react";
|
|
2
|
+
import { Icon } from "semantic-ui-react";
|
|
4
3
|
import { Link } from "react-router";
|
|
5
|
-
import {
|
|
6
|
-
import ConfirmModal from "@truedat/core/components/ConfirmModal";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
7
5
|
import { linkTo } from "@truedat/core/routes";
|
|
8
|
-
import { deleteGroup } from "../routines";
|
|
9
6
|
|
|
10
|
-
export const GroupCard = ({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
export const GroupCard = ({ group }) => {
|
|
8
|
+
const { formatMessage } = useIntl();
|
|
9
|
+
const { id, name, alias, description, users } = group;
|
|
10
|
+
const displayName = alias || name;
|
|
11
|
+
const count = Array.isArray(users) ? users.length : null;
|
|
12
|
+
const isEmpty = count === 0;
|
|
13
|
+
return (
|
|
14
|
+
<article className="td-group-card">
|
|
15
|
+
<Link className="gc-content" to={linkTo.GROUP({ id })}>
|
|
16
|
+
<span className="gc-avatar" aria-hidden="true">
|
|
17
|
+
<Icon name="users" />
|
|
18
|
+
</span>
|
|
19
|
+
<h3>{displayName}</h3>
|
|
20
|
+
{alias && alias !== name && <span className="gc-name">{name}</span>}
|
|
21
|
+
{description && <p className="gc-description">{description}</p>}
|
|
22
|
+
</Link>
|
|
23
|
+
<div className="gc-actions">
|
|
24
|
+
<Link
|
|
28
25
|
to={linkTo.GROUP_EDIT({ id })}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
content={<FormattedMessage id="actions.delete" />}
|
|
38
|
-
/>
|
|
39
|
-
}
|
|
40
|
-
header={
|
|
41
|
-
<FormattedMessage id="group.actions.delete.confirmation.header" />
|
|
42
|
-
}
|
|
43
|
-
content={
|
|
44
|
-
<FormattedMessage
|
|
45
|
-
id="group.actions.delete.confirmation.content"
|
|
46
|
-
values={{ name: <i>{alias || name}</i> }}
|
|
47
|
-
/>
|
|
48
|
-
}
|
|
49
|
-
onConfirm={() => deleteGroup({ id })}
|
|
50
|
-
/>
|
|
26
|
+
aria-label={formatMessage({ id: "actions.edit" })}
|
|
27
|
+
title={formatMessage({ id: "actions.edit" })}
|
|
28
|
+
>
|
|
29
|
+
<Icon name="pencil alternate" />
|
|
30
|
+
<span className="gc-action-label">
|
|
31
|
+
{formatMessage({ id: "actions.edit" })}
|
|
32
|
+
</span>
|
|
33
|
+
</Link>
|
|
51
34
|
</div>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
{count !== null && (
|
|
36
|
+
<span className={`gc-count${isEmpty ? " gc-count--empty" : ""}`}>
|
|
37
|
+
<Icon name="users" />
|
|
38
|
+
{isEmpty ? (
|
|
39
|
+
<span className="gc-count-empty-marker" aria-hidden="true">
|
|
40
|
+
-
|
|
41
|
+
</span>
|
|
42
|
+
) : (
|
|
43
|
+
count
|
|
44
|
+
)}
|
|
45
|
+
<span className="gc-count-label">
|
|
46
|
+
{formatMessage(
|
|
47
|
+
isEmpty
|
|
48
|
+
? { id: "group.members.none", defaultMessage: "Sin miembros" }
|
|
49
|
+
: { id: "navigation.members", defaultMessage: "miembros" },
|
|
50
|
+
)}
|
|
51
|
+
</span>
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</article>
|
|
55
|
+
);
|
|
59
56
|
};
|
|
60
|
-
|
|
61
|
-
export default
|
|
57
|
+
GroupCard.propTypes = { group: PropTypes.object.isRequired };
|
|
58
|
+
export default GroupCard;
|
|
@@ -1,33 +1,68 @@
|
|
|
1
1
|
import PropTypes from "prop-types";
|
|
2
2
|
import { Link } from "react-router";
|
|
3
|
-
import {
|
|
4
|
-
import { Icon, Card } from "semantic-ui-react";
|
|
3
|
+
import { Button, Icon } from "semantic-ui-react";
|
|
5
4
|
import { FormattedMessage } from "react-intl";
|
|
6
5
|
import { GROUP_CREATE } from "@truedat/core/routes";
|
|
6
|
+
import { useGroups } from "../hooks/useGroups";
|
|
7
7
|
import GroupCard from "./GroupCard";
|
|
8
8
|
|
|
9
|
-
export const GroupCards = ({ groups }) => (
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
export const GroupCards = ({ groups = [] }) => (
|
|
10
|
+
<>
|
|
11
|
+
<div className="groups-toolbar">
|
|
12
|
+
<Button
|
|
13
|
+
primary
|
|
14
|
+
as={Link}
|
|
15
|
+
to={GROUP_CREATE}
|
|
16
|
+
className="group-create-button"
|
|
17
|
+
>
|
|
18
|
+
<span className="group-create-icon" aria-hidden="true">
|
|
19
|
+
<Icon name="users" />
|
|
20
|
+
<svg viewBox="0 0 16 16" focusable="false">
|
|
21
|
+
<path
|
|
22
|
+
d="M8 3v10M3 8h10"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
strokeWidth="2"
|
|
26
|
+
strokeLinecap="round"
|
|
27
|
+
/>
|
|
28
|
+
</svg>
|
|
29
|
+
</span>
|
|
30
|
+
<FormattedMessage id="groups.actions.create" />
|
|
31
|
+
</Button>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="groups-card-list">
|
|
34
|
+
{groups.map((group) => (
|
|
35
|
+
<GroupCard key={group.id} group={group} />
|
|
36
|
+
))}
|
|
37
|
+
</div>
|
|
38
|
+
</>
|
|
25
39
|
);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
groups
|
|
40
|
+
GroupCards.propTypes = { groups: PropTypes.array };
|
|
41
|
+
export const GroupsList = () => {
|
|
42
|
+
const { groups, error, loading, retry } = useGroups();
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{loading && (
|
|
46
|
+
<p role="status">
|
|
47
|
+
<FormattedMessage
|
|
48
|
+
id="group.loading"
|
|
49
|
+
defaultMessage="Cargando grupo…"
|
|
50
|
+
/>
|
|
51
|
+
</p>
|
|
52
|
+
)}
|
|
53
|
+
{error && (
|
|
54
|
+
<div role="alert">
|
|
55
|
+
<FormattedMessage id="group.unavailable" />
|
|
56
|
+
<Button onClick={() => retry().catch(() => {})}>
|
|
57
|
+
<FormattedMessage
|
|
58
|
+
id="group.members.retry"
|
|
59
|
+
defaultMessage="Reintentar"
|
|
60
|
+
/>
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
<GroupCards groups={groups} />
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
29
67
|
};
|
|
30
|
-
|
|
31
|
-
const mapStateToProps = ({ groups }) => ({ groups });
|
|
32
|
-
|
|
33
|
-
export default connect(mapStateToProps)(GroupCards);
|
|
68
|
+
export default GroupsList;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import { useIntl } from "react-intl";
|
|
3
|
+
import { TrueModal } from "@truedat/core/components/TrueModal";
|
|
4
|
+
import { useGroupOperation } from "../hooks/useGroupOperation";
|
|
5
|
+
|
|
6
|
+
export const GroupDeleteModal = ({ group, onClose, onDeleted, member }) => {
|
|
7
|
+
const { formatMessage } = useIntl();
|
|
8
|
+
const { submit, pending, failed } = useGroupOperation(
|
|
9
|
+
member ? "removeMember" : "delete",
|
|
10
|
+
group?.id,
|
|
11
|
+
() => {
|
|
12
|
+
onClose();
|
|
13
|
+
onDeleted?.();
|
|
14
|
+
},
|
|
15
|
+
);
|
|
16
|
+
const message = (id, defaultMessage) => formatMessage({ id, defaultMessage });
|
|
17
|
+
return (
|
|
18
|
+
<TrueModal
|
|
19
|
+
open
|
|
20
|
+
size="mini"
|
|
21
|
+
headerColor="#ffffff"
|
|
22
|
+
headerTextColor="var(--td-danger, #b91c1c)"
|
|
23
|
+
aria-busy={pending}
|
|
24
|
+
className="group-modal group-modal--delete"
|
|
25
|
+
title={message(
|
|
26
|
+
member
|
|
27
|
+
? "groupUsers.actions.delete.confirmation.header"
|
|
28
|
+
: "group.actions.delete.confirmation.header",
|
|
29
|
+
)}
|
|
30
|
+
icon="warning sign"
|
|
31
|
+
onClose={() => !pending && onClose()}
|
|
32
|
+
closeDisabled={pending}
|
|
33
|
+
closeOnEscape={!pending}
|
|
34
|
+
closeOnDimmerClick={!pending}
|
|
35
|
+
banner={
|
|
36
|
+
failed
|
|
37
|
+
? message(
|
|
38
|
+
"group.operation.error",
|
|
39
|
+
"No se ha podido completar la operación. Inténtalo de nuevo.",
|
|
40
|
+
)
|
|
41
|
+
: null
|
|
42
|
+
}
|
|
43
|
+
bannerType="danger"
|
|
44
|
+
actions={[
|
|
45
|
+
{
|
|
46
|
+
key: "cancel",
|
|
47
|
+
content: message("actions.cancel"),
|
|
48
|
+
secondary: true,
|
|
49
|
+
disabled: pending,
|
|
50
|
+
onClick: onClose,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "delete",
|
|
54
|
+
content: message("actions.delete"),
|
|
55
|
+
negative: true,
|
|
56
|
+
loading: pending,
|
|
57
|
+
disabled: pending,
|
|
58
|
+
onClick: () => submit(member ? { id: member.id } : {}),
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
<p>
|
|
63
|
+
{formatMessage(
|
|
64
|
+
{
|
|
65
|
+
id: member
|
|
66
|
+
? "groupUsers.actions.delete.confirmation.content"
|
|
67
|
+
: "group.delete.description",
|
|
68
|
+
defaultMessage: member
|
|
69
|
+
? "Se va a eliminar al usuario seleccionado del grupo, ¿estás seguro?"
|
|
70
|
+
: "Se va a eliminar el grupo <strong>{name}</strong>. <danger>Esta acción no se puede deshacer.</danger>",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: group?.alias || group?.name,
|
|
74
|
+
strong: (chunks) => <strong>{chunks}</strong>,
|
|
75
|
+
danger: (chunks) => (
|
|
76
|
+
<span className="group-danger-text">{chunks}</span>
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
)}
|
|
80
|
+
</p>
|
|
81
|
+
{!member && (
|
|
82
|
+
<p>
|
|
83
|
+
{message(
|
|
84
|
+
"group.delete.confirm",
|
|
85
|
+
"¿Seguro que quieres eliminar este grupo?",
|
|
86
|
+
)}
|
|
87
|
+
</p>
|
|
88
|
+
)}
|
|
89
|
+
</TrueModal>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
GroupDeleteModal.propTypes = {
|
|
93
|
+
group: PropTypes.object.isRequired,
|
|
94
|
+
onClose: PropTypes.func.isRequired,
|
|
95
|
+
onDeleted: PropTypes.func,
|
|
96
|
+
member: PropTypes.object,
|
|
97
|
+
};
|
|
98
|
+
export default GroupDeleteModal;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { Form } from "semantic-ui-react";
|
|
4
|
+
|
|
5
|
+
const GroupDropdown = ({ onOpen, onClose, ...props }) => {
|
|
6
|
+
const anchor = useRef(null);
|
|
7
|
+
const [open, setOpen] = useState(false);
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
if (!open) return undefined;
|
|
10
|
+
const trigger = anchor.current?.querySelector(".ui.dropdown");
|
|
11
|
+
const menu = trigger?.querySelector(":scope > .menu");
|
|
12
|
+
if (!menu) return undefined;
|
|
13
|
+
const previousStyle = menu.getAttribute("style");
|
|
14
|
+
const frame = { current: null };
|
|
15
|
+
const position = () => {
|
|
16
|
+
const rect = trigger.getBoundingClientRect();
|
|
17
|
+
const below = window.innerHeight - rect.bottom - 8;
|
|
18
|
+
const above = rect.top - 8;
|
|
19
|
+
const upward = below < 180 && above > below;
|
|
20
|
+
const maxHeight = Math.max(0, Math.min(180, upward ? above : below));
|
|
21
|
+
const width = Math.min(rect.width, window.innerWidth - 16);
|
|
22
|
+
const styles = {
|
|
23
|
+
position: "fixed",
|
|
24
|
+
margin: "0",
|
|
25
|
+
inset: "auto",
|
|
26
|
+
left: `${Math.max(8, Math.min(rect.left, window.innerWidth - width - 8))}px`,
|
|
27
|
+
width: `${width}px`,
|
|
28
|
+
minWidth: "0",
|
|
29
|
+
maxHeight: `${maxHeight}px`,
|
|
30
|
+
top: upward ? "auto" : `${rect.bottom}px`,
|
|
31
|
+
bottom: upward ? `${window.innerHeight - rect.top}px` : "auto",
|
|
32
|
+
};
|
|
33
|
+
Object.assign(menu.style, styles);
|
|
34
|
+
};
|
|
35
|
+
const schedule = (event) => {
|
|
36
|
+
if (event?.target === menu) return;
|
|
37
|
+
cancelAnimationFrame(frame.current);
|
|
38
|
+
frame.current = requestAnimationFrame(position);
|
|
39
|
+
};
|
|
40
|
+
menu.classList.add("group-dropdown-layer");
|
|
41
|
+
menu.setAttribute("popover", "manual");
|
|
42
|
+
position();
|
|
43
|
+
menu.showPopover?.();
|
|
44
|
+
const handleEscape = (event) => {
|
|
45
|
+
if (event.key !== "Escape") return;
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopImmediatePropagation();
|
|
48
|
+
setOpen(false);
|
|
49
|
+
};
|
|
50
|
+
window.addEventListener("keydown", handleEscape, true);
|
|
51
|
+
window.addEventListener("resize", schedule);
|
|
52
|
+
document.addEventListener("scroll", schedule, true);
|
|
53
|
+
const observer =
|
|
54
|
+
typeof ResizeObserver === "undefined"
|
|
55
|
+
? null
|
|
56
|
+
: new ResizeObserver(schedule);
|
|
57
|
+
observer?.observe(trigger);
|
|
58
|
+
return () => {
|
|
59
|
+
cancelAnimationFrame(frame.current);
|
|
60
|
+
observer?.disconnect();
|
|
61
|
+
window.removeEventListener("keydown", handleEscape, true);
|
|
62
|
+
window.removeEventListener("resize", schedule);
|
|
63
|
+
document.removeEventListener("scroll", schedule, true);
|
|
64
|
+
if (menu.hidePopover && menu.matches(":popover-open")) menu.hidePopover();
|
|
65
|
+
menu.removeAttribute("popover");
|
|
66
|
+
menu.classList.remove("group-dropdown-layer");
|
|
67
|
+
if (previousStyle === null) menu.removeAttribute("style");
|
|
68
|
+
else menu.setAttribute("style", previousStyle);
|
|
69
|
+
};
|
|
70
|
+
}, [open]);
|
|
71
|
+
return (
|
|
72
|
+
<div className="group-dropdown-anchor" ref={anchor}>
|
|
73
|
+
<Form.Dropdown
|
|
74
|
+
{...props}
|
|
75
|
+
open={open}
|
|
76
|
+
onOpen={(event, data) => {
|
|
77
|
+
anchor.current
|
|
78
|
+
?.querySelector("input.search")
|
|
79
|
+
?.focus({ preventScroll: true });
|
|
80
|
+
setOpen(true);
|
|
81
|
+
onOpen?.(event, data);
|
|
82
|
+
}}
|
|
83
|
+
onClose={(event, data) => {
|
|
84
|
+
setOpen(false);
|
|
85
|
+
onClose?.(event, data);
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
GroupDropdown.propTypes = { onOpen: PropTypes.func, onClose: PropTypes.func };
|
|
92
|
+
export default GroupDropdown;
|