@wipco/sdui 0.50.6 → 1.0.1

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.
Files changed (111) hide show
  1. package/dist/{DatePicker-BK_jsV7i.d.ts → DatePicker-C_TT_SOw.d.ts} +25 -0
  2. package/dist/{chunk-OEWTVLPZ.js → chunk-2HLXCKK7.js} +2 -2
  3. package/dist/{chunk-PWVHAUJC.js → chunk-2YP5N4VV.js} +6 -4
  4. package/dist/{chunk-QTHYLNJW.js → chunk-37SFFW37.js} +2 -13
  5. package/dist/{chunk-YQOMSWW5.js → chunk-5WW4MBRO.js} +1 -1
  6. package/dist/{chunk-LIOS3PF2.js → chunk-5XPZ3RSA.js} +3 -4
  7. package/dist/{chunk-ZD3VNVRF.js → chunk-7BGDSSAN.js} +5 -13
  8. package/dist/{chunk-Y2LIEWQX.js → chunk-7FZTUI6U.js} +6 -5
  9. package/dist/{chunk-HJ5VSXR4.js → chunk-7P7N43JV.js} +2 -2
  10. package/dist/{chunk-55VB6SDU.js → chunk-A5XQOEVH.js} +23 -20
  11. package/dist/{chunk-77LKJCMM.js → chunk-A6LAIGUS.js} +20 -108
  12. package/dist/{chunk-YXS5FXVS.js → chunk-AC5TI3BE.js} +7 -16
  13. package/dist/{chunk-AIOJYVJ2.js → chunk-AXGF4M57.js} +1 -1
  14. package/dist/{chunk-5MPK3NZK.js → chunk-B6WMC2AN.js} +3 -3
  15. package/dist/{chunk-C6ZV4VBN.js → chunk-C37U3GCC.js} +4 -4
  16. package/dist/{chunk-265YPW4W.js → chunk-DMLDKW7K.js} +1 -1
  17. package/dist/{chunk-FZ72IDVF.js → chunk-E6ISNQK3.js} +2 -2
  18. package/dist/{chunk-STX2AX6Y.js → chunk-FLAN7CV3.js} +7 -39
  19. package/dist/{chunk-IV5YTVHR.js → chunk-GE6BYNQJ.js} +18 -33
  20. package/dist/{chunk-ZX36LKJ7.js → chunk-GTGS2QRL.js} +7 -6
  21. package/dist/{chunk-NL3I6F2I.js → chunk-HBBTENSV.js} +3 -3
  22. package/dist/{chunk-RUU7WBGR.js → chunk-I45EUZC6.js} +19 -9
  23. package/dist/{chunk-JBKNIALK.js → chunk-I5SD7XRA.js} +9 -6
  24. package/dist/{chunk-X7GHY2RI.js → chunk-IFKYGJXP.js} +14 -42
  25. package/dist/{chunk-J3YMUR6R.js → chunk-IZ3JA2BR.js} +3 -3
  26. package/dist/{chunk-AUW5RP24.js → chunk-JIV4YHCZ.js} +7 -6
  27. package/dist/{chunk-M6KM3FJS.js → chunk-JYAR7D5U.js} +2 -2
  28. package/dist/{chunk-DKPNFEVU.js → chunk-KO6OEY3G.js} +19 -6
  29. package/dist/{chunk-JHHMFL5D.js → chunk-LFEJLOFN.js} +28 -16
  30. package/dist/chunk-MMIGOPO5.js +111 -0
  31. package/dist/{chunk-YSSNP6JM.js → chunk-N25YP7ID.js} +6 -21
  32. package/dist/{chunk-F2W2FWOT.js → chunk-N5RKM6IC.js} +7 -7
  33. package/dist/{chunk-C4HYWZML.js → chunk-O4IISNE7.js} +1 -1
  34. package/dist/chunk-Q4SWUINA.js +51 -0
  35. package/dist/{chunk-TUOG7EEI.js → chunk-RNOUXUBO.js} +418 -22
  36. package/dist/{chunk-4VZI3PLI.js → chunk-RYHEV5E3.js} +15 -39
  37. package/dist/{chunk-AVRNQOB5.js → chunk-SAPBDJJQ.js} +14 -13
  38. package/dist/{chunk-OPJ4DYNI.js → chunk-SJ3737RA.js} +156 -158
  39. package/dist/{chunk-UMANGYRD.js → chunk-TFGNKFL2.js} +1 -1
  40. package/dist/{chunk-KYGSMUUU.js → chunk-VY7MVBSE.js} +2 -2
  41. package/dist/{chunk-VDBUXYEB.js → chunk-WOYMEEPH.js} +8 -8
  42. package/dist/{chunk-I5Z2RVDF.js → chunk-WSLT32Q4.js} +278 -30
  43. package/dist/{chunk-NYUHSMUU.js → chunk-XGFDY43I.js} +2 -2
  44. package/dist/{chunk-ODKZF4IT.js → chunk-YOZN35HD.js} +17 -9
  45. package/dist/{chunk-EKP6Z3G6.js → chunk-YVTV4ZV6.js} +1 -1
  46. package/dist/{chunk-6BCWFPNJ.js → chunk-YWUDBLO6.js} +2 -2
  47. package/dist/chunk-Z4PYMTTS.js +41 -0
  48. package/dist/{chunk-WXIQLWIX.js → chunk-ZFNFPXPR.js} +2 -2
  49. package/dist/{chunk-7GOWOSX6.js → chunk-ZORQPB6B.js} +11 -5
  50. package/dist/index.d.ts +1096 -941
  51. package/dist/index.js +369 -610
  52. package/dist/meta.d.ts +13 -12
  53. package/dist/meta.js +84 -16
  54. package/dist/rich/ExcelUploadCard.d.ts +7 -0
  55. package/dist/rich/ExcelUploadCard.js +17 -13
  56. package/dist/rich.d.ts +1 -1
  57. package/dist/rich.js +25 -12
  58. package/dist/styles.css +10 -2
  59. package/dist/{types-D14eqLpf.d.ts → types-C7y9CpgF.d.ts} +7 -5
  60. package/dist/widgets/ApprovalLineEditor.js +7 -6
  61. package/dist/widgets/ApprovalOverridesEditor.d.ts +11 -1
  62. package/dist/widgets/ApprovalOverridesEditor.js +9 -8
  63. package/dist/widgets/ApprovalStepper.js +3 -3
  64. package/dist/widgets/AutocompleteField.d.ts +1 -0
  65. package/dist/widgets/AutocompleteField.js +7 -6
  66. package/dist/widgets/ColumnBoard.js +3 -3
  67. package/dist/widgets/DiffView.js +3 -3
  68. package/dist/widgets/Disclosure.js +3 -3
  69. package/dist/widgets/EventCalendar.d.ts +16 -1
  70. package/dist/widgets/EventCalendar.js +3 -3
  71. package/dist/widgets/FileDropzone.js +7 -6
  72. package/dist/widgets/ImageLightbox.js +5 -4
  73. package/dist/widgets/InlineEdit.js +3 -3
  74. package/dist/widgets/JsonField.js +3 -3
  75. package/dist/widgets/LiveClock.js +2 -2
  76. package/dist/widgets/OptionSchemaEditor.js +4 -4
  77. package/dist/widgets/OptionValueFields.d.ts +5 -0
  78. package/dist/widgets/OptionValueFields.js +4 -4
  79. package/dist/widgets/OrgChart.js +4 -3
  80. package/dist/widgets/OrgChartBoard.d.ts +93 -4
  81. package/dist/widgets/OrgChartBoard.js +9 -6
  82. package/dist/widgets/PeriodFilter.js +4 -4
  83. package/dist/widgets/PermissionMatrixEditor.d.ts +16 -0
  84. package/dist/widgets/PermissionMatrixEditor.js +4 -4
  85. package/dist/widgets/PositionSelect.d.ts +6 -1
  86. package/dist/widgets/PositionSelect.js +5 -4
  87. package/dist/widgets/PrintSheet.js +3 -3
  88. package/dist/widgets/ProfileEditDiffCard.d.ts +6 -0
  89. package/dist/widgets/ProfileEditDiffCard.js +4 -4
  90. package/dist/widgets/QRDisplay.js +4 -4
  91. package/dist/widgets/RankBoard.d.ts +6 -0
  92. package/dist/widgets/RankBoard.js +6 -5
  93. package/dist/widgets/RecordSheet.js +2 -2
  94. package/dist/widgets/RoleMemberPicker.d.ts +11 -5
  95. package/dist/widgets/RoleMemberPicker.js +5 -4
  96. package/dist/widgets/SearchableMultiPicker.js +5 -5
  97. package/dist/widgets/SecureScreen.d.ts +12 -0
  98. package/dist/widgets/SecureScreen.js +3 -3
  99. package/dist/widgets/StackedMeter.js +2 -2
  100. package/dist/widgets/StagePeriodEditor.js +4 -5
  101. package/dist/widgets/Strip.js +2 -2
  102. package/dist/widgets/SummaryTable.js +2 -2
  103. package/dist/widgets/TagMultiSelect.d.ts +0 -1
  104. package/dist/widgets/TagMultiSelect.js +4 -4
  105. package/dist/widgets/Timeline.js +4 -4
  106. package/dist/widgets/TreePicker.js +6 -6
  107. package/dist/widgets/TreeView.js +5 -5
  108. package/dist/widgets/index.d.ts +3 -3
  109. package/dist/widgets/index.js +81 -79
  110. package/package.json +1 -1
  111. package/dist/chunk-EKVA7T7P.js +0 -198
@@ -1,13 +1,16 @@
1
- import { ImageLightbox } from './chunk-KYGSMUUU.js';
2
- import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-DKPNFEVU.js';
3
- import { Badge, Button, IconButton, Icon } from './chunk-TUOG7EEI.js';
4
- import { boxed } from './chunk-YQOMSWW5.js';
5
- import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
6
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ import { parseJobPositionRow } from './chunk-Z4PYMTTS.js';
2
+ import { usePanZoomCanvas } from './chunk-MMIGOPO5.js';
3
+ import { ImageLightbox } from './chunk-VY7MVBSE.js';
4
+ import { useSduiAdapter, hostFetchOf, defaultAdapter } from './chunk-KO6OEY3G.js';
5
+ import { warnOnce, Badge, Button, IconButton, Icon } from './chunk-RNOUXUBO.js';
6
+ import { boxed } from './chunk-5WW4MBRO.js';
7
+ import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
9
 
8
10
  var PAGE_SIZE = 500;
9
11
  var MAX_PAGES = 20;
10
12
  var UNASSIGNED_GROUP_ID = "kc_unassigned_department";
13
+ var UNASSIGNED_GROUP_NAMES = ["\uBBF8\uC9C0\uC815"];
11
14
  var BOARD_COLORS = [
12
15
  "var(--wp-series-1)",
13
16
  "var(--wp-series-2)",
@@ -38,8 +41,6 @@ var LEADER_WIDTH = 230;
38
41
  var LEADER_HEIGHT = 62;
39
42
  var TOP_GAP = 58;
40
43
  var ROOT_ROW_GAP = 70;
41
- var MIN_ZOOM = 0.35;
42
- var MAX_ZOOM = 1.8;
43
44
  var DEFAULT_VIEW = { scale: 0.82, x: 48, y: 48 };
44
45
  var DUTY_ORDER = [
45
46
  "\uD68C\uC7A5",
@@ -74,6 +75,34 @@ var GRADE_ORDER = [
74
75
  "\uC0AC\uC6D0",
75
76
  "\uC778\uD134"
76
77
  ];
78
+ var LEADERSHIP_MATCH = [
79
+ ["\uD68C\uC7A5"],
80
+ ["\uB300\uD45C\uC774\uC0AC"],
81
+ ["\uB300\uD45C"],
82
+ ["\uC0AC\uC7A5", "CEO"]
83
+ ];
84
+ var DEFAULT_VOCAB = {
85
+ dutyOrder: DUTY_ORDER,
86
+ gradeOrder: GRADE_ORDER,
87
+ leadership: LEADERSHIP_MATCH,
88
+ unassignedIds: /* @__PURE__ */ new Set([UNASSIGNED_GROUP_ID]),
89
+ unassignedNames: UNASSIGNED_GROUP_NAMES
90
+ };
91
+ function orgVocabOf(props) {
92
+ const { dutyOrder, gradeOrder, leadershipMatch, unassignedGroupIds, unassignedNames } = props;
93
+ return {
94
+ dutyOrder: dutyOrder && dutyOrder.length > 0 ? dutyOrder : DUTY_ORDER,
95
+ gradeOrder: gradeOrder && gradeOrder.length > 0 ? gradeOrder : GRADE_ORDER,
96
+ leadership: leadershipMatch && leadershipMatch.length > 0 ? leadershipMatch.map((entry) => Array.isArray(entry) ? entry : [entry]) : LEADERSHIP_MATCH,
97
+ unassignedIds: new Set(
98
+ unassignedGroupIds && unassignedGroupIds.length > 0 ? unassignedGroupIds : [UNASSIGNED_GROUP_ID]
99
+ ),
100
+ unassignedNames: unassignedNames && unassignedNames.length > 0 ? unassignedNames : UNASSIGNED_GROUP_NAMES
101
+ };
102
+ }
103
+ function unassignedNamesOf(vocab) {
104
+ return vocab.unassignedNames ?? UNASSIGNED_GROUP_NAMES;
105
+ }
77
106
  var OCB_CSS = `
78
107
  .erp-ocb-member { transition: border-color .15s ease, background-color .15s ease; }
79
108
  .erp-ocb-member:hover { border-color: var(--wp-border-brand); background: var(--wp-surface-hover); }
@@ -105,8 +134,20 @@ function rowsOf(body, keys) {
105
134
  function pickEmployees(body) {
106
135
  return rowsOf(body, ["data", "employees"]).flatMap((row) => {
107
136
  if (typeof row !== "object" || row === null) return [];
108
- const record = row;
109
- if (typeof record.id !== "string" || typeof record.name !== "string") return [];
137
+ const raw = row;
138
+ if (typeof raw.id !== "string" || typeof raw.name !== "string") return [];
139
+ const record = { ...raw };
140
+ if (record.department_id == null && typeof raw.departmentId === "string") {
141
+ record.department_id = raw.departmentId;
142
+ }
143
+ if (record.job_title == null && typeof raw.jobTitle === "string") {
144
+ record.job_title = raw.jobTitle;
145
+ }
146
+ const ref = record.department;
147
+ if (ref && typeof ref === "object" && ref.parent_id == null) {
148
+ const camelParent = ref.parentId;
149
+ if (typeof camelParent === "string") record.department = { ...ref, parent_id: camelParent };
150
+ }
110
151
  return [record];
111
152
  });
112
153
  }
@@ -115,11 +156,12 @@ function pickGroups(body) {
115
156
  if (typeof row !== "object" || row === null) return [];
116
157
  const record = row;
117
158
  if (typeof record.id !== "string" || typeof record.name !== "string") return [];
159
+ const parentId = typeof record.parent_id === "string" ? record.parent_id : typeof record.parentId === "string" ? record.parentId : null;
118
160
  return [
119
161
  {
120
162
  id: record.id,
121
163
  name: record.name,
122
- parent_id: typeof record.parent_id === "string" ? record.parent_id : null,
164
+ parent_id: parentId,
123
165
  path: typeof record.path === "string" ? record.path : null
124
166
  }
125
167
  ];
@@ -127,16 +169,10 @@ function pickGroups(body) {
127
169
  }
128
170
  function pickPositions(body) {
129
171
  return rowsOf(body, ["data"]).flatMap((row) => {
130
- if (typeof row !== "object" || row === null) return [];
131
- const record = row;
132
- if (typeof record.id !== "string" || typeof record.name !== "string") return [];
172
+ const parsed = parseJobPositionRow(row);
173
+ if (!parsed || parsed.id === void 0) return [];
133
174
  return [
134
- {
135
- id: record.id,
136
- name: record.name,
137
- rank_order: typeof record.rank_order === "number" ? record.rank_order : 0,
138
- is_active: typeof record.is_active === "boolean" ? record.is_active : true
139
- }
175
+ { id: parsed.id, name: parsed.name, rank_order: parsed.rankOrder, is_active: parsed.isActive }
140
176
  ];
141
177
  });
142
178
  }
@@ -179,7 +215,7 @@ function rankIn(order, value) {
179
215
  });
180
216
  return best;
181
217
  }
182
- function compareGrades(a, b, positionRanks) {
218
+ function compareGrades(a, b, positionRanks, gradeOrder) {
183
219
  const rankA = jobPositionRankOf(positionRanks, a);
184
220
  const rankB = jobPositionRankOf(positionRanks, b);
185
221
  if (rankA !== null || rankB !== null) {
@@ -187,12 +223,12 @@ function compareGrades(a, b, positionRanks) {
187
223
  if (rankB === null) return -1;
188
224
  return rankA - rankB;
189
225
  }
190
- return rankIn(GRADE_ORDER, a) - rankIn(GRADE_ORDER, b);
226
+ return rankIn(gradeOrder, a) - rankIn(gradeOrder, b);
191
227
  }
192
- function compareMembers(a, b, positionRanks) {
193
- const dutyDiff = rankIn(DUTY_ORDER, a.job_title) - rankIn(DUTY_ORDER, b.job_title);
228
+ function compareMembers(a, b, positionRanks, dutyOrder = DUTY_ORDER, gradeOrder = GRADE_ORDER) {
229
+ const dutyDiff = rankIn(dutyOrder, a.job_title) - rankIn(dutyOrder, b.job_title);
194
230
  if (dutyDiff !== 0) return dutyDiff;
195
- const gradeDiff = compareGrades(a.grade, b.grade, positionRanks);
231
+ const gradeDiff = compareGrades(a.grade, b.grade, positionRanks, gradeOrder);
196
232
  if (gradeDiff !== 0) return gradeDiff;
197
233
  const levelDiff = (b.level ?? 0) - (a.level ?? 0);
198
234
  if (levelDiff !== 0) return levelDiff;
@@ -201,40 +237,45 @@ function compareMembers(a, b, positionRanks) {
201
237
  function positionText(employee) {
202
238
  return (employee.grade ?? "").trim() || (employee.job_title ?? "").trim() || "-";
203
239
  }
204
- function isUnassignedGroup(group) {
205
- return group.id === UNASSIGNED_GROUP_ID || (group.name ?? "").trim() === "\uBBF8\uC9C0\uC815" || group.id.trim() === "\uBBF8\uC9C0\uC815";
240
+ function isUnassignedGroup(group, unassignedIds = DEFAULT_VOCAB.unassignedIds, unassignedNames = UNASSIGNED_GROUP_NAMES) {
241
+ return unassignedIds.has(group.id) || unassignedNames.includes((group.name ?? "").trim()) || unassignedNames.includes(group.id.trim());
206
242
  }
207
243
  function employeeGroupId(employee) {
208
244
  return employee.department_id ?? employee.department?.id ?? "";
209
245
  }
210
- function groupsFromEmployees(employees) {
246
+ function groupsFromEmployees(employees, vocab) {
247
+ const names = unassignedNamesOf(vocab);
211
248
  const groupsById = /* @__PURE__ */ new Map();
212
249
  for (const employee of employees) {
213
250
  const ref = employee.department;
214
251
  const id = employee.department_id ?? ref?.id ?? "";
215
252
  if (!id || groupsById.has(id)) continue;
216
- const name = (ref?.name ?? "").trim() || "\uBBF8\uC9C0\uC815";
217
- if (isUnassignedGroup({ id, name })) continue;
253
+ const name = (ref?.name ?? "").trim() || (names[0] ?? "\uBBF8\uC9C0\uC815");
254
+ if (isUnassignedGroup({ id, name }, vocab.unassignedIds, names)) continue;
218
255
  groupsById.set(id, { id, name, parent_id: ref?.parent_id ?? null });
219
256
  }
220
257
  return Array.from(groupsById.values());
221
258
  }
222
- function mergeEmployeeGroups(groups, employees) {
259
+ function mergeEmployeeGroups(groups, employees, vocab) {
260
+ const names = unassignedNamesOf(vocab);
223
261
  const groupsById = new Map(
224
- groups.filter((group) => !isUnassignedGroup(group)).map((group) => [group.id, group])
262
+ groups.filter((group) => !isUnassignedGroup(group, vocab.unassignedIds, names)).map((group) => [group.id, group])
225
263
  );
226
264
  for (const employee of employees) {
227
265
  const ref = employee.department;
228
266
  const id = employee.department_id ?? ref?.id ?? "";
229
267
  if (!id || groupsById.has(id)) continue;
230
- const name = (ref?.name ?? "").trim() || "\uBBF8\uC9C0\uC815";
231
- if (isUnassignedGroup({ id, name })) continue;
268
+ const name = (ref?.name ?? "").trim() || (names[0] ?? "\uBBF8\uC9C0\uC815");
269
+ if (isUnassignedGroup({ id, name }, vocab.unassignedIds, names)) continue;
232
270
  groupsById.set(id, { id, name, parent_id: ref?.parent_id ?? null });
233
271
  }
234
272
  return Array.from(groupsById.values());
235
273
  }
236
- function buildModel(groups, employees, positionRanks) {
237
- const visibleGroups = groups.filter((group) => !isUnassignedGroup(group));
274
+ function buildModel(groups, employees, positionRanks, vocab = DEFAULT_VOCAB) {
275
+ const names = unassignedNamesOf(vocab);
276
+ const visibleGroups = groups.filter(
277
+ (group) => !isUnassignedGroup(group, vocab.unassignedIds, names)
278
+ );
238
279
  const groupsById = new Map(visibleGroups.map((group) => [group.id, group]));
239
280
  const employeesByGroupId = /* @__PURE__ */ new Map();
240
281
  for (const employee of employees) {
@@ -255,7 +296,7 @@ function buildModel(groups, employees, positionRanks) {
255
296
  (items) => items.sort((a, b) => a.name.localeCompare(b.name, "ko"))
256
297
  );
257
298
  employeesByGroupId.forEach(
258
- (items) => items.sort((a, b) => compareMembers(a, b, positionRanks))
299
+ (items) => items.sort((a, b) => compareMembers(a, b, positionRanks, vocab.dutyOrder, vocab.gradeOrder))
259
300
  );
260
301
  return {
261
302
  childrenByParent,
@@ -275,22 +316,18 @@ function groupEmployees(groupId, model, excludedIds) {
275
316
  (employee) => !excludedIds.has(employee.id)
276
317
  );
277
318
  }
278
- function leadershipEmployees(employees) {
279
- return employees.filter((employee) => {
280
- const title = `${employee.grade ?? ""} ${employee.job_title ?? ""}`;
281
- return /회장|대표|CEO|사장/.test(title);
282
- }).sort((a, b) => leadershipRank(a) - leadershipRank(b)).slice(0, 3);
283
- }
284
- function leadershipRank(employee) {
319
+ function leadershipRankOf(employee, leadership) {
285
320
  const title = `${employee.grade ?? ""} ${employee.job_title ?? ""}`;
286
- if (title.includes("\uD68C\uC7A5")) return 0;
287
- if (title.includes("\uB300\uD45C\uC774\uC0AC")) return 1;
288
- if (title.includes("\uB300\uD45C")) return 2;
289
- if (title.includes("\uC0AC\uC7A5") || title.includes("CEO")) return 3;
290
- return 9;
321
+ for (let index = 0; index < leadership.length; index += 1) {
322
+ if (leadership[index].some((name) => title.includes(name))) return index;
323
+ }
324
+ return -1;
291
325
  }
292
- function buildCanvasLayout(model, employees) {
293
- const leadership = leadershipEmployees(employees);
326
+ function leadershipEmployees(employees, leadership = LEADERSHIP_MATCH) {
327
+ return employees.map((employee) => ({ employee, rank: leadershipRankOf(employee, leadership) })).filter((entry) => entry.rank >= 0).sort((a, b) => a.rank - b.rank).slice(0, 3).map((entry) => entry.employee);
328
+ }
329
+ function buildCanvasLayout(model, employees, leadershipGroups) {
330
+ const leadership = leadershipEmployees(employees, leadershipGroups);
294
331
  const leadershipIds = new Set(leadership.map((employee) => employee.id));
295
332
  const topGroup = model.visibleRoots.length === 1 ? model.visibleRoots[0] : null;
296
333
  const topChildren = topGroup ? model.childrenByParent.get(topGroup.id) ?? [] : [];
@@ -645,17 +682,6 @@ function connectorPath(connector) {
645
682
  `V ${connector.toY}`
646
683
  ].join(" ");
647
684
  }
648
- function clamp(value, min, max) {
649
- return Math.min(max, Math.max(min, value));
650
- }
651
- function applyZoom(current, nextScale, focal) {
652
- const scale = clamp(nextScale, MIN_ZOOM, MAX_ZOOM);
653
- return {
654
- scale,
655
- x: focal.x - (focal.x - current.x) / current.scale * scale,
656
- y: focal.y - (focal.y - current.y) / current.scale * scale
657
- };
658
- }
659
685
  function EmployeeAvatar({ employee, featured }) {
660
686
  const size = featured ? 40 : 32;
661
687
  if (employee.image) {
@@ -877,92 +903,31 @@ function CanvasEmployeeNode({
877
903
  function OrganizationCanvas({
878
904
  model,
879
905
  employees,
906
+ leadership,
880
907
  selectedGroupId,
881
908
  onSelect,
882
909
  onOpenCard,
883
910
  height
884
911
  }) {
885
- const viewportRef = useRef(null);
886
- const dragRef = useRef(null);
887
- const [view, setView] = useState(DEFAULT_VIEW);
888
- const [dragging, setDragging] = useState(false);
889
- const [isFullscreen, setIsFullscreen] = useState(false);
890
- const layout = useMemo(() => buildCanvasLayout(model, employees), [model, employees]);
891
- useEffect(() => {
892
- const handleFullscreenChange = () => {
893
- setIsFullscreen(document.fullscreenElement === viewportRef.current);
894
- };
895
- document.addEventListener("fullscreenchange", handleFullscreenChange);
896
- return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
897
- }, []);
912
+ const {
913
+ viewportRef,
914
+ view,
915
+ setView,
916
+ dragging,
917
+ isFullscreen,
918
+ viewportCenter,
919
+ zoomAt,
920
+ toggleFullscreen,
921
+ pointerHandlers
922
+ } = usePanZoomCanvas({ stopSelector: "[data-org-card]", initialView: DEFAULT_VIEW });
923
+ const layout = useMemo(
924
+ () => buildCanvasLayout(model, employees, leadership),
925
+ [model, employees, leadership]
926
+ );
898
927
  useEffect(() => {
899
928
  setView(DEFAULT_VIEW);
900
- }, [layout.width, layout.height]);
901
- useEffect(() => {
902
- const viewport = viewportRef.current;
903
- if (!viewport) return;
904
- const onWheel = (event) => {
905
- event.preventDefault();
906
- const rect = viewport.getBoundingClientRect();
907
- const focal = { x: event.clientX - rect.left, y: event.clientY - rect.top };
908
- setView(
909
- (current) => applyZoom(current, current.scale * (event.deltaY > 0 ? 0.9 : 1.1), focal)
910
- );
911
- };
912
- viewport.addEventListener("wheel", onWheel, { passive: false });
913
- return () => viewport.removeEventListener("wheel", onWheel);
914
- }, []);
915
- const viewportCenter = useCallback(() => {
916
- const rect = viewportRef.current?.getBoundingClientRect();
917
- if (!rect) return { x: 0, y: 0 };
918
- return { x: rect.width / 2, y: rect.height / 2 };
919
- }, []);
920
- const zoomStep = (delta) => {
921
- const focal = viewportCenter();
922
- setView((current) => applyZoom(current, current.scale + delta, focal));
923
- };
924
- const handlePointerDown = (event) => {
925
- const target = event.target;
926
- if (target.closest("[data-org-card]")) return;
927
- dragRef.current = {
928
- pointerId: event.pointerId,
929
- startX: event.clientX,
930
- startY: event.clientY,
931
- originX: view.x,
932
- originY: view.y
933
- };
934
- setDragging(true);
935
- event.currentTarget.setPointerCapture(event.pointerId);
936
- };
937
- const handlePointerMove = (event) => {
938
- const drag = dragRef.current;
939
- if (!drag || drag.pointerId !== event.pointerId) return;
940
- setView((current) => ({
941
- ...current,
942
- x: drag.originX + event.clientX - drag.startX,
943
- y: drag.originY + event.clientY - drag.startY
944
- }));
945
- };
946
- const handlePointerUp = (event) => {
947
- if (dragRef.current?.pointerId !== event.pointerId) return;
948
- dragRef.current = null;
949
- setDragging(false);
950
- if (event.currentTarget.hasPointerCapture(event.pointerId)) {
951
- event.currentTarget.releasePointerCapture(event.pointerId);
952
- }
953
- };
954
- const toggleFullscreen = async () => {
955
- const viewport = viewportRef.current;
956
- if (!viewport) return;
957
- try {
958
- if (document.fullscreenElement === viewport) {
959
- await document.exitFullscreen();
960
- return;
961
- }
962
- await viewport.requestFullscreen();
963
- } catch {
964
- }
965
- };
929
+ }, [layout.width, layout.height, setView]);
930
+ const zoomStep = (delta) => zoomAt(view.scale + delta, viewportCenter());
966
931
  return /* @__PURE__ */ jsxs(
967
932
  "div",
968
933
  {
@@ -978,10 +943,7 @@ function OrganizationCanvas({
978
943
  cursor: dragging ? "grabbing" : "grab",
979
944
  touchAction: "none"
980
945
  },
981
- onPointerDown: handlePointerDown,
982
- onPointerMove: handlePointerMove,
983
- onPointerUp: handlePointerUp,
984
- onPointerCancel: handlePointerUp,
946
+ ...pointerHandlers,
985
947
  children: [
986
948
  /* @__PURE__ */ jsxs(
987
949
  "div",
@@ -1080,7 +1042,12 @@ function OrgChartBoardImpl({
1080
1042
  height = "72vh",
1081
1043
  employeesEndpoint = "/employees",
1082
1044
  departmentsEndpoint = "/departments?include=paths",
1083
- positionsEndpoint = "/job-positions"
1045
+ positionsEndpoint = "/job-positions",
1046
+ dutyOrder,
1047
+ gradeOrder,
1048
+ leadershipMatch,
1049
+ unassignedGroupIds,
1050
+ unassignedNames
1084
1051
  }) {
1085
1052
  const adapter = useSduiAdapter();
1086
1053
  const api = hostFetchOf(adapter) ?? defaultAdapter.fetch;
@@ -1090,24 +1057,46 @@ function OrgChartBoardImpl({
1090
1057
  const [state, setState] = useState("idle");
1091
1058
  const [offline, setOffline] = useState(false);
1092
1059
  const [selectedGroupId, setSelectedGroupId] = useState("");
1060
+ const vocabKey = JSON.stringify({
1061
+ dutyOrder,
1062
+ gradeOrder,
1063
+ leadershipMatch,
1064
+ unassignedGroupIds,
1065
+ unassignedNames
1066
+ });
1067
+ const vocab = useMemo(
1068
+ () => orgVocabOf(JSON.parse(vocabKey)),
1069
+ [vocabKey]
1070
+ );
1071
+ const apiRef = useRef(api);
1072
+ apiRef.current = api;
1073
+ const vocabRef = useRef(vocab);
1074
+ vocabRef.current = vocab;
1093
1075
  const load = useCallback(async () => {
1076
+ const fetchApi = apiRef.current;
1077
+ const currentVocab = vocabRef.current;
1094
1078
  setState("loading");
1095
1079
  setOffline(false);
1096
1080
  try {
1097
1081
  const [nextEmployees, fetchedGroups, nextPositions] = await Promise.all([
1098
- fetchAllPages(api, employeesEndpoint, pickEmployees),
1082
+ fetchAllPages(fetchApi, employeesEndpoint, pickEmployees),
1099
1083
  // 부서 조회가 죽어도 조직도는 떠야 한다 — 사원 데이터에서 합성한다.
1100
- fetchAllPages(api, departmentsEndpoint, pickGroups).catch(
1084
+ fetchAllPages(fetchApi, departmentsEndpoint, pickGroups).catch(
1101
1085
  () => []
1102
1086
  ),
1103
1087
  // 직위 마스터는 정렬 보조라 못 불러와도 조직도는 떠야 한다.
1104
- fetchAllPages(api, positionsEndpoint, pickPositions).catch(
1088
+ fetchAllPages(fetchApi, positionsEndpoint, pickPositions).catch(
1105
1089
  () => []
1106
1090
  )
1107
1091
  ]);
1108
- let nextGroups = fetchedGroups.length > 0 ? fetchedGroups : groupsFromEmployees(nextEmployees);
1109
- nextGroups = mergeEmployeeGroups(nextGroups, nextEmployees);
1110
- const model2 = buildModel(nextGroups, nextEmployees, jobPositionRankMap(nextPositions));
1092
+ let nextGroups = fetchedGroups.length > 0 ? fetchedGroups : groupsFromEmployees(nextEmployees, currentVocab);
1093
+ nextGroups = mergeEmployeeGroups(nextGroups, nextEmployees, currentVocab);
1094
+ const model2 = buildModel(
1095
+ nextGroups,
1096
+ nextEmployees,
1097
+ jobPositionRankMap(nextPositions),
1098
+ currentVocab
1099
+ );
1111
1100
  const firstGroupId = model2.visibleRoots[0]?.id ?? (nextEmployees[0] ? employeeGroupId(nextEmployees[0]) : "");
1112
1101
  setEmployees(nextEmployees);
1113
1102
  setGroups(nextGroups);
@@ -1118,17 +1107,25 @@ function OrgChartBoardImpl({
1118
1107
  setOffline(error?.status === 503);
1119
1108
  setState("error");
1120
1109
  }
1121
- }, []);
1110
+ }, [employeesEndpoint, departmentsEndpoint, positionsEndpoint]);
1122
1111
  useEffect(() => {
1123
1112
  void load();
1124
1113
  }, [load]);
1125
1114
  const model = useMemo(
1126
- () => buildModel(groups, employees, jobPositionRankMap(positions)),
1127
- [groups, employees, positions]
1115
+ () => buildModel(groups, employees, jobPositionRankMap(positions), vocab),
1116
+ [groups, employees, positions, vocab]
1128
1117
  );
1129
1118
  const openCard = useCallback(
1130
1119
  (employee) => {
1131
- onMemberClick?.(employee);
1120
+ if (!onMemberClick) {
1121
+ warnOnce(
1122
+ "orgchartboard:member-click",
1123
+ "[sdui] OrgChartBoard: onMemberClick\uC774 \uC5C6\uC5B4 \uC0AC\uC6D0 \uCE74\uB4DC \uD074\uB9AD\uC744 \uBB34\uC2DC\uD569\uB2C8\uB2E4 \u2014 setState(fromEvent)\uB85C \uD589\uC744 \uBC1B\uC544 navigate\uB85C \uC774\uC73C\uC138\uC694",
1124
+ "warn"
1125
+ );
1126
+ return;
1127
+ }
1128
+ onMemberClick(employee);
1132
1129
  },
1133
1130
  [onMemberClick]
1134
1131
  );
@@ -1175,6 +1172,7 @@ function OrgChartBoardImpl({
1175
1172
  {
1176
1173
  model,
1177
1174
  employees,
1175
+ leadership: vocab.leadership,
1178
1176
  selectedGroupId,
1179
1177
  onSelect: setSelectedGroupId,
1180
1178
  onOpenCard: openCard,
@@ -1192,7 +1190,7 @@ function OrgChartBoardImpl({
1192
1190
  overflow: "hidden"
1193
1191
  },
1194
1192
  children: [
1195
- /* @__PURE__ */ jsx("style", { children: OCB_CSS }),
1193
+ /* @__PURE__ */ jsx("style", { href: "erp-ocb", precedence: "medium", children: OCB_CSS }),
1196
1194
  /* @__PURE__ */ jsxs(
1197
1195
  "div",
1198
1196
  {
@@ -1239,4 +1237,4 @@ function OrgChartBoardImpl({
1239
1237
  }
1240
1238
  var OrgChartBoard = boxed(OrgChartBoardImpl, "OrgChartBoard");
1241
1239
 
1242
- export { OrgChartBoard };
1240
+ export { OrgChartBoard, buildModel, compareMembers, isUnassignedGroup, leadershipEmployees, orgVocabOf, pickEmployees, pickGroups, pickPositions };
@@ -1,5 +1,5 @@
1
1
  import { OverlayLayer } from './chunk-XQPTSEJC.js';
2
- import { Dialog, warnOnce } from './chunk-TUOG7EEI.js';
2
+ import { Dialog, warnOnce } from './chunk-RNOUXUBO.js';
3
3
  import { createContext, useContext, useMemo } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
@@ -1,5 +1,5 @@
1
- import { ImageViewer } from './chunk-AVRNQOB5.js';
2
- import { boxed } from './chunk-YQOMSWW5.js';
1
+ import { ImageViewer } from './chunk-SAPBDJJQ.js';
2
+ import { boxed } from './chunk-5WW4MBRO.js';
3
3
  import { useState } from 'react';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
5
 
@@ -1,6 +1,6 @@
1
- import { DateRangePicker } from './chunk-I5Z2RVDF.js';
1
+ import { DateRangePicker } from './chunk-WSLT32Q4.js';
2
2
  import { useOverlayLayer } from './chunk-XQPTSEJC.js';
3
- import { Input, Button, IconButton, Tag, Field, Checkbox } from './chunk-TUOG7EEI.js';
3
+ import { Input, Button, IconButton, Tag, Field, Checkbox } from './chunk-RNOUXUBO.js';
4
4
  import { useRef, useEffect, useState, useMemo } from 'react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
@@ -29,20 +29,20 @@ function Popover({
29
29
  }, [open]);
30
30
  useOverlayLayer(open, () => closeRef.current(), hostRef);
31
31
  return (
32
- // 트리거와 패널을 함께 감싸는 기준 상자 — .erp-popover-host의 position:relative가
32
+ // 트리거와 패널을 함께 감싸는 기준 상자 — .wp-popover-host의 position:relative가
33
33
  // 패널 absolute의 기준이 된다
34
- /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "erp-popover-host", children: [
34
+ /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "wp-popover-host erp-popover-host", children: [
35
35
  trigger,
36
36
  open ? (
37
- // 기본은 오른쪽 기준(.erp-popover), --left가 붙으면 왼쪽 기준으로 뒤집힌다
37
+ // 기본은 오른쪽 기준(.wp-popover), --left가 붙으면 왼쪽 기준으로 뒤집힌다
38
38
  /* @__PURE__ */ jsxs(
39
39
  "div",
40
40
  {
41
- className: alignLeft ? "erp-popover erp-popover--left" : "erp-popover",
41
+ className: alignLeft ? "wp-popover wp-popover--left erp-popover erp-popover--left" : "wp-popover erp-popover",
42
42
  style: width ? { width } : void 0,
43
43
  children: [
44
- /* @__PURE__ */ jsx("div", { className: "erp-popover__body", children }),
45
- footer ? /* @__PURE__ */ jsx("div", { className: "erp-popover__foot", children: footer }) : null
44
+ /* @__PURE__ */ jsx("div", { className: "wp-popover__body erp-popover__body", children }),
45
+ footer ? /* @__PURE__ */ jsx("div", { className: "wp-popover__foot erp-popover__foot", children: footer }) : null
46
46
  ]
47
47
  }
48
48
  )