diff --git a/packages/esm-patient-chart-app/package.json b/packages/esm-patient-chart-app/package.json index ba93efa501..9b104a07aa 100644 --- a/packages/esm-patient-chart-app/package.json +++ b/packages/esm-patient-chart-app/package.json @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" diff --git a/packages/esm-patient-chart-app/src/index.ts b/packages/esm-patient-chart-app/src/index.ts index ccbaa15a03..5c42f8cfb0 100644 --- a/packages/esm-patient-chart-app/src/index.ts +++ b/packages/esm-patient-chart-app/src/index.ts @@ -217,6 +217,11 @@ export const startVisitDialog = getAsyncLifecycle(() => import('./visit/visit-pr moduleName, }); +export const deleteVisitDialog = getAsyncLifecycle(() => import('./visit/visit-prompt/delete-visit-dialog.component'), { + featureName: 'delete visit', + moduleName, +}); + export const endVisitDialog = getAsyncLifecycle(() => import('./visit/visit-prompt/end-visit-dialog.component'), { featureName: 'end visit', moduleName, @@ -239,3 +244,13 @@ export const deleteEncounterModal = getAsyncLifecycle( moduleName, }, ); + +export const editVisitDetailsActionButton = getAsyncLifecycle( + () => import('./visit/visit-action-items/edit-visit-details.component'), + { featureName: 'edit-visit-details', moduleName }, +); + +export const deleteVisitActionButton = getAsyncLifecycle( + () => import('./visit/visit-action-items/delete-visit-action-item.component'), + { featureName: 'delete-visit', moduleName }, +); diff --git a/packages/esm-patient-chart-app/src/routes.json b/packages/esm-patient-chart-app/src/routes.json index d53a2ff9a2..f7831dd2db 100644 --- a/packages/esm-patient-chart-app/src/routes.json +++ b/packages/esm-patient-chart-app/src/routes.json @@ -160,6 +160,12 @@ "online": true, "offline": true }, + { + "name": "delete-visit-dialog", + "component": "deleteVisitDialog", + "online": true, + "offline": true + }, { "name": "end-visit-dialog", "component": "endVisitDialog", @@ -206,6 +212,22 @@ }, "online": true, "offline": true + }, + { + "name": "edit-visit-action-button", + "slot": "visit-detail-overview-actions", + "component": "editVisitDetailsActionButton", + "online": true, + "offline": true, + "order": 0 + }, + { + "name": "delete-visit-action-button", + "slot": "visit-detail-overview-actions", + "component": "deleteVisitActionButton", + "online": true, + "offline": true, + "order": 1 } ], "pages": [ diff --git a/packages/esm-patient-chart-app/src/visit/hooks/useDeleteVisit.hook.tsx b/packages/esm-patient-chart-app/src/visit/hooks/useDeleteVisit.hook.tsx new file mode 100644 index 0000000000..ba9542b3a6 --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/hooks/useDeleteVisit.hook.tsx @@ -0,0 +1,81 @@ +import { Visit, showActionableNotification, showNotification, useVisit } from '@openmrs/esm-framework'; +import { deleteVisit, restoreVisit, useVisits } from '../visits-widget/visit.resource'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; + +export function useDeleteVisit(patientUuid: string, visit: Visit, onVisitDelete = () => {}, onVisitRestore = () => {}) { + const { mutateVisits } = useVisits(patientUuid); + const { mutate: mutateCurrentVisit } = useVisit(patientUuid); + const [isDeletingVisit, setIsDeletingVisit] = useState(false); + const { t } = useTranslation(); + + const restoreDeletedVisit = () => { + restoreVisit(visit?.uuid) + .then(() => { + showNotification({ + title: t('visitRestored', 'Visit restored'), + description: t('visitRestoredSuccessfully', '{{visit}} restored successfully', { + visit: visit?.visitType?.display ?? t('visit', 'Visit'), + }), + kind: 'success', + }); + mutateVisits(); + mutateCurrentVisit(); + onVisitRestore?.(); + }) + .catch(() => { + showNotification({ + title: t('visitNotRestored', "Visit couldn't be restored"), + description: t('errorWhenRestoringVisit', 'Error occured when restoring {{visit}}', { + visit: visit?.visitType?.display ?? t('visit', 'Visit'), + }), + kind: 'error', + }); + }); + }; + + const initiateDeletingVisit = () => { + setIsDeletingVisit(true); + const isCurrentVisitDeleted = !visit?.stopDatetime; + + deleteVisit(visit?.uuid) + .then(() => { + mutateVisits(); + mutateCurrentVisit(); + // TODO: Needs to be replaced with Actionable Snackbar when Actionable + showActionableNotification({ + title: !isCurrentVisitDeleted + ? t('visitDeleted', '{{visit}} deleted', { + visit: visit?.visitType?.display ?? t('visit', 'Visit'), + }) + : t('visitCancelled', 'Visit cancelled'), + kind: 'success', + subtitle: !isCurrentVisitDeleted + ? t('visitCancelSuccessMessage', 'Active {{visit}} cancelled successfully', { + visit: visit?.visitType?.display ?? t('visit', 'Visit'), + }) + : t('visitCanceled', 'Canceled active visit successfully'), + actionButtonLabel: t('undo', 'Undo'), + onActionButtonClick: restoreDeletedVisit, + }); + onVisitDelete?.(); + }) + .catch(() => { + showNotification({ + title: isCurrentVisitDeleted + ? t('errorCancellingVisit', 'Error cancelling active visit') + : t('errorDeletingVisit', 'Error deleting visit'), + kind: 'error', + description: t('errorOccuredDeletingVisit', 'An error occured when deleting visit'), + }); + }) + .finally(() => { + setIsDeletingVisit(false); + }); + }; + + return { + initiateDeletingVisit, + isDeletingVisit, + }; +} diff --git a/packages/esm-patient-chart-app/src/visit/visit-action-items/delete-visit-action-item.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-action-items/delete-visit-action-item.component.tsx new file mode 100644 index 0000000000..83c3a12985 --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/visit-action-items/delete-visit-action-item.component.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Button } from '@carbon/react'; +import { UserHasAccess, Visit, showModal, useLayoutType } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { TrashCan } from '@carbon/react/icons'; + +interface DeleteVisitActionItemProps { + patientUuid: string; + visit: Visit; +} + +const DeleteVisitActionItem: React.FC = ({ patientUuid, visit }) => { + const { t } = useTranslation(); + const isTablet = useLayoutType() === 'tablet'; + + const deleteVisit = () => { + const dispose = showModal('delete-visit-dialog', { + patientUuid, + visit, + closeModal: () => dispose(), + }); + }; + + const cancelVisit = () => { + const dispose = showModal('cancel-visit-dialog', { + patientUuid, + closeModal: () => dispose(), + }); + }; + + const isActiveVisit = !visit?.stopDatetime; + + if (visit?.encounters?.length) { + return null; + } + + return ( + + + + ); +}; + +export default DeleteVisitActionItem; diff --git a/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx new file mode 100644 index 0000000000..7717edf1aa --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Button } from '@carbon/react'; +import { UserHasAccess, Visit, useLayoutType } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; +import { Edit } from '@carbon/react/icons'; + +interface EditVisitDetailsActionItemProps { + patientUuid: string; + visit: Visit; +} + +const EditVisitDetailsActionItem: React.FC = ({ visit }) => { + const { t } = useTranslation(); + + const isTablet = useLayoutType() === 'tablet'; + + const editVisitDetails = () => { + launchPatientWorkspace('start-visit-workspace-form', { + workspaceTitle: t('editVisitDetails', 'Edit visit details'), + visitToEdit: visit, + }); + }; + + return ( + + + + ); +}; + +export default EditVisitDetailsActionItem; diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/base-visit-type.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/base-visit-type.component.tsx index 8884fee76f..98770a3031 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/base-visit-type.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/base-visit-type.component.tsx @@ -7,7 +7,7 @@ import isEmpty from 'lodash-es/isEmpty'; import { Layer, RadioButtonGroup, RadioButton, Search, StructuredListSkeleton } from '@carbon/react'; import { PatientChartPagination } from '@openmrs/esm-patient-common-lib'; import { useLayoutType, usePagination, VisitType } from '@openmrs/esm-framework'; -import { VisitFormData } from './visit-form.component'; +import { VisitFormData } from './visit-form.resource'; import styles from './visit-type-overview.scss'; interface BaseVisitTypeProps { diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/location-selection.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/location-selection.component.tsx index e80f2d10e9..aedc948ff7 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/location-selection.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/location-selection.component.tsx @@ -7,7 +7,7 @@ import { useFormContext, Controller } from 'react-hook-form'; import { Location, OpenmrsResource, useConfig, useSession } from '@openmrs/esm-framework'; import { useDefaultLoginLocation } from '../hooks/useDefaultLocation'; import { useLocations } from '../hooks/useLocations'; -import { VisitFormData } from './visit-form.component'; +import { VisitFormData } from './visit-form.resource'; import { ChartConfig } from '../../config-schema'; import styles from './visit-form.scss'; diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-attribute-type.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-attribute-type.component.tsx index 81daf07406..0dfc8f0a1f 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-attribute-type.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-attribute-type.component.tsx @@ -17,8 +17,8 @@ import { import { useTranslation } from 'react-i18next'; import styles from './visit-attribute-type.scss'; import { Controller, ControllerRenderProps, useFormContext } from 'react-hook-form'; -import { VisitFormData } from './visit-form.component'; import dayjs from 'dayjs'; +import { VisitFormData } from './visit-form.resource'; interface VisitAttributes { [uuid: string]: string; diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-date-time.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-date-time.component.tsx new file mode 100644 index 0000000000..b9d0a0d210 --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-date-time.component.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import styles from './visit-form.scss'; +import { Controller, useFormContext } from 'react-hook-form'; +import { VisitFormData } from './visit-form.resource'; +import { DatePicker, DatePickerInput, Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react'; +import classNames from 'classnames'; +import { useLayoutType } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { amPm } from '@openmrs/esm-patient-common-lib'; + +interface VisitDateTimeFieldProps { + dateFieldName: 'visitStartDate' | 'visitStopDate'; + timeFieldName: 'visitStartTime' | 'visitStopTime'; + timeFormatFieldName: 'visitStartTimeFormat' | 'visitStopTimeFormat'; + minDate?: number; + maxDate?: number; +} + +const VisitDateTimeField: React.FC = ({ + dateFieldName, + timeFieldName, + timeFormatFieldName, + minDate, + maxDate, +}) => { + const { t } = useTranslation(); + const isTablet = useLayoutType() === 'tablet'; + const { + control, + formState: { errors }, + getValues, + } = useFormContext(); + + // Since we have the separate date and time fields, the final validation needs to be done at the form + // submission, hence just using the min date with hours/ minutes/ seconds set to 0 and max date set to + // last second of the day. We want to just compare dates and not time. + minDate = minDate ? new Date(minDate).setHours(0, 0, 0, 0) : null; + maxDate = maxDate ? new Date(maxDate).setHours(23, 59, 59, 59) : null; + + return ( +
+
{t('dateAndTimeOfVisit', 'Date and time of visit')}
+
+ ( + + onChange(date)} + value={value} + > + + + + )} + /> + + ( + onChange(event.target.value as amPm)} + pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$" + style={{ marginLeft: '0.125rem', flex: 'none' }} + value={value} + onBlur={onBlur} + invalid={errors[timeFieldName]?.message} + invalidText={errors[timeFieldName]?.message} + > + ( + onChange(event.target.value as amPm)} + value={value} + aria-label={t('timeFormat ', 'Time Format')} + invalid={errors[timeFormatFieldName]?.message} + invalidText={errors[timeFormatFieldName]?.message} + > + + + + )} + /> + + )} + /> + +
+
+ ); +}; + +export default VisitDateTimeField; + +function ResponsiveWrapper({ children, isTablet }: { children: React.ReactNode; isTablet: boolean }) { + return isTablet ? {children} : <>{children}; +} diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx index 9a209cd247..343ac9be2a 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx @@ -1,24 +1,17 @@ import React, { useCallback, useState, useMemo, useEffect } from 'react'; -import classNames from 'classnames'; import dayjs from 'dayjs'; import { Button, ButtonSet, ContentSwitcher, - DatePicker, - DatePickerInput, Form, FormGroup, InlineNotification, - Layer, RadioButton, RadioButtonGroup, Row, - SelectItem, Stack, Switch, - TimePicker, - TimePickerSelect, } from '@carbon/react'; import { useTranslation } from 'react-i18next'; import { useForm, Controller, FormProvider } from 'react-hook-form'; @@ -38,13 +31,15 @@ import { useVisitTypes, useConfig, useVisit, + Visit, + updateVisit, useConnectivity, } from '@openmrs/esm-framework'; import { - amPm, convertTime12to24, createOfflineVisitForPatient, DefaultWorkspaceProps, + time12HourFormatRegex, useActivePatientEnrollment, } from '@openmrs/esm-patient-common-lib'; import { MemoizedRecommendedVisitType } from './recommended-visit-type.component'; @@ -57,24 +52,23 @@ import BaseVisitType from './base-visit-type.component'; import LocationSelector from './location-selection.component'; import VisitAttributeTypeFields from './visit-attribute-type.component'; import styles from './visit-form.scss'; +import { VisitFormData } from './visit-form.resource'; +import VisitDateTimeField from './visit-date-time.component'; +import { useVisits } from '../visits-widget/visit.resource'; import { useOfflineVisitType } from '../hooks/useOfflineVisitType'; -export type VisitFormData = { - visitDate: Date; - visitTime: string; - timeFormat: 'PM' | 'AM'; - programType: string; - visitType: string; - visitLocation: { - display: string; - uuid: string; - }; - visitAttributes: { - [x: string]: string; - }; -}; +interface StartVisitFormProps extends DefaultWorkspaceProps { + visitToEdit: Visit; + showVisitEndDateTimeFields: boolean; +} -const StartVisitForm: React.FC = ({ patientUuid, closeWorkspace, promptBeforeClosing }) => { +const StartVisitForm: React.FC = ({ + patientUuid, + closeWorkspace, + promptBeforeClosing, + visitToEdit, + showVisitEndDateTimeFields, +}) => { const { t } = useTranslation(); const isTablet = useLayoutType() === 'tablet'; const isOnline = useConnectivity(); @@ -86,6 +80,8 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor const [isSubmitting, setIsSubmitting] = useState(false); const visitHeaderSlotState = useMemo(() => ({ patientUuid }), [patientUuid]); const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid); + const { mutate: mutateCurrentVisit } = useVisit(patientUuid); + const { mutateVisits } = useVisits(patientUuid); const allVisitTypes = isOnline ? useVisitTypes() : useOfflineVisitType(); const { mutate } = useVisit(patientUuid); const { mutate: mutateVisit } = useVisit(patientUuid); @@ -99,6 +95,11 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor const [visitUuid, setVisitUuid] = useState(''); const { mutate: mutateQueueEntry } = useVisitQueueEntry(patientUuid, visitUuid); + const displayVisitStopDateTimeFields = useMemo( + () => visitToEdit?.stopDatetime || showVisitEndDateTimeFields, + [visitToEdit?.stopDatetime, showVisitEndDateTimeFields], + ); + const visitFormSchema = useMemo(() => { const visitAttributes = (config.visitAttributeTypes ?? [])?.reduce( (acc, { uuid, required }) => ({ @@ -115,9 +116,18 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor ); return z.object({ - visitDate: z.date(), - visitTime: z.string(), - timeFormat: z.enum(['PM', 'AM']), + visitStartDate: z.date(), + visitStartTime: z + .string() + .refine((value) => value.match(time12HourFormatRegex), t('invalidTimeFormat', 'Invalid time format')), + visitStartTimeFormat: z.enum(['PM', 'AM']), + visitStopDate: displayVisitStopDateTimeFields ? z.date() : z.date().optional(), + visitStopTime: displayVisitStopDateTimeFields + ? z + .string() + .refine((value) => value.match(time12HourFormatRegex), t('invalidTimeFormat', 'Invalid time format')) + : z.string().optional(), + visitStopTimeFormat: displayVisitStopDateTimeFields ? z.enum(['PM', 'AM']) : z.enum(['PM', 'AM']).optional(), programType: z.string().optional(), visitType: z.string().refine((value) => !!value, t('visitTypeRequired', 'Visit type is required')), visitLocation: z.object({ @@ -126,20 +136,44 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor }), visitAttributes: z.object(visitAttributes), }); - }, [t, config]); + }, [t, config, displayVisitStopDateTimeFields]); - type VisitFormData = z.infer; + const defaultValues = useMemo(() => { + const visitStartDate = visitToEdit?.startDatetime ? new Date(visitToEdit?.startDatetime) : new Date(); + const visitStopDate = visitToEdit?.stopDatetime ? new Date(visitToEdit?.stopDatetime) : null; + let defaultValues: Partial = { + visitStartDate, + visitStartTime: dayjs(visitStartDate).format('hh:mm'), + visitStartTimeFormat: visitStartDate.getDate() >= 12 ? 'PM' : 'AM', + + visitType: visitToEdit?.visitType?.uuid, + visitLocation: visitToEdit?.location ?? sessionLocation ?? {}, + visitAttributes: + visitToEdit?.attributes.reduce( + (acc, curr) => ({ + ...acc, + [curr.attributeType.uuid]: typeof curr.value === 'object' ? curr?.value?.uuid : `${curr.value ?? ''}`, + }), + {}, + ) ?? {}, + }; + + if (visitStopDate) { + defaultValues = { + ...defaultValues, + visitStopDate, + visitStopTime: dayjs(visitStopDate).format('hh:mm'), + visitStopTimeFormat: visitStopDate.getDate() >= 12 ? 'PM' : 'AM', + }; + } + + return defaultValues; + }, [visitToEdit]); const methods = useForm({ mode: 'all', resolver: zodResolver(visitFormSchema), - defaultValues: { - visitDate: new Date(), - visitTime: dayjs(new Date()).format('hh:mm'), - timeFormat: new Date().getHours() >= 12 ? 'PM' : 'AM', - visitLocation: sessionLocation ? sessionLocation : {}, - visitAttributes: {}, - }, + defaultValues, }); const { @@ -147,21 +181,99 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor control, getValues, formState: { errors }, + setError, } = methods; + const validateVisitStartStopDatetime = useCallback(() => { + let visitStartDate = getValues('visitStartDate'); + const visitStartTime = getValues('visitStartTime'); + const visitStartTimeFormat = getValues('visitStartTimeFormat'); + + const [visitStartHours, visitStartMinutes] = convertTime12to24(visitStartTime, visitStartTimeFormat); + + const visitStartDatetime = visitStartDate.setHours(visitStartHours, visitStartMinutes); + + let validSubmission = true; + + if (maxVisitStartDatetime && visitStartDatetime >= maxVisitStartDatetime) { + validSubmission = false; + setError('visitStartDate', { + message: t('invalidVisitStartDate', 'Start date needs to be on or before {{firstEncounterDatetime}}', { + firstEncounterDatetime: new Date(maxVisitStartDatetime).toLocaleString(), + interpolation: { + escapeValue: false, + }, + }), + }); + } + + if (!displayVisitStopDateTimeFields) { + return validSubmission; + } + + let visitStopDate = getValues('visitStopDate'); + const visitStopTime = getValues('visitStopTime'); + const visitStopTimeFormat = getValues('visitStopTimeFormat'); + + const [visitStopHours, visitStopMinutes] = convertTime12to24(visitStopTime, visitStopTimeFormat); + + const visitStopDatetime = visitStopDate.setHours(visitStopHours, visitStopMinutes); + + if (minVisitStopDatetime && visitStopDatetime <= minVisitStopDatetime) { + validSubmission = false; + setError('visitStopDate', { + message: t('invalidVisitStopDate', 'Stop date needs to be on or after {{lastEncounterDatetime}}', { + lastEncounterDatetime: new Date(minVisitStopDatetime).toLocaleString(), + interpolation: { + escapeValue: false, + }, + }), + }); + } + + if (visitStartDatetime >= visitStopDatetime) { + validSubmission = false; + setError('visitStopDate', { + message: t('invalidVisitStopDate', 'Visit stop date time cannot be on or before visit start date time'), + }); + } + + return validSubmission; + }, [setError]); + const onSubmit = useCallback( (data: VisitFormData, event) => { - const { timeFormat, visitDate, visitLocation, visitTime, visitType, visitAttributes } = data; + if (visitToEdit && !validateVisitStartStopDatetime()) { + return; + } + + const { + visitStartTimeFormat, + visitStartDate, + visitLocation, + visitStartTime, + visitType, + visitAttributes, + visitStopDate, + visitStopTime, + visitStopTimeFormat, + } = data; setIsSubmitting(true); - const [hours, minutes] = convertTime12to24(visitTime, timeFormat); + const [hours, minutes] = convertTime12to24(visitStartTime, visitStartTimeFormat); - const payload: NewVisitPayload = { + let payload: NewVisitPayload = { patient: patientUuid, startDatetime: toDateObjectStrict( toOmrsIsoString( - new Date(dayjs(visitDate).year(), dayjs(visitDate).month(), dayjs(visitDate).date(), hours, minutes), + new Date( + dayjs(visitStartDate).year(), + dayjs(visitStartDate).month(), + dayjs(visitStartDate).date(), + hours, + minutes, + ), ), ), visitType: visitType, @@ -173,10 +285,150 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor value: value as string, })), }; + if (visitToEdit?.uuid) { + // The request throws 400 (Bad request)error when patient is passed in the update payload + + delete payload.patient; + } + + if (displayVisitStopDateTimeFields) { + const [visitStopHours, visitStopMinutes] = convertTime12to24(visitStopTime, visitStopTimeFormat); + + payload = { + ...payload, + stopDatetime: toDateObjectStrict( + toOmrsIsoString( + new Date( + dayjs(visitStopDate).year(), + dayjs(visitStopDate).month(), + dayjs(visitStopDate).date(), + visitStopHours, + visitStopMinutes, + ), + ), + ), + }; + } const abortController = new AbortController(); - if (!isOnline) { + if (isOnline) { + (visitToEdit?.uuid + ? updateVisit(visitToEdit?.uuid, payload, abortController) + : saveVisit(payload, abortController) + ) + .pipe(first()) + .subscribe( + (response) => { + if (response.status === 201) { + if (config.showServiceQueueFields) { + // retrieve values from queue extension + setVisitUuid(response.data.uuid); + const queueLocation = event?.target['queueLocation']?.value; + const serviceUuid = event?.target['service']?.value; + const priority = event?.target['priority']?.value; + const status = event?.target['status']?.value; + const sortWeight = event?.target['sortWeight']?.value; + + saveQueueEntry( + response.data.uuid, + serviceUuid, + patientUuid, + priority, + status, + sortWeight, + new AbortController(), + queueLocation, + visitQueueNumberAttributeUuid, + ).then( + ({ status }) => { + if (status === 201) { + mutateCurrentVisit(); + mutateVisits(); + mutateQueueEntry(); + showToast({ + kind: 'success', + title: t('visitStarted', 'Visit started'), + description: t('queueAddedSuccessfully', `Patient added to the queue successfully.`), + }); + } + }, + (error) => { + showNotification({ + title: t('queueEntryError', 'Error adding patient to the queue'), + kind: 'error', + critical: true, + description: error?.message, + }); + }, + ); + } + if (config.showUpcomingAppointments && upcomingAppointment) { + const appointmentPayload: AppointmentPayload = { + appointmentKind: upcomingAppointment?.appointmentKind, + serviceUuid: upcomingAppointment?.service.uuid, + startDateTime: upcomingAppointment?.startDateTime, + endDateTime: upcomingAppointment?.endDateTime, + locationUuid: visitLocation?.uuid, + patientUuid: patientUuid, + uuid: upcomingAppointment?.uuid, + dateHonored: dayjs(visitStartDate).format(), + }; + saveAppointment(appointmentPayload, abortController).then( + ({ status }) => { + if (status === 201) { + mutateCurrentVisit(); + mutateVisits(); + showToast({ + critical: true, + kind: 'success', + description: t('appointmentUpdate', 'Upcoming appointment updated successfully'), + title: t('appointmentEdited', 'Appointment edited'), + }); + } + }, + (error) => { + showNotification({ + title: t('updateError', 'Error updating upcoming appointment'), + kind: 'error', + critical: true, + description: error?.message, + }); + }, + ); + } + } + mutateCurrentVisit(); + mutateVisits(); + closeWorkspace(); + + showToast({ + critical: true, + kind: 'success', + description: !visitToEdit + ? t('visitStartedSuccessfully', '{{visit}} started successfully', { + visit: response?.data?.visitType?.display ?? t('visit', 'Visit'), + }) + : t('visitDetailsUpdatedSuccessfully', '{{visit}} updated successfully', { + visit: response?.data?.visitType?.display ?? t('pastVisit', 'Past visit'), + }), + title: !visitToEdit + ? t('visitStarted', 'Visit started') + : t('visitDetailsUpdated', 'Visit details updated'), + }); + }, + (error) => { + showNotification({ + title: !visitToEdit + ? t('startVisitError', 'Error starting visit') + : t('errorUpdatingVisitDetails', 'Error updating visit details'), + kind: 'error', + critical: true, + description: error?.message, + }); + }, + ); + } else { createOfflineVisitForPatient( patientUuid, visitLocation.uuid, @@ -207,118 +459,20 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor ); return; } - - saveVisit(payload, abortController) - .pipe(first()) - .subscribe( - (response) => { - if (response.status === 201) { - if (config.showServiceQueueFields) { - // retrieve values from queue extension - setVisitUuid(response.data.uuid); - const queueLocation = event?.target['queueLocation']?.value; - const serviceUuid = event?.target['service']?.value; - const priority = event?.target['priority']?.value; - const status = event?.target['status']?.value; - const sortWeight = event?.target['sortWeight']?.value; - - saveQueueEntry( - response.data.uuid, - serviceUuid, - patientUuid, - priority, - status, - sortWeight, - new AbortController(), - queueLocation, - visitQueueNumberAttributeUuid, - ).then( - ({ status }) => { - if (status === 201) { - mutateVisit(); - mutateQueueEntry(); - showToast({ - kind: 'success', - title: t('visitStarted', 'Visit started'), - description: t('queueAddedSuccessfully', `Patient added to the queue successfully.`), - }); - } - }, - (error) => { - showNotification({ - title: t('queueEntryError', 'Error adding patient to the queue'), - kind: 'error', - critical: true, - description: error?.message, - }); - }, - ); - } - if (config.showUpcomingAppointments && upcomingAppointment) { - const appointmentPayload: AppointmentPayload = { - appointmentKind: upcomingAppointment?.appointmentKind, - serviceUuid: upcomingAppointment?.service.uuid, - startDateTime: upcomingAppointment?.startDateTime, - endDateTime: upcomingAppointment?.endDateTime, - locationUuid: visitLocation?.uuid, - patientUuid: patientUuid, - uuid: upcomingAppointment?.uuid, - dateHonored: dayjs(visitDate).format(), - }; - saveAppointment(appointmentPayload, abortController).then( - ({ status }) => { - if (status === 201) { - mutateVisit(); - showToast({ - critical: true, - kind: 'success', - description: t('appointmentUpdate', 'Upcoming appointment updated successfully'), - title: t('appointmentEdited', 'Appointment edited'), - }); - } - }, - (error) => { - showNotification({ - title: t('updateError', 'Error updating upcoming appointment'), - kind: 'error', - critical: true, - description: error?.message, - }); - }, - ); - } - mutateVisit(); - closeWorkspace(); - - showToast({ - critical: true, - kind: 'success', - description: t('visitStartedSuccessfully', '{{visit}} started successfully', { - visit: response?.data?.visitType?.display ?? `Visit`, - }), - title: t('visitStarted', 'Visit started'), - }); - } - }, - (error) => { - showNotification({ - title: t('startVisitError', 'Error starting visit'), - kind: 'error', - critical: true, - description: error?.message, - }); - }, - ); }, [ closeWorkspace, config.showServiceQueueFields, config.showUpcomingAppointments, visitQueueNumberAttributeUuid, - mutateVisit, + mutateCurrentVisit, + mutateVisits, + patientUuid, upcomingAppointment, t, + visitToEdit, + displayVisitStopDateTimeFields, ], ); @@ -327,6 +481,22 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor promptBeforeClosing(() => true); }; + let [maxVisitStartDatetime, minVisitStopDatetime] = useMemo(() => { + if (!visitToEdit?.encounters?.length) { + return [null, null]; + } + + const allEncountersDateTime = visitToEdit?.encounters?.map(({ encounterDatetime }) => + Date.parse(encounterDatetime), + ); + const maxVisitStartDatetime = Math.min(...allEncountersDateTime); + const minVisitStopDatetime = Math.max(...allEncountersDateTime); + return [maxVisitStartDatetime, minVisitStopDatetime]; + }, [visitToEdit]); + + const visitStartDate = getValues('visitStartDate'); + minVisitStopDatetime = minVisitStopDatetime ?? Date.parse(visitStartDate.toLocaleString()); + useEffect(() => { if (errorFetchingLocations) { setErrorFetchingResources((prev) => ({ @@ -358,69 +528,21 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor )} - {/* Date and time of visit. Defaults to the current date and time. */} -
-
{t('dateAndTimeOfVisit', 'Date and time of visit')}
-
- ( - - onChange(date)} - value={value} - > - - - - )} - /> - - ( - onChange(event.target.value as amPm)} - pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$" - style={{ marginLeft: '0.125rem', flex: 'none' }} - value={value} - onBlur={onBlur} - > - ( - onChange(event.target.value as amPm)} - value={value} - aria-label={t('timeFormat ', 'Time Format')} - > - - - - )} - /> - - )} - /> - -
-
+ + + {displayVisitStopDateTimeFields && ( + + )} {/* Upcoming appointments. This get shown when upcoming appointments are configured */} {config.showUpcomingAppointments && ( @@ -546,7 +668,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor kind="primary" type="submit" > - {t('startVisit', 'Start visit')} + {!visitToEdit ? t('startVisit', 'Start visit') : t('updateVisit', 'Update visit')} @@ -554,8 +676,4 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor ); }; -function ResponsiveWrapper({ children, isTablet }: { children: React.ReactNode; isTablet: boolean }) { - return isTablet ? {children} : <>{children}; -} - export default StartVisitForm; diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts new file mode 100644 index 0000000000..50310b3c19 --- /dev/null +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts @@ -0,0 +1,19 @@ +import { amPm } from '@openmrs/esm-patient-common-lib'; + +export type VisitFormData = { + visitStartDate: Date; + visitStartTime: string; + visitStartTimeFormat: amPm; + visitStopDate: Date; + visitStopTime: string; + visitStopTimeFormat: amPm; + programType: string; + visitType: string; + visitLocation: { + display?: string; + uuid?: string; + }; + visitAttributes: { + [x: string]: string; + }; +}; diff --git a/packages/esm-patient-chart-app/src/visit/visit-prompt/cancel-visit-dialog.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-prompt/cancel-visit-dialog.component.tsx index 5ae8141f35..9be6e09e31 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-prompt/cancel-visit-dialog.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-prompt/cancel-visit-dialog.component.tsx @@ -1,10 +1,11 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; -import { useVisit, openmrsFetch, showToast, showNotification } from '@openmrs/esm-framework'; +import { useVisit } from '@openmrs/esm-framework'; import { removeQueuedPatient } from '../hooks/useServiceQueue'; import { useVisitQueueEntry } from '../queue-entry/queue.resource'; import styles from './cancel-visit-dialog.scss'; +import { useDeleteVisit } from '../hooks/useDeleteVisit.hook'; interface CancelVisitDialogProps { patientUuid: string; @@ -13,46 +14,18 @@ interface CancelVisitDialogProps { const CancelVisitDialog: React.FC = ({ patientUuid, closeModal }) => { const { t } = useTranslation(); - const { currentVisit, mutate } = useVisit(patientUuid); - const [submitting, setSubmitting] = useState(false); + const { currentVisit } = useVisit(patientUuid); const visitQueryEntry = useVisitQueueEntry(patientUuid, currentVisit?.uuid); - const cancelActiveVisit = useCallback(() => { - // TODO: Extend `updateVisit` functionality in esm-framework to support this request - setSubmitting(true); - openmrsFetch(`/ws/rest/v1/visit/${currentVisit.uuid}`, { - headers: { - 'Content-type': 'application/json', - }, - method: 'POST', - body: { voided: true }, - }).then( - () => { - const queueEntry = visitQueryEntry?.queueEntry; - mutate(); - closeModal(); - setSubmitting(false); - if (queueEntry) { - removeQueuedPatient(queueEntry.queueUuid, queueEntry.queueEntryUuid, new AbortController()); - } + const onDeleteVisit = useCallback(() => { + const queueEntry = visitQueryEntry?.queueEntry; + if (queueEntry) { + removeQueuedPatient(queueEntry.queueUuid, queueEntry.queueEntryUuid, new AbortController()); + } + closeModal(); + }, []); - showToast({ - title: t('visitCancelled', 'Visit cancelled'), - kind: 'success', - description: t('visitCancelSuccessMessage', 'Active visit cancelled successfully'), - }); - }, - (error) => { - showNotification({ - title: t('errorCancellingVisit', 'Error cancelling visit'), - kind: 'error', - critical: true, - description: error?.message, - }); - setSubmitting(false); - }, - ); - }, [closeModal, currentVisit.uuid, mutate, t, visitQueryEntry?.queueEntry]); + const { initiateDeletingVisit, isDeletingVisit } = useDeleteVisit(patientUuid, currentVisit, onDeleteVisit); return (
@@ -69,9 +42,9 @@ const CancelVisitDialog: React.FC = ({ patientUuid, clos - + + +
+ ); +}; + +export default DeleteVisitDialog; diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.component.tsx index a0c370eb40..eaeb01e61e 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.component.tsx @@ -64,7 +64,11 @@ function VisitDetailOverviewComponent({ patientUuid }: VisitOverviewComponentPro
- +
diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.scss b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.scss index 7b309b2a32..f24eca8ad8 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.scss +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/visit-detail-overview.scss @@ -181,6 +181,12 @@ margin-bottom: 2rem; } +.visitDetailOverviewActions { + display: flex; + justify-content: flex-end; + align-items: center; +} + // Overriding styles for RTL support html[dir='rtl'] { .header { @@ -191,3 +197,4 @@ html[dir='rtl'] { } } } + diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx index 819175f725..192e1480c6 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx @@ -3,7 +3,7 @@ import { openmrsFetch, OpenmrsResource, Privilege, Visit } from '@openmrs/esm-fr export function useVisits(patientUuid: string) { const customRepresentation = - 'custom:(uuid,encounters:(uuid,diagnoses:(uuid,display,rank,diagnosis),form:(uuid,display),encounterDatetime,orders:full,obs:full,encounterType:(uuid,display,viewPrivilege,editPrivilege),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime,stopDatetime,patient'; + 'custom:(uuid,encounters:(uuid,diagnoses:(uuid,display,rank,diagnosis),form:(uuid,display),encounterDatetime,orders:full,obs:full,encounterType:(uuid,display,viewPrivilege,editPrivilege),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime,stopDatetime,patient,attributes:(attributeType:ref,display,uuid,value)'; const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array } }, Error>( `/ws/rest/v1/visit?patient=${patientUuid}&v=${customRepresentation}`, @@ -69,6 +69,22 @@ export function usePastVisits(patientUuid: string) { }; } +export function deleteVisit(visitUuid: string) { + return openmrsFetch(`/ws/rest/v1/visit/${visitUuid}`, { + method: 'DELETE', + }); +} + +export function restoreVisit(visitUuid: string) { + return openmrsFetch(`/ws/rest/v1/visit/${visitUuid}`, { + headers: { + 'content-type': 'application/json', + }, + method: 'POST', + body: { voided: false }, + }); +} + export function mapEncounters(visit: Visit): MappedEncounter[] { return visit?.encounters?.map((encounter) => ({ id: encounter?.uuid, diff --git a/packages/esm-patient-chart-app/translations/am.json b/packages/esm-patient-chart-app/translations/am.json index 126e796a09..18ac679227 100644 --- a/packages/esm-patient-chart-app/translations/am.json +++ b/packages/esm-patient-chart-app/translations/am.json @@ -11,6 +11,7 @@ "Attachments dashboard": "Attachments dashboard", "cancel": "Cancel", "cancelActiveVisitConfirmation": "Are you sure you want to cancel this active visit?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "Cancel visit", "cancelVisitExplainerMessage": "Cancelling this visit will delete its associated encounters", "causeOfDeath": "Cause of death", @@ -20,6 +21,7 @@ "Conditions dashboard": "Conditions dashboard", "confirm": "Confirm", "confirmDeceased": "Confirm Deceased", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "Are you sure, you want to mark patient as alive?", "currentVisit": "Current Visit", "date": "Date", @@ -30,12 +32,17 @@ "deleteEncounter": "Delete Encounter", "deleteEncounterConfirmationText": "Are you sure you want to delete this encounter? This action can't be undone.", "deleteThisEncounter": "Delete this encounter", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "Diagnoses", "discard": "Discard", "dose": "Dose", "editPastVisit": "Edit past visit", "editThisEncounter": "Edit this encounter", "editThisVisit": "Edit this visit", + "editVisitDetails": "Edit visit details", "emptyStateText": "There are no {{displayText}} to display for this patient", "encounterDeleted": "Encounter deleted", "encounters": "Encounters", @@ -49,9 +56,13 @@ "endVisit_title": "End Visit", "endVisitExplainerMessage": "Ending this visit means that you will no longer be able to add encounters to it. If you need to add an encounter, you can create a new visit for this patient or edit a past one.", "error": "Error", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "couldn't be deleted", "failedToLoadCurrentVisit": "Failed loading current visit", "female": "Female", @@ -61,6 +72,9 @@ "goToThisEncounter": "Go to this encounter", "hide": "Hide", "indication": "Indication", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "Loading", "loadingVisit": "Loading current visit...", "location": "Location", @@ -142,16 +156,26 @@ "time": "Time", "timeFormat ": "Time Format", "type": "Type", + "undo": "Undo", "unknown": "Unknown", "unsavedChanges": "You have unsaved changes", "unsavedChangesInForm": "There are unsaved changes in {{formName}}. Please save them before opening another form.", "updateError": "Error updating upcoming appointment", + "updateVisit": "Update visit", + "visit": "Visit", "visitAttributes": "Visit attributes", + "visitCanceled": "Canceled active visit successfully", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "Visit ended", "visitEndSuccessfully": "visitEndSuccessfully", "visitLocation": "Visit Location", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "visits", "Visits": "Visits", "Visits dashboard": "Visits dashboard", diff --git a/packages/esm-patient-chart-app/translations/ar.json b/packages/esm-patient-chart-app/translations/ar.json index 5be78a698b..f29c80293d 100644 --- a/packages/esm-patient-chart-app/translations/ar.json +++ b/packages/esm-patient-chart-app/translations/ar.json @@ -11,6 +11,7 @@ "Attachments dashboard": "لوحة المرفقات", "cancel": "إلغاء", "cancelActiveVisitConfirmation": "هل أنت متأكد أنك تريد إلغاء هذه الزيارة النشطة؟", + "cancellingVisit": "Cancelling visit", "cancelVisit": "إلغاء الزيارة", "cancelVisitExplainerMessage": "إلغاء هذه الزيارة سيؤدي إلى حذف اللقاءات المرتبطة بها", "causeOfDeath": "سبب الوفاة", @@ -20,6 +21,7 @@ "Conditions dashboard": "لوحة الحالات", "confirm": "تأكيد", "confirmDeceased": "تأكيد الوفاة", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "هل أنت متأكد أنك تريد وضع علامة على المريض كأنه على قيد الحياة؟", "currentVisit": "الزيارة الحالية", "date": "التاريخ", @@ -30,12 +32,17 @@ "deleteEncounter": "حذف اللقاء", "deleteEncounterConfirmationText": "هل أنت متأكد أنك تريد حذف هذا اللقاء؟ لا يمكن التراجع عن هذا الإجراء.", "deleteThisEncounter": "حذف هذا اللقاء", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "التشخيصات", "discard": "تجاهل", "dose": "الجرعة", "editPastVisit": "تعديل الزيارة السابقة", "editThisEncounter": "تعديل هذا اللقاء", "editThisVisit": "تعديل هذه الزيارة", + "editVisitDetails": "Edit visit details", "emptyStateText": "لا يوجد {{displayText}} لعرضه لهذا المريض", "encounterDeleted": "تم حذف اللقاء", "encounters": "اللقاءات", @@ -49,9 +56,13 @@ "endVisit_title": "إنهاء الزيارة", "endVisitExplainerMessage": "إنهاء هذه الزيارة يعني أنك لن تتمكن بعد الآن من إضافة لقاءات إليها. إذا كنت بحاجة إلى إضافة لقاء، يمكنك إنشاء زيارة جديدة لهذا المريض أو تعديل زيارة سابقة.", "error": "خطأ", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "عذرًا، حدثت مشكلة أثناء عرض هذه المعلومات. يمكنك محاولة إعادة تحميل هذه الصفحة، أو الاتصال بمسؤول الموقع واقتباس رمز الخطأ أعلاه.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "تعذر الحذف", "failedToLoadCurrentVisit": "فشل في تحميل الزيارة الحالية", "female": "أنثى", @@ -61,6 +72,9 @@ "goToThisEncounter": "اذهب إلى هذا اللقاء", "hide": "إخفاء", "indication": "دلالة", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "جار التحميل", "loadingVisit": "جار تحميل الزيارة الحالية...", "location": "الموقع", @@ -150,16 +164,26 @@ "time": "الوقت", "timeFormat ": "تنسيق الوقت", "type": "النوع", + "undo": "Undo", "unknown": "غير معروف", "unsavedChanges": "لديك تغييرات غير محفوظة", "unsavedChangesInForm": "هناك تغييرات غير محفوظة في {{formName}}. الرجاء حفظها قبل فتح نموذج آخر.", "updateError": "خطأ في تحديث الموعد القادم", + "updateVisit": "Update visit", + "visit": "زيارة", "visitAttributes": "سمات الزيارة", + "visitCanceled": "تم إلغاء الزيارة النشطة بنجاح", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "انتهت الزيارة", "visitEndSuccessfully": "انتهت الزيارة بنجاح", "visitLocation": "موقع الزيارة", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "زيارات", "Visits": "الزيارات", "Visits dashboard": "لوحة تحكم الزيارات", diff --git a/packages/esm-patient-chart-app/translations/en.json b/packages/esm-patient-chart-app/translations/en.json index 38325e7100..961f6ca4d9 100644 --- a/packages/esm-patient-chart-app/translations/en.json +++ b/packages/esm-patient-chart-app/translations/en.json @@ -11,6 +11,7 @@ "Attachments dashboard": "Attachments dashboard", "cancel": "Cancel", "cancelActiveVisitConfirmation": "Are you sure you want to cancel this active visit?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "Cancel visit", "cancelVisitExplainerMessage": "Cancelling this visit will delete its associated encounters", "causeOfDeath": "Cause of death", @@ -20,6 +21,7 @@ "Conditions dashboard": "Conditions dashboard", "confirm": "Confirm", "confirmDeceased": "Confirm Deceased", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "Are you sure, you want to mark patient as alive?", "currentVisit": "Current Visit", "date": "Date", @@ -30,12 +32,17 @@ "deleteEncounter": "Delete Encounter", "deleteEncounterConfirmationText": "Are you sure you want to delete this encounter? This action can't be undone.", "deleteThisEncounter": "Delete this encounter", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "Diagnoses", "discard": "Discard", "dose": "Dose", "editPastVisit": "Edit Past Visit", "editThisEncounter": "Edit this encounter", "editThisVisit": "Edit this visit", + "editVisitDetails": "Edit visit details", "emptyStateText": "There are no {{displayText}} to display for this patient", "encounterDeleted": "Encounter deleted", "encounters": "Encounters", @@ -49,9 +56,13 @@ "endVisit_title": "End Visit", "endVisitExplainerMessage": "Ending this visit means that you will no longer be able to add encounters to it. If you need to add an encounter, you can create a new visit for this patient or edit a past one.", "error": "Error", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "couldn't be deleted", "failedToLoadCurrentVisit": "Failed loading current visit", "female": "Female", @@ -61,6 +72,9 @@ "goToThisEncounter": "Go to this encounter", "hide": "Hide", "indication": "Indication", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "Loading", "loadingVisit": "Loading current visit...", "location": "Location", @@ -135,23 +149,33 @@ "startDate": "Start date", "startNewVisit": "Start new visit", "startVisit": "Start a visit", - "startVisitError": "Error starting current visit", + "startVisitError": "Error starting visit", "successfullyDeleted": "successfully deleted", "Test Results dashboard": "Test Results dashboard", "tests": "Tests", "time": "Time", "timeFormat ": "Time Format", "type": "Type", + "undo": "Undo", "unknown": "Unknown", "unsavedChanges": "You have unsaved changes", "unsavedChangesInForm": "There are unsaved changes in {{formName}}. Please save them before opening another form.", "updateError": "Error updating upcoming appointment", + "updateVisit": "Update visit", + "visit": "Visit", "visitAttributes": "Visit Attributes", + "visitCanceled": "Canceled active visit successfully", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "Visit ended", "visitEndSuccessfully": "Ended current visit successfully", "visitLocation": "Visit Location", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "visits", "Visits": "Visits", "Visits dashboard": "Visits dashboard", diff --git a/packages/esm-patient-chart-app/translations/es.json b/packages/esm-patient-chart-app/translations/es.json index 56742c4275..127a3020f9 100644 --- a/packages/esm-patient-chart-app/translations/es.json +++ b/packages/esm-patient-chart-app/translations/es.json @@ -11,6 +11,7 @@ "Attachments dashboard": "Panel de Archivos adjuntos", "cancel": "Cancelar", "cancelActiveVisitConfirmation": "¿Estás seguro de que quieres cancelar esta visita activa?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "Cancelar visita", "cancelVisitExplainerMessage": "Cancelar esta visita eliminará los encuentros asociados a ella", "causeOfDeath": "Causa de muerte", @@ -20,6 +21,7 @@ "Conditions dashboard": "Panel de Condiciones", "confirm": "Confirmar", "confirmDeceased": "Confirmar fallecido", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "¿Estás seguro de que deseas marcar al paciente como vivo?", "currentVisit": "Visita actual", "date": "Fecha", @@ -30,12 +32,17 @@ "deleteEncounter": "Eliminar Encuentro", "deleteEncounterConfirmationText": "¿Estás seguro de que deseas eliminar este encuentro? Esta acción no se puede deshacer.", "deleteThisEncounter": "Eliminar este encuentro", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "Diagnósticos", "discard": "Descartar", "dose": "Dosis", "editPastVisit": "Editar Visita Pasada", "editThisEncounter": "Editar este encuentro", "editThisVisit": "Editar esta visita", + "editVisitDetails": "Edit visit details", "emptyStateText": "No hay {{displayText}} para mostrar para este paciente", "encounterDeleted": "Encuentro eliminado", "encounters": "Encuentros", @@ -49,9 +56,13 @@ "endVisit_title": "Finalizar Visita", "endVisitExplainerMessage": "Finalizar esta visita significa que ya no podrás añadir encuentros a la misma. Si necesitas añadir un encuentro, puedes crear una nueva visita para este paciente o editar una pasada.", "error": "Error", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "Lo siento, hubo un problema al mostrar esta información. Puedes intentar recargar la página o ponerte en contacto con el administrador del sitio y citar el código de error de arriba.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "no se pudo eliminar", "failedToLoadCurrentVisit": "Error al cargar la visita actual", "female": "Femenino", @@ -61,6 +72,9 @@ "goToThisEncounter": "Ir a este encuentro", "hide": "Ocultar", "indication": "Indicación", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "Cargando", "loadingVisit": "Cargando visita actual...", "location": "Ubicación", @@ -144,16 +158,26 @@ "time": "Hora", "timeFormat ": "Formato de Hora", "type": "Tipo", + "undo": "Undo", "unknown": "Desconocido", "unsavedChanges": "Tienes cambios sin guardar", "unsavedChangesInForm": "Hay cambios sin guardar en {{formName}}. Por favor, guárdalos antes de abrir otro formulario.", "updateError": "Error al actualizar la cita próxima", + "updateVisit": "Update visit", + "visit": "Visita", "visitAttributes": "Atributos de la Visita", + "visitCanceled": "Visita activa cancelada con éxito", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "Visita finalizada", "visitEndSuccessfully": "Visita actual finalizada con éxito", "visitLocation": "Ubicación de la visita", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "visitas", "Visits": "Visitas", "Visits dashboard": "Panel de Visitas", diff --git a/packages/esm-patient-chart-app/translations/fr.json b/packages/esm-patient-chart-app/translations/fr.json index c8a3e28b12..ef2dbf7380 100644 --- a/packages/esm-patient-chart-app/translations/fr.json +++ b/packages/esm-patient-chart-app/translations/fr.json @@ -11,6 +11,7 @@ "Attachments dashboard": "Tableau de bord des pièces jointes", "cancel": "Annuller", "cancelActiveVisitConfirmation": "Êtes-vous sûr de vouloir annuler cette visite active?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "Annuler la visite", "cancelVisitExplainerMessage": "L'annulation de cette visite supprimera les rencontres associées", "causeOfDeath": "Cause de décès", @@ -20,6 +21,7 @@ "Conditions dashboard": "Tableau de bord des conditions", "confirm": "Confirmer", "confirmDeceased": "Confirmer le décès", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "Êtes-vous sûr de vouloir marquer le patient comme vivant?", "currentVisit": "Current Visit", "date": "Date", @@ -30,12 +32,17 @@ "deleteEncounter": "Delete Encounter", "deleteEncounterConfirmationText": "Are you sure you want to delete this encounter? This action can't be undone.", "deleteThisEncounter": "Supprimer cette rencontre", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "Diagnostics", "discard": "Abandonner", "dose": "Dose", "editPastVisit": "Editer une visite passée", "editThisEncounter": "Editer cette rencontre", "editThisVisit": "Edit this visit", + "editVisitDetails": "Edit visit details", "emptyStateText": "Il n'y a pas de {{displayText}} à afficher pour ce patient.", "encounterDeleted": "Encounter deleted", "encounters": "Rencontres", @@ -49,9 +56,13 @@ "endVisit_title": "End Visit", "endVisitExplainerMessage": "Mettre fin à cette visite signifie que vous ne pourrez plus y ajouter de rencontres. Si vous devez ajouter une rencontre, vous pouvez créer une nouvelle visite pour ce patient ou modifier une visite passée.", "error": "Error", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "couldn't be deleted", "failedToLoadCurrentVisit": "Failed loading current visit", "female": "Femme", @@ -61,6 +72,9 @@ "goToThisEncounter": "Aller à cette rencontre", "hide": "Cacher", "indication": "Indication", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "En cours de chargement", "loadingVisit": "Loading current visit...", "location": "Emplacement", @@ -144,16 +158,26 @@ "time": "Heure", "timeFormat ": "Time Format", "type": "Type", + "undo": "Undo", "unknown": "Inconnu", "unsavedChanges": "Vous avez des changements non enregistrés", "unsavedChangesInForm": "Il existe des modifications non enregistrées dans {{formName}}. Veuillez les enregistrer avant d'ouvrir un autre formulaire.", "updateError": "Error updating upcoming appointment", + "updateVisit": "Update visit", + "visit": "Visite", "visitAttributes": "Visit attributes", + "visitCanceled": "Visite active annulée avec succès", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "Visite close", "visitEndSuccessfully": "Visite actuelle terminée avec succès", "visitLocation": "Emplacement de la visite", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "visites", "Visits": "Visits", "Visits dashboard": "Tableau de bord des visites", diff --git a/packages/esm-patient-chart-app/translations/he.json b/packages/esm-patient-chart-app/translations/he.json index 29c4445b2b..e35ad077da 100644 --- a/packages/esm-patient-chart-app/translations/he.json +++ b/packages/esm-patient-chart-app/translations/he.json @@ -11,6 +11,7 @@ "Attachments dashboard": "Attachments dashboard", "cancel": "ביטול", "cancelActiveVisitConfirmation": "האם אתה בטוח שאתה רוצה לבטל את הביקור הפעיל הזה?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "ביטול ביקור", "cancelVisitExplainerMessage": "ביטול הביקור הזה ימחק את המפגשים המשויכים אליו", "causeOfDeath": "סיבת המוות", @@ -20,6 +21,7 @@ "Conditions dashboard": "Conditions dashboard", "confirm": "אישור", "confirmDeceased": "אישור מצב פטירה", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "האם אתה בטוח שברצונך לסמן את המטופל כחי?", "currentVisit": "Current Visit", "date": "תאריך", @@ -30,12 +32,17 @@ "deleteEncounter": "Delete Encounter", "deleteEncounterConfirmationText": "Are you sure you want to delete this encounter? This action can't be undone.", "deleteThisEncounter": "מחק ביקור זה", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "אבחנות", "discard": "בטל", "dose": "מנה", "editPastVisit": "ערוך ביקור עבר", "editThisEncounter": "ערוך ביקור זה", "editThisVisit": "Edit this visit", + "editVisitDetails": "Edit visit details", "emptyStateText": "אין {{displayText}} להצגה עבור מטופל זה", "encounterDeleted": "Encounter deleted", "encounters": "ביקורים", @@ -49,9 +56,13 @@ "endVisit_title": "End Visit", "endVisitExplainerMessage": "סיום הביקור הזה אומר שלא תוכל יותר להוסיף מפגשים אליו. אם אתה צריך להוסיף מפגש, אתה יכול ליצור ביקור חדש למטופל זה או לערוך ביקור קודם.", "error": "Error", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "couldn't be deleted", "failedToLoadCurrentVisit": "Failed loading current visit", "female": "נקבה", @@ -61,6 +72,9 @@ "goToThisEncounter": "עבור לביקור זה", "hide": "הסתר", "indication": "רמז", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "טוען", "loadingVisit": "Loading current visit...", "location": "מיקום", @@ -146,16 +160,26 @@ "time": "זמן", "timeFormat ": "Time Format", "type": "סוג", + "undo": "Undo", "unknown": "לא ידוע", "unsavedChanges": "יש לך שינויים שלא נשמרו", "unsavedChangesInForm": "ישנם שינויים שלא נשמרו ב {{formName}}. נא לשמור אותם לפני פתיחת טופס נוסף.", "updateError": "Error updating upcoming appointment", + "updateVisit": "Update visit", + "visit": "ביקור", "visitAttributes": "Visit attributes", + "visitCanceled": "ביקור פעיל בוטל בהצלחה", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "הביקור הסתיים", "visitEndSuccessfully": "הביקור הנוכחי הסתיים בהצלחה", "visitLocation": "מיקום הביקור", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "ביקורים", "Visits": "ביקורים", "Visits dashboard": "Visits dashboard", diff --git a/packages/esm-patient-chart-app/translations/km.json b/packages/esm-patient-chart-app/translations/km.json index 9986fc191f..adb5dca22d 100644 --- a/packages/esm-patient-chart-app/translations/km.json +++ b/packages/esm-patient-chart-app/translations/km.json @@ -11,6 +11,7 @@ "Attachments dashboard": "ផ្ទាំងគ្រប់គ្រងឯកសារភ្ជាប់", "cancel": "បោះបង់", "cancelActiveVisitConfirmation": "Are you sure you want to cancel this active visit?", + "cancellingVisit": "Cancelling visit", "cancelVisit": "បោះបង់ការជួបពិនិត្យជំងឺ", "cancelVisitExplainerMessage": "Cancelling this visit will delete its associated encounters", "causeOfDeath": "មូលហេតុនៃការស្លាប់", @@ -20,6 +21,7 @@ "Conditions dashboard": "ផ្ទាំងគ្រប់គ្រងលក្ខខណ្ឌ", "confirm": "បញ្ជាក់", "confirmDeceased": "បញ្ជាក់ថាបានស្លាប់", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", "confirmMarkAsAlive": "តើអ្នកប្រាកដទេថា អ្នកចង់ដាក់ថាអ្នកជំងឺថានៅរស់វិញ?", "currentVisit": "ដំណើរទស្សនកិច្ចបច្ចុប្បន្ន", "date": "កាលបរិច្ឆេទ", @@ -30,12 +32,17 @@ "deleteEncounter": "លុបចោលការពិនិត្យ", "deleteEncounterConfirmationText": "តើអ្នកប្រាកដថាចង់លុបការពិនិត្យនេះទេ? ពេលលុបហើយ មិនអាចយកទិន្នន័យត្រឡប់មកវិញបានទេ", "deleteThisEncounter": "លុបការជួបពិនិត្យជំងឺនេះ", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", "diagnoses": "រោគវិនិច្ឆ័យ", "discard": "បោះបង់", "dose": "កំរិតប្រើប្រាស់", "editPastVisit": "កែសម្រួលការមកពិនិត្យជំងឺលើកមុន", "editThisEncounter": "កែសម្រួលការជួបប្រទះលើកនេះ", "editThisVisit": "កែសម្រួលការមកពិនិត្យនេះ។", + "editVisitDetails": "Edit visit details", "emptyStateText": "មិនមាន {{displayText}} បង្ហាញអំពីអ្នកជំងឺនេះទេ", "encounterDeleted": "លុបចោលការមកពិនិត្យ", "encounters": "ការជួបប្រទះ", @@ -49,9 +56,13 @@ "endVisit_title": "បញ្ចប់ការមកពីនិត្យ", "endVisitExplainerMessage": "Ending this visit means that you will no longer be able to add encounters to it. If you need to add an encounter, you can create a new visit for this patient or edit a past one.", "error": "មានកំហុស", - "errorCancellingVisit": "Error cancelling visit", + "errorCancellingVisit": "Error cancelling active visit", "errorCopy": "សូមអធ្យាស្រ័យ មានបញ្ហាក្នុងការបង្ហាញព័ត៌មាននេះ។ អ្នកអាចព្យាយាមទាញទំព័រនេះម្តងទៀត ឬទាក់ទងអ្នកគ្រប់គ្រងប្រព័ន្ធទិន្នន័យ ហើយស្រង់លេខកូដដែលមិនដំណើរការមុខងារខាងលើ។", + "errorDeletingVisit": "Error deleting visit", "errorEndingVisit": "Error ending visit", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", "failedDeleting": "មិនអាចលុបបានទេ", "failedToLoadCurrentVisit": "ការទាញទិន្នន័យពិនិត្យជំងឺបច្ចុប្បន្នបរាជ័យ", "female": "ស្រី", @@ -61,6 +72,9 @@ "goToThisEncounter": "ទៅរកការជួបពិនិត្យជំងឺលើកនេះ", "hide": "លាក់មិនឱ្យមើលឃើញ", "indication": "ការចង្អុលបង្ហាញ", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", "loading": "កំពុងផ្ទុក", "loadingVisit": "ការទាញទិន្នន័យពិនិត្យជំងឺបច្ចុប្បន្ន...", "location": "ទីតាំង", @@ -140,16 +154,26 @@ "time": "ពេលវេលា", "timeFormat ": "ទម្រង់ពេលវេលា", "type": "ប្រភេទ", + "undo": "Undo", "unknown": "មិនដឹង", "unsavedChanges": "អ្នកមានការផ្លាស់ប្ដូរដែលមិនបានរក្សាទុក", "unsavedChangesInForm": "មានការផ្លាស់ប្តូរដែលមិនបានរក្សាទុកនៅក្នុង {{formName}} ។ សូមរក្សាទុកពួកវាមុនពេលបើកទម្រង់ផ្សេងទៀត។", "updateError": "ការធ្វើបច្ចុប្បន្នភាពការណាត់ជួបនាពេលខាងមុខមិនដំណើរការ", + "updateVisit": "Update visit", + "visit": "ការមកពិនិត្យជំងឺ", "visitAttributes": "គុណលក្ខណៈពិនិត្យជំងឺ", + "visitCanceled": "បានបោះបង់ការពិនិត្យជំងឺសកម្មដោយជោគជ័យ", "visitCancelled": "Visit cancelled", - "visitCancelSuccessMessage": "Active visit cancelled successfully", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", "visitEnded": "ការពិនិត្យបានបញ្ចប់", "visitEndSuccessfully": "បានបញ្ចប់ការមកពិនិត្យជំងឺបច្ចុប្បន្នដោយជោគជ័យ", "visitLocation": "ទីតាំងការមកពិនិត្យជំងឺ", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", "visits": "មកពិនិត្យជំងឺ", "Visits": "មកពិនិត្យជំងឺ", "Visits dashboard": "ចូលទៅកាន់ផ្ទាំងគ្រប់គ្រង", diff --git a/packages/esm-patient-common-lib/src/time-helper.ts b/packages/esm-patient-common-lib/src/time-helper.ts index d39e7814c2..520b58ede7 100644 --- a/packages/esm-patient-common-lib/src/time-helper.ts +++ b/packages/esm-patient-common-lib/src/time-helper.ts @@ -1,14 +1,17 @@ export type amPm = 'AM' | 'PM'; -export const convertTime12to24 = (time12h, timeFormat: amPm) => { - let [hours, minutes] = time12h.split(':'); +export const time12HourFormatRegex = new RegExp(/^(1[0-2]|0?[1-9]):[0-5][0-9]$/); - if (hours === '12' && timeFormat === 'AM') { - hours = '00'; +export const convertTime12to24 = (time12h: string, timeFormat: amPm) => { + if (!time12h.match(time12HourFormatRegex)) { + return [0, 0]; } + let [hours, minutes] = time12h.split(':').map((item) => parseInt(item, 10)); + hours = hours % 12; + if (timeFormat === 'PM') { - hours = hours === '12' ? hours : parseInt(hours, 10) + 12; + hours += 12; } return [hours, minutes];