feat: US-007 - Colour-match work experience cards to constellation node colours
This commit is contained in:
+1
-1
@@ -138,7 +138,7 @@
|
||||
"Verify in browser: NHS roles show blue-tinted cards, Tesco roles red-tinted, Paydens green, education purple"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": false,
|
||||
"passes": true,
|
||||
"notes": "All changes in WorkExperienceSubsection.tsx (~299 lines). consultation.orgColor already exists on each consultation object but is not currently used for card styling. Create a helper function hexToRgba(hex: string, opacity: number): string that converts hex to rgba — needed for tinted backgrounds and borders. Replace hardcoded values: '#0D6E6E' for dot (line ~82), 'rgba(10,128,128,0.03)' for highlight bg, 'var(--accent-border)' for border, 'var(--accent)' for links/text. Each RoleItem already receives its consultation — use consultation.orgColor. For coded entry tags: text in orgColor, bg in hexToRgba(orgColor, 0.08), border in hexToRgba(orgColor, 0.2). Also update LastConsultationSubsection in DashboardLayout.tsx if it has hardcoded teal colours. The WORK EXPERIENCE CardHeader dot stays teal. Use the d3-viz skill."
|
||||
},
|
||||
{
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
- Constellation role nodes, skill mappings, and links are in constellation.ts — adding nodes there automatically extends yScale domain and screen reader description
|
||||
- Mobile accordion (coarse pointer): pinnedNodeId drives both graph highlight AND accordion visibility. Accordion only shows for role-type nodes (not skills)
|
||||
- SVG background rect has class `.bg-rect` — used for "tap elsewhere to close" handler on touch devices
|
||||
- consultation.orgColor is the source of per-employer colour for cards, dots, borders, and coded entries. Use hexToRgba(orgColor, opacity) for tinted variants
|
||||
- hexToRgba(hex, opacity) helper exists in both WorkExperienceSubsection.tsx and DashboardLayout.tsx for converting hex to rgba
|
||||
|
||||
## 2026-02-16 - US-001
|
||||
- Added Duty Pharmacy Manager (2016-2017, Tesco PLC) and Pre-Registration Pharmacist (2015-2016, Paydens Pharmacy) role nodes to constellation.ts
|
||||
@@ -133,3 +135,26 @@
|
||||
- Not all consultations have >3 examination items — the "Show more" button only renders conditionally, and plan items are only shown when expanded
|
||||
- Browser testing for coarse pointer features requires touch emulation — Playwright's default Chromium reports fine pointer, so the accordion won't appear without explicit touch device emulation
|
||||
---
|
||||
|
||||
## 2026-02-16 - US-007
|
||||
- Created hexToRgba(hex, opacity) helper function in both WorkExperienceSubsection.tsx and DashboardLayout.tsx
|
||||
- WorkExperienceSubsection.tsx: replaced all hardcoded teal/accent colour references with consultation.orgColor:
|
||||
- Dot indicator: '#0D6E6E' → consultation.orgColor
|
||||
- Highlight background: 'rgba(10,128,128,0.03)' → hexToRgba(orgColor, 0.03)
|
||||
- Expanded/highlighted border: 'var(--accent-border)' → hexToRgba(orgColor, 0.2)
|
||||
- Hover border: 'var(--accent-border)' → hexToRgba(orgColor, 0.2)
|
||||
- Left border on expanded detail: 'var(--accent)' → orgColor
|
||||
- Bullet dots: 'var(--accent)' → orgColor at 0.5 opacity
|
||||
- Coded entry tags: bg hexToRgba(orgColor, 0.08), text orgColor, border hexToRgba(orgColor, 0.2)
|
||||
- "View full record" link: 'var(--accent)' → orgColor, hover uses opacity 0.7 instead of accent-hover
|
||||
- DashboardLayout.tsx LastConsultationSubsection: same pattern applied:
|
||||
- Highlight border/bg, hover bg, role title, bullet dots, "View full record" link all use consultation.orgColor
|
||||
- CardHeader dot for "WORK EXPERIENCE" section title remains teal (unchanged)
|
||||
- Files changed: src/components/WorkExperienceSubsection.tsx, src/components/DashboardLayout.tsx, Ralph/prd.json, Ralph/progress.txt
|
||||
- Browser verified: NHS roles show blue dots/borders, Tesco roles show red, Paydens shows green, education shows purple. Expanded Tesco card shows red left border, red bullet dots, and red-tinted coded entries
|
||||
- **Learnings for future iterations:**
|
||||
- consultation.orgColor exists on every Consultation object — it's the single source for per-employer colour throughout the UI
|
||||
- hexToRgba(hex, opacity) is needed in both WorkExperienceSubsection.tsx and DashboardLayout.tsx — not extracted to a shared utility since it's a small helper and only used in two files
|
||||
- For hover effects on org-coloured links, use opacity change (0.7) instead of a separate --accent-hover variable, since each employer has a different base colour
|
||||
- The hover mouseenter/mouseleave pattern using parentElement!.style is used for border/shadow effects — it directly mutates the parent wrapper's inline styles
|
||||
---
|
||||
|
||||
@@ -55,6 +55,13 @@ const contentVariants = {
|
||||
},
|
||||
}
|
||||
|
||||
function hexToRgba(hex: string, opacity: number): string {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
const b = parseInt(hex.slice(5, 7), 16)
|
||||
return `rgba(${r},${g},${b},${opacity})`
|
||||
}
|
||||
|
||||
interface LastConsultationSubsectionProps {
|
||||
highlightedRoleId?: string | null
|
||||
}
|
||||
@@ -114,8 +121,8 @@ function LastConsultationSubsection({ highlightedRoleId }: LastConsultationSubse
|
||||
marginTop: '24px',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: '1px solid',
|
||||
borderColor: isHighlighted ? 'var(--accent-border)' : 'transparent',
|
||||
background: isHighlighted ? 'rgba(10,128,128,0.03)' : 'transparent',
|
||||
borderColor: isHighlighted ? hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.2) : 'transparent',
|
||||
background: isHighlighted ? hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.03) : 'transparent',
|
||||
transition: 'border-color 150ms ease-out, background-color 150ms ease-out',
|
||||
padding: '8px',
|
||||
margin: '-8px',
|
||||
@@ -142,7 +149,7 @@ function LastConsultationSubsection({ highlightedRoleId }: LastConsultationSubse
|
||||
transition: 'background-color 150ms ease-out',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(10,128,128,0.04)'
|
||||
e.currentTarget.style.backgroundColor = hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.04)
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent'
|
||||
@@ -171,7 +178,7 @@ function LastConsultationSubsection({ highlightedRoleId }: LastConsultationSubse
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--accent)',
|
||||
color: consultation.orgColor ?? 'var(--accent)',
|
||||
marginBottom: '12px',
|
||||
}}
|
||||
>
|
||||
@@ -209,7 +216,7 @@ function LastConsultationSubsection({ highlightedRoleId }: LastConsultationSubse
|
||||
width: '5px',
|
||||
height: '5px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--accent)',
|
||||
backgroundColor: consultation.orgColor ?? 'var(--accent)',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
@@ -226,19 +233,19 @@ function LastConsultationSubsection({ highlightedRoleId }: LastConsultationSubse
|
||||
gap: '6px',
|
||||
fontSize: '13px',
|
||||
fontWeight: 500,
|
||||
color: 'var(--accent)',
|
||||
color: consultation.orgColor ?? 'var(--accent)',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
padding: '6px 0',
|
||||
minHeight: '44px',
|
||||
cursor: 'pointer',
|
||||
transition: 'color 150ms ease-out',
|
||||
transition: 'opacity 150ms ease-out',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent-hover)'
|
||||
e.currentTarget.style.opacity = '0.7'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent)'
|
||||
e.currentTarget.style.opacity = '1'
|
||||
}}
|
||||
aria-label="View full consultation record"
|
||||
>
|
||||
|
||||
@@ -7,6 +7,13 @@ import { useDetailPanel } from '@/contexts/DetailPanelContext'
|
||||
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
|
||||
function hexToRgba(hex: string, opacity: number): string {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
const b = parseInt(hex.slice(5, 7), 16)
|
||||
return `rgba(${r},${g},${b},${opacity})`
|
||||
}
|
||||
|
||||
interface RoleItemProps {
|
||||
consultation: typeof consultations[0]
|
||||
isExpanded: boolean
|
||||
@@ -34,9 +41,9 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: isHighlightedFromGraph ? 'rgba(10,128,128,0.03)' : 'var(--bg-dashboard)',
|
||||
background: isHighlightedFromGraph ? hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.03) : 'var(--bg-dashboard)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
border: `1px solid ${isExpanded || isHighlightedFromGraph ? 'var(--accent-border)' : 'var(--border-light)'}`,
|
||||
border: `1px solid ${isExpanded || isHighlightedFromGraph ? hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.2) : 'var(--border-light)'}`,
|
||||
transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.15s',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
@@ -61,7 +68,7 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isExpanded) {
|
||||
e.currentTarget.parentElement!.style.borderColor = 'var(--accent-border)'
|
||||
e.currentTarget.parentElement!.style.borderColor = hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.2)
|
||||
e.currentTarget.parentElement!.style.boxShadow = 'var(--shadow-md)'
|
||||
}
|
||||
}}
|
||||
@@ -72,14 +79,14 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Teal dot */}
|
||||
{/* Org colour dot */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: '9px',
|
||||
height: '9px',
|
||||
borderRadius: '50%',
|
||||
background: '#0D6E6E',
|
||||
background: consultation.orgColor ?? '#0D6E6E',
|
||||
flexShrink: 0,
|
||||
marginTop: '4px',
|
||||
}}
|
||||
@@ -150,7 +157,7 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
padding: '0 12px 12px 30px',
|
||||
borderTop: '1px solid var(--border-light)',
|
||||
paddingTop: '12px',
|
||||
borderLeft: '2px solid var(--accent)',
|
||||
borderLeft: `2px solid ${consultation.orgColor ?? 'var(--accent)'}`,
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
@@ -185,7 +192,7 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
borderRadius: '50%',
|
||||
background: 'var(--accent)',
|
||||
background: consultation.orgColor ?? 'var(--accent)',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
@@ -211,9 +218,9 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
fontFamily: 'var(--font-mono)',
|
||||
padding: '3px 8px',
|
||||
borderRadius: '4px',
|
||||
background: 'var(--accent-light)',
|
||||
color: 'var(--accent)',
|
||||
border: '1px solid var(--accent-border)',
|
||||
background: hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.08),
|
||||
color: consultation.orgColor ?? 'var(--accent)',
|
||||
border: `1px solid ${hexToRgba(consultation.orgColor ?? '#0D6E6E', 0.2)}`,
|
||||
}}
|
||||
>
|
||||
{entry.code}: {entry.description}
|
||||
@@ -233,7 +240,7 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
gap: '4px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
color: 'var(--accent)',
|
||||
color: consultation.orgColor ?? 'var(--accent)',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
padding: '4px 0',
|
||||
@@ -241,10 +248,10 @@ function RoleItem({ consultation, isExpanded, isHighlightedFromGraph, onToggle,
|
||||
fontFamily: 'inherit',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent-hover)'
|
||||
e.currentTarget.style.opacity = '0.7'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = 'var(--accent)'
|
||||
e.currentTarget.style.opacity = '1'
|
||||
}}
|
||||
>
|
||||
View full record
|
||||
|
||||
Reference in New Issue
Block a user