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.
@@ -0,0 +1,226 @@
1
+ import dayjs from 'dayjs';
2
+ import { capitalize } from '../../helpers/StringHelper.js';
3
+ import { renderNumber } from './numbers.js';
4
+
5
+ /**
6
+ * Formats a date based on the time filter
7
+ * @param {dayjs.Dayjs} date - The date to format
8
+ * @param {boolean} breakLine - Whether to add a line break (for tooltips)
9
+ * @param {string} timeFilter - The time filter ('daily', 'weekly', 'monthly')
10
+ * @returns {string} Formatted date string
11
+ */
12
+ export const getFormatDate = (date, breakLine = false, timeFilter = 'monthly') => {
13
+ switch (timeFilter) {
14
+ case "daily":
15
+ return date.format("DD/MM");
16
+ case "weekly" :
17
+ return `W${renderNumber(date.week())}`;
18
+ default:
19
+ // Monthly format: "Dec 24", "Jan 25", etc.
20
+
21
+ return breakLine
22
+ ? `${capitalize(date.format("MMM"))}\n${date.format("YY")}`
23
+ : `${capitalize(date.format("MMM"))} ${date.format("YY")}`;
24
+ }
25
+ };
26
+
27
+ /**
28
+ * 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')
31
+ */
32
+ export const getTimeQuantity = (timeFilter = 'monthly') => {
33
+ return timeFilter === "monthly"
34
+ ? "months"
35
+ : timeFilter === "daily"
36
+ ? "days"
37
+ : "weeks";
38
+ };
39
+
40
+ /**
41
+ * Gets previous cumulative score from data before start date
42
+ * @param {Array} dates - Array of data objects with date field
43
+ * @param {dayjs.Dayjs} startDate - The start date
44
+ * @param {string} timeFilter - The time filter
45
+ * @param {string} valueField - The field name to extract value from (default: 'total')
46
+ * @returns {Object} Object with hasPreviousData, previousCumulativeScore, previousMaxScore
47
+ */
48
+ export const getPreviousGraphData = (dates, startDate, timeFilter, valueField = 'total') => {
49
+ let previousCumulativeScore = 0;
50
+ let previousMaxScore = 0;
51
+ let hasPreviousData = false;
52
+
53
+ dates.forEach((d) => {
54
+ const date = dayjs(d.date, "YYYY-MM-DD");
55
+ if (!date.isValid()) return;
56
+
57
+ let isBeforeStart = false;
58
+ switch (timeFilter) {
59
+ case "daily":
60
+ isBeforeStart = date.isBefore(startDate, 'day');
61
+ break;
62
+ case "weekly":
63
+ isBeforeStart = date.isBefore(startDate, 'week');
64
+ break;
65
+ default:
66
+ isBeforeStart = date.isBefore(startDate, 'month');
67
+ break;
68
+ }
69
+
70
+ if (isBeforeStart) {
71
+ hasPreviousData = true;
72
+ const value = Number(d[valueField] || d.count || d.jobs || d.value || 0) || 0;
73
+ previousCumulativeScore += value;
74
+ previousMaxScore = Math.max(previousMaxScore, value);
75
+ }
76
+ });
77
+
78
+ return {
79
+ hasPreviousData,
80
+ previousCumulativeScore,
81
+ previousMaxScore,
82
+ };
83
+ };
84
+
85
+ /**
86
+ * Processes chart data with time filtering support
87
+ * @param {Object} params - Parameters object
88
+ * @param {Array} params.mainData - Array of data objects with date and value fields
89
+ * @param {string} params.timeFilter - Time filter ('daily', 'weekly', 'monthly')
90
+ * @param {Object} params.filters - Optional filters object with timeframe
91
+ * @param {boolean} params.isCumulative - Whether to calculate cumulative values (default: false)
92
+ * @param {string} params.valueField - Field name to extract value from (default: 'total', also checks 'count', 'jobs', 'value')
93
+ * @returns {Array} Processed chart data array
94
+ */
95
+ export const processChartDateData = ({
96
+ mainData,
97
+ timeFilter: filter,
98
+ filters = {},
99
+ isCumulative = false,
100
+ valueField = 'total'
101
+ }) => {
102
+ if (!mainData || !Array.isArray(mainData) || mainData.length === 0) {
103
+ return [];
104
+ }
105
+
106
+ const timeQuantity = getTimeQuantity(filter);
107
+ const dates = mainData;
108
+
109
+ const _data = [];
110
+ let end = filters?.timeframe?.endDate || dayjs();
111
+ let start = filters?.timeframe?.startDate || dayjs().add(-12, timeQuantity);
112
+
113
+ // Normalize start and end to period boundaries
114
+ if (filter === "daily") {
115
+ start = start.startOf('day');
116
+ end = end.startOf('day');
117
+ } else if (filter === "weekly") {
118
+ start = start.startOf('week');
119
+ end = end.startOf('week');
120
+ } else {
121
+ start = start.startOf('month');
122
+ end = end.startOf('month');
123
+ }
124
+
125
+ let i = 0;
126
+ let cumulativeScore = 0;
127
+
128
+ // Calculate initial cumulative value if needed
129
+ if (isCumulative) {
130
+ const { hasPreviousData, previousCumulativeScore } = getPreviousGraphData(
131
+ dates,
132
+ start,
133
+ filter,
134
+ valueField
135
+ );
136
+ cumulativeScore = hasPreviousData ? previousCumulativeScore : 0;
137
+ }
138
+
139
+ // Loop until we reach the end date
140
+ let currentDate = start.clone();
141
+ while (currentDate.isBefore(end) || currentDate.isSame(end, filter === "daily" ? "day" : filter === "weekly" ? "week" : "month")) {
142
+ // Filter data points that fall within this period
143
+ const score = dates
144
+ .filter((d) => {
145
+ if (!d.date) return false;
146
+ switch (filter) {
147
+ case "daily":
148
+ return d.date === currentDate.format("YYYY-MM-DD");
149
+ case "weekly":
150
+ return dayjs(d.date, "YYYY-MM-DD").week() === currentDate.week() &&
151
+ dayjs(d.date, "YYYY-MM-DD").year() === currentDate.year();
152
+ default:
153
+ return (
154
+ dayjs(d.date, "YYYY-MM-DD").format("YYYY-MM") ===
155
+ currentDate.format("YYYY-MM")
156
+ );
157
+ }
158
+ })
159
+ .reduce((a, b) => {
160
+ const value = Number(b[valueField] || b.count || b.jobs || b.value || 0) || 0;
161
+ return a + value;
162
+ }, 0);
163
+
164
+ if (isCumulative) {
165
+ cumulativeScore += score;
166
+ }
167
+
168
+ _data.push({
169
+ date: getFormatDate(currentDate, false, filter),
170
+ value: isCumulative ? cumulativeScore : score,
171
+ period: score, // Period value for tooltip
172
+ jobs: score, // For compatibility with jobs field
173
+ month: currentDate.format('YYYY-MM-DD'), // For compatibility
174
+ key: i,
175
+ });
176
+
177
+ currentDate = currentDate.add(1, timeQuantity);
178
+ i++;
179
+ }
180
+
181
+ return _data;
182
+ };
183
+
184
+ /**
185
+ * Formats date axis labels, checking if already formatted
186
+ * @param {string} label - The label to format
187
+ * @param {Function} getFormatDateFn - Function to format dates
188
+ * @returns {string} Formatted date string
189
+ */
190
+ export const formatDateAxis = (label, getFormatDateFn) => {
191
+ if (!label) return label;
192
+
193
+ // Check if label is already in the correct format (MMM YY, DD/MM, or W#)
194
+ // If it matches our format patterns, return as-is
195
+ if (typeof label === 'string') {
196
+ // Check for MMM YY format (e.g., "Dec 24", "Jan 25")
197
+ if (/^[A-Z][a-z]{2} \d{2}$/.test(label)) {
198
+ return label;
199
+ }
200
+ // Check for DD/MM format (e.g., "03/11")
201
+ if (/^\d{2}\/\d{2}$/.test(label)) {
202
+ return label;
203
+ }
204
+ // Check for W# format (e.g., "W1", "W12")
205
+ if (/^W\d+$/.test(label)) {
206
+ return label;
207
+ }
208
+ }
209
+
210
+ // Otherwise, try to parse and format it
211
+ let date = dayjs(label);
212
+
213
+ // If first attempt fails, try parsing as ISO date string
214
+ if (!date.isValid() && typeof label === 'string') {
215
+ date = dayjs(label, ['YYYY-MM-DD', 'YYYY-MM', 'MMM YY', 'MMM YYYY', 'DD/MM'], true);
216
+ }
217
+
218
+ // If it's a valid date, format it using getFormatDate
219
+ if (date.isValid() && getFormatDateFn) {
220
+ return getFormatDateFn(date, false);
221
+ }
222
+
223
+ // Return as-is if we can't parse it
224
+ return label;
225
+ };
226
+