@isaiahiroko/tilmart-ngx-apps-admin 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs +154 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +294 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +224 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs +261 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs +365 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs +286 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs +73 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs +404 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs +290 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs +112 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs +116 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs +281 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs +115 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs +185 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs +70 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +274 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs +84 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs +104 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +106 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs +81 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +105 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +222 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs +150 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +107 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs +92 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs +111 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +1 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +475 -0
- package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -0
- package/package.json +34 -0
- package/types/isaiahiroko-tilmart-ngx-apps-admin.d.ts +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @isaiahiroko/tilmart-ngx-apps-admin
|
|
2
|
+
|
|
3
|
+
Ops console UI: the internal-facing surface for the founder/coordinator/picker
|
|
4
|
+
during the founder-led delivery pilot (see `docs/TRD.md` §7). Currently a bare
|
|
5
|
+
boilerplate — a single placeholder page. Intended to grow into: catalog/basket
|
|
6
|
+
management, the cycle board, route/day assignment, payment reconciliation, the
|
|
7
|
+
WhatsApp conversation inbox, and complaint/swap logging.
|
|
8
|
+
|
|
9
|
+
Mounted at `/admin` by `cloud/web`.
|
|
@@ -0,0 +1,154 @@
|
|
|
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 { R as RichEditorComponent, a as RichEditorModule } from './isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs';
|
|
9
|
+
import { takeUntil, firstValueFrom } from 'rxjs';
|
|
10
|
+
import * as i1 from '@angular/forms';
|
|
11
|
+
|
|
12
|
+
const STATUSES = ['draft', 'published', 'archived'];
|
|
13
|
+
const CATEGORIES = [
|
|
14
|
+
// Learn hub
|
|
15
|
+
'guides',
|
|
16
|
+
'how-to',
|
|
17
|
+
'glossary',
|
|
18
|
+
'news',
|
|
19
|
+
'case-study',
|
|
20
|
+
// Help centre (SUP-01)
|
|
21
|
+
'getting-started',
|
|
22
|
+
'orders-delivery',
|
|
23
|
+
'payments-refunds',
|
|
24
|
+
'returns-warranty',
|
|
25
|
+
'account',
|
|
26
|
+
'faq',
|
|
27
|
+
];
|
|
28
|
+
// P6.11, CMS-02 — the Learn-hub article editor. List + a metadata form
|
|
29
|
+
// with a WYSIWYG body. The storefront `/learn` hub renders the published
|
|
30
|
+
// set. ops CRUD is covered by `admin:*:apps:commerce:*`.
|
|
31
|
+
class ArticlesComponent extends BaseDirective {
|
|
32
|
+
constructor() {
|
|
33
|
+
super(...arguments);
|
|
34
|
+
this.source = this.apiService.on('apps/commerce/article');
|
|
35
|
+
this.statuses = STATUSES;
|
|
36
|
+
this.categories = CATEGORIES;
|
|
37
|
+
this.loading = true;
|
|
38
|
+
this.articles = [];
|
|
39
|
+
this.tagsInput = '';
|
|
40
|
+
this.newSlug = '';
|
|
41
|
+
this.newTitle = '';
|
|
42
|
+
this.init = () => {
|
|
43
|
+
this.load();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
load() {
|
|
47
|
+
this.loading = true;
|
|
48
|
+
this.source
|
|
49
|
+
.getMany({ perPage: 200 })
|
|
50
|
+
.pipe(takeUntil(this.$))
|
|
51
|
+
.subscribe(using((res) => {
|
|
52
|
+
this.articles = (res?.data ?? []).sort((a, b) => String(a.title).localeCompare(String(b.title)));
|
|
53
|
+
this.loading = false;
|
|
54
|
+
if (this.selected) {
|
|
55
|
+
this.selected = this.articles.find((a) => a.id === this.selected.id);
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
async create() {
|
|
60
|
+
const slug = this.newSlug.trim().toLowerCase().replace(/\s+/g, '-');
|
|
61
|
+
const title = this.newTitle.trim();
|
|
62
|
+
if (!slug || !title)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
const res = await firstValueFrom(this.source.create({ slug, title, status: 'draft', category: 'guides' }));
|
|
66
|
+
this.newSlug = '';
|
|
67
|
+
this.newTitle = '';
|
|
68
|
+
notify('Article created.', undefined, 'success');
|
|
69
|
+
this.load();
|
|
70
|
+
if (res?.data?.id)
|
|
71
|
+
setTimeout(() => this.select({ ...res.data }), 200);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
notify('Could not create the article (slug may be taken).', undefined, 'error');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
select(article) {
|
|
78
|
+
this.selected = article;
|
|
79
|
+
this.tagsInput = (article.tags ?? []).join(', ');
|
|
80
|
+
}
|
|
81
|
+
onBodyChange(html) {
|
|
82
|
+
if (this.selected)
|
|
83
|
+
this.selected.body = html;
|
|
84
|
+
}
|
|
85
|
+
async save() {
|
|
86
|
+
if (!this.selected)
|
|
87
|
+
return;
|
|
88
|
+
const a = this.selected;
|
|
89
|
+
try {
|
|
90
|
+
await firstValueFrom(this.source.update(a.id, {
|
|
91
|
+
slug: a.slug,
|
|
92
|
+
title: a.title,
|
|
93
|
+
summary: a.summary || undefined,
|
|
94
|
+
body: a.body || undefined,
|
|
95
|
+
category: a.category || undefined,
|
|
96
|
+
tags: this.tagsInput.split(',').map((t) => t.trim()).filter(Boolean),
|
|
97
|
+
authorName: a.authorName || undefined,
|
|
98
|
+
readMinutes: a.readMinutes ? Number(a.readMinutes) : undefined,
|
|
99
|
+
status: a.status,
|
|
100
|
+
publishAt: a.publishAt ? new Date(a.publishAt).toISOString() : null,
|
|
101
|
+
}));
|
|
102
|
+
notify('Article saved.', undefined, 'success');
|
|
103
|
+
this.load();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
notify('Could not save the article.', undefined, 'error');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async remove() {
|
|
110
|
+
if (!this.selected)
|
|
111
|
+
return;
|
|
112
|
+
try {
|
|
113
|
+
await firstValueFrom(this.source.remove(this.selected.id));
|
|
114
|
+
this.selected = undefined;
|
|
115
|
+
notify('Article deleted.', undefined, 'success');
|
|
116
|
+
this.load();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
notify('Could not delete the article.', undefined, 'error');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ArticlesComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
123
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: ArticlesComponent, isStandalone: false, selector: "x-articles", usesInheritance: true, ngImport: i0, template: "<div class=\"x-art-head\">\n <div class=\"x-art-title\">Learn articles</div>\n <div class=\"x-art-sub\">Guides, how-tos and glossary entries for the storefront Learn hub.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-art-layout\">\n <aside class=\"x-art-list\">\n <div class=\"x-art-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newTitle\" />\n <button type=\"button\" (click)=\"create()\">Add article</button>\n </div>\n @for (a of articles; track a.id) {\n <button\n type=\"button\"\n class=\"x-art-list-item\"\n [class.x-art-list-active]=\"selected?.id === a.id\"\n (click)=\"select(a)\"\n >\n <span class=\"x-art-list-title\">{{ a.title }}</span>\n <span class=\"x-art-list-meta\">{{ a.category || '\u2014' }} \u00B7 {{ a.status }}</span>\n </button>\n } @empty {\n <div class=\"x-art-empty\">No articles yet.</div>\n }\n </aside>\n\n <section class=\"x-art-editor\">\n @if (!selected) {\n <div class=\"x-art-empty\">Pick an article, or add one.</div>\n } @else {\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-art-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Summary\n <input type=\"text\" [(ngModel)]=\"selected.summary\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Category\n <select [(ngModel)]=\"selected.category\">\n @for (c of categories; track c) { <option [value]=\"c\">{{ c }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Author\n <input type=\"text\" [(ngModel)]=\"selected.authorName\" />\n </label>\n <label class=\"x-art-f\">\n Read minutes\n <input type=\"number\" [(ngModel)]=\"selected.readMinutes\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Tags (comma-separated)\n <input type=\"text\" [(ngModel)]=\"tagsInput\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Body\n <x-rich-editor [content]=\"selected.body || ''\" (contentChange)=\"onBodyChange($event)\"></x-rich-editor>\n </label>\n <div class=\"x-art-actions\">\n <button type=\"button\" class=\"x-art-btn x-art-btn-primary\" (click)=\"save()\">Save</button>\n <button type=\"button\" class=\"x-art-btn x-art-danger\" (click)=\"remove()\">Delete</button>\n </div>\n }\n </section>\n </div>\n}\n\n<style>\n .x-art-head { margin-bottom: 20px; }\n .x-art-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-art-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-art-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-art-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-art-list { display: flex; flex-direction: column; gap: 6px; }\n .x-art-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-art-new input, .x-art-f input, .x-art-f select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-art-new button, .x-art-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-art-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-art-danger { color: var(--color-danger, #c0362c); }\n .x-art-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-art-list-active { border-color: var(--color-accent); }\n .x-art-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-art-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-art-editor {\n min-width: 0; background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .x-art-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-art-f-row { display: flex; gap: 10px; }\n .x-art-actions { display: flex; gap: 8px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 200px; }\n</style>\n", styles: [".x-art-head{margin-bottom:20px}.x-art-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-art-sub{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-art-empty{font-size:14px;color:var(--color-text-muted);padding:12px 0}.x-art-layout{display:grid;grid-template-columns:260px 1fr;gap:20px;align-items:start}.x-art-list{display:flex;flex-direction:column;gap:6px}.x-art-new{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.x-art-new input,.x-art-f input,.x-art-f select{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-art-new button,.x-art-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-art-btn-primary{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-art-danger{color:var(--color-danger, #c0362c)}.x-art-list-item{text-align:left;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);padding:8px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.x-art-list-active{border-color:var(--color-accent)}.x-art-list-title{font-size:13px;font-weight:700;color:var(--color-text)}.x-art-list-meta{font-size:11px;color:var(--color-text-muted)}.x-art-editor{min-width:0;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;display:flex;flex-direction:column;gap:10px}.x-art-f{display:flex;flex-direction:column;gap:4px;flex:1;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-art-f-row{display:flex;gap:10px}.x-art-actions{display:flex;gap:8px}x-rich-editor{display:block;border:1px solid var(--color-border);border-radius:var(--radius-md);min-height:200px}\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.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"] }, { kind: "component", type: RichEditorComponent, selector: "x-rich-editor", inputs: ["content"], outputs: ["contentChange"] }] }); }
|
|
124
|
+
}
|
|
125
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ArticlesComponent, decorators: [{
|
|
126
|
+
type: Component,
|
|
127
|
+
args: [{ standalone: false, selector: 'x-articles', template: "<div class=\"x-art-head\">\n <div class=\"x-art-title\">Learn articles</div>\n <div class=\"x-art-sub\">Guides, how-tos and glossary entries for the storefront Learn hub.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-art-layout\">\n <aside class=\"x-art-list\">\n <div class=\"x-art-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newTitle\" />\n <button type=\"button\" (click)=\"create()\">Add article</button>\n </div>\n @for (a of articles; track a.id) {\n <button\n type=\"button\"\n class=\"x-art-list-item\"\n [class.x-art-list-active]=\"selected?.id === a.id\"\n (click)=\"select(a)\"\n >\n <span class=\"x-art-list-title\">{{ a.title }}</span>\n <span class=\"x-art-list-meta\">{{ a.category || '\u2014' }} \u00B7 {{ a.status }}</span>\n </button>\n } @empty {\n <div class=\"x-art-empty\">No articles yet.</div>\n }\n </aside>\n\n <section class=\"x-art-editor\">\n @if (!selected) {\n <div class=\"x-art-empty\">Pick an article, or add one.</div>\n } @else {\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-art-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Summary\n <input type=\"text\" [(ngModel)]=\"selected.summary\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Category\n <select [(ngModel)]=\"selected.category\">\n @for (c of categories; track c) { <option [value]=\"c\">{{ c }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Author\n <input type=\"text\" [(ngModel)]=\"selected.authorName\" />\n </label>\n <label class=\"x-art-f\">\n Read minutes\n <input type=\"number\" [(ngModel)]=\"selected.readMinutes\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Tags (comma-separated)\n <input type=\"text\" [(ngModel)]=\"tagsInput\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Body\n <x-rich-editor [content]=\"selected.body || ''\" (contentChange)=\"onBodyChange($event)\"></x-rich-editor>\n </label>\n <div class=\"x-art-actions\">\n <button type=\"button\" class=\"x-art-btn x-art-btn-primary\" (click)=\"save()\">Save</button>\n <button type=\"button\" class=\"x-art-btn x-art-danger\" (click)=\"remove()\">Delete</button>\n </div>\n }\n </section>\n </div>\n}\n\n<style>\n .x-art-head { margin-bottom: 20px; }\n .x-art-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-art-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-art-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-art-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-art-list { display: flex; flex-direction: column; gap: 6px; }\n .x-art-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-art-new input, .x-art-f input, .x-art-f select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-art-new button, .x-art-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-art-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-art-danger { color: var(--color-danger, #c0362c); }\n .x-art-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-art-list-active { border-color: var(--color-accent); }\n .x-art-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-art-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-art-editor {\n min-width: 0; background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .x-art-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-art-f-row { display: flex; gap: 10px; }\n .x-art-actions { display: flex; gap: 8px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 200px; }\n</style>\n" }]
|
|
128
|
+
}] });
|
|
129
|
+
|
|
130
|
+
class ArticlesModule {
|
|
131
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ArticlesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
132
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: ArticlesModule, declarations: [ArticlesComponent], imports: [SharedModule,
|
|
133
|
+
ElementsModule,
|
|
134
|
+
RichEditorModule, i1$2.RouterModule] }); }
|
|
135
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ArticlesModule, imports: [SharedModule,
|
|
136
|
+
ElementsModule,
|
|
137
|
+
RichEditorModule,
|
|
138
|
+
RouterModule.forChild([{ path: '', component: ArticlesComponent }])] }); }
|
|
139
|
+
}
|
|
140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ArticlesModule, decorators: [{
|
|
141
|
+
type: NgModule,
|
|
142
|
+
args: [{
|
|
143
|
+
declarations: [ArticlesComponent],
|
|
144
|
+
imports: [
|
|
145
|
+
SharedModule,
|
|
146
|
+
ElementsModule,
|
|
147
|
+
RichEditorModule,
|
|
148
|
+
RouterModule.forChild([{ path: '', component: ArticlesComponent }]),
|
|
149
|
+
],
|
|
150
|
+
}]
|
|
151
|
+
}] });
|
|
152
|
+
|
|
153
|
+
export { ArticlesModule };
|
|
154
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs","sources":["../../../projects/admin/src/lib/pages/articles/articles.component.ts","../../../projects/admin/src/lib/pages/articles/articles.component.html","../../../projects/admin/src/lib/pages/articles/articles.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, notify, using } from '@thescaffold/ngx-core';\nimport { firstValueFrom, takeUntil } from 'rxjs';\n\ninterface ArticleVm {\n id: string;\n slug: string;\n title: string;\n summary?: string;\n body?: string;\n category?: string;\n tags?: string[];\n authorName?: string;\n readMinutes?: number;\n status: string;\n publishAt?: string;\n}\n\nconst STATUSES = ['draft', 'published', 'archived'];\nconst CATEGORIES = [\n // Learn hub\n 'guides',\n 'how-to',\n 'glossary',\n 'news',\n 'case-study',\n // Help centre (SUP-01)\n 'getting-started',\n 'orders-delivery',\n 'payments-refunds',\n 'returns-warranty',\n 'account',\n 'faq',\n];\n\n// P6.11, CMS-02 — the Learn-hub article editor. List + a metadata form\n// with a WYSIWYG body. The storefront `/learn` hub renders the published\n// set. ops CRUD is covered by `admin:*:apps:commerce:*`.\n@Component({\n standalone: false,\n selector: 'x-articles',\n templateUrl: './articles.component.html',\n})\nexport class ArticlesComponent extends BaseDirective {\n private readonly source = this.apiService.on('apps/commerce/article');\n\n readonly statuses = STATUSES;\n readonly categories = CATEGORIES;\n\n loading = true;\n articles: ArticleVm[] = [];\n selected?: ArticleVm;\n tagsInput = '';\n\n newSlug = '';\n newTitle = '';\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.articles = (res?.data ?? []).sort((a: any, b: any) =>\n String(a.title).localeCompare(String(b.title)),\n );\n this.loading = false;\n if (this.selected) {\n this.selected = this.articles.find((a) => a.id === this.selected!.id);\n }\n }),\n );\n }\n\n async create(): Promise<void> {\n const slug = this.newSlug.trim().toLowerCase().replace(/\\s+/g, '-');\n const title = this.newTitle.trim();\n if (!slug || !title) return;\n try {\n const res: any = await firstValueFrom(\n this.source.create({ slug, title, status: 'draft', category: 'guides' } as any),\n );\n this.newSlug = '';\n this.newTitle = '';\n notify('Article created.', undefined, 'success');\n this.load();\n if (res?.data?.id) setTimeout(() => this.select({ ...res.data }), 200);\n } catch {\n notify('Could not create the article (slug may be taken).', undefined, 'error');\n }\n }\n\n select(article: ArticleVm): void {\n this.selected = article;\n this.tagsInput = (article.tags ?? []).join(', ');\n }\n\n onBodyChange(html: string): void {\n if (this.selected) this.selected.body = html;\n }\n\n async save(): Promise<void> {\n if (!this.selected) return;\n const a = this.selected;\n try {\n await firstValueFrom(\n this.source.update(a.id, {\n slug: a.slug,\n title: a.title,\n summary: a.summary || undefined,\n body: a.body || undefined,\n category: a.category || undefined,\n tags: this.tagsInput.split(',').map((t) => t.trim()).filter(Boolean),\n authorName: a.authorName || undefined,\n readMinutes: a.readMinutes ? Number(a.readMinutes) : undefined,\n status: a.status,\n publishAt: a.publishAt ? new Date(a.publishAt).toISOString() : null,\n } as any),\n );\n notify('Article saved.', undefined, 'success');\n this.load();\n } catch {\n notify('Could not save the article.', undefined, 'error');\n }\n }\n\n async remove(): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(this.source.remove(this.selected.id));\n this.selected = undefined;\n notify('Article deleted.', undefined, 'success');\n this.load();\n } catch {\n notify('Could not delete the article.', undefined, 'error');\n }\n }\n}\n","<div class=\"x-art-head\">\n <div class=\"x-art-title\">Learn articles</div>\n <div class=\"x-art-sub\">Guides, how-tos and glossary entries for the storefront Learn hub.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-art-layout\">\n <aside class=\"x-art-list\">\n <div class=\"x-art-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newTitle\" />\n <button type=\"button\" (click)=\"create()\">Add article</button>\n </div>\n @for (a of articles; track a.id) {\n <button\n type=\"button\"\n class=\"x-art-list-item\"\n [class.x-art-list-active]=\"selected?.id === a.id\"\n (click)=\"select(a)\"\n >\n <span class=\"x-art-list-title\">{{ a.title }}</span>\n <span class=\"x-art-list-meta\">{{ a.category || '—' }} · {{ a.status }}</span>\n </button>\n } @empty {\n <div class=\"x-art-empty\">No articles yet.</div>\n }\n </aside>\n\n <section class=\"x-art-editor\">\n @if (!selected) {\n <div class=\"x-art-empty\">Pick an article, or add one.</div>\n } @else {\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-art-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Summary\n <input type=\"text\" [(ngModel)]=\"selected.summary\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Category\n <select [(ngModel)]=\"selected.category\">\n @for (c of categories; track c) { <option [value]=\"c\">{{ c }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Author\n <input type=\"text\" [(ngModel)]=\"selected.authorName\" />\n </label>\n <label class=\"x-art-f\">\n Read minutes\n <input type=\"number\" [(ngModel)]=\"selected.readMinutes\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Tags (comma-separated)\n <input type=\"text\" [(ngModel)]=\"tagsInput\" />\n </label>\n <div class=\"x-art-f-row\">\n <label class=\"x-art-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-art-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <label class=\"x-art-f\">\n Body\n <x-rich-editor [content]=\"selected.body || ''\" (contentChange)=\"onBodyChange($event)\"></x-rich-editor>\n </label>\n <div class=\"x-art-actions\">\n <button type=\"button\" class=\"x-art-btn x-art-btn-primary\" (click)=\"save()\">Save</button>\n <button type=\"button\" class=\"x-art-btn x-art-danger\" (click)=\"remove()\">Delete</button>\n </div>\n }\n </section>\n </div>\n}\n\n<style>\n .x-art-head { margin-bottom: 20px; }\n .x-art-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-art-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-art-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-art-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-art-list { display: flex; flex-direction: column; gap: 6px; }\n .x-art-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-art-new input, .x-art-f input, .x-art-f select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-art-new button, .x-art-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-art-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-art-danger { color: var(--color-danger, #c0362c); }\n .x-art-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-art-list-active { border-color: var(--color-accent); }\n .x-art-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-art-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-art-editor {\n min-width: 0; background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n }\n .x-art-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-art-f-row { display: flex; gap: 10px; }\n .x-art-actions { display: flex; gap: 8px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 200px; }\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 { RichEditorModule } from '../_shared/rich-editor/rich-editor.module';\nimport { ArticlesComponent } from './articles.component';\n\n@NgModule({\n declarations: [ArticlesComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RichEditorModule,\n RouterModule.forChild([{ path: '', component: ArticlesComponent }]),\n ],\n})\nexport class ArticlesModule {}\n"],"names":["i2","i3.RichEditorComponent","i1"],"mappings":";;;;;;;;;;;AAkBA,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC;AACnD,MAAM,UAAU,GAAG;;IAEjB,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,MAAM;IACN,YAAY;;IAEZ,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,SAAS;IACT,KAAK;CACN;AAED;AACA;AACA;AAMM,MAAO,iBAAkB,SAAQ,aAAa,CAAA;AALpD,IAAA,WAAA,GAAA;;QAMmB,IAAA,CAAA,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,uBAAuB,CAAC;QAE5D,IAAA,CAAA,QAAQ,GAAG,QAAQ;QACnB,IAAA,CAAA,UAAU,GAAG,UAAU;QAEhC,IAAA,CAAA,OAAO,GAAG,IAAI;QACd,IAAA,CAAA,QAAQ,GAAgB,EAAE;QAE1B,IAAA,CAAA,SAAS,GAAG,EAAE;QAEd,IAAA,CAAA,OAAO,GAAG,EAAE;QACZ,IAAA,CAAA,QAAQ,GAAG,EAAE;QAEJ,IAAA,CAAA,IAAI,GAAG,MAAK;YACnB,IAAI,CAAC,IAAI,EAAE;AACb,QAAA,CAAC;AAmFF,IAAA;IAjFC,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,CAAC,CAAC,CAAM,EAAE,CAAM,KACpD,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAC/C;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;AAEA,IAAA,MAAM,MAAM,GAAA;AACV,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AAClC,QAAA,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE;AACrB,QAAA,IAAI;YACF,MAAM,GAAG,GAAQ,MAAM,cAAc,CACnC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAS,CAAC,CAChF;AACD,YAAA,IAAI,CAAC,OAAO,GAAG,EAAE;AACjB,YAAA,IAAI,CAAC,QAAQ,GAAG,EAAE;AAClB,YAAA,MAAM,CAAC,kBAAkB,EAAE,SAAS,EAAE,SAAS,CAAC;YAChD,IAAI,CAAC,IAAI,EAAE;AACX,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE;AAAE,gBAAA,UAAU,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC;QACxE;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,mDAAmD,EAAE,SAAS,EAAE,OAAO,CAAC;QACjF;IACF;AAEA,IAAA,MAAM,CAAC,OAAkB,EAAA;AACvB,QAAA,IAAI,CAAC,QAAQ,GAAG,OAAO;AACvB,QAAA,IAAI,CAAC,SAAS,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC;IAClD;AAEA,IAAA,YAAY,CAAC,IAAY,EAAA;QACvB,IAAI,IAAI,CAAC,QAAQ;AAAE,YAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI;IAC9C;AAEA,IAAA,MAAM,IAAI,GAAA;QACR,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE;AACpB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ;AACvB,QAAA,IAAI;YACF,MAAM,cAAc,CAClB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;gBACvB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;AACd,gBAAA,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS;AAC/B,gBAAA,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,SAAS;AACzB,gBAAA,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,SAAS;gBACjC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;AACpE,gBAAA,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,SAAS;AACrC,gBAAA,WAAW,EAAE,CAAC,CAAC,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,SAAS;gBAC9D,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI;AAC7D,aAAA,CAAC,CACV;AACD,YAAA,MAAM,CAAC,gBAAgB,EAAE,SAAS,EAAE,SAAS,CAAC;YAC9C,IAAI,CAAC,IAAI,EAAE;QACb;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,6BAA6B,EAAE,SAAS,EAAE,OAAO,CAAC;QAC3D;IACF;AAEA,IAAA,MAAM,MAAM,GAAA;QACV,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,CAAC,CAAC;AAC1D,YAAA,IAAI,CAAC,QAAQ,GAAG,SAAS;AACzB,YAAA,MAAM,CAAC,kBAAkB,EAAE,SAAS,EAAE,SAAS,CAAC;YAChD,IAAI,CAAC,IAAI,EAAE;QACb;AAAE,QAAA,MAAM;AACN,YAAA,MAAM,CAAC,+BAA+B,EAAE,SAAS,EAAE,OAAO,CAAC;QAC7D;IACF;+GAlGW,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,8FC3C9B,imLAgIA,EAAA,MAAA,EAAA,CAAA,22DAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,cAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,uBAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,mBAAA,EAAA,QAAA,EAAA,iGAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,0BAAA,EAAA,QAAA,EAAA,6GAAA,EAAA,MAAA,EAAA,CAAA,aAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,IAAA,CAAA,eAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAC,mBAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,CAAA,EAAA,CAAA,CAAA;;4FDrFa,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAL7B,SAAS;AACI,YAAA,IAAA,EAAA,CAAA,EAAA,UAAA,EAAA,KAAK,YACP,YAAY,EAAA,QAAA,EAAA,imLAAA,EAAA;;;MExBX,cAAc,CAAA;+GAAd,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gHAAd,cAAc,EAAA,YAAA,EAAA,CARV,iBAAiB,CAAA,EAAA,OAAA,EAAA,CAE9B,YAAY;YACZ,cAAc;YACd,gBAAgB,EAAAC,IAAA,CAAA,YAAA,CAAA,EAAA,CAAA,CAAA;AAIP,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,YANvB,YAAY;YACZ,cAAc;YACd,gBAAgB;AAChB,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;kBAT1B,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,YAAY,EAAE,CAAC,iBAAiB,CAAC;AACjC,oBAAA,OAAO,EAAE;wBACP,YAAY;wBACZ,cAAc;wBACd,gBAAgB;AAChB,wBAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACpE,qBAAA;AACF,iBAAA;;;;;"}
|
|
@@ -0,0 +1,84 @@
|
|
|
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, SharedModule } from '@thescaffold/ngx-core';
|
|
6
|
+
import * as i1$1 from '@thescaffold/ngx-ui';
|
|
7
|
+
import { ElementsModule } from '@thescaffold/ngx-ui';
|
|
8
|
+
import { takeUntil } from 'rxjs';
|
|
9
|
+
import * as i1 from '@angular/forms';
|
|
10
|
+
import * as i2 from '@angular/common';
|
|
11
|
+
|
|
12
|
+
// P7.6, ADM-14 / SEC-07 — the audit-log viewer. `ntx-apps-audit` records
|
|
13
|
+
// every write that carries a request actor: all CrudControllerFactory
|
|
14
|
+
// writes automatically, plus the privileged workflow actions the
|
|
15
|
+
// commerce `AuditTrail` helper re-emits (order transitions, payment
|
|
16
|
+
// verification, fulfilment, returns, warranty triage, recall publish,
|
|
17
|
+
// review moderation).
|
|
18
|
+
class AuditComponent extends BaseDirective {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this.source = this.apiService.on('apps/audit');
|
|
22
|
+
this.loading = true;
|
|
23
|
+
this.logs = [];
|
|
24
|
+
this.page = 1;
|
|
25
|
+
this.perPage = 50;
|
|
26
|
+
this.total = 0;
|
|
27
|
+
this.filter = '';
|
|
28
|
+
this.init = () => {
|
|
29
|
+
this.load();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
load(page = this.page) {
|
|
33
|
+
this.loading = true;
|
|
34
|
+
this.page = page;
|
|
35
|
+
this.source
|
|
36
|
+
.getOne(`activities?page=${page}&perPage=${this.perPage}`)
|
|
37
|
+
.pipe(takeUntil(this.$))
|
|
38
|
+
.subscribe(using((res) => {
|
|
39
|
+
this.logs = res?.data ?? [];
|
|
40
|
+
this.total = res?.meta?.total ?? this.logs.length;
|
|
41
|
+
this.loading = false;
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
get shown() {
|
|
45
|
+
const q = this.filter.trim().toLowerCase();
|
|
46
|
+
if (!q)
|
|
47
|
+
return this.logs;
|
|
48
|
+
return this.logs.filter((l) => `${l.desc} ${l.entityName} ${l.action} ${l.userId}`
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.includes(q));
|
|
51
|
+
}
|
|
52
|
+
get pages() {
|
|
53
|
+
return Math.max(1, Math.ceil(this.total / this.perPage));
|
|
54
|
+
}
|
|
55
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: AuditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
56
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: AuditComponent, isStandalone: false, selector: "x-audit", usesInheritance: true, ngImport: i0, template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Audit log</div>\n <div class=\"x-page-subtitle\">\n Every privileged action, with its actor. Immutable (SEC-07).\n </div>\n</div>\n\n<input\n class=\"x-au-filter\"\n type=\"search\"\n placeholder=\"Filter this page\u2026\"\n [(ngModel)]=\"filter\"\n/>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-au-table-wrap\">\n <table class=\"x-au-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Action</th>\n <th>Entity</th>\n <th>Description</th>\n <th>Actor</th>\n </tr>\n </thead>\n <tbody>\n @for (l of shown; track l.id) {\n <tr>\n <td>{{ l.createdAt | date: 'short' }}</td>\n <td><span class=\"x-au-action\">{{ l.action || '\u2014' }}</span></td>\n <td>{{ l.entityName || l.service }}</td>\n <td>{{ l.desc || '\u2014' }}</td>\n <td class=\"x-au-actor\">{{ l.userId || '\u2014' }}</td>\n </tr>\n } @empty {\n <tr><td colspan=\"5\" class=\"x-au-empty\">No entries.</td></tr>\n }\n </tbody>\n </table>\n </div>\n\n <div class=\"x-au-pager\">\n <button type=\"button\" [disabled]=\"page <= 1\" (click)=\"load(page - 1)\">Prev</button>\n <span>Page {{ page }} of {{ pages }}</span>\n <button type=\"button\" [disabled]=\"page >= pages\" (click)=\"load(page + 1)\">Next</button>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 16px; }\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-au-filter { width: 100%; max-width: 320px; font: inherit; padding: 8px 10px; margin-bottom: 14px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); }\n .x-au-table-wrap { overflow-x: auto; }\n .x-au-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }\n .x-au-table th, .x-au-table td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--color-border); vertical-align: top; }\n .x-au-table th { color: var(--color-text-muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }\n .x-au-action { font-family: var(--font-mono, monospace); font-size: 11px; }\n .x-au-actor { font-family: var(--font-mono, monospace); font-size: 11px; color: var(--color-text-muted); }\n .x-au-empty { text-align: center; color: var(--color-text-muted); padding: 24px; }\n .x-au-pager { display: flex; gap: 12px; align-items: center; margin-top: 14px; font-size: 12.5px; color: var(--color-text-muted); }\n .x-au-pager button { font: inherit; font-weight: 600; padding: 5px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-au-pager button:disabled { opacity: 0.4; cursor: not-allowed; }\n</style>\n", styles: [".x-page-head{margin-bottom:16px}.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-au-filter{width:100%;max-width:320px;font:inherit;padding:8px 10px;margin-bottom:14px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-surface, #fff);color:var(--color-text)}.x-au-table-wrap{overflow-x:auto}.x-au-table{width:100%;border-collapse:collapse;font-size:12.5px}.x-au-table th,.x-au-table td{text-align:left;padding:7px 10px;border-bottom:1px solid var(--color-border);vertical-align:top}.x-au-table th{color:var(--color-text-muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.03em}.x-au-action{font-family:var(--font-mono, monospace);font-size:11px}.x-au-actor{font-family:var(--font-mono, monospace);font-size:11px;color:var(--color-text-muted)}.x-au-empty{text-align:center;color:var(--color-text-muted);padding:24px}.x-au-pager{display:flex;gap:12px;align-items:center;margin-top:14px;font-size:12.5px;color:var(--color-text-muted)}.x-au-pager button{font:inherit;font-weight:600;padding:5px 12px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-surface, #fff);color:var(--color-text);cursor:pointer}.x-au-pager button:disabled{opacity:.4;cursor:not-allowed}\n"], dependencies: [{ 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.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"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }] }); }
|
|
57
|
+
}
|
|
58
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: AuditComponent, decorators: [{
|
|
59
|
+
type: Component,
|
|
60
|
+
args: [{ standalone: false, selector: 'x-audit', template: "<div class=\"x-page-head\">\n <div class=\"x-page-title\">Audit log</div>\n <div class=\"x-page-subtitle\">\n Every privileged action, with its actor. Immutable (SEC-07).\n </div>\n</div>\n\n<input\n class=\"x-au-filter\"\n type=\"search\"\n placeholder=\"Filter this page\u2026\"\n [(ngModel)]=\"filter\"\n/>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-au-table-wrap\">\n <table class=\"x-au-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Action</th>\n <th>Entity</th>\n <th>Description</th>\n <th>Actor</th>\n </tr>\n </thead>\n <tbody>\n @for (l of shown; track l.id) {\n <tr>\n <td>{{ l.createdAt | date: 'short' }}</td>\n <td><span class=\"x-au-action\">{{ l.action || '\u2014' }}</span></td>\n <td>{{ l.entityName || l.service }}</td>\n <td>{{ l.desc || '\u2014' }}</td>\n <td class=\"x-au-actor\">{{ l.userId || '\u2014' }}</td>\n </tr>\n } @empty {\n <tr><td colspan=\"5\" class=\"x-au-empty\">No entries.</td></tr>\n }\n </tbody>\n </table>\n </div>\n\n <div class=\"x-au-pager\">\n <button type=\"button\" [disabled]=\"page <= 1\" (click)=\"load(page - 1)\">Prev</button>\n <span>Page {{ page }} of {{ pages }}</span>\n <button type=\"button\" [disabled]=\"page >= pages\" (click)=\"load(page + 1)\">Next</button>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 16px; }\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-au-filter { width: 100%; max-width: 320px; font: inherit; padding: 8px 10px; margin-bottom: 14px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); }\n .x-au-table-wrap { overflow-x: auto; }\n .x-au-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }\n .x-au-table th, .x-au-table td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--color-border); vertical-align: top; }\n .x-au-table th { color: var(--color-text-muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }\n .x-au-action { font-family: var(--font-mono, monospace); font-size: 11px; }\n .x-au-actor { font-family: var(--font-mono, monospace); font-size: 11px; color: var(--color-text-muted); }\n .x-au-empty { text-align: center; color: var(--color-text-muted); padding: 24px; }\n .x-au-pager { display: flex; gap: 12px; align-items: center; margin-top: 14px; font-size: 12.5px; color: var(--color-text-muted); }\n .x-au-pager button { font: inherit; font-weight: 600; padding: 5px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-au-pager button:disabled { opacity: 0.4; cursor: not-allowed; }\n</style>\n" }]
|
|
61
|
+
}] });
|
|
62
|
+
|
|
63
|
+
class AuditModule {
|
|
64
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: AuditModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
65
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: AuditModule, declarations: [AuditComponent], imports: [SharedModule,
|
|
66
|
+
ElementsModule, i1$2.RouterModule] }); }
|
|
67
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: AuditModule, imports: [SharedModule,
|
|
68
|
+
ElementsModule,
|
|
69
|
+
RouterModule.forChild([{ path: '', component: AuditComponent }])] }); }
|
|
70
|
+
}
|
|
71
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: AuditModule, decorators: [{
|
|
72
|
+
type: NgModule,
|
|
73
|
+
args: [{
|
|
74
|
+
declarations: [AuditComponent],
|
|
75
|
+
imports: [
|
|
76
|
+
SharedModule,
|
|
77
|
+
ElementsModule,
|
|
78
|
+
RouterModule.forChild([{ path: '', component: AuditComponent }]),
|
|
79
|
+
],
|
|
80
|
+
}]
|
|
81
|
+
}] });
|
|
82
|
+
|
|
83
|
+
export { AuditModule };
|
|
84
|
+
//# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs","sources":["../../../projects/admin/src/lib/pages/audit/audit.component.ts","../../../projects/admin/src/lib/pages/audit/audit.component.html","../../../projects/admin/src/lib/pages/audit/audit.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, using } from '@thescaffold/ngx-core';\nimport { takeUntil } from 'rxjs';\n\ninterface LogVm {\n id: string;\n desc?: string;\n action?: string;\n group?: string;\n service?: string;\n entityName?: string;\n entityId?: string;\n userId?: string;\n createdAt: string;\n}\n\n// P7.6, ADM-14 / SEC-07 — the audit-log viewer. `ntx-apps-audit` records\n// every write that carries a request actor: all CrudControllerFactory\n// writes automatically, plus the privileged workflow actions the\n// commerce `AuditTrail` helper re-emits (order transitions, payment\n// verification, fulfilment, returns, warranty triage, recall publish,\n// review moderation).\n@Component({\n standalone: false,\n selector: 'x-audit',\n templateUrl: './audit.component.html',\n})\nexport class AuditComponent extends BaseDirective {\n private readonly source = this.apiService.on('apps/audit');\n\n loading = true;\n logs: LogVm[] = [];\n page = 1;\n perPage = 50;\n total = 0;\n filter = '';\n\n override init = () => {\n this.load();\n };\n\n load(page = this.page): void {\n this.loading = true;\n this.page = page;\n this.source\n .getOne(`activities?page=${page}&perPage=${this.perPage}`)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.logs = res?.data ?? [];\n this.total = res?.meta?.total ?? this.logs.length;\n this.loading = false;\n }),\n );\n }\n\n get shown(): LogVm[] {\n const q = this.filter.trim().toLowerCase();\n if (!q) return this.logs;\n return this.logs.filter((l) =>\n `${l.desc} ${l.entityName} ${l.action} ${l.userId}`\n .toLowerCase()\n .includes(q),\n );\n }\n\n get pages(): number {\n return Math.max(1, Math.ceil(this.total / this.perPage));\n }\n}\n","<div class=\"x-page-head\">\n <div class=\"x-page-title\">Audit log</div>\n <div class=\"x-page-subtitle\">\n Every privileged action, with its actor. Immutable (SEC-07).\n </div>\n</div>\n\n<input\n class=\"x-au-filter\"\n type=\"search\"\n placeholder=\"Filter this page…\"\n [(ngModel)]=\"filter\"\n/>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-au-table-wrap\">\n <table class=\"x-au-table\">\n <thead>\n <tr>\n <th>When</th>\n <th>Action</th>\n <th>Entity</th>\n <th>Description</th>\n <th>Actor</th>\n </tr>\n </thead>\n <tbody>\n @for (l of shown; track l.id) {\n <tr>\n <td>{{ l.createdAt | date: 'short' }}</td>\n <td><span class=\"x-au-action\">{{ l.action || '—' }}</span></td>\n <td>{{ l.entityName || l.service }}</td>\n <td>{{ l.desc || '—' }}</td>\n <td class=\"x-au-actor\">{{ l.userId || '—' }}</td>\n </tr>\n } @empty {\n <tr><td colspan=\"5\" class=\"x-au-empty\">No entries.</td></tr>\n }\n </tbody>\n </table>\n </div>\n\n <div class=\"x-au-pager\">\n <button type=\"button\" [disabled]=\"page <= 1\" (click)=\"load(page - 1)\">Prev</button>\n <span>Page {{ page }} of {{ pages }}</span>\n <button type=\"button\" [disabled]=\"page >= pages\" (click)=\"load(page + 1)\">Next</button>\n </div>\n}\n\n<style>\n .x-page-head { margin-bottom: 16px; }\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-au-filter { width: 100%; max-width: 320px; font: inherit; padding: 8px 10px; margin-bottom: 14px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); }\n .x-au-table-wrap { overflow-x: auto; }\n .x-au-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }\n .x-au-table th, .x-au-table td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--color-border); vertical-align: top; }\n .x-au-table th { color: var(--color-text-muted); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }\n .x-au-action { font-family: var(--font-mono, monospace); font-size: 11px; }\n .x-au-actor { font-family: var(--font-mono, monospace); font-size: 11px; color: var(--color-text-muted); }\n .x-au-empty { text-align: center; color: var(--color-text-muted); padding: 24px; }\n .x-au-pager { display: flex; gap: 12px; align-items: center; margin-top: 14px; font-size: 12.5px; color: var(--color-text-muted); }\n .x-au-pager button { font: inherit; font-weight: 600; padding: 5px 12px; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-surface, #fff); color: var(--color-text); cursor: pointer; }\n .x-au-pager button:disabled { opacity: 0.4; cursor: not-allowed; }\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 { AuditComponent } from './audit.component';\n\n@NgModule({\n declarations: [AuditComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RouterModule.forChild([{ path: '', component: AuditComponent }]),\n ],\n})\nexport class AuditModule {}\n"],"names":["i2","i3","i1"],"mappings":";;;;;;;;;;;AAgBA;AACA;AACA;AACA;AACA;AACA;AAMM,MAAO,cAAe,SAAQ,aAAa,CAAA;AALjD,IAAA,WAAA,GAAA;;QAMmB,IAAA,CAAA,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC;QAE1D,IAAA,CAAA,OAAO,GAAG,IAAI;QACd,IAAA,CAAA,IAAI,GAAY,EAAE;QAClB,IAAA,CAAA,IAAI,GAAG,CAAC;QACR,IAAA,CAAA,OAAO,GAAG,EAAE;QACZ,IAAA,CAAA,KAAK,GAAG,CAAC;QACT,IAAA,CAAA,MAAM,GAAG,EAAE;QAEF,IAAA,CAAA,IAAI,GAAG,MAAK;YACnB,IAAI,CAAC,IAAI,EAAE;AACb,QAAA,CAAC;AA8BF,IAAA;AA5BC,IAAA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAA;AACnB,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;AACnB,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AAChB,QAAA,IAAI,CAAC;aACF,MAAM,CAAC,mBAAmB,IAAI,CAAA,SAAA,EAAY,IAAI,CAAC,OAAO,EAAE;AACxD,aAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACtB,aAAA,SAAS,CACR,KAAK,CAAC,CAAC,GAAQ,KAAI;YACjB,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE,IAAI,IAAI,EAAE;AAC3B,YAAA,IAAI,CAAC,KAAK,GAAG,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM;AACjD,YAAA,IAAI,CAAC,OAAO,GAAG,KAAK;QACtB,CAAC,CAAC,CACH;IACL;AAEA,IAAA,IAAI,KAAK,GAAA;QACP,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AAC1C,QAAA,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI;QACxB,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KACxB,CAAA,EAAG,CAAC,CAAC,IAAI,CAAA,CAAA,EAAI,CAAC,CAAC,UAAU,CAAA,CAAA,EAAI,CAAC,CAAC,MAAM,CAAA,CAAA,EAAI,CAAC,CAAC,MAAM,CAAA;AAC9C,aAAA,WAAW;AACX,aAAA,QAAQ,CAAC,CAAC,CAAC,CACf;IACH;AAEA,IAAA,IAAI,KAAK,GAAA;AACP,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1D;+GAzCW,cAAc,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAd,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,cAAc,2FC3B3B,w+FAmEA,EAAA,MAAA,EAAA,CAAA,mzCAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,oBAAA,EAAA,QAAA,EAAA,8MAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,qDAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,gBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAAA,IAAA,CAAA,eAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAAC,EAAA,CAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA,CAAA;;4FDxCa,cAAc,EAAA,UAAA,EAAA,CAAA;kBAL1B,SAAS;AACI,YAAA,IAAA,EAAA,CAAA,EAAA,UAAA,EAAA,KAAK,YACP,SAAS,EAAA,QAAA,EAAA,w+FAAA,EAAA;;;MEVR,WAAW,CAAA;+GAAX,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gHAAX,WAAW,EAAA,YAAA,EAAA,CAPP,cAAc,CAAA,EAAA,OAAA,EAAA,CAE3B,YAAY;YACZ,cAAc,EAAAC,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,WAAW,YALpB,YAAY;YACZ,cAAc;AACd,YAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA,EAAA,CAAA,CAAA;;4FAGvD,WAAW,EAAA,UAAA,EAAA,CAAA;kBARvB,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,YAAY,EAAE,CAAC,cAAc,CAAC;AAC9B,oBAAA,OAAO,EAAE;wBACP,YAAY;wBACZ,cAAc;AACd,wBAAA,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AACjE,qBAAA;AACF,iBAAA;;;;;"}
|