@simplysm/solid 13.0.62 → 13.0.65
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/README.md +6 -0
- package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.js +3 -2
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/features/address/AddressSearch.d.ts +8 -0
- package/dist/components/features/address/AddressSearch.d.ts.map +1 -0
- package/dist/components/features/address/AddressSearch.js +72 -0
- package/dist/components/features/address/AddressSearch.js.map +6 -0
- package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-detail/CrudDetail.js +62 -41
- package/dist/components/features/crud-detail/CrudDetail.js.map +6 -0
- package/dist/components/features/crud-detail/CrudDetailAfter.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-detail/CrudDetailAfter.js.map +1 -1
- package/dist/components/features/crud-detail/CrudDetailBefore.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-detail/CrudDetailBefore.js.map +1 -1
- package/dist/components/features/crud-detail/CrudDetailTools.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-detail/CrudDetailTools.js.map +1 -1
- package/dist/components/features/crud-detail/types.d.ts.map +1 -0
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-sheet/CrudSheet.js +166 -21
- package/dist/components/features/crud-sheet/CrudSheet.js.map +6 -0
- package/dist/components/features/crud-sheet/CrudSheetColumn.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-sheet/CrudSheetColumn.js +1 -1
- package/dist/components/{data → features}/crud-sheet/CrudSheetColumn.js.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheetFilter.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-sheet/CrudSheetFilter.js.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheetHeader.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-sheet/CrudSheetHeader.js.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheetTools.d.ts.map +1 -0
- package/dist/components/{data → features}/crud-sheet/CrudSheetTools.js.map +1 -1
- package/dist/components/{data → features}/crud-sheet/types.d.ts +10 -4
- package/dist/components/features/crud-sheet/types.d.ts.map +1 -0
- package/dist/components/features/data-select-button/DataSelectButton.d.ts +38 -0
- package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -0
- package/dist/components/features/data-select-button/DataSelectButton.js +184 -0
- package/dist/components/features/data-select-button/DataSelectButton.js.map +6 -0
- package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -0
- package/dist/components/{data → features}/permission-table/PermissionTable.js +1 -1
- package/dist/components/{data → features}/permission-table/PermissionTable.js.map +1 -1
- package/dist/components/features/shared-data/SharedDataSelect.d.ts +32 -0
- package/dist/components/features/shared-data/SharedDataSelect.d.ts.map +1 -0
- package/dist/components/features/shared-data/SharedDataSelect.js +74 -0
- package/dist/components/features/shared-data/SharedDataSelect.js.map +6 -0
- package/dist/components/features/shared-data/SharedDataSelectButton.d.ts +29 -0
- package/dist/components/features/shared-data/SharedDataSelectButton.d.ts.map +1 -0
- package/dist/components/features/shared-data/SharedDataSelectButton.js +17 -0
- package/dist/components/features/shared-data/SharedDataSelectButton.js.map +6 -0
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts +29 -0
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -0
- package/dist/components/features/shared-data/SharedDataSelectList.js +80 -0
- package/dist/components/features/shared-data/SharedDataSelectList.js.map +6 -0
- package/dist/components/form-control/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.js +10 -10
- package/dist/components/form-control/checkbox/Checkbox.js.map +2 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.styles.js +2 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.js.map +1 -1
- package/dist/components/form-control/checkbox/Radio.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Radio.js +13 -13
- package/dist/components/form-control/checkbox/Radio.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts +7 -3
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +146 -45
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/select-list/SelectList.d.ts +54 -0
- package/dist/components/form-control/select-list/SelectList.d.ts.map +1 -0
- package/dist/components/form-control/select-list/SelectList.js +280 -0
- package/dist/components/form-control/select-list/SelectList.js.map +6 -0
- package/dist/components/form-control/select-list/SelectListContext.d.ts +13 -0
- package/dist/components/form-control/select-list/SelectListContext.d.ts.map +1 -0
- package/dist/components/form-control/select-list/SelectListContext.js +14 -0
- package/dist/components/form-control/select-list/SelectListContext.js.map +6 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/providers/ServiceClientContext.d.ts +5 -5
- package/dist/providers/ServiceClientContext.d.ts.map +1 -1
- package/dist/providers/ServiceClientProvider.d.ts.map +1 -1
- package/dist/providers/ServiceClientProvider.js +12 -8
- package/dist/providers/ServiceClientProvider.js.map +2 -2
- package/dist/providers/shared-data/SharedDataContext.d.ts +16 -2
- package/dist/providers/shared-data/SharedDataContext.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataContext.js.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.d.ts +1 -2
- package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.js +27 -13
- package/dist/providers/shared-data/SharedDataProvider.js.map +2 -2
- package/docs/data-components.md +15 -4
- package/docs/form-controls.md +257 -0
- package/docs/hooks.md +30 -0
- package/docs/providers.md +7 -0
- package/package.json +5 -3
- package/src/components/data/sheet/DataSheet.tsx +6 -7
- package/src/components/features/address/AddressSearch.tsx +75 -0
- package/src/components/{data → features}/crud-detail/CrudDetail.tsx +51 -26
- package/src/components/{data → features}/crud-sheet/CrudSheet.tsx +160 -23
- package/src/components/{data → features}/crud-sheet/CrudSheetColumn.tsx +1 -1
- package/src/components/{data → features}/crud-sheet/types.ts +14 -4
- package/src/components/features/data-select-button/DataSelectButton.tsx +279 -0
- package/src/components/{data → features}/permission-table/PermissionTable.tsx +1 -1
- package/src/components/features/shared-data/SharedDataSelect.tsx +101 -0
- package/src/components/features/shared-data/SharedDataSelectButton.tsx +47 -0
- package/src/components/features/shared-data/SharedDataSelectList.tsx +85 -0
- package/src/components/form-control/checkbox/Checkbox.styles.ts +2 -2
- package/src/components/form-control/checkbox/Checkbox.tsx +18 -20
- package/src/components/form-control/checkbox/Radio.tsx +18 -20
- package/src/components/form-control/select/Select.tsx +192 -36
- package/src/components/form-control/select-list/SelectList.tsx +385 -0
- package/src/components/form-control/select-list/SelectListContext.ts +23 -0
- package/src/index.ts +29 -5
- package/src/providers/ServiceClientContext.ts +5 -5
- package/src/providers/ServiceClientProvider.tsx +17 -12
- package/src/providers/shared-data/SharedDataContext.ts +16 -2
- package/src/providers/shared-data/SharedDataProvider.tsx +33 -17
- package/dist/components/data/crud-detail/CrudDetail.d.ts.map +0 -1
- package/dist/components/data/crud-detail/CrudDetail.js.map +0 -6
- package/dist/components/data/crud-detail/CrudDetailAfter.d.ts.map +0 -1
- package/dist/components/data/crud-detail/CrudDetailBefore.d.ts.map +0 -1
- package/dist/components/data/crud-detail/CrudDetailTools.d.ts.map +0 -1
- package/dist/components/data/crud-detail/types.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/CrudSheet.js.map +0 -6
- package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/CrudSheetTools.d.ts.map +0 -1
- package/dist/components/data/crud-sheet/types.d.ts.map +0 -1
- package/dist/components/data/permission-table/PermissionTable.d.ts.map +0 -1
- /package/dist/components/{data → features}/crud-detail/CrudDetail.d.ts +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailAfter.d.ts +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailAfter.js +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailBefore.d.ts +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailBefore.js +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailTools.d.ts +0 -0
- /package/dist/components/{data → features}/crud-detail/CrudDetailTools.js +0 -0
- /package/dist/components/{data → features}/crud-detail/types.d.ts +0 -0
- /package/dist/components/{data → features}/crud-detail/types.js +0 -0
- /package/dist/components/{data → features}/crud-detail/types.js.map +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheet.d.ts +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetColumn.d.ts +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetFilter.d.ts +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetFilter.js +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetHeader.d.ts +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetHeader.js +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetTools.d.ts +0 -0
- /package/dist/components/{data → features}/crud-sheet/CrudSheetTools.js +0 -0
- /package/dist/components/{data → features}/crud-sheet/types.js +0 -0
- /package/dist/components/{data → features}/crud-sheet/types.js.map +0 -0
- /package/dist/components/{data → features}/permission-table/PermissionTable.d.ts +0 -0
- /package/src/components/{data → features}/crud-detail/CrudDetailAfter.tsx +0 -0
- /package/src/components/{data → features}/crud-detail/CrudDetailBefore.tsx +0 -0
- /package/src/components/{data → features}/crud-detail/CrudDetailTools.tsx +0 -0
- /package/src/components/{data → features}/crud-detail/types.ts +0 -0
- /package/src/components/{data → features}/crud-sheet/CrudSheetFilter.tsx +0 -0
- /package/src/components/{data → features}/crud-sheet/CrudSheetHeader.tsx +0 -0
- /package/src/components/{data → features}/crud-sheet/CrudSheetTools.tsx +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type Component, createSignal, onMount } from "solid-js";
|
|
2
|
+
import { BusyContainer } from "../../feedback/busy/BusyContainer";
|
|
3
|
+
import { useDialogInstance } from "../../disclosure/DialogInstanceContext";
|
|
4
|
+
|
|
5
|
+
export interface AddressSearchResult {
|
|
6
|
+
postNumber: string | undefined;
|
|
7
|
+
address: string | undefined;
|
|
8
|
+
buildingName: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const AddressSearchContent: Component = () => {
|
|
12
|
+
const dialogInstance = useDialogInstance<AddressSearchResult>();
|
|
13
|
+
|
|
14
|
+
const [initialized, setInitialized] = createSignal(false);
|
|
15
|
+
let contentEl!: HTMLDivElement;
|
|
16
|
+
|
|
17
|
+
onMount(async () => {
|
|
18
|
+
if (!document.getElementById("daum_address")) {
|
|
19
|
+
await new Promise<void>((resolve) => {
|
|
20
|
+
const scriptEl = document.createElement("script");
|
|
21
|
+
scriptEl.src = "//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js";
|
|
22
|
+
scriptEl.setAttribute("id", "daum_address");
|
|
23
|
+
|
|
24
|
+
scriptEl.onload = (): void => {
|
|
25
|
+
// @ts-expect-error -- Daum Postcode 글로벌 API
|
|
26
|
+
daum.postcode.load(() => {
|
|
27
|
+
resolve();
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
document.head.appendChild(scriptEl);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// @ts-expect-error -- Daum Postcode 글로벌 API
|
|
35
|
+
new daum.Postcode({
|
|
36
|
+
oncomplete: (data: any): void => {
|
|
37
|
+
const addr = data.userSelectedType === "R" ? data.roadAddress : data.jibunAddress;
|
|
38
|
+
|
|
39
|
+
let extraAddr = "";
|
|
40
|
+
if (data.userSelectedType === "R") {
|
|
41
|
+
if (data.bname !== "" && /[동로가]$/g.test(data.bname)) {
|
|
42
|
+
extraAddr += data.bname;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (data.buildingName !== "" && data.apartment === "Y") {
|
|
46
|
+
extraAddr += extraAddr !== "" ? ", " + data.buildingName : data.buildingName;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (extraAddr !== "") {
|
|
50
|
+
extraAddr = " (" + extraAddr + ")";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dialogInstance?.close({
|
|
55
|
+
postNumber: data.zonecode,
|
|
56
|
+
address: addr + extraAddr,
|
|
57
|
+
buildingName: data.buildingName,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onresize: (size: any): void => {
|
|
61
|
+
contentEl.style.height = size.height + "px";
|
|
62
|
+
},
|
|
63
|
+
width: "100%",
|
|
64
|
+
height: "100%",
|
|
65
|
+
}).embed(contentEl, { autoClose: false });
|
|
66
|
+
|
|
67
|
+
setInitialized(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<BusyContainer busy={!initialized()}>
|
|
72
|
+
<div ref={contentEl} data-address-content style={{ "min-height": "100px" }} />
|
|
73
|
+
</BusyContainer>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -200,6 +200,23 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
|
|
|
200
200
|
저장
|
|
201
201
|
</Button>
|
|
202
202
|
</Show>
|
|
203
|
+
<Show
|
|
204
|
+
when={
|
|
205
|
+
canEdit() && local.toggleDelete && info() && !info()!.isNew && (local.deletable ?? true)
|
|
206
|
+
}
|
|
207
|
+
>
|
|
208
|
+
{(_) => (
|
|
209
|
+
<Button
|
|
210
|
+
size="lg"
|
|
211
|
+
variant="ghost"
|
|
212
|
+
theme="danger"
|
|
213
|
+
onClick={() => void handleToggleDelete()}
|
|
214
|
+
>
|
|
215
|
+
<Icon icon={info()!.isDeleted ? IconTrashOff : IconTrash} class="mr-1" />
|
|
216
|
+
{info()!.isDeleted ? "복구" : "삭제"}
|
|
217
|
+
</Button>
|
|
218
|
+
)}
|
|
219
|
+
</Show>
|
|
203
220
|
<Button size="lg" variant="ghost" theme="info" onClick={() => void handleRefresh()}>
|
|
204
221
|
<Icon icon={IconRefresh} class="mr-1" />
|
|
205
222
|
새로고침
|
|
@@ -255,38 +272,46 @@ const CrudDetailBase = <TData extends object>(props: CrudDetailProps<TData>) =>
|
|
|
255
272
|
busy={busyCount() > 0}
|
|
256
273
|
class={clsx("flex h-full flex-col", local.class)}
|
|
257
274
|
>
|
|
258
|
-
{/* Toolbar
|
|
259
|
-
<Show when={!isModal &&
|
|
275
|
+
{/* Toolbar */}
|
|
276
|
+
<Show when={(!isModal && !topbarCtx) || defs().tools}>
|
|
260
277
|
<div class="flex gap-2 p-2 pb-0">
|
|
261
|
-
<Show when={
|
|
262
|
-
<
|
|
263
|
-
size="sm"
|
|
264
|
-
theme="primary"
|
|
265
|
-
variant="ghost"
|
|
266
|
-
onClick={() => formRef?.requestSubmit()}
|
|
267
|
-
>
|
|
268
|
-
<Icon icon={IconDeviceFloppy} class="mr-1" />
|
|
269
|
-
저장
|
|
270
|
-
</Button>
|
|
271
|
-
</Show>
|
|
272
|
-
<Button size="sm" theme="info" variant="ghost" onClick={() => void handleRefresh()}>
|
|
273
|
-
<Icon icon={IconRefresh} class="mr-1" />
|
|
274
|
-
새로고침
|
|
275
|
-
</Button>
|
|
276
|
-
<Show
|
|
277
|
-
when={local.toggleDelete && info() && !info()!.isNew && (local.deletable ?? true)}
|
|
278
|
-
>
|
|
279
|
-
{(_) => (
|
|
278
|
+
<Show when={!topbarCtx && !isModal}>
|
|
279
|
+
<Show when={canEdit() && local.submit}>
|
|
280
280
|
<Button
|
|
281
281
|
size="sm"
|
|
282
|
-
theme="
|
|
282
|
+
theme="primary"
|
|
283
283
|
variant="ghost"
|
|
284
|
-
onClick={() =>
|
|
284
|
+
onClick={() => formRef?.requestSubmit()}
|
|
285
285
|
>
|
|
286
|
-
<Icon icon={
|
|
287
|
-
|
|
286
|
+
<Icon icon={IconDeviceFloppy} class="mr-1" />
|
|
287
|
+
저장
|
|
288
288
|
</Button>
|
|
289
|
-
|
|
289
|
+
</Show>
|
|
290
|
+
<Show
|
|
291
|
+
when={
|
|
292
|
+
canEdit() &&
|
|
293
|
+
local.toggleDelete &&
|
|
294
|
+
info() &&
|
|
295
|
+
!info()!.isNew &&
|
|
296
|
+
(local.deletable ?? true)
|
|
297
|
+
}
|
|
298
|
+
>
|
|
299
|
+
{(_) => (
|
|
300
|
+
<Button
|
|
301
|
+
size="sm"
|
|
302
|
+
theme="danger"
|
|
303
|
+
variant="ghost"
|
|
304
|
+
onClick={() => void handleToggleDelete()}
|
|
305
|
+
>
|
|
306
|
+
<Icon icon={info()!.isDeleted ? IconTrashOff : IconTrash} class="mr-1" />
|
|
307
|
+
{info()!.isDeleted ? "복구" : "삭제"}
|
|
308
|
+
</Button>
|
|
309
|
+
)}
|
|
310
|
+
</Show>
|
|
311
|
+
<Button size="sm" theme="info" variant="ghost" onClick={() => void handleRefresh()}>
|
|
312
|
+
<Icon icon={IconRefresh} class="mr-1" />
|
|
313
|
+
새로고침
|
|
314
|
+
</Button>
|
|
290
315
|
</Show>
|
|
291
316
|
<Show when={defs().tools}>{(toolsDef) => toolsDef().children}</Show>
|
|
292
317
|
</div>
|
|
@@ -11,11 +11,12 @@ import {
|
|
|
11
11
|
} from "solid-js";
|
|
12
12
|
import { createStore, produce, reconcile } from "solid-js/store";
|
|
13
13
|
import { createControllableStore } from "../../../hooks/createControllableStore";
|
|
14
|
-
import {
|
|
14
|
+
import type { DateTime } from "@simplysm/core-common";
|
|
15
|
+
import { objClone, objGetChainValue } from "@simplysm/core-common";
|
|
15
16
|
import "@simplysm/core-common"; // register extensions
|
|
16
|
-
import type { SortingDef } from "
|
|
17
|
-
import { DataSheet } from "
|
|
18
|
-
import { DataSheetColumn } from "
|
|
17
|
+
import type { SortingDef } from "../../data/sheet/types";
|
|
18
|
+
import { DataSheet } from "../../data/sheet/DataSheet";
|
|
19
|
+
import { DataSheetColumn } from "../../data/sheet/DataSheetColumn";
|
|
19
20
|
import { BusyContainer } from "../../feedback/busy/BusyContainer";
|
|
20
21
|
import { useNotification } from "../../feedback/notification/NotificationContext";
|
|
21
22
|
import { Button } from "../../form-control/Button";
|
|
@@ -26,6 +27,7 @@ import { useDialogInstance } from "../../disclosure/DialogInstanceContext";
|
|
|
26
27
|
import { Dialog } from "../../disclosure/Dialog";
|
|
27
28
|
import { Link } from "../../display/Link";
|
|
28
29
|
import { createEventListener } from "@solid-primitives/event-listener";
|
|
30
|
+
import { useBeforeLeave } from "@solidjs/router";
|
|
29
31
|
import clsx from "clsx";
|
|
30
32
|
import {
|
|
31
33
|
IconDeviceFloppy,
|
|
@@ -67,11 +69,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
67
69
|
"search",
|
|
68
70
|
"getItemKey",
|
|
69
71
|
"persistKey",
|
|
70
|
-
"itemsPerPage",
|
|
71
72
|
"editable",
|
|
72
73
|
"itemEditable",
|
|
73
74
|
"itemDeletable",
|
|
74
75
|
"itemDeleted",
|
|
76
|
+
"isItemSelectable",
|
|
77
|
+
"lastModifiedAtProp",
|
|
78
|
+
"lastModifiedByProp",
|
|
79
|
+
"onSubmitted",
|
|
75
80
|
"filterInitial",
|
|
76
81
|
"items",
|
|
77
82
|
"onItemsChange",
|
|
@@ -123,6 +128,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
123
128
|
const [ready, setReady] = createSignal(false);
|
|
124
129
|
|
|
125
130
|
const [selectedItems, setSelectedItems] = createSignal<TItem[]>([]);
|
|
131
|
+
const [selectedKeys, setSelectedKeys] = createSignal<Set<string | number>>(new Set());
|
|
126
132
|
|
|
127
133
|
let formRef: HTMLFormElement | undefined;
|
|
128
134
|
|
|
@@ -130,6 +136,23 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
130
136
|
void doRefresh();
|
|
131
137
|
});
|
|
132
138
|
|
|
139
|
+
// -- Key-based selection: restore selectedItems when items change --
|
|
140
|
+
createEffect(() => {
|
|
141
|
+
const currentItems = items as unknown as TItem[];
|
|
142
|
+
const keys = selectedKeys();
|
|
143
|
+
if (keys.size === 0) {
|
|
144
|
+
if (selectedItems().length > 0) {
|
|
145
|
+
setSelectedItems([]);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const restored = currentItems.filter((item) => {
|
|
150
|
+
const key = local.getItemKey(item);
|
|
151
|
+
return key != null && keys.has(key);
|
|
152
|
+
});
|
|
153
|
+
setSelectedItems(restored);
|
|
154
|
+
});
|
|
155
|
+
|
|
133
156
|
async function doRefresh() {
|
|
134
157
|
setBusyCount((c) => c + 1);
|
|
135
158
|
try {
|
|
@@ -142,12 +165,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
async function refresh() {
|
|
145
|
-
const
|
|
146
|
-
const result: SearchResult<TItem> = await local.search(
|
|
147
|
-
lastFilter(),
|
|
148
|
-
usePagination ? page() : 0,
|
|
149
|
-
sorts(),
|
|
150
|
-
);
|
|
168
|
+
const result: SearchResult<TItem> = await local.search(lastFilter(), page(), sorts());
|
|
151
169
|
setItems(reconcile(result.items));
|
|
152
170
|
originalItems = objClone(result.items);
|
|
153
171
|
setTotalPageCount(result.pageCount ?? 0);
|
|
@@ -155,20 +173,28 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
155
173
|
|
|
156
174
|
/* eslint-disable solid/reactivity -- 이벤트 핸들러에서만 호출, store 즉시 읽기 */
|
|
157
175
|
function getItemDiffs() {
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
})
|
|
176
|
+
return items.oneWayDiffs(originalItems, (item) => local.getItemKey(item), {
|
|
177
|
+
excludes: local.inlineEdit?.diffsExcludes,
|
|
178
|
+
});
|
|
161
179
|
}
|
|
162
180
|
/* eslint-enable solid/reactivity */
|
|
163
181
|
|
|
182
|
+
function checkIgnoreChanges(): boolean {
|
|
183
|
+
if (!local.inlineEdit) return true;
|
|
184
|
+
if (getItemDiffs().length === 0) return true;
|
|
185
|
+
return confirm("변경사항이 있습니다. 무시하시겠습니까?");
|
|
186
|
+
}
|
|
187
|
+
|
|
164
188
|
// -- Filter --
|
|
165
189
|
function handleFilterSubmit(e: Event) {
|
|
166
190
|
e.preventDefault();
|
|
191
|
+
if (!checkIgnoreChanges()) return;
|
|
167
192
|
setPage(1);
|
|
168
193
|
setLastFilter(() => objClone(filter));
|
|
169
194
|
}
|
|
170
195
|
|
|
171
196
|
async function handleRefresh() {
|
|
197
|
+
if (!checkIgnoreChanges()) return;
|
|
172
198
|
await doRefresh();
|
|
173
199
|
}
|
|
174
200
|
|
|
@@ -216,6 +242,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
216
242
|
await local.inlineEdit.submit(diffs);
|
|
217
243
|
noti.success("저장 완료", "저장되었습니다.");
|
|
218
244
|
await refresh();
|
|
245
|
+
local.onSubmitted?.();
|
|
219
246
|
} catch (err) {
|
|
220
247
|
noti.error(err, "저장 실패");
|
|
221
248
|
}
|
|
@@ -257,13 +284,28 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
257
284
|
setBusyCount((c) => c - 1);
|
|
258
285
|
}
|
|
259
286
|
|
|
287
|
+
async function handleRestoreItems() {
|
|
288
|
+
if (!local.modalEdit?.restoreItems) return;
|
|
289
|
+
const result = await local.modalEdit.restoreItems(selectedItems());
|
|
290
|
+
if (!result) return;
|
|
291
|
+
|
|
292
|
+
setBusyCount((c) => c + 1);
|
|
293
|
+
try {
|
|
294
|
+
await refresh();
|
|
295
|
+
noti.success("복구 완료", "복구되었습니다.");
|
|
296
|
+
} catch (err) {
|
|
297
|
+
noti.error(err, "복구 실패");
|
|
298
|
+
}
|
|
299
|
+
setBusyCount((c) => c - 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
260
302
|
// -- Excel --
|
|
261
303
|
async function handleExcelDownload() {
|
|
262
304
|
if (!local.excel) return;
|
|
263
305
|
|
|
264
306
|
setBusyCount((c) => c + 1);
|
|
265
307
|
try {
|
|
266
|
-
const result = await local.search(lastFilter(),
|
|
308
|
+
const result = await local.search(lastFilter(), undefined, sorts());
|
|
267
309
|
await local.excel.download(result.items);
|
|
268
310
|
} catch (err) {
|
|
269
311
|
noti.error(err, "엑셀 다운로드 실패");
|
|
@@ -295,16 +337,51 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
295
337
|
}
|
|
296
338
|
|
|
297
339
|
// -- Select Mode --
|
|
340
|
+
function handleSelectedItemsChange(newSelectedItems: TItem[]) {
|
|
341
|
+
// 현재 페이지 아이템들의 key Set
|
|
342
|
+
const currentItems = items as unknown as TItem[];
|
|
343
|
+
const currentKeys = new Set<string | number>();
|
|
344
|
+
for (const item of currentItems) {
|
|
345
|
+
const key = local.getItemKey(item);
|
|
346
|
+
if (key != null) currentKeys.add(key);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 새로 선택된 아이템들의 key
|
|
350
|
+
const newSelectedKeys = new Set<string | number>();
|
|
351
|
+
for (const item of newSelectedItems) {
|
|
352
|
+
const key = local.getItemKey(item);
|
|
353
|
+
if (key != null) newSelectedKeys.add(key);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 다른 페이지 key 보존 + 현재 페이지 key 갱신
|
|
357
|
+
const merged = new Set<string | number>();
|
|
358
|
+
for (const key of selectedKeys()) {
|
|
359
|
+
if (!currentKeys.has(key)) {
|
|
360
|
+
merged.add(key); // 다른 페이지 key 보존
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const key of newSelectedKeys) {
|
|
364
|
+
merged.add(key); // 현재 페이지 선택 추가
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
setSelectedKeys(merged);
|
|
368
|
+
setSelectedItems(newSelectedItems);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function clearSelection() {
|
|
372
|
+
setSelectedKeys(new Set<string | number>());
|
|
373
|
+
setSelectedItems([]);
|
|
374
|
+
}
|
|
375
|
+
|
|
298
376
|
function handleSelectConfirm() {
|
|
299
377
|
local.onSelect?.({
|
|
300
378
|
items: selectedItems(),
|
|
301
|
-
keys:
|
|
302
|
-
.map((item) => local.getItemKey(item))
|
|
303
|
-
.filter((k): k is string | number => k != null),
|
|
379
|
+
keys: [...selectedKeys()],
|
|
304
380
|
});
|
|
305
381
|
}
|
|
306
382
|
|
|
307
383
|
function handleSelectCancel() {
|
|
384
|
+
clearSelection();
|
|
308
385
|
local.onSelect?.({ items: [], keys: [] });
|
|
309
386
|
}
|
|
310
387
|
|
|
@@ -316,10 +393,25 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
316
393
|
}
|
|
317
394
|
if (e.ctrlKey && e.altKey && e.key === "l") {
|
|
318
395
|
e.preventDefault();
|
|
396
|
+
if (!checkIgnoreChanges()) return;
|
|
319
397
|
await doRefresh();
|
|
320
398
|
}
|
|
321
399
|
});
|
|
322
400
|
|
|
401
|
+
// -- Route Leave Guard --
|
|
402
|
+
// eslint-disable-next-line solid/reactivity -- inlineEdit는 초기 설정값으로만 사용
|
|
403
|
+
if (!isModal && local.inlineEdit) {
|
|
404
|
+
try {
|
|
405
|
+
useBeforeLeave((e) => {
|
|
406
|
+
if (!checkIgnoreChanges()) {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
// Router context 없으면 skip
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
323
415
|
// -- Topbar Actions --
|
|
324
416
|
if (topbarCtx) {
|
|
325
417
|
createTopbarActions(() => (
|
|
@@ -361,6 +453,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
361
453
|
addItem: handleAddRow,
|
|
362
454
|
setPage,
|
|
363
455
|
setSorts,
|
|
456
|
+
clearSelection,
|
|
364
457
|
};
|
|
365
458
|
|
|
366
459
|
// -- Render --
|
|
@@ -458,13 +551,32 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
458
551
|
onClick={handleDeleteItems}
|
|
459
552
|
disabled={
|
|
460
553
|
selectedItems().length === 0 ||
|
|
461
|
-
!selectedItems().some(
|
|
554
|
+
!selectedItems().some(
|
|
555
|
+
(item) =>
|
|
556
|
+
(local.itemDeletable?.(item) ?? true) &&
|
|
557
|
+
!(local.itemDeleted?.(item) ?? false),
|
|
558
|
+
)
|
|
462
559
|
}
|
|
463
560
|
>
|
|
464
561
|
<Icon icon={IconTrash} class="mr-1" />
|
|
465
562
|
선택 삭제
|
|
466
563
|
</Button>
|
|
467
564
|
</Show>
|
|
565
|
+
<Show when={canEdit() && local.modalEdit?.restoreItems}>
|
|
566
|
+
<Button
|
|
567
|
+
size="sm"
|
|
568
|
+
theme="warning"
|
|
569
|
+
variant="ghost"
|
|
570
|
+
onClick={handleRestoreItems}
|
|
571
|
+
disabled={
|
|
572
|
+
selectedItems().length === 0 ||
|
|
573
|
+
!selectedItems().some((item) => local.itemDeleted?.(item) ?? false)
|
|
574
|
+
}
|
|
575
|
+
>
|
|
576
|
+
<Icon icon={IconTrashOff} class="mr-1" />
|
|
577
|
+
선택 복구
|
|
578
|
+
</Button>
|
|
579
|
+
</Show>
|
|
468
580
|
|
|
469
581
|
{/* Excel buttons */}
|
|
470
582
|
<Show when={canEdit() && local.excel?.upload}>
|
|
@@ -492,21 +604,21 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
492
604
|
class="h-full"
|
|
493
605
|
items={items}
|
|
494
606
|
persistKey={local.persistKey != null ? `${local.persistKey}-sheet` : undefined}
|
|
495
|
-
page={
|
|
607
|
+
page={totalPageCount() > 0 ? page() : undefined}
|
|
496
608
|
onPageChange={setPage}
|
|
497
609
|
totalPageCount={totalPageCount()}
|
|
498
|
-
itemsPerPage={local.itemsPerPage}
|
|
499
610
|
sorts={sorts()}
|
|
500
611
|
onSortsChange={setSorts}
|
|
612
|
+
isItemSelectable={local.isItemSelectable}
|
|
501
613
|
selectMode={
|
|
502
614
|
isSelectMode()
|
|
503
615
|
? local.selectMode
|
|
504
|
-
: local.modalEdit?.deleteItems != null
|
|
616
|
+
: local.modalEdit?.deleteItems != null || local.modalEdit?.restoreItems != null
|
|
505
617
|
? "multiple"
|
|
506
618
|
: undefined
|
|
507
619
|
}
|
|
508
620
|
selectedItems={selectedItems()}
|
|
509
|
-
onSelectedItemsChange={
|
|
621
|
+
onSelectedItemsChange={handleSelectedItemsChange}
|
|
510
622
|
autoSelect={isSelectMode() && local.selectMode === "single" ? "click" : undefined}
|
|
511
623
|
cellClass={(item) => {
|
|
512
624
|
if (isItemDeleted(item)) {
|
|
@@ -593,6 +705,31 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
593
705
|
</DataSheetColumn>
|
|
594
706
|
)}
|
|
595
707
|
</For>
|
|
708
|
+
|
|
709
|
+
{/* Auto lastModified columns */}
|
|
710
|
+
<Show when={local.lastModifiedAtProp}>
|
|
711
|
+
<DataSheetColumn<TItem> key={local.lastModifiedAtProp!} header="수정일시" hidden>
|
|
712
|
+
{(dsCtx) => (
|
|
713
|
+
<div class="px-2 py-1 text-center">
|
|
714
|
+
{(
|
|
715
|
+
objGetChainValue(dsCtx.item, local.lastModifiedAtProp!, true) as
|
|
716
|
+
| DateTime
|
|
717
|
+
| undefined
|
|
718
|
+
)?.toFormatString("yyyy-MM-dd HH:mm")}
|
|
719
|
+
</div>
|
|
720
|
+
)}
|
|
721
|
+
</DataSheetColumn>
|
|
722
|
+
</Show>
|
|
723
|
+
|
|
724
|
+
<Show when={local.lastModifiedByProp}>
|
|
725
|
+
<DataSheetColumn<TItem> key={local.lastModifiedByProp!} header="수정자" hidden>
|
|
726
|
+
{(dsCtx) => (
|
|
727
|
+
<div class="px-2 py-1 text-center">
|
|
728
|
+
{objGetChainValue(dsCtx.item, local.lastModifiedByProp!, true) as string}
|
|
729
|
+
</div>
|
|
730
|
+
)}
|
|
731
|
+
</DataSheetColumn>
|
|
732
|
+
</Show>
|
|
596
733
|
</DataSheet>
|
|
597
734
|
</form>
|
|
598
735
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "solid-js";
|
|
2
2
|
import type { CrudSheetColumnDef, CrudSheetColumnProps } from "./types";
|
|
3
|
-
import { normalizeHeader } from "
|
|
3
|
+
import { normalizeHeader } from "../../data/sheet/sheetUtils";
|
|
4
4
|
|
|
5
5
|
export function isCrudSheetColumnDef(value: unknown): value is CrudSheetColumnDef<unknown> {
|
|
6
6
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JSX } from "solid-js";
|
|
2
2
|
import type { SetStoreFunction } from "solid-js/store";
|
|
3
3
|
import type { ArrayDiffs2Result } from "@simplysm/core-common";
|
|
4
|
-
import type { DataSheetColumnProps, SortingDef } from "
|
|
4
|
+
import type { DataSheetColumnProps, SortingDef } from "../../data/sheet/types";
|
|
5
5
|
|
|
6
6
|
// ── Search ──
|
|
7
7
|
|
|
@@ -16,11 +16,13 @@ export interface InlineEditConfig<TItem> {
|
|
|
16
16
|
submit: (diffs: ArrayDiffs2Result<TItem>[]) => Promise<void>;
|
|
17
17
|
newItem: () => TItem;
|
|
18
18
|
deleteProp?: keyof TItem & string;
|
|
19
|
+
diffsExcludes?: string[];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface ModalEditConfig<TItem> {
|
|
22
|
-
editItem: (item?: TItem) => Promise<boolean>;
|
|
23
|
+
editItem: (item?: TItem) => Promise<boolean | undefined>;
|
|
23
24
|
deleteItems?: (items: TItem[]) => Promise<boolean>;
|
|
25
|
+
restoreItems?: (items: TItem[]) => Promise<boolean>;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface ExcelConfig<TItem> {
|
|
@@ -56,6 +58,7 @@ export interface CrudSheetContext<TItem> {
|
|
|
56
58
|
save(): Promise<void>;
|
|
57
59
|
refresh(): Promise<void>;
|
|
58
60
|
addItem(): void;
|
|
61
|
+
clearSelection(): void;
|
|
59
62
|
setPage(page: number): void;
|
|
60
63
|
setSorts(sorts: SortingDef[]): void;
|
|
61
64
|
}
|
|
@@ -73,20 +76,27 @@ export type CrudSheetProps<TItem, TFilter extends Record<string, any>> = CrudShe
|
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
|
|
76
|
-
search: (
|
|
79
|
+
search: (
|
|
80
|
+
filter: TFilter,
|
|
81
|
+
page: number | undefined,
|
|
82
|
+
sorts: SortingDef[],
|
|
83
|
+
) => Promise<SearchResult<TItem>>;
|
|
77
84
|
getItemKey: (item: TItem) => string | number | undefined;
|
|
78
85
|
persistKey?: string;
|
|
79
|
-
itemsPerPage?: number;
|
|
80
86
|
editable?: boolean;
|
|
81
87
|
itemEditable?: (item: TItem) => boolean;
|
|
82
88
|
itemDeletable?: (item: TItem) => boolean;
|
|
83
89
|
itemDeleted?: (item: TItem) => boolean;
|
|
90
|
+
isItemSelectable?: (item: TItem) => boolean | string;
|
|
91
|
+
lastModifiedAtProp?: string;
|
|
92
|
+
lastModifiedByProp?: string;
|
|
84
93
|
filterInitial?: TFilter;
|
|
85
94
|
items?: TItem[];
|
|
86
95
|
onItemsChange?: (items: TItem[]) => void;
|
|
87
96
|
excel?: ExcelConfig<TItem>;
|
|
88
97
|
selectMode?: "single" | "multiple";
|
|
89
98
|
onSelect?: (result: SelectResult<TItem>) => void;
|
|
99
|
+
onSubmitted?: () => void;
|
|
90
100
|
hideAutoTools?: boolean;
|
|
91
101
|
class?: string;
|
|
92
102
|
children: JSX.Element;
|