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

Commit fc388f9

Browse files
committed
Fixed tooltips for team names. Refactored payment cancelling. Added BA check on update.
1 parent 4be0527 commit fc388f9

File tree

9 files changed

+155
-93
lines changed

9 files changed

+155
-93
lines changed

src/constants/workPeriods.js

+2
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,6 @@ export const ALERT_MESSAGE_MAP = {
168168
[ALERT.LAST_BOOKING_WEEK]: "Last Booking Week",
169169
};
170170

171+
export const SERVER_DATA_UPDATE_DELAY = 3000;
172+
171173
export const DAYS_WORKED_HARD_LIMIT = 10;

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

+14-44
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { useDispatch } from "react-redux";
33
import PT from "prop-types";
44
import Modal from "components/Modal";
55
import Spinner from "components/Spinner";
6-
import { makeToast } from "components/ToastrMessage";
7-
import { setWorkPeriodPaymentData } from "store/actions/workPeriods";
8-
import { loadWorkPeriodAfterPaymentCancel } from "store/thunks/workPeriods";
9-
import { cancelWorkPeriodPayment } from "services/workPeriods";
6+
import { cancelWorkPeriodPayment } from "store/thunks/workPeriods";
107

118
/**
129
* Displays a Cancel button. Shows a modal with payment cancelling confirmation
@@ -16,61 +13,35 @@ import { cancelWorkPeriodPayment } from "services/workPeriods";
1613
* @param {Object} props.payment payment object with id, workPeriodId and status
1714
* @param {() => void} props.removeModal function called when the closing
1815
* animation of the modal is finished
19-
* @param {number} [props.timeout] timeout the delay after cancelling payment
20-
* after which an attempt will be made to update working period's data from the server
2116
* @returns {JSX.Element}
2217
*/
23-
const PaymentModalCancel = ({ payment, removeModal, timeout = 3000 }) => {
18+
const PaymentModalCancel = ({ payment, removeModal }) => {
2419
const [isModalOpen, setIsModalOpen] = useState(true);
25-
const [isCancelPending, setIsCancelPending] = useState(false);
26-
const [isCancelSuccess, setIsCancelSuccess] = useState(false);
20+
const [isProcessing, setIsProcessing] = useState(false);
2721
const dispatch = useDispatch();
2822
const { id: paymentId, workPeriodId: periodId } = payment;
2923

3024
const onApprove = useCallback(() => {
31-
setIsCancelPending(true);
25+
setIsProcessing(true);
3226
}, []);
3327

3428
const onDismiss = useCallback(() => {
3529
setIsModalOpen(false);
3630
}, []);
3731

3832
useEffect(() => {
39-
if (!isCancelPending) {
33+
if (!isProcessing) {
4034
return;
4135
}
42-
cancelWorkPeriodPayment(paymentId)
43-
.then((paymentData) => {
44-
dispatch(setWorkPeriodPaymentData(paymentData));
45-
setIsCancelSuccess(true);
46-
})
47-
.catch((error) => {
48-
makeToast(error.toString());
49-
setIsCancelPending(false);
50-
});
51-
}, [isCancelPending, paymentId, dispatch]);
52-
53-
useEffect(() => {
54-
let timeoutId = 0;
55-
if (!isCancelSuccess) {
56-
return;
57-
}
58-
timeoutId = window.setTimeout(async () => {
59-
timeoutId = 0;
60-
await dispatch(loadWorkPeriodAfterPaymentCancel(periodId, paymentId));
61-
setIsModalOpen(false);
62-
setIsCancelSuccess(false);
63-
setIsCancelPending(false);
64-
}, timeout);
65-
return () => {
66-
if (timeoutId) {
67-
clearTimeout(timeoutId);
68-
}
69-
};
70-
}, [isCancelSuccess, paymentId, periodId, timeout, dispatch]);
36+
(async function () {
37+
let ok = await dispatch(cancelWorkPeriodPayment(periodId, paymentId));
38+
setIsModalOpen(!ok);
39+
setIsProcessing(false);
40+
})();
41+
}, [isProcessing, paymentId, periodId, dispatch]);
7142

7243
let title, controls;
73-
if (isCancelPending) {
44+
if (isProcessing) {
7445
controls = null;
7546
title = "Marking as cancelled...";
7647
} else {
@@ -83,14 +54,14 @@ const PaymentModalCancel = ({ payment, removeModal, timeout = 3000 }) => {
8354
approveText="Mark as cancelled"
8455
dismissText="Cancel cancelling"
8556
title={title}
86-
isDisabled={isCancelPending}
57+
isDisabled={isProcessing}
8758
isOpen={isModalOpen}
8859
controls={controls}
8960
onApprove={onApprove}
9061
onClose={removeModal}
9162
onDismiss={onDismiss}
9263
>
93-
{isCancelPending ? (
64+
{isProcessing ? (
9465
<Spinner />
9566
) : (
9667
`Cancelling payment here will only mark it as cancelled in TaaS system.
@@ -108,7 +79,6 @@ PaymentModalCancel.propTypes = {
10879
workPeriodId: PT.string.isRequired,
10980
}).isRequired,
11081
removeModal: PT.func.isRequired,
111-
timeout: PT.number,
11282
};
11383

11484
export default PaymentModalCancel;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
108108
}
109109
(async function () {
110110
let ok = await dispatch(
111-
updatePaymentsBillingAccount(period.id, billingAccountId, 5000)
111+
updatePaymentsBillingAccount(period.id, billingAccountId)
112112
);
113113
setIsModalOpen(!ok);
114114
setIsProcessing(false);

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ const PeriodItem = ({
177177
<td className={styles.userHandle}>
178178
<Tooltip
179179
content={jobName}
180+
strategy="fixed"
180181
targetClassName={styles.userHandleContainer}
181182
>
182183
<a
@@ -191,8 +192,9 @@ const PeriodItem = ({
191192
</td>
192193
<td className={styles.teamName}>
193194
<Tooltip
194-
targetClassName={styles.projectNameContainer}
195195
content={projectIdAndTeamName}
196+
strategy="fixed"
197+
targetClassName={styles.projectNameContainer}
196198
>
197199
<ProjectName
198200
className={styles.projectName}

src/store/actionTypes/workPeriods.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const WP_SET_ALERT_OPTION = "WP_SET_ALERT_OPTION";
2424
export const WP_SET_PERIOD_DATA_PENDING = "WP_SET_PERIOD_DATA_PENDING";
2525
export const WP_SET_PERIOD_DATA_SUCCESS = "WP_SET_PERIOD_DATA_SUCCESS";
2626
export const WP_SET_PERIOD_DATA_ERROR = "WP_SET_PERIOD_DATA_ERROR";
27+
export const WP_SET_PERIOD_PAYMENTS = "WP_SET_PERIOD_PAYMENTS";
2728
export const WP_SET_SORT_BY = "WP_SET_SORT_BY";
2829
export const WP_SET_SORT_ORDER = "WP_SET_SORT_ORDER";
2930
export const WP_SET_SORTING = "WP_SET_SORTING";

src/store/actions/workPeriods.js

+5
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ export const setWorkPeriodPaymentData = (paymentData) => ({
315315
payload: paymentData,
316316
});
317317

318+
export const setWorkPeriodPayments = (periodId, payments) => ({
319+
type: ACTION_TYPE.WP_SET_PERIOD_PAYMENTS,
320+
payload: { periodId, payments },
321+
});
322+
318323
/**
319324
* Creates an action to change working days for specific working period.
320325
*

src/store/reducers/workPeriods.js

+15
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,21 @@ const actionHandlers = {
653653
periodsData: [periodsData],
654654
};
655655
},
656+
[ACTION_TYPE.WP_SET_PERIOD_PAYMENTS]: (state, { periodId, payments }) => {
657+
const periodsData = state.periodsData[0];
658+
const periodData = periodsData[periodId];
659+
if (!periodData) {
660+
return state;
661+
}
662+
periodsData[periodId] = {
663+
...periodData,
664+
payments,
665+
};
666+
return {
667+
...state,
668+
periodsData: [periodsData],
669+
};
670+
},
656671
[ACTION_TYPE.WP_SET_PAYMENT_DATA]: (state, paymentData) => {
657672
const periodId = paymentData.workPeriodId;
658673
const periodsData = state.periodsData[0];

src/store/thunks/workPeriods.js

+102-47
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import * as actions from "store/actions/workPeriods";
44
import * as selectors from "store/selectors/workPeriods";
55
import * as services from "services/workPeriods";
66
import {
7-
SORT_BY_MAP,
7+
API_CHALLENGE_PAYMENT_STATUS,
8+
API_FIELDS_QUERY,
89
API_SORT_BY,
910
DATE_FORMAT_API,
1011
PAYMENT_STATUS_MAP,
11-
API_FIELDS_QUERY,
12-
API_CHALLENGE_PAYMENT_STATUS,
12+
SERVER_DATA_UPDATE_DELAY,
13+
SORT_BY_MAP,
1314
} from "constants/workPeriods";
1415
import {
1516
delay,
@@ -21,6 +22,7 @@ import {
2122
makeUrlQuery,
2223
normalizeBillingAccounts,
2324
normalizeDetailsPeriodItems,
25+
normalizePaymentData,
2426
normalizePeriodData,
2527
normalizePeriodItems,
2628
} from "utils/workPeriods";
@@ -34,37 +36,38 @@ import {
3436
import { RESOURCE_BOOKING_STATUS, WORK_PERIODS_PATH } from "constants/index.js";
3537
import { currencyFormatter } from "utils/formatters";
3638

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)) {
39+
/**
40+
* A thunk that cancels specific working period payment, reloads WP data
41+
* and updates store's state after certain delay.
42+
*
43+
* @param {string} periodId working period id
44+
* @param {string} paymentId working period's payment id
45+
* @param {number} [periodUpdateDelay] update delay for period data
46+
* @returns {function}
47+
*/
48+
export const cancelWorkPeriodPayment =
49+
(periodId, paymentId, periodUpdateDelay = SERVER_DATA_UPDATE_DELAY) =>
50+
async (dispatch) => {
51+
let paymentData = null;
52+
let errorMessage = null;
53+
try {
54+
paymentData = await services.cancelWorkPeriodPayment(paymentId);
55+
paymentData = normalizePaymentData(paymentData);
56+
} catch (error) {
5157
errorMessage = error.toString();
5258
}
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));
67-
if (periodData) {
59+
if (errorMessage) {
60+
makeToast(errorMessage);
61+
return false;
62+
}
63+
dispatch(actions.setWorkPeriodPaymentData(paymentData));
64+
let periodData;
65+
[periodData, errorMessage] = await dispatch(
66+
loadWorkPeriodData(periodId, periodUpdateDelay)
67+
);
68+
if (errorMessage) {
69+
makeToast("Failed to reload working period data. " + errorMessage);
70+
} else if (periodData) {
6871
let userHandle = periodData.userHandle;
6972
let amount = null;
7073
for (let payment of periodData.payments) {
@@ -77,9 +80,47 @@ export const loadWorkPeriodAfterPaymentCancel =
7780
`Payment ${amount} for ${userHandle} was marked as "cancelled"`,
7881
"success"
7982
);
80-
} else if (error) {
81-
makeToast("Failed to reload working period data. " + error);
8283
}
84+
return true;
85+
};
86+
87+
/**
88+
* A thunk that loads specific working period data and updates store's state.
89+
*
90+
* @param {string} periodId working period id
91+
* @param {number} [updateDelay] update delay in milliseconds
92+
* @returns {function}
93+
*/
94+
export const loadWorkPeriodData =
95+
(periodId, updateDelay = 0) =>
96+
async (dispatch, getState) => {
97+
if (updateDelay > 0) {
98+
await delay(updateDelay);
99+
}
100+
let [periodsData] = selectors.getWorkPeriodsData(getState());
101+
periodsData[periodId]?.cancelSource?.cancel();
102+
const [promise, source] = services.fetchWorkPeriod(periodId);
103+
dispatch(actions.setWorkPeriodDataPending(periodId, source));
104+
let userHandle = null;
105+
let periodData = null;
106+
let errorMessage = null;
107+
try {
108+
const data = await promise;
109+
userHandle = data.userHandle;
110+
periodData = normalizePeriodData(data);
111+
} catch (error) {
112+
if (!axios.isCancel(error)) {
113+
errorMessage = error.toString();
114+
}
115+
}
116+
if (periodData) {
117+
dispatch(actions.setWorkPeriodDataSuccess(periodId, periodData));
118+
return [{ ...periodData, userHandle }, null];
119+
} else if (errorMessage) {
120+
dispatch(actions.setWorkPeriodDataError(periodId, errorMessage));
121+
return [null, errorMessage];
122+
}
123+
return [null, null];
83124
};
84125

85126
/**
@@ -254,17 +295,14 @@ export const toggleWorkPeriodDetails =
254295
*
255296
* @param {string} periodId working period id
256297
* @param {number} billingAccountId desired billing account id
257-
* @param {number} [periodDataDelay] timeout after which the period data gets
258-
* reloaded
259298
* @returns {function}
260299
*/
261300
export const updatePaymentsBillingAccount =
262-
(periodId, billingAccountId, periodDataDelay = 3000) =>
263-
async (dispatch, getState) => {
301+
(periodId, billingAccountId) => async (dispatch, getState) => {
264302
let [periodsData] = selectors.getWorkPeriodsData(getState());
265303
let periodData = periodsData[periodId];
266304
if (!periodData) {
267-
return;
305+
return true; // no period to update
268306
}
269307
let paymentsToUpdate = [];
270308
for (let payment of periodData.payments) {
@@ -290,18 +328,35 @@ export const updatePaymentsBillingAccount =
290328
makeToast(errorMessage);
291329
return false;
292330
}
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-
}
300331
let paymentsNotUpdated = [];
332+
let paymentsUpdated = new Map();
301333
for (let payment of paymentsData) {
302-
if (payment.billingAccountId !== billingAccountId) {
334+
if ("error" in payment || payment.billingAccountId !== billingAccountId) {
303335
paymentsNotUpdated.push(payment);
336+
} else {
337+
paymentsUpdated.set(payment.id, payment);
338+
}
339+
}
340+
periodData = periodsData[periodId];
341+
if (!periodData) {
342+
return true; // no period to update
343+
}
344+
if (paymentsUpdated.size) {
345+
let payments = [];
346+
let paymentsOld = periodData.payments;
347+
for (let i = 0, len = paymentsOld.length; i < len; i++) {
348+
let paymentOld = paymentsOld[i];
349+
if (paymentsUpdated.has(paymentOld.id)) {
350+
// We update only billingAccountId because other payment properties
351+
// may have been updated on the server and as a result the UI state
352+
// may become inconsistent, i.e. WP properties like status and
353+
// total paid may become inconsisten with payments' properties.
354+
payments.push({ ...paymentOld, billingAccountId });
355+
} else {
356+
payments.push(paymentOld);
357+
}
304358
}
359+
dispatch(actions.setWorkPeriodPayments(periodId, payments));
305360
}
306361
if (paymentsNotUpdated.length) {
307362
makeToast("Could not update billing account for some payments.");

0 commit comments

Comments
 (0)