@nest-my-admin/core 0.3.2 → 0.5.0
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/README.md +56 -0
- package/dist/admin.module.js +6 -0
- package/dist/admin.module.js.map +1 -1
- package/dist/api/action.service.js +3 -0
- package/dist/api/action.service.js.map +1 -1
- package/dist/api/admin-api.service.d.ts +54 -2
- package/dist/api/admin-api.service.js +320 -11
- package/dist/api/admin-api.service.js.map +1 -1
- package/dist/api/menu-routes.d.ts +16 -0
- package/dist/api/menu-routes.js +10 -0
- package/dist/api/menu-routes.js.map +1 -0
- package/dist/api/menu.d.ts +15 -0
- package/dist/api/menu.js +85 -0
- package/dist/api/menu.js.map +1 -0
- package/dist/appearance/appearance.service.d.ts +28 -0
- package/dist/appearance/appearance.service.js +88 -0
- package/dist/appearance/appearance.service.js.map +1 -0
- package/dist/appearance/entities.d.ts +8 -0
- package/dist/appearance/entities.js +35 -0
- package/dist/appearance/entities.js.map +1 -0
- package/dist/audit/entities.d.ts +4 -0
- package/dist/audit/entities.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/contract.d.ts +256 -7
- package/dist/crud/computed-columns.d.ts +65 -0
- package/dist/crud/computed-columns.js +264 -0
- package/dist/crud/computed-columns.js.map +1 -0
- package/dist/crud/list-query-builder.d.ts +3 -1
- package/dist/crud/list-query-builder.js +5 -2
- package/dist/crud/list-query-builder.js.map +1 -1
- package/dist/crud/list-query.js +6 -1
- package/dist/crud/list-query.js.map +1 -1
- package/dist/crud/list-tabs.d.ts +7 -0
- package/dist/crud/list-tabs.js +44 -0
- package/dist/crud/list-tabs.js.map +1 -0
- package/dist/dashboard/dashboard-routes.d.ts +18 -0
- package/dist/dashboard/dashboard-routes.js +13 -0
- package/dist/dashboard/dashboard-routes.js.map +1 -0
- package/dist/dashboard/dashboard.service.d.ts +20 -0
- package/dist/dashboard/dashboard.service.js +80 -0
- package/dist/dashboard/dashboard.service.js.map +1 -0
- package/dist/dashboard/widget-registry.d.ts +22 -0
- package/dist/dashboard/widget-registry.js +77 -0
- package/dist/dashboard/widget-registry.js.map +1 -0
- package/dist/decorators/admin-action.d.ts +15 -2
- package/dist/decorators/admin-action.js +21 -2
- package/dist/decorators/admin-action.js.map +1 -1
- package/dist/decorators/admin-field.d.ts +23 -3
- package/dist/decorators/admin-field.js +1 -1
- package/dist/decorators/admin-field.js.map +1 -1
- package/dist/decorators/admin-print.d.ts +35 -0
- package/dist/decorators/admin-print.js +53 -0
- package/dist/decorators/admin-print.js.map +1 -0
- package/dist/decorators/admin-resource.d.ts +7 -0
- package/dist/decorators/admin-resource.js.map +1 -1
- package/dist/decorators/admin-widget.d.ts +27 -0
- package/dist/decorators/admin-widget.js +14 -0
- package/dist/decorators/admin-widget.js.map +1 -0
- package/dist/export/export-cells.d.ts +8 -0
- package/dist/export/export-cells.js +45 -0
- package/dist/export/export-cells.js.map +1 -0
- package/dist/export/xlsx.d.ts +23 -0
- package/dist/export/xlsx.js +115 -0
- package/dist/export/xlsx.js.map +1 -0
- package/dist/http/admin-http.server.d.ts +5 -1
- package/dist/http/admin-http.server.js +41 -2
- package/dist/http/admin-http.server.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/inlines/inline-config.d.ts +3 -0
- package/dist/inlines/inline-config.js +11 -3
- package/dist/inlines/inline-config.js.map +1 -1
- package/dist/inlines/inline-reader.d.ts +5 -0
- package/dist/inlines/inline-reader.js +9 -2
- package/dist/inlines/inline-reader.js.map +1 -1
- package/dist/inlines/inline-writer.js +4 -0
- package/dist/inlines/inline-writer.js.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/options.d.ts +49 -0
- package/dist/options.js +31 -0
- package/dist/options.js.map +1 -1
- package/dist/policy/admin-policy.service.d.ts +14 -1
- package/dist/policy/admin-policy.service.js +127 -9
- package/dist/policy/admin-policy.service.js.map +1 -1
- package/dist/policy/catalog.d.ts +2 -2
- package/dist/policy/catalog.js +5 -4
- package/dist/policy/catalog.js.map +1 -1
- package/dist/policy/explain.js +3 -1
- package/dist/policy/explain.js.map +1 -1
- package/dist/print/html.d.ts +16 -0
- package/dist/print/html.js +37 -0
- package/dist/print/html.js.map +1 -0
- package/dist/print/print-page.d.ts +10 -0
- package/dist/print/print-page.js +47 -0
- package/dist/print/print-page.js.map +1 -0
- package/dist/rbac/rbac.service.d.ts +3 -1
- package/dist/rbac/rbac.service.js +8 -4
- package/dist/rbac/rbac.service.js.map +1 -1
- package/dist/registry/resource-registry.d.ts +25 -0
- package/dist/registry/resource-registry.js +109 -0
- package/dist/registry/resource-registry.js.map +1 -1
- package/dist/resource/admin-resource-base.d.ts +120 -9
- package/dist/resource/admin-resource-base.js +16 -2
- package/dist/resource/admin-resource-base.js.map +1 -1
- package/dist/schema/build-resource-schema.js +104 -6
- package/dist/schema/build-resource-schema.js.map +1 -1
- package/dist/schema/currency.d.ts +9 -0
- package/dist/schema/currency.js +11 -0
- package/dist/schema/currency.js.map +1 -0
- package/dist/schema/field-config.d.ts +33 -4
- package/dist/schema/field-config.js +68 -3
- package/dist/schema/field-config.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
14
|
+
import { ADMIN_OPTIONS } from '../constants.js';
|
|
15
|
+
import { AdminBadRequestError, AdminNotFoundError } from '../errors.js';
|
|
16
|
+
import { resolveText } from '../i18n/localized-text.js';
|
|
17
|
+
import { AdminPolicy } from '../policy/admin-policy.service.js';
|
|
18
|
+
import { WidgetRegistry } from './widget-registry.js';
|
|
19
|
+
const PERIODS = ['day', 'week', 'month', 'year', 'range'];
|
|
20
|
+
const MAX_SPAN_MS = 366 * 24 * 60 * 60 * 1000;
|
|
21
|
+
/** The dashboard (spec §5.8): the widgets a user may see, and each one's data for a period. */
|
|
22
|
+
let DashboardService = class DashboardService {
|
|
23
|
+
widgets;
|
|
24
|
+
policy;
|
|
25
|
+
options;
|
|
26
|
+
constructor(widgets, policy, options) {
|
|
27
|
+
this.widgets = widgets;
|
|
28
|
+
this.policy = policy;
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
visible(ctx) {
|
|
32
|
+
const perms = this.policy.permissionsOf(ctx);
|
|
33
|
+
return this.widgets.list().filter((widget) => perms.can(widget.code));
|
|
34
|
+
}
|
|
35
|
+
/** Whether the user has a dashboard at all (`meta.dashboard`). */
|
|
36
|
+
has(ctx) {
|
|
37
|
+
return this.visible(ctx).length > 0;
|
|
38
|
+
}
|
|
39
|
+
layout(ctx) {
|
|
40
|
+
const locale = ctx.locale ?? this.options.locale;
|
|
41
|
+
return { widgets: this.visible(ctx).map((widget) => ({ name: widget.name, type: widget.type, title: resolveText(widget.title, locale, this.options.locale), size: widget.size })) };
|
|
42
|
+
}
|
|
43
|
+
/** One widget's data: 404 for a widget the user may not see (or that does not exist), 400 for a bad period. */
|
|
44
|
+
async load(name, query, ctx) {
|
|
45
|
+
const widget = this.visible(ctx).find((candidate) => candidate.name === name);
|
|
46
|
+
if (!widget)
|
|
47
|
+
throw new AdminNotFoundError(`Unknown widget "${name}"`);
|
|
48
|
+
return widget.source.load(parsePeriod(query), ctx);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
DashboardService = __decorate([
|
|
52
|
+
Injectable(),
|
|
53
|
+
__param(2, Inject(ADMIN_OPTIONS)),
|
|
54
|
+
__metadata("design:paramtypes", [WidgetRegistry,
|
|
55
|
+
AdminPolicy, Object])
|
|
56
|
+
], DashboardService);
|
|
57
|
+
export { DashboardService };
|
|
58
|
+
/** The period from the query: a known kind, instants in order, at most 366 days (the comparison window too). */
|
|
59
|
+
export function parsePeriod(query) {
|
|
60
|
+
const period = query.get('period') ?? '';
|
|
61
|
+
if (!PERIODS.includes(period))
|
|
62
|
+
throw new AdminBadRequestError(`period must be one of ${PERIODS.join(', ')}`);
|
|
63
|
+
const instant = (key) => {
|
|
64
|
+
const raw = query.get(key);
|
|
65
|
+
const time = raw ? Date.parse(raw) : Number.NaN;
|
|
66
|
+
if (Number.isNaN(time))
|
|
67
|
+
throw new AdminBadRequestError(`${key} must be an ISO date and time`);
|
|
68
|
+
return { raw: new Date(time).toISOString(), time };
|
|
69
|
+
};
|
|
70
|
+
const from = instant('from');
|
|
71
|
+
const to = instant('to');
|
|
72
|
+
const previousFrom = instant('previousFrom');
|
|
73
|
+
const previousTo = instant('previousTo');
|
|
74
|
+
if (from.time >= to.time || previousFrom.time >= previousTo.time)
|
|
75
|
+
throw new AdminBadRequestError('a period starts before it ends');
|
|
76
|
+
if (to.time - from.time > MAX_SPAN_MS || previousTo.time - previousFrom.time > MAX_SPAN_MS)
|
|
77
|
+
throw new AdminBadRequestError('a period spans at most 366 days');
|
|
78
|
+
return { period: period, from: from.raw, to: to.raw, previousFrom: previousFrom.raw, previousTo: previousTo.raw };
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=dashboard.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.service.js","sourceRoot":"","sources":["../../src/dashboard/dashboard.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { DiscoveryService } from '@nestjs/core';
|
|
3
|
+
import { type AdminWidgetDefinition, type AdminWidgetSource } from '../decorators/admin-widget.js';
|
|
4
|
+
export interface RegisteredWidget extends AdminWidgetDefinition {
|
|
5
|
+
className: string;
|
|
6
|
+
source: AdminWidgetSource;
|
|
7
|
+
order: number;
|
|
8
|
+
size: 1 | 2 | 3 | 4;
|
|
9
|
+
/** The permission that shows it (`dashboard.<name>` unless the widget names one). */
|
|
10
|
+
code: string;
|
|
11
|
+
}
|
|
12
|
+
/** The dashboard's widgets (`@AdminWidget` providers), checked at module init and kept in order. */
|
|
13
|
+
export declare class WidgetRegistry implements OnModuleInit {
|
|
14
|
+
private readonly discovery;
|
|
15
|
+
private widgets;
|
|
16
|
+
constructor(discovery: DiscoveryService);
|
|
17
|
+
onModuleInit(): void;
|
|
18
|
+
list(): RegisteredWidget[];
|
|
19
|
+
find(name: string): RegisteredWidget | undefined;
|
|
20
|
+
/** Every widget permission code (for role checks and the role editor's catalog). */
|
|
21
|
+
codes(): string[];
|
|
22
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable } from '@nestjs/common';
|
|
11
|
+
import { DiscoveryService } from '@nestjs/core';
|
|
12
|
+
import { getAdminWidgetDefinition } from '../decorators/admin-widget.js';
|
|
13
|
+
const TYPES = ['stat', 'chart', 'table', 'progress', 'list'];
|
|
14
|
+
/** The dashboard's widgets (`@AdminWidget` providers), checked at module init and kept in order. */
|
|
15
|
+
let WidgetRegistry = class WidgetRegistry {
|
|
16
|
+
discovery;
|
|
17
|
+
widgets = [];
|
|
18
|
+
constructor(discovery) {
|
|
19
|
+
this.discovery = discovery;
|
|
20
|
+
}
|
|
21
|
+
onModuleInit() {
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const wrapper of this.discovery.getProviders()) {
|
|
25
|
+
const metatype = wrapper.metatype;
|
|
26
|
+
if (typeof metatype !== 'function')
|
|
27
|
+
continue;
|
|
28
|
+
const definition = getAdminWidgetDefinition(metatype);
|
|
29
|
+
if (!definition)
|
|
30
|
+
continue;
|
|
31
|
+
const instance = wrapper.instance;
|
|
32
|
+
const fail = (message) => {
|
|
33
|
+
throw new Error(`${metatype.name}: @AdminWidget("${definition.name}"): ${message}`);
|
|
34
|
+
};
|
|
35
|
+
if (!wrapper.isDependencyTreeStatic() || !instance)
|
|
36
|
+
fail('widgets must be singleton-scoped providers');
|
|
37
|
+
if (seen.has(instance))
|
|
38
|
+
continue;
|
|
39
|
+
seen.add(instance);
|
|
40
|
+
if (typeof definition.name !== 'string' || !/^[a-z0-9][a-z0-9_-]*$/i.test(definition.name))
|
|
41
|
+
fail('the name must be letters, digits, - and _');
|
|
42
|
+
if (out.some((widget) => widget.name === definition.name))
|
|
43
|
+
fail('the name is used twice');
|
|
44
|
+
if (!TYPES.includes(definition.type))
|
|
45
|
+
fail(`unknown type "${String(definition.type)}" (use ${TYPES.join(', ')})`);
|
|
46
|
+
const size = definition.size ?? 1;
|
|
47
|
+
if (![1, 2, 3, 4].includes(size))
|
|
48
|
+
fail('size is 1, 2, 3 or 4 columns');
|
|
49
|
+
const order = definition.order ?? 100;
|
|
50
|
+
if (!Number.isFinite(order))
|
|
51
|
+
fail('order must be a finite number');
|
|
52
|
+
if (typeof instance.load !== 'function')
|
|
53
|
+
fail('a widget needs load(params, ctx)');
|
|
54
|
+
const code = definition.permission ?? `dashboard.${definition.name}`;
|
|
55
|
+
if (!/^[a-z0-9][a-z0-9_.-]*$/i.test(code) || code.includes('*'))
|
|
56
|
+
fail(`permission "${code}" must be a permission code`);
|
|
57
|
+
out.push({ ...definition, className: metatype.name, source: instance, order, size, code });
|
|
58
|
+
}
|
|
59
|
+
this.widgets = out.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
|
|
60
|
+
}
|
|
61
|
+
list() {
|
|
62
|
+
return this.widgets;
|
|
63
|
+
}
|
|
64
|
+
find(name) {
|
|
65
|
+
return this.widgets.find((widget) => widget.name === name);
|
|
66
|
+
}
|
|
67
|
+
/** Every widget permission code (for role checks and the role editor's catalog). */
|
|
68
|
+
codes() {
|
|
69
|
+
return [...new Set(this.widgets.map((widget) => widget.code))];
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
WidgetRegistry = __decorate([
|
|
73
|
+
Injectable(),
|
|
74
|
+
__metadata("design:paramtypes", [DiscoveryService])
|
|
75
|
+
], WidgetRegistry);
|
|
76
|
+
export { WidgetRegistry };
|
|
77
|
+
//# sourceMappingURL=widget-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-registry.js","sourceRoot":"","sources":["../../src/dashboard/widget-registry.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAsD,MAAM,+BAA+B,CAAC;AAE7H,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAW7D,oGAAoG;AAE7F,IAAM,cAAc,GAApB,MAAM,cAAc;IAGI,SAAS;IAF9B,OAAO,GAAuB,EAAE,CAAC;IAEzC,YAA6B,SAA2B;yBAA3B,SAAS;IAAqB,CAAC;IAE5D,YAAY;QACV,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,CAAC;YACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YAClC,IAAI,OAAO,QAAQ,KAAK,UAAU;gBAAE,SAAS;YAC7C,MAAM,UAAU,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;YACtD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAkD,CAAC;YAC5E,MAAM,IAAI,GAAG,CAAC,OAAe,EAAS,EAAE;gBACtC,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,CAAC,IAAI,mBAAmB,UAAU,CAAC,IAAI,OAAO,OAAO,EAAE,CAAC,CAAC;YACtF,CAAC,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,IAAI,CAAC,QAAQ;gBAAE,IAAI,CAAC,4CAA4C,CAAC,CAAC;YACvG,IAAI,IAAI,CAAC,GAAG,CAAC,QAAS,CAAC;gBAAE,SAAS;YAClC,IAAI,CAAC,GAAG,CAAC,QAAS,CAAC,CAAC;YACpB,IAAI,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,2CAA2C,CAAC,CAAC;YAC9I,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC;YAC1F,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,iBAAiB,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,8BAA8B,CAAC,CAAC;YACvE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,GAAG,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,+BAA+B,CAAC,CAAC;YACnE,IAAI,OAAO,QAAS,CAAC,IAAI,KAAK,UAAU;gBAAE,IAAI,CAAC,kCAAkC,CAAC,CAAC;YACnF,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,IAAI,aAAa,UAAU,CAAC,IAAI,EAAE,CAAC;YACrE,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,IAAI,CAAC,eAAe,IAAI,6BAA6B,CAAC,CAAC;YACxH,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAA6B,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAClH,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvF,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,IAAI,CAAC,IAAY;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,oFAAoF;IACpF,KAAK;QACH,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;CACF,CAAA;AA/CY,cAAc;IAD1B,UAAU,EAAE;qCAI6B,gBAAgB;GAH7C,cAAc,CA+C1B"}
|
|
@@ -4,8 +4,18 @@ import { type LocalizedText } from '../i18n/localized-text.js';
|
|
|
4
4
|
import { type DtoForm } from '../schema/dto-form.js';
|
|
5
5
|
import type { DtoClass } from '../schema/dto-fields.js';
|
|
6
6
|
export interface AdminActionOptions {
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* `'Mark shipped'` or `{ en: 'Mark shipped', fa: 'ارسال شد' }`. Defaults to the humanized method name. A label per
|
|
9
|
+
* record: `{ static, for }`, where `for(record)` names the action for that record («انتقال به آماده ارسال») or
|
|
10
|
+
* returns `null` to hide it there (a run is then refused too); `static` names it where no record is at hand (bulk
|
|
11
|
+
* menus, the role editor, the audit log).
|
|
12
|
+
*/
|
|
13
|
+
label?: LocalizedText | {
|
|
14
|
+
static: LocalizedText;
|
|
15
|
+
for: (record: any) => LocalizedText | null;
|
|
16
|
+
};
|
|
17
|
+
/** The record's next step: a filled button, last in the record's header. One per resource, record actions without `input`. */
|
|
18
|
+
primary?: boolean;
|
|
9
19
|
/**
|
|
10
20
|
* `record` (default): the method is `(record, input, ctx)` and runs on a record the user may view and update
|
|
11
21
|
* (the detail page; a list selection too with `bulk`). `resource`: the method is `(input, ctx)` and runs once,
|
|
@@ -43,6 +53,9 @@ export interface ActionDefinition extends AdminActionOptions {
|
|
|
43
53
|
export interface RegisteredAction {
|
|
44
54
|
name: string;
|
|
45
55
|
label: LocalizedText;
|
|
56
|
+
/** The per-record label (`label.for`): `null` hides the action on that record. */
|
|
57
|
+
labelFor?: (record: any) => LocalizedText | null;
|
|
58
|
+
primary: boolean;
|
|
46
59
|
target: 'record' | 'resource';
|
|
47
60
|
bulk: boolean;
|
|
48
61
|
atomic: boolean;
|
|
@@ -60,8 +60,21 @@ export function resolveActions(resourceClass, reserved, fail) {
|
|
|
60
60
|
const { confirm, input, icon, label } = definition;
|
|
61
61
|
if (confirm !== undefined && typeof confirm !== 'boolean' && !isText(confirm))
|
|
62
62
|
fail(`${where}: confirm must be true, false or a text`);
|
|
63
|
-
|
|
63
|
+
const perRecord = typeof label === 'object' && label !== null && 'for' in label;
|
|
64
|
+
if (perRecord) {
|
|
65
|
+
const { static: fixed, for: labelFor } = label;
|
|
66
|
+
if (typeof labelFor !== 'function')
|
|
67
|
+
fail(`${where}: label.for must be a function (record) => text | null`);
|
|
68
|
+
if (fixed === undefined || !isText(fixed))
|
|
69
|
+
fail(`${where}: a per-record label needs \`static\`, the label where no record is at hand (bulk menus, roles, the audit log)`);
|
|
70
|
+
}
|
|
71
|
+
else if (label !== undefined && !isText(label))
|
|
64
72
|
fail(`${where}: label must be a text`);
|
|
73
|
+
const primary = definition.primary === true;
|
|
74
|
+
if (primary && target !== 'record')
|
|
75
|
+
fail(`${where}: \`primary\` is a record's next step; resource actions cannot be primary`);
|
|
76
|
+
if (primary && input !== undefined)
|
|
77
|
+
fail(`${where}: a primary action runs in one click, so it takes no \`input\``);
|
|
65
78
|
if (icon !== undefined && typeof icon !== 'string')
|
|
66
79
|
fail(`${where}: icon must be an icon name`);
|
|
67
80
|
let form;
|
|
@@ -77,7 +90,9 @@ export function resolveActions(resourceClass, reserved, fail) {
|
|
|
77
90
|
}
|
|
78
91
|
actions.set(name, {
|
|
79
92
|
name,
|
|
80
|
-
label: label ?? humanize(name),
|
|
93
|
+
label: perRecord ? label.static : (label ?? humanize(name)),
|
|
94
|
+
...(perRecord ? { labelFor: label.for } : {}),
|
|
95
|
+
primary,
|
|
81
96
|
target,
|
|
82
97
|
bulk,
|
|
83
98
|
atomic,
|
|
@@ -87,6 +102,9 @@ export function resolveActions(resourceClass, reserved, fail) {
|
|
|
87
102
|
...(input ? { input, form } : {}),
|
|
88
103
|
});
|
|
89
104
|
}
|
|
105
|
+
const primaries = [...actions.values()].filter((action) => action.primary);
|
|
106
|
+
if (primaries.length > 1)
|
|
107
|
+
fail(`@AdminAction: only one action can be \`primary\` (${primaries.map((action) => action.name).join(', ')})`);
|
|
90
108
|
// A rule under a name nothing checks would silently never apply.
|
|
91
109
|
for (const rule of getCanRuleNames(resourceClass)) {
|
|
92
110
|
if (rule === 'update' || rule === 'delete')
|
|
@@ -115,6 +133,7 @@ export function toActionSchema(action, locale, defaultLocale) {
|
|
|
115
133
|
atomic: action.atomic,
|
|
116
134
|
...(confirm !== undefined ? { confirm: confirm === true ? true : resolveText(confirm, locale, defaultLocale) } : {}),
|
|
117
135
|
variant: action.variant,
|
|
136
|
+
...(action.primary ? { primary: true } : {}),
|
|
118
137
|
...(action.icon ? { icon: action.icon } : {}),
|
|
119
138
|
...(action.form ? { input: localizeDtoForm(action.form, locale, defaultLocale) } : {}),
|
|
120
139
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-action.js","sourceRoot":"","sources":["../../src/decorators/admin-action.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAsB,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAgB,MAAM,uBAAuB,CAAC;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-action.js","sourceRoot":"","sources":["../../src/decorators/admin-action.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import type { LocalizedText } from '../i18n/localized-text.js';
|
|
3
|
+
import type { AdminCurrency } from '../schema/currency.js';
|
|
3
4
|
/** Inputs and displays the UI has (spec §5.6). */
|
|
4
|
-
export declare const WIDGETS: readonly ['text', 'textarea', 'number', 'money', 'switch', 'checkbox', 'select', 'radio', 'date', 'datetime', 'json', 'color', 'badge', 'slug', 'password', 'email', 'url', 'file', 'image', 'richtext'];
|
|
5
|
+
export declare const WIDGETS: readonly ['text', 'textarea', 'number', 'money', 'switch', 'checkbox', 'select', 'radio', 'date', 'datetime', 'json', 'color', 'badge', 'slug', 'password', 'email', 'url', 'file', 'image', 'richtext', 'stepper'];
|
|
5
6
|
export type WidgetName = (typeof WIDGETS)[number];
|
|
6
7
|
/** Colours a badge value can take; the UI maps them to its palette in light and dark themes. */
|
|
7
8
|
export declare const BADGE_COLORS: readonly ['gray', 'red', 'amber', 'green', 'blue', 'purple', 'pink'];
|
|
@@ -31,10 +32,29 @@ export interface AdminFieldOptions {
|
|
|
31
32
|
showIf?: Record<string, ShowIfValue | ShowIfValue[]>;
|
|
32
33
|
/** Shown on edit forms but never written by update. */
|
|
33
34
|
readonly?: boolean;
|
|
35
|
+
/** `stepper`: the steps in order (default: the enum's order). */
|
|
36
|
+
steps?: string[];
|
|
37
|
+
/** `stepper`: values that are not steps (cancelled): shown as a badge, with no current step. */
|
|
38
|
+
exclude?: string[];
|
|
39
|
+
/**
|
|
40
|
+
* `stepper`: when each step was reached, from a child resource logging the changes (one row per change: `value` is
|
|
41
|
+
* the value reached, `at` when; `where` narrows to this field's rows when the log holds several fields).
|
|
42
|
+
*/
|
|
43
|
+
history?: {
|
|
44
|
+
inline: abstract new (...args: any[]) => unknown;
|
|
45
|
+
value: string;
|
|
46
|
+
at: string;
|
|
47
|
+
where?: Record<string, string>;
|
|
48
|
+
};
|
|
49
|
+
/** A soft warning under the input while the field is empty (an image without alt text): it never stops a save. */
|
|
50
|
+
warnIf?: {
|
|
51
|
+
empty: true;
|
|
52
|
+
message: LocalizedText;
|
|
53
|
+
};
|
|
34
54
|
/** The `slug` widget fills itself from this field until someone edits it. */
|
|
35
55
|
slugFrom?: string;
|
|
36
|
-
/**
|
|
37
|
-
currency?: string;
|
|
56
|
+
/** The unit shown with the `money` widget (display only): a code such as `USD`, or a unit and its position. Wins over the global `currency`. */
|
|
57
|
+
currency?: string | AdminCurrency;
|
|
38
58
|
/** `file` and `image` widgets: type, size and visibility limits; `richtext`: `maxBytes` and `visibility` of editor images. */
|
|
39
59
|
file?: FileFieldOptions;
|
|
40
60
|
/** `file` and `image` widgets on a json column: a list of files instead of one. */
|
|
@@ -3,7 +3,7 @@ import { ADMIN_FIELD_METADATA } from '../constants.js';
|
|
|
3
3
|
/** Inputs and displays the UI has (spec §5.6). */
|
|
4
4
|
export const WIDGETS = [
|
|
5
5
|
'text', 'textarea', 'number', 'money', 'switch', 'checkbox', 'select', 'radio', 'date', 'datetime', 'json', 'color',
|
|
6
|
-
'badge', 'slug', 'password', 'email', 'url', 'file', 'image', 'richtext',
|
|
6
|
+
'badge', 'slug', 'password', 'email', 'url', 'file', 'image', 'richtext', 'stepper',
|
|
7
7
|
];
|
|
8
8
|
/** Colours a badge value can take; the UI maps them to its palette in light and dark themes. */
|
|
9
9
|
export const BADGE_COLORS = ['gray', 'red', 'amber', 'green', 'blue', 'purple', 'pink'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-field.js","sourceRoot":"","sources":["../../src/decorators/admin-field.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-field.js","sourceRoot":"","sources":["../../src/decorators/admin-field.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAIvD,kDAAkD;AAClD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO;IACnH,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS;CAC3E,CAAC;AAGX,gGAAgG;AAChG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAmEjG,gHAAgH;AAChH,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC;QACjC,MAAM,SAAS,GAAI,OAAO,CAAC,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAmD,IAAI,EAAE,CAAC;QAC5H,OAAO,CAAC,cAAc,CAAC,oBAAoB,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5I,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAC/D,OAAO,CAAC,MAAM,IAAK,OAAO,CAAC,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAmD,CAAC,IAAI,EAAE,CAAC;AAChI,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { PrintSchema } from '../contract.js';
|
|
3
|
+
import { type LocalizedText } from '../i18n/localized-text.js';
|
|
4
|
+
/** Paper sizes by name; any other string is used as the CSS `@page { size }` value. */
|
|
5
|
+
export declare const PAPERS: Record<string, string>;
|
|
6
|
+
export interface AdminPrintOptions {
|
|
7
|
+
/** `'Invoice'` or `{ en: 'Invoice', fa: 'فاکتور' }`. Defaults to the humanized method name. */
|
|
8
|
+
label?: LocalizedText;
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** `A4` (default), `A5`, `label` (100×150 mm) or a CSS `@page` size such as `'80mm 200mm'`. */
|
|
11
|
+
paper?: string;
|
|
12
|
+
/** Also offered on a list selection: one page per record, up to 100. */
|
|
13
|
+
bulk?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** A print template checked at boot. */
|
|
16
|
+
export interface RegisteredPrint {
|
|
17
|
+
name: string;
|
|
18
|
+
label: LocalizedText;
|
|
19
|
+
icon?: string;
|
|
20
|
+
/** The CSS `@page` size. */
|
|
21
|
+
size: string;
|
|
22
|
+
bulk: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A print template of a resource (an invoice, a shipping label): the method `(records, ctx)` returns the body HTML of
|
|
26
|
+
* the printed page, one or more records (`bulk`). Build it with the `html` tag, which escapes every value. The admin
|
|
27
|
+
* wraps it in a page sized for `paper` that prints itself. Printing needs `{resource}.view`, the user's row scopes and
|
|
28
|
+
* `{resource}.print.{name}`; each printed record is written to the audit log. Throw an `HttpException` to refuse.
|
|
29
|
+
*/
|
|
30
|
+
export declare function AdminPrint(options?: AdminPrintOptions): MethodDecorator;
|
|
31
|
+
/** Boot checks of a resource class's print templates; `taken` are names already in use (base methods, actions). */
|
|
32
|
+
export declare function resolvePrints(resourceClass: Function, taken: readonly string[], fail: (message: string) => never): Map<string, RegisteredPrint>;
|
|
33
|
+
/** The permission code of a print template. */
|
|
34
|
+
export declare const printCode: (resource: string, name: string) => string;
|
|
35
|
+
export declare function toPrintSchema(print: RegisteredPrint, locale: string, defaultLocale: string): PrintSchema;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ADMIN_PRINTS_METADATA } from '../constants.js';
|
|
3
|
+
import { resolveText } from '../i18n/localized-text.js';
|
|
4
|
+
import { humanize } from '../schema/humanize.js';
|
|
5
|
+
/** Paper sizes by name; any other string is used as the CSS `@page { size }` value. */
|
|
6
|
+
export const PAPERS = { A4: 'A4', A5: 'A5', label: '100mm 150mm' };
|
|
7
|
+
/**
|
|
8
|
+
* A print template of a resource (an invoice, a shipping label): the method `(records, ctx)` returns the body HTML of
|
|
9
|
+
* the printed page, one or more records (`bulk`). Build it with the `html` tag, which escapes every value. The admin
|
|
10
|
+
* wraps it in a page sized for `paper` that prints itself. Printing needs `{resource}.view`, the user's row scopes and
|
|
11
|
+
* `{resource}.print.{name}`; each printed record is written to the audit log. Throw an `HttpException` to refuse.
|
|
12
|
+
*/
|
|
13
|
+
export function AdminPrint(options = {}) {
|
|
14
|
+
return (target, propertyKey) => {
|
|
15
|
+
if (typeof propertyKey !== 'string')
|
|
16
|
+
throw new TypeError('@AdminPrint must decorate a method with a string name');
|
|
17
|
+
const owner = target.constructor;
|
|
18
|
+
const inherited = Reflect.getMetadata(ADMIN_PRINTS_METADATA, owner) ?? {};
|
|
19
|
+
Reflect.defineMetadata(ADMIN_PRINTS_METADATA, { ...inherited, [propertyKey]: { ...options, name: propertyKey } }, owner);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Boot checks of a resource class's print templates; `taken` are names already in use (base methods, actions). */
|
|
23
|
+
export function resolvePrints(resourceClass, taken, fail) {
|
|
24
|
+
const prints = new Map();
|
|
25
|
+
const declared = Reflect.getMetadata(ADMIN_PRINTS_METADATA, resourceClass) ?? {};
|
|
26
|
+
for (const definition of Object.values(declared)) {
|
|
27
|
+
const { name } = definition;
|
|
28
|
+
const where = `@AdminPrint "${name}"`;
|
|
29
|
+
if (taken.includes(name))
|
|
30
|
+
fail(`${where}: the name is taken (a base method or an action); give the method another name`);
|
|
31
|
+
if (!/^[a-z][a-z0-9_]*$/i.test(name))
|
|
32
|
+
fail(`${where}: the method name must be a word (letters, digits, _)`);
|
|
33
|
+
const paper = definition.paper ?? 'A4';
|
|
34
|
+
if (typeof paper !== 'string' || !/^[a-z0-9 .]+$/i.test(paper))
|
|
35
|
+
fail(`${where}: paper must be A4, A5, label or a CSS page size`);
|
|
36
|
+
if (definition.icon !== undefined && typeof definition.icon !== 'string')
|
|
37
|
+
fail(`${where}: icon must be an icon name`);
|
|
38
|
+
prints.set(name, {
|
|
39
|
+
name,
|
|
40
|
+
label: definition.label ?? humanize(name),
|
|
41
|
+
...(definition.icon ? { icon: definition.icon } : {}),
|
|
42
|
+
size: PAPERS[paper] ?? paper,
|
|
43
|
+
bulk: definition.bulk === true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return prints;
|
|
47
|
+
}
|
|
48
|
+
/** The permission code of a print template. */
|
|
49
|
+
export const printCode = (resource, name) => `${resource}.print.${name}`;
|
|
50
|
+
export function toPrintSchema(print, locale, defaultLocale) {
|
|
51
|
+
return { name: print.name, label: resolveText(print.label, locale, defaultLocale), bulk: print.bulk, ...(print.icon ? { icon: print.icon } : {}) };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=admin-print.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-print.js","sourceRoot":"","sources":["../../src/decorators/admin-print.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,13 @@ export interface AdminResourceOptions {
|
|
|
14
14
|
* (A group lists its resources first, then its settings pages.)
|
|
15
15
|
*/
|
|
16
16
|
order?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the resource has its own entry in the sidebar, home, group pages, palette and global search. Default
|
|
19
|
+
* `true`, except for a resource another resource edits inline (`{ inline: ThisAdmin }` in a form layout): those are
|
|
20
|
+
* edited on their parent's page and default to `false`. Either way the resource keeps its URLs, permissions,
|
|
21
|
+
* history and audit entries.
|
|
22
|
+
*/
|
|
23
|
+
nav?: boolean;
|
|
17
24
|
/**
|
|
18
25
|
* The record's display name in lists, pickers and headers: a column name, or a function of the entity.
|
|
19
26
|
* Functions see columns (relations only when the loader joined them). Default: the first string column called
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-resource.js","sourceRoot":"","sources":["../../src/decorators/admin-resource.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-resource.js","sourceRoot":"","sources":["../../src/decorators/admin-resource.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AA+C1D,mGAAmG;AACnG,MAAM,UAAU,aAAa,CAAC,MAAgB,EAAE,OAAO,GAAyB,EAAE;IAChF,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,MAAM,UAAU,GAA4B,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC;QACnE,OAAO,CAAC,cAAc,CAAC,uBAAuB,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACpE,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAgB;IACzD,OAAO,OAAO,CAAC,cAAc,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import type { WidgetData, WidgetParams, WidgetType } from '../contract.js';
|
|
3
|
+
import type { LocalizedText } from '../i18n/localized-text.js';
|
|
4
|
+
import type { AdminContext } from '../resource/admin-context.js';
|
|
5
|
+
export interface AdminWidgetOptions {
|
|
6
|
+
type: WidgetType;
|
|
7
|
+
title: LocalizedText;
|
|
8
|
+
/** Lower comes first on the dashboard. Default 100. */
|
|
9
|
+
order?: number;
|
|
10
|
+
/** Columns it spans on the desktop grid (4 columns; phones show one). Default 1. */
|
|
11
|
+
size?: 1 | 2 | 3 | 4;
|
|
12
|
+
/** The permission that shows it. Default `dashboard.<name>`; widgets may share one. */
|
|
13
|
+
permission?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* What a dashboard widget class implements: its data for a period (spec §5.8). Return the shape of its `type`:
|
|
17
|
+
* `stat`, `chart`, `table`, `progress` or `list` (`WidgetData`). The admin checks the permission and the period first.
|
|
18
|
+
*/
|
|
19
|
+
export interface AdminWidgetSource {
|
|
20
|
+
load(params: WidgetParams, ctx: AdminContext): Promise<WidgetData>;
|
|
21
|
+
}
|
|
22
|
+
export interface AdminWidgetDefinition extends AdminWidgetOptions {
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
25
|
+
/** A dashboard widget: a provider whose `load` answers its data. The dashboard replaces the home page once one exists. */
|
|
26
|
+
export declare function AdminWidget(name: string, options: AdminWidgetOptions): ClassDecorator;
|
|
27
|
+
export declare function getAdminWidgetDefinition(target: Function): AdminWidgetDefinition | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Injectable } from '@nestjs/common';
|
|
3
|
+
import { ADMIN_WIDGET_METADATA } from '../constants.js';
|
|
4
|
+
/** A dashboard widget: a provider whose `load` answers its data. The dashboard replaces the home page once one exists. */
|
|
5
|
+
export function AdminWidget(name, options) {
|
|
6
|
+
return (target) => {
|
|
7
|
+
Reflect.defineMetadata(ADMIN_WIDGET_METADATA, { ...options, name }, target);
|
|
8
|
+
Injectable()(target);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function getAdminWidgetDefinition(target) {
|
|
12
|
+
return Reflect.getOwnMetadata(ADMIN_WIDGET_METADATA, target);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=admin-widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-widget.js","sourceRoot":"","sources":["../../src/decorators/admin-widget.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA4BxD,0HAA0H;AAC1H,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAA2B;IACnE,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,OAAO,CAAC,cAAc,CAAC,qBAAqB,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAkC,EAAE,MAAM,CAAC,CAAC;QAC5G,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAgB;IACvD,OAAO,OAAO,CAAC,cAAc,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FieldSchema } from '../contract.js';
|
|
2
|
+
import type { Cell } from './xlsx.js';
|
|
3
|
+
/**
|
|
4
|
+
* One exported cell from a serialized record value (`serializeRecord`): numbers stay numbers, enums show their labels,
|
|
5
|
+
* relations their titles, booleans yes/no in `locale`, dates real dates. `offset` (minutes east of UTC) shifts
|
|
6
|
+
* date-times to the exporter's clock, since a spreadsheet cell has no time zone.
|
|
7
|
+
*/
|
|
8
|
+
export declare function toCell(value: unknown, field: FieldSchema, locale: string, offset: number): Cell;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const YES_NO = { en: ['Yes', 'No'], fa: ['بله', 'خیر'] };
|
|
2
|
+
/** A decimal or bigint string as a number when a double holds it exactly, else the text. */
|
|
3
|
+
function numeric(text) {
|
|
4
|
+
if (!/^-?\d+(\.\d+)?$/.test(text))
|
|
5
|
+
return text;
|
|
6
|
+
const digits = text.replace(/^-/, '').replace('.', '').replace(/^0+/, '');
|
|
7
|
+
return digits.length <= 15 ? Number(text) : text;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One exported cell from a serialized record value (`serializeRecord`): numbers stay numbers, enums show their labels,
|
|
11
|
+
* relations their titles, booleans yes/no in `locale`, dates real dates. `offset` (minutes east of UTC) shifts
|
|
12
|
+
* date-times to the exporter's clock, since a spreadsheet cell has no time zone.
|
|
13
|
+
*/
|
|
14
|
+
export function toCell(value, field, locale, offset) {
|
|
15
|
+
if (value === null || value === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
if (field.type === 'relation') {
|
|
18
|
+
const refs = (Array.isArray(value) ? value : [value]);
|
|
19
|
+
return refs.map((ref) => ref.title ?? String(ref.id)).join(locale.startsWith('fa') ? '، ' : ', ');
|
|
20
|
+
}
|
|
21
|
+
switch (field.type) {
|
|
22
|
+
case 'number':
|
|
23
|
+
return typeof value === 'number' ? value : String(value);
|
|
24
|
+
case 'decimal':
|
|
25
|
+
case 'bigint':
|
|
26
|
+
return numeric(String(value));
|
|
27
|
+
case 'boolean': {
|
|
28
|
+
const [yes, no] = YES_NO[locale.split('-')[0]] ?? YES_NO.en;
|
|
29
|
+
return value ? yes : no;
|
|
30
|
+
}
|
|
31
|
+
case 'enum':
|
|
32
|
+
return field.enumLabels?.[String(value)] ?? String(value);
|
|
33
|
+
case 'date': {
|
|
34
|
+
const date = new Date(`${String(value).slice(0, 10)}T00:00:00Z`);
|
|
35
|
+
return Number.isNaN(date.getTime()) ? String(value) : { date, time: false };
|
|
36
|
+
}
|
|
37
|
+
case 'datetime': {
|
|
38
|
+
const date = new Date(String(value));
|
|
39
|
+
return Number.isNaN(date.getTime()) ? String(value) : { date: new Date(date.getTime() + offset * 60_000), time: true };
|
|
40
|
+
}
|
|
41
|
+
default:
|
|
42
|
+
return typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=export-cells.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-cells.js","sourceRoot":"","sources":["../../src/export/export-cells.ts"],"names":[],"mappings":"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"}
|