Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CMS] feat: add proposal spending summary and details #2032

Merged
merged 9 commits into from
Jul 28, 2020
Merged
27 changes: 27 additions & 0 deletions src/actions/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,33 @@ export const onPayApprovedInvoices = () => (dispatch) => {
});
};

export const onGetSpendingSummary = () => (dispatch) => {
dispatch(act.REQUEST_SPENDING_SUMMARY({}));
return api
.getSpendingSummary()
.then((response) => {
dispatch(act.RECEIVE_SPENDING_SUMMARY(response));
})
.catch((error) => {
dispatch(act.RECEIVE_SPENDING_SUMMARY(null, error));
});
};

export const onFetchSpendingDetails = (token) =>
withCsrf((dispatch, _, csrf) => {
dispatch(act.REQUEST_SPENDING_DETAILS({ token }));
return api
.getSpendingDetails(csrf, token)
.then((response) => {
dispatch(act.RECEIVE_SPENDING_DETAILS({ ...response }));
return response;
})
.catch((error) => {
dispatch(act.RECEIVE_SPENDING_DETAILS(null, error));
throw error;
});
});

export const onFetchExchangeRate = (month, year) =>
withCsrf((dispatch, _, csrf) => {
dispatch(act.REQUEST_EXCHANGE_RATE({ month, year }));
Expand Down
6 changes: 6 additions & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ export const RECEIVE_INVOICE_PAYOUTS = "API_RECEIVE_INVOICE_PAYOUTS";
export const REQUEST_PAY_APPROVED = "API_REQUEST_PAY_APPROVED";
export const RECEIVE_PAY_APPROVED = "API_RECEIVE_PAY_APPROVED";

export const REQUEST_SPENDING_SUMMARY = "API_REQUEST_SPENDING_SUMMARY";
export const RECEIVE_SPENDING_SUMMARY = "API_RECEIVE_SPENDING_SUMMARY";

export const REQUEST_SPENDING_DETAILS = "API_REQUEST_SPENDING_DETAILS";
export const RECEIVE_SPENDING_DETAILS = "API_RECEIVE_SPENDING_DETAILS";

export const REQUEST_EXCHANGE_RATE = "API_REQUEST_EXCHANGE_RATE";
export const RECEIVE_EXCHANGE_RATE = "API_RECEIVE_EXCHANGE_RATE";

Expand Down
10 changes: 7 additions & 3 deletions src/components/InvoiceDatasheet/InvoiceDatasheet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const TableRow = ({ children, className }) => (

const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
value,
omit,
onChange,
readOnly,
userRate,
Expand Down Expand Up @@ -105,7 +106,8 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
currentRate,
policy,
proposalsOptions,
subContractors
subContractors,
omit
);
setGrid(grid);
},
Expand All @@ -116,7 +118,8 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
currentRate,
policy,
proposalsOptions,
subContractors
subContractors,
omit
]
);

Expand Down Expand Up @@ -182,7 +185,7 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
[onChange, value, grid.length]
);

const headers = useMemo(() => createTableHeaders(), []);
const headers = useMemo(() => createTableHeaders(omit), [omit]);

const onContextMenu = useCallback(
(e, cell) => (cell.readOnly ? e.preventDefault() : null),
Expand Down Expand Up @@ -251,6 +254,7 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({

InvoiceDatasheet.propTypes = {
value: PropTypes.array.isRequired,
omit: PropTypes.array,
readOnly: PropTypes.bool.isRequired,
onChange: PropTypes.func,
proposals: PropTypes.array.isRequired
Expand Down
75 changes: 42 additions & 33 deletions src/components/InvoiceDatasheet/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
proposalViewWrapper,
proposalSelectWrapper
} from "./wrappers";
import _omit from "lodash/fp/omit";

export const columnTypes = {
TYPE_COL: 1,
Expand Down Expand Up @@ -78,7 +79,8 @@ export const convertLineItemsToGrid = (
userRate = 0,
policy,
proposalsOptions,
subContractors
subContractors,
omit
) => {
const {
supporteddomains: policyDomains,
Expand All @@ -87,24 +89,25 @@ export const convertLineItemsToGrid = (
const grid = [];
const { grid: gridBody, expenseTotal, laborTotal, total } = lineItems.reduce(
(acc, line, idx) => {
const newLine = _omit(omit, line);
const isLaborReadonly =
line.type === 2 ? true : line.type === 3 ? true : readOnly;
const isSubContractorReadonly = line.type !== 4 ? true : readOnly;
newLine.type === 2 ? true : newLine.type === 3 ? true : readOnly;
const isSubContractorReadonly = newLine.type !== 4 ? true : readOnly;
const isExpenseReadonly =
line.type === 1 || line.type === 4 ? true : readOnly;
const laborHours = +fromMinutesToHours(line.labor);
const expenses = +fromUSDCentsToUSDUnits(line.expenses);
const subRate = +fromUSDCentsToUSDUnits(line.subrate);
newLine.type === 1 || newLine.type === 4 ? true : readOnly;
const laborHours = +fromMinutesToHours(newLine.labor);
const expenses = +fromUSDCentsToUSDUnits(newLine.expenses);
const subRate = +fromUSDCentsToUSDUnits(newLine.subrate);
const lineSubTotal =
line.type !== 4
newLine.type !== 4
? laborHours * userRate + expenses
: laborHours * subRate;
const rowErrors = (errors && errors[idx]) || {};
const newLine = [
const tableLine = [
{ readOnly: true, value: idx + 1 },
{
readOnly,
value: line.type,
value: newLine.type,
error: rowErrors && rowErrors.type,
dataEditor: selectWrapper(
policyLineItemTypes.map((op) => ({
Expand All @@ -115,7 +118,7 @@ export const convertLineItemsToGrid = (
},
{
readOnly,
value: line.domain,
value: newLine.domain,
error: rowErrors && rowErrors.domain,
dataEditor: selectWrapper(
policyDomains.map((op) => ({
Expand All @@ -126,35 +129,35 @@ export const convertLineItemsToGrid = (
},
{
readOnly,
value: line.subdomain,
value: newLine.subdomain,
error: rowErrors && rowErrors.subdomain
},
{
readOnly,
value: line.description,
value: newLine.description,
error: rowErrors && rowErrors.description,
dataEditor: textAreaWrapper({
error: rowErrors && rowErrors.description
}),
valueViewer: multilineTextWrapper()
},
{
newLine.proposaltoken && {
readOnly,
value: line.proposaltoken,
value: newLine.proposaltoken,
error: rowErrors && rowErrors.proposaltoken,
dataEditor: proposalSelectWrapper(proposalsOptions),
valueViewer: proposalViewWrapper(proposalsOptions)
},
{
readOnly: isSubContractorReadonly,
value: line.subuserid,
value: newLine.subuserid,
error: rowErrors && rowErrors.subuserid,
dataEditor: selectWrapper(getSubcontractorOptions(subContractors))
},
{
readOnly: isSubContractorReadonly,
error: rowErrors && rowErrors.subrate,
value: +fromUSDCentsToUSDUnits(line.subrate)
value: +fromUSDCentsToUSDUnits(newLine.subrate)
},
{
readOnly: isLaborReadonly,
Expand All @@ -172,9 +175,9 @@ export const convertLineItemsToGrid = (
}
];
return {
grid: acc.grid.concat([newLine]),
expenseTotal: acc.expenseTotal + line.expenses,
laborTotal: acc.laborTotal + line.labor,
grid: acc.grid.concat([tableLine.filter(Boolean)]),
expenseTotal: acc.expenseTotal + newLine.expenses,
laborTotal: acc.laborTotal + newLine.labor,
total: acc.total + lineSubTotal
};
},
Expand Down Expand Up @@ -231,19 +234,25 @@ export const convertGridToLineItems = (grid) => {
}, []);
};

export const createTableHeaders = () => [
{ readOnly: true, value: "", width: "2rem" },
{ value: "Type", readOnly: true, width: "4rem" },
{ value: "Domain", readOnly: true, width: "12rem" },
{ value: "Subdomain", readOnly: true, width: "14rem" },
{ value: "Description", readOnly: true, width: "30rem" },
{ value: "Proposal", readOnly: true, width: "30rem" },
{ value: "Subcontr. ID", readOnly: true, width: "10rem" },
{ value: "Subcontr. Rate (USD)", readOnly: true, width: "8rem" },
{ value: "Labor (hours)", readOnly: true, width: "7rem" },
{ value: "Expense (USD)", readOnly: true, width: "7.5rem" },
{ value: "Subtotal (USD)", readOnly: true, width: "7.5rem" }
];
export const createTableHeaders = (omit) =>
[
{ readOnly: true, value: "", width: "2rem" },
{ value: "Type", readOnly: true, width: "4rem" },
{ value: "Domain", readOnly: true, width: "12rem" },
{ value: "Subdomain", readOnly: true, width: "14rem" },
{ value: "Description", readOnly: true, width: "30rem" },
{
value: "Proposal Token",
key: "proposaltoken",
readOnly: true,
width: "10rem"
},
{ value: "Subcontr. ID", readOnly: true, width: "10rem" },
{ value: "Subcontr. Rate (USD)", readOnly: true, width: "8rem" },
{ value: "Labor (hours)", readOnly: true, width: "7rem" },
{ value: "Expense (USD)", readOnly: true, width: "7.5rem" },
{ value: "Subtotal (USD)", readOnly: true, width: "7.5rem" }
].filter((header) => (omit ? !omit.includes(header.key) : true));

export const updateGridCell = (grid, row, col, values) => {
grid[row][col] = { ...grid[row][col], ...values };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const PageDetails = ({
title,
subtitle,
actionsContent,
actionsClassName,
children,
headerClassName,
titleAndSubtitleWrapperClassName,
Expand Down Expand Up @@ -91,7 +92,10 @@ const PageDetails = ({
{titleContent}
{!!subtitle && <Subtitle>{subtitle}</Subtitle>}
</div>
<div className={styles.pageDetailsActions}>{actionsContent}</div>
<div
className={classNames(styles.pageDetailsActions, actionsClassName)}>
{actionsContent}
</div>
</div>

{children}
Expand Down
13 changes: 10 additions & 3 deletions src/containers/Invoice/Admin/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Row } from "src/components/layout";
import useModalContext from "src/hooks/utils/useModalContext";

const ActionsContent = ({ openInviteModal }) => {
const mobile = useMediaQuery("(max-width: 560px)");
const mobile = useMediaQuery("(max-width: 768px)");

const inviteContractorLink = (
<UILink className="cursor-pointer" onClick={openInviteModal}>
Expand All @@ -38,23 +38,30 @@ const ActionsContent = ({ openInviteModal }) => {
Payout summaries
</Link>
);
const proposalBillingSummaryLink = (
<Link className="cursor-pointer" to="/admin/proposalsbilling">
Proposal billing
</Link>
);

return (
<div>
<>
{!mobile ? (
<Row justify="space-between" className={styles.actionsWrapper}>
{inviteContractorLink}
{generatePayoutsLink}
{payoutSummariesLink}
{proposalBillingSummaryLink}
</Row>
) : (
<Dropdown title="Actions" className={styles.actionsWrapper}>
<DropdownItem>{inviteContractorLink}</DropdownItem>
<DropdownItem>{generatePayoutsLink}</DropdownItem>
<DropdownItem>{payoutSummariesLink}</DropdownItem>
<DropdownItem>{proposalBillingSummaryLink}</DropdownItem>
</Dropdown>
)}
</div>
</>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/containers/Invoice/Admin/List.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
}

.actionsWrapper {
width: 39rem;
width: 50rem;
}

/* EXTRA SMALL SCREENS*/
@media screen and (max-width: 560px) {
@media screen and (max-width: 768px) {
.actionsWrapper {
width: 9rem;
justify-content: flex-end;
Expand Down
Loading