@hisptz/dhis2-analytics 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +45 -0
  2. package/d2.config.js +8 -0
  3. package/package.json +55 -0
  4. package/src/components/ChartAnalytics/ChartAnalytics.stories.tsx +250 -0
  5. package/src/components/ChartAnalytics/ChartAnalytics.test.tsx +51 -0
  6. package/src/components/ChartAnalytics/components/DownloadMenu/components/Menu.tsx +48 -0
  7. package/src/components/ChartAnalytics/components/DownloadMenu/constants/menu.ts +38 -0
  8. package/src/components/ChartAnalytics/components/DownloadMenu/index.tsx +67 -0
  9. package/src/components/ChartAnalytics/components/DownloadMenu/interfaces/menu.ts +1 -0
  10. package/src/components/ChartAnalytics/data/column-data.json +210 -0
  11. package/src/components/ChartAnalytics/data/complex-multi-series-data.json +124 -0
  12. package/src/components/ChartAnalytics/data/multi-series-data.json +536 -0
  13. package/src/components/ChartAnalytics/data/pie-data.json +115 -0
  14. package/src/components/ChartAnalytics/data/stacked-chart-data.json +415 -0
  15. package/src/components/ChartAnalytics/hooks/useChart.ts +35 -0
  16. package/src/components/ChartAnalytics/index.tsx +23 -0
  17. package/src/components/ChartAnalytics/models/column.ts +50 -0
  18. package/src/components/ChartAnalytics/models/index.ts +78 -0
  19. package/src/components/ChartAnalytics/models/line.ts +31 -0
  20. package/src/components/ChartAnalytics/models/multi-series.ts +115 -0
  21. package/src/components/ChartAnalytics/models/pie.ts +54 -0
  22. package/src/components/ChartAnalytics/services/export.ts +38 -0
  23. package/src/components/ChartAnalytics/styles/custom-highchart.css +48 -0
  24. package/src/components/ChartAnalytics/types/props.tsx +48 -0
  25. package/src/components/ChartAnalytics/utils/chart.ts +123 -0
  26. package/src/components/CircularProgressDashboard/CircularProgressIndicator.stories.tsx +41 -0
  27. package/src/components/CircularProgressDashboard/CircularProgressIndicator.test.tsx +9 -0
  28. package/src/components/CircularProgressDashboard/index.tsx +35 -0
  29. package/src/components/CircularProgressDashboard/types/props.tsx +17 -0
  30. package/src/components/Map/Map.stories.tsx +339 -0
  31. package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfigModal.stories.tsx +28 -0
  32. package/src/components/Map/components/EarthEngineLayerConfiguration/EarthEngineLayerConfiguration.stories.tsx +34 -0
  33. package/src/components/Map/components/EarthEngineLayerConfiguration/index.tsx +412 -0
  34. package/src/components/Map/components/MapArea/index.tsx +83 -0
  35. package/src/components/Map/components/MapArea/interfaces/index.ts +39 -0
  36. package/src/components/Map/components/MapControls/components/CustomControl/index.tsx +24 -0
  37. package/src/components/Map/components/MapControls/components/DownloadControl/index.tsx +10 -0
  38. package/src/components/Map/components/MapControls/components/FullscreenControl/index.tsx +7 -0
  39. package/src/components/Map/components/MapControls/index.tsx +24 -0
  40. package/src/components/Map/components/MapLayer/components/BoundaryLayer/hooks/useBoundaryData.ts +7 -0
  41. package/src/components/Map/components/MapLayer/components/BoundaryLayer/index.tsx +55 -0
  42. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/components/EarthEngineLegend.tsx +76 -0
  43. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/constants/index.ts +430 -0
  44. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/hooks/index.ts +34 -0
  45. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/index.tsx +185 -0
  46. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/interfaces/index.ts +56 -0
  47. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/api.js +34233 -0
  48. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/services/engine.ts +423 -0
  49. package/src/components/Map/components/MapLayer/components/GoogleEngineLayer/utils/index.ts +105 -0
  50. package/src/components/Map/components/MapLayer/components/LegendArea/LegendArea.module.css +12 -0
  51. package/src/components/Map/components/MapLayer/components/LegendArea/components/LegendCardHeader/index.tsx +17 -0
  52. package/src/components/Map/components/MapLayer/components/LegendArea/index.tsx +168 -0
  53. package/src/components/Map/components/MapLayer/components/PointLayer/components/PointLegend/index.tsx +44 -0
  54. package/src/components/Map/components/MapLayer/components/PointLayer/hooks/index.ts +8 -0
  55. package/src/components/Map/components/MapLayer/components/PointLayer/index.tsx +36 -0
  56. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubble.tsx +48 -0
  57. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/components/Bubbles.tsx +150 -0
  58. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/components/BubbleLegend/index.tsx +39 -0
  59. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Bubble/index.tsx +57 -0
  60. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/components/ChoroplethLegend.tsx +43 -0
  61. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/Choropleth/index.tsx +38 -0
  62. package/src/components/Map/components/MapLayer/components/ThematicLayer/components/CustomTooltip/index.tsx +26 -0
  63. package/src/components/Map/components/MapLayer/components/ThematicLayer/hooks/config.ts +10 -0
  64. package/src/components/Map/components/MapLayer/components/ThematicLayer/index.tsx +46 -0
  65. package/src/components/Map/components/MapLayer/components/ThematicLayer/styles/legends.css +62 -0
  66. package/src/components/Map/components/MapLayer/index.tsx +32 -0
  67. package/src/components/Map/components/MapLayer/interfaces/index.ts +139 -0
  68. package/src/components/Map/components/MapProvider/components/MapLayerProvider/hooks/index.tsx +359 -0
  69. package/src/components/Map/components/MapProvider/components/MapLayerProvider/index.tsx +105 -0
  70. package/src/components/Map/components/MapProvider/hooks/index.ts +14 -0
  71. package/src/components/Map/components/MapProvider/index.tsx +93 -0
  72. package/src/components/Map/components/MapUpdater/index.tsx +8 -0
  73. package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfigModal.stories.tsx +28 -0
  74. package/src/components/Map/components/ThematicLayerConfiguration/ThematicLayerConfiguration.stories.tsx +34 -0
  75. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/components/ColorScale/index.tsx +24 -0
  76. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/constants/colors.ts +433 -0
  77. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/index.tsx +50 -0
  78. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScale.module.css +15 -0
  79. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/styles/ColorScaleSelect.module.css +12 -0
  80. package/src/components/Map/components/ThematicLayerConfiguration/components/ColorScaleSelect/utils/colors.ts +91 -0
  81. package/src/components/Map/components/ThematicLayerConfiguration/components/CustomLegend/index.tsx +45 -0
  82. package/src/components/Map/components/ThematicLayerConfiguration/components/IndicatorSelectorModal/index.tsx +47 -0
  83. package/src/components/Map/components/ThematicLayerConfiguration/components/LegendSetSelector/index.tsx +57 -0
  84. package/src/components/Map/components/ThematicLayerConfiguration/index.tsx +248 -0
  85. package/src/components/Map/constants/colors.ts +433 -0
  86. package/src/components/Map/constants/legendSet.ts +19 -0
  87. package/src/components/Map/hooks/map.ts +47 -0
  88. package/src/components/Map/index.tsx +65 -0
  89. package/src/components/Map/interfaces/index.ts +57 -0
  90. package/src/components/Map/state/index.tsx +31 -0
  91. package/src/components/Map/utils/colors.ts +95 -0
  92. package/src/components/Map/utils/helpers.ts +15 -0
  93. package/src/components/Map/utils/map.ts +150 -0
  94. package/src/components/SingleValueContainer/SingleValueContainer.stories.tsx +146 -0
  95. package/src/components/SingleValueContainer/SingleValueContainer.test.tsx +24 -0
  96. package/src/components/SingleValueContainer/components/SingleValueItem/SingleValueItem.tsx +46 -0
  97. package/src/components/SingleValueContainer/components/SingleValueItem/SingleValuePercentage.tsx +12 -0
  98. package/src/components/SingleValueContainer/index.tsx +30 -0
  99. package/src/components/SingleValueContainer/styles/SingleValueContainer.module.css +39 -0
  100. package/src/components/SingleValueContainer/types/props.tsx +16 -0
  101. package/src/data/map.json +5984 -0
  102. package/src/dataProviders/map.tsx +24 -0
  103. package/src/index.test.ts +7 -0
  104. package/src/index.ts +4 -0
  105. package/tsconfig.json +45 -0
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ This project was bootstrapped with [DHIS2 Application Platform](https://github.com/dhis2/app-platform).
2
+
3
+ ## Available Scripts
4
+
5
+ In the project directory, you can run:
6
+
7
+ ### `yarn start`
8
+
9
+ Runs the app in the development mode.<br />
10
+ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11
+
12
+ The page will reload if you make edits.<br />
13
+ You will also see any lint errors in the console.
14
+
15
+ ### `yarn test`
16
+
17
+ Launches the test runner and runs all available tests found in `/src`.<br />
18
+
19
+ See the section about [running tests](https://platform.dhis2.nu/#/scripts/test) for more information.
20
+
21
+ ### `yarn build`
22
+
23
+ Builds the app for production to the `build` folder.<br />
24
+ It correctly bundles React in production mode and optimizes the build for the best performance.
25
+
26
+ The build is minified and the filenames include the hashes.<br />
27
+ A deployable `.zip` file can be found in `build/bundle`!
28
+
29
+ See the section about [building](https://platform.dhis2.nu/#/scripts/build) for more information.
30
+
31
+ ### `yarn deploy`
32
+
33
+ Deploys the built app in the `build` folder to a running DHIS2 instance.<br />
34
+ This command will prompt you to enter a server URL as well as the username and password of a DHIS2 user with the App Management authority.<br/>
35
+ You must run `yarn build` before running `yarn deploy`.<br />
36
+
37
+ See the section about [deploying](https://platform.dhis2.nu/#/scripts/deploy) for more information.
38
+
39
+ ## Learn More
40
+
41
+ You can learn more about the platform in the [DHIS2 Application Platform Documentation](https://platform.dhis2.nu/).
42
+
43
+ You can learn more about the runtime in the [DHIS2 Application Runtime Documentation](https://runtime.dhis2.nu/).
44
+
45
+ To learn React, check out the [React documentation](https://reactjs.org/).
package/d2.config.js ADDED
@@ -0,0 +1,8 @@
1
+ const config = {
2
+ type: 'lib',
3
+ entryPoints: {
4
+ lib: './src/index.ts',
5
+ },
6
+ }
7
+
8
+ module.exports = config
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@hisptz/dhis2-analytics",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "BSD-3-Clause",
6
+ "scripts": {
7
+ "build": "d2-app-scripts build --no-verify",
8
+ "start": "d2-app-scripts start",
9
+ "test": "d2-app-scripts test --passWithNoTests",
10
+ "deploy": "d2-app-scripts deploy",
11
+ "publish:analytics": "yarn publish --access public"
12
+ },
13
+ "devDependencies": {
14
+ "@cypress/react": "^7.0.2",
15
+ "@dhis2/cli-app-scripts": "^10.2.0",
16
+ "@dhis2/cypress-commands": "^9.0.2",
17
+ "@dhis2/cypress-plugins": "^9.0.2",
18
+ "@google/earthengine": "^0.1.336",
19
+ "@types/d3-scale": "^4.0.3",
20
+ "@types/leaflet": "^1.9.0",
21
+ "@types/lodash": "^4.14.191",
22
+ "@types/react-helmet": "^6.1.6",
23
+ "cypress": "^12.3.0",
24
+ "lodash": "^4.17.21",
25
+ "react-hook-form": "^7.42.1"
26
+ },
27
+ "dependencies": {
28
+ "@dhis2/app-runtime": "^3.7.0",
29
+ "@hisptz/dhis2-ui": "*",
30
+ "async-es": "^3.2.4",
31
+ "d3-color": "^3.1.0",
32
+ "d3-scale": "^4.0.2",
33
+ "highcharts": "^10.3.2",
34
+ "highcharts-react-official": "^3.1.0",
35
+ "leaflet": "^1.9.3",
36
+ "leaflet-easyprint": "^2.1.9",
37
+ "leaflet.fullscreen": "^2.4.0",
38
+ "react-circular-progressbar": "^2.1.0",
39
+ "react-helmet": "^6.1.0",
40
+ "react-leaflet": "^4.2.0",
41
+ "react-leaflet-custom-control": "^1.3.1",
42
+ "react-spring": "^9.6.1",
43
+ "screenfull": "^6.0.2"
44
+ },
45
+ "main": "./build/cjs/index.js",
46
+ "module": "./build/es/index.js",
47
+ "exports": {
48
+ "import": "./build/es/index.js",
49
+ "require": "./build/cjs/index.js"
50
+ },
51
+ "peerDependencies": {
52
+ "lodash": "^4",
53
+ "react-hook-form": "^7"
54
+ }
55
+ }
@@ -0,0 +1,250 @@
1
+ import {CssReset} from "@dhis2/ui";
2
+ import type {Story} from "@storybook/react";
3
+ import HighCharts from "highcharts";
4
+ import HighchartsReact from "highcharts-react-official";
5
+ import React, {useState} from "react";
6
+ import ChartDownloadMenu from "./components/DownloadMenu";
7
+ import columnData from "./data/column-data.json";
8
+ import complexMultiSeriesData from "./data/complex-multi-series-data.json";
9
+ import multiSeriesData from "./data/multi-series-data.json";
10
+ import pieData from "./data/pie-data.json";
11
+ import stackedChartData from "./data/stacked-chart-data.json";
12
+ import {setupHighchartsModules} from "./services/export";
13
+ import {ChartAnalyticsProps} from "./types/props";
14
+ import ChartAnalytics from ".";
15
+
16
+ const Template: Story<ChartAnalyticsProps> = (args) => <ChartAnalytics {...args} />;
17
+ setupHighchartsModules(HighCharts);
18
+
19
+ export const Column = Template.bind({});
20
+ Column.args = {
21
+ analytics: columnData as any,
22
+ config: {
23
+ layout: {
24
+ series: ["dx"],
25
+ category: ["ou"],
26
+ filter: ["pe"],
27
+ },
28
+ type: "column",
29
+ height: 500,
30
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
31
+ },
32
+ };
33
+
34
+ export const MultipleColumns = Template.bind({});
35
+ MultipleColumns.args = {
36
+ analytics: multiSeriesData as any,
37
+ config: {
38
+ layout: {
39
+ series: ["ou"],
40
+ category: ["pe"],
41
+ filter: ["dx"],
42
+ },
43
+ type: "column",
44
+ height: 1000,
45
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
46
+ },
47
+ };
48
+
49
+ export const StackedColumn = Template.bind({});
50
+ StackedColumn.args = {
51
+ analytics: stackedChartData as any,
52
+ config: {
53
+ layout: {
54
+ series: ["ou"],
55
+ category: ["pe"],
56
+ filter: ["dx"],
57
+ },
58
+ type: "stacked-column",
59
+ height: 1000,
60
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
61
+ },
62
+ };
63
+
64
+ export const Line = Template.bind({});
65
+ Line.args = {
66
+ analytics: columnData as any,
67
+ config: {
68
+ layout: {
69
+ series: ["dx"],
70
+ category: ["ou"],
71
+ filter: ["pe"],
72
+ },
73
+ type: "line",
74
+ height: 1000,
75
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
76
+ },
77
+ };
78
+
79
+ export const MultipleLines = Template.bind({});
80
+ MultipleLines.args = {
81
+ analytics: multiSeriesData as any,
82
+ config: {
83
+ layout: {
84
+ series: ["ou"],
85
+ category: ["pe"],
86
+ filter: ["dx"],
87
+ },
88
+ type: "line",
89
+ height: 500,
90
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
91
+ },
92
+ };
93
+
94
+ export const PieChart = Template.bind({});
95
+ PieChart.args = {
96
+ analytics: pieData as any,
97
+ config: {
98
+ layout: {
99
+ series: ["dx"],
100
+ category: [],
101
+ filter: ["dx", "pe"],
102
+ },
103
+ type: "pie",
104
+ height: 500,
105
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c4255", "#a6c96a"],
106
+ },
107
+ };
108
+
109
+ export const MultiSeries = Template.bind({});
110
+ MultiSeries.args = {
111
+ analytics: multiSeriesData as any,
112
+ config: {
113
+ layout: {
114
+ series: ["ou"],
115
+ category: ["pe"],
116
+ filter: ["dx"],
117
+ },
118
+ type: "multi-series",
119
+ height: 500,
120
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
121
+ multiSeries: {
122
+ series: [
123
+ {
124
+ id: "qhqAxPSTUXp",
125
+ as: "column",
126
+ yAxis: 0,
127
+ },
128
+ {
129
+ id: "Vth0fbpFcsO",
130
+ as: "line",
131
+ cumulative: true,
132
+ yAxis: 1,
133
+ },
134
+ ],
135
+ yAxes: [
136
+ {
137
+ id: "yAxis1",
138
+ title: {
139
+ text: "Koinandugu",
140
+ },
141
+ labels: {
142
+ format: "{value}",
143
+ },
144
+ },
145
+ {
146
+ id: "yAxis2",
147
+ title: {
148
+ text: "Kono",
149
+ },
150
+ labels: {
151
+ format: "{value}",
152
+ },
153
+ opposite: true,
154
+ },
155
+ ],
156
+ target: {
157
+ id: "",
158
+ styles: {
159
+ color: "blue",
160
+ },
161
+ value: 45,
162
+ label: {
163
+ text: "Target",
164
+ textAlign: "center",
165
+ verticalAlign: "middle",
166
+ },
167
+ },
168
+ },
169
+ },
170
+ };
171
+
172
+ export const ComplexMultiSeries = Template.bind({});
173
+ ComplexMultiSeries.args = {
174
+ analytics: complexMultiSeriesData as any,
175
+ config: {
176
+ layout: {
177
+ series: ["dx"],
178
+ category: ["pe"],
179
+ filter: ["ou"],
180
+ },
181
+ type: "multi-series",
182
+ height: 500,
183
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
184
+ multiSeries: {
185
+ series: [
186
+ {
187
+ id: "QQkOAJFukyY",
188
+ as: "column",
189
+ yAxis: 0,
190
+ },
191
+ {
192
+ id: "QQkOAJFukyY",
193
+ as: "line",
194
+ cumulative: true,
195
+ yAxis: 1,
196
+ },
197
+ ],
198
+ yAxes: [
199
+ {
200
+ id: "yAxis1",
201
+ title: {
202
+ text: "Koinandugu",
203
+ },
204
+ labels: {
205
+ format: "{value}",
206
+ },
207
+ },
208
+ {
209
+ id: "yAxis2",
210
+ title: {
211
+ text: "Kono",
212
+ },
213
+ labels: {
214
+ format: "{value}",
215
+ },
216
+ opposite: true,
217
+ },
218
+ ],
219
+ target: {
220
+ id: "",
221
+ styles: {
222
+ color: "blue",
223
+ },
224
+ value: 45,
225
+ label: {
226
+ text: "Target",
227
+ textAlign: "center",
228
+ verticalAlign: "middle",
229
+ },
230
+ },
231
+ },
232
+ },
233
+ };
234
+
235
+ export default {
236
+ title: "Components/Chart Analytics",
237
+ component: ChartAnalytics,
238
+ decorators: [
239
+ (ChartStory: any) => {
240
+ const [chartRef, setChartRef] = useState<HighchartsReact.RefObject | null>(null);
241
+ return (
242
+ <div style={{ width: 1000, height: "100%", display: "flex", gap: 8, flexDirection: "column" }}>
243
+ <CssReset />
244
+ <div style={{ width: "100%", display: "flex", justifyContent: "end" }}>{<ChartDownloadMenu chartRef={chartRef} />}</div>
245
+ <ChartStory ref={setChartRef} />
246
+ </div>
247
+ );
248
+ },
249
+ ],
250
+ };
@@ -0,0 +1,51 @@
1
+ import {mount} from "@cypress/react";
2
+ import HighCharts from "highcharts";
3
+ import HighchartsReact from "highcharts-react-official";
4
+ import React, {useState} from "react";
5
+ import ChartDownloadMenu from "./components/DownloadMenu";
6
+ import columnData from "./data/column-data.json";
7
+ import {setupHighchartsModules} from "./services/export";
8
+ import ChartAnalytics from ".";
9
+
10
+ const props: any = {
11
+ analytics: columnData as any,
12
+ config: {
13
+ layout: {
14
+ series: ["dx"],
15
+ category: ["ou"],
16
+ filter: ["pe"],
17
+ },
18
+ type: "column",
19
+ height: 500,
20
+ colors: ["#2f7ed8", "#0d233a", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"],
21
+ },
22
+ };
23
+
24
+ function TestComponent() {
25
+ return <ChartAnalytics {...props} />;
26
+ }
27
+
28
+ function ExportTestComponent() {
29
+ const [chartRef, setChartRef] = useState<HighchartsReact.RefObject | null>(null);
30
+
31
+ return (
32
+ <div style={{ display: "flex", flexDirection: "column" }}>
33
+ <div>
34
+ <ChartDownloadMenu chartRef={chartRef} />
35
+ </div>
36
+ <ChartAnalytics {...props} ref={setChartRef} />
37
+ </div>
38
+ );
39
+ }
40
+
41
+ describe("Chart Component Tests", () => {
42
+ setupHighchartsModules(HighCharts);
43
+
44
+ it("Mounts without errors", () => {
45
+ mount(<TestComponent />);
46
+ });
47
+
48
+ it("Can be exported as PDF", () => {
49
+ mount(<ExportTestComponent />);
50
+ });
51
+ });
@@ -0,0 +1,48 @@
1
+ import {FlyoutMenu, Menu, MenuDivider, MenuItem, Popover} from "@dhis2/ui";
2
+ import {isEmpty, map} from "lodash";
3
+ import React from "react";
4
+ import {chartMenuSections} from "../constants/menu";
5
+ import {ChartExportMenuItem} from "../interfaces/menu";
6
+
7
+ export function ChartMenu({
8
+ menuRef,
9
+ onClick,
10
+ onClose,
11
+ exclude = [],
12
+ }: {
13
+ menuRef: HTMLButtonElement;
14
+ onClick: (action: string) => void;
15
+ onClose: () => void;
16
+ exclude?: ChartExportMenuItem[];
17
+ }) {
18
+ const onMenuClick = (action: string) => () => {
19
+ onClick(action);
20
+ onClose();
21
+ };
22
+
23
+ const menuSections = map(chartMenuSections, (section) => {
24
+ return {
25
+ ...section,
26
+ menuItems: section.menuItems.filter((item: any) => !exclude.includes(item.name)),
27
+ };
28
+ }).filter((section) => !isEmpty(section.menuItems));
29
+
30
+ return (
31
+ <Popover onClickOutside={onClose} onClose={onClose} reference={menuRef}>
32
+ <FlyoutMenu>
33
+ <Menu>
34
+ {menuSections?.map((section, index) => {
35
+ return (
36
+ <>
37
+ {section.menuItems?.map(({ name, label }) => {
38
+ return <MenuItem key={`${name}-menu-item`} dataTest={`download-${name}`} label={label} onClick={onMenuClick(name)} />;
39
+ })}
40
+ {index !== menuSections.length - 1 && <MenuDivider />}
41
+ </>
42
+ );
43
+ })}
44
+ </Menu>
45
+ </FlyoutMenu>
46
+ </Popover>
47
+ );
48
+ }
@@ -0,0 +1,38 @@
1
+ import i18n from "@dhis2/d2-i18n";
2
+
3
+ export const chartMenuSections = [
4
+ {
5
+ name: "download",
6
+ menuItems: [
7
+ {
8
+ name: "png",
9
+ label: i18n.t("Download PNG"),
10
+ },
11
+ {
12
+ name: "jpeg",
13
+ label: i18n.t("Download JPEG"),
14
+ },
15
+ {
16
+ name: "svg",
17
+ label: i18n.t("Download SVG"),
18
+ },
19
+ {
20
+ name: "pdf",
21
+ label: i18n.t("Download PDF"),
22
+ },
23
+ ],
24
+ },
25
+ {
26
+ name: "view",
27
+ menuItems: [
28
+ {
29
+ name: "table",
30
+ label: i18n.t("View as table"),
31
+ },
32
+ {
33
+ name: "full-screen",
34
+ label: i18n.t("View full screen"),
35
+ },
36
+ ],
37
+ },
38
+ ];
@@ -0,0 +1,67 @@
1
+ import {IconMore24} from "@dhis2/ui";
2
+ import HighchartsReact from "highcharts-react-official";
3
+ import React, {useRef, useState} from "react";
4
+ import {onCSVDownload, onFullScreenView, onImageDownload, onPDFDownload, onViewAsTable} from "../../services/export";
5
+ import {ChartMenu} from "./components/Menu";
6
+ import {ChartExportMenuItem} from "./interfaces/menu";
7
+
8
+ function ChartDownloadMenu({
9
+ chartRef,
10
+ exclude,
11
+ icon,
12
+ }: {
13
+ chartRef: HighchartsReact.RefObject | null;
14
+ exclude?: ChartExportMenuItem[];
15
+ icon?: React.ReactNode;
16
+ }) {
17
+ const menuButtonRef = useRef<HTMLButtonElement | null>(null);
18
+ const [menuRef, setMenuRef] = useState<HTMLButtonElement | null>(null);
19
+ const toggleMenu = () => {
20
+ if (menuRef === null) {
21
+ setMenuRef(menuButtonRef.current);
22
+ } else {
23
+ setMenuRef(null);
24
+ }
25
+ };
26
+
27
+ const chart = chartRef;
28
+
29
+ const onMenuClick = (action: string) => {
30
+ if (chart) {
31
+ switch (action) {
32
+ case "png":
33
+ onImageDownload(chart, "png");
34
+ break;
35
+ case "jpeg":
36
+ onImageDownload(chart, "jpeg");
37
+ break;
38
+ case "svg":
39
+ onImageDownload(chart, "svg+xml");
40
+ break;
41
+ case "csv":
42
+ onCSVDownload(chart);
43
+ break;
44
+ case "pdf":
45
+ onPDFDownload(chart);
46
+ break;
47
+ case "table":
48
+ onViewAsTable(chart, true);
49
+ break;
50
+ case "full-screen":
51
+ onFullScreenView(chart);
52
+ break;
53
+ }
54
+ }
55
+ };
56
+
57
+ return (
58
+ <>
59
+ <div onClick={toggleMenu} ref={menuButtonRef}>
60
+ {icon ?? <IconMore24 />}
61
+ </div>
62
+ {menuRef && <ChartMenu exclude={exclude} onClick={onMenuClick} onClose={toggleMenu} menuRef={menuRef} />}
63
+ </>
64
+ );
65
+ }
66
+
67
+ export default ChartDownloadMenu;
@@ -0,0 +1 @@
1
+ export type ChartExportMenuItem = "png" | "jpeg" | "svg" | "csv" | "pdf" | "table" | "full-screen";