datastake-daf 0.6.308 → 0.6.310

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": "datastake-daf",
3
- "version": "0.6.308",
3
+ "version": "0.6.310",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -72,11 +72,15 @@ const AreaChart = ({
72
72
  formattedXAxis = (s) => s,
73
73
  fillOpacity = 0.7,
74
74
  height,
75
+ t = (s) => s,
76
+ isPdf = false,
77
+ extraLegendConfig = {},
75
78
  ...rest
76
79
  }) => {
77
80
  const containerRef = useRef(null);
78
81
  const chartRef = useRef(null);
79
82
  const { token } = useToken();
83
+ const hasLegendConfig = Object.keys(extraLegendConfig).length > 0;
80
84
 
81
85
  useEffect(() => {
82
86
  if (!containerRef.current) {
@@ -117,7 +121,15 @@ const AreaChart = ({
117
121
  areaStyle: {
118
122
  fillOpacity,
119
123
  },
120
- legend: false,
124
+ legend: isPdf
125
+ ? {
126
+ itemName: {
127
+ formatter: (text) => t(text),
128
+ },
129
+ position: 'bottom',
130
+ ...extraLegendConfig,
131
+ }
132
+ : hasLegendConfig || false,
121
133
  line: false,
122
134
  ...rest,
123
135
  };
@@ -84,11 +84,16 @@ export default function BarChart({
84
84
  formattedXAxis = (s) => s,
85
85
  color,
86
86
  height,
87
+ t = (s) => s,
88
+ isPdf = false,
89
+ extraLegendConfig = {},
90
+ ...rest
87
91
  }) {
88
92
  const containerRef = React.useRef(null);
89
93
  const chartRef = React.useRef(null);
90
94
  const { token } = useToken();
91
-
95
+ const hasLegendConfig = Object.keys(extraLegendConfig).length > 0;
96
+
92
97
  React.useEffect(() => {
93
98
  if (!containerRef.current) {
94
99
  return;
@@ -129,7 +134,15 @@ export default function BarChart({
129
134
  isStack,
130
135
  color: color || token.colorPrimary7,
131
136
  seriesField,
132
- legend: false,
137
+ legend: isPdf
138
+ ? {
139
+ itemName: {
140
+ formatter: (text) => t(text),
141
+ },
142
+ position: 'bottom',
143
+ ...extraLegendConfig,
144
+ }
145
+ : hasLegendConfig || false,
133
146
  animation: animated,
134
147
  };
135
148
 
@@ -232,20 +232,23 @@ export const Percentage = {
232
232
  },
233
233
  };
234
234
 
235
- const colorConfig = {
235
+ export const colorConfig = {
236
236
  governance: {
237
- evaluation: "#4b6ae7",
238
- improvement: "#96b1ff",
237
+ improvement: "#4b6ae7",
238
+ evaluation: "#96b1ff",
239
+ negative: "#F04438",
239
240
  },
240
241
  social: {
241
- evaluation: "#864dd7",
242
- improvement: "#bf92ef",
242
+ improvement: "#864dd7",
243
+ evaluation: "#bf92ef",
244
+ negative: "#F04438",
243
245
  },
244
246
  environmental: {
245
- evaluation: "#f9b836",
246
- improvement: "#fedb7e",
247
+ improvement: "#f9b836",
248
+ evaluation: "#fedb7e",
249
+ negative: "#F04438",
247
250
  },
248
- };
251
+ }
249
252
 
250
253
  export const functionColor = {
251
254
  name: "Function Color",
@@ -301,3 +304,160 @@ export const functionColor = {
301
304
  },
302
305
  },
303
306
  };
307
+
308
+ const getChartColor = (chartData, currentSelectedOption) => (s) => {
309
+ const all = chartData;
310
+ const [category, type] = s.groupBy.split("-");
311
+ const evaluationItem = all.find(
312
+ (item) => item.groupBy === `${category}-Baseline`,
313
+ );
314
+ const improvementItem = all.find(
315
+ (item) => item.groupBy === `${category}-Improvement`,
316
+ );
317
+ const isImprovementNegative =
318
+ improvementItem?.total - evaluationItem?.evaluationScore < 0;
319
+
320
+ if (currentSelectedOption === "aggregate") {
321
+ return (
322
+ (isImprovementNegative && type === "Improvement"
323
+ ? colorConfig?.[category].negative
324
+ : colorConfig?.[category]?.[
325
+ type === "Baseline" ? "evaluation" : type.toLowerCase()
326
+ ]) || "#BFBFBF"
327
+ );
328
+ } else {
329
+ return (
330
+ (isImprovementNegative && type === "Improvement"
331
+ ? colorConfig?.[
332
+ currentSelectedOption.includes("governance")
333
+ ? "governance"
334
+ : currentSelectedOption
335
+ ].negative
336
+ : colorConfig?.[
337
+ currentSelectedOption.includes("governance")
338
+ ? "governance"
339
+ : currentSelectedOption
340
+ ]?.[type === "Baseline" ? "evaluation" : type.toLowerCase()]) ||
341
+ "#BFBFBF"
342
+ );
343
+ }
344
+ };
345
+ const graphData = [
346
+ {
347
+ "label": "Governance & Organisational Capacity",
348
+ "type": "Improvement",
349
+ "combinedKey": "governance-Improvement",
350
+ "groupBy": "governance-Improvement",
351
+ "value": 31,
352
+ "improvementScore": -31,
353
+ "evaluationScore": 76,
354
+ "color": "#F04438",
355
+ "total": 45,
356
+ "isNa": false
357
+ },
358
+ {
359
+ "label": "Governance & Organisational Capacity",
360
+ "type": "Baseline",
361
+ "combinedKey": "governance-Baseline",
362
+ "groupBy": "governance-Baseline",
363
+ "value": 45,
364
+ "evaluationScore": 76,
365
+ "color": "#96b1ff",
366
+ "total": 45,
367
+ "isNa": false
368
+ },
369
+ {
370
+ "label": "Social",
371
+ "type": "Improvement",
372
+ "combinedKey": "social-Improvement",
373
+ "groupBy": "social-Improvement",
374
+ "value": 61,
375
+ "improvementScore": -61,
376
+ "evaluationScore": 94,
377
+ "color": "#F04438",
378
+ "total": 33,
379
+ "isNa": false
380
+ },
381
+ {
382
+ "label": "Social",
383
+ "type": "Baseline",
384
+ "combinedKey": "social-Baseline",
385
+ "groupBy": "social-Baseline",
386
+ "value": 33,
387
+ "evaluationScore": 94,
388
+ "color": "#bf92ef",
389
+ "total": 33,
390
+ "isNa": false
391
+ },
392
+ {
393
+ "label": "Environmental",
394
+ "type": "Improvement",
395
+ "combinedKey": "environmental-Improvement",
396
+ "groupBy": "environmental-Improvement",
397
+ "value": 0,
398
+ "improvementScore": 0,
399
+ "evaluationScore": 100,
400
+ "color": "#f9b836",
401
+ "total": 100,
402
+ "isNa": false
403
+ },
404
+ {
405
+ "label": "Environmental",
406
+ "type": "Baseline",
407
+ "combinedKey": "environmental-Baseline",
408
+ "groupBy": "environmental-Baseline",
409
+ "value": 100,
410
+ "evaluationScore": 100,
411
+ "color": "#fedb7e",
412
+ "total": 100,
413
+ "isNa": false
414
+ }
415
+ ]
416
+
417
+ const renderTooltipContent = (title, data) => {
418
+ const baselineItem = data.find((item) => item?.data?.type === "Baseline");
419
+ const improvementItem = data.find((item) => item?.data?.type === "Improvement");
420
+ const baseline = Number(baselineItem?.data?.evaluationScore) || 0;
421
+ const improvement = Number(improvementItem?.data?.improvementScore);
422
+ const total = Math.min(100, baseline + improvement);
423
+
424
+ return {
425
+ title,
426
+ items: [
427
+ {
428
+ label: "sbg::current",
429
+ value: improvementItem?.data?.isNa ? "N/A" : `${total}%`,
430
+ },
431
+ {
432
+ label: improvement < 0 ? "sbg::deterioration" : "sbg::improvement",
433
+ value: improvementItem?.data?.isNa ? "N/A" : `${improvement}%`,
434
+ color: improvementItem?.data?.color || "green",
435
+ },
436
+ {
437
+ label: "sbg::baseline",
438
+ value: baselineItem?.data?.isNa ? "N/A" : `${baseline}%`,
439
+ color: baselineItem?.data?.color || "blue",
440
+ },
441
+ ],
442
+ };
443
+ }
444
+
445
+ export const Pdf = {
446
+ name: "Pdf",
447
+ args: {
448
+ data: graphData,
449
+ isPdf: true,
450
+ xFieldKey: "label",
451
+ yFieldKey: "value",
452
+ isStack: true,
453
+ seriesField:"groupBy",
454
+ groupField:"combinedKey",
455
+ color: getChartColor(graphData, "aggregate"),
456
+ renderTooltipContent: renderTooltipContent,
457
+ showBackground: true,
458
+ isPercentage: true,
459
+ },
460
+ render: (args) => {
461
+ return <ColumnChart {...args} />;
462
+ },
463
+ }
@@ -86,10 +86,15 @@ export default function ColumnChart({
86
86
  color,
87
87
  height,
88
88
  groupField,
89
+ t = (s) => s,
90
+ isPdf = false,
91
+ extraLegendConfig = {},
92
+ ...rest
89
93
  }) {
90
94
  const containerRef = React.useRef(null);
91
95
  const chartRef = React.useRef(null);
92
96
  const { token } = useToken();
97
+ const hasLegendConfig = Object.keys(extraLegendConfig).length > 0;
93
98
 
94
99
  React.useEffect(() => {
95
100
  if (!containerRef.current) {
@@ -137,7 +142,15 @@ export default function ColumnChart({
137
142
  : formattedYAxis,
138
143
  },
139
144
  },
140
- legend: false,
145
+ legend: isPdf
146
+ ? {
147
+ itemName: {
148
+ formatter: (text) => t(text),
149
+ },
150
+ position: 'bottom',
151
+ ...extraLegendConfig,
152
+ }
153
+ : hasLegendConfig || false,
141
154
  // Add background pattern only when showBackground is true and isPercentage is true
142
155
  ...(showBackground && isPercentage && {
143
156
  columnBackground: {
@@ -71,12 +71,16 @@ export default function DonutPie({
71
71
  tooltipConfig = {},
72
72
  meta,
73
73
  animation = false,
74
+ t = (s) => s,
75
+ isPdf = false,
76
+ extraLegendConfig = {},
74
77
  ...rest
75
78
  }) {
76
79
  const containerRef = useRef(null);
77
80
  const chartRef = useRef(null);
78
81
  const { token } = useToken();
79
-
82
+ const hasLegendConfig = Object.keys(extraLegendConfig).length > 0;
83
+
80
84
  // Memoize processed data for progress mode
81
85
  const processedData = useMemo(() => {
82
86
  if (!data || data.length === 0) {
@@ -108,7 +112,15 @@ export default function DonutPie({
108
112
  color: color || [token.colorPrimary7, "#E8EDF3"],
109
113
  radius,
110
114
  innerRadius,
111
- legend,
115
+ legend: isPdf
116
+ ? {
117
+ itemName: {
118
+ formatter: (text) => t(text),
119
+ },
120
+ position: 'bottom',
121
+ ...extraLegendConfig,
122
+ }
123
+ : hasLegendConfig || false,
112
124
  label,
113
125
  statistic,
114
126
  tooltip: tooltipOption,
@@ -82,11 +82,16 @@ export default function LineChart({
82
82
  isPercentage = false,
83
83
  height,
84
84
  autoHideXLabel = true,
85
+ t = (s) => s,
86
+ isPdf = false,
87
+ extraLegendConfig = {},
88
+ ...rest
85
89
  }) {
86
90
  const containerRef = React.useRef(null);
87
91
  const chartRef = React.useRef(null);
88
92
  const { token } = useToken();
89
-
93
+ const hasLegendConfig = Object.keys(extraLegendConfig).length > 0;
94
+
90
95
  React.useEffect(() => {
91
96
  if (!containerRef.current) {
92
97
  return;
@@ -140,7 +145,15 @@ export default function LineChart({
140
145
  fillOpacity: isArea ? 0.15 : 0,
141
146
  },
142
147
  },
143
- legend: false,
148
+ legend: isPdf
149
+ ? {
150
+ itemName: {
151
+ formatter: (text) => t(text),
152
+ },
153
+ position: 'bottom',
154
+ ...extraLegendConfig,
155
+ }
156
+ : hasLegendConfig || false,
144
157
  };
145
158
 
146
159
  if (!chartRef.current) {
@@ -165,6 +178,9 @@ export default function LineChart({
165
178
  isPercentage,
166
179
  renderTooltipContent,
167
180
  tooltipConfig,
181
+ isPdf,
182
+ extraLegendConfig,
183
+ t,
168
184
  ]);
169
185
 
170
186
  React.useEffect(() => {
@@ -20,20 +20,27 @@ export const Primary = {
20
20
  data: [
21
21
  {
22
22
  "percent": 0.8333333333333334,
23
+ "label": "Label 1",
23
24
  "color": "#F5C2AC"
24
25
  },
25
26
  {
26
27
  "percent": 0.06666666666666667,
28
+ "label": "Label 2",
27
29
  "color": "#F0A888"
28
30
  },
29
31
  {
30
32
  "percent": 0.06666666666666667,
33
+ "label": "Label 3",
31
34
  "color": "#DF571E"
32
35
  },
33
36
  {
34
37
  "percent": 0.03333333333333333,
38
+ "label": "Label 4",
35
39
  "color": "#C04B19"
36
40
  }
37
41
  ],
42
+ legend: false,
43
+ isPdf: true,
44
+ isPercentage: true,
38
45
  },
39
46
  };
@@ -1,7 +1,7 @@
1
1
  import PropTypes from 'prop-types'
2
2
  import React, { useCallback, useMemo, useRef, useState, Fragment } from 'react';
3
3
  import Tooltip from './tooltip.jsx';
4
- import { ChartStyle } from './style.js';
4
+ import { ChartStyle, LegendStyle } from './style.js';
5
5
 
6
6
  const Chart = ({
7
7
  data = [],
@@ -16,6 +16,9 @@ const Chart = ({
16
16
  mouseXOffset = 30,
17
17
  mouseYOffset = 50,
18
18
  children = null,
19
+ legend,
20
+ isPdf = false,
21
+ isPercentage = false,
19
22
  }) => {
20
23
  const [hoveredGroup, setHoveredGroup] = useState(null);
21
24
  const svgRef = useRef();
@@ -50,68 +53,121 @@ const Chart = ({
50
53
  setMouseX(x);
51
54
 
52
55
  if (hoveredGroup === null) {
53
- setHoveredGroup({ ...hG, id });
56
+ setHoveredGroup({ ...hG, id, fromLegend: false });
54
57
  }
55
58
  }, [svgRef, containerRef, hoveredGroup, mouseLocked]);
56
59
 
60
+ const legendConfig = useMemo(() => {
61
+ if (legend === false || legend === null) {
62
+ return isPdf ? {} : null;
63
+ }
64
+
65
+ if (typeof legend === 'object') {
66
+ return legend;
67
+ }
68
+
69
+ if (legend === true || legend === undefined || isPdf) {
70
+ return {};
71
+ }
72
+
73
+ return null;
74
+ }, [legend, isPdf]);
75
+
76
+
77
+ const legendItems = useMemo(() => {
78
+ const items = Array.isArray(data) ? data : [];
79
+
80
+ if (!legendConfig) {
81
+ return [];
82
+ }
83
+
84
+ const { itemName } = legendConfig;
85
+ const getName = typeof itemName?.formatter === 'function'
86
+ ? (item, index) => itemName.formatter(item.name || item.label || `Item ${index + 1}`, item, index)
87
+ : (item, index) => item.name || item.label || `Item ${index + 1}`;
88
+
89
+ return items.map((item, index) => ({
90
+ ...item,
91
+ label: getName(item, index),
92
+ }));
93
+ }, [data, legendConfig]);
94
+
57
95
  const dataToShow = useMemo(() => {
58
96
  let cumulativePercent = 0;
97
+ function getRotationAngle(percent) {
98
+ let angle = percent * 360 - 90;
99
+
100
+ if (angle > 90 && angle < 270) {
101
+ angle += 180;
102
+ }
103
+
104
+ const tilt = 30;
105
+ return angle + tilt;
106
+ }
107
+
59
108
 
60
109
  return data.map((d, i) => {
61
110
  const [startX, startY] = getCoordinatesForPercent(cumulativePercent);
62
- cumulativePercent += d.percent - 0;
111
+ const startPercent = cumulativePercent;
112
+
113
+ cumulativePercent += d.percent;
63
114
  const [endX, endY] = getCoordinatesForPercent(cumulativePercent);
64
- const [borderStartX, borderStartY] = getCoordinatesForPercent(cumulativePercent);
65
- cumulativePercent += 0;
66
- const [borderEndX, borderEndY] = getCoordinatesForPercent(cumulativePercent);
67
- const largeArcFlag = d.percent > .5 ? 1 : 0;
68
-
115
+
116
+ // Midpoint of this slice
117
+ const midPercent = startPercent + d.percent / 2;
118
+ const [labelX, labelY] = getCoordinatesForPercent(midPercent);
119
+
120
+ const largeArcFlag = d.percent > 0.5 ? 1 : 0;
121
+
69
122
  const pathData = [
70
123
  `M ${startX} ${startY}`,
71
124
  `A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`,
72
125
  `L 0 0`,
73
126
  ].join(' ');
74
-
75
- const borderPathData = [
76
- `M ${borderStartX} ${borderStartY}`,
77
- `A 1 1 0 0 1 ${borderEndX} ${borderEndY}`,
78
- `L 0 0`,
79
- ].join(' ');
80
-
127
+
81
128
  return (
82
129
  <Fragment key={i.toString()}>
83
130
  <path
84
131
  d={pathData}
85
- data-bs-toggle="tooltip"
86
- data-bs-placement="top"
87
- title="Tooltip on top"
88
132
  fill={d.color}
89
133
  style={{
90
134
  opacity: changeOpacityOnHover
91
- ? hoveredGroup === null ? 1 : hoveredGroup.id === i ? 1 : 0.4
135
+ ? hoveredGroup === null
136
+ ? 1
137
+ : hoveredGroup.id === i
138
+ ? 1
139
+ : 0.4
92
140
  : 1,
93
141
  }}
94
142
  onMouseEnter={() => {
95
- if (mouseLocked) {
96
- return;
143
+ if (!mouseLocked) {
144
+ setHoveredGroup({ ...d, id: i });
97
145
  }
98
-
99
- setHoveredGroup({ ...d, id: i })
100
146
  }}
101
147
  onMouseLeave={() => {
102
- if (mouseLocked) {
103
- return;
148
+ if (!mouseLocked) {
149
+ setHoveredGroup(null);
104
150
  }
105
-
106
- setHoveredGroup(null)
107
151
  }}
108
152
  onMouseMove={(e) => onMouseLeaveHandler(e, d, i)}
109
153
  onClick={() => setMouseLocked((prev) => !prev)}
110
154
  />
111
- <path
112
- d={borderPathData}
113
- fill="white"
114
- />
155
+
156
+ {/* White border if needed */}
157
+ <path d={pathData} fill="none" stroke="white" strokeWidth="0.001" />
158
+
159
+ {/* Label */}
160
+ {isPdf ? <text
161
+ x={labelX * 0.6}
162
+ y={labelY * 0.6}
163
+ textAnchor="middle"
164
+ dominantBaseline="middle"
165
+ fontSize="0.1"
166
+ fill="#000"
167
+ transform={`rotate(${getRotationAngle(midPercent)}, ${labelX * 0.6}, ${labelY * 0.6})`}
168
+ >
169
+ {d.percent ? isPercentage ? `${Math.round(d.percent * 100)}%` : d.percent : d.label}
170
+ </text> : null}
115
171
  </Fragment>
116
172
  );
117
173
  });
@@ -163,6 +219,61 @@ const Chart = ({
163
219
  {dataToShow}
164
220
  </svg>
165
221
  {tooltip}
222
+ {!!legendConfig && legendItems.length > 0 && (
223
+ <LegendStyle className={legendConfig?.className} data-position={legendConfig?.position || 'bottom'}>
224
+ {legendItems.map((item, index) => {
225
+ const markerStyle = legendConfig?.marker || {};
226
+
227
+ return (
228
+ <li
229
+ key={item.id ?? index}
230
+ className={item.className}
231
+ onMouseEnter={() => {
232
+ if (mouseLocked) {
233
+ return;
234
+ }
235
+
236
+ setHoveredGroup({ ...item, id: index, fromLegend: true });
237
+ }}
238
+ onMouseLeave={() => {
239
+ if (mouseLocked) {
240
+ return;
241
+ }
242
+
243
+ setHoveredGroup(null);
244
+ }}
245
+ onClick={() => {
246
+ if (!changeOpacityOnHover) {
247
+ return;
248
+ }
249
+
250
+ if (hoveredGroup?.id === index && hoveredGroup?.fromLegend) {
251
+ setHoveredGroup(null);
252
+ setMouseLocked(false);
253
+ return;
254
+ }
255
+
256
+ setHoveredGroup({ ...item, id: index, fromLegend: true });
257
+ setMouseLocked((prev) => !prev);
258
+ }}
259
+ >
260
+ <span
261
+ className="legend-marker"
262
+ style={{
263
+ backgroundColor: item.color,
264
+ width: markerStyle?.size || 12,
265
+ height: markerStyle?.size || 12,
266
+ borderRadius: markerStyle?.shape === 'square' ? 2 : '50%',
267
+ border: markerStyle?.style === 'line' ? `2px solid ${item.color}` : undefined,
268
+ background: markerStyle?.style === 'line' ? 'transparent' : item.color,
269
+ }}
270
+ />
271
+ <span>{item.label}</span>
272
+ </li>
273
+ );
274
+ })}
275
+ </LegendStyle>
276
+ )}
166
277
  {children}
167
278
  </ChartStyle>
168
279
  );
@@ -197,4 +197,29 @@ export const TooltipStyle = styled.div`
197
197
 
198
198
  `;
199
199
 
200
+ export const LegendStyle = styled.ul`
201
+ list-style: none;
202
+ margin: var(--size-lg) 0 0;
203
+ padding: 0;
204
+ display: flex;
205
+ flex-wrap: wrap;
206
+ gap: 12px;
207
+ justify-content: center;
208
+
209
+ li {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 8px;
213
+ font-size: 14px;
214
+ color: #344054;
215
+ white-space: nowrap;
216
+
217
+ .legend-marker {
218
+ width: 12px;
219
+ height: 12px;
220
+ border-radius: 50%;
221
+ }
222
+ }
223
+ `;
224
+
200
225
  export default Style;
@@ -8,6 +8,7 @@ export default function ImageWidget({
8
8
  description,
9
9
  imgAlt,
10
10
  noDescriptionText,
11
+ isPdf = false,
11
12
  ...props
12
13
  }) {
13
14
  return (
@@ -42,7 +43,7 @@ export default function ImageWidget({
42
43
 
43
44
  <div
44
45
  style={{
45
- maxHeight: "195px",
46
+ maxHeight: isPdf ? "100%" : "195px",
46
47
  paddingRight: "20px",
47
48
  overflowY: "auto",
48
49
  scrollbarWidth: "thin",
@@ -23,6 +23,7 @@ export const Primary = {
23
23
  image: "https://picsum.photos/200/300",
24
24
  description:
25
25
  "This is a description of the image widget. It can be long and scrollable. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
26
+ isPdf: true,
26
27
  },
27
28
  };
28
29