2026-04-29 13:32:22 +03:30
|
|
|
'use client';
|
|
|
|
|
|
2026-06-20 14:51:43 +03:30
|
|
|
import { useTranslations } from 'next-intl';
|
2026-05-18 12:31:21 +03:30
|
|
|
import { Button } from '@/components/ui/shared/Button';
|
2026-05-18 13:09:46 +03:30
|
|
|
import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton';
|
2026-05-18 12:31:21 +03:30
|
|
|
import { Input } from '@/components/ui/shared/Input';
|
2026-04-29 13:32:22 +03:30
|
|
|
import { CreatePatientInput } from '@/types/patient';
|
|
|
|
|
|
|
|
|
|
interface CreatePatientModalProps {
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
formData: CreatePatientInput;
|
|
|
|
|
onChange: (patch: Partial<CreatePatientInput>) => void;
|
|
|
|
|
onSubmit: () => void;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
loading?: boolean;
|
2026-05-18 13:09:46 +03:30
|
|
|
/** Inline panel on Patients page; centered dialog on Appointments. */
|
|
|
|
|
variant?: 'inline' | 'dialog';
|
2026-04-29 13:32:22 +03:30
|
|
|
}
|
|
|
|
|
|
2026-05-18 13:09:46 +03:30
|
|
|
function CreatePatientFormFields({
|
2026-04-29 13:32:22 +03:30
|
|
|
formData,
|
|
|
|
|
onChange,
|
|
|
|
|
onSubmit,
|
|
|
|
|
onClose,
|
2026-05-18 13:09:46 +03:30
|
|
|
loading,
|
|
|
|
|
showCancel,
|
|
|
|
|
}: {
|
|
|
|
|
formData: CreatePatientInput;
|
|
|
|
|
onChange: (patch: Partial<CreatePatientInput>) => void;
|
|
|
|
|
onSubmit: () => void;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
loading: boolean;
|
|
|
|
|
showCancel: boolean;
|
|
|
|
|
}) {
|
2026-06-20 14:51:43 +03:30
|
|
|
const t = useTranslations('patients');
|
|
|
|
|
const tCommon = useTranslations('common');
|
|
|
|
|
|
2026-04-29 13:32:22 +03:30
|
|
|
return (
|
2026-05-18 13:09:46 +03:30
|
|
|
<>
|
2026-04-29 13:32:22 +03:30
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
|
|
|
<Input
|
2026-06-20 14:51:43 +03:30
|
|
|
label={t('firstName')}
|
2026-04-29 13:32:22 +03:30
|
|
|
value={formData.firstName || ''}
|
|
|
|
|
onChange={(e) => onChange({ firstName: e.target.value })}
|
|
|
|
|
/>
|
|
|
|
|
<Input
|
2026-06-20 14:51:43 +03:30
|
|
|
label={t('lastName')}
|
2026-04-29 13:32:22 +03:30
|
|
|
value={formData.lastName || ''}
|
|
|
|
|
onChange={(e) => onChange({ lastName: e.target.value })}
|
|
|
|
|
/>
|
|
|
|
|
<Input
|
2026-06-20 14:51:43 +03:30
|
|
|
label={t('phone')}
|
2026-04-29 13:32:22 +03:30
|
|
|
value={formData.phone || ''}
|
|
|
|
|
onChange={(e) => onChange({ phone: e.target.value })}
|
|
|
|
|
/>
|
|
|
|
|
<Input
|
2026-06-20 14:51:43 +03:30
|
|
|
label={tCommon('email')}
|
2026-04-29 13:32:22 +03:30
|
|
|
type="email"
|
|
|
|
|
value={formData.email || ''}
|
|
|
|
|
onChange={(e) => onChange({ email: e.target.value })}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
onClick={onSubmit}
|
|
|
|
|
isLoading={loading}
|
|
|
|
|
disabled={!formData.firstName || !formData.lastName}
|
|
|
|
|
>
|
2026-06-20 14:51:43 +03:30
|
|
|
{t('savePatient')}
|
2026-04-29 13:32:22 +03:30
|
|
|
</Button>
|
2026-05-18 13:09:46 +03:30
|
|
|
{showCancel && (
|
|
|
|
|
<Button variant="ghost" onClick={onClose}>
|
2026-06-20 14:51:43 +03:30
|
|
|
{tCommon('cancel')}
|
2026-05-18 13:09:46 +03:30
|
|
|
</Button>
|
|
|
|
|
)}
|
2026-04-29 13:32:22 +03:30
|
|
|
</div>
|
2026-05-18 13:09:46 +03:30
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function CreatePatientModal({
|
|
|
|
|
isOpen,
|
|
|
|
|
formData,
|
|
|
|
|
onChange,
|
|
|
|
|
onSubmit,
|
|
|
|
|
onClose,
|
|
|
|
|
loading = false,
|
|
|
|
|
variant = 'inline',
|
|
|
|
|
}: CreatePatientModalProps) {
|
2026-06-20 14:51:43 +03:30
|
|
|
const t = useTranslations('patients');
|
|
|
|
|
|
2026-05-18 13:09:46 +03:30
|
|
|
if (!isOpen) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (variant === 'dialog') {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className="fixed inset-0 z-[60] flex items-center justify-center p-4 bg-black/55"
|
|
|
|
|
role="presentation"
|
|
|
|
|
onMouseDown={(e) => {
|
|
|
|
|
if (e.target === e.currentTarget) {
|
|
|
|
|
onClose();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
2026-06-24 17:09:06 +03:30
|
|
|
className="surface-card w-full max-w-[min(56rem,calc(100vw-15rem))] p-5 space-y-4 shadow-xl"
|
2026-05-18 13:09:46 +03:30
|
|
|
role="dialog"
|
|
|
|
|
aria-modal="true"
|
|
|
|
|
aria-labelledby="create-patient-dialog-title"
|
|
|
|
|
onMouseDown={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-start justify-between gap-2">
|
|
|
|
|
<h2
|
|
|
|
|
id="create-patient-dialog-title"
|
|
|
|
|
className="text-lg font-semibold text-text-primary pr-2"
|
|
|
|
|
>
|
2026-06-20 14:51:43 +03:30
|
|
|
{t('dialogTitle')}
|
2026-05-18 13:09:46 +03:30
|
|
|
</h2>
|
|
|
|
|
<DialogCloseButton onClick={onClose} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<CreatePatientFormFields
|
|
|
|
|
formData={formData}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
onSubmit={onSubmit}
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
loading={loading}
|
|
|
|
|
showCancel={false}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="surface-card p-4 space-y-3">
|
|
|
|
|
<CreatePatientFormFields
|
|
|
|
|
formData={formData}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
onSubmit={onSubmit}
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
loading={loading}
|
|
|
|
|
showCancel
|
|
|
|
|
/>
|
2026-04-29 13:32:22 +03:30
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|