1b19087782
Move the self-contained LastConsultationSubsection component (191 lines) into its own file as LastConsultationCard. It uses only context and one prop, with no dependency on DashboardLayout state. DashboardLayout drops from 493 to 293 lines.
205 lines
5.8 KiB
TypeScript
205 lines
5.8 KiB
TypeScript
import React from 'react'
|
|
import { ChevronRight } from 'lucide-react'
|
|
import { CardHeader } from './Card'
|
|
import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
|
import { timelineConsultations } from '@/data/timeline'
|
|
import { hexToRgba } from '@/lib/utils'
|
|
import { DEFAULT_ORG_COLOR } from '@/lib/theme-colors'
|
|
|
|
interface LastConsultationCardProps {
|
|
highlightedRoleId?: string | null
|
|
}
|
|
|
|
export function LastConsultationCard({ highlightedRoleId }: LastConsultationCardProps) {
|
|
const { openPanel } = useDetailPanel()
|
|
const consultation = timelineConsultations[0]
|
|
if (!consultation) {
|
|
return null
|
|
}
|
|
const isHighlighted = highlightedRoleId === consultation.id
|
|
|
|
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 (
|
|
<div
|
|
style={{
|
|
marginTop: '24px',
|
|
borderRadius: 'var(--radius-sm)',
|
|
border: '1px solid',
|
|
borderColor: isHighlighted ? hexToRgba(consultation.orgColor ?? DEFAULT_ORG_COLOR, 0.2) : 'transparent',
|
|
background: isHighlighted ? hexToRgba(consultation.orgColor ?? DEFAULT_ORG_COLOR, 0.03) : 'transparent',
|
|
transition: 'border-color 150ms ease-out, background-color 150ms ease-out',
|
|
padding: '8px',
|
|
margin: '-8px',
|
|
}}
|
|
>
|
|
<CardHeader dotColor="green" title="LAST CONSULTATION" rightText="Most recent role" />
|
|
|
|
<div
|
|
role="button"
|
|
tabIndex={0}
|
|
onClick={handleOpenPanel}
|
|
onKeyDown={handleKeyDown}
|
|
style={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
gap: '20px',
|
|
marginBottom: '1=px',
|
|
paddingBottom: '14px',
|
|
borderBottom: '1px solid var(--border-light)',
|
|
cursor: 'pointer',
|
|
borderRadius: 'var(--radius-sm)',
|
|
padding: '8px',
|
|
margin: '-8px -8px 14px -8px',
|
|
transition: 'background-color 150ms ease-out',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.backgroundColor = hexToRgba(consultation.orgColor ?? DEFAULT_ORG_COLOR, 0.04)
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.backgroundColor = 'transparent'
|
|
}}
|
|
aria-label={`View full details for ${consultation.role}`}
|
|
>
|
|
<div>
|
|
<div style={fieldLabelStyle}>Date</div>
|
|
<div style={fieldValueStyle}>{formatDate(consultation.date)}</div>
|
|
</div>
|
|
<div>
|
|
<div style={fieldLabelStyle}>Organisation</div>
|
|
<div style={fieldValueStyle}>{consultation.organization}</div>
|
|
</div>
|
|
<div>
|
|
<div style={fieldLabelStyle}>Type</div>
|
|
<div style={fieldValueStyle}>{getEmploymentType()}</div>
|
|
</div>
|
|
<div>
|
|
<div style={fieldLabelStyle}>Band</div>
|
|
<div style={fieldValueStyle}>{getBand()}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
fontSize: '15px',
|
|
fontWeight: 600,
|
|
color: consultation.orgColor ?? 'var(--accent)',
|
|
marginBottom: '12px',
|
|
}}
|
|
>
|
|
{consultation.role}
|
|
</div>
|
|
|
|
<ul
|
|
style={{
|
|
listStyle: 'none',
|
|
padding: 0,
|
|
margin: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '7px',
|
|
marginBottom: '0px',
|
|
}}
|
|
>
|
|
{consultation.examination.map((bullet, index) => (
|
|
<li
|
|
key={index}
|
|
style={{
|
|
fontSize: '14px',
|
|
color: 'var(--text-primary)',
|
|
paddingLeft: '16px',
|
|
lineHeight: '1.5',
|
|
position: 'relative',
|
|
}}
|
|
>
|
|
<span
|
|
aria-hidden="true"
|
|
style={{
|
|
position: 'absolute',
|
|
left: '0',
|
|
top: '8px',
|
|
width: '5px',
|
|
height: '5px',
|
|
borderRadius: '50%',
|
|
backgroundColor: consultation.orgColor ?? 'var(--accent)',
|
|
opacity: 0.5,
|
|
}}
|
|
/>
|
|
{bullet}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<button
|
|
onClick={handleOpenPanel}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '6px',
|
|
fontSize: '13px',
|
|
fontWeight: 500,
|
|
color: consultation.orgColor ?? 'var(--accent)',
|
|
background: 'transparent',
|
|
border: 'none',
|
|
padding: '6px 0',
|
|
minHeight: '44px',
|
|
cursor: 'pointer',
|
|
transition: 'opacity 150ms ease-out',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.opacity = '0.7'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.opacity = '1'
|
|
}}
|
|
aria-label="View full consultation record"
|
|
>
|
|
<span>View full record</span>
|
|
<ChevronRight size={15} strokeWidth={2.5} />
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|