datastake-daf 0.6.774 → 0.6.775

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.
@@ -1,40 +1,153 @@
1
- import React, { useState } from 'react';
2
- import { Widget } from '../../../../../../../../src/index.js';
1
+ import React, { useState, useMemo, useEffect, useCallback } from 'react';
2
+ import { Widget, StickyTable, SearchFilters } from '../../../../../../../../src/index.js';
3
+ import { useWidgetFetch } from '../../../../../../hooks/useWidgetFetch.js';
4
+ import { getColumns } from './config.js';
5
+ import { getRedirectLink } from '../../../../../../../utils.js';
3
6
 
4
- const ACTIVITIES_TAB = 'activities';
5
- const PARTNERS_TAB = 'partners';
6
- const INCIDENTS_TAB = 'incidents';
7
+ // Constants
8
+ export const ACTIVITIES_TAB = 'activities';
9
+ export const PARTNERS_TAB = 'partners';
10
+ export const INCIDENTS_TAB = 'incidents';
7
11
 
12
+ export const DEFAULT_SEARCH_FIELDS = ["name", "datastakeId"];
13
+ const URL_PATTERN = /\/summary\/[^/]+\/(.+)/;
8
14
 
9
- const AssociatedInformation = ({
10
- activityData,
11
- loading = false,
12
- t = (s) => s
15
+ // Configuration
16
+ export const TABS_CONFIG = [
17
+ { label: "straatos::activities", value: ACTIVITIES_TAB },
18
+ { label: "straatos::partners", value: PARTNERS_TAB },
19
+ { label: "straatos::incidents", value: INCIDENTS_TAB, disabled: true },
20
+ ];
21
+
22
+ // Helper functions
23
+ export const getSearchFields = (activeTab) => DEFAULT_SEARCH_FIELDS;
24
+
25
+ export const buildSearchFilter = (search, fields) =>
26
+ search ? { search: { qs: search, fields } } : {};
27
+
28
+ export const extractTypeFromUrl = (url) => {
29
+ const match = url.match(URL_PATTERN);
30
+ if (!match) {
31
+ throw new Error(`Invalid URL format: ${url}`);
32
+ }
33
+ return match[1];
34
+ };
35
+
36
+ export const ensureArray = (data) => Array.isArray(data) ? data : [];
37
+
38
+ const AssociatedInformation = ({
39
+ id,
40
+ navigate,
41
+ getSummaryDetail,
42
+ loading = false,
43
+ projectId,
44
+ basepath = "planting-cycle",
45
+ endpoint = "associated-information",
46
+ tabsConfig = TABS_CONFIG,
47
+ searchFieldsMap = getSearchFields,
48
+ selectOptions,
49
+ t = (s) => s
13
50
  }) => {
14
51
  const [activeTab, setActiveTab] = useState(ACTIVITIES_TAB);
52
+ const [search, setSearch] = useState('');
53
+
54
+ const searchFields = useMemo(() => searchFieldsMap(activeTab), [activeTab, searchFieldsMap]);
55
+
56
+ const filters = useMemo(() => ({
57
+ type: activeTab,
58
+ ...buildSearchFilter(search, searchFields)
59
+ }), [activeTab, search, searchFields]);
60
+
61
+ const defaultConfig = useMemo(() => ({
62
+ basepath,
63
+ url: `/summary/${id}/${endpoint}`,
64
+ filters,
65
+ stop: !id,
66
+ }), [id, filters, basepath, endpoint]);
67
+
68
+ const customGetData = useMemo(() => {
69
+ if (!getSummaryDetail || !id) return undefined;
70
+
71
+ return (rest) => {
72
+ const { url, filters: restFilters } = rest;
73
+ const type = extractTypeFromUrl(url);
74
+ const params = {
75
+ ...(restFilters || {}),
76
+ type: restFilters?.type || activeTab
77
+ };
78
+ return getSummaryDetail(id, type, params);
79
+ };
80
+ }, [getSummaryDetail, id, activeTab]);
81
+
82
+ const { loading: associatedInformationLoading, data: associatedInformationData, setData } = useWidgetFetch({
83
+ config: defaultConfig,
84
+ getData: customGetData
85
+ });
86
+
87
+ // Reset data and search when tab changes
88
+ useEffect(() => {
89
+ setData([]);
90
+ setSearch('');
91
+ }, [activeTab, setData]);
92
+
93
+ const handleSearch = useCallback((activeFilter, searchValue) => {
94
+ setSearch(searchValue || '');
95
+ }, []);
96
+
97
+ const handleTabChange = useCallback((value) => {
98
+ setActiveTab(value);
99
+ }, []);
100
+
101
+ const columns = useMemo(() => getColumns({
102
+ t,
103
+ activeTab,
104
+ view: activeTab,
105
+ projectId,
106
+ navigate,
107
+ getRedirectLink,
108
+ selectOptions,
109
+ }), [t, activeTab, projectId, navigate, selectOptions]);
110
+
111
+ const tableDataSource = useMemo(() =>
112
+ ensureArray(associatedInformationData),
113
+ [associatedInformationData]
114
+ );
115
+
116
+ const translatedTabs = useMemo(() =>
117
+ tabsConfig.map(tab => ({ ...tab, label: t(tab.label) })),
118
+ [tabsConfig, t]
119
+ );
15
120
 
16
121
  return (
17
122
  <section>
18
-
19
- <Widget
20
- className="v2-widget no-px h-w-btn-header no-p-body"
123
+ <Widget
124
+ className="v2-widget no-px no-p-body h-w-btn-header with-border-header"
21
125
  title={t("Associated Information")}
22
126
  tabsConfig={{
23
- tabs: [
24
- { label: t("straatos::activities"), value: ACTIVITIES_TAB },
25
- { label: t("straatos::partners"), value: PARTNERS_TAB },
26
- { label: t("straatos::incidents"), value: INCIDENTS_TAB },
27
- ],
127
+ tabs: translatedTabs,
28
128
  value: activeTab,
29
- onChange: (value) => {
30
- setActiveTab(value);
31
- // setData([]);
32
- },
129
+ onChange: handleTabChange,
33
130
  }}
34
131
  >
35
- <div>
36
- </div>
37
- </Widget>
132
+ <div className='mt-6 ml-6 mr-6'>
133
+ <SearchFilters
134
+ t={t}
135
+ showFilter={false}
136
+ hasError={false}
137
+ canClear={true}
138
+ setHasError={() => {}}
139
+ onSearch={handleSearch}
140
+ activeFilters={{ search }}
141
+ />
142
+ </div>
143
+ <div className='mb-6'>
144
+ <StickyTable
145
+ columns={columns}
146
+ dataSource={tableDataSource}
147
+ loading={associatedInformationLoading || loading}
148
+ />
149
+ </div>
150
+ </Widget>
38
151
  </section>
39
152
  );
40
153
  };
@@ -1,119 +1,38 @@
1
1
  import React, { useMemo } from 'react';
2
- import dayjs from 'dayjs';
3
2
  import { Widget, ColumnChart } from '../../../../../../../../index.js';
3
+ import { Select } from 'antd';
4
+ import { useTimeFilter } from '../../../../../../../hooks/useTimeFilter.js';
5
+
6
+ const selectOptions = [
7
+ { label: "Daily", value: "daily" },
8
+ { label: "Weekly", value: "weekly" },
9
+ { label: "Monthly", value: "monthly" },
10
+ ];
4
11
 
5
12
  const JobsTimeline = ({
6
13
  dayJobsTimeline,
7
14
  loading = false,
8
15
  t = (s) => s
9
16
  }) => {
17
+ const { timeFilter, setTimeFilter, formatDateAxis, processChartDateData } = useTimeFilter({ defaultFilter: 'monthly' });
10
18
 
11
- console.log('dayJobsTimeline', dayJobsTimeline);
12
19
  const jobsData = Array.isArray(dayJobsTimeline)
13
20
  ? dayJobsTimeline
14
21
  : (dayJobsTimeline?.jobsTimeline || dayJobsTimeline?.jobs || dayJobsTimeline?.timeline || []);
15
22
 
16
- const formatDateAxis = useMemo(() => {
17
- return (label) => {
18
- if (!label) return label;
19
-
20
- // Try to parse the date using dayjs with various formats
21
- let date = dayjs(label);
22
-
23
- // If first attempt fails, try parsing as ISO date string
24
- if (!date.isValid() && typeof label === 'string') {
25
- date = dayjs(label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY'], true);
26
- }
27
-
28
- // If it's a valid date, format it as "Mmm YY"
29
- if (date.isValid()) {
30
- return date.format('MMM YY');
31
- }
32
-
33
- // If it's already in "Mmm YY" format or similar, return as is
34
- // Otherwise return the original label
35
- return label;
36
- };
37
- }, []);
38
-
39
23
  const jobsTimelineData = useMemo(() => {
40
- // Always show last 12 months, even if no data
41
- const now = dayjs().startOf('month');
42
- const twelveMonthsAgo = now.subtract(11, 'month'); // 11 months ago + current month = 12 months
43
-
44
- // Create a map of existing data by month (YYYY-MM format)
45
- const dataMap = new Map();
46
- const dates = [];
47
-
48
- // Process jobs data if available
49
- if (jobsData && Array.isArray(jobsData) && jobsData.length > 0) {
50
- jobsData.forEach((item) => {
51
- if (typeof item === 'object' && item !== null && item.date) {
52
- const date = dayjs(item.date);
53
- if (date.isValid()) {
54
- const monthKey = date.format('YYYY-MM');
55
- const count = Number(item.total || item.count || item.jobs || item.value || 0) || 0;
56
- dates.push(date);
57
-
58
- // If multiple entries for same month, sum them
59
- if (dataMap.has(monthKey)) {
60
- dataMap.set(monthKey, {
61
- ...dataMap.get(monthKey),
62
- jobs: dataMap.get(monthKey).jobs + count,
63
- });
64
- } else {
65
- dataMap.set(monthKey, {
66
- month: item.date,
67
- jobs: count,
68
- date: item.date,
69
- });
70
- }
71
- }
72
- }
73
- });
24
+ if (!jobsData || !Array.isArray(jobsData) || jobsData.length === 0) {
25
+ return [];
74
26
  }
75
-
76
- // Determine date range
77
- let minDate = twelveMonthsAgo;
78
- let maxDate = now;
79
-
80
- // If we have data, adjust range to include it
81
- if (dates.length > 0) {
82
- const sortedDates = dates.sort((a, b) => a.valueOf() - b.valueOf());
83
- const firstDataDate = sortedDates[0].startOf('month');
84
- const lastDataDate = sortedDates[sortedDates.length - 1].startOf('month');
85
-
86
- // Start from the earlier of: 12 months ago, or first data date
87
- minDate = twelveMonthsAgo.isBefore(firstDataDate) ? twelveMonthsAgo : firstDataDate;
88
-
89
- // End at the later of: current month, or last data date
90
- maxDate = now.isAfter(lastDataDate) ? now : lastDataDate;
91
- }
92
-
93
- // Generate all months in the range
94
- const result = [];
95
- let currentDate = minDate.clone();
96
-
97
- while (currentDate.isBefore(maxDate) || currentDate.isSame(maxDate, 'month')) {
98
- const monthKey = currentDate.format('YYYY-MM');
99
- const existingData = dataMap.get(monthKey);
100
-
101
- if (existingData) {
102
- result.push(existingData);
103
- } else {
104
- // Fill missing month with 0
105
- result.push({
106
- month: currentDate.format('YYYY-MM-DD'), // Use first day of month
107
- jobs: 0,
108
- date: currentDate.format('YYYY-MM-DD'),
109
- });
110
- }
111
-
112
- currentDate = currentDate.add(1, 'month');
113
- }
114
-
115
- return result;
116
- }, [jobsData]);
27
+
28
+ // Process data without cumulative calculation (for jobs timeline)
29
+ // Try to find value in total, count, jobs, or value fields
30
+ return processChartDateData({
31
+ mainData: jobsData,
32
+ isCumulative: false,
33
+ valueField: 'total', // Will fallback to count/jobs/value if total doesn't exist
34
+ });
35
+ }, [jobsData, processChartDateData]);
117
36
 
118
37
  const maxYValue = useMemo(() => {
119
38
  if (!jobsTimelineData || jobsTimelineData.length === 0) {
@@ -132,10 +51,22 @@ const JobsTimeline = ({
132
51
  <Widget
133
52
  title={<div>{t("Day Jobs Timeline")}</div>}
134
53
  className="with-border-header h-w-btn-header "
54
+ addedHeader={
55
+ <>
56
+ <div className="flex-1" />
57
+ <Select
58
+ value={timeFilter}
59
+ style={{ width: 100 }}
60
+ onChange={(value) => setTimeFilter(value)}
61
+ options={selectOptions}
62
+ popupMatchSelectWidth={120}
63
+ />
64
+ </>
65
+ }
135
66
  >
136
67
  <ColumnChart
137
68
  data={jobsTimelineData}
138
- xFieldKey="month"
69
+ xFieldKey="date"
139
70
  yFieldKey="jobs"
140
71
  animated={true}
141
72
  // height={200}
@@ -1,6 +1,6 @@
1
1
  const HEALTH_SAFETY_COLORS = {
2
- compliant: '#52C41A',
3
- notCompliant: '#FF4D4F',
2
+ compliant: '#016C6E',
3
+ notCompliant: '#F97066',
4
4
  empty: '#D9D9D9',
5
5
  };
6
6
 
@@ -61,8 +61,9 @@ export const calculateHealthAndSafetyPieData = (healthAndSafetyDistributionData,
61
61
  const total = Object.values(healthAndSafetyDistributionData).reduce((all, val) => all + (val || 0), 0);
62
62
 
63
63
  const labels = {
64
- compliant: t("Compliant"),
65
- notCompliant: t("Not Compliant"),
64
+ compliant: t("Available"),
65
+ notCompliant: t("Not available"),
66
+ empty: t("Not answered"),
66
67
  };
67
68
 
68
69
  return Object.keys(healthAndSafetyDistributionData).map((key) => {
@@ -103,8 +104,9 @@ export const getHealthAndSafetyTooltipChildren = (item, isEmpty, healthAndSafety
103
104
  }
104
105
 
105
106
  const labels = {
106
- compliant: t("Compliant"),
107
- notCompliant: t("Not Compliant"),
107
+ compliant: t("Available"),
108
+ notCompliant: t("Not available"),
109
+ empty: t("Not answered"),
108
110
  };
109
111
 
110
112
  // Filter items with values > 0
@@ -2,13 +2,83 @@ import React, { useMemo, useCallback } from 'react';
2
2
  import { Widget, PieChart } from '../../../../../../../../index.js';
3
3
  import { getHealthAndSafetyDistributionData, isHealthAndSafetyDistributionEmpty, calculateHealthAndSafetyPieData, getHealthAndSafetyTooltipChildren } from './helper';
4
4
  import { renderTooltipJsx } from '../../../../../../../../utils';
5
+ import { useWidgetFetch } from '../../../../../../../hooks/useWidgetFetch.js';
5
6
 
6
7
  const HealthAndSafety = ({
7
- activityData,
8
+ id,
9
+ getSummaryDetail,
8
10
  loading = false,
9
11
  t = (s) => s
10
12
  }) => {
11
- const healthAndSafetyDistributionData = useMemo(() => getHealthAndSafetyDistributionData(activityData), [activityData]);
13
+ const defaultConfig = useMemo(
14
+ () => ({
15
+ basepath: "planting-cycle",
16
+ url: `/summary/${id}/filtered-piechart`,
17
+ filters: { field: 'duosFormed' },
18
+ stop: !id,
19
+ }),
20
+ [id],
21
+ );
22
+
23
+ const customGetData = useMemo(() => {
24
+ if (getSummaryDetail && id) {
25
+ return (rest) => {
26
+ const { url, filters: restFilters } = rest;
27
+ const match = url.match(/\/summary\/[^/]+\/(.+)/);
28
+ if (match) {
29
+ const [, type] = match;
30
+ // Pass filters as params for the query string
31
+ const params = {
32
+ ...(restFilters || {}),
33
+ };
34
+ return getSummaryDetail(id, type, params);
35
+ }
36
+ throw new Error(`Invalid URL format: ${url}`);
37
+ };
38
+ }
39
+ return undefined;
40
+ }, [getSummaryDetail, id]);
41
+
42
+ const { loading: pieChartLoading, data: pieChartData } = useWidgetFetch({
43
+ config: defaultConfig,
44
+ getData: customGetData
45
+ });
46
+
47
+ // Process the fetched pie chart data
48
+ // The API returns data in format: [{count: 1, duosFormed: "null"}, {count: 1, duosFormed: "no"}, {count: 1, duosFormed: "yes"}]
49
+ const healthAndSafetyDistributionData = useMemo(() => {
50
+ if (!pieChartData) return { compliant: 0, notCompliant: 0, empty: 0 };
51
+
52
+ // If it's already a distribution object
53
+ if (pieChartData.compliant !== undefined || pieChartData.notCompliant !== undefined) {
54
+ return pieChartData;
55
+ }
56
+
57
+ // If it's an array, process it
58
+ if (Array.isArray(pieChartData)) {
59
+ const distribution = { compliant: 0, notCompliant: 0, empty: 0 };
60
+
61
+ pieChartData.forEach(item => {
62
+ const duosFormedValue = item.duosFormed;
63
+ const count = item.count || 0;
64
+
65
+ // Map duosFormed values to distribution categories
66
+ if (duosFormedValue === "yes" || duosFormedValue === true) {
67
+ distribution.compliant += count;
68
+ } else if (duosFormedValue === "no" || duosFormedValue === false) {
69
+ distribution.notCompliant += count;
70
+ } else if (duosFormedValue === "null" || duosFormedValue === null || duosFormedValue === undefined) {
71
+ distribution.empty += count;
72
+ }
73
+ });
74
+
75
+ return distribution;
76
+ }
77
+
78
+ // Fallback: try to extract from activityData-like structure
79
+ return getHealthAndSafetyDistributionData(pieChartData);
80
+ }, [pieChartData]);
81
+
12
82
  const isEmpty = useMemo(() => isHealthAndSafetyDistributionEmpty(healthAndSafetyDistributionData), [healthAndSafetyDistributionData]);
13
83
  const pieData = useMemo(() => calculateHealthAndSafetyPieData(healthAndSafetyDistributionData, t), [healthAndSafetyDistributionData, t]);
14
84
 
@@ -19,7 +89,7 @@ const HealthAndSafety = ({
19
89
 
20
90
  return (
21
91
  <Widget
22
- loading={loading}
92
+ loading={loading || pieChartLoading}
23
93
  title={<div>{t("Health and Safety")}</div>}
24
94
  className="with-border-header h-w-btn-header "
25
95
  >
@@ -46,4 +116,3 @@ const HealthAndSafety = ({
46
116
  };
47
117
 
48
118
  export default HealthAndSafety;
49
-
@@ -100,7 +100,7 @@ const CycleIndicators = ({
100
100
  <CyclePartners cyclePartners={cyclePartners} loading={indicatorsLoading} t={t} />
101
101
  </section>
102
102
  <section style={{ flex: 1 }}>
103
- <HealthAndSafety activityData={indicatorsData} loading={indicatorsLoading} t={t} />
103
+ <HealthAndSafety id={id} getSummaryDetail={getSummaryDetail} loading={indicatorsLoading} t={t} />
104
104
  </section>
105
105
  </div>
106
106
  </Widget>
@@ -0,0 +1,148 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Widget, ColumnChart } from '../../../../../../../../src/index.js';
3
+ import { Select } from 'antd';
4
+ import { useTimeFilter } from '../../../../../../hooks/useTimeFilter.js';
5
+
6
+ const selectOptions = [
7
+ { label: "Daily", value: "daily" },
8
+ { label: "Weekly", value: "weekly" },
9
+ { label: "Monthly", value: "monthly" },
10
+ ];
11
+
12
+ const PlantingActivitiesTimeline = ({
13
+ activitiesTimelineChart,
14
+ t = (s) => s
15
+ }) => {
16
+ const { timeFilter, setTimeFilter, formatDateAxis, processChartDateData } = useTimeFilter({ defaultFilter: 'monthly' });
17
+
18
+ // Map activitiesTimelineChart data to ColumnChart format with time filter support
19
+ // Data structure: [{count: 2, date: "2025-11-03"}]
20
+ // Fill all periods in the range, even if empty
21
+ const activitiesTimelineData = useMemo(() => {
22
+ if (!activitiesTimelineChart || !Array.isArray(activitiesTimelineChart) || activitiesTimelineChart.length === 0) {
23
+ return [];
24
+ }
25
+
26
+ // Process data without cumulative calculation (for activities timeline)
27
+ return processChartDateData({
28
+ mainData: activitiesTimelineChart,
29
+ isCumulative: false,
30
+ valueField: 'count',
31
+ });
32
+ }, [activitiesTimelineChart, processChartDateData]);
33
+
34
+ // Calculate max value for Y-axis (default to 100 if all values are 0 or very small)
35
+ const maxActivitiesYValue = useMemo(() => {
36
+ if (!activitiesTimelineData || activitiesTimelineData.length === 0) {
37
+ return 100;
38
+ }
39
+ const maxValue = Math.max(...activitiesTimelineData.map(item => item.jobs || 0));
40
+ // If max is 0, set default to 100 to show Y-axis
41
+ if (maxValue === 0) {
42
+ return 100;
43
+ }
44
+ // Round up to nearest 10, but ensure minimum of 100
45
+ const roundedMax = Math.ceil(maxValue / 10) * 10;
46
+ return Math.max(100, roundedMax);
47
+ }, [activitiesTimelineData]);
48
+
49
+ return (
50
+ <Widget
51
+ title={t("Planting Activities Timeline")}
52
+ className="with-border-header h-w-btn-header"
53
+ addedHeader={
54
+ <>
55
+ <div className="flex-1" />
56
+ <Select
57
+ value={timeFilter}
58
+ style={{ width: 100 }}
59
+ onChange={(value) => setTimeFilter(value)}
60
+ options={selectOptions}
61
+ popupMatchSelectWidth={120}
62
+ />
63
+ </>
64
+ }
65
+ >
66
+ <div className="flex flex-1 flex-column justify-content-center">
67
+ <div className="flex justify-content-center w-full">
68
+ <ColumnChart
69
+ data={activitiesTimelineData}
70
+ xFieldKey="date"
71
+ yFieldKey="jobs"
72
+ animated={true}
73
+ height={400}
74
+ color="#016C6E"
75
+ renderTooltipContent={(title, data) => {
76
+ if (!data || data.length === 0) return {};
77
+ // For ColumnChart, data structure: data[0]?.data contains the actual data point
78
+ const item = data[0]?.data || data[0];
79
+ const count = item?.jobs || item?.value || 0;
80
+ // Title is the X-axis value (month/date), use it for formatting
81
+ const dateValue = item?.date || title || '';
82
+
83
+ return {
84
+ title: t("Planting Activities"),
85
+ subTitle: formatDateAxis(dateValue),
86
+ items: [
87
+ {
88
+ label: t("Total"),
89
+ value: count,
90
+ },
91
+ ],
92
+ };
93
+ }}
94
+ formattedXAxis={formatDateAxis}
95
+ formattedYAxis={(value) => {
96
+ return `${value}`.replace(/\d{1,3}(?=(\d{3})+$)/g, (s) => `${s},`);
97
+ }}
98
+ yAxis={{
99
+ min: 0,
100
+ max: maxActivitiesYValue,
101
+ tickMethod: () => {
102
+ // Generate ticks: for 100 max, show 0, 20, 40, 60, 80, 100
103
+ // For other values, show ticks every 20 units
104
+ const step = maxActivitiesYValue <= 100 ? 20 : Math.max(20, Math.floor(maxActivitiesYValue / 5));
105
+ const ticks = [];
106
+ for (let i = 0; i <= maxActivitiesYValue; i += step) {
107
+ ticks.push(i);
108
+ }
109
+ // Ensure max value is included
110
+ if (ticks.length === 0 || ticks[ticks.length - 1] < maxActivitiesYValue) {
111
+ ticks.push(maxActivitiesYValue);
112
+ }
113
+ return ticks;
114
+ },
115
+ label: {
116
+ style: {
117
+ fontSize: 12,
118
+ fill: '#666',
119
+ },
120
+ },
121
+ grid: {
122
+ line: {
123
+ style: {
124
+ stroke: '#E5E7EB',
125
+ lineWidth: 1,
126
+ },
127
+ },
128
+ },
129
+ }}
130
+ xAxis={{
131
+ label: {
132
+ formatter: formatDateAxis,
133
+ autoHide: true,
134
+ style: {
135
+ fontSize: 12,
136
+ fill: '#666',
137
+ },
138
+ },
139
+ }}
140
+ />
141
+ </div>
142
+ </div>
143
+ </Widget>
144
+ );
145
+ };
146
+
147
+ export default PlantingActivitiesTimeline;
148
+