85 lines
3.6 KiB
JavaScript
85 lines
3.6 KiB
JavaScript
import { useMemo } from 'react';
|
|
import { usePreferences } from '@/lib/krowHooks';
|
|
import {
|
|
isSkillOnPage, pageDefinitionForSkillId, skillStatesForPage, workforceSkillStates,
|
|
workforceSkillsForPage,
|
|
} from './pageSkills';
|
|
|
|
/**
|
|
* A page's skill attachments, resolved once.
|
|
*
|
|
* Every consuming page needs the same two things before it can ask anything
|
|
* else: the account's custom definitions (a skill added in Settings is as real
|
|
* as one on disk) and the ids it has switched off. Reading those in one hook
|
|
* means no page can forget either, and none has to know where they are stored.
|
|
*
|
|
* The hook is deliberately thin — it resolves attachment and hands back the
|
|
* helpers already bound to this page. The rules themselves stay in
|
|
* `pageSkills.js`, which is what keeps them testable without React.
|
|
*
|
|
* Usage is always the same shape:
|
|
*
|
|
* const { skills, statesFor } = usePageSkills('profile');
|
|
* if (!skills.length) return null; // nothing attached: no section
|
|
*
|
|
* `attaches(skillId)` is the gate for a contextual addition to something that
|
|
* already exists — a training prompt beside a position requirement, say — where
|
|
* the page is not listing skills but is about to mention one.
|
|
*/
|
|
/**
|
|
* Every training path, for the views that manage training rather than surface it.
|
|
*
|
|
* The same preferences plumbing as `usePageSkills`, asking `pageSkills.js` the
|
|
* other question it answers — so Workspace & Skills and Skill Development count
|
|
* paths from the registry rather than keeping a list of their own.
|
|
*/
|
|
export function useWorkforcePaths() {
|
|
const preferences = usePreferences();
|
|
|
|
const customKey = JSON.stringify(preferences.customSkills || []);
|
|
const disabledKey = JSON.stringify(preferences.disabledSkills || []);
|
|
|
|
return useMemo(() => {
|
|
const options = {
|
|
customSources: JSON.parse(customKey),
|
|
disabled: JSON.parse(disabledKey),
|
|
};
|
|
return {
|
|
/** The training paths themselves, before anyone's progress is applied. */
|
|
paths: workforceSkillStates([], null, options).map((entry) => entry.definition),
|
|
/** Each path paired with one person's state in it. */
|
|
statesFor: (courses, profile) => workforceSkillStates(courses, profile, options),
|
|
};
|
|
}, [customKey, disabledKey]);
|
|
}
|
|
|
|
export function usePageSkills(pageId) {
|
|
const preferences = usePreferences();
|
|
|
|
/* `usePreferences` rebuilds its object on every read, so the arrays inside it
|
|
are new identities each render. Keying the memo on their content is what
|
|
stops every consumer from recomputing — and re-rendering — continuously. */
|
|
const customKey = JSON.stringify(preferences.customSkills || []);
|
|
const disabledKey = JSON.stringify(preferences.disabledSkills || []);
|
|
|
|
return useMemo(() => {
|
|
const customSources = JSON.parse(customKey);
|
|
const disabled = JSON.parse(disabledKey);
|
|
const options = { customSources, disabled };
|
|
const skills = workforceSkillsForPage(pageId, options);
|
|
|
|
return {
|
|
/** The definitions attached here. Empty means render nothing. */
|
|
skills,
|
|
/** True when this page may surface that capability at all. */
|
|
attaches: (skillId) => isSkillOnPage(skillId, pageId, options),
|
|
/** The definition behind a capability, but only if attached here. */
|
|
definitionFor: (skillId) => pageDefinitionForSkillId(skillId, pageId, options),
|
|
/** Each attached definition paired with one person's state in it. */
|
|
statesFor: (courses, profile) => skillStatesForPage(pageId, courses, profile, options),
|
|
/** Passed through to components that resolve attachment themselves. */
|
|
customSources,
|
|
};
|
|
}, [pageId, customKey, disabledKey]);
|
|
}
|