@simplysm/angular 14.0.10 → 14.0.11
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/dist/features/data-view/sd-data-detail.control.d.ts +7 -4
- package/dist/features/data-view/sd-data-detail.control.d.ts.map +1 -1
- package/dist/features/data-view/sd-data-detail.control.js +64 -53
- package/dist/features/data-view/sd-data-sheet.control.d.ts +2 -0
- package/dist/features/data-view/sd-data-sheet.control.d.ts.map +1 -1
- package/dist/features/data-view/sd-data-sheet.control.js +112 -81
- package/dist/features/permission-table/sd-permission-table.control.d.ts +28 -0
- package/dist/features/permission-table/sd-permission-table.control.d.ts.map +1 -0
- package/dist/features/permission-table/sd-permission-table.control.js +397 -0
- package/dist/features/shared-data/sd-shared-data-select.control.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/ui/overlay/modal/sd-modal.provider.d.ts.map +1 -1
- package/dist/ui/overlay/modal/sd-modal.provider.js +12 -0
- package/package.json +5 -5
- package/src/features/data-view/sd-data-detail.control.ts +54 -35
- package/src/features/data-view/sd-data-sheet.control.ts +24 -7
- package/src/features/permission-table/sd-permission-table.control.ts +385 -0
- package/src/features/shared-data/sd-shared-data-select.control.ts +1 -1
- package/src/index.ts +3 -0
- package/src/ui/overlay/modal/sd-modal.provider.ts +13 -0
|
@@ -145,14 +145,17 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
145
145
|
if (!this.toggleDelete) return;
|
|
146
146
|
|
|
147
147
|
this.busyCount.update((v) => v + 1);
|
|
148
|
-
await this._sdToast.try(
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
await this._sdToast.try(
|
|
149
|
+
async () => {
|
|
150
|
+
const result = await this.toggleDelete!(del);
|
|
151
|
+
if (!result) return;
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
this._sdToast.success(`${del ? "삭제" : "복구"}되었습니다.`);
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
this.close.emit(result);
|
|
156
|
+
},
|
|
157
|
+
(err) => this._getOrmDataEditToastErrorMessage(err),
|
|
158
|
+
);
|
|
156
159
|
this.busyCount.update((v) => v - 1);
|
|
157
160
|
}
|
|
158
161
|
|
|
@@ -172,18 +175,32 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
this.busyCount.update((v) => v + 1);
|
|
175
|
-
await this._sdToast.try(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
await this._sdToast.try(
|
|
179
|
+
async () => {
|
|
180
|
+
const result = await this.submit!(this.data());
|
|
181
|
+
if (!result) return;
|
|
178
182
|
|
|
179
|
-
|
|
183
|
+
this._sdToast.success("저장되었습니다.");
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
this.close.emit(result);
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
await this.refresh();
|
|
188
|
+
},
|
|
189
|
+
(err) => this._getOrmDataEditToastErrorMessage(err),
|
|
190
|
+
);
|
|
185
191
|
this.busyCount.update((v) => v - 1);
|
|
186
192
|
}
|
|
193
|
+
|
|
194
|
+
private _getOrmDataEditToastErrorMessage(err: unknown) {
|
|
195
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
196
|
+
if (
|
|
197
|
+
message.includes("a parent row: a foreign key constraint") ||
|
|
198
|
+
message.includes("conflicted with the REFERENCE")
|
|
199
|
+
) {
|
|
200
|
+
return "경고! 연결된 작업에 의한 처리 거부. 후속작업 확인 요망";
|
|
201
|
+
}
|
|
202
|
+
return message;
|
|
203
|
+
}
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
//#endregion
|
|
@@ -219,13 +236,13 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
219
236
|
<ng-template #pageTopbarTpl>
|
|
220
237
|
@if (parent.canEdit() && parent.submit) {
|
|
221
238
|
<sd-button [theme]="'link-primary'" (click)="onSubmitButtonClick()">
|
|
222
|
-
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
239
|
+
<ng-icon [svg]="icons.tablerDeviceFloppy" />
|
|
223
240
|
저장
|
|
224
241
|
<small>(CTRL+S)</small>
|
|
225
242
|
</sd-button>
|
|
226
243
|
}
|
|
227
244
|
<sd-button [theme]="'link-info'" (click)="onRefreshButtonClick()">
|
|
228
|
-
<ng-icon [svg]="tablerRefresh" />
|
|
245
|
+
<ng-icon [svg]="icons.tablerRefresh" />
|
|
229
246
|
새로고침
|
|
230
247
|
<small>(CTRL+ALT+L)</small>
|
|
231
248
|
</sd-button>
|
|
@@ -238,12 +255,12 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
238
255
|
@if (parent.viewType() === "control" && parent.canEdit()) {
|
|
239
256
|
@if (parent.submit) {
|
|
240
257
|
<sd-button [theme]="'primary'" (click)="onSubmitButtonClick()">
|
|
241
|
-
<ng-icon [svg]="tablerDeviceFloppy" />
|
|
258
|
+
<ng-icon [svg]="icons.tablerDeviceFloppy" />
|
|
242
259
|
저장
|
|
243
260
|
<small>(CTRL+S)</small>
|
|
244
261
|
</sd-button>
|
|
245
262
|
<sd-button [theme]="'info'" (click)="onRefreshButtonClick()">
|
|
246
|
-
<ng-icon [svg]="tablerRefresh" />
|
|
263
|
+
<ng-icon [svg]="icons.tablerRefresh" />
|
|
247
264
|
새로고침
|
|
248
265
|
<small>(CTRL+ALT+L)</small>
|
|
249
266
|
</sd-button>
|
|
@@ -255,12 +272,12 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
255
272
|
) {
|
|
256
273
|
@if (parent.dataInfo()?.isDeleted) {
|
|
257
274
|
<sd-button [theme]="'warning'" (click)="onRestoreButtonClick()">
|
|
258
|
-
<ng-icon [svg]="tablerRestore" />
|
|
275
|
+
<ng-icon [svg]="icons.tablerRestore" />
|
|
259
276
|
복구
|
|
260
277
|
</sd-button>
|
|
261
278
|
} @else {
|
|
262
279
|
<sd-button [theme]="'danger'" (click)="onDeleteButtonClick()">
|
|
263
|
-
<ng-icon [svg]="tablerEraser" />
|
|
280
|
+
<ng-icon [svg]="icons.tablerEraser" />
|
|
264
281
|
삭제
|
|
265
282
|
</sd-button>
|
|
266
283
|
}
|
|
@@ -278,7 +295,7 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
278
295
|
}
|
|
279
296
|
|
|
280
297
|
<div class="flex-fill">
|
|
281
|
-
<sd-form #formCtrl (
|
|
298
|
+
<sd-form #formCtrl (formSubmit)="onSubmit()">
|
|
282
299
|
<ng-template [ngTemplateOutlet]="contentTplRef()" />
|
|
283
300
|
</sd-form>
|
|
284
301
|
</div>
|
|
@@ -332,18 +349,18 @@ export abstract class AbsSdDataDetail<T extends object, R = boolean>
|
|
|
332
349
|
</div>
|
|
333
350
|
</div>
|
|
334
351
|
</ng-template>
|
|
335
|
-
|
|
336
|
-
<ng-template #modalActionTpl>
|
|
337
|
-
<sd-anchor
|
|
338
|
-
[theme]="'gray'"
|
|
339
|
-
class="p-sm-default"
|
|
340
|
-
(click)="onRefreshButtonClick()"
|
|
341
|
-
title="새로고침(CTRL+ALT+L)"
|
|
342
|
-
>
|
|
343
|
-
<ng-icon [svg]="tablerRefresh" />
|
|
344
|
-
</sd-anchor>
|
|
345
|
-
</ng-template>
|
|
346
352
|
}
|
|
353
|
+
|
|
354
|
+
<ng-template #modalActionTpl>
|
|
355
|
+
<sd-anchor
|
|
356
|
+
[theme]="'gray'"
|
|
357
|
+
class="p-sm-default"
|
|
358
|
+
(click)="onRefreshButtonClick()"
|
|
359
|
+
title="새로고침(CTRL+ALT+L)"
|
|
360
|
+
>
|
|
361
|
+
<ng-icon [svg]="icons.tablerRefresh" />
|
|
362
|
+
</sd-anchor>
|
|
363
|
+
</ng-template>
|
|
347
364
|
</sd-base-container>
|
|
348
365
|
`,
|
|
349
366
|
})
|
|
@@ -385,10 +402,12 @@ export class SdDataDetailControl {
|
|
|
385
402
|
await this.parent.doSubmit({ permCheck: true });
|
|
386
403
|
}
|
|
387
404
|
|
|
388
|
-
protected readonly
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
405
|
+
protected readonly icons = {
|
|
406
|
+
tablerDeviceFloppy,
|
|
407
|
+
tablerRefresh,
|
|
408
|
+
tablerRestore,
|
|
409
|
+
tablerEraser,
|
|
410
|
+
};
|
|
392
411
|
}
|
|
393
412
|
|
|
394
413
|
//#endregion
|
|
@@ -256,12 +256,11 @@ export abstract class AbsSdDataSheet<
|
|
|
256
256
|
this.pageLength.set(result.pageLength ?? 0);
|
|
257
257
|
this.summaryData.set(result.summary ?? {});
|
|
258
258
|
|
|
259
|
+
const selectedKeySet = new Set(
|
|
260
|
+
this.selectedItems().map((sel) => this.getItemInfoFn(sel).key),
|
|
261
|
+
);
|
|
259
262
|
this.selectedItems.set(
|
|
260
|
-
this.items().filter((item) =>
|
|
261
|
-
this.selectedItems().some(
|
|
262
|
-
(sel) => this.getItemInfoFn(sel).key === this.getItemInfoFn(item).key,
|
|
263
|
-
),
|
|
264
|
-
),
|
|
263
|
+
this.items().filter((item) => selectedKeySet.has(this.getItemInfoFn(item).key)),
|
|
265
264
|
);
|
|
266
265
|
}
|
|
267
266
|
|
|
@@ -300,12 +299,11 @@ export abstract class AbsSdDataSheet<
|
|
|
300
299
|
|
|
301
300
|
this._sdToast.success("저장되었습니다.");
|
|
302
301
|
await this.refresh();
|
|
302
|
+
this.submitted.emit(true);
|
|
303
303
|
},
|
|
304
304
|
(err) => this._getOrmDataEditToastErrorMessage(err),
|
|
305
305
|
);
|
|
306
306
|
this.busyCount.update((v) => v - 1);
|
|
307
|
-
|
|
308
|
-
this.submitted.emit(true);
|
|
309
307
|
}
|
|
310
308
|
|
|
311
309
|
doToggleDeleteItem(item: TItem) {
|
|
@@ -758,6 +756,17 @@ export abstract class AbsSdDataSheet<
|
|
|
758
756
|
}
|
|
759
757
|
</div>
|
|
760
758
|
</ng-template>
|
|
759
|
+
|
|
760
|
+
<ng-template #modalActionTpl>
|
|
761
|
+
<sd-anchor
|
|
762
|
+
[theme]="'gray'"
|
|
763
|
+
class="p-sm-default"
|
|
764
|
+
(click)="onRefreshButtonClick()"
|
|
765
|
+
title="새로고침(CTRL+ALT+L)"
|
|
766
|
+
>
|
|
767
|
+
<ng-icon [svg]="icons.tablerRefresh" />
|
|
768
|
+
</sd-anchor>
|
|
769
|
+
</ng-template>
|
|
761
770
|
}
|
|
762
771
|
</sd-base-container>
|
|
763
772
|
`,
|
|
@@ -782,6 +791,14 @@ export class SdDataSheetControl {
|
|
|
782
791
|
|
|
783
792
|
columnControls = contentChildren(SdDataSheetColumnDirective);
|
|
784
793
|
|
|
794
|
+
modalActionTplRef = viewChild("modalActionTpl", { read: TemplateRef });
|
|
795
|
+
|
|
796
|
+
constructor() {
|
|
797
|
+
effect(() => {
|
|
798
|
+
this.parent.actionTplRef = this.modalActionTplRef();
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
785
802
|
protected readonly icons = {
|
|
786
803
|
tablerRefresh,
|
|
787
804
|
tablerDeviceFloppy,
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
2
|
+
import {
|
|
3
|
+
booleanAttribute,
|
|
4
|
+
ChangeDetectionStrategy,
|
|
5
|
+
Component,
|
|
6
|
+
computed,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
signal,
|
|
10
|
+
ViewEncapsulation,
|
|
11
|
+
} from "@angular/core";
|
|
12
|
+
import { obj } from "@simplysm/core-common";
|
|
13
|
+
import type { ISdPermission } from "../../core/providers/sd-app-structure.provider";
|
|
14
|
+
import { SdCheckboxControl } from "../../ui/form/checkbox/sd-checkbox.control";
|
|
15
|
+
import { SdCollapseIconControl } from "../../ui/navigation/collapse/sd-collapse-icon.control";
|
|
16
|
+
import { SdTypedTemplateDirective } from "../../core/directives/sd-typed-template.directive";
|
|
17
|
+
import { SdAnchorControl } from "../../ui/form/button/sd-anchor.control";
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
selector: "sd-permission-table",
|
|
21
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
22
|
+
encapsulation: ViewEncapsulation.None,
|
|
23
|
+
standalone: true,
|
|
24
|
+
imports: [
|
|
25
|
+
SdTypedTemplateDirective,
|
|
26
|
+
NgTemplateOutlet,
|
|
27
|
+
SdCollapseIconControl,
|
|
28
|
+
SdCheckboxControl,
|
|
29
|
+
SdAnchorControl,
|
|
30
|
+
],
|
|
31
|
+
styles: [
|
|
32
|
+
/* language=SCSS */ `
|
|
33
|
+
sd-permission-table {
|
|
34
|
+
table {
|
|
35
|
+
border-collapse: collapse;
|
|
36
|
+
|
|
37
|
+
> * > tr {
|
|
38
|
+
> * {
|
|
39
|
+
padding: var(--gap-sm) var(--gap-lg);
|
|
40
|
+
position: sticky;
|
|
41
|
+
top: 0;
|
|
42
|
+
border-top: 1px solid transparent;
|
|
43
|
+
border-bottom: 1px solid transparent;
|
|
44
|
+
|
|
45
|
+
color: var(--text-trans-default);
|
|
46
|
+
|
|
47
|
+
> * {
|
|
48
|
+
color: var(--text-trans-default) !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&._title {
|
|
52
|
+
border-top-left-radius: 14px;
|
|
53
|
+
border-bottom-left-radius: 14px;
|
|
54
|
+
padding-left: var(--gap-lg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&[data-sd-collapse="true"] {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&[data-sd-theme="first"] {
|
|
63
|
+
> * {
|
|
64
|
+
&._title,
|
|
65
|
+
&._after {
|
|
66
|
+
background: var(--theme-info-default);
|
|
67
|
+
|
|
68
|
+
color: var(--text-trans-rev-default);
|
|
69
|
+
|
|
70
|
+
> * {
|
|
71
|
+
color: var(--text-trans-rev-default) !important;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&[data-sd-theme="info"] {
|
|
78
|
+
> * {
|
|
79
|
+
&._title,
|
|
80
|
+
&._after {
|
|
81
|
+
background: var(--theme-info-lightest);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&[data-sd-theme="warning"] {
|
|
87
|
+
> * {
|
|
88
|
+
&._title,
|
|
89
|
+
&._after {
|
|
90
|
+
background: var(--theme-warning-lightest);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&[data-sd-theme="success"] {
|
|
96
|
+
> * {
|
|
97
|
+
&._title,
|
|
98
|
+
&._after {
|
|
99
|
+
background: var(--theme-success-lightest);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
],
|
|
108
|
+
template: `
|
|
109
|
+
<table>
|
|
110
|
+
<tbody>
|
|
111
|
+
@for (item of items(); track item.codeChain.join(".")) {
|
|
112
|
+
<ng-template
|
|
113
|
+
[ngTemplateOutlet]="itemTpl"
|
|
114
|
+
[ngTemplateOutletContext]="{
|
|
115
|
+
item: item,
|
|
116
|
+
parentKey: 'root',
|
|
117
|
+
depth: 0,
|
|
118
|
+
parent: undefined,
|
|
119
|
+
}"
|
|
120
|
+
></ng-template>
|
|
121
|
+
}
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
|
|
125
|
+
<ng-template
|
|
126
|
+
#itemTpl
|
|
127
|
+
[typed]="itemTemplateType"
|
|
128
|
+
let-item="item"
|
|
129
|
+
let-parentKey="parentKey"
|
|
130
|
+
let-depth="depth"
|
|
131
|
+
let-parent="parent"
|
|
132
|
+
>
|
|
133
|
+
@if (
|
|
134
|
+
(item.children && item.children.length !== 0) || (item.perms && item.perms.length > 0)
|
|
135
|
+
) {
|
|
136
|
+
<tr
|
|
137
|
+
[attr.data-sd-collapse]="!!parent && getIsPermCollapsed(parent)"
|
|
138
|
+
[attr.data-sd-theme]="
|
|
139
|
+
depth === 0
|
|
140
|
+
? 'first'
|
|
141
|
+
: depth % 3 === 0
|
|
142
|
+
? 'success'
|
|
143
|
+
: depth % 3 === 1
|
|
144
|
+
? 'info'
|
|
145
|
+
: 'warning'
|
|
146
|
+
"
|
|
147
|
+
>
|
|
148
|
+
@for (i of arr(depth + 1); track i) {
|
|
149
|
+
<td class="_before"> </td>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
<td class="_title">
|
|
153
|
+
@if (item.children && item.children.length > 0) {
|
|
154
|
+
<sd-anchor (click)="onPermCollapseToggle(item)">
|
|
155
|
+
<sd-collapse-icon [open]="getIsPermCollapsed(item)" />
|
|
156
|
+
{{ item.title }}
|
|
157
|
+
</sd-anchor>
|
|
158
|
+
} @else {
|
|
159
|
+
<div style="padding-left: 14px;">
|
|
160
|
+
{{ item.title }}
|
|
161
|
+
</div>
|
|
162
|
+
}
|
|
163
|
+
</td>
|
|
164
|
+
|
|
165
|
+
@for (i of arr(depthLength() - (depth + 1)); track i) {
|
|
166
|
+
<td class="_after"> </td>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
<td class="_after">
|
|
170
|
+
@if (getIsPermExists(item, "use")) {
|
|
171
|
+
<sd-checkbox
|
|
172
|
+
[inline]="true"
|
|
173
|
+
[value]="getIsPermChecked(item, 'use')"
|
|
174
|
+
(valueChange)="onPermCheckChange(item, 'use', $event)"
|
|
175
|
+
[disabled]="disabled()"
|
|
176
|
+
>
|
|
177
|
+
사용
|
|
178
|
+
</sd-checkbox>
|
|
179
|
+
}
|
|
180
|
+
</td>
|
|
181
|
+
|
|
182
|
+
<td class="_after">
|
|
183
|
+
@if (getIsPermExists(item, "edit")) {
|
|
184
|
+
<sd-checkbox
|
|
185
|
+
[inline]="true"
|
|
186
|
+
[value]="getIsPermChecked(item, 'edit')"
|
|
187
|
+
(valueChange)="onPermCheckChange(item, 'edit', $event)"
|
|
188
|
+
[disabled]="getEditDisabled(item)"
|
|
189
|
+
>
|
|
190
|
+
편집
|
|
191
|
+
</sd-checkbox>
|
|
192
|
+
}
|
|
193
|
+
</td>
|
|
194
|
+
</tr>
|
|
195
|
+
}
|
|
196
|
+
@if (item.children && item.children.length > 0) {
|
|
197
|
+
@for (child of item.children; track child.codeChain.join(".")) {
|
|
198
|
+
<ng-template
|
|
199
|
+
[ngTemplateOutlet]="itemTpl"
|
|
200
|
+
[ngTemplateOutletContext]="{
|
|
201
|
+
item: child,
|
|
202
|
+
parentKey: parentKey + '_' + item.codeChain.join('.'),
|
|
203
|
+
depth: depth + 1,
|
|
204
|
+
parent: item,
|
|
205
|
+
}"
|
|
206
|
+
></ng-template>
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
</ng-template>
|
|
210
|
+
`,
|
|
211
|
+
})
|
|
212
|
+
export class SdPermissionTableControl<TModule> {
|
|
213
|
+
value = model<Record<string, boolean>>({});
|
|
214
|
+
|
|
215
|
+
items = input<ISdPermission<TModule>[]>([]);
|
|
216
|
+
disabled = input(false, { transform: booleanAttribute });
|
|
217
|
+
|
|
218
|
+
collapsedItems = signal(new Set<ISdPermission<TModule>>());
|
|
219
|
+
|
|
220
|
+
depthLength = computed(() => {
|
|
221
|
+
return this._getDepthLength(this.items(), 0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
arr(len: number): number[] {
|
|
225
|
+
return Array(len)
|
|
226
|
+
.fill(0)
|
|
227
|
+
.map((_, i) => i);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
getIsPermCollapsed(item: ISdPermission<TModule>): boolean {
|
|
231
|
+
return this.collapsedItems().has(item);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getAllChildren(item: ISdPermission<TModule>): ISdPermission<TModule>[] {
|
|
235
|
+
return item.children?.mapMany((child) => [child, ...this.getAllChildren(child)]) ?? [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
getEditDisabled(item: ISdPermission<TModule>) {
|
|
239
|
+
if (this.disabled()) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (item.perms) {
|
|
244
|
+
if (this.getIsPermExists(item, "use") && !this.getIsPermChecked(item, "use")) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
if (
|
|
249
|
+
item.children?.every(
|
|
250
|
+
(child) => !this.getIsPermExists(child, "edit") || this.getEditDisabled(child),
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getIsPermExists(item: ISdPermission<TModule>, type: "use" | "edit"): boolean {
|
|
261
|
+
if (item.perms) {
|
|
262
|
+
return item.perms.includes(type);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (item.children) {
|
|
266
|
+
for (const child of item.children) {
|
|
267
|
+
if (this.getIsPermExists(child, type)) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getIsPermChecked(item: ISdPermission<TModule>, type: "use" | "edit"): boolean {
|
|
277
|
+
if (item.perms) {
|
|
278
|
+
const permCode = item.codeChain.join(".");
|
|
279
|
+
return this.value()[permCode + "." + type] ?? false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (item.children) {
|
|
283
|
+
for (const child of item.children) {
|
|
284
|
+
if (this.getIsPermChecked(child, type)) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
onPermCollapseToggle(item: ISdPermission<TModule>) {
|
|
294
|
+
this.collapsedItems.update((v) => {
|
|
295
|
+
const r = new Set(v);
|
|
296
|
+
if (r.has(item)) {
|
|
297
|
+
r.delete(item);
|
|
298
|
+
} else {
|
|
299
|
+
r.add(item);
|
|
300
|
+
const allChildren = this.getAllChildren(item);
|
|
301
|
+
for (const allChild of allChildren) {
|
|
302
|
+
r.add(allChild);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return r;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
onPermCheckChange(item: ISdPermission<TModule>, type: "use" | "edit", val: boolean) {
|
|
310
|
+
this.value.update((v) => {
|
|
311
|
+
const r = obj.clone(v);
|
|
312
|
+
this._changePermCheck(r, item, type, val);
|
|
313
|
+
return r;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private _changePermCheck(
|
|
318
|
+
value: Record<string, boolean>,
|
|
319
|
+
item: ISdPermission<TModule>,
|
|
320
|
+
type: "use" | "edit",
|
|
321
|
+
val: boolean,
|
|
322
|
+
) {
|
|
323
|
+
let changed = false;
|
|
324
|
+
|
|
325
|
+
if (item.perms) {
|
|
326
|
+
const permCode = item.codeChain.join(".");
|
|
327
|
+
|
|
328
|
+
if (
|
|
329
|
+
type === "edit" &&
|
|
330
|
+
val &&
|
|
331
|
+
this.getIsPermExists(item, "use") &&
|
|
332
|
+
!this.getIsPermChecked(item, "use")
|
|
333
|
+
) {
|
|
334
|
+
// use가 체크되지 않은 상태에서 edit 체크 시도 → 무시
|
|
335
|
+
} else {
|
|
336
|
+
if (this.getIsPermExists(item, type) && value[permCode + "." + type] !== val) {
|
|
337
|
+
value[permCode + "." + type] = val;
|
|
338
|
+
changed = true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// USE권한 지우면 EDIT권한도 자동으로 지움
|
|
343
|
+
if (
|
|
344
|
+
type === "use" &&
|
|
345
|
+
!val &&
|
|
346
|
+
this.getIsPermExists(item, "edit") &&
|
|
347
|
+
value[permCode + ".edit"]
|
|
348
|
+
) {
|
|
349
|
+
value[permCode + ".edit"] = false;
|
|
350
|
+
changed = true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 하위 권한을 함께 변경함
|
|
355
|
+
if (item.children) {
|
|
356
|
+
for (const child of item.children) {
|
|
357
|
+
const childChanged = this._changePermCheck(value, child, type, val);
|
|
358
|
+
if (childChanged) {
|
|
359
|
+
changed = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return changed;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private _getDepthLength(items: ISdPermission<TModule>[], depth: number): number {
|
|
368
|
+
return (
|
|
369
|
+
items.max((item) => {
|
|
370
|
+
if (item.children) {
|
|
371
|
+
return this._getDepthLength(item.children, depth + 1);
|
|
372
|
+
} else {
|
|
373
|
+
return depth + 1;
|
|
374
|
+
}
|
|
375
|
+
}) ?? depth
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
protected readonly itemTemplateType!: {
|
|
380
|
+
item: ISdPermission<TModule>;
|
|
381
|
+
parentKey: string;
|
|
382
|
+
depth: number;
|
|
383
|
+
parent: ISdPermission<TModule> | undefined;
|
|
384
|
+
};
|
|
385
|
+
}
|
|
@@ -255,7 +255,7 @@ export class SdSharedDataSelectControl<
|
|
|
255
255
|
|
|
256
256
|
getItemSelectable(item: TItem, _index: number, depth: number): boolean {
|
|
257
257
|
if (!this.hasParentKey()) return true;
|
|
258
|
-
// depth가 0이면서
|
|
258
|
+
// 트리 구조에서 depth가 0이면서 __parentKey가 있는 항목은 선택 불가
|
|
259
259
|
return depth !== 0 || item.__parentKey == null;
|
|
260
260
|
}
|
|
261
261
|
|
package/src/index.ts
CHANGED
|
@@ -72,6 +72,9 @@ export {
|
|
|
72
72
|
type IAddress,
|
|
73
73
|
} from "./features/address/sd-address-search.modal";
|
|
74
74
|
|
|
75
|
+
// features/permission-table
|
|
76
|
+
export { SdPermissionTableControl } from "./features/permission-table/sd-permission-table.control";
|
|
77
|
+
|
|
75
78
|
// features
|
|
76
79
|
export { SdBaseContainerControl } from "./features/base/sd-base-container.control";
|
|
77
80
|
export {
|
|
@@ -139,6 +139,19 @@ export class SdModalProvider {
|
|
|
139
139
|
activatedModal.modalComponent.set(modalRef.instance);
|
|
140
140
|
activatedModal.contentComponent.set(contentRef.instance);
|
|
141
141
|
|
|
142
|
+
// 7-1. actionTplRef 브릿지: 컨텐츠 컴포넌트 → 모달 컴포넌트
|
|
143
|
+
if ("actionTplRef" in contentRef.instance) {
|
|
144
|
+
let _actionTplRef = contentRef.instance.actionTplRef;
|
|
145
|
+
Object.defineProperty(contentRef.instance, "actionTplRef", {
|
|
146
|
+
get: () => _actionTplRef,
|
|
147
|
+
set: (value: TemplateRef<any> | undefined) => {
|
|
148
|
+
_actionTplRef = value;
|
|
149
|
+
modalRef.setInput("actionTplRef", value);
|
|
150
|
+
},
|
|
151
|
+
configurable: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
142
155
|
// 8. appRef에 뷰 등록 + body에 삽입
|
|
143
156
|
this._appRef.attachView(contentRef.hostView);
|
|
144
157
|
this._appRef.attachView(modalRef.hostView);
|