@forwardimpact/pathway 0.25.12 → 0.25.20
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/bin/fit-pathway.js +62 -54
- package/package.json +3 -4
- package/src/commands/agent-io.js +120 -0
- package/src/commands/agent.js +266 -349
- package/src/commands/init.js +2 -2
- package/src/commands/job.js +237 -183
- package/src/components/comparison-radar.js +118 -103
- package/src/components/progression-table.js +244 -208
- package/src/formatters/interview/markdown.js +100 -88
- package/src/formatters/job/description.js +76 -75
- package/src/formatters/job/dom.js +113 -97
- package/src/formatters/level/dom.js +87 -102
- package/src/formatters/questions/markdown.js +37 -33
- package/src/formatters/questions/shared.js +142 -75
- package/src/formatters/skill/dom.js +102 -93
- package/src/lib/comparison-radar-chart.js +256 -0
- package/src/lib/radar-utils.js +199 -0
- package/src/lib/radar.js +25 -662
- package/src/pages/agent-builder-download.js +170 -0
- package/src/pages/agent-builder-preview.js +344 -0
- package/src/pages/agent-builder.js +6 -550
- package/src/pages/progress-comparison.js +110 -0
- package/src/pages/progress.js +11 -111
- package/src/pages/self-assessment-steps.js +494 -0
- package/src/pages/self-assessment.js +54 -504
|
@@ -3,30 +3,20 @@
|
|
|
3
3
|
* A step-by-step interface for users to assess their skills and behaviours
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
render,
|
|
8
|
-
div,
|
|
9
|
-
h1,
|
|
10
|
-
h2,
|
|
11
|
-
h3,
|
|
12
|
-
h4,
|
|
13
|
-
p,
|
|
14
|
-
span,
|
|
15
|
-
button,
|
|
16
|
-
a,
|
|
17
|
-
} from "../lib/render.js";
|
|
6
|
+
import { render, div, h1, p, span, button } from "../lib/render.js";
|
|
18
7
|
import { getState } from "../lib/state.js";
|
|
19
|
-
import { createBadge } from "../components/card.js";
|
|
20
|
-
import { createDisciplineSelect } from "../lib/form-controls.js";
|
|
21
8
|
import {
|
|
22
|
-
SKILL_PROFICIENCY_ORDER,
|
|
23
|
-
BEHAVIOUR_MATURITY_ORDER,
|
|
24
9
|
groupSkillsByCapability,
|
|
25
10
|
getCapabilityOrder,
|
|
26
11
|
getCapabilityEmoji,
|
|
27
12
|
getConceptEmoji,
|
|
28
13
|
} from "@forwardimpact/map/levels";
|
|
29
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
renderIntroStep,
|
|
16
|
+
renderSkillsStep,
|
|
17
|
+
renderBehavioursStep,
|
|
18
|
+
renderResultsPreview,
|
|
19
|
+
} from "./self-assessment-steps.js";
|
|
30
20
|
|
|
31
21
|
/**
|
|
32
22
|
* Assessment state stored in memory
|
|
@@ -59,6 +49,17 @@ export function getAssessmentState() {
|
|
|
59
49
|
return assessmentState;
|
|
60
50
|
}
|
|
61
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Format capability name for display
|
|
54
|
+
* @param {string} capabilityId
|
|
55
|
+
* @param {Array} capabilities
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function formatCapability(capabilityId, capabilities) {
|
|
59
|
+
const capability = capabilities.find((c) => c.id === capabilityId);
|
|
60
|
+
return capability?.name || capabilityId;
|
|
61
|
+
}
|
|
62
|
+
|
|
62
63
|
/**
|
|
63
64
|
* Get steps for the wizard
|
|
64
65
|
* @param {Object} data - App data
|
|
@@ -79,7 +80,6 @@ function getWizardSteps(data) {
|
|
|
79
80
|
},
|
|
80
81
|
];
|
|
81
82
|
|
|
82
|
-
// Add a step for each non-empty skill capability
|
|
83
83
|
for (const capability of getCapabilityOrder(data.capabilities)) {
|
|
84
84
|
const skills = skillsByCapability[capability];
|
|
85
85
|
if (skills && skills.length > 0) {
|
|
@@ -94,7 +94,6 @@ function getWizardSteps(data) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
// Add behaviours step
|
|
98
97
|
steps.push({
|
|
99
98
|
id: "behaviours",
|
|
100
99
|
name: "Behaviours",
|
|
@@ -103,7 +102,6 @@ function getWizardSteps(data) {
|
|
|
103
102
|
items: data.behaviours,
|
|
104
103
|
});
|
|
105
104
|
|
|
106
|
-
// Add results step
|
|
107
105
|
steps.push({
|
|
108
106
|
id: "results",
|
|
109
107
|
name: "Results",
|
|
@@ -114,36 +112,54 @@ function getWizardSteps(data) {
|
|
|
114
112
|
return steps;
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
/**
|
|
118
|
-
* Format capability name for display
|
|
119
|
-
* @param {string} capabilityId
|
|
120
|
-
* @param {Array} capabilities
|
|
121
|
-
* @returns {string}
|
|
122
|
-
*/
|
|
123
|
-
function formatCapability(capabilityId, capabilities) {
|
|
124
|
-
const capability = capabilities.find((c) => c.id === capabilityId);
|
|
125
|
-
return capability?.name || capabilityId;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
115
|
/**
|
|
129
116
|
* Calculate progress percentage
|
|
130
117
|
* @param {Object} data - App data
|
|
131
118
|
* @returns {number}
|
|
132
119
|
*/
|
|
133
120
|
function calculateProgress(data) {
|
|
134
|
-
const
|
|
135
|
-
const totalBehaviours = data.behaviours.length;
|
|
136
|
-
const totalItems = totalSkills + totalBehaviours;
|
|
137
|
-
|
|
121
|
+
const totalItems = data.skills.length + data.behaviours.length;
|
|
138
122
|
if (totalItems === 0) return 0;
|
|
139
123
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
124
|
+
const assessedItems =
|
|
125
|
+
Object.keys(assessmentState.skills).length +
|
|
126
|
+
Object.keys(assessmentState.behaviours).length;
|
|
143
127
|
|
|
144
128
|
return Math.round((assessedItems / totalItems) * 100);
|
|
145
129
|
}
|
|
146
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Render content for the current step
|
|
133
|
+
* @param {Object} step - Current step configuration
|
|
134
|
+
* @param {Object} data - App data
|
|
135
|
+
* @returns {HTMLElement}
|
|
136
|
+
*/
|
|
137
|
+
function renderStepContent(step, data) {
|
|
138
|
+
switch (step.type) {
|
|
139
|
+
case "intro":
|
|
140
|
+
return renderIntroStep(data, assessmentState);
|
|
141
|
+
case "skills":
|
|
142
|
+
return renderSkillsStep(
|
|
143
|
+
step,
|
|
144
|
+
data,
|
|
145
|
+
assessmentState,
|
|
146
|
+
renderSelfAssessment,
|
|
147
|
+
formatCapability,
|
|
148
|
+
);
|
|
149
|
+
case "behaviours":
|
|
150
|
+
return renderBehavioursStep(
|
|
151
|
+
step,
|
|
152
|
+
data,
|
|
153
|
+
assessmentState,
|
|
154
|
+
renderSelfAssessment,
|
|
155
|
+
);
|
|
156
|
+
case "results":
|
|
157
|
+
return renderResultsPreview(data, assessmentState, calculateProgress);
|
|
158
|
+
default:
|
|
159
|
+
return div({}, "Unknown step");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
147
163
|
/**
|
|
148
164
|
* Render the self-assessment wizard
|
|
149
165
|
*/
|
|
@@ -155,7 +171,6 @@ export function renderSelfAssessment() {
|
|
|
155
171
|
|
|
156
172
|
const page = div(
|
|
157
173
|
{ className: "self-assessment-page" },
|
|
158
|
-
// Header
|
|
159
174
|
div(
|
|
160
175
|
{ className: "page-header" },
|
|
161
176
|
h1({ className: "page-title" }, "Self-Assessment"),
|
|
@@ -164,17 +179,11 @@ export function renderSelfAssessment() {
|
|
|
164
179
|
"Assess your skills and behaviours to find matching roles and identify development opportunities.",
|
|
165
180
|
),
|
|
166
181
|
),
|
|
167
|
-
|
|
168
|
-
// Progress bar
|
|
169
182
|
createProgressBar(data, steps, currentStep),
|
|
170
|
-
|
|
171
|
-
// Step content
|
|
172
183
|
div(
|
|
173
184
|
{ className: "assessment-content", id: "assessment-content" },
|
|
174
185
|
renderStepContent(step, data),
|
|
175
186
|
),
|
|
176
|
-
|
|
177
|
-
// Navigation buttons
|
|
178
187
|
createNavigationButtons(steps, currentStep),
|
|
179
188
|
);
|
|
180
189
|
|
|
@@ -193,7 +202,6 @@ function createProgressBar(data, steps, currentStep) {
|
|
|
193
202
|
|
|
194
203
|
return div(
|
|
195
204
|
{ className: "assessment-progress" },
|
|
196
|
-
// Progress percentage
|
|
197
205
|
div(
|
|
198
206
|
{ className: "progress-header" },
|
|
199
207
|
span({ className: "progress-label" }, `${progress}% Complete`),
|
|
@@ -203,12 +211,10 @@ function createProgressBar(data, steps, currentStep) {
|
|
|
203
211
|
`${Object.keys(assessmentState.behaviours).length}/${data.behaviours.length} behaviours`,
|
|
204
212
|
),
|
|
205
213
|
),
|
|
206
|
-
// Progress bar
|
|
207
214
|
div(
|
|
208
215
|
{ className: "progress-bar" },
|
|
209
216
|
div({ className: "progress-bar-fill", style: `width: ${progress}%` }),
|
|
210
217
|
),
|
|
211
|
-
// Step indicators
|
|
212
218
|
div(
|
|
213
219
|
{ className: "step-indicators" },
|
|
214
220
|
...steps.map((step, index) =>
|
|
@@ -216,7 +222,6 @@ function createProgressBar(data, steps, currentStep) {
|
|
|
216
222
|
{
|
|
217
223
|
className: `step-indicator ${index === currentStep ? "active" : ""} ${index < currentStep ? "completed" : ""}`,
|
|
218
224
|
onClick: () => {
|
|
219
|
-
// Allow jumping to any step except results (unless assessment is complete)
|
|
220
225
|
if (index < steps.length - 1 || calculateProgress(data) >= 50) {
|
|
221
226
|
assessmentState.currentStep = index;
|
|
222
227
|
renderSelfAssessment();
|
|
@@ -231,461 +236,6 @@ function createProgressBar(data, steps, currentStep) {
|
|
|
231
236
|
);
|
|
232
237
|
}
|
|
233
238
|
|
|
234
|
-
/**
|
|
235
|
-
* Render content for the current step
|
|
236
|
-
* @param {Object} step - Current step configuration
|
|
237
|
-
* @param {Object} data - App data
|
|
238
|
-
* @returns {HTMLElement}
|
|
239
|
-
*/
|
|
240
|
-
function renderStepContent(step, data) {
|
|
241
|
-
switch (step.type) {
|
|
242
|
-
case "intro":
|
|
243
|
-
return renderIntroStep(data);
|
|
244
|
-
case "skills":
|
|
245
|
-
return renderSkillsStep(step, data);
|
|
246
|
-
case "behaviours":
|
|
247
|
-
return renderBehavioursStep(step, data);
|
|
248
|
-
case "results":
|
|
249
|
-
return renderResultsPreview(data);
|
|
250
|
-
default:
|
|
251
|
-
return div({}, "Unknown step");
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Render introduction step
|
|
257
|
-
* @param {Object} data - App data
|
|
258
|
-
* @returns {HTMLElement}
|
|
259
|
-
*/
|
|
260
|
-
function renderIntroStep(data) {
|
|
261
|
-
return div(
|
|
262
|
-
{ className: "assessment-step assessment-intro" },
|
|
263
|
-
div(
|
|
264
|
-
{ className: "intro-card" },
|
|
265
|
-
h2({}, "Welcome to the Self-Assessment"),
|
|
266
|
-
p(
|
|
267
|
-
{},
|
|
268
|
-
"This assessment helps you understand your current skill proficiencies and behaviours, " +
|
|
269
|
-
"then matches you with suitable roles in the organization.",
|
|
270
|
-
),
|
|
271
|
-
|
|
272
|
-
div(
|
|
273
|
-
{ className: "intro-info" },
|
|
274
|
-
div(
|
|
275
|
-
{ className: "info-item" },
|
|
276
|
-
span(
|
|
277
|
-
{ className: "info-icon" },
|
|
278
|
-
getConceptEmoji(data.framework, "skill"),
|
|
279
|
-
),
|
|
280
|
-
div(
|
|
281
|
-
{},
|
|
282
|
-
h4({}, `${data.skills.length} Skills`),
|
|
283
|
-
p({}, "Across " + data.capabilities.length + " capabilities"),
|
|
284
|
-
),
|
|
285
|
-
),
|
|
286
|
-
div(
|
|
287
|
-
{ className: "info-item" },
|
|
288
|
-
span(
|
|
289
|
-
{ className: "info-icon" },
|
|
290
|
-
getConceptEmoji(data.framework, "behaviour"),
|
|
291
|
-
),
|
|
292
|
-
div(
|
|
293
|
-
{},
|
|
294
|
-
h4({}, `${data.behaviours.length} Behaviours`),
|
|
295
|
-
p({}, "Key mindsets and ways of working"),
|
|
296
|
-
),
|
|
297
|
-
),
|
|
298
|
-
div(
|
|
299
|
-
{ className: "info-item" },
|
|
300
|
-
span({ className: "info-icon" }, "⏱️"),
|
|
301
|
-
div({}, h4({}, "10-15 Minutes"), p({}, "Complete at your own pace")),
|
|
302
|
-
),
|
|
303
|
-
),
|
|
304
|
-
|
|
305
|
-
// Optional discipline filter
|
|
306
|
-
div(
|
|
307
|
-
{ className: "discipline-filter" },
|
|
308
|
-
h3({}, "Optional: Focus on a Discipline"),
|
|
309
|
-
p(
|
|
310
|
-
{ className: "text-muted" },
|
|
311
|
-
"Select a discipline to highlight which skills are most relevant for that role. " +
|
|
312
|
-
"You can still assess all skills.",
|
|
313
|
-
),
|
|
314
|
-
createDisciplineSelect({
|
|
315
|
-
id: "discipline-filter-select",
|
|
316
|
-
disciplines: data.disciplines,
|
|
317
|
-
initialValue: assessmentState.discipline || "",
|
|
318
|
-
placeholder: "Select discipline",
|
|
319
|
-
onChange: (value) => {
|
|
320
|
-
assessmentState.discipline = value || null;
|
|
321
|
-
},
|
|
322
|
-
getDisplayName: (d) => d.specialization,
|
|
323
|
-
}),
|
|
324
|
-
),
|
|
325
|
-
|
|
326
|
-
div(
|
|
327
|
-
{ className: "intro-tips" },
|
|
328
|
-
h3({}, "Tips for Accurate Self-Assessment"),
|
|
329
|
-
div(
|
|
330
|
-
{ className: "auto-grid-sm" },
|
|
331
|
-
createTipCard(
|
|
332
|
-
"🎯",
|
|
333
|
-
"Be Honest",
|
|
334
|
-
"Rate yourself where you genuinely are, not where you aspire to be.",
|
|
335
|
-
),
|
|
336
|
-
createTipCard(
|
|
337
|
-
"📚",
|
|
338
|
-
"Read Descriptions",
|
|
339
|
-
"Hover over levels to see detailed descriptions for each.",
|
|
340
|
-
),
|
|
341
|
-
createTipCard(
|
|
342
|
-
"⏭️",
|
|
343
|
-
"Skip if Unsure",
|
|
344
|
-
"You can leave items unrated and come back later.",
|
|
345
|
-
),
|
|
346
|
-
createTipCard(
|
|
347
|
-
"💾",
|
|
348
|
-
"Auto-Saved",
|
|
349
|
-
"Your progress is kept while you navigate between steps.",
|
|
350
|
-
),
|
|
351
|
-
),
|
|
352
|
-
),
|
|
353
|
-
),
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Create a tip card
|
|
359
|
-
* @param {string} icon
|
|
360
|
-
* @param {string} title
|
|
361
|
-
* @param {string} text
|
|
362
|
-
* @returns {HTMLElement}
|
|
363
|
-
*/
|
|
364
|
-
function createTipCard(icon, title, text) {
|
|
365
|
-
return div(
|
|
366
|
-
{ className: "tip-card" },
|
|
367
|
-
span({ className: "tip-icon" }, icon),
|
|
368
|
-
h4({}, title),
|
|
369
|
-
p({}, text),
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Render skills assessment step
|
|
375
|
-
* @param {Object} step - Step configuration with capability and items
|
|
376
|
-
* @param {Object} data - App data
|
|
377
|
-
* @returns {HTMLElement}
|
|
378
|
-
*/
|
|
379
|
-
function renderSkillsStep(step, data) {
|
|
380
|
-
const { capability, items } = step;
|
|
381
|
-
const selectedDiscipline = assessmentState.discipline
|
|
382
|
-
? data.disciplines.find((d) => d.id === assessmentState.discipline)
|
|
383
|
-
: null;
|
|
384
|
-
|
|
385
|
-
// Determine skill relevance if a discipline is selected
|
|
386
|
-
const getSkillRelevance = (skill) => {
|
|
387
|
-
if (!selectedDiscipline) return null;
|
|
388
|
-
if (selectedDiscipline.coreSkills?.includes(skill.id)) return "primary";
|
|
389
|
-
if (selectedDiscipline.supportingSkills?.includes(skill.id))
|
|
390
|
-
return "secondary";
|
|
391
|
-
if (selectedDiscipline.broadSkills?.includes(skill.id)) return "broad";
|
|
392
|
-
return null;
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
// Sort items: relevant skills first
|
|
396
|
-
const sortedItems = [...items].sort((a, b) => {
|
|
397
|
-
const relevanceA = getSkillRelevance(a);
|
|
398
|
-
const relevanceB = getSkillRelevance(b);
|
|
399
|
-
const order = { primary: 0, secondary: 1, broad: 2 };
|
|
400
|
-
|
|
401
|
-
if (relevanceA && !relevanceB) return -1;
|
|
402
|
-
if (!relevanceA && relevanceB) return 1;
|
|
403
|
-
if (relevanceA && relevanceB) {
|
|
404
|
-
return (order[relevanceA] ?? 3) - (order[relevanceB] ?? 3);
|
|
405
|
-
}
|
|
406
|
-
return a.name.localeCompare(b.name);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
const assessedCount = items.filter(
|
|
410
|
-
(item) => assessmentState.skills[item.id],
|
|
411
|
-
).length;
|
|
412
|
-
|
|
413
|
-
return div(
|
|
414
|
-
{ className: "assessment-step" },
|
|
415
|
-
div(
|
|
416
|
-
{ className: "step-header" },
|
|
417
|
-
h2(
|
|
418
|
-
{},
|
|
419
|
-
span({ className: "step-header-icon" }, step.icon),
|
|
420
|
-
` ${formatCapability(capability, data.capabilities)} Skills`,
|
|
421
|
-
),
|
|
422
|
-
span(
|
|
423
|
-
{ className: "step-progress" },
|
|
424
|
-
`${assessedCount}/${items.length} rated`,
|
|
425
|
-
),
|
|
426
|
-
),
|
|
427
|
-
|
|
428
|
-
selectedDiscipline &&
|
|
429
|
-
div(
|
|
430
|
-
{ className: "discipline-context" },
|
|
431
|
-
span({}, `Showing relevance for: `),
|
|
432
|
-
span(
|
|
433
|
-
{ className: "discipline-name" },
|
|
434
|
-
selectedDiscipline.specialization,
|
|
435
|
-
),
|
|
436
|
-
),
|
|
437
|
-
|
|
438
|
-
div(
|
|
439
|
-
{ className: "assessment-items" },
|
|
440
|
-
...sortedItems.map((skill) =>
|
|
441
|
-
createSkillAssessmentItem(skill, getSkillRelevance(skill)),
|
|
442
|
-
),
|
|
443
|
-
),
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Create a skill assessment item
|
|
449
|
-
* @param {Object} skill - Skill data
|
|
450
|
-
* @param {string|null} relevance - Skill relevance for selected discipline
|
|
451
|
-
* @returns {HTMLElement}
|
|
452
|
-
*/
|
|
453
|
-
function createSkillAssessmentItem(skill, relevance) {
|
|
454
|
-
const currentLevel = assessmentState.skills[skill.id];
|
|
455
|
-
|
|
456
|
-
return div(
|
|
457
|
-
{
|
|
458
|
-
className: `assessment-item ${currentLevel ? "assessed" : ""} ${relevance ? `relevance-${relevance}` : ""}`,
|
|
459
|
-
},
|
|
460
|
-
div(
|
|
461
|
-
{ className: "assessment-item-header" },
|
|
462
|
-
div(
|
|
463
|
-
{ className: "assessment-item-title" },
|
|
464
|
-
a({ href: `#/skill/${skill.id}` }, skill.name),
|
|
465
|
-
relevance && createBadge(relevance, relevance),
|
|
466
|
-
),
|
|
467
|
-
currentLevel &&
|
|
468
|
-
span({ className: "current-level-badge" }, formatLevel(currentLevel)),
|
|
469
|
-
),
|
|
470
|
-
|
|
471
|
-
p({ className: "assessment-item-description" }, skill.description),
|
|
472
|
-
|
|
473
|
-
div(
|
|
474
|
-
{ className: "level-selector" },
|
|
475
|
-
...SKILL_PROFICIENCY_ORDER.map((level, index) =>
|
|
476
|
-
createLevelButton(skill, level, index, "skill"),
|
|
477
|
-
),
|
|
478
|
-
// Clear button
|
|
479
|
-
button(
|
|
480
|
-
{
|
|
481
|
-
className: "level-clear-btn",
|
|
482
|
-
title: "Clear selection",
|
|
483
|
-
onClick: () => {
|
|
484
|
-
delete assessmentState.skills[skill.id];
|
|
485
|
-
renderSelfAssessment();
|
|
486
|
-
},
|
|
487
|
-
},
|
|
488
|
-
"✕",
|
|
489
|
-
),
|
|
490
|
-
),
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Create a level selection button
|
|
496
|
-
* @param {Object} item - Skill or behaviour
|
|
497
|
-
* @param {string} level - Level value
|
|
498
|
-
* @param {number} index - Level index
|
|
499
|
-
* @param {string} type - 'skill' or 'behaviour'
|
|
500
|
-
* @returns {HTMLElement}
|
|
501
|
-
*/
|
|
502
|
-
function createLevelButton(item, level, index, type) {
|
|
503
|
-
const stateKey = type === "skill" ? "skills" : "behaviours";
|
|
504
|
-
const currentLevel = assessmentState[stateKey][item.id];
|
|
505
|
-
const isSelected = currentLevel === level;
|
|
506
|
-
const proficiencyDescriptions =
|
|
507
|
-
type === "skill" ? item.proficiencyDescriptions : item.maturityDescriptions;
|
|
508
|
-
const description = proficiencyDescriptions?.[level] || "";
|
|
509
|
-
|
|
510
|
-
return button(
|
|
511
|
-
{
|
|
512
|
-
className: `level-btn level-${index + 1} ${isSelected ? "selected" : ""}`,
|
|
513
|
-
title: `${formatLevel(level)}: ${description}`,
|
|
514
|
-
onClick: () => {
|
|
515
|
-
assessmentState[stateKey][item.id] = level;
|
|
516
|
-
renderSelfAssessment();
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
span({ className: "level-btn-number" }, String(index + 1)),
|
|
520
|
-
span({ className: "level-btn-name" }, formatLevel(level)),
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Render behaviours assessment step
|
|
526
|
-
* @param {Object} step - Step configuration
|
|
527
|
-
* @param {Object} data - App data
|
|
528
|
-
* @returns {HTMLElement}
|
|
529
|
-
*/
|
|
530
|
-
function renderBehavioursStep(step, data) {
|
|
531
|
-
const { items } = step;
|
|
532
|
-
const assessedCount = items.filter(
|
|
533
|
-
(item) => assessmentState.behaviours[item.id],
|
|
534
|
-
).length;
|
|
535
|
-
|
|
536
|
-
return div(
|
|
537
|
-
{ className: "assessment-step" },
|
|
538
|
-
div(
|
|
539
|
-
{ className: "step-header" },
|
|
540
|
-
h2(
|
|
541
|
-
{},
|
|
542
|
-
span(
|
|
543
|
-
{ className: "step-header-icon" },
|
|
544
|
-
getConceptEmoji(data.framework, "behaviour"),
|
|
545
|
-
),
|
|
546
|
-
" Behaviours",
|
|
547
|
-
),
|
|
548
|
-
span(
|
|
549
|
-
{ className: "step-progress" },
|
|
550
|
-
`${assessedCount}/${items.length} rated`,
|
|
551
|
-
),
|
|
552
|
-
),
|
|
553
|
-
|
|
554
|
-
p(
|
|
555
|
-
{ className: "step-intro" },
|
|
556
|
-
"Behaviours describe how you approach work—your mindsets and ways of working. " +
|
|
557
|
-
"These are equally important as technical skills.",
|
|
558
|
-
),
|
|
559
|
-
|
|
560
|
-
div(
|
|
561
|
-
{ className: "assessment-items" },
|
|
562
|
-
...items.map((behaviour) => createBehaviourAssessmentItem(behaviour)),
|
|
563
|
-
),
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Create a behaviour assessment item
|
|
569
|
-
* @param {Object} behaviour - Behaviour data
|
|
570
|
-
* @returns {HTMLElement}
|
|
571
|
-
*/
|
|
572
|
-
function createBehaviourAssessmentItem(behaviour) {
|
|
573
|
-
const currentLevel = assessmentState.behaviours[behaviour.id];
|
|
574
|
-
|
|
575
|
-
return div(
|
|
576
|
-
{ className: `assessment-item ${currentLevel ? "assessed" : ""}` },
|
|
577
|
-
div(
|
|
578
|
-
{ className: "assessment-item-header" },
|
|
579
|
-
div(
|
|
580
|
-
{ className: "assessment-item-title" },
|
|
581
|
-
a({ href: `#/behaviour/${behaviour.id}` }, behaviour.name),
|
|
582
|
-
),
|
|
583
|
-
currentLevel &&
|
|
584
|
-
span({ className: "current-level-badge" }, formatLevel(currentLevel)),
|
|
585
|
-
),
|
|
586
|
-
|
|
587
|
-
p({ className: "assessment-item-description" }, behaviour.description),
|
|
588
|
-
|
|
589
|
-
div(
|
|
590
|
-
{ className: "level-selector" },
|
|
591
|
-
...BEHAVIOUR_MATURITY_ORDER.map((level, index) =>
|
|
592
|
-
createLevelButton(behaviour, level, index, "behaviour"),
|
|
593
|
-
),
|
|
594
|
-
// Clear button
|
|
595
|
-
button(
|
|
596
|
-
{
|
|
597
|
-
className: "level-clear-btn",
|
|
598
|
-
title: "Clear selection",
|
|
599
|
-
onClick: () => {
|
|
600
|
-
delete assessmentState.behaviours[behaviour.id];
|
|
601
|
-
renderSelfAssessment();
|
|
602
|
-
},
|
|
603
|
-
},
|
|
604
|
-
"✕",
|
|
605
|
-
),
|
|
606
|
-
),
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Render results preview before navigating to full results
|
|
612
|
-
* @param {Object} data - App data
|
|
613
|
-
* @returns {HTMLElement}
|
|
614
|
-
*/
|
|
615
|
-
function renderResultsPreview(data) {
|
|
616
|
-
const progress = calculateProgress(data);
|
|
617
|
-
const skillCount = Object.keys(assessmentState.skills).length;
|
|
618
|
-
const behaviourCount = Object.keys(assessmentState.behaviours).length;
|
|
619
|
-
|
|
620
|
-
if (progress < 20) {
|
|
621
|
-
return div(
|
|
622
|
-
{ className: "assessment-step results-preview" },
|
|
623
|
-
div(
|
|
624
|
-
{ className: "results-incomplete" },
|
|
625
|
-
h2({}, "Complete More of the Assessment"),
|
|
626
|
-
p(
|
|
627
|
-
{},
|
|
628
|
-
"Please complete at least 20% of the assessment to see job matches. " +
|
|
629
|
-
`You've currently assessed ${skillCount} skills and ${behaviourCount} behaviours.`,
|
|
630
|
-
),
|
|
631
|
-
div(
|
|
632
|
-
{ className: "progress-summary" },
|
|
633
|
-
div(
|
|
634
|
-
{ className: "progress-bar large" },
|
|
635
|
-
div({
|
|
636
|
-
className: "progress-bar-fill",
|
|
637
|
-
style: `width: ${progress}%`,
|
|
638
|
-
}),
|
|
639
|
-
),
|
|
640
|
-
span({}, `${progress}% complete`),
|
|
641
|
-
),
|
|
642
|
-
),
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
return div(
|
|
647
|
-
{ className: "assessment-step results-preview" },
|
|
648
|
-
div(
|
|
649
|
-
{ className: "results-ready" },
|
|
650
|
-
h2({}, "🎉 Assessment Complete!"),
|
|
651
|
-
p({}, "Great work! You're ready to see your job matches."),
|
|
652
|
-
|
|
653
|
-
div(
|
|
654
|
-
{ className: "results-summary" },
|
|
655
|
-
div(
|
|
656
|
-
{ className: "summary-stat" },
|
|
657
|
-
span({ className: "summary-value" }, String(skillCount)),
|
|
658
|
-
span({ className: "summary-label" }, "Skills Assessed"),
|
|
659
|
-
),
|
|
660
|
-
div(
|
|
661
|
-
{ className: "summary-stat" },
|
|
662
|
-
span({ className: "summary-value" }, String(behaviourCount)),
|
|
663
|
-
span({ className: "summary-label" }, "Behaviours Assessed"),
|
|
664
|
-
),
|
|
665
|
-
div(
|
|
666
|
-
{ className: "summary-stat" },
|
|
667
|
-
span({ className: "summary-value" }, `${progress}%`),
|
|
668
|
-
span({ className: "summary-label" }, "Complete"),
|
|
669
|
-
),
|
|
670
|
-
),
|
|
671
|
-
|
|
672
|
-
div(
|
|
673
|
-
{ className: "results-actions" },
|
|
674
|
-
button(
|
|
675
|
-
{
|
|
676
|
-
className: "btn btn-primary btn-lg",
|
|
677
|
-
onClick: () => {
|
|
678
|
-
// Navigate to results page
|
|
679
|
-
window.location.hash = "/self-assessment/results";
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
"View My Job Matches →",
|
|
683
|
-
),
|
|
684
|
-
),
|
|
685
|
-
),
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
239
|
/**
|
|
690
240
|
* Create navigation buttons for the wizard
|
|
691
241
|
* @param {Array} steps - Wizard steps
|