refactor(swift): update references from Medical Scribe to Swift Assistent
- Updated comments and documentation to reflect the new branding of the chat API and related components. - Renamed functions and variables to align with the Swift Assistent terminology. - Enhanced UI components with animations using Framer Motion for a smoother user experience. - Removed outdated architecture documentation related to the Medical Scribe system. This change is part of the transition to the Swift Assistent branding, ensuring consistency across the application.
This commit is contained in:
@@ -5,6 +5,7 @@ import { AgendaListView } from './agenda-list-view';
|
||||
import { AgendaCreateForm } from './agenda-create-form';
|
||||
import { AgendaCancelView } from './agenda-cancel-view';
|
||||
import { AgendaRescheduleForm } from './agenda-reschedule-form';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
export interface AgendaBlockProps {
|
||||
mode: 'list' | 'create' | 'cancel' | 'reschedule';
|
||||
@@ -36,6 +37,7 @@ export function AgendaBlock({
|
||||
case 'list':
|
||||
return (
|
||||
<AgendaListView
|
||||
key="list"
|
||||
appointments={appointments}
|
||||
dateRange={dateRange}
|
||||
onClose={onClose}
|
||||
@@ -44,25 +46,43 @@ export function AgendaBlock({
|
||||
/>
|
||||
);
|
||||
case 'create':
|
||||
return <AgendaCreateForm prefillData={prefillData} onClose={onClose} />;
|
||||
return <AgendaCreateForm key="create" prefillData={prefillData} onClose={onClose} />;
|
||||
case 'cancel':
|
||||
return (
|
||||
<AgendaCancelView
|
||||
key="cancel"
|
||||
disambiguationOptions={disambiguationOptions}
|
||||
prefillData={prefillData}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
case 'reschedule':
|
||||
return <AgendaRescheduleForm prefillData={prefillData} onClose={onClose} />;
|
||||
return <AgendaRescheduleForm key="reschedule" prefillData={prefillData} onClose={onClose} />;
|
||||
default:
|
||||
return <div className="p-4 text-red-500">Unknown mode: {mode}</div>;
|
||||
return <div key="error" className="p-4 text-red-500">Unknown mode: {mode}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[600px] max-h-[80vh] overflow-y-auto bg-white border rounded shadow-sm">
|
||||
{renderContent()}
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20, scale: 0.98 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.98 }}
|
||||
transition={{ type: "spring", stiffness: 350, damping: 25 }}
|
||||
className="w-full max-w-[600px] max-h-[80vh] h-[600px] overflow-hidden bg-white/95 backdrop-blur-xl border border-black/5 rounded-2xl shadow-2xl flex flex-col"
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={mode}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex flex-col h-full overflow-hidden"
|
||||
>
|
||||
{renderContent()}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { cancelEncounter } from '@/app/epd/agenda/actions';
|
||||
import { CalendarEvent, APPOINTMENT_TYPES, AppointmentTypeCode } from '@/app/epd/agenda/types';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface AgendaCancelViewProps {
|
||||
disambiguationOptions?: CalendarEvent[];
|
||||
@@ -74,7 +75,7 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Afspraak geannuleerd</h3>
|
||||
<p className="text-sm text-gray-500 mt-1 mb-6">De afspraak is succesvol verwijderd uit de agenda.</p>
|
||||
<Button onClick={onClose} variant="outline">Sluiten</Button>
|
||||
<Button onClick={onClose} variant="outline" className="border-black/5 bg-white/50 hover:bg-white">Sluiten</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -82,16 +83,21 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
// Disambiguation Mode
|
||||
if (!effectiveEncounter && disambiguationOptions && disambiguationOptions.length > 1) {
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-red-700">Afspraak annuleren</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4 overflow-y-auto">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
<div className="flex-1 p-5 overflow-y-auto">
|
||||
<p className="text-sm text-gray-600 mb-4 font-medium">
|
||||
Er zijn meerdere afspraken gevonden. Welke wil je annuleren?
|
||||
</p>
|
||||
|
||||
@@ -103,11 +109,11 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
const timeStr = format(new Date(evt.start), 'HH:mm');
|
||||
|
||||
return (
|
||||
<div key={evt.id} className="flex items-center space-x-2 border rounded-lg p-3 hover:bg-gray-50 cursor-pointer">
|
||||
<div key={evt.id} className="flex items-center space-x-3 border border-black/5 rounded-xl p-4 hover:bg-white/50 cursor-pointer bg-white/40 transition-colors">
|
||||
<RadioGroupItem value={evt.id} id={evt.id} />
|
||||
<Label htmlFor={evt.id} className="flex-1 cursor-pointer">
|
||||
<div className="font-medium text-gray-900">{evt.title}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<div className="font-semibold text-gray-900">{evt.title}</div>
|
||||
<div className="text-sm text-gray-500 mt-0.5">
|
||||
{dateStr} om {timeStr} • {encounter.type_display || APPOINTMENT_TYPES[typeCode]}
|
||||
</div>
|
||||
</Label>
|
||||
@@ -117,17 +123,17 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} className="flex-1">Annuleren</Button>
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">Annuleren</Button>
|
||||
<Button
|
||||
onClick={() => { /* State updates automatically via RadioGroup */ }}
|
||||
disabled={!selectedEncounterId}
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white"
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
Volgende
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,63 +146,70 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
const endTimeStr = effectiveEncounter.end ? format(new Date(effectiveEncounter.end), 'HH:mm') : '';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-red-700">Weet je het zeker?</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
<div className="bg-red-50 border border-red-100 rounded-lg p-4 mb-6">
|
||||
<div className="flex-1 p-5">
|
||||
<div className="bg-red-50/70 border border-red-100/50 rounded-xl p-4 mb-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-red-600 mt-0.5" />
|
||||
<div className="text-sm text-red-800">
|
||||
<p className="font-medium">Deze actie kan niet ongedaan worden gemaakt.</p>
|
||||
<p className="mt-1 opacity-90">De afspraak wordt permanent uit de agenda verwijderd.</p>
|
||||
<div className="p-2 bg-red-100 rounded-full mt-0.5">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
</div>
|
||||
<div className="text-sm text-red-900">
|
||||
<p className="font-semibold text-base">Deze actie kan niet ongedaan worden gemaakt.</p>
|
||||
<p className="mt-1 opacity-90 leading-relaxed">De afspraak met <span className="font-semibold">{effectiveEncounter.title}</span> wordt permanent uit de agenda verwijderd.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<h4 className="font-medium text-gray-900 mb-2">{effectiveEncounter.title}</h4>
|
||||
<div className="space-y-2 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="border border-black/5 rounded-xl p-5 bg-white/60 shadow-sm backdrop-blur-sm">
|
||||
<h4 className="font-semibold text-gray-900 mb-3 text-lg">{effectiveEncounter.title}</h4>
|
||||
<div className="space-y-3 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<Calendar className="h-4 w-4 text-gray-400" />
|
||||
<span className="capitalize">{dateStr}</span>
|
||||
<span className="capitalize font-medium">{dateStr}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Clock className="h-4 w-4 text-gray-400" />
|
||||
<span>{timeStr} {endTimeStr && `- ${endTimeStr}`}</span>
|
||||
<span className="font-medium">{timeStr} {endTimeStr && `- ${endTimeStr}`}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-block w-20 text-gray-400">Type:</span>
|
||||
<span className="font-medium">{encounter.type_display || APPOINTMENT_TYPES[typeCode]}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-block w-5 text-center text-gray-400">•</span>
|
||||
<span className="font-medium bg-gray-100 px-2 py-0.5 rounded text-gray-700">{encounter.type_display || APPOINTMENT_TYPES[typeCode]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mt-4 p-3 bg-red-100 text-red-700 text-sm rounded-md">
|
||||
<div className="mt-4 p-4 bg-red-50 text-red-700 text-sm rounded-xl border border-red-100">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Terug
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white"
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? 'Annuleren...' : 'Ja, annuleer afspraak'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -165,17 +166,22 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h3 className="font-semibold text-lg text-teal-700">Nieuwe afspraak inplannen</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-gray-900">Nieuwe afspraak inplannen</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 text-gray-500 hover:text-gray-900 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form Body */}
|
||||
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-4 space-y-5">
|
||||
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-5 space-y-5">
|
||||
|
||||
{error && (
|
||||
<AgendaErrorAlert
|
||||
@@ -186,7 +192,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
|
||||
{/* Patient Selection */}
|
||||
<div className="space-y-1.5" ref={searchRef}>
|
||||
<div className="space-y-2" ref={searchRef}>
|
||||
<Label htmlFor="patient" className="text-sm font-medium text-gray-700">Patiënt <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -197,7 +203,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Zoek op naam..."
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{isSearching && (
|
||||
@@ -207,16 +213,16 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
|
||||
{showResults && searchResults.length > 0 && (
|
||||
<div className="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg max-h-48 overflow-y-auto">
|
||||
<div className="absolute z-10 w-full mt-1 bg-white/95 backdrop-blur-xl border border-black/5 rounded-xl shadow-xl max-h-48 overflow-y-auto">
|
||||
{searchResults.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => handlePatientSelect(p)}
|
||||
className="w-full text-left px-3 py-2 hover:bg-teal-50 text-sm flex flex-col border-b last:border-0"
|
||||
className="w-full text-left px-4 py-3 hover:bg-teal-50/50 text-sm flex flex-col border-b last:border-0 border-black/5 transition-colors"
|
||||
>
|
||||
<span className="font-medium text-gray-900">{p.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-gray-500 mt-0.5">
|
||||
{p.birthDate && format(new Date(p.birthDate), 'dd-MM-yyyy')}
|
||||
{p.bsn && ` • BSN: ${p.bsn}`}
|
||||
</span>
|
||||
@@ -228,8 +234,8 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Date & Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date" className="text-sm font-medium text-gray-700">Datum <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -240,13 +246,13 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="time" className="text-sm font-medium text-gray-700">Tijd <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -257,7 +263,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={(e) => setTime(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -265,9 +271,9 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Type Selection */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">Type afspraak <span className="text-red-500">*</span></Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="grid grid-cols-2 gap-2.5">
|
||||
{(Object.keys(APPOINTMENT_TYPES) as AppointmentTypeCode[]).map((t) => {
|
||||
const bg = APPOINTMENT_TYPE_COLORS[t].bg;
|
||||
const text = APPOINTMENT_TYPE_COLORS[t].text;
|
||||
@@ -280,18 +286,18 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="button"
|
||||
onClick={() => setType(t)}
|
||||
className={`
|
||||
px-3 py-2 text-xs font-medium rounded-md border text-left transition-all
|
||||
${isActive ? 'ring-2 ring-offset-1 ring-teal-500' : 'hover:bg-gray-50'}
|
||||
px-3 py-2.5 text-xs font-medium rounded-lg border text-left transition-all
|
||||
${isActive ? 'ring-2 ring-offset-1 ring-teal-500 shadow-sm' : 'hover:bg-black/5 bg-white/40'}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: isActive ? bg : 'white',
|
||||
backgroundColor: isActive ? bg : undefined,
|
||||
color: isActive ? text : '#374151',
|
||||
borderColor: isActive ? border : '#e5e7eb'
|
||||
borderColor: isActive ? border : 'rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{APPOINTMENT_TYPES[t]}</span>
|
||||
{isActive && <Check className="h-3 w-3" />}
|
||||
{isActive && <Check className="h-3.5 w-3.5" />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -300,24 +306,24 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Location Selection */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">Locatie <span className="text-red-500">*</span></Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2.5">
|
||||
{(Object.keys(LOCATION_CLASSES) as LocationClassCode[]).map((l) => (
|
||||
<button
|
||||
key={l}
|
||||
type="button"
|
||||
onClick={() => setLocation(l)}
|
||||
className={`
|
||||
flex-1 py-2 px-3 text-xs font-medium rounded-md border flex items-center justify-center gap-1.5 transition-all
|
||||
flex-1 py-2.5 px-3 text-xs font-medium rounded-lg border flex items-center justify-center gap-2 transition-all
|
||||
${location === l
|
||||
? 'bg-teal-50 border-teal-200 text-teal-800 ring-2 ring-teal-500 ring-opacity-20'
|
||||
: 'bg-white border-gray-200 text-gray-600 hover:bg-gray-50'}
|
||||
? 'bg-teal-50 border-teal-200 text-teal-800 ring-2 ring-teal-500 ring-opacity-20 shadow-sm'
|
||||
: 'bg-white/40 border-black/10 text-gray-600 hover:bg-black/5'}
|
||||
`}
|
||||
>
|
||||
{l === 'AMB' && <MapPin className="h-3 w-3" />}
|
||||
{l === 'VR' && <div className="h-3 w-3 border rounded-full" />}
|
||||
{l === 'HH' && <div className="h-3 w-3 bg-current rounded-sm" />}
|
||||
{l === 'AMB' && <MapPin className="h-3.5 w-3.5" />}
|
||||
{l === 'VR' && <div className="h-3.5 w-3.5 border rounded-full" />}
|
||||
{l === 'HH' && <div className="h-3.5 w-3.5 bg-current rounded-sm" />}
|
||||
{LOCATION_CLASSES[l]}
|
||||
</button>
|
||||
))}
|
||||
@@ -325,14 +331,14 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="notes" className="text-sm font-medium text-gray-700">Notities (optioneel)</Label>
|
||||
<Textarea
|
||||
id="notes"
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
placeholder="Bijv. bijzonderheden, reden van komst..."
|
||||
className="h-20 text-sm resize-none"
|
||||
className="h-24 text-sm resize-none bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -340,15 +346,15 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</form>
|
||||
|
||||
{/* Footer / Actions */}
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Annuleren
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white"
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white shadow-md hover:shadow-lg transition-all h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -360,6 +366,6 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nl } from 'date-fns/locale';
|
||||
import { Calendar, MapPin, Globe, Home, Clock, X, Info, ChevronRight, Ban } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
CalendarEvent,
|
||||
APPOINTMENT_TYPE_COLORS,
|
||||
@@ -22,6 +23,21 @@ interface AgendaListViewProps {
|
||||
onViewDetails?: (encounter: CalendarEvent) => void;
|
||||
}
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
show: { opacity: 1, y: 0 }
|
||||
};
|
||||
|
||||
export function AgendaListView({
|
||||
appointments = [],
|
||||
dateRange,
|
||||
@@ -55,33 +71,43 @@ export function AgendaListView({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex flex-col h-full bg-transparent">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center gap-2 text-teal-700">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<h3 className="font-semibold text-lg capitalize">{formatDateLabel()}</h3>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<div className="flex items-center gap-3 text-teal-700">
|
||||
<div className="p-2 bg-teal-50 rounded-lg">
|
||||
<Calendar className="h-5 w-5" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg text-gray-900 capitalize leading-tight">{formatDateLabel()}</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 text-gray-500 hover:text-gray-900 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<motion.div
|
||||
className="flex-1 overflow-y-auto p-5 space-y-3"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
>
|
||||
{appointments.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-10 text-center text-gray-500">
|
||||
<div className="bg-gray-50 p-4 rounded-full mb-3">
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="flex flex-col items-center justify-center h-full text-center text-gray-500"
|
||||
>
|
||||
<div className="bg-gray-50/50 p-4 rounded-full mb-4">
|
||||
<Calendar className="h-8 w-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="font-medium">Geen afspraken gevonden</p>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
<p className="font-medium text-gray-900">Geen afspraken gevonden</p>
|
||||
<p className="text-sm text-gray-500 mt-1 max-w-[200px]">
|
||||
Er staan geen afspraken gepland voor deze periode.
|
||||
</p>
|
||||
<Button variant="outline" className="mt-4 gap-2 text-teal-600 border-teal-200 hover:bg-teal-50">
|
||||
<Button variant="outline" className="mt-6 gap-2 text-teal-700 border-teal-200/50 hover:bg-teal-50/50 bg-white/50">
|
||||
<span className="text-lg leading-none">+</span> Maak nieuwe afspraak
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
appointments.map((evt) => {
|
||||
const encounter = evt.extendedProps.encounter;
|
||||
@@ -94,18 +120,19 @@ export function AgendaListView({
|
||||
const endTime = evt.end ? format(new Date(evt.end), 'HH:mm') : '';
|
||||
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
key={evt.id}
|
||||
className="group border rounded-lg p-3 hover:border-teal-200 hover:shadow-sm transition-all bg-white"
|
||||
variants={itemVariants}
|
||||
className="group border border-black/5 rounded-2xl p-4 hover:border-teal-200/50 hover:shadow-md transition-all bg-white/60 hover:bg-white/90 backdrop-blur-sm"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
||||
<Clock className="h-3.5 w-3.5 text-gray-400" />
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-500 mb-0.5">
|
||||
<Clock className="h-3.5 w-3.5" />
|
||||
<span>{startTime} {endTime && `- ${endTime}`}</span>
|
||||
</div>
|
||||
<button
|
||||
className="text-left font-semibold text-teal-700 hover:underline mt-0.5"
|
||||
className="text-left text-base font-semibold text-gray-900 hover:text-teal-700 hover:underline transition-colors"
|
||||
onClick={() => console.log('Open patient context', patient?.id)}
|
||||
>
|
||||
{evt.title}
|
||||
@@ -118,7 +145,7 @@ export function AgendaListView({
|
||||
color: typeColor.text,
|
||||
borderColor: typeColor.border
|
||||
}}
|
||||
className="whitespace-nowrap shadow-none"
|
||||
className="whitespace-nowrap shadow-none px-2.5 py-0.5 text-xs font-medium rounded-md"
|
||||
>
|
||||
{encounter.type_display || APPOINTMENT_TYPES[typeCode] || typeCode}
|
||||
</Badge>
|
||||
@@ -130,15 +157,15 @@ export function AgendaListView({
|
||||
<span>{encounter.class_display || LOCATION_CLASSES[classCode] || classCode}</span>
|
||||
</div>
|
||||
{encounter.status === 'cancelled' && (
|
||||
<Badge variant="destructive" className="h-5 px-1.5">Geannuleerd</Badge>
|
||||
<Badge variant="destructive" className="h-5 px-1.5 font-normal rounded-sm">Geannuleerd</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 mt-3 pt-2 border-t border-gray-50 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div className="flex items-center justify-end gap-2 mt-4 pt-3 border-t border-black/5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs text-gray-500 hover:text-red-600 hover:bg-red-50"
|
||||
className="h-8 text-xs font-medium text-gray-500 hover:text-red-600 hover:bg-red-50/50 rounded-lg px-3"
|
||||
onClick={() => onCancelAppointment?.(evt)}
|
||||
>
|
||||
Annuleren
|
||||
@@ -146,25 +173,25 @@ export function AgendaListView({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs text-teal-600 hover:bg-teal-50"
|
||||
className="h-8 text-xs font-medium text-teal-700 hover:bg-teal-50/50 rounded-lg px-3"
|
||||
onClick={() => onViewDetails?.(evt)}
|
||||
>
|
||||
Details <ChevronRight className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-3 bg-gray-50 border-t text-center">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 text-center">
|
||||
<a
|
||||
href="/epd/agenda"
|
||||
className="text-xs font-medium text-teal-600 hover:text-teal-700 hover:underline inline-flex items-center gap-1"
|
||||
className="text-sm font-medium text-teal-700 hover:text-teal-800 hover:underline inline-flex items-center gap-1 transition-colors"
|
||||
>
|
||||
Open volledige agenda <ChevronRight className="h-3 w-3" />
|
||||
Open volledige agenda <ChevronRight className="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { rescheduleEncounter } from '@/app/epd/agenda/actions';
|
||||
import { CalendarEvent } from '@/app/epd/agenda/types';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface AgendaRescheduleFormProps {
|
||||
prefillData?: {
|
||||
@@ -98,7 +99,7 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
<p className="text-sm text-gray-500 mt-1 mb-6">
|
||||
De afspraak is verplaatst naar {format(new Date(`${date}T${time}`), 'd MMMM HH:mm', { locale: nl })}.
|
||||
</p>
|
||||
<Button onClick={onClose} variant="outline">Sluiten</Button>
|
||||
<Button onClick={onClose} variant="outline" className="border-black/5 bg-white/50 hover:bg-white">Sluiten</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,30 +118,39 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
const currentStr = currentStart ? format(currentStart, 'd MMMM yyyy HH:mm', { locale: nl }) : 'Onbekend';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-teal-700">Afspraak verzetten</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="flex-1 p-4 overflow-y-auto space-y-6">
|
||||
<form onSubmit={handleSubmit} className="flex-1 p-5 overflow-y-auto space-y-6">
|
||||
|
||||
{/* Info Card */}
|
||||
<div className="bg-blue-50 border border-blue-100 rounded-lg p-4">
|
||||
{encounter && <h4 className="font-medium text-blue-900 mb-2">{encounter.title}</h4>}
|
||||
<div className="flex items-center gap-2 text-sm text-blue-700 opacity-80 decoration-slate-400">
|
||||
<div className="bg-blue-50/70 border border-blue-100/50 rounded-xl p-5 shadow-sm">
|
||||
{encounter && <h4 className="font-semibold text-blue-900 mb-3 text-lg">{encounter.title}</h4>}
|
||||
<div className="flex items-center gap-3 text-sm text-blue-800/60 decoration-slate-400 mb-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span className="line-through decoration-blue-900/40">{currentStr}</span>
|
||||
<span className="line-through decoration-blue-900/30">{currentStr}</span>
|
||||
</div>
|
||||
<div className="flex justify-center my-1">
|
||||
<ArrowRight className="h-4 w-4 text-blue-400 rotate-90 sm:rotate-0" />
|
||||
|
||||
<div className="pl-0.5 my-2">
|
||||
<div className="h-6 border-l-2 border-blue-200 ml-2 border-dashed"></div>
|
||||
<ArrowRight className="h-5 w-5 text-blue-500 my-1" />
|
||||
<div className="h-2 border-l-2 border-blue-200 ml-2 border-dashed"></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-blue-800">
|
||||
<Calendar className="h-4 w-4" />
|
||||
|
||||
<div className="flex items-center gap-3 text-base font-bold text-blue-800 bg-blue-100/50 p-3 rounded-lg border border-blue-200/50">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<span>
|
||||
{date && time ? format(new Date(`${date}T${time}`), 'd MMMM yyyy HH:mm', { locale: nl }) : '...'}
|
||||
</span>
|
||||
@@ -148,14 +158,14 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-md flex items-center gap-2 text-sm text-red-700">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-xl flex items-center gap-3 text-sm text-red-700">
|
||||
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date" className="text-sm font-medium text-gray-700">Nieuwe datum</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -166,13 +176,13 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="time" className="text-sm font-medium text-gray-700">Nieuwe tijd</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -183,7 +193,7 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={(e) => setTime(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -192,19 +202,19 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
</form>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Annuleren
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white"
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? 'Verplaatsen...' : 'Bevestig wijziging'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export function ChatPanel() {
|
||||
<div className="max-w-md text-center text-slate-500">
|
||||
<div className="text-4xl mb-4">💬</div>
|
||||
<h3 className="text-lg font-medium text-slate-700 mb-2">
|
||||
Welkom bij Swift Medical Scribe
|
||||
Welkom bij Swift Assistent
|
||||
</h3>
|
||||
<p className="text-sm mb-4">
|
||||
Typ of spreek wat je wilt doen...
|
||||
|
||||
Reference in New Issue
Block a user