@simplysm/solid 13.0.96 → 13.0.97
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/package.json +19 -26
- package/README.md +0 -158
- package/docs/display-feedback.md +0 -404
- package/docs/features.md +0 -693
- package/docs/form-controls.md +0 -587
- package/docs/layout-data.md +0 -392
- package/docs/providers-hooks.md +0 -516
package/docs/features.md
DELETED
|
@@ -1,693 +0,0 @@
|
|
|
1
|
-
# 기능 컴포넌트
|
|
2
|
-
|
|
3
|
-
엔터프라이즈 비즈니스 로직을 캡슐화한 고수준 컴포넌트.
|
|
4
|
-
|
|
5
|
-
## CRUD 컴포넌트 동작 모드
|
|
6
|
-
|
|
7
|
-
`CrudSheet`과 `CrudDetail`은 겸용 컴포넌트로, `close` prop 전달 여부와 `Topbar` 컨텍스트에 따라 3가지 모드로 동작한다. CRUD 기반 컴포넌트를 만들 때는 관례적으로 `close` prop을 받아 그대로 전달한다.
|
|
8
|
-
|
|
9
|
-
| 모드 | 조건 | 툴바 위치 | 저장 후 동작 |
|
|
10
|
-
|------|------|-----------|-------------|
|
|
11
|
-
| **Dialog** | `close` 전달됨 | Dialog 헤더에 refresh, 하단에 저장/삭제 액션바 | `close(true)`로 다이얼로그 닫기 |
|
|
12
|
-
| **Page** | `close` 미전달 + `Topbar` 내부 | `Topbar`에 저장/삭제/새로고침 버튼 주입 | 데이터 재조회 |
|
|
13
|
-
| **Control** | `close` 미전달 + `Topbar` 외부 | 자체 인라인 툴바 표시 | 데이터 재조회 |
|
|
14
|
-
|
|
15
|
-
```tsx
|
|
16
|
-
// CRUD 기반 컴포넌트는 close prop을 받아 그대로 전달하는 것이 관례
|
|
17
|
-
function UserList(props: { close?: (result?: boolean) => void }) {
|
|
18
|
-
return (
|
|
19
|
-
<CrudSheet close={props.close} search={...} getItemKey={...} inlineEdit={...}>
|
|
20
|
-
...
|
|
21
|
-
</CrudSheet>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Dialog 모드 -- close가 전달되어 모달로 동작
|
|
26
|
-
const result = await dialog.open(UserList);
|
|
27
|
-
|
|
28
|
-
// Page 모드 -- Topbar 하위 라우트에 배치, close 미전달
|
|
29
|
-
<Route path="/users" component={UserList} />
|
|
30
|
-
|
|
31
|
-
// Control 모드 -- 다른 페이지의 일부로 임베드, close 미전달
|
|
32
|
-
<div class="grid grid-cols-2">
|
|
33
|
-
<UserList />
|
|
34
|
-
<OrderList />
|
|
35
|
-
</div>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## CrudSheet
|
|
41
|
-
|
|
42
|
-
DataSheet 기반 CRUD 기능 통합 컴포넌트. 인라인/다이얼로그 편집, 검색, Excel 가져오기/내보내기, 선택 모드, 배치 작업을 지원한다.
|
|
43
|
-
|
|
44
|
-
편집 방식은 `inlineEdit`과 `dialogEdit` 중 하나를 선택한다 (동시 사용 불가).
|
|
45
|
-
|
|
46
|
-
- **inlineEdit**: 셀 직접 편집, 행 추가/삭제, diff 기반 일괄 저장
|
|
47
|
-
- **dialogEdit**: 행 클릭 시 다이얼로그로 편집, 선택 항목 일괄 삭제/복원
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
import { CrudSheet } from "@simplysm/solid";
|
|
51
|
-
|
|
52
|
-
// 인라인 편집
|
|
53
|
-
function UserSheet(props: { close?: (result?: boolean) => void }) {
|
|
54
|
-
return (
|
|
55
|
-
<CrudSheet
|
|
56
|
-
close={props.close}
|
|
57
|
-
search={(filter, page, sorts) => fetchUsers(filter, page, sorts)}
|
|
58
|
-
getItemKey={(item) => item.id}
|
|
59
|
-
inlineEdit={{
|
|
60
|
-
submit: (diffs) => saveUsers(diffs),
|
|
61
|
-
newItem: () => ({ id: undefined, name: "", email: "" }),
|
|
62
|
-
deleteProp: "isDeleted",
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
65
|
-
<CrudSheet.Column header="이름" key="name">
|
|
66
|
-
{(ctx) => <TextInput value={ctx.item.name} onValueChange={(v) => ctx.setItem("name", v)} />}
|
|
67
|
-
</CrudSheet.Column>
|
|
68
|
-
</CrudSheet>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 다이얼로그 편집
|
|
73
|
-
function ProductSheet(props: { close?: (result?: boolean) => void }) {
|
|
74
|
-
const dialog = useDialog();
|
|
75
|
-
return (
|
|
76
|
-
<CrudSheet
|
|
77
|
-
close={props.close}
|
|
78
|
-
search={(filter, page, sorts) => fetchProducts(filter, page, sorts)}
|
|
79
|
-
getItemKey={(item) => item.id}
|
|
80
|
-
dialogEdit={{
|
|
81
|
-
editItem: (item) => dialog.open(ProductDetail, { item }),
|
|
82
|
-
deleteItems: (items) => deleteProducts(items),
|
|
83
|
-
restoreItems: (items) => restoreProducts(items),
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
<CrudSheet.Column header="상품명" key="name" editTrigger>
|
|
87
|
-
{(ctx) => ctx.item.name}
|
|
88
|
-
</CrudSheet.Column>
|
|
89
|
-
</CrudSheet>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### CrudSheet Props
|
|
95
|
-
|
|
96
|
-
| Prop | 타입 | 설명 |
|
|
97
|
-
|------|------|------|
|
|
98
|
-
| `search` | `(filter, page, sorts) => Promise<SearchResult<TItem>>` | 데이터 조회 함수. `page`가 `undefined`면 전체 조회 (Excel 등) |
|
|
99
|
-
| `getItemKey` | `(item: TItem) => string \| number \| undefined` | 아이템 고유 키 반환 |
|
|
100
|
-
| `close` | `() => void` | Dialog 모드 활성화. 전달 시 Dialog 헤더에 새로고침 버튼 표시 |
|
|
101
|
-
| `inlineEdit` | `InlineEditConfig<TItem>` | 인라인 편집 설정 (`dialogEdit`과 동시 사용 불가) |
|
|
102
|
-
| `dialogEdit` | `DialogEditConfig<TItem>` | 다이얼로그 편집 설정 (`inlineEdit`과 동시 사용 불가) |
|
|
103
|
-
| `editable` | `boolean` | 편집 버튼 표시 여부 (기본값: `true`) |
|
|
104
|
-
| `isItemEditable` | `(item: TItem) => boolean` | 아이템별 편집 가능 여부 (dialogEdit의 editTrigger 링크에 적용) |
|
|
105
|
-
| `isItemDeletable` | `(item: TItem) => boolean` | 아이템별 삭제 가능 여부 (삭제 버튼/링크 비활성화) |
|
|
106
|
-
| `isItemDeleted` | `(item: TItem) => boolean` | 아이템 삭제 상태 확인 (취소선 표시) |
|
|
107
|
-
| `isItemSelectable` | `(item: TItem) => boolean \| string` | 아이템별 선택 가능 여부 |
|
|
108
|
-
| `filterInitial` | `TFilter` | 필터 초기값 |
|
|
109
|
-
| `items` | `TItem[]` | 제어 모드: 외부에서 아이템 배열 전달 |
|
|
110
|
-
| `onItemsChange` | `(items: TItem[]) => void` | 제어 모드: 아이템 변경 콜백 |
|
|
111
|
-
| `storageKey` | `string` | DataSheet 컬럼 너비 등 상태 저장 키 |
|
|
112
|
-
| `lastModifiedAtProp` | `string` | 자동 "최종수정일시" 컬럼 추가 (DateTime 타입, hidden) |
|
|
113
|
-
| `lastModifiedByProp` | `string` | 자동 "수정자" 컬럼 추가 (string 타입, hidden) |
|
|
114
|
-
| `excel` | `ExcelConfig<TItem>` | Excel 가져오기/내보내기 설정 |
|
|
115
|
-
| `selectionMode` | `"single" \| "multiple"` | 선택 모드 활성화 |
|
|
116
|
-
| `selectedKeys` | `(string \| number)[]` | 제어 모드: 선택된 키 배열 |
|
|
117
|
-
| `onSelectedKeysChange` | `(keys) => void` | 제어 모드: 선택 변경 콜백 |
|
|
118
|
-
| `onSelect` | `(result: SelectResult<TItem>) => void` | 선택 확인 콜백 (single 모드는 클릭 시 자동 호출) |
|
|
119
|
-
| `onSubmitComplete` | `() => void` | 저장 완료 후 콜백 |
|
|
120
|
-
| `hideAutoTools` | `boolean` | 자동 생성 툴바 버튼 숨기기 |
|
|
121
|
-
| `class` | `string` | CSS 클래스 |
|
|
122
|
-
|
|
123
|
-
### 설정 타입
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
// 인라인 편집 설정
|
|
127
|
-
interface InlineEditConfig<TItem> {
|
|
128
|
-
submit: (diffs: ArrayOneWayDiffResult<TItem>[]) => Promise<void>;
|
|
129
|
-
newItem: () => TItem;
|
|
130
|
-
deleteProp?: keyof TItem & string; // 삭제 플래그 프로퍼티 (예: "isDeleted")
|
|
131
|
-
diffsExcludes?: string[]; // diff 비교 제외 프로퍼티
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 다이얼로그 편집 설정
|
|
135
|
-
interface DialogEditConfig<TItem> {
|
|
136
|
-
editItem: (item?: TItem) => Promise<boolean | undefined>; // 등록(item 없음)/수정 다이얼로그
|
|
137
|
-
deleteItems?: (items: TItem[]) => Promise<boolean>; // 선택 항목 삭제
|
|
138
|
-
restoreItems?: (items: TItem[]) => Promise<boolean>; // 선택 항목 복원
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Excel 설정
|
|
142
|
-
interface ExcelConfig<TItem> {
|
|
143
|
-
download: (items: TItem[]) => Promise<void>; // 전체 데이터 다운로드
|
|
144
|
-
upload?: (file: File) => Promise<void>; // .xlsx 파일 업로드
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 조회 결과
|
|
148
|
-
interface SearchResult<TItem> {
|
|
149
|
-
items: TItem[];
|
|
150
|
-
pageCount?: number; // 페이지 수 (미설정 시 페이징 없음)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 선택 결과
|
|
154
|
-
interface SelectResult<TItem> {
|
|
155
|
-
items: TItem[];
|
|
156
|
-
keys: (string | number)[];
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### 선택 모드
|
|
161
|
-
|
|
162
|
-
`selectionMode`를 설정하면 체크박스 컬럼이 자동 추가된다. Dialog 모드에서 사용하면 선택 전용 UI로 전환된다.
|
|
163
|
-
|
|
164
|
-
```tsx
|
|
165
|
-
// 다중 선택 (제어 모드)
|
|
166
|
-
const [selectedKeys, setSelectedKeys] = createSignal<number[]>([]);
|
|
167
|
-
|
|
168
|
-
<CrudSheet
|
|
169
|
-
search={searchFn}
|
|
170
|
-
getItemKey={(item) => item.id}
|
|
171
|
-
selectionMode="multiple"
|
|
172
|
-
selectedKeys={selectedKeys()}
|
|
173
|
-
onSelectedKeysChange={setSelectedKeys}
|
|
174
|
-
>
|
|
175
|
-
<CrudSheet.Column key="name" header="이름">
|
|
176
|
-
{(ctx) => <div>{ctx.item.name}</div>}
|
|
177
|
-
</CrudSheet.Column>
|
|
178
|
-
</CrudSheet>
|
|
179
|
-
|
|
180
|
-
// Dialog에서 단일 선택 (클릭 시 자동 확정)
|
|
181
|
-
<CrudSheet
|
|
182
|
-
close={props.close}
|
|
183
|
-
search={searchFn}
|
|
184
|
-
getItemKey={(item) => item.id}
|
|
185
|
-
selectionMode="single"
|
|
186
|
-
onSelect={(result) => props.close?.({ selectedKeys: result.keys })}
|
|
187
|
-
>
|
|
188
|
-
...
|
|
189
|
-
</CrudSheet>
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Excel 가져오기/내보내기
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
<CrudSheet
|
|
196
|
-
search={searchFn}
|
|
197
|
-
getItemKey={(item) => item.id}
|
|
198
|
-
excel={{
|
|
199
|
-
download: async (items) => {
|
|
200
|
-
// items는 전체 데이터 (페이징 무시하고 재조회)
|
|
201
|
-
await exportToExcel(items);
|
|
202
|
-
},
|
|
203
|
-
upload: async (file) => {
|
|
204
|
-
// .xlsx 파일 업로드 후 자동 새로고침
|
|
205
|
-
await importFromExcel(file);
|
|
206
|
-
},
|
|
207
|
-
}}
|
|
208
|
-
>
|
|
209
|
-
...
|
|
210
|
-
</CrudSheet>
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### 키보드 단축키
|
|
214
|
-
|
|
215
|
-
| 단축키 | 동작 |
|
|
216
|
-
|--------|------|
|
|
217
|
-
| `Ctrl+S` | 저장 (인라인 편집 모드) |
|
|
218
|
-
| `Ctrl+Alt+L` | 새로고침 |
|
|
219
|
-
|
|
220
|
-
활성화 조건: 해당 CrudSheet 영역에 포커스/클릭이 있어야 한다 (다중 CrudSheet 환경에서 충돌 방지).
|
|
221
|
-
|
|
222
|
-
### CrudSheet 서브 컴포넌트
|
|
223
|
-
|
|
224
|
-
| 컴포넌트 | 설명 |
|
|
225
|
-
|----------|------|
|
|
226
|
-
| `CrudSheet.Column` | 컬럼 정의. `editTrigger`로 dialogEdit 시 클릭 편집 링크 표시 |
|
|
227
|
-
| `CrudSheet.Filter` | 검색 필터 영역. render prop으로 `(filter, setFilter)` 전달 |
|
|
228
|
-
| `CrudSheet.Tools` | 커스텀 툴바 버튼. render prop으로 `CrudSheetContext` 전달 |
|
|
229
|
-
| `CrudSheet.Header` | 시트 상단 커스텀 헤더 영역 |
|
|
230
|
-
|
|
231
|
-
### CrudSheet.Column
|
|
232
|
-
|
|
233
|
-
`DataSheetColumn`의 모든 props를 상속하며, 추가로 `editTrigger`와 CRUD 전용 셀 컨텍스트를 제공한다.
|
|
234
|
-
|
|
235
|
-
```tsx
|
|
236
|
-
<CrudSheet.Column<TItem>
|
|
237
|
-
key="name" // 컬럼 식별 키 (정렬, 저장에 사용)
|
|
238
|
-
header="이름" // 헤더 텍스트 또는 JSX
|
|
239
|
-
editTrigger // dialogEdit 시 클릭 편집 링크 표시
|
|
240
|
-
fixed // 고정 컬럼
|
|
241
|
-
hidden // 기본 숨김 (사용자가 토글 가능)
|
|
242
|
-
collapse // 접기 가능
|
|
243
|
-
width="150px" // 초기 너비
|
|
244
|
-
sortable={true} // 정렬 가능 (기본: true)
|
|
245
|
-
resizable={true} // 리사이즈 가능 (기본: true)
|
|
246
|
-
summary={(items) => items.reduce((sum, item) => sum + item.amount, 0)}
|
|
247
|
-
>
|
|
248
|
-
{(ctx) => (
|
|
249
|
-
// ctx: CrudSheetCellContext<TItem>
|
|
250
|
-
// ctx.item: 현재 아이템
|
|
251
|
-
// ctx.index: 배열 인덱스
|
|
252
|
-
// ctx.row: 행 번호
|
|
253
|
-
// ctx.depth: 트리 깊이
|
|
254
|
-
// ctx.setItem(key, value): 인라인 편집 시 값 변경
|
|
255
|
-
<TextInput value={ctx.item.name} onValueChange={(v) => ctx.setItem("name", v)} />
|
|
256
|
-
)}
|
|
257
|
-
</CrudSheet.Column>
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### CrudSheet.Filter
|
|
261
|
-
|
|
262
|
-
검색 필터 영역. render prop으로 현재 `filter` 상태와 `setFilter` (SolidJS store setter)를 전달한다. 검색 버튼은 자동 생성된다.
|
|
263
|
-
|
|
264
|
-
```tsx
|
|
265
|
-
<CrudSheet.Filter<{ searchText?: string; status?: string }>>
|
|
266
|
-
{(filter, setFilter) => (
|
|
267
|
-
<>
|
|
268
|
-
<FormGroup.Item label="검색어">
|
|
269
|
-
<TextInput value={filter.searchText ?? ""} onValueChange={(v) => setFilter("searchText", v)} />
|
|
270
|
-
</FormGroup.Item>
|
|
271
|
-
<FormGroup.Item label="상태">
|
|
272
|
-
<Select
|
|
273
|
-
value={filter.status}
|
|
274
|
-
onValueChange={(v) => setFilter("status", v)}
|
|
275
|
-
items={["active", "inactive"]}
|
|
276
|
-
/>
|
|
277
|
-
</FormGroup.Item>
|
|
278
|
-
</>
|
|
279
|
-
)}
|
|
280
|
-
</CrudSheet.Filter>
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
### CrudSheet.Tools
|
|
284
|
-
|
|
285
|
-
커스텀 툴바 버튼. render prop으로 `CrudSheetContext`를 전달하여 내부 상태 접근 및 액션 호출이 가능하다.
|
|
286
|
-
|
|
287
|
-
```tsx
|
|
288
|
-
<CrudSheet.Tools<TItem>>
|
|
289
|
-
{(ctx) => (
|
|
290
|
-
<>
|
|
291
|
-
<Button size="sm" onClick={() => void handleCustomAction(ctx.selection())}>
|
|
292
|
-
커스텀 액션 ({ctx.selection().length}건)
|
|
293
|
-
</Button>
|
|
294
|
-
<Show when={ctx.hasChanges()}>
|
|
295
|
-
<span class="text-warning-500">변경사항 있음</span>
|
|
296
|
-
</Show>
|
|
297
|
-
</>
|
|
298
|
-
)}
|
|
299
|
-
</CrudSheet.Tools>
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
#### CrudSheetContext API
|
|
303
|
-
|
|
304
|
-
| 멤버 | 타입 | 설명 |
|
|
305
|
-
|------|------|------|
|
|
306
|
-
| `items()` | `TItem[]` | 현재 페이지 아이템 목록 |
|
|
307
|
-
| `selection()` | `TItem[]` | 현재 선택된 아이템 목록 |
|
|
308
|
-
| `page()` | `number` | 현재 페이지 번호 |
|
|
309
|
-
| `sorts()` | `SortingDef[]` | 현재 정렬 정의 |
|
|
310
|
-
| `busy()` | `boolean` | 로딩 중 여부 |
|
|
311
|
-
| `hasChanges()` | `boolean` | 변경사항 존재 여부 (인라인 편집) |
|
|
312
|
-
| `save()` | `Promise<void>` | 저장 실행 |
|
|
313
|
-
| `refresh()` | `Promise<void>` | 데이터 새로고침 |
|
|
314
|
-
| `addItem()` | `void` | 행 추가 (인라인 편집) |
|
|
315
|
-
| `clearSelection()` | `void` | 선택 초기화 |
|
|
316
|
-
| `setPage(page)` | `void` | 페이지 변경 |
|
|
317
|
-
| `setSorts(sorts)` | `void` | 정렬 변경 |
|
|
318
|
-
|
|
319
|
-
### CrudSheet.Header
|
|
320
|
-
|
|
321
|
-
시트 상단에 커스텀 헤더 영역을 추가한다.
|
|
322
|
-
|
|
323
|
-
```tsx
|
|
324
|
-
<CrudSheet.Header>
|
|
325
|
-
<div class="p-2 text-lg font-bold">사용자 관리</div>
|
|
326
|
-
</CrudSheet.Header>
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## CrudDetail
|
|
332
|
-
|
|
333
|
-
단건 레코드 편집 폼. 저장/삭제/복원, 변경사항 자동 추적을 지원한다.
|
|
334
|
-
|
|
335
|
-
```tsx
|
|
336
|
-
import { CrudDetail } from "@simplysm/solid";
|
|
337
|
-
|
|
338
|
-
function UserDetail(props: { close?: (result?: boolean) => void; userId: number }) {
|
|
339
|
-
return (
|
|
340
|
-
<CrudDetail
|
|
341
|
-
close={props.close}
|
|
342
|
-
load={() => fetchUser(props.userId)}
|
|
343
|
-
submit={(data) => saveUser(data)}
|
|
344
|
-
toggleDelete={(del) => toggleDeleteUser(props.userId, del)}
|
|
345
|
-
>
|
|
346
|
-
{(ctx) => (
|
|
347
|
-
<FormGroup>
|
|
348
|
-
<FormGroup.Item label="이름">
|
|
349
|
-
<TextInput value={ctx.data.name} onValueChange={(v) => ctx.setData("name", v)} />
|
|
350
|
-
</FormGroup.Item>
|
|
351
|
-
</FormGroup>
|
|
352
|
-
)}
|
|
353
|
-
</CrudDetail>
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Dialog로 열기
|
|
358
|
-
const result = await dialog.open(UserDetail, { userId: 123 });
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### CrudDetail Props
|
|
362
|
-
|
|
363
|
-
| Prop | 타입 | 설명 |
|
|
364
|
-
|------|------|------|
|
|
365
|
-
| `load` | `() => Promise<{ data: TData; info: CrudDetailInfo }>` | 데이터 로드 함수 (필수) |
|
|
366
|
-
| `children` | `(ctx: CrudDetailContext<TData>) => JSX.Element` | 폼 렌더링 (필수) |
|
|
367
|
-
| `submit` | `(data: TData) => Promise<boolean \| undefined>` | 저장 함수 |
|
|
368
|
-
| `toggleDelete` | `(del: boolean) => Promise<boolean \| undefined>` | 삭제/복원 토글 |
|
|
369
|
-
| `editable` | `boolean` | 편집 가능 여부 |
|
|
370
|
-
| `deletable` | `boolean` | 삭제 가능 여부 |
|
|
371
|
-
| `data` | `TData` | 제어 모드: 외부 데이터 |
|
|
372
|
-
| `onDataChange` | `(data: TData) => void` | 제어 모드: 데이터 변경 콜백 |
|
|
373
|
-
| `close` | `(result?: boolean) => void` | Dialog 모드 활성화 |
|
|
374
|
-
| `class` | `string` | CSS 클래스 |
|
|
375
|
-
|
|
376
|
-
### CrudDetailInfo
|
|
377
|
-
|
|
378
|
-
```typescript
|
|
379
|
-
interface CrudDetailInfo {
|
|
380
|
-
isNew: boolean; // 신규 레코드 여부
|
|
381
|
-
isDeleted: boolean; // 삭제 상태
|
|
382
|
-
lastModifiedAt?: DateTime; // 최종 수정 일시
|
|
383
|
-
lastModifiedBy?: string; // 최종 수정자
|
|
384
|
-
}
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### CrudDetailContext
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
interface CrudDetailContext<TData> {
|
|
391
|
-
data: TData; // 현재 데이터 (store)
|
|
392
|
-
setData: SetStoreFunction<TData>; // 데이터 수정 (SolidJS store setter)
|
|
393
|
-
info: () => CrudDetailInfo; // 상세 정보
|
|
394
|
-
busy: () => boolean; // 로딩 중 여부
|
|
395
|
-
hasChanges: () => boolean; // 변경사항 존재 여부
|
|
396
|
-
save: () => Promise<void>; // 저장 실행
|
|
397
|
-
refresh: () => Promise<void>; // 데이터 새로고침
|
|
398
|
-
}
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
### CrudDetail 서브 컴포넌트
|
|
402
|
-
|
|
403
|
-
| 컴포넌트 | 설명 |
|
|
404
|
-
|----------|------|
|
|
405
|
-
| `CrudDetail.Tools` | 커스텀 툴바 버튼 |
|
|
406
|
-
| `CrudDetail.Before` | 폼 위쪽 영역 (폼 외부) |
|
|
407
|
-
| `CrudDetail.After` | 폼 아래쪽 영역 (폼 외부) |
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## DataSelectButton
|
|
412
|
-
|
|
413
|
-
다이얼로그 기반 데이터 선택 버튼. 선택 다이얼로그를 열어 항목을 선택하고, 선택된 항목을 표시한다.
|
|
414
|
-
|
|
415
|
-
```tsx
|
|
416
|
-
import { DataSelectButton, type SelectDialogBaseProps } from "@simplysm/solid";
|
|
417
|
-
|
|
418
|
-
// 선택 다이얼로그 컴포넌트 정의
|
|
419
|
-
function UserSelectDialog(props: SelectDialogBaseProps & { department?: string }) {
|
|
420
|
-
// props.selectionMode: "single" | "multiple"
|
|
421
|
-
// props.selectedKeys: 기존 선택된 키 배열
|
|
422
|
-
// props.close: 결과 반환 함수
|
|
423
|
-
|
|
424
|
-
const handleSelect = (userIds: number[]) => {
|
|
425
|
-
props.close?.({ selectedKeys: userIds });
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
return <div>...</div>;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// 단일 선택
|
|
432
|
-
<DataSelectButton
|
|
433
|
-
value={selectedUserId()}
|
|
434
|
-
onValueChange={setSelectedUserId}
|
|
435
|
-
load={async (keys) => await loadUsers(keys)}
|
|
436
|
-
renderItem={(user) => <span>{user.name}</span>}
|
|
437
|
-
dialog={UserSelectDialog}
|
|
438
|
-
dialogOptions={{ header: "사용자 선택", width: "600px" }}
|
|
439
|
-
/>
|
|
440
|
-
|
|
441
|
-
// 다중 선택
|
|
442
|
-
<DataSelectButton
|
|
443
|
-
multiple
|
|
444
|
-
value={selectedUserIds()}
|
|
445
|
-
onValueChange={setSelectedUserIds}
|
|
446
|
-
load={async (keys) => await loadUsers(keys)}
|
|
447
|
-
renderItem={(user) => <span>{user.name}</span>}
|
|
448
|
-
dialog={UserSelectDialog}
|
|
449
|
-
dialogOptions={{ header: "사용자 선택" }}
|
|
450
|
-
/>
|
|
451
|
-
|
|
452
|
-
// 다이얼로그에 추가 props 전달
|
|
453
|
-
<DataSelectButton
|
|
454
|
-
value={selectedUserId()}
|
|
455
|
-
onValueChange={setSelectedUserId}
|
|
456
|
-
load={loadUsers}
|
|
457
|
-
renderItem={(user) => <span>{user.name}</span>}
|
|
458
|
-
dialog={UserSelectDialog}
|
|
459
|
-
dialogProps={{ department: "engineering" }}
|
|
460
|
-
/>
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### SelectDialogBaseProps (선택 다이얼로그 필수 props)
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
interface SelectDialogBaseProps<TKey = string | number> {
|
|
467
|
-
close?: (result?: DataSelectDialogResult<TKey>) => void;
|
|
468
|
-
selectionMode: "single" | "multiple";
|
|
469
|
-
selectedKeys: TKey[];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
interface DataSelectDialogResult<TKey> {
|
|
473
|
-
selectedKeys: TKey[];
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
## SharedDataSelect
|
|
480
|
-
|
|
481
|
-
SharedData 기반 Select 드롭다운. SharedDataAccessor를 연결하면 자동으로 아이템 목록, 검색, 트리 구조를 지원한다.
|
|
482
|
-
|
|
483
|
-
```tsx
|
|
484
|
-
import { SharedDataSelect } from "@simplysm/solid";
|
|
485
|
-
|
|
486
|
-
const sharedData = useSharedData<{ users: User }>();
|
|
487
|
-
|
|
488
|
-
// 단일 선택
|
|
489
|
-
<SharedDataSelect
|
|
490
|
-
data={sharedData.users}
|
|
491
|
-
value={selectedUserId()}
|
|
492
|
-
onValueChange={setSelectedUserId}
|
|
493
|
-
>
|
|
494
|
-
<SharedDataSelect.ItemTemplate>
|
|
495
|
-
{(item: User) => <span>{item.name}</span>}
|
|
496
|
-
</SharedDataSelect.ItemTemplate>
|
|
497
|
-
</SharedDataSelect>
|
|
498
|
-
|
|
499
|
-
// 다중 선택
|
|
500
|
-
<SharedDataSelect
|
|
501
|
-
multiple
|
|
502
|
-
data={sharedData.users}
|
|
503
|
-
value={selectedUserIds()}
|
|
504
|
-
onValueChange={setSelectedUserIds}
|
|
505
|
-
>
|
|
506
|
-
<SharedDataSelect.ItemTemplate>
|
|
507
|
-
{(item: User) => <span>{item.name}</span>}
|
|
508
|
-
</SharedDataSelect.ItemTemplate>
|
|
509
|
-
</SharedDataSelect>
|
|
510
|
-
|
|
511
|
-
// 다이얼로그 검색 연동
|
|
512
|
-
<SharedDataSelect
|
|
513
|
-
data={sharedData.users}
|
|
514
|
-
value={selectedUserId()}
|
|
515
|
-
onValueChange={setSelectedUserId}
|
|
516
|
-
dialog={UserSearchDialog}
|
|
517
|
-
dialogOptions={{ header: "사용자 검색" }}
|
|
518
|
-
>
|
|
519
|
-
<SharedDataSelect.ItemTemplate>
|
|
520
|
-
{(item: User) => <span>{item.name}</span>}
|
|
521
|
-
</SharedDataSelect.ItemTemplate>
|
|
522
|
-
<SharedDataSelect.Action onClick={handleCustomAction}>
|
|
523
|
-
<Icon icon={IconPlus} />
|
|
524
|
-
</SharedDataSelect.Action>
|
|
525
|
-
</SharedDataSelect>
|
|
526
|
-
|
|
527
|
-
// 아이템 필터링
|
|
528
|
-
<SharedDataSelect
|
|
529
|
-
data={sharedData.departments}
|
|
530
|
-
value={deptId()}
|
|
531
|
-
onValueChange={setDeptId}
|
|
532
|
-
filterFn={(item) => item.isActive}
|
|
533
|
-
>
|
|
534
|
-
<SharedDataSelect.ItemTemplate>
|
|
535
|
-
{(item) => <span>{item.name}</span>}
|
|
536
|
-
</SharedDataSelect.ItemTemplate>
|
|
537
|
-
</SharedDataSelect>
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
| Prop | 타입 | 설명 |
|
|
541
|
-
|------|------|------|
|
|
542
|
-
| `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
|
|
543
|
-
| `value` / `onValueChange` | `TKey \| TKey[]` | 선택된 키 값 |
|
|
544
|
-
| `multiple` | `boolean` | 다중 선택 모드 |
|
|
545
|
-
| `filterFn` | `(item, index) => boolean` | 아이템 필터 |
|
|
546
|
-
| `dialog` | `Component<TDialogProps>` | 검색 다이얼로그 컴포넌트 |
|
|
547
|
-
| `dialogOptions` | `DialogShowOptions` | 다이얼로그 옵션 |
|
|
548
|
-
| `required`, `disabled`, `size`, `inset` | | 공통 폼 컨트롤 props |
|
|
549
|
-
|
|
550
|
-
### 서브 컴포넌트
|
|
551
|
-
|
|
552
|
-
| 컴포넌트 | 설명 |
|
|
553
|
-
|----------|------|
|
|
554
|
-
| `SharedDataSelect.ItemTemplate` | 드롭다운 아이템 렌더링 커스터마이징 |
|
|
555
|
-
| `SharedDataSelect.Action` | 드롭다운 하단 커스텀 액션 버튼 |
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## SharedDataSelectButton
|
|
560
|
-
|
|
561
|
-
SharedData 기반 DataSelectButton. SharedDataAccessor와 다이얼로그를 연결한다.
|
|
562
|
-
|
|
563
|
-
```tsx
|
|
564
|
-
import { SharedDataSelectButton } from "@simplysm/solid";
|
|
565
|
-
|
|
566
|
-
<SharedDataSelectButton
|
|
567
|
-
data={sharedData.users}
|
|
568
|
-
value={selectedUserId()}
|
|
569
|
-
onValueChange={setSelectedUserId}
|
|
570
|
-
dialog={UserSearchDialog}
|
|
571
|
-
dialogOptions={{ header: "사용자 선택" }}
|
|
572
|
-
>
|
|
573
|
-
{(user) => <span>{user.name}</span>}
|
|
574
|
-
</SharedDataSelectButton>
|
|
575
|
-
|
|
576
|
-
// 다중 선택
|
|
577
|
-
<SharedDataSelectButton
|
|
578
|
-
multiple
|
|
579
|
-
data={sharedData.users}
|
|
580
|
-
value={selectedUserIds()}
|
|
581
|
-
onValueChange={setSelectedUserIds}
|
|
582
|
-
dialog={UserSearchDialog}
|
|
583
|
-
>
|
|
584
|
-
{(user) => <span>{user.name}</span>}
|
|
585
|
-
</SharedDataSelectButton>
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
---
|
|
589
|
-
|
|
590
|
-
## SharedDataSelectList
|
|
591
|
-
|
|
592
|
-
SharedData 기반 목록 선택 UI. 검색, 필터, 페이징을 내장한다.
|
|
593
|
-
|
|
594
|
-
```tsx
|
|
595
|
-
import { SharedDataSelectList } from "@simplysm/solid";
|
|
596
|
-
|
|
597
|
-
<SharedDataSelectList
|
|
598
|
-
data={sharedData.departments}
|
|
599
|
-
value={selectedDept()}
|
|
600
|
-
onValueChange={setSelectedDept}
|
|
601
|
-
pageSize={20}
|
|
602
|
-
header={<h3>부서 선택</h3>}
|
|
603
|
-
>
|
|
604
|
-
<SharedDataSelectList.ItemTemplate>
|
|
605
|
-
{(dept: Department) => <span>{dept.name}</span>}
|
|
606
|
-
</SharedDataSelectList.ItemTemplate>
|
|
607
|
-
</SharedDataSelectList>
|
|
608
|
-
|
|
609
|
-
// 커스텀 필터 UI
|
|
610
|
-
<SharedDataSelectList data={sharedData.items} value={v()} onValueChange={setV}>
|
|
611
|
-
<SharedDataSelectList.ItemTemplate>
|
|
612
|
-
{(item) => <span>{item.name}</span>}
|
|
613
|
-
</SharedDataSelectList.ItemTemplate>
|
|
614
|
-
<SharedDataSelectList.Filter>
|
|
615
|
-
<MyCustomFilter />
|
|
616
|
-
</SharedDataSelectList.Filter>
|
|
617
|
-
</SharedDataSelectList>
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
| Prop | 타입 | 설명 |
|
|
621
|
-
|------|------|------|
|
|
622
|
-
| `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
|
|
623
|
-
| `value` / `onValueChange` | `TItem \| undefined` | 선택된 항목 (아이템 참조) |
|
|
624
|
-
| `filterFn` | `(item, index) => boolean` | 아이템 필터 |
|
|
625
|
-
| `canChange` | `(item) => boolean \| Promise<boolean>` | 변경 가드 |
|
|
626
|
-
| `pageSize` | `number` | 페이지 크기 (설정 시 페이지네이션 표시) |
|
|
627
|
-
| `header` | `JSX.Element` | 상단 헤더 |
|
|
628
|
-
|
|
629
|
-
---
|
|
630
|
-
|
|
631
|
-
## PermissionTable
|
|
632
|
-
|
|
633
|
-
`createAppStructure`에서 생성한 권한 트리를 DataSheet로 렌더링하는 권한 관리 테이블.
|
|
634
|
-
|
|
635
|
-
```tsx
|
|
636
|
-
import { PermissionTable } from "@simplysm/solid";
|
|
637
|
-
|
|
638
|
-
const app = useAppStructure();
|
|
639
|
-
|
|
640
|
-
<PermissionTable
|
|
641
|
-
items={app.usablePerms()}
|
|
642
|
-
value={permissions()}
|
|
643
|
-
onValueChange={setPermissions}
|
|
644
|
-
modules={activeModules()}
|
|
645
|
-
/>
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
| Prop | 타입 | 설명 |
|
|
649
|
-
|------|------|------|
|
|
650
|
-
| `items` | `AppPerm[]` | 권한 트리 (`createAppStructure`의 `usablePerms()`) |
|
|
651
|
-
| `value` | `Record<string, boolean>` | 권한 레코드 (`"href/perm": true`) |
|
|
652
|
-
| `onValueChange` | `(v: Record<string, boolean>) => void` | 변경 콜백 |
|
|
653
|
-
| `modules` | `TModule[]` | 활성 모듈 필터 |
|
|
654
|
-
| `disabled` | `boolean` | 비활성화 |
|
|
655
|
-
|
|
656
|
-
동작 규칙:
|
|
657
|
-
- 기본 권한(`perms[0]`, 보통 `"use"`)이 꺼지면 하위 권한도 자동으로 꺼진다
|
|
658
|
-
- 그룹 노드 체크 시 모든 자식에 대해 일괄 적용
|
|
659
|
-
- 모듈 필터링: `modules` prop에 따라 해당 모듈의 항목만 표시
|
|
660
|
-
|
|
661
|
-
---
|
|
662
|
-
|
|
663
|
-
## AddressSearch
|
|
664
|
-
|
|
665
|
-
다음(카카오) 우편번호 API 기반 주소 검색.
|
|
666
|
-
|
|
667
|
-
```tsx
|
|
668
|
-
import { AddressSearchContent, type AddressSearchResult } from "@simplysm/solid";
|
|
669
|
-
|
|
670
|
-
// Dialog로 사용
|
|
671
|
-
const dialog = useDialog();
|
|
672
|
-
const result = await dialog.show<AddressSearchResult>(AddressSearchContent, {}, {
|
|
673
|
-
header: "주소 검색",
|
|
674
|
-
width: "500px",
|
|
675
|
-
height: "500px",
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
if (result) {
|
|
679
|
-
setPostCode(result.postNumber);
|
|
680
|
-
setAddress(result.address);
|
|
681
|
-
setBuildingName(result.buildingName);
|
|
682
|
-
}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
### AddressSearchResult
|
|
686
|
-
|
|
687
|
-
```typescript
|
|
688
|
-
interface AddressSearchResult {
|
|
689
|
-
postNumber: string | undefined; // 우편번호
|
|
690
|
-
address: string | undefined; // 주소
|
|
691
|
-
buildingName: string | undefined; // 건물명
|
|
692
|
-
}
|
|
693
|
-
```
|