@truedat/dd 8.9.1 → 8.10.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "8.9.1",
3
+ "version": "8.10.1",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -51,7 +51,7 @@
51
51
  "@testing-library/jest-dom": "^6.6.3",
52
52
  "@testing-library/react": "^16.3.0",
53
53
  "@testing-library/user-event": "^14.6.1",
54
- "@truedat/test": "8.9.1",
54
+ "@truedat/test": "8.10.1",
55
55
  "identity-obj-proxy": "^3.0.0",
56
56
  "jest": "^29.7.0",
57
57
  "redux-saga-test-plan": "^4.0.6"
@@ -86,5 +86,5 @@
86
86
  "svg-pan-zoom": "^3.6.2",
87
87
  "swr": "^2.3.3"
88
88
  },
89
- "gitHead": "778d7597a979b8c64dd2bbd5d512fd7c4a24b453"
89
+ "gitHead": "77921b9429f2536dbc3c51e7e5feae55c0e5ebef"
90
90
  }
package/src/api.js CHANGED
@@ -19,6 +19,7 @@ const API_GRANT_REQUEST_STATUS = "/api/grant_requests/:id/status";
19
19
  const API_GRANT_REQUEST_GROUP = "/api/grant_request_groups";
20
20
  const API_GRANT_REQUEST_GROUPS_BULK = "/api/grant_request_groups/bulk";
21
21
  const API_GRANT_REQUESTS = "/api/grant_requests";
22
+ const API_GRANT_REQUESTS_DOWNLOAD = "/api/grant_requests/xlsx/download";
22
23
  const API_GRANT_REQUEST_BULK_APPROVAL = "/api/grant_requests/bulk_approval";
23
24
  const API_GRANT_REQUESTS_SEARCH = "/api/grant_requests/search";
24
25
  const API_GRANT_REQUESTS_FILTERS_SEARCH = "/api/grant_requests_filters/search";
@@ -82,6 +83,7 @@ export {
82
83
  API_GRANT_REQUESTS,
83
84
  API_GRANT_REQUESTS_SEARCH,
84
85
  API_GRANT_REQUESTS_FILTERS_SEARCH,
86
+ API_GRANT_REQUESTS_DOWNLOAD,
85
87
  API_GRANTS_SEARCH,
86
88
  API_GRAPH,
87
89
  API_GRAPH_HASH,
@@ -9,17 +9,17 @@ import { GRANT_REQUESTS_APPROVALS_RESULT } from "@truedat/core/routes";
9
9
  import { useGrantRequestBulkApproval } from "@truedat/dd/hooks/useGrantRequest";
10
10
  import GrantRequestBulkApprovalPopup from "./GrantRequestBulkApprovalPopup";
11
11
 
12
- // import GrantRequestBulkRoleSelector from "./GrantRequestBulkRoleSelector";
13
-
14
12
  export const GrantRequestBulkActions = ({
15
- selectedGrantRequests,
16
- cleanSelectedGrantRequests,
13
+ selectedGrantRequests = [],
14
+ cleanSelectedGrantRequests = () => {},
15
+ approveView = true,
17
16
  }) => {
18
17
  const {
19
18
  searchData,
20
19
  loading,
21
20
  toggleHiddenFilterValue,
22
21
  resetFilters,
22
+ setHiddenFilters,
23
23
  searchMust,
24
24
  cleanQuery,
25
25
  } = useSearchContext();
@@ -30,10 +30,17 @@ export const GrantRequestBulkActions = ({
30
30
  const { trigger } = useGrantRequestBulkApproval();
31
31
 
32
32
  const selectRole = (role) => {
33
- setSelectedRole(role);
34
33
  resetFilters();
35
34
  cleanSelectedGrantRequests();
36
35
  cleanQuery();
36
+
37
+ if (!role) {
38
+ setSelectedRole(null);
39
+ setHiddenFilters({});
40
+ return;
41
+ }
42
+
43
+ setSelectedRole(role);
37
44
  toggleHiddenFilterValue({
38
45
  filter: "must_not_approved_by",
39
46
  value: [role],
@@ -44,10 +51,12 @@ export const GrantRequestBulkActions = ({
44
51
  const grantRequests = _.propOr([], "data")(searchData);
45
52
  const zeroResults = _.isEmpty(grantRequests);
46
53
 
47
- const firstRole = _.first(rolesPermission);
48
54
  useEffect(() => {
49
- selectRole(firstRole);
50
- }, [firstRole]);
55
+ if (!approveView) {
56
+ setSelectedRole(null);
57
+ cleanSelectedGrantRequests();
58
+ }
59
+ }, [approveView]);
51
60
 
52
61
  const onSubmit = ({ comment, isRejection }) => {
53
62
  setSaving(true);
@@ -77,34 +86,59 @@ export const GrantRequestBulkActions = ({
77
86
  _.sortBy(_.identity),
78
87
  _.map((role) => ({ key: role, value: role, text: role })),
79
88
  )(rolesPermission);
89
+ const roleSelectorPlaceholder = formatMessage({
90
+ id: "grantRequestApproval.form.role.select",
91
+ defaultMessage: "Seleccione un rol",
92
+ });
93
+ const firstRole = _.first(rolesPermission);
94
+ useEffect(() => {
95
+ selectRole(firstRole);
96
+ }, [firstRole]);
97
+
98
+ const roleSelectorOptions = [
99
+ {
100
+ key: "select-role",
101
+ text: roleSelectorPlaceholder,
102
+ disabled: true,
103
+ className: "header",
104
+ },
105
+ ...roleOptions,
106
+ ];
80
107
 
81
108
  return (
82
109
  <>
83
- <Dropdown
84
- className="grant-requests-bulk-role-selector"
85
- placeholder={formatMessage({
86
- id: "grantRequestApproval.form.role.select",
87
- })}
88
- selection
89
- options={roleOptions}
90
- onChange={(_e, { value }) => selectRole(value)}
91
- value={selectedRole}
92
- />
93
- <GrantRequestBulkApprovalPopup
94
- onSubmit={onSubmit}
95
- content={formatMessage({ id: "grantRequest.actions.approve" })}
96
- disabled={_.isNil(selectedRole) || loading || zeroResults}
97
- loading={saving}
98
- primary
99
- />
100
- <GrantRequestBulkApprovalPopup
101
- onSubmit={onSubmit}
102
- isRejection
103
- content={formatMessage({ id: "grantRequest.actions.reject" })}
104
- disabled={_.isNil(selectedRole) || loading || zeroResults}
105
- loading={saving}
106
- secondary
107
- />
110
+ {approveView ? (
111
+ <>
112
+ <Dropdown
113
+ className={`grant-requests-bulk-role-selector ${
114
+ selectedRole ? "" : "grant-requests-bulk-role-selector-empty"
115
+ }`}
116
+ placeholder={formatMessage({
117
+ id: "grantRequestApproval.form.role.select",
118
+ defaultMessage: "Seleccione un rol",
119
+ })}
120
+ selection
121
+ options={roleSelectorOptions}
122
+ onChange={(_e, { value }) => selectRole(value)}
123
+ value={selectedRole || ""}
124
+ />
125
+ <GrantRequestBulkApprovalPopup
126
+ onSubmit={onSubmit}
127
+ content={formatMessage({ id: "grantRequest.actions.approve" })}
128
+ disabled={_.isNil(selectedRole) || loading || zeroResults}
129
+ loading={saving}
130
+ primary
131
+ />
132
+ <GrantRequestBulkApprovalPopup
133
+ onSubmit={onSubmit}
134
+ isRejection
135
+ content={formatMessage({ id: "grantRequest.actions.reject" })}
136
+ disabled={_.isNil(selectedRole) || loading || zeroResults}
137
+ loading={saving}
138
+ secondary
139
+ />
140
+ </>
141
+ ) : null}
108
142
  </>
109
143
  );
110
144
  };
@@ -112,6 +146,7 @@ export const GrantRequestBulkActions = ({
112
146
  GrantRequestBulkActions.propTypes = {
113
147
  selectedGrantRequests: PropTypes.array,
114
148
  cleanSelectedGrantRequests: PropTypes.func,
149
+ approveView: PropTypes.bool,
115
150
  };
116
151
 
117
152
  export default GrantRequestBulkActions;
@@ -1,43 +1,54 @@
1
1
  import { useState } from "react";
2
- import { Button, Popup } from "semantic-ui-react";
3
2
  import PropTypes from "prop-types";
3
+ import { Button, Popup } from "semantic-ui-react";
4
4
  import GrantRequestBulkApprovalForm from "./GrantRequestBulkApprovalForm";
5
5
 
6
6
  export const GrantRequestBulkApprovalPopup = ({
7
7
  onSubmit,
8
8
  isRejection,
9
+ content,
10
+ className,
9
11
  ...buttonProps
10
12
  }) => {
11
13
  const [open, setOpen] = useState(false);
14
+ const buttonClassName = [
15
+ "button icon group-actions grant-requests-approval-button",
16
+ className,
17
+ ]
18
+ .filter(Boolean)
19
+ .join(" ");
12
20
 
13
21
  return (
14
22
  <Popup
15
23
  on="click"
16
- basic
17
- flowing
18
- content={
19
- <GrantRequestBulkApprovalForm
20
- onSubmit={(item) => onSubmit(item, () => setOpen(false))}
21
- isRejection={isRejection}
22
- />
23
- }
24
- onOpen={() => setOpen(true)}
24
+ modal
25
25
  onClose={() => setOpen(false)}
26
+ onOpen={() => setOpen(true)}
26
27
  open={open}
27
- position="bottom right"
28
+ closeOnDocumentClick={false}
29
+ wide="very"
28
30
  size="large"
29
31
  positionFixed
30
32
  trigger={
31
- <Button className="button icon group-actions" {...buttonProps} />
33
+ <Button className={buttonClassName} {...buttonProps}>
34
+ {content}
35
+ </Button>
32
36
  }
33
- />
37
+ >
38
+ <GrantRequestBulkApprovalForm
39
+ onSubmit={onSubmit}
40
+ isRejection={isRejection}
41
+ onCancel={() => setOpen(false)}
42
+ />
43
+ </Popup>
34
44
  );
35
45
  };
36
46
 
37
47
  GrantRequestBulkApprovalPopup.propTypes = {
38
- grantRequest: PropTypes.object,
39
48
  isRejection: PropTypes.bool,
40
49
  saving: PropTypes.bool,
41
50
  onSubmit: PropTypes.func,
51
+ content: PropTypes.node,
52
+ className: PropTypes.string,
42
53
  };
43
54
  export default GrantRequestBulkApprovalPopup;
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
- import { Checkbox, Table } from "semantic-ui-react";
4
3
  import { useNavigate } from "react-router";
4
+ import { Checkbox, Table } from "semantic-ui-react";
5
5
  import { linkTo } from "@truedat/core/routes";
6
6
  import { columnDecoratorComponent } from "@truedat/core/services";
7
7
 
@@ -40,10 +40,6 @@ export const GrantRequestRow = ({
40
40
  key={key}
41
41
  textAlign={column.textAlign}
42
42
  onClick={(ev) => {
43
- /* Only do history.push if the table cell but not
44
- * some possibly contained anchor (or an element
45
- * inside an anchor, such as an icon) is clicked
46
- */
47
43
  !ev.target.closest("a") &&
48
44
  navigate(linkTo.GRANT_REQUEST(grantRequest));
49
45
  }}
@@ -0,0 +1,81 @@
1
+ import _ from "lodash/fp";
2
+ import { useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Button } from "semantic-ui-react";
5
+ import { useIntl } from "react-intl";
6
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
7
+ import { useGrantRequestsDownload } from "../hooks/useGrantRequest";
8
+
9
+ const staticLabels = [
10
+ "current_status",
11
+ "user_full_name",
12
+ "created_by_full_name",
13
+ "structure_external_id",
14
+ "domains",
15
+ "system_name",
16
+ "structure_path",
17
+ "request_type",
18
+ ];
19
+
20
+ export const GrantRequestsDownloadButton = ({
21
+ searchBy,
22
+ setDownloading,
23
+ selectedGrantRequests = [],
24
+ }) => {
25
+ const { formatMessage, locale } = useIntl();
26
+
27
+ const {
28
+ searchData,
29
+ loading: grantRequestsLoading,
30
+ filterParams: searchParams,
31
+ } = useSearchContext();
32
+
33
+ const { trigger: triggerDownload, isMutating: grantRequestsDownloading } =
34
+ useGrantRequestsDownload();
35
+
36
+ useEffect(() => {
37
+ setDownloading(grantRequestsDownloading);
38
+ }, [setDownloading, grantRequestsDownloading]);
39
+
40
+ const grantRequests = searchData?.data;
41
+
42
+ const headerLabels = _.flow(
43
+ _.map((l) => [l, formatMessage({ id: `grantRequests.props.${l}` })]),
44
+ _.fromPairs,
45
+ )(staticLabels);
46
+
47
+ return (
48
+ <Button
49
+ secondary
50
+ icon="download"
51
+ onClick={() =>
52
+ triggerDownload({
53
+ search_by: searchBy,
54
+ header_labels: headerLabels,
55
+ lang: locale,
56
+ ...(_.isEmpty(selectedGrantRequests)
57
+ ? searchParams
58
+ : { must: { id: selectedGrantRequests } }),
59
+ })
60
+ }
61
+ loading={grantRequestsDownloading}
62
+ disabled={
63
+ grantRequestsLoading ||
64
+ (_.isEmpty(selectedGrantRequests) &&
65
+ (_.isEmpty(grantRequests) || _.isEmpty(searchParams)))
66
+ }
67
+ data-tooltip={formatMessage({
68
+ id: "grantRequest.actions.download.tooltip",
69
+ })}
70
+ data-position="top right"
71
+ />
72
+ );
73
+ };
74
+
75
+ GrantRequestsDownloadButton.propTypes = {
76
+ searchBy: PropTypes.oneOf(["approve", "all"]),
77
+ setDownloading: PropTypes.func,
78
+ selectedGrantRequests: PropTypes.array,
79
+ };
80
+
81
+ export default GrantRequestsDownloadButton;
@@ -1,29 +1,35 @@
1
+ import _ from "lodash/fp";
1
2
  import PropTypes from "prop-types";
2
3
  import { FormattedMessage } from "react-intl";
3
4
  import { Label } from "semantic-ui-react";
4
5
  import { useSearchContext } from "@truedat/core/search/SearchContext";
5
6
 
6
7
  export const GrantRequestsLabelResults = ({ selectedCount, approveView }) => {
7
- const { count, loading } = useSearchContext();
8
+ const { count, searchMust } = useSearchContext();
9
+
10
+ const hasRoleSelected = !_.isEmpty(
11
+ _.prop("must_not_approved_by")(searchMust),
12
+ );
13
+
14
+ const displayCount =
15
+ approveView && !selectedCount && hasRoleSelected
16
+ ? count
17
+ : (selectedCount ?? count);
8
18
 
9
19
  return (
10
20
  <div className="grant-requests-bulk-label-results">
11
21
  <Label>
12
- {loading ? (
13
- <FormattedMessage id="grantRequests.searching" />
14
- ) : (
15
- <FormattedMessage
16
- id="grantRequests.retrieved.results"
17
- values={{ count }}
18
- />
19
- )}
22
+ <FormattedMessage
23
+ id="grantRequests.retrieved.results"
24
+ values={{ count }}
25
+ />
20
26
  </Label>
21
- {!loading && approveView ? (
27
+ {approveView ? (
22
28
  <Label>
23
29
  <FormattedMessage
24
30
  id="grantRequests.bulk.selected"
25
31
  values={{
26
- count: selectedCount || count,
32
+ count: displayCount,
27
33
  }}
28
34
  />
29
35
  </Label>
@@ -7,8 +7,7 @@ import {
7
7
  Segment,
8
8
  Dimmer,
9
9
  Loader,
10
- GridColumn,
11
- GridRow,
10
+ Divider,
12
11
  } from "semantic-ui-react";
13
12
  import { FormattedMessage, useIntl } from "react-intl";
14
13
  import SearchWidget from "@truedat/core/search/SearchWidget";
@@ -17,6 +16,7 @@ import Pagination from "@truedat/core/search/Pagination";
17
16
  import GrantRequestBulkActions from "./GrantRequestBulkActions";
18
17
  import GrantRequestsTable from "./GrantRequestsTable";
19
18
  import GrantRequestsLabelResults from "./GrantRequestsLabelResults";
19
+ import GrantRequestsDownloadButton from "./GrantRequestsDownloadButton";
20
20
 
21
21
  export const GrantRequestsSearchResults = () => {
22
22
  const { searchData, loading, setOnSearchChange, setHiddenFilters } =
@@ -42,6 +42,7 @@ export const GrantRequestsSearchResults = () => {
42
42
 
43
43
  const setSearchView = () => {
44
44
  setApproveView(false);
45
+ cleanSelection();
45
46
  setHiddenFilters({});
46
47
  };
47
48
 
@@ -92,73 +93,82 @@ export const GrantRequestsSearchResults = () => {
92
93
 
93
94
  return (
94
95
  <Segment>
95
- <Header as="h2">
96
- <Icon
97
- circular
98
- name={formatMessage({
99
- id: "grant.header.icon",
100
- defaultMessage: "key",
101
- })}
102
- />
103
- <Header.Content>
104
- <FormattedMessage id={"grantRequests.header.approve"} />
105
- <Header.Subheader>
106
- <FormattedMessage id={"grantRequests.subheader.approve"} />
107
- </Header.Subheader>
108
- </Header.Content>
109
- </Header>
96
+ <div className="grant-requests-header">
97
+ <Header as="h2">
98
+ <Icon
99
+ circular
100
+ name={formatMessage({
101
+ id: "grant.header.icon",
102
+ defaultMessage: "key",
103
+ })}
104
+ />
105
+ <Header.Content>
106
+ <FormattedMessage id={"grantRequests.header.approve"} />
107
+ <Header.Subheader>
108
+ <FormattedMessage id={"grantRequests.subheader.approve"} />
109
+ </Header.Subheader>
110
+ </Header.Content>
111
+ </Header>
112
+ {hasApproveRoles ? (
113
+ <div
114
+ className={`grant-requests-view-actions ${
115
+ approveView
116
+ ? "grant-requests-view-actions-approve"
117
+ : "grant-requests-view-actions-search"
118
+ }`}
119
+ >
120
+ <Button
121
+ active={approveView}
122
+ className="grant-requests-view-button"
123
+ onClick={() => setApproveView(true)}
124
+ >
125
+ <FormattedMessage
126
+ id="grantRequests.view.approve"
127
+ defaultMessage="Approve view"
128
+ />
129
+ </Button>
130
+ <Button
131
+ active={!approveView}
132
+ className="grant-requests-view-button"
133
+ onClick={() => setSearchView()}
134
+ >
135
+ <FormattedMessage
136
+ id="grantRequests.view.list"
137
+ defaultMessage="List view"
138
+ />
139
+ </Button>
140
+ </div>
141
+ ) : null}
142
+ </div>
143
+ <Divider hidden />
110
144
  <Segment attached="bottom">
111
- <div className="grant-requests-bulk-actions">
112
- <div>
145
+ <div className="grant-requests-toolbar">
146
+ <div className="grant-requests-search">
113
147
  <SearchWidget />
114
148
  </div>
115
- <div>
116
- {hasApproveRoles ? (
117
- <>
118
- <GridRow className="grant-requests-approve-actions">
119
- <GridColumn width={5}>
120
- <Button.Group>
121
- <Button
122
- active={approveView}
123
- icon
124
- onClick={() => setApproveView(true)}
125
- >
126
- Approve view
127
- </Button>
128
- <Button
129
- active={!approveView}
130
- icon
131
- onClick={() => setSearchView()}
132
- >
133
- Search view
134
- </Button>
135
- </Button.Group>
136
- </GridColumn>
137
- </GridRow>
138
- {approveView ? (
139
- <GridRow>
140
- <GridColumn textAlign="right" width={5}>
141
- <GrantRequestBulkActions
142
- selectedGrantRequests={selectedGrantRequests}
143
- cleanSelectedGrantRequests={cleanSelection}
144
- />
145
- </GridColumn>
146
- </GridRow>
147
- ) : null}
148
- </>
149
+ <div className="grant-requests-approve-actions">
150
+ {hasApproveRoles && approveView ? (
151
+ <GrantRequestBulkActions
152
+ selectedGrantRequests={selectedGrantRequests}
153
+ cleanSelectedGrantRequests={cleanSelection}
154
+ approveView={approveView}
155
+ />
149
156
  ) : null}
150
157
  </div>
158
+ <div className="grant-requests-download-actions">
159
+ <GrantRequestsDownloadButton
160
+ searchBy={approveView ? "approve" : "all"}
161
+ setDownloading={() => {}}
162
+ selectedGrantRequests={selectedGrantRequests}
163
+ />
164
+ </div>
151
165
  </div>
166
+ <GrantRequestsLabelResults
167
+ approveView={approveView}
168
+ selectedCount={_.size(selectedGrantRequests)}
169
+ />
152
170
  <Dimmer.Dimmable dimmed={loading}>
153
- {loading ? (
154
- <Dimmer active inverted>
155
- <Loader />
156
- </Dimmer>
157
- ) : null}
158
- <GrantRequestsLabelResults
159
- approveView={approveView}
160
- selectedCount={_.size(selectedGrantRequests)}
161
- />
171
+ <Dimmer active={loading} inverted />
162
172
  <GrantRequestsTable
163
173
  approveView={approveView}
164
174
  addAll={addAll}