feature: phase0 - Global patients + mobile normalization
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
} from '@/components/appointments/appointmentOverlapLayout';
|
||||
import { purposeStyle } from '@/components/ui/appointments/appointmentPurposeStyles';
|
||||
import { AppointmentOverlapPopover } from '@/components/ui/appointments/AppointmentOverlapPopover';
|
||||
import { formatMobileForDisplay } from '@/lib/phone';
|
||||
import { startOfLocalDay } from '@/components/appointments/appointmentTime';
|
||||
|
||||
const HOUR_PX = 80;
|
||||
@@ -300,7 +301,9 @@ export function AppointmentScheduleGrid({
|
||||
clusterSize > 1
|
||||
? t('overlappingChoose', { count: clusterSize })
|
||||
: null,
|
||||
!isUnderOneHour && apt.patient.phone ? apt.patient.phone : null,
|
||||
!isUnderOneHour && apt.patient.mobile
|
||||
? formatMobileForDisplay(apt.patient.mobile)
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ');
|
||||
@@ -338,10 +341,10 @@ export function AppointmentScheduleGrid({
|
||||
{patientName}
|
||||
</span>
|
||||
{!isUnderOneHour &&
|
||||
apt.patient.phone &&
|
||||
apt.patient.mobile &&
|
||||
lane.laneCount === 1 && (
|
||||
<span className="block w-full truncate pointer-events-none text-[10px] leading-tight opacity-90">
|
||||
{apt.patient.phone}
|
||||
{formatMobileForDisplay(apt.patient.mobile)}
|
||||
</span>
|
||||
)}
|
||||
{!isUnderOneHour && clusterSize > 1 && (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Search } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { formatMobileForDisplay } from '@/lib/phone';
|
||||
import type { Patient } from '@/types/patient';
|
||||
|
||||
interface AppointmentsPatientSearchProps {
|
||||
@@ -82,7 +83,7 @@ export function AppointmentsPatientSearch({
|
||||
{patient.firstName} {patient.lastName}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
{patient.phone || patient.email || tPatients('noContact')}
|
||||
{formatMobileForDisplay(patient.mobile) || patient.email || tPatients('noContact')}
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/shared/Button';
|
||||
import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { CreatePatientInput } from '@/types/patient';
|
||||
import { isValidMobile, normalizeMobile } from '@/lib/phone';
|
||||
|
||||
interface CreatePatientModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -49,9 +50,10 @@ function CreatePatientFormFields({
|
||||
onChange={(e) => onChange({ lastName: e.target.value })}
|
||||
/>
|
||||
<Input
|
||||
label={t('phone')}
|
||||
value={formData.phone || ''}
|
||||
onChange={(e) => onChange({ phone: e.target.value })}
|
||||
label={t('mobile')}
|
||||
value={formData.mobile || ''}
|
||||
onChange={(e) => onChange({ mobile: e.target.value })}
|
||||
placeholder={t('mobilePlaceholder')}
|
||||
/>
|
||||
<Input
|
||||
label={tCommon('email')}
|
||||
@@ -66,7 +68,7 @@ function CreatePatientFormFields({
|
||||
variant="primary"
|
||||
onClick={onSubmit}
|
||||
isLoading={loading}
|
||||
disabled={!formData.firstName || !formData.lastName}
|
||||
disabled={!formData.firstName || !formData.lastName || !isValidMobile(normalizeMobile(formData.mobile || '') ?? '')}
|
||||
>
|
||||
{t('savePatient')}
|
||||
</Button>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { formatMobileForDisplay } from '@/lib/phone';
|
||||
import { Patient } from '@/types/patient';
|
||||
|
||||
interface PatientSearchSelectProps {
|
||||
@@ -56,7 +57,9 @@ export function PatientSearchSelect({
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
{patient.firstName} {patient.lastName}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">{patient.phone || patient.email || t('noContact')}</p>
|
||||
<p className="text-xs text-text-muted">
|
||||
{formatMobileForDisplay(patient.mobile) || patient.email || t('noContact')}
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { formatMobileForDisplay } from '@/lib/phone';
|
||||
import { Patient } from '@/types/patient';
|
||||
|
||||
interface PatientSummaryCardProps {
|
||||
@@ -24,7 +25,7 @@ export function PatientSummaryCard({ patient }: PatientSummaryCardProps) {
|
||||
{patient.firstName} {patient.lastName}
|
||||
</h2>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{t('phoneLabel')} {patient.phone || t('emptyValue')}
|
||||
{t('mobileLabel')} {formatMobileForDisplay(patient.mobile)}
|
||||
</p>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{t('emailLabel')} {patient.email || t('emptyValue')}
|
||||
|
||||
Reference in New Issue
Block a user