bugfix: appointment hours now use the client timezone on UTC servers.

Logical API errors throw stable codes so users see translated messages instead of a generic bad request.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-19 01:43:50 +03:30
parent d6958b2e48
commit 80167c622c
38 changed files with 833 additions and 392 deletions

View File

@@ -20,6 +20,7 @@ import { prosthesisTypeColorFromCatalog } from '@/components/treatment/prosthesi
import type { ProsthesisCatalogEntry, TreatmentCatalogEntry } from '@/types/treatment-catalog';
import type { LabCaseDraft, LinkedOrganizationOption, TreatmentDetailDraft } from '@/types/treatment';
import type { PatientLabCaseSummary } from '@/types/lab-case-activity';
import { getUserFacingError } from '@/components/shared/formatApiError';
import { groupsFromFlatTeeth } from '@/components/treatment/toothSelectionGroups';
interface LabCasesDispatchPanelProps {
@@ -124,6 +125,7 @@ export function LabCasesDispatchPanel({
onCommentError,
}: LabCasesDispatchPanelProps) {
const t = useTranslations('treatment');
const tErrors = useTranslations('errors');
const [prosthesisOptions, setProsthesisOptions] = useState<ProsthesisCatalogEntry[]>([]);
const [applyAllProsthesis, setApplyAllProsthesis] = useState('');
const [pendingComment, setPendingComment] = useState('');
@@ -273,7 +275,7 @@ export function LabCasesDispatchPanel({
taskProgress: response.data.taskProgress ?? activeLabCase.taskProgress,
});
} catch (error) {
onCommentError?.(error instanceof Error ? error.message : t('dueDateUpdateError'));
onCommentError?.(getUserFacingError(error, tErrors, t('dueDateUpdateError')));
}
}

View File

@@ -1,4 +1,5 @@
import { apiClient } from './client';
import { getClientTimeZone } from '@/lib/i18n/clientTimeZone';
export type WorkingHoursPayload = {
autoRepeatWeekly: boolean;
@@ -42,7 +43,10 @@ export const accountApi = {
upsertMyWorkingHours: async (
payload: WorkingHoursPayload,
): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.put('/auth/profile/working-hours', payload);
const response = await apiClient.put('/auth/profile/working-hours', {
...payload,
timeZone: getClientTimeZone(),
});
return response.data;
},
};

View File

@@ -1,5 +1,6 @@
import { apiClient } from './client';
import type { AppointmentColumnProvider, AppointmentRecord } from '@/types/appointment';
import { getClientTimeZone } from '@/lib/i18n/clientTimeZone';
import { toDateInputValue } from '@/components/appointments/appointmentTime';
export interface CreateAppointmentBody {
@@ -27,7 +28,10 @@ export const appointmentsApi = {
},
create: async (body: CreateAppointmentBody): Promise<{ success: boolean; data: AppointmentRecord }> => {
const response = await apiClient.post('/appointments', body);
const response = await apiClient.post('/appointments', {
...body,
timeZone: getClientTimeZone(),
});
return response.data;
},
@@ -35,7 +39,10 @@ export const appointmentsApi = {
id: string,
body: UpdateAppointmentBody,
): Promise<{ success: boolean; data: AppointmentRecord }> => {
const response = await apiClient.patch(`/appointments/${id}`, body);
const response = await apiClient.patch(`/appointments/${id}`, {
...body,
timeZone: getClientTimeZone(),
});
return response.data;
},

View File

@@ -1,4 +1,5 @@
import { apiClient } from './client';
import { getClientTimeZone } from '@/lib/i18n/clientTimeZone';
export interface StaffMemberDto {
id: string;
@@ -142,7 +143,10 @@ export const staffApi = {
blocks: { dayOfWeek: number; startMinute: number; endMinute: number; sortOrder?: number }[];
},
): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.put(`/staff/members/${membershipId}/working-hours`, body);
const response = await apiClient.put(`/staff/members/${membershipId}/working-hours`, {
...body,
timeZone: getClientTimeZone(),
});
return response.data;
},
};

View File

@@ -0,0 +1,8 @@
/** Browser IANA zone (e.g. Asia/Tehran). Used so the UTC server can interpret wall-clock hours. */
export function getClientTimeZone(): string {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
} catch {
return 'UTC';
}
}