@wipco/sdui 0.1.0 → 0.3.0
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/LICENSE +21 -0
- package/NOTICE +27 -0
- package/dist/chunk-4SU2KYPU.js +59 -0
- package/dist/chunk-56KKWLNI.js +186 -0
- package/dist/chunk-7Y4J7EOK.js +115 -0
- package/dist/chunk-APCXOCAO.js +110 -0
- package/dist/chunk-AROOQ5W6.js +119 -0
- package/dist/chunk-DGPRX4MU.js +251 -0
- package/dist/chunk-IL2KS4WS.js +25 -0
- package/dist/chunk-KADFN76X.js +541 -0
- package/dist/chunk-KBHI3IUR.js +40 -0
- package/dist/chunk-LZJTKVMO.js +767 -0
- package/dist/chunk-MOIU7MUA.js +282 -0
- package/dist/chunk-MSAV4J4I.js +77 -0
- package/dist/chunk-OOHHW4K4.js +159 -0
- package/dist/chunk-Q6SBH63R.js +190 -0
- package/dist/chunk-QZQDHSMD.js +78 -0
- package/dist/chunk-RH7ESUE6.js +309 -0
- package/dist/chunk-SEBPBUZR.js +260 -0
- package/dist/chunk-USKRM22W.js +39 -0
- package/dist/chunk-WNYWTFGW.js +61 -0
- package/dist/chunk-WVRCIVAD.js +72 -0
- package/dist/chunk-YJMD6MOE.js +404 -0
- package/dist/chunk-ZNITJ4KK.js +433 -0
- package/dist/chunk-ZQR3ULVH.js +193 -0
- package/dist/chunk-ZUCI34QY.js +1290 -0
- package/dist/index.d.ts +391 -79
- package/dist/index.js +277 -758
- package/dist/meta.d.ts +10 -2
- package/dist/meta.js +268 -27
- package/dist/rich.js +3 -2
- package/dist/styles.css +334 -13
- package/dist/widgets/AutocompleteField.d.ts +96 -0
- package/dist/widgets/AutocompleteField.js +3 -0
- package/dist/widgets/ColumnBoard.d.ts +47 -0
- package/dist/widgets/ColumnBoard.js +2 -0
- package/dist/widgets/DiffView.d.ts +30 -0
- package/dist/widgets/DiffView.js +2 -0
- package/dist/widgets/Disclosure.d.ts +20 -0
- package/dist/widgets/Disclosure.js +2 -0
- package/dist/widgets/EventCalendar.d.ts +59 -0
- package/dist/widgets/EventCalendar.js +2 -0
- package/dist/widgets/ImageLightbox.d.ts +25 -0
- package/dist/widgets/ImageLightbox.js +2 -0
- package/dist/widgets/InlineEdit.d.ts +23 -0
- package/dist/widgets/InlineEdit.js +2 -0
- package/dist/widgets/JsonField.d.ts +24 -0
- package/dist/widgets/JsonField.js +2 -0
- package/dist/widgets/LiveClock.d.ts +21 -0
- package/dist/widgets/LiveClock.js +1 -0
- package/dist/widgets/OrgChart.d.ts +32 -0
- package/dist/widgets/OrgChart.js +2 -0
- package/dist/widgets/PeriodFilter.d.ts +48 -0
- package/dist/widgets/PeriodFilter.js +2 -0
- package/dist/widgets/PrintSheet.d.ts +23 -0
- package/dist/widgets/PrintSheet.js +2 -0
- package/dist/widgets/SearchableMultiPicker.d.ts +29 -0
- package/dist/widgets/SearchableMultiPicker.js +3 -0
- package/dist/widgets/SecureScreen.d.ts +25 -0
- package/dist/widgets/SecureScreen.js +2 -0
- package/dist/widgets/StackedMeter.d.ts +26 -0
- package/dist/widgets/StackedMeter.js +1 -0
- package/dist/widgets/Strip.d.ts +20 -0
- package/dist/widgets/Strip.js +1 -0
- package/dist/widgets/SummaryTable.d.ts +31 -0
- package/dist/widgets/SummaryTable.js +1 -0
- package/dist/widgets/TagMultiSelect.d.ts +25 -0
- package/dist/widgets/TagMultiSelect.js +2 -0
- package/dist/widgets/Timeline.d.ts +30 -0
- package/dist/widgets/Timeline.js +3 -0
- package/dist/widgets/TreePicker.d.ts +21 -0
- package/dist/widgets/TreePicker.js +3 -0
- package/dist/widgets/TreeView.d.ts +26 -0
- package/dist/widgets/TreeView.js +2 -0
- package/dist/widgets/index.d.ts +76 -0
- package/dist/widgets/index.js +69 -0
- package/package.json +93 -3
- package/dist/chunk-AR2JYE75.js +0 -889
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { Tag, Input, Button, IconButton, Field, Checkbox } from './chunk-ZUCI34QY.js';
|
|
2
|
+
import { useRef, useEffect, useState, useMemo } from 'react';
|
|
3
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function Popover({
|
|
6
|
+
open,
|
|
7
|
+
onClose,
|
|
8
|
+
trigger,
|
|
9
|
+
footer,
|
|
10
|
+
alignLeft = true,
|
|
11
|
+
width,
|
|
12
|
+
children
|
|
13
|
+
}) {
|
|
14
|
+
const hostRef = useRef(null);
|
|
15
|
+
const closeRef = useRef(onClose);
|
|
16
|
+
closeRef.current = onClose;
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!open) return;
|
|
19
|
+
const closeOnOutside = (event) => {
|
|
20
|
+
if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
|
|
21
|
+
closeRef.current();
|
|
22
|
+
};
|
|
23
|
+
const closeOnEscape = (event) => {
|
|
24
|
+
if (event.key === "Escape") closeRef.current();
|
|
25
|
+
};
|
|
26
|
+
document.addEventListener("pointerdown", closeOnOutside);
|
|
27
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
28
|
+
return () => {
|
|
29
|
+
document.removeEventListener("pointerdown", closeOnOutside);
|
|
30
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
31
|
+
};
|
|
32
|
+
}, [open]);
|
|
33
|
+
return (
|
|
34
|
+
// 트리거와 패널을 함께 감싸는 기준 상자 — .erp-popover-host의 position:relative가
|
|
35
|
+
// 패널 absolute의 기준이 된다
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { ref: hostRef, className: "erp-popover-host", children: [
|
|
37
|
+
trigger,
|
|
38
|
+
open ? (
|
|
39
|
+
// 기본은 오른쪽 기준(.erp-popover), --left가 붙으면 왼쪽 기준으로 뒤집힌다
|
|
40
|
+
/* @__PURE__ */ jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: alignLeft ? "erp-popover erp-popover--left" : "erp-popover",
|
|
44
|
+
style: width ? { width } : void 0,
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx("div", { className: "erp-popover__body", children }),
|
|
47
|
+
footer ? /* @__PURE__ */ jsx("div", { className: "erp-popover__foot", children: footer }) : null
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
) : null
|
|
52
|
+
] })
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function splitMultiValue(value) {
|
|
56
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
function joinMultiValue(values) {
|
|
59
|
+
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).sort().join(",");
|
|
60
|
+
}
|
|
61
|
+
function normalizeSearchText(value) {
|
|
62
|
+
return value.toLowerCase().replace(/\s+/g, "");
|
|
63
|
+
}
|
|
64
|
+
function includesLooseSearch(text, keyword) {
|
|
65
|
+
const normalizedKeyword = normalizeSearchText(keyword);
|
|
66
|
+
return normalizedKeyword.length === 0 || normalizeSearchText(text).includes(normalizedKeyword);
|
|
67
|
+
}
|
|
68
|
+
function searchFieldKeys(fields) {
|
|
69
|
+
return fields.flatMap((field) => {
|
|
70
|
+
if (field.type === "dateRange") {
|
|
71
|
+
return [field.fromKey ?? `${field.key}From`, field.toKey ?? `${field.key}To`];
|
|
72
|
+
}
|
|
73
|
+
if (field.type === "numberRange") {
|
|
74
|
+
return [field.fromKey ?? `${field.key}Min`, field.toKey ?? `${field.key}Max`];
|
|
75
|
+
}
|
|
76
|
+
return [field.key];
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function searchValuesFromParams(fields, params) {
|
|
80
|
+
const values = {};
|
|
81
|
+
for (const key of searchFieldKeys(fields)) {
|
|
82
|
+
const value = params.get(key);
|
|
83
|
+
if (value) values[key] = value;
|
|
84
|
+
}
|
|
85
|
+
return values;
|
|
86
|
+
}
|
|
87
|
+
function cleanSearchValues(fields, values) {
|
|
88
|
+
const allowed = new Set(searchFieldKeys(fields));
|
|
89
|
+
const fieldsByKey = new Map(fields.map((field) => [field.key, field]));
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
Object.entries(values).map(([key, value]) => {
|
|
92
|
+
const field = fieldsByKey.get(key);
|
|
93
|
+
const cleaned = field?.type === "select" || field?.type === "cascading" ? joinMultiValue(splitMultiValue(value)) : value.trim();
|
|
94
|
+
return [key, cleaned];
|
|
95
|
+
}).filter(([key, value]) => allowed.has(key) && value)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function cascadingPathMap(options) {
|
|
99
|
+
const paths = /* @__PURE__ */ new Map();
|
|
100
|
+
const visit = (option, parents) => {
|
|
101
|
+
const path = [...parents, option];
|
|
102
|
+
paths.set(option.value, path);
|
|
103
|
+
option.children?.forEach((child) => visit(child, path));
|
|
104
|
+
};
|
|
105
|
+
options.forEach((option) => visit(option, []));
|
|
106
|
+
return paths;
|
|
107
|
+
}
|
|
108
|
+
function cascadingValuePaths(options) {
|
|
109
|
+
const map = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const [value, path] of cascadingPathMap(options)) {
|
|
111
|
+
map.set(
|
|
112
|
+
value,
|
|
113
|
+
path.map((option) => option.value)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return map;
|
|
117
|
+
}
|
|
118
|
+
function cascadingPathLabel(pathMap, value) {
|
|
119
|
+
const path = pathMap.get(value);
|
|
120
|
+
return path?.length ? path.map((option) => option.label).join(" > ") : value;
|
|
121
|
+
}
|
|
122
|
+
function activeSearchCriteria(fields, values) {
|
|
123
|
+
const criteria = [];
|
|
124
|
+
for (const field of fields) {
|
|
125
|
+
if (field.type === "select") {
|
|
126
|
+
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
127
|
+
const label = field.options.find((o) => o.value === value2)?.label ?? value2;
|
|
128
|
+
criteria.push({
|
|
129
|
+
key: `${field.key}:${value2}`,
|
|
130
|
+
label: `${field.label}: ${label}`,
|
|
131
|
+
clearKeys: [field.key],
|
|
132
|
+
valueKey: field.key,
|
|
133
|
+
value: value2
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (field.type === "cascading") {
|
|
139
|
+
const pathMap = cascadingPathMap(field.options);
|
|
140
|
+
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
141
|
+
criteria.push({
|
|
142
|
+
key: `${field.key}:${value2}`,
|
|
143
|
+
label: `${field.label}: ${cascadingPathLabel(pathMap, value2)}`,
|
|
144
|
+
clearKeys: [field.key],
|
|
145
|
+
valueKey: field.key,
|
|
146
|
+
value: value2
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (field.type === "dateRange" || field.type === "numberRange") {
|
|
152
|
+
const fromKey = field.fromKey ?? `${field.key}${field.type === "dateRange" ? "From" : "Min"}`;
|
|
153
|
+
const toKey = field.toKey ?? `${field.key}${field.type === "dateRange" ? "To" : "Max"}`;
|
|
154
|
+
const from = values[fromKey];
|
|
155
|
+
const to = values[toKey];
|
|
156
|
+
if (from || to) {
|
|
157
|
+
criteria.push({
|
|
158
|
+
key: field.key,
|
|
159
|
+
label: `${field.label}: ${from ?? ""}~${to ?? ""}`,
|
|
160
|
+
clearKeys: [fromKey, toKey]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const value = values[field.key];
|
|
166
|
+
if (value) {
|
|
167
|
+
criteria.push({
|
|
168
|
+
key: field.key,
|
|
169
|
+
label: `${field.label}: ${value}`,
|
|
170
|
+
clearKeys: [field.key]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return criteria;
|
|
175
|
+
}
|
|
176
|
+
var EMPTY_FIELDS = [];
|
|
177
|
+
var EMPTY_VALUES = {};
|
|
178
|
+
function SearchToolbar({
|
|
179
|
+
search,
|
|
180
|
+
onSearchChange,
|
|
181
|
+
onSearch,
|
|
182
|
+
advancedFields = EMPTY_FIELDS,
|
|
183
|
+
advancedValues = EMPTY_VALUES,
|
|
184
|
+
placeholder = "\uAC80\uC0C9",
|
|
185
|
+
className,
|
|
186
|
+
hideSearchInput = false
|
|
187
|
+
}) {
|
|
188
|
+
const [open, setOpen] = useState(false);
|
|
189
|
+
const [draft, setDraft] = useState(advancedValues);
|
|
190
|
+
const hasAdvanced = advancedFields.length > 0;
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!open) setDraft(advancedValues);
|
|
193
|
+
}, [advancedValues, open]);
|
|
194
|
+
const activeCriteria = useMemo(
|
|
195
|
+
() => activeSearchCriteria(advancedFields, advancedValues),
|
|
196
|
+
[advancedFields, advancedValues]
|
|
197
|
+
);
|
|
198
|
+
const submit = (event) => {
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
onSearch(search.trim(), cleanSearchValues(advancedFields, advancedValues));
|
|
201
|
+
setOpen(false);
|
|
202
|
+
};
|
|
203
|
+
const applyAdvancedSearch = () => {
|
|
204
|
+
onSearch(search.trim(), cleanSearchValues(advancedFields, draft));
|
|
205
|
+
setOpen(false);
|
|
206
|
+
};
|
|
207
|
+
const resetAdvancedSearch = () => {
|
|
208
|
+
setDraft({});
|
|
209
|
+
onSearch(search.trim(), {});
|
|
210
|
+
setOpen(false);
|
|
211
|
+
};
|
|
212
|
+
const removeCriterion = (criterion) => {
|
|
213
|
+
const nextValues = { ...advancedValues };
|
|
214
|
+
if (criterion.valueKey && criterion.value !== void 0) {
|
|
215
|
+
const remaining = splitMultiValue(nextValues[criterion.valueKey] ?? "").filter(
|
|
216
|
+
(value) => value !== criterion.value
|
|
217
|
+
);
|
|
218
|
+
if (remaining.length > 0) nextValues[criterion.valueKey] = joinMultiValue(remaining);
|
|
219
|
+
else delete nextValues[criterion.valueKey];
|
|
220
|
+
} else {
|
|
221
|
+
criterion.clearKeys.forEach((key) => delete nextValues[key]);
|
|
222
|
+
}
|
|
223
|
+
setDraft(nextValues);
|
|
224
|
+
onSearch(search.trim(), cleanSearchValues(advancedFields, nextValues));
|
|
225
|
+
};
|
|
226
|
+
return /* @__PURE__ */ jsxs("form", { className: ["wp-row", className].filter(Boolean).join(" "), onSubmit: submit, children: [
|
|
227
|
+
activeCriteria.length > 0 ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: { flexWrap: "wrap" }, children: activeCriteria.map((criterion) => /* @__PURE__ */ jsx(Tag, { onRemove: () => removeCriterion(criterion), children: criterion.label }, criterion.key)) }) : null,
|
|
228
|
+
!hideSearchInput ? /* @__PURE__ */ jsx(
|
|
229
|
+
Input,
|
|
230
|
+
{
|
|
231
|
+
icon: "search",
|
|
232
|
+
value: search,
|
|
233
|
+
onChange: (event) => onSearchChange(event.target.value),
|
|
234
|
+
placeholder
|
|
235
|
+
}
|
|
236
|
+
) : null,
|
|
237
|
+
hasAdvanced ? /* @__PURE__ */ jsx(
|
|
238
|
+
Popover,
|
|
239
|
+
{
|
|
240
|
+
open,
|
|
241
|
+
onClose: () => setOpen(false),
|
|
242
|
+
alignLeft: false,
|
|
243
|
+
width: "min(560px, 80vw)",
|
|
244
|
+
trigger: (
|
|
245
|
+
// 검색 입력창 옆에 나란히 서는 자리라 테두리가 있어야 한 덩어리로 읽힌다.
|
|
246
|
+
// 열린 상태는 인라인 배경이 아니라 pressed(aria-pressed 틴트)로 알린다.
|
|
247
|
+
/* @__PURE__ */ jsx(
|
|
248
|
+
IconButton,
|
|
249
|
+
{
|
|
250
|
+
type: "button",
|
|
251
|
+
icon: "sliders-horizontal",
|
|
252
|
+
label: "\uC0C1\uC138\uAC80\uC0C9",
|
|
253
|
+
variant: "outlined",
|
|
254
|
+
pressed: open,
|
|
255
|
+
"aria-expanded": open,
|
|
256
|
+
onClick: () => setOpen((value) => !value)
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
),
|
|
260
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
261
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", icon: "refresh-cw", onClick: resetAdvancedSearch, children: "\uCD08\uAE30\uD654" }),
|
|
262
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: applyAdvancedSearch, children: "\uC801\uC6A9" })
|
|
263
|
+
] }),
|
|
264
|
+
children: /* @__PURE__ */ jsx("div", { className: "wp-grid wp-grid--2", children: advancedFields.map((field) => /* @__PURE__ */ jsx(
|
|
265
|
+
SearchFieldInput,
|
|
266
|
+
{
|
|
267
|
+
field,
|
|
268
|
+
values: draft,
|
|
269
|
+
onChange: (patch) => setDraft((current) => ({ ...current, ...patch }))
|
|
270
|
+
},
|
|
271
|
+
field.key
|
|
272
|
+
)) })
|
|
273
|
+
}
|
|
274
|
+
) : null
|
|
275
|
+
] });
|
|
276
|
+
}
|
|
277
|
+
function SearchFieldInput({
|
|
278
|
+
field,
|
|
279
|
+
values,
|
|
280
|
+
onChange
|
|
281
|
+
}) {
|
|
282
|
+
if (field.type === "select") {
|
|
283
|
+
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(MultiSelectInput, { field, values, onChange }) });
|
|
284
|
+
}
|
|
285
|
+
if (field.type === "cascading") {
|
|
286
|
+
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(CascadingInput, { field, values, onChange }) });
|
|
287
|
+
}
|
|
288
|
+
if (field.type === "dateRange") {
|
|
289
|
+
const fromKey = field.fromKey ?? `${field.key}From`;
|
|
290
|
+
const toKey = field.toKey ?? `${field.key}To`;
|
|
291
|
+
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
292
|
+
/* @__PURE__ */ jsx(
|
|
293
|
+
Input,
|
|
294
|
+
{
|
|
295
|
+
type: "date",
|
|
296
|
+
mono: true,
|
|
297
|
+
value: values[fromKey] ?? "",
|
|
298
|
+
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
299
|
+
}
|
|
300
|
+
),
|
|
301
|
+
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
Input,
|
|
304
|
+
{
|
|
305
|
+
type: "date",
|
|
306
|
+
mono: true,
|
|
307
|
+
value: values[toKey] ?? "",
|
|
308
|
+
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
] }) });
|
|
312
|
+
}
|
|
313
|
+
if (field.type === "numberRange") {
|
|
314
|
+
const fromKey = field.fromKey ?? `${field.key}Min`;
|
|
315
|
+
const toKey = field.toKey ?? `${field.key}Max`;
|
|
316
|
+
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
317
|
+
/* @__PURE__ */ jsx(
|
|
318
|
+
Input,
|
|
319
|
+
{
|
|
320
|
+
type: "number",
|
|
321
|
+
mono: true,
|
|
322
|
+
placeholder: field.minPlaceholder ?? "\uCD5C\uC18C",
|
|
323
|
+
value: values[fromKey] ?? "",
|
|
324
|
+
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
325
|
+
}
|
|
326
|
+
),
|
|
327
|
+
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
328
|
+
/* @__PURE__ */ jsx(
|
|
329
|
+
Input,
|
|
330
|
+
{
|
|
331
|
+
type: "number",
|
|
332
|
+
mono: true,
|
|
333
|
+
placeholder: field.maxPlaceholder ?? "\uCD5C\uB300",
|
|
334
|
+
value: values[toKey] ?? "",
|
|
335
|
+
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
] }) });
|
|
339
|
+
}
|
|
340
|
+
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(
|
|
341
|
+
Input,
|
|
342
|
+
{
|
|
343
|
+
placeholder: field.placeholder,
|
|
344
|
+
value: values[field.key] ?? "",
|
|
345
|
+
onChange: (e) => onChange({ [field.key]: e.target.value })
|
|
346
|
+
}
|
|
347
|
+
) });
|
|
348
|
+
}
|
|
349
|
+
function multiSelectLabel(field, values) {
|
|
350
|
+
if (values.length === 0) return field.allLabel ?? "\uC804\uCCB4";
|
|
351
|
+
const labels = values.map((value) => field.options.find((item) => item.value === value)?.label ?? value);
|
|
352
|
+
if (labels.length <= 2) return labels.join(", ");
|
|
353
|
+
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
354
|
+
}
|
|
355
|
+
function MultiSelectInput({
|
|
356
|
+
field,
|
|
357
|
+
values,
|
|
358
|
+
onChange
|
|
359
|
+
}) {
|
|
360
|
+
const [open, setOpen] = useState(false);
|
|
361
|
+
const selected = splitMultiValue(values[field.key] ?? "");
|
|
362
|
+
const toggle = (value, checked) => {
|
|
363
|
+
const next = checked ? [...selected, value] : selected.filter((item) => item !== value);
|
|
364
|
+
onChange({ [field.key]: joinMultiValue(next) });
|
|
365
|
+
};
|
|
366
|
+
return /* @__PURE__ */ jsx(
|
|
367
|
+
Popover,
|
|
368
|
+
{
|
|
369
|
+
open,
|
|
370
|
+
onClose: () => setOpen(false),
|
|
371
|
+
trigger: /* @__PURE__ */ jsx(
|
|
372
|
+
Button,
|
|
373
|
+
{
|
|
374
|
+
type: "button",
|
|
375
|
+
variant: "secondary",
|
|
376
|
+
block: true,
|
|
377
|
+
iconEnd: "chevron-down",
|
|
378
|
+
onClick: () => setOpen((value) => !value),
|
|
379
|
+
children: multiSelectLabel(field, selected)
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
children: /* @__PURE__ */ jsx("div", { className: "wp-col", children: field.options.map((option) => /* @__PURE__ */ jsx(
|
|
383
|
+
Checkbox,
|
|
384
|
+
{
|
|
385
|
+
label: option.label,
|
|
386
|
+
checked: selected.includes(option.value),
|
|
387
|
+
onChange: (e) => toggle(option.value, e.target.checked)
|
|
388
|
+
},
|
|
389
|
+
option.value
|
|
390
|
+
)) })
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
function uniqueCascadingOptions(options) {
|
|
395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
396
|
+
return options.filter((option) => {
|
|
397
|
+
if (seen.has(option.value)) return false;
|
|
398
|
+
seen.add(option.value);
|
|
399
|
+
return true;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function cascadingLevels(options, selectedPaths) {
|
|
403
|
+
const levels = [];
|
|
404
|
+
let currentOptions = options;
|
|
405
|
+
let depth = 0;
|
|
406
|
+
while (currentOptions.length > 0) {
|
|
407
|
+
levels.push(currentOptions);
|
|
408
|
+
const selectedAtDepth = uniqueCascadingOptions(
|
|
409
|
+
selectedPaths.map((path) => path[depth]).filter(
|
|
410
|
+
(option) => Boolean(option && currentOptions.some((current) => current.value === option.value))
|
|
411
|
+
)
|
|
412
|
+
);
|
|
413
|
+
const nextOptions = uniqueCascadingOptions(selectedAtDepth.flatMap((option) => option.children ?? []));
|
|
414
|
+
if (nextOptions.length === 0) break;
|
|
415
|
+
currentOptions = nextOptions;
|
|
416
|
+
depth += 1;
|
|
417
|
+
}
|
|
418
|
+
return levels;
|
|
419
|
+
}
|
|
420
|
+
function selectedValuesForCascadingLevel(selectedPaths, depth, options) {
|
|
421
|
+
const optionValues = new Set(options.map((option) => option.value));
|
|
422
|
+
return Array.from(
|
|
423
|
+
new Set(
|
|
424
|
+
selectedPaths.map((path) => path[depth]?.value).filter((value) => Boolean(value) && optionValues.has(value))
|
|
425
|
+
)
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
function cascadingPathIncludes(pathMap, value, maybeAncestor) {
|
|
429
|
+
return pathMap.get(value)?.some((option) => option.value === maybeAncestor) ?? false;
|
|
430
|
+
}
|
|
431
|
+
function cascadingValuesOverlap(leftValue, rightValue, pathMap) {
|
|
432
|
+
return cascadingPathIncludes(pathMap, leftValue, rightValue) || cascadingPathIncludes(pathMap, rightValue, leftValue);
|
|
433
|
+
}
|
|
434
|
+
function cascadingLevelLabel(values, allLabel, pathMap, showPathLabels) {
|
|
435
|
+
if (values.length === 0) return allLabel;
|
|
436
|
+
const labels = values.map((value) => {
|
|
437
|
+
if (showPathLabels) return cascadingPathLabel(pathMap, value);
|
|
438
|
+
return pathMap.get(value)?.at(-1)?.label ?? value;
|
|
439
|
+
});
|
|
440
|
+
if (labels.length <= 2) return labels.join(", ");
|
|
441
|
+
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
442
|
+
}
|
|
443
|
+
function cascadingOptionLabel(option, pathMap, showPathLabels) {
|
|
444
|
+
return showPathLabels ? cascadingPathLabel(pathMap, option.value) : option.label;
|
|
445
|
+
}
|
|
446
|
+
function CascadingInput({
|
|
447
|
+
field,
|
|
448
|
+
values,
|
|
449
|
+
onChange
|
|
450
|
+
}) {
|
|
451
|
+
const pathMap = useMemo(() => cascadingPathMap(field.options), [field.options]);
|
|
452
|
+
const selectedValues = useMemo(
|
|
453
|
+
() => splitMultiValue(values[field.key] ?? "").filter((value) => pathMap.has(value)),
|
|
454
|
+
[field.key, pathMap, values]
|
|
455
|
+
);
|
|
456
|
+
const selectedPaths = useMemo(
|
|
457
|
+
() => selectedValues.map((value) => pathMap.get(value)).filter((path) => Boolean(path)),
|
|
458
|
+
[pathMap, selectedValues]
|
|
459
|
+
);
|
|
460
|
+
const levels = useMemo(() => cascadingLevels(field.options, selectedPaths), [field.options, selectedPaths]);
|
|
461
|
+
const setSelectedValues = (nextValues) => {
|
|
462
|
+
onChange({ [field.key]: joinMultiValue(nextValues) });
|
|
463
|
+
};
|
|
464
|
+
const toggleOption = (option, nextChecked) => {
|
|
465
|
+
const withoutOverlap = selectedValues.filter(
|
|
466
|
+
(value) => !cascadingValuesOverlap(option.value, value, pathMap)
|
|
467
|
+
);
|
|
468
|
+
setSelectedValues(nextChecked ? [...withoutOverlap, option.value] : withoutOverlap);
|
|
469
|
+
};
|
|
470
|
+
const clearLevel = (depth) => {
|
|
471
|
+
if (depth === 0) {
|
|
472
|
+
setSelectedValues([]);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const parents = selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []);
|
|
476
|
+
const rest = selectedValues.filter((value) => {
|
|
477
|
+
const parentValue = pathMap.get(value)?.[depth - 1]?.value;
|
|
478
|
+
return !parentValue || !parents.includes(parentValue);
|
|
479
|
+
});
|
|
480
|
+
setSelectedValues([...rest, ...parents]);
|
|
481
|
+
};
|
|
482
|
+
return /* @__PURE__ */ jsx("div", { className: "wp-col", children: levels.map((options, depth) => {
|
|
483
|
+
const levelValues = selectedValuesForCascadingLevel(selectedPaths, depth, options);
|
|
484
|
+
const showPathLabels = depth > 0 && selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []).length > 1;
|
|
485
|
+
return /* @__PURE__ */ jsx(
|
|
486
|
+
CascadingLevelSelect,
|
|
487
|
+
{
|
|
488
|
+
allLabel: depth === 0 ? field.allLabel ?? "\uC804\uCCB4" : "\uC804\uCCB4",
|
|
489
|
+
options,
|
|
490
|
+
pathMap,
|
|
491
|
+
selectedValues: levelValues,
|
|
492
|
+
showPathLabels,
|
|
493
|
+
onClear: () => clearLevel(depth),
|
|
494
|
+
onToggle: toggleOption
|
|
495
|
+
},
|
|
496
|
+
depth
|
|
497
|
+
);
|
|
498
|
+
}) });
|
|
499
|
+
}
|
|
500
|
+
function CascadingLevelSelect({
|
|
501
|
+
options,
|
|
502
|
+
pathMap,
|
|
503
|
+
selectedValues,
|
|
504
|
+
allLabel,
|
|
505
|
+
showPathLabels,
|
|
506
|
+
onClear,
|
|
507
|
+
onToggle
|
|
508
|
+
}) {
|
|
509
|
+
const [open, setOpen] = useState(false);
|
|
510
|
+
const selectedSet = new Set(selectedValues);
|
|
511
|
+
return /* @__PURE__ */ jsx(
|
|
512
|
+
Popover,
|
|
513
|
+
{
|
|
514
|
+
open,
|
|
515
|
+
onClose: () => setOpen(false),
|
|
516
|
+
trigger: /* @__PURE__ */ jsx(
|
|
517
|
+
Button,
|
|
518
|
+
{
|
|
519
|
+
type: "button",
|
|
520
|
+
variant: "secondary",
|
|
521
|
+
block: true,
|
|
522
|
+
iconEnd: "chevron-down",
|
|
523
|
+
onClick: () => setOpen((value) => !value),
|
|
524
|
+
children: cascadingLevelLabel(selectedValues, allLabel, pathMap, showPathLabels)
|
|
525
|
+
}
|
|
526
|
+
),
|
|
527
|
+
footer: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "xs", icon: "refresh-cw", onClick: onClear, children: allLabel }),
|
|
528
|
+
children: /* @__PURE__ */ jsx("div", { className: "wp-col", style: { minWidth: 200 }, children: options.map((option) => /* @__PURE__ */ jsx(
|
|
529
|
+
Checkbox,
|
|
530
|
+
{
|
|
531
|
+
label: cascadingOptionLabel(option, pathMap, showPathLabels),
|
|
532
|
+
checked: selectedSet.has(option.value),
|
|
533
|
+
onChange: (e) => onToggle(option, e.target.checked)
|
|
534
|
+
},
|
|
535
|
+
option.value
|
|
536
|
+
)) })
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export { Popover, SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Button } from './chunk-ZUCI34QY.js';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var PRINT_CSS = `
|
|
6
|
+
.erp-print-shell{display:flex;flex-direction:column;align-items:center;gap:var(--wp-space-4);padding:var(--wp-space-6) var(--wp-space-4)}
|
|
7
|
+
.erp-print-toolbar{display:flex;justify-content:flex-end;gap:var(--wp-space-2);width:100%;max-width:210mm}
|
|
8
|
+
.erp-print-sheet{width:100%;max-width:210mm;box-sizing:border-box;background:var(--wp-surface-card);border:var(--wp-border-w) solid var(--wp-border-subtle);border-radius:var(--wp-radius-lg);box-shadow:var(--wp-shadow-lg);padding:var(--wp-space-7)}
|
|
9
|
+
.erp-print-sheet table{border-collapse:collapse;width:100%}
|
|
10
|
+
.erp-print-sheet th,.erp-print-sheet td{border:var(--wp-border-w) solid var(--wp-border-default);padding:var(--wp-space-1) var(--wp-space-2)}
|
|
11
|
+
@media print{
|
|
12
|
+
@page{size:A4;margin:1cm}
|
|
13
|
+
.erp-print-hidden{display:none !important}
|
|
14
|
+
.erp-print-shell{display:block;padding:0}
|
|
15
|
+
.erp-print-sheet{--wp-border-default:rgba(0,0,0,.85);--wp-border-subtle:rgba(0,0,0,.6);max-width:none;border:0;border-radius:0;box-shadow:none;padding:0;font-size:12pt;color:rgba(0,0,0,.92)}
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
function PrintStyles() {
|
|
19
|
+
return /* @__PURE__ */ jsx("style", { href: "erp-print", precedence: "medium", children: PRINT_CSS });
|
|
20
|
+
}
|
|
21
|
+
function PrintSheet({ title, children, autoPrint = false }) {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!autoPrint || !window.opener) return;
|
|
24
|
+
const timer = window.setTimeout(() => window.print(), 500);
|
|
25
|
+
return () => window.clearTimeout(timer);
|
|
26
|
+
}, [autoPrint]);
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", { className: "erp-print-shell", children: [
|
|
28
|
+
/* @__PURE__ */ jsx(PrintStyles, {}),
|
|
29
|
+
/* @__PURE__ */ jsxs("div", { className: "erp-print-toolbar erp-print-hidden", children: [
|
|
30
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", onClick: () => window.print(), children: "\uC778\uC1C4\uD558\uAE30" }),
|
|
31
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: () => window.close(), children: "\uB2EB\uAE30" })
|
|
32
|
+
] }),
|
|
33
|
+
/* @__PURE__ */ jsxs("section", { className: "erp-print-sheet", children: [
|
|
34
|
+
title ? /* @__PURE__ */ jsx("h1", { className: "wp-h3", style: { margin: "0 0 var(--wp-space-5)" }, children: title }) : null,
|
|
35
|
+
children
|
|
36
|
+
] })
|
|
37
|
+
] });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { PrintSheet };
|