import { useEffect, useState } from "preact/hooks"; import { t } from "../../i18n"; import { api, apiErrorText } from "../../features/customize/api"; import { closeCust, custTab } from "../../features/customize/actions"; import { nestedEditor } from "../../features/customize/state"; import { skillReadinessNoteText } from "../../features/customize/environment"; import { asList, asString, confirmAction, dropSkillsCatalog, effProject, hint, insertSkillMention, } from "../../features/customize/host"; import { useAlive } from "./use-timer-lease"; import { markCustomizeFailed, markCustomizeLoaded } from "../../features/customize/load"; import { Empty, Hdr, IconGhost, Pill, Toggle } from "./ui"; type Skill = Record; function skillScope(s: Skill): "project" | "bundled" | "personal" { if (s.scope === "project") return "project"; if (s.scope === "bundled") return "bundled"; return "personal"; } function SkillRow({ s, pid }: { s: Skill; pid: string | null }) { const scope = skillScope(s); const name = asString(s.displayName || s.name); const note = skillReadinessNoteText(s); const [enabled, setEnabled] = useState(s.enabled !== false); return (
{name} {t(`skill.scope.${scope}`)}
{asString(s.description)}
{note ?
{note}
: null}
{ closeCust(); insertSkillMention(asString(s.name)); }} /> {s.versioned ? ( { nestedEditor.value = { kind: "skill-history", name: asString(s.name), scope, projectId: scope === "project" ? pid : null, }; }} /> ) : null} {s.editable && scope === "personal" ? ( <> { nestedEditor.value = { kind: "skill", name: asString(s.name) }; }} /> { if (!confirmAction(t("cust.skills.deleteConfirm", asString(s.name)))) return; try { await api(`/skills/${encodeURIComponent(asString(s.name))}`, { method: "DELETE", }); dropSkillsCatalog(); custTab("skills"); } catch (e) { hint(t("toast.deleteFailed", apiErrorText(e)), true); } }} /> ) : null} {scope !== "project" ? ( { const on = !enabled; setEnabled(on); try { await api(`/skills/catalog/${encodeURIComponent(asString(s.name))}/enabled`, { method: "PUT", body: JSON.stringify({ enabled: on }), }); } catch { setEnabled(!on); } }} /> ) : null}
); } export function SkillsTab() { const alive = useAlive(); const [err, setErr] = useState(null); const [skills, setSkills] = useState([]); const [openCollections, setOpenCollections] = useState>({}); const pid = effProject(); useEffect(() => { void (async () => { try { const personalRequest = api("/skills/catalog"); const projectRequest = pid ? api(`/projects/${encodeURIComponent(pid)}/skills/catalog`).catch(() => ({ skills: [], })) : Promise.resolve({ skills: [] }); const [personalData, projectData] = await Promise.all([ personalRequest, projectRequest, ]); if (!alive()) return; const personalSkills = asList(personalData.skills) as Skill[]; const projectSkills = asList(projectData.skills) as Skill[]; setSkills([...personalSkills, ...projectSkills]); markCustomizeLoaded(); } catch (e) { if (!alive()) return; const message = t("versions.load.err", (e as Error).message); setErr(message); markCustomizeFailed(message); } })(); }, [alive, pid]); if (err) return
{err}
; const singles: Skill[] = []; const collections = new Map(); skills.forEach((s) => { const cid = asString(s.collection); if (!cid) { singles.push(s); return; } const members = collections.get(cid) || []; members.push(s); collections.set(cid, members); }); return (
{t("cust.skills.yourSkills")}
{singles.map((s) => ( ))} {[...collections.keys()].sort().map((cid) => { const members = collections.get(cid) || []; const open = !!openCollections[cid]; return (
{t("cust.skills.collection", cid, members.length)}
{t("cust.skills.collectionDesc")}
{open ? members.map((s) => ( )) : null}
); })} {!skills.length ? {t("cust.skills.desc", 0)} : null}
); }