feat: add standalone skill guidance modal

This commit is contained in:
lizerk 2026-08-25 14:31:45 +03:00
parent cae44c47cb
commit 572c433bbd

View File

@ -825,7 +825,6 @@ export function ProfileSkillsInline({
const [endorsementReason, setEndorsementReason] = useState("");
const [endorsementSaving, setEndorsementSaving] = useState(false);
const listId = `${title.toLowerCase().replace(/\s+/g, "-")}-skills-list`;
const helpId = `${listId}-help`;
const sortedSkills = sortProfileSkills(skills);
const openSkillProgress = async (skill: Skill) => {
@ -931,12 +930,12 @@ export function ProfileSkillsInline({
<span>{title}</span>
<button
type="button"
onClick={() => setHelpOpen((value) => !value)}
onClick={() => setHelpOpen(true)}
className="app-accent-text inline-flex h-5 w-5 items-center justify-center rounded-full transition hover:bg-[var(--surface-tertiary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-primary)]"
aria-label="Как правильно заполнять навыки"
aria-label="Открыть справку о навыках"
aria-haspopup="dialog"
aria-expanded={helpOpen}
aria-controls={helpId}
title="Показать правила заполнения"
title="Как работают навыки"
>
<CircleHelp size={14} strokeWidth={2} />
</button>
@ -946,23 +945,6 @@ export function ProfileSkillsInline({
closeOnClickOutside
>
<div className="space-y-4">
{helpOpen ? (
<div
id={helpId}
className="app-surface-muted rounded-xl border border-[var(--border-subtle)] px-3 py-3 text-sm"
>
<p className="font-semibold text-[var(--foreground)]">Как заполнять навыки</p>
<ul className="app-text-muted mt-2 space-y-1.5 text-xs leading-relaxed">
<li>• Один навык — одна конкретная компетенция или технология.</li>
<li>• Выбирайте существующее название из списка, если оно уже есть.</li>
<li>• Не объединяйте несколько навыков и не указывайте должность или уровень владения.</li>
</ul>
<p className="app-text-muted mt-2 text-xs">
Например: «Python», «1С», «Электромонтаж».
</p>
</div>
) : null}
<div className="flex min-w-0 gap-2">
<input
list={listId}
@ -1035,6 +1017,83 @@ export function ProfileSkillsInline({
</div>
</Modal>
<Modal
isOpen={helpOpen}
onClose={() => setHelpOpen(false)}
title="Как работают навыки"
size="sm"
stackLevel={1}
closeOnClickOutside
>
<div className="space-y-3 text-sm">
<section className="app-surface-muted rounded-xl border border-[var(--border-subtle)] p-3">
<h3 className="font-semibold text-[var(--foreground)]">Как добавлять</h3>
<ul className="app-text-muted mt-2 space-y-1.5 text-xs leading-relaxed">
<li>• Один навык — одна конкретная компетенция или технология.</li>
<li>• Выбирайте существующее название, если оно уже есть в списке.</li>
<li>• Не объединяйте навыки и не указывайте должность или уровень владения.</li>
</ul>
<p className="app-text-muted mt-2 text-xs">
Например: «Python», «1С», «Электромонтаж».
</p>
</section>
<section className="app-surface-muted rounded-xl border border-[var(--border-subtle)] p-3">
<h3 className="font-semibold text-[var(--foreground)]">Опыт и ранг</h3>
<p className="app-text-muted mt-1 text-xs leading-relaxed">
Ранг показывает личный уровень сотрудника в конкретном навыке. Опыт начисляется за завершённые задачи с этим навыком и подтверждения коллег.
</p>
<div className="mt-3 grid grid-cols-2 gap-1.5 text-xs sm:grid-cols-3">
{[
{ rank: "none" as const, label: "Без ранга", xp: "0 XP" },
{ rank: "bronze" as const, label: "Бронза", xp: "100 XP" },
{ rank: "silver" as const, label: "Серебро", xp: "300 XP" },
{ rank: "gold" as const, label: "Золото", xp: "800 XP" },
{ rank: "ruby" as const, label: "Рубин", xp: "1 800 XP" },
{ rank: "diamond" as const, label: "Бриллиант", xp: "4 000 XP" },
].map((item) => (
<div
key={item.rank}
className="app-surface flex min-w-0 items-center gap-2 rounded-lg border border-[var(--border-subtle)] px-2.5 py-2"
>
<SkillRankMark rank={item.rank} />
<div className="min-w-0">
<p className="truncate font-medium text-[var(--foreground)]">{item.label}</p>
<p className="app-text-muted text-[10px]">{item.xp}</p>
</div>
</div>
))}
</div>
<p className="app-text-muted mt-2 text-[11px] leading-relaxed">
Завершённая задача даёт 25 XP. Подтверждение коллеги — 5–15 XP в зависимости от его роли.
</p>
</section>
<section className="app-surface-muted rounded-xl border border-[var(--border-subtle)] p-3">
<h3 className="font-semibold text-[var(--foreground)]">Редкость</h3>
<p className="app-text-muted mt-1 text-xs leading-relaxed">
Редкость относится к самому навыку и показывает, насколько редко он встречается среди подтверждённых обладателей в компании. Ранг при этом остаётся личным для каждого сотрудника.
</p>
<div className="mt-2 flex flex-wrap gap-1.5">
{[
{ rarity: "legendary" as const, label: "Легендарный · 1" },
{ rarity: "epic" as const, label: "Эпический · 2–3" },
{ rarity: "rare" as const, label: "Редкий · 4–9" },
{ rarity: "uncommon" as const, label: "Необычный · 10–24" },
{ rarity: "common" as const, label: "Обычный · 25+" },
].map((item) => (
<span
key={item.rarity}
className={`rounded-full px-2 py-1 text-[11px] font-semibold ${skillRarityBadgeStyles[item.rarity]}`}
>
{item.label}
</span>
))}
</div>
</section>
</div>
</Modal>
<Modal
isOpen={progressOpen}
onClose={() => {