@wipco/sdui 0.28.0 → 0.30.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,340 @@
1
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
2
+ import { Icon, Badge, Checkbox, Select, Button } from './chunk-WIDDHR7L.js';
3
+ import { boxed } from './chunk-SAN4IX6N.js';
4
+ import { useRef, useEffect, useMemo, useState } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var OPERATIONS = [
8
+ { key: "read", label: "\uC870\uD68C" },
9
+ { key: "create", label: "\uC0DD\uC131" },
10
+ { key: "update", label: "\uC218\uC815" },
11
+ { key: "delete", label: "\uC0AD\uC81C" }
12
+ ];
13
+ var SCOPE_OPTIONS = [
14
+ { value: "team", label: "\uD300" },
15
+ { value: "headquarter", label: "\uBCF8\uBD80" },
16
+ { value: "all", label: "\uC804\uCCB4" }
17
+ ];
18
+ var CATEGORY_LABELS = {
19
+ fi: "\uC7AC\uBB34",
20
+ asset: "\uC790\uC0B0",
21
+ hr: "\uC778\uC0AC",
22
+ core: "\uACF5\uD1B5",
23
+ rbac: "\uAD8C\uD55C"
24
+ };
25
+ var CATEGORY_ORDER = ["fi", "asset", "hr", "core", "rbac"];
26
+ var entityOf = (code) => {
27
+ const i = code.lastIndexOf(".");
28
+ return i > 0 ? code.slice(0, i) : code;
29
+ };
30
+ var operationOf = (code) => {
31
+ const i = code.lastIndexOf(".");
32
+ const op = i > 0 ? code.slice(i + 1) : "";
33
+ return OPERATIONS.some((o) => o.key === op) ? op : null;
34
+ };
35
+ function groupCatalog(catalog) {
36
+ const categories = /* @__PURE__ */ new Map();
37
+ for (const entry of catalog) {
38
+ if (!entry || typeof entry.code !== "string") continue;
39
+ const category = entry.category?.trim() || "\uAE30\uD0C0";
40
+ const entity = entityOf(entry.code);
41
+ const byEntity = categories.get(category) ?? /* @__PURE__ */ new Map();
42
+ categories.set(category, byEntity);
43
+ const row = byEntity.get(entity) ?? { entity, label: "", cells: {}, extras: [] };
44
+ byEntity.set(entity, row);
45
+ const op = operationOf(entry.code);
46
+ if (op) {
47
+ row.cells[op] = entry;
48
+ if (!row.label) row.label = entry.name.replace(/\s*(조회|생성|수정|삭제)\s*$/, "") || entity;
49
+ } else {
50
+ row.extras.push(entry);
51
+ if (!row.label) row.label = entry.name;
52
+ }
53
+ }
54
+ const order = (c) => {
55
+ const i = CATEGORY_ORDER.indexOf(c);
56
+ return i < 0 ? CATEGORY_ORDER.length : i;
57
+ };
58
+ return [...categories.entries()].sort((a, b) => order(a[0]) - order(b[0]) || a[0].localeCompare(b[0])).map(([category, byEntity]) => ({
59
+ category,
60
+ label: CATEGORY_LABELS[category] ?? category,
61
+ rows: [...byEntity.values()].sort((a, b) => a.entity.localeCompare(b.entity))
62
+ }));
63
+ }
64
+ function PermissionMatrixEditorImpl({
65
+ permissions,
66
+ value,
67
+ onChange,
68
+ sourceEndpoint,
69
+ disabled = false
70
+ }) {
71
+ const adapter = useSduiAdapter();
72
+ const api = hostFetchOf(adapter) ?? defaultAdapter.fetch;
73
+ const onChangeRef = useRef(onChange);
74
+ onChangeRef.current = onChange;
75
+ const seededFor = useRef(null);
76
+ useEffect(() => {
77
+ if (!sourceEndpoint || seededFor.current === sourceEndpoint) return;
78
+ seededFor.current = sourceEndpoint;
79
+ api(sourceEndpoint, { method: "GET" }).then((response) => {
80
+ if (seededFor.current !== sourceEndpoint) return;
81
+ const body = response;
82
+ const rows = Array.isArray(body?.permissions) ? body.permissions : [];
83
+ onChangeRef.current?.(
84
+ rows.flatMap((row) => {
85
+ if (typeof row !== "object" || row === null) return [];
86
+ const { code, scope } = row;
87
+ if (typeof code !== "string" || !code) return [];
88
+ return [{ code, scope: typeof scope === "string" ? scope : "all" }];
89
+ })
90
+ );
91
+ }).catch(() => {
92
+ });
93
+ }, [sourceEndpoint]);
94
+ const grants = useMemo(() => Array.isArray(value) ? value : [], [value]);
95
+ const catalog = useMemo(
96
+ () => Array.isArray(permissions) ? permissions : [],
97
+ [permissions]
98
+ );
99
+ const groups = useMemo(() => groupCatalog(catalog), [catalog]);
100
+ const granted = useMemo(() => new Set(grants.map((g) => g.code)), [grants]);
101
+ const scopeOf = useMemo(() => {
102
+ const map = /* @__PURE__ */ new Map();
103
+ for (const g of grants) {
104
+ const e = entityOf(g.code);
105
+ if (!map.has(e) && g.scope) map.set(e, g.scope);
106
+ }
107
+ return map;
108
+ }, [grants]);
109
+ const [closed, setClosed] = useState(/* @__PURE__ */ new Set());
110
+ const toggleCategory = (c) => setClosed((prev) => {
111
+ const next = new Set(prev);
112
+ if (next.has(c)) next.delete(c);
113
+ else next.add(c);
114
+ return next;
115
+ });
116
+ const emit = (nextGranted, nextScopes) => {
117
+ onChange?.(
118
+ [...nextGranted].sort().map((code) => ({
119
+ code,
120
+ scope: nextScopes.get(entityOf(code)) ?? "all"
121
+ }))
122
+ );
123
+ };
124
+ const toggleCode = (code, on) => {
125
+ const next = new Set(granted);
126
+ if (on) next.add(code);
127
+ else next.delete(code);
128
+ emit(next, scopeOf);
129
+ };
130
+ const setEntityScope = (entity, scope) => {
131
+ const next = new Map(scopeOf);
132
+ next.set(entity, scope);
133
+ emit(granted, next);
134
+ };
135
+ const toggleRow = (row, on) => {
136
+ const next = new Set(granted);
137
+ for (const op of OPERATIONS) {
138
+ const cell = row.cells[op.key];
139
+ if (!cell) continue;
140
+ if (on) next.add(cell.code);
141
+ else next.delete(cell.code);
142
+ }
143
+ for (const extra of row.extras) {
144
+ if (on) next.add(extra.code);
145
+ else next.delete(extra.code);
146
+ }
147
+ emit(next, scopeOf);
148
+ };
149
+ if (catalog.length === 0) {
150
+ return /* @__PURE__ */ jsx(
151
+ "div",
152
+ {
153
+ className: "wp-body-sm",
154
+ style: {
155
+ border: "var(--wp-border-w) dashed var(--wp-border-default)",
156
+ borderRadius: "var(--wp-radius-lg)",
157
+ padding: "var(--wp-space-4)",
158
+ textAlign: "center",
159
+ color: "var(--wp-text-muted)"
160
+ },
161
+ children: "\uAD8C\uD55C \uCE74\uD0C8\uB85C\uADF8\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uD398\uC774\uC9C0\uC758 data \uC120\uC5B8(/permissions?limit=1000)\uC744 \uD655\uC778\uD558\uC138\uC694."
162
+ }
163
+ );
164
+ }
165
+ return /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: "var(--wp-space-2)" }, children: [
166
+ /* @__PURE__ */ jsxs("span", { className: "wp-caption", children: [
167
+ "\uAD8C\uD55C ",
168
+ catalog.length,
169
+ "\uC885 \uC911 ",
170
+ granted.size,
171
+ "\uAC1C \uBD80\uC5EC\uB428 \xB7 \uBC94\uC704\uB294 \uC5D4\uD2F0\uD2F0 \uB2E8\uC704\uB85C \uC801\uC6A9\uB429\uB2C8\uB2E4 (\uD300 < \uBCF8\uBD80 < \uC804\uCCB4)"
172
+ ] }),
173
+ /* @__PURE__ */ jsx(
174
+ "div",
175
+ {
176
+ style: {
177
+ position: "relative",
178
+ border: "var(--wp-border-w) solid var(--wp-border-default)",
179
+ borderRadius: "var(--wp-radius-lg)",
180
+ overflowY: "auto",
181
+ maxHeight: "min(46vh, 400px)",
182
+ minHeight: 160
183
+ },
184
+ children: groups.map((group) => {
185
+ const total = group.rows.reduce(
186
+ (a, r) => a + Object.keys(r.cells).length + r.extras.length,
187
+ 0
188
+ );
189
+ const on = group.rows.reduce(
190
+ (a, r) => a + OPERATIONS.filter((o) => r.cells[o.key] && granted.has(r.cells[o.key].code)).length + r.extras.filter((x) => granted.has(x.code)).length,
191
+ 0
192
+ );
193
+ const isClosed = closed.has(group.category);
194
+ return /* @__PURE__ */ jsxs("div", { children: [
195
+ /* @__PURE__ */ jsxs(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ className: "wp-row",
200
+ onClick: () => toggleCategory(group.category),
201
+ style: {
202
+ position: "sticky",
203
+ top: 0,
204
+ zIndex: 1,
205
+ width: "100%",
206
+ alignItems: "center",
207
+ padding: "var(--wp-space-2) var(--wp-space-3)",
208
+ background: "var(--wp-surface-sunken)",
209
+ border: "none",
210
+ borderTop: "var(--wp-border-w) solid var(--wp-border-default)",
211
+ cursor: "pointer",
212
+ textAlign: "left"
213
+ },
214
+ "aria-expanded": !isClosed,
215
+ children: [
216
+ /* @__PURE__ */ jsx(Icon, { name: isClosed ? "chevron-right" : "chevron-down", size: 14 }),
217
+ /* @__PURE__ */ jsx("span", { className: "wp-label", style: { flex: 1 }, children: group.label }),
218
+ /* @__PURE__ */ jsxs(Badge, { tone: on > 0 ? "brand" : "neutral", children: [
219
+ on,
220
+ "/",
221
+ total
222
+ ] })
223
+ ]
224
+ }
225
+ ),
226
+ isClosed ? null : /* @__PURE__ */ jsxs("div", { className: "wp-col", style: { gap: 0 }, children: [
227
+ /* @__PURE__ */ jsxs(
228
+ "div",
229
+ {
230
+ className: "wp-row wp-caption",
231
+ style: {
232
+ alignItems: "center",
233
+ padding: "var(--wp-space-1) var(--wp-space-3)",
234
+ borderTop: "var(--wp-border-w) solid var(--wp-divider)",
235
+ color: "var(--wp-text-muted)"
236
+ },
237
+ children: [
238
+ /* @__PURE__ */ jsx("span", { style: { flex: 1.6 }, children: "\uC5D4\uD2F0\uD2F0" }),
239
+ OPERATIONS.map((op) => /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 52, textAlign: "center" }, children: op.label }, op.key)),
240
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 96, textAlign: "center" }, children: "\uBC94\uC704" }),
241
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 60 } })
242
+ ]
243
+ }
244
+ ),
245
+ group.rows.map((row) => {
246
+ const rowCodes = [
247
+ ...OPERATIONS.flatMap(
248
+ (o) => row.cells[o.key] ? [row.cells[o.key].code] : []
249
+ ),
250
+ ...row.extras.map((x) => x.code)
251
+ ];
252
+ const allOn = rowCodes.length > 0 && rowCodes.every((c) => granted.has(c));
253
+ const anyOn = rowCodes.some((c) => granted.has(c));
254
+ return /* @__PURE__ */ jsxs(
255
+ "div",
256
+ {
257
+ className: "wp-row",
258
+ style: {
259
+ alignItems: "center",
260
+ padding: "var(--wp-space-1) var(--wp-space-3)",
261
+ borderTop: "var(--wp-border-w) solid var(--wp-divider)"
262
+ },
263
+ children: [
264
+ /* @__PURE__ */ jsxs("span", { className: "wp-body-sm", style: { flex: 1.6, minWidth: 0 }, children: [
265
+ row.label || row.entity,
266
+ /* @__PURE__ */ jsx(
267
+ "span",
268
+ {
269
+ className: "wp-caption",
270
+ style: {
271
+ marginLeft: "var(--wp-space-2)",
272
+ color: "var(--wp-text-muted)"
273
+ },
274
+ children: row.entity
275
+ }
276
+ )
277
+ ] }),
278
+ OPERATIONS.map((op) => {
279
+ const cell = row.cells[op.key];
280
+ return /* @__PURE__ */ jsx(
281
+ "span",
282
+ {
283
+ style: {
284
+ flex: "none",
285
+ width: 52,
286
+ display: "inline-flex",
287
+ justifyContent: "center"
288
+ },
289
+ children: cell ? /* @__PURE__ */ jsx(
290
+ Checkbox,
291
+ {
292
+ "aria-label": `${row.label || row.entity} ${op.label}`,
293
+ checked: granted.has(cell.code),
294
+ disabled,
295
+ onChange: (event) => toggleCode(cell.code, event.target.checked)
296
+ }
297
+ ) : /* @__PURE__ */ jsx("span", { className: "wp-caption", style: { color: "var(--wp-text-muted)" }, children: "\u2013" })
298
+ },
299
+ op.key
300
+ );
301
+ }),
302
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 96 }, children: /* @__PURE__ */ jsx(
303
+ Select,
304
+ {
305
+ "aria-label": `${row.label || row.entity} \uBC94\uC704`,
306
+ value: scopeOf.get(row.entity) ?? "all",
307
+ options: SCOPE_OPTIONS,
308
+ disabled: disabled || !anyOn,
309
+ onChange: (event) => setEntityScope(row.entity, event.target.value)
310
+ }
311
+ ) }),
312
+ /* @__PURE__ */ jsx("span", { style: { flex: "none", width: 60, textAlign: "right" }, children: /* @__PURE__ */ jsx(
313
+ Button,
314
+ {
315
+ type: "button",
316
+ size: "sm",
317
+ variant: "ghost",
318
+ disabled,
319
+ onClick: () => toggleRow(row, !allOn),
320
+ children: allOn ? "\uD574\uC81C" : "\uC804\uCCB4"
321
+ }
322
+ ) })
323
+ ]
324
+ },
325
+ row.entity
326
+ );
327
+ })
328
+ ] })
329
+ ] }, group.category);
330
+ })
331
+ }
332
+ )
333
+ ] });
334
+ }
335
+ var PermissionMatrixEditor = boxed(
336
+ PermissionMatrixEditorImpl,
337
+ "PermissionMatrixEditor"
338
+ );
339
+
340
+ export { PermissionMatrixEditor };
@@ -1,5 +1,5 @@
1
1
  import { ImageLightbox } from './chunk-XPBZMECW.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Badge, Button, IconButton, Icon } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
@@ -1,6 +1,6 @@
1
1
  import { TagMultiSelect } from './chunk-QDEZXA34.js';
2
- import { ApprovalLineEditor, stepTypeIcon, stepLabel } from './chunk-SAPJVLZM.js';
3
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { ApprovalLineEditor, stepTypeIcon, stepLabel } from './chunk-S4LOH2TU.js';
3
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
4
4
  import { Card, Icon, Input, IconButton, Field, Button, Badge } from './chunk-WIDDHR7L.js';
5
5
  import { boxed } from './chunk-SAN4IX6N.js';
6
6
  import React, { useState, useEffect } from 'react';
@@ -1,5 +1,5 @@
1
1
  import { includesLooseSearch } from './chunk-BD75U73S.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Icon, Badge, IconButton, Field, Select, Input, Tag, Spinner, Button } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import React, { useState, useId, useRef, useEffect } from 'react';
@@ -1,5 +1,5 @@
1
1
  import { useSduiDispatch, useSduiCan, SduiDialog } from './chunk-QU7YVMFA.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DVOPZFQS.js';
2
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
3
  import { Card, Input, Button, Icon, Badge, IconButton } from './chunk-WIDDHR7L.js';
4
4
  import { boxed } from './chunk-SAN4IX6N.js';
5
5
  import React, { useState, useEffect, useCallback, useMemo } from 'react';