@kenyaemr/esm-billing-app 5.4.2-pre.2548 → 5.4.2-pre.2555
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 +4 -4
- package/dist/{113.js → 570.js} +1 -1
- package/dist/570.js.map +1 -0
- package/dist/kenyaemr-esm-billing-app.js +2 -2
- package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +27 -27
- 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/billing-dashboard/billing-dashboard.component.tsx +20 -10
- package/src/bills-table/bills-table.component.tsx +1 -1
- package/src/claims/dashboard/form/claims-form.component.tsx +281 -63
- package/src/config-schema.ts +6 -0
- package/src/hooks/useOTP.ts +292 -0
- package/src/hooks/usePhoneNumber.ts +35 -0
- package/src/invoice/invoice-actions.component.tsx +2 -2
- package/src/left-panel-link.component.tsx +42 -15
- package/src/root.component.tsx +1 -1
- package/src/types/index.ts +35 -0
- package/dist/113.js.map +0 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { OtpPayload, OtpResponse, ClaimSummary } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a random OTP of a specified length.
|
|
6
|
+
*/
|
|
7
|
+
export function generateOTP(length = 5) {
|
|
8
|
+
let otpNumbers = '0123456789';
|
|
9
|
+
let OTP = '';
|
|
10
|
+
const len = otpNumbers.length;
|
|
11
|
+
for (let i = 0; i < length; i++) {
|
|
12
|
+
OTP += otpNumbers[Math.floor(Math.random() * len)];
|
|
13
|
+
}
|
|
14
|
+
return OTP;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Replaces placeholders in a template string with values from a given context.
|
|
19
|
+
*/
|
|
20
|
+
export function parseMessage<T extends Record<string, string | number>>(context: T, template: string): string {
|
|
21
|
+
if (!template?.trim()) {
|
|
22
|
+
throw new Error('Template must be a non-empty string');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const placeholderRegex = /\{\{([^{}]+)\}\}/g;
|
|
26
|
+
|
|
27
|
+
return template.replace(placeholderRegex, (match, key: string) => {
|
|
28
|
+
const trimmedKey = key.trim();
|
|
29
|
+
return trimmedKey in context ? String(context[trimmedKey]) : match;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds a URL for sending an SMS message.
|
|
35
|
+
*/
|
|
36
|
+
function buildSmsUrl(message: string, receiver: string): string {
|
|
37
|
+
const encodedMessage = encodeURIComponent(message);
|
|
38
|
+
return `${restBaseUrl}/kenyaemr/send-kenyaemr-sms?message=${encodedMessage}&phone=${receiver}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validates that the required parameters for sending an OTP message are present.
|
|
43
|
+
*/
|
|
44
|
+
function validateOtpInputs(otp: string, receiver: string, patientName: string): void {
|
|
45
|
+
if (!otp?.trim() || !receiver?.trim() || !patientName?.trim()) {
|
|
46
|
+
throw new Error('Missing required parameters: otp, receiver, or patientName');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sends an OTP message to a patient's phone number with claim details.
|
|
52
|
+
*/
|
|
53
|
+
export async function sendOtp(
|
|
54
|
+
payload: OtpPayload,
|
|
55
|
+
patientName: string,
|
|
56
|
+
claimSummary: ClaimSummary,
|
|
57
|
+
expiryMinutes: number = 5,
|
|
58
|
+
): Promise<OtpResponse> {
|
|
59
|
+
const { otp, receiver } = payload;
|
|
60
|
+
validateOtpInputs(otp, receiver, patientName);
|
|
61
|
+
|
|
62
|
+
const otpContext = {
|
|
63
|
+
patientName: patientName,
|
|
64
|
+
claimAmount: `KES ${claimSummary.totalAmount.toLocaleString()}`,
|
|
65
|
+
servicesSummary:
|
|
66
|
+
claimSummary.services.length > 100 ? claimSummary.services.substring(0, 97) + '...' : claimSummary.services,
|
|
67
|
+
startDate: claimSummary.startDate,
|
|
68
|
+
endDate: claimSummary.endDate,
|
|
69
|
+
facility: claimSummary.facility,
|
|
70
|
+
expiryTime: expiryMinutes,
|
|
71
|
+
otp: otp,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const claimConsentTemplate =
|
|
75
|
+
'Dear {{patientName}}, ' +
|
|
76
|
+
'We are submitting a claim to your insurance for services provided from {{startDate}} to {{endDate}} at {{facility}}. ' +
|
|
77
|
+
'Total claim amount: {{claimAmount}}. ' +
|
|
78
|
+
'Services: {{servicesSummary}}. ' +
|
|
79
|
+
'Your OTP for consent is {{otp}} (valid {{expiryTime}} mins). ';
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const message = parseMessage(otpContext, claimConsentTemplate);
|
|
83
|
+
const url = buildSmsUrl(message, receiver);
|
|
84
|
+
|
|
85
|
+
const response = await openmrsFetch(url, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
redirect: 'follow',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return response.json();
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
97
|
+
throw new Error(`Failed to send OTP: ${errorMessage}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class OTPManager {
|
|
102
|
+
private otpStore: Map<string, { otp: string; timestamp: number; attempts: number; expiryTime: number }> = new Map();
|
|
103
|
+
private readonly MAX_ATTEMPTS = 3;
|
|
104
|
+
private readonly DEFAULT_EXPIRY_TIME = 5 * 60 * 1000;
|
|
105
|
+
|
|
106
|
+
async requestOTP(
|
|
107
|
+
phoneNumber: string,
|
|
108
|
+
patientName: string,
|
|
109
|
+
claimSummary: ClaimSummary,
|
|
110
|
+
expiryMinutes: number = this.DEFAULT_EXPIRY_TIME,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const otp = generateOTP(5);
|
|
113
|
+
const expiryTime = expiryMinutes * 60 * 1000;
|
|
114
|
+
|
|
115
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
116
|
+
|
|
117
|
+
const otpData = {
|
|
118
|
+
otp,
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
attempts: 0,
|
|
121
|
+
expiryTime,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.otpStore.set(normalizedPhone, otpData);
|
|
125
|
+
|
|
126
|
+
await sendOtp({ otp, receiver: phoneNumber }, patientName, claimSummary, expiryMinutes);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async verifyOTP(phoneNumber: string, inputOtp: string): Promise<boolean> {
|
|
130
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
131
|
+
|
|
132
|
+
const storedData = this.otpStore.get(normalizedPhone);
|
|
133
|
+
|
|
134
|
+
if (!storedData) {
|
|
135
|
+
throw new Error('No OTP found for this phone number. Please request a new OTP.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Date.now() - storedData.timestamp > storedData.expiryTime) {
|
|
139
|
+
this.otpStore.delete(normalizedPhone);
|
|
140
|
+
throw new Error('OTP has expired. Please request a new OTP.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
storedData.attempts++;
|
|
144
|
+
|
|
145
|
+
if (storedData.attempts > this.MAX_ATTEMPTS) {
|
|
146
|
+
this.otpStore.delete(normalizedPhone);
|
|
147
|
+
throw new Error('Maximum OTP attempts exceeded. Please request a new OTP.');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (storedData.otp === inputOtp.trim()) {
|
|
151
|
+
this.otpStore.delete(normalizedPhone);
|
|
152
|
+
return true;
|
|
153
|
+
} else {
|
|
154
|
+
this.otpStore.set(normalizedPhone, storedData);
|
|
155
|
+
throw new Error(`Invalid OTP. ${this.MAX_ATTEMPTS - storedData.attempts} attempts remaining.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
clearOTP(phoneNumber: string): void {
|
|
160
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
161
|
+
this.otpStore.delete(normalizedPhone);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
clearAllOTPs(): void {
|
|
165
|
+
this.otpStore.clear();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
cleanupExpiredOTPs(): void {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
for (const [phoneNumber, data] of this.otpStore.entries()) {
|
|
171
|
+
if (now - data.timestamp > data.expiryTime) {
|
|
172
|
+
this.otpStore.delete(phoneNumber);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
hasValidOTP(phoneNumber: string): boolean {
|
|
178
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
179
|
+
const storedData = this.otpStore.get(normalizedPhone);
|
|
180
|
+
if (!storedData) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return Date.now() - storedData.timestamp <= storedData.expiryTime;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getRemainingTimeMinutes(phoneNumber: string): number {
|
|
188
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
189
|
+
const storedData = this.otpStore.get(normalizedPhone);
|
|
190
|
+
if (!storedData) {
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const elapsed = Date.now() - storedData.timestamp;
|
|
195
|
+
const remaining = Math.max(0, storedData.expiryTime - elapsed);
|
|
196
|
+
return Math.ceil(remaining / (60 * 1000));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getRemainingAttempts(phoneNumber: string): number {
|
|
200
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
201
|
+
const storedData = this.otpStore.get(normalizedPhone);
|
|
202
|
+
if (!storedData) {
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return Math.max(0, this.MAX_ATTEMPTS - storedData.attempts);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getStoredOTP(phoneNumber: string): string | null {
|
|
210
|
+
const normalizedPhone = this.normalizePhoneNumber(phoneNumber);
|
|
211
|
+
const storedData = this.otpStore.get(normalizedPhone);
|
|
212
|
+
return storedData ? storedData.otp : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
transferOTP(oldPhoneNumber: string, newPhoneNumber: string): boolean {
|
|
216
|
+
const oldNormalized = this.normalizePhoneNumber(oldPhoneNumber);
|
|
217
|
+
const newNormalized = this.normalizePhoneNumber(newPhoneNumber);
|
|
218
|
+
|
|
219
|
+
if (oldNormalized === newNormalized) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const storedData = this.otpStore.get(oldNormalized);
|
|
224
|
+
if (!storedData) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.otpStore.set(newNormalized, storedData);
|
|
229
|
+
this.otpStore.delete(oldNormalized);
|
|
230
|
+
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private normalizePhoneNumber(phoneNumber: string): string {
|
|
235
|
+
if (!phoneNumber) {
|
|
236
|
+
return '';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let normalized = phoneNumber.replace(/\D/g, '');
|
|
240
|
+
|
|
241
|
+
if (normalized.startsWith('254') && normalized.length === 12) {
|
|
242
|
+
return normalized;
|
|
243
|
+
} else if (normalized.startsWith('0') && normalized.length === 10) {
|
|
244
|
+
return '254' + normalized.substring(1);
|
|
245
|
+
} else if (normalized.length === 9) {
|
|
246
|
+
return '254' + normalized;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return normalized;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export const otpManager = new OTPManager();
|
|
254
|
+
|
|
255
|
+
setInterval(() => {
|
|
256
|
+
otpManager.cleanupExpiredOTPs();
|
|
257
|
+
}, 2 * 60 * 1000);
|
|
258
|
+
|
|
259
|
+
export function createOtpHandlers(patientName: string, expiryMinutes: number) {
|
|
260
|
+
return {
|
|
261
|
+
onRequestOtp: async (phone: string, claimSummary: ClaimSummary): Promise<void> => {
|
|
262
|
+
await otpManager.requestOTP(phone, patientName, claimSummary, expiryMinutes);
|
|
263
|
+
},
|
|
264
|
+
onVerify: async (otp: string, phoneNumber: string): Promise<void> => {
|
|
265
|
+
const isValid = await otpManager.verifyOTP(phoneNumber, otp);
|
|
266
|
+
if (!isValid) {
|
|
267
|
+
throw new Error('OTP verification failed');
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
hasValidOTP: (phone: string): boolean => {
|
|
271
|
+
return otpManager.hasValidOTP(phone);
|
|
272
|
+
},
|
|
273
|
+
getRemainingTime: (phone: string): number => {
|
|
274
|
+
return otpManager.getRemainingTimeMinutes(phone);
|
|
275
|
+
},
|
|
276
|
+
getRemainingAttempts: (phone: string): number => {
|
|
277
|
+
return otpManager.getRemainingAttempts(phone);
|
|
278
|
+
},
|
|
279
|
+
clearOTP: (phone: string): void => {
|
|
280
|
+
otpManager.clearOTP(phone);
|
|
281
|
+
},
|
|
282
|
+
clearAllOTPs: (): void => {
|
|
283
|
+
otpManager.clearAllOTPs();
|
|
284
|
+
},
|
|
285
|
+
getStoredOTP: (phone: string): string | null => {
|
|
286
|
+
return otpManager.getStoredOTP(phone);
|
|
287
|
+
},
|
|
288
|
+
transferOTP: (oldPhone: string, newPhone: string): boolean => {
|
|
289
|
+
return otpManager.transferOTP(oldPhone, newPhone);
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { BillingConfig } from '../config-schema';
|
|
4
|
+
|
|
5
|
+
interface PersonAttribute {
|
|
6
|
+
uuid: string;
|
|
7
|
+
value: string;
|
|
8
|
+
attributeType?: {
|
|
9
|
+
uuid: string;
|
|
10
|
+
display: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface PersonAttributeResponse {
|
|
15
|
+
results: PersonAttribute[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const usePhoneNumberAttribute = (patientUuid: string) => {
|
|
19
|
+
const url = `${restBaseUrl}/person/${patientUuid}/attribute?v=custom:(uuid,value,attributeType:(uuid,display))`;
|
|
20
|
+
const { phoneNumberAttributeTypeUUID } = useConfig<BillingConfig>();
|
|
21
|
+
|
|
22
|
+
const { data, isLoading, error } = useSWR<{ data: PersonAttributeResponse }>(patientUuid ? url : null, openmrsFetch);
|
|
23
|
+
|
|
24
|
+
const phoneNumberAttribute = data?.data?.results?.find(
|
|
25
|
+
(attr) => attr.uuid === phoneNumberAttributeTypeUUID || attr.attributeType?.uuid === phoneNumberAttributeTypeUUID,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
attribute: phoneNumberAttribute,
|
|
30
|
+
phoneNumber: phoneNumberAttribute?.value || null,
|
|
31
|
+
isLoading,
|
|
32
|
+
error,
|
|
33
|
+
allAttributes: data?.data?.results,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -124,9 +124,9 @@ export function InvoiceActions({ bill, selectedLineItems = [], activeVisit }: In
|
|
|
124
124
|
|
|
125
125
|
if (activeVisit) {
|
|
126
126
|
await handleEndVisit();
|
|
127
|
-
navigate({ to: `${spaBasePath}/
|
|
127
|
+
navigate({ to: `${spaBasePath}/accounting/patient/${patientUuid}/${billUuid}/claims` });
|
|
128
128
|
} else {
|
|
129
|
-
navigate({ to: `${spaBasePath}/
|
|
129
|
+
navigate({ to: `${spaBasePath}/accounting/patient/${patientUuid}/${billUuid}/claims` });
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
@@ -16,37 +16,64 @@ export function LinkExtension({ config }: { config: LinkConfig }) {
|
|
|
16
16
|
const location = useLocation();
|
|
17
17
|
const spaBasePath = window.getOpenmrsSpaBase() + 'home';
|
|
18
18
|
|
|
19
|
-
let urlSegment = useMemo(() =>
|
|
19
|
+
let urlSegment = useMemo(() => {
|
|
20
|
+
const rawSegment = last(location.pathname.split('/'));
|
|
21
|
+
const decodedSegment = decodeURIComponent(rawSegment);
|
|
22
|
+
return decodedSegment;
|
|
23
|
+
}, [location.pathname]);
|
|
20
24
|
|
|
21
25
|
const isUUID = (value: string) => {
|
|
22
26
|
const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
|
23
|
-
|
|
27
|
+
const result = regex.test(value);
|
|
28
|
+
return result;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
if (isUUID(urlSegment)) {
|
|
27
32
|
if (location.pathname.includes('payment-points')) {
|
|
28
|
-
|
|
33
|
+
const newSegment = location.pathname.split('/').at(-2);
|
|
34
|
+
urlSegment = newSegment;
|
|
29
35
|
} else {
|
|
30
|
-
|
|
36
|
+
const pathParts = location.pathname.split('/');
|
|
37
|
+
const mainSectionIndex = pathParts.findIndex((part) => part === nameSegment);
|
|
38
|
+
|
|
39
|
+
if (mainSectionIndex > -1) {
|
|
40
|
+
urlSegment = nameSegment;
|
|
41
|
+
} else {
|
|
42
|
+
const containsOurSection =
|
|
43
|
+
location.pathname.includes('/' + nameSegment + '/') || location.pathname.includes('/' + nameSegment);
|
|
44
|
+
if (containsOurSection) {
|
|
45
|
+
urlSegment = nameSegment;
|
|
46
|
+
} else {
|
|
47
|
+
urlSegment = '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
31
50
|
}
|
|
32
51
|
} else if (location.pathname.endsWith('claims') || location.pathname.endsWith('claims/')) {
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const containsOurSection =
|
|
53
|
+
location.pathname.includes('/' + nameSegment + '/') || location.pathname.split('/').includes(nameSegment);
|
|
54
|
+
if (containsOurSection) {
|
|
55
|
+
urlSegment = nameSegment;
|
|
56
|
+
} else {
|
|
57
|
+
urlSegment = '';
|
|
58
|
+
}
|
|
35
59
|
}
|
|
36
60
|
|
|
37
61
|
const isActive = nameSegment === urlSegment;
|
|
62
|
+
const finalUrl = spaBasePath + '/' + name;
|
|
63
|
+
|
|
38
64
|
return (
|
|
39
|
-
<ConfigurableLink
|
|
40
|
-
to={spaBasePath + '/' + name}
|
|
41
|
-
className={`cds--side-nav__link ${isActive && 'active-left-nav-link'}`}>
|
|
65
|
+
<ConfigurableLink to={finalUrl} className={`cds--side-nav__link ${isActive && 'active-left-nav-link'}`}>
|
|
42
66
|
{t(title)}
|
|
43
67
|
</ConfigurableLink>
|
|
44
68
|
);
|
|
45
69
|
}
|
|
46
70
|
|
|
47
|
-
export const createLeftPanelLink = (config: LinkConfig) =>
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
export const createLeftPanelLink = (config: LinkConfig) => {
|
|
72
|
+
return () => {
|
|
73
|
+
return (
|
|
74
|
+
<BrowserRouter>
|
|
75
|
+
<LinkExtension config={config} />
|
|
76
|
+
</BrowserRouter>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
};
|
package/src/root.component.tsx
CHANGED
|
@@ -16,7 +16,7 @@ import { PaymentPoints } from './payment-points/payment-points.component';
|
|
|
16
16
|
import BillDepositDashboard from './bill-deposit/components/dashboard/bill-deposit-dashboard.component';
|
|
17
17
|
|
|
18
18
|
const RootComponent: React.FC = () => {
|
|
19
|
-
const baseName = window.getOpenmrsSpaBase() + 'home/
|
|
19
|
+
const baseName = window.getOpenmrsSpaBase() + 'home/accounting';
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
22
|
<BrowserRouter basename={baseName}>
|
package/src/types/index.ts
CHANGED
|
@@ -631,3 +631,38 @@ export interface ProgressTracker {
|
|
|
631
631
|
export interface checkSHARegNumResponse {
|
|
632
632
|
registrationNumber: string;
|
|
633
633
|
}
|
|
634
|
+
export type OTPVerificationModalOptions = {
|
|
635
|
+
otpLength?: number;
|
|
636
|
+
onVerify?: (otp: string) => Promise<void>;
|
|
637
|
+
obscureText?: boolean;
|
|
638
|
+
centerBoxes?: boolean;
|
|
639
|
+
phoneNumber: string;
|
|
640
|
+
onRequestOtp?: (phoneNumber: string) => Promise<void>;
|
|
641
|
+
onVerificationSuccess?: () => void;
|
|
642
|
+
expiryMinutes?: number;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
export interface OtpPayload {
|
|
646
|
+
otp: string;
|
|
647
|
+
receiver: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export interface OtpContext extends Record<string, string | number> {
|
|
651
|
+
otp: string;
|
|
652
|
+
patient_name: string;
|
|
653
|
+
expiry_time: number;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export interface OtpResponse {
|
|
657
|
+
success: boolean;
|
|
658
|
+
message: string;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export interface ClaimSummary {
|
|
662
|
+
totalAmount: number;
|
|
663
|
+
facility: string;
|
|
664
|
+
totalItems: number;
|
|
665
|
+
services: string;
|
|
666
|
+
startDate: string;
|
|
667
|
+
endDate: string;
|
|
668
|
+
}
|