import React, { useState, useEffect, useCallback } from 'react' import { motion } from 'framer-motion' import { ChevronRight } from 'lucide-react' import { TopBar } from './TopBar' import { SubNav } from './SubNav' import Sidebar from './Sidebar' import { CommandPalette } from './CommandPalette' import { DetailPanel } from './DetailPanel' import { CardHeader } from './Card' import { PatientSummaryTile } from './tiles/PatientSummaryTile' import { EducationSubsection } from './EducationSubsection' import { ProjectsTile } from './tiles/ProjectsTile' import { ParentSection } from './ParentSection' import CareerConstellation from './CareerConstellation' import { WorkExperienceSubsection } from './WorkExperienceSubsection' import { RepeatMedicationsSubsection } from './RepeatMedicationsSubsection' import { ChatWidget } from './ChatWidget' import { useActiveSection } from '@/hooks/useActiveSection' import { useDetailPanel } from '@/contexts/DetailPanelContext' import { consultations } from '@/data/consultations' import { skills } from '@/data/skills' import type { PaletteAction } from '@/lib/search' const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches const topbarVariants = { hidden: prefersReducedMotion ? { y: 0, opacity: 1 } : { y: -48, opacity: 0 }, visible: { y: 0, opacity: 1, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' }, }, } const sidebarVariants = { hidden: prefersReducedMotion ? { x: 0, opacity: 1 } : { x: -272, opacity: 0 }, visible: { x: 0, opacity: 1, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.25, ease: 'easeOut', delay: 0.05 }, }, } const contentVariants = { hidden: prefersReducedMotion ? { opacity: 1 } : { opacity: 0 }, visible: { opacity: 1, transition: prefersReducedMotion ? { duration: 0 } : { duration: 0.3, delay: 0.15 }, }, } function LastConsultationSubsection() { const { openPanel } = useDetailPanel() const consultation = consultations[0] const handleOpenPanel = () => { openPanel({ type: 'consultation', consultation }) } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() handleOpenPanel() } } const formatDate = (dateStr: string): string => { const date = new Date(dateStr) return date.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }) } const getEmploymentType = (): string => { if (consultation.organization.includes('ICB')) { return 'Permanent · Full-time' } return 'Permanent' } const getBand = (): string => { if (consultation.role.includes('Head')) { return '8a' } return '—' } const fieldLabelStyle: React.CSSProperties = { fontSize: '12px', textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--text-tertiary)', marginBottom: '3px', } const fieldValueStyle: React.CSSProperties = { fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)', } return (
{ e.currentTarget.style.backgroundColor = 'rgba(10,128,128,0.04)' }} onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = 'transparent' }} aria-label={`View full details for ${consultation.role}`} >
Date
{formatDate(consultation.date)}
Organisation
{consultation.organization}
Type
{getEmploymentType()}
Band
{getBand()}
{consultation.role}
) } export function DashboardLayout() { const [commandPaletteOpen, setCommandPaletteOpen] = useState(false) const [highlightedNodeId, setHighlightedNodeId] = useState(null) const activeSection = useActiveSection() const { openPanel } = useDetailPanel() const handleSearchClick = () => { setCommandPaletteOpen(true) } const handlePaletteClose = useCallback(() => { setCommandPaletteOpen(false) }, []) // eslint-disable-next-line @typescript-eslint/no-unused-vars const handleSectionClick = useCallback((_sectionId: string) => { // SubNav handles scrolling internally }, []) // Constellation graph handlers const handleRoleClick = useCallback( (roleId: string) => { const consultation = consultations.find((c) => c.id === roleId) if (consultation) { openPanel({ type: 'career-role', consultation }) } }, [openPanel], ) const handleSkillClick = useCallback( (skillId: string) => { const skill = skills.find((s) => s.id === skillId) if (skill) { openPanel({ type: 'skill', skill }) } }, [openPanel], ) const handleNodeHighlight = useCallback((id: string | null) => { setHighlightedNodeId(id) }, []) // Global Ctrl+K listener to open command palette useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault() setCommandPaletteOpen(prev => !prev) } } document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) }, []) // Handle palette actions (scroll to tile, expand item, open link, download) const handlePaletteAction = useCallback((action: PaletteAction) => { switch (action.type) { case 'scroll': { const tileEl = document.querySelector(`[data-tile-id="${action.tileId}"]`) if (tileEl) { tileEl.scrollIntoView({ behavior: 'smooth', block: 'start' }) } break } case 'expand': { const tileEl = document.querySelector(`[data-tile-id="${action.tileId}"]`) if (tileEl) { tileEl.scrollIntoView({ behavior: 'smooth', block: 'start' }) // Dispatch a custom event that the tile can listen for to expand the item const expandEvent = new CustomEvent('palette-expand', { detail: { tileId: action.tileId, itemId: action.itemId }, }) document.dispatchEvent(expandEvent) } break } case 'link': { window.open(action.url, '_blank', 'noopener,noreferrer') break } case 'download': { // For now, open the CV file or trigger a download // This can be wired to an actual PDF when available window.open('/References/CV_v4.md', '_blank') break } case 'panel': { openPanel(action.panelContent) break } } }, [openPanel]) return (
{/* TopBar — fixed at top */} {/* SubNav — sticky below TopBar */} {/* Layout below TopBar + SubNav: Sidebar + Main */}
{/* Sidebar — hidden on mobile/tablet, visible on desktop */} {/* Main content — scrollable card grid */}
{/* PatientSummaryTile — full width (includes Latest Results subsection) */} {/* ProjectsTile — half width */} {/* Patient Pathway — parent section with constellation graph + subsections */}
Clinical Record Stream
Chronological role and education entries. Select items to inspect full records.
Role
Role
Education
{/* Command palette overlay */} {/* Detail panel */} {/* Floating chat widget */}
) }