Files
doormilxpress_astryx/src/lib/skills/usePageSkills.js

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]);
}