@dhis2/analytics 28.1.3 → 29.0.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/build/cjs/__demo__/InterpretationsUnit.stories.js +9 -6
- package/build/cjs/__fixtures__/interpretationsMockData.js +204 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/DashboardInterpretationThread.js +56 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/DashboardItemInterpretations.js +54 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/index.js +12 -0
- package/build/cjs/components/Interpretations/InterpretationModal/Comment.js +12 -17
- package/build/cjs/components/Interpretations/InterpretationModal/CommentAddForm.js +20 -34
- package/build/cjs/components/Interpretations/InterpretationModal/CommentDeleteButton.js +11 -36
- package/build/cjs/components/Interpretations/InterpretationModal/CommentUpdateForm.js +11 -27
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +11 -68
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationThread.js +11 -24
- package/build/cjs/components/Interpretations/InterpretationsProvider/InterpretationsManager.js +275 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/InterpretationsProvider.js +28 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/__tests__/groupInterpretationIdsByDate.spec.js +37 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/__tests__/hooks.spec.js +565 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js +16 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/hooks.js +278 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/index.js +12 -0
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationForm.js +25 -30
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationList.js +8 -38
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +22 -73
- package/build/cjs/components/Interpretations/common/Interpretation/Interpretation.js +20 -34
- package/build/cjs/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +10 -12
- package/build/cjs/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +14 -25
- package/build/cjs/components/Interpretations/common/Message/MessageEditorContainer.js +3 -3
- package/build/cjs/index.js +72 -63
- package/build/cjs/locales/en/translations.json +10 -1
- package/build/es/__demo__/InterpretationsUnit.stories.js +9 -6
- package/build/es/__fixtures__/interpretationsMockData.js +198 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/DashboardInterpretationThread.js +48 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/DashboardItemInterpretations.js +45 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/index.js +1 -0
- package/build/es/components/Interpretations/InterpretationModal/Comment.js +14 -19
- package/build/es/components/Interpretations/InterpretationModal/CommentAddForm.js +21 -35
- package/build/es/components/Interpretations/InterpretationModal/CommentDeleteButton.js +11 -35
- package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +12 -28
- package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +12 -69
- package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +11 -24
- package/build/es/components/Interpretations/InterpretationsProvider/InterpretationsManager.js +268 -0
- package/build/es/components/Interpretations/InterpretationsProvider/InterpretationsProvider.js +19 -0
- package/build/es/components/Interpretations/InterpretationsProvider/__tests__/groupInterpretationIdsByDate.spec.js +35 -0
- package/build/es/components/Interpretations/InterpretationsProvider/__tests__/hooks.spec.js +561 -0
- package/build/es/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js +9 -0
- package/build/es/components/Interpretations/InterpretationsProvider/hooks.js +258 -0
- package/build/es/components/Interpretations/InterpretationsProvider/index.js +1 -0
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +26 -31
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationList.js +8 -38
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +23 -75
- package/build/es/components/Interpretations/common/Interpretation/Interpretation.js +21 -35
- package/build/es/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +11 -13
- package/build/es/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +15 -26
- package/build/es/components/Interpretations/common/Message/MessageEditorContainer.js +3 -3
- package/build/es/index.js +3 -1
- package/build/es/locales/en/translations.json +10 -1
- package/package.json +1 -1
- package/build/cjs/components/Interpretations/common/Interpretation/useLike.js +0 -56
- package/build/es/components/Interpretations/common/Interpretation/useLike.js +0 -50
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import _JSXStyle from "styled-jsx/style";
|
|
2
|
-
import { useDataQuery } from '@dhis2/app-runtime';
|
|
3
2
|
import i18n from '@dhis2/d2-i18n';
|
|
4
3
|
import { Modal, ModalActions, ModalContent, NoticeBox, Button, spacers, colors, Layer, CenteredContent, CircularLoader } from '@dhis2/ui';
|
|
5
4
|
import cx from 'classnames';
|
|
6
5
|
import PropTypes from 'prop-types';
|
|
7
|
-
import React, {
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { useActiveInterpretation, useInterpretationsCurrentUser } from '../InterpretationsProvider/hooks.js';
|
|
8
8
|
import { InterpretationThread } from './InterpretationThread.js';
|
|
9
9
|
import { useModalContentWidth } from './useModalContentWidth.js';
|
|
10
10
|
const modalCSS = {
|
|
@@ -22,25 +22,12 @@ function getModalContentCSS(width) {
|
|
|
22
22
|
className: _JSXStyle.dynamic([["2099285089", [width]]])
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
const query = {
|
|
26
|
-
interpretation: {
|
|
27
|
-
resource: 'interpretations',
|
|
28
|
-
id: ({
|
|
29
|
-
id
|
|
30
|
-
}) => id,
|
|
31
|
-
params: {
|
|
32
|
-
fields: ['access[write,manage]', 'id', 'text', 'created', 'createdBy[id,displayName]', 'likes', 'likedBy', 'comments[id,text,created,createdBy[id,displayName]]']
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
25
|
const InterpretationModal = ({
|
|
37
|
-
currentUser,
|
|
38
26
|
isVisualizationLoading,
|
|
39
27
|
visualization,
|
|
40
28
|
onResponsesReceived,
|
|
41
29
|
downloadMenuComponent,
|
|
42
30
|
onClose,
|
|
43
|
-
onInterpretationUpdate,
|
|
44
31
|
interpretationId,
|
|
45
32
|
initialFocus,
|
|
46
33
|
pluginComponent: VisualizationPlugin
|
|
@@ -48,53 +35,14 @@ const InterpretationModal = ({
|
|
|
48
35
|
var _currentUser$settings;
|
|
49
36
|
const modalContentWidth = useModalContentWidth();
|
|
50
37
|
const modalContentCSS = getModalContentCSS(modalContentWidth);
|
|
51
|
-
const
|
|
38
|
+
const currentUser = useInterpretationsCurrentUser();
|
|
52
39
|
const {
|
|
53
|
-
data,
|
|
54
|
-
error,
|
|
40
|
+
data: interpretation,
|
|
55
41
|
loading,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} = useDataQuery(query, {
|
|
59
|
-
lazy: true
|
|
60
|
-
});
|
|
61
|
-
const interpretation = data === null || data === void 0 ? void 0 : data.interpretation;
|
|
42
|
+
error
|
|
43
|
+
} = useActiveInterpretation(interpretationId);
|
|
62
44
|
const shouldRenderModalContent = !error && interpretation;
|
|
63
45
|
const loadingInProgress = loading || isVisualizationLoading;
|
|
64
|
-
const handleClose = () => {
|
|
65
|
-
if (isDirty) {
|
|
66
|
-
onInterpretationUpdate();
|
|
67
|
-
setIsDirty(false);
|
|
68
|
-
}
|
|
69
|
-
onClose();
|
|
70
|
-
};
|
|
71
|
-
const onThreadUpdated = affectsInterpretation => {
|
|
72
|
-
if (affectsInterpretation) {
|
|
73
|
-
setIsDirty(true);
|
|
74
|
-
}
|
|
75
|
-
refetch({
|
|
76
|
-
id: interpretationId
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
const onLikeToggled = ({
|
|
80
|
-
likedBy
|
|
81
|
-
}) => {
|
|
82
|
-
setIsDirty(true);
|
|
83
|
-
interpretation.likedBy = likedBy;
|
|
84
|
-
interpretation.likes = likedBy.length;
|
|
85
|
-
};
|
|
86
|
-
const onInterpretationDeleted = () => {
|
|
87
|
-
setIsDirty(false);
|
|
88
|
-
onInterpretationUpdate();
|
|
89
|
-
onClose();
|
|
90
|
-
};
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (interpretationId) {
|
|
93
|
-
refetch({
|
|
94
|
-
id: interpretationId
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}, [interpretationId, refetch]);
|
|
98
46
|
const filters = useMemo(() => {
|
|
99
47
|
return {
|
|
100
48
|
relativePeriodDate: interpretation === null || interpretation === void 0 ? void 0 : interpretation.created
|
|
@@ -102,7 +50,7 @@ const InterpretationModal = ({
|
|
|
102
50
|
}, [interpretation === null || interpretation === void 0 ? void 0 : interpretation.created]);
|
|
103
51
|
return /*#__PURE__*/React.createElement(React.Fragment, null, loadingInProgress && /*#__PURE__*/React.createElement(Layer, null, /*#__PURE__*/React.createElement(CenteredContent, null, /*#__PURE__*/React.createElement(CircularLoader, null))), /*#__PURE__*/React.createElement(Modal, {
|
|
104
52
|
fluid: true,
|
|
105
|
-
onClose:
|
|
53
|
+
onClose: onClose,
|
|
106
54
|
className: cx(modalCSS.className, {
|
|
107
55
|
hidden: loadingInProgress
|
|
108
56
|
}),
|
|
@@ -135,24 +83,20 @@ const InterpretationModal = ({
|
|
|
135
83
|
})), /*#__PURE__*/React.createElement("div", {
|
|
136
84
|
className: _JSXStyle.dynamic([["2014146191", [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]]]) + " " + "thread-wrap"
|
|
137
85
|
}, /*#__PURE__*/React.createElement(InterpretationThread, {
|
|
138
|
-
|
|
139
|
-
fetching: fetching,
|
|
86
|
+
loading: loading,
|
|
140
87
|
interpretation: interpretation,
|
|
141
|
-
onInterpretationDeleted: onInterpretationDeleted,
|
|
142
|
-
onThreadUpdated: onThreadUpdated,
|
|
143
88
|
initialFocus: initialFocus,
|
|
144
89
|
downloadMenuComponent: downloadMenuComponent,
|
|
145
|
-
|
|
90
|
+
onInterpretationDeleted: onClose
|
|
146
91
|
}))))), /*#__PURE__*/React.createElement(ModalActions, null, /*#__PURE__*/React.createElement(Button, {
|
|
147
|
-
disabled:
|
|
148
|
-
onClick:
|
|
92
|
+
disabled: loading,
|
|
93
|
+
onClick: onClose
|
|
149
94
|
}, i18n.t('Hide interpretation'))), modalCSS.styles, modalContentCSS.styles, /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
150
95
|
id: "2014146191",
|
|
151
96
|
dynamic: [colors.grey900, spacers.dp24, spacers.dp4, spacers.dp4]
|
|
152
97
|
}, [`.title.__jsx-style-dynamic-selector{color:${colors.grey900};margin:0px;padding:${spacers.dp24} 0 ${spacers.dp4};}`, ".ellipsis.__jsx-style-dynamic-selector{display:inline-block;font-size:20px;font-weight:500;line-height:24px;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis;}", ".container.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:100%;}", ".row.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;gap:16px;height:100%;}", ".visualisation-wrap.__jsx-style-dynamic-selector{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;min-width:0;}", `.thread-wrap.__jsx-style-dynamic-selector{padding-right:${spacers.dp4};-webkit-flex-basis:300px;-ms-flex-preferred-size:300px;flex-basis:300px;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;}`])));
|
|
153
98
|
};
|
|
154
99
|
InterpretationModal.propTypes = {
|
|
155
|
-
currentUser: PropTypes.object.isRequired,
|
|
156
100
|
interpretationId: PropTypes.string.isRequired,
|
|
157
101
|
isVisualizationLoading: PropTypes.bool.isRequired,
|
|
158
102
|
pluginComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
|
|
@@ -160,7 +104,6 @@ InterpretationModal.propTypes = {
|
|
|
160
104
|
onClose: PropTypes.func.isRequired,
|
|
161
105
|
onResponsesReceived: PropTypes.func.isRequired,
|
|
162
106
|
downloadMenuComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
|
163
|
-
initialFocus: PropTypes.bool
|
|
164
|
-
onInterpretationUpdate: PropTypes.func
|
|
107
|
+
initialFocus: PropTypes.bool
|
|
165
108
|
};
|
|
166
109
|
export { InterpretationModal };
|
|
@@ -6,34 +6,33 @@ import moment from 'moment';
|
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
7
|
import React, { useRef, useEffect } from 'react';
|
|
8
8
|
import { Interpretation, getInterpretationAccess } from '../common/index.js';
|
|
9
|
+
import { useInterpretationsCurrentUser } from '../InterpretationsProvider/hooks.js';
|
|
9
10
|
import { Comment } from './Comment.js';
|
|
10
11
|
import { CommentAddForm } from './CommentAddForm.js';
|
|
11
12
|
const InterpretationThread = ({
|
|
12
|
-
|
|
13
|
-
fetching,
|
|
13
|
+
loading,
|
|
14
14
|
interpretation,
|
|
15
15
|
onInterpretationDeleted,
|
|
16
|
-
onLikeToggled,
|
|
17
16
|
initialFocus,
|
|
18
|
-
onThreadUpdated,
|
|
19
17
|
downloadMenuComponent: DownloadMenu,
|
|
20
18
|
dashboardRedirectUrl
|
|
21
19
|
}) => {
|
|
20
|
+
const currentUser = useInterpretationsCurrentUser();
|
|
22
21
|
const {
|
|
23
22
|
fromServerDate
|
|
24
23
|
} = useTimeZoneConversion();
|
|
25
24
|
const focusRef = useRef();
|
|
26
25
|
useEffect(() => {
|
|
27
26
|
if (initialFocus && focusRef.current) {
|
|
28
|
-
window.
|
|
27
|
+
window.setTimeout(() => {
|
|
29
28
|
focusRef.current.focus();
|
|
30
|
-
});
|
|
29
|
+
}, 25);
|
|
31
30
|
}
|
|
32
31
|
}, [initialFocus]);
|
|
33
32
|
const interpretationAccess = getInterpretationAccess(interpretation, currentUser);
|
|
34
33
|
return /*#__PURE__*/React.createElement("div", {
|
|
35
34
|
className: "jsx-3292109121" + " " + (cx('container', {
|
|
36
|
-
fetching,
|
|
35
|
+
fetching: loading,
|
|
37
36
|
dashboard: !!dashboardRedirectUrl
|
|
38
37
|
}) || "")
|
|
39
38
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -46,44 +45,32 @@ const InterpretationThread = ({
|
|
|
46
45
|
}), /*#__PURE__*/React.createElement("div", {
|
|
47
46
|
className: "jsx-3292109121" + " " + 'thread'
|
|
48
47
|
}, /*#__PURE__*/React.createElement(Interpretation, {
|
|
49
|
-
|
|
50
|
-
interpretation: interpretation,
|
|
51
|
-
onLikeToggled: onLikeToggled,
|
|
48
|
+
id: interpretation.id,
|
|
52
49
|
onReplyIconClick: interpretationAccess.comment ? () => {
|
|
53
50
|
var _focusRef$current;
|
|
54
51
|
return (_focusRef$current = focusRef.current) === null || _focusRef$current === void 0 ? void 0 : _focusRef$current.focus();
|
|
55
52
|
} : null,
|
|
56
|
-
onUpdated: () => onThreadUpdated(true),
|
|
57
|
-
onDeleted: onInterpretationDeleted,
|
|
58
53
|
dashboardRedirectUrl: dashboardRedirectUrl,
|
|
59
|
-
isInThread: true
|
|
54
|
+
isInThread: true,
|
|
55
|
+
onDeleted: onInterpretationDeleted
|
|
60
56
|
}), /*#__PURE__*/React.createElement("div", {
|
|
61
57
|
className: "jsx-3292109121" + " " + 'comments'
|
|
62
58
|
}, interpretation.comments.map(comment => /*#__PURE__*/React.createElement(Comment, {
|
|
63
59
|
key: comment.id,
|
|
64
60
|
comment: comment,
|
|
65
|
-
currentUser: currentUser,
|
|
66
|
-
interpretationId: interpretation.id,
|
|
67
|
-
onThreadUpdated: onThreadUpdated,
|
|
68
61
|
canComment: interpretationAccess.comment
|
|
69
62
|
})))), interpretationAccess.comment && /*#__PURE__*/React.createElement(CommentAddForm, {
|
|
70
|
-
currentUser: currentUser,
|
|
71
|
-
interpretationId: interpretation.id,
|
|
72
|
-
onSave: () => onThreadUpdated(true),
|
|
73
63
|
focusRef: focusRef
|
|
74
64
|
}), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
75
65
|
id: "3292109121"
|
|
76
66
|
}, [".thread.jsx-3292109121{margin-top:var(--spacers-dp16);overflow-y:auto;-webkit-scroll-behavior:smooth;-moz-scroll-behavior:smooth;-ms-scroll-behavior:smooth;scroll-behavior:smooth;}", ".dashboard.jsx-3292109121 .thread.jsx-3292109121{overflow-y:hidden;}", ".container.jsx-3292109121{position:relative;overflow:auto;max-height:calc(100vh - 285px);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}", ".container.dashboard.jsx-3292109121{max-height:none;}", ".container.fetching.jsx-3292109121::before{content:'';position:absolute;inset:0px;background-color:rgba(255,255,255,0.8);}", ".container.fetching.jsx-3292109121::after{content:'';position:absolute;top:calc(50% - 12px);left:calc(50% - 12px);width:24px;height:24px;border-width:4px;border-style:solid;border-color:rgba(110,122,138,0.15) rgba(110,122,138,0.15) rgb(20,124,215);border-image:initial;border-radius:50%;-webkit-animation:1s linear 0s infinite normal none running rotation-jsx-3292109121;animation:1s linear 0s infinite normal none running rotation-jsx-3292109121;}", ".title.jsx-3292109121{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:var(--spacers-dp8);color:var(--colors-grey900);font-size:14px;line-height:18px;}", ".comments.jsx-3292109121{padding-left:16px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding-top:var(--spacers-dp4);gap:var(--spacers-dp4);}", "@-webkit-keyframes rotation-jsx-3292109121{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}", "@keyframes rotation-jsx-3292109121{0%{-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);}100%{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);}}"]));
|
|
77
67
|
};
|
|
78
68
|
InterpretationThread.propTypes = {
|
|
79
|
-
currentUser: PropTypes.object.isRequired,
|
|
80
|
-
fetching: PropTypes.bool.isRequired,
|
|
81
69
|
interpretation: PropTypes.object.isRequired,
|
|
70
|
+
loading: PropTypes.bool.isRequired,
|
|
82
71
|
onInterpretationDeleted: PropTypes.func.isRequired,
|
|
83
|
-
onLikeToggled: PropTypes.func.isRequired,
|
|
84
72
|
dashboardRedirectUrl: PropTypes.string,
|
|
85
73
|
downloadMenuComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
|
86
|
-
initialFocus: PropTypes.bool
|
|
87
|
-
onThreadUpdated: PropTypes.func
|
|
74
|
+
initialFocus: PropTypes.bool
|
|
88
75
|
};
|
|
89
76
|
export { InterpretationThread };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { groupInterpretationIdsByDate } from './groupInterpretationIdsByDate.js';
|
|
2
|
+
export class InterpretationsManager {
|
|
3
|
+
constructor(dataEngine, currentUser) {
|
|
4
|
+
if (!dataEngine || !currentUser) {
|
|
5
|
+
throw new Error('Initialised InterpretationsManager without dataEngine or currentUser');
|
|
6
|
+
}
|
|
7
|
+
this.query = dataEngine.query.bind(dataEngine);
|
|
8
|
+
this.mutate = dataEngine.mutate.bind(dataEngine);
|
|
9
|
+
this.currentUser = currentUser;
|
|
10
|
+
this.currentVisualizationId = null;
|
|
11
|
+
this.currentType = null;
|
|
12
|
+
this.interpretations = new Map();
|
|
13
|
+
this.activeInterpretationId = null;
|
|
14
|
+
this.interpretationsListCallback = null;
|
|
15
|
+
this.interpretationObservers = new Map();
|
|
16
|
+
}
|
|
17
|
+
getInterpretation(id) {
|
|
18
|
+
const interpretation = this.interpretations.get(id);
|
|
19
|
+
if (!interpretation) {
|
|
20
|
+
throw new Error(`Could not get interpretation with id ${id}`);
|
|
21
|
+
}
|
|
22
|
+
return interpretation;
|
|
23
|
+
}
|
|
24
|
+
getCurrentUser() {
|
|
25
|
+
return this.currentUser;
|
|
26
|
+
}
|
|
27
|
+
getActiveInterpretation() {
|
|
28
|
+
const activeInterpretation = this.interpretations.get(this.activeInterpretationId);
|
|
29
|
+
if (!activeInterpretation) {
|
|
30
|
+
throw new Error('There currently is no active interpretation');
|
|
31
|
+
}
|
|
32
|
+
return activeInterpretation;
|
|
33
|
+
}
|
|
34
|
+
subscribeToInterpretationsListUpdates(callback) {
|
|
35
|
+
this.interpretationsListCallback = callback;
|
|
36
|
+
|
|
37
|
+
// return cleanup function for useEffect hooks
|
|
38
|
+
return () => {
|
|
39
|
+
this.interpretationsListCallback = null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
subscribeToInterpretationUpdates(id, callback) {
|
|
43
|
+
// create callback Set if needed on the fly
|
|
44
|
+
if (!this.interpretationObservers.has(id)) {
|
|
45
|
+
this.interpretationObservers.set(id, new Set());
|
|
46
|
+
}
|
|
47
|
+
this.interpretationObservers.get(id).add(callback);
|
|
48
|
+
|
|
49
|
+
// return cleanup function for useEffect hooks
|
|
50
|
+
return () => {
|
|
51
|
+
this.interpretationObservers.get(id).delete(callback);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
notifyInterpretationsListObserver() {
|
|
55
|
+
if (this.interpretationsListCallback) {
|
|
56
|
+
this.interpretationsListCallback(groupInterpretationIdsByDate(Array.from(this.interpretations.values())));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
notifyInterpretationObservers(id) {
|
|
60
|
+
const callbacks = this.interpretationObservers.get(id);
|
|
61
|
+
if (callbacks) {
|
|
62
|
+
for (const callback of callbacks) {
|
|
63
|
+
callback(this.getInterpretation(id));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
clearActiveInterpretation() {
|
|
68
|
+
this.activeInterpretationId = null;
|
|
69
|
+
}
|
|
70
|
+
clearInterpretations() {
|
|
71
|
+
this.clearActiveInterpretation();
|
|
72
|
+
this.currentVisualizationId = null;
|
|
73
|
+
this.currentType = null;
|
|
74
|
+
this.interpretations.clear();
|
|
75
|
+
}
|
|
76
|
+
resetInterpretations(newInterpretations) {
|
|
77
|
+
this.interpretations.clear();
|
|
78
|
+
for (const interpretation of newInterpretations) {
|
|
79
|
+
this.interpretations.set(interpretation.id, interpretation);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async fetchInterpretationDetails(id) {
|
|
83
|
+
const result = await this.query({
|
|
84
|
+
interpretation: {
|
|
85
|
+
resource: 'interpretations',
|
|
86
|
+
id,
|
|
87
|
+
params: {
|
|
88
|
+
fields: ['access[write,manage]', 'comments[id,text,created,createdBy[id,displayName]]', 'created', 'createdBy[id,displayName]', 'id', 'likedBy', 'likes', 'text']
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return result.interpretation;
|
|
93
|
+
}
|
|
94
|
+
async fetchInterpretationsList() {
|
|
95
|
+
if (!this.currentType || !this.currentVisualizationId) {
|
|
96
|
+
throw new Error('Called fetchInterpretationsList before currentType or currentVisualizationId was set');
|
|
97
|
+
}
|
|
98
|
+
const result = await this.query({
|
|
99
|
+
interpretations: {
|
|
100
|
+
resource: 'interpretations',
|
|
101
|
+
params: {
|
|
102
|
+
fields: ['access[write,manage]', 'comments[id]', 'created', 'createdBy[id,displayName]', 'id', 'likedBy[id]', 'likes', 'text'],
|
|
103
|
+
filter: `${this.currentType}.id:eq:${this.currentVisualizationId}`,
|
|
104
|
+
paging: false
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return result.interpretations.interpretations;
|
|
109
|
+
}
|
|
110
|
+
async loadActiveInterpretation(id) {
|
|
111
|
+
const interpretation = await this.fetchInterpretationDetails(id);
|
|
112
|
+
this.interpretations.set(id, interpretation);
|
|
113
|
+
this.activeInterpretationId = id;
|
|
114
|
+
return this.getActiveInterpretation();
|
|
115
|
+
}
|
|
116
|
+
async loadInterpretationsForVisualization(type, id) {
|
|
117
|
+
this.currentType = type;
|
|
118
|
+
this.currentVisualizationId = id;
|
|
119
|
+
const interpretations = await this.fetchInterpretationsList();
|
|
120
|
+
this.resetInterpretations(interpretations);
|
|
121
|
+
return groupInterpretationIdsByDate(interpretations);
|
|
122
|
+
}
|
|
123
|
+
async createInterpretation({
|
|
124
|
+
type,
|
|
125
|
+
id,
|
|
126
|
+
text,
|
|
127
|
+
onComplete
|
|
128
|
+
}) {
|
|
129
|
+
await this.mutate({
|
|
130
|
+
resource: `interpretations/${type}/${id}`,
|
|
131
|
+
type: 'create',
|
|
132
|
+
data: text
|
|
133
|
+
}, {
|
|
134
|
+
onComplete
|
|
135
|
+
});
|
|
136
|
+
/* since the create request does not return an ID we must refetch the list
|
|
137
|
+
* and cannot return the created interpretation */
|
|
138
|
+
const interpretations = await this.fetchInterpretationsList();
|
|
139
|
+
this.resetInterpretations(interpretations);
|
|
140
|
+
this.notifyInterpretationsListObserver();
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
async deleteInterpretation({
|
|
144
|
+
id,
|
|
145
|
+
onComplete,
|
|
146
|
+
onError
|
|
147
|
+
}) {
|
|
148
|
+
await this.mutate({
|
|
149
|
+
resource: 'interpretations',
|
|
150
|
+
id,
|
|
151
|
+
type: 'delete'
|
|
152
|
+
}, {
|
|
153
|
+
onComplete,
|
|
154
|
+
onError
|
|
155
|
+
});
|
|
156
|
+
// This happens when deleting the interpretation from the modal
|
|
157
|
+
if (this.activeInterpretationId && id === this.activeInterpretationId) {
|
|
158
|
+
this.clearActiveInterpretation();
|
|
159
|
+
}
|
|
160
|
+
this.interpretations.delete(id);
|
|
161
|
+
this.notifyInterpretationsListObserver();
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
async updateInterpretationText({
|
|
165
|
+
id,
|
|
166
|
+
text,
|
|
167
|
+
onComplete,
|
|
168
|
+
onError
|
|
169
|
+
}) {
|
|
170
|
+
await this.mutate({
|
|
171
|
+
resource: 'interpretations',
|
|
172
|
+
type: 'update',
|
|
173
|
+
partial: false,
|
|
174
|
+
id,
|
|
175
|
+
data: text
|
|
176
|
+
}, {
|
|
177
|
+
onComplete,
|
|
178
|
+
onError
|
|
179
|
+
});
|
|
180
|
+
const updatedInterpretation = {
|
|
181
|
+
...this.getInterpretation(id),
|
|
182
|
+
text
|
|
183
|
+
};
|
|
184
|
+
this.interpretations.set(id, updatedInterpretation);
|
|
185
|
+
this.notifyInterpretationObservers(id);
|
|
186
|
+
return updatedInterpretation;
|
|
187
|
+
}
|
|
188
|
+
async toggleInterpretationLike(id) {
|
|
189
|
+
const interpretation = this.getInterpretation(id);
|
|
190
|
+
const wasLikedByCurrentUser = interpretation.likedBy.some(likedBy => likedBy.id === this.currentUser.id);
|
|
191
|
+
await this.mutate({
|
|
192
|
+
resource: `interpretations/${id}/like`,
|
|
193
|
+
type: wasLikedByCurrentUser ? 'delete' : 'create'
|
|
194
|
+
});
|
|
195
|
+
const isLikedByCurrentUser = !wasLikedByCurrentUser;
|
|
196
|
+
const updatedInterpretation = {
|
|
197
|
+
...interpretation,
|
|
198
|
+
likedBy: isLikedByCurrentUser ? interpretation.likedBy.concat({
|
|
199
|
+
id: this.currentUser.id
|
|
200
|
+
}) : interpretation.likedBy.filter(lb => lb.id !== this.currentUser.id),
|
|
201
|
+
likes: isLikedByCurrentUser ? interpretation.likes + 1 : interpretation.likes - 1
|
|
202
|
+
};
|
|
203
|
+
this.interpretations.set(id, updatedInterpretation);
|
|
204
|
+
this.notifyInterpretationObservers(id);
|
|
205
|
+
return updatedInterpretation;
|
|
206
|
+
}
|
|
207
|
+
async addCommentToActiveInterpretation({
|
|
208
|
+
text,
|
|
209
|
+
onComplete
|
|
210
|
+
}) {
|
|
211
|
+
const {
|
|
212
|
+
id
|
|
213
|
+
} = this.getActiveInterpretation();
|
|
214
|
+
await this.mutate({
|
|
215
|
+
resource: `interpretations/${id}/comments`,
|
|
216
|
+
type: 'create',
|
|
217
|
+
data: text
|
|
218
|
+
}, {
|
|
219
|
+
onComplete
|
|
220
|
+
});
|
|
221
|
+
const interpretation = await this.fetchInterpretationDetails(id);
|
|
222
|
+
this.interpretations.set(id, interpretation);
|
|
223
|
+
this.notifyInterpretationObservers(id);
|
|
224
|
+
return interpretation;
|
|
225
|
+
}
|
|
226
|
+
async deleteCommentFromActiveInterpretation({
|
|
227
|
+
id
|
|
228
|
+
}) {
|
|
229
|
+
const activeInterpretation = this.getActiveInterpretation();
|
|
230
|
+
await this.mutate({
|
|
231
|
+
resource: `interpretations/${activeInterpretation.id}/comments/${id}`,
|
|
232
|
+
type: 'delete'
|
|
233
|
+
});
|
|
234
|
+
const updatedInterpretation = {
|
|
235
|
+
...activeInterpretation,
|
|
236
|
+
comments: activeInterpretation.comments.filter(({
|
|
237
|
+
id: commentId
|
|
238
|
+
}) => commentId !== id)
|
|
239
|
+
};
|
|
240
|
+
this.interpretations.set(activeInterpretation.id, updatedInterpretation);
|
|
241
|
+
this.notifyInterpretationObservers(activeInterpretation.id);
|
|
242
|
+
return updatedInterpretation;
|
|
243
|
+
}
|
|
244
|
+
async updateCommentForActiveInterpretation({
|
|
245
|
+
id,
|
|
246
|
+
text,
|
|
247
|
+
onComplete
|
|
248
|
+
}) {
|
|
249
|
+
const activeInterpretation = this.getActiveInterpretation();
|
|
250
|
+
this.mutate({
|
|
251
|
+
resource: `interpretations/${activeInterpretation.id}/comments/${id}`,
|
|
252
|
+
type: 'update',
|
|
253
|
+
partial: false,
|
|
254
|
+
data: text
|
|
255
|
+
}, {
|
|
256
|
+
onComplete: () => onComplete(text)
|
|
257
|
+
});
|
|
258
|
+
const updatedInterpretation = {
|
|
259
|
+
...activeInterpretation,
|
|
260
|
+
comments: activeInterpretation.comments.map(comment => comment.id === id ? {
|
|
261
|
+
...comment,
|
|
262
|
+
text
|
|
263
|
+
} : comment)
|
|
264
|
+
};
|
|
265
|
+
this.interpretations.set(activeInterpretation.id, updatedInterpretation);
|
|
266
|
+
return updatedInterpretation;
|
|
267
|
+
}
|
|
268
|
+
}
|
package/build/es/components/Interpretations/InterpretationsProvider/InterpretationsProvider.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useDataEngine } from '@dhis2/app-runtime';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import React, { createContext, useState } from 'react';
|
|
4
|
+
import { InterpretationsManager } from './InterpretationsManager.js';
|
|
5
|
+
export const InterpretationsContext = /*#__PURE__*/createContext(null);
|
|
6
|
+
export const InterpretationsProvider = ({
|
|
7
|
+
currentUser,
|
|
8
|
+
children
|
|
9
|
+
}) => {
|
|
10
|
+
const dataEngine = useDataEngine();
|
|
11
|
+
const [interpretationsManager] = useState(() => new InterpretationsManager(dataEngine, currentUser));
|
|
12
|
+
return /*#__PURE__*/React.createElement(InterpretationsContext.Provider, {
|
|
13
|
+
value: interpretationsManager
|
|
14
|
+
}, children);
|
|
15
|
+
};
|
|
16
|
+
InterpretationsProvider.propTypes = {
|
|
17
|
+
children: PropTypes.node,
|
|
18
|
+
currentUser: PropTypes.object
|
|
19
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { groupInterpretationIdsByDate } from '../groupInterpretationIdsByDate.js';
|
|
2
|
+
describe('groupInterpretationIdsByDate', () => {
|
|
3
|
+
it('should group interpretations by date and sort them correctly', () => {
|
|
4
|
+
const interpretations = [{
|
|
5
|
+
id: 'id1',
|
|
6
|
+
created: '2025-09-04T07:47:12.477'
|
|
7
|
+
}, {
|
|
8
|
+
id: 'id2',
|
|
9
|
+
created: '2025-09-04T15:30:45.123'
|
|
10
|
+
}, {
|
|
11
|
+
id: 'id3',
|
|
12
|
+
created: '2025-09-03T10:20:30.456'
|
|
13
|
+
}, {
|
|
14
|
+
id: 'id4',
|
|
15
|
+
created: '2025-09-05T09:15:22.789'
|
|
16
|
+
}, {
|
|
17
|
+
id: 'id5',
|
|
18
|
+
created: '2025-09-03T18:45:15.012'
|
|
19
|
+
}];
|
|
20
|
+
const result = groupInterpretationIdsByDate(interpretations);
|
|
21
|
+
|
|
22
|
+
// Check that dates are grouped correctly
|
|
23
|
+
expect(Object.keys(result)).toEqual(['2025-09-05', '2025-09-04', '2025-09-03']);
|
|
24
|
+
|
|
25
|
+
// Check that within each date group, items are sorted from most recent to oldest
|
|
26
|
+
expect(result['2025-09-04']).toEqual(['id2', 'id1']); // 15:30 before 07:47
|
|
27
|
+
expect(result['2025-09-03']).toEqual(['id5', 'id3']); // 18:45 before 10:20
|
|
28
|
+
expect(result['2025-09-05']).toEqual(['id4']);
|
|
29
|
+
});
|
|
30
|
+
it('should handle empty array', () => {
|
|
31
|
+
const interpretations = [];
|
|
32
|
+
const result = groupInterpretationIdsByDate(interpretations);
|
|
33
|
+
expect(result).toEqual({});
|
|
34
|
+
});
|
|
35
|
+
});
|