@kenyaemr/esm-billing-app 5.4.2-pre.2135 → 5.4.2-pre.2138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +113 -85
  2. package/dist/574.js +1 -1
  3. package/dist/919.js +1 -0
  4. package/dist/919.js.map +1 -0
  5. package/dist/{746.js → 933.js} +3 -3
  6. package/dist/{746.js.map → 933.js.map} +1 -1
  7. package/dist/kenyaemr-esm-billing-app.js +1 -1
  8. package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +86 -86
  9. package/dist/main.js +3 -3
  10. package/dist/main.js.map +1 -1
  11. package/dist/routes.json +1 -1
  12. package/package.json +1 -1
  13. package/src/bill-deposit/components/dashboard/bill-deposit-dashboard.component.tsx +35 -0
  14. package/src/bill-deposit/components/forms/add-deposit.workspace.scss +26 -0
  15. package/src/bill-deposit/components/forms/add-deposit.workspace.tsx +212 -0
  16. package/src/{billing-form/bill-deposit/bill-deposit.scss → bill-deposit/components/forms/deposit-transactions/deposit-transaction.workspace.scss} +10 -17
  17. package/src/bill-deposit/components/forms/deposit-transactions/deposit-transaction.workspace.tsx +254 -0
  18. package/src/bill-deposit/components/modal/delete-deposit.modal.tsx +58 -0
  19. package/src/bill-deposit/components/modal/reverse-transaction.modal.tsx +59 -0
  20. package/src/bill-deposit/components/search/bill-deposit-search.component.tsx +106 -0
  21. package/src/bill-deposit/components/search/bill-deposit-search.scss +107 -0
  22. package/src/bill-deposit/components/search/components/deposit-table.tsx +150 -0
  23. package/src/bill-deposit/components/search/components/patient-info.tsx +38 -0
  24. package/src/bill-deposit/components/search/components/patient-search.tsx +24 -0
  25. package/src/bill-deposit/components/search/components/transaction-list/transaction-list.component.tsx +65 -0
  26. package/src/bill-deposit/components/search/components/transaction-list/transaction-list.scss +5 -0
  27. package/src/bill-deposit/constants/bill-deposit.constants.ts +25 -0
  28. package/src/bill-deposit/hooks/useBillDeposit.ts +43 -0
  29. package/src/bill-deposit/styles/bill-deposit-dashboard.scss +11 -0
  30. package/src/bill-deposit/types/bill-deposit.types.ts +61 -0
  31. package/src/bill-deposit/utils/bill-deposit.utils.ts +94 -0
  32. package/src/index.ts +23 -2
  33. package/src/past-patient-bills/patient-bills-dashboard/empty-patient-bill.component.tsx +29 -12
  34. package/src/past-patient-bills/patient-bills-dashboard/patient-bills-dashboard.scss +1 -0
  35. package/src/root.component.tsx +2 -0
  36. package/src/routes.json +22 -3
  37. package/src/types/index.ts +1 -0
  38. package/translations/en.json +3 -0
  39. package/dist/912.js +0 -1
  40. package/dist/912.js.map +0 -1
  41. package/src/billing-form/bill-deposit/bill-deposit.workspace.tsx +0 -236
  42. /package/dist/{746.js.LICENSE.txt → 933.js.LICENSE.txt} +0 -0
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"billableServicesHome","route":"billable-services"},{"component":"requirePaymentModal","routeRegex":"^patient/.+/chart","online":true,"offline":false}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"slot":"billing-dashboard-slot"}},{"component":"benefitsPackageDashboardLink","name":"benefits-package-dashboard-link","slot":"patient-chart-dashboard-slot","meta":{"name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot","path":"insurance-benefits","columns":1,"columnSpan":1},"featureFlag":"healthInformationExchange"},{"component":"benefitsPackage","name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"component":"benefitsEligibilyRequestForm","name":"benefits-eligibility-request-form"},{"component":"benefitsPreAuthForm","name":"benefits-pre-auth-form"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing","layoutMode":"anchored"}},{"name":"billing-check-in-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm"},{"name":"require-billing-modal","component":"requirePaymentModal"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"initiate-payment-modal","component":"initiatePaymentDialog"},{"name":"delete-billableservice-modal","component":"deleteBillableServiceModal"},{"name":"refund-bill-modal","component":"refundBillModal"},{"name":"delete-bill-modal","component":"deleteBillModal"},{"name":"lab-order-billable-item","component":"labOrder","slot":"top-of-lab-order-form-slot"},{"name":"procedure-order-billable-item","component":"procedureOrder","slot":"top-of-procedure-order-form-slot"},{"name":"imaging-order-billable-item","component":"imagingOrder","slot":"top-of-imaging-order-form-slot"},{"name":"price-info-order","component":"priceInfoOrder"},{"name":"drug-order-billable-item","component":"drugOrder","slot":"medication-info-slot"},{"name":"order-action-button","component":"orderActionButton","slots":["prescription-action-button-slot","imaging-orders-action","procedure-orders-action","tests-ordered-actions-slot"],"order":0},{"component":"billingOverviewLink","name":"billing-overview-link","order":0,"slot":"billing-dashboard-link-slot"},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-link-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-link-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-link-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-link-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-link-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-link-slot"},{"component":"claimsManagementSideNavGroup","name":"claims-management-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"claims-management","title":"Claims management Overview","slot":"case-management-slot"},"featureFlag":"healthInformationExchange"},{"component":"claimsManagementOverviewDashboardLink","name":"claims-management-overview-link","order":0,"slot":"claims-management-dashboard-link-slot"},{"component":"preAuthRequestsDashboardLink","name":"preauthrequest-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"claimsOverview","name":"claims-overview-dashboard-link","slot":"claims-management-overview-slot"},{"component":"waiveBillActionButton","name":"waive-bill-action-button","slot":"bill-actions-slot"},{"component":"deleteBillActionButton","name":"delete-bill-action-button","slot":"bill-actions-slot"},{"component":"refundLineItem","name":"refund-line-item","slot":"bill-actions-overflow-menu-slot"},{"name":"edit-line-item","component":"editLineItem","slot":"bill-actions-overflow-menu-slot"},{"name":"cancel-line-item","component":"cancelLineItem","slot":"bill-actions-overflow-menu-slot"}],"workspaces":[{"name":"create-bill-workspace","component":"createBillWorkspace","title":"Create Bill Workspace","type":"other-form"},{"name":"waive-bill-form","component":"waiveBillForm","title":"Waive Bill Form","type":"other-form"},{"name":"edit-bill-form","component":"editBillForm","title":"Edit Bill Form","type":"other-form"},{"name":"billable-service-form","component":"addServiceForm","title":"Create Charge Item Form","type":"other-form"},{"name":"commodity-form","component":"addCommodityForm","title":"Create Charge Item Form","type":"other-form"},{"name":"billing-form","component":"billingForm","title":"Billing Form","type":"other-form","width":"extra-wide"},{"name":"payment-mode-workspace","component":"paymentModeWorkspace","title":"Payment Mode Workspace","type":"other-form"},{"name":"cancel-bill-workspace","component":"cancelBillWorkspace","title":"Cancel Bill Workspace","type":"other-form"},{"name":"bill-deposit-workspace","component":"billDepositWorkspace","title":"Bill Deposit Workspace","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"name":"clock-out-modal","component":"clockOut"},{"name":"bulk-import-billable-services-modal","component":"bulkImportBillableServicesModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"manage-claim-request-modal","component":"manageClaimRequestModal"},{"name":"paid-bill-receipt-print-preview-modal","component":"paidBillReceiptPrintPreviewModal"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"}],"version":"5.4.2-pre.2135"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"billableServicesHome","route":"billable-services"},{"component":"requirePaymentModal","routeRegex":"^patient/.+/chart","online":true,"offline":false}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"slot":"billing-dashboard-slot"}},{"component":"benefitsPackageDashboardLink","name":"benefits-package-dashboard-link","slot":"patient-chart-dashboard-slot","meta":{"name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot","path":"insurance-benefits","columns":1,"columnSpan":1},"featureFlag":"healthInformationExchange"},{"component":"benefitsPackage","name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"component":"benefitsEligibilyRequestForm","name":"benefits-eligibility-request-form"},{"component":"benefitsPreAuthForm","name":"benefits-pre-auth-form"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing","layoutMode":"anchored"}},{"name":"billing-check-in-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm"},{"name":"require-billing-modal","component":"requirePaymentModal"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"initiate-payment-modal","component":"initiatePaymentDialog"},{"name":"delete-billableservice-modal","component":"deleteBillableServiceModal"},{"name":"refund-bill-modal","component":"refundBillModal"},{"name":"delete-bill-modal","component":"deleteBillModal"},{"name":"lab-order-billable-item","component":"labOrder","slot":"top-of-lab-order-form-slot"},{"name":"procedure-order-billable-item","component":"procedureOrder","slot":"top-of-procedure-order-form-slot"},{"name":"imaging-order-billable-item","component":"imagingOrder","slot":"top-of-imaging-order-form-slot"},{"name":"price-info-order","component":"priceInfoOrder"},{"name":"drug-order-billable-item","component":"drugOrder","slot":"medication-info-slot"},{"name":"order-action-button","component":"orderActionButton","slots":["prescription-action-button-slot","imaging-orders-action","procedure-orders-action","tests-ordered-actions-slot"],"order":0},{"component":"billingOverviewLink","name":"billing-overview-link","order":0,"slot":"billing-dashboard-link-slot"},{"component":"billDepositDashboardLink","name":"bill-deposit-dashboard-link","slots":["billing-dashboard-link-slot","billing-dashboard-group-nav-slot"]},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-link-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-link-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-link-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-link-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-link-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-link-slot"},{"component":"claimsManagementSideNavGroup","name":"claims-management-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"claims-management","title":"Claims management Overview","slot":"case-management-slot"},"featureFlag":"healthInformationExchange"},{"component":"claimsManagementOverviewDashboardLink","name":"claims-management-overview-link","order":0,"slot":"claims-management-dashboard-link-slot"},{"component":"preAuthRequestsDashboardLink","name":"preauthrequest-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"claimsOverview","name":"claims-overview-dashboard-link","slot":"claims-management-overview-slot"},{"component":"waiveBillActionButton","name":"waive-bill-action-button","slot":"bill-actions-slot"},{"component":"deleteBillActionButton","name":"delete-bill-action-button","slot":"bill-actions-slot"},{"component":"refundLineItem","name":"refund-line-item","slot":"bill-actions-overflow-menu-slot"},{"name":"edit-line-item","component":"editLineItem","slot":"bill-actions-overflow-menu-slot"},{"name":"cancel-line-item","component":"cancelLineItem","slot":"bill-actions-overflow-menu-slot"}],"workspaces":[{"name":"create-bill-workspace","component":"createBillWorkspace","title":"Create Bill Workspace","type":"other-form"},{"name":"waive-bill-form","component":"waiveBillForm","title":"Waive Bill Form","type":"other-form"},{"name":"edit-bill-form","component":"editBillForm","title":"Edit Bill Form","type":"other-form"},{"name":"billable-service-form","component":"addServiceForm","title":"Create Charge Item Form","type":"other-form"},{"name":"commodity-form","component":"addCommodityForm","title":"Create Charge Item Form","type":"other-form"},{"name":"billing-form","component":"billingForm","title":"Billing Form","type":"other-form","width":"extra-wide"},{"name":"payment-mode-workspace","component":"paymentModeWorkspace","title":"Payment Mode Workspace","type":"other-form"},{"name":"cancel-bill-workspace","component":"cancelBillWorkspace","title":"Cancel Bill Workspace","type":"other-form"},{"name":"add-deposit-workspace","component":"addDepositWorkspace","title":"Add Deposit","type":"other-form"},{"name":"deposit-transaction-workspace","component":"depositTransactionWorkspace","title":"Deposit Transaction","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"name":"clock-out-modal","component":"clockOut"},{"name":"bulk-import-billable-services-modal","component":"bulkImportBillableServicesModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"manage-claim-request-modal","component":"manageClaimRequestModal"},{"name":"paid-bill-receipt-print-preview-modal","component":"paidBillReceiptPrintPreviewModal"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"},{"name":"delete-deposit-modal","component":"deleteDepositModal"},{"name":"reverse-transaction-modal","component":"reverseTransactionModal"}],"version":"5.4.2-pre.2138"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-billing-app",
3
- "version": "5.4.2-pre.2135",
3
+ "version": "5.4.2-pre.2138",
4
4
  "description": "Billing app for KenyaEMR",
5
5
  "browser": "dist/kenyaemr-esm-billing-app.js",
6
6
  "main": "src/index.ts",
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { HomePictogram, PageHeader, PageHeaderContent } from '@openmrs/esm-framework';
3
+ import styles from '../../styles/bill-deposit-dashboard.scss';
4
+ import { Tabs, TabList, Tab, TabPanels, TabPanel, OverflowMenu, OverflowMenuItem } from '@carbon/react';
5
+ import { Search } from '@carbon/react/icons';
6
+ import { useTranslation } from 'react-i18next';
7
+ import BillDepositSearch from '../search/bill-deposit-search.component';
8
+
9
+ const BillDepositDashboard: React.FC = () => {
10
+ const { t } = useTranslation();
11
+ return (
12
+ <div>
13
+ <PageHeader className={styles.billDepositDashboard}>
14
+ <PageHeaderContent title="Bill Deposit" illustration={<HomePictogram />} />
15
+ <OverflowMenu flipped aria-label="overflow-menu">
16
+ <OverflowMenuItem itemText={t('addDeposit', 'Add Deposit')} />
17
+ </OverflowMenu>
18
+ </PageHeader>
19
+ <div className={styles.tabsContainer}>
20
+ <Tabs>
21
+ <TabList contained>
22
+ <Tab renderIcon={Search}>{t('search', 'Search')}</Tab>
23
+ </TabList>
24
+ <TabPanels>
25
+ <TabPanel>
26
+ <BillDepositSearch />
27
+ </TabPanel>
28
+ </TabPanels>
29
+ </Tabs>
30
+ </div>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default BillDepositDashboard;
@@ -0,0 +1,26 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ height: 100%;
10
+ }
11
+
12
+ .formContainer {
13
+ margin: layout.$spacing-05;
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: layout.$spacing-05;
17
+ }
18
+
19
+ .tablet {
20
+ padding: layout.$spacing-06 layout.$spacing-05;
21
+ background-color: colors.$white;
22
+ }
23
+
24
+ .desktop {
25
+ padding: 0;
26
+ }
@@ -0,0 +1,212 @@
1
+ import React, { useEffect } from 'react';
2
+ import {
3
+ type DefaultWorkspaceProps,
4
+ ResponsiveWrapper,
5
+ useLayoutType,
6
+ useSession,
7
+ showToast,
8
+ showSnackbar,
9
+ restBaseUrl,
10
+ } from '@openmrs/esm-framework';
11
+ import { useTranslation } from 'react-i18next';
12
+ import { Controller, useForm } from 'react-hook-form';
13
+ import styles from './add-deposit.workspace.scss';
14
+ import { ButtonSet, Button, InlineLoading, TextInput, NumberInput } from '@carbon/react';
15
+ import classNames from 'classnames';
16
+ import { z } from 'zod';
17
+ import { zodResolver } from '@hookform/resolvers/zod';
18
+ import { mutate } from 'swr';
19
+ import { type BillDeposit } from '../../types/bill-deposit.types';
20
+ import { generateReferenceNumber, saveDeposit } from '../../utils/bill-deposit.utils';
21
+
22
+ type AddDepositWorkspaceProps = DefaultWorkspaceProps & {
23
+ patientUuid: string;
24
+ deposit?: BillDeposit;
25
+ };
26
+
27
+ const depositFormSchema = z.object({
28
+ patient: z.string().min(1),
29
+ amount: z.number().min(1),
30
+ depositType: z.string().min(1),
31
+ referenceNumber: z.string().min(1),
32
+ description: z.string().min(1),
33
+ });
34
+
35
+ type DepositFormType = z.infer<typeof depositFormSchema>;
36
+
37
+ const AddDepositWorkspace: React.FC<AddDepositWorkspaceProps> = ({
38
+ patientUuid,
39
+ closeWorkspace,
40
+ closeWorkspaceWithSavedChanges,
41
+ promptBeforeClosing,
42
+ deposit,
43
+ }) => {
44
+ const { t } = useTranslation();
45
+ const session = useSession();
46
+ const location = session?.sessionLocation?.display;
47
+ const isTablet = useLayoutType() === 'tablet';
48
+ const defaultValues = deposit
49
+ ? {
50
+ patient: deposit.patient.uuid,
51
+ amount: deposit.amount,
52
+ depositType: deposit.depositType,
53
+ referenceNumber: deposit.referenceNumber,
54
+ description: deposit.description,
55
+ }
56
+ : {
57
+ patient: patientUuid,
58
+ amount: 0,
59
+ depositType: '',
60
+ referenceNumber: generateReferenceNumber(location ?? ''),
61
+ description: '',
62
+ };
63
+ const {
64
+ handleSubmit,
65
+ control,
66
+ formState: { isSubmitting, isDirty, errors },
67
+ } = useForm<DepositFormType>({
68
+ resolver: zodResolver(depositFormSchema),
69
+ defaultValues: defaultValues,
70
+ });
71
+
72
+ const onSubmit = async (data: DepositFormType) => {
73
+ const deposityPayload: Partial<BillDeposit> = {
74
+ patient: patientUuid as any,
75
+ amount: data.amount,
76
+ depositType: data.depositType,
77
+ referenceNumber: data.referenceNumber,
78
+ description: data.description,
79
+ status: deposit?.status ?? 'PENDING',
80
+ };
81
+
82
+ try {
83
+ const response = await saveDeposit(deposityPayload, deposit?.uuid);
84
+ if (response.status === 201 || response.status === 200) {
85
+ showSnackbar({
86
+ title: t('success', 'Success'),
87
+ kind: 'success',
88
+ subtitle:
89
+ response.status === 201
90
+ ? t('depositCreated', 'Deposit created successfully')
91
+ : t('depositSaved', 'Deposit saved successfully'),
92
+ });
93
+ }
94
+ mutate(
95
+ (key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/cashier/deposit?patient=${patientUuid}`),
96
+ undefined,
97
+ { revalidate: true },
98
+ );
99
+ closeWorkspaceWithSavedChanges();
100
+ } catch (error: any) {
101
+ showSnackbar({
102
+ title: t('error', 'Error'),
103
+ kind: 'error',
104
+ subtitle: error?.message ?? t('depositSaveError', 'Error saving deposit'),
105
+ });
106
+ }
107
+ };
108
+
109
+ const handleError = (err) => {
110
+ showToast({
111
+ title: t('error', 'Error'),
112
+ kind: 'error',
113
+ description: t('formError', 'Please check the form for errors'),
114
+ });
115
+ };
116
+
117
+ useEffect(() => {
118
+ promptBeforeClosing(() => isDirty);
119
+ }, [isDirty]);
120
+
121
+ return (
122
+ <form onSubmit={handleSubmit(onSubmit, handleError)} className={styles.form}>
123
+ <div className={styles.formContainer}>
124
+ <ResponsiveWrapper>
125
+ <Controller
126
+ control={control}
127
+ name="depositType"
128
+ render={({ field }) => (
129
+ <TextInput
130
+ id="depositType"
131
+ placeholder={t('depositType', 'Deposit Type')}
132
+ labelText={t('depositType', 'Deposit Type')}
133
+ value={field.value}
134
+ onChange={field.onChange}
135
+ invalid={!!errors.depositType?.message}
136
+ invalidText={errors.depositType?.message}
137
+ />
138
+ )}
139
+ />
140
+ </ResponsiveWrapper>
141
+ <ResponsiveWrapper>
142
+ <Controller
143
+ control={control}
144
+ name="amount"
145
+ render={({ field }) => (
146
+ <NumberInput
147
+ id="amount"
148
+ invalidText={errors.amount?.message}
149
+ label={t('amount', 'Amount')}
150
+ onChange={(e, { value }) => field.onChange(parseInt(value, 10))}
151
+ size="md"
152
+ step={1}
153
+ value={field.value}
154
+ />
155
+ )}
156
+ />
157
+ </ResponsiveWrapper>
158
+ <ResponsiveWrapper>
159
+ <Controller
160
+ control={control}
161
+ name="referenceNumber"
162
+ render={({ field }) => (
163
+ <TextInput
164
+ id="referenceNumber"
165
+ placeholder={t('referenceNumber', 'Reference Number')}
166
+ labelText={t('referenceNumber', 'Reference Number')}
167
+ value={field.value}
168
+ onChange={field.onChange}
169
+ invalid={!!errors.referenceNumber?.message}
170
+ invalidText={errors.referenceNumber?.message}
171
+ readOnly
172
+ />
173
+ )}
174
+ />
175
+ </ResponsiveWrapper>
176
+ <ResponsiveWrapper>
177
+ <Controller
178
+ control={control}
179
+ name="description"
180
+ render={({ field }) => (
181
+ <TextInput
182
+ id="description"
183
+ placeholder={t('description', 'Description')}
184
+ labelText={t('description', 'Description')}
185
+ value={field.value}
186
+ onChange={field.onChange}
187
+ invalid={!!errors.description?.message}
188
+ invalidText={errors.description?.message}
189
+ />
190
+ )}
191
+ />
192
+ </ResponsiveWrapper>
193
+ </div>
194
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
195
+ <Button style={{ maxWidth: '50%' }} kind="secondary" onClick={closeWorkspace}>
196
+ {t('cancel', 'Cancel')}
197
+ </Button>
198
+ <Button disabled={isSubmitting || !isDirty} style={{ maxWidth: '50%' }} kind="primary" type="submit">
199
+ {isSubmitting ? (
200
+ <span style={{ display: 'flex', justifyItems: 'center' }}>
201
+ {t('submitting', 'Submitting...')} <InlineLoading status="active" iconDescription="Loading" />
202
+ </span>
203
+ ) : (
204
+ t('saveAndClose', 'Save & close')
205
+ )}
206
+ </Button>
207
+ </ButtonSet>
208
+ </form>
209
+ );
210
+ };
211
+
212
+ export default AddDepositWorkspace;
@@ -10,31 +10,24 @@
10
10
  }
11
11
 
12
12
  .formContainer {
13
- display: flex;
14
- flex-direction: column;
15
13
  margin: layout.$spacing-05;
16
- row-gap: layout.$spacing-03;
17
- }
18
-
19
- .formField {
20
- margin-bottom: layout.$spacing-05;
21
- @include type.type-style('label-02');
22
- color: colors.$gray-70;
23
- }
24
-
25
- .button {
26
- height: layout.$spacing-10;
27
14
  display: flex;
28
- align-content: flex-start;
29
- align-items: baseline;
30
- min-width: 50%;
15
+ flex-direction: column;
16
+ gap: layout.$spacing-05;
31
17
  }
32
18
 
33
19
  .tablet {
34
20
  padding: layout.$spacing-06 layout.$spacing-05;
35
- background-color: colors.$white-0;
21
+ background-color: colors.$white;
36
22
  }
37
23
 
38
24
  .desktop {
39
25
  padding: 0;
40
26
  }
27
+
28
+ .loadingContainer {
29
+ height: 100vh;
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ }
@@ -0,0 +1,254 @@
1
+ import React, { useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DefaultWorkspaceProps,
5
+ ResponsiveWrapper,
6
+ restBaseUrl,
7
+ showSnackbar,
8
+ useLayoutType,
9
+ } from '@openmrs/esm-framework';
10
+ import { usePatientBills } from '../../../../prompt-payment/prompt-payment.resource';
11
+ import { type LineItem, PaymentStatus } from '../../../../types';
12
+ import { ButtonSet, Button, InlineLoading, ComboBox, NumberInput, TextInput, InlineNotification } from '@carbon/react';
13
+ import { useForm, Controller } from 'react-hook-form';
14
+ import styles from './deposit-transaction.workspace.scss';
15
+ import classNames from 'classnames';
16
+ import { z } from 'zod';
17
+ import { zodResolver } from '@hookform/resolvers/zod';
18
+ import { convertToCurrency, extractString } from '../../../../helpers';
19
+ import uniqBy from 'lodash-es/uniqBy';
20
+ import { mutate } from 'swr';
21
+ import { BILL_DEPOSIT_TRANSACTION_TYPES } from '../../../constants/bill-deposit.constants';
22
+ import { type FormattedDeposit } from '../../../types/bill-deposit.types';
23
+ import { addDepositTransaction } from '../../../utils/bill-deposit.utils';
24
+
25
+ type DepositTransactionWorkspaceProps = DefaultWorkspaceProps & {
26
+ deposit: FormattedDeposit;
27
+ patientUuid: string;
28
+ };
29
+
30
+ const DepositTransactionWorkspace: React.FC<DepositTransactionWorkspaceProps> = ({
31
+ deposit,
32
+ patientUuid,
33
+ closeWorkspace,
34
+ closeWorkspaceWithSavedChanges,
35
+ promptBeforeClosing,
36
+ }) => {
37
+ const { t } = useTranslation();
38
+ const isTablet = useLayoutType() === 'tablet';
39
+ const { isLoading, patientBills, error } = usePatientBills(patientUuid);
40
+ const pendingLineItems: Array<LineItem> = uniqBy(
41
+ patientBills
42
+ ?.filter((bill) => bill.status !== PaymentStatus.PAID && bill.status !== PaymentStatus.EXEMPTED)
43
+ .map((bill) => bill.lineItems)
44
+ .flat() ?? [],
45
+ 'uuid',
46
+ );
47
+
48
+ const transactionTypes = Object.entries(BILL_DEPOSIT_TRANSACTION_TYPES).map(([key, value]) => ({
49
+ key,
50
+ value,
51
+ }));
52
+
53
+ const depositTransactionFormSchema = z.object({
54
+ billLineItem: z.string().min(1),
55
+ amount: z
56
+ .number()
57
+ .min(1)
58
+ .max(deposit?.availableBalance ?? 0),
59
+ transactionType: z.string().min(1),
60
+ reason: z.string().min(1),
61
+ });
62
+ type DepositTransactionFormType = z.infer<typeof depositTransactionFormSchema>;
63
+
64
+ const {
65
+ control,
66
+ handleSubmit,
67
+ formState: { isDirty, isSubmitting, errors },
68
+ } = useForm({
69
+ resolver: zodResolver(depositTransactionFormSchema),
70
+ defaultValues: {
71
+ billLineItem: '',
72
+ amount: 0,
73
+ transactionType: '',
74
+ reason: '',
75
+ },
76
+ });
77
+
78
+ const onSubmit = async (data: DepositTransactionFormType) => {
79
+ const payload = {
80
+ billLineItem: data.billLineItem,
81
+ amount: parseFloat(data.amount.toString()),
82
+ transactionType: data.transactionType,
83
+ reason: data.reason,
84
+ };
85
+
86
+ try {
87
+ const response = await addDepositTransaction(deposit.id, payload);
88
+ if (response.ok) {
89
+ showSnackbar({
90
+ title: t('transactionAdded', 'Transaction added successfully'),
91
+ kind: 'success',
92
+ subtitle: t('transactionAddedDetails', 'The transaction has been added successfully.'),
93
+ isLowContrast: true,
94
+ timeoutInMs: 5000,
95
+ autoClose: true,
96
+ });
97
+ mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/cashier/deposit`), undefined, {
98
+ revalidate: true,
99
+ });
100
+ } else {
101
+ showSnackbar({
102
+ title: t('errorAddingTransaction', 'Error adding transaction'),
103
+ kind: 'error',
104
+ subtitle: t('errorAddingTransactionDetails', 'Please try again later. Check the console for more details.'),
105
+ isLowContrast: true,
106
+ timeoutInMs: 5000,
107
+ autoClose: true,
108
+ });
109
+ }
110
+ } catch (error) {
111
+ console.error('Error submitting form:', error);
112
+ showSnackbar({
113
+ title: t('errorSubmittingForm', 'Error submitting form'),
114
+ kind: 'error',
115
+ subtitle: t('errorSubmittingFormDetails', 'Please try again later. Check the console for more details.'),
116
+ isLowContrast: true,
117
+ timeoutInMs: 5000,
118
+ autoClose: true,
119
+ });
120
+ } finally {
121
+ closeWorkspaceWithSavedChanges();
122
+ }
123
+ };
124
+ const handleError = (error: any) => {
125
+ console.error('Error submitting form:', error);
126
+ };
127
+
128
+ useEffect(() => {
129
+ promptBeforeClosing(() => isDirty);
130
+ }, [isDirty]);
131
+
132
+ if (error) {
133
+ return (
134
+ <InlineNotification
135
+ aria-label="closes notification"
136
+ kind="error"
137
+ lowContrast={true}
138
+ statusIconDescription="notification"
139
+ subtitle={error.message ?? 'An error occurred while fetching the patient bills'}
140
+ title={t('error', 'Error')}
141
+ />
142
+ );
143
+ }
144
+
145
+ if (isLoading) {
146
+ return (
147
+ <div className={styles.loadingContainer}>
148
+ <InlineLoading status="active" iconDescription="Loading" />
149
+ </div>
150
+ );
151
+ }
152
+
153
+ return (
154
+ <form onSubmit={handleSubmit(onSubmit, handleError)} className={styles.form}>
155
+ <div className={styles.formContainer}>
156
+ <ResponsiveWrapper>
157
+ <Controller
158
+ control={control}
159
+ name="billLineItem"
160
+ render={({ field }) => (
161
+ <ComboBox
162
+ id="billLineItem"
163
+ itemToString={(item: LineItem) =>
164
+ item ? `${extractString(item.billableService)} - ${convertToCurrency(item.price)}` : ''
165
+ }
166
+ items={pendingLineItems ?? []}
167
+ onChange={({ selectedItem }) => field.onChange(selectedItem?.uuid)}
168
+ placeholder={t('selectBillLineItem', 'Select bill line item')}
169
+ invalid={!!errors.billLineItem}
170
+ invalidText={errors.billLineItem?.message}
171
+ titleText={t('billLineItem', 'Bill line item')}
172
+ onToggleClick={() => {}}
173
+ />
174
+ )}
175
+ />
176
+ </ResponsiveWrapper>
177
+ <ResponsiveWrapper>
178
+ <Controller
179
+ control={control}
180
+ name="amount"
181
+ render={({ field }) => (
182
+ <NumberInput
183
+ id="amount"
184
+ invalid={!!errors.amount}
185
+ invalidText={errors.amount?.message}
186
+ label={t('amount', 'Amount')}
187
+ onChange={({ target }) => field.onChange(Number(target.value))}
188
+ max={deposit?.availableBalance}
189
+ min={0}
190
+ size="md"
191
+ hideSteppers
192
+ value={field.value}
193
+ />
194
+ )}
195
+ />
196
+ </ResponsiveWrapper>
197
+ <ResponsiveWrapper>
198
+ <Controller
199
+ control={control}
200
+ name="transactionType"
201
+ render={({ field }) => (
202
+ <ComboBox
203
+ id="transactionType"
204
+ itemToString={(item) => item?.key}
205
+ items={transactionTypes ?? []}
206
+ onChange={({ selectedItem }) => field.onChange(selectedItem.value)}
207
+ placeholder={t('selectTransactionType', 'Select transaction type')}
208
+ invalid={!!errors.transactionType}
209
+ invalidText={errors.transactionType?.message}
210
+ titleText={t('transactionType', 'Transaction type')}
211
+ size="md"
212
+ />
213
+ )}
214
+ />
215
+ </ResponsiveWrapper>
216
+ <ResponsiveWrapper>
217
+ <Controller
218
+ control={control}
219
+ name="reason"
220
+ render={({ field }) => (
221
+ <TextInput
222
+ labelText={t('reason', 'Reason')}
223
+ id="reason"
224
+ invalid={!!errors.reason}
225
+ invalidText={errors.reason?.message}
226
+ label={t('reason', 'Reason')}
227
+ onChange={({ target }) => field.onChange(target.value)}
228
+ max={deposit?.availableBalance}
229
+ size="md"
230
+ value={field.value}
231
+ />
232
+ )}
233
+ />
234
+ </ResponsiveWrapper>
235
+ </div>
236
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
237
+ <Button style={{ maxWidth: '50%' }} kind="secondary" onClick={closeWorkspace}>
238
+ {t('cancel', 'Cancel')}
239
+ </Button>
240
+ <Button disabled={isSubmitting || !isDirty} style={{ maxWidth: '50%' }} kind="primary" type="submit">
241
+ {isSubmitting ? (
242
+ <span style={{ display: 'flex', justifyItems: 'center' }}>
243
+ {t('submitting', 'Submitting...')} <InlineLoading status="active" iconDescription="Loading" />
244
+ </span>
245
+ ) : (
246
+ t('saveAndClose', 'Save & close')
247
+ )}
248
+ </Button>
249
+ </ButtonSet>
250
+ </form>
251
+ );
252
+ };
253
+
254
+ export default DepositTransactionWorkspace;
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { deleteDeposit } from '../../utils/bill-deposit.utils';
5
+ import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
6
+ import { mutate } from 'swr';
7
+
8
+ type DeleteDepositModalProps = {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ depositUuid: string;
12
+ };
13
+
14
+ const DeleteDepositModal: React.FC<DeleteDepositModalProps> = ({ isOpen, onClose, depositUuid }) => {
15
+ const { t } = useTranslation();
16
+ const [isLoading, setIsLoading] = React.useState(false);
17
+
18
+ const handleDelete = async () => {
19
+ setIsLoading(true);
20
+ const response = await deleteDeposit(depositUuid);
21
+ if (response.status === 204) {
22
+ showSnackbar({
23
+ title: t('success', 'Success'),
24
+ kind: 'success',
25
+ subtitle: t('depositDeleted', 'Deposit deleted successfully'),
26
+ });
27
+ mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/cashier/deposit`), undefined, {
28
+ revalidate: true,
29
+ });
30
+
31
+ onClose();
32
+ } else {
33
+ showSnackbar({
34
+ title: t('error', 'Error'),
35
+ kind: 'error',
36
+ subtitle: t('depositDeleteError', 'Error deleting deposit'),
37
+ });
38
+ }
39
+ setIsLoading(false);
40
+ };
41
+
42
+ return (
43
+ <div>
44
+ <ModalHeader onClose={onClose}>{t('deleteDeposit', 'Delete Deposit')}</ModalHeader>
45
+ <ModalBody>{t('deleteDepositConfirmation', 'Are you sure you want to delete this deposit?')}</ModalBody>
46
+ <ModalFooter>
47
+ <Button kind="secondary" onClick={onClose}>
48
+ {t('cancel', 'Cancel')}
49
+ </Button>
50
+ <Button kind="danger" onClick={handleDelete} disabled={isLoading}>
51
+ {t('delete', 'Delete')}
52
+ </Button>
53
+ </ModalFooter>
54
+ </div>
55
+ );
56
+ };
57
+
58
+ export default DeleteDepositModal;