@burdenoff/microfe-crewfoundry 2026.1030.3 → 2026.1102.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.
- package/dist/crewfoundry/CrewFoundryRoutes.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.js +18 -14
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/EmployeeSkillsPanel.d.ts +9 -0
- package/dist/crewfoundry/components/EmployeeSkillsPanel.d.ts.map +1 -1
- package/dist/crewfoundry/components/EmployeeSkillsPanel.js +111 -77
- package/dist/crewfoundry/components/EmployeeSkillsPanel.js.map +1 -1
- package/dist/crewfoundry/components/skills/FindPeopleBySkillCard.d.ts +13 -0
- package/dist/crewfoundry/components/skills/FindPeopleBySkillCard.d.ts.map +1 -0
- package/dist/crewfoundry/components/skills/FindPeopleBySkillCard.js +128 -0
- package/dist/crewfoundry/components/skills/FindPeopleBySkillCard.js.map +1 -0
- package/dist/crewfoundry/components/skills/MergeSkillDialog.d.ts +15 -0
- package/dist/crewfoundry/components/skills/MergeSkillDialog.d.ts.map +1 -0
- package/dist/crewfoundry/components/skills/MergeSkillDialog.js +58 -0
- package/dist/crewfoundry/components/skills/MergeSkillDialog.js.map +1 -0
- package/dist/crewfoundry/components/skills/RoleSkillMatchPanel.d.ts +15 -0
- package/dist/crewfoundry/components/skills/RoleSkillMatchPanel.d.ts.map +1 -0
- package/dist/crewfoundry/components/skills/RoleSkillMatchPanel.js +163 -0
- package/dist/crewfoundry/components/skills/RoleSkillMatchPanel.js.map +1 -0
- package/dist/crewfoundry/components/skills/SkillPicker.d.ts +20 -0
- package/dist/crewfoundry/components/skills/SkillPicker.d.ts.map +1 -0
- package/dist/crewfoundry/components/skills/SkillPicker.js +73 -0
- package/dist/crewfoundry/components/skills/SkillPicker.js.map +1 -0
- package/dist/crewfoundry/components/skills/SkillRelationsDialog.d.ts +13 -0
- package/dist/crewfoundry/components/skills/SkillRelationsDialog.d.ts.map +1 -0
- package/dist/crewfoundry/components/skills/SkillRelationsDialog.js +156 -0
- package/dist/crewfoundry/components/skills/SkillRelationsDialog.js.map +1 -0
- package/dist/crewfoundry/lib/analytics.d.ts +1 -29
- package/dist/crewfoundry/lib/analytics.d.ts.map +1 -1
- package/dist/crewfoundry/lib/analytics.js +3 -48
- package/dist/crewfoundry/lib/analytics.js.map +1 -1
- package/dist/crewfoundry/lib/skillMatching.d.ts +70 -0
- package/dist/crewfoundry/lib/skillMatching.d.ts.map +1 -0
- package/dist/crewfoundry/lib/skillMatching.js +73 -0
- package/dist/crewfoundry/lib/skillMatching.js.map +1 -0
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.d.ts +8 -0
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js +468 -212
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +210 -177
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +6 -6
- package/dist/crewfoundry/pages/HrSkillsTaxonomyPagePage.d.ts +9 -0
- package/dist/crewfoundry/pages/HrSkillsTaxonomyPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/HrSkillsTaxonomyPagePage.js +456 -0
- package/dist/crewfoundry/pages/HrSkillsTaxonomyPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +50 -102
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.d.ts +145 -0
- package/dist/crewfoundry/store/crewfoundryStore.d.ts.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +188 -57
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
|
+
import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
|
|
3
|
+
import { StatCard as n } from "../components/viz/StatCard.js";
|
|
4
|
+
import "../components/viz/index.js";
|
|
5
|
+
import { FormModal as r } from "../components/forms/FormModal.js";
|
|
6
|
+
import { ConfirmDialog as ee } from "../components/forms/ConfirmDialog.js";
|
|
7
|
+
import { getTags as i, getText as a } from "../components/forms/formValues.js";
|
|
8
|
+
import { MergeSkillDialog as te } from "../components/skills/MergeSkillDialog.js";
|
|
9
|
+
import { SkillRelationsDialog as ne } from "../components/skills/SkillRelationsDialog.js";
|
|
10
|
+
import { useEffect as o, useMemo as s, useState as c } from "react";
|
|
11
|
+
import { AlertTriangle as re, GitMerge as l, Layers as ie, Pencil as u, Plus as ae, Search as oe, Share2 as se, Sparkles as ce, Trash2 as d } from "lucide-react";
|
|
12
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
13
|
+
import { Badge as le, Button as m, Card as h, CardContent as g, CardDescription as _, CardHeader as v, CardTitle as y, GradientText as ue, IllustratedEmptyState as b, Input as de, PagePurpose as fe, Pagination as pe, ResponsiveTable as me, Select as x, SelectContent as S, SelectItem as C, SelectTrigger as w, SelectValue as T, Spinner as he } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
import { usePagination as ge } from "@burdenoff/fe-libs/shared/hooks";
|
|
15
|
+
import { useGlobalUi as E } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
16
|
+
//#region src/crewfoundry/pages/HrSkillsTaxonomyPagePage.tsx
|
|
17
|
+
var D = "all", _e = [
|
|
18
|
+
{
|
|
19
|
+
value: D,
|
|
20
|
+
label: "All sources"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "TAXONOMY",
|
|
24
|
+
label: "Starter taxonomy"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: "CUSTOM",
|
|
28
|
+
label: "Custom"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: "BACKFILL",
|
|
32
|
+
label: "Needs triage"
|
|
33
|
+
}
|
|
34
|
+
], ve = {
|
|
35
|
+
TAXONOMY: "Starter",
|
|
36
|
+
CUSTOM: "Custom",
|
|
37
|
+
BACKFILL: "Needs triage"
|
|
38
|
+
}, O = (e) => e.employeeCount + e.candidateCount, ye = (e) => ({
|
|
39
|
+
name: e.name,
|
|
40
|
+
category: e.category,
|
|
41
|
+
aliases: e.aliases,
|
|
42
|
+
description: e.description ?? ""
|
|
43
|
+
});
|
|
44
|
+
function k() {
|
|
45
|
+
let { showToast: k } = E(), A = e((e) => e.initialized), j = e((e) => e.skillsTaxonomy), M = e((e) => e.skillsTaxonomyLoaded), N = e((e) => e.skillCategories), P = e((e) => e.fetchSkillsTaxonomy), be = e((e) => e.createTaxonomySkill), xe = e((e) => e.updateTaxonomySkill), Se = e((e) => e.removeTaxonomySkill), [F, Ce] = c(""), [I, we] = c(D), [L, Te] = c(D), [Ee, R] = c(!1), [z, B] = c(null), [De, V] = c(null), [Oe, H] = c(null), [U, W] = c(null), [G, K] = c(!1), { page: ke, pageSize: q, offset: J, setPage: Y } = ge({ initialPageSize: 25 });
|
|
46
|
+
o(() => {
|
|
47
|
+
!A || M || P().catch((e) => {
|
|
48
|
+
K(!0), k(e instanceof Error ? e.message : "Unable to load the skills taxonomy", "error");
|
|
49
|
+
});
|
|
50
|
+
}, [
|
|
51
|
+
A,
|
|
52
|
+
M,
|
|
53
|
+
P,
|
|
54
|
+
k
|
|
55
|
+
]);
|
|
56
|
+
let X = s(() => [
|
|
57
|
+
{
|
|
58
|
+
name: "name",
|
|
59
|
+
label: "Skill name",
|
|
60
|
+
type: "text",
|
|
61
|
+
required: !0
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "category",
|
|
65
|
+
label: "Category",
|
|
66
|
+
type: "select",
|
|
67
|
+
required: !0,
|
|
68
|
+
options: N.map((e) => ({
|
|
69
|
+
label: t(e),
|
|
70
|
+
value: e
|
|
71
|
+
}))
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "aliases",
|
|
75
|
+
label: "Aliases (other spellings)",
|
|
76
|
+
type: "tags",
|
|
77
|
+
colSpan: 2,
|
|
78
|
+
placeholder: "e.g. K8s"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "description",
|
|
82
|
+
label: "Description",
|
|
83
|
+
type: "textarea",
|
|
84
|
+
colSpan: 2
|
|
85
|
+
}
|
|
86
|
+
], [N]), Z = s(() => j.filter((e) => e.source === "BACKFILL").sort((e, t) => O(t) - O(e) || e.name.localeCompare(t.name)), [j]), Q = s(() => ({
|
|
87
|
+
total: j.length,
|
|
88
|
+
custom: j.filter((e) => e.source === "CUSTOM").length,
|
|
89
|
+
triage: Z.length,
|
|
90
|
+
unused: j.filter((e) => O(e) === 0).length
|
|
91
|
+
}), [j, Z]), $ = s(() => {
|
|
92
|
+
let e = F.trim().toLowerCase();
|
|
93
|
+
return j.filter((t) => (I === D || t.category === I) && (L === D || t.source === L) && (e === "" || t.name.toLowerCase().includes(e) || t.aliases.some((t) => t.toLowerCase().includes(e))));
|
|
94
|
+
}, [
|
|
95
|
+
j,
|
|
96
|
+
F,
|
|
97
|
+
I,
|
|
98
|
+
L
|
|
99
|
+
]), Ae = $.slice(J, J + q);
|
|
100
|
+
return !A || !M && !G ? /* @__PURE__ */ f("div", {
|
|
101
|
+
className: "flex min-h-[60vh] items-center justify-center p-6",
|
|
102
|
+
children: /* @__PURE__ */ f(he, { size: "lg" })
|
|
103
|
+
}) : /* @__PURE__ */ p("div", {
|
|
104
|
+
className: "space-y-6 p-6",
|
|
105
|
+
"data-testid": "skills-taxonomy-page",
|
|
106
|
+
children: [
|
|
107
|
+
/* @__PURE__ */ p("div", {
|
|
108
|
+
className: "flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between",
|
|
109
|
+
children: [/* @__PURE__ */ p("div", { children: [
|
|
110
|
+
/* @__PURE__ */ f("p", {
|
|
111
|
+
className: "text-sm text-text-secondary",
|
|
112
|
+
children: "Skills are the unit of work"
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ f("h1", {
|
|
115
|
+
className: "text-2xl font-semibold",
|
|
116
|
+
children: /* @__PURE__ */ f(ue, { children: "Skills Taxonomy" })
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ f(fe, {
|
|
119
|
+
className: "mt-1",
|
|
120
|
+
children: "The canonical skills your workspace matches people on. Add your own, connect them with relations, and triage skills imported from free-text profiles."
|
|
121
|
+
})
|
|
122
|
+
] }), /* @__PURE__ */ p(m, {
|
|
123
|
+
size: "sm",
|
|
124
|
+
onClick: () => R(!0),
|
|
125
|
+
disabled: N.length === 0,
|
|
126
|
+
children: [/* @__PURE__ */ f(ae, { className: "mr-1 h-4 w-4" }), "Add skill"]
|
|
127
|
+
})]
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ p("div", {
|
|
130
|
+
className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ f(n, {
|
|
133
|
+
label: "Skills",
|
|
134
|
+
value: Q.total,
|
|
135
|
+
icon: /* @__PURE__ */ f(ie, { className: "h-4 w-4" })
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ f(n, {
|
|
138
|
+
label: "Custom",
|
|
139
|
+
value: Q.custom,
|
|
140
|
+
icon: /* @__PURE__ */ f(ce, { className: "h-4 w-4" })
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ f(n, {
|
|
143
|
+
label: "Needs triage",
|
|
144
|
+
value: Q.triage,
|
|
145
|
+
icon: /* @__PURE__ */ f(re, { className: "h-4 w-4" })
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ f(n, {
|
|
148
|
+
label: "Unused",
|
|
149
|
+
value: Q.unused,
|
|
150
|
+
icon: /* @__PURE__ */ f(d, { className: "h-4 w-4" })
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ p(h, {
|
|
155
|
+
"data-testid": "skills-triage",
|
|
156
|
+
children: [/* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(y, {
|
|
157
|
+
className: "text-base",
|
|
158
|
+
children: "Needs triage"
|
|
159
|
+
}), /* @__PURE__ */ f(_, { children: "Skill strings from existing profiles that matched nothing in the taxonomy. Recategorise one to accept it as a custom skill, or merge it into the skill it really means." })] }), /* @__PURE__ */ f(g, { children: Z.length > 0 ? /* @__PURE__ */ f("ul", {
|
|
160
|
+
className: "space-y-2",
|
|
161
|
+
"aria-label": "Skills awaiting triage",
|
|
162
|
+
children: Z.map((e) => /* @__PURE__ */ p("li", {
|
|
163
|
+
className: "flex flex-col gap-2 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-3 sm:flex-row sm:items-center sm:justify-between",
|
|
164
|
+
children: [/* @__PURE__ */ p("div", {
|
|
165
|
+
className: "min-w-0",
|
|
166
|
+
children: [/* @__PURE__ */ f("p", {
|
|
167
|
+
className: "font-medium text-text-primary",
|
|
168
|
+
children: e.name
|
|
169
|
+
}), /* @__PURE__ */ p("p", {
|
|
170
|
+
className: "text-xs text-text-muted",
|
|
171
|
+
children: [
|
|
172
|
+
e.employeeCount,
|
|
173
|
+
" people · ",
|
|
174
|
+
e.candidateCount,
|
|
175
|
+
" candidates ·",
|
|
176
|
+
" ",
|
|
177
|
+
t(e.category)
|
|
178
|
+
]
|
|
179
|
+
})]
|
|
180
|
+
}), /* @__PURE__ */ p("div", {
|
|
181
|
+
className: "flex flex-wrap gap-2",
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ p(m, {
|
|
184
|
+
size: "sm",
|
|
185
|
+
variant: "outline",
|
|
186
|
+
onClick: () => B(e),
|
|
187
|
+
children: [/* @__PURE__ */ f(u, { className: "mr-1 h-3.5 w-3.5" }), "Review"]
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ p(m, {
|
|
190
|
+
size: "sm",
|
|
191
|
+
variant: "outline",
|
|
192
|
+
onClick: () => V(e),
|
|
193
|
+
children: [/* @__PURE__ */ f(l, { className: "mr-1 h-3.5 w-3.5" }), "Merge…"]
|
|
194
|
+
}),
|
|
195
|
+
O(e) === 0 ? /* @__PURE__ */ p(m, {
|
|
196
|
+
size: "sm",
|
|
197
|
+
variant: "ghost",
|
|
198
|
+
onClick: () => W(e),
|
|
199
|
+
children: [/* @__PURE__ */ f(d, { className: "mr-1 h-3.5 w-3.5 text-status-error-fg" }), "Remove"]
|
|
200
|
+
}) : null
|
|
201
|
+
]
|
|
202
|
+
})]
|
|
203
|
+
}, e.id))
|
|
204
|
+
}) : /* @__PURE__ */ f(b, {
|
|
205
|
+
illustration: "empty-data",
|
|
206
|
+
title: "Nothing to triage",
|
|
207
|
+
description: "Every skill on your people's profiles maps to a reviewed skill."
|
|
208
|
+
}) })]
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ p(h, { children: [/* @__PURE__ */ p(v, { children: [/* @__PURE__ */ f(y, {
|
|
211
|
+
className: "text-base",
|
|
212
|
+
children: "Taxonomy"
|
|
213
|
+
}), /* @__PURE__ */ p(_, { children: [
|
|
214
|
+
$.length,
|
|
215
|
+
" of ",
|
|
216
|
+
j.length,
|
|
217
|
+
" skills"
|
|
218
|
+
] })] }), /* @__PURE__ */ p(g, {
|
|
219
|
+
className: "space-y-4",
|
|
220
|
+
children: [
|
|
221
|
+
/* @__PURE__ */ p("div", {
|
|
222
|
+
className: "flex flex-col gap-3 md:flex-row md:items-center",
|
|
223
|
+
children: [
|
|
224
|
+
/* @__PURE__ */ p("div", {
|
|
225
|
+
className: "relative flex-1 md:max-w-sm",
|
|
226
|
+
children: [/* @__PURE__ */ f(oe, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ f(de, {
|
|
227
|
+
"aria-label": "Search skills",
|
|
228
|
+
placeholder: "Search skills or aliases…",
|
|
229
|
+
className: "pl-9",
|
|
230
|
+
value: F,
|
|
231
|
+
onChange: (e) => {
|
|
232
|
+
Ce(e.target.value), Y(1);
|
|
233
|
+
}
|
|
234
|
+
})]
|
|
235
|
+
}),
|
|
236
|
+
/* @__PURE__ */ p(x, {
|
|
237
|
+
value: I,
|
|
238
|
+
onValueChange: (e) => {
|
|
239
|
+
we(e), Y(1);
|
|
240
|
+
},
|
|
241
|
+
children: [/* @__PURE__ */ f(w, {
|
|
242
|
+
className: "w-full md:w-56",
|
|
243
|
+
"aria-label": "Filter by category",
|
|
244
|
+
children: /* @__PURE__ */ f(T, {})
|
|
245
|
+
}), /* @__PURE__ */ p(S, { children: [/* @__PURE__ */ f(C, {
|
|
246
|
+
value: D,
|
|
247
|
+
children: "All categories"
|
|
248
|
+
}), N.map((e) => /* @__PURE__ */ f(C, {
|
|
249
|
+
value: e,
|
|
250
|
+
children: t(e)
|
|
251
|
+
}, e))] })]
|
|
252
|
+
}),
|
|
253
|
+
/* @__PURE__ */ p(x, {
|
|
254
|
+
value: L,
|
|
255
|
+
onValueChange: (e) => {
|
|
256
|
+
Te(e), Y(1);
|
|
257
|
+
},
|
|
258
|
+
children: [/* @__PURE__ */ f(w, {
|
|
259
|
+
className: "w-full md:w-48",
|
|
260
|
+
"aria-label": "Filter by source",
|
|
261
|
+
children: /* @__PURE__ */ f(T, {})
|
|
262
|
+
}), /* @__PURE__ */ f(S, { children: _e.map((e) => /* @__PURE__ */ f(C, {
|
|
263
|
+
value: e.value,
|
|
264
|
+
children: e.label
|
|
265
|
+
}, e.value)) })]
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
}),
|
|
269
|
+
/* @__PURE__ */ f(me, {
|
|
270
|
+
data: Ae,
|
|
271
|
+
rowKey: (e) => e.id,
|
|
272
|
+
columns: [
|
|
273
|
+
{
|
|
274
|
+
key: "name",
|
|
275
|
+
header: "Skill",
|
|
276
|
+
priority: "primary",
|
|
277
|
+
cell: (e) => /* @__PURE__ */ p("div", {
|
|
278
|
+
className: "min-w-0",
|
|
279
|
+
children: [/* @__PURE__ */ f("p", {
|
|
280
|
+
className: "font-medium text-text-primary",
|
|
281
|
+
children: e.name
|
|
282
|
+
}), e.aliases.length > 0 ? /* @__PURE__ */ p("p", {
|
|
283
|
+
className: "truncate text-xs text-text-muted",
|
|
284
|
+
children: ["Also: ", e.aliases.join(", ")]
|
|
285
|
+
}) : null]
|
|
286
|
+
})
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
key: "category",
|
|
290
|
+
header: "Category",
|
|
291
|
+
priority: "secondary",
|
|
292
|
+
cell: (e) => /* @__PURE__ */ f("span", {
|
|
293
|
+
className: "text-sm text-text-secondary",
|
|
294
|
+
children: t(e.category)
|
|
295
|
+
})
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
key: "source",
|
|
299
|
+
header: "Source",
|
|
300
|
+
priority: "secondary",
|
|
301
|
+
cell: (e) => /* @__PURE__ */ f(le, {
|
|
302
|
+
variant: e.source === "BACKFILL" ? "destructive" : "outline",
|
|
303
|
+
className: "text-[10px]",
|
|
304
|
+
children: ve[e.source] ?? t(e.source)
|
|
305
|
+
})
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: "holders",
|
|
309
|
+
header: "People",
|
|
310
|
+
priority: "primary",
|
|
311
|
+
cell: (e) => /* @__PURE__ */ p("span", {
|
|
312
|
+
className: "text-sm tabular-nums md:block md:text-right",
|
|
313
|
+
children: [e.employeeCount, /* @__PURE__ */ p("span", {
|
|
314
|
+
className: "text-text-muted",
|
|
315
|
+
children: [
|
|
316
|
+
" · ",
|
|
317
|
+
e.candidateCount,
|
|
318
|
+
" cand."
|
|
319
|
+
]
|
|
320
|
+
})]
|
|
321
|
+
})
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
key: "relations",
|
|
325
|
+
header: "Relations",
|
|
326
|
+
priority: "secondary",
|
|
327
|
+
cell: (e) => /* @__PURE__ */ f("span", {
|
|
328
|
+
className: "text-sm tabular-nums md:block md:text-right",
|
|
329
|
+
children: e.relations.length
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
],
|
|
333
|
+
rowActions: (e) => /* @__PURE__ */ p("div", {
|
|
334
|
+
className: "flex items-center justify-end gap-1",
|
|
335
|
+
children: [
|
|
336
|
+
/* @__PURE__ */ f(m, {
|
|
337
|
+
variant: "ghost",
|
|
338
|
+
size: "sm",
|
|
339
|
+
"aria-label": `Relations of ${e.name}`,
|
|
340
|
+
onClick: () => H(e),
|
|
341
|
+
children: /* @__PURE__ */ f(se, { className: "h-3.5 w-3.5" })
|
|
342
|
+
}),
|
|
343
|
+
/* @__PURE__ */ f(m, {
|
|
344
|
+
variant: "ghost",
|
|
345
|
+
size: "sm",
|
|
346
|
+
"aria-label": `Edit ${e.name}`,
|
|
347
|
+
onClick: () => B(e),
|
|
348
|
+
children: /* @__PURE__ */ f(u, { className: "h-3.5 w-3.5" })
|
|
349
|
+
}),
|
|
350
|
+
e.source === "TAXONOMY" ? null : /* @__PURE__ */ f(m, {
|
|
351
|
+
variant: "ghost",
|
|
352
|
+
size: "sm",
|
|
353
|
+
"aria-label": `Merge ${e.name} into another skill`,
|
|
354
|
+
onClick: () => V(e),
|
|
355
|
+
children: /* @__PURE__ */ f(l, { className: "h-3.5 w-3.5" })
|
|
356
|
+
}),
|
|
357
|
+
/* @__PURE__ */ f(m, {
|
|
358
|
+
variant: "ghost",
|
|
359
|
+
size: "sm",
|
|
360
|
+
"aria-label": `Remove ${e.name}`,
|
|
361
|
+
title: O(e) > 0 ? "Held by people; merge it or detach it first" : void 0,
|
|
362
|
+
disabled: O(e) > 0,
|
|
363
|
+
onClick: () => W(e),
|
|
364
|
+
children: /* @__PURE__ */ f(d, { className: "h-3.5 w-3.5 text-status-error-fg" })
|
|
365
|
+
})
|
|
366
|
+
]
|
|
367
|
+
}),
|
|
368
|
+
emptyState: /* @__PURE__ */ f(b, {
|
|
369
|
+
illustration: "empty-search",
|
|
370
|
+
title: G ? "The taxonomy could not be loaded" : "No skills match",
|
|
371
|
+
description: G ? "Reload the page to try again." : "Try a different search, category or source."
|
|
372
|
+
})
|
|
373
|
+
}),
|
|
374
|
+
$.length > q ? /* @__PURE__ */ f(pe, {
|
|
375
|
+
page: ke,
|
|
376
|
+
pageSize: q,
|
|
377
|
+
totalItems: $.length,
|
|
378
|
+
onPageChange: Y
|
|
379
|
+
}) : null
|
|
380
|
+
]
|
|
381
|
+
})] }),
|
|
382
|
+
/* @__PURE__ */ f(r, {
|
|
383
|
+
open: Ee,
|
|
384
|
+
onOpenChange: R,
|
|
385
|
+
title: "Add skill",
|
|
386
|
+
description: "A custom skill for this workspace. Names and aliases must not clash with an existing skill.",
|
|
387
|
+
fields: X,
|
|
388
|
+
initialValues: {
|
|
389
|
+
category: "UNCATEGORIZED",
|
|
390
|
+
aliases: []
|
|
391
|
+
},
|
|
392
|
+
submitLabel: "Add skill",
|
|
393
|
+
onSubmit: async (e) => {
|
|
394
|
+
await be({
|
|
395
|
+
name: a(e, "name").trim(),
|
|
396
|
+
category: a(e, "category"),
|
|
397
|
+
aliases: i(e, "aliases"),
|
|
398
|
+
description: a(e, "description").trim() || void 0
|
|
399
|
+
}), k("Skill added to the taxonomy", "success");
|
|
400
|
+
}
|
|
401
|
+
}),
|
|
402
|
+
/* @__PURE__ */ f(r, {
|
|
403
|
+
open: z !== null,
|
|
404
|
+
onOpenChange: (e) => {
|
|
405
|
+
e || B(null);
|
|
406
|
+
},
|
|
407
|
+
title: z?.source === "BACKFILL" ? "Review skill" : "Edit skill",
|
|
408
|
+
description: z?.source === "BACKFILL" ? "Saving accepts this as a custom skill. A rename keeps the old spelling as an alias." : "A rename keeps the old spelling as an alias, so existing profiles still resolve.",
|
|
409
|
+
fields: X,
|
|
410
|
+
initialValues: z ? ye(z) : void 0,
|
|
411
|
+
submitLabel: z?.source === "BACKFILL" ? "Accept skill" : "Save changes",
|
|
412
|
+
onSubmit: async (e) => {
|
|
413
|
+
if (!z) return;
|
|
414
|
+
let t = z.source === "BACKFILL";
|
|
415
|
+
await xe(z.id, {
|
|
416
|
+
name: a(e, "name").trim(),
|
|
417
|
+
category: a(e, "category"),
|
|
418
|
+
aliases: i(e, "aliases"),
|
|
419
|
+
description: a(e, "description").trim()
|
|
420
|
+
}), B(null), k(t ? "Skill reviewed and saved" : "Skill saved", "success");
|
|
421
|
+
}
|
|
422
|
+
}),
|
|
423
|
+
/* @__PURE__ */ f(te, {
|
|
424
|
+
source: De,
|
|
425
|
+
skills: j,
|
|
426
|
+
onOpenChange: (e) => {
|
|
427
|
+
e || V(null);
|
|
428
|
+
}
|
|
429
|
+
}),
|
|
430
|
+
/* @__PURE__ */ f(ne, {
|
|
431
|
+
skill: Oe,
|
|
432
|
+
skills: j,
|
|
433
|
+
onOpenChange: (e) => {
|
|
434
|
+
e || H(null);
|
|
435
|
+
}
|
|
436
|
+
}),
|
|
437
|
+
/* @__PURE__ */ f(ee, {
|
|
438
|
+
open: U !== null,
|
|
439
|
+
onOpenChange: (e) => {
|
|
440
|
+
e || W(null);
|
|
441
|
+
},
|
|
442
|
+
title: "Remove skill",
|
|
443
|
+
description: U ? `Remove ${U.name} and its relations? Nobody holds it, so no profile changes.` : void 0,
|
|
444
|
+
confirmLabel: "Remove",
|
|
445
|
+
destructive: !0,
|
|
446
|
+
onConfirm: async () => {
|
|
447
|
+
U && (await Se(U.id), k(`${U.name} removed`, "success"), W(null));
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
]
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
454
|
+
export { k as HrSkillsTaxonomyPagePage };
|
|
455
|
+
|
|
456
|
+
//# sourceMappingURL=HrSkillsTaxonomyPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HrSkillsTaxonomyPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrSkillsTaxonomyPagePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n AlertTriangle,\n GitMerge,\n Layers,\n Pencil,\n Plus,\n Search,\n Share2,\n Sparkles,\n Trash2,\n} from 'lucide-react';\nimport { useCrewFoundryStore, type CrewTaxonomySkill } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getTags, getText } from '../components/forms/formValues';\nimport { MergeSkillDialog } from '../components/skills/MergeSkillDialog';\nimport { SkillRelationsDialog } from '../components/skills/SkillRelationsDialog';\nimport { StatCard } from '../components/viz';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst ALL = 'all';\nconst SOURCE_FILTERS = [\n { value: ALL, label: 'All sources' },\n { value: 'TAXONOMY', label: 'Starter taxonomy' },\n { value: 'CUSTOM', label: 'Custom' },\n { value: 'BACKFILL', label: 'Needs triage' },\n];\n\nconst SOURCE_LABEL: Record<string, string> = {\n TAXONOMY: 'Starter',\n CUSTOM: 'Custom',\n BACKFILL: 'Needs triage',\n};\n\nconst holders = (skill: CrewTaxonomySkill): number => skill.employeeCount + skill.candidateCount;\n\nconst skillToValues = (skill: CrewTaxonomySkill): FormValues => ({\n name: skill.name,\n category: skill.category,\n aliases: skill.aliases,\n description: skill.description ?? '',\n});\n\n/**\n * Skills taxonomy (BOFF-10361). Browse the workspace's canonical skills by\n * category, add custom ones, wire up the relations matching traverses, and\n * triage BACKFILL skills — legacy free-text strings the taxonomy did not\n * recognise — by recategorising them (marks them reviewed) or merging them\n * into the skill they really mean. Unused skills can be removed.\n */\nexport function HrSkillsTaxonomyPagePage() {\n const { showToast } = useGlobalUi();\n const initialized = useCrewFoundryStore((state) => state.initialized);\n const skills = useCrewFoundryStore((state) => state.skillsTaxonomy);\n const loaded = useCrewFoundryStore((state) => state.skillsTaxonomyLoaded);\n const categories = useCrewFoundryStore((state) => state.skillCategories);\n const fetchSkillsTaxonomy = useCrewFoundryStore((state) => state.fetchSkillsTaxonomy);\n const createTaxonomySkill = useCrewFoundryStore((state) => state.createTaxonomySkill);\n const updateTaxonomySkill = useCrewFoundryStore((state) => state.updateTaxonomySkill);\n const removeTaxonomySkill = useCrewFoundryStore((state) => state.removeTaxonomySkill);\n\n const [search, setSearch] = useState('');\n const [category, setCategory] = useState(ALL);\n const [source, setSource] = useState(ALL);\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<CrewTaxonomySkill | null>(null);\n const [mergeTarget, setMergeTarget] = useState<CrewTaxonomySkill | null>(null);\n const [relationsTarget, setRelationsTarget] = useState<CrewTaxonomySkill | null>(null);\n const [removeTarget, setRemoveTarget] = useState<CrewTaxonomySkill | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n useEffect(() => {\n if (!initialized || loaded) return;\n fetchSkillsTaxonomy().catch((error: unknown) => {\n setLoadFailed(true);\n showToast(\n error instanceof Error ? error.message : 'Unable to load the skills taxonomy',\n 'error'\n );\n });\n }, [initialized, loaded, fetchSkillsTaxonomy, showToast]);\n\n const fields = useMemo<FieldConfig[]>(\n () => [\n { name: 'name', label: 'Skill name', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: categories.map((value) => ({ label: formatEnumLabel(value), value })),\n },\n {\n name: 'aliases',\n label: 'Aliases (other spellings)',\n type: 'tags',\n colSpan: 2,\n placeholder: 'e.g. K8s',\n },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n ],\n [categories]\n );\n\n const triage = useMemo(\n () =>\n skills\n .filter((skill) => skill.source === 'BACKFILL')\n .sort((a, b) => holders(b) - holders(a) || a.name.localeCompare(b.name)),\n [skills]\n );\n const stats = useMemo(\n () => ({\n total: skills.length,\n custom: skills.filter((skill) => skill.source === 'CUSTOM').length,\n triage: triage.length,\n unused: skills.filter((skill) => holders(skill) === 0).length,\n }),\n [skills, triage]\n );\n\n const filtered = useMemo(() => {\n const needle = search.trim().toLowerCase();\n return skills.filter(\n (skill) =>\n (category === ALL || skill.category === category) &&\n (source === ALL || skill.source === source) &&\n (needle === '' ||\n skill.name.toLowerCase().includes(needle) ||\n skill.aliases.some((alias) => alias.toLowerCase().includes(needle)))\n );\n }, [skills, search, category, source]);\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleCreate = async (values: FormValues) => {\n await createTaxonomySkill({\n name: getText(values, 'name').trim(),\n category: getText(values, 'category'),\n aliases: getTags(values, 'aliases'),\n description: getText(values, 'description').trim() || undefined,\n });\n showToast('Skill added to the taxonomy', 'success');\n };\n\n const handleEdit = async (values: FormValues) => {\n if (!editTarget) return;\n const wasTriage = editTarget.source === 'BACKFILL';\n await updateTaxonomySkill(editTarget.id, {\n name: getText(values, 'name').trim(),\n category: getText(values, 'category'),\n aliases: getTags(values, 'aliases'),\n description: getText(values, 'description').trim(),\n });\n setEditTarget(null);\n showToast(wasTriage ? 'Skill reviewed and saved' : 'Skill saved', 'success');\n };\n\n const handleRemove = async () => {\n if (!removeTarget) return;\n await removeTaxonomySkill(removeTarget.id);\n showToast(`${removeTarget.name} removed`, 'success');\n setRemoveTarget(null);\n };\n\n const rowActions = (skill: CrewTaxonomySkill) => (\n <div className=\"flex items-center justify-end gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Relations of ${skill.name}`}\n onClick={() => setRelationsTarget(skill)}\n >\n <Share2 className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Edit ${skill.name}`}\n onClick={() => setEditTarget(skill)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n {skill.source !== 'TAXONOMY' ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Merge ${skill.name} into another skill`}\n onClick={() => setMergeTarget(skill)}\n >\n <GitMerge className=\"h-3.5 w-3.5\" />\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Remove ${skill.name}`}\n title={holders(skill) > 0 ? 'Held by people; merge it or detach it first' : undefined}\n disabled={holders(skill) > 0}\n onClick={() => setRemoveTarget(skill)}\n >\n <Trash2 className=\"h-3.5 w-3.5 text-status-error-fg\" />\n </Button>\n </div>\n );\n\n const columns: ResponsiveTableColumn<CrewTaxonomySkill>[] = [\n {\n key: 'name',\n header: 'Skill',\n priority: 'primary',\n cell: (skill) => (\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{skill.name}</p>\n {skill.aliases.length > 0 ? (\n <p className=\"truncate text-xs text-text-muted\">Also: {skill.aliases.join(', ')}</p>\n ) : null}\n </div>\n ),\n },\n {\n key: 'category',\n header: 'Category',\n priority: 'secondary',\n cell: (skill) => (\n <span className=\"text-sm text-text-secondary\">{formatEnumLabel(skill.category)}</span>\n ),\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'secondary',\n cell: (skill) => (\n <Badge\n variant={skill.source === 'BACKFILL' ? 'destructive' : 'outline'}\n className=\"text-[10px]\"\n >\n {SOURCE_LABEL[skill.source] ?? formatEnumLabel(skill.source)}\n </Badge>\n ),\n },\n {\n key: 'holders',\n header: 'People',\n priority: 'primary',\n cell: (skill) => (\n <span className=\"text-sm tabular-nums md:block md:text-right\">\n {skill.employeeCount}\n <span className=\"text-text-muted\"> · {skill.candidateCount} cand.</span>\n </span>\n ),\n },\n {\n key: 'relations',\n header: 'Relations',\n priority: 'secondary',\n cell: (skill) => (\n <span className=\"text-sm tabular-nums md:block md:text-right\">\n {skill.relations.length}\n </span>\n ),\n },\n ];\n\n if (!initialized || (!loaded && !loadFailed)) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"skills-taxonomy-page\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n <div>\n <p className=\"text-sm text-text-secondary\">Skills are the unit of work</p>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Skills Taxonomy</GradientText>\n </h1>\n <PagePurpose className=\"mt-1\">\n The canonical skills your workspace matches people on. Add your own, connect them with\n relations, and triage skills imported from free-text profiles.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setCreateOpen(true)} disabled={categories.length === 0}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Add skill\n </Button>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <StatCard label=\"Skills\" value={stats.total} icon={<Layers className=\"h-4 w-4\" />} />\n <StatCard label=\"Custom\" value={stats.custom} icon={<Sparkles className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Needs triage\"\n value={stats.triage}\n icon={<AlertTriangle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Unused\" value={stats.unused} icon={<Trash2 className=\"h-4 w-4\" />} />\n </div>\n\n <Card data-testid=\"skills-triage\">\n <CardHeader>\n <CardTitle className=\"text-base\">Needs triage</CardTitle>\n <CardDescription>\n Skill strings from existing profiles that matched nothing in the taxonomy. Recategorise\n one to accept it as a custom skill, or merge it into the skill it really means.\n </CardDescription>\n </CardHeader>\n <CardContent>\n {triage.length > 0 ? (\n <ul className=\"space-y-2\" aria-label=\"Skills awaiting triage\">\n {triage.map((skill) => (\n <li\n key={skill.id}\n className=\"flex flex-col gap-2 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-3 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary\">{skill.name}</p>\n <p className=\"text-xs text-text-muted\">\n {skill.employeeCount} people · {skill.candidateCount} candidates ·{' '}\n {formatEnumLabel(skill.category)}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setEditTarget(skill)}>\n <Pencil className=\"mr-1 h-3.5 w-3.5\" />\n Review\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setMergeTarget(skill)}>\n <GitMerge className=\"mr-1 h-3.5 w-3.5\" />\n Merge…\n </Button>\n {holders(skill) === 0 ? (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setRemoveTarget(skill)}>\n <Trash2 className=\"mr-1 h-3.5 w-3.5 text-status-error-fg\" />\n Remove\n </Button>\n ) : null}\n </div>\n </li>\n ))}\n </ul>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Nothing to triage\"\n description=\"Every skill on your people's profiles maps to a reviewed skill.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Taxonomy</CardTitle>\n <CardDescription>\n {filtered.length} of {skills.length} skills\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col gap-3 md:flex-row md:items-center\">\n <div className=\"relative flex-1 md:max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n aria-label=\"Search skills\"\n placeholder=\"Search skills or aliases…\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={category}\n onValueChange={(value) => {\n setCategory(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full md:w-56\" aria-label=\"Filter by category\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>All categories</SelectItem>\n {categories.map((value) => (\n <SelectItem key={value} value={value}>\n {formatEnumLabel(value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select\n value={source}\n onValueChange={(value) => {\n setSource(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full md:w-48\" aria-label=\"Filter by source\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SOURCE_FILTERS.map((item) => (\n <SelectItem key={item.value} value={item.value}>\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <ResponsiveTable\n data={paged}\n rowKey={(skill) => skill.id}\n columns={columns}\n rowActions={rowActions}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={loadFailed ? 'The taxonomy could not be loaded' : 'No skills match'}\n description={\n loadFailed\n ? 'Reload the page to try again.'\n : 'Try a different search, category or source.'\n }\n />\n }\n />\n {filtered.length > pageSize ? (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n ) : null}\n </CardContent>\n </Card>\n\n <FormModal\n open={createOpen}\n onOpenChange={setCreateOpen}\n title=\"Add skill\"\n description=\"A custom skill for this workspace. Names and aliases must not clash with an existing skill.\"\n fields={fields}\n initialValues={{ category: 'UNCATEGORIZED', aliases: [] }}\n submitLabel=\"Add skill\"\n onSubmit={handleCreate}\n />\n <FormModal\n open={editTarget !== null}\n onOpenChange={(open) => {\n if (!open) setEditTarget(null);\n }}\n title={editTarget?.source === 'BACKFILL' ? 'Review skill' : 'Edit skill'}\n description={\n editTarget?.source === 'BACKFILL'\n ? 'Saving accepts this as a custom skill. A rename keeps the old spelling as an alias.'\n : 'A rename keeps the old spelling as an alias, so existing profiles still resolve.'\n }\n fields={fields}\n initialValues={editTarget ? skillToValues(editTarget) : undefined}\n submitLabel={editTarget?.source === 'BACKFILL' ? 'Accept skill' : 'Save changes'}\n onSubmit={handleEdit}\n />\n <MergeSkillDialog\n source={mergeTarget}\n skills={skills}\n onOpenChange={(open) => {\n if (!open) setMergeTarget(null);\n }}\n />\n <SkillRelationsDialog\n skill={relationsTarget}\n skills={skills}\n onOpenChange={(open) => {\n if (!open) setRelationsTarget(null);\n }}\n />\n <ConfirmDialog\n open={removeTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRemoveTarget(null);\n }}\n title=\"Remove skill\"\n description={\n removeTarget\n ? `Remove ${removeTarget.name} and its relations? Nobody holds it, so no profile changes.`\n : undefined\n }\n confirmLabel=\"Remove\"\n destructive\n onConfirm={handleRemove}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsResourcingPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/ProjectsResourcingPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsResourcingPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/ProjectsResourcingPagePage.tsx"],"names":[],"mappings":"AA0EA,wBAAgB,0BAA0B,gCAsNzC"}
|