refactor: remove dead code — orphaned files, unused types and functions
Delete 3 orphaned files (SubNav, TopBar, problems.ts), remove 4 unused type definitions from pmr.ts (ViewId, NavItem, ReferralFormData, Problem), trim types/index.ts to only Phase, and remove unused utility functions (calculateSkillOffset, formatBootLine, getProfileContent, DotColorName).
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
interface NavSection {
|
||||
id: string
|
||||
label: string
|
||||
tileId: string // data-tile-id to scroll to
|
||||
}
|
||||
|
||||
interface SubNavProps {
|
||||
activeSection: string
|
||||
onSectionClick: (sectionId: string) => void
|
||||
}
|
||||
|
||||
const sections: NavSection[] = [
|
||||
{ id: 'overview', label: 'Overview', tileId: 'patient-summary' },
|
||||
{ id: 'skills', label: 'Skills', tileId: 'section-skills' },
|
||||
{ id: 'experience', label: 'Experience', tileId: 'section-experience' },
|
||||
{ id: 'projects', label: 'Significant Interventions', tileId: 'projects' },
|
||||
{ id: 'education', label: 'Education', tileId: 'section-education' },
|
||||
]
|
||||
|
||||
export function SubNav({ activeSection, onSectionClick }: SubNavProps) {
|
||||
const handleSectionClick = (section: NavSection) => {
|
||||
// Scroll to the tile
|
||||
const tileEl = document.querySelector(`[data-tile-id="${section.tileId}"]`)
|
||||
if (tileEl) {
|
||||
tileEl.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
// Notify parent of section change
|
||||
onSectionClick(section.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
aria-label="Section navigation"
|
||||
className="subnav-scroll md:justify-center"
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 'var(--topbar-height)',
|
||||
zIndex: 99,
|
||||
height: 'var(--subnav-height)',
|
||||
background: 'var(--surface)',
|
||||
borderBottom: '1px solid var(--border-light)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '24px',
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
padding: '0 16px',
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
>
|
||||
{sections.map((section) => {
|
||||
const isActive = activeSection === section.id
|
||||
|
||||
return (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => handleSectionClick(section)}
|
||||
aria-current={isActive ? 'true' : undefined}
|
||||
style={{
|
||||
position: 'relative',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
color: isActive ? 'var(--accent)' : 'var(--text-secondary)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
padding: '0 4px 2px',
|
||||
cursor: 'pointer',
|
||||
transition: 'color 200ms ease-out',
|
||||
fontFamily: 'var(--font-ui)',
|
||||
flexShrink: 0,
|
||||
minHeight: '42px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{section.label}
|
||||
{isActive && (
|
||||
<span
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '2px',
|
||||
background: 'var(--accent)',
|
||||
transition: 'all 200ms ease-out',
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Search } from 'lucide-react'
|
||||
import { CvmisLogo } from './CvmisLogo'
|
||||
|
||||
interface TopBarProps {
|
||||
onSearchClick?: () => void
|
||||
}
|
||||
|
||||
export function TopBar({ onSearchClick }: TopBarProps) {
|
||||
const [currentTime, setCurrentTime] = useState(() => formatTime(new Date()))
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(formatTime(new Date()))
|
||||
}, 60_000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header
|
||||
className="fixed top-0 left-0 right-0 flex items-center justify-between font-ui"
|
||||
style={{
|
||||
height: 'var(--topbar-height)',
|
||||
background: 'var(--surface)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
padding: '0 20px',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
{/* Skip to main content link (only visible on focus) */}
|
||||
<a
|
||||
href="#main-content"
|
||||
className="skip-link"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-40px',
|
||||
left: '0',
|
||||
background: 'var(--accent)',
|
||||
color: '#FFFFFF',
|
||||
padding: '8px 16px',
|
||||
textDecoration: 'none',
|
||||
zIndex: 101,
|
||||
borderRadius: '0 0 4px 0',
|
||||
fontSize: '14px',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.top = '0'
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.top = '-40px'
|
||||
}}
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
{/* Brand */}
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<CvmisLogo size={24} />
|
||||
<span
|
||||
className="font-ui hidden sm:inline"
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
>
|
||||
Headhunt Medical Center
|
||||
</span>
|
||||
<span
|
||||
className="font-ui sm:hidden"
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--text-primary)',
|
||||
}}
|
||||
>
|
||||
HMC
|
||||
</span>
|
||||
<span
|
||||
className="hidden lg:inline"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
fontWeight: 400,
|
||||
color: 'var(--text-tertiary)',
|
||||
marginLeft: '2px',
|
||||
}}
|
||||
>
|
||||
Remote
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Search bar (center) — triggers command palette, no inline search */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSearchClick}
|
||||
className="hidden lg:flex items-center gap-2 cursor-pointer font-ui"
|
||||
style={{
|
||||
maxWidth: '560px',
|
||||
minWidth: '400px',
|
||||
height: '46px',
|
||||
border: '1.5px solid var(--border)',
|
||||
borderRadius: 'var(--radius-card)',
|
||||
padding: '0 14px',
|
||||
background: 'var(--surface)',
|
||||
transition: 'border-color 150ms, box-shadow 150ms',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--accent-border)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (document.activeElement !== e.currentTarget) {
|
||||
e.currentTarget.style.borderColor = 'var(--border)'
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||
e.currentTarget.style.boxShadow = '0 0 0 3px rgba(13,110,110,0.12)'
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--border)'
|
||||
e.currentTarget.style.boxShadow = 'none'
|
||||
}}
|
||||
aria-label="Search records, experience, skills. Press Control plus K"
|
||||
>
|
||||
<Search
|
||||
size={17}
|
||||
style={{ color: 'var(--text-tertiary)', flexShrink: 0 }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className="flex-1 text-left"
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
color: 'var(--text-tertiary)',
|
||||
fontFamily: 'var(--font-ui)',
|
||||
}}
|
||||
>
|
||||
Search records, experience, skills...
|
||||
</span>
|
||||
<kbd
|
||||
className="font-geist"
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: 'var(--text-tertiary)',
|
||||
background: 'var(--bg-dashboard)',
|
||||
border: '1px solid var(--border)',
|
||||
padding: '2px 6px',
|
||||
borderRadius: '4px',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
Ctrl+K
|
||||
</kbd>
|
||||
</button>
|
||||
|
||||
{/* Session info (right) */}
|
||||
<div
|
||||
className="flex items-center gap-2 sm:gap-3 shrink-0"
|
||||
aria-label="Active session information"
|
||||
>
|
||||
<span
|
||||
className="hidden sm:inline"
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
color: 'var(--text-secondary)',
|
||||
fontFamily: 'var(--font-ui)',
|
||||
}}
|
||||
>
|
||||
A.RECRUITER
|
||||
</span>
|
||||
<span
|
||||
className="font-geist hidden xs:inline"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: 'var(--text-tertiary)',
|
||||
background: 'var(--accent-light)',
|
||||
padding: '3px 10px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--accent-border)',
|
||||
}}
|
||||
>
|
||||
Active Session · {currentTime}
|
||||
</span>
|
||||
<span
|
||||
className="font-geist xs:hidden"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: 'var(--text-tertiary)',
|
||||
background: 'var(--accent-light)',
|
||||
padding: '3px 8px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--accent-border)',
|
||||
}}
|
||||
>
|
||||
{currentTime}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
function formatTime(date: Date): string {
|
||||
return date.toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
})
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import type { Problem } from '@/types/pmr'
|
||||
|
||||
export const problems: Problem[] = [
|
||||
{
|
||||
id: 'prob-budget',
|
||||
code: 'MGT001',
|
||||
description: '£220M prescribing budget oversight and management',
|
||||
since: 'Jul 2024',
|
||||
status: 'Active',
|
||||
narrative: 'Responsible for managing the £220M prescribing budget for NHS Norfolk & Waveney ICB. Developed sophisticated forecasting models identifying cost pressures and enabling proactive financial planning. This is an ongoing responsibility requiring continuous monitoring and strategic intervention.',
|
||||
linkedConsultations: ['deputy-head-2024'],
|
||||
},
|
||||
{
|
||||
id: 'prob-sql-transform',
|
||||
code: 'TRN001',
|
||||
description: 'Patient-level SQL analytics transformation',
|
||||
since: '2025',
|
||||
status: 'In Progress',
|
||||
narrative: 'Leading transformation from practice-level data to patient-level SQL analytics, enabling targeted interventions and a self-serve model for the wider team. This foundational change will unlock previously impossible analysis at population scale.',
|
||||
linkedConsultations: ['interim-head-2025', 'deputy-head-2024'],
|
||||
},
|
||||
{
|
||||
id: 'prob-data-literacy',
|
||||
code: 'LEA001',
|
||||
description: 'Team data literacy programme',
|
||||
since: 'Jul 2024',
|
||||
status: 'In Progress',
|
||||
narrative: 'Educating colleagues on data interpretation and analytics best practices, improving data fluency across the team through training, documentation, and self-serve tools. Ongoing initiative to build sustainable analytical capability.',
|
||||
linkedConsultations: ['deputy-head-2024'],
|
||||
},
|
||||
{
|
||||
id: 'prob-efficiency',
|
||||
code: 'EFF001',
|
||||
description: 'Manual prescribing analysis inefficiency',
|
||||
resolved: 'Oct 2025',
|
||||
status: 'Resolved',
|
||||
outcome: 'Python algorithm: 14,000 pts, £2.6M/yr',
|
||||
narrative: 'Built Python-based switching algorithm using real-world GP prescribing data to automatically identify patients on expensive drugs suitable for cost-effective alternatives. Compressed months of manual analysis into 3 days. Identified 14,000 patients and £2.6M in annual savings, with £2M on target for delivery this financial year.',
|
||||
linkedConsultations: ['interim-head-2025'],
|
||||
},
|
||||
{
|
||||
id: 'prob-efficiency-target',
|
||||
code: 'EFF002',
|
||||
description: '£14.6M efficiency target identification and delivery',
|
||||
resolved: 'Oct 2025',
|
||||
status: 'Resolved',
|
||||
outcome: 'Over-target performance achieved',
|
||||
narrative: 'Identified and prioritised a £14.6M efficiency programme through comprehensive data analysis. Achieved over-target performance by October 2025 through targeted, evidence-based interventions across the integrated care system.',
|
||||
linkedConsultations: ['interim-head-2025'],
|
||||
},
|
||||
{
|
||||
id: 'prob-blueteq-backlog',
|
||||
code: 'AUT001',
|
||||
description: 'Blueteq form creation backlog',
|
||||
resolved: '2023',
|
||||
status: 'Resolved',
|
||||
outcome: '70% reduction, 200hrs saved',
|
||||
narrative: 'Developed software automating Blueteq prior approval form creation. Achieved 70% reduction in required forms, 200 hours immediate savings, and ongoing 7–8 hours weekly efficiency gains.',
|
||||
linkedConsultations: ['high-cost-drugs-2022'],
|
||||
},
|
||||
{
|
||||
id: 'prob-asthma-screening',
|
||||
code: 'INN001',
|
||||
description: 'Asthma screening scalability',
|
||||
resolved: '2019',
|
||||
status: 'Resolved',
|
||||
outcome: 'National rollout: ~300 branches, ~£1M',
|
||||
narrative: 'Identified and shared an asthma screening process that was adopted nationally across the Tesco pharmacy estate (~300 branches). Reduced pharmacist time from approximately 60 hours to 6 hours per store per month, enabling the network to claim approximately £1M in revenue.',
|
||||
linkedConsultations: ['pharmacy-manager-2017'],
|
||||
},
|
||||
{
|
||||
id: 'prob-incentive-calc',
|
||||
code: 'AUT002',
|
||||
description: 'Incentive scheme manual calculation',
|
||||
resolved: '2025',
|
||||
status: 'Resolved',
|
||||
outcome: 'Automated: 50% Rx reduction in 2 months',
|
||||
narrative: 'Automated incentive scheme analysis, improving accuracy and targeting precision whilst enabling a novel GP payment system linking rewards to delivered savings. Achieved 50% reduction in targeted prescribing within the first two months of deployment.',
|
||||
linkedConsultations: ['interim-head-2025'],
|
||||
},
|
||||
{
|
||||
id: 'prob-hcd-tracking',
|
||||
code: 'DAT001',
|
||||
description: 'High-cost drug spend tracking gaps',
|
||||
resolved: '2023',
|
||||
status: 'Resolved',
|
||||
outcome: 'Blueteq-secondary care data integration',
|
||||
narrative: 'Integrated Blueteq data with secondary care activity databases, resolving critical data-matching limitations and enabling accurate high-cost drug spend tracking across the system.',
|
||||
linkedConsultations: ['high-cost-drugs-2022'],
|
||||
},
|
||||
{
|
||||
id: 'prob-pathway-opacity',
|
||||
code: 'VIS001',
|
||||
description: 'Patient pathway opacity',
|
||||
resolved: '2023',
|
||||
status: 'Resolved',
|
||||
outcome: 'Sankey chart analysis tool',
|
||||
narrative: 'Created Python-based Sankey chart analysis tool visualising patient journeys through high-cost drug pathways, enabling trusts to audit compliance and identify improvement opportunities.',
|
||||
linkedConsultations: ['high-cost-drugs-2022'],
|
||||
},
|
||||
{
|
||||
id: 'prob-opioid-monitoring',
|
||||
code: 'MON001',
|
||||
description: 'Population opioid exposure monitoring',
|
||||
resolved: '2024',
|
||||
status: 'Resolved',
|
||||
outcome: 'CD monitoring system: OME tracking',
|
||||
narrative: 'Developed Python-based controlled drug monitoring system calculating oral morphine equivalents across all opioid prescriptions to track patient-level exposure over time, identifying high-risk patients and potential diversion—enabling previously impossible patient safety analysis at population scale.',
|
||||
linkedConsultations: ['deputy-head-2024'],
|
||||
},
|
||||
]
|
||||
@@ -5,16 +5,11 @@ import type {
|
||||
EducationCopyEntry,
|
||||
ExperienceEducationUICopy,
|
||||
LatestResultsCopy,
|
||||
ProfileContent,
|
||||
QuickActionCopyEntry,
|
||||
SidebarCopy,
|
||||
SkillsUICopy,
|
||||
} from '@/types/profile-content'
|
||||
|
||||
export function getProfileContent(): DeepReadonly<ProfileContent> {
|
||||
return profileContent
|
||||
}
|
||||
|
||||
export function getProfileSummaryText(): string {
|
||||
return profileContent.profile.patientSummaryNarrative
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ export const DOT_COLORS = {
|
||||
purple: '#7C3AED',
|
||||
} as const
|
||||
|
||||
export type DotColorName = keyof typeof DOT_COLORS
|
||||
|
||||
/** KPI color variants (subset of DOT_COLORS) */
|
||||
export const KPI_COLORS: Record<'green' | 'amber' | 'teal', string> = {
|
||||
green: DOT_COLORS.green,
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
export function calculateSkillOffset(level: number, radius: number): number {
|
||||
const circumference = 2 * Math.PI * radius
|
||||
return circumference * (1 - level / 100)
|
||||
}
|
||||
|
||||
export function formatBootLine(text: string): string {
|
||||
return text
|
||||
}
|
||||
|
||||
export function hexToRgba(hex: string, opacity: number): string {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
|
||||
@@ -1,41 +1 @@
|
||||
export interface Skill {
|
||||
name: string
|
||||
level: number
|
||||
category: 'Technical' | 'Clinical' | 'Strategic'
|
||||
color: 'teal' | 'coral'
|
||||
}
|
||||
|
||||
export interface Experience {
|
||||
role: string
|
||||
org: string
|
||||
date: string
|
||||
bullets: string[]
|
||||
isCurrent?: boolean
|
||||
}
|
||||
|
||||
export interface Education {
|
||||
degree: string
|
||||
institution: string
|
||||
period: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
title: string
|
||||
description: string
|
||||
link?: string
|
||||
}
|
||||
|
||||
export interface ContactItem {
|
||||
icon: 'phone' | 'mail' | 'linkedin' | 'mapPin'
|
||||
value: string
|
||||
label: string
|
||||
href?: string
|
||||
}
|
||||
|
||||
export type Phase = 'boot' | 'ecg' | 'login' | 'pmr'
|
||||
|
||||
export interface BootLine {
|
||||
html: string
|
||||
delay: number
|
||||
}
|
||||
|
||||
+1
-45
@@ -48,34 +48,6 @@ export interface PrescribingHistoryEntry {
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface Medication {
|
||||
id: string
|
||||
name: string
|
||||
dose: number
|
||||
frequency: 'Daily' | 'Weekly' | 'Monthly' | 'As needed'
|
||||
startYear: number
|
||||
status: 'Active' | 'Historical'
|
||||
category: 'Active' | 'Clinical' | 'PRN'
|
||||
prescribingHistory: PrescribingHistoryEntry[]
|
||||
}
|
||||
|
||||
export interface Problem {
|
||||
id: string
|
||||
code: string
|
||||
description: string
|
||||
since?: string
|
||||
resolved?: string
|
||||
status: 'Active' | 'In Progress' | 'Resolved'
|
||||
outcome?: string
|
||||
narrative?: string
|
||||
linkedConsultations?: string[]
|
||||
}
|
||||
|
||||
export interface InvestigationResult {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Investigation {
|
||||
id: string
|
||||
name: string
|
||||
@@ -123,23 +95,6 @@ export interface Patient {
|
||||
registrationYear: string
|
||||
}
|
||||
|
||||
export type ViewId = 'summary' | 'consultations' | 'medications' | 'problems' | 'investigations' | 'documents' | 'referrals'
|
||||
|
||||
export interface NavItem {
|
||||
id: ViewId
|
||||
label: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface ReferralFormData {
|
||||
priority: 'Urgent' | 'Routine' | 'Two-Week Wait'
|
||||
referrerName: string
|
||||
referrerEmail: string
|
||||
referrerOrg?: string
|
||||
reason: string
|
||||
contactMethod: 'Email' | 'Phone' | 'LinkedIn'
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
label: string
|
||||
colorVariant: 'teal' | 'amber' | 'green'
|
||||
@@ -171,6 +126,7 @@ export interface SkillMedication {
|
||||
category: 'Technical' | 'Domain' | 'Leadership'
|
||||
status: 'Active' | 'Historical'
|
||||
icon: string
|
||||
prescribingHistory?: PrescribingHistoryEntry[]
|
||||
}
|
||||
|
||||
// Skill categories for grouped display
|
||||
|
||||
Reference in New Issue
Block a user