@simplysm/sd-cli 14.0.95 → 14.0.96

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 (42) hide show
  1. package/dist/commands/init/generators/client.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client.js +9 -0
  3. package/dist/commands/init/generators/client.js.map +1 -1
  4. package/dist/commands/init/generators/server.d.ts.map +1 -1
  5. package/dist/commands/init/generators/server.js +1 -0
  6. package/dist/commands/init/generators/server.js.map +1 -1
  7. package/package.json +5 -5
  8. package/src/commands/init/generators/client.ts +45 -0
  9. package/src/commands/init/generators/server.ts +5 -0
  10. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +1 -1
  11. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.detail.ts.hbs +221 -0
  12. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +106 -0
  13. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +277 -0
  14. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +537 -0
  15. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +337 -0
  16. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +540 -0
  17. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +4 -6
  18. package/src/commands/init/templates/client/src/app/home/system/data-log/data-log.list.ts.hbs +355 -0
  19. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +382 -0
  20. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +3 -4
  21. package/src/commands/init/templates/client/src/app.root.ts.hbs +9 -3
  22. package/src/commands/init/templates/client/src/index.html.hbs +1 -0
  23. package/src/commands/init/templates/client/src/main.ts.hbs +23 -18
  24. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +30 -0
  25. package/src/commands/init/templates/client/src/routes.ts.hbs +22 -0
  26. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -4
  27. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +3 -3
  28. package/src/commands/init/templates/client-common/src/providers/app-orm.provider.ts.hbs +0 -11
  29. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +24 -10
  30. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +2 -2
  31. package/src/commands/init/templates/common/package.json.hbs +2 -1
  32. package/src/commands/init/templates/common/src/app-structure.ts.hbs +7 -2
  33. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -1
  34. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +2 -2
  35. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +2 -2
  36. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +1 -0
  37. package/src/commands/init/templates/common/src/index.ts.hbs +1 -1
  38. package/src/commands/init/templates/server/src/index.ts.hbs +3 -0
  39. package/src/commands/init/templates/server/src/main.ts.hbs +15 -1
  40. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +28 -22
  41. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +5 -5
  42. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +191 -0
@@ -0,0 +1,355 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ effect,
5
+ inject,
6
+ signal,
7
+ untracked,
8
+ ViewEncapsulation,
9
+ } from "@angular/core";
10
+ import {
11
+ FormatPipe,
12
+ injectPermsSignal,
13
+ injectViewTitleSignal,
14
+ injectViewTypeSignal,
15
+ mark,
16
+ SdAnchor,
17
+ SdButton,
18
+ SdCrudList,
19
+ SdDateRangePicker,
20
+ SdItemOfTemplate,
21
+ SdModalProvider,
22
+ SdSharedDataSelect,
23
+ SdSheetColumn,
24
+ SdSheetColumnCellTemplate,
25
+ SdTextfield,
26
+ SdToastProvider,
27
+ type SortingDef,
28
+ } from "@simplysm/angular";
29
+ import { expr } from "@simplysm/orm-common";
30
+ import { DateOnly, DateTime, obj } from "@simplysm/core-common";
31
+ import { downloadBlob } from "@simplysm/core-browser";
32
+ import { ExcelWrapper } from "@simplysm/excel";
33
+ import { z } from "zod";
34
+ import { AppOrmProvider, useSharedSignal } from "@{{workspaceName}}/client-common";
35
+ import { NgIcon } from "@ng-icons/core";
36
+ import { tablerDownload } from "@ng-icons/tabler-icons";
37
+ import { TextViewModal } from "../../../../modals/text-view.modal";
38
+
39
+ const ITEMS_PER_PAGE = 50;
40
+
41
+ @Component({
42
+ selector: "app-data-log-list",
43
+ changeDetection: ChangeDetectionStrategy.OnPush,
44
+ encapsulation: ViewEncapsulation.None,
45
+ standalone: true,
46
+ imports: [
47
+ SdCrudList,
48
+ SdSheetColumn,
49
+ SdSheetColumnCellTemplate,
50
+ SdTextfield,
51
+ SdDateRangePicker,
52
+ SdSharedDataSelect,
53
+ SdItemOfTemplate,
54
+ SdAnchor,
55
+ SdButton,
56
+ NgIcon,
57
+ FormatPipe,
58
+ ],
59
+ template: `
60
+ <sd-crud-list
61
+ [key]="'data-log-list'"
62
+ [(ready)]="ready"
63
+ [initialized]="initialized()"
64
+ [(busyCount)]="busyCount"
65
+ [viewType]="viewType()"
66
+ [restricted]="!perms().includes('use')"
67
+ [readonly]="true"
68
+ [inlineEdit]="false"
69
+ [items]="items()"
70
+ [trackByFn]="trackByFn"
71
+ [(currentPage)]="page"
72
+ [totalPageCount]="pageLength()"
73
+ [(sorts)]="sortingDefs"
74
+ (filterSubmit)="onFilterSubmit()"
75
+ >
76
+ <ng-template #filterTpl>
77
+ <div class="form-box-inline">
78
+ <div>
79
+ <label>기간</label>
80
+ <sd-date-range-picker
81
+ [(from)]="filter().fromDate"
82
+ (fromChange)="mark(filter)"
83
+ [(to)]="filter().toDate"
84
+ (toChange)="mark(filter)"
85
+ />
86
+ </div>
87
+ <div>
88
+ <label>검색어</label>
89
+ <sd-textfield
90
+ [type]="'text'"
91
+ [(value)]="filter().searchText"
92
+ (valueChange)="mark(filter)"
93
+ />
94
+ </div>
95
+ <div>
96
+ <label>수행자</label>
97
+ <sd-shared-data-select
98
+ [selectMode]="'multi'"
99
+ [items]="shared{{userEntityPascal}}s.items()"
100
+ [(value)]="filter().{{userEntityCamel}}Ids"
101
+ (valueChange)="mark(filter)"
102
+ >
103
+ <ng-template [itemOf]="shared{{userEntityPascal}}s.items()" let-item="item">
104
+ \{{ item.name }}
105
+ </ng-template>
106
+ </sd-shared-data-select>
107
+ </div>
108
+ </div>
109
+ </ng-template>
110
+
111
+ <ng-template #toolTpl>
112
+ <sd-button [size]="'sm'" [theme]="'link-success'" (click)="onDownloadExcelButtonClick()">
113
+ <ng-icon [svg]="tablerDownload" />
114
+ 엑셀 다운로드
115
+ </sd-button>
116
+ </ng-template>
117
+
118
+ <sd-sheet-column [key]="'id'" [header]="'#'">
119
+ <ng-template [cell]="items()" let-item="item">
120
+ <div class="p-xs-sm tx-right">\{{ item.id }}</div>
121
+ </ng-template>
122
+ </sd-sheet-column>
123
+
124
+ <sd-sheet-column [key]="'dateTime'" [header]="'변경시각'">
125
+ <ng-template [cell]="items()" let-item="item">
126
+ <div class="p-xs-sm">\{{ item.dateTime | format: "yyyy-MM-dd HH:mm:ss" }}</div>
127
+ </ng-template>
128
+ </sd-sheet-column>
129
+
130
+ <sd-sheet-column [key]="'tableName'" [header]="'테이블'">
131
+ <ng-template [cell]="items()" let-item="item">
132
+ <div class="p-xs-sm">\{{ item.tableDescription ?? item.tableName }}</div>
133
+ </ng-template>
134
+ </sd-sheet-column>
135
+
136
+ <sd-sheet-column [key]="'action'" [header]="'변경유형'">
137
+ <ng-template [cell]="items()" let-item="item">
138
+ <div class="p-xs-sm">\{{ item.action }}</div>
139
+ </ng-template>
140
+ </sd-sheet-column>
141
+
142
+ <sd-sheet-column [key]="'itemId'" [header]="'대상'">
143
+ <ng-template [cell]="items()" let-item="item">
144
+ <div class="p-xs-sm tx-right">\{{ item.itemId ?? "&nbsp;" }}</div>
145
+ </ng-template>
146
+ </sd-sheet-column>
147
+
148
+ <sd-sheet-column [key]="'{{userEntityCamel}}Name'" [header]="'수행자'">
149
+ <ng-template [cell]="items()" let-item="item">
150
+ <div class="p-xs-sm">\{{ item.{{userEntityCamel}}Name ?? "시스템" }}</div>
151
+ </ng-template>
152
+ </sd-sheet-column>
153
+
154
+ <sd-sheet-column [key]="'value'" [header]="'변경값'">
155
+ <ng-template [cell]="items()" let-item="item">
156
+ @if (item.valueJson) {
157
+ <sd-anchor (click)="onOpenValue(item)">
158
+ <div
159
+ class="p-xs-sm"
160
+ style="max-width: 40em; overflow: clip; white-space: nowrap; text-overflow: ellipsis;"
161
+ >
162
+ \{{ item.valueJson }}
163
+ </div>
164
+ </sd-anchor>
165
+ } @else {
166
+ <div class="p-xs-sm">&nbsp;</div>
167
+ }
168
+ </ng-template>
169
+ </sd-sheet-column>
170
+ </sd-crud-list>
171
+ `,
172
+ })
173
+ export class DataLogList {
174
+ private readonly _sdToast = inject(SdToastProvider);
175
+ private readonly _sdModal = inject(SdModalProvider);
176
+ private readonly _appOrm = inject(AppOrmProvider);
177
+
178
+ perms = injectPermsSignal(["system.data-log"], ["use"]);
179
+ viewType = injectViewTypeSignal();
180
+ viewTitle = injectViewTitleSignal();
181
+
182
+ ready = signal(false);
183
+ initialized = signal(false);
184
+ busyCount = signal(0);
185
+
186
+ shared{{userEntityPascal}}s = useSharedSignal("{{userEntityLabel}}");
187
+
188
+ items = signal<IItem[]>([]);
189
+ page = signal(0);
190
+ pageLength = signal(0);
191
+ sortingDefs = signal<SortingDef[]>([]);
192
+
193
+ filter = signal<IFilter>({ {{userEntityCamel}}Ids: [] });
194
+ lastFilter = signal<IFilter>({ {{userEntityCamel}}Ids: [] });
195
+
196
+ trackByFn = (item: IItem): number => item.id;
197
+
198
+ private readonly _excelWrapper = new ExcelWrapper(
199
+ z.object({
200
+ dateTime: z.custom<DateTime>().optional().describe("변경시각"),
201
+ table: z.string().optional().describe("테이블"),
202
+ action: z.string().optional().describe("변경유형"),
203
+ itemId: z.number().optional().describe("대상"),
204
+ {{userEntityCamel}}Name: z.string().optional().describe("수행자"),
205
+ valueJson: z.string().optional().describe("변경값"),
206
+ }),
207
+ );
208
+
209
+ constructor() {
210
+ effect(() => {
211
+ if (!this.perms().includes("use") || !this.ready()) {
212
+ this.initialized.set(true);
213
+ return;
214
+ }
215
+
216
+ this.lastFilter();
217
+ this.page();
218
+ this.sortingDefs();
219
+
220
+ void untracked(async () => {
221
+ this.busyCount.update((v) => v + 1);
222
+ await this._sdToast.try(async () => {
223
+ await this._refresh();
224
+ });
225
+ this.busyCount.update((v) => v - 1);
226
+ this.initialized.set(true);
227
+ });
228
+ });
229
+ }
230
+
231
+ onFilterSubmit(): void {
232
+ this.page.set(0);
233
+ this.lastFilter.set({ ...this.filter() });
234
+ }
235
+
236
+ async onOpenValue(item: IItem): Promise<void> {
237
+ if (item.valueJson == null || item.valueJson === "") return;
238
+ await this._sdModal.showAsync({
239
+ type: TextViewModal,
240
+ title: "변경값",
241
+ inputs: { text: item.valueJson },
242
+ });
243
+ }
244
+
245
+ async onDownloadExcelButtonClick(): Promise<void> {
246
+ if (this.busyCount() > 0) return;
247
+
248
+ this.busyCount.update((v) => v + 1);
249
+ await this._sdToast.try(async () => {
250
+ const r = await this._search(false);
251
+ const records = r.items.map((item) => ({
252
+ dateTime: item.dateTime,
253
+ table: item.tableDescription ?? item.tableName,
254
+ action: item.action,
255
+ itemId: item.itemId,
256
+ {{userEntityCamel}}Name: item.{{userEntityCamel}}Name,
257
+ valueJson: item.valueJson,
258
+ }));
259
+ const wb = await this._excelWrapper.write(this.viewTitle(), records);
260
+ try {
261
+ downloadBlob(
262
+ await wb.toBlob(),
263
+ `${this.viewTitle()}_${new DateTime().toFormatString("yyMMdd")}.xlsx`,
264
+ );
265
+ } finally {
266
+ await wb.close();
267
+ }
268
+ });
269
+ this.busyCount.update((v) => v - 1);
270
+ }
271
+
272
+ private async _refresh(): Promise<void> {
273
+ const r = await this._search(true);
274
+ this.items.set(r.items);
275
+ this.pageLength.set(r.pageLength);
276
+ }
277
+
278
+ private async _search(usePagination: boolean): Promise<{ items: IItem[]; pageLength: number }> {
279
+ return this._appOrm.connectAsync(async (db) => {
280
+ let qr = db.dataLog();
281
+
282
+ const fromDate = this.lastFilter().fromDate;
283
+ if (fromDate != null) {
284
+ const fromBoundary = new DateTime(fromDate.year, fromDate.month, fromDate.day);
285
+ qr = qr.where((c) => [expr.gte(c.dateTime, fromBoundary)]);
286
+ }
287
+
288
+ const toDate = this.lastFilter().toDate;
289
+ if (toDate != null) {
290
+ const toBoundary = new DateTime(toDate.year, toDate.month, toDate.day).addDays(1);
291
+ qr = qr.where((c) => [expr.lt(c.dateTime, toBoundary)]);
292
+ }
293
+
294
+ const searchText = this.lastFilter().searchText?.trim();
295
+ if (searchText != null && searchText.length > 0) {
296
+ qr = qr.search((c) => [c.tableName, c.tableDescription, c.action], searchText);
297
+ }
298
+
299
+ const {{userEntityCamel}}Ids = this.lastFilter().{{userEntityCamel}}Ids;
300
+ if ({{userEntityCamel}}Ids.length > 0) {
301
+ qr = qr.where((c) => [expr.in(c.{{userEntityCamel}}Id, {{userEntityCamel}}Ids)]);
302
+ }
303
+
304
+ const pageLength = usePagination ? Math.ceil((await qr.count()) / ITEMS_PER_PAGE) : 0;
305
+
306
+ let qr2 = qr
307
+ .include((c) => c.{{userEntityCamel}})
308
+ .select((c) => ({
309
+ id: c.id,
310
+ dateTime: c.dateTime,
311
+ tableName: c.tableName,
312
+ tableDescription: c.tableDescription,
313
+ action: c.action,
314
+ itemId: c.itemId,
315
+ valueJson: c.valueJson,
316
+ {{userEntityCamel}}Name: c.{{userEntityCamel}}?.name,
317
+ }));
318
+
319
+ for (const sort of this.sortingDefs()) {
320
+ qr2 = qr2.orderBy((c) => obj.getChainValue(c, sort.key) as any, sort.desc ? "DESC" : "ASC");
321
+ }
322
+ if (!this.sortingDefs().some((s) => s.key === "id")) {
323
+ qr2 = qr2.orderBy((c) => c.id, "DESC");
324
+ }
325
+
326
+ if (usePagination) {
327
+ qr2 = qr2.limit(this.page() * ITEMS_PER_PAGE, ITEMS_PER_PAGE);
328
+ }
329
+
330
+ const items = (await qr2.execute()) as IItem[];
331
+ return { items, pageLength };
332
+ });
333
+ }
334
+
335
+ protected readonly mark = mark;
336
+ protected readonly tablerDownload = tablerDownload;
337
+ }
338
+
339
+ interface IItem {
340
+ id: number;
341
+ dateTime: DateTime;
342
+ tableName: string;
343
+ tableDescription?: string;
344
+ action: string;
345
+ itemId?: number;
346
+ valueJson?: string;
347
+ {{userEntityCamel}}Name?: string;
348
+ }
349
+
350
+ interface IFilter {
351
+ fromDate?: DateOnly;
352
+ toDate?: DateOnly;
353
+ searchText?: string;
354
+ {{userEntityCamel}}Ids: number[];
355
+ }