@isaiahiroko/tilmart-ngx-apps-admin 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +9 -0
  2. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs +154 -0
  3. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map +1 -0
  4. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs +84 -0
  5. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map +1 -0
  6. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +294 -0
  7. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +1 -0
  8. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +224 -0
  9. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +1 -0
  10. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +106 -0
  11. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +1 -0
  12. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs +261 -0
  13. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map +1 -0
  14. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs +365 -0
  15. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map +1 -0
  16. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +106 -0
  17. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +1 -0
  18. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +106 -0
  19. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +1 -0
  20. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs +286 -0
  21. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map +1 -0
  22. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs +104 -0
  23. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map +1 -0
  24. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs +73 -0
  25. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map +1 -0
  26. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs +404 -0
  27. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map +1 -0
  28. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs +290 -0
  29. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map +1 -0
  30. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs +112 -0
  31. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map +1 -0
  32. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs +116 -0
  33. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map +1 -0
  34. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs +281 -0
  35. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map +1 -0
  36. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs +115 -0
  37. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map +1 -0
  38. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs +185 -0
  39. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +1 -0
  40. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +106 -0
  41. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +1 -0
  42. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs +70 -0
  43. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs.map +1 -0
  44. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +274 -0
  45. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +1 -0
  46. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +106 -0
  47. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +1 -0
  48. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs +84 -0
  49. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map +1 -0
  50. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +106 -0
  51. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +1 -0
  52. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs +106 -0
  53. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +1 -0
  54. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs +104 -0
  55. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map +1 -0
  56. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +106 -0
  57. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +1 -0
  58. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs +81 -0
  59. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs.map +1 -0
  60. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +105 -0
  61. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +1 -0
  62. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +222 -0
  63. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +1 -0
  64. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs +150 -0
  65. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +1 -0
  66. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +107 -0
  67. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +1 -0
  68. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs +92 -0
  69. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map +1 -0
  70. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs +111 -0
  71. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +1 -0
  72. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +475 -0
  73. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -0
  74. package/package.json +34 -0
  75. package/types/isaiahiroko-tilmart-ngx-apps-admin.d.ts +32 -0
@@ -0,0 +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;;;;;"}
@@ -0,0 +1,73 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import { BaseDirective, SharedModule } from '@thescaffold/ngx-core';
6
+
7
+ // P7.1 — ops console section pass (PRD 6.2). Payment-provider disputes
8
+ // (chargebacks) are a stub for R1: no dispute resource exists yet — the
9
+ // Paystack/Flutterwave webhook ingestion that would land `Dispute` rows
10
+ // is R2 (PAY-*). The section is present so the nav map matches PRD 6.2;
11
+ // it activates when that webhook work lands.
12
+ class DisputesComponent extends BaseDirective {
13
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DisputesComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
14
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.16", type: DisputesComponent, isStandalone: false, selector: "x-disputes", usesInheritance: true, ngImport: i0, template: `
15
+ <div class="x-page-head">
16
+ <div class="x-page-title">Disputes</div>
17
+ <div class="x-page-subtitle">Card chargebacks and payment disputes.</div>
18
+ </div>
19
+
20
+ <div class="x-stub">
21
+ <p>
22
+ Dispute management is not active for R1. It turns on with the
23
+ payment-provider webhook ingestion (PAY-*, R2), which lands dispute
24
+ events from Paystack / Flutterwave into a queue here.
25
+ </p>
26
+ <p class="x-stub-meanwhile">
27
+ Meanwhile, disputed transactions are handled directly in the provider
28
+ dashboard and reconciled under <a routerLink="/admin/payments">Finance</a>.
29
+ </p>
30
+ </div>
31
+ `, 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-stub{max-width:560px;border:1px solid var(--color-border);border-radius:10px;padding:20px;font-size:14px;line-height:1.6;color:var(--color-text-muted)}.x-stub-meanwhile{margin-bottom:0}.x-stub a{color:var(--color-brand)}\n"], dependencies: [{ kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
32
+ }
33
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DisputesComponent, decorators: [{
34
+ type: Component,
35
+ args: [{ standalone: false, selector: 'x-disputes', template: `
36
+ <div class="x-page-head">
37
+ <div class="x-page-title">Disputes</div>
38
+ <div class="x-page-subtitle">Card chargebacks and payment disputes.</div>
39
+ </div>
40
+
41
+ <div class="x-stub">
42
+ <p>
43
+ Dispute management is not active for R1. It turns on with the
44
+ payment-provider webhook ingestion (PAY-*, R2), which lands dispute
45
+ events from Paystack / Flutterwave into a queue here.
46
+ </p>
47
+ <p class="x-stub-meanwhile">
48
+ Meanwhile, disputed transactions are handled directly in the provider
49
+ dashboard and reconciled under <a routerLink="/admin/payments">Finance</a>.
50
+ </p>
51
+ </div>
52
+ `, 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-stub{max-width:560px;border:1px solid var(--color-border);border-radius:10px;padding:20px;font-size:14px;line-height:1.6;color:var(--color-text-muted)}.x-stub-meanwhile{margin-bottom:0}.x-stub a{color:var(--color-brand)}\n"] }]
53
+ }] });
54
+
55
+ class DisputesModule {
56
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DisputesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
57
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: DisputesModule, declarations: [DisputesComponent], imports: [SharedModule, i1.RouterModule] }); }
58
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DisputesModule, imports: [SharedModule,
59
+ RouterModule.forChild([{ path: '', component: DisputesComponent }])] }); }
60
+ }
61
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: DisputesModule, decorators: [{
62
+ type: NgModule,
63
+ args: [{
64
+ declarations: [DisputesComponent],
65
+ imports: [
66
+ SharedModule,
67
+ RouterModule.forChild([{ path: '', component: DisputesComponent }]),
68
+ ],
69
+ }]
70
+ }] });
71
+
72
+ export { DisputesModule };
73
+ //# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs","sources":["../../../projects/admin/src/lib/pages/disputes/disputes.component.ts","../../../projects/admin/src/lib/pages/disputes/disputes.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective } from '@thescaffold/ngx-core';\n\n// P7.1 — ops console section pass (PRD 6.2). Payment-provider disputes\n// (chargebacks) are a stub for R1: no dispute resource exists yet — the\n// Paystack/Flutterwave webhook ingestion that would land `Dispute` rows\n// is R2 (PAY-*). The section is present so the nav map matches PRD 6.2;\n// it activates when that webhook work lands.\n@Component({\n standalone: false,\n selector: 'x-disputes',\n template: `\n <div class=\"x-page-head\">\n <div class=\"x-page-title\">Disputes</div>\n <div class=\"x-page-subtitle\">Card chargebacks and payment disputes.</div>\n </div>\n\n <div class=\"x-stub\">\n <p>\n Dispute management is not active for R1. It turns on with the\n payment-provider webhook ingestion (PAY-*, R2), which lands dispute\n events from Paystack / Flutterwave into a queue here.\n </p>\n <p class=\"x-stub-meanwhile\">\n Meanwhile, disputed transactions are handled directly in the provider\n dashboard and reconciled under <a routerLink=\"/admin/payments\">Finance</a>.\n </p>\n </div>\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-stub {\n max-width: 560px;\n border: 1px solid var(--color-border);\n border-radius: 10px;\n padding: 20px;\n font-size: 14px;\n line-height: 1.6;\n color: var(--color-text-muted);\n }\n .x-stub-meanwhile {\n margin-bottom: 0;\n }\n .x-stub a {\n color: var(--color-brand);\n }\n `,\n ],\n})\nexport class DisputesComponent extends BaseDirective {}\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { DisputesComponent } from './disputes.component';\n\n@NgModule({\n declarations: [DisputesComponent],\n imports: [\n SharedModule,\n RouterModule.forChild([{ path: '', component: DisputesComponent }]),\n ],\n})\nexport class DisputesModule {}\n"],"names":[],"mappings":";;;;;;AAGA;AACA;AACA;AACA;AACA;AAuDM,MAAO,iBAAkB,SAAQ,aAAa,CAAA;+GAAvC,iBAAiB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAjB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,iBAAiB,EAAA,YAAA,EAAA,KAAA,EAAA,QAAA,EAAA,YAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnDlB;;;;;;;;;;;;;;;;;AAiBT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,uZAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,aAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,YAAA,EAAA,YAAA,CAAA,EAAA,CAAA,EAAA,CAAA,CAAA;;4FAkCU,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAtD7B,SAAS;iCACI,KAAK,EAAA,QAAA,EACP,YAAY,EAAA,QAAA,EACZ;;;;;;;;;;;;;;;;;AAiBT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,uZAAA,CAAA,EAAA;;;MChBU,cAAc,CAAA;+GAAd,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gHAAd,cAAc,EAAA,YAAA,EAAA,CANV,iBAAiB,CAAA,EAAA,OAAA,EAAA,CAE9B,YAAY,EAAA,EAAA,CAAA,YAAA,CAAA,EAAA,CAAA,CAAA;AAIH,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,cAAc,YAJvB,YAAY;AACZ,YAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAA,EAAA,CAAA,CAAA;;4FAG1D,cAAc,EAAA,UAAA,EAAA,CAAA;kBAP1B,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,YAAY,EAAE,CAAC,iBAAiB,CAAC;AACjC,oBAAA,OAAO,EAAE;wBACP,YAAY;AACZ,wBAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACpE,qBAAA;AACF,iBAAA;;;;;"}
@@ -0,0 +1,404 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, NgModule } from '@angular/core';
3
+ import * as i1$2 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import { BaseDirective, using, notify, SharedModule } from '@thescaffold/ngx-core';
6
+ import * as i1$1 from '@thescaffold/ngx-ui';
7
+ import { ElementsModule } from '@thescaffold/ngx-ui';
8
+ import { takeUntil, forkJoin, catchError, of, firstValueFrom } from 'rxjs';
9
+ import * as i1 from '@angular/forms';
10
+
11
+ // P6.7, PROMO-06 / TRD §9.2 — the homepage composer. A drag-ordered list
12
+ // of `MerchandisingModule` rows for a page, each with a type-aware config
13
+ // editor, schedule and targeting, plus a resolved preview line (ids →
14
+ // names) and a page-shape preview mode. The storefront renderer is P6.8.
15
+ const TYPES = [
16
+ {
17
+ value: 'hero_carousel',
18
+ label: 'Hero carousel',
19
+ fields: [{ key: 'bannerIds', label: 'Banner IDs', kind: 'ids' }],
20
+ },
21
+ {
22
+ value: 'promo_banner',
23
+ label: 'Promo banner',
24
+ fields: [{ key: 'bannerIds', label: 'Banner IDs', kind: 'ids' }],
25
+ },
26
+ {
27
+ value: 'product_rail',
28
+ label: 'Product rail',
29
+ fields: [
30
+ { key: 'categoryId', label: 'From category ID', kind: 'id' },
31
+ { key: 'productIds', label: 'Or explicit product IDs', kind: 'ids' },
32
+ { key: 'limit', label: 'Max items', kind: 'num' },
33
+ ],
34
+ },
35
+ {
36
+ value: 'category_grid',
37
+ label: 'Category grid',
38
+ fields: [{ key: 'categoryIds', label: 'Category IDs', kind: 'ids' }],
39
+ },
40
+ {
41
+ value: 'brand_strip',
42
+ label: 'Brand strip',
43
+ fields: [{ key: 'brandIds', label: 'Brand IDs', kind: 'ids' }],
44
+ },
45
+ {
46
+ value: 'flash_sale_rail',
47
+ label: 'Flash sale rail',
48
+ fields: [{ key: 'flashSaleId', label: 'Flash sale ID (blank = all live)', kind: 'id' }],
49
+ },
50
+ {
51
+ value: 'article_rail',
52
+ label: 'Article rail',
53
+ fields: [
54
+ { key: 'articleIds', label: 'Article IDs', kind: 'ids' },
55
+ { key: 'limit', label: 'Max items', kind: 'num' },
56
+ ],
57
+ },
58
+ {
59
+ value: 'rich_text',
60
+ label: 'Rich text',
61
+ fields: [{ key: 'html', label: 'HTML', kind: 'text' }],
62
+ },
63
+ { value: 'newsletter', label: 'Newsletter signup', fields: [] },
64
+ { value: 'trust_bar', label: 'Trust bar', fields: [] },
65
+ ];
66
+ class HomepageComponent extends BaseDirective {
67
+ constructor() {
68
+ super(...arguments);
69
+ this.source = this.apiService.on('apps/commerce/merchandising-module');
70
+ this.types = TYPES;
71
+ this.page = 'home';
72
+ this.loading = true;
73
+ this.modules = [];
74
+ this.previewMode = false;
75
+ this.newType = TYPES[0].value;
76
+ this.editModel = this.blankEditModel();
77
+ this.dragIndex = null;
78
+ // id → name lookups for the preview line.
79
+ this.productName = new Map();
80
+ this.categoryName = new Map();
81
+ this.brandName = new Map();
82
+ this.bannerName = new Map();
83
+ this.flashSaleName = new Map();
84
+ this.init = () => {
85
+ this.loadLookups();
86
+ this.load();
87
+ };
88
+ }
89
+ typeLabel(value) {
90
+ return TYPES.find((t) => t.value === value)?.label ?? value;
91
+ }
92
+ typeDef(value) {
93
+ return TYPES.find((t) => t.value === value);
94
+ }
95
+ get orderedModules() {
96
+ return [...this.modules].sort((a, b) => a.position - b.position);
97
+ }
98
+ get previewModules() {
99
+ const now = Date.now();
100
+ return this.orderedModules.filter((m) => m.enabled &&
101
+ (!m.startsAt || Date.parse(m.startsAt) <= now) &&
102
+ (!m.endsAt || Date.parse(m.endsAt) >= now));
103
+ }
104
+ load() {
105
+ this.loading = true;
106
+ this.source
107
+ .getMany({ page: this.page, perPage: 200 })
108
+ .pipe(takeUntil(this.$))
109
+ .subscribe(using((res) => {
110
+ this.modules = (res?.data ?? [])
111
+ .filter((m) => m.page === this.page)
112
+ .map((m) => ({ ...m, config: m.config ?? {} }))
113
+ .sort((a, b) => a.position - b.position);
114
+ this.loading = false;
115
+ }));
116
+ }
117
+ changePage(page) {
118
+ this.page = page.trim() || 'home';
119
+ this.cancelEdit();
120
+ this.load();
121
+ }
122
+ loadLookups() {
123
+ forkJoin({
124
+ products: this.apiService
125
+ .on('apps/commerce/product')
126
+ .getMany({ perPage: 500 })
127
+ .pipe(catchError(() => of({ data: [] }))),
128
+ categories: this.apiService
129
+ .on('apps/commerce/category')
130
+ .getMany({ perPage: 500 })
131
+ .pipe(catchError(() => of({ data: [] }))),
132
+ brands: this.apiService
133
+ .on('apps/commerce/brand')
134
+ .getMany({ perPage: 500 })
135
+ .pipe(catchError(() => of({ data: [] }))),
136
+ banners: this.apiService
137
+ .on('apps/commerce/banner')
138
+ .getMany({ perPage: 500 })
139
+ .pipe(catchError(() => of({ data: [] }))),
140
+ flashSales: this.apiService
141
+ .on('apps/commerce/flash-sale')
142
+ .getMany({ perPage: 500 })
143
+ .pipe(catchError(() => of({ data: [] }))),
144
+ })
145
+ .pipe(takeUntil(this.$))
146
+ .subscribe(using((res) => {
147
+ for (const p of res?.products?.data ?? [])
148
+ this.productName.set(p.id, p.name);
149
+ for (const c of res?.categories?.data ?? [])
150
+ this.categoryName.set(c.id, c.name);
151
+ for (const b of res?.brands?.data ?? [])
152
+ this.brandName.set(b.id, b.name);
153
+ for (const b of res?.banners?.data ?? [])
154
+ this.bannerName.set(b.id, b.title || b.slot || b.id);
155
+ for (const f of res?.flashSales?.data ?? [])
156
+ this.flashSaleName.set(f.id, f.name);
157
+ }));
158
+ }
159
+ previewLine(m) {
160
+ const c = m.config ?? {};
161
+ const names = (ids, map) => (Array.isArray(ids) ? ids : [])
162
+ .map((id) => map.get(id) ?? id)
163
+ .filter(Boolean);
164
+ switch (m.type) {
165
+ case 'hero_carousel':
166
+ case 'promo_banner': {
167
+ const list = names(c.bannerIds, this.bannerName);
168
+ return list.length ? `${list.length} banner(s): ${list.join(', ')}` : 'No banners set';
169
+ }
170
+ case 'product_rail': {
171
+ if (c.categoryId) {
172
+ const cat = this.categoryName.get(c.categoryId) ?? c.categoryId;
173
+ return `Top ${c.limit || 12} from ${cat}`;
174
+ }
175
+ const list = names(c.productIds, this.productName);
176
+ return list.length ? `${list.length} product(s): ${list.join(', ')}` : 'No products set';
177
+ }
178
+ case 'category_grid': {
179
+ const list = names(c.categoryIds, this.categoryName);
180
+ return list.length ? list.join(', ') : 'No categories set';
181
+ }
182
+ case 'brand_strip': {
183
+ const list = names(c.brandIds, this.brandName);
184
+ return list.length ? list.join(', ') : 'No brands set';
185
+ }
186
+ case 'flash_sale_rail':
187
+ return c.flashSaleId
188
+ ? this.flashSaleName.get(c.flashSaleId) ?? c.flashSaleId
189
+ : 'All live flash sales';
190
+ case 'article_rail': {
191
+ const n = Array.isArray(c.articleIds) ? c.articleIds.length : 0;
192
+ return n ? `${n} article(s)` : `Latest ${c.limit || 6} articles`;
193
+ }
194
+ case 'rich_text':
195
+ return (c.html ? String(c.html).replace(/<[^>]+>/g, ' ').trim().slice(0, 80) : '') || 'Empty';
196
+ default:
197
+ return '—';
198
+ }
199
+ }
200
+ scheduleLine(m) {
201
+ if (!m.startsAt && !m.endsAt)
202
+ return '';
203
+ const fmt = (v) => (v ? new Date(v).toLocaleDateString() : '…');
204
+ return `${fmt(m.startsAt)} → ${fmt(m.endsAt)}`;
205
+ }
206
+ // ── ordering ──────────────────────────────────────────────────────
207
+ onDragStart(index) {
208
+ this.dragIndex = index;
209
+ }
210
+ onDragOver(event) {
211
+ event.preventDefault();
212
+ }
213
+ onDrop(targetIndex) {
214
+ if (this.dragIndex === null || this.dragIndex === targetIndex)
215
+ return;
216
+ const ordered = this.orderedModules;
217
+ const [moved] = ordered.splice(this.dragIndex, 1);
218
+ ordered.splice(targetIndex, 0, moved);
219
+ this.dragIndex = null;
220
+ void this.persistOrder(ordered);
221
+ }
222
+ moveUp(index) {
223
+ if (index <= 0)
224
+ return;
225
+ const ordered = this.orderedModules;
226
+ [ordered[index - 1], ordered[index]] = [ordered[index], ordered[index - 1]];
227
+ void this.persistOrder(ordered);
228
+ }
229
+ moveDown(index) {
230
+ const ordered = this.orderedModules;
231
+ if (index >= ordered.length - 1)
232
+ return;
233
+ [ordered[index + 1], ordered[index]] = [ordered[index], ordered[index + 1]];
234
+ void this.persistOrder(ordered);
235
+ }
236
+ async persistOrder(ordered) {
237
+ const changed = [];
238
+ ordered.forEach((m, i) => {
239
+ if (m.position !== i) {
240
+ m.position = i;
241
+ changed.push(m);
242
+ }
243
+ });
244
+ this.modules = ordered;
245
+ try {
246
+ await Promise.all(changed.map((m) => firstValueFrom(this.source.update(m.id, { position: m.position }))));
247
+ }
248
+ catch {
249
+ notify('Could not save the new order.', undefined, 'error');
250
+ this.load();
251
+ }
252
+ }
253
+ // ── add / toggle / delete ─────────────────────────────────────────
254
+ async addModule() {
255
+ try {
256
+ await firstValueFrom(this.source.create({
257
+ page: this.page,
258
+ type: this.newType,
259
+ position: this.modules.length,
260
+ config: {},
261
+ enabled: false,
262
+ }));
263
+ notify('Module added.', undefined, 'success');
264
+ this.load();
265
+ }
266
+ catch {
267
+ notify('Could not add the module.', undefined, 'error');
268
+ }
269
+ }
270
+ async toggleEnabled(m) {
271
+ const next = !m.enabled;
272
+ try {
273
+ await firstValueFrom(this.source.update(m.id, { enabled: next }));
274
+ m.enabled = next;
275
+ }
276
+ catch {
277
+ notify('Could not update the module.', undefined, 'error');
278
+ }
279
+ }
280
+ async removeModule(m) {
281
+ try {
282
+ await firstValueFrom(this.source.remove(m.id));
283
+ this.modules = this.modules.filter((x) => x.id !== m.id);
284
+ if (this.editing?.id === m.id)
285
+ this.cancelEdit();
286
+ notify('Module removed.', undefined, 'success');
287
+ }
288
+ catch {
289
+ notify('Could not remove the module.', undefined, 'error');
290
+ }
291
+ }
292
+ // ── edit ──────────────────────────────────────────────────────────
293
+ editModule(m) {
294
+ this.editing = m;
295
+ const config = {};
296
+ for (const field of this.typeDef(m.type)?.fields ?? []) {
297
+ const value = (m.config ?? {})[field.key];
298
+ config[field.key] = Array.isArray(value) ? value.join(', ') : value != null ? String(value) : '';
299
+ }
300
+ this.editModel = {
301
+ title: m.title ?? '',
302
+ startsAt: this.toLocalInput(m.startsAt),
303
+ endsAt: this.toLocalInput(m.endsAt),
304
+ channelIds: (m.channelIds ?? []).join(', '),
305
+ segmentIds: (m.segmentIds ?? []).join(', '),
306
+ config,
307
+ };
308
+ }
309
+ cancelEdit() {
310
+ this.editing = undefined;
311
+ this.editModel = this.blankEditModel();
312
+ }
313
+ async saveEdit() {
314
+ if (!this.editing)
315
+ return;
316
+ const def = this.typeDef(this.editing.type);
317
+ const config = { ...(this.editing.config ?? {}) };
318
+ for (const field of def?.fields ?? []) {
319
+ const raw = (this.editModel.config[field.key] ?? '').trim();
320
+ if (field.kind === 'ids') {
321
+ config[field.key] = raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];
322
+ }
323
+ else if (field.kind === 'num') {
324
+ config[field.key] = raw ? Number(raw) : undefined;
325
+ }
326
+ else {
327
+ config[field.key] = raw || undefined;
328
+ }
329
+ }
330
+ const csv = (s) => s.split(',').map((x) => x.trim()).filter(Boolean);
331
+ const body = {
332
+ title: this.editModel.title.trim() || undefined,
333
+ startsAt: this.editModel.startsAt ? new Date(this.editModel.startsAt).toISOString() : null,
334
+ endsAt: this.editModel.endsAt ? new Date(this.editModel.endsAt).toISOString() : null,
335
+ channelIds: csv(this.editModel.channelIds),
336
+ segmentIds: csv(this.editModel.segmentIds),
337
+ config,
338
+ };
339
+ try {
340
+ await firstValueFrom(this.source.update(this.editing.id, body));
341
+ Object.assign(this.editing, {
342
+ title: body.title,
343
+ startsAt: body.startsAt ?? undefined,
344
+ endsAt: body.endsAt ?? undefined,
345
+ channelIds: body.channelIds,
346
+ segmentIds: body.segmentIds,
347
+ config,
348
+ });
349
+ notify('Module saved.', undefined, 'success');
350
+ this.cancelEdit();
351
+ }
352
+ catch {
353
+ notify('Could not save the module.', undefined, 'error');
354
+ }
355
+ }
356
+ blankEditModel() {
357
+ return {
358
+ title: '',
359
+ startsAt: '',
360
+ endsAt: '',
361
+ channelIds: '',
362
+ segmentIds: '',
363
+ config: {},
364
+ };
365
+ }
366
+ toLocalInput(iso) {
367
+ if (!iso)
368
+ return '';
369
+ const d = new Date(iso);
370
+ if (Number.isNaN(d.getTime()))
371
+ return '';
372
+ const pad = (n) => String(n).padStart(2, '0');
373
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
374
+ }
375
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HomepageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
376
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: HomepageComponent, isStandalone: false, selector: "x-homepage", usesInheritance: true, ngImport: i0, template: "<div class=\"x-hc-head\">\n <div>\n <div class=\"x-hc-title\">Homepage composer</div>\n <div class=\"x-hc-sub\">Drag to reorder the modules that build a storefront page.</div>\n </div>\n <div class=\"x-hc-head-controls\">\n <label class=\"x-hc-page-field\">\n Page\n <input\n type=\"text\"\n [ngModel]=\"page\"\n (ngModelChange)=\"changePage($event)\"\n placeholder=\"home\"\n />\n </label>\n <button\n type=\"button\"\n class=\"x-hc-btn\"\n [class.x-hc-btn-active]=\"previewMode\"\n (click)=\"previewMode = !previewMode\"\n >\n {{ previewMode ? 'Exit preview' : 'Preview' }}\n </button>\n </div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n @if (!previewMode) {\n <div class=\"x-hc-add\">\n <select [(ngModel)]=\"newType\">\n @for (t of types; track t.value) {\n <option [value]=\"t.value\">{{ t.label }}</option>\n }\n </select>\n <button type=\"button\" class=\"x-hc-btn x-hc-btn-primary\" (click)=\"addModule()\">\n Add module\n </button>\n </div>\n }\n\n @if (!orderedModules.length) {\n <div class=\"x-hc-empty\">No modules on this page yet.</div>\n }\n\n @if (previewMode) {\n <div class=\"x-hc-preview\">\n @for (m of previewModules; track m.id) {\n <div class=\"x-hc-preview-block\">\n <span class=\"x-hc-preview-type\">{{ typeLabel(m.type) }}</span>\n <span class=\"x-hc-preview-title\">{{ m.title || previewLine(m) }}</span>\n </div>\n } @empty {\n <div class=\"x-hc-empty\">Nothing live on this page right now.</div>\n }\n </div>\n } @else {\n <div class=\"x-hc-list\">\n @for (m of orderedModules; track m.id; let i = $index) {\n <div\n class=\"x-hc-item\"\n [class.x-hc-item-off]=\"!m.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-hc-item-main\">\n <span class=\"x-hc-grip\" title=\"Drag to reorder\">\u283F</span>\n <div class=\"x-hc-order\">\n <button type=\"button\" (click)=\"moveUp(i)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveDown(i)\" [disabled]=\"i === orderedModules.length - 1\">\u25BC</button>\n </div>\n <div class=\"x-hc-item-body\">\n <div class=\"x-hc-item-row\">\n <span class=\"x-hc-type\">{{ typeLabel(m.type) }}</span>\n @if (m.title) {\n <span class=\"x-hc-item-title\">{{ m.title }}</span>\n }\n @if (scheduleLine(m)) {\n <span class=\"x-hc-sched\">{{ scheduleLine(m) }}</span>\n }\n </div>\n <div class=\"x-hc-item-preview\">{{ previewLine(m) }}</div>\n </div>\n <div class=\"x-hc-item-actions\">\n <button type=\"button\" class=\"x-hc-toggle\" (click)=\"toggleEnabled(m)\">\n {{ m.enabled ? 'On' : 'Off' }}\n </button>\n <button type=\"button\" class=\"x-hc-link\" (click)=\"editModule(m)\">Edit</button>\n <button type=\"button\" class=\"x-hc-link x-hc-danger\" (click)=\"removeModule(m)\">Delete</button>\n </div>\n </div>\n\n @if (editing?.id === m.id) {\n <div class=\"x-hc-edit\">\n <label class=\"x-hc-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"editModel.title\" />\n </label>\n @for (field of typeDef(m.type)?.fields ?? []; track field.key) {\n <label class=\"x-hc-f\">\n {{ field.label }}\n @if (field.kind === 'text') {\n <textarea rows=\"3\" [(ngModel)]=\"editModel.config[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"editModel.config[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-hc-f-row\">\n <label class=\"x-hc-f\">\n Starts\n <input type=\"datetime-local\" [(ngModel)]=\"editModel.startsAt\" />\n </label>\n <label class=\"x-hc-f\">\n Ends\n <input type=\"datetime-local\" [(ngModel)]=\"editModel.endsAt\" />\n </label>\n </div>\n <div class=\"x-hc-f-row\">\n <label class=\"x-hc-f\">\n Channel IDs\n <input type=\"text\" [(ngModel)]=\"editModel.channelIds\" placeholder=\"blank = all\" />\n </label>\n <label class=\"x-hc-f\">\n Segment IDs\n <input type=\"text\" [(ngModel)]=\"editModel.segmentIds\" placeholder=\"blank = all\" />\n </label>\n </div>\n <div class=\"x-hc-edit-actions\">\n <button type=\"button\" class=\"x-hc-btn x-hc-btn-primary\" (click)=\"saveEdit()\">Save</button>\n <button type=\"button\" class=\"x-hc-btn\" (click)=\"cancelEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n}\n\n<style>\n .x-hc-head {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n margin-bottom: 20px;\n flex-wrap: wrap;\n }\n .x-hc-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-hc-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-hc-head-controls { display: flex; gap: 10px; align-items: flex-end; }\n .x-hc-page-field {\n display: flex; flex-direction: column; gap: 4px;\n font-size: 11.5px; color: var(--color-text-muted); font-weight: 700;\n text-transform: uppercase; letter-spacing: 0.4px;\n }\n .x-hc-page-field input, .x-hc-add select, .x-hc-f input, .x-hc-f textarea {\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: 6px 10px;\n font-size: 13px;\n background: var(--color-surface);\n color: var(--color-text);\n }\n .x-hc-add { display: flex; gap: 10px; margin-bottom: 16px; }\n .x-hc-btn {\n border: 1px solid var(--color-border);\n background: transparent;\n border-radius: var(--radius-md);\n padding: 6px 14px;\n font-size: 12.5px;\n font-weight: 700;\n cursor: pointer;\n color: var(--color-text-muted);\n }\n .x-hc-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-hc-btn-active { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-hc-empty { font-size: 14px; color: var(--color-text-muted); }\n .x-hc-list { display: flex; flex-direction: column; gap: 10px; max-width: 860px; }\n .x-hc-item {\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n padding: 12px 14px;\n }\n .x-hc-item-off { opacity: 0.55; }\n .x-hc-item-main { display: flex; align-items: center; gap: 12px; }\n .x-hc-grip { cursor: grab; color: var(--color-text-muted); font-size: 16px; }\n .x-hc-order { display: flex; flex-direction: column; }\n .x-hc-order button {\n border: 0; background: transparent; cursor: pointer;\n color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px;\n }\n .x-hc-order button:disabled { opacity: 0.3; cursor: default; }\n .x-hc-item-body { flex: 1; min-width: 0; }\n .x-hc-item-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }\n .x-hc-type {\n font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px;\n font-size: 11px; color: var(--color-accent-hover);\n }\n .x-hc-item-title { font-size: 13.5px; font-weight: 600; color: var(--color-text); }\n .x-hc-sched { font-size: 11px; color: var(--color-text-muted); }\n .x-hc-item-preview { font-size: 12.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-hc-item-actions { display: flex; gap: 8px; align-items: center; }\n .x-hc-toggle {\n border: 1px solid var(--color-border); border-radius: var(--radius-sm);\n background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700;\n cursor: pointer; color: var(--color-text-muted);\n }\n .x-hc-link {\n border: 0; background: transparent; cursor: pointer;\n font-size: 12px; font-weight: 700; color: var(--color-accent-hover);\n }\n .x-hc-danger { color: var(--color-danger, #c0362c); }\n .x-hc-edit {\n margin-top: 12px; padding-top: 12px;\n border-top: 1px solid var(--color-border);\n display: flex; flex-direction: column; gap: 10px;\n }\n .x-hc-f {\n display: flex; flex-direction: column; gap: 4px; flex: 1;\n font-size: 11.5px; color: var(--color-text-muted); font-weight: 700;\n }\n .x-hc-f-row { display: flex; gap: 10px; }\n .x-hc-edit-actions { display: flex; gap: 8px; }\n .x-hc-preview { display: flex; flex-direction: column; gap: 6px; max-width: 860px; }\n .x-hc-preview-block {\n border: 1px dashed var(--color-border-strong);\n border-radius: var(--radius-md);\n padding: 18px 16px;\n display: flex; flex-direction: column; gap: 4px;\n background: var(--color-surface);\n }\n .x-hc-preview-type {\n font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px;\n color: var(--color-accent-hover);\n }\n .x-hc-preview-title { font-size: 14px; color: var(--color-text); }\n</style>\n", styles: [".x-hc-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:20px;flex-wrap:wrap}.x-hc-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-hc-sub{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-hc-head-controls{display:flex;gap:10px;align-items:flex-end}.x-hc-page-field{display:flex;flex-direction:column;gap:4px;font-size:11.5px;color:var(--color-text-muted);font-weight:700;text-transform:uppercase;letter-spacing:.4px}.x-hc-page-field input,.x-hc-add select,.x-hc-f input,.x-hc-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)}.x-hc-add{display:flex;gap:10px;margin-bottom:16px}.x-hc-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-hc-btn-primary,.x-hc-btn-active{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-hc-empty{font-size:14px;color:var(--color-text-muted)}.x-hc-list{display:flex;flex-direction:column;gap:10px;max-width:860px}.x-hc-item{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px 14px}.x-hc-item-off{opacity:.55}.x-hc-item-main{display:flex;align-items:center;gap:12px}.x-hc-grip{cursor:grab;color:var(--color-text-muted);font-size:16px}.x-hc-order{display:flex;flex-direction:column}.x-hc-order button{border:0;background:transparent;cursor:pointer;color:var(--color-text-muted);font-size:10px;line-height:1.1;padding:1px 3px}.x-hc-order button:disabled{opacity:.3;cursor:default}.x-hc-item-body{flex:1;min-width:0}.x-hc-item-row{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}.x-hc-type{font-weight:800;text-transform:uppercase;letter-spacing:.4px;font-size:11px;color:var(--color-accent-hover)}.x-hc-item-title{font-size:13.5px;font-weight:600;color:var(--color-text)}.x-hc-sched{font-size:11px;color:var(--color-text-muted)}.x-hc-item-preview{font-size:12.5px;color:var(--color-text-muted);margin-top:2px}.x-hc-item-actions{display:flex;gap:8px;align-items:center}.x-hc-toggle{border:1px solid var(--color-border);border-radius:var(--radius-sm);background:transparent;padding:3px 10px;font-size:11.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-hc-link{border:0;background:transparent;cursor:pointer;font-size:12px;font-weight:700;color:var(--color-accent-hover)}.x-hc-danger{color:var(--color-danger, #c0362c)}.x-hc-edit{margin-top:12px;padding-top:12px;border-top:1px solid var(--color-border);display:flex;flex-direction:column;gap:10px}.x-hc-f{display:flex;flex-direction:column;gap:4px;flex:1;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-hc-f-row{display:flex;gap:10px}.x-hc-edit-actions{display:flex;gap:8px}.x-hc-preview{display:flex;flex-direction:column;gap:6px;max-width:860px}.x-hc-preview-block{border:1px dashed var(--color-border-strong);border-radius:var(--radius-md);padding:18px 16px;display:flex;flex-direction:column;gap:4px;background:var(--color-surface)}.x-hc-preview-type{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:var(--color-accent-hover)}.x-hc-preview-title{font-size:14px;color:var(--color-text)}\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.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: i1$1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }] }); }
377
+ }
378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HomepageComponent, decorators: [{
379
+ type: Component,
380
+ args: [{ standalone: false, selector: 'x-homepage', template: "<div class=\"x-hc-head\">\n <div>\n <div class=\"x-hc-title\">Homepage composer</div>\n <div class=\"x-hc-sub\">Drag to reorder the modules that build a storefront page.</div>\n </div>\n <div class=\"x-hc-head-controls\">\n <label class=\"x-hc-page-field\">\n Page\n <input\n type=\"text\"\n [ngModel]=\"page\"\n (ngModelChange)=\"changePage($event)\"\n placeholder=\"home\"\n />\n </label>\n <button\n type=\"button\"\n class=\"x-hc-btn\"\n [class.x-hc-btn-active]=\"previewMode\"\n (click)=\"previewMode = !previewMode\"\n >\n {{ previewMode ? 'Exit preview' : 'Preview' }}\n </button>\n </div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n @if (!previewMode) {\n <div class=\"x-hc-add\">\n <select [(ngModel)]=\"newType\">\n @for (t of types; track t.value) {\n <option [value]=\"t.value\">{{ t.label }}</option>\n }\n </select>\n <button type=\"button\" class=\"x-hc-btn x-hc-btn-primary\" (click)=\"addModule()\">\n Add module\n </button>\n </div>\n }\n\n @if (!orderedModules.length) {\n <div class=\"x-hc-empty\">No modules on this page yet.</div>\n }\n\n @if (previewMode) {\n <div class=\"x-hc-preview\">\n @for (m of previewModules; track m.id) {\n <div class=\"x-hc-preview-block\">\n <span class=\"x-hc-preview-type\">{{ typeLabel(m.type) }}</span>\n <span class=\"x-hc-preview-title\">{{ m.title || previewLine(m) }}</span>\n </div>\n } @empty {\n <div class=\"x-hc-empty\">Nothing live on this page right now.</div>\n }\n </div>\n } @else {\n <div class=\"x-hc-list\">\n @for (m of orderedModules; track m.id; let i = $index) {\n <div\n class=\"x-hc-item\"\n [class.x-hc-item-off]=\"!m.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-hc-item-main\">\n <span class=\"x-hc-grip\" title=\"Drag to reorder\">\u283F</span>\n <div class=\"x-hc-order\">\n <button type=\"button\" (click)=\"moveUp(i)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveDown(i)\" [disabled]=\"i === orderedModules.length - 1\">\u25BC</button>\n </div>\n <div class=\"x-hc-item-body\">\n <div class=\"x-hc-item-row\">\n <span class=\"x-hc-type\">{{ typeLabel(m.type) }}</span>\n @if (m.title) {\n <span class=\"x-hc-item-title\">{{ m.title }}</span>\n }\n @if (scheduleLine(m)) {\n <span class=\"x-hc-sched\">{{ scheduleLine(m) }}</span>\n }\n </div>\n <div class=\"x-hc-item-preview\">{{ previewLine(m) }}</div>\n </div>\n <div class=\"x-hc-item-actions\">\n <button type=\"button\" class=\"x-hc-toggle\" (click)=\"toggleEnabled(m)\">\n {{ m.enabled ? 'On' : 'Off' }}\n </button>\n <button type=\"button\" class=\"x-hc-link\" (click)=\"editModule(m)\">Edit</button>\n <button type=\"button\" class=\"x-hc-link x-hc-danger\" (click)=\"removeModule(m)\">Delete</button>\n </div>\n </div>\n\n @if (editing?.id === m.id) {\n <div class=\"x-hc-edit\">\n <label class=\"x-hc-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"editModel.title\" />\n </label>\n @for (field of typeDef(m.type)?.fields ?? []; track field.key) {\n <label class=\"x-hc-f\">\n {{ field.label }}\n @if (field.kind === 'text') {\n <textarea rows=\"3\" [(ngModel)]=\"editModel.config[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"editModel.config[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-hc-f-row\">\n <label class=\"x-hc-f\">\n Starts\n <input type=\"datetime-local\" [(ngModel)]=\"editModel.startsAt\" />\n </label>\n <label class=\"x-hc-f\">\n Ends\n <input type=\"datetime-local\" [(ngModel)]=\"editModel.endsAt\" />\n </label>\n </div>\n <div class=\"x-hc-f-row\">\n <label class=\"x-hc-f\">\n Channel IDs\n <input type=\"text\" [(ngModel)]=\"editModel.channelIds\" placeholder=\"blank = all\" />\n </label>\n <label class=\"x-hc-f\">\n Segment IDs\n <input type=\"text\" [(ngModel)]=\"editModel.segmentIds\" placeholder=\"blank = all\" />\n </label>\n </div>\n <div class=\"x-hc-edit-actions\">\n <button type=\"button\" class=\"x-hc-btn x-hc-btn-primary\" (click)=\"saveEdit()\">Save</button>\n <button type=\"button\" class=\"x-hc-btn\" (click)=\"cancelEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n}\n\n<style>\n .x-hc-head {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n gap: 16px;\n margin-bottom: 20px;\n flex-wrap: wrap;\n }\n .x-hc-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-hc-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-hc-head-controls { display: flex; gap: 10px; align-items: flex-end; }\n .x-hc-page-field {\n display: flex; flex-direction: column; gap: 4px;\n font-size: 11.5px; color: var(--color-text-muted); font-weight: 700;\n text-transform: uppercase; letter-spacing: 0.4px;\n }\n .x-hc-page-field input, .x-hc-add select, .x-hc-f input, .x-hc-f textarea {\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: 6px 10px;\n font-size: 13px;\n background: var(--color-surface);\n color: var(--color-text);\n }\n .x-hc-add { display: flex; gap: 10px; margin-bottom: 16px; }\n .x-hc-btn {\n border: 1px solid var(--color-border);\n background: transparent;\n border-radius: var(--radius-md);\n padding: 6px 14px;\n font-size: 12.5px;\n font-weight: 700;\n cursor: pointer;\n color: var(--color-text-muted);\n }\n .x-hc-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-hc-btn-active { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-hc-empty { font-size: 14px; color: var(--color-text-muted); }\n .x-hc-list { display: flex; flex-direction: column; gap: 10px; max-width: 860px; }\n .x-hc-item {\n background: var(--color-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n padding: 12px 14px;\n }\n .x-hc-item-off { opacity: 0.55; }\n .x-hc-item-main { display: flex; align-items: center; gap: 12px; }\n .x-hc-grip { cursor: grab; color: var(--color-text-muted); font-size: 16px; }\n .x-hc-order { display: flex; flex-direction: column; }\n .x-hc-order button {\n border: 0; background: transparent; cursor: pointer;\n color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px;\n }\n .x-hc-order button:disabled { opacity: 0.3; cursor: default; }\n .x-hc-item-body { flex: 1; min-width: 0; }\n .x-hc-item-row { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }\n .x-hc-type {\n font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px;\n font-size: 11px; color: var(--color-accent-hover);\n }\n .x-hc-item-title { font-size: 13.5px; font-weight: 600; color: var(--color-text); }\n .x-hc-sched { font-size: 11px; color: var(--color-text-muted); }\n .x-hc-item-preview { font-size: 12.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-hc-item-actions { display: flex; gap: 8px; align-items: center; }\n .x-hc-toggle {\n border: 1px solid var(--color-border); border-radius: var(--radius-sm);\n background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700;\n cursor: pointer; color: var(--color-text-muted);\n }\n .x-hc-link {\n border: 0; background: transparent; cursor: pointer;\n font-size: 12px; font-weight: 700; color: var(--color-accent-hover);\n }\n .x-hc-danger { color: var(--color-danger, #c0362c); }\n .x-hc-edit {\n margin-top: 12px; padding-top: 12px;\n border-top: 1px solid var(--color-border);\n display: flex; flex-direction: column; gap: 10px;\n }\n .x-hc-f {\n display: flex; flex-direction: column; gap: 4px; flex: 1;\n font-size: 11.5px; color: var(--color-text-muted); font-weight: 700;\n }\n .x-hc-f-row { display: flex; gap: 10px; }\n .x-hc-edit-actions { display: flex; gap: 8px; }\n .x-hc-preview { display: flex; flex-direction: column; gap: 6px; max-width: 860px; }\n .x-hc-preview-block {\n border: 1px dashed var(--color-border-strong);\n border-radius: var(--radius-md);\n padding: 18px 16px;\n display: flex; flex-direction: column; gap: 4px;\n background: var(--color-surface);\n }\n .x-hc-preview-type {\n font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.5px;\n color: var(--color-accent-hover);\n }\n .x-hc-preview-title { font-size: 14px; color: var(--color-text); }\n</style>\n" }]
381
+ }] });
382
+
383
+ class HomepageModule {
384
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HomepageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
385
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: HomepageModule, declarations: [HomepageComponent], imports: [SharedModule,
386
+ ElementsModule, i1$2.RouterModule] }); }
387
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HomepageModule, imports: [SharedModule,
388
+ ElementsModule,
389
+ RouterModule.forChild([{ path: '', component: HomepageComponent }])] }); }
390
+ }
391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HomepageModule, decorators: [{
392
+ type: NgModule,
393
+ args: [{
394
+ declarations: [HomepageComponent],
395
+ imports: [
396
+ SharedModule,
397
+ ElementsModule,
398
+ RouterModule.forChild([{ path: '', component: HomepageComponent }]),
399
+ ],
400
+ }]
401
+ }] });
402
+
403
+ export { HomepageModule };
404
+ //# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map