Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit ff9e07d

Browse files
committed
Implemented reloading of working period data after BA update.
1 parent 57a794c commit ff9e07d

File tree

6 files changed

+183
-91
lines changed

6 files changed

+183
-91
lines changed

src/routes/WorkPeriods/components/PaymentModalCancel/index.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const PaymentModalCancel = ({ payment, removeModal, timeout = 3000 }) => {
8383
approveText="Mark as cancelled"
8484
dismissText="Cancel cancelling"
8585
title={title}
86+
isDisabled={isCancelPending}
8687
isOpen={isModalOpen}
8788
controls={controls}
8889
onApprove={onApprove}

src/routes/WorkPeriods/components/PaymentModalUpdateBA/index.jsx

+27-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useCallback, useEffect, useState } from "react";
2+
import { useDispatch } from "react-redux";
23
import PT from "prop-types";
34
import cn from "classnames";
45
import moment from "moment";
@@ -7,11 +8,8 @@ import Modal from "components/Modal";
78
import ProjectName from "components/ProjectName";
89
import Spinner from "components/Spinner";
910
import SelectField from "components/SelectField";
10-
import { makeToast } from "components/ToastrMessage";
11-
import {
12-
fetchBillingAccounts,
13-
patchWorkPeriodPayments,
14-
} from "services/workPeriods";
11+
import { fetchBillingAccounts } from "services/workPeriods";
12+
import { updatePaymentsBillingAccount } from "store/thunks/workPeriods";
1513
import {
1614
createAssignedBillingAccountOption,
1715
normalizeBillingAccounts,
@@ -42,6 +40,21 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
4240
const [billingAccountsDisabled, setBillingAccountsDisabled] = useState(true);
4341
const [billingAccountsError, setBillingAccountsError] = useState(null);
4442
const [isProcessing, setIsProcessing] = useState(false);
43+
const dispatch = useDispatch();
44+
45+
const accountIdMap = {};
46+
for (let payment of payments) {
47+
accountIdMap[payment.billingAccountId] = true;
48+
}
49+
const accountIds = Object.keys(accountIdMap);
50+
51+
const onApprove = useCallback(() => {
52+
setIsProcessing(true);
53+
}, []);
54+
55+
const onDismiss = useCallback(() => {
56+
setIsModalOpen(false);
57+
}, []);
4558

4659
useEffect(() => {
4760
const [bilAccsPromise] = fetchBillingAccounts(period.projectId);
@@ -93,38 +106,14 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
93106
if (!isProcessing) {
94107
return;
95108
}
96-
const paymentsUpdated = [];
97-
for (let { id } of payments) {
98-
paymentsUpdated.push({ id, billingAccountId });
99-
}
100-
patchWorkPeriodPayments(paymentsUpdated)
101-
.then(() => {
102-
makeToast(
103-
"Billing account was successfully updated for all the payments",
104-
"success"
105-
);
106-
setIsModalOpen(false);
107-
})
108-
.catch((error) => {
109-
makeToast(error.toString());
110-
})
111-
.finally(() => {
112-
setIsProcessing(false);
113-
});
114-
}, [billingAccountId, isProcessing, payments, period.id]);
115-
116-
const onApprove = useCallback(() => {
117-
setIsProcessing(true);
118-
}, []);
119-
120-
const onDismiss = useCallback(() => {
121-
setIsModalOpen(false);
122-
}, []);
123-
124-
const accountIdsHash = {};
125-
for (let payment of payments) {
126-
accountIdsHash[payment.billingAccountId] = true;
127-
}
109+
(async function () {
110+
let ok = await dispatch(
111+
updatePaymentsBillingAccount(period.id, billingAccountId, 5000)
112+
);
113+
setIsModalOpen(!ok);
114+
setIsProcessing(false);
115+
})();
116+
}, [billingAccountId, isProcessing, period.id, dispatch]);
128117

129118
return (
130119
<Modal
@@ -181,7 +170,7 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
181170
<tr>
182171
<th>Current BA(s) used:</th>
183172
<td className={styles.accountIds}>
184-
{Object.keys(accountIdsHash).join(", ") || "-"}
173+
{accountIds.join(", ") || "-"}
185174
</td>
186175
</tr>
187176
</tbody>

src/routes/WorkPeriods/components/PeriodActions/index.jsx

+16-17
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,37 @@ import styles from "./styles.module.scss";
1313
* @returns {JSX.Element}
1414
*/
1515
const PeriodActions = ({ className, period, periodData }) => {
16-
const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);
17-
const [isUpdateBAModalOpen, setIsUpdateBAModalOpen] = useState(false);
16+
const [isOpenAddPaymentModal, setIsOpenAddPaymentModal] = useState(false);
17+
const [isOpenUpdateBAModal, setIsOpenUpdateBAModal] = useState(false);
1818
const payments = periodData.payments;
1919

20-
const openAddPaymentModal = useCallback(() => {
21-
setIsAddPaymentModalOpen(true);
22-
}, []);
23-
2420
const closeAddPaymentModal = useCallback(() => {
25-
setIsAddPaymentModalOpen(false);
26-
}, []);
27-
28-
const openUpdateBAModal = useCallback(() => {
29-
setIsUpdateBAModalOpen(true);
21+
setIsOpenAddPaymentModal(false);
3022
}, []);
3123

3224
const closeUpdateBAModal = useCallback(() => {
33-
setIsUpdateBAModalOpen(false);
25+
setIsOpenUpdateBAModal(false);
3426
}, []);
3527

3628
const actions = useMemo(() => {
3729
let actions = [
38-
{ label: "Additional Payment", action: openAddPaymentModal },
30+
{
31+
label: "Additional Payment",
32+
action() {
33+
setIsOpenAddPaymentModal(true);
34+
},
35+
},
3936
];
4037
if (payments?.length) {
4138
actions.push({
4239
label: "Update BA for payments",
43-
action: openUpdateBAModal,
40+
action() {
41+
setIsOpenUpdateBAModal(true);
42+
},
4443
});
4544
}
4645
return actions;
47-
}, [payments, openAddPaymentModal, openUpdateBAModal]);
46+
}, [payments]);
4847

4948
return (
5049
<div className={cn(styles.container, className)}>
@@ -53,13 +52,13 @@ const PeriodActions = ({ className, period, periodData }) => {
5352
popupStrategy="fixed"
5453
stopClickPropagation={true}
5554
/>
56-
{isAddPaymentModalOpen && (
55+
{isOpenAddPaymentModal && (
5756
<PaymentModalAdditional
5857
period={period}
5958
removeModal={closeAddPaymentModal}
6059
/>
6160
)}
62-
{isUpdateBAModalOpen && (
61+
{isOpenUpdateBAModal && (
6362
<PaymentModalUpdateBA
6463
payments={payments}
6564
period={period}

src/store/thunks/workPeriods.js

+99-23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
API_CHALLENGE_PAYMENT_STATUS,
1313
} from "constants/workPeriods";
1414
import {
15+
delay,
1516
extractResponseData,
1617
extractResponsePagination,
1718
replaceItems,
@@ -33,42 +34,51 @@ import {
3334
import { RESOURCE_BOOKING_STATUS, WORK_PERIODS_PATH } from "constants/index.js";
3435
import { currencyFormatter } from "utils/formatters";
3536

36-
export const loadWorkPeriodAfterPaymentCancel =
37-
(periodId, paymentId) => async (dispatch, getState) => {
38-
let [periodsData] = selectors.getWorkPeriodsData(getState());
39-
periodsData[periodId]?.cancelSource?.cancel();
40-
const [promise, source] = services.fetchWorkPeriod(periodId);
41-
dispatch(actions.setWorkPeriodDataPending(periodId, source));
42-
let periodData = null;
43-
let userHandle = null;
44-
let errorMessage = null;
45-
try {
46-
const data = await promise;
47-
periodData = normalizePeriodData(data);
48-
userHandle = data.userHandle;
49-
} catch (error) {
50-
if (!axios.isCancel(error)) {
51-
errorMessage = error.toString();
52-
}
37+
export const loadWorkPeriodData = (periodId) => async (dispatch, getState) => {
38+
let [periodsData] = selectors.getWorkPeriodsData(getState());
39+
periodsData[periodId]?.cancelSource?.cancel();
40+
const [promise, source] = services.fetchWorkPeriod(periodId);
41+
dispatch(actions.setWorkPeriodDataPending(periodId, source));
42+
let userHandle = null;
43+
let periodData = null;
44+
let errorMessage = null;
45+
try {
46+
const data = await promise;
47+
userHandle = data.userHandle;
48+
periodData = normalizePeriodData(data);
49+
} catch (error) {
50+
if (!axios.isCancel(error)) {
51+
errorMessage = error.toString();
5352
}
53+
}
54+
if (periodData) {
55+
dispatch(actions.setWorkPeriodDataSuccess(periodId, periodData));
56+
return [{ ...periodData, userHandle }, null];
57+
} else if (errorMessage) {
58+
dispatch(actions.setWorkPeriodDataError(periodId, errorMessage));
59+
return [null, errorMessage];
60+
}
61+
return [null, null];
62+
};
63+
64+
export const loadWorkPeriodAfterPaymentCancel =
65+
(periodId, paymentId) => async (dispatch) => {
66+
let [periodData, error] = await dispatch(loadWorkPeriodData(periodId));
5467
if (periodData) {
68+
let userHandle = periodData.userHandle;
5569
let amount = null;
5670
for (let payment of periodData.payments) {
5771
if (payment.id === paymentId) {
5872
amount = currencyFormatter.format(payment.amount);
5973
break;
6074
}
6175
}
62-
dispatch(actions.setWorkPeriodDataSuccess(periodId, periodData));
6376
makeToast(
6477
`Payment ${amount} for ${userHandle} was marked as "cancelled"`,
6578
"success"
6679
);
67-
} else if (errorMessage) {
68-
dispatch(actions.setWorkPeriodDataError(periodId, errorMessage));
69-
makeToast(
70-
`Failed to load data for working period ${periodId}.\n` + errorMessage
71-
);
80+
} else if (error) {
81+
makeToast("Failed to reload working period data. " + error);
7282
}
7383
};
7484

@@ -238,6 +248,72 @@ export const toggleWorkPeriodDetails =
238248
}
239249
};
240250

251+
/**
252+
* A thunk that updates the billing accounts for all the payments from the
253+
* specific working period.
254+
*
255+
* @param {string} periodId working period id
256+
* @param {number} billingAccountId desired billing account id
257+
* @param {number} [periodDataDelay] timeout after which the period data gets
258+
* reloaded
259+
* @returns {function}
260+
*/
261+
export const updatePaymentsBillingAccount =
262+
(periodId, billingAccountId, periodDataDelay = 3000) =>
263+
async (dispatch, getState) => {
264+
let [periodsData] = selectors.getWorkPeriodsData(getState());
265+
let periodData = periodsData[periodId];
266+
if (!periodData) {
267+
return;
268+
}
269+
let paymentsToUpdate = [];
270+
for (let payment of periodData.payments) {
271+
if (payment.billingAccountId !== billingAccountId) {
272+
paymentsToUpdate.push({ id: payment.id, billingAccountId });
273+
}
274+
}
275+
if (!paymentsToUpdate.length) {
276+
makeToast(
277+
"All payments have desired billing account. Nothing to update.",
278+
"success"
279+
);
280+
return true;
281+
}
282+
let paymentsData = null;
283+
let errorMessage = null;
284+
try {
285+
paymentsData = await services.patchWorkPeriodPayments(paymentsToUpdate);
286+
} catch (error) {
287+
errorMessage = error.toString();
288+
}
289+
if (errorMessage) {
290+
makeToast(errorMessage);
291+
return false;
292+
}
293+
await delay(periodDataDelay);
294+
[periodData, errorMessage] = await dispatch(loadWorkPeriodData(periodId));
295+
if (errorMessage) {
296+
makeToast("Failed to reload payments' data. " + errorMessage);
297+
} else if (periodData) {
298+
paymentsData = periodData.payments;
299+
}
300+
let paymentsNotUpdated = [];
301+
for (let payment of paymentsData) {
302+
if (payment.billingAccountId !== billingAccountId) {
303+
paymentsNotUpdated.push(payment);
304+
}
305+
}
306+
if (paymentsNotUpdated.length) {
307+
makeToast("Could not update billing account for some payments.");
308+
return false;
309+
}
310+
makeToast(
311+
"Billing account was successfully updated for all the payments.",
312+
"success"
313+
);
314+
return true;
315+
};
316+
241317
/**
242318
*
243319
* @param {string} rbId

src/utils/misc.js

+11
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ export const buildRequestQuery = (params) => {
139139
return queryParams.join("&");
140140
};
141141

142+
/**
143+
* Function that returns a promise which resolves after the provided delay.
144+
*
145+
* @param {number} ms number of milliseconds
146+
* @returns {Promise}
147+
*/
148+
export const delay = (ms) =>
149+
new Promise((resolve) => {
150+
setTimeout(resolve, ms);
151+
});
152+
142153
export const extractResponsePagination = ({ headers }) => ({
143154
totalCount: +headers["x-total"] || 0,
144155
pageCount: +headers["x-total-pages"] || 0,

0 commit comments

Comments
 (0)