feature: localization's first implmentation done. all frontend hardcoded text is now localized.

This commit is contained in:
2026-06-20 14:51:43 +03:30
parent 284fbd08aa
commit b2f4dfa4ca
52 changed files with 3306 additions and 1041 deletions

View File

@@ -1,11 +1,13 @@
'use client';
import { useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/shared/Button';
import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton';
import { Dropdown } from '@/components/ui/shared/Dropdown';
import type { AppointmentPurpose, AppointmentRecord } from '@/types/appointment';
import { APPOINTMENT_PURPOSE_LABEL } from '@/components/ui/appointments/appointmentPurposeStyles';
import { APPOINTMENT_PURPOSES } from '@/types/appointment';
import { getPurposeLabel } from '@/components/ui/appointments/appointmentPurposeStyles';
import type { Patient } from '@/types/patient';
import {
combineLocalDateAndTime,
@@ -36,6 +38,14 @@ interface AppointmentBookingModalProps {
deleting?: boolean;
}
const PURPOSE_OPTION_COLORS: Record<AppointmentPurpose, string> = {
consultation: '#ddd6fe',
filling: '#fed7aa',
endo: '#fecaca',
visit: '#bae6fd',
hygiene: '#d9f99d',
};
export function AppointmentBookingModal({
open,
scheduleDate,
@@ -51,20 +61,15 @@ export function AppointmentBookingModal({
onDelete,
deleting = false,
}: AppointmentBookingModalProps) {
const t = useTranslations('appointments');
const tCommon = useTranslations('common');
const tPatients = useTranslations('patients');
const [startTime, setStartTime] = useState('09:00');
const [endTime, setEndTime] = useState('10:00');
const [purpose, setPurpose] = useState<AppointmentPurpose>('consultation');
const [error, setError] = useState('');
const purposeTextColor =
purpose === 'consultation'
? '#ddd6fe'
: purpose === 'filling'
? '#fed7aa'
: purpose === 'endo'
? '#fecaca'
: purpose === 'visit'
? '#bae6fd'
: '#d9f99d';
const purposeTextColor = PURPOSE_OPTION_COLORS[purpose];
useEffect(() => {
if (!open) {
@@ -116,7 +121,7 @@ export function AppointmentBookingModal({
return;
}
if (!editingAppointment && !patient) {
setError('Select a patient first.');
setError(t('errorSelectPatient'));
return;
}
@@ -124,26 +129,26 @@ export function AppointmentBookingModal({
const endAt = combineLocalDateAndTime(scheduleDate, endTime);
if (endAt <= startAt) {
setError('End time must be after start time.');
setError(t('errorEndAfterStart'));
return;
}
const now = new Date();
if (isSameLocalCalendarDay(scheduleDate, now) && startAt.getTime() < now.getTime()) {
setError('Cannot schedule in the past.');
setError(t('errorPastSchedule'));
return;
}
const today = new Date();
if (compareLocalDayStart(scheduleDate, today) < 0) {
setError('Past appointments are view-only.');
setError(t('errorPastViewOnly'));
return;
}
const effectivePatientId = editingAppointment?.patientId ?? patient?.id;
const effectiveProviderId = editingAppointment?.providerUserId ?? providerUserId;
if (!effectivePatientId || !effectiveProviderId) {
setError('Missing appointment details.');
setError(t('errorMissingDetails'));
return;
}
@@ -166,29 +171,34 @@ export function AppointmentBookingModal({
>
<div className="flex items-start justify-between gap-2">
<h2 id="appointment-modal-title" className="text-lg font-semibold text-text-primary pr-2">
{editingAppointment ? 'Edit appointment' : 'New appointment'}
{editingAppointment ? t('editTitle') : t('newTitle')}
</h2>
<DialogCloseButton onClick={onClose} />
</div>
<p className="text-sm text-text-secondary">
Provider: <span className="text-text-primary font-medium">{providerName}</span>
{t('providerLabel')}{' '}
<span className="text-text-primary font-medium">{providerName}</span>
</p>
<div>
<label className="block text-sm font-medium text-text-secondary mb-1">Patient</label>
<label className="block text-sm font-medium text-text-secondary mb-1">
{t('patientLabel')}
</label>
<p className="text-sm text-text-primary rounded-[var(--radius-md)] border border-border bg-background-secondary/60 px-3 py-2">
{editingAppointment
? `${editingAppointment.patient.firstName} ${editingAppointment.patient.lastName}`
: patient
? `${patient.firstName} ${patient.lastName}`
: '—'}
: tPatients('emptyValue')}
</p>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-text-secondary mb-1">Start</label>
<label className="block text-sm font-medium text-text-secondary mb-1">
{t('startLabel')}
</label>
<input
type="time"
step={60}
@@ -198,7 +208,9 @@ export function AppointmentBookingModal({
/>
</div>
<div>
<label className="block text-sm font-medium text-text-secondary mb-1">End</label>
<label className="block text-sm font-medium text-text-secondary mb-1">
{t('endLabel')}
</label>
<input
type="time"
step={60}
@@ -210,26 +222,20 @@ export function AppointmentBookingModal({
</div>
<Dropdown
label="Purpose"
label={t('purposeLabel')}
value={purpose}
onChange={(e) => setPurpose(e.target.value as AppointmentPurpose)}
style={{ color: purposeTextColor }}
>
<option value="consultation" style={{ color: '#ddd6fe', backgroundColor: '#14253d' }}>
{APPOINTMENT_PURPOSE_LABEL.consultation}
</option>
<option value="filling" style={{ color: '#fed7aa', backgroundColor: '#14253d' }}>
{APPOINTMENT_PURPOSE_LABEL.filling}
</option>
<option value="endo" style={{ color: '#fecaca', backgroundColor: '#14253d' }}>
{APPOINTMENT_PURPOSE_LABEL.endo}
</option>
<option value="visit" style={{ color: '#bae6fd', backgroundColor: '#14253d' }}>
{APPOINTMENT_PURPOSE_LABEL.visit}
</option>
<option value="hygiene" style={{ color: '#d9f99d', backgroundColor: '#14253d' }}>
{APPOINTMENT_PURPOSE_LABEL.hygiene}
</option>
{APPOINTMENT_PURPOSES.map((purposeOption) => (
<option
key={purposeOption}
value={purposeOption}
style={{ color: PURPOSE_OPTION_COLORS[purposeOption], backgroundColor: '#14253d' }}
>
{getPurposeLabel(purposeOption, t)}
</option>
))}
</Dropdown>
{error && <p className="text-sm text-red-400">{error}</p>}
@@ -243,14 +249,14 @@ export function AppointmentBookingModal({
disabled={loading || deleting}
isLoading={deleting}
>
Delete
{tCommon('delete')}
</Button>
) : (
<span />
)}
<div className="flex gap-2 ml-auto">
<Button type="button" variant="ghost" onClick={onClose} disabled={loading || deleting}>
Cancel
{tCommon('cancel')}
</Button>
<Button
type="button"
@@ -259,7 +265,7 @@ export function AppointmentBookingModal({
isLoading={loading}
disabled={deleting}
>
Save
{tCommon('save')}
</Button>
</div>
</div>