@simplysm/angular 14.0.18 → 14.0.21

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 (129) hide show
  1. package/dist/core/provideSdAngular.js +1 -1
  2. package/dist/core/providers/sd-activated-modal.provider.d.ts +13 -0
  3. package/dist/core/providers/sd-activated-modal.provider.d.ts.map +1 -0
  4. package/dist/core/providers/sd-activated-modal.provider.js +15 -0
  5. package/dist/core/providers/sd-app-structure.provider.d.ts +3 -64
  6. package/dist/core/providers/sd-app-structure.provider.d.ts.map +1 -1
  7. package/dist/core/providers/sd-app-structure.provider.js +1 -252
  8. package/dist/core/providers/sd-app-structure.types.d.ts +52 -0
  9. package/dist/core/providers/sd-app-structure.types.d.ts.map +1 -0
  10. package/dist/core/providers/sd-app-structure.types.js +1 -0
  11. package/dist/core/providers/sd-app-structure.utils.d.ts +13 -0
  12. package/dist/core/providers/sd-app-structure.utils.d.ts.map +1 -0
  13. package/dist/core/providers/sd-app-structure.utils.js +250 -0
  14. package/dist/{ui/overlay/busy → core/providers}/sd-busy.provider.d.ts +1 -1
  15. package/dist/core/providers/sd-busy.provider.d.ts.map +1 -0
  16. package/dist/{ui/overlay/busy → core/providers}/sd-busy.provider.js +1 -1
  17. package/dist/core/providers/sd-print.provider.js +1 -1
  18. package/dist/core/providers/sd-service-client-factory.provider.js +1 -1
  19. package/dist/{ui/overlay/toast → core/providers}/sd-toast.provider.d.ts +1 -1
  20. package/dist/core/providers/sd-toast.provider.d.ts.map +1 -0
  21. package/dist/{ui/overlay/toast → core/providers}/sd-toast.provider.js +3 -3
  22. package/dist/core/types/select-modal-output-result.d.ts +8 -0
  23. package/dist/core/types/select-modal-output-result.d.ts.map +1 -0
  24. package/dist/core/types/select-modal-output-result.js +1 -0
  25. package/dist/core/utils/setups/setupCanDeactivate.js +1 -1
  26. package/dist/core/utils/useViewTitleSignal.js +1 -1
  27. package/dist/core/utils/useViewTypeSignal.js +1 -1
  28. package/dist/features/base/sd-base-container.control.js +1 -1
  29. package/dist/features/data-view/sd-data-detail.control.js +1 -1
  30. package/dist/features/data-view/sd-data-sheet.control.d.ts +24 -37
  31. package/dist/features/data-view/sd-data-sheet.control.d.ts.map +1 -1
  32. package/dist/features/data-view/sd-data-sheet.control.js +98 -152
  33. package/dist/features/data-view/sd-data-sheet.types.d.ts +17 -0
  34. package/dist/features/data-view/sd-data-sheet.types.d.ts.map +1 -0
  35. package/dist/features/data-view/sd-data-sheet.types.js +1 -0
  36. package/dist/{core/utils/setups → features/data-view}/setupCloserWhenSingleSelectionChange.d.ts +1 -1
  37. package/dist/features/data-view/setupCloserWhenSingleSelectionChange.d.ts.map +1 -0
  38. package/dist/features/data-view/useDataSheetExcelManager.d.ts +14 -0
  39. package/dist/features/data-view/useDataSheetExcelManager.d.ts.map +1 -0
  40. package/dist/features/data-view/useDataSheetExcelManager.js +31 -0
  41. package/dist/features/data-view/useDataSheetFilterManager.d.ts +13 -0
  42. package/dist/features/data-view/useDataSheetFilterManager.d.ts.map +1 -0
  43. package/dist/features/data-view/useDataSheetFilterManager.js +22 -0
  44. package/dist/features/data-view/useDataSheetInlineEditManager.d.ts +26 -0
  45. package/dist/features/data-view/useDataSheetInlineEditManager.d.ts.map +1 -0
  46. package/dist/features/data-view/useDataSheetInlineEditManager.js +54 -0
  47. package/dist/features/data-view/useDataSheetModalEditManager.d.ts +19 -0
  48. package/dist/features/data-view/useDataSheetModalEditManager.d.ts.map +1 -0
  49. package/dist/features/data-view/useDataSheetModalEditManager.js +44 -0
  50. package/dist/features/data-view/useDataSheetRefreshManager.d.ts +25 -0
  51. package/dist/features/data-view/useDataSheetRefreshManager.d.ts.map +1 -0
  52. package/dist/features/data-view/useDataSheetRefreshManager.js +50 -0
  53. package/dist/features/permission-table/sd-permission-table.control.d.ts +1 -1
  54. package/dist/features/permission-table/sd-permission-table.control.d.ts.map +1 -1
  55. package/dist/index.d.ts +12 -6
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +10 -5
  58. package/dist/ui/data/sheet/sd-sheet.control.d.ts +22 -30
  59. package/dist/ui/data/sheet/sd-sheet.control.d.ts.map +1 -1
  60. package/dist/ui/data/sheet/sd-sheet.control.js +52 -210
  61. package/dist/ui/data/sheet/useSheetCellStyling.d.ts +22 -0
  62. package/dist/ui/data/sheet/useSheetCellStyling.d.ts.map +1 -0
  63. package/dist/ui/data/sheet/useSheetCellStyling.js +95 -0
  64. package/dist/ui/data/sheet/useSheetColumnResizing.d.ts +17 -0
  65. package/dist/ui/data/sheet/useSheetColumnResizing.d.ts.map +1 -0
  66. package/dist/ui/data/sheet/useSheetColumnResizing.js +65 -0
  67. package/dist/ui/data/sheet/useSheetDisplayPipeline.d.ts +24 -0
  68. package/dist/ui/data/sheet/useSheetDisplayPipeline.d.ts.map +1 -0
  69. package/dist/ui/data/sheet/useSheetDisplayPipeline.js +52 -0
  70. package/dist/ui/form/button/sd-modal-select-button.control.d.ts +1 -7
  71. package/dist/ui/form/button/sd-modal-select-button.control.d.ts.map +1 -1
  72. package/dist/ui/form/button/sd-modal-select-button.control.js +1 -1
  73. package/dist/ui/form/choice/sd-state-preset.control.js +1 -1
  74. package/dist/ui/navigation/menu-utils.d.ts +2 -7
  75. package/dist/ui/navigation/menu-utils.d.ts.map +1 -1
  76. package/dist/ui/navigation/topbar/sd-topbar.control.d.ts.map +1 -1
  77. package/dist/ui/navigation/topbar/sd-topbar.control.js +4 -3
  78. package/dist/ui/overlay/busy/sd-busy-container.control.d.ts +1 -1
  79. package/dist/ui/overlay/busy/sd-busy-container.control.d.ts.map +1 -1
  80. package/dist/ui/overlay/busy/sd-busy-container.control.js +1 -1
  81. package/dist/ui/overlay/modal/sd-modal.control.d.ts.map +1 -1
  82. package/dist/ui/overlay/modal/sd-modal.control.js +10 -14
  83. package/dist/ui/overlay/modal/sd-modal.provider.d.ts +0 -10
  84. package/dist/ui/overlay/modal/sd-modal.provider.d.ts.map +1 -1
  85. package/dist/ui/overlay/modal/sd-modal.provider.js +27 -27
  86. package/dist/ui/overlay/toast/sd-toast.control.d.ts +1 -1
  87. package/dist/ui/overlay/toast/sd-toast.control.d.ts.map +1 -1
  88. package/package.json +5 -5
  89. package/src/core/provideSdAngular.ts +1 -1
  90. package/src/core/providers/sd-activated-modal.provider.ts +12 -0
  91. package/src/core/providers/sd-app-structure.provider.ts +2 -405
  92. package/src/core/providers/sd-app-structure.types.ts +60 -0
  93. package/src/core/providers/sd-app-structure.utils.ts +350 -0
  94. package/src/{ui/overlay/busy → core/providers}/sd-busy.provider.ts +1 -1
  95. package/src/core/providers/sd-print.provider.ts +1 -1
  96. package/src/core/providers/sd-service-client-factory.provider.ts +1 -1
  97. package/src/{ui/overlay/toast → core/providers}/sd-toast.provider.ts +4 -4
  98. package/src/core/types/select-modal-output-result.ts +7 -0
  99. package/src/core/utils/setups/setupCanDeactivate.ts +1 -1
  100. package/src/core/utils/useViewTitleSignal.ts +1 -1
  101. package/src/core/utils/useViewTypeSignal.ts +1 -1
  102. package/src/features/base/sd-base-container.control.ts +1 -1
  103. package/src/features/data-view/sd-data-detail.control.ts +1 -1
  104. package/src/features/data-view/sd-data-sheet.control.ts +117 -216
  105. package/src/features/data-view/sd-data-sheet.types.ts +18 -0
  106. package/src/{core/utils/setups → features/data-view}/setupCloserWhenSingleSelectionChange.ts +1 -1
  107. package/src/features/data-view/useDataSheetExcelManager.ts +57 -0
  108. package/src/features/data-view/useDataSheetFilterManager.ts +30 -0
  109. package/src/features/data-view/useDataSheetInlineEditManager.ts +89 -0
  110. package/src/features/data-view/useDataSheetModalEditManager.ts +76 -0
  111. package/src/features/data-view/useDataSheetRefreshManager.ts +90 -0
  112. package/src/features/permission-table/sd-permission-table.control.ts +1 -1
  113. package/src/index.ts +17 -11
  114. package/src/ui/data/sheet/sd-sheet.control.ts +50 -238
  115. package/src/ui/data/sheet/useSheetCellStyling.ts +113 -0
  116. package/src/ui/data/sheet/useSheetColumnResizing.ts +92 -0
  117. package/src/ui/data/sheet/useSheetDisplayPipeline.ts +64 -0
  118. package/src/ui/form/button/sd-modal-select-button.control.ts +1 -8
  119. package/src/ui/form/choice/sd-state-preset.control.ts +1 -1
  120. package/src/ui/navigation/menu-utils.ts +3 -7
  121. package/src/ui/navigation/topbar/sd-topbar.control.ts +2 -1
  122. package/src/ui/overlay/busy/sd-busy-container.control.ts +1 -1
  123. package/src/ui/overlay/modal/sd-modal.control.ts +224 -1
  124. package/src/ui/overlay/modal/sd-modal.provider.ts +31 -26
  125. package/src/ui/overlay/toast/sd-toast.control.ts +1 -1
  126. package/dist/core/utils/setups/setupCloserWhenSingleSelectionChange.d.ts.map +0 -1
  127. package/dist/ui/overlay/busy/sd-busy.provider.d.ts.map +0 -1
  128. package/dist/ui/overlay/toast/sd-toast.provider.d.ts.map +0 -1
  129. /package/dist/{core/utils/setups → features/data-view}/setupCloserWhenSingleSelectionChange.js +0 -0
@@ -7,7 +7,6 @@ import {
7
7
  contentChildren,
8
8
  Directive,
9
9
  effect,
10
- inject,
11
10
  input,
12
11
  type InputSignal,
13
12
  model,
@@ -16,34 +15,34 @@ import {
16
15
  signal,
17
16
  type Signal,
18
17
  TemplateRef,
18
+ type WritableSignal,
19
19
  viewChild,
20
20
  ViewEncapsulation,
21
21
  } from "@angular/core";
22
- import { type ArrayOneWayDiffResult, obj } from "@simplysm/core-common";
22
+ import type { ArrayOneWayDiffResult } from "@simplysm/core-common";
23
23
  import { mark } from "../../core/utils/mark";
24
24
  import { SdButtonControl } from "../../ui/form/button/sd-button.control";
25
25
  import { SdFormControl } from "../../ui/form/sd-form.control";
26
26
  import { SdSheetColumnDirective } from "../../ui/data/sheet/sd-sheet-column.directive";
27
27
  import { SdSheetControl, type ISortingDef } from "../../ui/data/sheet/sd-sheet.control";
28
- import { SdFileDialogProvider } from "../../core/providers/sd-file-dialog.provider";
29
- import { SdToastProvider } from "../../ui/overlay/toast/sd-toast.provider";
30
- import { SdSharedDataProvider } from "../../core/providers/sd-shared-data.provider";
31
28
  import { useViewTypeSignal } from "../../core/utils/useViewTypeSignal";
32
29
  import { setupCumulateSelectedKeys } from "../../core/utils/setups/setupCumulateSelectedKeys";
33
- import { setupCloserWhenSingleSelectionChange } from "../../core/utils/setups/setupCloserWhenSingleSelectionChange";
30
+ import { setupCloserWhenSingleSelectionChange } from "./setupCloserWhenSingleSelectionChange";
34
31
  import { setupCanDeactivate } from "../../core/utils/setups/setupCanDeactivate";
35
32
  import { injectParent } from "../../core/utils/injectParent";
36
- import { withBusy } from "../../core/utils/withBusy";
37
33
  import { FormatPipe } from "../../core/pipes/format.pipe";
38
34
  import { TXT_CHANGE_IGNORE_CONFIRM } from "../../core/commons";
39
35
  import { SdBaseContainerControl } from "../base/sd-base-container.control";
40
36
  import { SdDataSheetColumnDirective } from "./sd-data-sheet-column.directive";
41
- import type {
42
- ISdSelectModal,
43
- ISelectModalOutputResult,
44
- } from "../../ui/form/button/sd-modal-select-button.control";
37
+ import type { ISdSelectModal } from "../../ui/form/button/sd-modal-select-button.control";
38
+ import type { ISelectModalOutputResult } from "../../core/types/select-modal-output-result";
45
39
  import { SdAnchorControl } from "../../ui/form/button/sd-anchor.control";
46
40
  import { NgIcon } from "@ng-icons/core";
41
+ import { useDataSheetFilterManager } from "./useDataSheetFilterManager";
42
+ import { useDataSheetRefreshManager } from "./useDataSheetRefreshManager";
43
+ import { useDataSheetInlineEditManager } from "./useDataSheetInlineEditManager";
44
+ import { useDataSheetModalEditManager } from "./useDataSheetModalEditManager";
45
+ import { useDataSheetExcelManager } from "./useDataSheetExcelManager";
47
46
  import {
48
47
  tablerDeviceFloppy,
49
48
  tablerEdit,
@@ -56,28 +55,16 @@ import {
56
55
  tablerUpload,
57
56
  } from "@ng-icons/tabler-icons";
58
57
 
59
- //#region Interfaces
60
-
61
- export interface ISdDataSheetItemPropInfo<I> {
62
- isDeleted: (keyof I & string) | undefined;
63
- lastModifiedAt: (keyof I & string) | undefined;
64
- lastModifiedBy: (keyof I & string) | undefined;
65
- }
66
-
67
- export interface ISdDataSheetItemInfo<K> {
68
- key: K;
69
- canSelect: boolean;
70
- canEdit: boolean;
71
- canDelete: boolean;
72
- }
73
-
74
- export interface ISdDataSheetSearchResult<I> {
75
- items: I[];
76
- pageLength?: number;
77
- summary?: Partial<I>;
78
- }
79
-
80
- //#endregion
58
+ export type {
59
+ ISdDataSheetItemPropInfo,
60
+ ISdDataSheetItemInfo,
61
+ ISdDataSheetSearchResult,
62
+ } from "./sd-data-sheet.types";
63
+ import type {
64
+ ISdDataSheetItemPropInfo,
65
+ ISdDataSheetItemInfo,
66
+ ISdDataSheetSearchResult,
67
+ } from "./sd-data-sheet.types";
81
68
 
82
69
  //#region AbsSdDataSheet
83
70
 
@@ -116,12 +103,18 @@ export abstract class AbsSdDataSheet<
116
103
  downloadExcel?(items: TItem[]): Promise<void> | void;
117
104
  uploadExcel?(file: File): Promise<void> | void;
118
105
 
119
- //-- injected
120
- private readonly _sdToast = inject(SdToastProvider);
121
- private readonly _sdSharedData = inject(SdSharedDataProvider);
122
- private readonly _sdFileDialog = inject(SdFileDialogProvider);
123
-
124
- //-- state
106
+ //-- composable instances
107
+ private readonly _filterMgr: ReturnType<typeof useDataSheetFilterManager<TFilter>>;
108
+ private readonly _refreshMgr: ReturnType<typeof useDataSheetRefreshManager<TItem, TKey>>;
109
+ private readonly _inlineEditMgr: ReturnType<
110
+ typeof useDataSheetInlineEditManager<TItem, TKey>
111
+ >;
112
+ private readonly _modalEditMgr: ReturnType<
113
+ typeof useDataSheetModalEditManager<TItem, TKey>
114
+ >;
115
+ private readonly _excelMgr: ReturnType<typeof useDataSheetExcelManager<TItem>>;
116
+
117
+ //-- shared state (D1: class 소유)
125
118
  key = reflectComponentType(this.constructor as any)?.selector;
126
119
 
127
120
  viewType = useViewTypeSignal(() => this);
@@ -150,11 +143,9 @@ export abstract class AbsSdDataSheet<
150
143
  pageLength = signal(0);
151
144
  sortingDefs = signal<ISortingDef[]>([]);
152
145
 
153
- filter = signal<TFilter>({} as TFilter);
154
- lastFilter = signal<TFilter>({} as TFilter);
155
-
156
- //-- change tracking
157
- private _itemsSnapshot: TItem[] = [];
146
+ //-- filter state (composable에서 생성, 재할당)
147
+ filter!: WritableSignal<TFilter>;
148
+ lastFilter!: WritableSignal<TFilter>;
158
149
 
159
150
  //-- computed
160
151
  isSelectedItemsHasDeleted = computed(() =>
@@ -196,56 +187,84 @@ export abstract class AbsSdDataSheet<
196
187
  close: this.close,
197
188
  });
198
189
 
199
- effect(() => {
200
- const filter = this.bindFilter();
201
- this.filter.set(filter);
202
- this.lastFilter.set(obj.clone(filter));
190
+ //-- filter composable
191
+ this._filterMgr = useDataSheetFilterManager({
192
+ bindFilter: () => this.bindFilter(),
193
+ busyCount: this.busyCount,
194
+ canUse: () => this.canUse(),
195
+ page: this.page,
196
+ checkIgnoreChanges: () => this.checkIgnoreChanges(),
197
+ });
198
+ this.filter = this._filterMgr.filter;
199
+ this.lastFilter = this._filterMgr.lastFilter;
200
+
201
+ //-- refresh composable
202
+ this._refreshMgr = useDataSheetRefreshManager({
203
+ busyCount: this.busyCount,
204
+ initialized: this.initialized,
205
+ canUse: () => this.canUse(),
206
+ items: this.items,
207
+ selectedItems: this.selectedItems,
208
+ pageLength: this.pageLength,
209
+ summaryData: this.summaryData,
210
+ page: this.page,
211
+ lastFilter: this.lastFilter,
212
+ sortingDefs: this.sortingDefs,
213
+ getItemInfoFn: (item) => this.getItemInfoFn(item),
214
+ search: (p) => this.search(p),
215
+ prepareRefreshEffect: () => this.prepareRefreshEffect?.(),
216
+ getDiffsExcludes: () => this.diffsExcludes,
203
217
  });
204
218
 
205
- effect((onCleanup) => {
206
- this.page();
207
- this.lastFilter();
208
- this.sortingDefs();
209
- this.prepareRefreshEffect?.();
210
-
211
- let cancelled = false;
212
- onCleanup(() => {
213
- cancelled = true;
214
- });
215
-
216
- queueMicrotask(async () => {
217
- if (cancelled) return;
218
- if (!this.canUse()) {
219
- this.initialized.set(true);
220
- return;
221
- }
219
+ //-- inline edit composable
220
+ this._inlineEditMgr = useDataSheetInlineEditManager({
221
+ busyCount: this.busyCount,
222
+ canEdit: () => this.canEdit(),
223
+ items: this.items,
224
+ submitted: this.submitted,
225
+ itemPropInfo: () => this.itemPropInfo,
226
+ getItemInfoFn: (item) => this.getItemInfoFn(item),
227
+ getDiffs: () => this._refreshMgr.getDiffs(),
228
+ refresh: () => this._refreshMgr.refresh(),
229
+ getNewItemFn: () => this.newItem?.bind(this),
230
+ getSubmitFn: () => this.submit?.bind(this),
231
+ errorMessageFn: (err) => this._getOrmDataEditToastErrorMessage(err),
232
+ });
222
233
 
223
- await withBusy(this.busyCount, () =>
224
- this._sdToast.try(async () => {
225
- await this._sdSharedData.wait();
226
- await this.refresh();
227
- }),
228
- );
229
- this.initialized.set(true);
230
- });
234
+ //-- modal edit composable
235
+ this._modalEditMgr = useDataSheetModalEditManager({
236
+ busyCount: this.busyCount,
237
+ canEdit: () => this.canEdit(),
238
+ selectedItemKeys: this.selectedItemKeys,
239
+ selectedItems: this.selectedItems,
240
+ close: this.close,
241
+ refresh: () => this._refreshMgr.refresh(),
242
+ getEditItemFn: () => this.editItem?.bind(this),
243
+ getToggleDeleteItemsFn: () => this.toggleDeleteItems?.bind(this),
244
+ errorMessageFn: (err) => this._getOrmDataEditToastErrorMessage(err),
245
+ });
246
+
247
+ //-- excel composable
248
+ this._excelMgr = useDataSheetExcelManager({
249
+ busyCount: this.busyCount,
250
+ search: (p) => this.search(p),
251
+ refresh: () => this._refreshMgr.refresh(),
252
+ getDownloadExcelFn: () => this.downloadExcel?.bind(this),
253
+ getUploadExcelFn: () => this.uploadExcel?.bind(this),
254
+ errorMessageFn: (err) => this._getOrmDataEditToastErrorMessage(err),
231
255
  });
232
256
 
233
257
  setupCanDeactivate(() => this.viewType() === "modal" || this.checkIgnoreChanges());
234
258
  }
235
259
 
236
- //-- query
260
+ //-- D2: class 메서드 (커스터마이징 가능)
237
261
 
238
262
  checkIgnoreChanges() {
239
- return this._getDiffs().length === 0 || confirm(TXT_CHANGE_IGNORE_CONFIRM);
263
+ return this._refreshMgr.getDiffs().length === 0 || confirm(TXT_CHANGE_IGNORE_CONFIRM);
240
264
  }
241
265
 
242
266
  doFilterSubmit() {
243
- if (this.busyCount() > 0) return;
244
- if (!this.canUse()) return;
245
- if (!this.checkIgnoreChanges()) return;
246
-
247
- this.page.set(0);
248
- this.lastFilter.set(obj.clone(this.filter()));
267
+ this._filterMgr.doFilterSubmit();
249
268
  }
250
269
 
251
270
  doRefresh() {
@@ -257,171 +276,53 @@ export abstract class AbsSdDataSheet<
257
276
  }
258
277
 
259
278
  async refresh() {
260
- const result = await this.search(true);
261
- this.items.set(result.items);
262
- this._itemsSnapshot = obj.clone(result.items);
263
-
264
- this.pageLength.set(result.pageLength ?? 0);
265
- this.summaryData.set(result.summary ?? {});
266
-
267
- const selectedKeySet = new Set(
268
- this.selectedItems().map((sel) => this.getItemInfoFn(sel).key),
269
- );
270
- this.selectedItems.set(
271
- this.items().filter((item) => selectedKeySet.has(this.getItemInfoFn(item).key)),
272
- );
279
+ await this._refreshMgr.refresh();
273
280
  }
274
281
 
275
- //-- inline edit
282
+ //-- inline edit (delegated to composable)
276
283
 
277
284
  async doAddItem() {
278
- if (!this.newItem) return;
279
-
280
- await withBusy(this.busyCount, () =>
281
- this._sdToast.try(async () => {
282
- const newItem = await this.newItem!();
283
- this.items.update((items) => [newItem, ...items]);
284
- }),
285
- );
285
+ await this._inlineEditMgr.doAddItem();
286
286
  }
287
287
 
288
288
  async doSubmit(opt?: { permCheck?: boolean; hideNoChangeMessage?: boolean }) {
289
- if (this.busyCount() > 0) return;
290
- if (opt?.permCheck && !this.canEdit()) return;
291
- if (!this.submit) return;
292
-
293
- const diffs = this._getDiffs();
294
-
295
- if (diffs.length === 0) {
296
- if (!opt?.hideNoChangeMessage) {
297
- this._sdToast.info("변경사항이 없습니다.");
298
- }
299
- return;
300
- }
301
-
302
- await withBusy(this.busyCount, () =>
303
- this._sdToast.try(
304
- async () => {
305
- const result = await this.submit!(diffs);
306
- if (!result) return;
307
-
308
- this._sdToast.success("저장되었습니다.");
309
- await this.refresh();
310
- this.submitted.emit(true);
311
- },
312
- (err) => this._getOrmDataEditToastErrorMessage(err),
313
- ),
314
- );
289
+ await this._inlineEditMgr.doSubmit(opt);
315
290
  }
316
291
 
317
292
  doToggleDeleteItem(item: TItem) {
318
- if (!this.canEdit()) return;
319
- if (this.itemPropInfo.isDeleted == null) return;
320
-
321
- if (this.getItemInfoFn(item).key == null) {
322
- this.items.update((items) => items.filter((item1) => item1 !== item));
323
- return;
324
- }
325
-
326
- (item[this.itemPropInfo.isDeleted] as boolean) = !(item[
327
- this.itemPropInfo.isDeleted
328
- ] as boolean);
329
- mark(this.items);
293
+ this._inlineEditMgr.doToggleDeleteItem(item);
330
294
  }
331
295
 
332
- //-- modal edit
296
+ //-- modal edit (delegated to composable)
333
297
 
334
298
  async doEditItem(item?: TItem) {
335
- if (!this.editItem) return;
336
-
337
- const result = await this.editItem(item);
338
- if (!result) return;
339
-
340
- await withBusy(this.busyCount, () =>
341
- this._sdToast.try(async () => {
342
- await this.refresh();
343
- }),
344
- );
299
+ await this._modalEditMgr.doEditItem(item);
345
300
  }
346
301
 
347
302
  async doToggleDeleteItems(del: boolean) {
348
- if (!this.canEdit()) return;
349
- if (!this.toggleDeleteItems) return;
350
-
351
- await withBusy(this.busyCount, () =>
352
- this._sdToast.try(
353
- async () => {
354
- const result = await this.toggleDeleteItems!(del);
355
- if (!result) return;
356
-
357
- await this.refresh();
358
- this._sdToast.success(`${del ? "삭제" : "복구"} 되었습니다.`);
359
- },
360
- (err) => this._getOrmDataEditToastErrorMessage(err),
361
- ),
362
- );
303
+ await this._modalEditMgr.doToggleDeleteItems(del);
363
304
  }
364
305
 
365
- //-- excel
366
-
367
- async doDownloadExcel() {
368
- if (!this.downloadExcel) return;
369
-
370
- await withBusy(this.busyCount, () =>
371
- this._sdToast.try(async () => {
372
- const items = (await this.search(false)).items;
373
- await this.downloadExcel!(items);
374
- }),
375
- );
306
+ doModalConfirm() {
307
+ this._modalEditMgr.doModalConfirm();
376
308
  }
377
309
 
378
- async doUploadExcel() {
379
- if (!this.uploadExcel) return;
380
-
381
- const file = await this._sdFileDialog.showAsync(
382
- false,
383
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
384
- );
385
- if (!file) return;
386
-
387
- await withBusy(this.busyCount, () =>
388
- this._sdToast.try(
389
- async () => {
390
- await this.uploadExcel!(file);
391
- await this.refresh();
392
- this._sdToast.success("엑셀 업로드가 완료 되었습니다.");
393
- },
394
- (err) => this._getOrmDataEditToastErrorMessage(err),
395
- ),
396
- );
310
+ doModalCancel() {
311
+ this._modalEditMgr.doModalCancel();
397
312
  }
398
313
 
399
- //-- modal selection
314
+ //-- excel (delegated to composable)
400
315
 
401
- doModalConfirm() {
402
- this.close.emit({
403
- selectedItemKeys: this.selectedItemKeys(),
404
- selectedItems: this.selectedItems(),
405
- });
316
+ async doDownloadExcel() {
317
+ await this._excelMgr.doDownloadExcel();
406
318
  }
407
319
 
408
- doModalCancel() {
409
- this.close.emit({
410
- selectedItemKeys: [],
411
- selectedItems: [],
412
- });
320
+ async doUploadExcel() {
321
+ await this._excelMgr.doUploadExcel();
413
322
  }
414
323
 
415
324
  //-- private
416
325
 
417
- private _getDiffs(): ArrayOneWayDiffResult<TItem>[] {
418
- return this.items().oneWayDiffs(
419
- this._itemsSnapshot,
420
- (item) => this.getItemInfoFn(item).key,
421
- this.diffsExcludes ? { excludes: this.diffsExcludes } : undefined,
422
- ).filter((d) => d.type !== "same");
423
- }
424
-
425
326
  private _getOrmDataEditToastErrorMessage(err: unknown) {
426
327
  const message = err instanceof Error ? err.message : String(err);
427
328
  if (
@@ -0,0 +1,18 @@
1
+ export interface ISdDataSheetItemPropInfo<I> {
2
+ isDeleted: (keyof I & string) | undefined;
3
+ lastModifiedAt: (keyof I & string) | undefined;
4
+ lastModifiedBy: (keyof I & string) | undefined;
5
+ }
6
+
7
+ export interface ISdDataSheetItemInfo<K> {
8
+ key: K;
9
+ canSelect: boolean;
10
+ canEdit: boolean;
11
+ canDelete: boolean;
12
+ }
13
+
14
+ export interface ISdDataSheetSearchResult<I> {
15
+ items: I[];
16
+ pageLength?: number;
17
+ summary?: Partial<I>;
18
+ }
@@ -1,5 +1,5 @@
1
1
  import { type OutputEmitterRef, type Signal, effect } from "@angular/core";
2
- import type { ISelectModalOutputResult } from "../../../ui/form/button/sd-modal-select-button.control";
2
+ import type { ISelectModalOutputResult } from "../../core/types/select-modal-output-result";
3
3
 
4
4
  export function setupCloserWhenSingleSelectionChange<TItem, TKey>(options: {
5
5
  selectedItemKeys: Signal<TKey[]>;
@@ -0,0 +1,57 @@
1
+ import { type WritableSignal, inject } from "@angular/core";
2
+ import { SdToastProvider } from "../../core/providers/sd-toast.provider";
3
+ import { SdFileDialogProvider } from "../../core/providers/sd-file-dialog.provider";
4
+ import { withBusy } from "../../core/utils/withBusy";
5
+ import type { ISdDataSheetSearchResult } from "./sd-data-sheet.types";
6
+
7
+ export function useDataSheetExcelManager<TItem>(options: {
8
+ busyCount: WritableSignal<number>;
9
+ search: (
10
+ usePagination: boolean,
11
+ ) => Promise<ISdDataSheetSearchResult<TItem>> | ISdDataSheetSearchResult<TItem>;
12
+ refresh: () => Promise<void>;
13
+ getDownloadExcelFn: () =>
14
+ | ((items: TItem[]) => Promise<void> | void)
15
+ | undefined;
16
+ getUploadExcelFn: () => ((file: File) => Promise<void> | void) | undefined;
17
+ errorMessageFn: (err: unknown) => string;
18
+ }) {
19
+ const sdToast = inject(SdToastProvider);
20
+ const sdFileDialog = inject(SdFileDialogProvider);
21
+
22
+ async function doDownloadExcel(): Promise<void> {
23
+ const downloadExcelFn = options.getDownloadExcelFn();
24
+ if (!downloadExcelFn) return;
25
+
26
+ await withBusy(options.busyCount, () =>
27
+ sdToast.try(async () => {
28
+ const items = (await options.search(false)).items;
29
+ await downloadExcelFn(items);
30
+ }),
31
+ );
32
+ }
33
+
34
+ async function doUploadExcel(): Promise<void> {
35
+ const uploadExcelFn = options.getUploadExcelFn();
36
+ if (!uploadExcelFn) return;
37
+
38
+ const file = await sdFileDialog.showAsync(
39
+ false,
40
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
41
+ );
42
+ if (!file) return;
43
+
44
+ await withBusy(options.busyCount, () =>
45
+ sdToast.try(
46
+ async () => {
47
+ await uploadExcelFn(file);
48
+ await options.refresh();
49
+ sdToast.success("엑셀 업로드가 완료 되었습니다.");
50
+ },
51
+ (err) => options.errorMessageFn(err),
52
+ ),
53
+ );
54
+ }
55
+
56
+ return { doDownloadExcel, doUploadExcel };
57
+ }
@@ -0,0 +1,30 @@
1
+ import { type Signal, type WritableSignal, effect, signal } from "@angular/core";
2
+ import { obj } from "@simplysm/core-common";
3
+
4
+ export function useDataSheetFilterManager<TFilter extends Record<string, any>>(options: {
5
+ bindFilter: () => TFilter;
6
+ busyCount: Signal<number>;
7
+ canUse: () => boolean;
8
+ page: WritableSignal<number>;
9
+ checkIgnoreChanges: () => boolean;
10
+ }) {
11
+ const filter = signal<TFilter>({} as TFilter);
12
+ const lastFilter = signal<TFilter>({} as TFilter);
13
+
14
+ effect(() => {
15
+ const f = options.bindFilter();
16
+ filter.set(f);
17
+ lastFilter.set(obj.clone(f));
18
+ });
19
+
20
+ function doFilterSubmit(): void {
21
+ if (options.busyCount() > 0) return;
22
+ if (!options.canUse()) return;
23
+ if (!options.checkIgnoreChanges()) return;
24
+
25
+ options.page.set(0);
26
+ lastFilter.set(obj.clone(filter()));
27
+ }
28
+
29
+ return { filter, lastFilter, doFilterSubmit };
30
+ }
@@ -0,0 +1,89 @@
1
+ import {
2
+ type OutputEmitterRef,
3
+ type WritableSignal,
4
+ inject,
5
+ } from "@angular/core";
6
+ import type { ArrayOneWayDiffResult } from "@simplysm/core-common";
7
+ import { mark } from "../../core/utils/mark";
8
+ import { SdToastProvider } from "../../core/providers/sd-toast.provider";
9
+ import { withBusy } from "../../core/utils/withBusy";
10
+ import type { ISdDataSheetItemPropInfo } from "./sd-data-sheet.types";
11
+
12
+ export function useDataSheetInlineEditManager<TItem, TKey>(options: {
13
+ busyCount: WritableSignal<number>;
14
+ canEdit: () => boolean;
15
+ items: WritableSignal<TItem[]>;
16
+ submitted: OutputEmitterRef<boolean>;
17
+ itemPropInfo: () => ISdDataSheetItemPropInfo<TItem>;
18
+ getItemInfoFn: (item: TItem) => { key: TKey };
19
+ getDiffs: () => ArrayOneWayDiffResult<TItem>[];
20
+ refresh: () => Promise<void>;
21
+ getNewItemFn: () => (() => Promise<TItem> | TItem) | undefined;
22
+ getSubmitFn: () =>
23
+ | ((diffs: ArrayOneWayDiffResult<TItem>[]) => Promise<boolean> | boolean)
24
+ | undefined;
25
+ errorMessageFn: (err: unknown) => string;
26
+ }) {
27
+ const sdToast = inject(SdToastProvider);
28
+
29
+ async function doAddItem(): Promise<void> {
30
+ const newItemFn = options.getNewItemFn();
31
+ if (!newItemFn) return;
32
+
33
+ await withBusy(options.busyCount, () =>
34
+ sdToast.try(async () => {
35
+ const newItem = await newItemFn();
36
+ options.items.update((items) => [newItem, ...items]);
37
+ }),
38
+ );
39
+ }
40
+
41
+ async function doSubmit(opt?: {
42
+ permCheck?: boolean;
43
+ hideNoChangeMessage?: boolean;
44
+ }): Promise<void> {
45
+ if (options.busyCount() > 0) return;
46
+ if (opt?.permCheck && !options.canEdit()) return;
47
+ const submitFn = options.getSubmitFn();
48
+ if (!submitFn) return;
49
+
50
+ const diffs = options.getDiffs();
51
+
52
+ if (diffs.length === 0) {
53
+ if (!opt?.hideNoChangeMessage) {
54
+ sdToast.info("변경사항이 없습니다.");
55
+ }
56
+ return;
57
+ }
58
+
59
+ await withBusy(options.busyCount, () =>
60
+ sdToast.try(
61
+ async () => {
62
+ const result = await submitFn(diffs);
63
+ if (!result) return;
64
+
65
+ sdToast.success("저장되었습니다.");
66
+ await options.refresh();
67
+ options.submitted.emit(true);
68
+ },
69
+ (err) => options.errorMessageFn(err),
70
+ ),
71
+ );
72
+ }
73
+
74
+ function doToggleDeleteItem(item: TItem): void {
75
+ if (!options.canEdit()) return;
76
+ const propInfo = options.itemPropInfo();
77
+ if (propInfo.isDeleted == null) return;
78
+
79
+ if (options.getItemInfoFn(item).key == null) {
80
+ options.items.update((items) => items.filter((item1) => item1 !== item));
81
+ return;
82
+ }
83
+
84
+ (item[propInfo.isDeleted] as boolean) = !(item[propInfo.isDeleted] as boolean);
85
+ mark(options.items);
86
+ }
87
+
88
+ return { doAddItem, doSubmit, doToggleDeleteItem };
89
+ }