Skip to content

Commit

Permalink
[cms] feat: switch proposal token to proposal name on invoice lineitem (
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgcramos committed Jul 23, 2020
1 parent 49883cf commit ca866ff
Show file tree
Hide file tree
Showing 21 changed files with 323 additions and 75 deletions.
6 changes: 3 additions & 3 deletions src/actions/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,16 @@ export const onFetchAdminInvoices = () =>

export const onFetchProposalsBatchWithoutState = (
tokens,
fetchPropsoals = true,
fetchProposals = true,
fetchVoteSummary = true
) =>
withCsrf(async (_, __, csrf) => {
const res = await Promise.all([
fetchPropsoals && api.proposalsBatch(csrf, tokens),
fetchProposals && api.proposalsBatch(csrf, tokens),
fetchVoteSummary && api.proposalsBatchVoteSummary(csrf, tokens)
]);
const proposals =
fetchPropsoals && res.find((res) => res && res.proposals).proposals;
fetchProposals && res.find((res) => res && res.proposals).proposals;
const summaries =
fetchVoteSummary && res.find((res) => res && res.summaries).summaries;
return [proposals, summaries];
Expand Down
4 changes: 2 additions & 2 deletions src/components/Diff/Diff.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const DiffText = ({ newText, oldText }) => {
));
};

export const DiffInvoices = ({ oldData, newData, className }) => {
export const DiffInvoices = ({ oldData, newData, className, proposals }) => {
const newLineitems = setLineitemParams(newData.lineitems, {
rate: newData.contractorrate
});
Expand All @@ -169,7 +169,7 @@ export const DiffInvoices = ({ oldData, newData, className }) => {

return (
<SheetRenderer headers={createTableHeaders()} className={className}>
<DiffLineitemsDatasheet lineItems={lineitemsDiff} />
<DiffLineitemsDatasheet lineItems={lineitemsDiff} proposals={proposals} />
</SheetRenderer>
);
};
Expand Down
23 changes: 16 additions & 7 deletions src/components/Diff/DiffLineitems.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useMemo } from "react";
import React, { useMemo, useCallback } from "react";
import PropTypes from "prop-types";
import { classNames, useTheme } from "pi-ui";
import { fromUSDCentsToUSDUnits, fromMinutesToHours } from "src/helpers";
import styles from "./Diff.module.css";
import { TableRow } from "src/components/InvoiceDatasheet/InvoiceDatasheet";
import { getTotalsLine } from "src/components/InvoiceDatasheet/helpers";
import get from "lodash/get";

const renderGrid = (lineItems) =>
lineItems.reduce(
Expand All @@ -15,7 +16,7 @@ const renderGrid = (lineItems) =>
domain,
subdomain,
description,
proposaltoken,
proposalname,
subuserid,
subrate,
labor,
Expand All @@ -40,7 +41,7 @@ const renderGrid = (lineItems) =>
value: description,
multiline: true
},
{ value: proposaltoken },
{ value: proposalname },
{ value: subuserid },
{ value: subRate },
{ value: laborHours },
Expand All @@ -57,15 +58,23 @@ const renderGrid = (lineItems) =>
{ grid: [], expenseTotal: 0, laborTotal: 0, total: 0 }
);

const DiffLineitems = ({ lineItems }) => {
const DiffLineitems = ({ lineItems, proposals }) => {
const { themeName } = useTheme();
const isDarkTheme = themeName === "dark";

const { grid, expenseTotal, laborTotal, total } = useMemo(
() => renderGrid(lineItems),
[lineItems]
const getProposalName = useCallback(
(token) => get(proposals, [token, "name"]),
[proposals]
);

const { grid, expenseTotal, laborTotal, total } = useMemo(() => {
const newLineItems = lineItems.map((item) => ({
...item,
proposalname: getProposalName(item.proposaltoken)
}));
return renderGrid(newLineItems);
}, [lineItems, getProposalName]);

const numberOfCols = grid.length && grid[0] && grid[0].items.length;

const newTotals = [
Expand Down
7 changes: 4 additions & 3 deletions src/components/Invoice/Invoice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import ThumbnailGrid from "src/components/Files";
import { useLoaderContext } from "src/containers/Loader";
import VersionPicker from "src/components/VersionPicker";

const Invoice = ({ invoice, extended, approvedProposalsTokens }) => {
const Invoice = ({ invoice, extended, approvedProposals }) => {
const {
censorshiprecord,
file,
Expand Down Expand Up @@ -113,6 +113,7 @@ const Invoice = ({ invoice, extended, approvedProposalsTokens }) => {
)}
version={version}
token={invoiceToken}
proposals={approvedProposals}
/>
)}
</Subtitle>
Expand Down Expand Up @@ -189,7 +190,7 @@ const Invoice = ({ invoice, extended, approvedProposalsTokens }) => {
value={invoice && invoice.input.lineitems}
readOnly
userRate={invContractorRate / 100}
proposalsTokens={approvedProposalsTokens || []}
proposals={approvedProposals || []}
/>
</>
)}
Expand All @@ -203,7 +204,7 @@ const Invoice = ({ invoice, extended, approvedProposalsTokens }) => {

Invoice.propTypes = {
invoice: PropTypes.object.isRequired,
approvedProposalsTokens: PropTypes.array
approvedProposals: PropTypes.array
};

export default Invoice;
19 changes: 15 additions & 4 deletions src/components/InvoiceDatasheet/InvoiceDatasheet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CellRenderer from "./components/CellRenderer";
import TableButton from "./components/TableButton";
import usePolicy from "src/hooks/api/usePolicy";
import useSubContractors from "src/hooks/api/useSubContractors";

import {
processCellsChange,
convertLineItemsToGrid,
Expand Down Expand Up @@ -50,13 +51,23 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
readOnly,
userRate,
errors,
proposalsTokens
proposals
}) {
const { policy } = usePolicy();
const { subContractors } = useSubContractors();
const [grid, setGrid] = useState([]);
const [currentRate, setCurrentRate] = useState(userRate || 0);

const proposalsOptions = useMemo(
() =>
proposals &&
proposals.map((p) => ({
label: p.name,
value: p.censorshiprecord.token
})),
[proposals]
);

const handleCellsChange = useCallback(
(changes) => {
const { grid: newGrid } = processCellsChange(grid, changes, userRate);
Expand Down Expand Up @@ -93,7 +104,7 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
errors,
currentRate,
policy,
proposalsTokens,
proposalsOptions,
subContractors
);
setGrid(grid);
Expand All @@ -104,7 +115,7 @@ const InvoiceDatasheet = React.memo(function InvoiceDatasheet({
errors,
currentRate,
policy,
proposalsTokens,
proposalsOptions,
subContractors
]
);
Expand Down Expand Up @@ -242,7 +253,7 @@ InvoiceDatasheet.propTypes = {
value: PropTypes.array.isRequired,
readOnly: PropTypes.bool.isRequired,
onChange: PropTypes.func,
proposalsTokens: PropTypes.array.isRequired
proposals: PropTypes.array.isRequired
};

InvoiceDatasheet.defaultProps = {
Expand Down
62 changes: 62 additions & 0 deletions src/components/InvoiceDatasheet/components/LazySelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState, useCallback, useMemo, useEffect } from "react";
import { Select, Spinner } from "pi-ui";

const fetchingOption = {
isFetchingOption: true,
label: "Fetch more...",
value: ""
};

const LazySelector = ({
options,
onFetch,
needsFetch,
onChange,
onCommit,
error = null
}) => {
const [selected, setSelected] = useState();
const [loading, setLoading] = useState(false);
const getValueObj = useCallback(
(value) => options.find((op) => op.value === value),
[options]
);

const handleChange = useCallback(
({ isFetchingOption = false, value }) => {
if (isFetchingOption) {
setLoading(true);
onFetch();
} else {
setSelected(getValueObj(value));
onChange(value);
onCommit(value);
}
},
[onFetch, setSelected, onChange, onCommit, getValueObj]
);

const ops = useMemo(
() => (needsFetch ? [...options, fetchingOption] : options),
[options, needsFetch]
);

useEffect(
function onOptionsChangeOrError() {
return () => {
setLoading(false);
};
},
[options, error]
);

return loading ? (
<div className="margin-top-s">
<Spinner invert />
</div>
) : (
<Select options={ops} value={selected} onChange={handleChange} />
);
};

export default LazySelector;
18 changes: 10 additions & 8 deletions src/components/InvoiceDatasheet/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
import {
selectWrapper,
textAreaWrapper,
multilineTextWrapper
multilineTextWrapper,
proposalViewWrapper,
proposalSelectWrapper
} from "./wrappers";

export const columnTypes = {
Expand Down Expand Up @@ -75,7 +77,7 @@ export const convertLineItemsToGrid = (
errors,
userRate = 0,
policy,
proposalsTokens,
proposalsOptions,
subContractors
) => {
const {
Expand Down Expand Up @@ -140,11 +142,8 @@ export const convertLineItemsToGrid = (
readOnly,
value: line.proposaltoken,
error: rowErrors && rowErrors.proposaltoken,
dataEditor: selectWrapper(
proposalsTokens.map((token) => {
return { label: token, value: token };
})
)
dataEditor: proposalSelectWrapper(proposalsOptions),
valueViewer: proposalViewWrapper(proposalsOptions)
},
{
readOnly: isSubContractorReadonly,
Expand Down Expand Up @@ -238,7 +237,7 @@ export const createTableHeaders = () => [
{ value: "Domain", readOnly: true, width: "12rem" },
{ value: "Subdomain", readOnly: true, width: "14rem" },
{ value: "Description", readOnly: true, width: "30rem" },
{ value: "Proposal Token", readOnly: true, width: "10rem" },
{ 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" },
Expand Down Expand Up @@ -363,3 +362,6 @@ export const processCellsChange = (currentGrid, changes, userRate = 0) => {
{ grid: currentGrid }
);
};

export const getProposalsOptions = (proposals = []) =>
proposals.map((p) => ({ label: p.name, value: p.censorshiprecord.token }));
47 changes: 46 additions & 1 deletion src/components/InvoiceDatasheet/wrappers.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React from "react";
import React, { useCallback } from "react";
import SelectEditor from "./components/SelectEditor";
import TextArea from "./components/TextArea";
import LazySelector from "./components/LazySelector";
import { isEmpty } from "src/helpers";
import { Spinner } from "pi-ui";
import useApprovedProposals from "src/hooks/api/useApprovedProposals";

import { multilineCellValue, textWrapper } from "./InvoiceDatasheet.module.css";

const PROPOSAL_PAGE_SIZE = 20;

export const selectWrapper = (options) => (props) => (
<SelectEditor {...{ ...props, options }} />
);
Expand All @@ -16,3 +23,41 @@ export const textAreaWrapper = () => (props) => (
export const multilineTextWrapper = () => ({ value }) => (
<div className={multilineCellValue}>{value}</div>
);

export const proposalViewWrapper = (proposals) => ({ cell: { value } }) => {
const findProposal = useCallback(
(v) => proposals && proposals.find((p) => p.value === v),
[]
);

const selectedProposal = findProposal(value);

return (
<>
{!isEmpty(value) && !selectedProposal && <Spinner invert />}
<span>{selectedProposal && selectedProposal.label}</span>
</>
);
};

export const proposalSelectWrapper = (options) => (props) => {
const {
remainingTokens,
onFetchRemainingProposalsBatch,
error
} = useApprovedProposals();

const onLoadMoreOptions = useCallback(() => {
onFetchRemainingProposalsBatch(PROPOSAL_PAGE_SIZE);
}, [onFetchRemainingProposalsBatch]);

return (
<LazySelector
options={options}
onFetch={onLoadMoreOptions}
needsFetch={remainingTokens.length > 0}
error={error}
{...props}
/>
);
};
Loading

0 comments on commit ca866ff

Please sign in to comment.