@wipco/sdui 0.29.0 → 0.31.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.
@@ -0,0 +1,606 @@
1
+ import { loadOptionalModule } from '../chunk-RRMKCZYQ.js';
2
+ import { useSduiAdapter, useSduiSandbox, hostFetchOf, defaultAdapter, hostRawFetchOf } from '../chunk-DKPNFEVU.js';
3
+ import { Select, Input, Card, Button, Spinner, Icon, Alert, StatCard, Badge, Table, EmptyState } from '../chunk-WIDDHR7L.js';
4
+ import { boxed } from '../chunk-SAN4IX6N.js';
5
+ import { useRef, useState, useMemo, useCallback } from 'react';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ var ACCEPT = ".xlsx,.xls";
9
+ function pickCell(raw, column) {
10
+ const keys = Object.keys(raw);
11
+ const match = keys.find((key) => key.trim() === column.header) ?? keys.find((key) => key.trim() === column.key);
12
+ const value = match !== void 0 ? raw[match] : "";
13
+ return value === null || value === void 0 ? "" : String(value).trim();
14
+ }
15
+ function validateCell(column, value) {
16
+ if (value === "") return column.required ? "\uD544\uC218 \uC785\uB825\uC785\uB2C8\uB2E4" : null;
17
+ if (column.options && !column.options.includes(value)) {
18
+ return `\uD5C8\uC6A9\uAC12\uC774 \uC544\uB2D9\uB2C8\uB2E4 (\uD5C8\uC6A9: ${column.options.join(", ")})`;
19
+ }
20
+ return null;
21
+ }
22
+ function formatBytes(bytes) {
23
+ if (bytes < 1024) return `${bytes}B`;
24
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
25
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
26
+ }
27
+ function collectSummary(result, summaryLabels) {
28
+ const nested = result.summary && typeof result.summary === "object" ? result.summary : void 0;
29
+ const lookup = (key) => nested?.[key] ?? result[key];
30
+ if (summaryLabels) {
31
+ return Object.entries(summaryLabels).flatMap(([key, label]) => {
32
+ const value = lookup(key);
33
+ return typeof value === "number" && Number.isFinite(value) ? [{ key, label, value }] : [];
34
+ });
35
+ }
36
+ const source = nested ?? result;
37
+ return Object.entries(source).flatMap(
38
+ ([key, value]) => typeof value === "number" && Number.isFinite(value) ? [{ key, label: key, value }] : []
39
+ );
40
+ }
41
+ function errorMessage(error, fallback) {
42
+ return error instanceof Error && error.message ? error.message : fallback;
43
+ }
44
+ function ExcelUploadCardImpl({
45
+ mode = "server",
46
+ endpoint,
47
+ templateEndpoint,
48
+ templateColumns,
49
+ columns,
50
+ summaryLabels,
51
+ onComplete
52
+ }) {
53
+ const adapter = useSduiAdapter();
54
+ const sandbox = useSduiSandbox();
55
+ const api = hostFetchOf(adapter) ?? defaultAdapter.fetch;
56
+ const rawFetch = hostRawFetchOf(adapter) ?? defaultAdapter.rawFetch;
57
+ const inputRef = useRef(null);
58
+ const [inputKey, setInputKey] = useState(0);
59
+ const [fileName, setFileName] = useState(null);
60
+ const [notice, setNotice] = useState(null);
61
+ const [demoMode, setDemoMode] = useState(false);
62
+ const [downloading, setDownloading] = useState(false);
63
+ const [file, setFile] = useState(null);
64
+ const [uploading, setUploading] = useState(false);
65
+ const [result, setResult] = useState(null);
66
+ const [rows, setRows] = useState(null);
67
+ const [parsing, setParsing] = useState(false);
68
+ const excelRowNums = useRef([]);
69
+ const [importing, setImporting] = useState(false);
70
+ const schemaColumns = useMemo(() => columns ?? [], [columns]);
71
+ const reset = useCallback(() => {
72
+ setFile(null);
73
+ setFileName(null);
74
+ setResult(null);
75
+ setRows(null);
76
+ setNotice(null);
77
+ setInputKey((key) => key + 1);
78
+ }, []);
79
+ const hasTemplate = (templateColumns?.length ?? 0) > 0 || Boolean(templateEndpoint);
80
+ const handleTemplateDownload = useCallback(async () => {
81
+ setDownloading(true);
82
+ try {
83
+ if (templateColumns && templateColumns.length > 0) {
84
+ const XLSX = await loadOptionalModule(() => import('xlsx'), "xlsx");
85
+ const headers = templateColumns.map((column) => column.header);
86
+ const examples = templateColumns.map((column) => column.example ?? "");
87
+ const sheet = XLSX.utils.aoa_to_sheet([headers, examples]);
88
+ sheet["!cols"] = templateColumns.map((column) => ({
89
+ wch: Math.max(column.header.length * 2, (column.example ?? "").length + 2, 12)
90
+ }));
91
+ const workbook = XLSX.utils.book_new();
92
+ XLSX.utils.book_append_sheet(workbook, sheet, "\uC5C5\uB85C\uB4DC\uC591\uC2DD");
93
+ XLSX.writeFile(workbook, "\uC5D1\uC140_\uC5C5\uB85C\uB4DC_\uD15C\uD50C\uB9BF.xlsx");
94
+ } else if (templateEndpoint) {
95
+ const res = await rawFetch(templateEndpoint, { method: "GET" });
96
+ if (res.status === 503) {
97
+ setDemoMode(true);
98
+ setNotice({
99
+ tone: "warning",
100
+ text: "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \uC0C1\uD0DC\uB77C \uD15C\uD50C\uB9BF\uC744 \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB370\uBAA8 \uBAA8\uB4DC)"
101
+ });
102
+ return;
103
+ }
104
+ if (!res.ok) throw new Error(`\uD15C\uD50C\uB9BF \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${res.status})`);
105
+ const blob = await res.blob();
106
+ const url = URL.createObjectURL(blob);
107
+ const link = document.createElement("a");
108
+ link.href = url;
109
+ link.download = "\uC5D1\uC140_\uC5C5\uB85C\uB4DC_\uD15C\uD50C\uB9BF.xlsx";
110
+ document.body.appendChild(link);
111
+ link.click();
112
+ link.remove();
113
+ URL.revokeObjectURL(url);
114
+ }
115
+ } catch (error) {
116
+ setNotice({ tone: "danger", text: errorMessage(error, "\uD15C\uD50C\uB9BF \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4") });
117
+ } finally {
118
+ setDownloading(false);
119
+ }
120
+ }, [templateColumns, templateEndpoint]);
121
+ const handleServerUpload = useCallback(async () => {
122
+ if (!file || !endpoint) return;
123
+ setUploading(true);
124
+ setNotice(null);
125
+ setResult(null);
126
+ try {
127
+ const formData = new FormData();
128
+ formData.append("file", file, file.name);
129
+ if (sandbox) {
130
+ setNotice({ tone: "warning", text: "\uBBF8\uB9AC\uBCF4\uAE30\uC5D0\uC11C\uB294 \uC5C5\uB85C\uB4DC\uAC00 \uB098\uAC00\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
131
+ return;
132
+ }
133
+ const res = await rawFetch(endpoint, { method: "POST", body: formData });
134
+ if (res.status === 503) {
135
+ setDemoMode(true);
136
+ return;
137
+ }
138
+ const json = await res.json().catch(() => null);
139
+ if (!res.ok) {
140
+ if (json) setResult(json);
141
+ setNotice({
142
+ tone: "danger",
143
+ text: typeof json?.message === "string" && json.message || typeof json?.error === "string" && json.error || `\uC5C5\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (${res.status})`
144
+ });
145
+ return;
146
+ }
147
+ setResult(json ?? {});
148
+ setNotice({ tone: "success", text: "\uC5C5\uB85C\uB4DC\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4" });
149
+ setFile(null);
150
+ setFileName(null);
151
+ setInputKey((key) => key + 1);
152
+ onComplete?.(json ?? {});
153
+ } catch (error) {
154
+ setNotice({ tone: "danger", text: errorMessage(error, "\uC5C5\uB85C\uB4DC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4") });
155
+ } finally {
156
+ setUploading(false);
157
+ }
158
+ }, [endpoint, file, onComplete]);
159
+ const validateRow = useCallback(
160
+ (row) => {
161
+ const errors = {};
162
+ for (const column of schemaColumns) {
163
+ const message = validateCell(column, row.cells[column.key] ?? "");
164
+ if (message) errors[column.key] = message;
165
+ }
166
+ return { ...row, errors };
167
+ },
168
+ [schemaColumns]
169
+ );
170
+ const parsePreviewFile = useCallback(
171
+ async (selected) => {
172
+ setParsing(true);
173
+ setNotice(null);
174
+ setRows(null);
175
+ try {
176
+ const buffer = await selected.arrayBuffer();
177
+ const XLSX = await loadOptionalModule(() => import('xlsx'), "xlsx");
178
+ const workbook = XLSX.read(buffer, { type: "array" });
179
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
180
+ if (!sheet) throw new Error("\uC5D1\uC140 \uD30C\uC77C\uC5D0\uC11C \uC2DC\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
181
+ const raw = XLSX.utils.sheet_to_json(sheet, {
182
+ defval: "",
183
+ raw: false
184
+ });
185
+ if (raw.length === 0) {
186
+ setNotice({ tone: "warning", text: "\uB370\uC774\uD130 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uD15C\uD50C\uB9BF \uC591\uC2DD\uC744 \uD655\uC778\uD558\uC138\uC694" });
187
+ return;
188
+ }
189
+ excelRowNums.current = raw.map((rawRow, index) => {
190
+ const rowNum = rawRow.__rowNum__;
191
+ return typeof rowNum === "number" ? rowNum + 1 : index + 2;
192
+ });
193
+ setRows(
194
+ raw.map((rawRow, index) => {
195
+ const cells = {};
196
+ for (const column of schemaColumns) cells[column.key] = pickCell(rawRow, column);
197
+ return validateRow({ id: index, cells });
198
+ })
199
+ );
200
+ setFileName(selected.name);
201
+ } catch (error) {
202
+ setNotice({ tone: "danger", text: errorMessage(error, "\uC5D1\uC140 \uD30C\uC77C\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4") });
203
+ } finally {
204
+ setParsing(false);
205
+ }
206
+ },
207
+ [schemaColumns, validateRow]
208
+ );
209
+ const commitCell = useCallback(
210
+ (rowId, key, value) => {
211
+ setRows(
212
+ (current) => current ? current.map(
213
+ (row) => row.id === rowId ? validateRow({ id: row.id, cells: { ...row.cells, [key]: value.trim() } }) : row
214
+ ) : current
215
+ );
216
+ },
217
+ [validateRow]
218
+ );
219
+ const validRows = useMemo(
220
+ () => (rows ?? []).filter((row) => Object.keys(row.errors).length === 0),
221
+ [rows]
222
+ );
223
+ const invalidCount = (rows?.length ?? 0) - validRows.length;
224
+ const handleImport = useCallback(async () => {
225
+ if (validRows.length === 0) return;
226
+ const payload = validRows.map((row) => ({ ...row.cells }));
227
+ setImporting(true);
228
+ setNotice(null);
229
+ try {
230
+ if (endpoint) {
231
+ if (sandbox) {
232
+ setNotice({ tone: "warning", text: "\uBBF8\uB9AC\uBCF4\uAE30\uC5D0\uC11C\uB294 \uB4F1\uB85D\uC774 \uB098\uAC00\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4" });
233
+ return;
234
+ }
235
+ const response = await api(endpoint, { method: "POST", body: { rows: payload } });
236
+ setNotice({ tone: "success", text: `${payload.length}\uAC74\uC744 \uAC00\uC838\uC654\uC2B5\uB2C8\uB2E4` });
237
+ onComplete?.(response ?? payload);
238
+ } else {
239
+ setNotice({ tone: "success", text: `${payload.length}\uAC74\uC744 \uAC00\uC838\uC654\uC2B5\uB2C8\uB2E4` });
240
+ onComplete?.(payload);
241
+ }
242
+ setRows(null);
243
+ setFileName(null);
244
+ setInputKey((key) => key + 1);
245
+ } catch (error) {
246
+ if (error?.status === 503) {
247
+ setDemoMode(true);
248
+ setNotice({
249
+ tone: "warning",
250
+ text: `\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \u2014 \uAC80\uC99D\uB41C ${payload.length}\uAC74\uC744 \uB85C\uCEEC \uACB0\uACFC\uB85C \uC804\uB2EC\uD588\uC2B5\uB2C8\uB2E4 (\uB370\uBAA8 \uBAA8\uB4DC)`
251
+ });
252
+ onComplete?.(payload);
253
+ setRows(null);
254
+ setFileName(null);
255
+ setInputKey((key) => key + 1);
256
+ } else {
257
+ setNotice({ tone: "danger", text: errorMessage(error, "\uAC00\uC838\uC624\uAE30\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4") });
258
+ }
259
+ } finally {
260
+ setImporting(false);
261
+ }
262
+ }, [endpoint, onComplete, validRows]);
263
+ const previewTableColumns = useMemo(() => {
264
+ const base = [
265
+ {
266
+ key: "_row",
267
+ header: "#",
268
+ width: "48px",
269
+ align: "right",
270
+ mono: true,
271
+ // 엑셀 행 번호(헤더 1행 + 1-기반)
272
+ render: (row) => excelRowNums.current[row.id] ?? row.id + 2
273
+ },
274
+ {
275
+ key: "_status",
276
+ header: "\uC0C1\uD0DC",
277
+ width: "64px",
278
+ render: (row) => Object.keys(row.errors).length === 0 ? /* @__PURE__ */ jsx(Badge, { tone: "success", children: "\uC815\uC0C1" }) : /* @__PURE__ */ jsx(Badge, { tone: "danger", children: "\uC624\uB958" })
279
+ }
280
+ ];
281
+ const dataColumns = schemaColumns.map((column) => ({
282
+ key: column.key,
283
+ header: column.required ? `${column.header} *` : column.header,
284
+ render: (row) => {
285
+ const value = row.cells[column.key] ?? "";
286
+ const error = row.errors[column.key];
287
+ if (!error) {
288
+ return value !== "" ? value : /* @__PURE__ */ jsx("span", { style: { color: "var(--wp-text-muted)" }, children: "\u2014" });
289
+ }
290
+ return /* @__PURE__ */ jsxs(
291
+ "div",
292
+ {
293
+ className: "wp-col",
294
+ style: {
295
+ gap: "var(--wp-space-half)",
296
+ padding: "var(--wp-space-1)",
297
+ background: "var(--wp-status-danger-bg)",
298
+ borderRadius: "var(--wp-radius-sm)",
299
+ minWidth: 140
300
+ },
301
+ children: [
302
+ column.options ? /* @__PURE__ */ jsx(
303
+ Select,
304
+ {
305
+ size: "sm",
306
+ invalid: true,
307
+ value: "",
308
+ placeholder: value !== "" ? `${value} (\uD5C8\uC6A9\uAC12 \uC544\uB2D8)` : "\uAC12 \uC120\uD0DD",
309
+ "aria-label": `${column.header} \uC218\uC815`,
310
+ options: column.options.map((option) => ({ value: option, label: option })),
311
+ onChange: (event) => {
312
+ if (event.target.value) commitCell(row.id, column.key, event.target.value);
313
+ }
314
+ }
315
+ ) : /* @__PURE__ */ jsx(
316
+ Input,
317
+ {
318
+ size: "sm",
319
+ invalid: true,
320
+ defaultValue: value,
321
+ "aria-label": `${column.header} \uC218\uC815`,
322
+ placeholder: column.header,
323
+ onBlur: (event) => commitCell(row.id, column.key, event.target.value),
324
+ onKeyDown: (event) => {
325
+ if (event.key === "Enter") {
326
+ commitCell(row.id, column.key, event.currentTarget.value);
327
+ }
328
+ }
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx("span", { className: "wp-caption", style: { color: "var(--wp-text-danger)" }, children: error })
332
+ ]
333
+ }
334
+ );
335
+ }
336
+ }));
337
+ return [...base, ...dataColumns];
338
+ }, [commitCell, schemaColumns]);
339
+ const handleFileChange = (event) => {
340
+ const selected = event.target.files?.[0];
341
+ if (!selected) return;
342
+ if (mode === "preview") {
343
+ void parsePreviewFile(selected);
344
+ } else {
345
+ setFile(selected);
346
+ setFileName(selected.name);
347
+ setResult(null);
348
+ setNotice(null);
349
+ }
350
+ };
351
+ const previewSchemaMissing = mode === "preview" && schemaColumns.length === 0;
352
+ const pickerDisabled = uploading || parsing || importing || previewSchemaMissing;
353
+ const pickerReason = previewSchemaMissing ? "columns \uC2A4\uD0A4\uB9C8\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 \uBBF8\uB9AC\uBCF4\uAE30\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" : void 0;
354
+ const uploadDisabledReason = !endpoint ? "\uC5C5\uB85C\uB4DC endpoint\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4" : demoMode ? "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 \uC0C1\uD0DC\uC785\uB2C8\uB2E4 (\uB370\uBAA8 \uBAA8\uB4DC)" : !file ? "\uC5D1\uC140 \uD30C\uC77C\uC744 \uBA3C\uC800 \uC120\uD0DD\uD558\uC138\uC694" : void 0;
355
+ const importDisabledReason = validRows.length === 0 ? "\uAC00\uC838\uC62C \uC218 \uC788\uB294 \uC815\uC0C1 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4" : void 0;
356
+ const summaryEntries = result ? collectSummary(result, summaryLabels) : [];
357
+ const rowErrors = Array.isArray(result?.errors) ? result.errors : [];
358
+ const hasReset = fileName !== null || file !== null || result !== null || rows !== null || notice !== null;
359
+ return /* @__PURE__ */ jsx(
360
+ Card,
361
+ {
362
+ title: "\uC5D1\uC140 \uC77C\uAD04 \uC5C5\uB85C\uB4DC",
363
+ subtitle: mode === "preview" ? "\uC5D1\uC140 \uD30C\uC77C\uC744 \uBD88\uB7EC\uC640 \uAC80\uC99D\uD55C \uB4A4 \uC815\uC0C1 \uD589\uB9CC \uAC00\uC838\uC635\uB2C8\uB2E4" : "\uC5D1\uC140 \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uBA74 \uC11C\uBC84\uC5D0\uC11C \uC77C\uAD04 \uCC98\uB9AC\uD569\uB2C8\uB2E4",
364
+ actions: hasTemplate ? /* @__PURE__ */ jsx(
365
+ Button,
366
+ {
367
+ variant: "secondary",
368
+ size: "sm",
369
+ icon: "download",
370
+ loading: downloading,
371
+ onClick: () => void handleTemplateDownload(),
372
+ children: "\uD15C\uD50C\uB9BF \uB2E4\uC6B4\uB85C\uB4DC"
373
+ }
374
+ ) : void 0,
375
+ children: /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-3)" }, children: [
376
+ /* @__PURE__ */ jsx(
377
+ "input",
378
+ {
379
+ ref: inputRef,
380
+ type: "file",
381
+ accept: ACCEPT,
382
+ hidden: true,
383
+ onChange: handleFileChange
384
+ },
385
+ inputKey
386
+ ),
387
+ /* @__PURE__ */ jsxs(
388
+ "div",
389
+ {
390
+ style: {
391
+ display: "flex",
392
+ alignItems: "center",
393
+ flexWrap: "wrap",
394
+ gap: "var(--wp-space-2)"
395
+ },
396
+ children: [
397
+ /* @__PURE__ */ jsx(
398
+ Button,
399
+ {
400
+ variant: "secondary",
401
+ icon: "upload",
402
+ disabled: pickerDisabled,
403
+ title: pickerReason,
404
+ onClick: () => inputRef.current?.click(),
405
+ children: "\uD30C\uC77C \uC120\uD0DD"
406
+ }
407
+ ),
408
+ parsing ? /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "\uC5D1\uC140 \uD30C\uC2F1 \uC911\u2026" }) : fileName ? /* @__PURE__ */ jsxs(
409
+ "span",
410
+ {
411
+ className: "wp-body-sm",
412
+ style: {
413
+ display: "inline-flex",
414
+ alignItems: "center",
415
+ gap: "var(--wp-space-1)",
416
+ color: "var(--wp-text-default)"
417
+ },
418
+ children: [
419
+ /* @__PURE__ */ jsx(Icon, { name: "file-text", size: 16, style: { color: "var(--wp-text-muted)" } }),
420
+ fileName,
421
+ file ? /* @__PURE__ */ jsxs("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: [
422
+ "(",
423
+ formatBytes(file.size),
424
+ ")"
425
+ ] }) : null
426
+ ]
427
+ }
428
+ ) : /* @__PURE__ */ jsx("span", { className: "wp-caption", children: ".xlsx / .xls \uD30C\uC77C\uC744 \uC120\uD0DD\uD558\uC138\uC694" }),
429
+ /* @__PURE__ */ jsxs("div", { style: { marginLeft: "auto", display: "flex", gap: "var(--wp-space-2)" }, children: [
430
+ /* @__PURE__ */ jsx(
431
+ Button,
432
+ {
433
+ variant: "ghost",
434
+ icon: "rotate-ccw",
435
+ disabled: !hasReset || uploading || importing || parsing,
436
+ title: !hasReset ? "\uCD08\uAE30\uD654\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4" : void 0,
437
+ onClick: reset,
438
+ children: "\uCD08\uAE30\uD654"
439
+ }
440
+ ),
441
+ mode === "server" ? /* @__PURE__ */ jsx(
442
+ Button,
443
+ {
444
+ variant: "primary",
445
+ icon: "cloud-upload",
446
+ loading: uploading,
447
+ disabled: uploadDisabledReason !== void 0,
448
+ title: uploadDisabledReason,
449
+ onClick: () => void handleServerUpload(),
450
+ children: "\uC5C5\uB85C\uB4DC"
451
+ }
452
+ ) : rows !== null ? /* @__PURE__ */ jsxs(
453
+ Button,
454
+ {
455
+ variant: "primary",
456
+ icon: "check",
457
+ loading: importing,
458
+ disabled: importDisabledReason !== void 0,
459
+ title: importDisabledReason,
460
+ onClick: () => void handleImport(),
461
+ children: [
462
+ validRows.length,
463
+ "\uAC74 \uAC00\uC838\uC624\uAE30",
464
+ invalidCount > 0 ? ` (\uC624\uB958 ${invalidCount}\uAC74 \uC81C\uC678)` : ""
465
+ ]
466
+ }
467
+ ) : null
468
+ ] })
469
+ ]
470
+ }
471
+ ),
472
+ demoMode ? /* @__PURE__ */ jsx(Alert, { tone: "warning", title: "\uBC31\uC5D4\uB4DC \uBBF8\uC5F0\uB3D9 (\uB370\uBAA8 \uBAA8\uB4DC)", children: "\uC11C\uBC84 \uCC98\uB9AC\uAC00 \uBD88\uAC00\uB2A5\uD55C \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uC5F0\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694." }) : null,
473
+ notice ? /* @__PURE__ */ jsx(Alert, { tone: notice.tone, children: notice.text }) : null,
474
+ mode === "server" && summaryEntries.length > 0 ? /* @__PURE__ */ jsx(
475
+ "div",
476
+ {
477
+ style: {
478
+ display: "grid",
479
+ gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))",
480
+ gap: "var(--wp-space-2)"
481
+ },
482
+ children: summaryEntries.map((entry) => /* @__PURE__ */ jsx(
483
+ StatCard,
484
+ {
485
+ size: "sm",
486
+ label: entry.label,
487
+ value: entry.value.toLocaleString("ko-KR")
488
+ },
489
+ entry.key
490
+ ))
491
+ }
492
+ ) : null,
493
+ mode === "server" && rowErrors.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-1)" }, children: [
494
+ /* @__PURE__ */ jsxs(
495
+ "span",
496
+ {
497
+ className: "wp-body-sm",
498
+ style: { fontWeight: "var(--wp-weight-semibold)", color: "var(--wp-text-strong)" },
499
+ children: [
500
+ "\uC624\uB958 \uBAA9\uB85D (",
501
+ rowErrors.length,
502
+ "\uAC74)"
503
+ ]
504
+ }
505
+ ),
506
+ /* @__PURE__ */ jsx(
507
+ "div",
508
+ {
509
+ style: {
510
+ maxHeight: 240,
511
+ overflowY: "auto",
512
+ border: "var(--wp-border-w) solid var(--wp-border-subtle)",
513
+ borderRadius: "var(--wp-radius-md)",
514
+ padding: "var(--wp-space-2)"
515
+ },
516
+ children: rowErrors.map((rowError, index) => /* @__PURE__ */ jsxs(
517
+ "div",
518
+ {
519
+ className: "wp-body-sm",
520
+ style: {
521
+ display: "flex",
522
+ gap: "var(--wp-space-2)",
523
+ padding: "var(--wp-space-1) 0"
524
+ },
525
+ children: [
526
+ /* @__PURE__ */ jsxs(
527
+ "span",
528
+ {
529
+ style: {
530
+ color: "var(--wp-text-danger)",
531
+ fontWeight: "var(--wp-weight-medium)",
532
+ flexShrink: 0
533
+ },
534
+ children: [
535
+ rowError.row_number ?? rowError.rowNumber,
536
+ "\uD589"
537
+ ]
538
+ }
539
+ ),
540
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--wp-text-default)" }, children: rowError.message })
541
+ ]
542
+ },
543
+ `${rowError.row_number ?? rowError.rowNumber}-${index}`
544
+ ))
545
+ }
546
+ )
547
+ ] }) : null,
548
+ mode === "preview" && rows !== null ? /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-2)" }, children: [
549
+ /* @__PURE__ */ jsxs(
550
+ "div",
551
+ {
552
+ style: {
553
+ display: "flex",
554
+ alignItems: "center",
555
+ gap: "var(--wp-space-2)",
556
+ flexWrap: "wrap"
557
+ },
558
+ children: [
559
+ /* @__PURE__ */ jsxs(Badge, { tone: "neutral", children: [
560
+ "\uCD1D ",
561
+ rows.length,
562
+ "\uAC74"
563
+ ] }),
564
+ /* @__PURE__ */ jsxs(Badge, { tone: "success", dot: true, children: [
565
+ "\uC815\uC0C1 ",
566
+ validRows.length,
567
+ "\uAC74"
568
+ ] }),
569
+ invalidCount > 0 ? /* @__PURE__ */ jsxs(Badge, { tone: "danger", dot: true, children: [
570
+ "\uC624\uB958 ",
571
+ invalidCount,
572
+ "\uAC74"
573
+ ] }) : null,
574
+ invalidCount > 0 ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: "\uC624\uB958 \uC140\uC744 \uD45C\uC5D0\uC11C \uBC14\uB85C \uC218\uC815\uD558\uBA74 \uC7AC\uAC80\uC99D\uB429\uB2C8\uB2E4" }) : null
575
+ ]
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsx(
579
+ "div",
580
+ {
581
+ style: {
582
+ maxHeight: 360,
583
+ overflow: "auto",
584
+ border: "var(--wp-border-w) solid var(--wp-border-subtle)",
585
+ borderRadius: "var(--wp-radius-md)"
586
+ },
587
+ children: /* @__PURE__ */ jsx(
588
+ Table,
589
+ {
590
+ columns: previewTableColumns,
591
+ rows,
592
+ dense: true,
593
+ sticky: true,
594
+ empty: /* @__PURE__ */ jsx(EmptyState, { icon: "table", title: "\uD45C\uC2DC\uD560 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4", tight: true, children: "\uC5D1\uC140 \uD30C\uC77C\uC5D0 \uB370\uC774\uD130 \uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
595
+ }
596
+ )
597
+ }
598
+ )
599
+ ] }) : null
600
+ ] })
601
+ }
602
+ );
603
+ }
604
+ var ExcelUploadCard = boxed(ExcelUploadCardImpl, "ExcelUploadCard");
605
+
606
+ export { ExcelUploadCard };
package/dist/rich.js CHANGED
@@ -1,9 +1,9 @@
1
- import { useOptionalModule, DependencyPlaceholder } from './chunk-WXOU7QTN.js';
2
- export { TimePicker } from './chunk-WXOU7QTN.js';
1
+ export { TimePicker } from './chunk-SNRKPEWT.js';
2
+ import { useOptionalModule, DependencyPlaceholder } from './chunk-RRMKCZYQ.js';
3
3
  import { HoistedStyle } from './chunk-EFMKWPZ3.js';
4
4
  export { DatePicker, DateRangePicker } from './chunk-EFMKWPZ3.js';
5
5
  import './chunk-XQPTSEJC.js';
6
- import { useSduiAssetResolver } from './chunk-DVOPZFQS.js';
6
+ import { useSduiAssetResolver } from './chunk-DKPNFEVU.js';
7
7
  import { Spinner, EmptyState } from './chunk-WIDDHR7L.js';
8
8
  import './chunk-SAN4IX6N.js';
9
9
  import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
@@ -1,7 +1,7 @@
1
- export { ApprovalLineEditor, stepLabel, stepTypeIcon } from '../chunk-SAPJVLZM.js';
1
+ export { ApprovalLineEditor, stepLabel, stepTypeIcon } from '../chunk-S4LOH2TU.js';
2
2
  import '../chunk-BD75U73S.js';
3
3
  import '../chunk-EFMKWPZ3.js';
4
4
  import '../chunk-XQPTSEJC.js';
5
- import '../chunk-DVOPZFQS.js';
5
+ import '../chunk-DKPNFEVU.js';
6
6
  import '../chunk-WIDDHR7L.js';
7
7
  import '../chunk-SAN4IX6N.js';
@@ -1,9 +1,9 @@
1
- export { ApprovalOverridesEditor } from '../chunk-ZDRERW4G.js';
1
+ export { ApprovalOverridesEditor } from '../chunk-OR27SRKL.js';
2
2
  import '../chunk-QDEZXA34.js';
3
- import '../chunk-SAPJVLZM.js';
3
+ import '../chunk-S4LOH2TU.js';
4
4
  import '../chunk-BD75U73S.js';
5
5
  import '../chunk-EFMKWPZ3.js';
6
6
  import '../chunk-XQPTSEJC.js';
7
- import '../chunk-DVOPZFQS.js';
7
+ import '../chunk-DKPNFEVU.js';
8
8
  import '../chunk-WIDDHR7L.js';
9
9
  import '../chunk-SAN4IX6N.js';
@@ -1,7 +1,7 @@
1
- export { AutocompleteField, endpointSearchFn, toAutocompleteOptions } from '../chunk-6S5CFNFM.js';
1
+ export { AutocompleteField, endpointSearchFn, toAutocompleteOptions } from '../chunk-CVCP52OD.js';
2
2
  import '../chunk-BD75U73S.js';
3
3
  import '../chunk-EFMKWPZ3.js';
4
4
  import '../chunk-XQPTSEJC.js';
5
- import '../chunk-DVOPZFQS.js';
5
+ import '../chunk-DKPNFEVU.js';
6
6
  import '../chunk-WIDDHR7L.js';
7
7
  import '../chunk-SAN4IX6N.js';