@kenyaemr/esm-billing-app 5.4.2-pre.2137 → 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.
- package/.turbo/turbo-build.log +115 -87
- package/dist/574.js +1 -1
- package/dist/919.js +1 -0
- package/dist/919.js.map +1 -0
- package/dist/{746.js → 933.js} +3 -3
- package/dist/{746.js.map → 933.js.map} +1 -1
- package/dist/kenyaemr-esm-billing-app.js +1 -1
- package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +86 -86
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bill-deposit/components/dashboard/bill-deposit-dashboard.component.tsx +35 -0
- package/src/bill-deposit/components/forms/add-deposit.workspace.scss +26 -0
- package/src/bill-deposit/components/forms/add-deposit.workspace.tsx +212 -0
- package/src/{billing-form/bill-deposit/bill-deposit.scss → bill-deposit/components/forms/deposit-transactions/deposit-transaction.workspace.scss} +10 -17
- package/src/bill-deposit/components/forms/deposit-transactions/deposit-transaction.workspace.tsx +254 -0
- package/src/bill-deposit/components/modal/delete-deposit.modal.tsx +58 -0
- package/src/bill-deposit/components/modal/reverse-transaction.modal.tsx +59 -0
- package/src/bill-deposit/components/search/bill-deposit-search.component.tsx +106 -0
- package/src/bill-deposit/components/search/bill-deposit-search.scss +107 -0
- package/src/bill-deposit/components/search/components/deposit-table.tsx +150 -0
- package/src/bill-deposit/components/search/components/patient-info.tsx +38 -0
- package/src/bill-deposit/components/search/components/patient-search.tsx +24 -0
- package/src/bill-deposit/components/search/components/transaction-list/transaction-list.component.tsx +65 -0
- package/src/bill-deposit/components/search/components/transaction-list/transaction-list.scss +5 -0
- package/src/bill-deposit/constants/bill-deposit.constants.ts +25 -0
- package/src/bill-deposit/hooks/useBillDeposit.ts +43 -0
- package/src/bill-deposit/styles/bill-deposit-dashboard.scss +11 -0
- package/src/bill-deposit/types/bill-deposit.types.ts +61 -0
- package/src/bill-deposit/utils/bill-deposit.utils.ts +94 -0
- package/src/index.ts +23 -2
- package/src/past-patient-bills/patient-bills-dashboard/empty-patient-bill.component.tsx +29 -12
- package/src/past-patient-bills/patient-bills-dashboard/patient-bills-dashboard.scss +1 -0
- package/src/root.component.tsx +2 -0
- package/src/routes.json +22 -3
- package/src/types/index.ts +1 -0
- package/translations/en.json +3 -0
- package/dist/912.js +0 -1
- package/dist/912.js.map +0 -1
- package/src/billing-form/bill-deposit/bill-deposit.workspace.tsx +0 -236
- /package/dist/{746.js.LICENSE.txt → 933.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
|
|
5
|
+
import { mutate } from 'swr';
|
|
6
|
+
import { type BillDepositTransaction } from '../../types/bill-deposit.types';
|
|
7
|
+
import { reverseTransaction } from '../../utils/bill-deposit.utils';
|
|
8
|
+
|
|
9
|
+
type ReverseTransactionModalProps = {
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
depositUuid: string;
|
|
12
|
+
transaction: BillDepositTransaction;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ReverseTransactionModal: React.FC<ReverseTransactionModalProps> = ({ onClose, depositUuid, transaction }) => {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
18
|
+
|
|
19
|
+
const handleReverse = async () => {
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
const response = await reverseTransaction(depositUuid, transaction.uuid);
|
|
22
|
+
if (response.status === 204) {
|
|
23
|
+
showSnackbar({
|
|
24
|
+
title: t('success', 'Success'),
|
|
25
|
+
kind: 'success',
|
|
26
|
+
subtitle: t('transactionReversed', 'Transaction reversed successfully'),
|
|
27
|
+
});
|
|
28
|
+
mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/cashier/deposit`), undefined, {
|
|
29
|
+
revalidate: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
onClose();
|
|
33
|
+
} else {
|
|
34
|
+
showSnackbar({
|
|
35
|
+
title: t('error', 'Error'),
|
|
36
|
+
kind: 'error',
|
|
37
|
+
subtitle: t('transactionReverseError', 'Error reversing transaction'),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<ModalHeader onClose={onClose}>{t('reverseTransaction', 'Reverse Transaction')}</ModalHeader>
|
|
46
|
+
<ModalBody>{t('reverseTransactionConfirmation', 'Are you sure you want to reverse this transaction?')}</ModalBody>
|
|
47
|
+
<ModalFooter>
|
|
48
|
+
<Button kind="secondary" onClick={onClose}>
|
|
49
|
+
{t('cancel', 'Cancel')}
|
|
50
|
+
</Button>
|
|
51
|
+
<Button kind="danger" onClick={handleReverse} disabled={isLoading}>
|
|
52
|
+
{t('reverse', 'Reverse')}
|
|
53
|
+
</Button>
|
|
54
|
+
</ModalFooter>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default ReverseTransactionModal;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { formatDate, launchWorkspace, parseDate } from '@openmrs/esm-framework';
|
|
3
|
+
import { useBillDeposit } from '../../hooks/useBillDeposit';
|
|
4
|
+
import { Button, InlineLoading } from '@carbon/react';
|
|
5
|
+
import styles from './bill-deposit-search.scss';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Add } from '@carbon/react/icons';
|
|
8
|
+
import EmptyPatientBill from '../../../past-patient-bills/patient-bills-dashboard/empty-patient-bill.component';
|
|
9
|
+
import PatientSearch from './components/patient-search';
|
|
10
|
+
import PatientInfo from './components/patient-info';
|
|
11
|
+
import DepositTable from './components/deposit-table';
|
|
12
|
+
|
|
13
|
+
const BillDepositSearch: React.FC = () => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const [selectedPatientUuid, setSelectedPatientUuid] = useState<string | null>(null);
|
|
16
|
+
const [isInitialLoad, setIsInitialLoad] = useState(false);
|
|
17
|
+
const { deposits, isLoading } = useBillDeposit(selectedPatientUuid);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (selectedPatientUuid) {
|
|
21
|
+
setIsInitialLoad(true);
|
|
22
|
+
}
|
|
23
|
+
}, [selectedPatientUuid]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!isLoading && isInitialLoad) {
|
|
27
|
+
setIsInitialLoad(false);
|
|
28
|
+
}
|
|
29
|
+
}, [isLoading, isInitialLoad]);
|
|
30
|
+
|
|
31
|
+
const formattedDeposits = deposits.map((deposit) => ({
|
|
32
|
+
id: deposit.uuid,
|
|
33
|
+
depositType: deposit.depositType,
|
|
34
|
+
amount: deposit.amount,
|
|
35
|
+
status: deposit.status,
|
|
36
|
+
dateCreated: formatDate(parseDate(deposit.dateCreated)),
|
|
37
|
+
referenceNumber: deposit.referenceNumber,
|
|
38
|
+
availableBalance: deposit.availableBalance,
|
|
39
|
+
patient: deposit.patient,
|
|
40
|
+
description: deposit.description,
|
|
41
|
+
transactions: deposit.transactions,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const handleLaunchDepositForm = () => {
|
|
45
|
+
launchWorkspace('add-deposit-workspace', {
|
|
46
|
+
patientUuid: selectedPatientUuid,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handlePatientSelect = (uuid: string) => {
|
|
51
|
+
setSelectedPatientUuid(uuid);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const renderContent = () => {
|
|
55
|
+
if (!selectedPatientUuid) {
|
|
56
|
+
return <EmptyPatientBill />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isInitialLoad) {
|
|
60
|
+
return (
|
|
61
|
+
<div className={styles.initialLoadingContainer}>
|
|
62
|
+
<InlineLoading description={t('loading', 'Loading...')} />
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
<div className={styles.depositResultsHeader}>
|
|
70
|
+
<PatientInfo patientUuid={selectedPatientUuid} />
|
|
71
|
+
<div className={styles.actions}>
|
|
72
|
+
<Button renderIcon={Add} kind="ghost" onClick={handleLaunchDepositForm}>
|
|
73
|
+
{t('newDeposit', 'New Deposit')}
|
|
74
|
+
</Button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
{deposits.length === 0 ? (
|
|
78
|
+
<EmptyPatientBill
|
|
79
|
+
launchForm={handleLaunchDepositForm}
|
|
80
|
+
buttonText={t('newDeposit', 'New Deposit')}
|
|
81
|
+
title={t('noDepositsFound', 'No deposits found')}
|
|
82
|
+
subTitle={t('createNewDeposit', 'Create a new deposit for this patient')}
|
|
83
|
+
/>
|
|
84
|
+
) : (
|
|
85
|
+
<DepositTable deposits={formattedDeposits} />
|
|
86
|
+
)}
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className={styles.billDepositSearchContainer}>
|
|
93
|
+
<PatientSearch onPatientSelect={handlePatientSelect} />
|
|
94
|
+
<div className={styles.searchResultsContainer}>
|
|
95
|
+
{renderContent()}
|
|
96
|
+
{isLoading && !isInitialLoad && (
|
|
97
|
+
<div className={styles.loadingOverlay}>
|
|
98
|
+
<InlineLoading description={t('loading', 'Loading...')} />
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default BillDepositSearch;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/colors';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.searchResultsContainer {
|
|
6
|
+
margin-top: layout.$spacing-05;
|
|
7
|
+
position: relative;
|
|
8
|
+
min-height: 200px;
|
|
9
|
+
padding: layout.$spacing-03;
|
|
10
|
+
border: 1px solid colors.$gray-20;
|
|
11
|
+
}
|
|
12
|
+
.searchBar {
|
|
13
|
+
& form {
|
|
14
|
+
border: none;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.billDepositSearchContainer {
|
|
19
|
+
background-color: colors.$white-0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.depositResultsHeader {
|
|
23
|
+
height: layout.$layout-04;
|
|
24
|
+
background-color: colors.$white-0;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
opacity: 0;
|
|
29
|
+
transform: translateY(-10px);
|
|
30
|
+
animation: fadeInDown 0.3s ease-out forwards;
|
|
31
|
+
margin-bottom: layout.$spacing-05;
|
|
32
|
+
border-bottom: 1px solid colors.$gray-20;
|
|
33
|
+
|
|
34
|
+
& .patientInfo {
|
|
35
|
+
align-items: center;
|
|
36
|
+
display: flex;
|
|
37
|
+
column-gap: layout.$spacing-03;
|
|
38
|
+
|
|
39
|
+
& .patientName {
|
|
40
|
+
@include type.type-style('heading-03');
|
|
41
|
+
color: colors.$gray-100;
|
|
42
|
+
margin: 0 layout.$spacing-03 0 layout.$spacing-05;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
& .patientGender {
|
|
46
|
+
@include type.type-style('body-compact-02');
|
|
47
|
+
color: colors.$gray-70;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
& .patientIdentifier {
|
|
51
|
+
@include type.type-style('body-compact-02');
|
|
52
|
+
color: colors.$gray-70;
|
|
53
|
+
margin: 0 layout.$spacing-03;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
& .actions {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.initialLoadingContainer {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
min-height: 200px;
|
|
68
|
+
width: 100%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.loadingOverlay {
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: 0;
|
|
74
|
+
left: 0;
|
|
75
|
+
right: 0;
|
|
76
|
+
bottom: 0;
|
|
77
|
+
background: rgba(255, 255, 255, 0.8);
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
z-index: 1;
|
|
82
|
+
backdrop-filter: blur(2px);
|
|
83
|
+
transition: opacity 0.2s ease-in-out;
|
|
84
|
+
opacity: 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@keyframes fadeInDown {
|
|
88
|
+
from {
|
|
89
|
+
opacity: 0;
|
|
90
|
+
transform: translateY(-10px);
|
|
91
|
+
}
|
|
92
|
+
to {
|
|
93
|
+
opacity: 1;
|
|
94
|
+
transform: translateY(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes fadeInUp {
|
|
99
|
+
from {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
transform: translateY(10px);
|
|
102
|
+
}
|
|
103
|
+
to {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
transform: translateY(0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
DataTable,
|
|
5
|
+
Table,
|
|
6
|
+
TableHead,
|
|
7
|
+
TableRow,
|
|
8
|
+
TableHeader,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableExpandHeader,
|
|
12
|
+
TableExpandRow,
|
|
13
|
+
TableExpandedRow,
|
|
14
|
+
OverflowMenu,
|
|
15
|
+
OverflowMenuItem,
|
|
16
|
+
Pagination,
|
|
17
|
+
} from '@carbon/react';
|
|
18
|
+
import { launchWorkspace, showModal, usePagination } from '@openmrs/esm-framework';
|
|
19
|
+
import { usePaginationInfo } from '@openmrs/esm-patient-common-lib';
|
|
20
|
+
import { type FormattedDeposit } from '../../../types/bill-deposit.types';
|
|
21
|
+
import { BILL_DEPOSIT_STATUS } from '../../../constants/bill-deposit.constants';
|
|
22
|
+
import TransactionList from './transaction-list/transaction-list.component';
|
|
23
|
+
|
|
24
|
+
interface DepositTableProps {
|
|
25
|
+
deposits: Array<FormattedDeposit>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DepositTable: React.FC<DepositTableProps> = ({ deposits }) => {
|
|
29
|
+
const { t } = useTranslation();
|
|
30
|
+
const [pageSize, setPageSize] = useState(10);
|
|
31
|
+
const { results, totalPages, currentPage, goTo } = usePagination(deposits, pageSize);
|
|
32
|
+
const { pageSizes } = usePaginationInfo(pageSize, totalPages, currentPage, results.length);
|
|
33
|
+
|
|
34
|
+
const headers = [
|
|
35
|
+
{ header: t('dateCreated', 'Date Created'), key: 'dateCreated' },
|
|
36
|
+
{ header: t('referenceNumber', 'Reference Number'), key: 'referenceNumber' },
|
|
37
|
+
{ header: t('depositType', 'Deposit Type'), key: 'depositType' },
|
|
38
|
+
{ header: t('amount', 'Amount'), key: 'amount' },
|
|
39
|
+
{ header: t('availableBalance', 'Available Balance'), key: 'availableBalance' },
|
|
40
|
+
{ header: t('status', 'Status'), key: 'status' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const handleEditDeposit = (deposit: FormattedDeposit) => {
|
|
44
|
+
launchWorkspace('add-deposit-workspace', {
|
|
45
|
+
deposit: { ...deposit, uuid: deposit.id },
|
|
46
|
+
patientUuid: deposit?.patient?.uuid,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleDeleteDeposit = (deposit: FormattedDeposit) => {
|
|
51
|
+
const dispose = showModal('delete-deposit-modal', {
|
|
52
|
+
depositUuid: deposit.id,
|
|
53
|
+
onClose: () => dispose(),
|
|
54
|
+
isOpen: true,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleApplyDepositToBill = (deposit: FormattedDeposit) => {
|
|
59
|
+
launchWorkspace('deposit-transaction-workspace', {
|
|
60
|
+
deposit: { ...deposit, uuid: deposit.id },
|
|
61
|
+
patientUuid: deposit?.patient?.uuid,
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<>
|
|
67
|
+
<DataTable size="sm" useZebraStyles rows={results} headers={headers} isExpandable>
|
|
68
|
+
{({
|
|
69
|
+
rows,
|
|
70
|
+
headers,
|
|
71
|
+
getTableProps,
|
|
72
|
+
getHeaderProps,
|
|
73
|
+
getRowProps,
|
|
74
|
+
getTableContainerProps,
|
|
75
|
+
getExpandHeaderProps,
|
|
76
|
+
}) => (
|
|
77
|
+
<Table {...getTableProps()}>
|
|
78
|
+
<TableHead>
|
|
79
|
+
<TableRow>
|
|
80
|
+
<TableExpandHeader {...getExpandHeaderProps()} />
|
|
81
|
+
{headers.map((header) => (
|
|
82
|
+
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
83
|
+
{header.header}
|
|
84
|
+
</TableHeader>
|
|
85
|
+
))}
|
|
86
|
+
<TableHeader aria-label={t('overflowMenu', 'Overflow menu')} />
|
|
87
|
+
</TableRow>
|
|
88
|
+
</TableHead>
|
|
89
|
+
<TableBody>
|
|
90
|
+
{rows.map((row, index) => (
|
|
91
|
+
<React.Fragment key={row.id}>
|
|
92
|
+
<TableExpandRow {...getRowProps({ row })} isExpanded={row.isExpanded}>
|
|
93
|
+
{row.cells.map((cell) => (
|
|
94
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
95
|
+
))}
|
|
96
|
+
<TableCell className="cds--table-column-menu">
|
|
97
|
+
<OverflowMenu size="sm" flipped>
|
|
98
|
+
{deposits[index].status !== BILL_DEPOSIT_STATUS.USED && (
|
|
99
|
+
<>
|
|
100
|
+
<OverflowMenuItem
|
|
101
|
+
itemText={t('applyDepositToBill', 'Apply Deposit to Bill')}
|
|
102
|
+
onClick={() => handleApplyDepositToBill(deposits[index])}
|
|
103
|
+
/>
|
|
104
|
+
<OverflowMenuItem
|
|
105
|
+
itemText={t('editDeposit', 'Edit Deposit')}
|
|
106
|
+
onClick={() => handleEditDeposit(deposits[index])}
|
|
107
|
+
/>
|
|
108
|
+
<OverflowMenuItem
|
|
109
|
+
hasDivider
|
|
110
|
+
isDelete
|
|
111
|
+
itemText={t('deleteDeposit', 'Delete Deposit')}
|
|
112
|
+
onClick={() => handleDeleteDeposit(deposits[index])}
|
|
113
|
+
/>
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
</OverflowMenu>
|
|
117
|
+
</TableCell>
|
|
118
|
+
</TableExpandRow>
|
|
119
|
+
{row.isExpanded && (
|
|
120
|
+
<TableExpandedRow colSpan={headers.length + 2}>
|
|
121
|
+
<TransactionList transactions={deposits[index].transactions} depositUuid={deposits[index].id} />
|
|
122
|
+
</TableExpandedRow>
|
|
123
|
+
)}
|
|
124
|
+
</React.Fragment>
|
|
125
|
+
))}
|
|
126
|
+
</TableBody>
|
|
127
|
+
</Table>
|
|
128
|
+
)}
|
|
129
|
+
</DataTable>
|
|
130
|
+
|
|
131
|
+
<Pagination
|
|
132
|
+
backwardText="Previous page"
|
|
133
|
+
forwardText="Next page"
|
|
134
|
+
itemsPerPageText="Items per page:"
|
|
135
|
+
page={currentPage}
|
|
136
|
+
pageNumberText="Page Number"
|
|
137
|
+
pageSize={pageSize}
|
|
138
|
+
pageSizes={pageSizes}
|
|
139
|
+
size="md"
|
|
140
|
+
totalItems={deposits.length}
|
|
141
|
+
onChange={({ page, pageSize: newPageSize }) => {
|
|
142
|
+
goTo(page);
|
|
143
|
+
setPageSize(newPageSize);
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export default DepositTable;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { InlineLoading } from '@carbon/react';
|
|
3
|
+
import { usePatient, getPatientName, ErrorState } from '@openmrs/esm-framework';
|
|
4
|
+
import styles from '../bill-deposit-search.scss';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import capitalize from 'lodash-es/capitalize';
|
|
7
|
+
|
|
8
|
+
interface PatientInfoProps {
|
|
9
|
+
patientUuid: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PatientInfo: React.FC<PatientInfoProps> = ({ patientUuid }) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const { patient, isLoading, error } = usePatient(patientUuid);
|
|
15
|
+
|
|
16
|
+
const patientName = patient ? getPatientName(patient) : '--';
|
|
17
|
+
const patientGender = capitalize(patient?.gender ?? '--');
|
|
18
|
+
const patientIdentifier = Array.isArray(patient?.identifier)
|
|
19
|
+
? patient?.identifier[0]?.value ?? '--'
|
|
20
|
+
: patient?.identifier ?? '--';
|
|
21
|
+
|
|
22
|
+
if (isLoading) {
|
|
23
|
+
return <InlineLoading description={t('loading', 'Loading...')} />;
|
|
24
|
+
}
|
|
25
|
+
if (error || !patient) {
|
|
26
|
+
return <ErrorState error={error} headerTitle={t('error', 'Error')} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={styles.patientInfo}>
|
|
31
|
+
<span className={styles.patientName}>{patientName}</span>
|
|
32
|
+
<span className={styles.patientGender}>• {patientGender} •</span>
|
|
33
|
+
<span className={styles.patientIdentifier}>{patientIdentifier}</span>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default PatientInfo;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ExtensionSlot } from '@openmrs/esm-framework';
|
|
3
|
+
import styles from '../bill-deposit-search.scss';
|
|
4
|
+
|
|
5
|
+
interface PatientSearchProps {
|
|
6
|
+
onPatientSelect: (uuid: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PatientSearch: React.FC<PatientSearchProps> = ({ onPatientSelect }) => {
|
|
10
|
+
return (
|
|
11
|
+
<ExtensionSlot
|
|
12
|
+
className={styles.searchBar}
|
|
13
|
+
name="patient-search-bar-slot"
|
|
14
|
+
state={{
|
|
15
|
+
selectPatientAction: onPatientSelect,
|
|
16
|
+
buttonProps: {
|
|
17
|
+
kind: 'secondary',
|
|
18
|
+
},
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default PatientSearch;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { ContainedList, ContainedListItem, OverflowMenu, OverflowMenuItem, InlineNotification } from '@carbon/react';
|
|
4
|
+
import { BillDepositTransaction } from '../../../../types/bill-deposit.types';
|
|
5
|
+
import { formatDate, parseDate, showModal } from '@openmrs/esm-framework';
|
|
6
|
+
import { convertToCurrency } from '../../../../../helpers';
|
|
7
|
+
import styles from './transaction-list.scss';
|
|
8
|
+
|
|
9
|
+
type TransactionListProps = {
|
|
10
|
+
transactions: Array<BillDepositTransaction>;
|
|
11
|
+
depositUuid: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const TransactionList: React.FC<TransactionListProps> = ({ transactions, depositUuid }) => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
|
|
17
|
+
const handleReverse = (transaction: BillDepositTransaction) => {
|
|
18
|
+
const dispose = showModal('reverse-transaction-modal', {
|
|
19
|
+
depositUuid: depositUuid,
|
|
20
|
+
transaction,
|
|
21
|
+
onClose: () => dispose(),
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const filterTransactions = (transactions: Array<BillDepositTransaction>) => {
|
|
26
|
+
return transactions.filter((transaction) => transaction.voided === false);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (filterTransactions(transactions).length === 0) {
|
|
30
|
+
return (
|
|
31
|
+
<InlineNotification
|
|
32
|
+
aria-label="closes notification"
|
|
33
|
+
kind="error"
|
|
34
|
+
lowContrast
|
|
35
|
+
statusIconDescription="notification"
|
|
36
|
+
subtitle={t('noTransactions', 'No transactions found')}
|
|
37
|
+
title={t('noTransactions', 'No transactions found')}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ContainedList
|
|
44
|
+
className={styles.transactionListContainer}
|
|
45
|
+
size="sm"
|
|
46
|
+
label={t('transactionList', 'Transaction List')}>
|
|
47
|
+
{filterTransactions(transactions).map((transaction) => (
|
|
48
|
+
<ContainedListItem
|
|
49
|
+
key={transaction.uuid}
|
|
50
|
+
action={
|
|
51
|
+
<OverflowMenu flipped size="sm" ariaLabel={t('transactionListOptions', 'Transaction list options')}>
|
|
52
|
+
<OverflowMenuItem onClick={() => handleReverse(transaction)} itemText={t('reverse', 'Reverse')} />
|
|
53
|
+
<OverflowMenuItem itemText={t('remove', 'Remove')} isDelete hasDivider />
|
|
54
|
+
</OverflowMenu>
|
|
55
|
+
}>
|
|
56
|
+
{`${formatDate(parseDate(transaction.dateCreated))} - ${transaction.transactionType} - ${convertToCurrency(
|
|
57
|
+
transaction.amount,
|
|
58
|
+
)}${transaction.reason ? ' - ' + transaction.reason : ''}`}
|
|
59
|
+
</ContainedListItem>
|
|
60
|
+
))}
|
|
61
|
+
</ContainedList>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default TransactionList;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const BILL_DEPOSIT_STATUS = {
|
|
2
|
+
PENDING: 'PENDING',
|
|
3
|
+
ACTIVE: 'ACTIVE',
|
|
4
|
+
USED: 'USED',
|
|
5
|
+
REFUNDED: 'REFUNDED',
|
|
6
|
+
VOIDED: 'VOIDED',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export const BILL_DEPOSIT_TYPES = {
|
|
10
|
+
CASH: 'CASH',
|
|
11
|
+
INSURANCE: 'INSURANCE',
|
|
12
|
+
MOBILE_MONEY: 'MOBILE_MONEY',
|
|
13
|
+
WAIVER: 'WAIVER',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const CUSTOM_REPRESENTATION =
|
|
17
|
+
'custom:(uuid,display,voided,patient:(uuid,display,person,identifiers:(uuid,display,identifier)),amount,depositType,status,referenceNumber,description,transactions:full,dateCreated,availableBalance)';
|
|
18
|
+
|
|
19
|
+
export const MAX_REFERENCE_NUMBER_COUNTER = 999;
|
|
20
|
+
|
|
21
|
+
export const BILL_DEPOSIT_TRANSACTION_TYPES = {
|
|
22
|
+
APPLY: 'APPLY',
|
|
23
|
+
REFUND: 'REFUND',
|
|
24
|
+
TRANSFER: 'REVERSE',
|
|
25
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { type BillDeposit, type CreateDepositPayload } from '../types/bill-deposit.types';
|
|
4
|
+
import { CUSTOM_REPRESENTATION } from '../constants/bill-deposit.constants';
|
|
5
|
+
import { saveDeposit, deleteDeposit } from '../utils/bill-deposit.utils';
|
|
6
|
+
|
|
7
|
+
export const useBillDeposit = (patientUuid: string) => {
|
|
8
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: { results: Array<BillDeposit> } }>(
|
|
9
|
+
patientUuid
|
|
10
|
+
? `${restBaseUrl}/cashier/deposit?patient=${patientUuid}&includeAll=false&v=${CUSTOM_REPRESENTATION}`
|
|
11
|
+
: null,
|
|
12
|
+
openmrsFetch,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const deposits = data?.data.results ?? [];
|
|
16
|
+
|
|
17
|
+
const createDeposit = async (deposit: CreateDepositPayload) => {
|
|
18
|
+
const response = await saveDeposit(deposit);
|
|
19
|
+
await mutate();
|
|
20
|
+
return response;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const updateDeposit = async (deposit: Partial<BillDeposit>, uuid: string) => {
|
|
24
|
+
const response = await saveDeposit(deposit, uuid);
|
|
25
|
+
await mutate();
|
|
26
|
+
return response;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const removeDeposit = async (uuid: string) => {
|
|
30
|
+
const response = await deleteDeposit(uuid);
|
|
31
|
+
await mutate();
|
|
32
|
+
return response;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
deposits,
|
|
37
|
+
isLoading,
|
|
38
|
+
error,
|
|
39
|
+
createDeposit,
|
|
40
|
+
updateDeposit,
|
|
41
|
+
removeDeposit,
|
|
42
|
+
};
|
|
43
|
+
};
|