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

Commit e5e67b2

Browse files
authored
Merge pull request #91 from MadOPcode/feature/payment-updates
Compacted WP table, added WP data reload after payments' BA update
2 parents 345eb9e + fc388f9 commit e5e67b2

File tree

23 files changed

+362
-154
lines changed

23 files changed

+362
-154
lines changed

src/components/ActionsMenu/index.jsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import compStyles from "./styles.module.scss";
1414
* @param {Object} props component properties
1515
* @param {'primary'|'error'|'warning'} [props.handleColor] menu handle color
1616
* @param {'small'|'medium'} [props.handleSize] menu handle size
17+
* @param {string} [props.handleText] text to show inside menu handle
1718
* @param {Array} props.items menu items
1819
* @param {'absolute'|'fixed'} [props.popupStrategy] popup positioning strategy
1920
* @param {boolean} [props.stopClickPropagation] whether to stop click event propagation
@@ -22,6 +23,7 @@ import compStyles from "./styles.module.scss";
2223
const ActionsMenu = ({
2324
handleColor = "primary",
2425
handleSize = "small",
26+
handleText,
2527
items = [],
2628
popupStrategy = "absolute",
2729
stopClickPropagation = false,
@@ -89,14 +91,16 @@ const ActionsMenu = ({
8991
<Button
9092
color={handleColor}
9193
size={handleSize}
94+
style={handleText ? "rounded" : "circle"}
9295
variant="contained"
9396
onClick={isOpen ? null : toggleMenu}
9497
className={cn(compStyles.handle, {
9598
[compStyles.handleMenuOpen]: isOpen,
9699
})}
97100
innerRef={setReferenceElement}
98101
>
99-
Actions <IconArrowDown className={compStyles.iconArrowDown} />
102+
{handleText ? <span>{handleText}&nbsp;</span> : null}
103+
<IconArrowDown className={compStyles.iconArrowDown} />
100104
</Button>
101105
{isOpen && (
102106
<Menu
@@ -113,6 +117,7 @@ const ActionsMenu = ({
113117
ActionsMenu.propTypes = {
114118
handleColor: PT.oneOf(["primary", "error", "warning"]),
115119
handleSize: PT.oneOf(["small", "medium"]),
120+
handleText: PT.string,
116121
items: PT.arrayOf(
117122
PT.shape({
118123
label: PT.string,

src/components/ActionsMenu/styles.module.scss

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@
88
.handle {
99
display: inline-flex;
1010
align-items: center;
11+
12+
> span {
13+
+ .iconArrowDown {
14+
margin-left: 8px;
15+
}
16+
}
1117
}
1218

1319
.iconArrowDown {
1420
display: inline-block;
1521
width: 12px;
1622
height: 8px;
17-
margin-left: 8px;
1823
}
1924

2025
.handleMenuOpen {

src/components/ProjectName/index.jsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ const ProjectName = ({ className, projectId }) => {
1313

1414
const projectName = getName(projectId) || projectId;
1515

16-
return (
17-
<span className={cn(styles.container, className)} title={projectName}>
18-
{projectName}
19-
</span>
20-
);
16+
return <span className={cn(styles.container, className)}>{projectName}</span>;
2117
};
2218

2319
ProjectName.propTypes = {

src/components/ProjectName/styles.module.scss

-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
.container {
44
display: inline-block;
5-
max-width: 20em;
6-
overflow: hidden;
7-
text-overflow: ellipsis;
85
white-space: nowrap;
96
@include roboto-medium;
107
}

src/constants/workPeriods.js

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const TAAS_TEAM_API_URL = `${API.V5}/taas-teams`;
3131
export const DATE_FORMAT_API = "YYYY-MM-DD";
3232
export const DATE_FORMAT_ISO = "YYYY-MM-DD";
3333
export const DATE_FORMAT_UI = "MMM DD, YYYY";
34+
export const DATETIME_FORMAT_UI = "MMM DD, YYYY h:mm a";
3435

3536
// Field names that are required to be retrieved for display, filtering and sorting.
3637
export const API_REQUIRED_FIELDS = [
@@ -167,4 +168,6 @@ export const ALERT_MESSAGE_MAP = {
167168
[ALERT.LAST_BOOKING_WEEK]: "Last Booking Week",
168169
};
169170

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

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

+14-43
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,13 +54,14 @@ const PaymentModalCancel = ({ payment, removeModal, timeout = 3000 }) => {
8354
approveText="Mark as cancelled"
8455
dismissText="Cancel cancelling"
8556
title={title}
57+
isDisabled={isProcessing}
8658
isOpen={isModalOpen}
8759
controls={controls}
8860
onApprove={onApprove}
8961
onClose={removeModal}
9062
onDismiss={onDismiss}
9163
>
92-
{isCancelPending ? (
64+
{isProcessing ? (
9365
<Spinner />
9466
) : (
9567
`Cancelling payment here will only mark it as cancelled in TaaS system.
@@ -107,7 +79,6 @@ PaymentModalCancel.propTypes = {
10779
workPeriodId: PT.string.isRequired,
10880
}).isRequired,
10981
removeModal: PT.func.isRequired,
110-
timeout: PT.number,
11182
};
11283

11384
export default PaymentModalCancel;

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)
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/PaymentsList/index.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const PaymentsList = ({ className, daysPaid, daysWorked, payments }) => (
1919
<th>Weekly Rate</th>
2020
<th>Days</th>
2121
<th>Amount</th>
22+
<th className={styles.createdAt}>Created At</th>
2223
<th className={styles.paymentStatus}>Status</th>
2324
<th></th>
2425
</tr>

src/routes/WorkPeriods/components/PaymentsList/styles.module.scss

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ table.paymentsList {
2525
background: #f4f4f4;
2626

2727
&:first-child,
28+
&.createdAt,
2829
&.paymentStatus {
2930
text-align: left;
3031
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import PT from "prop-types";
55
import PaymentActions from "../PaymentActions";
66
import PaymentError from "../PaymentError";
77
import PaymentStatus from "../PaymentStatus";
8-
import { currencyFormatter, formatChallengeUrl } from "utils/formatters";
8+
import {
9+
currencyFormatter,
10+
formatChallengeUrl,
11+
formatDateTimeAsLocal,
12+
} from "utils/formatters";
913
import { PAYMENT_STATUS } from "constants/workPeriods";
1014
import styles from "./styles.module.scss";
1115

@@ -55,6 +59,9 @@ const PaymentsListItem = ({ daysPaid, daysWorked, item }) => {
5559
</td>
5660
<td className={styles.days}>{item.days}</td>
5761
<td className={styles.amount}>{currencyFormatter.format(item.amount)}</td>
62+
<td className={styles.createdAt}>
63+
{formatDateTimeAsLocal(item.createdAt)}
64+
</td>
5865
<td className={styles.paymentStatus}>
5966
<div className={styles.statusWithError}>
6067
<PaymentStatus status={item.status} />
@@ -84,6 +91,7 @@ PaymentsListItem.propTypes = {
8491
id: PT.oneOfType([PT.string, PT.number]).isRequired,
8592
amount: PT.number.isRequired,
8693
challengeId: PT.oneOfType([PT.string, PT.number]),
94+
createdAt: PT.number.isRequired,
8795
days: PT.number.isRequired,
8896
memberRate: PT.number.isRequired,
8997
status: PT.string.isRequired,

src/routes/WorkPeriods/components/PaymentsListItem/styles.module.scss

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
text-align: right;
5959
}
6060

61+
.createdAt {
62+
text-align: left;
63+
}
64+
6165
.paymentStatus {
6266
white-space: nowrap;
6367
}

0 commit comments

Comments
 (0)