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

@@ -1080,6 +1080,101 @@
"VALIDATION_FIELD_REQUIRED": "Please fill in all required fields.",
"VALIDATION_LANGUAGE_INVALID": "Please select a supported language.",
"VALIDATION_INVALID_REQUEST": "The request contains invalid data.",
"VALIDATION_TIMEZONE_INVALID": "Your timezone could not be determined. Refresh the page and try again.",
"APPOINTMENT_INVALID_TIME": "The appointment start or end time is invalid.",
"APPOINTMENT_END_BEFORE_START": "End time must be after start time.",
"APPOINTMENT_TOO_LONG": "An appointment cannot last more than 24 hours.",
"APPOINTMENT_IN_PAST": "You cannot book an appointment in the past.",
"APPOINTMENT_INVALID_RANGE": "The date range is invalid.",
"APPOINTMENT_INVALID_RANGE_ORDER": "The end of the range must be after the start.",
"APPOINTMENT_INVALID_DATE": "The selected date is invalid.",
"APPOINTMENT_PROVIDER_NOT_MEMBER": "This provider is not a member of the organization.",
"APPOINTMENT_PROVIDER_INACTIVE": "This provider is not an active staff member.",
"APPOINTMENT_PROVIDER_NO_TREATMENT_EDIT": "This provider cannot take treatment appointments.",
"APPOINTMENT_PROVIDER_NO_WORKING_HOURS": "This provider has no working hours set.",
"APPOINTMENT_PROVIDER_NOT_WORKING_DAY": "This provider does not work on the selected day.",
"APPOINTMENT_OUTSIDE_WORKING_HOURS": "This time is outside the providers working hours.",
"APPOINTMENT_NOT_FOUND": "Appointment not found.",
"APPOINTMENT_NOT_PROVIDER": "You are not the provider for this appointment.",
"PATIENT_NOT_FOUND": "Patient not found.",
"WORKING_HOURS_INVALID": "Working hours are invalid. Check that shifts do not overlap.",
"WORKING_HOURS_OWNER_NOT_ALLOWED": "Set owner working hours from account settings.",
"WORKING_HOURS_CONFLICTS_WITH_APPOINTMENTS": "These hours conflict with upcoming appointments. Reschedule or remove those appointments first.",
"STAFF_UNKNOWN_PERMISSIONS": "One or more selected permissions are not valid.",
"STAFF_NO_SUBSCRIPTION": "Choose a plan before adding staff.",
"STAFF_SEAT_LIMIT": "Your plan has no free seats. Disable a member or upgrade to add more.",
"STAFF_INVITE_OWNER_EMAIL": "The organization owner is already a member.",
"STAFF_ALREADY_MEMBER": "This person is already a member.",
"STAFF_OWNER_NO_INVITE_LINK": "The owner does not use an invitation link.",
"STAFF_ALREADY_ACCEPTED": "This member has already accepted their invitation.",
"STAFF_INVITE_MISSING": "No invitation was found for this member.",
"STAFF_INVITE_ALREADY_ACCEPTED": "This invitation has already been accepted.",
"STAFF_INVITE_INVALID": "This invitation is no longer valid.",
"STAFF_INVITE_REVOKED": "This invitation has been revoked.",
"STAFF_INVITE_EXPIRED": "This invitation has expired.",
"STAFF_ALREADY_ACTIVE": "This member is already active.",
"STAFF_ALREADY_DISABLED": "This member is already disabled or pending.",
"STAFF_CANNOT_DISABLE_SELF": "You cannot disable your own access.",
"STAFF_MEMBER_NOT_FOUND": "Staff member not found.",
"STAFF_CANNOT_EDIT_OWNER": "The owners membership cannot be edited here.",
"STAFF_CANNOT_ENABLE_OWNER": "The organization owner cannot be enabled this way.",
"STAFF_CANNOT_DISABLE_OWNER": "The organization owner cannot be disabled.",
"STAFF_CANNOT_REMOVE_OWNER": "The organization owner cannot be removed.",
"ORG_CANNOT_LINK_SELF": "You cannot link an organization to itself.",
"ORG_LINK_WRONG_TYPE": "You can only link to the matching organization type (clinic or lab).",
"ORG_TARGET_NO_SUBSCRIPTION": "The other organization does not have an active subscription.",
"ORG_LINK_EXISTS": "These organizations are already linked.",
"ORG_REQUEST_NOT_PENDING": "Only pending connection requests can be answered.",
"ORG_CANNOT_RESPOND_OWN": "You cannot respond to your own connection request.",
"ORG_INVITE_ALREADY_ACCEPTED": "This invitation has already been accepted.",
"ORG_INVITE_INVALID": "This invitation is no longer valid.",
"ORG_INVITE_REVOKED": "This invitation has been revoked.",
"ORG_INVITE_EXPIRED": "This invitation has expired.",
"ORG_INVITE_TYPE_MISMATCH": "Organization type does not match this invitation.",
"ORG_INVITE_OWNER_HAS_ORG": "This owner already has an organization. Find it in search instead of sending an invitation.",
"ORG_UNKNOWN_TYPE": "Unknown organization type.",
"ORG_COUNTERPART_NOT_LAB": "The connected organization is not a lab.",
"ORG_COUNTERPART_NOT_CLINIC": "The connected organization is not a clinic.",
"ORG_CONNECTION_NOT_FOUND": "Connected organization not found.",
"ORG_INVITE_NOT_FOUND": "Invitation not found.",
"ORG_ONLY_CLINIC_COMMENT": "Only the clinic can comment on this case.",
"CATALOG_UNKNOWN_TREATMENT_TYPE": "Unknown treatment type.",
"CATALOG_UNKNOWN_PROSTHESIS_TYPE": "Unknown prosthesis type.",
"CATALOG_TREATMENT_NOT_LAB_DEPENDENT": "This treatment type is completed in the clinic and cannot be sent to a lab.",
"TREATMENT_SAVE_DETAILS_BEFORE_LAB": "Save treatment details before creating lab cases.",
"TREATMENT_DETAIL_ONE_CASE": "Each treatment detail can belong to only one lab case.",
"TREATMENT_DETAILS_NOT_FOUND": "One or more treatment details were not found.",
"TREATMENT_DEST_NOT_LINKED": "That organization is not an active linked lab.",
"TREATMENT_TEETH_REQUIRED_TO_SHIP": "Select teeth before sending this work to a lab.",
"TREATMENT_TOOTH_UNKNOWN_DETAIL": "A tooth is linked to an unknown treatment detail.",
"TREATMENT_TOOTH_NOT_ON_DETAIL": "A selected tooth is not on this treatment detail.",
"TREATMENT_CASE_NO_DEST": "This lab case has no destination organization.",
"TREATMENT_CASE_NEEDS_DETAIL": "A lab case must include a treatment detail.",
"TREATMENT_CASE_ONE_DETAIL": "A lab case can include only one treatment detail.",
"TREATMENT_ONLY_PROVIDER_SEND": "Only the treatment provider can send this lab case.",
"TREATMENT_CASE_ALREADY_SENT": "This case was already sent to that lab.",
"TREATMENT_DUE_DATE_LOCKED": "The due date cannot be changed after all tasks are completed.",
"TREATMENT_INVALID_DUE_DATE": "The due date is invalid.",
"TREATMENT_DETAIL_KEY_REQUIRED": "A treatment detail is required for this upload.",
"TREATMENT_FILE_REQUIRED": "At least one file is required.",
"TREATMENT_ATTACHMENT_NOT_FOUND": "Attachment not found.",
"TREATMENT_FILE_UNAVAILABLE": "This file is no longer available.",
"TREATMENT_CASE_INVALID_ATTACHMENTS": "One or more attachments are not valid for this lab case.",
"TREATMENT_TOOTH_PROSTHESIS_INCOMPLETE": "Each tooth needs a prosthesis type before sending.",
"LAB_CASE_NOT_FOUND": "Lab case not found.",
"TASK_ASSIGNEE_INVALID": "The selected person cannot be assigned this task.",
"TASK_ASSIGNED_TO_OTHER": "This task is assigned to another staff member.",
"TASK_PAGE_DATE_SORT_ONLY": "Jumping to a task in the list is only available when sorted by date.",
"INVALID_SENT_FROM": "The start date of the range is invalid.",
"INVALID_SENT_TO": "The end date of the range is invalid.",
"CASE_NOT_FOUND": "Case not found.",
"CASE_ATTACHMENT_NOT_FOUND": "Attachment not found.",
"CASE_ATTACHMENT_MISSING_FILE": "The attachment file is missing.",
"CASE_TASK_NOT_FOUND": "Task not found.",
"COMMENT_NOT_FOUND": "Comment not found.",
"COMMENT_VISIBILITY_LAB_ONLY": "Only lab comments can change visibility.",
"TODAY_INVALID_RANGE": "The date range is invalid.",
"TODAY_INVALID_RANGE_ORDER": "The end of the range must be after the start.",
"NOT_FOUND": "The requested item was not found.",
"CONFLICT": "This action conflicts with existing data.",
"CONFLICT_FUTURE_APPOINTMENTS": "You cannot stop participating in treatments while you have future appointments. Reassign or cancel them first.",

View File

@@ -1081,6 +1081,101 @@
"VALIDATION_FIELD_REQUIRED": "لطفاً همه فیلدهای الزامی را پر کنید.",
"VALIDATION_LANGUAGE_INVALID": "زبان پشتیبانی‌شده انتخاب کنید.",
"VALIDATION_INVALID_REQUEST": "درخواست حاوی داده نامعتبر است.",
"VALIDATION_TIMEZONE_INVALID": "منطقه زمانی شما مشخص نشد. صفحه را تازه‌سازی کنید و دوباره تلاش کنید.",
"APPOINTMENT_INVALID_TIME": "زمان شروع یا پایان نوبت نامعتبر است.",
"APPOINTMENT_END_BEFORE_START": "زمان پایان باید بعد از زمان شروع باشد.",
"APPOINTMENT_TOO_LONG": "مدت نوبت نمی‌تواند بیش از ۲۴ ساعت باشد.",
"APPOINTMENT_IN_PAST": "نمی‌توانید نوبت در گذشته ثبت کنید.",
"APPOINTMENT_INVALID_RANGE": "بازه تاریخ نامعتبر است.",
"APPOINTMENT_INVALID_RANGE_ORDER": "پایان بازه باید بعد از شروع باشد.",
"APPOINTMENT_INVALID_DATE": "تاریخ انتخاب‌شده نامعتبر است.",
"APPOINTMENT_PROVIDER_NOT_MEMBER": "این ارائه‌دهنده عضو سازمان نیست.",
"APPOINTMENT_PROVIDER_INACTIVE": "این ارائه‌دهنده عضو فعال پرسنل نیست.",
"APPOINTMENT_PROVIDER_NO_TREATMENT_EDIT": "این ارائه‌دهنده نمی‌تواند نوبت درمان بپذیرد.",
"APPOINTMENT_PROVIDER_NO_WORKING_HOURS": "برای این ارائه‌دهنده ساعات کاری تعریف نشده است.",
"APPOINTMENT_PROVIDER_NOT_WORKING_DAY": "این ارائه‌دهنده در روز انتخاب‌شده کار نمی‌کند.",
"APPOINTMENT_OUTSIDE_WORKING_HOURS": "این زمان خارج از ساعات کاری ارائه‌دهنده است.",
"APPOINTMENT_NOT_FOUND": "نوبت یافت نشد.",
"APPOINTMENT_NOT_PROVIDER": "شما ارائه‌دهنده این نوبت نیستید.",
"PATIENT_NOT_FOUND": "بیمار یافت نشد.",
"WORKING_HOURS_INVALID": "ساعات کاری نامعتبر است. هم‌پوشانی شیفت‌ها را بررسی کنید.",
"WORKING_HOURS_OWNER_NOT_ALLOWED": "ساعات کاری مالک را از تنظیمات حساب تنظیم کنید.",
"WORKING_HOURS_CONFLICTS_WITH_APPOINTMENTS": "این ساعات با نوبت‌های آینده تداخل دارد. ابتدا آن نوبت‌ها را تغییر دهید یا حذف کنید.",
"STAFF_UNKNOWN_PERMISSIONS": "یک یا چند دسترسی انتخاب‌شده نامعتبر است.",
"STAFF_NO_SUBSCRIPTION": "قبل از افزودن پرسنل یک طرح انتخاب کنید.",
"STAFF_SEAT_LIMIT": "در طرح شما صندلی خالی نیست. یک عضو را غیرفعال کنید یا طرح را ارتقا دهید.",
"STAFF_INVITE_OWNER_EMAIL": "مالک سازمان از قبل عضو است.",
"STAFF_ALREADY_MEMBER": "این فرد از قبل عضو است.",
"STAFF_OWNER_NO_INVITE_LINK": "مالک از لینک دعوت استفاده نمی‌کند.",
"STAFF_ALREADY_ACCEPTED": "این عضو دعوتنامه را پذیرفته است.",
"STAFF_INVITE_MISSING": "دعوتنامه‌ای برای این عضو یافت نشد.",
"STAFF_INVITE_ALREADY_ACCEPTED": "این دعوتنامه قبلاً پذیرفته شده است.",
"STAFF_INVITE_INVALID": "این دعوتنامه دیگر معتبر نیست.",
"STAFF_INVITE_REVOKED": "این دعوتنامه لغو شده است.",
"STAFF_INVITE_EXPIRED": "این دعوتنامه منقضی شده است.",
"STAFF_ALREADY_ACTIVE": "این عضو از قبل فعال است.",
"STAFF_ALREADY_DISABLED": "این عضو از قبل غیرفعال یا در انتظار است.",
"STAFF_CANNOT_DISABLE_SELF": "نمی‌توانید دسترسی خود را غیرفعال کنید.",
"STAFF_MEMBER_NOT_FOUND": "عضو پرسنل یافت نشد.",
"STAFF_CANNOT_EDIT_OWNER": "عضویت مالک را نمی‌توان از اینجا ویرایش کرد.",
"STAFF_CANNOT_ENABLE_OWNER": "مالک سازمان را نمی‌توان این‌گونه فعال کرد.",
"STAFF_CANNOT_DISABLE_OWNER": "مالک سازمان را نمی‌توان غیرفعال کرد.",
"STAFF_CANNOT_REMOVE_OWNER": "مالک سازمان را نمی‌توان حذف کرد.",
"ORG_CANNOT_LINK_SELF": "نمی‌توانید سازمان را به خودش متصل کنید.",
"ORG_LINK_WRONG_TYPE": "فقط می‌توانید به نوع سازمان متناظر (کلینیک یا لابراتوار) متصل شوید.",
"ORG_TARGET_NO_SUBSCRIPTION": "سازمان مقابل اشتراک فعال ندارد.",
"ORG_LINK_EXISTS": "این سازمان‌ها از قبل متصل هستند.",
"ORG_REQUEST_NOT_PENDING": "فقط درخواست‌های در انتظار را می‌توان پاسخ داد.",
"ORG_CANNOT_RESPOND_OWN": "نمی‌توانید به درخواست اتصال خودتان پاسخ دهید.",
"ORG_INVITE_ALREADY_ACCEPTED": "این دعوتنامه قبلاً پذیرفته شده است.",
"ORG_INVITE_INVALID": "این دعوتنامه دیگر معتبر نیست.",
"ORG_INVITE_REVOKED": "این دعوتنامه لغو شده است.",
"ORG_INVITE_EXPIRED": "این دعوتنامه منقضی شده است.",
"ORG_INVITE_TYPE_MISMATCH": "نوع سازمان با این دعوتنامه مطابقت ندارد.",
"ORG_INVITE_OWNER_HAS_ORG": "این مالک از قبل سازمان دارد. به‌جای ارسال دعوتنامه آن را در جستجو پیدا کنید.",
"ORG_UNKNOWN_TYPE": "نوع سازمان ناشناخته است.",
"ORG_COUNTERPART_NOT_LAB": "سازمان متصل‌شده لابراتوار نیست.",
"ORG_COUNTERPART_NOT_CLINIC": "سازمان متصل‌شده کلینیک نیست.",
"ORG_CONNECTION_NOT_FOUND": "سازمان متصل یافت نشد.",
"ORG_INVITE_NOT_FOUND": "دعوتنامه یافت نشد.",
"ORG_ONLY_CLINIC_COMMENT": "فقط کلینیک می‌تواند روی این کیس نظر بگذارد.",
"CATALOG_UNKNOWN_TREATMENT_TYPE": "نوع درمان ناشناخته است.",
"CATALOG_UNKNOWN_PROSTHESIS_TYPE": "نوع پروتز ناشناخته است.",
"CATALOG_TREATMENT_NOT_LAB_DEPENDENT": "این نوع درمان در کلینیک تکمیل می‌شود و قابل ارسال به لابراتوار نیست.",
"TREATMENT_SAVE_DETAILS_BEFORE_LAB": "قبل از ایجاد کیس لابراتوار، جزئیات درمان را ذخیره کنید.",
"TREATMENT_DETAIL_ONE_CASE": "هر جزء درمان فقط می‌تواند به یک کیس لابراتوار تعلق داشته باشد.",
"TREATMENT_DETAILS_NOT_FOUND": "یک یا چند جزء درمان یافت نشد.",
"TREATMENT_DEST_NOT_LINKED": "آن سازمان لابراتوار متصل فعال نیست.",
"TREATMENT_TEETH_REQUIRED_TO_SHIP": "قبل از ارسال به لابراتوار دندان‌ها را انتخاب کنید.",
"TREATMENT_TOOTH_UNKNOWN_DETAIL": "یک دندان به جزء درمان ناشناخته لینک شده است.",
"TREATMENT_TOOTH_NOT_ON_DETAIL": "دندان انتخاب‌شده روی این جزء درمان نیست.",
"TREATMENT_CASE_NO_DEST": "این کیس لابراتوار مقصد ندارد.",
"TREATMENT_CASE_NEEDS_DETAIL": "کیس لابراتوار باید شامل یک جزء درمان باشد.",
"TREATMENT_CASE_ONE_DETAIL": "کیس لابراتوار فقط می‌تواند یک جزء درمان داشته باشد.",
"TREATMENT_ONLY_PROVIDER_SEND": "فقط ارائه‌دهنده درمان می‌تواند این کیس را ارسال کند.",
"TREATMENT_CASE_ALREADY_SENT": "این کیس قبلاً به آن لابراتوار ارسال شده است.",
"TREATMENT_DUE_DATE_LOCKED": "پس از تکمیل همه کارها نمی‌توان موعد را تغییر داد.",
"TREATMENT_INVALID_DUE_DATE": "موعد نامعتبر است.",
"TREATMENT_DETAIL_KEY_REQUIRED": "برای این بارگذاری یک جزء درمان لازم است.",
"TREATMENT_FILE_REQUIRED": "حداقل یک فایل لازم است.",
"TREATMENT_ATTACHMENT_NOT_FOUND": "پیوست یافت نشد.",
"TREATMENT_FILE_UNAVAILABLE": "این فایل دیگر در دسترس نیست.",
"TREATMENT_CASE_INVALID_ATTACHMENTS": "یک یا چند پیوست برای این کیس معتبر نیست.",
"TREATMENT_TOOTH_PROSTHESIS_INCOMPLETE": "قبل از ارسال، هر دندان باید نوع پروتز داشته باشد.",
"LAB_CASE_NOT_FOUND": "کیس لابراتوار یافت نشد.",
"TASK_ASSIGNEE_INVALID": "فرد انتخاب‌شده را نمی‌توان به این کار تخصیص داد.",
"TASK_ASSIGNED_TO_OTHER": "این کار به عضو دیگری تخصیص داده شده است.",
"TASK_PAGE_DATE_SORT_ONLY": "پرش به کار در فهرست فقط وقتی مرتب‌سازی بر اساس تاریخ است ممکن است.",
"INVALID_SENT_FROM": "تاریخ شروع بازه نامعتبر است.",
"INVALID_SENT_TO": "تاریخ پایان بازه نامعتبر است.",
"CASE_NOT_FOUND": "کیس یافت نشد.",
"CASE_ATTACHMENT_NOT_FOUND": "پیوست یافت نشد.",
"CASE_ATTACHMENT_MISSING_FILE": "فایل پیوست موجود نیست.",
"CASE_TASK_NOT_FOUND": "کار یافت نشد.",
"COMMENT_NOT_FOUND": "نظر یافت نشد.",
"COMMENT_VISIBILITY_LAB_ONLY": "فقط نظرات لابراتوار می‌توانند وضعیت نمایش را تغییر دهند.",
"TODAY_INVALID_RANGE": "بازه تاریخ نامعتبر است.",
"TODAY_INVALID_RANGE_ORDER": "پایان بازه باید بعد از شروع باشد.",
"NOT_FOUND": "مورد درخواستی یافت نشد.",
"CONFLICT": "این عمل با داده‌های موجود در تضاد است.",
"CONFLICT_FUTURE_APPOINTMENTS": "تا وقتی نوبت‌های آینده دارید نمی‌توانید مشارکت در درمان را متوقف کنید. ابتدا آن‌ها را لغو یا واگذار کنید.",

View File

@@ -1080,6 +1080,101 @@
"VALIDATION_FIELD_REQUIRED": "Vul alle verplichte velden in.",
"VALIDATION_LANGUAGE_INVALID": "Selecteer een ondersteunde taal.",
"VALIDATION_INVALID_REQUEST": "Het verzoek bevat ongeldige gegevens.",
"VALIDATION_TIMEZONE_INVALID": "Uw tijdzone kon niet worden bepaald. Vernieuw de pagina en probeer opnieuw.",
"APPOINTMENT_INVALID_TIME": "De start- of eindtijd van de afspraak is ongeldig.",
"APPOINTMENT_END_BEFORE_START": "De eindtijd moet na de starttijd liggen.",
"APPOINTMENT_TOO_LONG": "Een afspraak mag niet langer dan 24 uur duren.",
"APPOINTMENT_IN_PAST": "U kunt geen afspraak in het verleden boeken.",
"APPOINTMENT_INVALID_RANGE": "Het datumbereik is ongeldig.",
"APPOINTMENT_INVALID_RANGE_ORDER": "Het einde van het bereik moet na het begin liggen.",
"APPOINTMENT_INVALID_DATE": "De geselecteerde datum is ongeldig.",
"APPOINTMENT_PROVIDER_NOT_MEMBER": "Deze zorgverlener is geen lid van de organisatie.",
"APPOINTMENT_PROVIDER_INACTIVE": "Deze zorgverlener is geen actief teamlid.",
"APPOINTMENT_PROVIDER_NO_TREATMENT_EDIT": "Deze zorgverlener kan geen behandelafspraken aannemen.",
"APPOINTMENT_PROVIDER_NO_WORKING_HOURS": "Voor deze zorgverlener zijn geen werktijden ingesteld.",
"APPOINTMENT_PROVIDER_NOT_WORKING_DAY": "Deze zorgverlener werkt niet op de geselecteerde dag.",
"APPOINTMENT_OUTSIDE_WORKING_HOURS": "Dit tijdstip valt buiten de werktijden van de zorgverlener.",
"APPOINTMENT_NOT_FOUND": "Afspraak niet gevonden.",
"APPOINTMENT_NOT_PROVIDER": "U bent niet de zorgverlener van deze afspraak.",
"PATIENT_NOT_FOUND": "Patiënt niet gevonden.",
"WORKING_HOURS_INVALID": "De werktijden zijn ongeldig. Controleer of diensten niet overlappen.",
"WORKING_HOURS_OWNER_NOT_ALLOWED": "Stel werktijden van de eigenaar in via accountinstellingen.",
"WORKING_HOURS_CONFLICTS_WITH_APPOINTMENTS": "Deze tijden conflicteren met aankomende afspraken. Plan die eerst om of verwijder ze.",
"STAFF_UNKNOWN_PERMISSIONS": "Een of meer geselecteerde rechten zijn ongeldig.",
"STAFF_NO_SUBSCRIPTION": "Kies een abonnement voordat u personeel toevoegt.",
"STAFF_SEAT_LIMIT": "Uw abonnement heeft geen vrije plaatsen. Schakel een lid uit of upgrade.",
"STAFF_INVITE_OWNER_EMAIL": "De eigenaar van de organisatie is al lid.",
"STAFF_ALREADY_MEMBER": "Deze persoon is al lid.",
"STAFF_OWNER_NO_INVITE_LINK": "De eigenaar gebruikt geen uitnodigingslink.",
"STAFF_ALREADY_ACCEPTED": "Dit lid heeft de uitnodiging al geaccepteerd.",
"STAFF_INVITE_MISSING": "Er is geen uitnodiging voor dit lid gevonden.",
"STAFF_INVITE_ALREADY_ACCEPTED": "Deze uitnodiging is al geaccepteerd.",
"STAFF_INVITE_INVALID": "Deze uitnodiging is niet meer geldig.",
"STAFF_INVITE_REVOKED": "Deze uitnodiging is ingetrokken.",
"STAFF_INVITE_EXPIRED": "Deze uitnodiging is verlopen.",
"STAFF_ALREADY_ACTIVE": "Dit lid is al actief.",
"STAFF_ALREADY_DISABLED": "Dit lid is al uitgeschakeld of in afwachting.",
"STAFF_CANNOT_DISABLE_SELF": "U kunt uw eigen toegang niet uitschakelen.",
"STAFF_MEMBER_NOT_FOUND": "Teamlid niet gevonden.",
"STAFF_CANNOT_EDIT_OWNER": "Het lidmaatschap van de eigenaar kan hier niet worden bewerkt.",
"STAFF_CANNOT_ENABLE_OWNER": "De eigenaar kan op deze manier niet worden ingeschakeld.",
"STAFF_CANNOT_DISABLE_OWNER": "De eigenaar kan niet worden uitgeschakeld.",
"STAFF_CANNOT_REMOVE_OWNER": "De eigenaar kan niet worden verwijderd.",
"ORG_CANNOT_LINK_SELF": "U kunt een organisatie niet aan zichzelf koppelen.",
"ORG_LINK_WRONG_TYPE": "U kunt alleen koppelen aan het bijbehorende type (kliniek of lab).",
"ORG_TARGET_NO_SUBSCRIPTION": "De andere organisatie heeft geen actief abonnement.",
"ORG_LINK_EXISTS": "Deze organisaties zijn al gekoppeld.",
"ORG_REQUEST_NOT_PENDING": "Alleen openstaande verbindingsverzoeken kunnen worden beantwoord.",
"ORG_CANNOT_RESPOND_OWN": "U kunt niet op uw eigen verbindingsverzoek reageren.",
"ORG_INVITE_ALREADY_ACCEPTED": "Deze uitnodiging is al geaccepteerd.",
"ORG_INVITE_INVALID": "Deze uitnodiging is niet meer geldig.",
"ORG_INVITE_REVOKED": "Deze uitnodiging is ingetrokken.",
"ORG_INVITE_EXPIRED": "Deze uitnodiging is verlopen.",
"ORG_INVITE_TYPE_MISMATCH": "Het organisatietype komt niet overeen met deze uitnodiging.",
"ORG_INVITE_OWNER_HAS_ORG": "Deze eigenaar heeft al een organisatie. Zoek die in plaats van een uitnodiging te sturen.",
"ORG_UNKNOWN_TYPE": "Onbekend organisatietype.",
"ORG_COUNTERPART_NOT_LAB": "De gekoppelde organisatie is geen lab.",
"ORG_COUNTERPART_NOT_CLINIC": "De gekoppelde organisatie is geen kliniek.",
"ORG_CONNECTION_NOT_FOUND": "Gekoppelde organisatie niet gevonden.",
"ORG_INVITE_NOT_FOUND": "Uitnodiging niet gevonden.",
"ORG_ONLY_CLINIC_COMMENT": "Alleen de kliniek kan reageren op deze case.",
"CATALOG_UNKNOWN_TREATMENT_TYPE": "Onbekend behandelingstype.",
"CATALOG_UNKNOWN_PROSTHESIS_TYPE": "Onbekend prothesetype.",
"CATALOG_TREATMENT_NOT_LAB_DEPENDENT": "Dit behandelingstype wordt in de kliniek afgerond en kan niet naar een lab worden gestuurd.",
"TREATMENT_SAVE_DETAILS_BEFORE_LAB": "Sla behandeldetails op voordat u labcases aanmaakt.",
"TREATMENT_DETAIL_ONE_CASE": "Elk behandeldetail kan tot slechts één labcase behoren.",
"TREATMENT_DETAILS_NOT_FOUND": "Een of meer behandeldetails zijn niet gevonden.",
"TREATMENT_DEST_NOT_LINKED": "Die organisatie is geen actief gekoppeld lab.",
"TREATMENT_TEETH_REQUIRED_TO_SHIP": "Selecteer tanden voordat u dit werk naar een lab stuurt.",
"TREATMENT_TOOTH_UNKNOWN_DETAIL": "Een tand is gekoppeld aan een onbekend behandeldetail.",
"TREATMENT_TOOTH_NOT_ON_DETAIL": "Een geselecteerde tand staat niet op dit behandeldetail.",
"TREATMENT_CASE_NO_DEST": "Deze labcase heeft geen bestemmingsorganisatie.",
"TREATMENT_CASE_NEEDS_DETAIL": "Een labcase moet een behandeldetail bevatten.",
"TREATMENT_CASE_ONE_DETAIL": "Een labcase mag slechts één behandeldetail bevatten.",
"TREATMENT_ONLY_PROVIDER_SEND": "Alleen de behandelaar kan deze labcase versturen.",
"TREATMENT_CASE_ALREADY_SENT": "Deze case is al naar dat lab gestuurd.",
"TREATMENT_DUE_DATE_LOCKED": "De deadline kan niet worden gewijzigd nadat alle taken zijn voltooid.",
"TREATMENT_INVALID_DUE_DATE": "De deadline is ongeldig.",
"TREATMENT_DETAIL_KEY_REQUIRED": "Voor deze upload is een behandeldetail vereist.",
"TREATMENT_FILE_REQUIRED": "Er is minstens één bestand vereist.",
"TREATMENT_ATTACHMENT_NOT_FOUND": "Bijlage niet gevonden.",
"TREATMENT_FILE_UNAVAILABLE": "Dit bestand is niet meer beschikbaar.",
"TREATMENT_CASE_INVALID_ATTACHMENTS": "Een of meer bijlagen zijn ongeldig voor deze labcase.",
"TREATMENT_TOOTH_PROSTHESIS_INCOMPLETE": "Elke tand heeft een prothesetype nodig voordat u verstuurt.",
"LAB_CASE_NOT_FOUND": "Labcase niet gevonden.",
"TASK_ASSIGNEE_INVALID": "De geselecteerde persoon kan deze taak niet toegewezen krijgen.",
"TASK_ASSIGNED_TO_OTHER": "Deze taak is aan een ander teamlid toegewezen.",
"TASK_PAGE_DATE_SORT_ONLY": "Naar een taak springen kan alleen bij sorteren op datum.",
"INVALID_SENT_FROM": "De startdatum van het bereik is ongeldig.",
"INVALID_SENT_TO": "De einddatum van het bereik is ongeldig.",
"CASE_NOT_FOUND": "Case niet gevonden.",
"CASE_ATTACHMENT_NOT_FOUND": "Bijlage niet gevonden.",
"CASE_ATTACHMENT_MISSING_FILE": "Het bijlagebestand ontbreekt.",
"CASE_TASK_NOT_FOUND": "Taak niet gevonden.",
"COMMENT_NOT_FOUND": "Reactie niet gevonden.",
"COMMENT_VISIBILITY_LAB_ONLY": "Alleen labreacties kunnen de zichtbaarheid wijzigen.",
"TODAY_INVALID_RANGE": "Het datumbereik is ongeldig.",
"TODAY_INVALID_RANGE_ORDER": "Het einde van het bereik moet na het begin liggen.",
"NOT_FOUND": "Het gevraagde item is niet gevonden.",
"CONFLICT": "Deze actie conflicteert met bestaande gegevens.",
"CONFLICT_FUTURE_APPOINTMENTS": "U kunt niet stoppen met deelnemen aan behandelingen zolang u toekomstige afspraken hebt. Wijs ze eerst opnieuw toe of annuleer ze.",

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';
}
}