Skip to content

Commit

Permalink
Merge branch 'main' into edit-service
Browse files Browse the repository at this point in the history
  • Loading branch information
Munyua123 authored Dec 16, 2024
2 parents 4ae50e8 + 4ec8d8c commit 2bd9754
Show file tree
Hide file tree
Showing 19 changed files with 4,361 additions and 467 deletions.
4 changes: 3 additions & 1 deletion packages/esm-billing-app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ export const colorsArray = [
'warm-gray',
'high-contrast',
'outline',
];
];

export const PAYMENT_MODE_ATTRIBUTE_FORMATS = ['java.lang.String', 'java.lang.Integer', 'java.lang.Double'];
13 changes: 13 additions & 0 deletions packages/esm-billing-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { BulkImportBillableServices } from './billable-services/bulk-import-bill
import { CreatePaymentPoint } from './payment-points/create-payment-point.component';
import { ClockIn } from './payment-points/payment-point/clock-in.component';
import { ClockOut } from './payment-points/payment-point/clock-out.component';
import DeletePaymentModeModal from './payment-modes/delete-payment-mode.modal';
import PaymentModeWorkspace from "./payment-modes/payment-mode.workspace";

const moduleName = '@ehospital/esm-billing-app';

Expand Down Expand Up @@ -69,6 +71,17 @@ export const paymentPointsPanelLink = getSyncLifecycle(
options,
);

export const paymentModesPanelLink = getSyncLifecycle(
createLeftPanelLink({
name: 'payment-mode',
title: 'Payment Modes',
}),
options,
);

export const paymentModeWorkspace = getSyncLifecycle(PaymentModeWorkspace, options);
export const deletePaymentModeModal = getSyncLifecycle(DeletePaymentModeModal, options);

export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');

export function startupApp() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@use '@carbon/type';
@use '@carbon/layout';

.modalHeaderLabel {
@include type.type-style('heading-compact-02');

& > div {
display: none;
}
}

.modalHeaderHeading {
@include type.type-style('heading-compact-01');
}

.button_spinner {
padding: 0;
margin-right: layout.$spacing-03;
}

.loading_wrapper {
display: flex;
align-items: center;
}

.contentContainer {
padding-left: layout.$spacing-05;
}

.contentValue {
@include type.type-style('heading-compact-02');
margin-bottom: layout.$spacing-03;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import { ModalBody, ModalFooter, Button, ModalHeader } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { PaymentMode } from '../types';
import { deletePaymentMode, handleMutation } from './payment-mode.resource';
import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
import styles from './delete-payment-mode.modal.scss';

type DeletePaymentModeModalProps = {
closeModal: () => void;
paymentMode: PaymentMode;
};

const DeletePaymentModeModal: React.FC<DeletePaymentModeModalProps> = ({ closeModal, paymentMode }) => {
const { t } = useTranslation();

const handleDelete = async () => {
try {
await deletePaymentMode(paymentMode.uuid);
showSnackbar({
title: t('paymentModeDeleted', 'Payment mode deleted'),
subtitle: t('paymentModeDeletedSubtitle', 'The payment mode has been deleted'),
kind: 'success',
isLowContrast: true,
timeoutInMs: 5000,
autoClose: true,
});
handleMutation(`${restBaseUrl}/billing/paymentMode?v=full`);
closeModal();
} catch (error) {
showSnackbar({
title: t('paymentModeDeletedError', 'Error deleting payment mode'),
subtitle: t('paymentModeDeletedErrorSubtitle', 'An error occurred while deleting the payment mode'),
kind: 'error',
isLowContrast: true,
timeoutInMs: 5000,
autoClose: true,
});
}
};

return (
<>
<ModalHeader onClose={closeModal} className={styles.modalHeaderLabel} closeModal={closeModal}>
{t('deletePaymentMode', 'Delete Payment Mode')}
</ModalHeader>
<ModalBody className={styles.modalHeaderHeading}>
{t('deletePaymentModeDescription', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={closeModal}>
{t('cancel', 'Cancel')}
</Button>
<Button kind="danger" onClick={handleDelete}>
{t('delete', 'Delete')}
</Button>
</ModalFooter>
</>
);
};

export default DeletePaymentModeModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import DeletePaymentModeModal from './delete-payment-mode.modal';
import userEvent from '@testing-library/user-event';
import { PaymentMode } from '../types';
import { deletePaymentMode } from './payment-mode.resource';

const mockDeletePaymentMode = jest.mocked(deletePaymentMode);

jest.mock('./payment-mode.resource', () => ({
deletePaymentMode: jest.fn(),
}));

describe('DeletePaymentModeModal', () => {
test('should be able to delete payment mode when user clicks on delete button', async () => {
const user = userEvent.setup();
const mockCloseModal = jest.fn();
const mockPaymentMode = {
uuid: '123',
name: 'Test Payment Mode',
description: 'Test Description',
retired: false,
auditInfo: {
creator: {
uuid: '456',
display: 'Test Creator',
},
dateCreated: '2021-01-01',
},
} as PaymentMode;

render(<DeletePaymentModeModal closeModal={mockCloseModal} paymentMode={mockPaymentMode} />);

const deleteButton = await screen.findByRole('button', { name: /Delete/i });
await user.click(deleteButton);

expect(mockDeletePaymentMode).toHaveBeenCalledWith(mockPaymentMode.uuid);
});

test('should be able to cancel deletion of payment mode when user clicks on cancel button', async () => {
const user = userEvent.setup();
const mockCloseModal = jest.fn();
const mockPaymentMode = {
uuid: '123',
name: 'Test Payment Mode',
description: 'Test Description',
} as PaymentMode;

render(<DeletePaymentModeModal closeModal={mockCloseModal} paymentMode={mockPaymentMode} />);

const cancelButton = await screen.findByRole('button', { name: /Cancel/i });
await user.click(cancelButton);

expect(mockCloseModal).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Controller, Control, useFormContext } from 'react-hook-form';
import { ResponsiveWrapper } from '@openmrs/esm-framework';
import { TextInput, Button, Dropdown, Toggle } from '@carbon/react';
import styles from './payment-mode-attributes.scss';
import { PAYMENT_MODE_ATTRIBUTE_FORMATS } from '../../constants';

type PaymentModeAttributeFields = {
field: Record<string, any>;
index: number;
control: Control<Record<string, any>>;
removeAttributeType: (index: number) => void;
errors: Record<string, any>;
};

const PaymentModeAttributeFields: React.FC<PaymentModeAttributeFields> = ({
field,
index,
control,
removeAttributeType,
errors,
}) => {
const { t } = useTranslation();
const { watch } = useFormContext();
const retired = watch(`attributeTypes.${index}.retired`);

return (
<div className={styles.paymentModeAttributes}>
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.name`}
render={({ field }) => (
<TextInput
{...field}
labelText={t('attributeName', 'Attribute name')}
placeholder={t('enterAttributeName', 'Enter attribute name')}
invalid={!!errors?.attributeTypes?.[index]?.name}
invalidText={errors?.attributeTypes?.[index]?.name?.message}
id={`attributeTypes.${index}.name`}
/>
)}
/>
</ResponsiveWrapper>
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.description`}
render={({ field }) => (
<TextInput
{...field}
labelText={t('attributeDescription', 'Attribute description')}
placeholder={t('enterAttributeDescription', 'Enter attribute description')}
invalid={!!errors?.attributeTypes?.[index]?.description}
invalidText={errors?.attributeTypes?.[index]?.description?.message}
id={`attributeTypes.${index}.description`}
/>
)}
/>
</ResponsiveWrapper>
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.retired`}
render={({ field }) => (
<Toggle
{...field}
labelText={t('attributeRetired', 'Attribute retired')}
labelA="Off"
labelB="On"
toggled={field.value}
id={`attributeTypes.${index}.retired`}
onToggle={(value) => (value ? field.onChange(true) : field.onChange(false))}
/>
)}
/>
</ResponsiveWrapper>
{retired && (
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.retiredReason`}
render={({ field }) => (
<TextInput
{...field}
labelText={t('attributeRetiredReason', 'Attribute retired reason')}
placeholder={t('enterAttributeRetiredReason', 'Enter attribute retired reason')}
invalid={!!errors?.attributeTypes?.[index]?.retiredReason}
invalidText={errors?.attributeTypes?.[index]?.retiredReason?.message}
/>
)}
/>
</ResponsiveWrapper>
)}
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.format`}
render={({ field }) => (
<Dropdown
{...field}
id={`attributeTypes.${index}.format`}
titleText={t('attributeFormat', 'Attribute format')}
placeholder={t('selectAttributeFormat', 'Select attribute format')}
onChange={(event) => field.onChange(event.selectedItem)}
initialSelectedItem={field.value}
helperText={t('attributeFormatHelperText', 'The format of the attribute value e.g Free text, Number')}
items={PAYMENT_MODE_ATTRIBUTE_FORMATS}
invalid={!!errors?.attributeTypes?.[index]?.format}
invalidText={errors?.attributeTypes?.[index]?.format?.message}
/>
)}
/>
</ResponsiveWrapper>
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.regExp`}
render={({ field }) => (
<TextInput
{...field}
labelText={t('regExp', 'Regular expression')}
placeholder={t('enterRegExp', 'Enter regular expression')}
/>
)}
/>
</ResponsiveWrapper>
<ResponsiveWrapper>
<Controller
control={control}
name={`attributeTypes.${index}.required`}
render={({ field }) => (
<Toggle
{...field}
labelText={t('attributeRequired', 'Attribute required')}
labelA="Off"
labelB="On"
toggled={field.value}
id={`attributeTypes.${index}.required`}
onToggle={(value) => (value ? field.onChange(true) : field.onChange(false))}
/>
)}
/>
</ResponsiveWrapper>
<Button size="sm" kind="danger--tertiary" onClick={() => removeAttributeType(index)}>
{t('remove', 'Remove')}
</Button>
</div>
);
};

export default PaymentModeAttributeFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@use '@carbon/layout';
@use '@carbon/colors';

.paymentModeAttributes {
display: flex;
flex-direction: column;
gap: layout.$spacing-05;
outline: 1px solid colors.$gray-10;
padding: layout.$spacing-05;
}
Loading

0 comments on commit 2bd9754

Please sign in to comment.