@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.
Files changed (107) hide show
  1. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs → isaiahiroko-tilmart-ngx-apps-admin-articles.module-Cpx9BZcg.mjs} +5 -5
  2. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-articles.module-Cpx9BZcg.mjs.map} +1 -1
  3. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-attribute-templates.module-CwQnW1cH.mjs +239 -0
  4. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-attribute-templates.module-CwQnW1cH.mjs.map +1 -0
  5. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs → isaiahiroko-tilmart-ngx-apps-admin-audit.module-D5YaCThD.mjs} +6 -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
  7. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-banners.module-KSapnx6Y.mjs +76 -0
  8. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-banners.module-KSapnx6Y.mjs.map +1 -0
  9. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-brands.module-7I0ni-tQ.mjs +70 -0
  10. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-brands.module-7I0ni-tQ.mjs.map +1 -0
  11. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-DoxDNaNn.mjs +538 -0
  12. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-DoxDNaNn.mjs.map +1 -0
  13. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-categories.module-OTUfenf0.mjs +94 -0
  14. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-categories.module-OTUfenf0.mjs.map +1 -0
  15. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Dk9mz7Wj.mjs +137 -0
  16. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Dk9mz7Wj.mjs.map +1 -0
  17. 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
  18. 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
  19. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs → isaiahiroko-tilmart-ngx-apps-admin-content.module-Bx6Zj0WX.mjs} +5 -5
  20. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-content.module-Bx6Zj0WX.mjs.map} +1 -1
  21. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CoRh6Hxq.mjs +85 -0
  22. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CoRh6Hxq.mjs.map +1 -0
  23. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs +123 -0
  24. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-CslGiOdq.mjs.map +1 -0
  25. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs → isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-CvFmMoJe.mjs} +6 -6
  26. 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
  27. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs → isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-CuZwNVPO.mjs} +6 -6
  28. 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
  29. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs +83 -0
  30. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-flash-sales.module-BPRnC17l.mjs.map +1 -0
  31. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs → isaiahiroko-tilmart-ngx-apps-admin-homepage.module-vtAoIwSu.mjs} +5 -5
  32. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-homepage.module-vtAoIwSu.mjs.map} +1 -1
  33. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs → isaiahiroko-tilmart-ngx-apps-admin-inbox.module-CRkfjWQT.mjs} +6 -6
  34. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-inbox.module-CRkfjWQT.mjs.map} +1 -1
  35. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-list-page.base-Cl7JZ-Tp.mjs +66 -0
  36. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-list-page.base-Cl7JZ-Tp.mjs.map +1 -0
  37. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs → isaiahiroko-tilmart-ngx-apps-admin-macros.module-CElxL9W4.mjs} +5 -5
  38. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-macros.module-CElxL9W4.mjs.map} +1 -1
  39. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs → isaiahiroko-tilmart-ngx-apps-admin-moderation.module-PGmFX7nk.mjs} +6 -6
  40. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-moderation.module-PGmFX7nk.mjs.map} +1 -1
  41. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs → isaiahiroko-tilmart-ngx-apps-admin-navigation.module-De1Cvqqa.mjs} +6 -6
  42. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-navigation.module-De1Cvqqa.mjs.map} +1 -1
  43. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs → isaiahiroko-tilmart-ngx-apps-admin-order-board.module-CmLNiEEP.mjs} +4 -4
  44. 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
  45. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs → isaiahiroko-tilmart-ngx-apps-admin-orders.module-Cfeb0QjH.mjs} +15 -33
  46. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Cfeb0QjH.mjs.map +1 -0
  47. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-DstLDYM7.mjs +114 -0
  48. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-DstLDYM7.mjs.map +1 -0
  49. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG03OY1q.mjs +94 -0
  50. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG03OY1q.mjs.map +1 -0
  51. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs → isaiahiroko-tilmart-ngx-apps-admin-queues.module-D7iLwX0A.mjs} +6 -6
  52. 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
  53. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-oQw5Xo5T.mjs +78 -0
  54. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-oQw5Xo5T.mjs.map +1 -0
  55. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs → isaiahiroko-tilmart-ngx-apps-admin-referrals.module-BIAY4L_u.mjs} +9 -34
  56. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-BIAY4L_u.mjs.map +1 -0
  57. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs → isaiahiroko-tilmart-ngx-apps-admin-reports.module-DORiIVLQ.mjs} +5 -5
  58. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map → isaiahiroko-tilmart-ngx-apps-admin-reports.module-DORiIVLQ.mjs.map} +1 -1
  59. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-C9Iu9CX6.mjs +163 -0
  60. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-C9Iu9CX6.mjs.map +1 -0
  61. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-CGBBHwId.mjs +81 -0
  62. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-CGBBHwId.mjs.map +1 -0
  63. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-Cz6xPNwB.mjs +318 -0
  64. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-Cz6xPNwB.mjs.map +1 -0
  65. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs → isaiahiroko-tilmart-ngx-apps-admin-shipments.module-Cs5CLamm.mjs} +13 -32
  66. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-Cs5CLamm.mjs.map +1 -0
  67. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-simple-crud.base-C8SPw14H.mjs +140 -0
  68. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-simple-crud.base-C8SPw14H.mjs.map +1 -0
  69. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-Bt9W1oGR.mjs +102 -0
  70. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-Bt9W1oGR.mjs.map +1 -0
  71. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs → isaiahiroko-tilmart-ngx-apps-admin-trace.module-9sRlGZCC.mjs} +6 -6
  72. 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
  73. package/fesm2022/{isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs → isaiahiroko-tilmart-ngx-apps-admin-users.module-C31RJJJ_.mjs} +9 -34
  74. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-C31RJJJ_.mjs.map +1 -0
  75. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +96 -72
  76. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -1
  77. package/package.json +1 -1
  78. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +0 -294
  79. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +0 -1
  80. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +0 -224
  81. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +0 -1
  82. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +0 -106
  83. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +0 -1
  84. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +0 -106
  85. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +0 -1
  86. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +0 -106
  87. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +0 -1
  88. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +0 -1
  89. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +0 -106
  90. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +0 -1
  91. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +0 -274
  92. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +0 -1
  93. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +0 -106
  94. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +0 -1
  95. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +0 -106
  96. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +0 -1
  97. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +0 -1
  98. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +0 -106
  99. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +0 -1
  100. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +0 -105
  101. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +0 -1
  102. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +0 -222
  103. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +0 -1
  104. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +0 -1
  105. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +0 -107
  106. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +0 -1
  107. 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":"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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$1 from '@angular/router';
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 i1 from '@thescaffold/ngx-ui';
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 i2 from '@angular/common';
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: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
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$1.RouterModule] }); }
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-Bfgh3zE5.mjs.map
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$1 from '@angular/router';
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 i1 from '@thescaffold/ngx-ui';
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 i2 from '@angular/common';
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 &amp; deletion requests (SEC-04 / SEC-05).\n </div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-dr-layout\">\n <div class=\"x-dr-list\">\n @for (r of requests; track r.id) {\n <button\n type=\"button\"\n class=\"x-dr-row\"\n [class.is-active]=\"selected?.id === r.id\"\n (click)=\"selected = r\"\n >\n <span class=\"x-dr-type x-dr-type-{{ r.type }}\">{{ r.type }}</span>\n <span class=\"x-dr-status\">{{ r.status.replace('_', ' ') }}</span>\n <span class=\"x-dr-when\">{{ r.createdAt | date: 'shortDate' }}</span>\n </button>\n } @empty {\n <div class=\"x-dr-empty\">No requests.</div>\n }\n </div>\n\n <div class=\"x-dr-detail\">\n @if (!selected) {\n <div class=\"x-dr-empty\">Select a request.</div>\n } @else {\n <div class=\"x-dr-d-title\">{{ selected.type | titlecase }} request</div>\n <dl class=\"x-dr-meta\">\n <dt>Status</dt><dd>{{ selected.status.replace('_', ' ') }}</dd>\n <dt>Customer</dt><dd>{{ selected.customerId || '\u2014' }}</dd>\n <dt>Contact</dt><dd>{{ selected.email }} {{ selected.phone }}</dd>\n <dt>Lodged</dt><dd>{{ selected.createdAt | date: 'medium' }}</dd>\n @if (selected.completedAt) {\n <dt>Completed</dt><dd>{{ selected.completedAt | date: 'medium' }}</dd>\n }\n </dl>\n @if (selected.note) {\n <p class=\"x-dr-note\"><strong>Note:</strong> {{ selected.note }}</p>\n }\n @if (selected.retentionNote) {\n <div class=\"x-dr-retain\">\n <strong>Retained:</strong>\n <pre>{{ selected.retentionNote }}</pre>\n </div>\n }\n\n @if (selected.status !== 'completed' && selected.status !== 'rejected') {\n <div class=\"x-dr-actions\">\n @if (selected.type === 'deletion' && selected.customerId) {\n <button type=\"button\" class=\"x-dr-btn x-dr-btn-danger\" [disabled]=\"fulfilling\" (click)=\"fulfil()\">\n {{ fulfilling ? 'Working\u2026' : 'Fulfil deletion' }}\n </button>\n }\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('in_progress')\">Mark in progress</button>\n <button type=\"button\" class=\"x-dr-btn\" (click)=\"setStatus('rejected')\">Reject</button>\n </div>\n }\n }\n </div>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 20px; }\n .x-page-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-page-subtitle { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-dr-layout { display: grid; grid-template-columns: 260px 1fr; gap: 16px; align-items: start; }\n @media (max-width: 720px) { .x-dr-layout { grid-template-columns: 1fr; } }\n .x-dr-list { display: flex; flex-direction: column; gap: 4px; }\n .x-dr-row { text-align: left; appearance: none; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-surface, #fff); padding: 9px 11px; cursor: pointer; display: flex; gap: 8px; align-items: center; font-size: 12.5px; }\n .x-dr-row.is-active { border-color: var(--color-brand); }\n .x-dr-type { font-weight: 700; text-transform: uppercase; font-size: 10.5px; }\n .x-dr-type-deletion { color: var(--color-danger, #c0392b); }\n .x-dr-status { color: var(--color-text-muted); text-transform: capitalize; }\n .x-dr-when { margin-left: auto; color: var(--color-text-muted); }\n .x-dr-detail { border: 1px solid var(--color-border); border-radius: 12px; padding: 16px; }\n .x-dr-d-title { font-size: 16px; font-weight: 800; color: var(--color-text); margin-bottom: 10px; }\n .x-dr-meta { display: grid; grid-template-columns: 90px 1fr; gap: 4px 12px; font-size: 13px; margin: 0 0 12px; }\n .x-dr-meta dt { color: var(--color-text-muted); }\n .x-dr-meta dd { margin: 0; color: var(--color-text); }\n .x-dr-note { font-size: 13px; color: var(--color-text-muted); }\n .x-dr-retain pre { white-space: pre-wrap; font: inherit; font-size: 12.5px; color: var(--color-text-muted); background: color-mix(in srgb, var(--color-border) 20%, transparent); padding: 8px; border-radius: 6px; }\n .x-dr-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }\n .x-dr-btn { font: inherit; font-weight: 600; font-size: 12.5px; padding: 7px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-dr-btn:disabled { opacity: 0.5; }\n .x-dr-btn-danger { color: #fff; background: var(--color-danger, #c0392b); border-color: var(--color-danger, #c0392b); }\n .x-dr-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--color-text-muted); }\n</style>\n", styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-dr-layout{display:grid;grid-template-columns:260px 1fr;gap:16px;align-items:start}@media(max-width:720px){.x-dr-layout{grid-template-columns:1fr}}.x-dr-list{display:flex;flex-direction:column;gap:4px}.x-dr-row{text-align:left;appearance:none;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface, #fff);padding:9px 11px;cursor:pointer;display:flex;gap:8px;align-items:center;font-size:12.5px}.x-dr-row.is-active{border-color:var(--color-brand)}.x-dr-type{font-weight:700;text-transform:uppercase;font-size:10.5px}.x-dr-type-deletion{color:var(--color-danger, #c0392b)}.x-dr-status{color:var(--color-text-muted);text-transform:capitalize}.x-dr-when{margin-left:auto;color:var(--color-text-muted)}.x-dr-detail{border:1px solid var(--color-border);border-radius:12px;padding:16px}.x-dr-d-title{font-size:16px;font-weight:800;color:var(--color-text);margin-bottom:10px}.x-dr-meta{display:grid;grid-template-columns:90px 1fr;gap:4px 12px;font-size:13px;margin:0 0 12px}.x-dr-meta dt{color:var(--color-text-muted)}.x-dr-meta dd{margin:0;color:var(--color-text)}.x-dr-note{font-size:13px;color:var(--color-text-muted)}.x-dr-retain pre{white-space:pre-wrap;font:inherit;font-size:12.5px;color:var(--color-text-muted);background:color-mix(in srgb,var(--color-border) 20%,transparent);padding:8px;border-radius:6px}.x-dr-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.x-dr-btn{font:inherit;font-weight:600;font-size:12.5px;padding:7px 12px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-surface, #fff);color:var(--color-text);cursor:pointer}.x-dr-btn:disabled{opacity:.5}.x-dr-btn-danger{color:#fff;background:var(--color-danger, #c0392b);border-color:var(--color-danger, #c0392b)}.x-dr-empty{padding:24px;text-align:center;font-size:13px;color:var(--color-text-muted)}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
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 &amp; 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$1.RouterModule] }); }
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-C6tPNlup.mjs.map
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 &amp; 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":";;;;;;;;;;AAiBA;AACA;AACA;AACA;AAMM,MAAO,qBAAsB,SAAQ,aAAa,CAAA;AALxD,IAAA,WAAA,GAAA;;QAMmB,IAAA,CAAA,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,4BAA4B,CAAC;QAE1E,IAAA,CAAA,OAAO,GAAG,IAAI;QACd,IAAA,CAAA,QAAQ,GAAgB,EAAE;QAE1B,IAAA,CAAA,UAAU,GAAG,KAAK;QAET,IAAA,CAAA,IAAI,GAAG,MAAK;YACnB,IAAI,CAAC,IAAI,EAAE;AACb,QAAA,CAAC;AA8DF,IAAA;IA5DC,IAAI,GAAA;AACF,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;AACnB,QAAA,IAAI,CAAC;AACF,aAAA,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,EAAS;AAC/B,aAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACtB,aAAA,SAAS,CACR,KAAK,CAAC,CAAC,GAAQ,KAAI;AACjB,YAAA,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,CACpC,CAAC,CAAY,EAAE,CAAY,KACzB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CACpE;AACD,YAAA,IAAI,CAAC,OAAO,GAAG,KAAK;AACpB,YAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;gBACjB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,QAAS,CAAC,EAAE,CAAC;YACvE;QACF,CAAC,CAAC,CACH;IACL;AAEQ,IAAA,IAAI,CAAC,CAAY,EAAA;QACvB,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;AAAE,YAAA,OAAO,CAAC;AACjE,QAAA,OAAO,CAAC,CAAC,IAAI,KAAK,UAAU,GAAG,CAAC,GAAG,CAAC;IACtC;IAEA,MAAM,SAAS,CAAC,MAAc,EAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE;AACpB,QAAA,IAAI;AACF,YAAA,MAAM,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAS,CAAC,CAAC;YAC7E,IAAI,CAAC,IAAI,EAAE;QACb;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,+BAA+B,EAAE,SAAS,EAAE,OAAO,CAAC;QAC7D;IACF;AAEA,IAAA,MAAM,MAAM,GAAA;AACV,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU;YAAE;AACvC,QAAA,IACE,CAAC,OAAO,CACN,yEAAyE,CAC1E,EACD;YACA;QACF;AACA,QAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AACtB,QAAA,IAAI;AACF,YAAA,MAAM,cAAc,CAClB,IAAI,CAAC,UAAU,CAAC,IAAI,CAClB,CAAA,2BAAA,EAA8B,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAA,OAAA,CAAS,EACvD,EAAE,CACH,CACF;AACD,YAAA,MAAM,CAAC,qBAAqB,EAAE,SAAS,EAAE,SAAS,CAAC;YACnD,IAAI,CAAC,IAAI,EAAE;QACb;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,kCAAkC,EAAE,SAAS,EAAE,OAAO,CAAC;QAChE;gBAAU;AACR,YAAA,IAAI,CAAC,UAAU,GAAG,KAAK;QACzB;IACF;+GAvEW,qBAAqB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAArB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,qBAAqB,mGC1BlC,k7JA8FA,EAAA,MAAA,EAAA,CAAA,u+DAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,IAAA,EAAA,WAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,EAAA,CAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA,CAAA;;4FDpEa,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBALjC,SAAS;AACI,YAAA,IAAA,EAAA,CAAA,EAAA,UAAA,EAAA,KAAK,YACP,iBAAiB,EAAA,QAAA,EAAA,k7JAAA,EAAA;;;METhB,kBAAkB,CAAA;+GAAlB,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gHAAlB,kBAAkB,EAAA,YAAA,EAAA,CAPd,qBAAqB,CAAA,EAAA,OAAA,EAAA,CAElC,YAAY;YACZ,cAAc,EAAAA,IAAA,CAAA,YAAA,CAAA,EAAA,CAAA,CAAA;AAIL,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,kBAAkB,YAL3B,YAAY;YACZ,cAAc;AACd,YAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAA,EAAA,CAAA,CAAA;;4FAG9D,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAR9B,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,YAAY,EAAE,CAAC,qBAAqB,CAAC;AACrC,oBAAA,OAAO,EAAE;wBACP,YAAY;wBACZ,cAAc;AACd,wBAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC,CAAC;AACxE,qBAAA;AACF,iBAAA;;;;;"}
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 &amp; 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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"}