US-020: Create SkillDetail renderer for detail panel

This commit is contained in:
2026-02-14 02:30:53 +00:00
parent 9d61d2c8ca
commit 9ec71ae0ed
3 changed files with 278 additions and 3 deletions
+271
View File
@@ -0,0 +1,271 @@
import type { SkillMedication } from '@/types/pmr'
import { roleSkillMappings, constellationNodes } from '@/data/constellation'
interface SkillDetailProps {
skill: SkillMedication
}
// Category display names
const categoryLabels: Record<SkillMedication['category'], string> = {
Technical: 'Technical',
Domain: 'Healthcare Domain',
Leadership: 'Strategic & Leadership',
}
// Proficiency bar color based on value
function getProficiencyColor(proficiency: number): string {
if (proficiency >= 90) return 'var(--success)'
if (proficiency >= 75) return 'var(--accent)'
return 'var(--amber)'
}
export function SkillDetail({ skill }: SkillDetailProps) {
// Find roles that use this skill from constellation data
const usedInRoles = roleSkillMappings
.filter((mapping) => mapping.skillIds.includes(skill.id))
.map((mapping) => {
const node = constellationNodes.find((n) => n.id === mapping.roleId && n.type === 'role')
return node
})
.filter(Boolean)
// Sort chronologically (earliest first)
.sort((a, b) => (a!.startYear ?? 0) - (b!.startYear ?? 0))
return (
<div
style={{
fontFamily: 'var(--font-ui)',
display: 'flex',
flexDirection: 'column',
gap: '24px',
}}
>
{/* Skill header */}
<div>
<div
style={{
fontSize: '20px',
fontWeight: 700,
color: 'var(--text-primary)',
lineHeight: '1.3',
marginBottom: '8px',
}}
>
{skill.name}
</div>
{/* Medication metaphor badges */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' }}>
<span
style={{
padding: '3px 10px',
backgroundColor: 'var(--accent-light)',
color: 'var(--accent)',
fontSize: '11px',
fontWeight: 600,
borderRadius: 'var(--radius-sm)',
fontFamily: 'var(--font-geist)',
}}
>
{skill.frequency}
</span>
<span
style={{
padding: '3px 10px',
backgroundColor:
skill.status === 'Active' ? 'var(--success-light)' : 'var(--bg-dashboard)',
color: skill.status === 'Active' ? 'var(--success)' : 'var(--text-tertiary)',
fontSize: '10px',
fontWeight: 600,
borderRadius: 'var(--radius-sm)',
textTransform: 'uppercase',
letterSpacing: '0.05em',
}}
>
{skill.status}
</span>
</div>
</div>
{/* Category label */}
<div>
<span
style={{
fontSize: '11px',
fontWeight: 500,
color: 'var(--text-tertiary)',
textTransform: 'uppercase',
letterSpacing: '0.06em',
}}
>
{categoryLabels[skill.category]}
</span>
</div>
{/* Proficiency bar */}
<div>
<h3
style={{
fontSize: '12px',
fontWeight: 600,
color: 'var(--text-secondary)',
textTransform: 'uppercase',
letterSpacing: '0.05em',
marginBottom: '8px',
}}
>
Proficiency
</h3>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div
style={{
flex: 1,
height: '6px',
backgroundColor: 'var(--border-light)',
borderRadius: '3px',
overflow: 'hidden',
}}
>
<div
style={{
width: `${skill.proficiency}%`,
height: '100%',
backgroundColor: getProficiencyColor(skill.proficiency),
borderRadius: '3px',
transition: 'width 400ms ease-out',
}}
/>
</div>
<span
style={{
fontSize: '13px',
fontWeight: 700,
fontFamily: 'var(--font-geist)',
color: getProficiencyColor(skill.proficiency),
minWidth: '36px',
textAlign: 'right',
}}
>
{skill.proficiency}%
</span>
</div>
</div>
{/* Years of experience */}
<div>
<h3
style={{
fontSize: '12px',
fontWeight: 600,
color: 'var(--text-secondary)',
textTransform: 'uppercase',
letterSpacing: '0.05em',
marginBottom: '8px',
}}
>
Experience
</h3>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '6px' }}>
<span
style={{
fontSize: '28px',
fontWeight: 700,
color: 'var(--text-primary)',
lineHeight: '1',
}}
>
{skill.yearsOfExperience}
</span>
<span
style={{
fontSize: '13px',
color: 'var(--text-secondary)',
}}
>
{skill.yearsOfExperience === 1 ? 'year' : 'years'}
</span>
<span
style={{
fontSize: '11px',
fontFamily: 'var(--font-geist)',
color: 'var(--text-tertiary)',
marginLeft: '4px',
}}
>
Since {skill.startYear}
</span>
</div>
</div>
{/* Used in roles */}
{usedInRoles.length > 0 && (
<div>
<h3
style={{
fontSize: '12px',
fontWeight: 600,
color: 'var(--text-secondary)',
textTransform: 'uppercase',
letterSpacing: '0.05em',
marginBottom: '10px',
}}
>
Used In
</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{usedInRoles.map((node) => (
<div
key={node!.id}
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
padding: '8px 12px',
backgroundColor: 'var(--bg-dashboard)',
border: '1px solid var(--border-light)',
borderRadius: 'var(--radius-sm)',
}}
>
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor: node!.orgColor ?? 'var(--accent)',
flexShrink: 0,
}}
aria-hidden="true"
/>
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
fontSize: '12.5px',
fontWeight: 600,
color: 'var(--text-primary)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{node!.label}
</div>
<div
style={{
fontSize: '10px',
fontFamily: 'var(--font-geist)',
color: 'var(--text-tertiary)',
marginTop: '1px',
}}
>
{node!.organization} · {node!.startYear}
{node!.endYear === null ? 'Present' : node!.endYear !== node!.startYear ? `${node!.endYear}` : ''}
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
)
}