@simplysm/angular 14.0.48 → 14.0.51

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 (144) hide show
  1. package/README.md +234 -226
  2. package/dist/controls/select/sd-select.js +3 -3
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +0 -2
  6. package/dist/layout/dock/sd-dock-container.js +1 -1
  7. package/dist/styles.css +9 -0
  8. package/docs/bootstrap/provide-sd-angular.md +37 -0
  9. package/docs/bootstrap/sd-angular-config-provider.md +16 -0
  10. package/docs/directives/sd-command-directive.md +30 -0
  11. package/docs/directives/sd-events.md +25 -0
  12. package/docs/directives/sd-intersection-directive.md +36 -0
  13. package/docs/directives/sd-invalid.md +24 -0
  14. package/docs/directives/sd-resize-directive.md +42 -0
  15. package/docs/directives/sd-ripple.md +23 -0
  16. package/docs/directives/sd-router-link.md +38 -0
  17. package/docs/directives/sd-show-effect.md +18 -0
  18. package/docs/directives/sd-typed-template.md +69 -0
  19. package/docs/features/sd-address-search-modal.md +50 -0
  20. package/docs/features/sd-permission-table.md +20 -0
  21. package/docs/features/sd-shared-data-components.md +158 -0
  22. package/docs/features/sd-tiptap-editor.md +26 -0
  23. package/docs/{pipes.md → pipes/format-pipe.md} +14 -5
  24. package/docs/plugins/sd-global-error-handler.md +23 -0
  25. package/docs/{plugins.md → plugins/sd-option-event-plugin.md} +9 -12
  26. package/docs/provider-types/sd-menu.md +65 -0
  27. package/docs/provider-types/sd-modal-content-def.md +148 -0
  28. package/docs/provider-types/sd-toast-content-def.md +73 -0
  29. package/docs/provider-types/shared-data-base.md +59 -0
  30. package/docs/providers/sd-activated-modal-provider.md +34 -0
  31. package/docs/providers/sd-app-structure-provider.md +81 -0
  32. package/docs/providers/sd-busy-provider.md +18 -0
  33. package/docs/providers/sd-file-dialog-provider.md +40 -0
  34. package/docs/providers/sd-local-storage-provider.md +20 -0
  35. package/docs/providers/sd-modal-provider.md +67 -0
  36. package/docs/providers/sd-navigate-window-provider.md +18 -0
  37. package/docs/providers/sd-print-provider.md +25 -0
  38. package/docs/providers/sd-service-client-factory-provider.md +43 -0
  39. package/docs/providers/sd-shared-data-provider.md +64 -0
  40. package/docs/providers/sd-system-config-provider.md +46 -0
  41. package/docs/providers/sd-system-log-provider.md +18 -0
  42. package/docs/providers/sd-theme-provider.md +38 -0
  43. package/docs/providers/sd-toast-provider.md +65 -0
  44. package/docs/recipes/_common-rules.md +244 -0
  45. package/docs/recipes/crud-detail/extension-a-edit-save.md +230 -0
  46. package/docs/recipes/crud-detail/extension-b-delete-restore.md +142 -0
  47. package/docs/recipes/crud-detail/extension-c-modal-view.md +214 -0
  48. package/docs/recipes/crud-detail/extension-d-control-view.md +103 -0
  49. package/docs/recipes/crud-detail/extension-e-auxiliary.md +87 -0
  50. package/docs/recipes/crud-detail/extension-f-complex-detail.md +234 -0
  51. package/docs/recipes/crud-detail.md +215 -722
  52. package/docs/recipes/crud-list/extension-a-inline-edit.md +410 -0
  53. package/docs/recipes/crud-list/extension-b-selection.md +226 -0
  54. package/docs/recipes/crud-list/extension-c-inline-delete.md +87 -0
  55. package/docs/recipes/crud-list/extension-d-select-modal.md +207 -0
  56. package/docs/recipes/crud-list/extension-e-readonly-modal.md +165 -0
  57. package/docs/recipes/crud-list/extension-f-modal-edit.md +177 -0
  58. package/docs/recipes/crud-list/extension-g-excel.md +157 -0
  59. package/docs/recipes/crud-list.md +293 -626
  60. package/docs/recipes/data-select-button.md +194 -101
  61. package/docs/recipes/page-modal-container.md +168 -86
  62. package/docs/styling/classes.md +149 -0
  63. package/docs/styling/mixins.md +100 -0
  64. package/docs/styling/themes.md +35 -0
  65. package/docs/styling/variables.md +147 -0
  66. package/docs/{type-utilities.md → type-utilities/directive-input-signals.md} +17 -35
  67. package/docs/ui-data/sd-list.md +37 -0
  68. package/docs/ui-data/sd-sheet.md +227 -0
  69. package/docs/ui-form/sd-additional-button.md +26 -0
  70. package/docs/ui-form/sd-anchor.md +31 -0
  71. package/docs/ui-form/sd-button.md +105 -0
  72. package/docs/ui-form/sd-checkbox-group.md +39 -0
  73. package/docs/ui-form/sd-checkbox.md +81 -0
  74. package/docs/ui-form/sd-date-range-picker.md +27 -0
  75. package/docs/ui-form/sd-form.md +89 -0
  76. package/docs/ui-form/sd-modal-select-button.md +54 -0
  77. package/docs/ui-form/sd-numpad.md +26 -0
  78. package/docs/ui-form/sd-range.md +26 -0
  79. package/docs/ui-form/sd-select.md +68 -0
  80. package/docs/ui-form/sd-shared-data-select.md +52 -0
  81. package/docs/ui-form/sd-state-preset.md +37 -0
  82. package/docs/ui-form/sd-switch.md +27 -0
  83. package/docs/ui-form/sd-textarea.md +33 -0
  84. package/docs/ui-form/sd-textfield.md +145 -0
  85. package/docs/ui-layout/sd-dock-container.md +64 -0
  86. package/docs/ui-layout/sd-dock.md +37 -0
  87. package/docs/ui-layout/sd-gap.md +26 -0
  88. package/docs/{ui-layout.md → ui-layout/sd-kanban-board.md} +41 -85
  89. package/docs/ui-layout/sd-kanban-lane.md +34 -0
  90. package/docs/ui-layout/sd-kanban.md +29 -0
  91. package/docs/ui-navigation/sd-collapse.md +35 -0
  92. package/docs/ui-navigation/sd-pagination.md +26 -0
  93. package/docs/ui-navigation/sd-sidebar-container.md +49 -0
  94. package/docs/ui-navigation/sd-sidebar-menu.md +22 -0
  95. package/docs/ui-navigation/sd-sidebar-user.md +43 -0
  96. package/docs/ui-navigation/sd-tab.md +51 -0
  97. package/docs/ui-navigation/sd-topbar-container.md +97 -0
  98. package/docs/ui-navigation/sd-topbar-menu.md +23 -0
  99. package/docs/ui-navigation/sd-topbar-user.md +38 -0
  100. package/docs/ui-navigation/sd-topbar.md +30 -0
  101. package/docs/ui-overlay/sd-busy-container.md +69 -0
  102. package/docs/ui-overlay/sd-confirm-modal.md +30 -0
  103. package/docs/ui-overlay/sd-dropdown.md +40 -0
  104. package/docs/ui-overlay/sd-modal.md +34 -0
  105. package/docs/ui-overlay/sd-prompt-modal.md +30 -0
  106. package/docs/ui-overlay/sd-toast.md +35 -0
  107. package/docs/ui-visual/sd-barcode.md +36 -0
  108. package/docs/ui-visual/sd-calendar.md +34 -0
  109. package/docs/ui-visual/sd-echarts.md +32 -0
  110. package/docs/ui-visual/sd-label.md +24 -0
  111. package/docs/ui-visual/sd-note.md +23 -0
  112. package/docs/ui-visual/sd-progress.md +23 -0
  113. package/docs/utils/inject-routing-signals.md +161 -0
  114. package/docs/utils/inject-sd-system-config-resource.md +35 -0
  115. package/docs/utils/mark.md +43 -0
  116. package/docs/utils/selection-managers.md +96 -0
  117. package/docs/utils/set-safe-style.md +19 -0
  118. package/docs/utils/setup-functions.md +93 -0
  119. package/package.json +7 -7
  120. package/scss/commons/_styles.scss +12 -0
  121. package/src/controls/select/sd-select.ts +3 -3
  122. package/src/core/modal/sd-modal.provider.ts +1 -1
  123. package/src/core/modal/sd-modal.ts +1 -1
  124. package/src/core/routing/menu-utils.ts +1 -1
  125. package/src/core/shared-data/sd-shared-data.provider.ts +7 -7
  126. package/src/data/shared-data/sd-shared-data-select.ts +2 -2
  127. package/src/index.ts +0 -3
  128. package/src/layout/dock/sd-dock-container.ts +1 -1
  129. package/dist/data/getOrmDataEditToastErrorMessage.d.ts +0 -2
  130. package/dist/data/getOrmDataEditToastErrorMessage.d.ts.map +0 -1
  131. package/dist/data/getOrmDataEditToastErrorMessage.js +0 -8
  132. package/docs/bootstrap.md +0 -38
  133. package/docs/directives.md +0 -236
  134. package/docs/features.md +0 -169
  135. package/docs/provider-types.md +0 -283
  136. package/docs/providers.md +0 -379
  137. package/docs/styling.md +0 -222
  138. package/docs/ui-data.md +0 -333
  139. package/docs/ui-form.md +0 -502
  140. package/docs/ui-navigation.md +0 -273
  141. package/docs/ui-overlay.md +0 -157
  142. package/docs/ui-visual.md +0 -127
  143. package/docs/utils.md +0 -244
  144. package/src/data/getOrmDataEditToastErrorMessage.ts +0 -10
@@ -0,0 +1,157 @@
1
+ ← [CRUD 리스트 레시피 진입점](../crud-list.md)
2
+
3
+ # 확장 G: 엑셀 업로드/다운로드
4
+
5
+ > **선행:** [확장 A: inline 편집/저장](./extension-a-inline-edit.md) (`_upsertItem` / `_search` / 감사 로그 재사용)
6
+
7
+ `SdFileDialogProvider`로 파일 선택, `ExcelWrapper`(@simplysm/excel) + `zod` 스키마로 읽기/쓰기를 수행한다. 다운로드는 확장 A의 `_search(false)`로 페이지네이션 없이 전체를 조회한 뒤 `@simplysm/core-browser`의 `downloadBlob`으로 내려받는다. 업로드된 각 행은 확장 A의 `_upsertItem`을 재사용해 중복 검사·감사 로그를 일관된 경로로 적용한다.
8
+
9
+ **이 확장이 도입하는 요소:**
10
+
11
+ - **imports:** `SdFileDialogProvider`, `DateTime`, `downloadBlob`(@simplysm/core-browser), `ExcelWrapper`(@simplysm/excel), `z`(zod), `tablerFileExcel`, `tablerUpload`
12
+ - **DI:** `SdFileDialogProvider`
13
+ - **필드:** `tablerFileExcel` / `tablerUpload` 아이콘, `_excelWrapper` (zod 스키마로 컬럼 정의)
14
+ - **메서드:** `onUploadExcelButtonClick`, `onDownloadExcelButtonClick`
15
+ - **템플릿:** 확장 A가 도입한 inline 도구 dock 뒤쪽에 엑셀 업로드/다운로드 버튼 2개 추가
16
+
17
+ > **아래 코드 블록은 diff 조각이다.** 독립 실행 가능한 완성 클래스가 아니며, 선행 확장(A) 위에 번호 순서대로 삽입할 지점을 나타낸다. 그대로 컴파일되지 않는다.
18
+
19
+ ```typescript
20
+ // 1) imports 추가
21
+ import { tablerFileExcel, tablerUpload } from "@ng-icons/tabler-icons";
22
+ import { SdFileDialogProvider } from "@simplysm/angular";
23
+ import { DateTime } from "@simplysm/core-common";
24
+ import { downloadBlob } from "@simplysm/core-browser";
25
+ import { ExcelWrapper } from "@simplysm/excel";
26
+ import { z } from "zod";
27
+
28
+ // 2) DI 추가
29
+ private readonly _sdFileDialog = inject(SdFileDialogProvider);
30
+
31
+ // 3) 클래스 필드 — 아이콘 + ExcelWrapper (zod 스키마로 컬럼 정의)
32
+ protected readonly tablerFileExcel = tablerFileExcel;
33
+ protected readonly tablerUpload = tablerUpload;
34
+
35
+ private readonly _excelWrapper = new ExcelWrapper(
36
+ z.object({
37
+ id: z.number().optional().describe("ID"),
38
+ name: z.string().describe("이름"),
39
+ phone: z.string().optional().describe("전화번호"),
40
+ categoryId: z.number().optional().describe("카테고리.ID"),
41
+ // isDeleted는 확장 B 적용 시(DB Table에 isDeleted 컬럼이 있는 경우)에만 추가:
42
+ // isDeleted: z.boolean().describe("삭제"),
43
+ lastModifiedAt: z.custom<DateTime>().optional().describe("최종수정일시"),
44
+ lastModifiedBy: z.string().optional().describe("최종수정자"),
45
+ }),
46
+ );
47
+
48
+ // 4) template — 확장 A가 도입한 inline 도구 dock 뒤쪽에 엑셀 버튼 2개 추가.
49
+ // 확장 B가 함께 적용되면 동일 dock 안에 "등록 → 선택 삭제 → 선택 복구 → 엑셀 업로드 → 엑셀 다운로드" 순으로 배치한다.
50
+ `
51
+ <!-- 확장 A가 도입한 도구 dock (canEdit && page 가드) -->
52
+ @if (canEdit() && viewType() === "page") {
53
+ <sd-dock class="flex-row gap-sm p-xs-default">
54
+ <sd-button [size]="'sm'" [theme]="'link-primary'" (click)="onAddItemButtonClick()">
55
+ <ng-icon [svg]="tablerCirclePlus" /> 등록
56
+ </sd-button>
57
+ <!-- 확장 B 적용 시: 선택 삭제 / 선택 복구 버튼이 여기에 위치 -->
58
+
59
+ <!-- ↓ 확장 G가 추가 -->
60
+ <sd-button [size]="'sm'" [theme]="'link-success'" (click)="onUploadExcelButtonClick()">
61
+ <ng-icon [svg]="tablerUpload" /> 엑셀 업로드
62
+ </sd-button>
63
+ <sd-button [size]="'sm'" [theme]="'link-success'" (click)="onDownloadExcelButtonClick()">
64
+ <ng-icon [svg]="tablerFileExcel" /> 엑셀 다운로드
65
+ </sd-button>
66
+ </sd-dock>
67
+ }
68
+ `
69
+
70
+ // 5) 메서드 추가
71
+ async onUploadExcelButtonClick(): Promise<void> {
72
+ const file = await this._sdFileDialog.showAsync(false, ".xlsx");
73
+ if (file == null) return;
74
+ if (Array.isArray(file)) return;
75
+
76
+ this.busyCount.update((v) => v + 1);
77
+ await this._sdToast.try(async () => {
78
+ const excelItems = await this._excelWrapper.read(file);
79
+ const changedIds: number[] = [];
80
+ await this._appOrm.connectAsync(async (db) => {
81
+ for (const raw of excelItems) {
82
+ changedIds.push(await this._upsertItem(db, raw, "엑셀업로드"));
83
+ }
84
+ });
85
+ await this._appSharedData.emitAsync(this.SHARED_DATA_KEY, changedIds);
86
+
87
+ this._sdToast.success("업로드되었습니다.");
88
+
89
+ await this._refresh();
90
+ });
91
+ this.busyCount.update((v) => v - 1);
92
+ }
93
+
94
+ async onDownloadExcelButtonClick(): Promise<void> {
95
+ if (this.busyCount() > 0) return;
96
+
97
+ this.busyCount.update((v) => v + 1);
98
+ await this._sdToast.try(async () => {
99
+ // 전체 조회 (페이지네이션 없이) — 확장 A의 _search를 그대로 재사용
100
+ const r = await this._search(false);
101
+ const wb = await this._excelWrapper.write(this.viewTitle(), r.items);
102
+ try {
103
+ downloadBlob(
104
+ await wb.toBlob(),
105
+ `${this.viewTitle()}_${new DateTime().toFormatString("yyMMdd")}.xlsx`,
106
+ );
107
+ } finally {
108
+ await wb.close();
109
+ }
110
+ });
111
+ this.busyCount.update((v) => v - 1);
112
+ }
113
+ ```
114
+
115
+ **포인트:**
116
+
117
+ - **다운로드는 `_search(false)`로 전체를 쿼리한다.** 페이지당 50건 제한이 걸리면 현재 페이지만 다운로드되므로 `usePagination: false`를 명시한다.
118
+ - **업로드는 `_excelWrapper.read(file)` → `_upsertItem` 루프로 수행한다.** 확장 A의 중복 검사·감사 로그가 동일하게 적용되며, `logType: "엑셀업로드"`로 감사 로그를 구분한다. 상세 Usage는 [`SdFileDialogProvider.showAsync`](../../providers/sd-file-dialog-provider.md#usage) 참조.
119
+ - **엑셀의 텍스트 컬럼(고객사명·MPN 등)을 FK id로 변환해야 하면 DB 재조회 대신 `useSharedSignal(...)`로 이미 로드된 공유 데이터를 재사용한다.** 예: `this.sharedCategories.items().toMapValues((it) => it.name, (it) => it.orderBy((v) => (v.__isHidden ? 1 : 0))[0])`. 같은 키에 숨김·비숨김 항목이 섞여 있으면 `orderBy`로 비숨김(`__isHidden: false`)을 우선순위로 정렬한다. 별도 `_buildIdMap` 같은 helper로 분리하지 않고 `toMapValues`를 `onUploadExcelButtonClick` 내부에 직접 인라인한다 (단일 호출처).
120
+ - **`busyMessage`는 필요할 때만 추가한다.** 최소 뼈대는 `<sd-busy-container [busy]="busyCount() > 0">`만 사용하고 `busyMessage` signal을 두지 않는다. 짧은 CRUD는 progress 아이콘만으로 충분하다. 오래 걸리는 작업(대량 엑셀 업로드·집계 등)에 진행 문구가 필요한 화면에만 `busyMessage = signal<string | undefined>(undefined)` 추가 + `[message]="busyMessage()"` 바인딩 + 구간별 `busyMessage.set(...)`/`set(undefined)` 제어를 추가한다. 미사용 시 선언·바인딩 모두 생략한다.
121
+
122
+ **🚫 흔한 실수**
123
+
124
+ > 공통 규칙(`mark` 오용, `setupCanDeactivate` 호출 위치, 시트 셀 `[inset]/[size]`, 공유 데이터 `wait()` 호출 위치 등)은 [레시피 공통 규칙](../_common-rules.md)을 참조한다. 이 섹션은 **엑셀 업로드/다운로드 고유 실수**만 다룬다.
125
+
126
+ ### 필수 필드를 `z.string().optional()`로 선언
127
+
128
+ ```typescript
129
+ // ❌ 모든 zod 필드에 .optional()을 부착 — 빈 셀·누락 행이 그대로 _upsertItem에 전달되어
130
+ // DB NOT NULL 위반 또는 name/phone이 공백인 row가 그대로 upsert된다.
131
+ new ExcelWrapper(
132
+ z.object({
133
+ id: z.number().optional().describe("ID"),
134
+ name: z.string().optional().describe("이름"), // ← 필수 필드인데 optional
135
+ phone: z.string().optional().describe("전화번호"),
136
+ categoryId: z.number().optional().describe("카테고리.ID"),
137
+ }),
138
+ );
139
+
140
+ // ✅ 필수 필드는 .optional() 없이 선언 — 빈 셀 행은 ExcelWrapper가 safeParse 단계에서 차단
141
+ new ExcelWrapper(
142
+ z.object({
143
+ id: z.number().optional().describe("ID"),
144
+ name: z.string().describe("이름"), // ← 필수
145
+ phone: z.string().optional().describe("전화번호"),
146
+ categoryId: z.number().optional().describe("카테고리.ID"),
147
+ }),
148
+ );
149
+ ```
150
+
151
+ **근거**: `ExcelWrapper.read`는 각 행을 `_schema.safeParse(record)`로 검증하고 실패 시 에러를 던진다(`packages/excel/src/excel-wrapper.ts:77`). zod 스키마가 업로드 유효성의 유일한 방어선이므로, 필수 필드에 `.optional()`을 달면 검증 자체를 통과해 잘못된 값이 `_upsertItem`까지 내려간다. `isDeleted` 컬럼이 있는 테이블에서는 [공통 규칙: 삭제 방식](../_common-rules.md#삭제-방식은-db-스키마에-따라-결정한다)에 따라 `isDeleted: z.boolean().describe("삭제")` 필드를 추가한다(확장 B 병용).
152
+
153
+ ## Cross-reference
154
+
155
+ - 진입점: [crud-list.md](../crud-list.md)
156
+ - 선행: [확장 A: inline 편집/저장](./extension-a-inline-edit.md) (`_upsertItem`/`_search`/감사 로그 재사용)
157
+ - 병용 가능: [확장 B: 선택 기능 + 선택 삭제/복구](./extension-b-selection.md) (`isDeleted` 컬럼이 있는 테이블 — zod 스키마에 `isDeleted` 필드 추가)