feat: US-010 - Clean up removed standalone tiles and verify layout
This commit is contained in:
@@ -61,7 +61,7 @@ export function SkillsAllDetail({ category }: SkillsAllDetailProps) {
|
||||
key={group.id}
|
||||
ref={(el) => { categoryRefs.current[group.id] = el }}
|
||||
>
|
||||
{/* Category header — matches CoreSkillsTile divider style */}
|
||||
{/* Category header — divider style */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { Card, CardHeader } from '../Card'
|
||||
import { documents } from '@/data/documents'
|
||||
import { consultations } from '@/data/consultations'
|
||||
import { skills } from '@/data/skills'
|
||||
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
import CareerConstellation from '../CareerConstellation'
|
||||
|
||||
type ActivityType = 'role' | 'project' | 'cert' | 'edu'
|
||||
|
||||
interface ActivityEntry {
|
||||
id: string
|
||||
type: ActivityType
|
||||
title: string
|
||||
meta: string
|
||||
date: string
|
||||
sortYear: number
|
||||
/** ID of the corresponding consultation in consultations.ts (role entries only) */
|
||||
consultationId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Build timeline from multiple data sources
|
||||
* Matches the concept HTML entries exactly
|
||||
*/
|
||||
function buildTimeline(): ActivityEntry[] {
|
||||
const entries: ActivityEntry[] = []
|
||||
|
||||
// Roles from consultations (matching CV_v4.md)
|
||||
entries.push({
|
||||
id: 'interim-head-2025',
|
||||
type: 'role',
|
||||
title: 'Interim Head, Population Health & Data Analysis',
|
||||
meta: 'NHS Norfolk & Waveney ICB',
|
||||
date: 'May – Nov 2025',
|
||||
sortYear: 2025,
|
||||
consultationId: 'interim-head-2025',
|
||||
})
|
||||
|
||||
entries.push({
|
||||
id: 'deputy-head-2024',
|
||||
type: 'role',
|
||||
title: 'Deputy Head, Population Health & Data Analysis',
|
||||
meta: 'NHS Norfolk & Waveney ICB',
|
||||
date: 'Jul 2024 – Present',
|
||||
sortYear: 2024,
|
||||
consultationId: 'deputy-head-2024',
|
||||
})
|
||||
|
||||
entries.push({
|
||||
id: 'high-cost-drugs-2022',
|
||||
type: 'role',
|
||||
title: 'High-Cost Drugs & Interface Pharmacist',
|
||||
meta: 'NHS Norfolk & Waveney ICB',
|
||||
date: 'May 2022 – Jul 2024',
|
||||
sortYear: 2022,
|
||||
consultationId: 'high-cost-drugs-2022',
|
||||
})
|
||||
|
||||
entries.push({
|
||||
id: 'pharmacy-manager-2017',
|
||||
type: 'role',
|
||||
title: 'Pharmacy Manager',
|
||||
meta: 'Tesco PLC',
|
||||
date: 'Nov 2017 – May 2022',
|
||||
sortYear: 2017,
|
||||
consultationId: 'pharmacy-manager-2017',
|
||||
})
|
||||
|
||||
// Certifications (matching CV_v4.md)
|
||||
entries.push({
|
||||
id: 'doc-gphc',
|
||||
type: 'cert',
|
||||
title: 'GPhC Registration',
|
||||
meta: 'General Pharmaceutical Council',
|
||||
date: 'August 2016',
|
||||
sortYear: 2016,
|
||||
})
|
||||
|
||||
entries.push({
|
||||
id: 'cert-mary-seacole',
|
||||
type: 'cert',
|
||||
title: 'NHS Leadership Academy — Mary Seacole Programme',
|
||||
meta: 'NHS leadership qualification',
|
||||
date: '2018',
|
||||
sortYear: 2018,
|
||||
})
|
||||
|
||||
// Education (matching CV_v4.md)
|
||||
const mpharm = documents.find((d) => d.id === 'doc-mpharm')
|
||||
if (mpharm) {
|
||||
entries.push({
|
||||
id: mpharm.id,
|
||||
type: 'edu',
|
||||
title: 'MPharm (Hons) — 2:1',
|
||||
meta: 'University of East Anglia',
|
||||
date: '2011 – 2015',
|
||||
sortYear: 2011,
|
||||
})
|
||||
}
|
||||
|
||||
entries.push({
|
||||
id: 'edu-alevels',
|
||||
type: 'edu',
|
||||
title: 'A-Levels: Mathematics (A*), Chemistry (B), Politics (C)',
|
||||
meta: 'Highworth Grammar School',
|
||||
date: '2009 – 2011',
|
||||
sortYear: 2009,
|
||||
})
|
||||
|
||||
return entries.sort((a, b) => {
|
||||
if (b.sortYear !== a.sortYear) return b.sortYear - a.sortYear
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const dotColorMap: Record<ActivityType, string> = {
|
||||
role: '#0D6E6E',
|
||||
project: '#D97706',
|
||||
cert: '#059669',
|
||||
edu: '#7C3AED',
|
||||
}
|
||||
|
||||
interface ActivityItemProps {
|
||||
entry: ActivityEntry
|
||||
onItemClick: () => void
|
||||
}
|
||||
|
||||
const ActivityItem: React.FC<ActivityItemProps> = ({ entry, onItemClick }) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const dotColor = dotColorMap[entry.type]
|
||||
const isClickable = entry.type === 'role' && entry.consultationId
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (!isClickable) return
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
onItemClick()
|
||||
}
|
||||
},
|
||||
[isClickable, onItemClick],
|
||||
)
|
||||
|
||||
// Get consultation data for preview text
|
||||
const consultation = isClickable
|
||||
? consultations.find((c) => c.id === entry.consultationId)
|
||||
: null
|
||||
|
||||
// Get preview text (first 1-2 lines from examination)
|
||||
const previewText =
|
||||
consultation && consultation.examination.length > 0
|
||||
? consultation.examination[0]
|
||||
: null
|
||||
|
||||
return (
|
||||
<div
|
||||
role={isClickable ? 'button' : undefined}
|
||||
tabIndex={isClickable ? 0 : undefined}
|
||||
onClick={isClickable ? onItemClick : undefined}
|
||||
onKeyDown={isClickable ? handleKeyDown : undefined}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: 'var(--bg-dashboard)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--border-light)',
|
||||
fontSize: '12px',
|
||||
transition: 'all 0.15s ease-out',
|
||||
cursor: isClickable ? 'pointer' : 'default',
|
||||
transform: isHovered && isClickable ? 'translateY(-1px)' : 'none',
|
||||
boxShadow: isHovered && isClickable
|
||||
? '0 2px 8px rgba(26,43,42,0.08)'
|
||||
: '0 1px 2px rgba(26,43,42,0.05)',
|
||||
borderColor: isHovered && isClickable ? 'var(--accent-border)' : 'var(--border-light)',
|
||||
}}
|
||||
>
|
||||
{/* Item header row */}
|
||||
<div style={{ display: 'flex', gap: '10px', padding: '10px 12px' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
background: dotColor,
|
||||
flexShrink: 0,
|
||||
marginTop: '2px',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-primary)',
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{entry.title}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--text-secondary)',
|
||||
marginTop: '2px',
|
||||
}}
|
||||
>
|
||||
{entry.meta}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
fontFamily: 'var(--font-mono)',
|
||||
color: 'var(--text-tertiary)',
|
||||
marginTop: '3px',
|
||||
}}
|
||||
>
|
||||
{entry.date}
|
||||
</div>
|
||||
|
||||
{/* Hover preview text for roles */}
|
||||
{isHovered && previewText && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--text-secondary)',
|
||||
marginTop: '6px',
|
||||
lineHeight: 1.4,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
>
|
||||
{previewText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CareerActivityTile: React.FC = () => {
|
||||
const timeline = buildTimeline()
|
||||
const { openPanel } = useDetailPanel()
|
||||
|
||||
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 handleItemClick = useCallback(
|
||||
(entry: ActivityEntry) => {
|
||||
if (entry.type === 'role' && entry.consultationId) {
|
||||
handleRoleClick(entry.consultationId)
|
||||
}
|
||||
},
|
||||
[handleRoleClick],
|
||||
)
|
||||
|
||||
return (
|
||||
<Card full tileId="career-activity">
|
||||
<CardHeader dotColor="teal" title="CAREER ACTIVITY" rightText="Full timeline" />
|
||||
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<CareerConstellation
|
||||
onRoleClick={handleRoleClick}
|
||||
onSkillClick={handleSkillClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="activity-grid">
|
||||
{timeline.map((entry) => (
|
||||
<ActivityItem
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
onItemClick={() => handleItemClick(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
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 { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
import type { SkillMedication, SkillCategory } from '@/types/pmr'
|
||||
|
||||
const iconMap: Record<string, LucideIcon> = {
|
||||
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 (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-label={`${skill.name}: ${skill.frequency}, ${skill.yearsOfExperience} years experience. Click for details.`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
padding: '8px 10px',
|
||||
minHeight: '44px',
|
||||
background: 'var(--bg-dashboard)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid var(--border-light)',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.15s, box-shadow 0.15s',
|
||||
}}
|
||||
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'
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
style={{
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
borderRadius: '6px',
|
||||
background: 'var(--accent-light)',
|
||||
color: 'var(--accent)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{IconComponent && <IconComponent size={13} />}
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12.5px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-primary)',
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{skill.name}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '10.5px',
|
||||
color: 'var(--text-tertiary)',
|
||||
fontFamily: '"Geist Mono", monospace',
|
||||
}}
|
||||
>
|
||||
{skill.frequency} · {skill.yearsOfExperience} yrs
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status badge */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
fontWeight: 500,
|
||||
padding: '2px 7px',
|
||||
borderRadius: '20px',
|
||||
background: 'var(--success-light)',
|
||||
color: 'var(--success)',
|
||||
border: '1px solid var(--success-border)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{skill.status}
|
||||
</div>
|
||||
|
||||
{/* Chevron */}
|
||||
<ChevronRight
|
||||
size={14}
|
||||
style={{ color: 'var(--text-tertiary)', flexShrink: 0 }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div style={{ marginTop: isFirst ? 0 : '16px' }}>
|
||||
{/* Category header — sidebar section divider style */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
fontWeight: 600,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.06em',
|
||||
color: 'var(--text-tertiary)',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
height: '1px',
|
||||
background: 'var(--border-light)',
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
color: 'var(--text-tertiary)',
|
||||
fontFamily: '"Geist Mono", monospace',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{categorySkills.length} items
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Skill rows */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
{visibleSkills.map((skill) => (
|
||||
<SkillRow
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
onClick={() => onSkillClick(skill)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* View all button */}
|
||||
{remainingCount > 0 && (
|
||||
<button
|
||||
onClick={() => onViewAll(categoryId)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
marginTop: '8px',
|
||||
padding: '4px 0',
|
||||
minHeight: '44px',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '11px',
|
||||
fontWeight: 500,
|
||||
color: 'var(--accent)',
|
||||
fontFamily: 'inherit',
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent-hover)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent)'
|
||||
}}
|
||||
aria-label={`View all ${categorySkills.length} ${label} skills`}
|
||||
>
|
||||
View all ({categorySkills.length})
|
||||
<ChevronRight size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CoreSkillsTile() {
|
||||
const { openPanel } = useDetailPanel()
|
||||
|
||||
// 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 (
|
||||
<Card full tileId="core-skills">
|
||||
<CardHeader
|
||||
dotColor="amber"
|
||||
title="REPEAT MEDICATIONS"
|
||||
rightText="Active prescriptions"
|
||||
/>
|
||||
{groupedSkills.map((group, index) => (
|
||||
<CategorySection
|
||||
key={group.id}
|
||||
label={group.label}
|
||||
categoryId={group.id}
|
||||
skills={group.skills}
|
||||
onSkillClick={handleSkillClick}
|
||||
onViewAll={handleViewAll}
|
||||
isFirst={index === 0}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Card, CardHeader } from '../Card'
|
||||
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 LatestResultsTile() {
|
||||
const gridStyles: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '12px',
|
||||
}
|
||||
|
||||
return (
|
||||
<Card tileId="latest-results">
|
||||
<CardHeader dotColor="teal" title="LATEST RESULTS" rightText="Updated May 2025" />
|
||||
<div style={gridStyles}>
|
||||
{kpis.map((kpi) => (
|
||||
<MetricCard key={kpi.id} kpi={kpi} />
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -286,20 +286,6 @@ html {
|
||||
}
|
||||
|
||||
|
||||
/* Activity grid responsive — mobile-first (used in CareerActivityTile) */
|
||||
.activity-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Tablet and up: 2 columns */
|
||||
@media (min-width: 768px) {
|
||||
.activity-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pathway two-column layout — mobile-first (used in Patient Pathway) */
|
||||
.pathway-columns {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user