datastake-daf 0.6.837 → 0.6.839
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/favicon.ico +0 -0
- package/build/logo192.png +0 -0
- package/build/logo512.png +0 -0
- package/build/manifest.json +25 -0
- package/build/robots.txt +3 -0
- package/dist/hooks/index.js +16 -3
- package/dist/pages/index.js +84 -31
- package/dist/style/datastake/mapbox-gl.css +330 -0
- package/package.json +1 -1
- package/src/@daf/hooks/useTimeFilter.js +1 -1
- package/src/@daf/hooks/useWidgetFetch.js +20 -7
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CommunityParticipation/JobsTimeline/index.jsx +7 -15
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/PlantingActivitiesTimeline.jsx +6 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/CycleOutcomes/RestoredArea.jsx +1 -0
- package/src/@daf/pages/Summary/Activities/PlantingCycle/components/PlantingLocations/index.jsx +5 -5
- package/src/@daf/utils/timeFilterUtils.js +43 -14
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"short_name": "React App",
|
|
3
|
+
"name": "Create React App Sample",
|
|
4
|
+
"icons": [
|
|
5
|
+
{
|
|
6
|
+
"src": "favicon.ico",
|
|
7
|
+
"sizes": "64x64 32x32 24x24 16x16",
|
|
8
|
+
"type": "image/x-icon"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"src": "logo192.png",
|
|
12
|
+
"type": "image/png",
|
|
13
|
+
"sizes": "192x192"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"src": "logo512.png",
|
|
17
|
+
"type": "image/png",
|
|
18
|
+
"sizes": "512x512"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"start_url": ".",
|
|
22
|
+
"display": "standalone",
|
|
23
|
+
"theme_color": "#000000",
|
|
24
|
+
"background_color": "#ffffff"
|
|
25
|
+
}
|
package/build/robots.txt
ADDED
package/dist/hooks/index.js
CHANGED
|
@@ -2307,6 +2307,8 @@ const useWidgetFetch = ({
|
|
|
2307
2307
|
} = config;
|
|
2308
2308
|
const [data, setData] = React.useState(defaultData || {});
|
|
2309
2309
|
const [loading, setLoading] = React.useState(false);
|
|
2310
|
+
const [initFetchDone, setInitFetchDone] = React.useState(false);
|
|
2311
|
+
const isMounted = React.useRef(true);
|
|
2310
2312
|
const fetchData = async () => {
|
|
2311
2313
|
if (stop) {
|
|
2312
2314
|
return;
|
|
@@ -2316,6 +2318,7 @@ const useWidgetFetch = ({
|
|
|
2316
2318
|
const {
|
|
2317
2319
|
data
|
|
2318
2320
|
} = await getData(rest);
|
|
2321
|
+
if (!isMounted.current) return;
|
|
2319
2322
|
setData(data || defaultData);
|
|
2320
2323
|
if (isErrorResponse(data)) {
|
|
2321
2324
|
const errorMessage = getErrorMessage(data);
|
|
@@ -2323,18 +2326,28 @@ const useWidgetFetch = ({
|
|
|
2323
2326
|
return;
|
|
2324
2327
|
}
|
|
2325
2328
|
onFetch();
|
|
2329
|
+
setInitFetchDone(true);
|
|
2326
2330
|
} catch (err) {
|
|
2327
2331
|
console.log(err);
|
|
2328
2332
|
}
|
|
2329
|
-
|
|
2333
|
+
if (isMounted.current) {
|
|
2334
|
+
setLoading(false);
|
|
2335
|
+
}
|
|
2330
2336
|
};
|
|
2337
|
+
React.useEffect(() => {
|
|
2338
|
+
isMounted.current = true;
|
|
2339
|
+
return () => {
|
|
2340
|
+
isMounted.current = false;
|
|
2341
|
+
};
|
|
2342
|
+
}, []);
|
|
2331
2343
|
React.useEffect(() => {
|
|
2332
2344
|
fetchData();
|
|
2333
|
-
}, [JSON.stringify(
|
|
2345
|
+
}, [JSON.stringify(config)]);
|
|
2334
2346
|
return {
|
|
2335
2347
|
data,
|
|
2336
2348
|
loading,
|
|
2337
|
-
setData
|
|
2349
|
+
setData,
|
|
2350
|
+
initFetchDone
|
|
2338
2351
|
};
|
|
2339
2352
|
};
|
|
2340
2353
|
|
package/dist/pages/index.js
CHANGED
|
@@ -7506,6 +7506,8 @@ const useWidgetFetch = ({
|
|
|
7506
7506
|
} = config;
|
|
7507
7507
|
const [data, setData] = React.useState(defaultData || {});
|
|
7508
7508
|
const [loading, setLoading] = React.useState(false);
|
|
7509
|
+
const [initFetchDone, setInitFetchDone] = React.useState(false);
|
|
7510
|
+
const isMounted = React.useRef(true);
|
|
7509
7511
|
const fetchData = async () => {
|
|
7510
7512
|
if (stop) {
|
|
7511
7513
|
return;
|
|
@@ -7515,6 +7517,7 @@ const useWidgetFetch = ({
|
|
|
7515
7517
|
const {
|
|
7516
7518
|
data
|
|
7517
7519
|
} = await getData(rest);
|
|
7520
|
+
if (!isMounted.current) return;
|
|
7518
7521
|
setData(data || defaultData);
|
|
7519
7522
|
if (isErrorResponse(data)) {
|
|
7520
7523
|
const errorMessage = getErrorMessage(data);
|
|
@@ -7522,18 +7525,28 @@ const useWidgetFetch = ({
|
|
|
7522
7525
|
return;
|
|
7523
7526
|
}
|
|
7524
7527
|
onFetch();
|
|
7528
|
+
setInitFetchDone(true);
|
|
7525
7529
|
} catch (err) {
|
|
7526
7530
|
console.log(err);
|
|
7527
7531
|
}
|
|
7528
|
-
|
|
7532
|
+
if (isMounted.current) {
|
|
7533
|
+
setLoading(false);
|
|
7534
|
+
}
|
|
7529
7535
|
};
|
|
7536
|
+
React.useEffect(() => {
|
|
7537
|
+
isMounted.current = true;
|
|
7538
|
+
return () => {
|
|
7539
|
+
isMounted.current = false;
|
|
7540
|
+
};
|
|
7541
|
+
}, []);
|
|
7530
7542
|
React.useEffect(() => {
|
|
7531
7543
|
fetchData();
|
|
7532
|
-
}, [JSON.stringify(
|
|
7544
|
+
}, [JSON.stringify(config)]);
|
|
7533
7545
|
return {
|
|
7534
7546
|
data,
|
|
7535
7547
|
loading,
|
|
7536
|
-
setData
|
|
7548
|
+
setData,
|
|
7549
|
+
initFetchDone
|
|
7537
7550
|
};
|
|
7538
7551
|
};
|
|
7539
7552
|
|
|
@@ -55561,7 +55574,7 @@ const PlantingLocations = ({
|
|
|
55561
55574
|
return eventsWithGPS.map((event, index) => {
|
|
55562
55575
|
const locationCheckArrival = event.locationCheckArrival;
|
|
55563
55576
|
const matchingLocation = locations.find(location => locationCheckArrival.name === location.name || locationCheckArrival._id === location.id || location.id === locationCheckArrival._id) || locations[0];
|
|
55564
|
-
const area = matchingLocation?.perimeter ? matchingLocation.perimeter.map(coord => Array.isArray(coord) && coord.length >= 2 ? [coord[
|
|
55577
|
+
const area = matchingLocation?.perimeter ? matchingLocation.perimeter.map(coord => Array.isArray(coord) && coord.length >= 2 ? [coord[0], coord[1]] : coord) : null;
|
|
55565
55578
|
|
|
55566
55579
|
// Only include area if it has at least 3 valid coordinates
|
|
55567
55580
|
const validArea = area && Array.isArray(area) && area.length >= 3 ? area : null;
|
|
@@ -55680,7 +55693,7 @@ const PlantingLocations = ({
|
|
|
55680
55693
|
* Formats a date based on the time filter
|
|
55681
55694
|
* @param {dayjs.Dayjs} date - The date to format
|
|
55682
55695
|
* @param {boolean} breakLine - Whether to add a line break (for tooltips)
|
|
55683
|
-
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
|
|
55696
|
+
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
55684
55697
|
* @returns {string} Formatted date string
|
|
55685
55698
|
*/
|
|
55686
55699
|
const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') => {
|
|
@@ -55689,20 +55702,30 @@ const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') => {
|
|
|
55689
55702
|
return date.format("DD/MM");
|
|
55690
55703
|
case "weekly":
|
|
55691
55704
|
return `W${renderNumber$1(date.week())}`;
|
|
55705
|
+
case "yearly":
|
|
55706
|
+
return date.format("YYYY");
|
|
55692
55707
|
default:
|
|
55693
55708
|
// Monthly format: "Dec 24", "Jan 25", etc.
|
|
55694
|
-
|
|
55695
55709
|
return breakLine ? `${capitalize(date.format("MMM"))}\n${date.format("YY")}` : `${capitalize(date.format("MMM"))} ${date.format("YY")}`;
|
|
55696
55710
|
}
|
|
55697
55711
|
};
|
|
55698
55712
|
|
|
55699
55713
|
/**
|
|
55700
55714
|
* Gets the time quantity string for dayjs operations
|
|
55701
|
-
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
|
|
55702
|
-
* @returns {string} Time quantity string ('days', 'weeks', 'months')
|
|
55715
|
+
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
55716
|
+
* @returns {string} Time quantity string ('days', 'weeks', 'months', 'years')
|
|
55703
55717
|
*/
|
|
55704
55718
|
const getTimeQuantity = (timeFilter = 'monthly') => {
|
|
55705
|
-
|
|
55719
|
+
switch (timeFilter) {
|
|
55720
|
+
case "daily":
|
|
55721
|
+
return "days";
|
|
55722
|
+
case "weekly":
|
|
55723
|
+
return "weeks";
|
|
55724
|
+
case "yearly":
|
|
55725
|
+
return "years";
|
|
55726
|
+
default:
|
|
55727
|
+
return "months";
|
|
55728
|
+
}
|
|
55706
55729
|
};
|
|
55707
55730
|
|
|
55708
55731
|
/**
|
|
@@ -55728,6 +55751,9 @@ const getPreviousGraphData = (dates, startDate, timeFilter, valueField = 'total'
|
|
|
55728
55751
|
case "weekly":
|
|
55729
55752
|
isBeforeStart = date.isBefore(startDate, 'week');
|
|
55730
55753
|
break;
|
|
55754
|
+
case "yearly":
|
|
55755
|
+
isBeforeStart = date.isBefore(startDate, 'year');
|
|
55756
|
+
break;
|
|
55731
55757
|
default:
|
|
55732
55758
|
isBeforeStart = date.isBefore(startDate, 'month');
|
|
55733
55759
|
break;
|
|
@@ -55750,7 +55776,7 @@ const getPreviousGraphData = (dates, startDate, timeFilter, valueField = 'total'
|
|
|
55750
55776
|
* Processes chart data with time filtering support
|
|
55751
55777
|
* @param {Object} params - Parameters object
|
|
55752
55778
|
* @param {Array} params.mainData - Array of data objects with date and value fields
|
|
55753
|
-
* @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly')
|
|
55779
|
+
* @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
55754
55780
|
* @param {Object} params.filters - Optional filters object with timeframe
|
|
55755
55781
|
* @param {boolean} params.isCumulative - Whether to calculate cumulative values (default: false)
|
|
55756
55782
|
* @param {string} params.valueField - Field name to extract value from (default: 'total', also checks 'count', 'jobs', 'value')
|
|
@@ -55777,6 +55803,9 @@ const processChartDateData = ({
|
|
|
55777
55803
|
} else if (filter === "weekly") {
|
|
55778
55804
|
start = start.startOf('week');
|
|
55779
55805
|
end = end.startOf('week');
|
|
55806
|
+
} else if (filter === "yearly") {
|
|
55807
|
+
start = start.startOf('year');
|
|
55808
|
+
end = end.startOf('year');
|
|
55780
55809
|
} else {
|
|
55781
55810
|
start = start.startOf('month');
|
|
55782
55811
|
end = end.startOf('month');
|
|
@@ -55793,9 +55822,24 @@ const processChartDateData = ({
|
|
|
55793
55822
|
cumulativeScore = hasPreviousData ? previousCumulativeScore : 0;
|
|
55794
55823
|
}
|
|
55795
55824
|
|
|
55825
|
+
// Get the period unit for comparison
|
|
55826
|
+
const getPeriodUnit = f => {
|
|
55827
|
+
switch (f) {
|
|
55828
|
+
case "daily":
|
|
55829
|
+
return "day";
|
|
55830
|
+
case "weekly":
|
|
55831
|
+
return "week";
|
|
55832
|
+
case "yearly":
|
|
55833
|
+
return "year";
|
|
55834
|
+
default:
|
|
55835
|
+
return "month";
|
|
55836
|
+
}
|
|
55837
|
+
};
|
|
55838
|
+
const periodUnit = getPeriodUnit(filter);
|
|
55839
|
+
|
|
55796
55840
|
// Loop until we reach the end date
|
|
55797
55841
|
let currentDate = start.clone();
|
|
55798
|
-
while (currentDate.isBefore(end) || currentDate.isSame(end,
|
|
55842
|
+
while (currentDate.isBefore(end) || currentDate.isSame(end, periodUnit)) {
|
|
55799
55843
|
// Filter data points that fall within this period
|
|
55800
55844
|
const score = isEmpty ? 0 : dates.filter(d => {
|
|
55801
55845
|
if (!d.date) return false;
|
|
@@ -55804,6 +55848,8 @@ const processChartDateData = ({
|
|
|
55804
55848
|
return d.date === currentDate.format("YYYY-MM-DD");
|
|
55805
55849
|
case "weekly":
|
|
55806
55850
|
return dayjs__default["default"](d.date, "YYYY-MM-DD").week() === currentDate.week() && dayjs__default["default"](d.date, "YYYY-MM-DD").year() === currentDate.year();
|
|
55851
|
+
case "yearly":
|
|
55852
|
+
return dayjs__default["default"](d.date, "YYYY-MM-DD").year() === currentDate.year();
|
|
55807
55853
|
default:
|
|
55808
55854
|
return dayjs__default["default"](d.date, "YYYY-MM-DD").format("YYYY-MM") === currentDate.format("YYYY-MM");
|
|
55809
55855
|
}
|
|
@@ -55840,7 +55886,7 @@ const processChartDateData = ({
|
|
|
55840
55886
|
const formatDateAxis = (label, getFormatDateFn) => {
|
|
55841
55887
|
if (!label) return label;
|
|
55842
55888
|
|
|
55843
|
-
// Check if label is already in the correct format (MMM YY, DD/MM, or
|
|
55889
|
+
// Check if label is already in the correct format (MMM YY, DD/MM, W#, or YYYY)
|
|
55844
55890
|
// If it matches our format patterns, return as-is
|
|
55845
55891
|
if (typeof label === 'string') {
|
|
55846
55892
|
// Check for MMM YY format (e.g., "Dec 24", "Jan 25")
|
|
@@ -55855,6 +55901,10 @@ const formatDateAxis = (label, getFormatDateFn) => {
|
|
|
55855
55901
|
if (/^W\d+$/.test(label)) {
|
|
55856
55902
|
return label;
|
|
55857
55903
|
}
|
|
55904
|
+
// Check for YYYY format (e.g., "2024", "2025")
|
|
55905
|
+
if (/^\d{4}$/.test(label)) {
|
|
55906
|
+
return label;
|
|
55907
|
+
}
|
|
55858
55908
|
}
|
|
55859
55909
|
|
|
55860
55910
|
// Otherwise, try to parse and format it
|
|
@@ -55862,7 +55912,7 @@ const formatDateAxis = (label, getFormatDateFn) => {
|
|
|
55862
55912
|
|
|
55863
55913
|
// If first attempt fails, try parsing as ISO date string
|
|
55864
55914
|
if (!date.isValid() && typeof label === 'string') {
|
|
55865
|
-
date = dayjs__default["default"](label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
|
|
55915
|
+
date = dayjs__default["default"](label, ['YYYY-MM-DD', 'YYYY-MM', 'YYYY', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
|
|
55866
55916
|
}
|
|
55867
55917
|
|
|
55868
55918
|
// If it's a valid date, format it using getFormatDate
|
|
@@ -55879,7 +55929,7 @@ const formatDateAxis = (label, getFormatDateFn) => {
|
|
|
55879
55929
|
* Provides state management and formatting functions for time-based charts
|
|
55880
55930
|
*
|
|
55881
55931
|
* @param {Object} options - Configuration options
|
|
55882
|
-
* @param {string} options.defaultFilter - Default time filter ('daily', 'weekly', 'monthly')
|
|
55932
|
+
* @param {string} options.defaultFilter - Default time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
55883
55933
|
* @returns {Object} Time filter state and utilities
|
|
55884
55934
|
*/
|
|
55885
55935
|
const useTimeFilter = ({
|
|
@@ -55930,6 +55980,9 @@ const selectOptions$2 = [{
|
|
|
55930
55980
|
}, {
|
|
55931
55981
|
label: "Monthly",
|
|
55932
55982
|
value: "monthly"
|
|
55983
|
+
}, {
|
|
55984
|
+
label: "Yearly",
|
|
55985
|
+
value: "yearly"
|
|
55933
55986
|
}];
|
|
55934
55987
|
const RestoredArea = ({
|
|
55935
55988
|
restoredAreaChart,
|
|
@@ -56073,6 +56126,9 @@ const selectOptions$1 = [{
|
|
|
56073
56126
|
}, {
|
|
56074
56127
|
label: "Monthly",
|
|
56075
56128
|
value: "monthly"
|
|
56129
|
+
}, {
|
|
56130
|
+
label: "Yearly",
|
|
56131
|
+
value: "yearly"
|
|
56076
56132
|
}];
|
|
56077
56133
|
const PlantingActivitiesTimeline = ({
|
|
56078
56134
|
activitiesTimelineChart,
|
|
@@ -56101,6 +56157,11 @@ const PlantingActivitiesTimeline = ({
|
|
|
56101
56157
|
while (lastNonZeroIndex >= 0 && (processedData[lastNonZeroIndex].jobs || 0) === 0) {
|
|
56102
56158
|
lastNonZeroIndex--;
|
|
56103
56159
|
}
|
|
56160
|
+
|
|
56161
|
+
// If all values are 0, return full processed data to show x-axis with default date range
|
|
56162
|
+
if (lastNonZeroIndex < 0) {
|
|
56163
|
+
return processedData;
|
|
56164
|
+
}
|
|
56104
56165
|
return processedData.slice(0, lastNonZeroIndex + 1);
|
|
56105
56166
|
}, [activitiesTimelineChart, processChartDateData]);
|
|
56106
56167
|
const maxActivitiesYValue = React.useMemo(() => {
|
|
@@ -56995,6 +57056,9 @@ const selectOptions = [{
|
|
|
56995
57056
|
}, {
|
|
56996
57057
|
label: "Monthly",
|
|
56997
57058
|
value: "monthly"
|
|
57059
|
+
}, {
|
|
57060
|
+
label: "Yearly",
|
|
57061
|
+
value: "yearly"
|
|
56998
57062
|
}];
|
|
56999
57063
|
const JobsTimeline = ({
|
|
57000
57064
|
dayJobsTimeline,
|
|
@@ -57010,22 +57074,6 @@ const JobsTimeline = ({
|
|
|
57010
57074
|
defaultFilter: 'monthly'
|
|
57011
57075
|
});
|
|
57012
57076
|
const jobsData = Array.isArray(dayJobsTimeline) ? dayJobsTimeline : dayJobsTimeline?.jobsTimeline || dayJobsTimeline?.jobs || dayJobsTimeline?.timeline || [];
|
|
57013
|
-
|
|
57014
|
-
// const jobsTimelineData = useMemo(() => {
|
|
57015
|
-
// // Always process data, even if empty, to generate default date range for x-axis
|
|
57016
|
-
// const dataToProcess = (!jobsData || !Array.isArray(jobsData) || jobsData.length === 0)
|
|
57017
|
-
// ? []
|
|
57018
|
-
// : jobsData;
|
|
57019
|
-
|
|
57020
|
-
// // Process data without cumulative calculation (for jobs timeline)
|
|
57021
|
-
// // Try to find value in total, count, jobs, or value fields
|
|
57022
|
-
// return processChartDateData({
|
|
57023
|
-
// mainData: dataToProcess,
|
|
57024
|
-
// isCumulative: false,
|
|
57025
|
-
// valueField: 'total', // Will fallback to count/jobs/value if total doesn't exist
|
|
57026
|
-
// });
|
|
57027
|
-
// }, [jobsData, processChartDateData]);
|
|
57028
|
-
|
|
57029
57077
|
const jobsTimelineData = React.useMemo(() => {
|
|
57030
57078
|
// Prepare data first
|
|
57031
57079
|
const dataToProcess = !jobsData || !Array.isArray(jobsData) || jobsData.length === 0 ? [] : jobsData;
|
|
@@ -57043,7 +57091,12 @@ const JobsTimeline = ({
|
|
|
57043
57091
|
lastNonZeroIndex--;
|
|
57044
57092
|
}
|
|
57045
57093
|
|
|
57046
|
-
//
|
|
57094
|
+
// If all values are 0, return full processed data to show x-axis with default date range
|
|
57095
|
+
if (lastNonZeroIndex < 0) {
|
|
57096
|
+
return processedData;
|
|
57097
|
+
}
|
|
57098
|
+
|
|
57099
|
+
// Otherwise, slice up to last period with data
|
|
57047
57100
|
return processedData.slice(0, lastNonZeroIndex + 1);
|
|
57048
57101
|
}, [jobsData, processChartDateData]);
|
|
57049
57102
|
const maxYValue = React.useMemo(() => {
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/* Isolated Mapbox GL CSS - Scoped to prevent Leaflet conflicts */
|
|
2
|
+
|
|
3
|
+
/* Mapbox GL Core Styles - Scoped with .mapbox-gl-scope */
|
|
4
|
+
.mapbox-gl-scope .mapboxgl-map {
|
|
5
|
+
font: 12px/20px Helvetica Neue, Arial, Helvetica, sans-serif;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
position: relative;
|
|
8
|
+
-webkit-tap-highlight-color: rgb(0 0 0/0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.mapbox-gl-scope .mapboxgl-canvas {
|
|
12
|
+
left: 0;
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.mapbox-gl-scope .mapboxgl-map:-webkit-full-screen {
|
|
18
|
+
height: 100%;
|
|
19
|
+
width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.mapbox-gl-scope .mapboxgl-canary {
|
|
23
|
+
background-color: salmon;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive,
|
|
27
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass {
|
|
28
|
+
cursor: grab;
|
|
29
|
+
-webkit-user-select: none;
|
|
30
|
+
user-select: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer {
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive:active,
|
|
38
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active {
|
|
39
|
+
cursor: grabbing;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,
|
|
43
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas {
|
|
44
|
+
touch-action: pan-x pan-y;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan,
|
|
48
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas {
|
|
49
|
+
touch-action: pinch-zoom;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,
|
|
53
|
+
.mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas {
|
|
54
|
+
touch-action: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Control positioning */
|
|
58
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom,
|
|
59
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-left,
|
|
60
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-right,
|
|
61
|
+
.mapbox-gl-scope .mapboxgl-ctrl-left,
|
|
62
|
+
.mapbox-gl-scope .mapboxgl-ctrl-right,
|
|
63
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top,
|
|
64
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-left,
|
|
65
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-right {
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
position: absolute;
|
|
68
|
+
z-index: 2;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-left {
|
|
72
|
+
left: 0;
|
|
73
|
+
top: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top {
|
|
77
|
+
left: 50%;
|
|
78
|
+
top: 0;
|
|
79
|
+
transform: translateX(-50%);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-right {
|
|
83
|
+
right: 0;
|
|
84
|
+
top: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.mapbox-gl-scope .mapboxgl-ctrl-right {
|
|
88
|
+
right: 0;
|
|
89
|
+
top: 50%;
|
|
90
|
+
transform: translateY(-50%);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-right {
|
|
94
|
+
bottom: 0;
|
|
95
|
+
right: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom {
|
|
99
|
+
bottom: 0;
|
|
100
|
+
left: 50%;
|
|
101
|
+
transform: translateX(-50%);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-left {
|
|
105
|
+
bottom: 0;
|
|
106
|
+
left: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.mapbox-gl-scope .mapboxgl-ctrl-left {
|
|
110
|
+
left: 0;
|
|
111
|
+
top: 50%;
|
|
112
|
+
transform: translateY(-50%);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.mapbox-gl-scope .mapboxgl-ctrl {
|
|
116
|
+
clear: both;
|
|
117
|
+
pointer-events: auto;
|
|
118
|
+
transform: translate(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-left .mapboxgl-ctrl {
|
|
122
|
+
float: left;
|
|
123
|
+
margin: 10px 0 0 10px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top .mapboxgl-ctrl {
|
|
127
|
+
float: left;
|
|
128
|
+
margin: 10px 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.mapbox-gl-scope .mapboxgl-ctrl-top-right .mapboxgl-ctrl {
|
|
132
|
+
float: right;
|
|
133
|
+
margin: 10px 10px 0 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl,
|
|
137
|
+
.mapbox-gl-scope .mapboxgl-ctrl-right .mapboxgl-ctrl {
|
|
138
|
+
float: right;
|
|
139
|
+
margin: 0 10px 10px 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom .mapboxgl-ctrl {
|
|
143
|
+
float: left;
|
|
144
|
+
margin: 10px 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.mapbox-gl-scope .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl,
|
|
148
|
+
.mapbox-gl-scope .mapboxgl-ctrl-left .mapboxgl-ctrl {
|
|
149
|
+
float: left;
|
|
150
|
+
margin: 0 0 10px 10px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Control group styling */
|
|
154
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group {
|
|
155
|
+
background: #fff;
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group:not(:empty) {
|
|
160
|
+
box-shadow: 0 0 0 2px #0000001a;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button {
|
|
164
|
+
background-color: initial;
|
|
165
|
+
border: 0;
|
|
166
|
+
box-sizing: border-box;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
display: block;
|
|
169
|
+
height: 29px;
|
|
170
|
+
outline: none;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
padding: 0;
|
|
173
|
+
width: 29px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button+button {
|
|
177
|
+
border-top: 1px solid #ddd;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mapbox-gl-scope .mapboxgl-ctrl button .mapboxgl-ctrl-icon {
|
|
181
|
+
background-position: 50%;
|
|
182
|
+
background-repeat: no-repeat;
|
|
183
|
+
display: block;
|
|
184
|
+
height: 100%;
|
|
185
|
+
width: 100%;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attrib-button:focus,
|
|
189
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button:focus {
|
|
190
|
+
box-shadow: 0 0 2px 2px #0096ff;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.mapbox-gl-scope .mapboxgl-ctrl button:disabled {
|
|
194
|
+
cursor: not-allowed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.mapbox-gl-scope .mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon {
|
|
198
|
+
opacity: .25;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button:first-child {
|
|
202
|
+
border-radius: 4px 4px 0 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button:last-child {
|
|
206
|
+
border-radius: 0 0 4px 4px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.mapbox-gl-scope .mapboxgl-ctrl-group button:only-child {
|
|
210
|
+
border-radius: inherit;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.mapbox-gl-scope .mapboxgl-ctrl button:not(:disabled):hover {
|
|
214
|
+
background-color: #0000000d;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Marker styles */
|
|
218
|
+
.mapbox-gl-scope .mapboxgl-marker {
|
|
219
|
+
position: absolute;
|
|
220
|
+
z-index: 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.mapbox-gl-scope .mapboxgl-marker svg {
|
|
224
|
+
display: block;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Popup styles */
|
|
228
|
+
.mapbox-gl-scope .mapboxgl-popup {
|
|
229
|
+
position: absolute;
|
|
230
|
+
text-align: center;
|
|
231
|
+
margin-bottom: 20px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.mapbox-gl-scope .mapboxgl-popup-content-wrapper {
|
|
235
|
+
padding: 1px;
|
|
236
|
+
text-align: left;
|
|
237
|
+
border-radius: 12px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.mapbox-gl-scope .mapboxgl-popup-content {
|
|
241
|
+
margin: 13px 24px 13px 20px;
|
|
242
|
+
line-height: 1.3;
|
|
243
|
+
font-size: 13px;
|
|
244
|
+
min-height: 1px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.mapbox-gl-scope .mapboxgl-popup-content p {
|
|
248
|
+
margin: 17px 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.mapbox-gl-scope .mapboxgl-popup-tip-container {
|
|
252
|
+
width: 40px;
|
|
253
|
+
height: 20px;
|
|
254
|
+
position: absolute;
|
|
255
|
+
left: 50%;
|
|
256
|
+
margin-top: -1px;
|
|
257
|
+
margin-left: -20px;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
pointer-events: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.mapbox-gl-scope .mapboxgl-popup-tip {
|
|
263
|
+
width: 17px;
|
|
264
|
+
height: 17px;
|
|
265
|
+
padding: 1px;
|
|
266
|
+
margin: -10px auto 0;
|
|
267
|
+
pointer-events: auto;
|
|
268
|
+
-webkit-transform: rotate(45deg);
|
|
269
|
+
-moz-transform: rotate(45deg);
|
|
270
|
+
-ms-transform: rotate(45deg);
|
|
271
|
+
transform: rotate(45deg);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.mapbox-gl-scope .mapboxgl-popup-content-wrapper,
|
|
275
|
+
.mapbox-gl-scope .mapboxgl-popup-tip {
|
|
276
|
+
background: white;
|
|
277
|
+
color: #333;
|
|
278
|
+
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.mapbox-gl-scope .mapboxgl-popup-close-button {
|
|
282
|
+
position: absolute;
|
|
283
|
+
top: 0;
|
|
284
|
+
right: 0;
|
|
285
|
+
border: none;
|
|
286
|
+
text-align: center;
|
|
287
|
+
width: 24px;
|
|
288
|
+
height: 24px;
|
|
289
|
+
font: 16px/24px Tahoma, Verdana, sans-serif;
|
|
290
|
+
color: #757575;
|
|
291
|
+
text-decoration: none;
|
|
292
|
+
background: transparent;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.mapbox-gl-scope .mapboxgl-popup-close-button:hover,
|
|
296
|
+
.mapbox-gl-scope .mapboxgl-popup-close-button:focus {
|
|
297
|
+
color: #585858;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Attribution */
|
|
301
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution {
|
|
302
|
+
background: #fff;
|
|
303
|
+
background: rgba(255, 255, 255, 0.8);
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution,
|
|
308
|
+
.mapbox-gl-scope .mapboxgl-ctrl-scale-line {
|
|
309
|
+
padding: 0 5px;
|
|
310
|
+
color: #333;
|
|
311
|
+
line-height: 1.4;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution a {
|
|
315
|
+
text-decoration: none;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution a:hover,
|
|
319
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution a:focus {
|
|
320
|
+
text-decoration: underline;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* Hide attribution by default */
|
|
324
|
+
.mapbox-gl-scope .mapboxgl-ctrl-attribution {
|
|
325
|
+
display: none !important;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.mapbox-gl-scope .mapboxgl-ctrl-logo {
|
|
329
|
+
display: none !important;
|
|
330
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import { getFormatDate, getTimeQuantity, processChartDateData, formatDateAxis }
|
|
|
6
6
|
* Provides state management and formatting functions for time-based charts
|
|
7
7
|
*
|
|
8
8
|
* @param {Object} options - Configuration options
|
|
9
|
-
* @param {string} options.defaultFilter - Default time filter ('daily', 'weekly', 'monthly')
|
|
9
|
+
* @param {string} options.defaultFilter - Default time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
10
10
|
* @returns {Object} Time filter state and utilities
|
|
11
11
|
*/
|
|
12
12
|
export const useTimeFilter = ({ defaultFilter = 'monthly' } = {}) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
2
|
import DashboardService from "../services/DashboardService.js";
|
|
3
3
|
import { isErrorResponse, getErrorMessage } from "../../helpers/errorHandling.js";
|
|
4
4
|
import { message } from "antd";
|
|
@@ -15,7 +15,8 @@ export const useWidgetFetch = ({config, getData = DashboardService.getWidget, on
|
|
|
15
15
|
const { stop, defaultData, ...rest} = config;
|
|
16
16
|
const [ data, setData ] = useState(defaultData || {});
|
|
17
17
|
const [ loading, setLoading ] = useState(false);
|
|
18
|
-
|
|
18
|
+
const [ initFetchDone, setInitFetchDone ] = useState(false);
|
|
19
|
+
const isMounted = useRef(true);
|
|
19
20
|
|
|
20
21
|
const fetchData = async () => {
|
|
21
22
|
if (stop) {
|
|
@@ -26,27 +27,39 @@ export const useWidgetFetch = ({config, getData = DashboardService.getWidget, on
|
|
|
26
27
|
|
|
27
28
|
try {
|
|
28
29
|
const { data } = await getData(rest);
|
|
30
|
+
if (!isMounted.current) return;
|
|
29
31
|
setData(data || defaultData);
|
|
30
32
|
if (isErrorResponse(data)) {
|
|
31
33
|
const errorMessage = getErrorMessage(data);
|
|
32
34
|
message.error(errorMessage);
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
|
-
onFetch()
|
|
37
|
+
onFetch();
|
|
38
|
+
setInitFetchDone(true);
|
|
36
39
|
} catch (err) {
|
|
37
40
|
console.log(err);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
if (isMounted.current) {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
41
46
|
};
|
|
42
47
|
|
|
43
48
|
useEffect(() => {
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
isMounted.current = true;
|
|
50
|
+
return () => {
|
|
51
|
+
isMounted.current = false;
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
fetchData();
|
|
57
|
+
}, [JSON.stringify(config)]);
|
|
46
58
|
|
|
47
59
|
return {
|
|
48
60
|
data,
|
|
49
61
|
loading,
|
|
50
|
-
setData
|
|
62
|
+
setData,
|
|
63
|
+
initFetchDone
|
|
51
64
|
}
|
|
52
65
|
}
|
|
@@ -7,6 +7,7 @@ const selectOptions = [
|
|
|
7
7
|
{ label: "Daily", value: "daily" },
|
|
8
8
|
{ label: "Weekly", value: "weekly" },
|
|
9
9
|
{ label: "Monthly", value: "monthly" },
|
|
10
|
+
{ label: "Yearly", value: "yearly" },
|
|
10
11
|
];
|
|
11
12
|
|
|
12
13
|
const JobsTimeline = ({
|
|
@@ -20,20 +21,6 @@ const JobsTimeline = ({
|
|
|
20
21
|
? dayJobsTimeline
|
|
21
22
|
: (dayJobsTimeline?.jobsTimeline || dayJobsTimeline?.jobs || dayJobsTimeline?.timeline || []);
|
|
22
23
|
|
|
23
|
-
// const jobsTimelineData = useMemo(() => {
|
|
24
|
-
// // Always process data, even if empty, to generate default date range for x-axis
|
|
25
|
-
// const dataToProcess = (!jobsData || !Array.isArray(jobsData) || jobsData.length === 0)
|
|
26
|
-
// ? []
|
|
27
|
-
// : jobsData;
|
|
28
|
-
|
|
29
|
-
// // Process data without cumulative calculation (for jobs timeline)
|
|
30
|
-
// // Try to find value in total, count, jobs, or value fields
|
|
31
|
-
// return processChartDateData({
|
|
32
|
-
// mainData: dataToProcess,
|
|
33
|
-
// isCumulative: false,
|
|
34
|
-
// valueField: 'total', // Will fallback to count/jobs/value if total doesn't exist
|
|
35
|
-
// });
|
|
36
|
-
// }, [jobsData, processChartDateData]);
|
|
37
24
|
|
|
38
25
|
const jobsTimelineData = useMemo(() => {
|
|
39
26
|
// Prepare data first
|
|
@@ -54,7 +41,12 @@ const JobsTimeline = ({
|
|
|
54
41
|
lastNonZeroIndex--;
|
|
55
42
|
}
|
|
56
43
|
|
|
57
|
-
//
|
|
44
|
+
// If all values are 0, return full processed data to show x-axis with default date range
|
|
45
|
+
if (lastNonZeroIndex < 0) {
|
|
46
|
+
return processedData;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Otherwise, slice up to last period with data
|
|
58
50
|
return processedData.slice(0, lastNonZeroIndex + 1);
|
|
59
51
|
}, [jobsData, processChartDateData]);
|
|
60
52
|
|
|
@@ -7,6 +7,7 @@ const selectOptions = [
|
|
|
7
7
|
{ label: "Daily", value: "daily" },
|
|
8
8
|
{ label: "Weekly", value: "weekly" },
|
|
9
9
|
{ label: "Monthly", value: "monthly" },
|
|
10
|
+
{ label: "Yearly", value: "yearly" },
|
|
10
11
|
];
|
|
11
12
|
|
|
12
13
|
const PlantingActivitiesTimeline = ({
|
|
@@ -34,6 +35,11 @@ const PlantingActivitiesTimeline = ({
|
|
|
34
35
|
lastNonZeroIndex--;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
// If all values are 0, return full processed data to show x-axis with default date range
|
|
39
|
+
if (lastNonZeroIndex < 0) {
|
|
40
|
+
return processedData;
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
return processedData.slice(0, lastNonZeroIndex + 1);
|
|
38
44
|
}, [activitiesTimelineChart, processChartDateData]);
|
|
39
45
|
|
package/src/@daf/pages/Summary/Activities/PlantingCycle/components/PlantingLocations/index.jsx
CHANGED
|
@@ -64,11 +64,11 @@ const PlantingLocations = ({
|
|
|
64
64
|
) || locations[0];
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
const area = matchingLocation?.perimeter ? matchingLocation.perimeter.map(coord =>
|
|
68
|
+
Array.isArray(coord) && coord.length >= 2
|
|
69
|
+
? [coord[0], coord[1]]
|
|
70
|
+
: coord
|
|
71
|
+
) : null;
|
|
72
72
|
|
|
73
73
|
// Only include area if it has at least 3 valid coordinates
|
|
74
74
|
const validArea = area && Array.isArray(area) && area.length >= 3 ? area : null;
|
|
@@ -6,18 +6,19 @@ import { renderNumber } from './numbers.js';
|
|
|
6
6
|
* Formats a date based on the time filter
|
|
7
7
|
* @param {dayjs.Dayjs} date - The date to format
|
|
8
8
|
* @param {boolean} breakLine - Whether to add a line break (for tooltips)
|
|
9
|
-
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
|
|
9
|
+
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
10
10
|
* @returns {string} Formatted date string
|
|
11
11
|
*/
|
|
12
12
|
export const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') => {
|
|
13
13
|
switch (timeFilter) {
|
|
14
14
|
case "daily":
|
|
15
15
|
return date.format("DD/MM");
|
|
16
|
-
case "weekly"
|
|
16
|
+
case "weekly":
|
|
17
17
|
return `W${renderNumber(date.week())}`;
|
|
18
|
+
case "yearly":
|
|
19
|
+
return date.format("YYYY");
|
|
18
20
|
default:
|
|
19
21
|
// Monthly format: "Dec 24", "Jan 25", etc.
|
|
20
|
-
|
|
21
22
|
return breakLine
|
|
22
23
|
? `${capitalize(date.format("MMM"))}\n${date.format("YY")}`
|
|
23
24
|
: `${capitalize(date.format("MMM"))} ${date.format("YY")}`;
|
|
@@ -26,15 +27,20 @@ export const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') =
|
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Gets the time quantity string for dayjs operations
|
|
29
|
-
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
|
|
30
|
-
* @returns {string} Time quantity string ('days', 'weeks', 'months')
|
|
30
|
+
* @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
31
|
+
* @returns {string} Time quantity string ('days', 'weeks', 'months', 'years')
|
|
31
32
|
*/
|
|
32
33
|
export const getTimeQuantity = (timeFilter = 'monthly') => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
switch (timeFilter) {
|
|
35
|
+
case "daily":
|
|
36
|
+
return "days";
|
|
37
|
+
case "weekly":
|
|
38
|
+
return "weeks";
|
|
39
|
+
case "yearly":
|
|
40
|
+
return "years";
|
|
41
|
+
default:
|
|
42
|
+
return "months";
|
|
43
|
+
}
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -62,6 +68,9 @@ export const getPreviousGraphData = (dates, startDate, timeFilter, valueField =
|
|
|
62
68
|
case "weekly":
|
|
63
69
|
isBeforeStart = date.isBefore(startDate, 'week');
|
|
64
70
|
break;
|
|
71
|
+
case "yearly":
|
|
72
|
+
isBeforeStart = date.isBefore(startDate, 'year');
|
|
73
|
+
break;
|
|
65
74
|
default:
|
|
66
75
|
isBeforeStart = date.isBefore(startDate, 'month');
|
|
67
76
|
break;
|
|
@@ -86,7 +95,7 @@ export const getPreviousGraphData = (dates, startDate, timeFilter, valueField =
|
|
|
86
95
|
* Processes chart data with time filtering support
|
|
87
96
|
* @param {Object} params - Parameters object
|
|
88
97
|
* @param {Array} params.mainData - Array of data objects with date and value fields
|
|
89
|
-
* @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly')
|
|
98
|
+
* @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly', 'yearly')
|
|
90
99
|
* @param {Object} params.filters - Optional filters object with timeframe
|
|
91
100
|
* @param {boolean} params.isCumulative - Whether to calculate cumulative values (default: false)
|
|
92
101
|
* @param {string} params.valueField - Field name to extract value from (default: 'total', also checks 'count', 'jobs', 'value')
|
|
@@ -114,6 +123,9 @@ export const processChartDateData = ({
|
|
|
114
123
|
} else if (filter === "weekly") {
|
|
115
124
|
start = start.startOf('week');
|
|
116
125
|
end = end.startOf('week');
|
|
126
|
+
} else if (filter === "yearly") {
|
|
127
|
+
start = start.startOf('year');
|
|
128
|
+
end = end.startOf('year');
|
|
117
129
|
} else {
|
|
118
130
|
start = start.startOf('month');
|
|
119
131
|
end = end.startOf('month');
|
|
@@ -133,9 +145,20 @@ export const processChartDateData = ({
|
|
|
133
145
|
cumulativeScore = hasPreviousData ? previousCumulativeScore : 0;
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
// Get the period unit for comparison
|
|
149
|
+
const getPeriodUnit = (f) => {
|
|
150
|
+
switch (f) {
|
|
151
|
+
case "daily": return "day";
|
|
152
|
+
case "weekly": return "week";
|
|
153
|
+
case "yearly": return "year";
|
|
154
|
+
default: return "month";
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const periodUnit = getPeriodUnit(filter);
|
|
158
|
+
|
|
136
159
|
// Loop until we reach the end date
|
|
137
160
|
let currentDate = start.clone();
|
|
138
|
-
while (currentDate.isBefore(end) || currentDate.isSame(end,
|
|
161
|
+
while (currentDate.isBefore(end) || currentDate.isSame(end, periodUnit)) {
|
|
139
162
|
// Filter data points that fall within this period
|
|
140
163
|
const score = isEmpty ? 0 : dates
|
|
141
164
|
.filter((d) => {
|
|
@@ -146,6 +169,8 @@ export const processChartDateData = ({
|
|
|
146
169
|
case "weekly":
|
|
147
170
|
return dayjs(d.date, "YYYY-MM-DD").week() === currentDate.week() &&
|
|
148
171
|
dayjs(d.date, "YYYY-MM-DD").year() === currentDate.year();
|
|
172
|
+
case "yearly":
|
|
173
|
+
return dayjs(d.date, "YYYY-MM-DD").year() === currentDate.year();
|
|
149
174
|
default:
|
|
150
175
|
return (
|
|
151
176
|
dayjs(d.date, "YYYY-MM-DD").format("YYYY-MM") ===
|
|
@@ -187,7 +212,7 @@ export const processChartDateData = ({
|
|
|
187
212
|
export const formatDateAxis = (label, getFormatDateFn) => {
|
|
188
213
|
if (!label) return label;
|
|
189
214
|
|
|
190
|
-
// Check if label is already in the correct format (MMM YY, DD/MM, or
|
|
215
|
+
// Check if label is already in the correct format (MMM YY, DD/MM, W#, or YYYY)
|
|
191
216
|
// If it matches our format patterns, return as-is
|
|
192
217
|
if (typeof label === 'string') {
|
|
193
218
|
// Check for MMM YY format (e.g., "Dec 24", "Jan 25")
|
|
@@ -202,6 +227,10 @@ export const formatDateAxis = (label, getFormatDateFn) => {
|
|
|
202
227
|
if (/^W\d+$/.test(label)) {
|
|
203
228
|
return label;
|
|
204
229
|
}
|
|
230
|
+
// Check for YYYY format (e.g., "2024", "2025")
|
|
231
|
+
if (/^\d{4}$/.test(label)) {
|
|
232
|
+
return label;
|
|
233
|
+
}
|
|
205
234
|
}
|
|
206
235
|
|
|
207
236
|
// Otherwise, try to parse and format it
|
|
@@ -209,7 +238,7 @@ export const formatDateAxis = (label, getFormatDateFn) => {
|
|
|
209
238
|
|
|
210
239
|
// If first attempt fails, try parsing as ISO date string
|
|
211
240
|
if (!date.isValid() && typeof label === 'string') {
|
|
212
|
-
date = dayjs(label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
|
|
241
|
+
date = dayjs(label, ['YYYY-MM-DD', 'YYYY-MM', 'YYYY', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
|
|
213
242
|
}
|
|
214
243
|
|
|
215
244
|
// If it's a valid date, format it using getFormatDate
|