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:
@@ -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')));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
8
frontend/src/lib/i18n/clientTimeZone.ts
Normal file
8
frontend/src/lib/i18n/clientTimeZone.ts
Normal 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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user