diff --git a/Ralph/prd.json b/Ralph/prd.json index 4199b95..039bd2f 100644 --- a/Ralph/prd.json +++ b/Ralph/prd.json @@ -86,7 +86,7 @@ "Verify in browser using dev-browser skill" ], "priority": 5, - "passes": false, + "passes": true, "notes": "The Latest Results subsection should look like a section within the parent card — use a CardHeader for 'LATEST RESULTS' and render the KPI grid below it. The parent ParentSection header 'Patient Summary' should be visually dominant." }, { @@ -102,7 +102,7 @@ "Verify in browser using dev-browser skill" ], "priority": 6, - "passes": false, + "passes": true, "notes": "This story just sets up the parent section with the graph. The Last Consultation, experience/skills columns, and education are added in subsequent stories." }, { @@ -118,7 +118,7 @@ "Verify in browser using dev-browser skill" ], "priority": 7, - "passes": false, + "passes": true, "notes": "Reuse the rendering logic from LastConsultationTile — either inline it or extract a shared sub-component. The important thing is it lives inside the Patient Pathway ParentSection now." }, { @@ -137,7 +137,7 @@ "Verify in browser using dev-browser skill" ], "priority": 8, - "passes": false, + "passes": true, "notes": "Reuse expansion patterns from existing CareerActivityTile and CoreSkillsTile. The two-column layout is within the Patient Pathway ParentSection card, not separate cards." }, { diff --git a/Ralph/progress.txt b/Ralph/progress.txt index 8d4a84b..9dd1933 100644 --- a/Ralph/progress.txt +++ b/Ralph/progress.txt @@ -905,3 +905,55 @@ - All 4 role entries now correctly reference their matching consultation IDs: interim-head-2025, deputy-head-2024, high-cost-drugs-2022, pharmacy-manager-2017 - Timeline now has 8 entries: 4 roles + GPhC cert + Mary Seacole cert + MPharm edu + A-Levels edu --- + +## 2026-02-14 - US-005 +- Restructured PatientSummaryTile to use ParentSection wrapper with "Patient Summary" as large h2 header +- Merged LatestResultsTile content (MetricCard components, KPI grid) into PatientSummaryTile as a subsection with CardHeader "LATEST RESULTS" +- Removed the 4 headline metric figures (9+ Years, 1.2M, £220M, £14.6M+) strip from PatientSummaryTile +- Removed standalone LatestResultsTile import and rendering from DashboardLayout +- Files changed: src/components/tiles/PatientSummaryTile.tsx, src/components/DashboardLayout.tsx +- **Learnings for future iterations:** + - The MetricCard component from LatestResultsTile was moved wholesale into PatientSummaryTile — it uses useDetailPanel for KPI click-to-detail, so the import chain followed + - ParentSection wraps Card with full prop, so the tileId passes through ParentSection → Card → data-tile-id attribute for command palette scroll targeting + - The LatestResultsTile.tsx file still exists but is no longer imported — US-010 will clean it up +--- + +## 2026-02-14 - US-006 +- Replaced standalone CareerActivityTile with ParentSection titled "Patient Pathway" containing CareerConstellation graph +- Moved onRoleClick/onSkillClick handlers from CareerActivityTile into DashboardLayout (using consultations + skills data imports) +- Removed CareerActivityTile import from DashboardLayout +- CareerActivityTile.tsx still exists but is no longer imported — the timeline entries will be handled in US-007/US-008 +- Files changed: src/components/DashboardLayout.tsx +- **Learnings for future iterations:** + - The CareerActivityTile contained both the constellation graph AND the timeline activity grid — US-006 only moves the graph into the parent section + - The onRoleClick/onSkillClick handlers were duplicated from CareerActivityTile into DashboardLayout since they need to be passed down to CareerConstellation. The same pattern (find consultation/skill by ID, openPanel) is used. + - ParentSection with tileId="patient-pathway" provides scroll targeting for command palette — US-013 will update palette data to use this new tileId +--- + +## 2026-02-14 - US-007 +- Moved Last Consultation content from standalone tile into Patient Pathway ParentSection as a subsection +- Created `LastConsultationSubsection` component within DashboardLayout.tsx — renders green dot CardHeader + date/org/type/band fields + role title + examination bullets + "View full record" button +- Deleted `src/components/tiles/LastConsultationTile.tsx` — content fully inlined +- Removed LastConsultationTile import from DashboardLayout +- Files changed: src/components/DashboardLayout.tsx (modified), src/components/tiles/LastConsultationTile.tsx (deleted) +- **Learnings for future iterations:** + - The LastConsultationSubsection is defined as a standalone function component in DashboardLayout.tsx (not inside the export) — this keeps it clean and avoids nesting component definitions + - `marginTop: 24px` on the subsection wrapper creates visual separation from the constellation graph above + - The subsection uses `useDetailPanel` independently — it doesn't need to receive openPanel as a prop from DashboardLayout + - All the same rendering logic from LastConsultationTile was preserved: formatDate, getEmploymentType, getBand, field labels, bullet dots, hover states, keyboard handlers, aria labels + - Added `aria-hidden="true"` to bullet dots (decorative) which was missing in the original LastConsultationTile +--- + +## 2026-02-14 - US-008 +- Added two-column work experience and skills layout inside Patient Pathway ParentSection, below Last Consultation subsection +- Created `src/components/WorkExperienceSubsection.tsx` — lists all 4 roles from consultations.ts with accordion expand (teal dot CardHeader "WORK EXPERIENCE"). Each role shows title, org, date range; expands to show examination bullets, coded entries, and "View full record" link +- Created `src/components/RepeatMedicationsSubsection.tsx` — categorised skills with expand (amber dot CardHeader "REPEAT MEDICATIONS"). Same content as CoreSkillsTile: 3 categories (Technical, Healthcare Domain, Strategic & Leadership), 4 skills per category with "View all" buttons, click to open detail panel +- Added `.pathway-columns` CSS class in index.css — 1 column default, 2 columns at 768px, 16px gap +- Removed standalone CoreSkillsTile from DashboardLayout grid +- Files changed: src/components/DashboardLayout.tsx, src/index.css, src/components/WorkExperienceSubsection.tsx (new), src/components/RepeatMedicationsSubsection.tsx (new) +- **Learnings for future iterations:** + - WorkExperienceSubsection and RepeatMedicationsSubsection are standalone components (not defined inside DashboardLayout) to keep the file manageable — same pattern as LastConsultationSubsection but in separate files due to size + - The two-column grid uses `.pathway-columns` (not `.activity-grid`) to avoid conflicting with the CareerActivityTile's existing responsive grid + - CoreSkillsTile.tsx still exists but is no longer imported from DashboardLayout — US-010 will clean it up + - The RepeatMedicationsSubsection is a near-copy of CoreSkillsTile internals minus the Card wrapper — both files exist until cleanup in US-010 +--- diff --git a/src/components/DashboardLayout.tsx b/src/components/DashboardLayout.tsx index 8127b1e..ba4dcc8 100644 --- a/src/components/DashboardLayout.tsx +++ b/src/components/DashboardLayout.tsx @@ -8,11 +8,12 @@ import { CommandPalette } from './CommandPalette' import { DetailPanel } from './DetailPanel' import { CardHeader } from './Card' import { PatientSummaryTile } from './tiles/PatientSummaryTile' -import { CoreSkillsTile } from './tiles/CoreSkillsTile' import { EducationTile } from './tiles/EducationTile' import { ProjectsTile } from './tiles/ProjectsTile' import { ParentSection } from './ParentSection' import CareerConstellation from './CareerConstellation' +import { WorkExperienceSubsection } from './WorkExperienceSubsection' +import { RepeatMedicationsSubsection } from './RepeatMedicationsSubsection' import { useActiveSection } from '@/hooks/useActiveSection' import { useDetailPanel } from '@/contexts/DetailPanelContext' import { consultations } from '@/data/consultations' @@ -373,9 +374,6 @@ export function DashboardLayout() { {/* ProjectsTile — half width */} - {/* CoreSkillsTile — full width */} - - {/* Patient Pathway — parent section with constellation graph + subsections */} + + {/* Two-column experience/skills grid */} +
+ + +
{/* EducationTile — full width */} diff --git a/src/components/RepeatMedicationsSubsection.tsx b/src/components/RepeatMedicationsSubsection.tsx new file mode 100644 index 0000000..6f58feb --- /dev/null +++ b/src/components/RepeatMedicationsSubsection.tsx @@ -0,0 +1,274 @@ +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 { CardHeader } from './Card' +import { skills } from '@/data/skills' +import { useDetailPanel } from '@/contexts/DetailPanelContext' +import type { SkillMedication, SkillCategory } from '@/types/pmr' + +const iconMap: Record = { + BarChart3, Code2, Database, PieChart, FileCode2, + Sheet, GitBranch, Workflow, Pill, Users, FileCheck, + TrendingUp, Route, ShieldAlert, Banknote, Handshake, + MessageSquare, UserPlus, RefreshCw, Calculator, Presentation, +} + +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 + onClick: () => void +} + +function SkillRow({ skill, onClick }: SkillRowProps) { + const IconComponent = iconMap[skill.icon] + + 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) => { + e.currentTarget.style.borderColor = 'var(--border-light)' + e.currentTarget.style.boxShadow = 'none' + }} + > +
+ {IconComponent && } +
+
+
+ {skill.name} +
+
+ {skill.frequency} · {skill.yearsOfExperience} yrs +
+
+
+ {skill.status} +
+ +
+ ) +} + +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 ( +
+
+ + {label} + +
+ + {categorySkills.length} items + +
+
+ {visibleSkills.map((skill) => ( + onSkillClick(skill)} + /> + ))} +
+ {remainingCount > 0 && ( + + )} +
+ ) +} + +export function RepeatMedicationsSubsection() { + const { openPanel } = useDetailPanel() + + 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 ( +
+ + {groupedSkills.map((group, index) => ( + + ))} +
+ ) +} diff --git a/src/components/WorkExperienceSubsection.tsx b/src/components/WorkExperienceSubsection.tsx new file mode 100644 index 0000000..e743b68 --- /dev/null +++ b/src/components/WorkExperienceSubsection.tsx @@ -0,0 +1,288 @@ +import React, { useState, useCallback } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { ChevronRight } from 'lucide-react' +import { CardHeader } from './Card' +import { consultations } from '@/data/consultations' +import { useDetailPanel } from '@/contexts/DetailPanelContext' + +const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches + +interface RoleItemProps { + consultation: typeof consultations[0] + isExpanded: boolean + onToggle: () => void + onViewFull: () => void +} + +function RoleItem({ consultation, isExpanded, onToggle, onViewFull }: RoleItemProps) { + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onToggle() + } + if (e.key === 'Escape' && isExpanded) { + e.preventDefault() + onToggle() + } + }, + [onToggle, isExpanded], + ) + + return ( +
+ {/* Clickable header */} +
{ + if (!isExpanded) { + e.currentTarget.parentElement!.style.borderColor = 'var(--accent-border)' + e.currentTarget.parentElement!.style.boxShadow = 'var(--shadow-md)' + } + }} + onMouseLeave={(e) => { + if (!isExpanded) { + e.currentTarget.parentElement!.style.borderColor = 'var(--border-light)' + e.currentTarget.parentElement!.style.boxShadow = 'none' + } + }} + > + {/* Teal dot */} + + + {/* Expandable detail content */} + + {isExpanded && ( + +
+ {/* Examination bullets */} +
    + {consultation.examination.map((bullet, i) => ( +
  • +
  • + ))} +
+ + {/* Coded entries */} +
+ {consultation.codedEntries.map((entry) => ( + + {entry.code}: {entry.description} + + ))} +
+ + {/* View full record link */} + +
+
+ )} +
+
+ ) +} + +export function WorkExperienceSubsection() { + const [expandedId, setExpandedId] = useState(null) + const { openPanel } = useDetailPanel() + + const handleToggle = useCallback((id: string) => { + setExpandedId((prev) => (prev === id ? null : id)) + }, []) + + const handleViewFull = useCallback( + (consultation: typeof consultations[0]) => { + openPanel({ type: 'career-role', consultation }) + }, + [openPanel], + ) + + return ( +
+ +
+ {consultations.map((c) => ( + handleToggle(c.id)} + onViewFull={() => handleViewFull(c)} + /> + ))} +
+
+ ) +} diff --git a/src/index.css b/src/index.css index f809533..adb7806 100644 --- a/src/index.css +++ b/src/index.css @@ -300,6 +300,20 @@ html { } } +/* Pathway two-column layout — mobile-first (used in Patient Pathway) */ +.pathway-columns { + display: grid; + grid-template-columns: 1fr; + gap: 16px; +} + +/* Desktop: 2 columns */ +@media (min-width: 768px) { + .pathway-columns { + grid-template-columns: 1fr 1fr; + } +} + /* ===== COMMAND PALETTE ANIMATIONS ===== */ @keyframes palette-overlay-in { from { opacity: 0; }