From a728463595a1ccb4d8ab3df4ae2ba21464e515ee Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:49:30 +0300 Subject: [PATCH 1/5] O3-4250 (feat) Ability to add cash points via a UI --- .../billable-services-home.component.tsx | 16 +- .../cash-point-configuration.component.tsx | 246 ++++++++++++++++++ .../cash-point/cash-point-configuration.scss | 23 ++ 3 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 src/billable-services/cash-point/cash-point-configuration.component.tsx create mode 100644 src/billable-services/cash-point/cash-point-configuration.scss diff --git a/src/billable-services/billable-services-home.component.tsx b/src/billable-services/billable-services-home.component.tsx index 8f634ae..f14d629 100644 --- a/src/billable-services/billable-services-home.component.tsx +++ b/src/billable-services/billable-services-home.component.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import { SideNav, SideNavItems, SideNavLink } from '@carbon/react'; -import { Wallet, Money } from '@carbon/react/icons'; +import { SideNav, SideNavItems, SideNavLink, SideNavMenu, SideNavMenuItem } from '@carbon/react'; +import { Wallet, Money, Settings } from '@carbon/react/icons'; import { useTranslation } from 'react-i18next'; import { UserHasAccess, navigate } from '@openmrs/esm-framework'; import AddBillableService from './create-edit/add-billable-service.component'; import BillWaiver from './bill-waiver/bill-waiver.component'; import BillableServicesDashboard from './dashboard/dashboard.component'; import BillingHeader from '../billing-header/billing-header.component'; +import CashPointConfiguration from './cash-point/cash-point-configuration.component'; +// import PaymentModesConfig from './settings/payment-modes-config.component'; import styles from './billable-services.scss'; const BillableServiceHome: React.FC = () => { @@ -35,6 +37,14 @@ const BillableServiceHome: React.FC = () => { handleNavigation('waive-bill')} renderIcon={Money}> {t('billWaiver', 'Bill waiver')} + + handleNavigation('cash-point-config')}> + {t('cashPointConfig', 'Cash Point Config')} + + handleNavigation('payment-modes-config')}> + {t('paymentModesConfig', 'Payment Modes Config')} + + @@ -45,6 +55,8 @@ const BillableServiceHome: React.FC = () => { } /> } /> } /> + } /> + {/* } /> */} diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx new file mode 100644 index 0000000..cbce6c8 --- /dev/null +++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx @@ -0,0 +1,246 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + Button, + DataTable, + TableContainer, + Table, + TableHead, + TableRow, + TableHeader, + TableBody, + TableCell, + Modal, + TextInput, + OverflowMenu, + OverflowMenuItem, + Dropdown, +} from '@carbon/react'; +import { Add } from '@carbon/react/icons'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { useForm, Controller } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { showToast, showSnackbar } from '@openmrs/esm-framework'; +import { CardHeader } from '@openmrs/esm-patient-common-lib'; +import styles from './cash-point-configuration.scss'; + +// Validation schema +const cashPointSchema = z.object({ + name: z.string().min(1, 'Cash Point Name is required'), + uuid: z + .string() + .min(1, 'UUID is required') + .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, 'Invalid UUID format'), + location: z.string().min(1, 'Location is required'), +}); + +type CashPointFormValues = z.infer; + +const CashPointConfiguration: React.FC = () => { + const { t } = useTranslation(); + const [cashPoints, setCashPoints] = useState([]); + const [locations, setLocations] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`; + + const { + control, + handleSubmit, + reset, + setValue, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(cashPointSchema), + defaultValues: { + name: '', + uuid: '', + location: '', + }, + }); + + const fetchCashPoints = useCallback(async () => { + try { + const response = await axios.get(`${baseUrl}/billing/cashPoint?v=full`); + setCashPoints(response.data.results || []); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorFetchingCashPoints', 'An error occurred while fetching cash points.'), + kind: 'error', + isLowContrast: false, + }); + } + }, [baseUrl, t]); + + const fetchLocations = useCallback(async () => { + try { + const response = await axios.get(`${baseUrl}/location?v=default`); + const allLocations = response.data.results.map((loc) => ({ + id: loc.uuid, + label: loc.display, + })); + setLocations(allLocations); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorFetchingLocations', 'An error occurred while fetching locations.'), + kind: 'error', + isLowContrast: false, + }); + } + }, [baseUrl, t]); + + useEffect(() => { + fetchCashPoints(); + fetchLocations(); + }, [fetchCashPoints, fetchLocations]); + + const onSubmit = async (data: CashPointFormValues) => { + try { + await axios.post(`${baseUrl}/billing/cashPoint`, { + name: data.name, + uuid: data.uuid, + location: { uuid: data.location }, + }); + + showToast({ + description: t('cashPointSaved', 'Cash point was successfully saved.'), + title: t('success', 'Success'), + kind: 'success', + }); + + setIsModalOpen(false); + reset({ name: '', uuid: '', location: '' }); + fetchCashPoints(); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorSavingCashPoint', 'An error occurred while saving the cash point.'), + kind: 'error', + isLowContrast: false, + }); + } + }; + + const rowData = cashPoints.map((point) => ({ + id: point.uuid, + name: point.name, + uuid: point.uuid, + location: point.location ? point.location.display : 'None', + })); + + const headerData = [ + { key: 'name', header: t('name', 'Name') }, + { key: 'uuid', header: t('uuid', 'UUID') }, + { key: 'location', header: t('location', 'Location') }, + { key: 'actions', header: t('actions', 'Actions') }, + ]; + + return ( +
+
+ + + +
+ + {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => ( + + + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row) => ( + + {row.cells.map((cell) => + cell.info.header !== 'actions' ? ( + {cell.value} + ) : ( + + + + + + ), + )} + + ))} + +
+
+ )} +
+
+
+ + {/* Modal for Adding New Cash Point */} + setIsModalOpen(false)} + onRequestSubmit={handleSubmit(onSubmit)} + primaryButtonText={t('save', 'Save')} + secondaryButtonText={t('cancel', 'Cancel')} + isPrimaryButtonDisabled={isSubmitting}> +
+ ( + + )} + /> + ( + + )} + /> + ( + loc.id === field.value)} + onChange={({ selectedItem }) => field.onChange(selectedItem?.id)} + invalid={!!errors.location} + invalidText={errors.location?.message} + /> + )} + /> + +
+
+ ); +}; + +export default CashPointConfiguration; diff --git a/src/billable-services/cash-point/cash-point-configuration.scss b/src/billable-services/cash-point/cash-point-configuration.scss new file mode 100644 index 0000000..06a2adc --- /dev/null +++ b/src/billable-services/cash-point/cash-point-configuration.scss @@ -0,0 +1,23 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.container { + padding: layout.$spacing-05; +} + +.card { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: layout.$spacing-05; +} + +.billHistoryContainer { + margin-top: layout.$spacing-05; +} + +.table { + width: 100%; + table-layout: auto; +} \ No newline at end of file From b72cf9a1d59192952848259ef3694cf86d541b85 Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:21:27 +0300 Subject: [PATCH 2/5] Add payment mode setting --- .../billable-services-home.component.tsx | 4 +- .../cash-point-configuration.component.tsx | 7 +- .../payment-modes-config.component.tsx | 247 ++++++++++++++++++ .../payyment-modes/payment-modes-config.scss | 23 ++ translations/en.json | 18 ++ 5 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 src/billable-services/payyment-modes/payment-modes-config.component.tsx create mode 100644 src/billable-services/payyment-modes/payment-modes-config.scss diff --git a/src/billable-services/billable-services-home.component.tsx b/src/billable-services/billable-services-home.component.tsx index f14d629..e140e7e 100644 --- a/src/billable-services/billable-services-home.component.tsx +++ b/src/billable-services/billable-services-home.component.tsx @@ -9,7 +9,7 @@ import BillWaiver from './bill-waiver/bill-waiver.component'; import BillableServicesDashboard from './dashboard/dashboard.component'; import BillingHeader from '../billing-header/billing-header.component'; import CashPointConfiguration from './cash-point/cash-point-configuration.component'; -// import PaymentModesConfig from './settings/payment-modes-config.component'; +import PaymentModesConfig from './payyment-modes/payment-modes-config.component'; import styles from './billable-services.scss'; const BillableServiceHome: React.FC = () => { @@ -56,7 +56,7 @@ const BillableServiceHome: React.FC = () => { } /> } /> } /> - {/* } /> */} + } /> diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx index cbce6c8..7ebc621 100644 --- a/src/billable-services/cash-point/cash-point-configuration.component.tsx +++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx @@ -21,7 +21,7 @@ import axios from 'axios'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { showToast, showSnackbar } from '@openmrs/esm-framework'; +import { showSnackbar } from '@openmrs/esm-framework'; import { CardHeader } from '@openmrs/esm-patient-common-lib'; import styles from './cash-point-configuration.scss'; @@ -48,7 +48,6 @@ const CashPointConfiguration: React.FC = () => { control, handleSubmit, reset, - setValue, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(cashPointSchema), @@ -104,9 +103,9 @@ const CashPointConfiguration: React.FC = () => { location: { uuid: data.location }, }); - showToast({ - description: t('cashPointSaved', 'Cash point was successfully saved.'), + showSnackbar({ title: t('success', 'Success'), + subtitle: t('cashPointSaved', 'Cash point was successfully saved.'), kind: 'success', }); diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx new file mode 100644 index 0000000..2aa3330 --- /dev/null +++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx @@ -0,0 +1,247 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + Button, + DataTable, + TableContainer, + Table, + TableHead, + TableRow, + TableHeader, + TableBody, + TableCell, + Modal, + TextInput, + OverflowMenu, + OverflowMenuItem, +} from '@carbon/react'; +import { Add } from '@carbon/react/icons'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { useForm, Controller } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { showSnackbar } from '@openmrs/esm-framework'; +import { CardHeader } from '@openmrs/esm-patient-common-lib'; +import styles from './payment-modes-config.scss'; + +// Validation schema +const paymentModeSchema = z.object({ + name: z.string().min(1, 'Payment Mode Name is required'), + description: z.string().optional(), +}); + +type PaymentModeFormValues = z.infer; + +const PaymentModesConfig: React.FC = () => { + const { t } = useTranslation(); + const [paymentModes, setPaymentModes] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [selectedPaymentMode, setSelectedPaymentMode] = useState(null); + const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`; + + const { + control, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(paymentModeSchema), + defaultValues: { + name: '', + description: '', + }, + }); + + const fetchPaymentModes = useCallback(async () => { + try { + const response = await axios.get(`${baseUrl}/billing/paymentMode?v=full`); + setPaymentModes(response.data.results || []); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorFetchingPaymentModes', 'An error occurred while fetching payment modes.'), + kind: 'error', + isLowContrast: false, + }); + } + }, [baseUrl, t]); + + useEffect(() => { + fetchPaymentModes(); + }, [fetchPaymentModes]); + + const onSubmit = async (data: PaymentModeFormValues) => { + try { + await axios.post(`${baseUrl}/billing/paymentMode`, { + name: data.name, + description: data.description, + }); + + showSnackbar({ + title: t('success', 'Success'), + subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'), + kind: 'success', + }); + + setIsModalOpen(false); + reset({ name: '', description: '' }); + fetchPaymentModes(); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'), + kind: 'error', + isLowContrast: false, + }); + } + }; + + const handleDelete = async () => { + if (!selectedPaymentMode) return; + + try { + await axios.delete(`${baseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`); + + showSnackbar({ + title: t('success', 'Success'), + subtitle: t('paymentModeDeleted', 'Payment mode was successfully deleted.'), + kind: 'success', + }); + + setIsDeleteModalOpen(false); + setSelectedPaymentMode(null); + fetchPaymentModes(); + } catch (err) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t('errorDeletingPaymentMode', 'An error occurred while deleting the payment mode.'), + kind: 'error', + isLowContrast: false, + }); + } + }; + + const rowData = paymentModes.map((mode) => ({ + id: mode.uuid, + name: mode.name, + description: mode.description || '--', + })); + + const headerData = [ + { key: 'name', header: t('name', 'Name') }, + { key: 'description', header: t('description', 'Description') }, + { key: 'actions', header: t('actions', 'Actions') }, + ]; + + return ( +
+
+ + + +
+ + {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => ( + + + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row) => ( + + {row.cells.map((cell) => + cell.info.header !== 'actions' ? ( + {cell.value} + ) : ( + + + { + const selected = paymentModes.find((p) => p.uuid === row.id); + setSelectedPaymentMode(selected); + setIsDeleteModalOpen(true); + }} + /> + + + ), + )} + + ))} + +
+
+ )} +
+
+
+ + {/* Modal for Adding New Payment Mode */} + setIsModalOpen(false)} + onRequestSubmit={handleSubmit(onSubmit)} + primaryButtonText={t('save', 'Save')} + secondaryButtonText={t('cancel', 'Cancel')} + isPrimaryButtonDisabled={isSubmitting}> +
+ ( + + )} + /> + ( + + )} + /> + +
+ + {/* Modal for Deleting Payment Mode */} + setIsDeleteModalOpen(false)} + onRequestSubmit={handleDelete} + primaryButtonText={t('delete', 'Delete')} + secondaryButtonText={t('cancel', 'Cancel')} + primaryButtonDanger + danger> +

{t('confirmDeleteMessage', 'Are you sure you want to delete this payment mode? Proceed cautiously.')}

+
+
+ ); +}; + +export default PaymentModesConfig; diff --git a/src/billable-services/payyment-modes/payment-modes-config.scss b/src/billable-services/payyment-modes/payment-modes-config.scss new file mode 100644 index 0000000..6b7efc4 --- /dev/null +++ b/src/billable-services/payyment-modes/payment-modes-config.scss @@ -0,0 +1,23 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.container { + padding: layout.$spacing-05; +} + +.card { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: layout.$spacing-05; +} + +.historyContainer { + margin-top: layout.$spacing-05; +} + +.table { + width: 100%; + table-layout: auto; +} \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index cc60651..673bbe4 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2,6 +2,7 @@ "actions": "Actions", "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", + "addCashPoint": "Add Cash Point", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", "addPaymentOptions": "Add payment option", @@ -18,6 +19,7 @@ "billing": "Billing", "billingForm": "Billing form", "billingHistory": "Billing History", + "billingSettings": "Billing Settings", "billItem": "Bill item", "billItems": "Bill Items", "billLineItemEmpty": "This bill has no line items", @@ -34,11 +36,20 @@ "billWaiverError": "Bill waiver failed {{error}}", "billWaiverSuccess": "Bill waiver successful", "cancel": "Cancel", + "cashPointConfig": "Cash Point Config", + "cashPointHistory": "Cash Point History", + "cashPointLocation": "Cash Point Location", + "cashPointName": "Cash Point Name", + "cashPointNamePlaceholder": "e.g., Pharmacy Cash Point", + "cashPointSaved": "Cash point was successfully saved.", + "cashPointUuid": "Cash Point UUID", + "cashPointUuidPlaceholder": "Enter UUID", "checkFilters": "Check the filters above", "clearSearchInput": "Clear search input", "clientBalance": "Client Balance", "createdSuccessfully": "Billable service created successfully", "currentPrice": "Current price", + "delete": "Delete", "discard": "Discard", "discount": "Discount", "editBillableService": "Edit billable service", @@ -49,8 +60,11 @@ "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", "error": "Error", + "errorFetchingCashPoints": "An error occurred while fetching cash points.", + "errorFetchingLocations": "An error occurred while fetching locations.", "errorLoadingBillServices": "Error loading bill services", "errorLoadingPaymentModes": "Payment modes error", + "errorSavingCashPoint": "An error occurred while saving the cash point.", "filterBy": "Filter by", "filterTable": "Filter table", "grandTotal": "Grand total", @@ -68,6 +82,7 @@ "loading": "Loading data...", "loadingBillingServices": "Loading billing services...", "loadingDescription": "Loading", + "location": "Select Location", "manageBillableServices": "Manage billable services", "name": "Name", "nextPage": "Next page", @@ -87,6 +102,7 @@ "paymentMethod": "Payment method", "paymentMethods": "Payment methods", "paymentMode": "Payment Mode", + "paymentModesConfig": "Payment Modes Config", "payments": "Payments", "pleaseRequiredFields": "Please fill all required fields", "policyNumber": "Policy number", @@ -127,11 +143,13 @@ "status": "Service Status", "stockItem": "Stock Item", "submitting": "Submitting...", + "success": "Success", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", "unitPrice": "Unit price", "updatedSuccessfully": "Billable service updated successfully", + "uuid": "UUID", "visitTime": "Visit time", "waiverForm": "Waiver form" } From 7b21b8abc5ef5bf8593cf06750da5a52d349743b Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:56:28 +0300 Subject: [PATCH 3/5] Prevent creating dublicates --- .../cash-point-configuration.component.tsx | 17 +++++++++++++++++ .../payment-modes-config.component.tsx | 16 ++++++++++++++++ translations/en.json | 13 +++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx index 7ebc621..3bc73e9 100644 --- a/src/billable-services/cash-point/cash-point-configuration.component.tsx +++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx @@ -96,6 +96,23 @@ const CashPointConfiguration: React.FC = () => { }, [fetchCashPoints, fetchLocations]); const onSubmit = async (data: CashPointFormValues) => { + const isDuplicate = cashPoints.some( + (point) => point.name.toLowerCase() === data.name.toLowerCase() || point.uuid === data.uuid, + ); + + if (isDuplicate) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t( + 'duplicateCashPointError', + 'A cash point with the same name or UUID already exists. Please use a unique name and UUID.', + ), + kind: 'error', + isLowContrast: false, + }); + return; + } + try { await axios.post(`${baseUrl}/billing/cashPoint`, { name: data.name, diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx index 2aa3330..671d645 100644 --- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx +++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx @@ -72,6 +72,22 @@ const PaymentModesConfig: React.FC = () => { }, [fetchPaymentModes]); const onSubmit = async (data: PaymentModeFormValues) => { + // Check for duplicate payment mode name + const isDuplicate = paymentModes.some((mode) => mode.name.toLowerCase() === data.name.toLowerCase()); + + if (isDuplicate) { + showSnackbar({ + title: t('error', 'Error'), + subtitle: t( + 'duplicatePaymentModeError', + 'A payment mode with the same name already exists. Please create another payment mode', + ), + kind: 'error', + isLowContrast: false, + }); + return; + } + try { await axios.post(`${baseUrl}/billing/paymentMode`, { name: data.name, diff --git a/translations/en.json b/translations/en.json index 673bbe4..bb316e9 100644 --- a/translations/en.json +++ b/translations/en.json @@ -5,6 +5,7 @@ "addCashPoint": "Add Cash Point", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", + "addPaymentMode": "Add Payment Mode", "addPaymentOptions": "Add payment option", "amount": "Amount", "amountDue": "Amount Due", @@ -47,9 +48,13 @@ "checkFilters": "Check the filters above", "clearSearchInput": "Clear search input", "clientBalance": "Client Balance", + "confirmDeleteMessage": "Are you sure you want to delete this payment mode? Proceed cautiously.", "createdSuccessfully": "Billable service created successfully", "currentPrice": "Current price", "delete": "Delete", + "deletePaymentMode": "Delete Payment Mode", + "description": "Description", + "descriptionPlaceholder": "e.g., Used for all cash transactions", "discard": "Discard", "discount": "Discount", "editBillableService": "Edit billable service", @@ -60,11 +65,14 @@ "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", "error": "Error", + "errorDeletingPaymentMode": "An error occurred while deleting the payment mode.", "errorFetchingCashPoints": "An error occurred while fetching cash points.", "errorFetchingLocations": "An error occurred while fetching locations.", + "errorFetchingPaymentModes": "An error occurred while fetching payment modes.", "errorLoadingBillServices": "Error loading bill services", "errorLoadingPaymentModes": "Payment modes error", "errorSavingCashPoint": "An error occurred while saving the cash point.", + "errorSavingPaymentMode": "An error occurred while saving the payment mode.", "filterBy": "Filter by", "filterTable": "Filter table", "grandTotal": "Grand total", @@ -102,6 +110,11 @@ "paymentMethod": "Payment method", "paymentMethods": "Payment methods", "paymentMode": "Payment Mode", + "paymentModeDeleted": "Payment mode was successfully deleted.", + "paymentModeHistory": "Payment Mode History", + "paymentModeName": "Payment Mode Name", + "paymentModeNamePlaceholder": "e.g., Cash, Credit Card", + "paymentModeSaved": "Payment mode was successfully saved.", "paymentModesConfig": "Payment Modes Config", "payments": "Payments", "pleaseRequiredFields": "Please fill all required fields", From fdb2edc7f004564104f3e4c551fa366b65cafca0 Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:28:25 +0300 Subject: [PATCH 4/5] Change to openmrsFetch --- .../cash-point-configuration.component.tsx | 52 ++++++++++++------- .../payment-modes-config.component.tsx | 51 ++++++++++++------ translations/en.json | 2 + 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx index 3bc73e9..f378d65 100644 --- a/src/billable-services/cash-point/cash-point-configuration.component.tsx +++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx @@ -17,11 +17,10 @@ import { } from '@carbon/react'; import { Add } from '@carbon/react/icons'; import { useTranslation } from 'react-i18next'; -import axios from 'axios'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { showSnackbar } from '@openmrs/esm-framework'; +import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework'; import { CardHeader } from '@openmrs/esm-patient-common-lib'; import styles from './cash-point-configuration.scss'; @@ -42,7 +41,6 @@ const CashPointConfiguration: React.FC = () => { const [cashPoints, setCashPoints] = useState([]); const [locations, setLocations] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); - const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`; const { control, @@ -60,7 +58,7 @@ const CashPointConfiguration: React.FC = () => { const fetchCashPoints = useCallback(async () => { try { - const response = await axios.get(`${baseUrl}/billing/cashPoint?v=full`); + const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint?v=full'); setCashPoints(response.data.results || []); } catch (err) { showSnackbar({ @@ -70,11 +68,11 @@ const CashPointConfiguration: React.FC = () => { isLowContrast: false, }); } - }, [baseUrl, t]); + }, [t]); const fetchLocations = useCallback(async () => { try { - const response = await axios.get(`${baseUrl}/location?v=default`); + const response = await openmrsFetch('/ws/rest/v1/location?v=default'); const allLocations = response.data.results.map((loc) => ({ id: loc.uuid, label: loc.display, @@ -88,7 +86,7 @@ const CashPointConfiguration: React.FC = () => { isLowContrast: false, }); } - }, [baseUrl, t]); + }, [t]); useEffect(() => { fetchCashPoints(); @@ -114,21 +112,37 @@ const CashPointConfiguration: React.FC = () => { } try { - await axios.post(`${baseUrl}/billing/cashPoint`, { - name: data.name, - uuid: data.uuid, - location: { uuid: data.location }, + const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: { + name: data.name, + uuid: data.uuid, + location: { uuid: data.location }, + }, }); - showSnackbar({ - title: t('success', 'Success'), - subtitle: t('cashPointSaved', 'Cash point was successfully saved.'), - kind: 'success', - }); + if (response.ok) { + showSnackbar({ + title: t('success', 'Success'), + subtitle: t('cashPointSaved', 'Cash point was successfully saved.'), + kind: 'success', + }); - setIsModalOpen(false); - reset({ name: '', uuid: '', location: '' }); - fetchCashPoints(); + setIsModalOpen(false); + reset({ name: '', uuid: '', location: '' }); + fetchCashPoints(); + } else { + const errorData = response.data || {}; + showSnackbar({ + title: t('error', 'Error'), + subtitle: errorData.message || t('errorSavingCashPoint', 'An error occurred while saving the cash point.'), + kind: 'error', + isLowContrast: false, + }); + } } catch (err) { showSnackbar({ title: t('error', 'Error'), diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx index 671d645..113b6e9 100644 --- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx +++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx @@ -16,11 +16,10 @@ import { } from '@carbon/react'; import { Add } from '@carbon/react/icons'; import { useTranslation } from 'react-i18next'; -import axios from 'axios'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { showSnackbar } from '@openmrs/esm-framework'; +import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework'; import { CardHeader } from '@openmrs/esm-patient-common-lib'; import styles from './payment-modes-config.scss'; @@ -38,7 +37,6 @@ const PaymentModesConfig: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedPaymentMode, setSelectedPaymentMode] = useState(null); - const baseUrl = `${window.location.origin}/openmrs/ws/rest/v1`; const { control, @@ -55,7 +53,7 @@ const PaymentModesConfig: React.FC = () => { const fetchPaymentModes = useCallback(async () => { try { - const response = await axios.get(`${baseUrl}/billing/paymentMode?v=full`); + const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode?v=full'); setPaymentModes(response.data.results || []); } catch (err) { showSnackbar({ @@ -65,7 +63,7 @@ const PaymentModesConfig: React.FC = () => { isLowContrast: false, }); } - }, [baseUrl, t]); + }, [t]); useEffect(() => { fetchPaymentModes(); @@ -89,20 +87,37 @@ const PaymentModesConfig: React.FC = () => { } try { - await axios.post(`${baseUrl}/billing/paymentMode`, { - name: data.name, - description: data.description, + const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: data.name, + description: data.description, + }), }); - showSnackbar({ - title: t('success', 'Success'), - subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'), - kind: 'success', - }); + if (response.ok) { + showSnackbar({ + title: t('success', 'Success'), + subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'), + kind: 'success', + }); - setIsModalOpen(false); - reset({ name: '', description: '' }); - fetchPaymentModes(); + setIsModalOpen(false); + reset({ name: '', description: '' }); + fetchPaymentModes(); + } else { + const errorData = response.data || {}; + showSnackbar({ + title: t('error', 'Error'), + subtitle: + errorData.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'), + kind: 'error', + isLowContrast: false, + }); + } } catch (err) { showSnackbar({ title: t('error', 'Error'), @@ -117,7 +132,9 @@ const PaymentModesConfig: React.FC = () => { if (!selectedPaymentMode) return; try { - await axios.delete(`${baseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`); + await openmrsFetch(`/ws/rest/v1/billing/paymentMode/${selectedPaymentMode.uuid}`, { + method: 'DELETE', + }); showSnackbar({ title: t('success', 'Success'), diff --git a/translations/en.json b/translations/en.json index bb316e9..0e3dfdb 100644 --- a/translations/en.json +++ b/translations/en.json @@ -57,6 +57,8 @@ "descriptionPlaceholder": "e.g., Used for all cash transactions", "discard": "Discard", "discount": "Discount", + "duplicateCashPointError": "A cash point with the same name or UUID already exists. Please use a unique name and UUID.", + "duplicatePaymentModeError": "A payment mode with the same name already exists. Please create another payment mode", "editBillableService": "Edit billable service", "editBillableServices": "Edit Billable Services", "editBillLineItem": "Edit bill line item?", From 02b932ba32ea7a4964eeb4685f38a717276bd232 Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:01:37 +0300 Subject: [PATCH 5/5] Use restBaseUrl --- .../cash-point/cash-point-configuration.component.tsx | 8 ++++---- .../payyment-modes/payment-modes-config.component.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/billable-services/cash-point/cash-point-configuration.component.tsx b/src/billable-services/cash-point/cash-point-configuration.component.tsx index f378d65..6df96b3 100644 --- a/src/billable-services/cash-point/cash-point-configuration.component.tsx +++ b/src/billable-services/cash-point/cash-point-configuration.component.tsx @@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework'; +import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { CardHeader } from '@openmrs/esm-patient-common-lib'; import styles from './cash-point-configuration.scss'; @@ -58,7 +58,7 @@ const CashPointConfiguration: React.FC = () => { const fetchCashPoints = useCallback(async () => { try { - const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint?v=full'); + const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint?v=full`); setCashPoints(response.data.results || []); } catch (err) { showSnackbar({ @@ -72,7 +72,7 @@ const CashPointConfiguration: React.FC = () => { const fetchLocations = useCallback(async () => { try { - const response = await openmrsFetch('/ws/rest/v1/location?v=default'); + const response = await openmrsFetch(`${restBaseUrl}/location?v=default`); const allLocations = response.data.results.map((loc) => ({ id: loc.uuid, label: loc.display, @@ -112,7 +112,7 @@ const CashPointConfiguration: React.FC = () => { } try { - const response = await openmrsFetch('/ws/rest/v1/billing/cashPoint', { + const response = await openmrsFetch(`${restBaseUrl}/billing/cashPoint`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/billable-services/payyment-modes/payment-modes-config.component.tsx b/src/billable-services/payyment-modes/payment-modes-config.component.tsx index 113b6e9..647bacd 100644 --- a/src/billable-services/payyment-modes/payment-modes-config.component.tsx +++ b/src/billable-services/payyment-modes/payment-modes-config.component.tsx @@ -19,7 +19,7 @@ import { useTranslation } from 'react-i18next'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { showSnackbar, openmrsFetch } from '@openmrs/esm-framework'; +import { showSnackbar, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { CardHeader } from '@openmrs/esm-patient-common-lib'; import styles from './payment-modes-config.scss'; @@ -53,7 +53,7 @@ const PaymentModesConfig: React.FC = () => { const fetchPaymentModes = useCallback(async () => { try { - const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode?v=full'); + const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode?v=full`); setPaymentModes(response.data.results || []); } catch (err) { showSnackbar({ @@ -87,7 +87,7 @@ const PaymentModesConfig: React.FC = () => { } try { - const response = await openmrsFetch('/ws/rest/v1/billing/paymentMode', { + const response = await openmrsFetch(`${restBaseUrl}/billing/paymentMode`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -132,7 +132,7 @@ const PaymentModesConfig: React.FC = () => { if (!selectedPaymentMode) return; try { - await openmrsFetch(`/ws/rest/v1/billing/paymentMode/${selectedPaymentMode.uuid}`, { + await openmrsFetch(`${restBaseUrl}/billing/paymentMode/${selectedPaymentMode.uuid}`, { method: 'DELETE', });