/
opt
/
mawid
/
apps
/
dashboard
/
app
/
(app)
/
patients
/
[id]
/
/opt/mawid/apps/dashboard/app/(app)/patients/[id]
mkdir
upload
Name
Size
Mode
Actions
page.tsx
4090
0644
edit
dl
rm
Edit:
/opt/mawid/apps/dashboard/app/(app)/patients/[id]/page.tsx
(4090B)
'use client'; import { useCallback, useEffect, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { api, serviceName, formatInTz } from '@/lib/api'; import { useLang } from '@/lib/i18n'; import { Badge, Button, Card, Field, Input, Select, Spinner, Textarea } from '@/components/ui'; interface PatientDetail { id: string; name: string | null; waPhone: string; language: string; notes: string | null; tags: string[]; appointments: Array<{ id: string; startsAt: string; status: string; service: { name: unknown }; staff: { name: string }; }>; } const STATUS_COLORS: Record<string, 'amber' | 'emerald' | 'slate' | 'blue' | 'red'> = { pending: 'amber', confirmed: 'emerald', cancelled: 'slate', completed: 'blue', no_show: 'red', }; export default function PatientDetailPage() { const { t, lang } = useLang(); const params = useParams<{ id: string }>(); const router = useRouter(); const [patient, setPatient] = useState<PatientDetail | null>(null); const [name, setName] = useState(''); const [language, setLanguage] = useState('tr'); const [notes, setNotes] = useState(''); const [tags, setTags] = useState(''); const [savedFlash, setSavedFlash] = useState(false); const load = useCallback(async () => { const data = await api<PatientDetail>(`/patients/${params.id}`); setPatient(data); setName(data.name ?? ''); setLanguage(data.language); setNotes(data.notes ?? ''); setTags(data.tags.join(', ')); }, [params.id]); useEffect(() => { load().catch(() => router.push('/patients')); }, [load, router]); async function save() { await api(`/patients/${params.id}`, { method: 'PATCH', body: JSON.stringify({ name: name || undefined, language, notes: notes || null, tags: tags .split(',') .map((s) => s.trim()) .filter(Boolean), }), }); setSavedFlash(true); setTimeout(() => setSavedFlash(false), 1500); load(); } if (!patient) return <Spinner />; return ( <div className="space-y-4"> <button onClick={() => router.push('/patients')} className="text-sm text-slate-500 hover:text-slate-700"> ← {t('back')} </button> <Card className="space-y-3"> <div className="text-sm text-slate-500" dir="ltr"> {patient.waPhone} </div> <Field label={t('name')}> <Input value={name} onChange={(e) => setName(e.target.value)} /> </Field> <Field label={t('language')}> <Select value={language} onChange={(e) => setLanguage(e.target.value)}> <option value="tr">Türkçe</option> <option value="ar">العربية</option> <option value="en">English</option> </Select> </Field> <Field label={t('notes')}> <Textarea rows={3} value={notes} onChange={(e) => setNotes(e.target.value)} /> </Field> <Field label={t('tags')}> <Input value={tags} onChange={(e) => setTags(e.target.value)} /> </Field> <Button onClick={save}>{savedFlash ? t('saved') : t('save')}</Button> </Card> <Card> <h2 className="mb-3 text-sm font-semibold text-slate-500">{t('history')}</h2> <div className="space-y-2"> {patient.appointments.map((appt) => ( <div key={appt.id} className="flex items-center justify-between gap-2 text-sm"> <span>{formatInTz(appt.startsAt, 'Europe/Istanbul', lang)}</span> <span className="flex-1 truncate text-slate-500"> {serviceName(appt.service.name, lang)} · {appt.staff.name} </span> <Badge color={STATUS_COLORS[appt.status] ?? 'slate'}> {t(`st_${appt.status}` as never)} </Badge> </div> ))} {patient.appointments.length === 0 && ( <p className="text-sm text-slate-400">{t('noResults')}</p> )} </div> </Card> </div> ); }
Save
cmd:
run