@levi-gemcommerce/analytics 0.0.1-dev.7 → 0.0.1-dev.8

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 (50) hide show
  1. package/dist/esm/components/GSelectableMetricChartCard/GSelectableMetricChartCard.d.ts +5 -3
  2. package/dist/esm/components/GSelectableMetricChartCard/MetricChartTab.d.ts +2 -2
  3. package/dist/esm/components/MetricDonutChartCard/MetricDonutChartCard.d.ts +16 -0
  4. package/dist/esm/components/MetricDonutChartCard/index.d.ts +2 -0
  5. package/dist/esm/components/MetricDonutChartCard/utils/donut-chart.d.ts +20 -0
  6. package/dist/esm/components/MetricDonutChartCard/utils/index.d.ts +1 -0
  7. package/dist/esm/components/SingleMetricChartCard/SingleMetricChartCard.d.ts +3 -1
  8. package/dist/esm/components/common/chart/MetricChart.d.ts +5 -1
  9. package/dist/esm/components/common/chart/MetricDonutChartSkeleton.d.ts +1 -0
  10. package/dist/esm/components/common/chart/index.d.ts +1 -0
  11. package/dist/esm/components/index.d.ts +1 -0
  12. package/dist/esm/constants/breakdown-targets.d.ts +5 -0
  13. package/dist/esm/constants/index.d.ts +1 -0
  14. package/dist/esm/core/gemxql/helpers/extractQueryData.d.ts +1 -1
  15. package/dist/esm/hooks/index.d.ts +2 -0
  16. package/dist/esm/hooks/useFormatLineChartData.d.ts +11 -0
  17. package/dist/esm/hooks/useWindowSize.d.ts +18 -0
  18. package/dist/esm/index.d.ts +2 -0
  19. package/dist/esm/index.js +668 -52
  20. package/dist/esm/index.mjs +668 -52
  21. package/dist/esm/providers/MetricChartProvider.d.ts +5 -1
  22. package/dist/esm/types/chart.d.ts +15 -1
  23. package/dist/esm/types/metric.d.ts +4 -1
  24. package/dist/esm/types.js +3 -0
  25. package/dist/esm/types.mjs +3 -0
  26. package/dist/style.css +1 -1
  27. package/dist/umd/esm/components/GSelectableMetricChartCard/GSelectableMetricChartCard.d.ts +5 -3
  28. package/dist/umd/esm/components/GSelectableMetricChartCard/MetricChartTab.d.ts +2 -2
  29. package/dist/umd/esm/components/MetricDonutChartCard/MetricDonutChartCard.d.ts +16 -0
  30. package/dist/umd/esm/components/MetricDonutChartCard/index.d.ts +2 -0
  31. package/dist/umd/esm/components/MetricDonutChartCard/utils/donut-chart.d.ts +20 -0
  32. package/dist/umd/esm/components/MetricDonutChartCard/utils/index.d.ts +1 -0
  33. package/dist/umd/esm/components/SingleMetricChartCard/SingleMetricChartCard.d.ts +3 -1
  34. package/dist/umd/esm/components/common/chart/MetricChart.d.ts +5 -1
  35. package/dist/umd/esm/components/common/chart/MetricDonutChartSkeleton.d.ts +1 -0
  36. package/dist/umd/esm/components/common/chart/index.d.ts +1 -0
  37. package/dist/umd/esm/components/index.d.ts +1 -0
  38. package/dist/umd/esm/constants/breakdown-targets.d.ts +5 -0
  39. package/dist/umd/esm/constants/index.d.ts +1 -0
  40. package/dist/umd/esm/core/gemxql/helpers/extractQueryData.d.ts +1 -1
  41. package/dist/umd/esm/hooks/index.d.ts +2 -0
  42. package/dist/umd/esm/hooks/useFormatLineChartData.d.ts +11 -0
  43. package/dist/umd/esm/hooks/useWindowSize.d.ts +18 -0
  44. package/dist/umd/esm/index.d.ts +2 -0
  45. package/dist/umd/esm/providers/MetricChartProvider.d.ts +5 -1
  46. package/dist/umd/esm/types/chart.d.ts +15 -1
  47. package/dist/umd/esm/types/metric.d.ts +4 -1
  48. package/dist/umd/index.js +1 -1
  49. package/dist/umd/types.js +1 -1
  50. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  "use client"
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import '@tanstack/react-query';
4
+ import dayjs from 'dayjs';
5
+ import quarterOfYear from 'dayjs/plugin/quarterOfYear';
6
+ import timezone from 'dayjs/plugin/timezone';
7
+ import utc from 'dayjs/plugin/utc';
8
+ import React, { useMemo, forwardRef, useState, useRef, useImperativeHandle, useEffect } from 'react';
3
9
  import { SkeletonDisplayText, Box, Popover, InlineStack, BlockStack, Text, List, Card, SkeletonBodyText, Icon } from '@shopify/polaris';
4
- import React, { useMemo, forwardRef, useState, useRef, useImperativeHandle } from 'react';
5
- import { PolarisVizProvider, LineChart } from '@shopify/polaris-viz';
10
+ import { PolarisVizProvider, LineChart, DonutChart } from '@shopify/polaris-viz';
6
11
 
7
12
  var EMetricKey;
8
13
  (function (EMetricKey) {
@@ -22,6 +27,9 @@ var EMetricKey;
22
27
  EMetricKey["AOV"] = "aov";
23
28
  EMetricKey["REVENUE"] = "revenue";
24
29
  EMetricKey["RPV"] = "revenue_per_visitor";
30
+ EMetricKey["VISITOR_ITEMS"] = "visitor_items";
31
+ EMetricKey["DEVICE_ITEMS"] = "device_items";
32
+ EMetricKey["TRAFFIC_SOURCE_ITEMS"] = "traffic_source_items";
25
33
  })(EMetricKey || (EMetricKey = {}));
26
34
 
27
35
  const ANALYTICS_METRIC_TOOLTIP = {
@@ -97,8 +105,524 @@ const ANALYTICS_METRIC_TOOLTIP = {
97
105
  title: 'Sessions with cart additions',
98
106
  content: 'Sessions in your online store in which a visitor added item to the cart',
99
107
  },
108
+ [EMetricKey.VISITOR_ITEMS]: {
109
+ title: 'Sessions by visitor type',
110
+ content: '<p>Numbers of new and returning visitors</p>',
111
+ contentList: [
112
+ 'New visitor: who accesses your store for the first time after GemX installation',
113
+ 'Returning visitor: who comes back to your store after GemX installation',
114
+ ],
115
+ },
116
+ [EMetricKey.DEVICE_ITEMS]: {
117
+ title: 'Sessions by device type',
118
+ content: `Sessions in your online store by the visitor's device type`,
119
+ },
120
+ [EMetricKey.TRAFFIC_SOURCE_ITEMS]: {
121
+ title: 'Sessions by traffic source',
122
+ content: 'Sessions on your page by where visitors come from',
123
+ },
124
+ };
125
+
126
+ const NONE_VALUE = 'None';
127
+ const PLACEHOLDER_VALUE$1 = '-';
128
+
129
+ const TOTALS_SUFFIX = '___totals';
130
+ const COMPARE_PREFIX = 'comparison___';
131
+ const COMPARE_SUFFIX = '___previous_period';
132
+ const COMPARE_TOTALS_SUFFIX = `${COMPARE_SUFFIX}${TOTALS_SUFFIX}`;
133
+
134
+ const OPERATOR_IS = 'is';
135
+
136
+ var EAnalyticDataType;
137
+ (function (EAnalyticDataType) {
138
+ EAnalyticDataType["DATE"] = "DATE";
139
+ EAnalyticDataType["ARRAY"] = "ARRAY";
140
+ EAnalyticDataType["OBJECT"] = "OBJECT";
141
+ EAnalyticDataType["STRING"] = "STRING";
142
+ EAnalyticDataType["INTEGER"] = "INTEGER";
143
+ EAnalyticDataType["CURRENCY"] = "CURRENCY";
144
+ EAnalyticDataType["PERCENT"] = "PERCENT";
145
+ EAnalyticDataType["DURATION"] = "DURATION";
146
+ EAnalyticDataType["MONTH"] = "MONTH_TIMESTAMP";
147
+ EAnalyticDataType["QUARTER"] = "QUARTER_TIMESTAMP";
148
+ EAnalyticDataType["DAY"] = "DAY_TIMESTAMP";
149
+ EAnalyticDataType["WEEK"] = "WEEK_TIMESTAMP";
150
+ EAnalyticDataType["YEAR"] = "YEAR_TIMESTAMP";
151
+ EAnalyticDataType["HOUR"] = "HOUR_TIMESTAMP";
152
+ })(EAnalyticDataType || (EAnalyticDataType = {}));
153
+ var EAnalyticColumnKey;
154
+ (function (EAnalyticColumnKey) {
155
+ EAnalyticColumnKey["CAMPAIGNS"] = "experiments";
156
+ EAnalyticColumnKey["VISITOR_ITEMS"] = "visitor_items";
157
+ EAnalyticColumnKey["DEVICE_ITEMS"] = "device_items";
158
+ EAnalyticColumnKey["TRAFFIC_SOURCE_ITEMS"] = "traffic_source_items";
159
+ })(EAnalyticColumnKey || (EAnalyticColumnKey = {}));
160
+
161
+ var EAnalyticMode;
162
+ (function (EAnalyticMode) {
163
+ EAnalyticMode["ALL_SESSION"] = "ALL_SESSION";
164
+ EAnalyticMode["FIRST_SESSION"] = "FIRST_SESSION";
165
+ EAnalyticMode["PAGE_ONLY"] = "PAGE_ONLY";
166
+ })(EAnalyticMode || (EAnalyticMode = {}));
167
+
168
+ var EAnalyticSource;
169
+ (function (EAnalyticSource) {
170
+ EAnalyticSource["SESSIONS"] = "sessions";
171
+ EAnalyticSource["SALES"] = "sales";
172
+ })(EAnalyticSource || (EAnalyticSource = {}));
173
+
174
+ var EVisitorType;
175
+ (function (EVisitorType) {
176
+ EVisitorType["NEW"] = "new";
177
+ EVisitorType["RETURNING"] = "returning";
178
+ })(EVisitorType || (EVisitorType = {}));
179
+ var EDeviceType;
180
+ (function (EDeviceType) {
181
+ EDeviceType["DESKTOP"] = "desktop";
182
+ EDeviceType["MOBILE"] = "mobile";
183
+ EDeviceType["TABLET"] = "tablet";
184
+ })(EDeviceType || (EDeviceType = {}));
185
+ var ETrafficSourceType;
186
+ (function (ETrafficSourceType) {
187
+ ETrafficSourceType["DIRECT"] = "direct";
188
+ ETrafficSourceType["EMAIL"] = "email";
189
+ ETrafficSourceType["REFERRAL"] = "referral";
190
+ ETrafficSourceType["ORGANIC_SOCIAL"] = "organic-social";
191
+ ETrafficSourceType["ORGANIC_SEARCH"] = "organic-search";
192
+ ETrafficSourceType["PAID_SOCIAL"] = "paid-social";
193
+ ETrafficSourceType["PAID_SEARCH"] = "paid-search";
194
+ ETrafficSourceType["SMS"] = "sms";
195
+ })(ETrafficSourceType || (ETrafficSourceType = {}));
196
+
197
+ var EComparisonOperator;
198
+ (function (EComparisonOperator) {
199
+ EComparisonOperator["EQ"] = "=";
200
+ EComparisonOperator["IN"] = "IN";
201
+ EComparisonOperator["LIKE"] = "LIKE";
202
+ })(EComparisonOperator || (EComparisonOperator = {}));
203
+ var EGroupOperator;
204
+ (function (EGroupOperator) {
205
+ EGroupOperator["OR"] = "OR";
206
+ EGroupOperator["AND"] = "AND";
207
+ })(EGroupOperator || (EGroupOperator = {}));
208
+
209
+ var EFilterField;
210
+ (function (EFilterField) {
211
+ EFilterField["DEVICE"] = "device";
212
+ EFilterField["DEVICES"] = "devices";
213
+ EFilterField["VISITOR"] = "visitor_type";
214
+ EFilterField["VISITORS"] = "visitor_types";
215
+ EFilterField["TRAFFIC_SOURCE"] = "traffic_source";
216
+ EFilterField["TRAFFIC_SOURCES"] = "traffic_sources";
217
+ EFilterField["VERSION"] = "version";
218
+ EFilterField["VERSIONS"] = "versions";
219
+ EFilterField["SINGLE_PAGE"] = "page_path";
220
+ EFilterField["LIST_PAGE"] = "page_paths";
221
+ EFilterField["GROUP_CAMPAIGN_ITEM"] = "group_campaign_item";
222
+ EFilterField["GROUP_CAMPAIGN_ITEMS"] = "group_campaign_items";
223
+ EFilterField["SINGLE_CAMPAIGN"] = "experiment_id";
224
+ EFilterField["GROUP_CAMPAIGN"] = "experiment_group_id";
225
+ EFilterField["CAMPAIGN_VERSION_ID"] = "version_id";
226
+ EFilterField["GROUP_CAMPAIGN_VERSION_ID"] = "group_version_id";
227
+ })(EFilterField || (EFilterField = {}));
228
+
229
+ var EOperatorField;
230
+ (function (EOperatorField) {
231
+ EOperatorField["DEVICE_OPERATOR"] = "deviceOperator";
232
+ EOperatorField["VISITOR_OPERATOR"] = "visitorOperator";
233
+ EOperatorField["TRAFFIC_SOURCE_OPERATOR"] = "trafficSourceOperator";
234
+ EOperatorField["VERSION_OPERATOR"] = "versionOperator";
235
+ EOperatorField["PAGE_OPERATOR"] = "pageOperator";
236
+ EOperatorField["CAMPAIGN_ITEM_OPERATOR"] = "campaignItemOperator";
237
+ })(EOperatorField || (EOperatorField = {}));
238
+
239
+ /**
240
+ * Controls which totals columns are appended to the query result.
241
+ *
242
+ * - NONE → no totals, GROUP BY only.
243
+ * - TOTALS → adds `<metric>___totals` (grand total only). Use with single-dimension GROUP BY.
244
+ * - ALL → adds subtotals per dimension group + grand total. Use with multi-dimension GROUP BY.
245
+ */
246
+ var EGroupWithClause;
247
+ (function (EGroupWithClause) {
248
+ EGroupWithClause["NONE"] = "";
249
+ EGroupWithClause["TOTALS"] = "TOTALS";
250
+ EGroupWithClause["ALL"] = "WITH GROUP_TOTALS, TOTALS";
251
+ })(EGroupWithClause || (EGroupWithClause = {}));
252
+
253
+ var EOrderDirectionType;
254
+ (function (EOrderDirectionType) {
255
+ EOrderDirectionType["ASC"] = "ASC";
256
+ EOrderDirectionType["DESC"] = "DESC";
257
+ })(EOrderDirectionType || (EOrderDirectionType = {}));
258
+
259
+ var EPageMetric;
260
+ (function (EPageMetric) {
261
+ EPageMetric["PAGE_ITEMS"] = "page_items";
262
+ EPageMetric["PAGE_PATHS"] = "page_paths";
263
+ })(EPageMetric || (EPageMetric = {}));
264
+ var EPageDimension;
265
+ (function (EPageDimension) {
266
+ EPageDimension["PAGE_PATH"] = "page_path";
267
+ })(EPageDimension || (EPageDimension = {}));
268
+ var EPageField;
269
+ (function (EPageField) {
270
+ EPageField["SHOPIFY_PAGE_ID"] = "shopify_page_id";
271
+ EPageField["LOCATION_PATH"] = "location_path";
272
+ EPageField["PAGE_TYPE"] = "page_type";
273
+ EPageField["PAGE_TITLE"] = "page_title";
274
+ EPageField["PAGE_PATH"] = "page_path";
275
+ EPageField["TEMPLATE_SUFFIX"] = "template_suffix";
276
+ })(EPageField || (EPageField = {}));
277
+
278
+ var ERowReaderMode;
279
+ (function (ERowReaderMode) {
280
+ ERowReaderMode["DEFAULT"] = "DEFAULT";
281
+ ERowReaderMode["COMPARISON"] = "COMPARISON";
282
+ ERowReaderMode["TOTALS"] = "TOTALS";
283
+ ERowReaderMode["COMPARISON_TOTALS"] = "COMPARISON_TOTALS";
284
+ })(ERowReaderMode || (ERowReaderMode = {}));
285
+
286
+ var ETimeDimension;
287
+ (function (ETimeDimension) {
288
+ ETimeDimension["HOUR"] = "hour";
289
+ ETimeDimension["DAY"] = "day";
290
+ ETimeDimension["WEEK"] = "week";
291
+ ETimeDimension["MONTH"] = "month";
292
+ ETimeDimension["QUARTER"] = "quarter";
293
+ ETimeDimension["YEAR"] = "year";
294
+ })(ETimeDimension || (ETimeDimension = {}));
295
+
296
+ ({
297
+ [ERowReaderMode.DEFAULT]: { prefix: '', suffix: '' },
298
+ [ERowReaderMode.COMPARISON]: { prefix: COMPARE_PREFIX, suffix: COMPARE_SUFFIX },
299
+ [ERowReaderMode.TOTALS]: { prefix: '', suffix: TOTALS_SUFFIX },
300
+ [ERowReaderMode.COMPARISON_TOTALS]: { prefix: COMPARE_PREFIX, suffix: COMPARE_TOTALS_SUFFIX },
301
+ });
302
+
303
+ EGroupOperator.OR;
304
+
305
+ [
306
+ {
307
+ operator: EOperatorField.DEVICE_OPERATOR,
308
+ singleField: EFilterField.DEVICE,
309
+ multiField: EFilterField.DEVICES,
310
+ fieldName: EFilterField.DEVICE,
311
+ },
312
+ {
313
+ operator: EOperatorField.VISITOR_OPERATOR,
314
+ singleField: EFilterField.VISITOR,
315
+ multiField: EFilterField.VISITORS,
316
+ fieldName: EFilterField.VISITOR,
317
+ },
318
+ {
319
+ operator: EOperatorField.TRAFFIC_SOURCE_OPERATOR,
320
+ singleField: EFilterField.TRAFFIC_SOURCE,
321
+ multiField: EFilterField.TRAFFIC_SOURCES,
322
+ fieldName: EFilterField.TRAFFIC_SOURCE,
323
+ },
324
+ {
325
+ operator: EOperatorField.VERSION_OPERATOR,
326
+ singleField: EFilterField.VERSION,
327
+ multiField: EFilterField.VERSIONS,
328
+ fieldName: EFilterField.VERSION,
329
+ },
330
+ {
331
+ operator: EOperatorField.PAGE_OPERATOR,
332
+ singleField: EFilterField.SINGLE_PAGE,
333
+ multiField: EFilterField.LIST_PAGE,
334
+ fieldName: EFilterField.SINGLE_PAGE,
335
+ },
336
+ {
337
+ operator: EOperatorField.CAMPAIGN_ITEM_OPERATOR,
338
+ singleField: EFilterField.GROUP_CAMPAIGN_ITEM,
339
+ multiField: EFilterField.GROUP_CAMPAIGN_ITEMS,
340
+ condition: EFilterField.GROUP_CAMPAIGN,
341
+ fieldName: EFilterField.SINGLE_CAMPAIGN,
342
+ },
343
+ {
344
+ operatorVal: OPERATOR_IS,
345
+ singleField: EFilterField.SINGLE_CAMPAIGN,
346
+ multiField: EFilterField.GROUP_CAMPAIGN,
347
+ },
348
+ ];
349
+
350
+ dayjs.extend(utc);
351
+ dayjs.extend(timezone);
352
+ dayjs.extend(quarterOfYear);
353
+ let tz = 'UTC';
354
+ const dayjsTz = (date) => {
355
+ if (!date)
356
+ return dayjs().tz(tz);
357
+ return dayjs(date).tz(tz);
358
+ };
359
+
360
+ const TRIM_DECIMAL_ZEROS_REGEX = /\.0+$/;
361
+ const DEFAULT_DECIMALS = 2;
362
+ const trimDecimalZeros = (number) => {
363
+ return `${number}`.replace(TRIM_DECIMAL_ZEROS_REGEX, '');
364
+ };
365
+ const cleanDecimal = (number, decimals = DEFAULT_DECIMALS) => {
366
+ return trimDecimalZeros(number.toFixed(decimals));
367
+ };
368
+
369
+ /**
370
+ * Utility function to calculate percentage and format it.
371
+ * @param part - The part value.
372
+ * @param total - The total value.
373
+ * @param decimals - The number of decimal places to format the percentage to.
374
+ * @returns The formatted percentage as a string.
375
+ */
376
+ const PERCENTAGE_THRESHOLD = 0.005;
377
+ const PERCENTAGE_THRESHOLD_STRING = '~0%';
378
+ const calcPercentage = (part, total, decimals = DEFAULT_DECIMALS) => {
379
+ if (typeof part !== 'number' || !total)
380
+ return undefined;
381
+ if (part === 0)
382
+ return 0;
383
+ const percentage = (part / total) * 100;
384
+ return parseFloat(cleanDecimal(percentage, decimals));
385
+ };
386
+ const isLessThanThreshold = (percentage) => percentage > 0 && percentage < PERCENTAGE_THRESHOLD;
387
+ const calcPercentageString = (part, total, decimals = 2) => {
388
+ const percentage = calcPercentage(part, total, decimals);
389
+ if (typeof percentage !== 'number')
390
+ return undefined;
391
+ if (isLessThanThreshold(percentage)) {
392
+ return PERCENTAGE_THRESHOLD_STRING;
393
+ }
394
+ return `${percentage}%`;
395
+ };
396
+ const formatPercentage = (percentage, decimals = DEFAULT_DECIMALS) => {
397
+ if (isLessThanThreshold(percentage)) {
398
+ return PERCENTAGE_THRESHOLD_STRING;
399
+ }
400
+ return `${cleanDecimal(percentage, decimals)}%`;
401
+ };
402
+
403
+ var AnalyticInterval;
404
+ (function (AnalyticInterval) {
405
+ AnalyticInterval["DAY"] = "DAY";
406
+ AnalyticInterval["HOUR"] = "HOUR";
407
+ AnalyticInterval["MONTH"] = "MONTH";
408
+ AnalyticInterval["QUARTER"] = "QUARTER";
409
+ AnalyticInterval["WEEK"] = "WEEK";
410
+ AnalyticInterval["YEAR"] = "YEAR";
411
+ })(AnalyticInterval || (AnalyticInterval = {}));
412
+ function numberWithCommas(x) {
413
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
414
+ }
415
+ const SECONDS_IN_MINUTE = 60;
416
+ const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * SECONDS_IN_MINUTE;
417
+ const getTimeDurationLabel = (valueInSeconds) => {
418
+ let data = valueInSeconds;
419
+ if (!Number.isFinite(valueInSeconds) || valueInSeconds == null) {
420
+ data = 0;
421
+ }
422
+ const fixedValue = cleanDecimal(data);
423
+ if (data >= SECONDS_IN_HOUR) {
424
+ const hours = Math.floor(data / SECONDS_IN_HOUR);
425
+ const minutes = Math.floor((data % SECONDS_IN_HOUR) / SECONDS_IN_MINUTE);
426
+ const seconds = Math.floor(data % SECONDS_IN_MINUTE);
427
+ return `${hours}h ${minutes}m ${seconds}s`;
428
+ }
429
+ else if (data >= SECONDS_IN_MINUTE) {
430
+ const minutes = Math.floor(data / SECONDS_IN_MINUTE);
431
+ const seconds = Math.floor(data % SECONDS_IN_MINUTE);
432
+ return `${minutes}m ${seconds}s`;
433
+ }
434
+ else {
435
+ return `${fixedValue}s`;
436
+ }
437
+ };
438
+ const getFormattedByInterval = (value, interval, options) => {
439
+ if (!value)
440
+ return '';
441
+ const optionFormat = options?.isExpandDetail
442
+ ? {
443
+ formatHouse: 'MMM D, h:mm A',
444
+ formatDay: 'MMM D, YYYY',
445
+ formatMonth: 'MMM YYYY',
446
+ formatYear: 'MMM YYYY',
447
+ }
448
+ : {
449
+ formatHouse: 'h A',
450
+ formatDay: 'MMM D',
451
+ formatMonth: 'MMM YYYY',
452
+ formatYear: 'YYYY',
453
+ };
454
+ switch (interval) {
455
+ case AnalyticInterval.HOUR:
456
+ return dayjsTz(value).format(optionFormat.formatHouse);
457
+ case AnalyticInterval.DAY:
458
+ case AnalyticInterval.WEEK:
459
+ return dayjsTz(value).format(optionFormat.formatDay);
460
+ case AnalyticInterval.MONTH:
461
+ return dayjsTz(value).format(optionFormat.formatMonth);
462
+ case AnalyticInterval.QUARTER: {
463
+ const d = dayjsTz(value);
464
+ return `Q${d.quarter()} ${d.format('YYYY')}`;
465
+ }
466
+ case AnalyticInterval.YEAR:
467
+ return dayjsTz(value).format(optionFormat.formatYear);
468
+ }
469
+ return dayjsTz(value).format(optionFormat.formatMonth);
100
470
  };
101
471
 
472
+ const formatAnalyticDate = (dateString) => {
473
+ if (!dateString)
474
+ return 'None';
475
+ const date = dayjsTz(dateString);
476
+ const now = dayjsTz();
477
+ const yesterday = dayjsTz().subtract(1, 'day');
478
+ const isToday = date.format('YYYY-MM-DD') === now.format('YYYY-MM-DD');
479
+ const isYesterday = date.format('YYYY-MM-DD') === yesterday.format('YYYY-MM-DD');
480
+ if (isToday) {
481
+ return `Today at ${date.format('HH:mm')}`;
482
+ }
483
+ if (isYesterday) {
484
+ return `Yesterday at ${date.format('HH:mm')}`;
485
+ }
486
+ const daysDiff = now.diff(date, 'day');
487
+ const isWithinWeek = daysDiff >= 0 && daysDiff < 7;
488
+ if (isWithinWeek) {
489
+ return `${date.format('dddd')} at ${date.format('HH:mm')}`;
490
+ }
491
+ return `${date.format('MMM D')} at ${date.format('hh:mm a')}`;
492
+ };
493
+
494
+ const formatAnalyticData = ({ value, formatter, getTextPrice, name, }) => {
495
+ const dataTypeIsObjectOrArray = value !== null && (typeof value === 'object' || Array.isArray(value));
496
+ if (dataTypeIsObjectOrArray)
497
+ return value;
498
+ switch (formatter) {
499
+ case EAnalyticDataType.INTEGER: {
500
+ return numberWithCommas((value ?? 0).toString());
501
+ }
502
+ case EAnalyticDataType.CURRENCY: {
503
+ if (!getTextPrice)
504
+ return `${value ?? 0}`;
505
+ return getTextPrice(value, false);
506
+ }
507
+ case EAnalyticDataType.DATE: {
508
+ return formatAnalyticDate(value);
509
+ }
510
+ case EAnalyticDataType.PERCENT: {
511
+ if (typeof value !== 'number')
512
+ return calcPercentageString(0, 1, 2) ?? '';
513
+ return calcPercentageString(value / 100, 1, 2) ?? '';
514
+ }
515
+ case EAnalyticDataType.DURATION: {
516
+ return getTimeDurationLabel(Number(value));
517
+ }
518
+ case EAnalyticDataType.STRING: {
519
+ const fallbackValue = name === EAnalyticColumnKey.CAMPAIGNS ? '' : NONE_VALUE;
520
+ return value ?? fallbackValue;
521
+ }
522
+ case EAnalyticDataType.DAY: {
523
+ return getFormattedByInterval(value, AnalyticInterval.DAY, { isExpandDetail: true });
524
+ }
525
+ case EAnalyticDataType.HOUR: {
526
+ return getFormattedByInterval(value, AnalyticInterval.HOUR, { isExpandDetail: true });
527
+ }
528
+ case EAnalyticDataType.MONTH: {
529
+ return getFormattedByInterval(value, AnalyticInterval.MONTH, { isExpandDetail: true });
530
+ }
531
+ case EAnalyticDataType.YEAR: {
532
+ return getFormattedByInterval(value, AnalyticInterval.YEAR);
533
+ }
534
+ case EAnalyticDataType.WEEK: {
535
+ return getFormattedByInterval(value, AnalyticInterval.WEEK, { isExpandDetail: true });
536
+ }
537
+ case EAnalyticDataType.QUARTER: {
538
+ return getFormattedByInterval(value, AnalyticInterval.QUARTER, {
539
+ isExpandDetail: true,
540
+ });
541
+ }
542
+ case EAnalyticDataType.OBJECT:
543
+ case EAnalyticDataType.ARRAY: {
544
+ return value;
545
+ }
546
+ default:
547
+ return `${value}`;
548
+ }
549
+ };
550
+
551
+ const SESSION_KEY = 'sessions';
552
+ const hasMetricData = (metric) => {
553
+ const sessions = metric?.[SESSION_KEY];
554
+ return typeof sessions === 'number' && sessions > 0;
555
+ };
556
+
557
+ const parseRawJson = (raw) => {
558
+ if (!raw)
559
+ return undefined;
560
+ return typeof raw === 'string' ? JSON.parse(raw) : raw;
561
+ };
562
+ const parseJsonArray = (raw) => {
563
+ try {
564
+ const parsed = parseRawJson(raw);
565
+ if (!Array.isArray(parsed))
566
+ return undefined;
567
+ return parsed;
568
+ }
569
+ catch {
570
+ return undefined;
571
+ }
572
+ };
573
+ const parseBreakdownItems = (raw) => parseJsonArray(raw)?.map((item) => ({ ...item, total: Number(item.total) }));
574
+
575
+ const readNumeric = (metric, key) => {
576
+ const raw = metric?.[key];
577
+ return typeof raw === 'number' ? raw : 0;
578
+ };
579
+
580
+ const useAnalyticData = (getTextPrice) => {
581
+ const formatData = ({ value, formatter, name }) => {
582
+ return formatAnalyticData({ value, formatter, getTextPrice, name });
583
+ };
584
+ const computeMetric = ({ metric, previousMetric, metricKey, formatter, }) => {
585
+ if (!hasMetricData(metric))
586
+ return { value: 0, change: PLACEHOLDER_VALUE$1 };
587
+ const currentValue = readNumeric(metric, metricKey);
588
+ const previousValue = readNumeric(previousMetric, metricKey);
589
+ const value = formatData({ value: currentValue, formatter, name: metricKey });
590
+ if (currentValue === 0 && previousValue !== 0)
591
+ return { value, change: -100 };
592
+ if (previousValue === 0)
593
+ return { value, change: PLACEHOLDER_VALUE$1 };
594
+ const change = ((currentValue - previousValue) / previousValue) * 100;
595
+ return { value, change };
596
+ };
597
+ return { formatData, computeMetric };
598
+ };
599
+
600
+ var GPaginationDirection;
601
+ (function (GPaginationDirection) {
602
+ GPaginationDirection["NEXT"] = "NEXT";
603
+ GPaginationDirection["PREV"] = "PREV";
604
+ })(GPaginationDirection || (GPaginationDirection = {}));
605
+
606
+ const TARGET_VISITOR = [
607
+ { value: EVisitorType.NEW, label: 'New' },
608
+ { value: EVisitorType.RETURNING, label: 'Returning' },
609
+ ];
610
+ const TARGET_DEVICES = [
611
+ { value: EDeviceType.DESKTOP, label: 'Desktop' },
612
+ { value: EDeviceType.TABLET, label: 'Tablet' },
613
+ { value: EDeviceType.MOBILE, label: 'Mobile' },
614
+ ];
615
+ const TARGET_CHANNEL = [
616
+ { value: ETrafficSourceType.DIRECT, label: 'Direct' },
617
+ { value: ETrafficSourceType.EMAIL, label: 'Email' },
618
+ { value: ETrafficSourceType.REFERRAL, label: 'Referral' },
619
+ { value: ETrafficSourceType.ORGANIC_SOCIAL, label: 'Organic social' },
620
+ { value: ETrafficSourceType.ORGANIC_SEARCH, label: 'Organic search' },
621
+ { value: ETrafficSourceType.PAID_SOCIAL, label: 'Paid social' },
622
+ { value: ETrafficSourceType.PAID_SEARCH, label: 'Paid search' },
623
+ { value: ETrafficSourceType.SMS, label: 'SMS' },
624
+ ];
625
+
102
626
  const DEFAULT_CURRENT_PERIOD_LABEL = 'Current';
103
627
  const DEFAULT_PREVIOUS_PERIOD_LABEL = 'Previous';
104
628
  const CHART_MIN_HEIGHT = 228;
@@ -148,32 +672,6 @@ const GBlockCenter = ({ height, align, inlineAlign, display, ...props }) => {
148
672
  }, children: jsx(Box, { ...props }) }));
149
673
  };
150
674
 
151
- const TRIM_DECIMAL_ZEROS_REGEX = /\.0+$/;
152
- const DEFAULT_DECIMALS = 2;
153
- const trimDecimalZeros = (number) => {
154
- return `${number}`.replace(TRIM_DECIMAL_ZEROS_REGEX, '');
155
- };
156
- const cleanDecimal = (number, decimals = DEFAULT_DECIMALS) => {
157
- return trimDecimalZeros(number.toFixed(decimals));
158
- };
159
-
160
- /**
161
- * Utility function to calculate percentage and format it.
162
- * @param part - The part value.
163
- * @param total - The total value.
164
- * @param decimals - The number of decimal places to format the percentage to.
165
- * @returns The formatted percentage as a string.
166
- */
167
- const PERCENTAGE_THRESHOLD = 0.005;
168
- const PERCENTAGE_THRESHOLD_STRING = '~0%';
169
- const isLessThanThreshold = (percentage) => percentage > 0 && percentage < PERCENTAGE_THRESHOLD;
170
- const formatPercentage = (percentage, decimals = DEFAULT_DECIMALS) => {
171
- if (isLessThanThreshold(percentage)) {
172
- return PERCENTAGE_THRESHOLD_STRING;
173
- }
174
- return `${cleanDecimal(percentage, decimals)}%`;
175
- };
176
-
177
675
  function toVal(mix) {
178
676
  if (typeof mix === 'string') {
179
677
  return mix;
@@ -246,7 +744,7 @@ const GTooltipCard = forwardRef((props, ref) => {
246
744
  useImperativeHandle(ref, () => ({ onClose: handleMouseLeave }));
247
745
  return (jsx(TooltipCardWrapper, { className: cls('GTooltipCard cursor-pointer', alignment && ALIGNMENT_MAP[alignment], {
248
746
  'GTooltipCard--text-underline': textDecoration === 'underline',
249
- }), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: jsx(Popover, { ref: popoverRef, activator: !isHideBorder ? (jsx(Box, { borderBlockEndWidth: "025", borderStyle: "dashed", borderColor: "border-tertiary", as: wrapper, ...activatorProps, children: props.children })) : (jsx(InlineStack, { children: props.children })), activatorWrapper: wrapper, onClose: () => { }, active: isMouseEnter, preferredPosition: "below", preferredAlignment: preferredAlignment, children: tooltip && (jsx("div", { className: cls(sizeClass, { 'GTooltipCard-arrow': showArrow }), children: jsx(Box, { padding: "400", children: jsxs(BlockStack, { gap: "200", children: [jsxs(BlockStack, { gap: "100", children: [jsx(Text, { as: "span", variant: "headingSm", fontWeight: "semibold", children: tooltip.title }), jsxs(BlockStack, { gap: "400", children: [jsx(Text, { as: "span", variant: "bodyMd", tone: "subdued", fontWeight: "medium", children: jsx("span", { dangerouslySetInnerHTML: { __html: tooltip.content } }) }), tooltip.contentList && (jsx(List, { type: "bullet", children: tooltip.contentList.map((item) => (jsx(List.Item, { children: jsx(Text, { as: "span", variant: "bodyMd", tone: "subdued", fontWeight: "medium", children: item }) }, item))) }))] })] }), tooltip.formula && (jsx("div", { className: "rounded-md font-mono", style: { fontSize: '12px' }, children: tooltip.formula }))] }) }) })) }) }));
747
+ }), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: jsx(Popover, { ref: popoverRef, activator: !isHideBorder ? (jsx(Box, { borderBlockEndWidth: "025", borderStyle: "dashed", borderColor: "border-tertiary", as: wrapper, ...activatorProps, children: props.children })) : (jsx(InlineStack, { children: props.children })), activatorWrapper: wrapper, onClose: () => { }, active: isMouseEnter, preferredPosition: "below", preferredAlignment: preferredAlignment, children: tooltip && (jsx("div", { className: cls(sizeClass, { 'GTooltipCard-arrow': showArrow }), children: jsx(Box, { padding: "400", children: jsxs(BlockStack, { gap: "200", children: [jsxs(BlockStack, { gap: "100", children: [jsx(Text, { as: "span", variant: "headingSm", fontWeight: "semibold", children: tooltip.title }), jsxs(BlockStack, { gap: "200", children: [jsx(Text, { as: "span", variant: "bodyMd", tone: "subdued", fontWeight: "medium", children: jsx("span", { dangerouslySetInnerHTML: { __html: tooltip.content } }) }), tooltip.contentList && (jsx(List, { type: "bullet", children: tooltip.contentList.map((item) => (jsx(List.Item, { children: jsx(Text, { as: "span", variant: "bodyMd", tone: "subdued", fontWeight: "medium", children: item }) }, item))) }))] })] }), tooltip.formula && (jsx("div", { className: "rounded-md font-mono", style: { fontSize: '12px' }, children: tooltip.formula }))] }) }) })) }) }));
250
748
  });
251
749
  GTooltipCard.displayName = 'GTooltipCard';
252
750
 
@@ -254,7 +752,12 @@ const GChartSkeleton = () => {
254
752
  return jsx(GSkeletonDisplayText, { height: "188px" });
255
753
  };
256
754
 
257
- const MetricChartProvider = ({ children, minHeight = CHART_MIN_HEIGHT }) => {
755
+ const LINE_SERIES_COLORS = {
756
+ comparison: SERIES_COLORS.comparison,
757
+ single: SERIES_COLORS.current,
758
+ all: [...SERIES_COLORS.all],
759
+ };
760
+ const MetricChartProvider = ({ children, minHeight = CHART_MIN_HEIGHT, seriesColors = LINE_SERIES_COLORS, }) => {
258
761
  return (jsx(PolarisVizProvider, { themes: {
259
762
  Light: {
260
763
  chartContainer: {
@@ -265,27 +768,72 @@ const MetricChartProvider = ({ children, minHeight = CHART_MIN_HEIGHT }) => {
265
768
  verticalOverflow: true,
266
769
  horizontalMargin: 0,
267
770
  },
268
- seriesColors: {
269
- comparison: SERIES_COLORS.comparison,
270
- single: SERIES_COLORS.current,
271
- all: [...SERIES_COLORS.all],
272
- },
771
+ seriesColors,
273
772
  },
274
773
  }, children: children }));
275
774
  };
276
775
 
277
- const defaultFormatValue = (value) => String(value);
278
- const MetricChart = ({ lineChartData, isLoading, isEmptyMetricData }) => {
776
+ const useFormatLineChartData = ({ metricKey, columnTypes }) => {
777
+ const { formatData } = useAnalyticData();
778
+ const formatter = metricKey ? columnTypes?.[metricKey] : undefined;
779
+ const formatValue = (value) => {
780
+ return String(formatData({ value, formatter }));
781
+ };
782
+ const yAxisOptions = {
783
+ labelFormatter: (value) => {
784
+ return formatValue(Number(value) || 0);
785
+ },
786
+ };
787
+ return { formatValue, yAxisOptions };
788
+ };
789
+
790
+ const useWindowSize = () => {
791
+ const [windowSize, setWindowSize] = useState(() => ({
792
+ width: typeof window !== 'undefined' ? window.innerWidth : 0,
793
+ height: typeof window !== 'undefined' ? window.innerHeight : 0,
794
+ }));
795
+ const windowWidth = useMemo(() => {
796
+ return {
797
+ xs: windowSize.width <= 768,
798
+ md: 768 < windowSize.width && windowSize.width <= 1024,
799
+ lg: windowSize.width > 1024,
800
+ xsDown: windowSize.width < 768,
801
+ '1200Down': windowSize.width < 1200,
802
+ '1040Down': windowSize.width < 1040,
803
+ };
804
+ }, [windowSize.width]);
805
+ const isMobileTabletView = !windowWidth.lg;
806
+ const isMobileView = windowWidth.xs;
807
+ useEffect(() => {
808
+ const windowSizeHandler = () => {
809
+ setWindowSize({ width: window.innerWidth, height: window.innerHeight });
810
+ };
811
+ window.addEventListener('resize', windowSizeHandler);
812
+ return () => {
813
+ window.removeEventListener('resize', windowSizeHandler);
814
+ };
815
+ }, []);
816
+ return { windowSize, windowWidth, isMobileTabletView, isMobileView };
817
+ };
818
+
819
+ const MetricChart = ({ lineChartData, isLoading, isEmptyMetricData, columnTypes, metricKey, }) => {
820
+ const { formatValue, yAxisOptions } = useFormatLineChartData({ metricKey, columnTypes: columnTypes || {} });
821
+ if (!metricKey) {
822
+ return jsx(Fragment, {});
823
+ }
279
824
  if (isLoading) {
280
825
  return jsx(GChartSkeleton, {});
281
826
  }
282
827
  if (isEmptyMetricData) {
283
828
  return jsx(MetricChartEmpty, { title: "No data yet", description: "Data needs time to gather" });
284
829
  }
285
- return (jsx(MetricChartProvider, { children: jsx(LineChart, { data: lineChartData, yAxisOptions: { labelFormatter: (value) => defaultFormatValue(Number(value) || 0) }, theme: "Light", tooltipOptions: {
286
- keyFormatter: (value) => value,
830
+ return (jsx(MetricChartProvider, { children: jsx(LineChart, { data: lineChartData, yAxisOptions: yAxisOptions, theme: "Light", tooltipOptions: {
831
+ titleFormatter: () => `${ANALYTICS_METRIC_TOOLTIP[metricKey]?.title ?? ''}`,
832
+ keyFormatter: (value) => {
833
+ return value;
834
+ },
287
835
  renderTooltipContent(data) {
288
- return jsx(MetricChartTooltip, { data: data, formatValue: defaultFormatValue });
836
+ return jsx(MetricChartTooltip, { data: data, formatValue: formatValue });
289
837
  },
290
838
  }, showLegend: true }) }));
291
839
  };
@@ -337,6 +885,17 @@ const MetricChartTooltip = ({ data, formatValue }) => {
337
885
  return (jsx("div", { className: "w-fit min-w-[175px]", children: jsx(Card, { padding: '200', children: jsxs(BlockStack, { gap: '100', children: [jsx(Text, { as: "p", variant: "bodySm", fontWeight: "semibold", children: data.formatters?.titleFormatter?.(data.title || '') || data.title }), jsxs(BlockStack, { gap: '100', children: [jsxs(InlineStack, { gap: '400', align: "space-between", blockAlign: "center", children: [jsxs(InlineStack, { gap: '100', blockAlign: "center", children: [jsx("div", { className: "h-[2px] w-[12px] rounded-[10px] bg-[#4FA9EA]" }), jsx(Text, { as: "p", variant: "bodySm", fontWeight: "medium", tone: "subdued", children: currentData.tooltipKey })] }), jsxs(InlineStack, { blockAlign: "center", gap: "100", children: [jsx(Text, { as: "span", variant: "bodySm", fontWeight: "semibold", children: formatValue(currentData.value) }), jsx(MetricPercentage, { change: formatPercent() })] })] }), jsxs(InlineStack, { gap: '400', align: "space-between", blockAlign: "center", children: [jsxs(InlineStack, { gap: '100', blockAlign: "center", children: [jsx("div", { className: "w-[12px] border border-dashed border-[#A1CAE7]" }), jsx(Text, { as: "p", variant: "bodySm", tone: "subdued", fontWeight: "medium", children: previousData.tooltipKey })] }), jsxs(InlineStack, { blockAlign: "center", gap: "100", children: [jsx(Text, { as: "span", variant: "bodySm", fontWeight: "semibold", children: formatValue(previousData.value) }), jsx("div", { className: "opacity-0", children: jsx(MetricPercentage, { change: formatPercent() }) })] })] })] })] }) }) }));
338
886
  };
339
887
 
888
+ const MetricInfoSkeleton = ({ isShowOneLine }) => {
889
+ if (isShowOneLine) {
890
+ return (jsx(Box, { width: "40%", children: jsx(SkeletonBodyText, { lines: 1 }) }));
891
+ }
892
+ return (jsxs(BlockStack, { gap: "200", children: [jsx(Box, { width: "60%", children: jsx(SkeletonBodyText, { lines: 1 }) }), jsx(Box, { width: "40%", children: jsx(SkeletonBodyText, { lines: 1 }) })] }));
893
+ };
894
+
895
+ const MetricDonutChartSkeleton = () => {
896
+ return (jsx(Card, { children: jsxs(BlockStack, { gap: "400", children: [jsx(MetricInfoSkeleton, { isShowOneLine: true }), jsx(GChartSkeleton, {})] }) }));
897
+ };
898
+
340
899
  var SvgChevronRightIcon = function SvgChevronRightIcon(props) {
341
900
  return /*#__PURE__*/React.createElement("svg", Object.assign({
342
901
  viewBox: "0 0 20 20"
@@ -347,13 +906,6 @@ var SvgChevronRightIcon = function SvgChevronRightIcon(props) {
347
906
  };
348
907
  SvgChevronRightIcon.displayName = "ChevronRightIcon";
349
908
 
350
- const MetricInfoSkeleton = ({ isShowOneLine }) => {
351
- if (isShowOneLine) {
352
- return (jsx(Box, { width: "40%", children: jsx(SkeletonBodyText, { lines: 1 }) }));
353
- }
354
- return (jsxs(BlockStack, { gap: "200", children: [jsx(Box, { width: "60%", children: jsx(SkeletonBodyText, { lines: 1 }) }), jsx(Box, { width: "40%", children: jsx(SkeletonBodyText, { lines: 1 }) })] }));
355
- };
356
-
357
909
  const MetricValueSummary = ({ totalValue, hideComparison }) => (jsx(BlockStack, { gap: "200", children: jsxs(InlineStack, { blockAlign: "center", gap: "200", wrap: false, children: [jsx(InlineStack, { blockAlign: "center", gap: "200", children: jsx(Text, { as: "span", variant: "headingSm", children: totalValue.value }) }), !hideComparison && jsx(MetricPercentage, { change: totalValue.change })] }) }));
358
910
 
359
911
  const MetricInfoBlock = ({ item, isHovered, isLoading, hideComparison, titleVariant = 'headingMd', titleFontWeight, onClickTitle, }) => {
@@ -373,7 +925,7 @@ const MetricChartTab = ({ item, isActive, isLoading, hideComparison, onSelect, o
373
925
  return (jsx("div", { className: "w-full cursor-pointer overflow-hidden", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onClick: () => onSelect(item.key), children: jsx(Box, { paddingBlock: "150", paddingInline: "300", borderRadius: "200", background: isHighlighted ? 'bg-surface-active' : undefined, children: jsx(MetricInfoBlock, { item: item, isHovered: isHovered, isLoading: isLoading, hideComparison: hideComparison, titleVariant: "headingSm", titleFontWeight: "semibold", onClickTitle: onClickTitle }) }) }));
374
926
  };
375
927
 
376
- const GSelectableMetricChartCard = ({ metricInfo, dataChart, defaultActiveTab, isLoading, isEmptyMetricData, hideComparison, currentPeriodLabel = DEFAULT_CURRENT_PERIOD_LABEL, previousPeriodLabel = DEFAULT_PREVIOUS_PERIOD_LABEL, }) => {
928
+ const GSelectableMetricChartCard = ({ metricInfo, dataChart, defaultActiveTab, isLoading, isEmptyMetricData, hideComparison, currentPeriodLabel = DEFAULT_CURRENT_PERIOD_LABEL, previousPeriodLabel = DEFAULT_PREVIOUS_PERIOD_LABEL, columnTypes, }) => {
377
929
  const [activeTab, setActiveTab] = useState(defaultActiveTab);
378
930
  const lineChartData = useMemo(() => {
379
931
  const chartData = activeTab ? dataChart[activeTab] : undefined;
@@ -389,12 +941,76 @@ const GSelectableMetricChartCard = ({ metricInfo, dataChart, defaultActiveTab, i
389
941
  gridTemplateColumns: `repeat(${metricInfo.length}, 1fr)`,
390
942
  gap: '16px',
391
943
  marginBottom: '16px',
392
- }, children: metricInfo.map((item) => (jsx(MetricChartTab, { item: item, isActive: activeTab === item.key, isLoading: isLoading, hideComparison: hideComparison, onSelect: setActiveTab }, item.key))) }), jsx(MetricChart, { lineChartData: lineChartData, isLoading: isLoading, isEmptyMetricData: isEmptyMetricData })] }));
944
+ }, children: metricInfo.map((item) => (jsx(MetricChartTab, { item: item, isActive: activeTab === item.key, isLoading: isLoading, hideComparison: hideComparison, onSelect: setActiveTab }, item.key))) }), jsx(MetricChart, { lineChartData: lineChartData, isLoading: isLoading, isEmptyMetricData: isEmptyMetricData, metricKey: activeTab, columnTypes: columnTypes })] }));
945
+ };
946
+
947
+ const calculatePercentageChange = (current, previous) => {
948
+ if (current === 0 && previous === 0) {
949
+ return undefined;
950
+ }
951
+ if (previous === 0) {
952
+ return current > 0 ? '100%' : '0%';
953
+ }
954
+ const change = ((current - previous) / previous) * 100;
955
+ return `${Math.abs(change).toFixed(0)}%`;
956
+ };
957
+ const computeDonutData = ({ name, currentValue, prevValue, hasPreviousData, }) => ({
958
+ name,
959
+ data: [{ key: 'value', value: currentValue }],
960
+ metadata: {
961
+ trend: {
962
+ value: hasPreviousData ? calculatePercentageChange(currentValue, prevValue) : undefined,
963
+ direction: currentValue >= prevValue ? 'upward' : 'downward',
964
+ trend: currentValue >= prevValue ? 'positive' : 'negative',
965
+ },
966
+ },
967
+ });
968
+ const getTotalCountByType = (stats, type) => {
969
+ const item = stats?.find((stat) => stat?.type?.toLowerCase() === type.toLowerCase());
970
+ return item?.total ?? 0;
971
+ };
972
+ const buildBreakdownDonutData = ({ targets, metricKey, totalsRow, comparisonTotalsRow, sort, }) => {
973
+ const items = parseBreakdownItems(totalsRow?.[metricKey]);
974
+ const comparisonItems = parseBreakdownItems(comparisonTotalsRow?.[metricKey]);
975
+ const hasPreviousData = !!comparisonTotalsRow?.[metricKey];
976
+ const result = targets.map(({ value, label }) => computeDonutData({
977
+ name: label,
978
+ currentValue: getTotalCountByType(items, value),
979
+ prevValue: getTotalCountByType(comparisonItems, value),
980
+ hasPreviousData,
981
+ }));
982
+ const getValue = (item) => item.data[0]?.value ?? 0;
983
+ return sort ? [...result].sort((a, b) => getValue(b) - getValue(a)) : result;
984
+ };
985
+
986
+ const DONUT_CHART_MIN_HEIGHT = 294;
987
+ const COMPACT_MAX_WIDTH = 1500;
988
+ const MetricDonutChartCard = ({ label, metricKey, targets, totalsRow, comparisonTotalsRow, sort, isLoading, isEmptyMetricData, minHeight = DONUT_CHART_MIN_HEIGHT, onClick, }) => {
989
+ const tooltip = ANALYTICS_METRIC_TOOLTIP[metricKey];
990
+ const { windowWidth, windowSize } = useWindowSize();
991
+ const [isHovered, setIsHovered] = useState(false);
992
+ const data = useMemo(() => buildBreakdownDonutData({ targets, metricKey, totalsRow, comparisonTotalsRow, sort }), [targets, metricKey, totalsRow, comparisonTotalsRow, sort]);
993
+ if (isLoading) {
994
+ return jsx(MetricDonutChartSkeleton, {});
995
+ }
996
+ const formatValue = (value) => {
997
+ return numberWithCommas(`${value}`);
998
+ };
999
+ const handleClickTitle = (event) => {
1000
+ event.stopPropagation();
1001
+ onClick();
1002
+ };
1003
+ const isCompactView = windowWidth.lg && windowSize.width < COMPACT_MAX_WIDTH;
1004
+ return (jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: jsx(Card, { children: jsxs(BlockStack, { gap: "200", children: [jsx(InlineStack, { children: jsx("div", { className: "hover:cursor-pointer hover:text-[--p-color-text-link-hover]", onClick: handleClickTitle, children: jsxs(InlineStack, { children: [jsx(GTooltipCard, { tooltip: tooltip, children: jsx(Text, { as: "h3", variant: "headingMd", children: label }) }), isHovered && jsx(Icon, { source: SvgChevronRightIcon, tone: "inherit" })] }) }) }), jsx("div", { className: cls('flex items-center justify-center', {
1005
+ 'max-h-[250px] overflow-hidden': isCompactView,
1006
+ }), children: isEmptyMetricData ? (jsx(MetricChartEmpty, { title: "No data yet", description: "Data needs time to gather" })) : (jsx(MetricChartProvider, { minHeight: minHeight, seriesColors: {}, children: jsx(DonutChart, { data: data, legendPosition: "left", showLegendValues: true, showLegend: true, theme: "Light", tooltipOptions: {
1007
+ valueFormatter: formatValue,
1008
+ }, labelFormatter: formatValue }) })) })] }) }) }));
393
1009
  };
394
1010
 
395
- const SingleMetricChartCard = ({ metricInfo, lineChartData, isLoading, hideComparison, isEmptyMetricData, onClickTitle, }) => {
1011
+ const SingleMetricChartCard = ({ metricInfo, lineChartData, isLoading, hideComparison, columnTypes, isEmptyMetricData, onClickTitle, }) => {
396
1012
  const [isHovered, setIsHovered] = useState(false);
397
- return (jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: jsx(Card, { children: jsxs(BlockStack, { gap: "200", children: [jsx(MetricInfoBlock, { item: metricInfo, isHovered: isHovered, isLoading: isLoading, hideComparison: hideComparison, onClickTitle: onClickTitle }), jsx(MetricChart, { lineChartData: lineChartData, isLoading: isLoading, isEmptyMetricData: isEmptyMetricData })] }) }) }));
1013
+ return (jsx("div", { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: jsx(Card, { children: jsxs(BlockStack, { gap: "200", children: [jsx(MetricInfoBlock, { item: metricInfo, isHovered: isHovered, isLoading: isLoading, hideComparison: hideComparison, onClickTitle: onClickTitle }), jsx(MetricChart, { lineChartData: lineChartData, isLoading: isLoading, isEmptyMetricData: isEmptyMetricData, columnTypes: columnTypes, metricKey: metricInfo.key })] }) }) }));
398
1014
  };
399
1015
 
400
- export { EMetricKey, GSelectableMetricChartCard, SingleMetricChartCard };
1016
+ export { ANALYTICS_METRIC_TOOLTIP, CHART_MIN_HEIGHT, DEFAULT_CURRENT_PERIOD_LABEL, DEFAULT_PREVIOUS_PERIOD_LABEL, EAnalyticColumnKey, EDeviceType, EMetricKey, ETrafficSourceType, EVisitorType, GSelectableMetricChartCard, MetricDonutChartCard, PLACEHOLDER_VALUE, SERIES_COLORS, SingleMetricChartCard, TARGET_CHANNEL, TARGET_DEVICES, TARGET_VISITOR, THUMB_PRODUCT_DEFAULT, TREND_TONE };