From e13a073a6f605dcf41affeab2085a6f8498281fa Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Fri, 13 Feb 2026 16:42:45 +0000 Subject: [PATCH] Redesign CVMIS system 2 --- .playwright-mcp/http-server | 278 +++ Ralph/GP System concept.html | 1711 ++++++++++++++++ Ralph/refs/ref-01-design-tokens.md | 99 + Ralph/refs/ref-02-data-types.md | 203 ++ Ralph/refs/ref-03-topbar-sidebar.md | 147 ++ Ralph/refs/ref-04-dashboard-layout.md | 136 ++ Ralph/refs/ref-05-card-and-top-tiles.md | 144 ++ Ralph/refs/ref-06-bottom-tiles.md | 204 ++ Ralph/refs/ref-07-interactions.md | 248 +++ Ralph/refs/ref-08-polish.md | 156 ++ References/GPSystemconcept.html | 1711 ++++++++++++++++ designs/01-obsidian.html | 1244 ++++++++++++ designs/02-ivory.html | 1356 +++++++++++++ designs/03-bauhaus.html | 1152 +++++++++++ designs/04-aurora.html | 1212 ++++++++++++ designs/05-carbon.html | 1285 ++++++++++++ designs/06-chalk.html | 1123 +++++++++++ designs/07-contextual-workspace.html | 1756 +++++++++++++++++ designs/08-the-foyer.html | 1510 ++++++++++++++ designs/09-hybrid.html | 1258 ++++++++++++ .../GP systems/concept-1-precision-light.html | 1095 ++++++++++ .../concept-2-dark-workstation.html | 1458 ++++++++++++++ .../GP systems/concept-3-warm-accessible.html | 1171 +++++++++++ .../concept-4-bold-contemporary.html | 1270 ++++++++++++ .../concept-5-timeline-clinical.html | 1435 ++++++++++++++ designs/GP systems/concept-6-multi-panel.html | 1043 ++++++++++ 26 files changed, 24405 insertions(+) create mode 100644 .playwright-mcp/http-server create mode 100644 Ralph/GP System concept.html create mode 100644 Ralph/refs/ref-01-design-tokens.md create mode 100644 Ralph/refs/ref-02-data-types.md create mode 100644 Ralph/refs/ref-03-topbar-sidebar.md create mode 100644 Ralph/refs/ref-04-dashboard-layout.md create mode 100644 Ralph/refs/ref-05-card-and-top-tiles.md create mode 100644 Ralph/refs/ref-06-bottom-tiles.md create mode 100644 Ralph/refs/ref-07-interactions.md create mode 100644 Ralph/refs/ref-08-polish.md create mode 100644 References/GPSystemconcept.html create mode 100644 designs/01-obsidian.html create mode 100644 designs/02-ivory.html create mode 100644 designs/03-bauhaus.html create mode 100644 designs/04-aurora.html create mode 100644 designs/05-carbon.html create mode 100644 designs/06-chalk.html create mode 100644 designs/07-contextual-workspace.html create mode 100644 designs/08-the-foyer.html create mode 100644 designs/09-hybrid.html create mode 100644 designs/GP systems/concept-1-precision-light.html create mode 100644 designs/GP systems/concept-2-dark-workstation.html create mode 100644 designs/GP systems/concept-3-warm-accessible.html create mode 100644 designs/GP systems/concept-4-bold-contemporary.html create mode 100644 designs/GP systems/concept-5-timeline-clinical.html create mode 100644 designs/GP systems/concept-6-multi-panel.html diff --git a/.playwright-mcp/http-server b/.playwright-mcp/http-server new file mode 100644 index 0000000..7c597fa --- /dev/null +++ b/.playwright-mcp/http-server @@ -0,0 +1,278 @@ +#!/usr/bin/env node + +'use strict'; + +var chalk = require('chalk'), + os = require('os'), + httpServer = require('../lib/http-server'), + portfinder = require('portfinder'), + opener = require('opener'), + + fs = require('fs'), + url = require('url'); +var argv = require('minimist')(process.argv.slice(2), { + alias: { + tls: 'ssl' + } +}); +var ifaces = os.networkInterfaces(); + +process.title = 'http-server'; + +if (argv.h || argv.help) { + console.log([ + 'usage: http-server [path] [options]', + '', + 'options:', + ' -p --port Port to use. If 0, look for open port. [8080]', + ' -a Address to use [0.0.0.0]', + ' -d Show directory listings [true]', + ' -i Display autoIndex [true]', + ' -g --gzip Serve gzip files when possible [false]', + ' -b --brotli Serve brotli files when possible [false]', + ' If both brotli and gzip are enabled, brotli takes precedence', + ' -e --ext Default file extension if none supplied [none]', + ' -s --silent Suppress log messages from output', + ' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header', + ' Optionally provide CORS headers list separated by commas', + ' -o [path] Open browser window after starting the server.', + ' Optionally provide a URL path to open the browser window to.', + ' -c Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.', + ' To disable caching, use -c-1.', + ' -t Connections timeout in seconds [120], e.g. -t60 for 1 minute.', + ' To disable timeout, use -t0', + ' -U --utc Use UTC time format in log messages.', + ' --log-ip Enable logging of the client\'s IP address', + '', + ' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com', + ' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false', + '', + ' --username Username for basic authentication [none]', + ' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME', + ' --password Password for basic authentication [none]', + ' Can also be specified with the env variable NODE_HTTP_SERVER_PASSWORD', + '', + ' -S --tls --ssl Enable secure request serving with TLS/SSL (HTTPS)', + ' -C --cert Path to TLS cert file (default: cert.pem)', + ' -K --key Path to TLS key file (default: key.pem)', + '', + ' -r --robots Respond to /robots.txt [User-agent: *\\nDisallow: /]', + ' --no-dotfiles Do not show dotfiles', + ' --mimetypes Path to a .types file for custom mimetype definition', + ' -h --help Print this list and exit.', + ' -v --version Print the version and exit.' + ].join('\n')); + process.exit(); +} + +var port = argv.p || argv.port || parseInt(process.env.PORT, 10), + host = argv.a || '0.0.0.0', + tls = argv.S || argv.tls, + sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE, + proxy = argv.P || argv.proxy, + proxyOptions = argv['proxy-options'], + utc = argv.U || argv.utc, + version = argv.v || argv.version, + logger; + +var proxyOptionsBooleanProps = [ + 'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin', + 'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse' +]; + +if (proxyOptions) { + Object.keys(proxyOptions).forEach(function (key) { + if (proxyOptionsBooleanProps.indexOf(key) > -1) { + proxyOptions[key] = proxyOptions[key].toLowerCase() === 'true'; + } + }); +} + +if (!argv.s && !argv.silent) { + logger = { + info: console.log, + request: function (req, res, error) { + var date = utc ? new Date().toUTCString() : new Date(); + var ip = argv['log-ip'] + ? req.headers['x-forwarded-for'] || '' + req.connection.remoteAddress + : ''; + if (error) { + logger.info( + '[%s] %s "%s %s" Error (%s): "%s"', + date, ip, chalk.red(req.method), chalk.red(req.url), + chalk.red(error.status.toString()), chalk.red(error.message) + ); + } + else { + logger.info( + '[%s] %s "%s %s" "%s"', + date, ip, chalk.cyan(req.method), chalk.cyan(req.url), + req.headers['user-agent'] + ); + } + } + }; +} +else if (chalk) { + logger = { + info: function () {}, + request: function () {} + }; +} + +if (version) { + logger.info('v' + require('../package.json').version); + process.exit(); +} + +if (!port) { + portfinder.basePort = 8080; + portfinder.getPort(function (err, port) { + if (err) { throw err; } + listen(port); + }); +} +else { + listen(port); +} + +function listen(port) { + var options = { + root: argv._[0], + cache: argv.c, + timeout: argv.t, + showDir: argv.d, + autoIndex: argv.i, + gzip: argv.g || argv.gzip, + brotli: argv.b || argv.brotli, + robots: argv.r || argv.robots, + ext: argv.e || argv.ext, + logFn: logger.request, + proxy: proxy, + proxyOptions: proxyOptions, + showDotfiles: argv.dotfiles, + mimetypes: argv.mimetypes, + username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME, + password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD + }; + + if (argv.cors) { + options.cors = true; + if (typeof argv.cors === 'string') { + options.corsHeaders = argv.cors; + } + } + + if (proxy) { + try { + new url.URL(proxy) + } + catch (err) { + logger.info(chalk.red('Error: Invalid proxy url')); + process.exit(1); + } + } + + if (tls) { + options.https = { + cert: argv.C || argv.cert || 'cert.pem', + key: argv.K || argv.key || 'key.pem', + passphrase: sslPassphrase, + }; + try { + fs.lstatSync(options.https.cert); + } + catch (err) { + logger.info(chalk.red('Error: Could not find certificate ' + options.https.cert)); + process.exit(1); + } + try { + fs.lstatSync(options.https.key); + } + catch (err) { + logger.info(chalk.red('Error: Could not find private key ' + options.https.key)); + process.exit(1); + } + } + + var server = httpServer.createServer(options); + server.listen(port, host, function () { + var protocol = tls ? 'https://' : 'http://'; + + logger.info([ + chalk.yellow('Starting up http-server, serving '), + chalk.cyan(server.root), + tls ? (chalk.yellow(' through') + chalk.cyan(' https')) : '' + ].join('')); + + logger.info([chalk.yellow('\nhttp-server version: '), chalk.cyan(require('../package.json').version)].join('')); + + logger.info([ + chalk.yellow('\nhttp-server settings: '), + ([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')), + ([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')), + ([chalk.yellow('Connection Timeout: '), argv.t === '0' ? chalk.red('disabled') : (argv.t ? chalk.cyan(argv.t + ' seconds') : chalk.cyan('120 seconds'))].join('')), + ([chalk.yellow('Directory Listings: '), argv.d ? chalk.red('not visible') : chalk.cyan('visible')].join('')), + ([chalk.yellow('AutoIndex: '), argv.i ? chalk.red('not visible') : chalk.cyan('visible')].join('')), + ([chalk.yellow('Serve GZIP Files: '), argv.g || argv.gzip ? chalk.cyan('true') : chalk.red('false')].join('')), + ([chalk.yellow('Serve Brotli Files: '), argv.b || argv.brotli ? chalk.cyan('true') : chalk.red('false')].join('')), + ([chalk.yellow('Default File Extension: '), argv.e ? chalk.cyan(argv.e) : (argv.ext ? chalk.cyan(argv.ext) : chalk.red('none'))].join('')) + ].join('\n')); + + logger.info(chalk.yellow('\nAvailable on:')); + + if (argv.a && host !== '0.0.0.0') { + logger.info(` ${protocol}${host}:${chalk.green(port.toString())}`); + } else { + Object.keys(ifaces).forEach(function (dev) { + ifaces[dev].forEach(function (details) { + if (details.family === 'IPv4') { + logger.info((' ' + protocol + details.address + ':' + chalk.green(port.toString()))); + } + }); + }); + } + + if (typeof proxy === 'string') { + if (proxyOptions) { + logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions)); + } + else { + logger.info('Unhandled requests will be served from: ' + proxy); + } + } + + logger.info('Hit CTRL-C to stop the server'); + if (argv.o) { + const openHost = host === '0.0.0.0' ? '127.0.0.1' : host; + let openUrl = `${protocol}${openHost}:${port}`; + if (typeof argv.o === 'string') { + openUrl += argv.o[0] === '/' ? argv.o : '/' + argv.o; + } + logger.info('Open: ' + openUrl); + opener(openUrl); + } + + // Spacing before logs + if (!argv.s) logger.info(); + }); +} + +if (process.platform === 'win32') { + require('readline').createInterface({ + input: process.stdin, + output: process.stdout + }).on('SIGINT', function () { + process.emit('SIGINT'); + }); +} + +process.on('SIGINT', function () { + logger.info(chalk.red('http-server stopped.')); + process.exit(); +}); + +process.on('SIGTERM', function () { + logger.info(chalk.red('http-server stopped.')); + process.exit(); +}); diff --git a/Ralph/GP System concept.html b/Ralph/GP System concept.html new file mode 100644 index 0000000..7934d60 --- /dev/null +++ b/Ralph/GP System concept.html @@ -0,0 +1,1711 @@ + + + +Practitioner Record System — CHARLWOOD, Andrew + + + + + + + + +
+
+ + + + + Headhunt Medical Center Remote +
+ +
+
+ + + + + Ctrl+K +
+
+ Dr. A.CHARLWOOD · Active Session · 12:23 + Ctrl+K +
+ + +
+ + + + +
+ + + + +
+ +
+
+ + Latest results + + Updated May 2025 +
+
+
+
£220M
+
Budget Oversight
+
NHS prescribing
+
+
+
£14.6M
+
Efficiency Savings
+
Identified & tracked
+
+
+
9+
+
Years in NHS
+
Since 2016
+
+
+
12
+
Team Size Led
+
Cross-functional
+
+
+
+ + +
+
+ + Repeat Medications +
+
+
+
+ +
+
+
Advanced SQL Certification
+
Q2 2026
+
+ Pending +
+
+
+ +
+
+
Cloud Architecture Training
+
Q3 2026
+
+ Scheduled +
+
+
+ +
+
+
Leadership Programme
+
2027
+
+ Planned +
+
+
+ + +
+
+ + Last Consultation + Most recent role +
+
+
+
Date
+
May 2025
+
+
+
Organisation
+
NHS Norfolk & Waveney ICB
+
+
+
Type
+
Permanent · Full-time
+
+
+
Band
+
8a
+
+
+
Interim Head, Population Health & Data Analysis
+
    +
  • Led a cross-functional team of 12 across data, analytics, and population health workstreams
  • +
  • Oversaw £220M prescribing budget with full analytical accountability and reporting to ICB board
  • +
  • Identified £14.6M in efficiency savings through data-driven prescribing interventions
  • +
  • Designed and deployed Power BI dashboards used by 200+ clinicians and commissioners
  • +
  • Spearheaded SQL analytics transformation, migrating legacy Access databases to modern data stack
  • +
  • Established team data literacy programme, upskilling 30+ non-technical staff in data interpretation
  • +
+
+ + +
+
+ + Career Activity + Full timeline +
+
+
+
+
+
Interim Head, Population Health & Data Analysis
+
NHS Norfolk & Waveney ICB
+
2024 – 2025
+
+
+
+
+
+
£220M Prescribing Budget Oversight
+
Lead analyst & budget owner
+
2024
+
+
+
+
+
+
Senior Data Analyst — Medicines Optimisation
+
NHS Norfolk & Waveney ICB
+
2021 – 2024
+
+
+
+
+
+
SQL Analytics Transformation
+
Legacy migration project lead
+
2025
+
+
+
+
+
+
Power BI Data Analyst Associate
+
Microsoft Certified
+
2023
+
+
+
+
+
+
Prescribing Data Pharmacist
+
NHS Norwich CCG
+
2018 – 2021
+
+
+
+
+
+
Clinical Pharmacy Diploma
+
Professional development
+
2019
+
+
+
+
+
+
Community Pharmacist
+
Boots UK
+
2016 – 2018
+
+
+
+
+
+
MPharm (Hons) — 2:1
+
University of East Anglia
+
2011 – 2015
+
+
+
+
+
+
GPhC Registration
+
General Pharmaceutical Council
+
August 2016
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
Interim Head, Population Health & Data Analysis
+
NHS Norfolk & Waveney ICB · 2024–2025
+
+
+
+
+
+
Senior Data Analyst — Medicines Optimisation
+
NHS Norfolk & Waveney ICB · 2021–2024
+
+
+
+
+
+
Prescribing Data Pharmacist
+
NHS Norwich CCG · 2018–2021
+
+
+
+
+
+
Community Pharmacist
+
Boots UK · 2016–2018
+
+
+
+
+
+
Data Analysis — 95%
+
Primary expertise · NHS population data
+
+
+
+
+
+
Power BI — 92%
+
Dashboard design & deployment
+
+
+
+
+
+
Python — 90%
+
Data pipelines, automation, analytics
+
+
+
+
+
+
SQL — 88%
+
Advanced queries, database migration
+
+
+
+
+
+
JavaScript / TypeScript — 70%
+
Web development & tooling
+
+
+
+
+
+
£220M Prescribing Budget
+
Budget oversight & analytical accountability · 2024
+
+
+
+
+
+
SQL Analytics Transformation
+
Legacy migration to modern data stack · 2025
+
+
+
+
+
+
Team Data Literacy Programme
+
Upskilling 30+ non-technical staff · 2024
+
+
+
+
+
+
£14.6M Efficiency Savings Identified
+
Data-driven prescribing interventions
+
+
+
+
+
+
£220M Budget Oversight
+
Full analytical accountability to ICB board
+
+
+
+
+
+
Power BI Dashboards for 200+ Users
+
Clinicians & commissioners across ICB
+
+
+
+
+
+
Team of 12 Led
+
Cross-functional data & population health
+
+
+
+
+
+
MPharm (Hons) — 2:1
+
University of East Anglia · 2011–2015
+
+
+
+
+
+
GPhC Registration
+
General Pharmaceutical Council · August 2016
+
+
+
+
+
+
Power BI Data Analyst Associate
+
Microsoft Certified · 2023
+
+
+
+
+
+
Clinical Pharmacy Diploma
+
Professional development · 2019
+
+
+
+
+
+
Download CV
+
Export as PDF
+
+
+
+
+
+
Send Email
+
andy@charlwood.xyz
+
+
+
+
+
+
View LinkedIn
+
Professional profile
+
+
+
+
+
+
View Projects
+
GitHub & portfolio
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/Ralph/refs/ref-01-design-tokens.md b/Ralph/refs/ref-01-design-tokens.md new file mode 100644 index 0000000..e5abb68 --- /dev/null +++ b/Ralph/refs/ref-01-design-tokens.md @@ -0,0 +1,99 @@ +# Reference: Task 1 — Design Tokens and Tailwind Config + +## Overview + +Update the design system from the dark-sidebar NHS Blue palette to the GP System concept's light teal palette. The concept reference is `References/GPSystemconcept.html`. + +## CSS Custom Properties (`src/index.css`) + +Add/update these variables in the PMR section (keep boot/ECG/login variables unchanged): + +```css +/* GP System Dashboard tokens */ +--bg: #F0F5F4; +--surface: #FFFFFF; +--sidebar-bg: #F7FAFA; +--text-primary: #1A2B2A; +--text-secondary: #5B7A78; +--text-tertiary: #8DA8A5; +--accent: #0D6E6E; +--accent-hover: #0A8080; +--accent-light: rgba(10,128,128,0.08); +--accent-border: rgba(10,128,128,0.18); +--amber: #D97706; +--amber-light: rgba(217,119,6,0.08); +--amber-border: rgba(217,119,6,0.18); +--success: #059669; +--success-light: rgba(5,150,105,0.08); +--success-border: rgba(5,150,105,0.18); +--alert: #DC2626; +--alert-light: rgba(220,38,38,0.08); +--alert-border: rgba(220,38,38,0.18); +--border: #D4E0DE; +--border-light: #E4EDEB; +--sidebar-width: 272px; +--topbar-height: 48px; +--radius: 8px; +--radius-sm: 6px; +--shadow-sm: 0 1px 2px rgba(26,43,42,0.05); +--shadow-md: 0 2px 8px rgba(26,43,42,0.08); +--shadow-lg: 0 8px 32px rgba(26,43,42,0.12); +--font-body: var(--font-ui); +--font-mono: 'Geist Mono', 'Fira Code', monospace; +``` + +## Tailwind Config (`tailwind.config.js`) + +Update the `extend` section: + +### Colors +```js +colors: { + 'pmr-bg': '#F0F5F4', + 'pmr-surface': '#FFFFFF', + 'pmr-sidebar': '#F7FAFA', + 'pmr-accent': '#0D6E6E', + 'pmr-accent-hover': '#0A8080', + 'pmr-text-primary': '#1A2B2A', + 'pmr-text-secondary': '#5B7A78', + 'pmr-text-tertiary': '#8DA8A5', + 'pmr-border': '#D4E0DE', + 'pmr-border-light': '#E4EDEB', + 'pmr-success': '#059669', + 'pmr-amber': '#D97706', + 'pmr-alert': '#DC2626', + 'pmr-purple': '#7C3AED', + // Keep pmr-nhsblue for backward compat during transition + 'pmr-nhsblue': '#005EB8', + // Keep pmr-content as fallback + 'pmr-content': '#F0F5F4', +} +``` + +### Shadows +```js +boxShadow: { + 'pmr-sm': '0 1px 2px rgba(26,43,42,0.05)', + 'pmr-md': '0 2px 8px rgba(26,43,42,0.08)', + 'pmr-lg': '0 8px 32px rgba(26,43,42,0.12)', + // Keep old pmr shadow as alias during transition + 'pmr': '0 1px 2px rgba(26,43,42,0.05)', +} +``` + +### Border Radius +```js +borderRadius: { + 'card': '8px', // was 4px — now 8px per concept + 'card-sm': '6px', // inner elements + 'login': '12px', // login card exception +} +``` + +## What NOT to Change + +- Boot phase variables (`--matrix-*`, `--terminal-*`) +- ECG phase variables +- Login phase background (`#1E293B` — handled by transition) +- Font declarations (Elvaro, Blumir, Geist Mono, Fira Code already set up correctly) +- Breakpoint values diff --git a/Ralph/refs/ref-02-data-types.md b/Ralph/refs/ref-02-data-types.md new file mode 100644 index 0000000..a00def8 --- /dev/null +++ b/Ralph/refs/ref-02-data-types.md @@ -0,0 +1,203 @@ +# Reference: Task 2 — Data Files and Types + +## Overview + +Create new data files for dashboard-specific content and update the type system. All CV content must match `References/CV_v4.md` exactly. + +## New Data Files + +### `src/data/profile.ts` + +```typescript +export const personalStatement = `Healthcare leader combining clinical pharmacy expertise with proficiency in Python, SQL, and data analytics, 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 leading population health analytics for NHS Norfolk & Waveney ICB, serving a population of 1.2 million. Experienced in working with messy, real-world prescribing data at scale to deliver actionable insights—from financial scenario modelling and pharmaceutical rebate negotiation to algorithm design and population-level pathway development. Proven track record of identifying and prioritising efficiency programmes worth £14.6M+ through automated, data-driven analysis. Skilled at translating complex clinical, financial, and analytical requirements into clear recommendations for executive stakeholders.` +``` + +### `src/data/tags.ts` + +```typescript +import type { Tag } from '@/types/pmr' + +export const tags: Tag[] = [ + { label: 'Pharmacist', colorVariant: 'teal' }, + { label: 'Data Lead', colorVariant: 'teal' }, + { label: 'NHS', colorVariant: 'teal' }, + { label: 'Population Health', colorVariant: 'amber' }, + { label: 'BI & Analytics', colorVariant: 'green' }, +] +``` + +### `src/data/alerts.ts` + +```typescript +import type { Alert } from '@/types/pmr' + +export const alerts: Alert[] = [ + { + message: '£14.6M SAVINGS IDENTIFIED', + severity: 'alert', + icon: 'AlertTriangle', // lucide-react icon name + }, + { + message: '£220M BUDGET OVERSIGHT', + severity: 'amber', + icon: 'AlertCircle', // lucide-react icon name + }, +] +``` + +### `src/data/kpis.ts` + +```typescript +import type { KPI } from '@/types/pmr' + +export const kpis: KPI[] = [ + { + id: 'budget', + value: '£220M', + label: 'Budget Oversight', + sub: 'NHS prescribing', + colorVariant: 'green', + explanation: 'Managed the ICB\'s total prescribing budget with sophisticated forecasting models identifying cost pressures and enabling proactive financial planning across Norfolk & Waveney.', + }, + { + id: 'savings', + value: '£14.6M', + label: 'Efficiency Savings', + sub: 'Identified & tracked', + colorVariant: 'amber', + explanation: 'Identified and prioritised a £14.6M efficiency programme through comprehensive data analysis; achieved over-target performance through targeted, evidence-based interventions across the integrated care system.', + }, + { + id: 'years', + value: '9+', + label: 'Years in NHS', + sub: 'Since 2016', + colorVariant: 'teal', + explanation: 'Continuous NHS service since August 2016, progressing from community pharmacy through prescribing data analysis to system-level population health data leadership.', + }, + { + id: 'team', + value: '12', + label: 'Team Size Led', + sub: 'Cross-functional', + colorVariant: 'green', + explanation: 'Led a cross-functional team of 12 spanning data analysts, population health specialists, and pharmacists across data, analytics, and population health workstreams.', + }, +] +``` + +### `src/data/skills.ts` + +Skills presented as "medications" with frequency (user-specified values) and years of experience. + +```typescript +import type { SkillMedication } from '@/types/pmr' + +export const skills: SkillMedication[] = [ + { + id: 'data-analysis', + name: 'Data Analysis', + frequency: 'Twice daily', + startYear: 2016, + yearsOfExperience: 9, + proficiency: 95, + category: 'Technical', + status: 'Active', + icon: 'BarChart3', + }, + { + id: 'python', + name: 'Python', + frequency: 'Daily', + startYear: 2019, + yearsOfExperience: 6, + proficiency: 90, + category: 'Technical', + status: 'Active', + icon: 'Code2', + }, + { + id: 'sql', + name: 'SQL', + frequency: 'Daily', + startYear: 2018, + yearsOfExperience: 7, + proficiency: 88, + category: 'Technical', + status: 'Active', + icon: 'Database', + }, + { + id: 'power-bi', + name: 'Power BI', + frequency: 'Once weekly', + startYear: 2020, + yearsOfExperience: 5, + proficiency: 92, + category: 'Technical', + status: 'Active', + icon: 'PieChart', + }, + { + id: 'javascript-typescript', + name: 'JavaScript / TypeScript', + frequency: 'When required', + startYear: 2022, + yearsOfExperience: 3, + proficiency: 70, + category: 'Technical', + status: 'Active', + icon: 'FileCode2', + }, +] +``` + +Note: Additional domain/leadership skills can be added later. Start with the 5 technical skills the user specified frequencies for. + +## Type Updates (`src/types/pmr.ts`) + +Add these interfaces (keep all existing types): + +```typescript +export interface Tag { + label: string + colorVariant: 'teal' | 'amber' | 'green' +} + +export interface Alert { + message: string + severity: 'alert' | 'amber' + icon: string +} + +export interface KPI { + id: string + value: string + label: string + sub: string + colorVariant: 'green' | 'amber' | 'teal' + explanation: string +} + +export interface SkillMedication { + id: string + name: string + frequency: string + startYear: number + yearsOfExperience: number + proficiency: number + category: 'Technical' | 'Domain' | 'Leadership' + status: 'Active' | 'Historical' + icon: string +} +``` + +## Existing Data — No Changes + +These files remain untouched: +- `src/data/patient.ts` +- `src/data/consultations.ts` +- `src/data/medications.ts` +- `src/data/problems.ts` +- `src/data/investigations.ts` +- `src/data/documents.ts` diff --git a/Ralph/refs/ref-03-topbar-sidebar.md b/Ralph/refs/ref-03-topbar-sidebar.md new file mode 100644 index 0000000..25c65f2 --- /dev/null +++ b/Ralph/refs/ref-03-topbar-sidebar.md @@ -0,0 +1,147 @@ +# Reference: Tasks 4-6 — TopBar and Sidebar + +## Concept Reference + +All specs below are derived from `References/GPSystemconcept.html`. Open it in a browser for visual reference. + +--- + +## Task 4: TopBar Component + +### File: `src/components/TopBar.tsx` + +### Structure +``` +┌─────────────────────────────────────────────────────────────┐ +│ [🏠] Headhunt Medical Center Remote │ [🔍 Search... Ctrl+K] │ Dr. A.CHARLWOOD · Active Session · 12:23 [Ctrl+K] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Specs + +**Container:** +- `position: fixed`, `top: 0`, `left: 0`, `right: 0` +- `height: var(--topbar-height)` (48px) +- `background: var(--surface)` (#FFFFFF) +- `border-bottom: 1px solid var(--border)` (#D4E0DE) +- `display: flex`, `align-items: center`, `justify-content: space-between` +- `padding: 0 20px` +- `z-index: 100` + +**Brand (left):** +- `Home` icon from lucide-react (18px, accent color) +- Text: "Headhunt Medical Center" — 13px, font-ui, 600 weight, text-primary +- Version badge: "Remote" — 11px, 400 weight, text-tertiary, margin-left 2px + +**Search bar (center):** +- Wrapper: `max-width: 560px`, `min-width: 400px` +- Container: `height: 42px`, `border: 1.5px solid var(--border)`, `border-radius: var(--radius)` (8px), `padding: 0 14px`, white bg +- Search icon (16px, tertiary) + input + "Ctrl+K" kbd badge +- Input: 13px, font-body, placeholder "Search records, experience, skills... (Ctrl+K)" +- Hover: `border-color: var(--accent-border)` +- Focus: `border-color: var(--accent)`, `box-shadow: 0 0 0 3px rgba(13,110,110,0.12)` +- **On click/focus: opens Command Palette** (Task 18). Does NOT do inline search. +- Kbd badge: mono font, 10px, tertiary, bg: var(--bg), border, padding 2px 6px, radius 4px + +**Session info (right):** +- Text: "Dr. A.CHARLWOOD · Active Session · [time]" — 12px, text-secondary +- Session pill: mono 11px, tertiary, `background: var(--accent-light)`, `padding: 3px 10px`, radius 4px, `border: 1px solid var(--accent-border)` +- Ctrl+K shortcut badge (same style as search bar badge) + +**Responsive:** +- Mobile (<768px): hide center search bar. Show only brand + session info (or hamburger). +- Tablet: search bar may shrink. + +--- + +## Task 5: Sidebar — PersonHeader + +### File: `src/components/Sidebar.tsx` + +### Overall Sidebar Container +- `width: var(--sidebar-width)` (272px) +- `min-width: var(--sidebar-width)` +- `background: var(--sidebar-bg)` (#F7FAFA) +- `border-right: 1px solid var(--border)` (#D4E0DE) +- `overflow-y: auto`, custom scrollbar (4px width, transparent track, border-colored thumb) +- `padding: 20px 16px` +- `display: flex`, `flex-direction: column`, `gap: 2px` + +### PersonHeader Section +Bordered below: `border-bottom: 2px solid var(--accent)`, `padding-bottom: 16px`, `margin-bottom: 6px` + +**Avatar:** +- 52px × 52px circle +- `background: linear-gradient(135deg, var(--accent), #0A8080)` +- White text "AC", 700 weight, 18px, centered +- `box-shadow: 0 2px 8px rgba(13,110,110,0.25)` +- `margin-bottom: 12px` + +**Name:** +- "CHARLWOOD, Andrew" +- 15px, 700 weight, text-primary, `letter-spacing: -0.01em` + +**Title:** +- "Pharmacy Data Technologist" +- 11.5px, mono font, 400 weight, text-secondary +- `margin-top: 2px` + +**Status badge:** +- Inline-flex, gap 5px +- `margin-top: 8px` +- 11px, 500 weight, success color (#059669) +- `background: var(--success-light)`, `border: 1px solid var(--success-border)` +- `padding: 3px 9px`, `border-radius: 20px` (pill) +- Animated dot: 6px circle, success color, `animation: pulse 2s infinite` (opacity 1→0.4→1) +- Text: "Open to Opportunities" + +**Details grid:** +- `display: grid`, `grid-template-columns: 1fr`, `gap: 6px`, `margin-top: 12px` +- Each row: `display: flex`, `justify-content: space-between`, `align-items: center`, 11.5px, `padding: 2px 0` +- Label: text-tertiary, 400 weight +- Value: text-primary, 500 weight, text-align right +- GPhC No. value: mono font, 11px, `letter-spacing: 0.12em` → "2211810" +- Education value: "MPharm 2.1 (Hons)" +- Location: "Norwich, Norfolk" +- Phone: link in accent color, `text-decoration: none`, underline on hover → "07795 553 088" +- Email: link → "andy@charlwood.xyz" +- Registered: "August 2016" + +**Data source:** `src/data/patient.ts` + +--- + +## Task 6: Sidebar — Tags + Alerts + +### Section Title Component +Reusable within sidebar. Used for "Tags", "Alerts / Highlights", and any future sections. + +- `font-size: 10px`, `font-weight: 600`, `text-transform: uppercase`, `letter-spacing: 0.08em` +- Color: text-tertiary +- `margin-bottom: 10px` +- Flex row with `::after` pseudo-element: `flex: 1`, `height: 1px`, `background: var(--border-light)`, `gap: 6px` + +### Tags Section +- Container: `display: flex`, `flex-wrap: wrap`, `gap: 5px` +- Each tag: 10.5px, 500 weight, `padding: 3px 8px`, `border-radius: 4px`, inline-flex, `line-height: 1.3` +- **Color variants:** + - `teal`: `background: var(--accent-light)`, `color: var(--accent)`, `border: 1px solid var(--accent-border)` + - `amber`: `background: var(--amber-light)`, `color: var(--amber)`, `border: 1px solid var(--amber-border)` + - `green`: `background: var(--success-light)`, `color: var(--success)`, `border: 1px solid var(--success-border)` +- **Data source:** `src/data/tags.ts` + +### Alerts / Highlights Section +- Container: `display: flex`, `flex-direction: column`, `gap: 6px` +- Each flag item: `display: flex`, `align-items: center`, `gap: 8px` + - 11px, 700 weight, `padding: 7px 10px`, `border-radius: var(--radius-sm)` (6px), `letter-spacing: 0.02em` +- **Alert variant** (red): + - `background: var(--alert-light)`, `color: var(--alert)`, `border: 1px solid var(--alert-border)` + - Icon: `AlertTriangle` from lucide-react (14px, 2.5 stroke-width) +- **Amber variant:** + - `background: var(--amber-light)`, `color: var(--amber)`, `border: 1px solid var(--amber-border)` + - Icon: `AlertCircle` from lucide-react (14px, 2.5 stroke-width) +- Icon container: 16px square, flex center, flex-shrink-0 +- **Data source:** `src/data/alerts.ts` + +### Section Padding +Each sidebar section: `padding: 14px 0 6px` diff --git a/Ralph/refs/ref-04-dashboard-layout.md b/Ralph/refs/ref-04-dashboard-layout.md new file mode 100644 index 0000000..9cc553d --- /dev/null +++ b/Ralph/refs/ref-04-dashboard-layout.md @@ -0,0 +1,136 @@ +# Reference: Task 7 — DashboardLayout + +## Overview + +Create the main layout component that replaces `PMRInterface.tsx`. This is the container that houses TopBar, Sidebar, and the scrollable card grid of tiles. + +## File: `src/components/DashboardLayout.tsx` + +### Layout Structure + +``` +┌────────────────────────────────────────────────────┐ +│ TopBar (fixed, z-100, height: 48px) │ +├──────────┬─────────────────────────────────────────┤ +│ │ │ +│ Sidebar │
— scrollable card grid │ +│ (272px) │ padding: 24px 28px 40px │ +│ fixed │ │ +│ │ grid: 1fr 1fr, gap: 16px │ +│ │ │ +│ │ [PatientSummary — full] │ +│ │ [LatestResults] [CoreSkills] │ +│ │ [LastConsultation — full] │ +│ │ [CareerActivity — full] │ +│ │ [Education — full] │ +│ │ [Projects — full] │ +│ │ │ +└──────────┴─────────────────────────────────────────┘ +``` + +### CSS Layout + +``` +.layout { + display: flex; + margin-top: var(--topbar-height); /* 48px */ + height: calc(100vh - var(--topbar-height)); +} + +.sidebar { + /* See ref-03-topbar-sidebar.md for sidebar specs */ + width: var(--sidebar-width); + min-width: var(--sidebar-width); + /* ... */ +} + +.main { + flex: 1; + overflow-y: auto; + padding: 24px 28px 40px; +} + +.card-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} + +@media (max-width: 900px) { + .card-grid { + grid-template-columns: 1fr; + } +} +``` + +Use Tailwind classes for all of this — the CSS above is for reference only. + +### Framer Motion Entrance Animations + +Staggered entrance when dashboard first renders (after login): + +1. **TopBar**: slides down from `-48px`, 200ms ease-out +2. **Sidebar**: slides from `-272px` left, 250ms ease-out, 50ms delay +3. **Main content**: fades in (opacity 0→1), 300ms, 150ms delay + +```typescript +const topbarVariants = { + hidden: { y: -48, opacity: 0 }, + visible: { y: 0, opacity: 1, transition: { duration: 0.2, ease: 'easeOut' } } +} + +const sidebarVariants = { + hidden: { x: -272, opacity: 0 }, + visible: { x: 0, opacity: 1, transition: { duration: 0.25, ease: 'easeOut', delay: 0.05 } } +} + +const contentVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { duration: 0.3, delay: 0.15 } } +} +``` + +With `prefers-reduced-motion`: all durations → 0, no delays. + +### Tile Ordering in Grid + +The card grid renders tiles in this order: +1. `PatientSummaryTile` — `grid-column: 1 / -1` (full width) +2. `LatestResultsTile` — single column (left) +3. `CoreSkillsTile` — single column (right) +4. `LastConsultationTile` — `grid-column: 1 / -1` (full width) +5. `CareerActivityTile` — `grid-column: 1 / -1` (full width) +6. `EducationTile` — `grid-column: 1 / -1` (full width) +7. `ProjectsTile` — `grid-column: 1 / -1` (full width) + +### App.tsx Wiring + +In `src/App.tsx`, the PMR phase currently renders ``. Change it to render ``. + +```typescript +// In App.tsx phase switch: +case 'pmr': + return +``` + +Keep all other phases (boot, ecg, login) unchanged. The SkipButton that skips to login should still work. + +### Scrollbar Styling + +Main content area scrollbar (matches concept): +- Width: 6px +- Track: transparent +- Thumb: var(--border) (#D4E0DE), border-radius 3px + +### Command Palette Integration + +The DashboardLayout should render the `CommandPalette` component (from Task 18) at the layout level, so it overlays the entire dashboard when triggered. For now (Task 7), just add a placeholder comment or empty div where it will go. The TopBar search bar's click handler should be wired to open the palette (but the palette itself comes in Task 18). + +### Background Color Transition + +The login screen has background `#1E293B`. The dashboard has background `#F0F5F4`. This transition should happen smoothly. Options: +1. The DashboardLayout entrance animation covers the transition (content fades in over the dark background, replacing it) +2. A brief CSS transition on the body/root background color +3. Handle it in App.tsx with a state-based background + +The simplest approach is option 1 — the dashboard's entrance animation effectively replaces the dark login background with the light dashboard. diff --git a/Ralph/refs/ref-05-card-and-top-tiles.md b/Ralph/refs/ref-05-card-and-top-tiles.md new file mode 100644 index 0000000..afb2989 --- /dev/null +++ b/Ralph/refs/ref-05-card-and-top-tiles.md @@ -0,0 +1,144 @@ +# Reference: Tasks 8-11 — Card Component and Top Tiles + +## Task 8: Reusable Card Component + +### File: `src/components/Card.tsx` + +### Base Card +```typescript +interface CardProps { + children: React.ReactNode + full?: boolean // spans both grid columns + className?: string +} +``` + +**Styling:** +- `background: var(--surface)` (#FFFFFF) +- `border: 1px solid var(--border-light)` (#E4EDEB) +- `border-radius: var(--radius)` (8px) +- `padding: 20px` +- `box-shadow: var(--shadow-sm)` (0 1px 2px rgba(26,43,42,0.05)) +- Hover: `box-shadow: var(--shadow-md)`, `border-color: var(--border)` (#D4E0DE) +- `transition: box-shadow 0.2s, border-color 0.2s` +- Full variant: `grid-column: 1 / -1` + +### CardHeader Sub-component +```typescript +interface CardHeaderProps { + dotColor: 'teal' | 'amber' | 'green' | 'alert' | 'purple' + title: string + rightText?: string +} +``` + +**Styling:** +- `display: flex`, `align-items: center`, `gap: 8px`, `margin-bottom: 16px` +- Dot: 8px circle, `border-radius: 50%`, flex-shrink-0 + - teal: `#0D6E6E`, amber: `#D97706`, green: `#059669`, alert: `#DC2626`, purple: `#7C3AED` +- Title: 12px, 600 weight, uppercase, `letter-spacing: 0.06em`, text-secondary (#5B7A78) +- Right text (optional): 10px, 400 weight, normal case, no tracking, text-tertiary, mono font, `margin-left: auto` + +--- + +## Task 9: PatientSummary Tile + +### File: `src/components/tiles/PatientSummaryTile.tsx` + +**Layout:** Full-width card, first in grid. + +**Content:** +- CardHeader: teal dot + "PATIENT SUMMARY" +- Body: personal statement text from `src/data/profile.ts` +- Typography: 13px, font-ui, `line-height: 1.6` (leading-relaxed), text-primary +- No interactive elements — read-only + +**Data:** `import { personalStatement } from '@/data/profile'` + +This is a simple tile. No expansion, no interactivity. + +--- + +## Task 10: LatestResults Tile + +### File: `src/components/tiles/LatestResultsTile.tsx` + +**Layout:** Half-width card (single grid column). Sits in the LEFT column. + +**Content:** +- CardHeader: teal dot + "LATEST RESULTS" + right text "Updated May 2025" +- 2×2 metric grid inside + +**Metric Grid:** +- `display: grid`, `grid-template-columns: 1fr 1fr`, `gap: 12px` + +**Each Metric Card:** +- `padding: 14px`, `border-radius: var(--radius-sm)` (6px) +- `border: 1px solid var(--border-light)`, `background: var(--bg)` (#F0F5F4) +- Value: 22px, 700 weight, `letter-spacing: -0.02em`, `line-height: 1.2` + - Color by variant: green=#059669, amber=#D97706, teal=#0D6E6E +- Label: 11px, text-secondary, 500 weight, `margin-top: 3px` +- Sub: 10px, text-tertiary, mono font, `margin-top: 4px` + +**Data:** `import { kpis } from '@/data/kpis'` + +**KPI flip prep:** Each metric card should accept a `data-kpi-id` or an `onClick` prop placeholder — Task 17 will add the flip interaction. For now, render as static display. + +**Values:** +| Value | Label | Sub | Color | +|-------|-------|-----|-------| +| £220M | Budget Oversight | NHS prescribing | green | +| £14.6M | Efficiency Savings | Identified & tracked | amber | +| 9+ | Years in NHS | Since 2016 | teal | +| 12 | Team Size Led | Cross-functional | green | + +--- + +## Task 11: CoreSkills Tile ("Repeat Medications") + +### File: `src/components/tiles/CoreSkillsTile.tsx` + +**Layout:** Half-width card (single grid column). Sits in the RIGHT column, next to LatestResults. + +**Content:** +- CardHeader: amber dot + "REPEAT MEDICATIONS" +- Vertical list of skill items, `gap: 10px` + +**Each Skill Item:** +Matches the concept's `.dev-item` pattern: +- `display: flex`, `align-items: center`, `gap: 10px` +- 12.5px font, `padding: 10px 12px` +- `background: var(--bg)` (#F0F5F4), `border-radius: var(--radius-sm)` (6px) +- `border: 1px solid var(--border-light)` + +**Item structure:** +- **Icon container** (28px square, 6px radius): + - `background: var(--accent-light)`, `color: var(--accent)` (teal) + - Lucide icon inside (14px): `BarChart3` for Data Analysis, `Code2` for Python, `Database` for SQL, `PieChart` for Power BI, `FileCode2` for JS/TS +- **Text block** (flex: 1): + - Name: 600 weight, text-primary (e.g., "Data Analysis") + - Frequency + years: 11px, text-tertiary, mono font (e.g., "Twice daily · Since 2016 · 9 yrs") +- **Optional status badge**: 10px, 500 weight, pill shape (padding 3px 8px, border-radius 20px), flex-shrink-0 + - Could show proficiency or "Active" status + +**Medication metaphor format:** +``` +[📊] Data Analysis Active + Twice daily · Since 2016 · 9 yrs + +[💻] Python Active + Daily · Since 2019 · 6 yrs + +[🗄️] SQL Active + Daily · Since 2018 · 7 yrs + +[📈] Power BI Active + Once weekly · Since 2020 · 5 yrs + +[📝] JavaScript / TypeScript Active + When required · Since 2022 · 3 yrs +``` + +**Data:** `import { skills } from '@/data/skills'` + +**Expansion prep:** Each item should accept an onClick prop placeholder — Task 16 will add expansion to show prescribing history (from existing medications data). diff --git a/Ralph/refs/ref-06-bottom-tiles.md b/Ralph/refs/ref-06-bottom-tiles.md new file mode 100644 index 0000000..a2b3282 --- /dev/null +++ b/Ralph/refs/ref-06-bottom-tiles.md @@ -0,0 +1,204 @@ +# Reference: Tasks 12-15 — Bottom Tiles + +## Task 12: LastConsultation Tile + +### File: `src/components/tiles/LastConsultationTile.tsx` + +**Layout:** Full-width card. + +**Content:** +- CardHeader: green dot + "LAST CONSULTATION" + right text "Most recent role" + +**Header info row:** +- `display: flex`, `flex-wrap: wrap`, `gap: 16px` +- `margin-bottom: 14px`, `padding-bottom: 14px`, `border-bottom: 1px solid var(--border-light)` +- Each field: + - Label: 10px, uppercase, `letter-spacing: 0.06em`, text-tertiary + - Value: 11.5px, 600 weight, text-primary + +| Label | Value | +|-------|-------| +| Date | May 2025 | +| Organisation | NHS Norfolk & Waveney ICB | +| Type | Permanent · Full-time | +| Band | 8a | + +**Role title:** +- "Interim Head, Population Health & Data Analysis" +- 13.5px, 600 weight, `color: var(--accent)` (#0D6E6E) +- `margin-bottom: 12px` + +**Bullet list:** +- `list-style: none`, flex column, `gap: 7px` +- Each bullet: 12.5px, text-primary, `padding-left: 16px`, `line-height: 1.5` +- Pseudo `::before`: 5px circle, accent color (#0D6E6E), `opacity: 0.5`, positioned left at top 7px + +**Bullets** (from first consultation's examination array): +- Led a cross-functional team of 12 across data, analytics, and population health workstreams +- Oversaw £220M prescribing budget with full analytical accountability and reporting to ICB board +- Identified £14.6M in efficiency savings through data-driven prescribing interventions +- Designed and deployed Power BI dashboards used by 200+ clinicians and commissioners +- Spearheaded SQL analytics transformation, migrating legacy Access databases to modern data stack +- Established team data literacy programme, upskilling 30+ non-technical staff in data interpretation + +**Data:** `import { consultations } from '@/data/consultations'` — use `consultations[0]` (the most recent). + +Map consultation fields: +- date → Date field +- organization → Organisation field +- role → Role title +- examination array → Bullet points + +--- + +## Task 13: CareerActivity Tile + +### File: `src/components/tiles/CareerActivityTile.tsx` + +**Layout:** Full-width card. + +**Content:** +- CardHeader: teal dot + "CAREER ACTIVITY" + right text "Full timeline" + +**Activity grid:** +- `display: grid`, `grid-template-columns: 1fr 1fr`, `gap: 10px` +- Below 900px: `grid-template-columns: 1fr` (single column) + +**Each activity item:** +- `display: flex`, `gap: 10px` +- `padding: 10px 12px` +- `background: var(--bg)` (#F0F5F4) +- `border-radius: var(--radius-sm)` (6px) +- `border: 1px solid var(--border-light)` +- 12px font +- `transition: border-color 0.15s` +- Hover: `border-color: var(--accent-border)` + +**Dot (left):** +- 8px circle, flex-shrink-0, `margin-top: 2px` (aligns with text) +- Color by type: + - Role: teal (#0D6E6E) + - Project: amber (#D97706) + - Certification: green (#059669) + - Education: purple (#7C3AED) + +**Content (right):** +- Title: 600 weight, text-primary, `line-height: 1.3` +- Meta: 11px, text-secondary, `margin-top: 2px` +- Date: 10px, mono font, text-tertiary, `margin-top: 3px` + +**Building the timeline data:** + +Merge entries from multiple data sources, sorted newest-first: + +```typescript +type ActivityType = 'role' | 'project' | 'cert' | 'edu' + +interface ActivityEntry { + id: string + type: ActivityType + title: string + meta: string + date: string + sortYear: number // for sorting +} +``` + +Sources: +1. `consultations` → type "role": title=role, meta=organization, date=duration +2. `investigations` (selected key ones) → type "project": title=name, meta=short description, date=year +3. `documents` where type='Certificate' → type "cert": title=title, meta=source, date=date +4. `documents` where type='Results' (MPharm) → type "edu": title=title, meta=source, date=date + +Match the concept HTML entries: +| Type | Title | Meta | Date | +|------|-------|------|------| +| role | Interim Head, Population Health & Data Analysis | NHS Norfolk & Waveney ICB | 2024 – 2025 | +| project | £220M Prescribing Budget Oversight | Lead analyst & budget owner | 2024 | +| role | Senior Data Analyst — Medicines Optimisation | NHS Norfolk & Waveney ICB | 2021 – 2024 | +| project | SQL Analytics Transformation | Legacy migration project lead | 2025 | +| cert | Power BI Data Analyst Associate | Microsoft Certified | 2023 | +| role | Prescribing Data Pharmacist | NHS Norwich CCG | 2018 – 2021 | +| cert | Clinical Pharmacy Diploma | Professional development | 2019 | +| role | Community Pharmacist | Boots UK | 2016 – 2018 | +| edu | MPharm (Hons) — 2:1 | University of East Anglia | 2011 – 2015 | +| cert | GPhC Registration | General Pharmaceutical Council | August 2016 | + +**Expansion prep:** Activity items should accept onClick for Task 16 (expand to show full role/project detail). + +--- + +## Task 14: Education Tile + +### File: `src/components/tiles/EducationTile.tsx` + +**Layout:** Full-width card, below Career Activity. + +**Content:** +- CardHeader: purple dot (#7C3AED) + "EDUCATION" + +**Education entries:** +Vertical stack of education items. + +Each item: +- `padding: 7px 10px` +- `background: var(--surface)` (#FFFFFF) +- `border: 1px solid var(--border-light)` +- `border-radius: var(--radius-sm)` (6px) +- 11.5px, text-primary + +Structure: +- Degree name: 600 weight, `display: block` +- Detail: text-secondary, 11px, `margin-top: 2px` + +**Entries** (from CV): +| Degree | Detail | +|--------|--------| +| MPharm (Hons) — 2:1 | University of East Anglia · 2015 | +| NHS Leadership Academy — Mary Seacole Programme | 2018 · 78% | +| A-Levels: Mathematics (A*), Chemistry (B), Politics (C) | Highworth Grammar School · 2009–2011 | + +**Data:** Filter `src/data/documents.ts` for education entries, or hardcode from CV since the documents data may not have all education entries. + +Note: The concept HTML only shows the MPharm entry. But the CV has more education. Include all CV education entries. + +--- + +## Task 15: Projects Tile + +### File: `src/components/tiles/ProjectsTile.tsx` + +**Layout:** Full-width card, prominent position. + +**Content:** +- CardHeader: amber dot + "ACTIVE PROJECTS" + +**Project entries:** +Vertical list, styled as interactive items. + +Each project: +- `display: flex`, `align-items: flex-start`, `gap: 8px` +- `padding: 7px 10px` +- `background: var(--surface)`, `border: 1px solid var(--border-light)` +- `border-radius: var(--radius-sm)` (6px) +- 11.5px, text-primary +- Hover: `border-color: var(--accent-border)` +- `transition: border-color 0.15s` + +Structure: +- **Status dot** (7px circle, flex-shrink-0, `margin-top: 4px`): + - Complete: success (#059669) + - Ongoing: accent (#0D6E6E) + - Live: success with pulse animation +- **Project name**: text-primary, flex 1 +- **Year badge**: 10px, mono font, text-tertiary, `margin-left: auto`, flex-shrink-0 + +**Data:** `import { investigations } from '@/data/investigations'` + +Map investigations to projects: +- name → Project name +- status → dot color +- requestedYear → Year badge +- resultSummary → Available for expansion (Task 16) + +**Expansion prep:** Each item should accept onClick for Task 16 (expand to show methodology, tech stack, results). diff --git a/Ralph/refs/ref-07-interactions.md b/Ralph/refs/ref-07-interactions.md new file mode 100644 index 0000000..3256ad9 --- /dev/null +++ b/Ralph/refs/ref-07-interactions.md @@ -0,0 +1,248 @@ +# Reference: Tasks 16-18 — Interactions + +## Task 16: Tile Expansion System + +### Overview + +Three tiles have expandable items: CareerActivity (roles), Projects, and CoreSkills. Clicking an item expands it in-place to reveal detail, like expanding a clinical record entry. + +### Expansion Pattern (consistent across all tiles) + +**Animation:** +- Framer Motion `AnimatePresence` + `motion.div` +- Height-only animation: 200ms, ease-out +- **No opacity fade on content** (guardrail) +- `overflow: hidden` on the animated container + +```typescript + + {isExpanded && ( + + {/* expanded content */} + + )} + +``` + +**Behavior:** +- Single-expand accordion: only one item expanded at a time within each tile +- Click expanded item again to collapse +- Click different item: collapses current, expands new +- State: `expandedItemId: string | null` in each tile component + +**Keyboard:** +- Enter/Space: toggle expand/collapse +- Escape: collapse current item +- `aria-expanded` on each clickable item + +**Visual:** +- Expanded content has slightly different background (`var(--bg)` or subtle border-left) +- Colored left border on expanded panel (accent color for roles, amber for projects, teal for skills) +- Content padding: 12-16px + +### CareerActivity Expansion (roles) + +When a role-type activity item is expanded: +- Show full role details from corresponding consultation entry +- Structure: role title, organization, date range +- Achievement bullets (examination array from consultation) +- Coded entries if available +- Match expanded content to `consultations` data by mapping activity item to consultation + +### Projects Expansion + +When a project item is expanded: +- Show from investigation data: + - Methodology + - Tech stack (as tags or inline list) + - Results (bulleted) + - External URL link if available ("View Results" button) + +### CoreSkills Expansion + +When a skill item is expanded: +- Show "prescribing history" — a timeline of skill development +- Source: Can use the existing `medications` data which has `prescribingHistory` entries +- Format: vertical timeline with year markers and descriptions + - Timeline dots: accent color, 6px, with connecting line + - Year: mono font, 12px, semibold + - Description: 12px, regular + +--- + +## Task 17: KPI Flip Cards + +### Overview + +In the LatestResults tile, each metric card can be clicked to "flip" and reveal an explanation of that KPI. + +### Flip Animation + +**CSS Perspective approach:** +```css +.metric-card { + perspective: 1000px; + cursor: pointer; +} + +.metric-card-inner { + transition: transform 0.4s ease-in-out; + transform-style: preserve-3d; + position: relative; +} + +.metric-card-inner.flipped { + transform: rotateY(180deg); +} + +.metric-card-front, +.metric-card-back { + backface-visibility: hidden; + position: absolute; + inset: 0; +} + +.metric-card-back { + transform: rotateY(180deg); +} +``` + +Or use Framer Motion `animate={{ rotateY: isFlipped ? 180 : 0 }}` with `perspective` on parent. + +**Behavior:** +- Click to flip front → back +- Click again to flip back → front +- Only one card flipped at a time (clicking another card flips the current one back) +- State: `flippedCardId: string | null` in LatestResultsTile + +**Front face:** Current metric display (value + label + sub) — same as Task 10. + +**Back face:** +- `background: var(--accent-light)` (subtle teal tint) +- `padding: 14px` +- Text: 12px, text-secondary, `line-height: 1.5` +- The explanation text from KPI data's `explanation` field + +**Reduced motion:** +- No 3D flip animation +- Instant content swap (front → back) +- Could use a simple crossfade or just replace content immediately + +**Keyboard:** +- Enter/Space to flip +- Each metric card should be `tabIndex={0}` with appropriate `aria-label` + +**KPI Explanations** (from `src/data/kpis.ts`): +- £220M: Budget management with forecasting models +- £14.6M: Efficiency programme through data analysis +- 9+ Years: NHS service progression since 2016 +- 12: Cross-functional team leadership + +--- + +## Task 18: Command Palette + +### File: `src/components/CommandPalette.tsx` + +### Trigger +- **Ctrl+K** (global `keydown` listener on `document`) +- **Click** on TopBar search bar (or focus on search input) +- The TopBar search input does NOT do inline search — it opens the palette + +### Overlay +- `position: fixed`, `inset: 0` +- `background: rgba(26,43,42,0.45)` +- `backdrop-filter: blur(4px)` +- `z-index: 1000` +- Fade in: `opacity: 0 → 1`, `visibility: hidden → visible`, 200ms transition +- Click overlay (outside modal) to close + +### Palette Modal +- `width: 580px`, `max-height: 520px` +- `background: var(--surface)` (#FFFFFF) +- `border-radius: 12px` +- `box-shadow: 0 20px 60px rgba(26,43,42,0.2), 0 0 0 1px rgba(26,43,42,0.08)` +- `overflow: hidden` +- Entrance: `transform: scale(0.97) translateY(-8px)` → `scale(1) translateY(0)`, 200ms cubic-bezier + +### Search Input +- Flex row: search icon (18px, accent) + input + "ESC" hint badge +- `padding: 14px 18px`, `border-bottom: 1px solid var(--border-light)` +- Input: 15px, font-body, placeholder "Search records, experience, skills..." +- ESC badge: mono 10px, tertiary, bg var(--bg), border, padding 2px 7px, radius 4px + +### Results Area +- `overflow-y: auto`, `padding: 8px`, `flex: 1` +- Custom scrollbar (4px) + +### Result Sections +Section label: 10px, 600 weight, uppercase, `letter-spacing: 0.08em`, text-tertiary, `padding: 8px 10px 5px` + +### Result Items +- `display: flex`, `align-items: center`, `gap: 10px` +- `padding: 9px 10px`, `border-radius: var(--radius-sm)` (6px) +- `cursor: pointer`, `transition: background 0.1s` +- 13px, text-primary +- Hover/selected: `background: var(--accent-light)` +- Selected also gets: `outline: 1.5px solid var(--accent-border)` + +**Item structure:** +- Icon container: 28px square, 6px radius, colored bg per section + - Experience: teal + - Core Skills: green + - Active Projects: amber + - Achievements: amber + - Education: purple + - Quick Actions: teal +- Text: title (500 weight) + subtitle (11px, tertiary, truncated) +- Optional badge: 10px, mono, tertiary + +### Fuzzy Search + +Adapt existing `src/lib/search.ts` (fuse.js integration): +- Rebuild search index to include new data (skills from skills.ts, KPIs, etc.) +- `threshold: 0.3`, weighted keys (title: 2, content: 1) +- `minMatchCharLength: 2` +- Group results by section +- Highlight matching text in titles using `` with accent-light background + +### Keyboard Navigation +- **Arrow Down/Up**: move selection through results +- **Enter**: select highlighted result (navigate to section or trigger action) +- **Escape**: close palette +- `selectedIndex` state tracks which result is highlighted +- Auto-scroll highlighted result into view + +### Quick Actions Section +| Title | Subtitle | Action | +|-------|----------|--------| +| Download CV | Export as PDF | Trigger download | +| Send Email | andy@charlwood.xyz | `mailto:` link | +| View LinkedIn | Professional profile | External link | +| View Projects | GitHub & portfolio | External link | + +### Footer +- `display: flex`, `gap: 12px` +- `padding: 10px 18px`, `border-top: 1px solid var(--border-light)` +- 11px, text-tertiary +- Keyboard hints: `↑ ↓ Navigate`, `Enter Select`, `Esc Close` +- Each key in `` styled element + +### Reduced Motion +- No scale/translate entrance animation +- Instant show/hide (opacity only, or immediate) + +### State Management +```typescript +const [isOpen, setIsOpen] = useState(false) +const [query, setQuery] = useState('') +const [selectedIndex, setSelectedIndex] = useState(-1) +``` + +Render the palette at the DashboardLayout level so it overlays everything. diff --git a/Ralph/refs/ref-08-polish.md b/Ralph/refs/ref-08-polish.md new file mode 100644 index 0000000..0335374 --- /dev/null +++ b/Ralph/refs/ref-08-polish.md @@ -0,0 +1,156 @@ +# Reference: Tasks 19-21 — Polish + +## Task 19: Responsive Design + +### Desktop (>1024px) +- Full sidebar (272px) + TopBar + 2-column card grid +- All tiles at full spec (as designed in Tasks 8-15) +- Command palette at 580px width + +### Tablet (768–1024px) +- Sidebar: collapse to icon-only (56px) or hide entirely with toggle +- TopBar: full, but search bar may shrink (reduce min-width) +- Card grid: can stay 2-column if space permits, or switch to 1-column +- Activity grid inside CareerActivity tile: switch to 1-column + +### Mobile (<768px) +- Sidebar: hidden entirely (off-canvas or removed) +- TopBar: simplified — brand text may truncate, hide search bar center section +- Navigation: consider a hamburger menu or bottom nav for key actions +- Card grid: single column +- All tiles stack vertically (full-width) +- Metric grid in LatestResults: stays 2x2 (compact enough) +- Activity grid in CareerActivity: single column +- Touch targets: all clickable elements 48px+ minimum +- Command palette: full-width with reduced padding + +### Breakpoint Strategy +Use Tailwind responsive prefixes: +- `lg:` for desktop (>1024px) +- `md:` for tablet (>768px) +- Default styles for mobile-first + +### Key responsive classes: +``` +/* Card grid */ +grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-[16px] + +/* Sidebar visibility */ +hidden lg:flex lg:flex-col + +/* TopBar search */ +hidden md:block + +/* Activity grid */ +grid grid-cols-1 md:grid-cols-2 + +/* Sidebar width */ +lg:w-[272px] lg:min-w-[272px] +``` + +--- + +## Task 20: Accessibility Audit + +### Semantic HTML +| Element | Tag | Notes | +|---------|-----|-------| +| TopBar | `
` | Fixed at top | +| Sidebar | `