feat: US-005 - Restructure Patient Summary as parent section with Latest Results subsection
This commit is contained in:
@@ -6,7 +6,6 @@ import Sidebar from './Sidebar'
|
||||
import { CommandPalette } from './CommandPalette'
|
||||
import { DetailPanel } from './DetailPanel'
|
||||
import { PatientSummaryTile } from './tiles/PatientSummaryTile'
|
||||
import { LatestResultsTile } from './tiles/LatestResultsTile'
|
||||
import { CoreSkillsTile } from './tiles/CoreSkillsTile'
|
||||
import { LastConsultationTile } from './tiles/LastConsultationTile'
|
||||
import { CareerActivityTile } from './tiles/CareerActivityTile'
|
||||
@@ -165,12 +164,10 @@ export function DashboardLayout() {
|
||||
}}
|
||||
>
|
||||
<div className="dashboard-grid">
|
||||
{/* PatientSummaryTile — full width */}
|
||||
{/* PatientSummaryTile — full width (includes Latest Results subsection) */}
|
||||
<PatientSummaryTile />
|
||||
|
||||
{/* LatestResultsTile — half width (left) */}
|
||||
<LatestResultsTile />
|
||||
{/* ProjectsTile — half width (right) */}
|
||||
{/* ProjectsTile — half width */}
|
||||
<ProjectsTile />
|
||||
|
||||
{/* CoreSkillsTile — full width */}
|
||||
|
||||
@@ -1,54 +1,105 @@
|
||||
import React from 'react'
|
||||
import { Card, CardHeader } from '../Card'
|
||||
import { CardHeader } from '../Card'
|
||||
import { ParentSection } from '../ParentSection'
|
||||
import { kpis } from '@/data/kpis'
|
||||
import type { KPI } from '@/types/pmr'
|
||||
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
|
||||
const colorMap: Record<KPI['colorVariant'], string> = {
|
||||
green: '#059669',
|
||||
amber: '#D97706',
|
||||
teal: '#0D6E6E',
|
||||
}
|
||||
|
||||
interface MetricCardProps {
|
||||
kpi: KPI
|
||||
}
|
||||
|
||||
function MetricCard({ kpi }: MetricCardProps) {
|
||||
const { openPanel } = useDetailPanel()
|
||||
|
||||
const handleClick = () => {
|
||||
openPanel({ type: 'kpi', kpi })
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
openPanel({ type: 'kpi', kpi })
|
||||
}
|
||||
}
|
||||
|
||||
const buttonStyles: React.CSSProperties = {
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
padding: '16px',
|
||||
background: 'var(--surface)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 150ms ease-out, box-shadow 150ms ease-out',
|
||||
}
|
||||
|
||||
const valueStyles: React.CSSProperties = {
|
||||
fontSize: '28px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '-0.02em',
|
||||
lineHeight: 1.2,
|
||||
color: colorMap[kpi.colorVariant],
|
||||
}
|
||||
|
||||
const labelStyles: React.CSSProperties = {
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
color: 'var(--text-primary)',
|
||||
marginTop: '4px',
|
||||
}
|
||||
|
||||
const subStyles: React.CSSProperties = {
|
||||
fontSize: '10px',
|
||||
color: 'var(--text-tertiary)',
|
||||
fontFamily: 'var(--font-geist-mono)',
|
||||
marginTop: '2px',
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
style={buttonStyles}
|
||||
aria-label={`${kpi.label}: ${kpi.value}. Click to view details.`}
|
||||
onMouseEnter={(e) => {
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<div style={valueStyles}>{kpi.value}</div>
|
||||
<div style={labelStyles}>{kpi.label}</div>
|
||||
<div style={subStyles}>{kpi.sub}</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function PatientSummaryTile() {
|
||||
// Key statistics from CV_v4.md
|
||||
const highlights = [
|
||||
{ label: '9+ Years', sublabel: 'Professional Experience' },
|
||||
{ label: '1.2M', sublabel: 'Population Served' },
|
||||
{ label: '£220M', sublabel: 'Budget Managed' },
|
||||
{ label: '£14.6M+', sublabel: 'Savings Identified' },
|
||||
]
|
||||
|
||||
const highlightStripStyles: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
|
||||
gap: '12px',
|
||||
marginBottom: '20px',
|
||||
paddingBottom: '20px',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
}
|
||||
|
||||
const highlightItemStyles: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
}
|
||||
|
||||
const highlightValueStyles: React.CSSProperties = {
|
||||
fontSize: '18px',
|
||||
fontWeight: 700,
|
||||
color: 'var(--accent)',
|
||||
fontFamily: 'var(--font-ui)',
|
||||
}
|
||||
|
||||
const highlightLabelStyles: React.CSSProperties = {
|
||||
fontSize: '11px',
|
||||
fontWeight: 500,
|
||||
color: 'var(--text-secondary)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.02em',
|
||||
}
|
||||
|
||||
const profileTextStyles: React.CSSProperties = {
|
||||
fontSize: '13px',
|
||||
lineHeight: '1.6',
|
||||
color: 'var(--text-primary)',
|
||||
}
|
||||
|
||||
// Split profile text into structured sections with bold key phrases
|
||||
const renderProfileWithHierarchy = () => {
|
||||
return (
|
||||
const kpiGridStyles: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
}
|
||||
|
||||
return (
|
||||
<ParentSection title="Patient Summary" tileId="patient-summary">
|
||||
{/* Profile text */}
|
||||
<div style={profileTextStyles}>
|
||||
<strong>Healthcare leader</strong> combining clinical pharmacy expertise with proficiency in{' '}
|
||||
<strong>Python, SQL, and data analytics</strong>, self-taught over the past decade through a drive to find root causes in data and build the most efficient solutions to complex problems. Currently{' '}
|
||||
@@ -58,25 +109,16 @@ export function PatientSummaryTile() {
|
||||
<strong>£14.6M+</strong> through automated, data-driven analysis. Skilled at translating complex clinical, financial, and analytical requirements into clear recommendations for{' '}
|
||||
<strong>executive stakeholders</strong>.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card full tileId="patient-summary">
|
||||
<CardHeader dotColor="teal" title="PATIENT SUMMARY" />
|
||||
|
||||
{/* Highlight strip with key stats */}
|
||||
<div style={highlightStripStyles}>
|
||||
{highlights.map((highlight, idx) => (
|
||||
<div key={idx} style={highlightItemStyles}>
|
||||
<div style={highlightValueStyles}>{highlight.label}</div>
|
||||
<div style={highlightLabelStyles}>{highlight.sublabel}</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Latest Results subsection */}
|
||||
<div style={{ marginTop: '24px' }}>
|
||||
<CardHeader dotColor="teal" title="LATEST RESULTS" rightText="Updated May 2025" />
|
||||
<div style={kpiGridStyles}>
|
||||
{kpis.map((kpi) => (
|
||||
<MetricCard key={kpi.id} kpi={kpi} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Profile text with visual hierarchy through bold key phrases */}
|
||||
{renderProfileWithHierarchy()}
|
||||
</Card>
|
||||
</ParentSection>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user