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

Payment improvements #96

Merged
merged 7 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/components/ActionsMenu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import compStyles from "./styles.module.scss";
* @param {Object} props component properties
* @param {'primary'|'error'|'warning'} [props.handleColor] menu handle color
* @param {'small'|'medium'} [props.handleSize] menu handle size
* @param {string} [props.handleText] text to show inside menu handle
* @param {Array} props.items menu items
* @param {'absolute'|'fixed'} [props.popupStrategy] popup positioning strategy
* @param {boolean} [props.stopClickPropagation] whether to stop click event propagation
Expand All @@ -22,6 +23,7 @@ import compStyles from "./styles.module.scss";
const ActionsMenu = ({
handleColor = "primary",
handleSize = "small",
handleText,
items = [],
popupStrategy = "absolute",
stopClickPropagation = false,
Expand Down Expand Up @@ -89,14 +91,16 @@ const ActionsMenu = ({
<Button
color={handleColor}
size={handleSize}
style={handleText ? "rounded" : "circle"}
variant="contained"
onClick={isOpen ? null : toggleMenu}
className={cn(compStyles.handle, {
[compStyles.handleMenuOpen]: isOpen,
})}
innerRef={setReferenceElement}
>
Actions <IconArrowDown className={compStyles.iconArrowDown} />
{handleText ? <span>{handleText}&nbsp;</span> : null}
<IconArrowDown className={compStyles.iconArrowDown} />
</Button>
{isOpen && (
<Menu
Expand All @@ -113,6 +117,7 @@ const ActionsMenu = ({
ActionsMenu.propTypes = {
handleColor: PT.oneOf(["primary", "error", "warning"]),
handleSize: PT.oneOf(["small", "medium"]),
handleText: PT.string,
items: PT.arrayOf(
PT.shape({
label: PT.string,
Expand Down
7 changes: 6 additions & 1 deletion src/components/ActionsMenu/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
.handle {
display: inline-flex;
align-items: center;

> span {
+ .iconArrowDown {
margin-left: 8px;
}
}
}

.iconArrowDown {
display: inline-block;
width: 12px;
height: 8px;
margin-left: 8px;
}

.handleMenuOpen {
Expand Down
6 changes: 1 addition & 5 deletions src/components/ProjectName/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ const ProjectName = ({ className, projectId }) => {

const projectName = getName(projectId) || projectId;

return (
<span className={cn(styles.container, className)} title={projectName}>
{projectName}
</span>
);
return <span className={cn(styles.container, className)}>{projectName}</span>;
};

ProjectName.propTypes = {
Expand Down
3 changes: 0 additions & 3 deletions src/components/ProjectName/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

.container {
display: inline-block;
max-width: 20em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@include roboto-medium;
}
3 changes: 3 additions & 0 deletions src/constants/workPeriods.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const TAAS_TEAM_API_URL = `${API.V5}/taas-teams`;
export const DATE_FORMAT_API = "YYYY-MM-DD";
export const DATE_FORMAT_ISO = "YYYY-MM-DD";
export const DATE_FORMAT_UI = "MMM DD, YYYY";
export const DATETIME_FORMAT_UI = "MMM DD, YYYY h:mm a";

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

export const SERVER_DATA_UPDATE_DELAY = 3000;

export const DAYS_WORKED_HARD_LIMIT = 10;
57 changes: 14 additions & 43 deletions src/routes/WorkPeriods/components/PaymentModalCancel/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { useDispatch } from "react-redux";
import PT from "prop-types";
import Modal from "components/Modal";
import Spinner from "components/Spinner";
import { makeToast } from "components/ToastrMessage";
import { setWorkPeriodPaymentData } from "store/actions/workPeriods";
import { loadWorkPeriodAfterPaymentCancel } from "store/thunks/workPeriods";
import { cancelWorkPeriodPayment } from "services/workPeriods";
import { cancelWorkPeriodPayment } from "store/thunks/workPeriods";

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

const onApprove = useCallback(() => {
setIsCancelPending(true);
setIsProcessing(true);
}, []);

const onDismiss = useCallback(() => {
setIsModalOpen(false);
}, []);

useEffect(() => {
if (!isCancelPending) {
if (!isProcessing) {
return;
}
cancelWorkPeriodPayment(paymentId)
.then((paymentData) => {
dispatch(setWorkPeriodPaymentData(paymentData));
setIsCancelSuccess(true);
})
.catch((error) => {
makeToast(error.toString());
setIsCancelPending(false);
});
}, [isCancelPending, paymentId, dispatch]);

useEffect(() => {
let timeoutId = 0;
if (!isCancelSuccess) {
return;
}
timeoutId = window.setTimeout(async () => {
timeoutId = 0;
await dispatch(loadWorkPeriodAfterPaymentCancel(periodId, paymentId));
setIsModalOpen(false);
setIsCancelSuccess(false);
setIsCancelPending(false);
}, timeout);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [isCancelSuccess, paymentId, periodId, timeout, dispatch]);
(async function () {
let ok = await dispatch(cancelWorkPeriodPayment(periodId, paymentId));
setIsModalOpen(!ok);
setIsProcessing(false);
})();
}, [isProcessing, paymentId, periodId, dispatch]);

let title, controls;
if (isCancelPending) {
if (isProcessing) {
controls = null;
title = "Marking as cancelled...";
} else {
Expand All @@ -83,13 +54,14 @@ const PaymentModalCancel = ({ payment, removeModal, timeout = 3000 }) => {
approveText="Mark as cancelled"
dismissText="Cancel cancelling"
title={title}
isDisabled={isProcessing}
isOpen={isModalOpen}
controls={controls}
onApprove={onApprove}
onClose={removeModal}
onDismiss={onDismiss}
>
{isCancelPending ? (
{isProcessing ? (
<Spinner />
) : (
`Cancelling payment here will only mark it as cancelled in TaaS system.
Expand All @@ -107,7 +79,6 @@ PaymentModalCancel.propTypes = {
workPeriodId: PT.string.isRequired,
}).isRequired,
removeModal: PT.func.isRequired,
timeout: PT.number,
};

export default PaymentModalCancel;
65 changes: 27 additions & 38 deletions src/routes/WorkPeriods/components/PaymentModalUpdateBA/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import PT from "prop-types";
import cn from "classnames";
import moment from "moment";
Expand All @@ -7,11 +8,8 @@ import Modal from "components/Modal";
import ProjectName from "components/ProjectName";
import Spinner from "components/Spinner";
import SelectField from "components/SelectField";
import { makeToast } from "components/ToastrMessage";
import {
fetchBillingAccounts,
patchWorkPeriodPayments,
} from "services/workPeriods";
import { fetchBillingAccounts } from "services/workPeriods";
import { updatePaymentsBillingAccount } from "store/thunks/workPeriods";
import {
createAssignedBillingAccountOption,
normalizeBillingAccounts,
Expand Down Expand Up @@ -42,6 +40,21 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
const [billingAccountsDisabled, setBillingAccountsDisabled] = useState(true);
const [billingAccountsError, setBillingAccountsError] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
const dispatch = useDispatch();

const accountIdMap = {};
for (let payment of payments) {
accountIdMap[payment.billingAccountId] = true;
}
const accountIds = Object.keys(accountIdMap);

const onApprove = useCallback(() => {
setIsProcessing(true);
}, []);

const onDismiss = useCallback(() => {
setIsModalOpen(false);
}, []);

useEffect(() => {
const [bilAccsPromise] = fetchBillingAccounts(period.projectId);
Expand Down Expand Up @@ -93,38 +106,14 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
if (!isProcessing) {
return;
}
const paymentsUpdated = [];
for (let { id } of payments) {
paymentsUpdated.push({ id, billingAccountId });
}
patchWorkPeriodPayments(paymentsUpdated)
.then(() => {
makeToast(
"Billing account was successfully updated for all the payments",
"success"
);
setIsModalOpen(false);
})
.catch((error) => {
makeToast(error.toString());
})
.finally(() => {
setIsProcessing(false);
});
}, [billingAccountId, isProcessing, payments, period.id]);

const onApprove = useCallback(() => {
setIsProcessing(true);
}, []);

const onDismiss = useCallback(() => {
setIsModalOpen(false);
}, []);

const accountIdsHash = {};
for (let payment of payments) {
accountIdsHash[payment.billingAccountId] = true;
}
(async function () {
let ok = await dispatch(
updatePaymentsBillingAccount(period.id, billingAccountId)
);
setIsModalOpen(!ok);
setIsProcessing(false);
})();
}, [billingAccountId, isProcessing, period.id, dispatch]);

return (
<Modal
Expand Down Expand Up @@ -181,7 +170,7 @@ const PaymentModalUpdateBA = ({ payments = [], period, removeModal }) => {
<tr>
<th>Current BA(s) used:</th>
<td className={styles.accountIds}>
{Object.keys(accountIdsHash).join(", ") || "-"}
{accountIds.join(", ") || "-"}
</td>
</tr>
</tbody>
Expand Down
1 change: 1 addition & 0 deletions src/routes/WorkPeriods/components/PaymentsList/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const PaymentsList = ({ className, daysPaid, daysWorked, payments }) => (
<th>Weekly Rate</th>
<th>Days</th>
<th>Amount</th>
<th className={styles.createdAt}>Created At</th>
<th className={styles.paymentStatus}>Status</th>
<th></th>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ table.paymentsList {
background: #f4f4f4;

&:first-child,
&.createdAt,
&.paymentStatus {
text-align: left;
}
Expand Down
10 changes: 9 additions & 1 deletion src/routes/WorkPeriods/components/PaymentsListItem/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import PT from "prop-types";
import PaymentActions from "../PaymentActions";
import PaymentError from "../PaymentError";
import PaymentStatus from "../PaymentStatus";
import { currencyFormatter, formatChallengeUrl } from "utils/formatters";
import {
currencyFormatter,
formatChallengeUrl,
formatDateTimeAsLocal,
} from "utils/formatters";
import { PAYMENT_STATUS } from "constants/workPeriods";
import styles from "./styles.module.scss";

Expand Down Expand Up @@ -55,6 +59,9 @@ const PaymentsListItem = ({ daysPaid, daysWorked, item }) => {
</td>
<td className={styles.days}>{item.days}</td>
<td className={styles.amount}>{currencyFormatter.format(item.amount)}</td>
<td className={styles.createdAt}>
{formatDateTimeAsLocal(item.createdAt)}
</td>
<td className={styles.paymentStatus}>
<div className={styles.statusWithError}>
<PaymentStatus status={item.status} />
Expand Down Expand Up @@ -84,6 +91,7 @@ PaymentsListItem.propTypes = {
id: PT.oneOfType([PT.string, PT.number]).isRequired,
amount: PT.number.isRequired,
challengeId: PT.oneOfType([PT.string, PT.number]),
createdAt: PT.number.isRequired,
days: PT.number.isRequired,
memberRate: PT.number.isRequired,
status: PT.string.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
text-align: right;
}

.createdAt {
text-align: left;
}

.paymentStatus {
white-space: nowrap;
}
Expand Down
Loading