@vendure/dashboard 3.4.3-master-202509250229 → 3.5.0-minor-202509261210
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/dist/plugin/api/api-extensions.js +11 -14
- package/dist/plugin/api/metrics.resolver.d.ts +2 -2
- package/dist/plugin/api/metrics.resolver.js +2 -2
- package/dist/plugin/config/metrics-strategies.d.ts +9 -9
- package/dist/plugin/config/metrics-strategies.js +6 -6
- package/dist/plugin/constants.d.ts +2 -0
- package/dist/plugin/constants.js +3 -1
- package/dist/plugin/dashboard.plugin.js +13 -0
- package/dist/plugin/service/metrics.service.d.ts +3 -3
- package/dist/plugin/service/metrics.service.js +37 -53
- package/dist/plugin/types.d.ts +9 -12
- package/dist/plugin/types.js +7 -11
- package/dist/vite/utils/compiler.js +2 -0
- package/dist/vite/vite-plugin-vendure-dashboard.js +2 -2
- package/package.json +4 -4
- package/src/app/routes/_authenticated/_collections/collections.tsx +7 -2
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +15 -2
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +14 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +10 -0
- package/src/app/routes/_authenticated/_products/components/product-option-group-badge.tsx +19 -0
- package/src/app/routes/_authenticated/_products/components/product-options-table.tsx +111 -0
- package/src/app/routes/_authenticated/_products/product-option-groups.graphql.ts +103 -0
- package/src/app/routes/_authenticated/_products/products.graphql.ts +13 -1
- package/src/app/routes/_authenticated/_products/products.tsx +27 -3
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +26 -9
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +181 -0
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +208 -0
- package/src/app/routes/_authenticated/_zones/components/zone-countries-sheet.tsx +4 -1
- package/src/app/routes/_authenticated/index.tsx +41 -24
- package/src/lib/components/data-display/json.tsx +16 -1
- package/src/lib/components/data-input/index.ts +3 -0
- package/src/lib/components/data-input/slug-input.tsx +296 -0
- package/src/lib/components/data-table/add-filter-menu.tsx +13 -6
- package/src/lib/components/data-table/data-table-bulk-action-item.tsx +38 -1
- package/src/lib/components/data-table/data-table-context.tsx +91 -0
- package/src/lib/components/data-table/data-table-filter-badge.tsx +9 -5
- package/src/lib/components/data-table/data-table-view-options.tsx +17 -8
- package/src/lib/components/data-table/data-table.tsx +146 -94
- package/src/lib/components/data-table/global-views-bar.tsx +97 -0
- package/src/lib/components/data-table/global-views-sheet.tsx +11 -0
- package/src/lib/components/data-table/manage-global-views-button.tsx +26 -0
- package/src/lib/components/data-table/my-views-button.tsx +47 -0
- package/src/lib/components/data-table/refresh-button.tsx +12 -3
- package/src/lib/components/data-table/save-view-button.tsx +45 -0
- package/src/lib/components/data-table/save-view-dialog.tsx +113 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +3 -1
- package/src/lib/components/data-table/user-views-sheet.tsx +11 -0
- package/src/lib/components/data-table/views-sheet.tsx +297 -0
- package/src/lib/components/date-range-picker.tsx +184 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +59 -32
- package/src/lib/components/ui/button.tsx +1 -1
- package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +29 -2
- package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +10 -7
- package/src/lib/framework/dashboard-widget/metrics-widget/metrics-widget.graphql.ts +9 -3
- package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +19 -75
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +33 -0
- package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +319 -9
- package/src/lib/framework/document-introspection/add-custom-fields.ts +60 -31
- package/src/lib/framework/document-introspection/get-document-structure.spec.ts +1 -159
- package/src/lib/framework/document-introspection/include-only-selected-list-fields.spec.ts +1840 -0
- package/src/lib/framework/document-introspection/include-only-selected-list-fields.ts +940 -0
- package/src/lib/framework/document-introspection/testing-utils.ts +161 -0
- package/src/lib/framework/extension-api/display-component-extensions.tsx +2 -0
- package/src/lib/framework/extension-api/types/data-table.ts +62 -4
- package/src/lib/framework/extension-api/types/navigation.ts +16 -0
- package/src/lib/framework/form-engine/utils.ts +34 -0
- package/src/lib/framework/page/list-page.tsx +289 -4
- package/src/lib/framework/page/use-extended-router.tsx +59 -17
- package/src/lib/graphql/api.ts +4 -2
- package/src/lib/graphql/graphql-env.d.ts +13 -10
- package/src/lib/hooks/use-extended-list-query.ts +5 -0
- package/src/lib/hooks/use-saved-views.ts +230 -0
- package/src/lib/index.ts +15 -0
- package/src/lib/types/saved-views.ts +39 -0
- package/src/lib/utils/saved-views-utils.ts +40 -0
|
@@ -6,33 +6,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.adminApiExtensions = void 0;
|
|
7
7
|
const graphql_tag_1 = __importDefault(require("graphql-tag"));
|
|
8
8
|
exports.adminApiExtensions = (0, graphql_tag_1.default) `
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
type: MetricType!
|
|
9
|
+
type DashboardMetricSummary {
|
|
10
|
+
type: DashboardMetricType!
|
|
12
11
|
title: String!
|
|
13
|
-
entries: [
|
|
12
|
+
entries: [DashboardMetricSummaryEntry!]!
|
|
14
13
|
}
|
|
15
|
-
enum
|
|
16
|
-
Daily
|
|
17
|
-
}
|
|
18
|
-
enum MetricType {
|
|
14
|
+
enum DashboardMetricType {
|
|
19
15
|
OrderCount
|
|
20
16
|
OrderTotal
|
|
21
17
|
AverageOrderValue
|
|
22
18
|
}
|
|
23
|
-
type
|
|
19
|
+
type DashboardMetricSummaryEntry {
|
|
24
20
|
label: String!
|
|
25
21
|
value: Float!
|
|
26
22
|
}
|
|
27
|
-
input
|
|
28
|
-
|
|
29
|
-
types: [MetricType!]!
|
|
23
|
+
input DashboardMetricSummaryInput {
|
|
24
|
+
types: [DashboardMetricType!]!
|
|
30
25
|
refresh: Boolean
|
|
26
|
+
startDate: DateTime!
|
|
27
|
+
endDate: DateTime!
|
|
31
28
|
}
|
|
32
29
|
extend type Query {
|
|
33
30
|
"""
|
|
34
|
-
Get metrics for the given
|
|
31
|
+
Get metrics for the given date range and metric types.
|
|
35
32
|
"""
|
|
36
|
-
|
|
33
|
+
dashboardMetricSummary(input: DashboardMetricSummaryInput): [DashboardMetricSummary!]!
|
|
37
34
|
}
|
|
38
35
|
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RequestContext } from '@vendure/core';
|
|
2
2
|
import { MetricsService } from '../service/metrics.service.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DashboardMetricSummary, DashboardMetricSummaryInput } from '../types.js';
|
|
4
4
|
export declare class MetricsResolver {
|
|
5
5
|
private service;
|
|
6
6
|
constructor(service: MetricsService);
|
|
7
|
-
|
|
7
|
+
dashboardMetricSummary(ctx: RequestContext, input: DashboardMetricSummaryInput): Promise<DashboardMetricSummary[]>;
|
|
8
8
|
}
|
|
@@ -20,7 +20,7 @@ let MetricsResolver = class MetricsResolver {
|
|
|
20
20
|
constructor(service) {
|
|
21
21
|
this.service = service;
|
|
22
22
|
}
|
|
23
|
-
async
|
|
23
|
+
async dashboardMetricSummary(ctx, input) {
|
|
24
24
|
return this.service.getMetrics(ctx, input);
|
|
25
25
|
}
|
|
26
26
|
};
|
|
@@ -33,7 +33,7 @@ __decorate([
|
|
|
33
33
|
__metadata("design:type", Function),
|
|
34
34
|
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
35
35
|
__metadata("design:returntype", Promise)
|
|
36
|
-
], MetricsResolver.prototype, "
|
|
36
|
+
], MetricsResolver.prototype, "dashboardMetricSummary", null);
|
|
37
37
|
exports.MetricsResolver = MetricsResolver = __decorate([
|
|
38
38
|
(0, graphql_1.Resolver)(),
|
|
39
39
|
__metadata("design:paramtypes", [metrics_service_js_1.MetricsService])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RequestContext } from '@vendure/core';
|
|
2
2
|
import { MetricData } from '../service/metrics.service.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DashboardMetricSummaryEntry, DashboardMetricType } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Calculate your metric data based on the given input.
|
|
6
6
|
* Be careful with heavy queries and calculations,
|
|
@@ -8,32 +8,32 @@ import { MetricInterval, MetricSummaryEntry, MetricType } from '../types.js';
|
|
|
8
8
|
*
|
|
9
9
|
*/
|
|
10
10
|
export interface MetricCalculation {
|
|
11
|
-
type:
|
|
11
|
+
type: DashboardMetricType;
|
|
12
12
|
getTitle(ctx: RequestContext): string;
|
|
13
|
-
calculateEntry(ctx: RequestContext,
|
|
13
|
+
calculateEntry(ctx: RequestContext, data: MetricData): DashboardMetricSummaryEntry;
|
|
14
14
|
}
|
|
15
15
|
export declare function getMonthName(monthNr: number): string;
|
|
16
16
|
/**
|
|
17
17
|
* Calculates the average order value per month/week
|
|
18
18
|
*/
|
|
19
19
|
export declare class AverageOrderValueMetric implements MetricCalculation {
|
|
20
|
-
readonly type =
|
|
20
|
+
readonly type = DashboardMetricType.AverageOrderValue;
|
|
21
21
|
getTitle(ctx: RequestContext): string;
|
|
22
|
-
calculateEntry(ctx: RequestContext,
|
|
22
|
+
calculateEntry(ctx: RequestContext, data: MetricData): DashboardMetricSummaryEntry;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Calculates number of orders
|
|
26
26
|
*/
|
|
27
27
|
export declare class OrderCountMetric implements MetricCalculation {
|
|
28
|
-
readonly type =
|
|
28
|
+
readonly type = DashboardMetricType.OrderCount;
|
|
29
29
|
getTitle(ctx: RequestContext): string;
|
|
30
|
-
calculateEntry(ctx: RequestContext,
|
|
30
|
+
calculateEntry(ctx: RequestContext, data: MetricData): DashboardMetricSummaryEntry;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Calculates order total
|
|
34
34
|
*/
|
|
35
35
|
export declare class OrderTotalMetric implements MetricCalculation {
|
|
36
|
-
readonly type =
|
|
36
|
+
readonly type = DashboardMetricType.OrderTotal;
|
|
37
37
|
getTitle(ctx: RequestContext): string;
|
|
38
|
-
calculateEntry(ctx: RequestContext,
|
|
38
|
+
calculateEntry(ctx: RequestContext, data: MetricData): DashboardMetricSummaryEntry;
|
|
39
39
|
}
|
|
@@ -12,12 +12,12 @@ function getMonthName(monthNr) {
|
|
|
12
12
|
*/
|
|
13
13
|
class AverageOrderValueMetric {
|
|
14
14
|
constructor() {
|
|
15
|
-
this.type = types_js_1.
|
|
15
|
+
this.type = types_js_1.DashboardMetricType.AverageOrderValue;
|
|
16
16
|
}
|
|
17
17
|
getTitle(ctx) {
|
|
18
18
|
return 'average-order-value';
|
|
19
19
|
}
|
|
20
|
-
calculateEntry(ctx,
|
|
20
|
+
calculateEntry(ctx, data) {
|
|
21
21
|
const label = data.date.toISOString();
|
|
22
22
|
if (!data.orders.length) {
|
|
23
23
|
return {
|
|
@@ -39,12 +39,12 @@ exports.AverageOrderValueMetric = AverageOrderValueMetric;
|
|
|
39
39
|
*/
|
|
40
40
|
class OrderCountMetric {
|
|
41
41
|
constructor() {
|
|
42
|
-
this.type = types_js_1.
|
|
42
|
+
this.type = types_js_1.DashboardMetricType.OrderCount;
|
|
43
43
|
}
|
|
44
44
|
getTitle(ctx) {
|
|
45
45
|
return 'order-count';
|
|
46
46
|
}
|
|
47
|
-
calculateEntry(ctx,
|
|
47
|
+
calculateEntry(ctx, data) {
|
|
48
48
|
const label = data.date.toISOString();
|
|
49
49
|
return {
|
|
50
50
|
label,
|
|
@@ -58,12 +58,12 @@ exports.OrderCountMetric = OrderCountMetric;
|
|
|
58
58
|
*/
|
|
59
59
|
class OrderTotalMetric {
|
|
60
60
|
constructor() {
|
|
61
|
-
this.type = types_js_1.
|
|
61
|
+
this.type = types_js_1.DashboardMetricType.OrderTotal;
|
|
62
62
|
}
|
|
63
63
|
getTitle(ctx) {
|
|
64
64
|
return 'order-totals';
|
|
65
65
|
}
|
|
66
|
-
calculateEntry(ctx,
|
|
66
|
+
calculateEntry(ctx, data) {
|
|
67
67
|
const label = data.date.toISOString();
|
|
68
68
|
return {
|
|
69
69
|
label,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { RwPermissionDefinition } from '@vendure/core';
|
|
1
2
|
export declare const DEFAULT_APP_PATH: string;
|
|
2
3
|
export declare const loggerCtx = "DashboardPlugin";
|
|
3
4
|
export declare const defaultLanguage = "en";
|
|
4
5
|
export declare const defaultLocale: undefined;
|
|
5
6
|
export declare const defaultAvailableLanguages: string[];
|
|
6
7
|
export declare const defaultAvailableLocales: string[];
|
|
8
|
+
export declare const manageDashboardGlobalViews: RwPermissionDefinition;
|
package/dist/plugin/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defaultAvailableLocales = exports.defaultAvailableLanguages = exports.defaultLocale = exports.defaultLanguage = exports.loggerCtx = exports.DEFAULT_APP_PATH = void 0;
|
|
3
|
+
exports.manageDashboardGlobalViews = exports.defaultAvailableLocales = exports.defaultAvailableLanguages = exports.defaultLocale = exports.defaultLanguage = exports.loggerCtx = exports.DEFAULT_APP_PATH = void 0;
|
|
4
|
+
const core_1 = require("@vendure/core");
|
|
4
5
|
const path_1 = require("path");
|
|
5
6
|
exports.DEFAULT_APP_PATH = (0, path_1.join)(__dirname, 'dist');
|
|
6
7
|
exports.loggerCtx = 'DashboardPlugin';
|
|
@@ -8,3 +9,4 @@ exports.defaultLanguage = 'en';
|
|
|
8
9
|
exports.defaultLocale = undefined;
|
|
9
10
|
exports.defaultAvailableLanguages = ['en', 'de', 'es', 'cs', 'zh_Hans', 'pt_BR', 'pt_PT', 'zh_Hant'];
|
|
10
11
|
exports.defaultAvailableLocales = ['en-US', 'de-DE', 'es-ES', 'zh-CN', 'zh-TW', 'pt-BR', 'pt-PT'];
|
|
12
|
+
exports.manageDashboardGlobalViews = new core_1.RwPermissionDefinition('DashboardGlobalViews');
|
|
@@ -154,11 +154,24 @@ exports.DashboardPlugin = DashboardPlugin = DashboardPlugin_1 = __decorate([
|
|
|
154
154
|
},
|
|
155
155
|
providers: [metrics_service_js_1.MetricsService],
|
|
156
156
|
configuration: config => {
|
|
157
|
+
config.authOptions.customPermissions.push(constants_js_1.manageDashboardGlobalViews);
|
|
157
158
|
config.settingsStoreFields['vendure.dashboard'] = [
|
|
158
159
|
{
|
|
159
160
|
name: 'userSettings',
|
|
160
161
|
scope: core_1.SettingsStoreScopes.user,
|
|
161
162
|
},
|
|
163
|
+
{
|
|
164
|
+
name: 'globalSavedViews',
|
|
165
|
+
scope: core_1.SettingsStoreScopes.global,
|
|
166
|
+
requiresPermission: {
|
|
167
|
+
read: constants_js_1.manageDashboardGlobalViews.Read,
|
|
168
|
+
write: constants_js_1.manageDashboardGlobalViews.Write,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'userSavedViews',
|
|
173
|
+
scope: core_1.SettingsStoreScopes.user,
|
|
174
|
+
},
|
|
162
175
|
];
|
|
163
176
|
return config;
|
|
164
177
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CacheService, Order, RequestContext, TransactionalConnection } from '@vendure/core';
|
|
2
2
|
import { MetricCalculation } from '../config/metrics-strategies.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DashboardMetricSummary, DashboardMetricSummaryInput } from '../types.js';
|
|
4
4
|
export type MetricData = {
|
|
5
5
|
date: Date;
|
|
6
6
|
orders: Order[];
|
|
@@ -10,6 +10,6 @@ export declare class MetricsService {
|
|
|
10
10
|
private cacheService;
|
|
11
11
|
metricCalculations: MetricCalculation[];
|
|
12
12
|
constructor(connection: TransactionalConnection, cacheService: CacheService);
|
|
13
|
-
getMetrics(ctx: RequestContext, {
|
|
14
|
-
loadData(ctx: RequestContext,
|
|
13
|
+
getMetrics(ctx: RequestContext, { types, refresh, startDate, endDate }: DashboardMetricSummaryInput): Promise<DashboardMetricSummary[]>;
|
|
14
|
+
loadData(ctx: RequestContext, startDate: Date, endDate: Date): Promise<Map<string, MetricData>>;
|
|
15
15
|
}
|
|
@@ -11,13 +11,11 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.MetricsService = void 0;
|
|
13
13
|
const common_1 = require("@nestjs/common");
|
|
14
|
-
const shared_utils_1 = require("@vendure/common/lib/shared-utils");
|
|
15
14
|
const core_1 = require("@vendure/core");
|
|
16
15
|
const crypto_1 = require("crypto");
|
|
17
16
|
const date_fns_1 = require("date-fns");
|
|
18
17
|
const metrics_strategies_js_1 = require("../config/metrics-strategies.js");
|
|
19
18
|
const constants_js_1 = require("../constants.js");
|
|
20
|
-
const types_js_1 = require("../types.js");
|
|
21
19
|
let MetricsService = class MetricsService {
|
|
22
20
|
constructor(connection, cacheService) {
|
|
23
21
|
this.connection = connection;
|
|
@@ -28,15 +26,15 @@ let MetricsService = class MetricsService {
|
|
|
28
26
|
new metrics_strategies_js_1.OrderTotalMetric(),
|
|
29
27
|
];
|
|
30
28
|
}
|
|
31
|
-
async getMetrics(ctx, {
|
|
32
|
-
|
|
33
|
-
const
|
|
29
|
+
async getMetrics(ctx, { types, refresh, startDate, endDate }) {
|
|
30
|
+
const calculatedStartDate = (0, date_fns_1.startOfDay)(new Date(startDate));
|
|
31
|
+
const calculatedEndDate = (0, date_fns_1.endOfDay)(new Date(endDate));
|
|
34
32
|
// Check if we have cached result
|
|
35
33
|
const hash = (0, crypto_1.createHash)('sha1')
|
|
36
34
|
.update(JSON.stringify({
|
|
37
|
-
|
|
35
|
+
startDate: calculatedStartDate,
|
|
36
|
+
endDate: calculatedEndDate,
|
|
38
37
|
types: types.sort(),
|
|
39
|
-
interval,
|
|
40
38
|
channel: ctx.channel.token,
|
|
41
39
|
}))
|
|
42
40
|
.digest('base64');
|
|
@@ -47,22 +45,21 @@ let MetricsService = class MetricsService {
|
|
|
47
45
|
return cachedMetricList;
|
|
48
46
|
}
|
|
49
47
|
// No cache, calculating new metrics
|
|
50
|
-
core_1.Logger.verbose(`No cache hit, calculating ${
|
|
51
|
-
const data = await this.loadData(ctx,
|
|
48
|
+
core_1.Logger.verbose(`No cache hit, calculating metrics from ${calculatedStartDate.toISOString()} to ${calculatedEndDate.toISOString()} for channel ${ctx.channel.token} for all orders`, constants_js_1.loggerCtx);
|
|
49
|
+
const data = await this.loadData(ctx, calculatedStartDate, calculatedEndDate);
|
|
52
50
|
const metrics = [];
|
|
53
51
|
for (const type of types) {
|
|
54
52
|
const metric = this.metricCalculations.find(m => m.type === type);
|
|
55
53
|
if (!metric) {
|
|
56
54
|
continue;
|
|
57
55
|
}
|
|
58
|
-
// Calculate
|
|
56
|
+
// Calculate entries for each day
|
|
59
57
|
const entries = [];
|
|
60
|
-
data.forEach(
|
|
61
|
-
entries.push(metric.calculateEntry(ctx,
|
|
58
|
+
data.forEach(dataPerDay => {
|
|
59
|
+
entries.push(metric.calculateEntry(ctx, dataPerDay));
|
|
62
60
|
});
|
|
63
61
|
// Create metric with calculated entries
|
|
64
62
|
metrics.push({
|
|
65
|
-
interval,
|
|
66
63
|
title: metric.getTitle(ctx),
|
|
67
64
|
type: metric.type,
|
|
68
65
|
entries,
|
|
@@ -71,26 +68,11 @@ let MetricsService = class MetricsService {
|
|
|
71
68
|
await this.cacheService.set(cacheKey, metrics, { ttl: 1000 * 60 * 60 * 2 }); // 2 hours
|
|
72
69
|
return metrics;
|
|
73
70
|
}
|
|
74
|
-
async loadData(ctx,
|
|
75
|
-
let nrOfEntries;
|
|
76
|
-
let backInTimeAmount;
|
|
71
|
+
async loadData(ctx, startDate, endDate) {
|
|
77
72
|
const orderRepo = this.connection.getRepository(ctx, core_1.Order);
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
switch (interval) {
|
|
82
|
-
case types_js_1.MetricInterval.Daily: {
|
|
83
|
-
nrOfEntries = 30;
|
|
84
|
-
backInTimeAmount = { days: nrOfEntries };
|
|
85
|
-
getTickNrFn = date_fns_1.getDayOfYear;
|
|
86
|
-
maxTick = 365;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
default:
|
|
90
|
-
(0, shared_utils_1.assertNever)(interval);
|
|
91
|
-
}
|
|
92
|
-
const startDate = (0, date_fns_1.startOfDay)((0, date_fns_1.sub)(endDate, backInTimeAmount));
|
|
93
|
-
const startTick = getTickNrFn(startDate);
|
|
73
|
+
// Calculate number of days between start and end
|
|
74
|
+
const diffTime = Math.abs(endDate.getTime() - startDate.getTime());
|
|
75
|
+
const nrOfDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
|
94
76
|
// Get orders in a loop until we have all
|
|
95
77
|
let skip = 0;
|
|
96
78
|
const take = 1000;
|
|
@@ -103,38 +85,40 @@ let MetricsService = class MetricsService {
|
|
|
103
85
|
.where('orderChannel.id=:channelId', { channelId: ctx.channelId })
|
|
104
86
|
.andWhere('order.orderPlacedAt >= :startDate', {
|
|
105
87
|
startDate: startDate.toISOString(),
|
|
88
|
+
})
|
|
89
|
+
.andWhere('order.orderPlacedAt <= :endDate', {
|
|
90
|
+
endDate: endDate.toISOString(),
|
|
106
91
|
})
|
|
107
92
|
.skip(skip)
|
|
108
93
|
.take(take);
|
|
109
94
|
const [items, nrOfOrders] = await query.getManyAndCount();
|
|
110
95
|
orders.push(...items);
|
|
111
|
-
core_1.Logger.verbose(`Fetched orders ${skip}-${skip + take} for channel ${ctx.channel.token} for
|
|
96
|
+
core_1.Logger.verbose(`Fetched orders ${skip}-${skip + take} for channel ${ctx.channel.token} for date range metrics`, constants_js_1.loggerCtx);
|
|
112
97
|
skip += items.length;
|
|
113
98
|
if (orders.length >= nrOfOrders) {
|
|
114
99
|
hasMoreOrders = false;
|
|
115
100
|
}
|
|
116
101
|
}
|
|
117
|
-
core_1.Logger.verbose(`Finished fetching all ${orders.length} orders for channel ${ctx.channel.token} for
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
for (let i =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
131
|
-
const ordersInCurrentTick = orders.filter(order => getTickNrFn(order.orderPlacedAt) === tick);
|
|
132
|
-
dataPerInterval.set(tick, {
|
|
133
|
-
orders: ordersInCurrentTick,
|
|
134
|
-
date: (0, date_fns_1.setDayOfYear)(endDate, tick),
|
|
102
|
+
core_1.Logger.verbose(`Finished fetching all ${orders.length} orders for channel ${ctx.channel.token} for date range metrics`, constants_js_1.loggerCtx);
|
|
103
|
+
const dataPerDay = new Map();
|
|
104
|
+
// Create a map entry for each day in the range
|
|
105
|
+
for (let i = 0; i < nrOfDays; i++) {
|
|
106
|
+
const currentDate = new Date(startDate);
|
|
107
|
+
currentDate.setDate(startDate.getDate() + i);
|
|
108
|
+
const dateKey = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
109
|
+
// Filter orders for this specific day
|
|
110
|
+
const ordersForDay = orders.filter(order => {
|
|
111
|
+
if (!order.orderPlacedAt)
|
|
112
|
+
return false;
|
|
113
|
+
const orderDate = new Date(order.orderPlacedAt).toISOString().split('T')[0];
|
|
114
|
+
return orderDate === dateKey;
|
|
135
115
|
});
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
dataPerDay.set(dateKey, {
|
|
117
|
+
orders: ordersForDay,
|
|
118
|
+
date: currentDate,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return dataPerDay;
|
|
138
122
|
}
|
|
139
123
|
};
|
|
140
124
|
exports.MetricsService = MetricsService;
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
export type
|
|
2
|
-
|
|
3
|
-
type: MetricType;
|
|
1
|
+
export type DashboardMetricSummary = {
|
|
2
|
+
type: DashboardMetricType;
|
|
4
3
|
title: string;
|
|
5
|
-
entries:
|
|
4
|
+
entries: DashboardMetricSummaryEntry[];
|
|
6
5
|
};
|
|
7
|
-
export declare enum
|
|
6
|
+
export declare enum DashboardMetricType {
|
|
8
7
|
OrderCount = "OrderCount",
|
|
9
8
|
OrderTotal = "OrderTotal",
|
|
10
9
|
AverageOrderValue = "AverageOrderValue"
|
|
11
10
|
}
|
|
12
|
-
export
|
|
13
|
-
Daily = "Daily"
|
|
14
|
-
}
|
|
15
|
-
export type MetricSummaryEntry = {
|
|
11
|
+
export type DashboardMetricSummaryEntry = {
|
|
16
12
|
label: string;
|
|
17
13
|
value: number;
|
|
18
14
|
};
|
|
19
|
-
export interface
|
|
20
|
-
|
|
21
|
-
types: MetricType[];
|
|
15
|
+
export interface DashboardMetricSummaryInput {
|
|
16
|
+
types: DashboardMetricType[];
|
|
22
17
|
refresh?: boolean;
|
|
18
|
+
startDate: string;
|
|
19
|
+
endDate: string;
|
|
23
20
|
}
|
package/dist/plugin/types.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var
|
|
5
|
-
(function (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
})(
|
|
10
|
-
var MetricInterval;
|
|
11
|
-
(function (MetricInterval) {
|
|
12
|
-
MetricInterval["Daily"] = "Daily";
|
|
13
|
-
})(MetricInterval || (exports.MetricInterval = MetricInterval = {}));
|
|
3
|
+
exports.DashboardMetricType = void 0;
|
|
4
|
+
var DashboardMetricType;
|
|
5
|
+
(function (DashboardMetricType) {
|
|
6
|
+
DashboardMetricType["OrderCount"] = "OrderCount";
|
|
7
|
+
DashboardMetricType["OrderTotal"] = "OrderTotal";
|
|
8
|
+
DashboardMetricType["AverageOrderValue"] = "AverageOrderValue";
|
|
9
|
+
})(DashboardMetricType || (exports.DashboardMetricType = DashboardMetricType = {}));
|
|
@@ -20,6 +20,8 @@ export async function compile(options) {
|
|
|
20
20
|
const { vendureConfigPath, outputPath, pathAdapter, logger = noopLogger, pluginPackageScanner } = options;
|
|
21
21
|
const getCompiledConfigPath = (_a = pathAdapter === null || pathAdapter === void 0 ? void 0 : pathAdapter.getCompiledConfigPath) !== null && _a !== void 0 ? _a : defaultPathAdapter.getCompiledConfigPath;
|
|
22
22
|
const transformTsConfigPathMappings = (_b = pathAdapter === null || pathAdapter === void 0 ? void 0 : pathAdapter.transformTsConfigPathMappings) !== null && _b !== void 0 ? _b : defaultPathAdapter.transformTsConfigPathMappings;
|
|
23
|
+
// 0. Clear the outputPath
|
|
24
|
+
fs.removeSync(outputPath);
|
|
23
25
|
// 1. Compile TypeScript files
|
|
24
26
|
const compileStart = Date.now();
|
|
25
27
|
await compileTypeScript({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import tailwindcss from '@tailwindcss/vite';
|
|
2
|
-
import {
|
|
2
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
|
3
3
|
import react from '@vitejs/plugin-react';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { adminApiSchemaPlugin } from './vite-plugin-admin-api-schema.js';
|
|
@@ -30,7 +30,7 @@ export function vendureDashboardPlugin(options) {
|
|
|
30
30
|
...(options.disableTansStackRouterPlugin
|
|
31
31
|
? []
|
|
32
32
|
: [
|
|
33
|
-
|
|
33
|
+
tanstackRouter({
|
|
34
34
|
autoCodeSplitting: true,
|
|
35
35
|
routeFileIgnorePattern: '.graphql.ts|components|hooks|utils',
|
|
36
36
|
routesDirectory: path.join(packageRoot, 'src/app/routes'),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.5.0-minor-202509261210",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"@types/react": "^19.0.10",
|
|
105
105
|
"@types/react-dom": "^19.0.4",
|
|
106
106
|
"@uidotdev/usehooks": "^2.4.1",
|
|
107
|
-
"@vendure/common": "^3.
|
|
108
|
-
"@vendure/core": "^3.
|
|
107
|
+
"@vendure/common": "^3.5.0-minor-202509261210",
|
|
108
|
+
"@vendure/core": "^3.5.0-minor-202509261210",
|
|
109
109
|
"@vitejs/plugin-react": "^4.3.4",
|
|
110
110
|
"acorn": "^8.11.3",
|
|
111
111
|
"acorn-walk": "^8.3.2",
|
|
@@ -156,5 +156,5 @@
|
|
|
156
156
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
157
157
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
158
158
|
},
|
|
159
|
-
"gitHead": "
|
|
159
|
+
"gitHead": "aa1c6971ebbd4954da1ce77fba1d6022051953db"
|
|
160
160
|
}
|
|
@@ -96,13 +96,18 @@ function CollectionListPage() {
|
|
|
96
96
|
}}
|
|
97
97
|
customizeColumns={{
|
|
98
98
|
name: {
|
|
99
|
+
meta: {
|
|
100
|
+
// This column needs the following fields to always be available
|
|
101
|
+
// in order to correctly render.
|
|
102
|
+
dependencies: ['children', 'breadcrumbs'],
|
|
103
|
+
},
|
|
99
104
|
header: 'Collection Name',
|
|
100
105
|
cell: ({ row }) => {
|
|
101
106
|
const isExpanded = row.getIsExpanded();
|
|
102
107
|
const hasChildren = !!row.original.children?.length;
|
|
103
108
|
return (
|
|
104
109
|
<div
|
|
105
|
-
style={{ marginLeft: (row.original.breadcrumbs
|
|
110
|
+
style={{ marginLeft: (row.original.breadcrumbs?.length - 2) * 20 + 'px' }}
|
|
106
111
|
className="flex gap-2 items-center"
|
|
107
112
|
>
|
|
108
113
|
<Button
|
|
@@ -143,7 +148,7 @@ function CollectionListPage() {
|
|
|
143
148
|
collectionId={row.original.id}
|
|
144
149
|
collectionName={row.original.name}
|
|
145
150
|
>
|
|
146
|
-
<Trans>{row.original.productVariants
|
|
151
|
+
<Trans>{row.original.productVariants?.totalItems} variants</Trans>
|
|
147
152
|
</CollectionContentsSheet>
|
|
148
153
|
);
|
|
149
154
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SlugInput } from '@/vdb/components/data-input/index.js';
|
|
1
2
|
import { RichTextInput } from '@/vdb/components/data-input/rich-text-input.js';
|
|
2
3
|
import { EntityAssets } from '@/vdb/components/shared/entity-assets.js';
|
|
3
4
|
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
@@ -89,7 +90,11 @@ function CollectionDetailPage() {
|
|
|
89
90
|
},
|
|
90
91
|
params: { id: params.id },
|
|
91
92
|
onSuccess: async data => {
|
|
92
|
-
toast(
|
|
93
|
+
toast(
|
|
94
|
+
i18n.t(
|
|
95
|
+
creatingNewEntity ? 'Successfully created collection' : 'Successfully updated collection',
|
|
96
|
+
),
|
|
97
|
+
);
|
|
93
98
|
resetForm();
|
|
94
99
|
if (creatingNewEntity) {
|
|
95
100
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
@@ -147,7 +152,15 @@ function CollectionDetailPage() {
|
|
|
147
152
|
control={form.control}
|
|
148
153
|
name="slug"
|
|
149
154
|
label={<Trans>Slug</Trans>}
|
|
150
|
-
render={({ field }) =>
|
|
155
|
+
render={({ field }) => (
|
|
156
|
+
<SlugInput
|
|
157
|
+
fieldName="slug"
|
|
158
|
+
watchFieldName="name"
|
|
159
|
+
entityName="Collection"
|
|
160
|
+
entityId={entity?.id}
|
|
161
|
+
{...field}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
151
164
|
/>
|
|
152
165
|
</DetailFormGrid>
|
|
153
166
|
<TranslatableFormFieldWrapper
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SlugInput } from '@/vdb/components/data-input/index.js';
|
|
1
2
|
import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
2
3
|
import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
|
|
3
4
|
import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
@@ -32,7 +33,10 @@ export const Route = createFileRoute('/_authenticated/_facets/facets_/$id')({
|
|
|
32
33
|
pageId,
|
|
33
34
|
queryDocument: facetDetailDocument,
|
|
34
35
|
breadcrumb(isNew, entity) {
|
|
35
|
-
return [
|
|
36
|
+
return [
|
|
37
|
+
{ path: '/facets', label: <Trans>Facets</Trans> },
|
|
38
|
+
isNew ? <Trans>New facet</Trans> : entity?.name,
|
|
39
|
+
];
|
|
36
40
|
},
|
|
37
41
|
}),
|
|
38
42
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -124,7 +128,15 @@ function FacetDetailPage() {
|
|
|
124
128
|
control={form.control}
|
|
125
129
|
name="code"
|
|
126
130
|
label={<Trans>Code</Trans>}
|
|
127
|
-
render={({ field }) =>
|
|
131
|
+
render={({ field }) => (
|
|
132
|
+
<SlugInput
|
|
133
|
+
fieldName="code"
|
|
134
|
+
watchFieldName="name"
|
|
135
|
+
entityName="Facet"
|
|
136
|
+
entityId={entity?.id}
|
|
137
|
+
{...field}
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
128
140
|
/>
|
|
129
141
|
</DetailFormGrid>
|
|
130
142
|
</PageBlock>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
2
|
+
import { Link } from '@tanstack/react-router';
|
|
3
|
+
import { Edit2 } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
interface ProductOptionGroupBadgeProps {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ProductOptionGroupBadge({ id, name }: ProductOptionGroupBadgeProps) {
|
|
11
|
+
return (
|
|
12
|
+
<Badge variant="secondary" className="text-xs">
|
|
13
|
+
<span>{name}</span>
|
|
14
|
+
<Link to={`option-groups/${id}`} className="ml-1.5 inline-flex">
|
|
15
|
+
<Edit2 className="h-3 w-3" />
|
|
16
|
+
</Link>
|
|
17
|
+
</Badge>
|
|
18
|
+
);
|
|
19
|
+
}
|