US-030: Update CommandPalette for expanded content and panel actions

This commit is contained in:
2026-02-14 03:08:54 +00:00
parent dbdd51243d
commit 97d353930c
2 changed files with 31 additions and 20 deletions
+7 -1
View File
@@ -13,6 +13,7 @@ import { CareerActivityTile } from './tiles/CareerActivityTile'
import { EducationTile } from './tiles/EducationTile' import { EducationTile } from './tiles/EducationTile'
import { ProjectsTile } from './tiles/ProjectsTile' import { ProjectsTile } from './tiles/ProjectsTile'
import { useActiveSection } from '@/hooks/useActiveSection' import { useActiveSection } from '@/hooks/useActiveSection'
import { useDetailPanel } from '@/contexts/DetailPanelContext'
import type { PaletteAction } from '@/lib/search' import type { PaletteAction } from '@/lib/search'
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
@@ -52,6 +53,7 @@ const contentVariants = {
export function DashboardLayout() { export function DashboardLayout() {
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false) const [commandPaletteOpen, setCommandPaletteOpen] = useState(false)
const activeSection = useActiveSection() const activeSection = useActiveSection()
const { openPanel } = useDetailPanel()
const handleSearchClick = () => { const handleSearchClick = () => {
setCommandPaletteOpen(true) setCommandPaletteOpen(true)
@@ -110,8 +112,12 @@ export function DashboardLayout() {
window.open('/References/CV_v4.md', '_blank') window.open('/References/CV_v4.md', '_blank')
break break
} }
case 'panel': {
openPanel(action.panelContent)
break
}
} }
}, []) }, [openPanel])
return ( return (
<div <div
+24 -19
View File
@@ -7,6 +7,8 @@ import { problems } from '@/data/problems'
import { investigations } from '@/data/investigations' import { investigations } from '@/data/investigations'
import { documents } from '@/data/documents' import { documents } from '@/data/documents'
import { skills } from '@/data/skills' import { skills } from '@/data/skills'
import { kpis } from '@/data/kpis'
import type { DetailPanelContent } from '@/types/pmr'
export type PaletteSection = 'Experience' | 'Core Skills' | 'Active Projects' | 'Achievements' | 'Education' | 'Quick Actions' export type PaletteSection = 'Experience' | 'Core Skills' | 'Active Projects' | 'Achievements' | 'Education' | 'Quick Actions'
@@ -15,6 +17,7 @@ export type PaletteAction =
| { type: 'expand'; tileId: string; itemId: string } | { type: 'expand'; tileId: string; itemId: string }
| { type: 'link'; url: string } | { type: 'link'; url: string }
| { type: 'download' } | { type: 'download' }
| { type: 'panel'; panelContent: DetailPanelContent }
export type IconColorVariant = 'teal' | 'green' | 'amber' | 'purple' export type IconColorVariant = 'teal' | 'green' | 'amber' | 'purple'
@@ -74,25 +77,17 @@ export function buildPaletteData(): PaletteItem[] {
}) })
}) })
// Core Skills — from skills.ts, matching concept format with proficiency % // Core Skills — all ~21 skills from skills.ts, opening detail panel on select
const skillDescriptions: Record<string, string> = {
'Data Analysis': 'Primary expertise \u00b7 NHS population data',
'Python': 'Data pipelines, automation, analytics',
'SQL': 'Advanced queries, database migration',
'Power BI': 'Dashboard design & deployment',
'JavaScript / TypeScript': 'Web development & tooling',
}
skills.forEach((skill) => { skills.forEach((skill) => {
items.push({ items.push({
id: `skill-${skill.id}`, id: `skill-${skill.id}`,
title: `${skill.name} \u2014 ${skill.proficiency}%`, title: `${skill.name} \u2014 ${skill.proficiency}%`,
subtitle: skillDescriptions[skill.name] ?? `${skill.frequency} \u00b7 Since ${skill.startYear}`, subtitle: `${skill.frequency} \u00b7 Since ${skill.startYear} \u00b7 ${skill.category}`,
section: 'Core Skills', section: 'Core Skills',
iconVariant: 'green', iconVariant: 'green',
iconType: 'skill', iconType: 'skill',
keywords: `${skill.name.toLowerCase()} ${skill.proficiency} ${skill.frequency.toLowerCase()}`, keywords: `${skill.name.toLowerCase()} ${skill.proficiency} ${skill.frequency.toLowerCase()} ${skill.category.toLowerCase()}`,
action: { type: 'expand', tileId: 'core-skills', itemId: skill.id }, action: { type: 'panel', panelContent: { type: 'skill', skill } },
}) })
}) })
@@ -119,6 +114,7 @@ export function buildPaletteData(): PaletteItem[] {
] ]
projectEntries.forEach((entry) => { projectEntries.forEach((entry) => {
const investigation = investigations.find(inv => inv.id === entry.investigationId)
items.push({ items.push({
id: `proj-${entry.investigationId}`, id: `proj-${entry.investigationId}`,
title: entry.name, title: entry.name,
@@ -127,35 +123,42 @@ export function buildPaletteData(): PaletteItem[] {
iconVariant: 'amber', iconVariant: 'amber',
iconType: 'project', iconType: 'project',
keywords: entry.keywords, keywords: entry.keywords,
action: { type: 'expand', tileId: 'projects', itemId: entry.investigationId }, action: investigation
? { type: 'panel', panelContent: { type: 'project', investigation } }
: { type: 'scroll', tileId: 'projects' },
}) })
}) })
// Achievements — matching concept HTML entries // Achievements — open corresponding KPI detail panel
const achievementEntries: Array<{ title: string; sub: string; keywords: string }> = [ const achievementEntries: Array<{ title: string; sub: string; keywords: string; kpiId: string }> = [
{ {
title: '\u00a314.6M Efficiency Savings Identified', title: '\u00a314.6M Efficiency Savings Identified',
sub: 'Data-driven prescribing interventions', sub: 'Data-driven prescribing interventions',
keywords: '14.6m efficiency savings identified data-driven prescribing interventions money cost', keywords: '14.6m efficiency savings identified data-driven prescribing interventions money cost',
kpiId: 'savings',
}, },
{ {
title: '\u00a3220M Budget Oversight', title: '\u00a3220M Budget Oversight',
sub: 'Full analytical accountability to ICB board', sub: 'Full analytical accountability to ICB board',
keywords: '220m budget oversight analytical accountability icb board', keywords: '220m budget oversight analytical accountability icb board',
kpiId: 'budget',
}, },
{ {
title: 'Power BI Dashboards for 200+ Users', title: 'Power BI Dashboards for 200+ Users',
sub: 'Clinicians & commissioners across ICB', sub: 'Clinicians & commissioners across ICB',
keywords: 'power bi dashboards 200 users clinicians commissioners', keywords: 'power bi dashboards 200 users clinicians commissioners',
kpiId: 'years',
}, },
{ {
title: 'Team of 12 Led', title: '1.2M Population Served',
sub: 'Cross-functional data & population health', sub: 'Norfolk & Waveney Integrated Care System',
keywords: 'team 12 led cross-functional data population health leadership management', keywords: '1.2m population served norfolk waveney ics integrated care system',
kpiId: 'population',
}, },
] ]
achievementEntries.forEach((entry, i) => { achievementEntries.forEach((entry, i) => {
const kpi = kpis.find(k => k.id === entry.kpiId)
items.push({ items.push({
id: `ach-${i}`, id: `ach-${i}`,
title: entry.title, title: entry.title,
@@ -164,7 +167,9 @@ export function buildPaletteData(): PaletteItem[] {
iconVariant: 'amber', iconVariant: 'amber',
iconType: 'achievement', iconType: 'achievement',
keywords: entry.keywords, keywords: entry.keywords,
action: { type: 'scroll', tileId: 'latest-results' }, action: kpi
? { type: 'panel', panelContent: { type: 'kpi', kpi } }
: { type: 'scroll', tileId: 'latest-results' },
}) })
}) })