@isaiahiroko/tilmart-ngx-apps-admin 0.2.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 +9 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs +154 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +294 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +224 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs +261 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs +365 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs +286 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs +73 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs +404 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs +290 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs +112 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs +116 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs +281 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs +115 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs +185 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs +70 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +274 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs +81 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +105 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +222 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs +150 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +107 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs +92 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs +111 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +475 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -0
- package/package.json +34 -0
- package/types/isaiahiroko-tilmart-ngx-apps-admin.d.ts +32 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs","sources":["../../../projects/admin/src/lib/pages/coupons/coupons.component.ts","../../../projects/admin/src/lib/pages/coupons/coupons.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, titleCase, using } from '@thescaffold/ngx-core';\nimport { BehaviorSubject, map, takeUntil } from 'rxjs';\n\n// P7.1 — ops console section pass (PRD 6.2, TRD 9.2). A standard list\n// view on `x-webget-collection` over `apps/commerce/coupon`, matching the\n// established admin pattern (users, routes, inbox). ops CRUD is covered\n// by `admin:*:apps:commerce:*`.\n@Component({\n standalone: false,\n selector: 'x-coupons',\n template: `\n <div class=\"x-page-head\">\n <div class=\"x-page-title\">Coupons</div>\n <div class=\"x-page-subtitle\">Redeemable codes bound to a promotion, with usage and spend limits.</div>\n </div>\n\n @if (list$ | async; as list) {\n <x-webget-collection [data]=\"list\"></x-webget-collection>\n } @else {\n <x-element-loader></x-element-loader>\n }\n `,\n styles: [\n `\n .x-page-head {\n margin-bottom: 20px;\n }\n .x-page-title {\n font-size: 24px;\n font-weight: 800;\n color: var(--color-text);\n }\n .x-page-subtitle {\n font-size: 13.5px;\n color: var(--color-text-muted);\n margin-top: 2px;\n }\n `,\n ],\n})\nexport class CouponsComponent extends BaseDirective {\n pluralName = 'coupons';\n dataSource = this.apiService.on('apps/commerce/coupon');\n\n columns = [\n { key: 'code', value: 'Code' },\n { key: 'usageCount', value: 'Used' },\n { key: 'usageLimit', value: 'Limit' },\n { key: 'enabled', value: 'Enabled', type: 'class', class: 'badge text-bg-light border' },\n ];\n\n page = 1;\n perPage = 25;\n\n list$ = new BehaviorSubject<any>(null);\n\n override init = () => {\n this.list();\n };\n\n list(page = this.page, perPage = this.perPage) {\n this.dataSource\n .getMany({ page, perPage })\n .pipe(\n takeUntil(this.$),\n map((response) => this.toCollection(response)),\n )\n .subscribe(using((data) => this.list$.next(data)));\n }\n\n private toCollection(response: any) {\n return {\n title: titleCase(this.pluralName),\n columns: this.columns,\n rows: response?.data,\n hasHeaders: true,\n hasTextFilter: true,\n page: response?.meta?.current_page,\n perPage: response?.meta?.per_page,\n total: response?.meta?.total,\n onPage: (page: number) => this.list(page),\n };\n }\n}\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';\nimport { CouponsComponent } from './coupons.component';\n\n@NgModule({\n declarations: [CouponsComponent],\n imports: [\n SharedModule,\n ElementsModule,\n WebgetsModule,\n RouterModule.forChild([{ path: '', component: CouponsComponent }]),\n ],\n})\nexport class CouponsModule {}\n"],"names":["i1"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { BaseDirective, using, titleCase, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { BehaviorSubject, takeUntil, map } from 'rxjs';
|
|
9
|
+
import * as i2 from '@angular/common';
|
|
10
|
+
|
|
11
|
+
// P7.1 — ops console section pass (PRD 6.2, TRD 9.2). A standard list
|
|
12
|
+
// view on `x-webget-collection` over `apps/commerce/customer`, matching the
|
|
13
|
+
// established admin pattern (users, routes, inbox). ops CRUD is covered
|
|
14
|
+
// by `admin:*:apps:commerce:*`.
|
|
15
|
+
class CustomersComponent extends BaseDirective {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.pluralName = 'customers';
|
|
19
|
+
this.dataSource = this.apiService.on('apps/commerce/customer');
|
|
20
|
+
this.columns = [
|
|
21
|
+
{ key: 'name', value: 'Name' },
|
|
22
|
+
{ key: 'email', value: 'Email' },
|
|
23
|
+
{ key: 'phone', value: 'Phone' },
|
|
24
|
+
{ key: 'createdAt', value: 'Joined', type: 'date' },
|
|
25
|
+
];
|
|
26
|
+
this.page = 1;
|
|
27
|
+
this.perPage = 25;
|
|
28
|
+
this.list$ = new BehaviorSubject(null);
|
|
29
|
+
this.init = () => {
|
|
30
|
+
this.list();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
list(page = this.page, perPage = this.perPage) {
|
|
34
|
+
this.dataSource
|
|
35
|
+
.getMany({ page, perPage })
|
|
36
|
+
.pipe(takeUntil(this.$), map((response) => this.toCollection(response)))
|
|
37
|
+
.subscribe(using((data) => this.list$.next(data)));
|
|
38
|
+
}
|
|
39
|
+
toCollection(response) {
|
|
40
|
+
return {
|
|
41
|
+
title: titleCase(this.pluralName),
|
|
42
|
+
columns: this.columns,
|
|
43
|
+
rows: response?.data,
|
|
44
|
+
hasHeaders: true,
|
|
45
|
+
hasTextFilter: true,
|
|
46
|
+
page: response?.meta?.current_page,
|
|
47
|
+
perPage: response?.meta?.per_page,
|
|
48
|
+
total: response?.meta?.total,
|
|
49
|
+
onPage: (page) => this.list(page),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
53
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: CustomersComponent, isStandalone: false, selector: "x-customers", usesInheritance: true, ngImport: i0, template: `
|
|
54
|
+
<div class="x-page-head">
|
|
55
|
+
<div class="x-page-title">Customers</div>
|
|
56
|
+
<div class="x-page-subtitle">Commerce customer records — orders, comms and group membership.</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
@if (list$ | async; as list) {
|
|
60
|
+
<x-webget-collection [data]="list"></x-webget-collection>
|
|
61
|
+
} @else {
|
|
62
|
+
<x-element-loader></x-element-loader>
|
|
63
|
+
}
|
|
64
|
+
`, isInline: true, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: i1.CollectionComponent, selector: "x-webget-collection", inputs: ["data"], outputs: ["search", "filter", "refresh", "select", "page", "show"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
|
|
65
|
+
}
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersComponent, decorators: [{
|
|
67
|
+
type: Component,
|
|
68
|
+
args: [{ standalone: false, selector: 'x-customers', template: `
|
|
69
|
+
<div class="x-page-head">
|
|
70
|
+
<div class="x-page-title">Customers</div>
|
|
71
|
+
<div class="x-page-subtitle">Commerce customer records — orders, comms and group membership.</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
@if (list$ | async; as list) {
|
|
75
|
+
<x-webget-collection [data]="list"></x-webget-collection>
|
|
76
|
+
} @else {
|
|
77
|
+
<x-element-loader></x-element-loader>
|
|
78
|
+
}
|
|
79
|
+
`, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"] }]
|
|
80
|
+
}] });
|
|
81
|
+
|
|
82
|
+
class CustomersModule {
|
|
83
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
84
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, declarations: [CustomersComponent], imports: [SharedModule,
|
|
85
|
+
ElementsModule,
|
|
86
|
+
WebgetsModule, i1$1.RouterModule] }); }
|
|
87
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, imports: [SharedModule,
|
|
88
|
+
ElementsModule,
|
|
89
|
+
WebgetsModule,
|
|
90
|
+
RouterModule.forChild([{ path: '', component: CustomersComponent }])] }); }
|
|
91
|
+
}
|
|
92
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, decorators: [{
|
|
93
|
+
type: NgModule,
|
|
94
|
+
args: [{
|
|
95
|
+
declarations: [CustomersComponent],
|
|
96
|
+
imports: [
|
|
97
|
+
SharedModule,
|
|
98
|
+
ElementsModule,
|
|
99
|
+
WebgetsModule,
|
|
100
|
+
RouterModule.forChild([{ path: '', component: CustomersComponent }]),
|
|
101
|
+
],
|
|
102
|
+
}]
|
|
103
|
+
}] });
|
|
104
|
+
|
|
105
|
+
export { CustomersModule };
|
|
106
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs","sources":["../../../projects/admin/src/lib/pages/customers/customers.component.ts","../../../projects/admin/src/lib/pages/customers/customers.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, titleCase, using } from '@thescaffold/ngx-core';\nimport { BehaviorSubject, map, takeUntil } from 'rxjs';\n\n// P7.1 — ops console section pass (PRD 6.2, TRD 9.2). A standard list\n// view on `x-webget-collection` over `apps/commerce/customer`, matching the\n// established admin pattern (users, routes, inbox). ops CRUD is covered\n// by `admin:*:apps:commerce:*`.\n@Component({\n standalone: false,\n selector: 'x-customers',\n template: `\n <div class=\"x-page-head\">\n <div class=\"x-page-title\">Customers</div>\n <div class=\"x-page-subtitle\">Commerce customer records — orders, comms and group membership.</div>\n </div>\n\n @if (list$ | async; as list) {\n <x-webget-collection [data]=\"list\"></x-webget-collection>\n } @else {\n <x-element-loader></x-element-loader>\n }\n `,\n styles: [\n `\n .x-page-head {\n margin-bottom: 20px;\n }\n .x-page-title {\n font-size: 24px;\n font-weight: 800;\n color: var(--color-text);\n }\n .x-page-subtitle {\n font-size: 13.5px;\n color: var(--color-text-muted);\n margin-top: 2px;\n }\n `,\n ],\n})\nexport class CustomersComponent extends BaseDirective {\n pluralName = 'customers';\n dataSource = this.apiService.on('apps/commerce/customer');\n\n columns = [\n { key: 'name', value: 'Name' },\n { key: 'email', value: 'Email' },\n { key: 'phone', value: 'Phone' },\n { key: 'createdAt', value: 'Joined', type: 'date' },\n ];\n\n page = 1;\n perPage = 25;\n\n list$ = new BehaviorSubject<any>(null);\n\n override init = () => {\n this.list();\n };\n\n list(page = this.page, perPage = this.perPage) {\n this.dataSource\n .getMany({ page, perPage })\n .pipe(\n takeUntil(this.$),\n map((response) => this.toCollection(response)),\n )\n .subscribe(using((data) => this.list$.next(data)));\n }\n\n private toCollection(response: any) {\n return {\n title: titleCase(this.pluralName),\n columns: this.columns,\n rows: response?.data,\n hasHeaders: true,\n hasTextFilter: true,\n page: response?.meta?.current_page,\n perPage: response?.meta?.per_page,\n total: response?.meta?.total,\n onPage: (page: number) => this.list(page),\n };\n }\n}\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';\nimport { CustomersComponent } from './customers.component';\n\n@NgModule({\n declarations: [CustomersComponent],\n imports: [\n SharedModule,\n ElementsModule,\n WebgetsModule,\n RouterModule.forChild([{ path: '', component: CustomersComponent }]),\n ],\n})\nexport class CustomersModule {}\n"],"names":["i1"],"mappings":"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"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { BaseDirective, using, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { BehaviorSubject, takeUntil } from 'rxjs';
|
|
9
|
+
import * as i2 from '@angular/common';
|
|
10
|
+
|
|
11
|
+
// P1.17, CAT-12 — reads `CatalogWorkflow.dataQualityReport` (P1.11's
|
|
12
|
+
// sibling addition) as-is; no client-side computation. Two of CAT-12's
|
|
13
|
+
// listed checks aren't here — "no reviews" and "zero stock with active
|
|
14
|
+
// demand" need `Review` (P5.10) and real order/cart demand signals (P4)
|
|
15
|
+
// that don't exist yet. See the report method's own doc comment.
|
|
16
|
+
class DataQualityComponent extends BaseDirective {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.reportSource = this.apiService.on('apps/commerce/data-quality');
|
|
20
|
+
this.report$ = new BehaviorSubject(null);
|
|
21
|
+
this.formatNaira = (amount) => '₦' + Math.round(amount || 0).toLocaleString('en-US');
|
|
22
|
+
this.init = () => {
|
|
23
|
+
this.load();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
load() {
|
|
27
|
+
// A bare GET, same as `.getMany()` hits for any other resource's list
|
|
28
|
+
// route — this endpoint just isn't a collection (`CatalogWorkflow
|
|
29
|
+
// .dataQualityReport` returns one report object, not `{data:[...]}`).
|
|
30
|
+
this.reportSource
|
|
31
|
+
.getMany()
|
|
32
|
+
.pipe(takeUntil(this.$))
|
|
33
|
+
.subscribe(using((response) => this.report$.next(response?.data ?? null)));
|
|
34
|
+
}
|
|
35
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
36
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: DataQualityComponent, isStandalone: false, selector: "x-data-quality", usesInheritance: true, ngImport: i0, template: `
|
|
37
|
+
<div class="x-page-head">
|
|
38
|
+
<div class="x-page-title">Data quality</div>
|
|
39
|
+
<div class="x-page-subtitle">
|
|
40
|
+
CAT-12 — catalogue completeness across every product.
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
@if (report$ | async; as report) {
|
|
45
|
+
<div class="x-summary-row">
|
|
46
|
+
<div class="x-summary-card">
|
|
47
|
+
<div class="x-summary-value">{{ report.totalProducts }}</div>
|
|
48
|
+
<div class="x-summary-label">Total products</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingRequiredAttributes.length">
|
|
51
|
+
<div class="x-summary-value">{{ report.missingRequiredAttributes.length }}</div>
|
|
52
|
+
<div class="x-summary-label">Missing required attributes</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingMedia.length">
|
|
55
|
+
<div class="x-summary-value">{{ report.missingMedia.length }}</div>
|
|
56
|
+
<div class="x-summary-label">Missing images</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingAltText.length">
|
|
59
|
+
<div class="x-summary-value">{{ report.missingAltText.length }}</div>
|
|
60
|
+
<div class="x-summary-label">Missing alt text</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingDocuments.length">
|
|
63
|
+
<div class="x-summary-value">{{ report.missingDocuments.length }}</div>
|
|
64
|
+
<div class="x-summary-label">Missing documents</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingPrice.length">
|
|
67
|
+
<div class="x-summary-value">{{ report.missingPrice.length }}</div>
|
|
68
|
+
<div class="x-summary-label">No price set</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.priceOutliers.length">
|
|
71
|
+
<div class="x-summary-value">{{ report.priceOutliers.length }}</div>
|
|
72
|
+
<div class="x-summary-label">Price outliers</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
@if (report.priceOutliers.length) {
|
|
77
|
+
<div class="x-section-head">
|
|
78
|
+
<div class="x-section-title">Price outliers</div>
|
|
79
|
+
<div class="x-section-subtitle">
|
|
80
|
+
More than 3× (or under a third of) their category's median
|
|
81
|
+
cheapest price — usually a typo, not a real price.
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="x-outlier-table">
|
|
85
|
+
<div class="x-outlier-row x-outlier-head">
|
|
86
|
+
<div>Product</div>
|
|
87
|
+
<div>Category</div>
|
|
88
|
+
<div>Price</div>
|
|
89
|
+
<div>Category median</div>
|
|
90
|
+
</div>
|
|
91
|
+
@for (row of report.priceOutliers; track row.productId) {
|
|
92
|
+
<div class="x-outlier-row">
|
|
93
|
+
<div>{{ row.name }}</div>
|
|
94
|
+
<div>{{ row.categoryName }}</div>
|
|
95
|
+
<div>{{ formatNaira(row.price) }}</div>
|
|
96
|
+
<div>{{ formatNaira(row.categoryMedian) }}</div>
|
|
97
|
+
</div>
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
<div class="x-lists-grid">
|
|
103
|
+
<div class="x-list-card">
|
|
104
|
+
<div class="x-list-title">Missing required attributes</div>
|
|
105
|
+
@for (row of report.missingRequiredAttributes; track row.productId) {
|
|
106
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
107
|
+
} @empty {
|
|
108
|
+
<div class="x-list-empty">None — every product's required fields are set.</div>
|
|
109
|
+
}
|
|
110
|
+
</div>
|
|
111
|
+
<div class="x-list-card">
|
|
112
|
+
<div class="x-list-title">Missing images</div>
|
|
113
|
+
@for (row of report.missingMedia; track row.productId) {
|
|
114
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
115
|
+
} @empty {
|
|
116
|
+
<div class="x-list-empty">None.</div>
|
|
117
|
+
}
|
|
118
|
+
</div>
|
|
119
|
+
<div class="x-list-card">
|
|
120
|
+
<div class="x-list-title">Missing alt text</div>
|
|
121
|
+
@for (row of report.missingAltText; track row.productId) {
|
|
122
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
123
|
+
} @empty {
|
|
124
|
+
<div class="x-list-empty">None.</div>
|
|
125
|
+
}
|
|
126
|
+
</div>
|
|
127
|
+
<div class="x-list-card">
|
|
128
|
+
<div class="x-list-title">Missing documents</div>
|
|
129
|
+
@for (row of report.missingDocuments; track row.productId) {
|
|
130
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
131
|
+
} @empty {
|
|
132
|
+
<div class="x-list-empty">None.</div>
|
|
133
|
+
}
|
|
134
|
+
</div>
|
|
135
|
+
<div class="x-list-card">
|
|
136
|
+
<div class="x-list-title">No price set</div>
|
|
137
|
+
@for (row of report.missingPrice; track row.productId) {
|
|
138
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
139
|
+
} @empty {
|
|
140
|
+
<div class="x-list-empty">None.</div>
|
|
141
|
+
}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
} @else {
|
|
145
|
+
<x-element-loader></x-element-loader>
|
|
146
|
+
}
|
|
147
|
+
`, isInline: true, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-summary-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:28px}.x-summary-card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:14px 16px}.x-summary-flag{border-color:var(--color-warn)}.x-summary-value{font-size:26px;font-weight:800;color:var(--color-text);font-family:var(--font-mono)}.x-summary-flag .x-summary-value{color:color-mix(in srgb,var(--color-warn) 75%,black)}.x-summary-label{font-size:11.5px;color:var(--color-text-muted);margin-top:2px}.x-section-head{margin:8px 0 12px}.x-section-title{font-size:16px;font-weight:800;color:var(--color-text)}.x-section-subtitle{font-size:12.5px;color:var(--color-text-muted);margin-top:2px;max-width:520px}.x-outlier-table{background:var(--color-surface);border-radius:var(--radius-lg);overflow:hidden;margin-bottom:28px}.x-outlier-row{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:12px;padding:10px 16px;font-size:13px;border-bottom:1px solid var(--color-border-muted)}.x-outlier-row:last-child{border-bottom:0}.x-outlier-head{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:var(--color-text-muted)}.x-lists-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}.x-list-card{background:var(--color-surface);border-radius:var(--radius-lg);padding:14px 16px}.x-list-title{font-size:12.5px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:var(--color-text-muted);margin-bottom:8px}.x-list-item{font-size:13px;color:var(--color-text);padding:4px 0;border-bottom:1px solid var(--color-border-muted)}.x-list-item:last-child{border-bottom:0}.x-list-empty{font-size:12.5px;color:var(--color-text-muted)}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
|
|
148
|
+
}
|
|
149
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityComponent, decorators: [{
|
|
150
|
+
type: Component,
|
|
151
|
+
args: [{ standalone: false, selector: 'x-data-quality', template: `
|
|
152
|
+
<div class="x-page-head">
|
|
153
|
+
<div class="x-page-title">Data quality</div>
|
|
154
|
+
<div class="x-page-subtitle">
|
|
155
|
+
CAT-12 — catalogue completeness across every product.
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
@if (report$ | async; as report) {
|
|
160
|
+
<div class="x-summary-row">
|
|
161
|
+
<div class="x-summary-card">
|
|
162
|
+
<div class="x-summary-value">{{ report.totalProducts }}</div>
|
|
163
|
+
<div class="x-summary-label">Total products</div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingRequiredAttributes.length">
|
|
166
|
+
<div class="x-summary-value">{{ report.missingRequiredAttributes.length }}</div>
|
|
167
|
+
<div class="x-summary-label">Missing required attributes</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingMedia.length">
|
|
170
|
+
<div class="x-summary-value">{{ report.missingMedia.length }}</div>
|
|
171
|
+
<div class="x-summary-label">Missing images</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingAltText.length">
|
|
174
|
+
<div class="x-summary-value">{{ report.missingAltText.length }}</div>
|
|
175
|
+
<div class="x-summary-label">Missing alt text</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingDocuments.length">
|
|
178
|
+
<div class="x-summary-value">{{ report.missingDocuments.length }}</div>
|
|
179
|
+
<div class="x-summary-label">Missing documents</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.missingPrice.length">
|
|
182
|
+
<div class="x-summary-value">{{ report.missingPrice.length }}</div>
|
|
183
|
+
<div class="x-summary-label">No price set</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="x-summary-card" [class.x-summary-flag]="report.priceOutliers.length">
|
|
186
|
+
<div class="x-summary-value">{{ report.priceOutliers.length }}</div>
|
|
187
|
+
<div class="x-summary-label">Price outliers</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
@if (report.priceOutliers.length) {
|
|
192
|
+
<div class="x-section-head">
|
|
193
|
+
<div class="x-section-title">Price outliers</div>
|
|
194
|
+
<div class="x-section-subtitle">
|
|
195
|
+
More than 3× (or under a third of) their category's median
|
|
196
|
+
cheapest price — usually a typo, not a real price.
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="x-outlier-table">
|
|
200
|
+
<div class="x-outlier-row x-outlier-head">
|
|
201
|
+
<div>Product</div>
|
|
202
|
+
<div>Category</div>
|
|
203
|
+
<div>Price</div>
|
|
204
|
+
<div>Category median</div>
|
|
205
|
+
</div>
|
|
206
|
+
@for (row of report.priceOutliers; track row.productId) {
|
|
207
|
+
<div class="x-outlier-row">
|
|
208
|
+
<div>{{ row.name }}</div>
|
|
209
|
+
<div>{{ row.categoryName }}</div>
|
|
210
|
+
<div>{{ formatNaira(row.price) }}</div>
|
|
211
|
+
<div>{{ formatNaira(row.categoryMedian) }}</div>
|
|
212
|
+
</div>
|
|
213
|
+
}
|
|
214
|
+
</div>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
<div class="x-lists-grid">
|
|
218
|
+
<div class="x-list-card">
|
|
219
|
+
<div class="x-list-title">Missing required attributes</div>
|
|
220
|
+
@for (row of report.missingRequiredAttributes; track row.productId) {
|
|
221
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
222
|
+
} @empty {
|
|
223
|
+
<div class="x-list-empty">None — every product's required fields are set.</div>
|
|
224
|
+
}
|
|
225
|
+
</div>
|
|
226
|
+
<div class="x-list-card">
|
|
227
|
+
<div class="x-list-title">Missing images</div>
|
|
228
|
+
@for (row of report.missingMedia; track row.productId) {
|
|
229
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
230
|
+
} @empty {
|
|
231
|
+
<div class="x-list-empty">None.</div>
|
|
232
|
+
}
|
|
233
|
+
</div>
|
|
234
|
+
<div class="x-list-card">
|
|
235
|
+
<div class="x-list-title">Missing alt text</div>
|
|
236
|
+
@for (row of report.missingAltText; track row.productId) {
|
|
237
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
238
|
+
} @empty {
|
|
239
|
+
<div class="x-list-empty">None.</div>
|
|
240
|
+
}
|
|
241
|
+
</div>
|
|
242
|
+
<div class="x-list-card">
|
|
243
|
+
<div class="x-list-title">Missing documents</div>
|
|
244
|
+
@for (row of report.missingDocuments; track row.productId) {
|
|
245
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
246
|
+
} @empty {
|
|
247
|
+
<div class="x-list-empty">None.</div>
|
|
248
|
+
}
|
|
249
|
+
</div>
|
|
250
|
+
<div class="x-list-card">
|
|
251
|
+
<div class="x-list-title">No price set</div>
|
|
252
|
+
@for (row of report.missingPrice; track row.productId) {
|
|
253
|
+
<div class="x-list-item">{{ row.name }}</div>
|
|
254
|
+
} @empty {
|
|
255
|
+
<div class="x-list-empty">None.</div>
|
|
256
|
+
}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
} @else {
|
|
260
|
+
<x-element-loader></x-element-loader>
|
|
261
|
+
}
|
|
262
|
+
`, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-summary-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:28px}.x-summary-card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:14px 16px}.x-summary-flag{border-color:var(--color-warn)}.x-summary-value{font-size:26px;font-weight:800;color:var(--color-text);font-family:var(--font-mono)}.x-summary-flag .x-summary-value{color:color-mix(in srgb,var(--color-warn) 75%,black)}.x-summary-label{font-size:11.5px;color:var(--color-text-muted);margin-top:2px}.x-section-head{margin:8px 0 12px}.x-section-title{font-size:16px;font-weight:800;color:var(--color-text)}.x-section-subtitle{font-size:12.5px;color:var(--color-text-muted);margin-top:2px;max-width:520px}.x-outlier-table{background:var(--color-surface);border-radius:var(--radius-lg);overflow:hidden;margin-bottom:28px}.x-outlier-row{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:12px;padding:10px 16px;font-size:13px;border-bottom:1px solid var(--color-border-muted)}.x-outlier-row:last-child{border-bottom:0}.x-outlier-head{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:var(--color-text-muted)}.x-lists-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}.x-list-card{background:var(--color-surface);border-radius:var(--radius-lg);padding:14px 16px}.x-list-title{font-size:12.5px;font-weight:800;text-transform:uppercase;letter-spacing:.3px;color:var(--color-text-muted);margin-bottom:8px}.x-list-item{font-size:13px;color:var(--color-text);padding:4px 0;border-bottom:1px solid var(--color-border-muted)}.x-list-item:last-child{border-bottom:0}.x-list-empty{font-size:12.5px;color:var(--color-text-muted)}\n"] }]
|
|
263
|
+
}] });
|
|
264
|
+
|
|
265
|
+
class DataQualityModule {
|
|
266
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
267
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, declarations: [DataQualityComponent], imports: [SharedModule,
|
|
268
|
+
ElementsModule, i1$1.RouterModule] }); }
|
|
269
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, imports: [SharedModule,
|
|
270
|
+
ElementsModule,
|
|
271
|
+
RouterModule.forChild([{ path: '', component: DataQualityComponent }])] }); }
|
|
272
|
+
}
|
|
273
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, decorators: [{
|
|
274
|
+
type: NgModule,
|
|
275
|
+
args: [{
|
|
276
|
+
declarations: [DataQualityComponent],
|
|
277
|
+
imports: [
|
|
278
|
+
SharedModule,
|
|
279
|
+
ElementsModule,
|
|
280
|
+
RouterModule.forChild([{ path: '', component: DataQualityComponent }]),
|
|
281
|
+
],
|
|
282
|
+
}]
|
|
283
|
+
}] });
|
|
284
|
+
|
|
285
|
+
export { DataQualityModule };
|
|
286
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs","sources":["../../../projects/admin/src/lib/pages/data-quality/data-quality.component.ts","../../../projects/admin/src/lib/pages/data-quality/data-quality.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, using } from '@thescaffold/ngx-core';\nimport { BehaviorSubject, takeUntil } from 'rxjs';\n\ninterface DataQualityRow {\n productId: string;\n name: string;\n}\n\ninterface PriceOutlierRow extends DataQualityRow {\n categoryName: string;\n price: number;\n categoryMedian: number;\n}\n\ninterface DataQualityReportVm {\n totalProducts: number;\n missingRequiredAttributes: DataQualityRow[];\n missingMedia: DataQualityRow[];\n missingAltText: DataQualityRow[];\n missingDocuments: DataQualityRow[];\n missingPrice: DataQualityRow[];\n priceOutliers: PriceOutlierRow[];\n}\n\n// P1.17, CAT-12 — reads `CatalogWorkflow.dataQualityReport` (P1.11's\n// sibling addition) as-is; no client-side computation. Two of CAT-12's\n// listed checks aren't here — \"no reviews\" and \"zero stock with active\n// demand\" need `Review` (P5.10) and real order/cart demand signals (P4)\n// that don't exist yet. See the report method's own doc comment.\n@Component({\n standalone: false,\n selector: 'x-data-quality',\n template: `\n <div class=\"x-page-head\">\n <div class=\"x-page-title\">Data quality</div>\n <div class=\"x-page-subtitle\">\n CAT-12 — catalogue completeness across every product.\n </div>\n </div>\n\n @if (report$ | async; as report) {\n <div class=\"x-summary-row\">\n <div class=\"x-summary-card\">\n <div class=\"x-summary-value\">{{ report.totalProducts }}</div>\n <div class=\"x-summary-label\">Total products</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.missingRequiredAttributes.length\">\n <div class=\"x-summary-value\">{{ report.missingRequiredAttributes.length }}</div>\n <div class=\"x-summary-label\">Missing required attributes</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.missingMedia.length\">\n <div class=\"x-summary-value\">{{ report.missingMedia.length }}</div>\n <div class=\"x-summary-label\">Missing images</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.missingAltText.length\">\n <div class=\"x-summary-value\">{{ report.missingAltText.length }}</div>\n <div class=\"x-summary-label\">Missing alt text</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.missingDocuments.length\">\n <div class=\"x-summary-value\">{{ report.missingDocuments.length }}</div>\n <div class=\"x-summary-label\">Missing documents</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.missingPrice.length\">\n <div class=\"x-summary-value\">{{ report.missingPrice.length }}</div>\n <div class=\"x-summary-label\">No price set</div>\n </div>\n <div class=\"x-summary-card\" [class.x-summary-flag]=\"report.priceOutliers.length\">\n <div class=\"x-summary-value\">{{ report.priceOutliers.length }}</div>\n <div class=\"x-summary-label\">Price outliers</div>\n </div>\n </div>\n\n @if (report.priceOutliers.length) {\n <div class=\"x-section-head\">\n <div class=\"x-section-title\">Price outliers</div>\n <div class=\"x-section-subtitle\">\n More than 3× (or under a third of) their category's median\n cheapest price — usually a typo, not a real price.\n </div>\n </div>\n <div class=\"x-outlier-table\">\n <div class=\"x-outlier-row x-outlier-head\">\n <div>Product</div>\n <div>Category</div>\n <div>Price</div>\n <div>Category median</div>\n </div>\n @for (row of report.priceOutliers; track row.productId) {\n <div class=\"x-outlier-row\">\n <div>{{ row.name }}</div>\n <div>{{ row.categoryName }}</div>\n <div>{{ formatNaira(row.price) }}</div>\n <div>{{ formatNaira(row.categoryMedian) }}</div>\n </div>\n }\n </div>\n }\n\n <div class=\"x-lists-grid\">\n <div class=\"x-list-card\">\n <div class=\"x-list-title\">Missing required attributes</div>\n @for (row of report.missingRequiredAttributes; track row.productId) {\n <div class=\"x-list-item\">{{ row.name }}</div>\n } @empty {\n <div class=\"x-list-empty\">None — every product's required fields are set.</div>\n }\n </div>\n <div class=\"x-list-card\">\n <div class=\"x-list-title\">Missing images</div>\n @for (row of report.missingMedia; track row.productId) {\n <div class=\"x-list-item\">{{ row.name }}</div>\n } @empty {\n <div class=\"x-list-empty\">None.</div>\n }\n </div>\n <div class=\"x-list-card\">\n <div class=\"x-list-title\">Missing alt text</div>\n @for (row of report.missingAltText; track row.productId) {\n <div class=\"x-list-item\">{{ row.name }}</div>\n } @empty {\n <div class=\"x-list-empty\">None.</div>\n }\n </div>\n <div class=\"x-list-card\">\n <div class=\"x-list-title\">Missing documents</div>\n @for (row of report.missingDocuments; track row.productId) {\n <div class=\"x-list-item\">{{ row.name }}</div>\n } @empty {\n <div class=\"x-list-empty\">None.</div>\n }\n </div>\n <div class=\"x-list-card\">\n <div class=\"x-list-title\">No price set</div>\n @for (row of report.missingPrice; track row.productId) {\n <div class=\"x-list-item\">{{ row.name }}</div>\n } @empty {\n <div class=\"x-list-empty\">None.</div>\n }\n </div>\n </div>\n } @else {\n <x-element-loader></x-element-loader>\n }\n `,\n styles: [\n `\n .x-page-head {\n margin-bottom: 20px;\n }\n .x-page-title {\n font-size: 24px;\n font-weight: 800;\n color: var(--color-text);\n }\n .x-page-subtitle {\n font-size: 13.5px;\n color: var(--color-text-muted);\n margin-top: 2px;\n }\n .x-summary-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 12px;\n margin-bottom: 28px;\n }\n .x-summary-card {\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n padding: 14px 16px;\n }\n .x-summary-flag {\n border-color: var(--color-warn);\n }\n .x-summary-value {\n font-size: 26px;\n font-weight: 800;\n color: var(--color-text);\n font-family: var(--font-mono);\n }\n .x-summary-flag .x-summary-value {\n color: color-mix(in srgb, var(--color-warn) 75%, black);\n }\n .x-summary-label {\n font-size: 11.5px;\n color: var(--color-text-muted);\n margin-top: 2px;\n }\n .x-section-head {\n margin: 8px 0 12px;\n }\n .x-section-title {\n font-size: 16px;\n font-weight: 800;\n color: var(--color-text);\n }\n .x-section-subtitle {\n font-size: 12.5px;\n color: var(--color-text-muted);\n margin-top: 2px;\n max-width: 520px;\n }\n .x-outlier-table {\n background: var(--color-surface);\n border-radius: var(--radius-lg);\n overflow: hidden;\n margin-bottom: 28px;\n }\n .x-outlier-row {\n display: grid;\n grid-template-columns: 2fr 1fr 1fr 1fr;\n gap: 12px;\n padding: 10px 16px;\n font-size: 13px;\n border-bottom: 1px solid var(--color-border-muted);\n }\n .x-outlier-row:last-child {\n border-bottom: 0;\n }\n .x-outlier-head {\n font-size: 11px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.4px;\n color: var(--color-text-muted);\n }\n .x-lists-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));\n gap: 16px;\n }\n .x-list-card {\n background: var(--color-surface);\n border-radius: var(--radius-lg);\n padding: 14px 16px;\n }\n .x-list-title {\n font-size: 12.5px;\n font-weight: 800;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: var(--color-text-muted);\n margin-bottom: 8px;\n }\n .x-list-item {\n font-size: 13px;\n color: var(--color-text);\n padding: 4px 0;\n border-bottom: 1px solid var(--color-border-muted);\n }\n .x-list-item:last-child {\n border-bottom: 0;\n }\n .x-list-empty {\n font-size: 12.5px;\n color: var(--color-text-muted);\n }\n `,\n ],\n})\nexport class DataQualityComponent extends BaseDirective {\n private readonly reportSource = this.apiService.on('apps/commerce/data-quality');\n\n report$ = new BehaviorSubject<DataQualityReportVm | null>(null);\n\n readonly formatNaira = (amount: number) =>\n '₦' + Math.round(amount || 0).toLocaleString('en-US');\n\n override init = () => {\n this.load();\n };\n\n load() {\n // A bare GET, same as `.getMany()` hits for any other resource's list\n // route — this endpoint just isn't a collection (`CatalogWorkflow\n // .dataQualityReport` returns one report object, not `{data:[...]}`).\n this.reportSource\n .getMany()\n .pipe(takeUntil(this.$))\n .subscribe(\n using((response: any) => this.report$.next(response?.data ?? null)),\n );\n }\n}\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule } from '@thescaffold/ngx-ui';\nimport { DataQualityComponent } from './data-quality.component';\n\n@NgModule({\n declarations: [DataQualityComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RouterModule.forChild([{ path: '', component: DataQualityComponent }]),\n ],\n})\nexport class DataQualityModule {}\n"],"names":["i1"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { BaseDirective, using, notify, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { takeUntil, firstValueFrom } from 'rxjs';
|
|
9
|
+
import * as i2 from '@angular/common';
|
|
10
|
+
|
|
11
|
+
// P7.7, SEC-04/SEC-05 — the NDPR request queue. `export` requests are
|
|
12
|
+
// self-served on the storefront; `deletion` requests land here for ops to
|
|
13
|
+
// fulfil within the SLA. Fulfilling runs
|
|
14
|
+
// `PrivacyWorkflow.anonymiseCustomer` and records what was retained.
|
|
15
|
+
class DataRequestsComponent extends BaseDirective {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.source = this.apiService.on('apps/commerce/data-request');
|
|
19
|
+
this.loading = true;
|
|
20
|
+
this.requests = [];
|
|
21
|
+
this.fulfilling = false;
|
|
22
|
+
this.init = () => {
|
|
23
|
+
this.load();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
load() {
|
|
27
|
+
this.loading = true;
|
|
28
|
+
this.source
|
|
29
|
+
.getMany({ perPage: 200 })
|
|
30
|
+
.pipe(takeUntil(this.$))
|
|
31
|
+
.subscribe(using((res) => {
|
|
32
|
+
this.requests = (res?.data ?? []).sort((a, b) => this.rank(a) - this.rank(b) ||
|
|
33
|
+
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
34
|
+
this.loading = false;
|
|
35
|
+
if (this.selected) {
|
|
36
|
+
this.selected = this.requests.find((r) => r.id === this.selected.id);
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
rank(r) {
|
|
41
|
+
if (r.status === 'completed' || r.status === 'rejected')
|
|
42
|
+
return 3;
|
|
43
|
+
return r.type === 'deletion' ? 0 : 1;
|
|
44
|
+
}
|
|
45
|
+
async setStatus(status) {
|
|
46
|
+
if (!this.selected)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
await firstValueFrom(this.source.update(this.selected.id, { status }));
|
|
50
|
+
this.load();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
notify('Could not update the request.', undefined, 'error');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async fulfil() {
|
|
57
|
+
if (!this.selected || this.fulfilling)
|
|
58
|
+
return;
|
|
59
|
+
if (!confirm('This will permanently anonymise the customer’s personal data. Continue?')) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.fulfilling = true;
|
|
63
|
+
try {
|
|
64
|
+
await firstValueFrom(this.apiService.post(`apps/commerce/data-request/${this.selected.id}/fulfil`, {}));
|
|
65
|
+
notify('Deletion completed.', undefined, 'success');
|
|
66
|
+
this.load();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
notify('Could not complete the deletion.', undefined, 'error');
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
this.fulfilling = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
76
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: DataRequestsComponent, isStandalone: false, selector: "x-data-requests", usesInheritance: true, ngImport: i0, template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Data requests</div>\n <div class=\"x-page-subtitle\">\n NDPR data-subject access & deletion requests (SEC-04 / SEC-05).\n </div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-dr-layout\">\n <div class=\"x-dr-list\">\n @for (r of requests; track r.id) {\n <button\n type=\"button\"\n class=\"x-dr-row\"\n [class.is-active]=\"selected?.id === r.id\"\n (click)=\"selected = r\"\n >\n <span class=\"x-dr-type x-dr-type-{{ r.type }}\">{{ r.type }}</span>\n <span class=\"x-dr-status\">{{ r.status.replace('_', ' ') }}</span>\n <span class=\"x-dr-when\">{{ r.createdAt | date: 'shortDate' }}</span>\n </button>\n } @empty {\n <div class=\"x-dr-empty\">No requests.</div>\n }\n </div>\n\n <div class=\"x-dr-detail\">\n @if (!selected) {\n <div class=\"x-dr-empty\">Select a request.</div>\n } @else {\n <div class=\"x-dr-d-title\">{{ selected.type | titlecase }} request</div>\n <dl class=\"x-dr-meta\">\n <dt>Status</dt><dd>{{ selected.status.replace('_', ' ') }}</dd>\n <dt>Customer</dt><dd>{{ selected.customerId || '\u2014' }}</dd>\n <dt>Contact</dt><dd>{{ selected.email }} {{ selected.phone }}</dd>\n <dt>Lodged</dt><dd>{{ selected.createdAt | date: 'medium' }}</dd>\n @if (selected.completedAt) {\n <dt>Completed</dt><dd>{{ selected.completedAt | date: 'medium' }}</dd>\n }\n </dl>\n @if (selected.note) {\n <p class=\"x-dr-note\"><strong>Note:</strong> {{ selected.note }}</p>\n }\n @if (selected.retentionNote) {\n <div class=\"x-dr-retain\">\n <strong>Retained:</strong>\n <pre>{{ selected.retentionNote }}</pre>\n </div>\n }\n\n @if (selected.status !== 'completed' && selected.status !== 'rejected') {\n <div class=\"x-dr-actions\">\n @if (selected.type === 'deletion' && selected.customerId) {\n <button type=\"button\" class=\"x-dr-btn x-dr-btn-danger\" [disabled]=\"fulfilling\" (click)=\"fulfil()\">\n {{ fulfilling ? 'Working\u2026' : 'Fulfil deletion' }}\n </button>\n }\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('in_progress')\">Mark in progress</button>\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('rejected')\">Reject</button>\n </div>\n }\n }\n </div>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 20px; }\n .x-page-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-page-subtitle { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-dr-layout { display: grid; grid-template-columns: 260px 1fr; gap: 16px; align-items: start; }\n @media (max-width: 720px) { .x-dr-layout { grid-template-columns: 1fr; } }\n .x-dr-list { display: flex; flex-direction: column; gap: 4px; }\n .x-dr-row { text-align: left; appearance: none; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-surface, #fff); padding: 9px 11px; cursor: pointer; display: flex; gap: 8px; align-items: center; font-size: 12.5px; }\n .x-dr-row.is-active { border-color: var(--color-brand); }\n .x-dr-type { font-weight: 700; text-transform: uppercase; font-size: 10.5px; }\n .x-dr-type-deletion { color: var(--color-danger, #c0392b); }\n .x-dr-status { color: var(--color-text-muted); text-transform: capitalize; }\n .x-dr-when { margin-left: auto; color: var(--color-text-muted); }\n .x-dr-detail { border: 1px solid var(--color-border); border-radius: 12px; padding: 16px; }\n .x-dr-d-title { font-size: 16px; font-weight: 800; color: var(--color-text); margin-bottom: 10px; }\n .x-dr-meta { display: grid; grid-template-columns: 90px 1fr; gap: 4px 12px; font-size: 13px; margin: 0 0 12px; }\n .x-dr-meta dt { color: var(--color-text-muted); }\n .x-dr-meta dd { margin: 0; color: var(--color-text); }\n .x-dr-note { font-size: 13px; color: var(--color-text-muted); }\n .x-dr-retain pre { white-space: pre-wrap; font: inherit; font-size: 12.5px; color: var(--color-text-muted); background: color-mix(in srgb, var(--color-border) 20%, transparent); padding: 8px; border-radius: 6px; }\n .x-dr-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }\n .x-dr-btn { font: inherit; font-weight: 600; font-size: 12.5px; padding: 7px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-dr-btn:disabled { opacity: 0.5; }\n .x-dr-btn-danger { color: #fff; background: var(--color-danger, #c0392b); border-color: var(--color-danger, #c0392b); }\n .x-dr-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--color-text-muted); }\n</style>\n", styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-dr-layout{display:grid;grid-template-columns:260px 1fr;gap:16px;align-items:start}@media(max-width:720px){.x-dr-layout{grid-template-columns:1fr}}.x-dr-list{display:flex;flex-direction:column;gap:4px}.x-dr-row{text-align:left;appearance:none;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface, #fff);padding:9px 11px;cursor:pointer;display:flex;gap:8px;align-items:center;font-size:12.5px}.x-dr-row.is-active{border-color:var(--color-brand)}.x-dr-type{font-weight:700;text-transform:uppercase;font-size:10.5px}.x-dr-type-deletion{color:var(--color-danger, #c0392b)}.x-dr-status{color:var(--color-text-muted);text-transform:capitalize}.x-dr-when{margin-left:auto;color:var(--color-text-muted)}.x-dr-detail{border:1px solid var(--color-border);border-radius:12px;padding:16px}.x-dr-d-title{font-size:16px;font-weight:800;color:var(--color-text);margin-bottom:10px}.x-dr-meta{display:grid;grid-template-columns:90px 1fr;gap:4px 12px;font-size:13px;margin:0 0 12px}.x-dr-meta dt{color:var(--color-text-muted)}.x-dr-meta dd{margin:0;color:var(--color-text)}.x-dr-note{font-size:13px;color:var(--color-text-muted)}.x-dr-retain pre{white-space:pre-wrap;font:inherit;font-size:12.5px;color:var(--color-text-muted);background:color-mix(in srgb,var(--color-border) 20%,transparent);padding:8px;border-radius:6px}.x-dr-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.x-dr-btn{font:inherit;font-weight:600;font-size:12.5px;padding:7px 12px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-surface, #fff);color:var(--color-text);cursor:pointer}.x-dr-btn:disabled{opacity:.5}.x-dr-btn-danger{color:#fff;background:var(--color-danger, #c0392b);border-color:var(--color-danger, #c0392b)}.x-dr-empty{padding:24px;text-align:center;font-size:13px;color:var(--color-text-muted)}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
|
|
77
|
+
}
|
|
78
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsComponent, decorators: [{
|
|
79
|
+
type: Component,
|
|
80
|
+
args: [{ standalone: false, selector: 'x-data-requests', template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Data requests</div>\n <div class=\"x-page-subtitle\">\n NDPR data-subject access & deletion requests (SEC-04 / SEC-05).\n </div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-dr-layout\">\n <div class=\"x-dr-list\">\n @for (r of requests; track r.id) {\n <button\n type=\"button\"\n class=\"x-dr-row\"\n [class.is-active]=\"selected?.id === r.id\"\n (click)=\"selected = r\"\n >\n <span class=\"x-dr-type x-dr-type-{{ r.type }}\">{{ r.type }}</span>\n <span class=\"x-dr-status\">{{ r.status.replace('_', ' ') }}</span>\n <span class=\"x-dr-when\">{{ r.createdAt | date: 'shortDate' }}</span>\n </button>\n } @empty {\n <div class=\"x-dr-empty\">No requests.</div>\n }\n </div>\n\n <div class=\"x-dr-detail\">\n @if (!selected) {\n <div class=\"x-dr-empty\">Select a request.</div>\n } @else {\n <div class=\"x-dr-d-title\">{{ selected.type | titlecase }} request</div>\n <dl class=\"x-dr-meta\">\n <dt>Status</dt><dd>{{ selected.status.replace('_', ' ') }}</dd>\n <dt>Customer</dt><dd>{{ selected.customerId || '\u2014' }}</dd>\n <dt>Contact</dt><dd>{{ selected.email }} {{ selected.phone }}</dd>\n <dt>Lodged</dt><dd>{{ selected.createdAt | date: 'medium' }}</dd>\n @if (selected.completedAt) {\n <dt>Completed</dt><dd>{{ selected.completedAt | date: 'medium' }}</dd>\n }\n </dl>\n @if (selected.note) {\n <p class=\"x-dr-note\"><strong>Note:</strong> {{ selected.note }}</p>\n }\n @if (selected.retentionNote) {\n <div class=\"x-dr-retain\">\n <strong>Retained:</strong>\n <pre>{{ selected.retentionNote }}</pre>\n </div>\n }\n\n @if (selected.status !== 'completed' && selected.status !== 'rejected') {\n <div class=\"x-dr-actions\">\n @if (selected.type === 'deletion' && selected.customerId) {\n <button type=\"button\" class=\"x-dr-btn x-dr-btn-danger\" [disabled]=\"fulfilling\" (click)=\"fulfil()\">\n {{ fulfilling ? 'Working\u2026' : 'Fulfil deletion' }}\n </button>\n }\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('in_progress')\">Mark in progress</button>\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('rejected')\">Reject</button>\n </div>\n }\n }\n </div>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 20px; }\n .x-page-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-page-subtitle { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-dr-layout { display: grid; grid-template-columns: 260px 1fr; gap: 16px; align-items: start; }\n @media (max-width: 720px) { .x-dr-layout { grid-template-columns: 1fr; } }\n .x-dr-list { display: flex; flex-direction: column; gap: 4px; }\n .x-dr-row { text-align: left; appearance: none; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-surface, #fff); padding: 9px 11px; cursor: pointer; display: flex; gap: 8px; align-items: center; font-size: 12.5px; }\n .x-dr-row.is-active { border-color: var(--color-brand); }\n .x-dr-type { font-weight: 700; text-transform: uppercase; font-size: 10.5px; }\n .x-dr-type-deletion { color: var(--color-danger, #c0392b); }\n .x-dr-status { color: var(--color-text-muted); text-transform: capitalize; }\n .x-dr-when { margin-left: auto; color: var(--color-text-muted); }\n .x-dr-detail { border: 1px solid var(--color-border); border-radius: 12px; padding: 16px; }\n .x-dr-d-title { font-size: 16px; font-weight: 800; color: var(--color-text); margin-bottom: 10px; }\n .x-dr-meta { display: grid; grid-template-columns: 90px 1fr; gap: 4px 12px; font-size: 13px; margin: 0 0 12px; }\n .x-dr-meta dt { color: var(--color-text-muted); }\n .x-dr-meta dd { margin: 0; color: var(--color-text); }\n .x-dr-note { font-size: 13px; color: var(--color-text-muted); }\n .x-dr-retain pre { white-space: pre-wrap; font: inherit; font-size: 12.5px; color: var(--color-text-muted); background: color-mix(in srgb, var(--color-border) 20%, transparent); padding: 8px; border-radius: 6px; }\n .x-dr-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }\n .x-dr-btn { font: inherit; font-weight: 600; font-size: 12.5px; padding: 7px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-dr-btn:disabled { opacity: 0.5; }\n .x-dr-btn-danger { color: #fff; background: var(--color-danger, #c0392b); border-color: var(--color-danger, #c0392b); }\n .x-dr-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--color-text-muted); }\n</style>\n" }]
|
|
81
|
+
}] });
|
|
82
|
+
|
|
83
|
+
class DataRequestsModule {
|
|
84
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
85
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, declarations: [DataRequestsComponent], imports: [SharedModule,
|
|
86
|
+
ElementsModule, i1$1.RouterModule] }); }
|
|
87
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, imports: [SharedModule,
|
|
88
|
+
ElementsModule,
|
|
89
|
+
RouterModule.forChild([{ path: '', component: DataRequestsComponent }])] }); }
|
|
90
|
+
}
|
|
91
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, decorators: [{
|
|
92
|
+
type: NgModule,
|
|
93
|
+
args: [{
|
|
94
|
+
declarations: [DataRequestsComponent],
|
|
95
|
+
imports: [
|
|
96
|
+
SharedModule,
|
|
97
|
+
ElementsModule,
|
|
98
|
+
RouterModule.forChild([{ path: '', component: DataRequestsComponent }]),
|
|
99
|
+
],
|
|
100
|
+
}]
|
|
101
|
+
}] });
|
|
102
|
+
|
|
103
|
+
export { DataRequestsModule };
|
|
104
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map
|