diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js new file mode 100644 index 000000000..02a01adff --- /dev/null +++ b/client/src/components/AccountsSelectList.js @@ -0,0 +1,47 @@ +import React, {useMemo, useCallback, useState} from 'react'; +import {omit} from 'lodash'; +import { + MenuItem, + Button +} from '@blueprintjs/core'; +import {Select} from '@blueprintjs/select'; +// import MultiSelect from 'components/MultiSelect'; + +export default function AccountsMultiSelect({ + accounts, + onAccountSelected, +}) { + const [selectedAccount, setSelectedAccount] = useState(null); + + // Account item of select accounts field. + const accountItem = useCallback((item, { handleClick, modifiers, query }) => { + return ( + + ); + }, []); + + const onAccountSelect = useCallback((account) => { + setSelectedAccount({ ...account }); + onAccountSelected && onAccountSelected(account); + }, [setSelectedAccount, onAccountSelected]); + + return ( + + ); +} \ No newline at end of file diff --git a/client/src/components/CurrenciesSelectList.js b/client/src/components/CurrenciesSelectList.js new file mode 100644 index 000000000..cb93c6399 --- /dev/null +++ b/client/src/components/CurrenciesSelectList.js @@ -0,0 +1,62 @@ + + +import React, {useCallback} from 'react'; +import { + FormGroup, + MenuItem, + Button, +} from '@blueprintjs/core'; +import { + Select +} from '@blueprintjs/select'; + + +export default function CurrenciesSelectList(props) { + const {formGroupProps, selectProps, onItemSelect} = props; + const currencies = [{ + name: 'USD US dollars', key: 'USD', + name: 'CAD Canadian dollars', key: 'CAD', + }]; + + // Handle currency item select. + const onCurrencySelect = useCallback((currency) => { + onItemSelect && onItemSelect(currency); + }, [onItemSelect]); + + // Filters currencies list. + const filterCurrenciesPredicator = useCallback((query, currency, _index, exactMatch) => { + const normalizedTitle = currency.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + return `${normalizedTitle}`.indexOf(normalizedQuery) >= 0; + }, []); + + // Currency item of select currencies field. + const currencyItem = (item, { handleClick, modifiers, query }) => { + return ( + + ); + }; + + return ( + + + + ); +} \ No newline at end of file diff --git a/client/src/components/DataTable.js b/client/src/components/DataTable.js index 510ec6f39..71bffc8a4 100644 --- a/client/src/components/DataTable.js +++ b/client/src/components/DataTable.js @@ -44,6 +44,7 @@ export default function DataTable({ virtualizedRows = false, fixedSizeHeight = 100, fixedItemSize = 30, + payload, }) { const { getTableProps, @@ -78,6 +79,7 @@ export default function DataTable({ getSubRows: row => row.children, manualSortBy, expandSubRows, + payload, }, useSortBy, useExpanded, @@ -122,7 +124,7 @@ export default function DataTable({ // Renders table row. const RenderRow = useCallback(({ style = {}, row }) => { prepareRow(row); - const rowClasses = rowClassNames && rowClassNames(row.original); + const rowClasses = rowClassNames && rowClassNames(row); return (
{ + return ( + + {}} /> + + ); +}; + +export default AccountCellRenderer; \ No newline at end of file diff --git a/client/src/components/DataTableCells/InputGroupCell.js b/client/src/components/DataTableCells/InputGroupCell.js new file mode 100644 index 000000000..6e45064f7 --- /dev/null +++ b/client/src/components/DataTableCells/InputGroupCell.js @@ -0,0 +1,31 @@ +import React, {useState, useEffect} from 'react'; +import { + InputGroup +} from '@blueprintjs/core'; + +const InputEditableCell = ({ + value: initialValue, + row: { index }, + column: { id }, + payload, +}) => { + const [value, setValue] = useState(initialValue) + + const onChange = e => { + setValue(e.target.value) + } + const onBlur = () => { + payload.updateData(index, id, value) + } + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + return (); +}; + +export default InputEditableCell; diff --git a/client/src/components/DataTableCells/MoneyFieldCell.js b/client/src/components/DataTableCells/MoneyFieldCell.js new file mode 100644 index 000000000..db1e9b2c5 --- /dev/null +++ b/client/src/components/DataTableCells/MoneyFieldCell.js @@ -0,0 +1,31 @@ +import React, { useCallback, useState } from 'react'; +import MoneyInputGroup from 'components/MoneyInputGroup'; + +// Input form cell renderer. +const MoneyFieldCellRenderer = ({ + row: { index }, + column: { id }, + cell: { value: initialValue }, + payload +}) => { + const [value, setValue] = useState(initialValue); + + const handleFieldChange = useCallback((e, value) => { + setValue(value); + }, []); + + const onBlur = () => { + payload.updateData(index, id, value) + }; + + return () +}; + +export default MoneyFieldCellRenderer; \ No newline at end of file diff --git a/client/src/components/DataTableCells/index.js b/client/src/components/DataTableCells/index.js new file mode 100644 index 000000000..a7bc525cb --- /dev/null +++ b/client/src/components/DataTableCells/index.js @@ -0,0 +1,9 @@ +import AccountsListFieldCell from './AccountsListFieldCell'; +import MoneyFieldCell from './MoneyFieldCell'; +import InputGroupCell from './InputGroupCell'; + +export { + AccountsListFieldCell, + MoneyFieldCell, + InputGroupCell, +} \ No newline at end of file diff --git a/client/src/components/Money.js b/client/src/components/Money.js index 0acd8d993..b708fded6 100644 --- a/client/src/components/Money.js +++ b/client/src/components/Money.js @@ -1,13 +1,5 @@ import React from 'react'; -import Currency from 'js-money/lib/currency'; -import accounting from 'accounting'; - -function formattedAmount(cents, currency) { - const { symbol, decimal_digits: precision } = Currency[currency]; - const amount = cents / Math.pow(10, precision); - - return accounting.formatMoney(amount, { symbol, precision }); -} +import {formattedAmount} from 'utils'; export default function Money({ amount, currency }) { return ( diff --git a/client/src/components/MoneyInputGroup.js b/client/src/components/MoneyInputGroup.js new file mode 100644 index 000000000..adefb2913 --- /dev/null +++ b/client/src/components/MoneyInputGroup.js @@ -0,0 +1,88 @@ +import React, {useState, useMemo, useEffect, useCallback} from 'react'; +import {InputGroup} from '@blueprintjs/core'; + +const joinIntegerAndDecimal = (integer, decimal, separator) => { + let output = `${integer}`; + + if (separator) { + output += separator; + output += decimal ? decimal : ''; + } + return output; +}; + +const hasSeparator = (input, separator) => { + return -1 !== input.indexOf(separator); +}; + +const addThousandSeparator = (integer, separator) => { + return integer.replace(/(\d)(?=(?:\d{3})+\b)/gm, `$1${separator}`) +}; + +const toString = (number) => `${number}`; + +const onlyNumbers = (input) => { + return toString(input).replace(/\D+/g, '') || '0' +}; + +const formatter = (value, options) => { + const input = toString(value); + const navigate = input.indexOf('-') >= 0 ? '-' : ''; + const parts = toString(input).split(options.decimal); + const integer = parseInt(onlyNumbers(parts[0]), 10); + const decimal = parts[1] ? onlyNumbers(parts[1]) : null; + const integerThousand = addThousandSeparator(toString(integer), options.thousands); + const separator = hasSeparator(input, options.decimal) + ? options.decimal : false; + + return `${navigate}${options.prefix}${joinIntegerAndDecimal(integerThousand, decimal, separator)}${options.suffix}`; +}; + +const unformatter = (input, options) => { + const navigate = input.indexOf('-') >= 0 ? '-' : ''; + const parts = toString(input).split(options.decimal); + const integer = parseInt(onlyNumbers(parts[0]), 10); + const decimal = parts[1] ? onlyNumbers(parts[1]) : null; + const separator = hasSeparator(input, options.decimal) + ? options.decimal : false; + + return `${navigate}${joinIntegerAndDecimal(integer, decimal, separator)}`; +}; + + +export default function MoneyFieldGroup({ + value, + prefix = '', + suffix = '', + thousands = ',', + decimal = '.', + precision = 2, + inputGroupProps, + onChange, +}) { + const [state, setState] = useState(value); + + const options = useMemo(() => ({ + prefix, suffix, thousands, decimal, precision, + }), []); + + const handleChange = useCallback((event) => { + const formatted = formatter(event.target.value, options); + const value = unformatter(event.target.value, options); + + setState(formatted); + onChange && onChange(event, value); + }, []); + + useEffect(() => { + const formatted = formatter(value, options); + setState(formatted) + }, []); + + return ( + + ); +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js index 0048cb4b5..0273b020a 100644 --- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js +++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesFooter.js @@ -8,30 +8,9 @@ import { FormattedList } from 'react-intl'; export default function MakeJournalEntriesFooter({ formik, }) { - const creditSum = useMemo(() => { - return formik.values.entries.reduce((sum, entry) => { - return entry.credit + sum; - }, 0); - }, [formik.values.entries]); - - const debitSum = useMemo(() => { - return formik.values.entries.reduce((sum, entry) => { - return entry.debit + sum; - }, 0); - }, [formik.values.entries]); return (
- - - - - - - - -
Total{ creditSum }{ debitSum }
-
); diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js index 471e0e559..ecd3ee860 100644 --- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js +++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesForm.js @@ -76,13 +76,14 @@ function MakeJournalEntriesForm({ }, }); - console.log(formik.errors); return ( -
- - - - +
+
+ + + + +
); } diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js index 2ac7a58d6..bf8ba92d9 100644 --- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js +++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesHeader.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo, useCallback} from 'react'; import * as Yup from 'yup'; import { InputGroup, @@ -12,6 +12,9 @@ import {useIntl} from 'react-intl'; import {Row, Col} from 'react-grid-system'; import moment from 'moment'; import {momentFormatter} from 'utils'; +import Icon from 'components/Icon'; +import CurrenciesSelectList from 'components/CurrenciesSelectList'; + export default function MakeJournalEntriesHeader({ formik @@ -22,25 +25,29 @@ export default function MakeJournalEntriesHeader({ const formatted = moment(date).format('YYYY-MM-DD'); formik.setFieldValue('date', formatted); }; + + const infoIcon = useMemo(() => (), []); + return (
- + + {...formik.getFieldProps('journal_number')} /> - + + + + + + + + + + + + + +
); } \ No newline at end of file diff --git a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesTable.js b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesTable.js index cc6476b63..5dde6a63f 100644 --- a/client/src/containers/Dashboard/Accounting/MakeJournalEntriesTable.js +++ b/client/src/containers/Dashboard/Accounting/MakeJournalEntriesTable.js @@ -1,55 +1,88 @@ -import React, {useState, useMemo} from 'react'; +import React, {useState, useMemo, useCallback} from 'react'; import DataTable from 'components/DataTable'; import { - FormGroup, - InputGroup, - MenuItem, Button, Intent, } from '@blueprintjs/core'; -import {Select} from '@blueprintjs/select'; import Icon from 'components/Icon'; import AccountsConnect from 'connectors/Accounts.connector.js'; -import {compose} from 'utils'; +import {compose, formattedAmount} from 'utils'; +import { + AccountsListFieldCell, + MoneyFieldCell, + InputGroupCell, +} from 'comp}onents/DataTableCells'; + +// Actions cell renderer. +const ActionsCellRenderer = ({ + row: { index }, + column: { id }, + cell: { value: initialValue }, + data, + payload, +}) => { + if (data.length <= (index + 2)) { + return ''; + } + const onClickRemoveRole = () => { + payload.removeRow(index); + }; + + return ( +
diff --git a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js index 09ef4a543..281a94458 100644 --- a/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js +++ b/client/src/containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheetHeader.js @@ -46,7 +46,7 @@ export default function BalanceSheetHeader({ formik.setFieldValue('display_columns_by', item.by); }, []); - // handle submit filter submit button. + // Handle submit filter submit button. const handleSubmitClick = useCallback(() => { formik.submitForm(); }, [formik]); diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js index 198c0f8d0..bbc23219f 100644 --- a/client/src/static/json/icons.js +++ b/client/src/static/json/icons.js @@ -95,4 +95,8 @@ export default { path: ['M452.515 237l31.843-18.382c9.426-5.441 13.996-16.542 11.177-27.054-11.404-42.531-33.842-80.547-64.058-110.797-7.68-7.688-19.575-9.246-28.985-3.811l-31.785 18.358a196.276 196.276 0 0 0-32.899-19.02V39.541a24.016 24.016 0 0 0-17.842-23.206c-41.761-11.107-86.117-11.121-127.93-.001-10.519 2.798-17.844 12.321-17.844 23.206v36.753a196.276 196.276 0 0 0-32.899 19.02l-31.785-18.358c-9.41-5.435-21.305-3.877-28.985 3.811-30.216 30.25-52.654 68.265-64.058 110.797-2.819 10.512 1.751 21.613 11.177 27.054L59.485 237a197.715 197.715 0 0 0 0 37.999l-31.843 18.382c-9.426 5.441-13.996 16.542-11.177 27.054 11.404 42.531 33.842 80.547 64.058 110.797 7.68 7.688 19.575 9.246 28.985 3.811l31.785-18.358a196.202 196.202 0 0 0 32.899 19.019v36.753a24.016 24.016 0 0 0 17.842 23.206c41.761 11.107 86.117 11.122 127.93.001 10.519-2.798 17.844-12.321 17.844-23.206v-36.753a196.34 196.34 0 0 0 32.899-19.019l31.785 18.358c9.41 5.435 21.305 3.877 28.985-3.811 30.216-30.25 52.654-68.266 64.058-110.797 2.819-10.512-1.751-21.613-11.177-27.054L452.515 275c1.22-12.65 1.22-25.35 0-38zm-52.679 63.019l43.819 25.289a200.138 200.138 0 0 1-33.849 58.528l-43.829-25.309c-31.984 27.397-36.659 30.077-76.168 44.029v50.599a200.917 200.917 0 0 1-67.618 0v-50.599c-39.504-13.95-44.196-16.642-76.168-44.029l-43.829 25.309a200.15 200.15 0 0 1-33.849-58.528l43.819-25.289c-7.63-41.299-7.634-46.719 0-88.038l-43.819-25.289c7.85-21.229 19.31-41.049 33.849-58.529l43.829 25.309c31.984-27.397 36.66-30.078 76.168-44.029V58.845a200.917 200.917 0 0 1 67.618 0v50.599c39.504 13.95 44.196 16.642 76.168 44.029l43.829-25.309a200.143 200.143 0 0 1 33.849 58.529l-43.819 25.289c7.631 41.3 7.634 46.718 0 88.037zM256 160c-52.935 0-96 43.065-96 96s43.065 96 96 96 96-43.065 96-96-43.065-96-96-96zm0 144c-26.468 0-48-21.532-48-48 0-26.467 21.532-48 48-48s48 21.533 48 48c0 26.468-21.532 48-48 48'], viewBox: '0 0 512 512', }, + "times-circle": { + path: ['M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 464c-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216 0 118.7-96.1 216-216 216zm94.8-285.3L281.5 256l69.3 69.3c4.7 4.7 4.7 12.3 0 17l-8.5 8.5c-4.7 4.7-12.3 4.7-17 0L256 281.5l-69.3 69.3c-4.7 4.7-12.3 4.7-17 0l-8.5-8.5c-4.7-4.7-4.7-12.3 0-17l69.3-69.3-69.3-69.3c-4.7-4.7-4.7-12.3 0-17l8.5-8.5c4.7-4.7 12.3-4.7 17 0l69.3 69.3 69.3-69.3c4.7-4.7 12.3-4.7 17 0l8.5 8.5c4.6 4.7 4.6 12.3 0 17z'], + viewBox: '0 0 512 512', + } } \ No newline at end of file diff --git a/client/src/style/components/data-table.scss b/client/src/style/components/data-table.scss index 2706f6237..f607ab572 100644 --- a/client/src/style/components/data-table.scss +++ b/client/src/style/components/data-table.scss @@ -47,7 +47,7 @@ margin: 0; padding: 0.5rem; - :last-child { + &:last-child { border-right: 0; } diff --git a/client/src/style/objects/buttons.scss b/client/src/style/objects/buttons.scss index c41e73483..28e574018 100644 --- a/client/src/style/objects/buttons.scss +++ b/client/src/style/objects/buttons.scss @@ -32,6 +32,11 @@ color: #555555; box-shadow: 0 0 0 transparent; + &.bp3-small{ + font-size: 13px; + min-height: 29px; + } + .form-group--select-list &{ border-radius: 0; diff --git a/client/src/style/objects/form.scss b/client/src/style/objects/form.scss index cd1bb465c..763488918 100644 --- a/client/src/style/objects/form.scss +++ b/client/src/style/objects/form.scss @@ -17,7 +17,7 @@ label{ } .#{$ns}-input{ - box-shadow: 0 0 0; + box-shadow: 0 0 0 transparent; border: 1px solid #ced4da; border-radius: 0; height: 32px; @@ -94,6 +94,10 @@ label{ .#{$ns}-icon-caret-down{ color: #8D8D8D; } + + &.bp3-fill{ + width: 100%; + } } diff --git a/client/src/style/pages/make-journal-entries.scss b/client/src/style/pages/make-journal-entries.scss index c7dd693d0..241044e82 100644 --- a/client/src/style/pages/make-journal-entries.scss +++ b/client/src/style/pages/make-journal-entries.scss @@ -2,38 +2,147 @@ .make-journal-entries{ &__header{ - padding: 25px 40px; + padding: 25px 27px 20px; background: #fbfbfb; + + .bp3-form-group{ + + .bp3-label{ + font-weight: 500; + font-size: 13px; + color: #444; + } + } } &__table{ + padding: 15px; + .bp3-form-group{ margin-bottom: 0; } - table{ - border: 1px solid transparent; + .table{ + border: 1px dotted rgb(195, 195, 195); + border-bottom: transparent; + border-left: transparent; - thead{ - th.index{ width: 3%; } - th.account{ width: 22%; } - th.note{ width: 40%; } - th.credit{ width: 17.5%; } - th.debit{ width: 17.5%; } + .th, + .td{ + border-left: 1px dotted rgb(195, 195, 195); + + &.index{ + + span{ + width: 100%; + font-weight: 500; + } + } } - th, - td{ - border-right: 1px dotted #999; + .thead{ + .tr .th{ + padding: 10px 10px; + background-color: #F2F5FA; + font-size: 14px; + font-weight: 500; + color: #333; + } } - th{ + + .tbody{ + .tr .td{ + padding: 7px; + border-bottom: 1px dotted rgb(195, 195, 195); + min-height: 46px; + + &.index{ + background-color: #F2F5FA; + text-align: center; + + > span{ + margin-top: auto; + margin-bottom: auto; + } + } + } + .tr{ + .bp3-input, + .form-group--select-list .bp3-button{ + border-color: #E5E5E5; + border-radius: 3px; + padding-left: 8px; + padding-right: 8px; + } + + &:last-of-type{ + + .td{ + border-bottom: transparent; + + .bp3-button, + .bp3-input-group{ + display: none; + } + } + } + + .td.actions{ + .bp3-button{ + background-color: transparent; + color: #E68F8E; + + &:hover{ + color: #c23030; + } + } + } + + &.row--total{ + + .account.td, + .debit.td, + .credit.td{ + > span{ + padding-top: 6px; + } + } + .debit.td, + .credit.td{ + > span{ + font-weight: 600; + color: #444; + } + } + } + } + } + .th{ color: #444; font-weight: 600; border-bottom: 1px dotted #666; } - td{ + .td{ border-bottom: 1px dotted #999; } + + .actions.td{ + .bp3-button{ + background: transparent; + margin: 0; + } + } } } + + .bp3-button{ + &.button--clear-lines{ + background-color: #FCEFEF; + } + } + .button--clear-lines, + .button--new-line{ + padding-left: 14px; + padding-right: 14px; + } }