From 980297ea9274e1326e7d4a262c84ec4258e06313 Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Fri, 13 Feb 2026 23:50:19 +0000 Subject: [PATCH] US-011: Redesign CoreSkillsTile with categorised groups and panel triggers Full-width card with skills grouped by Technical, Healthcare Domain, and Strategic & Leadership categories. Top 4 per category sorted by proficiency. Individual skills open detail panel; categories with >4 skills show 'View all' button triggering panel. Removed old single-expand accordion. Category headers use sidebar section divider styling. Co-Authored-By: Claude Opus 4.6 --- src/components/tiles/CoreSkillsTile.tsx | 436 +++++++++++++----------- 1 file changed, 244 insertions(+), 192 deletions(-) diff --git a/src/components/tiles/CoreSkillsTile.tsx b/src/components/tiles/CoreSkillsTile.tsx index 82caad9..da046b3 100644 --- a/src/components/tiles/CoreSkillsTile.tsx +++ b/src/components/tiles/CoreSkillsTile.tsx @@ -1,250 +1,302 @@ -import React, { useState, useCallback } from 'react' -import { AnimatePresence, motion } from 'framer-motion' -import { BarChart3, Code2, Database, PieChart, FileCode2 } from 'lucide-react' +import React from 'react' +import type { LucideIcon } from 'lucide-react' +import { + BarChart3, Code2, Database, PieChart, FileCode2, + Sheet, GitBranch, Workflow, Pill, Users, FileCheck, + TrendingUp, Route, ShieldAlert, Banknote, Handshake, + MessageSquare, UserPlus, RefreshCw, Calculator, Presentation, + ChevronRight, +} from 'lucide-react' import { Card, CardHeader } from '../Card' import { skills } from '@/data/skills' -import { medications } from '@/data/medications' -import type { SkillMedication } from '@/types/pmr' +import { useDetailPanel } from '@/contexts/DetailPanelContext' +import type { SkillMedication, SkillCategory } from '@/types/pmr' -const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches - -const iconMap = { +const iconMap: Record = { BarChart3, Code2, Database, PieChart, FileCode2, + Sheet, + GitBranch, + Workflow, + Pill, + Users, + FileCheck, + TrendingUp, + Route, + ShieldAlert, + Banknote, + Handshake, + MessageSquare, + UserPlus, + RefreshCw, + Calculator, + Presentation, } -interface SkillItemProps { +const SKILLS_PER_CATEGORY = 4 + +const categoryConfig: { id: SkillCategory; label: string }[] = [ + { id: 'Technical', label: 'Technical' }, + { id: 'Domain', label: 'Healthcare Domain' }, + { id: 'Leadership', label: 'Strategic & Leadership' }, +] + +interface SkillRowProps { skill: SkillMedication - isExpanded: boolean - onToggle: () => void + onClick: () => void } -function SkillItem({ skill, isExpanded, onToggle }: SkillItemProps) { - const IconComponent = iconMap[skill.icon as keyof typeof iconMap] +function SkillRow({ skill, onClick }: SkillRowProps) { + const IconComponent = iconMap[skill.icon] - // Find matching medication for prescribing history - const medication = medications.find((m) => m.name === skill.name) - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault() - onToggle() - } else if (e.key === 'Escape' && isExpanded) { - e.preventDefault() - onToggle() - } - }, - [isExpanded, onToggle], - ) + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onClick() + } + } return (
{ e.currentTarget.style.borderColor = 'var(--accent-border)' + e.currentTarget.style.boxShadow = 'var(--shadow-md)' }} onMouseLeave={(e) => { - if (!isExpanded) { - e.currentTarget.style.borderColor = 'var(--border-light)' - } + e.currentTarget.style.borderColor = 'var(--border-light)' + e.currentTarget.style.boxShadow = 'none' }} > - {/* Item header row */} + {/* Icon */} +
+ {IconComponent && } +
+ + {/* Text */} +
+
+ {skill.name} +
+
+ {skill.frequency} · {skill.yearsOfExperience} yrs +
+
+ + {/* Status badge */} +
+ {skill.status} +
+ + {/* Chevron */} + +
+ ) +} + +interface CategorySectionProps { + label: string + categoryId: SkillCategory + skills: SkillMedication[] + onSkillClick: (skill: SkillMedication) => void + onViewAll: (category: SkillCategory) => void + isFirst: boolean +} + +function CategorySection({ + label, + categoryId, + skills: categorySkills, + onSkillClick, + onViewAll, + isFirst, +}: CategorySectionProps) { + const visibleSkills = categorySkills.slice(0, SKILLS_PER_CATEGORY) + const remainingCount = categorySkills.length - SKILLS_PER_CATEGORY + + return ( +
+ {/* Category header — sidebar section divider style */}
- {/* Icon container */} -
- {IconComponent && } -
- - {/* Text block */} -
-
- {skill.name} -
-
- {skill.frequency} · Since {skill.startYear} · {skill.yearsOfExperience} yrs -
-
- - {/* Status badge */} -
- {skill.status} -
+ {label} + +
+ + {categorySkills.length} items +
- {/* Expanded content: prescribing history timeline */} - - {isExpanded && medication && medication.prescribingHistory && ( - -
- {/* Timeline entries */} -
- {medication.prescribingHistory.map((entry, i) => ( -
- {/* Timeline dot */} -
+ {/* Skill rows */} +
+ {visibleSkills.map((skill) => ( + onSkillClick(skill)} + /> + ))} +
- {/* Content */} -
-
- {entry.year} -
-
- {entry.description} -
-
-
- ))} -
-
- - )} - + {/* View all button */} + {remainingCount > 0 && ( + + )}
) } export function CoreSkillsTile() { - const [expandedItemId, setExpandedItemId] = useState(null) + const { openPanel } = useDetailPanel() - const handleToggle = useCallback( - (id: string) => { - setExpandedItemId((prev) => (prev === id ? null : id)) - }, - [], - ) + // Group skills by category, sorted by proficiency descending + const groupedSkills = categoryConfig.map(({ id, label }) => ({ + id, + label, + skills: skills + .filter((s) => s.category === id) + .sort((a, b) => b.proficiency - a.proficiency), + })) + + const handleSkillClick = (skill: SkillMedication) => { + openPanel({ type: 'skill', skill }) + } + + const handleViewAll = (category: SkillCategory) => { + openPanel({ type: 'skills-all', category }) + } return ( - -
- {skills.map((skill) => ( - handleToggle(skill.id)} - /> - ))} -
+ + {groupedSkills.map((group, index) => ( + + ))}
) }