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