@isaiahiroko/tilmart-ngx-apps-admin 0.2.0 → 0.2.2
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/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs → isaiahiroko-tilmart-ngx-apps-admin-articles.module-Cpx9BZcg.mjs} +5 -5
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-articles.module-Cpx9BZcg.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-attribute-templates.module-CwQnW1cH.mjs +239 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-attribute-templates.module-CwQnW1cH.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs → isaiahiroko-tilmart-ngx-apps-admin-audit.module-D5YaCThD.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-audit.module-D5YaCThD.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-banners.module-KSapnx6Y.mjs +76 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-banners.module-KSapnx6Y.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-brands.module-7I0ni-tQ.mjs +70 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-brands.module-7I0ni-tQ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-DoxDNaNn.mjs +538 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-DoxDNaNn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-categories.module-OTUfenf0.mjs +94 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-categories.module-OTUfenf0.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Dk9mz7Wj.mjs +137 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Dk9mz7Wj.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs → isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-JiFnNJj_.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-JiFnNJj_.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs → isaiahiroko-tilmart-ngx-apps-admin-content.module-Bx6Zj0WX.mjs} +5 -5
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-content.module-Bx6Zj0WX.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CoRh6Hxq.mjs +85 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CoRh6Hxq.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs +123 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs → isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-CvFmMoJe.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-CvFmMoJe.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs → isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-CuZwNVPO.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-CuZwNVPO.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs +83 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs → isaiahiroko-tilmart-ngx-apps-admin-homepage.module-vtAoIwSu.mjs} +5 -5
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-homepage.module-vtAoIwSu.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs → isaiahiroko-tilmart-ngx-apps-admin-inbox.module-CRkfjWQT.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-inbox.module-CRkfjWQT.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-list-page.base-Cl7JZ-Tp.mjs +66 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-list-page.base-Cl7JZ-Tp.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs → isaiahiroko-tilmart-ngx-apps-admin-macros.module-CElxL9W4.mjs} +5 -5
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-macros.module-CElxL9W4.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs → isaiahiroko-tilmart-ngx-apps-admin-moderation.module-PGmFX7nk.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-moderation.module-PGmFX7nk.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs → isaiahiroko-tilmart-ngx-apps-admin-navigation.module-De1Cvqqa.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-navigation.module-De1Cvqqa.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs → isaiahiroko-tilmart-ngx-apps-admin-order-board.module-CmLNiEEP.mjs} +4 -4
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-order-board.module-CmLNiEEP.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs → isaiahiroko-tilmart-ngx-apps-admin-orders.module-Cfeb0QjH.mjs} +15 -33
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Cfeb0QjH.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-DstLDYM7.mjs +114 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-DstLDYM7.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG03OY1q.mjs +94 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG03OY1q.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs → isaiahiroko-tilmart-ngx-apps-admin-queues.module-D7iLwX0A.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-queues.module-D7iLwX0A.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-oQw5Xo5T.mjs +78 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-oQw5Xo5T.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs → isaiahiroko-tilmart-ngx-apps-admin-referrals.module-BIAY4L_u.mjs} +9 -34
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-BIAY4L_u.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs → isaiahiroko-tilmart-ngx-apps-admin-reports.module-DORiIVLQ.mjs} +5 -5
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-reports.module-DORiIVLQ.mjs.map} +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-C9Iu9CX6.mjs +163 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-C9Iu9CX6.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-CGBBHwId.mjs +81 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-CGBBHwId.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-Cz6xPNwB.mjs +318 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-Cz6xPNwB.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs → isaiahiroko-tilmart-ngx-apps-admin-shipments.module-Cs5CLamm.mjs} +13 -32
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-Cs5CLamm.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-simple-crud.base-C8SPw14H.mjs +140 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-simple-crud.base-C8SPw14H.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-Bt9W1oGR.mjs +102 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-Bt9W1oGR.mjs.map +1 -0
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs → isaiahiroko-tilmart-ngx-apps-admin-trace.module-9sRlGZCC.mjs} +6 -6
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-trace.module-9sRlGZCC.mjs.map} +1 -1
- package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs → isaiahiroko-tilmart-ngx-apps-admin-users.module-C31RJJJ_.mjs} +9 -34
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-C31RJJJ_.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +96 -72
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -1
- package/package.json +1 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +0 -294
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +0 -224
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +0 -274
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +0 -106
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +0 -105
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +0 -222
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +0 -107
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +0 -1
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +0 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, TemplateRef, ViewChild, Component, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/router';
|
|
4
|
+
import { RouterModule } from '@angular/router';
|
|
5
|
+
import { using, notify, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i2 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { FlyoutService, ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { takeUntil, firstValueFrom } from 'rxjs';
|
|
9
|
+
import { L as ListPageDirective } from './isaiahiroko-tilmart-ngx-apps-admin-list-page.base-Cl7JZ-Tp.mjs';
|
|
10
|
+
import * as i1 from '@angular/forms';
|
|
11
|
+
import * as i3 from '@angular/common';
|
|
12
|
+
|
|
13
|
+
// R1 production-readiness pass — ADM-05. Was list-only with no drill-in
|
|
14
|
+
// at all. Adds a detail flyout: order history, support tickets, group/
|
|
15
|
+
// status editing. Wallet and act-on-behalf are real, separate scope —
|
|
16
|
+
// no `WalletAccount` entity exists yet (that's P10.10/R3), and
|
|
17
|
+
// impersonating a customer's session is an auth feature, not a display
|
|
18
|
+
// one; both deserve their own careful pass rather than a stub here.
|
|
19
|
+
class CustomersComponent extends ListPageDirective {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.flyoutService = inject(FlyoutService);
|
|
23
|
+
this.pluralName = 'customers';
|
|
24
|
+
this.dataSource = this.apiService.on('apps/commerce/customer');
|
|
25
|
+
this.orderSource = this.apiService.on('apps/commerce/order');
|
|
26
|
+
this.ticketSource = this.apiService.on('apps/commerce/complaint-log');
|
|
27
|
+
this.groupSource = this.apiService.on('apps/commerce/customer-group');
|
|
28
|
+
this.columns = [
|
|
29
|
+
{ key: 'name', value: 'Name' },
|
|
30
|
+
{ key: 'email', value: 'Email' },
|
|
31
|
+
{ key: 'phone', value: 'Phone' },
|
|
32
|
+
{ key: 'createdAt', value: 'Joined', type: 'date' },
|
|
33
|
+
];
|
|
34
|
+
this.detail = null;
|
|
35
|
+
this.orders = [];
|
|
36
|
+
this.tickets = [];
|
|
37
|
+
this.groups = [];
|
|
38
|
+
this.saving = false;
|
|
39
|
+
this.statuses = ['active', 'suspended'];
|
|
40
|
+
this.init = () => {
|
|
41
|
+
this.list();
|
|
42
|
+
this.groupSource
|
|
43
|
+
.getMany({ page: 1, perPage: 100 })
|
|
44
|
+
.pipe(takeUntil(this.$))
|
|
45
|
+
.subscribe(using((res) => (this.groups = res?.data ?? [])));
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
extraCollectionFields() {
|
|
49
|
+
return {
|
|
50
|
+
links: {
|
|
51
|
+
secondary: [{ title: 'View', fn: (_link, item) => this.view(item) }],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async view(item) {
|
|
56
|
+
this.detail = null;
|
|
57
|
+
this.orders = [];
|
|
58
|
+
this.tickets = [];
|
|
59
|
+
this.flyoutService.open(this.detailFlyout);
|
|
60
|
+
const [detailRes, ordersRes, ticketsRes] = await Promise.all([
|
|
61
|
+
firstValueFrom(this.dataSource.getOne(item.id)),
|
|
62
|
+
firstValueFrom(this.orderSource.getMany({ customerId: item.id, page: 1, perPage: 20 })),
|
|
63
|
+
firstValueFrom(this.ticketSource.getMany({ customerId: item.id, page: 1, perPage: 20 })),
|
|
64
|
+
]);
|
|
65
|
+
this.detail = detailRes?.data ?? item;
|
|
66
|
+
this.orders = ordersRes?.data ?? [];
|
|
67
|
+
this.tickets = ticketsRes?.data ?? [];
|
|
68
|
+
}
|
|
69
|
+
async save() {
|
|
70
|
+
if (!this.detail)
|
|
71
|
+
return;
|
|
72
|
+
this.saving = true;
|
|
73
|
+
try {
|
|
74
|
+
await firstValueFrom(this.dataSource.update(this.detail.id, {
|
|
75
|
+
status: this.detail.status,
|
|
76
|
+
customerGroupId: this.detail.customerGroupId || undefined,
|
|
77
|
+
}));
|
|
78
|
+
notify('Customer saved.', undefined, 'success');
|
|
79
|
+
this.list(this.page, this.perPage);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
notify('Could not save the customer.', undefined, 'error');
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
this.saving = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
89
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: CustomersComponent, isStandalone: false, selector: "x-customers", viewQueries: [{ propertyName: "detailFlyout", first: true, predicate: ["detailFlyout"], descendants: true, read: TemplateRef, static: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Customers</div>\n <div class=\"x-page-subtitle\">Commerce customer records \u2014 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<ng-template #detailFlyout>\n <x-webget-flyout [data]=\"{ title: 'Customer detail' }\">\n <div class=\"p-4 d-flex flex-column gap-3\">\n @if (detail) {\n <div>\n <div class=\"fw-bold\">{{ detail.name || '\u2014' }}</div>\n <div class=\"small text-secondary\">{{ detail.email }} @if (detail.phone) { \u00B7 {{ detail.phone }} }</div>\n </div>\n\n <div class=\"d-flex flex-wrap gap-2\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.customerGroupId\">\n <option value=\"\">\u2014 no group \u2014</option>\n @for (g of groups; track g.id) { <option [value]=\"g.id\">{{ g.name }}</option> }\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-outline-dark\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving\u2026' : 'Save' }}\n </button>\n </div>\n\n <hr class=\"my-1\" />\n\n <div>\n <div class=\"fw-bold small mb-1\">Orders ({{ orders.length }})</div>\n @for (o of orders; track o.id) {\n <div class=\"small text-secondary d-flex justify-content-between\">\n <a [routerLink]=\"['/admin/orders']\" [queryParams]=\"{ q: o.orderNumber }\">{{ o.orderNumber }}</a>\n <span>{{ o.status }} \u00B7 {{ o.currency }} {{ o.grandTotal }}</span>\n </div>\n } @empty {\n <div class=\"small text-secondary\">No orders yet.</div>\n }\n </div>\n\n <div>\n <div class=\"fw-bold small mb-1\">Support tickets ({{ tickets.length }})</div>\n @for (t of tickets; track t.id) {\n <div class=\"small text-secondary\">\n {{ t.subject || t.category }} \u00B7 {{ t.status }} \u00B7 {{ t.priority }}\n </div>\n } @empty {\n <div class=\"small text-secondary\">None.</div>\n }\n </div>\n } @else {\n <x-element-loader></x-element-loader>\n }\n </div>\n </x-webget-flyout>\n</ng-template>\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}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i2.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: i2.CollectionComponent, selector: "x-webget-collection", inputs: ["data"], outputs: ["search", "filter", "refresh", "select", "page", "show"] }, { kind: "component", type: i2.FlyoutComponent, selector: "x-webget-flyout", inputs: ["data"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }] }); }
|
|
90
|
+
}
|
|
91
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersComponent, decorators: [{
|
|
92
|
+
type: Component,
|
|
93
|
+
args: [{ standalone: false, selector: 'x-customers', template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Customers</div>\n <div class=\"x-page-subtitle\">Commerce customer records \u2014 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<ng-template #detailFlyout>\n <x-webget-flyout [data]=\"{ title: 'Customer detail' }\">\n <div class=\"p-4 d-flex flex-column gap-3\">\n @if (detail) {\n <div>\n <div class=\"fw-bold\">{{ detail.name || '\u2014' }}</div>\n <div class=\"small text-secondary\">{{ detail.email }} @if (detail.phone) { \u00B7 {{ detail.phone }} }</div>\n </div>\n\n <div class=\"d-flex flex-wrap gap-2\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.customerGroupId\">\n <option value=\"\">\u2014 no group \u2014</option>\n @for (g of groups; track g.id) { <option [value]=\"g.id\">{{ g.name }}</option> }\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-outline-dark\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving\u2026' : 'Save' }}\n </button>\n </div>\n\n <hr class=\"my-1\" />\n\n <div>\n <div class=\"fw-bold small mb-1\">Orders ({{ orders.length }})</div>\n @for (o of orders; track o.id) {\n <div class=\"small text-secondary d-flex justify-content-between\">\n <a [routerLink]=\"['/admin/orders']\" [queryParams]=\"{ q: o.orderNumber }\">{{ o.orderNumber }}</a>\n <span>{{ o.status }} \u00B7 {{ o.currency }} {{ o.grandTotal }}</span>\n </div>\n } @empty {\n <div class=\"small text-secondary\">No orders yet.</div>\n }\n </div>\n\n <div>\n <div class=\"fw-bold small mb-1\">Support tickets ({{ tickets.length }})</div>\n @for (t of tickets; track t.id) {\n <div class=\"small text-secondary\">\n {{ t.subject || t.category }} \u00B7 {{ t.status }} \u00B7 {{ t.priority }}\n </div>\n } @empty {\n <div class=\"small text-secondary\">None.</div>\n }\n </div>\n } @else {\n <x-element-loader></x-element-loader>\n }\n </div>\n </x-webget-flyout>\n</ng-template>\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}\n"] }]
|
|
94
|
+
}], propDecorators: { detailFlyout: [{
|
|
95
|
+
type: ViewChild,
|
|
96
|
+
args: ['detailFlyout', { read: TemplateRef, static: true }]
|
|
97
|
+
}] } });
|
|
98
|
+
|
|
99
|
+
class CustomersModule {
|
|
100
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
101
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, declarations: [CustomersComponent], imports: [SharedModule,
|
|
102
|
+
ElementsModule,
|
|
103
|
+
WebgetsModule, i1$1.RouterModule] }); }
|
|
104
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, imports: [SharedModule,
|
|
105
|
+
ElementsModule,
|
|
106
|
+
WebgetsModule,
|
|
107
|
+
RouterModule.forChild([{ path: '', component: CustomersComponent }])] }); }
|
|
108
|
+
}
|
|
109
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CustomersModule, decorators: [{
|
|
110
|
+
type: NgModule,
|
|
111
|
+
args: [{
|
|
112
|
+
declarations: [CustomersComponent],
|
|
113
|
+
imports: [
|
|
114
|
+
SharedModule,
|
|
115
|
+
ElementsModule,
|
|
116
|
+
WebgetsModule,
|
|
117
|
+
RouterModule.forChild([{ path: '', component: CustomersComponent }]),
|
|
118
|
+
],
|
|
119
|
+
}]
|
|
120
|
+
}] });
|
|
121
|
+
|
|
122
|
+
export { CustomersModule };
|
|
123
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs","sources":["../../../projects/admin/src/lib/pages/customers/customers.component.ts","../../../projects/admin/src/lib/pages/customers/customers.component.html","../../../projects/admin/src/lib/pages/customers/customers.module.ts"],"sourcesContent":["import { Component, TemplateRef, ViewChild, inject } from '@angular/core';\nimport { notify, using } from '@thescaffold/ngx-core';\nimport { FlyoutService } from '@thescaffold/ngx-ui';\nimport { firstValueFrom, takeUntil } from 'rxjs';\nimport { ListPageDirective } from '../_shared/list-page.base';\n\n// R1 production-readiness pass — ADM-05. Was list-only with no drill-in\n// at all. Adds a detail flyout: order history, support tickets, group/\n// status editing. Wallet and act-on-behalf are real, separate scope —\n// no `WalletAccount` entity exists yet (that's P10.10/R3), and\n// impersonating a customer's session is an auth feature, not a display\n// one; both deserve their own careful pass rather than a stub here.\n@Component({\n standalone: false,\n selector: 'x-customers',\n templateUrl: './customers.component.html',\n styleUrl: './customers.component.scss',\n})\nexport class CustomersComponent extends ListPageDirective {\n private readonly flyoutService = inject(FlyoutService);\n\n protected pluralName = 'customers';\n protected dataSource = this.apiService.on('apps/commerce/customer');\n private readonly orderSource = this.apiService.on('apps/commerce/order');\n private readonly ticketSource = this.apiService.on('apps/commerce/complaint-log');\n private readonly groupSource = this.apiService.on('apps/commerce/customer-group');\n\n protected 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 detail: any = null;\n orders: any[] = [];\n tickets: any[] = [];\n groups: { id: string; name: string }[] = [];\n saving = false;\n\n readonly statuses = ['active', 'suspended'];\n\n @ViewChild('detailFlyout', { read: TemplateRef, static: true })\n detailFlyout!: TemplateRef<any>;\n\n override init = () => {\n this.list();\n this.groupSource\n .getMany({ page: 1, perPage: 100 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(using((res: any) => (this.groups = res?.data ?? [])));\n };\n\n protected override extraCollectionFields() {\n return {\n links: {\n secondary: [{ title: 'View', fn: (_link: any, item: any) => this.view(item) }],\n },\n };\n }\n\n async view(item: any): Promise<void> {\n this.detail = null;\n this.orders = [];\n this.tickets = [];\n this.flyoutService.open(this.detailFlyout);\n\n const [detailRes, ordersRes, ticketsRes]: any = await Promise.all([\n firstValueFrom(this.dataSource.getOne(item.id)),\n firstValueFrom(this.orderSource.getMany({ customerId: item.id, page: 1, perPage: 20 } as any)),\n firstValueFrom(this.ticketSource.getMany({ customerId: item.id, page: 1, perPage: 20 } as any)),\n ]);\n this.detail = detailRes?.data ?? item;\n this.orders = ordersRes?.data ?? [];\n this.tickets = ticketsRes?.data ?? [];\n }\n\n async save(): Promise<void> {\n if (!this.detail) return;\n this.saving = true;\n try {\n await firstValueFrom(\n this.dataSource.update(this.detail.id, {\n status: this.detail.status,\n customerGroupId: this.detail.customerGroupId || undefined,\n } as any),\n );\n notify('Customer saved.', undefined, 'success');\n this.list(this.page, this.perPage);\n } catch {\n notify('Could not save the customer.', undefined, 'error');\n } finally {\n this.saving = false;\n }\n }\n}\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<ng-template #detailFlyout>\n <x-webget-flyout [data]=\"{ title: 'Customer detail' }\">\n <div class=\"p-4 d-flex flex-column gap-3\">\n @if (detail) {\n <div>\n <div class=\"fw-bold\">{{ detail.name || '—' }}</div>\n <div class=\"small text-secondary\">{{ detail.email }} @if (detail.phone) { · {{ detail.phone }} }</div>\n </div>\n\n <div class=\"d-flex flex-wrap gap-2\">\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n <select class=\"form-select form-select-sm\" style=\"width:auto\" [(ngModel)]=\"detail.customerGroupId\">\n <option value=\"\">— no group —</option>\n @for (g of groups; track g.id) { <option [value]=\"g.id\">{{ g.name }}</option> }\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-outline-dark\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving…' : 'Save' }}\n </button>\n </div>\n\n <hr class=\"my-1\" />\n\n <div>\n <div class=\"fw-bold small mb-1\">Orders ({{ orders.length }})</div>\n @for (o of orders; track o.id) {\n <div class=\"small text-secondary d-flex justify-content-between\">\n <a [routerLink]=\"['/admin/orders']\" [queryParams]=\"{ q: o.orderNumber }\">{{ o.orderNumber }}</a>\n <span>{{ o.status }} · {{ o.currency }} {{ o.grandTotal }}</span>\n </div>\n } @empty {\n <div class=\"small text-secondary\">No orders yet.</div>\n }\n </div>\n\n <div>\n <div class=\"fw-bold small mb-1\">Support tickets ({{ tickets.length }})</div>\n @for (t of tickets; track t.id) {\n <div class=\"small text-secondary\">\n {{ t.subject || t.category }} · {{ t.status }} · {{ t.priority }}\n </div>\n } @empty {\n <div class=\"small text-secondary\">None.</div>\n }\n </div>\n } @else {\n <x-element-loader></x-element-loader>\n }\n </div>\n </x-webget-flyout>\n</ng-template>\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":["i2","i3","i4","i1"],"mappings":";;;;;;;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AAOM,MAAO,kBAAmB,SAAQ,iBAAiB,CAAA;AANzD,IAAA,WAAA,GAAA;;AAOmB,QAAA,IAAA,CAAA,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;QAE5C,IAAA,CAAA,UAAU,GAAG,WAAW;QACxB,IAAA,CAAA,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,wBAAwB,CAAC;QAClD,IAAA,CAAA,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,qBAAqB,CAAC;QACvD,IAAA,CAAA,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,6BAA6B,CAAC;QAChE,IAAA,CAAA,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,8BAA8B,CAAC;AAEvE,QAAA,IAAA,CAAA,OAAO,GAAG;AAClB,YAAA,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;AAC9B,YAAA,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;AAChC,YAAA,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;YAChC,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;SACpD;QAED,IAAA,CAAA,MAAM,GAAQ,IAAI;QAClB,IAAA,CAAA,MAAM,GAAU,EAAE;QAClB,IAAA,CAAA,OAAO,GAAU,EAAE;QACnB,IAAA,CAAA,MAAM,GAAmC,EAAE;QAC3C,IAAA,CAAA,MAAM,GAAG,KAAK;AAEL,QAAA,IAAA,CAAA,QAAQ,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC;QAKlC,IAAA,CAAA,IAAI,GAAG,MAAK;YACnB,IAAI,CAAC,IAAI,EAAE;AACX,YAAA,IAAI,CAAC;iBACF,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAS;AACxC,iBAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;iBACtB,SAAS,CAAC,KAAK,CAAC,CAAC,GAAQ,MAAM,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;AACpE,QAAA,CAAC;AA4CF,IAAA;IA1CoB,qBAAqB,GAAA;QACtC,OAAO;AACL,YAAA,KAAK,EAAE;gBACL,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,KAAU,EAAE,IAAS,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/E,aAAA;SACF;IACH;IAEA,MAAM,IAAI,CAAC,IAAS,EAAA;AAClB,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;AAClB,QAAA,IAAI,CAAC,MAAM,GAAG,EAAE;AAChB,QAAA,IAAI,CAAC,OAAO,GAAG,EAAE;QACjB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;AAE1C,QAAA,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,GAAQ,MAAM,OAAO,CAAC,GAAG,CAAC;YAChE,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/C,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAS,CAAC,CAAC;YAC9F,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAS,CAAC,CAAC;AAChG,SAAA,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,SAAS,EAAE,IAAI,IAAI,IAAI;QACrC,IAAI,CAAC,MAAM,GAAG,SAAS,EAAE,IAAI,IAAI,EAAE;QACnC,IAAI,CAAC,OAAO,GAAG,UAAU,EAAE,IAAI,IAAI,EAAE;IACvC;AAEA,IAAA,MAAM,IAAI,GAAA;QACR,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE;AAClB,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;AAClB,QAAA,IAAI;AACF,YAAA,MAAM,cAAc,CAClB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE;AACrC,gBAAA,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;AAC1B,gBAAA,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,SAAS;AACnD,aAAA,CAAC,CACV;AACD,YAAA,MAAM,CAAC,iBAAiB,EAAE,SAAS,EAAE,SAAS,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC;QACpC;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,8BAA8B,EAAE,SAAS,EAAE,OAAO,CAAC;QAC5D;gBAAU;AACR,YAAA,IAAI,CAAC,MAAM,GAAG,KAAK;QACrB;IACF;+GA5EW,kBAAkB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAlB,kBAAkB,EAAA,YAAA,EAAA,KAAA,EAAA,QAAA,EAAA,aAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,cAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,cAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,IAAA,EAwBM,WAAW,EAAA,MAAA,EAAA,IAAA,EAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC1ChD,qlFA+DA,EAAA,MAAA,EAAA,CAAA,wLAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,uBAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,0BAAA,EAAA,QAAA,EAAA,6GAAA,EAAA,MAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,IAAA,CAAA,UAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,aAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,YAAA,EAAA,YAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAC,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,EAAA,CAAA,mBAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,QAAA,EAAA,QAAA,EAAA,SAAA,EAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAAC,EAAA,CAAA,SAAA,EAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,CAAA,CAAA;;4FD7Ca,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAN9B,SAAS;AACI,YAAA,IAAA,EAAA,CAAA,EAAA,UAAA,EAAA,KAAK,YACP,aAAa,EAAA,QAAA,EAAA,qlFAAA,EAAA,MAAA,EAAA,CAAA,wLAAA,CAAA,EAAA;;sBA4BtB,SAAS;uBAAC,cAAc,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;;;ME3BnD,eAAe,CAAA;+GAAf,eAAe,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gHAAf,eAAe,EAAA,YAAA,EAAA,CARX,kBAAkB,CAAA,EAAA,OAAA,EAAA,CAE/B,YAAY;YACZ,cAAc;YACd,aAAa,EAAAC,IAAA,CAAA,YAAA,CAAA,EAAA,CAAA,CAAA;AAIJ,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,YANxB,YAAY;YACZ,cAAc;YACd,aAAa;AACb,YAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAA,EAAA,CAAA,CAAA;;4FAG3D,eAAe,EAAA,UAAA,EAAA,CAAA;kBAT3B,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,YAAY,EAAE,CAAC,kBAAkB,CAAC;AAClC,oBAAA,OAAO,EAAE;wBACP,YAAY;wBACZ,cAAc;wBACd,aAAa;AACb,wBAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;AACrE,qBAAA;AACF,iBAAA;;;;;"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Component, NgModule } from '@angular/core';
|
|
3
|
-
import * as i1
|
|
3
|
+
import * as i1 from '@angular/router';
|
|
4
4
|
import { RouterModule } from '@angular/router';
|
|
5
5
|
import { BaseDirective, using, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
-
import * as
|
|
6
|
+
import * as i2 from '@thescaffold/ngx-ui';
|
|
7
7
|
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
8
|
import { BehaviorSubject, takeUntil } from 'rxjs';
|
|
9
|
-
import * as
|
|
9
|
+
import * as i3 from '@angular/common';
|
|
10
10
|
|
|
11
11
|
// P1.17, CAT-12 — reads `CatalogWorkflow.dataQualityReport` (P1.11's
|
|
12
12
|
// sibling addition) as-is; no client-side computation. Two of CAT-12's
|
|
@@ -144,7 +144,7 @@ class DataQualityComponent extends BaseDirective {
|
|
|
144
144
|
} @else {
|
|
145
145
|
<x-element-loader></x-element-loader>
|
|
146
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:
|
|
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: i2.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }] }); }
|
|
148
148
|
}
|
|
149
149
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityComponent, decorators: [{
|
|
150
150
|
type: Component,
|
|
@@ -265,7 +265,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
265
265
|
class DataQualityModule {
|
|
266
266
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
267
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
|
|
268
|
+
ElementsModule, i1.RouterModule] }); }
|
|
269
269
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataQualityModule, imports: [SharedModule,
|
|
270
270
|
ElementsModule,
|
|
271
271
|
RouterModule.forChild([{ path: '', component: DataQualityComponent }])] }); }
|
|
@@ -283,4 +283,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
283
283
|
}] });
|
|
284
284
|
|
|
285
285
|
export { DataQualityModule };
|
|
286
|
-
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-
|
|
286
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-CvFmMoJe.mjs.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-CvFmMoJe.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","i2"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Component, NgModule } from '@angular/core';
|
|
3
|
-
import * as i1
|
|
3
|
+
import * as i1 from '@angular/router';
|
|
4
4
|
import { RouterModule } from '@angular/router';
|
|
5
5
|
import { BaseDirective, using, notify, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
-
import * as
|
|
6
|
+
import * as i2 from '@thescaffold/ngx-ui';
|
|
7
7
|
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
8
|
import { takeUntil, firstValueFrom } from 'rxjs';
|
|
9
|
-
import * as
|
|
9
|
+
import * as i3 from '@angular/common';
|
|
10
10
|
|
|
11
11
|
// P7.7, SEC-04/SEC-05 — the NDPR request queue. `export` requests are
|
|
12
12
|
// self-served on the storefront; `deletion` requests land here for ops to
|
|
@@ -73,7 +73,7 @@ class DataRequestsComponent extends BaseDirective {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
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:
|
|
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: i2.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i3.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i3.DatePipe, name: "date" }] }); }
|
|
77
77
|
}
|
|
78
78
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsComponent, decorators: [{
|
|
79
79
|
type: Component,
|
|
@@ -83,7 +83,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
83
83
|
class DataRequestsModule {
|
|
84
84
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
85
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
|
|
86
|
+
ElementsModule, i1.RouterModule] }); }
|
|
87
87
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DataRequestsModule, imports: [SharedModule,
|
|
88
88
|
ElementsModule,
|
|
89
89
|
RouterModule.forChild([{ path: '', component: DataRequestsComponent }])] }); }
|
|
@@ -101,4 +101,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
101
101
|
}] });
|
|
102
102
|
|
|
103
103
|
export { DataRequestsModule };
|
|
104
|
-
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-
|
|
104
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-CuZwNVPO.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs","sources":["../../../projects/admin/src/lib/pages/data-requests/data-requests.component.ts","../../../projects/admin/src/lib/pages/data-requests/data-requests.component.html","../../../projects/admin/src/lib/pages/data-requests/data-requests.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, notify, using } from '@thescaffold/ngx-core';\nimport { firstValueFrom, takeUntil } from 'rxjs';\n\ninterface RequestVm {\n id: string;\n type: string;\n status: string;\n customerId?: string;\n email?: string;\n phone?: string;\n note?: string;\n retentionNote?: string;\n completedAt?: string;\n createdAt: string;\n}\n\n// P7.7, SEC-04/SEC-05 — the NDPR request queue. `export` requests are\n// self-served on the storefront; `deletion` requests land here for ops to\n// fulfil within the SLA. Fulfilling runs\n// `PrivacyWorkflow.anonymiseCustomer` and records what was retained.\n@Component({\n standalone: false,\n selector: 'x-data-requests',\n templateUrl: './data-requests.component.html',\n})\nexport class DataRequestsComponent extends BaseDirective {\n private readonly source = this.apiService.on('apps/commerce/data-request');\n\n loading = true;\n requests: RequestVm[] = [];\n selected?: RequestVm;\n fulfilling = false;\n\n override init = () => {\n this.load();\n };\n\n load(): void {\n this.loading = true;\n this.source\n .getMany({ perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.requests = (res?.data ?? []).sort(\n (a: RequestVm, b: RequestVm) =>\n this.rank(a) - this.rank(b) ||\n new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n );\n this.loading = false;\n if (this.selected) {\n this.selected = this.requests.find((r) => r.id === this.selected!.id);\n }\n }),\n );\n }\n\n private rank(r: RequestVm): number {\n if (r.status === 'completed' || r.status === 'rejected') return 3;\n return r.type === 'deletion' ? 0 : 1;\n }\n\n async setStatus(status: string): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(this.source.update(this.selected.id, { status } as any));\n this.load();\n } catch {\n notify('Could not update the request.', undefined, 'error');\n }\n }\n\n async fulfil(): Promise<void> {\n if (!this.selected || this.fulfilling) return;\n if (\n !confirm(\n 'This will permanently anonymise the customer’s personal data. Continue?',\n )\n ) {\n return;\n }\n this.fulfilling = true;\n try {\n await firstValueFrom(\n this.apiService.post(\n `apps/commerce/data-request/${this.selected.id}/fulfil`,\n {},\n ),\n );\n notify('Deletion completed.', undefined, 'success');\n this.load();\n } catch {\n notify('Could not complete the deletion.', undefined, 'error');\n } finally {\n this.fulfilling = false;\n }\n }\n}\n","<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 || '—' }}</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…' : '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","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule } from '@thescaffold/ngx-ui';\nimport { DataRequestsComponent } from './data-requests.component';\n\n@NgModule({\n declarations: [DataRequestsComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RouterModule.forChild([{ path: '', component: DataRequestsComponent }]),\n ],\n})\nexport class DataRequestsModule {}\n"],"names":["i1"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-CuZwNVPO.mjs","sources":["../../../projects/admin/src/lib/pages/data-requests/data-requests.component.ts","../../../projects/admin/src/lib/pages/data-requests/data-requests.component.html","../../../projects/admin/src/lib/pages/data-requests/data-requests.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, notify, using } from '@thescaffold/ngx-core';\nimport { firstValueFrom, takeUntil } from 'rxjs';\n\ninterface RequestVm {\n id: string;\n type: string;\n status: string;\n customerId?: string;\n email?: string;\n phone?: string;\n note?: string;\n retentionNote?: string;\n completedAt?: string;\n createdAt: string;\n}\n\n// P7.7, SEC-04/SEC-05 — the NDPR request queue. `export` requests are\n// self-served on the storefront; `deletion` requests land here for ops to\n// fulfil within the SLA. Fulfilling runs\n// `PrivacyWorkflow.anonymiseCustomer` and records what was retained.\n@Component({\n standalone: false,\n selector: 'x-data-requests',\n templateUrl: './data-requests.component.html',\n})\nexport class DataRequestsComponent extends BaseDirective {\n private readonly source = this.apiService.on('apps/commerce/data-request');\n\n loading = true;\n requests: RequestVm[] = [];\n selected?: RequestVm;\n fulfilling = false;\n\n override init = () => {\n this.load();\n };\n\n load(): void {\n this.loading = true;\n this.source\n .getMany({ perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.requests = (res?.data ?? []).sort(\n (a: RequestVm, b: RequestVm) =>\n this.rank(a) - this.rank(b) ||\n new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n );\n this.loading = false;\n if (this.selected) {\n this.selected = this.requests.find((r) => r.id === this.selected!.id);\n }\n }),\n );\n }\n\n private rank(r: RequestVm): number {\n if (r.status === 'completed' || r.status === 'rejected') return 3;\n return r.type === 'deletion' ? 0 : 1;\n }\n\n async setStatus(status: string): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(this.source.update(this.selected.id, { status } as any));\n this.load();\n } catch {\n notify('Could not update the request.', undefined, 'error');\n }\n }\n\n async fulfil(): Promise<void> {\n if (!this.selected || this.fulfilling) return;\n if (\n !confirm(\n 'This will permanently anonymise the customer’s personal data. Continue?',\n )\n ) {\n return;\n }\n this.fulfilling = true;\n try {\n await firstValueFrom(\n this.apiService.post(\n `apps/commerce/data-request/${this.selected.id}/fulfil`,\n {},\n ),\n );\n notify('Deletion completed.', undefined, 'success');\n this.load();\n } catch {\n notify('Could not complete the deletion.', undefined, 'error');\n } finally {\n this.fulfilling = false;\n }\n }\n}\n","<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 || '—' }}</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…' : '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","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule } from '@thescaffold/ngx-ui';\nimport { DataRequestsComponent } from './data-requests.component';\n\n@NgModule({\n declarations: [DataRequestsComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RouterModule.forChild([{ path: '', component: DataRequestsComponent }]),\n ],\n})\nexport class DataRequestsModule {}\n"],"names":["i1","i2"],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i2 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { S as SimpleCrudDirective } from './isaiahiroko-tilmart-ngx-apps-admin-simple-crud.base-C8SPw14H.mjs';
|
|
9
|
+
import * as i1 from '@angular/forms';
|
|
10
|
+
import * as i3 from '@angular/common';
|
|
11
|
+
|
|
12
|
+
// R1 production-readiness pass — PROMO-04. Previously missing entirely:
|
|
13
|
+
// the homepage composer's `flash_sale_rail` module references a flash
|
|
14
|
+
// sale by id, but there was no page to create the sale window itself.
|
|
15
|
+
// Per-SKU allocation/limits stay a follow-up — this covers the sale
|
|
16
|
+
// window (name, schedule, channels), which is what a module needs to
|
|
17
|
+
// resolve and what ops needs to schedule ahead of time.
|
|
18
|
+
class FlashSalesComponent extends SimpleCrudDirective {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this.pageTitle = 'Flash sales';
|
|
22
|
+
this.pageSubtitle = 'Time-boxed sale windows referenced by the homepage flash-sale rail.';
|
|
23
|
+
this.singularLabel = 'flash sale';
|
|
24
|
+
this.pluralName = 'flash sales';
|
|
25
|
+
this.dataSource = this.apiService.on('apps/commerce/flash-sale');
|
|
26
|
+
this.columns = [
|
|
27
|
+
{ key: 'name', value: 'Name' },
|
|
28
|
+
{ key: 'status', value: 'Status', type: 'class', class: 'badge text-bg-light border' },
|
|
29
|
+
{ key: 'startsAt', value: 'Starts', type: 'date' },
|
|
30
|
+
{ key: 'endsAt', value: 'Ends', type: 'date' },
|
|
31
|
+
];
|
|
32
|
+
this.fields = [
|
|
33
|
+
{ key: 'name', label: 'Name', type: 'text', required: true },
|
|
34
|
+
{ key: 'description', label: 'Description', type: 'textarea' },
|
|
35
|
+
{
|
|
36
|
+
key: 'status',
|
|
37
|
+
label: 'Status',
|
|
38
|
+
type: 'select',
|
|
39
|
+
options: [
|
|
40
|
+
{ value: 'draft', label: 'Draft' },
|
|
41
|
+
{ value: 'scheduled', label: 'Scheduled' },
|
|
42
|
+
{ value: 'live', label: 'Live' },
|
|
43
|
+
{ value: 'ended', label: 'Ended' },
|
|
44
|
+
{ value: 'cancelled', label: 'Cancelled' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{ key: 'startsAt', label: 'Starts at', type: 'date' },
|
|
48
|
+
{ key: 'endsAt', label: 'Ends at', type: 'date' },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
52
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: FlashSalesComponent, isStandalone: false, selector: "x-flash-sales", usesInheritance: true, ngImport: i0, template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">{{ pageTitle }}</div>\n <div class=\"x-page-subtitle\">{{ pageSubtitle }}</div>\n</div>\n\n@if (!editing) {\n <div class=\"x-scrud-toolbar\">\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" (click)=\"startCreate()\">+ New {{ singularLabel }}</button>\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} @else if (draft) {\n <div class=\"x-scrud-editor\">\n <div class=\"x-scrud-editor-head\">\n <div class=\"x-scrud-editor-title\">{{ draft['id'] ? 'Edit' : 'New' }} {{ singularLabel }}</div>\n <div class=\"x-scrud-editor-actions\">\n @if (draft['id'] && !hideDelete) {\n <button type=\"button\" class=\"x-scrud-btn x-scrud-danger\" (click)=\"remove(draft); cancelEdit()\">Delete</button>\n }\n <button type=\"button\" class=\"x-scrud-btn\" (click)=\"cancelEdit()\">Close</button>\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving\u2026' : 'Save' }}\n </button>\n </div>\n </div>\n\n <div class=\"x-scrud-grid\">\n @for (f of fields; track f.key) {\n <label class=\"x-scrud-f\" [class.x-scrud-f-wide]=\"f.type === 'textarea' || f.type === 'json'\">\n {{ f.label }}\n @if (f.required) {\n <span class=\"x-scrud-required\">*</span>\n }\n @switch (f.type) {\n @case ('boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"draft[f.key]\" />\n }\n @case ('select') {\n <select [(ngModel)]=\"draft[f.key]\">\n <option value=\"\">\u2014 choose \u2014</option>\n @for (opt of (f.optionsKey ? dynamicOptions[f.optionsKey] : f.options); track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @case ('textarea') {\n <textarea rows=\"3\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\"></textarea>\n }\n @case ('json') {\n <textarea rows=\"4\" class=\"x-scrud-json\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || '{}'\"></textarea>\n }\n @case ('number') {\n <input type=\"number\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n @case ('date') {\n <input type=\"datetime-local\" [(ngModel)]=\"draft[f.key]\" />\n }\n @default {\n <input type=\"text\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n }\n </label>\n }\n </div>\n </div>\n}\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-scrud-toolbar{display:flex;justify-content:flex-end;margin-bottom:14px}.x-scrud-btn{border:1px solid var(--color-border);background:transparent;border-radius:var(--radius-md);padding:6px 14px;font-size:12.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-scrud-btn:disabled{opacity:.5;cursor:default}.x-scrud-btn-primary{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-scrud-danger{color:var(--color-danger, #c0362c)}.x-scrud-editor{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;display:flex;flex-direction:column;gap:14px}.x-scrud-editor-head{display:flex;align-items:center;justify-content:space-between}.x-scrud-editor-title{font-size:16px;font-weight:800;color:var(--color-text)}.x-scrud-editor-actions{display:flex;gap:8px}.x-scrud-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.x-scrud-f{display:flex;flex-direction:column;gap:4px;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-scrud-f-wide{grid-column:1/-1}.x-scrud-f input,.x-scrud-f select,.x-scrud-f textarea{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:6px 10px;font-size:13px;background:var(--color-surface);color:var(--color-text);font-weight:400}.x-scrud-f input[type=checkbox]{width:18px;height:18px}.x-scrud-json{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.x-scrud-required{color:var(--color-danger, #c0362c)}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: i2.CollectionComponent, selector: "x-webget-collection", inputs: ["data"], outputs: ["search", "filter", "refresh", "select", "page", "show"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }] }); }
|
|
53
|
+
}
|
|
54
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesComponent, decorators: [{
|
|
55
|
+
type: Component,
|
|
56
|
+
args: [{ standalone: false, selector: 'x-flash-sales', template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">{{ pageTitle }}</div>\n <div class=\"x-page-subtitle\">{{ pageSubtitle }}</div>\n</div>\n\n@if (!editing) {\n <div class=\"x-scrud-toolbar\">\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" (click)=\"startCreate()\">+ New {{ singularLabel }}</button>\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} @else if (draft) {\n <div class=\"x-scrud-editor\">\n <div class=\"x-scrud-editor-head\">\n <div class=\"x-scrud-editor-title\">{{ draft['id'] ? 'Edit' : 'New' }} {{ singularLabel }}</div>\n <div class=\"x-scrud-editor-actions\">\n @if (draft['id'] && !hideDelete) {\n <button type=\"button\" class=\"x-scrud-btn x-scrud-danger\" (click)=\"remove(draft); cancelEdit()\">Delete</button>\n }\n <button type=\"button\" class=\"x-scrud-btn\" (click)=\"cancelEdit()\">Close</button>\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving\u2026' : 'Save' }}\n </button>\n </div>\n </div>\n\n <div class=\"x-scrud-grid\">\n @for (f of fields; track f.key) {\n <label class=\"x-scrud-f\" [class.x-scrud-f-wide]=\"f.type === 'textarea' || f.type === 'json'\">\n {{ f.label }}\n @if (f.required) {\n <span class=\"x-scrud-required\">*</span>\n }\n @switch (f.type) {\n @case ('boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"draft[f.key]\" />\n }\n @case ('select') {\n <select [(ngModel)]=\"draft[f.key]\">\n <option value=\"\">\u2014 choose \u2014</option>\n @for (opt of (f.optionsKey ? dynamicOptions[f.optionsKey] : f.options); track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @case ('textarea') {\n <textarea rows=\"3\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\"></textarea>\n }\n @case ('json') {\n <textarea rows=\"4\" class=\"x-scrud-json\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || '{}'\"></textarea>\n }\n @case ('number') {\n <input type=\"number\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n @case ('date') {\n <input type=\"datetime-local\" [(ngModel)]=\"draft[f.key]\" />\n }\n @default {\n <input type=\"text\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n }\n </label>\n }\n </div>\n </div>\n}\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-scrud-toolbar{display:flex;justify-content:flex-end;margin-bottom:14px}.x-scrud-btn{border:1px solid var(--color-border);background:transparent;border-radius:var(--radius-md);padding:6px 14px;font-size:12.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-scrud-btn:disabled{opacity:.5;cursor:default}.x-scrud-btn-primary{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-scrud-danger{color:var(--color-danger, #c0362c)}.x-scrud-editor{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;display:flex;flex-direction:column;gap:14px}.x-scrud-editor-head{display:flex;align-items:center;justify-content:space-between}.x-scrud-editor-title{font-size:16px;font-weight:800;color:var(--color-text)}.x-scrud-editor-actions{display:flex;gap:8px}.x-scrud-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.x-scrud-f{display:flex;flex-direction:column;gap:4px;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-scrud-f-wide{grid-column:1/-1}.x-scrud-f input,.x-scrud-f select,.x-scrud-f textarea{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:6px 10px;font-size:13px;background:var(--color-surface);color:var(--color-text);font-weight:400}.x-scrud-f input[type=checkbox]{width:18px;height:18px}.x-scrud-json{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.x-scrud-required{color:var(--color-danger, #c0362c)}\n"] }]
|
|
57
|
+
}] });
|
|
58
|
+
|
|
59
|
+
class FlashSalesModule {
|
|
60
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
61
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesModule, declarations: [FlashSalesComponent], imports: [SharedModule,
|
|
62
|
+
ElementsModule,
|
|
63
|
+
WebgetsModule, i1$1.RouterModule] }); }
|
|
64
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesModule, imports: [SharedModule,
|
|
65
|
+
ElementsModule,
|
|
66
|
+
WebgetsModule,
|
|
67
|
+
RouterModule.forChild([{ path: '', component: FlashSalesComponent }])] }); }
|
|
68
|
+
}
|
|
69
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: FlashSalesModule, decorators: [{
|
|
70
|
+
type: NgModule,
|
|
71
|
+
args: [{
|
|
72
|
+
declarations: [FlashSalesComponent],
|
|
73
|
+
imports: [
|
|
74
|
+
SharedModule,
|
|
75
|
+
ElementsModule,
|
|
76
|
+
WebgetsModule,
|
|
77
|
+
RouterModule.forChild([{ path: '', component: FlashSalesComponent }]),
|
|
78
|
+
],
|
|
79
|
+
}]
|
|
80
|
+
}] });
|
|
81
|
+
|
|
82
|
+
export { FlashSalesModule };
|
|
83
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs","sources":["../../../projects/admin/src/lib/pages/flash-sales/flash-sales.component.ts","../../../projects/admin/src/lib/pages/_shared/simple-crud.component.html","../../../projects/admin/src/lib/pages/flash-sales/flash-sales.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { CrudFieldSpec, SimpleCrudDirective } from '../_shared/simple-crud.base';\n\n// R1 production-readiness pass — PROMO-04. Previously missing entirely:\n// the homepage composer's `flash_sale_rail` module references a flash\n// sale by id, but there was no page to create the sale window itself.\n// Per-SKU allocation/limits stay a follow-up — this covers the sale\n// window (name, schedule, channels), which is what a module needs to\n// resolve and what ops needs to schedule ahead of time.\n@Component({\n standalone: false,\n selector: 'x-flash-sales',\n templateUrl: '../_shared/simple-crud.component.html',\n styleUrl: '../_shared/simple-crud.component.scss',\n})\nexport class FlashSalesComponent extends SimpleCrudDirective {\n protected pageTitle = 'Flash sales';\n protected pageSubtitle = 'Time-boxed sale windows referenced by the homepage flash-sale rail.';\n protected singularLabel = 'flash sale';\n protected pluralName = 'flash sales';\n protected dataSource = this.apiService.on('apps/commerce/flash-sale');\n\n protected columns = [\n { key: 'name', value: 'Name' },\n { key: 'status', value: 'Status', type: 'class', class: 'badge text-bg-light border' },\n { key: 'startsAt', value: 'Starts', type: 'date' },\n { key: 'endsAt', value: 'Ends', type: 'date' },\n ];\n\n protected fields: CrudFieldSpec[] = [\n { key: 'name', label: 'Name', type: 'text', required: true },\n { key: 'description', label: 'Description', type: 'textarea' },\n {\n key: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { value: 'draft', label: 'Draft' },\n { value: 'scheduled', label: 'Scheduled' },\n { value: 'live', label: 'Live' },\n { value: 'ended', label: 'Ended' },\n { value: 'cancelled', label: 'Cancelled' },\n ],\n },\n { key: 'startsAt', label: 'Starts at', type: 'date' },\n { key: 'endsAt', label: 'Ends at', type: 'date' },\n ];\n}\n","<div class=\"x-page-head\">\n <div class=\"x-page-title\">{{ pageTitle }}</div>\n <div class=\"x-page-subtitle\">{{ pageSubtitle }}</div>\n</div>\n\n@if (!editing) {\n <div class=\"x-scrud-toolbar\">\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" (click)=\"startCreate()\">+ New {{ singularLabel }}</button>\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} @else if (draft) {\n <div class=\"x-scrud-editor\">\n <div class=\"x-scrud-editor-head\">\n <div class=\"x-scrud-editor-title\">{{ draft['id'] ? 'Edit' : 'New' }} {{ singularLabel }}</div>\n <div class=\"x-scrud-editor-actions\">\n @if (draft['id'] && !hideDelete) {\n <button type=\"button\" class=\"x-scrud-btn x-scrud-danger\" (click)=\"remove(draft); cancelEdit()\">Delete</button>\n }\n <button type=\"button\" class=\"x-scrud-btn\" (click)=\"cancelEdit()\">Close</button>\n <button type=\"button\" class=\"x-scrud-btn x-scrud-btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\n {{ saving ? 'Saving…' : 'Save' }}\n </button>\n </div>\n </div>\n\n <div class=\"x-scrud-grid\">\n @for (f of fields; track f.key) {\n <label class=\"x-scrud-f\" [class.x-scrud-f-wide]=\"f.type === 'textarea' || f.type === 'json'\">\n {{ f.label }}\n @if (f.required) {\n <span class=\"x-scrud-required\">*</span>\n }\n @switch (f.type) {\n @case ('boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"draft[f.key]\" />\n }\n @case ('select') {\n <select [(ngModel)]=\"draft[f.key]\">\n <option value=\"\">— choose —</option>\n @for (opt of (f.optionsKey ? dynamicOptions[f.optionsKey] : f.options); track opt.value) {\n <option [value]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @case ('textarea') {\n <textarea rows=\"3\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\"></textarea>\n }\n @case ('json') {\n <textarea rows=\"4\" class=\"x-scrud-json\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || '{}'\"></textarea>\n }\n @case ('number') {\n <input type=\"number\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n @case ('date') {\n <input type=\"datetime-local\" [(ngModel)]=\"draft[f.key]\" />\n }\n @default {\n <input type=\"text\" [(ngModel)]=\"draft[f.key]\" [placeholder]=\"f.placeholder || ''\" />\n }\n }\n </label>\n }\n </div>\n </div>\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 { FlashSalesComponent } from './flash-sales.component';\n\n@NgModule({\n declarations: [FlashSalesComponent],\n imports: [\n SharedModule,\n ElementsModule,\n WebgetsModule,\n RouterModule.forChild([{ path: '', component: FlashSalesComponent }]),\n ],\n})\nexport class FlashSalesModule {}\n"],"names":["i1"],"mappings":"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"}
|