@forwardimpact/pathway 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- package/package.json +68 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Career progress detail page
|
|
3
|
+
* Shows skill and behaviour progression comparison across discipline × grade × track
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { render, div, h1, h2, p, a, label, section } from "../lib/render.js";
|
|
7
|
+
import { getState } from "../lib/state.js";
|
|
8
|
+
import { createBackLink } from "../components/nav.js";
|
|
9
|
+
import { createStatCard } from "../components/card.js";
|
|
10
|
+
import {
|
|
11
|
+
createComparisonSkillRadar,
|
|
12
|
+
createComparisonBehaviourRadar,
|
|
13
|
+
} from "../components/comparison-radar.js";
|
|
14
|
+
import { createProgressionTable } from "../components/progression-table.js";
|
|
15
|
+
import { renderError } from "../components/error-page.js";
|
|
16
|
+
import { createSelectWithValue } from "../lib/form-controls.js";
|
|
17
|
+
import {
|
|
18
|
+
prepareCurrentJob,
|
|
19
|
+
prepareCustomProgression,
|
|
20
|
+
getDefaultTargetGrade,
|
|
21
|
+
isValidCombination,
|
|
22
|
+
} from "../formatters/progress/shared.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Render career progress detail page
|
|
26
|
+
* @param {Object} params - Route params
|
|
27
|
+
*/
|
|
28
|
+
export function renderProgressDetail(params) {
|
|
29
|
+
const { discipline: disciplineId, track: trackId, grade: gradeId } = params;
|
|
30
|
+
const { data } = getState();
|
|
31
|
+
|
|
32
|
+
// Find the components
|
|
33
|
+
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
34
|
+
const track = data.tracks.find((t) => t.id === trackId);
|
|
35
|
+
const grade = data.grades.find((g) => g.id === gradeId);
|
|
36
|
+
|
|
37
|
+
if (!discipline || !track || !grade) {
|
|
38
|
+
renderError({
|
|
39
|
+
title: "Role Not Found",
|
|
40
|
+
message: "Invalid role combination. One or more components are missing.",
|
|
41
|
+
backPath: "/career-progress",
|
|
42
|
+
backText: "← Back to Career Progress",
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Prepare current job view
|
|
48
|
+
const currentJobView = prepareCurrentJob({
|
|
49
|
+
discipline,
|
|
50
|
+
grade,
|
|
51
|
+
track,
|
|
52
|
+
skills: data.skills,
|
|
53
|
+
behaviours: data.behaviours,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!currentJobView) {
|
|
57
|
+
renderError({
|
|
58
|
+
title: "Invalid Combination",
|
|
59
|
+
message: "This discipline, track, and grade combination is not valid.",
|
|
60
|
+
backPath: "/career-progress",
|
|
61
|
+
backText: "← Back to Career Progress",
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find next grade for default comparison
|
|
67
|
+
const nextGrade = getDefaultTargetGrade(grade, data.grades);
|
|
68
|
+
|
|
69
|
+
const page = div(
|
|
70
|
+
{ className: "progress-detail-page" },
|
|
71
|
+
// Header
|
|
72
|
+
div(
|
|
73
|
+
{ className: "page-header" },
|
|
74
|
+
createBackLink("/career-progress", "← Back to Career Progress"),
|
|
75
|
+
h1({ className: "page-title" }, "Career Progress"),
|
|
76
|
+
div(
|
|
77
|
+
{ className: "page-description" },
|
|
78
|
+
"Current role: ",
|
|
79
|
+
a({ href: `#/discipline/${discipline.id}` }, discipline.specialization),
|
|
80
|
+
" × ",
|
|
81
|
+
a({ href: `#/grade/${grade.id}` }, grade.id),
|
|
82
|
+
" × ",
|
|
83
|
+
a({ href: `#/track/${track.id}` }, track.name),
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
// Current role summary
|
|
88
|
+
section(
|
|
89
|
+
{ className: "section section-detail" },
|
|
90
|
+
h2({ className: "section-title" }, `📍 Current: ${currentJobView.title}`),
|
|
91
|
+
div(
|
|
92
|
+
{ className: "grid grid-3" },
|
|
93
|
+
createStatCard({
|
|
94
|
+
value: currentJobView.skillCount,
|
|
95
|
+
label: "Skills",
|
|
96
|
+
}),
|
|
97
|
+
createStatCard({
|
|
98
|
+
value: currentJobView.behaviourCount,
|
|
99
|
+
label: "Behaviours",
|
|
100
|
+
}),
|
|
101
|
+
createStatCard({
|
|
102
|
+
value: currentJobView.primarySkillCount,
|
|
103
|
+
label: "Primary Skills",
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
|
|
108
|
+
// Comparison selectors section
|
|
109
|
+
createComparisonSelectorsSection({
|
|
110
|
+
discipline,
|
|
111
|
+
currentGrade: grade,
|
|
112
|
+
currentTrack: track,
|
|
113
|
+
currentJobView,
|
|
114
|
+
nextGrade,
|
|
115
|
+
data,
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
// Actions
|
|
119
|
+
div(
|
|
120
|
+
{ className: "page-actions", style: "margin-top: 2rem" },
|
|
121
|
+
a(
|
|
122
|
+
{
|
|
123
|
+
href: `#/job/${disciplineId}/${trackId}/${gradeId}`,
|
|
124
|
+
className: "btn btn-secondary",
|
|
125
|
+
},
|
|
126
|
+
"View Full Job Definition",
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
render(page);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create the comparison selectors section
|
|
136
|
+
* Defaults to same discipline, same track, next grade up
|
|
137
|
+
* @param {Object} params
|
|
138
|
+
* @param {Object} params.discipline - Current discipline
|
|
139
|
+
* @param {Object} params.currentGrade - Current grade
|
|
140
|
+
* @param {Object} params.currentTrack - Current track
|
|
141
|
+
* @param {Object} params.currentJobView - Current job view from presenter
|
|
142
|
+
* @param {Object|null} params.nextGrade - Next grade (for default selection)
|
|
143
|
+
* @param {Object} params.data - Full data object with disciplines, grades, tracks, skills, behaviours
|
|
144
|
+
* @returns {HTMLElement}
|
|
145
|
+
*/
|
|
146
|
+
function createComparisonSelectorsSection({
|
|
147
|
+
discipline,
|
|
148
|
+
currentGrade,
|
|
149
|
+
currentTrack,
|
|
150
|
+
currentJobView,
|
|
151
|
+
nextGrade,
|
|
152
|
+
data,
|
|
153
|
+
}) {
|
|
154
|
+
// Create a container for dynamic comparison results
|
|
155
|
+
const comparisonResultsContainer = div({
|
|
156
|
+
className: "comparison-results",
|
|
157
|
+
id: "comparison-results",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// State to track current selections - default to same discipline, same track, next grade
|
|
161
|
+
let selectedDisciplineId = discipline.id;
|
|
162
|
+
let selectedGradeId = nextGrade?.id || "";
|
|
163
|
+
let selectedTrackId = currentTrack.id;
|
|
164
|
+
|
|
165
|
+
// Get available options based on selected discipline
|
|
166
|
+
function getAvailableOptions(disciplineId) {
|
|
167
|
+
const selectedDisc = data.disciplines.find((d) => d.id === disciplineId);
|
|
168
|
+
if (!selectedDisc) return { grades: [], tracks: [] };
|
|
169
|
+
|
|
170
|
+
const validGrades = [];
|
|
171
|
+
const validTracks = new Set();
|
|
172
|
+
|
|
173
|
+
for (const grade of data.grades) {
|
|
174
|
+
for (const track of data.tracks) {
|
|
175
|
+
if (isValidCombination({ discipline: selectedDisc, grade, track })) {
|
|
176
|
+
if (!validGrades.find((g) => g.id === grade.id)) {
|
|
177
|
+
validGrades.push(grade);
|
|
178
|
+
}
|
|
179
|
+
validTracks.add(track.id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
grades: validGrades.sort((a, b) => a.level - b.level),
|
|
186
|
+
tracks: data.tracks
|
|
187
|
+
.filter((t) => validTracks.has(t.id))
|
|
188
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Update the comparison results based on current selections
|
|
194
|
+
*/
|
|
195
|
+
function updateComparison() {
|
|
196
|
+
// Clear previous results
|
|
197
|
+
comparisonResultsContainer.innerHTML = "";
|
|
198
|
+
|
|
199
|
+
if (!selectedDisciplineId || !selectedGradeId || !selectedTrackId) {
|
|
200
|
+
comparisonResultsContainer.appendChild(
|
|
201
|
+
div(
|
|
202
|
+
{ className: "comparison-placeholder" },
|
|
203
|
+
p(
|
|
204
|
+
{ className: "text-muted" },
|
|
205
|
+
"Select a discipline, track, and grade to see the comparison.",
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const targetDiscipline = data.disciplines.find(
|
|
213
|
+
(d) => d.id === selectedDisciplineId,
|
|
214
|
+
);
|
|
215
|
+
const targetGrade = data.grades.find((g) => g.id === selectedGradeId);
|
|
216
|
+
const targetTrack = data.tracks.find((t) => t.id === selectedTrackId);
|
|
217
|
+
|
|
218
|
+
if (!targetDiscipline || !targetGrade || !targetTrack) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check if comparing to same role
|
|
223
|
+
if (
|
|
224
|
+
targetDiscipline.id === discipline.id &&
|
|
225
|
+
targetGrade.id === currentGrade.id &&
|
|
226
|
+
targetTrack.id === currentTrack.id
|
|
227
|
+
) {
|
|
228
|
+
comparisonResultsContainer.appendChild(
|
|
229
|
+
div(
|
|
230
|
+
{ className: "comparison-placeholder" },
|
|
231
|
+
p(
|
|
232
|
+
{ className: "text-muted" },
|
|
233
|
+
"Select a different role to compare with your current role.",
|
|
234
|
+
),
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Use formatter shared module to analyze the progression
|
|
241
|
+
const progressionView = prepareCustomProgression({
|
|
242
|
+
discipline,
|
|
243
|
+
currentGrade,
|
|
244
|
+
currentTrack,
|
|
245
|
+
targetDiscipline,
|
|
246
|
+
targetGrade,
|
|
247
|
+
targetTrack,
|
|
248
|
+
skills: data.skills,
|
|
249
|
+
behaviours: data.behaviours,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (!progressionView) {
|
|
253
|
+
comparisonResultsContainer.appendChild(
|
|
254
|
+
div(
|
|
255
|
+
{ className: "comparison-error" },
|
|
256
|
+
p({ className: "text-muted" }, "This combination is not valid."),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const { skillChanges, behaviourChanges, summary, target } = progressionView;
|
|
263
|
+
|
|
264
|
+
// Build flat comparison result sections
|
|
265
|
+
const result = div(
|
|
266
|
+
{ className: "comparison-result" },
|
|
267
|
+
|
|
268
|
+
// Summary stats
|
|
269
|
+
div(
|
|
270
|
+
{ className: "grid grid-6" },
|
|
271
|
+
summary.skillsGained > 0
|
|
272
|
+
? createStatCard({ value: summary.skillsGained, label: "New Skills" })
|
|
273
|
+
: null,
|
|
274
|
+
createStatCard({
|
|
275
|
+
value: summary.skillsUp,
|
|
276
|
+
label: "Skills to Grow",
|
|
277
|
+
}),
|
|
278
|
+
summary.skillsDown > 0
|
|
279
|
+
? createStatCard({
|
|
280
|
+
value: summary.skillsDown,
|
|
281
|
+
label: "Skills Decrease",
|
|
282
|
+
})
|
|
283
|
+
: null,
|
|
284
|
+
summary.skillsLost > 0
|
|
285
|
+
? createStatCard({
|
|
286
|
+
value: summary.skillsLost,
|
|
287
|
+
label: "Skills Removed",
|
|
288
|
+
})
|
|
289
|
+
: null,
|
|
290
|
+
createStatCard({
|
|
291
|
+
value: summary.behavioursUp,
|
|
292
|
+
label: "Behaviours to Mature",
|
|
293
|
+
}),
|
|
294
|
+
summary.behavioursDown > 0
|
|
295
|
+
? createStatCard({
|
|
296
|
+
value: summary.behavioursDown,
|
|
297
|
+
label: "Behaviours Decrease",
|
|
298
|
+
})
|
|
299
|
+
: null,
|
|
300
|
+
),
|
|
301
|
+
|
|
302
|
+
// Comparison radars
|
|
303
|
+
div(
|
|
304
|
+
{ className: "section auto-grid-lg" },
|
|
305
|
+
createComparisonSkillRadar(
|
|
306
|
+
currentJobView.skillMatrix,
|
|
307
|
+
target.skillMatrix,
|
|
308
|
+
{
|
|
309
|
+
title: "Skills Comparison",
|
|
310
|
+
currentLabel: `Current (${currentGrade.id})`,
|
|
311
|
+
targetLabel: `Target (${targetGrade.id})`,
|
|
312
|
+
size: 400,
|
|
313
|
+
},
|
|
314
|
+
),
|
|
315
|
+
createComparisonBehaviourRadar(
|
|
316
|
+
currentJobView.behaviourProfile,
|
|
317
|
+
target.behaviourProfile,
|
|
318
|
+
{
|
|
319
|
+
title: "Behaviours Comparison",
|
|
320
|
+
currentLabel: `Current (${currentGrade.id})`,
|
|
321
|
+
targetLabel: `Target (${targetGrade.id})`,
|
|
322
|
+
size: 400,
|
|
323
|
+
},
|
|
324
|
+
),
|
|
325
|
+
),
|
|
326
|
+
|
|
327
|
+
// Skill changes section
|
|
328
|
+
section(
|
|
329
|
+
{ className: "section section-detail" },
|
|
330
|
+
h2({ className: "section-title" }, "Skill Changes"),
|
|
331
|
+
createProgressionTable(skillChanges, "skill"),
|
|
332
|
+
),
|
|
333
|
+
|
|
334
|
+
// Behaviour changes section
|
|
335
|
+
section(
|
|
336
|
+
{ className: "section section-detail" },
|
|
337
|
+
h2({ className: "section-title" }, "Behaviour Changes"),
|
|
338
|
+
createProgressionTable(behaviourChanges, "behaviour"),
|
|
339
|
+
),
|
|
340
|
+
|
|
341
|
+
// Link to target job
|
|
342
|
+
div(
|
|
343
|
+
{ className: "page-actions" },
|
|
344
|
+
a(
|
|
345
|
+
{
|
|
346
|
+
href: `#/job/${targetDiscipline.id}/${targetTrack.id}/${targetGrade.id}`,
|
|
347
|
+
className: "btn btn-secondary",
|
|
348
|
+
},
|
|
349
|
+
`View ${targetGrade.id} ${targetTrack.name} Job Definition →`,
|
|
350
|
+
),
|
|
351
|
+
),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
comparisonResultsContainer.appendChild(result);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Get initial available options
|
|
358
|
+
let availableOptions = getAvailableOptions(selectedDisciplineId);
|
|
359
|
+
|
|
360
|
+
// References to select elements for updating
|
|
361
|
+
let gradeSelectEl = null;
|
|
362
|
+
let trackSelectEl = null;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Update grade and track selectors when discipline changes
|
|
366
|
+
*/
|
|
367
|
+
function updateSelectorsForDiscipline(newDisciplineId) {
|
|
368
|
+
availableOptions = getAvailableOptions(newDisciplineId);
|
|
369
|
+
|
|
370
|
+
// Update grade selector
|
|
371
|
+
if (gradeSelectEl) {
|
|
372
|
+
gradeSelectEl.innerHTML = "";
|
|
373
|
+
const placeholderOpt = document.createElement("option");
|
|
374
|
+
placeholderOpt.value = "";
|
|
375
|
+
placeholderOpt.textContent = "Select grade...";
|
|
376
|
+
gradeSelectEl.appendChild(placeholderOpt);
|
|
377
|
+
|
|
378
|
+
for (const grade of availableOptions.grades) {
|
|
379
|
+
const opt = document.createElement("option");
|
|
380
|
+
opt.value = grade.id;
|
|
381
|
+
opt.textContent = grade.id;
|
|
382
|
+
gradeSelectEl.appendChild(opt);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Try to keep current selection if valid
|
|
386
|
+
if (availableOptions.grades.find((g) => g.id === selectedGradeId)) {
|
|
387
|
+
gradeSelectEl.value = selectedGradeId;
|
|
388
|
+
} else {
|
|
389
|
+
selectedGradeId = "";
|
|
390
|
+
gradeSelectEl.value = "";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Update track selector
|
|
395
|
+
if (trackSelectEl) {
|
|
396
|
+
trackSelectEl.innerHTML = "";
|
|
397
|
+
const placeholderOpt = document.createElement("option");
|
|
398
|
+
placeholderOpt.value = "";
|
|
399
|
+
placeholderOpt.textContent = "Select track...";
|
|
400
|
+
trackSelectEl.appendChild(placeholderOpt);
|
|
401
|
+
|
|
402
|
+
for (const track of availableOptions.tracks) {
|
|
403
|
+
const opt = document.createElement("option");
|
|
404
|
+
opt.value = track.id;
|
|
405
|
+
opt.textContent = track.name;
|
|
406
|
+
trackSelectEl.appendChild(opt);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Try to keep current selection if valid
|
|
410
|
+
if (availableOptions.tracks.find((t) => t.id === selectedTrackId)) {
|
|
411
|
+
trackSelectEl.value = selectedTrackId;
|
|
412
|
+
} else {
|
|
413
|
+
selectedTrackId = "";
|
|
414
|
+
trackSelectEl.value = "";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Create grade and track selects with stored references
|
|
420
|
+
gradeSelectEl = createSelectWithValue({
|
|
421
|
+
id: "compare-grade-select",
|
|
422
|
+
items: availableOptions.grades,
|
|
423
|
+
initialValue: selectedGradeId,
|
|
424
|
+
placeholder: "Select grade...",
|
|
425
|
+
getDisplayName: (g) => g.id,
|
|
426
|
+
onChange: (value) => {
|
|
427
|
+
selectedGradeId = value;
|
|
428
|
+
updateComparison();
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
trackSelectEl = createSelectWithValue({
|
|
433
|
+
id: "compare-track-select",
|
|
434
|
+
items: availableOptions.tracks,
|
|
435
|
+
initialValue: selectedTrackId,
|
|
436
|
+
placeholder: "Select track...",
|
|
437
|
+
getDisplayName: (t) => t.name,
|
|
438
|
+
onChange: (value) => {
|
|
439
|
+
selectedTrackId = value;
|
|
440
|
+
updateComparison();
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Trigger initial comparison if we have defaults
|
|
445
|
+
if (selectedGradeId && selectedTrackId) {
|
|
446
|
+
// Use setTimeout to ensure DOM is ready
|
|
447
|
+
setTimeout(() => updateComparison(), 0);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Create the section with selectors
|
|
451
|
+
return section(
|
|
452
|
+
{ className: "section section-detail" },
|
|
453
|
+
h2({ className: "section-title" }, "📈 Compare Progression"),
|
|
454
|
+
p(
|
|
455
|
+
{ className: "text-muted", style: "margin-bottom: 1rem" },
|
|
456
|
+
"Compare your current role with another discipline, grade, or track combination.",
|
|
457
|
+
),
|
|
458
|
+
|
|
459
|
+
// Selector row
|
|
460
|
+
div(
|
|
461
|
+
{ className: "comparison-selectors" },
|
|
462
|
+
div(
|
|
463
|
+
{ className: "form-group" },
|
|
464
|
+
label({ for: "compare-discipline-select" }, "Target Discipline"),
|
|
465
|
+
createSelectWithValue({
|
|
466
|
+
id: "compare-discipline-select",
|
|
467
|
+
items: data.disciplines.sort((a, b) =>
|
|
468
|
+
a.specialization.localeCompare(b.specialization),
|
|
469
|
+
),
|
|
470
|
+
initialValue: selectedDisciplineId,
|
|
471
|
+
placeholder: "Select discipline...",
|
|
472
|
+
getDisplayName: (d) => d.specialization,
|
|
473
|
+
onChange: (value) => {
|
|
474
|
+
selectedDisciplineId = value;
|
|
475
|
+
updateSelectorsForDiscipline(value);
|
|
476
|
+
updateComparison();
|
|
477
|
+
},
|
|
478
|
+
}),
|
|
479
|
+
),
|
|
480
|
+
div(
|
|
481
|
+
{ className: "form-group" },
|
|
482
|
+
label({ for: "compare-grade-select" }, "Target Grade"),
|
|
483
|
+
gradeSelectEl,
|
|
484
|
+
),
|
|
485
|
+
div(
|
|
486
|
+
{ className: "form-group" },
|
|
487
|
+
label({ for: "compare-track-select" }, "Target Track"),
|
|
488
|
+
trackSelectEl,
|
|
489
|
+
),
|
|
490
|
+
),
|
|
491
|
+
|
|
492
|
+
// Placeholder for results
|
|
493
|
+
comparisonResultsContainer,
|
|
494
|
+
);
|
|
495
|
+
}
|