refactor(swift): Update UI styles and animations for improved consistency

- Changed background colors and text colors across various components to enhance readability and visual appeal.
- Updated animation properties for smoother transitions in CanvasArea and BlockContainer.
- Adjusted styles in FallbackPicker, OverdrachtBlock, and other components to align with the new design system.
- Improved accessibility by ensuring color contrasts meet standards.

This update aims to create a more cohesive user experience throughout the application.
This commit is contained in:
ikbenlit
2025-12-26 13:10:04 +01:00
parent d67405df5f
commit 5174b48f48
13 changed files with 513 additions and 153 deletions

View File

@@ -25,12 +25,18 @@ const SIZE_CLASSES: Record<BlockSize, string> = {
full: 'max-w-4xl',
};
// Container animations
// Container animations - consistent met CanvasArea slide up/down
// Slide up + fade in bij openen, scale 0.95 → 1.0 (200ms)
const containerVariants: Variants = {
initial: { scale: 0.98, opacity: 0 },
initial: {
scale: 0.95,
opacity: 0,
y: 0, // BlockContainer animatie wordt door CanvasArea gedaan
},
animate: {
scale: 1,
opacity: 1,
y: 0,
transition: {
duration: 0.2,
ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
@@ -67,15 +73,15 @@ export function BlockContainer({ title, size = 'md', children }: BlockContainerP
variants={containerVariants}
initial="initial"
animate="animate"
className={`w-full ${SIZE_CLASSES[size]} bg-slate-800 rounded-xl border border-slate-700 shadow-2xl`}
className={`w-full ${SIZE_CLASSES[size]} bg-white rounded-xl border border-slate-200 shadow-lg`}
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-700">
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-200">
<motion.h2
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.05, duration: 0.2 }}
className="text-lg font-medium text-white"
className="text-lg font-medium text-slate-900"
>
{title}
</motion.h2>
@@ -85,7 +91,7 @@ export function BlockContainer({ title, size = 'md', children }: BlockContainerP
initial="rest"
whileHover="hover"
whileTap="tap"
className="p-1 rounded hover:bg-slate-700 text-slate-400 hover:text-white transition-colors"
className="p-1 rounded hover:bg-slate-100 text-slate-400 hover:text-slate-700 transition-colors"
title="Sluiten (Esc)"
aria-label="Block sluiten"
>

View File

@@ -240,15 +240,15 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
<Label htmlFor="patient-search">Patiënt *</Label>
<div className="relative" ref={dropdownRef}>
{selectedPatient ? (
<div className="flex items-center gap-2 p-2 rounded-md border border-slate-700 bg-slate-800/50">
<User className="h-4 w-4 text-slate-400" />
<span className="flex-1 text-sm text-white">{formatPatientName(selectedPatient)}</span>
<div className="flex items-center gap-2 p-2 rounded-md border border-slate-200 bg-slate-50">
<User className="h-4 w-4 text-slate-500" />
<span className="flex-1 text-sm text-slate-900">{formatPatientName(selectedPatient)}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleClearPatient}
className="h-6 w-6 p-0 text-slate-400 hover:text-white"
className="h-6 w-6 p-0 text-slate-400 hover:text-slate-700"
>
×
</Button>
@@ -278,13 +278,13 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
)}
</div>
{showPatientDropdown && patients.length > 0 && (
<div className="absolute z-50 w-full mt-1 bg-slate-800 border border-slate-700 rounded-md shadow-lg max-h-60 overflow-auto">
<div className="absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-md shadow-lg max-h-60 overflow-auto">
{patients.map((patient) => (
<button
key={patient.id}
type="button"
onClick={() => handleSelectPatient(patient)}
className="w-full px-3 py-2 text-left text-sm text-slate-300 hover:bg-slate-700 transition-colors"
className="w-full px-3 py-2 text-left text-sm text-slate-700 hover:bg-slate-50 transition-colors"
>
<div className="font-medium">{formatPatientName(patient)}</div>
{patient.identifier_bsn && (
@@ -314,8 +314,8 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
className={cn(
'px-3 py-2 rounded-md text-sm font-medium transition-colors',
isSelected
? 'bg-slate-700 text-white border-2 border-slate-500'
: 'bg-slate-800/50 text-slate-400 border border-slate-700 hover:bg-slate-700 hover:text-white'
? 'bg-slate-900 text-white border-2 border-slate-700'
: 'bg-slate-50 text-slate-600 border border-slate-200 hover:bg-slate-100 hover:text-slate-900'
)}
>
{catConfig.label}
@@ -349,9 +349,9 @@ export function DagnotatieBlock({ prefill }: DagnotitieBlockProps) {
id="include-handover"
checked={includeInHandover}
onChange={(e) => setIncludeInHandover(e.target.checked)}
className="h-4 w-4 rounded border-slate-600 bg-slate-800 text-slate-600 focus:ring-slate-500"
className="h-4 w-4 rounded border-slate-300 bg-white text-blue-600 focus:ring-blue-500"
/>
<Label htmlFor="include-handover" className="cursor-pointer text-sm text-slate-300">
<Label htmlFor="include-handover" className="cursor-pointer text-sm text-slate-700">
Opnemen in overdracht
</Label>
</div>

View File

@@ -33,7 +33,7 @@ const BLOCK_OPTIONS: BlockOption[] = [
description: 'Schrijf een dagnotitie',
icon: FileText,
shortcut: '1',
color: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
color: 'bg-blue-50 text-blue-600 border-blue-200',
},
{
type: 'zoeken',
@@ -41,7 +41,7 @@ const BLOCK_OPTIONS: BlockOption[] = [
description: 'Zoek een patiënt',
icon: Search,
shortcut: '2',
color: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30',
color: 'bg-emerald-50 text-emerald-600 border-emerald-200',
},
{
type: 'overdracht',
@@ -49,7 +49,7 @@ const BLOCK_OPTIONS: BlockOption[] = [
description: 'Bekijk overdracht',
icon: ArrowRightLeft,
shortcut: '3',
color: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
color: 'bg-amber-50 text-amber-600 border-amber-200',
},
];
@@ -108,14 +108,14 @@ export function FallbackPicker({ originalInput }: FallbackPickerProps) {
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] as [number, number, number, number] }}
className="w-full max-w-md bg-slate-800 rounded-xl border border-slate-700 shadow-2xl overflow-hidden"
className="w-full max-w-md bg-white rounded-xl border border-slate-200 shadow-lg overflow-hidden"
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-700">
<h2 className="text-lg font-medium text-white">Wat wil je doen?</h2>
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-200">
<h2 className="text-lg font-medium text-slate-900">Wat wil je doen?</h2>
<button
onClick={closeBlock}
className="p-1 rounded hover:bg-slate-700 text-slate-400 hover:text-white transition-colors"
className="p-1 rounded hover:bg-slate-100 text-slate-400 hover:text-slate-700 transition-colors"
title="Sluiten (Esc)"
aria-label="Sluiten"
>
@@ -125,9 +125,9 @@ export function FallbackPicker({ originalInput }: FallbackPickerProps) {
{/* Original input display */}
{originalInput && (
<div className="px-4 py-2 bg-slate-900/50 border-b border-slate-700">
<p className="text-sm text-slate-400">
Je zei: <span className="text-slate-300">&quot;{originalInput}&quot;</span>
<div className="px-4 py-2 bg-slate-50 border-b border-slate-200">
<p className="text-sm text-slate-500">
Je zei: <span className="text-slate-700">&quot;{originalInput}&quot;</span>
</p>
</div>
)}
@@ -141,12 +141,12 @@ export function FallbackPicker({ originalInput }: FallbackPickerProps) {
onClick={() => handleSelect(option)}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className={`flex flex-col items-center gap-2 p-4 rounded-lg border transition-all ${option.color} hover:brightness-110`}
className={`flex flex-col items-center gap-2 p-4 rounded-lg border transition-all ${option.color} hover:shadow-md`}
>
<option.icon size={28} />
<span className="font-medium text-sm">{option.label}</span>
<span className="text-xs opacity-60 text-center">{option.description}</span>
<span className="mt-1 px-2 py-0.5 bg-slate-900/50 rounded text-xs text-slate-400">
<span className="text-xs opacity-70 text-center">{option.description}</span>
<span className="mt-1 px-2 py-0.5 bg-slate-100 rounded text-xs text-slate-500">
[{option.shortcut}]
</span>
</motion.button>
@@ -155,7 +155,7 @@ export function FallbackPicker({ originalInput }: FallbackPickerProps) {
</div>
{/* Footer hint */}
<div className="px-4 py-2 bg-slate-900/30 border-t border-slate-700">
<div className="px-4 py-2 bg-slate-50 border-t border-slate-200">
<p className="text-xs text-slate-500 text-center">
Druk op [1], [2] of [3] voor snelle selectie
</p>

View File

@@ -191,7 +191,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
<div className="space-y-6">
{/* Period Selector */}
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300">Periode</label>
<label className="text-sm font-medium text-slate-700">Periode</label>
<div className="grid grid-cols-4 gap-2">
{PERIOD_OPTIONS.map((option) => (
<button
@@ -201,8 +201,8 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
className={cn(
'px-3 py-2 rounded-lg text-sm font-medium transition-colors',
period === option.value
? 'bg-slate-700 text-white border-2 border-slate-500'
: 'bg-slate-800/50 text-slate-400 border border-slate-700 hover:bg-slate-700 hover:text-white'
? 'bg-slate-900 text-white border-2 border-slate-700'
: 'bg-slate-50 text-slate-600 border border-slate-200 hover:bg-slate-100 hover:text-slate-900'
)}
>
{option.label}
@@ -218,10 +218,10 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
{isLoadingPatients ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-slate-400 mr-2" />
<span className="text-sm text-slate-400">Patiënten laden...</span>
<span className="text-sm text-slate-500">Patiënten laden...</span>
</div>
) : displayPatients.length === 0 ? (
<div className="text-center py-8 text-slate-500">
<div className="text-center py-8 text-slate-400">
<Users className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">Geen patiënten gevonden voor deze periode</p>
</div>
@@ -236,21 +236,21 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
return (
<div
key={patient.id}
className="p-4 rounded-lg bg-slate-800/50 border border-slate-700"
className="p-4 rounded-lg bg-slate-50 border border-slate-200"
>
{/* Patient Header */}
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-base font-medium text-white">
<h3 className="text-base font-medium text-slate-900">
{formatPatientName(patient)}
</h3>
{patient.alerts.total > 0 && (
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-slate-400">
<span className="text-xs text-slate-500">
{patient.alerts.total} alert{patient.alerts.total > 1 ? 's' : ''}
</span>
{patient.alerts.high_risk_count > 0 && (
<span className="text-xs px-1.5 py-0.5 rounded bg-red-900/30 text-red-300">
<span className="text-xs px-1.5 py-0.5 rounded bg-red-100 text-red-700 border border-red-200">
{patient.alerts.high_risk_count} risico
</span>
)}
@@ -272,18 +272,18 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
{/* Loading State */}
{loading && (
<div className="py-6 text-center">
<Loader2 className="h-6 w-6 text-violet-400 mx-auto mb-2 animate-spin" />
<p className="text-sm text-slate-400">Samenvatting wordt gegenereerd...</p>
<Loader2 className="h-6 w-6 text-violet-600 mx-auto mb-2 animate-spin" />
<p className="text-sm text-slate-500">Samenvatting wordt gegenereerd...</p>
</div>
)}
{/* Error State */}
{error && (
<div className="py-4">
<div className="p-3 bg-red-900/20 rounded-lg border border-red-800 mb-3">
<div className="p-3 bg-red-50 rounded-lg border border-red-200 mb-3">
<div className="flex items-start gap-2">
<AlertTriangle className="h-4 w-4 text-red-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-300">{error}</p>
<AlertTriangle className="h-4 w-4 text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-700">{error}</p>
</div>
</div>
<Button
@@ -300,11 +300,11 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
{/* Summary Content */}
{summary && (
<div className="space-y-4 pt-4 border-t border-slate-700">
<div className="space-y-4 pt-4 border-t border-slate-200">
{/* Samenvatting */}
<div>
<h4 className="text-sm font-medium text-slate-300 mb-2">Samenvatting</h4>
<p className="text-sm text-slate-400 leading-relaxed bg-slate-900/50 p-3 rounded-lg">
<h4 className="text-sm font-medium text-slate-700 mb-2">Samenvatting</h4>
<p className="text-sm text-slate-600 leading-relaxed bg-white p-3 rounded-lg border border-slate-200">
{summary.samenvatting}
</p>
</div>
@@ -312,7 +312,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
{/* Aandachtspunten */}
{summary.aandachtspunten.length > 0 && (
<div>
<h4 className="text-sm font-medium text-slate-300 mb-2">
<h4 className="text-sm font-medium text-slate-700 mb-2">
Aandachtspunten ({summary.aandachtspunten.length})
</h4>
<div className="space-y-2">
@@ -326,16 +326,16 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
{/* Actiepunten */}
{summary.actiepunten.length > 0 && (
<div>
<h4 className="text-sm font-medium text-slate-300 mb-2">
<h4 className="text-sm font-medium text-slate-700 mb-2">
Actiepunten ({summary.actiepunten.length})
</h4>
<ul className="space-y-2">
{summary.actiepunten.map((actie, index) => (
<li
key={index}
className="flex items-start gap-2 text-sm text-slate-400"
className="flex items-start gap-2 text-sm text-slate-600"
>
<CheckCircle2 className="h-4 w-4 text-teal-400 flex-shrink-0 mt-0.5" />
<CheckCircle2 className="h-4 w-4 text-teal-600 flex-shrink-0 mt-0.5" />
<span>{actie}</span>
</li>
))}
@@ -344,7 +344,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {
)}
{/* Footer */}
<div className="pt-3 border-t border-slate-700 flex items-center justify-between">
<div className="pt-3 border-t border-slate-200 flex items-center justify-between">
<div className="flex items-center gap-2 text-xs text-slate-500">
<Clock className="h-3.5 w-3.5" />
<span>
@@ -388,15 +388,15 @@ function AandachtspuntItem({ punt }: { punt: AISamenvatting['aandachtspunten'][0
const getBronTypeStyle = (type: string): { bg: string; text: string } => {
switch (type) {
case 'observatie':
return { bg: 'bg-teal-900/30', text: 'text-teal-300' };
return { bg: 'bg-teal-50', text: 'text-teal-700' };
case 'rapportage':
return { bg: 'bg-indigo-900/30', text: 'text-indigo-300' };
return { bg: 'bg-indigo-50', text: 'text-indigo-700' };
case 'verpleegkundig':
return { bg: 'bg-amber-900/30', text: 'text-amber-300' };
return { bg: 'bg-amber-50', text: 'text-amber-700' };
case 'risico':
return { bg: 'bg-red-900/30', text: 'text-red-300' };
return { bg: 'bg-red-50', text: 'text-red-700' };
default:
return { bg: 'bg-slate-800', text: 'text-slate-400' };
return { bg: 'bg-slate-100', text: 'text-slate-600' };
}
};
@@ -407,16 +407,16 @@ function AandachtspuntItem({ punt }: { punt: AISamenvatting['aandachtspunten'][0
className={cn(
'p-3 rounded-lg border-l-4',
punt.urgent
? 'bg-red-900/20 border-red-500'
: 'bg-slate-800/50 border-slate-600'
? 'bg-red-50 border-red-500'
: 'bg-white border border-slate-200 border-l-slate-400'
)}
>
<div className="flex items-start gap-2 mb-2">
{punt.urgent && <AlertTriangle className="h-4 w-4 text-red-400 flex-shrink-0 mt-0.5" />}
{punt.urgent && <AlertTriangle className="h-4 w-4 text-red-500 flex-shrink-0 mt-0.5" />}
<p
className={cn(
'text-sm flex-1',
punt.urgent ? 'text-red-200 font-medium' : 'text-slate-300'
punt.urgent ? 'text-red-700 font-medium' : 'text-slate-700'
)}
>
{punt.tekst}

View File

@@ -61,12 +61,12 @@ export function PatientContextCard() {
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-slate-400 mr-2" />
<span className="text-sm text-slate-400">Context laden...</span>
<span className="text-sm text-slate-500">Context laden...</span>
</div>
) : error ? (
<div className="text-center py-8">
<AlertTriangle className="h-8 w-8 text-red-400 mx-auto mb-2" />
<p className="text-sm text-red-600">{error}</p>
<AlertTriangle className="h-8 w-8 text-red-500 mx-auto mb-2" />
<p className="text-sm text-red-700">{error}</p>
</div>
) : data ? (
<div className="space-y-6">
@@ -74,8 +74,8 @@ export function PatientContextCard() {
{data.reports && data.reports.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-3">
<FileText className="h-4 w-4 text-slate-400" />
<h3 className="text-sm font-medium text-slate-300">Recente notities</h3>
<FileText className="h-4 w-4 text-slate-500" />
<h3 className="text-sm font-medium text-slate-700">Recente notities</h3>
<span className="text-xs text-slate-500">({data.reports.length})</span>
</div>
<div className="space-y-2">
@@ -95,8 +95,8 @@ export function PatientContextCard() {
{data.vitals && data.vitals.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-3">
<Activity className="h-4 w-4 text-slate-400" />
<h3 className="text-sm font-medium text-slate-300">Vitale functies (vandaag)</h3>
<Activity className="h-4 w-4 text-slate-500" />
<h3 className="text-sm font-medium text-slate-700">Vitale functies (vandaag)</h3>
<span className="text-xs text-slate-500">({data.vitals.length})</span>
</div>
<div className="grid grid-cols-2 gap-2">
@@ -111,8 +111,8 @@ export function PatientContextCard() {
{data.conditions && data.conditions.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-3">
<Stethoscope className="h-4 w-4 text-slate-400" />
<h3 className="text-sm font-medium text-slate-300">Actieve diagnoses</h3>
<Stethoscope className="h-4 w-4 text-slate-500" />
<h3 className="text-sm font-medium text-slate-700">Actieve diagnoses</h3>
<span className="text-xs text-slate-500">({data.conditions.length})</span>
</div>
<div className="space-y-2">
@@ -127,8 +127,8 @@ export function PatientContextCard() {
{data.risks && data.risks.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-3">
<AlertTriangle className="h-4 w-4 text-amber-400" />
<h3 className="text-sm font-medium text-slate-300">Risico&apos;s</h3>
<AlertTriangle className="h-4 w-4 text-amber-500" />
<h3 className="text-sm font-medium text-slate-700">Risico&apos;s</h3>
<span className="text-xs text-slate-500">({data.risks.length})</span>
</div>
<div className="flex flex-wrap gap-2">
@@ -144,7 +144,7 @@ export function PatientContextCard() {
(!data.vitals || data.vitals.length === 0) &&
(!data.conditions || data.conditions.length === 0) &&
(!data.risks || data.risks.length === 0) && (
<div className="text-center py-8 text-slate-500">
<div className="text-center py-8 text-slate-400">
<p className="text-sm">Geen context beschikbaar voor deze patiënt</p>
</div>
)}
@@ -159,12 +159,12 @@ function ReportItem({ report }: { report: Report }) {
const formattedDate = date ? format(date, 'd MMM HH:mm', { locale: nl }) : 'Onbekend';
return (
<div className="p-3 rounded-lg bg-slate-800/50 border border-slate-700">
<div className="p-3 rounded-lg bg-slate-50 border border-slate-200">
<div className="flex items-start justify-between gap-2 mb-1">
<div className="flex items-center gap-2 flex-1 min-w-0">
<span className="text-xs font-medium text-slate-400 uppercase">{report.type}</span>
<span className="text-xs font-medium text-slate-500 uppercase">{report.type}</span>
{report.include_in_handover && (
<span className="text-xs px-1.5 py-0.5 rounded bg-blue-900/50 text-blue-300">
<span className="text-xs px-1.5 py-0.5 rounded bg-blue-50 text-blue-700 border border-blue-200">
Overdracht
</span>
)}
@@ -174,7 +174,7 @@ function ReportItem({ report }: { report: Report }) {
{formattedDate}
</div>
</div>
<p className="text-sm text-slate-300 line-clamp-2">{report.content}</p>
<p className="text-sm text-slate-700 line-clamp-2">{report.content}</p>
</div>
);
}
@@ -189,8 +189,8 @@ function VitalItem({ vital }: { vital: VitalSign }) {
className={cn(
'p-2 rounded-lg border',
isAbnormal
? 'bg-amber-900/20 border-amber-700 text-amber-200'
: 'bg-slate-800/50 border-slate-700 text-slate-300'
? 'bg-amber-50 border-amber-200 text-amber-700'
: 'bg-slate-50 border-slate-200 text-slate-700'
)}
>
<div className="text-xs font-medium mb-0.5">{vital.code_display}</div>
@@ -208,10 +208,10 @@ function ConditionItem({ condition }: { condition: Condition }) {
const formattedDate = date ? format(date, 'd MMM yyyy', { locale: nl }) : null;
return (
<div className="flex items-center gap-3 p-2 rounded-lg bg-slate-800/50 border border-slate-700">
<div className="w-1.5 h-1.5 rounded-full bg-blue-400" />
<div className="flex items-center gap-3 p-2 rounded-lg bg-slate-50 border border-slate-200">
<div className="w-1.5 h-1.5 rounded-full bg-blue-500" />
<div className="flex-1 min-w-0">
<p className="text-sm text-slate-300">{condition.code_display}</p>
<p className="text-sm text-slate-700">{condition.code_display}</p>
{formattedDate && (
<p className="text-xs text-slate-500">Sinds {formattedDate}</p>
)}
@@ -230,11 +230,11 @@ function RiskBadge({ risk }: { risk: RiskAssessment }) {
weglopen: 'Weglopen',
};
const RISK_LEVEL_STYLES: Record<string, { bg: string; text: string; dot: string }> = {
zeer_hoog: { bg: 'bg-red-900/30', text: 'text-red-300', dot: 'bg-red-500' },
hoog: { bg: 'bg-red-900/20', text: 'text-red-400', dot: 'bg-red-500' },
gemiddeld: { bg: 'bg-amber-900/20', text: 'text-amber-300', dot: 'bg-amber-500' },
laag: { bg: 'bg-green-900/20', text: 'text-green-300', dot: 'bg-green-500' },
const RISK_LEVEL_STYLES: Record<string, { bg: string; text: string; dot: string; border: string }> = {
zeer_hoog: { bg: 'bg-red-50', text: 'text-red-700', dot: 'bg-red-500', border: 'border-red-200' },
hoog: { bg: 'bg-red-50', text: 'text-red-600', dot: 'bg-red-500', border: 'border-red-200' },
gemiddeld: { bg: 'bg-amber-50', text: 'text-amber-700', dot: 'bg-amber-500', border: 'border-amber-200' },
laag: { bg: 'bg-green-50', text: 'text-green-700', dot: 'bg-green-500', border: 'border-green-200' },
};
const styles = RISK_LEVEL_STYLES[risk.risk_level] || RISK_LEVEL_STYLES.laag;
@@ -250,7 +250,7 @@ function RiskBadge({ risk }: { risk: RiskAssessment }) {
'inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium border',
styles.bg,
styles.text,
styles.dot === 'bg-red-500' ? 'border-red-700' : styles.dot === 'bg-amber-500' ? 'border-amber-700' : 'border-green-700'
styles.border
)}
>
<span className={cn('w-1.5 h-1.5 rounded-full', styles.dot)} />

View File

@@ -252,7 +252,7 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
{searchQuery.length >= 2 && (
<div className="space-y-2">
{isSearching ? (
<div className="flex items-center justify-center py-8 text-slate-400">
<div className="flex items-center justify-center py-8 text-slate-500">
<Loader2 className="h-5 w-5 animate-spin mr-2" />
<span className="text-sm">Zoeken...</span>
</div>
@@ -269,8 +269,8 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
className={cn(
'w-full px-3 py-2.5 rounded-lg border text-left transition-colors',
isSelected
? 'bg-slate-700 border-slate-600 cursor-wait'
: 'bg-slate-800/50 border-slate-700 hover:bg-slate-700 hover:border-slate-600 cursor-pointer'
? 'bg-slate-100 border-slate-300 cursor-wait'
: 'bg-slate-50 border-slate-200 hover:bg-slate-100 hover:border-slate-300 cursor-pointer'
)}
>
<div className="flex items-center justify-between">
@@ -284,17 +284,17 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
.toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-white truncate">
<div className="text-sm font-medium text-slate-900 truncate">
{formatPatientDisplay(patient)}
</div>
<div className="flex items-center gap-3 mt-0.5">
{patient.identifier_bsn && (
<span className="text-xs text-slate-400">
<span className="text-xs text-slate-500">
BSN: {patient.identifier_bsn}
</span>
)}
{patient.identifier_client_number && (
<span className="text-xs text-slate-400">
<span className="text-xs text-slate-500">
Client: {patient.identifier_client_number}
</span>
)}
@@ -302,9 +302,9 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
</div>
</div>
{isSelected ? (
<Loader2 className="h-4 w-4 animate-spin text-blue-400 shrink-0" />
<Loader2 className="h-4 w-4 animate-spin text-blue-600 shrink-0" />
) : (
<Check className="h-4 w-4 text-slate-500 shrink-0" />
<Check className="h-4 w-4 text-slate-400 shrink-0" />
)}
</div>
</button>
@@ -312,7 +312,7 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
})}
</div>
) : (
<div className="flex flex-col items-center justify-center py-8 text-slate-500">
<div className="flex flex-col items-center justify-center py-8 text-slate-400">
<User className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">Geen patiënten gevonden</p>
<p className="text-xs mt-1">Probeer een andere zoekterm</p>
@@ -323,7 +323,7 @@ export function ZoekenBlock({ prefill }: ZoekenBlockProps) {
{/* Empty State */}
{searchQuery.length < 2 && (
<div className="flex flex-col items-center justify-center py-8 text-slate-500">
<div className="flex flex-col items-center justify-center py-8 text-slate-400">
<Search className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">Typ minimaal 2 karakters om te zoeken</p>
</div>

View File

@@ -33,22 +33,50 @@ export function CanvasArea() {
}
}
// Block animations volgens UX specificatie (sectie 11.1):
// Openen: Slide up + fade in (200ms), Scale: 0.95 → 1.0
// Sluiten: Slide down + fade out (200ms), Scale: 1.0 → 0.95
const blockAnimations = {
initial: { opacity: 0, y: 20, scale: 0.95 },
animate: { opacity: 1, y: 0, scale: 1 },
exit: { opacity: 0, y: -20, scale: 0.95 },
transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] as [number, number, number, number] },
animate: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.2,
ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
},
},
exit: {
opacity: 0,
y: 20, // Slide down (niet omhoog)
scale: 0.95,
transition: {
duration: 0.2,
ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
},
},
};
return (
<main className="flex-1 flex items-center justify-center p-4 overflow-auto">
<AnimatePresence mode="wait">
<AnimatePresence mode="wait" initial={false}>
{activeBlock ? (
<motion.div key={activeBlock} {...blockAnimations}>
<motion.div
key={activeBlock}
initial={blockAnimations.initial}
animate={blockAnimations.animate}
exit={blockAnimations.exit}
>
{renderBlock(activeBlock, prefillData)}
</motion.div>
) : activePatient ? (
<motion.div key="patient-context" {...blockAnimations}>
<motion.div
key="patient-context"
initial={blockAnimations.initial}
animate={blockAnimations.animate}
exit={blockAnimations.exit}
>
<PatientContextCard />
</motion.div>
) : (
@@ -57,6 +85,7 @@ export function CanvasArea() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<EmptyState />
</motion.div>
@@ -70,7 +99,7 @@ function EmptyState() {
return (
<div className="text-center max-w-md">
<div className="mb-6">
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-slate-800 flex items-center justify-center">
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-white border border-slate-200 shadow-sm flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
@@ -81,14 +110,14 @@ function EmptyState() {
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="text-slate-500"
className="text-slate-400"
>
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
<line x1="12" x2="12" y1="19" y2="22" />
</svg>
</div>
<h2 className="text-xl font-medium text-slate-300 mb-2">Wat wil je doen?</h2>
<h2 className="text-xl font-medium text-slate-700 mb-2">Wat wil je doen?</h2>
<p className="text-sm text-slate-500">
Typ of spreek je intentie
</p>
@@ -100,8 +129,8 @@ function EmptyState() {
<ExampleCommand icon="📋" text="overdracht" />
</div>
<p className="mt-6 text-xs text-slate-600">
<kbd className="px-1.5 py-0.5 rounded bg-slate-800 text-slate-400">K</kbd> om te focussen
<p className="mt-6 text-xs text-slate-500">
<kbd className="px-1.5 py-0.5 rounded bg-white border border-slate-200 text-slate-600 shadow-sm">K</kbd> om te focussen
</p>
</div>
);
@@ -109,9 +138,9 @@ function EmptyState() {
function ExampleCommand({ icon, text }: { icon: string; text: string }) {
return (
<div className="flex items-center gap-3 px-3 py-2 rounded-lg bg-slate-800/50 text-sm">
<div className="flex items-center gap-3 px-3 py-2 rounded-lg bg-white border border-slate-200 shadow-sm text-sm">
<span>{icon}</span>
<span className="text-slate-400">&quot;{text}&quot;</span>
<span className="text-slate-600">&quot;{text}&quot;</span>
</div>
);
}

View File

@@ -72,7 +72,7 @@ export const CommandInput = forwardRef<HTMLInputElement>(function CommandInput(_
animationRef.current = requestAnimationFrame(draw);
analyserNode.getByteFrequencyData(dataArray);
ctx.fillStyle = 'rgb(30, 41, 59)'; // slate-800
ctx.fillStyle = 'rgb(248, 250, 252)'; // slate-50
ctx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = (canvas.width / bufferLength) * 2.5;
@@ -179,7 +179,7 @@ export const CommandInput = forwardRef<HTMLInputElement>(function CommandInput(_
const isDisabled = isProcessing || activeBlock !== null;
return (
<footer className="h-16 border-t border-slate-700 flex items-center px-4 shrink-0 bg-slate-900">
<footer className="h-16 border-t border-slate-200 flex items-center px-4 shrink-0 bg-white">
<form onSubmit={handleSubmit} className="flex-1 flex items-center gap-2">
{/* Input wrapper with optional waveform */}
<div className="relative flex-1">
@@ -200,11 +200,11 @@ export const CommandInput = forwardRef<HTMLInputElement>(function CommandInput(_
onChange={(e) => setInputValue(e.target.value)}
placeholder={getPlaceholder()}
disabled={isDisabled}
className={`w-full bg-slate-800 border rounded-xl py-3 text-white placeholder:text-slate-500
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 focus:bg-slate-800/80
className={`w-full bg-white border rounded-xl py-3 text-slate-900 placeholder:text-slate-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
disabled:opacity-50 disabled:cursor-not-allowed
transition-all duration-200
${isRecording ? 'pl-[220px] pr-12 border-red-500/50' : 'pl-4 pr-12 border-slate-600'}`}
${isRecording ? 'pl-[220px] pr-12 border-red-500/50' : 'pl-4 pr-12 border-slate-300'}`}
autoFocus
/>
@@ -252,7 +252,7 @@ export const CommandInput = forwardRef<HTMLInputElement>(function CommandInput(_
className={`p-3 rounded-xl transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed ${
isRecording
? 'bg-red-600 hover:bg-red-500 text-white ring-4 ring-red-600/30'
: 'bg-slate-700 hover:bg-slate-600 text-slate-300'
: 'bg-slate-100 hover:bg-slate-200 text-slate-600'
}`}
title={isRecording ? 'Stop opname' : 'Start voice input'}
>
@@ -268,7 +268,7 @@ export const CommandInput = forwardRef<HTMLInputElement>(function CommandInput(_
<button
type="button"
disabled
className="p-3 rounded-xl bg-slate-800 text-slate-600 cursor-not-allowed"
className="p-3 rounded-xl bg-slate-100 text-slate-400 cursor-not-allowed"
title="Spraakherkenning niet ondersteund in deze browser"
>
<MicOff size={20} />

View File

@@ -12,10 +12,10 @@ import { Sun, Moon, Sunrise, Sunset, X, User, ArrowLeft } from 'lucide-react';
import Link from 'next/link';
const SHIFT_CONFIG: Record<ShiftType, { icon: typeof Sun; label: string; color: string }> = {
nacht: { icon: Moon, label: 'Nachtdienst', color: 'text-indigo-400' },
ochtend: { icon: Sunrise, label: 'Ochtenddienst', color: 'text-amber-400' },
middag: { icon: Sun, label: 'Middagdienst', color: 'text-yellow-400' },
avond: { icon: Sunset, label: 'Avonddienst', color: 'text-orange-400' },
nacht: { icon: Moon, label: 'Nachtdienst', color: 'text-indigo-600' },
ochtend: { icon: Sunrise, label: 'Ochtenddienst', color: 'text-amber-600' },
middag: { icon: Sun, label: 'Middagdienst', color: 'text-yellow-600' },
avond: { icon: Sunset, label: 'Avonddienst', color: 'text-orange-600' },
};
export function ContextBar() {
@@ -24,19 +24,19 @@ export function ContextBar() {
const ShiftIcon = shiftConfig.icon;
return (
<header className="h-12 border-b border-slate-700 flex items-center px-4 justify-between shrink-0 bg-slate-900">
<header className="h-12 border-b border-slate-200 flex items-center px-4 justify-between shrink-0 bg-white">
{/* Left: Logo + Shift */}
<div className="flex items-center gap-4">
<Link
href="/epd"
className="flex items-center gap-2 text-slate-400 hover:text-white transition-colors"
className="flex items-center gap-2 text-slate-600 hover:text-slate-900 transition-colors"
title="Terug naar EPD"
>
<ArrowLeft size={16} />
<span className="text-sm font-semibold tracking-tight">Swift</span>
</Link>
<div className="h-4 w-px bg-slate-700" />
<div className="h-4 w-px bg-slate-200" />
<div className={`flex items-center gap-1.5 ${shiftConfig.color}`}>
<ShiftIcon size={14} />
@@ -47,17 +47,17 @@ export function ContextBar() {
{/* Center: Active Patient */}
<div className="flex items-center">
{activePatient ? (
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-slate-800 border border-slate-600">
<div className="w-5 h-5 rounded-full bg-blue-600 flex items-center justify-center text-[10px] font-medium">
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-slate-100 border border-slate-300">
<div className="w-5 h-5 rounded-full bg-blue-600 flex items-center justify-center text-[10px] font-medium text-white">
{activePatient.name_given[0]?.[0]}
{activePatient.name_family[0]}
</div>
<span className="text-sm text-white">
<span className="text-sm text-slate-900">
{activePatient.name_given.join(' ')} {activePatient.name_family}
</span>
<button
onClick={() => setActivePatient(null)}
className="p-0.5 rounded hover:bg-slate-700 text-slate-400 hover:text-white transition-colors"
className="p-0.5 rounded hover:bg-slate-200 text-slate-400 hover:text-slate-700 transition-colors"
title="Patiënt deselecteren"
>
<X size={14} />
@@ -70,7 +70,7 @@ export function ContextBar() {
{/* Right: User */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-slate-400">
<div className="flex items-center gap-2 text-slate-600">
<User size={16} />
<span className="text-xs hidden sm:block">Verpleegkundige</span>
</div>

View File

@@ -11,10 +11,10 @@ import { useSwiftStore, type SwiftIntent } from '@/stores/swift-store';
import { FileText, Search, ArrowRightLeft, HelpCircle, Clock } from 'lucide-react';
const INTENT_CONFIG: Record<SwiftIntent, { icon: typeof FileText; color: string; label: string }> = {
dagnotitie: { icon: FileText, color: 'text-blue-400 bg-blue-400/10', label: 'Notitie' },
zoeken: { icon: Search, color: 'text-emerald-400 bg-emerald-400/10', label: 'Zoeken' },
overdracht: { icon: ArrowRightLeft, color: 'text-purple-400 bg-purple-400/10', label: 'Overdracht' },
unknown: { icon: HelpCircle, color: 'text-slate-400 bg-slate-400/10', label: 'Actie' },
dagnotitie: { icon: FileText, color: 'text-blue-600 bg-blue-50 border border-blue-200', label: 'Notitie' },
zoeken: { icon: Search, color: 'text-emerald-600 bg-emerald-50 border border-emerald-200', label: 'Zoeken' },
overdracht: { icon: ArrowRightLeft, color: 'text-purple-600 bg-purple-50 border border-purple-200', label: 'Overdracht' },
unknown: { icon: HelpCircle, color: 'text-slate-600 bg-slate-50 border border-slate-200', label: 'Actie' },
};
function formatRelativeTime(date: Date): string {
@@ -45,7 +45,7 @@ export function RecentStrip() {
};
return (
<div className="h-12 border-t border-slate-700 flex items-center px-4 gap-3 shrink-0 bg-slate-900/50">
<div className="h-12 border-t border-slate-200 flex items-center px-4 gap-3 shrink-0 bg-white">
{/* Label */}
<div className="flex items-center gap-1.5 text-slate-500 shrink-0">
<Clock size={12} />
@@ -53,11 +53,11 @@ export function RecentStrip() {
</div>
{/* Divider */}
<div className="h-4 w-px bg-slate-700" />
<div className="h-4 w-px bg-slate-200" />
{/* Actions */}
{recentActions.length === 0 ? (
<span className="text-xs text-slate-600 italic">Nog geen acties</span>
<span className="text-xs text-slate-400 italic">Nog geen acties</span>
) : (
<div className="flex gap-2 overflow-x-auto scrollbar-hide">
{recentActions.map((action) => {
@@ -90,17 +90,17 @@ export function RecentStrip() {
{/* Quick actions hint (when empty) */}
{recentActions.length === 0 && (
<div className="ml-auto flex items-center gap-2 text-xs text-slate-600">
<div className="ml-auto flex items-center gap-2 text-xs text-slate-500">
<span>Probeer:</span>
<button
onClick={() => setInputValue('notitie ')}
className="px-2 py-0.5 rounded bg-slate-800 hover:bg-slate-700 text-slate-400 transition-colors"
className="px-2 py-0.5 rounded bg-slate-100 hover:bg-slate-200 text-slate-600 transition-colors"
>
notitie
</button>
<button
onClick={() => setInputValue('zoek ')}
className="px-2 py-0.5 rounded bg-slate-800 hover:bg-slate-700 text-slate-400 transition-colors"
className="px-2 py-0.5 rounded bg-slate-100 hover:bg-slate-200 text-slate-600 transition-colors"
>
zoek
</button>