From ee73efce11da22e982ea2b7d43030f01a8a4c82b Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Fri, 13 Feb 2026 22:57:28 +0000 Subject: [PATCH] US-001: Remove unused legacy components and hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete 23 dead files: old portfolio components (Contact, Education, Experience, FloatingNav, Footer, Hero, Projects, Skills), legacy PMR components (PMRInterface, PatientBanner, ClinicalSidebar, Breadcrumb, MobileBottomNav), all 7 views/ directory files, and 3 unused hooks (useScrollCondensation, useActiveSection, useScrollReveal). No imports referenced any of these files — clean removal with zero build or type errors. Co-Authored-By: Claude Opus 4.6 --- src/components/Breadcrumb.tsx | 96 ---- src/components/ClinicalSidebar.tsx | 406 ---------------- src/components/Contact.tsx | 108 ----- src/components/Education.tsx | 86 ---- src/components/Experience.tsx | 164 ------- src/components/FloatingNav.tsx | 68 --- src/components/Footer.tsx | 36 -- src/components/Hero.tsx | 85 ---- src/components/MobileBottomNav.tsx | 69 --- src/components/PMRInterface.tsx | 284 ------------ src/components/PatientBanner.tsx | 380 --------------- src/components/Projects.tsx | 105 ----- src/components/Skills.tsx | 193 -------- src/components/views/ConsultationsView.tsx | 249 ---------- src/components/views/DocumentsView.tsx | 344 -------------- src/components/views/InvestigationsView.tsx | 390 ---------------- src/components/views/MedicationsView.tsx | 433 ----------------- src/components/views/ProblemsView.tsx | 448 ------------------ src/components/views/ReferralsView.tsx | 487 -------------------- src/components/views/SummaryView.tsx | 462 ------------------- src/hooks/useActiveSection.ts | 58 --- src/hooks/useScrollCondensation.ts | 30 -- src/hooks/useScrollReveal.ts | 40 -- 23 files changed, 5021 deletions(-) delete mode 100644 src/components/Breadcrumb.tsx delete mode 100644 src/components/ClinicalSidebar.tsx delete mode 100644 src/components/Contact.tsx delete mode 100644 src/components/Education.tsx delete mode 100644 src/components/Experience.tsx delete mode 100644 src/components/FloatingNav.tsx delete mode 100644 src/components/Footer.tsx delete mode 100644 src/components/Hero.tsx delete mode 100644 src/components/MobileBottomNav.tsx delete mode 100644 src/components/PMRInterface.tsx delete mode 100644 src/components/PatientBanner.tsx delete mode 100644 src/components/Projects.tsx delete mode 100644 src/components/Skills.tsx delete mode 100644 src/components/views/ConsultationsView.tsx delete mode 100644 src/components/views/DocumentsView.tsx delete mode 100644 src/components/views/InvestigationsView.tsx delete mode 100644 src/components/views/MedicationsView.tsx delete mode 100644 src/components/views/ProblemsView.tsx delete mode 100644 src/components/views/ReferralsView.tsx delete mode 100644 src/components/views/SummaryView.tsx delete mode 100644 src/hooks/useActiveSection.ts delete mode 100644 src/hooks/useScrollCondensation.ts delete mode 100644 src/hooks/useScrollReveal.ts diff --git a/src/components/Breadcrumb.tsx b/src/components/Breadcrumb.tsx deleted file mode 100644 index 7a4c88a..0000000 --- a/src/components/Breadcrumb.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { ChevronRight } from 'lucide-react' -import type { ViewId } from '../types/pmr' - -interface BreadcrumbProps { - currentView: ViewId - expandedItem?: { - name: string - type: string - } - onNavigateToView?: (view: ViewId) => void - onCollapseItem?: () => void -} - -const viewLabels: Record = { - summary: 'Summary', - consultations: 'Experience', - medications: 'Skills', - problems: 'Achievements', - investigations: 'Projects', - documents: 'Education', - referrals: 'Contact', -} - -export function Breadcrumb({ - currentView, - expandedItem, - onNavigateToView, - onCollapseItem, -}: BreadcrumbProps) { - const handleNavigateToPatientRecord = () => { - if (onNavigateToView) { - onNavigateToView('summary') - } - } - - const handleNavigateToCurrentView = () => { - if (onCollapseItem) { - onCollapseItem() - } - } - - return ( - - ) -} diff --git a/src/components/ClinicalSidebar.tsx b/src/components/ClinicalSidebar.tsx deleted file mode 100644 index 736741a..0000000 --- a/src/components/ClinicalSidebar.tsx +++ /dev/null @@ -1,406 +0,0 @@ -import { useState, useEffect, useCallback, useMemo, useRef } from 'react' -import { - ClipboardList, - FileText, - Pill, - AlertTriangle, - FlaskConical, - FolderOpen, - Send, - Search, - X, -} from 'lucide-react' -import type { ViewId } from '../types/pmr' -import { useAccessibility } from '../contexts/AccessibilityContext' -import { buildLegacySearchIndex, groupResultsBySection, type SearchResult } from '../lib/search' -import type { FuseResult } from 'fuse.js' - -interface NavItem { - id: ViewId - label: string - icon: React.ReactNode -} - -interface ClinicalSidebarProps { - activeView: ViewId - onViewChange: (view: ViewId) => void - isTablet?: boolean -} - -const navItems: NavItem[] = [ - { id: 'summary', label: 'Summary', icon: }, - { id: 'consultations', label: 'Experience', icon: }, - { id: 'medications', label: 'Skills', icon: }, - { id: 'problems', label: 'Achievements', icon: }, - { id: 'investigations', label: 'Projects', icon: }, - { id: 'documents', label: 'Education', icon: }, - { id: 'referrals', label: 'Contact', icon: }, -] - -function getCurrentTime(): string { - const now = new Date() - return now.toLocaleTimeString('en-GB', { - hour: '2-digit', - minute: '2-digit', - }) -} - -export function ClinicalSidebar({ activeView, onViewChange, isTablet = false }: ClinicalSidebarProps) { - const [currentTime, setCurrentTime] = useState(getCurrentTime) - const [searchQuery, setSearchQuery] = useState('') - const [isSearchFocused, setIsSearchFocused] = useState(false) - const [focusedIndex, setFocusedIndex] = useState(null) - const [hoveredItem, setHoveredItem] = useState(null) - const navButtonRefs = useRef<(HTMLButtonElement | null)[]>([]) - const { focusAfterLoginRef, setExpandedItem } = useAccessibility() - - // Build search index once on mount - const searchIndex = useMemo(() => buildLegacySearchIndex(), []) - - const handleNavClick = useCallback( - (view: ViewId) => { - onViewChange(view) - window.location.hash = view - }, - [onViewChange] - ) - - const handleNavKeyDown = useCallback((e: React.KeyboardEvent, index: number) => { - switch (e.key) { - case 'ArrowDown': - e.preventDefault() - if (index < navItems.length - 1) { - setFocusedIndex(index + 1) - navButtonRefs.current[index + 1]?.focus() - } - break - case 'ArrowUp': - e.preventDefault() - if (index > 0) { - setFocusedIndex(index - 1) - navButtonRefs.current[index - 1]?.focus() - } - break - case 'Enter': - case ' ': - e.preventDefault() - handleNavClick(navItems[index].id) - break - case 'Home': - e.preventDefault() - setFocusedIndex(0) - navButtonRefs.current[0]?.focus() - break - case 'End': - e.preventDefault() - setFocusedIndex(navItems.length - 1) - navButtonRefs.current[navItems.length - 1]?.focus() - break - } - }, [handleNavClick]) - - // Update clock every minute - useEffect(() => { - const interval = setInterval(() => { - setCurrentTime(getCurrentTime()) - }, 60000) - return () => clearInterval(interval) - }, []) - - // Hash routing - useEffect(() => { - const handleHashChange = () => { - const hash = window.location.hash.slice(1) as ViewId - if (navItems.some(item => item.id === hash)) { - onViewChange(hash) - } - } - - handleHashChange() - window.addEventListener('hashchange', handleHashChange) - return () => window.removeEventListener('hashchange', handleHashChange) - }, [onViewChange]) - - // Alt+1-7 keyboard shortcuts and "/" for search - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.altKey && e.key >= '1' && e.key <= '7') { - e.preventDefault() - const index = parseInt(e.key) - 1 - if (navItems[index]) { - const view = navItems[index].id - onViewChange(view) - window.location.hash = view - } - } - if (e.key === '/' && !isSearchFocused && document.activeElement?.tagName !== 'INPUT') { - e.preventDefault() - const searchInput = document.getElementById('sidebar-search') - searchInput?.focus() - } - } - - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown) - }, [onViewChange, isSearchFocused]) - - // Set focus-after-login ref to first nav button - useEffect(() => { - if (navButtonRefs.current[0]) { - ;(focusAfterLoginRef as React.MutableRefObject).current = navButtonRefs.current[0] - } - }, [focusAfterLoginRef]) - - const handleSearchKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - setSearchQuery('') - ;(e.target as HTMLInputElement).blur() - } - } - - const clearSearch = () => { - setSearchQuery('') - const searchInput = document.getElementById('sidebar-search') - searchInput?.focus() - } - - // Fuzzy search with fuse.js - const searchResults = useMemo(() => { - if (!searchQuery.trim() || searchQuery.length < 2) return [] - const results = searchIndex.search(searchQuery) - return results.slice(0, 10) // Limit to top 10 results - }, [searchQuery, searchIndex]) - - // Group results by section for organized display - const groupedResults = useMemo(() => { - if (searchResults.length === 0) return new Map() - return groupResultsBySection(searchResults) - }, [searchResults]) - - const handleSearchResultClick = useCallback( - (result: FuseResult) => { - // Navigate to the section - onViewChange(result.item.section) - window.location.hash = result.item.section - - // Expand the matching item - setExpandedItem(result.item.id) - - // Clear search - setSearchQuery('') - }, - [onViewChange, setExpandedItem] - ) - - // ── Tablet: 56px icon-only sidebar ── - if (isTablet) { - return ( - - ) - } - - // ── Desktop: 220px full sidebar ── - return ( - - ) -} diff --git a/src/components/Contact.tsx b/src/components/Contact.tsx deleted file mode 100644 index 86a5f33..0000000 --- a/src/components/Contact.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { motion } from 'framer-motion' -import { Phone, Mail, Linkedin, MapPin } from 'lucide-react' -import { useScrollReveal } from '@/hooks/useScrollReveal' -import type { ContactItem } from '@/types' - -const contactData: ContactItem[] = [ - { - icon: 'phone', - value: '07795553088', - label: 'Phone', - }, - { - icon: 'mail', - value: 'andy@charlwood.xyz', - label: 'Email', - href: 'mailto:andy@charlwood.xyz', - }, - { - icon: 'linkedin', - value: 'linkedin.com/in/andrewcharlwood', - label: 'LinkedIn', - href: 'https://linkedin.com/in/andrewcharlwood', - }, - { - icon: 'mapPin', - value: 'Norwich, UK', - label: 'Location', - }, -] - -const iconMap = { - phone: Phone, - mail: Mail, - linkedin: Linkedin, - mapPin: MapPin, -} - -const ContactItemCard = ({ - item, - delay, - isVisible, -}: { - item: ContactItem - delay: number - isVisible: boolean -}) => { - const Icon = iconMap[item.icon] - - return ( - -
- -
-
- {item.href ? ( - - {item.value} - - ) : ( - item.value - )} -
-
- {item.label} -
-
- ) -} - -export function Contact() { - const [sectionRef, isVisible] = useScrollReveal({ - threshold: 0.1, - }) - - return ( -
- - Contact - - -
- {contactData.map((item, index) => ( - - ))} -
-
- ) -} diff --git a/src/components/Education.tsx b/src/components/Education.tsx deleted file mode 100644 index c3da69e..0000000 --- a/src/components/Education.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { motion } from 'framer-motion' -import { useScrollReveal } from '@/hooks/useScrollReveal' -import type { Education as EducationType } from '@/types' - -const educationData: EducationType[] = [ - { - degree: 'MPharm (Hons) Pharmacy', - institution: 'University of East Anglia', - period: '2011 — 2015', - detail: 'Upper Second-Class Honours (2:1)', - }, - { - degree: 'Mary Seacole Leadership Programme', - institution: 'NHS Leadership Academy', - period: '2018', - detail: 'National healthcare leadership development programme.', - }, -] - -const EducationCard = ({ - education, - delay, - isVisible, -}: { - education: EducationType - delay: number - isVisible: boolean -}) => { - return ( - -
-

- {education.degree} -

-

{education.institution}

-

{education.period}

-

- {education.detail} -

- - ) -} - -export function Education() { - const [sectionRef, isVisible] = useScrollReveal({ - threshold: 0.1, - }) - - return ( -
- - Education - - -
- {educationData.map((education, index) => ( - - ))} -
- - - A-Levels: Mathematics (A*), Chemistry (B), Politics (C) - -
- ) -} diff --git a/src/components/Experience.tsx b/src/components/Experience.tsx deleted file mode 100644 index 97f945f..0000000 --- a/src/components/Experience.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { motion } from 'framer-motion' -import { useScrollReveal } from '@/hooks/useScrollReveal' -import type { Experience as ExperienceType } from '@/types' - -const experiences: ExperienceType[] = [ - { - role: 'Interim Head of Population Health & Data Analysis', - org: 'NHS Norfolk & Waveney ICB', - date: 'May 2025 — Nov 2025', - bullets: [ - 'Led team through organisational transition, maintaining delivery of £14.6M efficiency programme', - 'Directed strategic priorities for population health analytics across Norfolk & Waveney (population ~1M)', - 'Managed stakeholder relationships with system leaders, provider trusts, and primary care networks', - ], - isCurrent: true, - }, - { - role: 'Deputy Head of Population Health & Data Analysis', - org: 'NHS Norfolk & Waveney ICB', - date: 'Jul 2024 — Present', - bullets: [ - 'Deputised for Head of department across all operational and strategic functions', - 'Oversaw £220M medicines budget and led programme of cost improvement initiatives', - 'Developed Python-based switching algorithm processing 14,000 patients, delivering £2.6M savings', - 'Built Blueteq automation system reducing processing time by 70%, saving 200+ hours annually', - 'Created PharMetrics dashboard platform for real-time medicines expenditure tracking', - ], - isCurrent: true, - }, - { - role: 'High-Cost Drugs & Interface Pharmacist', - org: 'NHS Norfolk & Waveney ICB', - date: 'May 2022 — Jul 2024', - bullets: [ - 'Managed high-cost drugs budget across acute and community settings', - 'Led NICE Technology Appraisal implementation and horizon scanning', - 'Developed health economic models for biosimilar switching programmes', - 'Built data pipelines for automated reporting of medicines expenditure', - ], - isCurrent: false, - }, - { - role: 'Pharmacy Manager', - org: 'Tesco Pharmacy', - date: 'Nov 2017 — May 2022', - bullets: [ - 'Managed community pharmacy delivering 3,000+ items monthly', - 'Pioneered asthma screening service generating £1M+ national revenue', - 'Led team of 6 through COVID-19 pandemic service delivery', - 'Completed Mary Seacole NHS Leadership Programme (2018)', - ], - isCurrent: false, - }, - { - role: 'Duty Pharmacy Manager', - org: 'Tesco Pharmacy', - date: 'Aug 2016 — Nov 2017', - bullets: [ - 'Supported pharmacy manager in daily operations and clinical services', - 'Delivered Medicines Use Reviews and New Medicine Service consultations', - 'Maintained controlled drug compliance and clinical governance standards', - ], - isCurrent: false, - }, -] - -const ECGDecoration = () => ( - -) - -const TimelineEntry = ({ - experience, - index, - isVisible, -}: { - experience: ExperienceType - index: number - isVisible: boolean -}) => { - return ( - -