WIP financial statements.

This commit is contained in:
Ahmed Bouhuolia
2020-03-31 16:30:38 +02:00
parent da05239e84
commit 1bf837ae17
26 changed files with 442 additions and 148 deletions

View File

@@ -16,6 +16,7 @@
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0", "@typescript-eslint/parser": "^2.10.0",
"accounting": "^0.4.1",
"axios": "^0.19.2", "axios": "^0.19.2",
"babel-eslint": "10.0.3", "babel-eslint": "10.0.3",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
@@ -45,6 +46,7 @@
"jest-environment-jsdom-fourteen": "1.0.1", "jest-environment-jsdom-fourteen": "1.0.1",
"jest-resolve": "24.9.0", "jest-resolve": "24.9.0",
"jest-watch-typeahead": "0.4.2", "jest-watch-typeahead": "0.4.2",
"js-money": "^0.6.3",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"mini-css-extract-plugin": "0.9.0", "mini-css-extract-plugin": "0.9.0",
"moment": "^2.24.0", "moment": "^2.24.0",

View File

@@ -131,7 +131,8 @@ function AccountsDataTable({
columns={columns} columns={columns}
data={accounts} data={accounts}
onFetchData={handleDatatableFetchData} onFetchData={handleDatatableFetchData}
manualSortBy={true} /> manualSortBy={true}
selectionColumn={true} />
</LoadingIndicator> </LoadingIndicator>
); );
} }

View File

@@ -12,7 +12,6 @@ import {
import {Checkbox} from '@blueprintjs/core'; import {Checkbox} from '@blueprintjs/core';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
// import { FixedSizeList } from 'react-window'
const IndeterminateCheckbox = React.forwardRef( const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => { ({ indeterminate, ...rest }, ref) => {
@@ -23,9 +22,7 @@ const IndeterminateCheckbox = React.forwardRef(
resolvedRef.current.indeterminate = indeterminate resolvedRef.current.indeterminate = indeterminate
}, [resolvedRef, indeterminate]) }, [resolvedRef, indeterminate])
return ( return (<Checkbox ref={resolvedRef} {...rest} />);
<Checkbox ref={resolvedRef} {...rest} />
);
} }
); );
@@ -35,7 +32,9 @@ export default function DataTable({
loading, loading,
onFetchData, onFetchData,
onSelectedRowsChange, onSelectedRowsChange,
manualSortBy = 'false' manualSortBy = 'false',
selectionColumn = false,
className
}) { }) {
const { const {
getTableProps, getTableProps,
@@ -51,8 +50,8 @@ export default function DataTable({
nextPage, nextPage,
previousPage, previousPage,
setPageSize, setPageSize,
selectedFlatRows, selectedFlatRows,
// Get the state from the instance // Get the state from the instance
state: { pageIndex, pageSize, sortBy, selectedRowIds }, state: { pageIndex, pageSize, sortBy, selectedRowIds },
} = useTable( } = useTable(
@@ -77,7 +76,7 @@ export default function DataTable({
hooks => { hooks => {
hooks.visibleColumns.push(columns => [ hooks.visibleColumns.push(columns => [
// Let's make a column for selection // Let's make a column for selection
{ ...(selectionColumn) ? [{
id: 'selection', id: 'selection',
disableResizing: true, disableResizing: true,
minWidth: 35, minWidth: 35,
@@ -97,23 +96,19 @@ export default function DataTable({
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} /> <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div> </div>
), ),
}, }] : [],
...columns, ...columns,
]) ])
} }
); );
// Debounce our onFetchData call for 100ms
const onFetchDataDebounced = useAsyncDebounce(onFetchData, 100);
const onSelectRowsDebounced = useAsyncDebounce(onSelectedRowsChange, 250);
// When these table states change, fetch new data! // When these table states change, fetch new data!
useEffect(() => { useEffect(() => {
onFetchDataDebounced({ pageIndex, pageSize, sortBy }) onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
}, []); }, [pageIndex, pageSize, sortBy]);
return ( return (
<div className={'bigcapital-datatable'}> <div className={classnames('bigcapital-datatable', className)}>
<div {...getTableProps()} className="table"> <div {...getTableProps()} className="table">
<div className="thead"> <div className="thead">
{headerGroups.map(headerGroup => ( {headerGroups.map(headerGroup => (
@@ -157,8 +152,7 @@ export default function DataTable({
className: classnames(cell.column.className || '', 'td'), className: classnames(cell.column.className || '', 'td'),
})}>{ cell.render('Cell') }</div> })}>{ cell.render('Cell') }</div>
})} })}
</div> </div>)
)
})} })}
</div> </div>
</div> </div>

View File

@@ -1,26 +1,35 @@
import React, { Children } from 'react'; import React, { Children } from 'react';
import moment from 'moment';
import classnames from 'classnames';
import LoadingIndicator from 'components/LoadingIndicator';
export default function FinancialSheet({ export default function FinancialSheet({
companyTitle, companyTitle,
sheetType, sheetType,
date, date,
children, children,
accountingBasis accountingBasis,
name,
loading,
}) { }) {
const formattedDate = moment(date).format('DD MMMM YYYY')
const nameModifer = name ? `financial-sheet--${name}` : '';
return ( return (
<div class="financial-sheet"> <div className={classnames('financial-sheet', nameModifer)}>
<h1 class="financial-sheet__title">{ companyTitle }</h1> <LoadingIndicator loading={loading}>
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6> <h1 class="financial-sheet__title">{ companyTitle }</h1>
<span class="financial-sheet__date">{ date }</span> <h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
<div class="financial-sheet__date">As of { formattedDate }</div>
<div class="financial-sheet__table"> <div class="financial-sheet__table">
{ children } { children }
</div> </div>
<div class="financial-sheet__accounting-basis"> <div class="financial-sheet__accounting-basis">
{ accountingBasis } { accountingBasis }
</div> </div>
</LoadingIndicator>
</div> </div>
); );
} }

View File

@@ -4,9 +4,10 @@ import { Spinner } from '@blueprintjs/core';
export default function LoadingIndicator({ export default function LoadingIndicator({
loading, loading,
spinnerSize = 40, spinnerSize = 40,
children children,
mount = true,
}) { }) {
const [rendered, setRendered] = useState(false); const [rendered, setRendered] = useState(mount);
useEffect(() => { useEffect(() => {
if (!loading) { setRendered(true); } if (!loading) { setRendered(true); }

View File

@@ -0,0 +1,16 @@
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 });
}
export default function Money({ amount, currency }) {
return (
<span>{ formattedAmount(amount, currency) }</span>
);
}

View File

@@ -6,12 +6,15 @@ import {
getTrialBalanceSheetIndex, getTrialBalanceSheetIndex,
getTrialBalanceAccounts, getTrialBalanceAccounts,
getTrialBalanceQuery, getTrialBalanceQuery,
getFinancialSheetIndexByQuery,
} from 'store/financialStatement/financialStatements.selectors'; } from 'store/financialStatement/financialStatements.selectors';
export const mapStateToProps = (state, props) => ({ export const mapStateToProps = (state, props) => ({
getTrialBalanceSheetIndex: (query) => getTrialBalanceSheetIndex(state.financialStatements.trialBalanceSheets, query), getTrialBalanceSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.trialBalance.sheets, query),
getTrialBalanceAccounts: (sheetIndex) => getTrialBalanceAccounts(state.financialStatements.trialBalanceSheets, sheetIndex), getTrialBalanceAccounts: (sheetIndex) => getTrialBalanceAccounts(state.financialStatements.trialBalance.sheets, sheetIndex),
getTrialBalanceQuery: (sheetIndex) => getTrialBalanceQuery(state.financialStatements.trialBalanceSheets, sheetIndex), getTrialBalanceQuery: (sheetIndex) => getTrialBalanceQuery(state.financialStatements.trialBalance.sheets, sheetIndex),
trialBalanceSheetLoading: state.financialStatements.trialBalance.loading,
}); });
export const mapDispatchToProps = (dispatch) => ({ export const mapDispatchToProps = (dispatch) => ({

View File

@@ -1,4 +1,4 @@
import React, {useEffect, useMemo, useState} from 'react'; import React, {useEffect, useMemo, useCallback, useState} from 'react';
import DashboardConnect from 'connectors/Dashboard.connector'; import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils'; import {compose} from 'utils';
import useAsync from 'hooks/async'; import useAsync from 'hooks/async';
@@ -30,39 +30,41 @@ function BalanceSheet({
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
await Promise.all([ await Promise.all([
fetchBalanceSheet(filter), fetchBalanceSheet({
...filter,
display_columns_type: 'total',
}),
]); ]);
setReload(false); setReload(false);
}); });
useEffect(() => { // Handle fetch the data of balance sheet.
if (!reload) { return; } const handleFetchData = useCallback(() => { fetchHook.execute(); }, [fetchHook]);
fetchHook.execute();
}, [reload]);
useEffect(() => { useEffect(() => {
changePageTitle('Balance Sheet'); changePageTitle('Balance Sheet');
}, []); }, []);
// Retrieve balance sheet index by the given filter query. // Retrieve balance sheet index by the given filter query.
const balanceSheetIndex = useMemo(() => { const balanceSheetIndex = useMemo(() =>
return getBalanceSheetIndexByQuery(filter); getBalanceSheetIndexByQuery(filter),
}, [filter, balanceSheets]); [filter, getBalanceSheetIndexByQuery]);
// Retreive balance sheet by the given sheet index. // Retreive balance sheet by the given sheet index.
const balanceSheet = useMemo(() => { const balanceSheet = useMemo(() =>
return getBalanceSheetByIndex(balanceSheetIndex); getBalanceSheetByIndex(balanceSheetIndex),
}, [balanceSheetIndex, balanceSheets]); [balanceSheetIndex, getBalanceSheetByIndex]);
// Handle re-fetch balance sheet after filter change. // Handle re-fetch balance sheet after filter change.
const handleFilterSubmit = (filter) => { const handleFilterSubmit = useCallback((filter) => {
setFilter({ setFilter({
...filter, ...filter,
from_date: moment(filter.from_date).format('YYYY-MM-DD'), from_date: moment(filter.from_date).format('YYYY-MM-DD'),
to_date: moment(filter.to_date).format('YYYY-MM-DD'), to_date: moment(filter.to_date).format('YYYY-MM-DD'),
}); });
setReload(true); setReload(true);
}; }, [setFilter]);
return ( return (
<div class="financial-statement"> <div class="financial-statement">
<BalanceSheetHeader <BalanceSheetHeader
@@ -73,7 +75,9 @@ function BalanceSheet({
<LoadingIndicator loading={fetchHook.pending}> <LoadingIndicator loading={fetchHook.pending}>
<BalanceSheetTable <BalanceSheetTable
balanceSheet={balanceSheet} balanceSheet={balanceSheet}
balanceSheetIndex={balanceSheetIndex} /> balanceSheetIndex={balanceSheetIndex}
onFetchData={handleFetchData}
asDate={new Date()} />
</LoadingIndicator> </LoadingIndicator>
</div> </div>
</div> </div>

View File

@@ -11,6 +11,7 @@ import {
HTMLSelect, HTMLSelect,
Intent, Intent,
Popover, Popover,
Classes,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import {Select} from '@blueprintjs/select'; import {Select} from '@blueprintjs/select';
import {DateInput} from '@blueprintjs/datetime'; import {DateInput} from '@blueprintjs/datetime';
@@ -21,6 +22,7 @@ import {
parseDateRangeQuery, parseDateRangeQuery,
} from 'utils'; } from 'utils';
import moment from 'moment'; import moment from 'moment';
import Icon from 'components/Icon';
export default function BalanceSheetHeader({ export default function BalanceSheetHeader({
onSubmitFilter, onSubmitFilter,
@@ -111,12 +113,16 @@ export default function BalanceSheetHeader({
</RadioGroup> </RadioGroup>
</div> </div>
); );
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
return ( return (
<FinancialStatementHeader> <FinancialStatementHeader>
<Row> <Row>
<Col sm={3}> <Col sm={3}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'report_date_range'})} label={intl.formatMessage({'id': 'report_date_range'})}
labelInfo={infoIcon}
minimal={true} minimal={true}
fill={true}> fill={true}>
@@ -131,6 +137,7 @@ export default function BalanceSheetHeader({
<Col sm={3}> <Col sm={3}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'from_date'})} label={intl.formatMessage({'id': 'from_date'})}
labelInfo={infoIcon}
minimal={true} minimal={true}
fill={true}> fill={true}>
@@ -146,6 +153,7 @@ export default function BalanceSheetHeader({
<Col sm={3}> <Col sm={3}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'to_date'})} label={intl.formatMessage({'id': 'to_date'})}
labelInfo={infoIcon}
minimal={true} minimal={true}
fill={true}> fill={true}>
@@ -163,7 +171,8 @@ export default function BalanceSheetHeader({
<Col sm={3}> <Col sm={3}>
<FormGroup <FormGroup
label={'Display report columns'} label={'Display report columns'}
className="{'form-group-display-columns-by'}" labelInfo={infoIcon}
className="form-group-display-columns-by form-group--select-list bp3-fill"
inline={false}> inline={false}>
<Select <Select
@@ -184,6 +193,7 @@ export default function BalanceSheetHeader({
<Col sm={3}> <Col sm={3}>
<FormGroup <FormGroup
label={'Show non-zero or active only'} label={'Show non-zero or active only'}
className="form-group--select-list bp3-fill"
inline={false}> inline={false}>
<Popover <Popover

View File

@@ -1,9 +1,10 @@
import React, {useMemo, useState, useEffect} from 'react'; import React, {useMemo, useState, useCallback, useEffect} from 'react';
import FinancialSheet from 'components/FinancialSheet'; import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import FinancialStatementConnect from 'connectors/FinancialStatements.connector'; import FinancialStatementConnect from 'connectors/FinancialStatements.connector';
import {compose} from 'utils'; import {compose} from 'utils';
import moment from 'moment'; import moment from 'moment';
import Money from 'components/Money';
function BalanceSheetTable({ function BalanceSheetTable({
balanceSheet, balanceSheet,
@@ -14,25 +15,32 @@ function BalanceSheetTable({
getBalanceSheetLiabilitiesAccounts, getBalanceSheetLiabilitiesAccounts,
getBalanceSheetQuery, getBalanceSheetQuery,
}) {
const balanceSheetColumns = useMemo(() => {
return getBalanceSheetColumns(balanceSheetIndex);
}, [getBalanceSheetColumns]);
const balanceSheetQuery = useMemo(() => { onFetchData,
return getBalanceSheetQuery(balanceSheetIndex); asDate,
}, [getBalanceSheetQuery]) }) {
const balanceSheetColumns = useMemo(() =>
getBalanceSheetColumns(balanceSheetIndex),
[getBalanceSheetColumns, balanceSheetIndex]);
const balanceSheetQuery = useMemo(() =>
getBalanceSheetQuery(balanceSheetIndex),
[getBalanceSheetQuery, balanceSheetIndex])
const columns = useMemo(() => [ const columns = useMemo(() => [
{ {
// Build our expander column // Build our expander column
id: 'expander', // Make sure it has an ID id: 'expander', // Make sure it has an ID
className: 'expander',
Header: ({ Header: ({
getToggleAllRowsExpandedProps, getToggleAllRowsExpandedProps,
isAllRowsExpanded isAllRowsExpanded
}) => ( }) => (
<span {...getToggleAllRowsExpandedProps()}> <span {...getToggleAllRowsExpandedProps()} className="toggle">
{isAllRowsExpanded ? '👇' : '👉'} {isAllRowsExpanded ?
(<span class="arrow-down" />) :
(<span class="arrow-right" />)
}
</span> </span>
), ),
Cell: ({ row }) => Cell: ({ row }) =>
@@ -47,30 +55,41 @@ function BalanceSheetTable({
// of the row // of the row
paddingLeft: `${row.depth * 2}rem`, paddingLeft: `${row.depth * 2}rem`,
}, },
className: 'toggle',
})} })}
> >
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ?
(<span class="arrow-down" />) :
(<span class="arrow-right" />)
}
</span> </span>
) : null, ) : null,
width: 20,
disableResizing: true,
}, },
{ {
Header: 'Account Name', Header: 'Account Name',
accessor: 'name', accessor: 'name',
className: "actions", className: "account_name",
}, },
{ {
Header: 'Code', Header: 'Code',
accessor: 'code', accessor: 'code',
className: "note", className: "code",
}, },
...(balanceSheetQuery && ...(balanceSheetQuery &&
balanceSheetQuery.display_columns_by === 'total') ? [ balanceSheetQuery.display_columns_by === 'total') ? [
{ {
Header: 'Total', Header: 'Total',
accessor: 'balance.formatted_amount', accessor: 'balance.formatted_amount',
Cell: ({ cell }) => {
const row = cell.row.original;
if (!row.balance) { return ''; }
return (<Money amount={row.balance.formatted_amount} currency={'USD'} />);
},
className: "credit", className: "credit",
} }
]: (balanceSheetColumns.map((column, index) => ({ ] : (balanceSheetColumns.map((column, index) => ({
Header: column, Header: column,
accessor: (row) => { accessor: (row) => {
if (row.periods_balance && row.periods_balance[index]) { if (row.periods_balance && row.periods_balance[index]) {
@@ -87,33 +106,32 @@ function BalanceSheetTable({
setData([ setData([
{ {
name: 'Assets', name: 'Assets',
code: '', children: getBalanceSheetAssetsAccounts(balanceSheetIndex),
children: [
...getBalanceSheetAssetsAccounts(balanceSheetIndex),
],
}, },
{ {
name: 'Liabilies & Equity', name: 'Liabilies & Equity',
code: '', children: getBalanceSheetLiabilitiesAccounts(balanceSheetIndex),
children: [
...getBalanceSheetLiabilitiesAccounts(balanceSheetIndex),
]
} }
]) ])
}, []) }, []);
const handleFetchData = useCallback(() => {
onFetchData && onFetchData();
}, [onFetchData]);
return ( return (
<FinancialSheet <FinancialSheet
companyTitle={'Facebook, Incopration'} companyTitle={'Facebook, Incopration'}
sheetType={'Balance Sheet'} sheetType={'Balance Sheet'}
date={''}> date={asDate}>
<DataTable <DataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={data} /> data={data}
onFetchData={handleFetchData} />
</FinancialSheet> </FinancialSheet>
) );
} }
export default compose( export default compose(

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useState, useMemo } from 'react'; import React, { useEffect, useCallback, useState, useMemo } from 'react';
import TrialBalanceSheetHeader from "./TrialBalanceSheetHeader"; import TrialBalanceSheetHeader from "./TrialBalanceSheetHeader";
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import TrialBalanceSheetTable from './TrialBalanceSheetTable'; import TrialBalanceSheetTable from './TrialBalanceSheetTable';
import { useAsync } from 'react-use'; import useAsync from 'hooks/async';
import moment from 'moment'; import moment from 'moment';
import {compose} from 'utils'; import {compose} from 'utils';
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect'; import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
@@ -13,44 +13,48 @@ function TrialBalanceSheet({
fetchTrialBalanceSheet, fetchTrialBalanceSheet,
getTrialBalanceSheetIndex, getTrialBalanceSheetIndex,
getTrialBalanceAccounts, getTrialBalanceAccounts,
trialBalanceSheetLoading,
}) { }) {
const [filter, setFilter] = useState({ const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'), from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'), to_date: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash', basis: 'accural',
none_zero: false, none_zero: false,
}); });
const [reload, setReload] = useState(false);
const fetchHook = useAsync(async () => { const fetchHook = useAsync((query = filter) => {
await Promise.all([ return Promise.all([
fetchTrialBalanceSheet(), fetchTrialBalanceSheet(query),
]); ]);
}); }, false);
// handle fetch data of trial balance table.
const handleFetchData = useCallback(() => { fetchHook.execute() }, [fetchHook]);
// Retrieve balance sheet index by the given filter query. // Retrieve balance sheet index by the given filter query.
const trialBalanceSheetIndex = useMemo(() => { const trialBalanceSheetIndex = useMemo(() =>
return getTrialBalanceSheetIndex(filter); getTrialBalanceSheetIndex(filter),
}, [getTrialBalanceSheetIndex, filter]); [getTrialBalanceSheetIndex, filter]);
// Retrieve balance sheet accounts bu the given sheet index. // Retrieve balance sheet accounts bu the given sheet index.
const trialBalanceAccounts = useMemo(() => { const trialBalanceAccounts = useMemo(() =>
return getTrialBalanceAccounts(trialBalanceSheetIndex); getTrialBalanceAccounts(trialBalanceSheetIndex),
}, [trialBalanceSheetIndex]); [getTrialBalanceAccounts, trialBalanceSheetIndex]);
// Change page title of the dashboard. // Change page title of the dashboard.
useEffect(() => { useEffect(() => {
changePageTitle('Trial Balance Sheet'); changePageTitle('Trial Balance Sheet');
}, []); }, []);
const handleFilterSubmit = (filter) => { const handleFilterSubmit = useCallback((filter) => {
setFilter({ const parsedFilter = {
...filter, ...filter,
from_date: moment(filter.from_date).format('YYYY-MM-DD'), from_date: moment(filter.from_date).format('YYYY-MM-DD'),
to_date: moment(filter.to_date).format('YYYY-MM-DD'), to_date: moment(filter.to_date).format('YYYY-MM-DD'),
}); };
setReload(true); setFilter(parsedFilter);
}; fetchHook.execute(parsedFilter);
}, [setFilter, fetchHook]);
return ( return (
<div class="financial-statement"> <div class="financial-statement">
@@ -59,11 +63,11 @@ function TrialBalanceSheet({
onSubmitFilter={handleFilterSubmit} /> onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body"> <div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}> <TrialBalanceSheetTable
<TrialBalanceSheetTable trialBalanceSheetAccounts={trialBalanceAccounts}
trialBalanceSheetAccounts={trialBalanceAccounts} trialBalanceSheetIndex={trialBalanceSheetIndex}
trialBalanceSheetIndex={trialBalanceSheetIndex} /> onFetchData={handleFetchData}
</LoadingIndicator> loading={trialBalanceSheetLoading} />
</div> </div>
</div> </div>
) )

View File

@@ -1,4 +1,4 @@
import React, {useState} from 'react'; import React, {useState, useCallback} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system'; import {Row, Col} from 'react-grid-system';
import { import {
@@ -48,7 +48,7 @@ export default function TrialBalanceSheetHeader({
setFilterByKey(name, date); setFilterByKey(name, date);
}; };
const handleSubmitClick = () => { onSubmitFilter(filter); }; const handleSubmitClick = useCallback(() => { onSubmitFilter(filter); }, [filter]);
return ( return (
<FinancialStatementHeader> <FinancialStatementHeader>

View File

@@ -1,11 +1,13 @@
import React, {useEffect, useState, useMemo} from 'react'; import React, {useEffect, useState, useCallback, useMemo} from 'react';
import FinancialSheet from 'components/FinancialSheet'; import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Money from 'components/Money';
export default function TrialBalanceSheetTable({ export default function TrialBalanceSheetTable({
trialBalanceSheetAccounts, trialBalanceSheetAccounts,
trialBalanceSheetIndex, trialBalanceSheetIndex,
onFetchData,
loading,
}) { }) {
const [data, setData] = useState([]); const [data, setData] = useState([]);
@@ -13,12 +15,16 @@ export default function TrialBalanceSheetTable({
{ {
// Build our expander column // Build our expander column
id: 'expander', // Make sure it has an ID id: 'expander', // Make sure it has an ID
className: 'expander',
Header: ({ Header: ({
getToggleAllRowsExpandedProps, getToggleAllRowsExpandedProps,
isAllRowsExpanded isAllRowsExpanded
}) => ( }) => (
<span {...getToggleAllRowsExpandedProps()}> <span {...getToggleAllRowsExpandedProps()} className="toggle">
{isAllRowsExpanded ? '👇' : '👉'} {isAllRowsExpanded ?
(<span class="arrow-down" />) :
(<span class="arrow-right" />)
}
</span> </span>
), ),
Cell: ({ row }) => Cell: ({ row }) =>
@@ -33,48 +39,66 @@ export default function TrialBalanceSheetTable({
// of the row // of the row
paddingLeft: `${row.depth * 2}rem`, paddingLeft: `${row.depth * 2}rem`,
}, },
className: 'toggle',
})} })}
> >
{row.isExpanded ? '👇' : '👉'} {row.isExpanded ?
(<span class="arrow-down" />) :
(<span class="arrow-right" />)
}
</span> </span>
) : null, ) : null,
width: 20,
disableResizing: true,
}, },
{ {
Header: 'Account Name', Header: 'Account Name',
accessor: 'name', accessor: 'name',
className: "actions", className: "name",
}, },
{ {
Header: 'Code', Header: 'Code',
accessor: 'code', accessor: 'code',
className: "note", className: "code",
width: 120,
}, },
{ {
Header: 'Credit', Header: 'Credit',
accessor: 'credit', accessor: r => (<Money amount={r.credit} currency="USD" />),
className: 'credit', className: 'credit',
width: 120,
}, },
{ {
Header: 'debit', Header: 'Debit',
accessor: 'debit', accessor: r => (<Money amount={r.debit} currency="USD" />),
className: 'debit', className: 'debit',
width: 120,
}, },
{ {
Header: 'Balance', Header: 'Balance',
accessor: 'balance', accessor: r => (<Money amount={r.balance} currency="USD" />),
className: 'balance', className: 'balance',
width: 120,
} }
], []); ], []);
const handleFetchData = useCallback(() => {
onFetchData && onFetchData();
}, [onFetchData]);
return ( return (
<FinancialSheet <FinancialSheet
companyTitle={'Facebook, Incopration'} companyTitle={'Facebook, Incopration'}
sheetType={'Trial Balance Sheet'} sheetType={'Trial Balance Sheet'}
date={''}> date={new Date()}
name="trial-balance"
loading={loading}>
<DataTable <DataTable
className="bigcapital-datatable--financial-report"
columns={columns} columns={columns}
data={trialBalanceSheetAccounts} /> data={trialBalanceSheetAccounts}
onFetchData={handleFetchData} />
</FinancialSheet> </FinancialSheet>
); );
} }

View File

@@ -9,12 +9,12 @@ const useAsync = (asyncFunction, immediate = true) => {
// handles setting state for pending, value, and error. // handles setting state for pending, value, and error.
// useCallback ensures the below useEffect is not called // useCallback ensures the below useEffect is not called
// on every render, but only if asyncFunction changes. // on every render, but only if asyncFunction changes.
const execute = useCallback(() => { const execute = useCallback((...args) => {
setPending(true); setPending(true);
setValue(null); setValue(null);
setError(null); setError(null);
return asyncFunction() return asyncFunction(...args)
.then(response => setValue(response)) .then(response => setValue(response))
.catch(error => setError(error)) .catch(error => setError(error))
.finally(() => setPending(false)); .finally(() => setPending(false));

View File

@@ -86,5 +86,9 @@ export default {
"sort-down": { "sort-down": {
path: ['M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z'], path: ['M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z'],
viewBox: '0 0 320 512' viewBox: '0 0 320 512'
},
"info-circle": {
path: ['M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z'],
viewBox: '0 0 512 512'
} }
} }

View File

@@ -28,11 +28,19 @@ export const fetchBalanceSheet = ({ query }) => {
export const fetchTrialBalanceSheet = ({ query }) => { export const fetchTrialBalanceSheet = ({ query }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) => new Promise((resolve, reject) => {
dispatch({
type: t.TRIAL_BALANCE_SHEET_LOADING,
loading: true,
});
ApiService.get('/financial_statements/trial_balance_sheet', { params: query }).then((response) => { ApiService.get('/financial_statements/trial_balance_sheet', { params: query }).then((response) => {
dispatch({ dispatch({
type: t.TRAIL_BALANCE_STATEMENT_SET, type: t.TRAIL_BALANCE_STATEMENT_SET,
data: response.data, data: response.data,
}); });
dispatch({
type: t.TRIAL_BALANCE_SHEET_LOADING,
loading: false,
});
resolve(response.data); resolve(response.data);
}).catch((error) => { reject(error); }) }).catch((error) => { reject(error); })
}) })

View File

@@ -2,13 +2,16 @@ import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types'; import t from 'store/types';
import { import {
getBalanceSheetIndexByQuery, getBalanceSheetIndexByQuery,
getTrialBalanceSheetIndex,
getFinancialSheetIndexByQuery, getFinancialSheetIndexByQuery,
// getFinancialSheetIndexByQuery,
} from './financialStatements.selectors'; } from './financialStatements.selectors';
const initialState = { const initialState = {
balanceSheets: [], balanceSheets: [],
trialBalanceSheets: [], trialBalance: {
sheets: [],
loading: false,
},
generalLedger: [], generalLedger: [],
journalSheets: [], journalSheets: [],
}; };
@@ -30,19 +33,22 @@ export default createReducer(initialState, {
}, },
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => { [t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
const index = getTrialBalanceSheetIndex(state.trialBalanceSheets, action.query); const index = getFinancialSheetIndexByQuery(state.trialBalance.sheets, action.query);
const trailBalanceSheet = { const trailBalanceSheet = {
accounts: action.data.accounts, accounts: action.data.accounts,
query: action.data.query, query: action.data.query,
}; };
if (index !== -1) { if (index !== -1) {
state.trialBalanceSheets[index] = trailBalanceSheet; state.trialBalance.sheets[index] = trailBalanceSheet;
} else { } else {
state.trailBalanceSheet.push(trailBalanceSheet); state.trialBalance.sheets.push(trailBalanceSheet);
} }
}, },
[t.TRIAL_BALANCE_SHEET_LOADING]: (state, action) => {
state.trialBalance.loading = !!action.loading;
},
[t.JOURNAL_SHEET_SET]: (state, action) => { [t.JOURNAL_SHEET_SET]: (state, action) => {
const index = getFinancialSheetIndexByQuery(state.journalSheets, action.query); const index = getFinancialSheetIndexByQuery(state.journalSheets, action.query);
console.log(index, 'INDEX'); console.log(index, 'INDEX');

View File

@@ -4,5 +4,6 @@ export default {
GENERAL_LEDGER_STATEMENT_SET: 'GENERAL_LEDGER_STATEMENT_SET', GENERAL_LEDGER_STATEMENT_SET: 'GENERAL_LEDGER_STATEMENT_SET',
BALANCE_SHEET_STATEMENT_SET: 'BALANCE_SHEET_STATEMENT_SET', BALANCE_SHEET_STATEMENT_SET: 'BALANCE_SHEET_STATEMENT_SET',
TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET', TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET',
TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING',
JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET', JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET',
} }

View File

@@ -20,6 +20,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
// Objects // Objects
@import "objects/form"; @import "objects/form";
@import "objects/typography"; @import "objects/typography";
@import "objects/buttons";
// Components // Components
@import "components/data-table"; @import "components/data-table";

View File

@@ -1,8 +1,6 @@
.bigcapital-datatable{ .bigcapital-datatable{
display: block; display: block;
// max-width: 100%;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
@@ -18,8 +16,6 @@
overflow-x: hidden; overflow-x: hidden;
.th{ .th{
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
background: #F8FAFA; background: #F8FAFA;
font-size: 14px; font-size: 14px;
@@ -78,7 +74,7 @@
.inner-resizer{ .inner-resizer{
height: 100%; height: 100%;
width: 1px; width: 1px;
background: #ececec; border-left: 1px solid #ececec;
margin: 0 auto; margin: 0 auto;
} }
@@ -104,6 +100,58 @@
border-radius: 2px; border-radius: 2px;
} }
} }
.tr .th.expander,
.tr .td.expander{
padding: 0;
.toggle{
cursor: pointer;
display: block;
padding: 14px 8px;
padding-left: 0;
margin-left: 4px;
}
.arrow-right{
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 8px solid #acacac;
display: block;
}
.arrow-down{
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 8px solid #acacac;
display: block;
}
+ .td,
+ .th{
padding-left: 0;
}
}
}
&--financial-report{
.thead{
.tr .th{
background: transparent;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
padding: 10px 1.5rem;
color: #222;
}
}
} }
} }

View File

@@ -0,0 +1,58 @@
.form-group--select-list{
.bp3-button{
border-radius: 0;
&,
&:hover{
background: #fff;
box-shadow: 0 0 0;
border: 1px solid #ced4da;
}
}
}
.form-group--select-list.bp3-fill{
.bp3-popover-wrapper{
&,
.bp3-popover-target{
width: 100%;
}
.bp3-button{
width: 100%;
justify-content: space-between;
color: #333;
}
}
}
.form-group--select-list{
.bp3-popover-open{
.bp3-button{
border-color: #80bdff;
}
}
}
.bp3-button{
min-width: 32px;
min-height: 32px;
&.bp3-intent-primary,
&.bp3-intent-success,
&.bp3-intent-danger,
&.bp3-intent-warning{
&,
&:hover{
box-shadow: 0 0 0;
}
}
}

View File

@@ -17,15 +17,22 @@ label{
} }
.#{$ns}-input{ .#{$ns}-input{
box-shadow: 0 0 0 0 rgba(19, 124, 189, 0), box-shadow: 0 0 0;
0 0 0 0 rgba(19, 124, 189, 0), border: 1px solid #ced4da;
inset 0 0 0 1px rgba(16, 22, 26, 0.1), border-radius: 0;
inset 0 1px 1px rgba(16, 22, 26, 0.15); height: 32px;
line-height: 32px;
color: #333;
&:focus,
&.bp3-active{
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25);
border-color: #80bdff;
}
} }
.#{$ns}-form-group{ .#{$ns}-form-group{
margin-bottom: 20px;
&.#{$ns}-intent-danger{ &.#{$ns}-intent-danger{
select{ select{
@@ -36,4 +43,51 @@ label{
inset 0 1px 1px rgba(16, 22, 26, 0.2); inset 0 1px 1px rgba(16, 22, 26, 0.2);
} }
} }
.#{$ns}-label{
margin-bottom: 6px;
.#{$ns}-icon-info-circle{
margin-left: 3px;
position: relative;
top: -1px;
color: #A1B2C5;
}
}
} }
.#{$ns}-button:not([class*=".#{$ns}-intent-"]) {
background-image: none;
}
.#{$ns}-html-select select,
.#{$ns}-select select{
background-image: none;
border-radius: 0;
&,
&:hover{
background: #fff;
box-shadow: 0 0 0;
border: 1px solid #ced4da;
}
&:focus{
box-shadow: 0 0 0 .2rem rgba(0,123,255,.25);
border-color: #80bdff;
}
}
.bp3-datepicker-caption select{
&,
&:hover{
border-color: transparent;
}
}
.form-group--select-list{
.#{$ns}-icon-caret-down{
color: #8D8D8D;
}
}

View File

@@ -1,4 +1,9 @@
body{
color: #212529;
}
.#{$ns}-heading{ .#{$ns}-heading{
font-weight: 300; font-weight: 300;
} }

View File

@@ -4,8 +4,14 @@
.financial-statement{ .financial-statement{
&__header{ &__header{
padding: 30px; padding: 25px 26px 25px;
background: #FDFDFD; background: #FDFDFD;
.bp3-form-group .bp3-label{
font-weight: 500;
font-size: 13px;
color: #444;
}
} }
&__body{ &__body{
@@ -21,28 +27,44 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 20px; padding: 20px;
margin-top: 40px; padding-top: 30px;
margin-top: 35px;
&__title{ &__title{
margin: 0;
font-weight: 200; font-weight: 200;
font-size: 22px; font-size: 22px;
color: #222; color: #222;
text-align: center; text-align: center;
} }
&__sheet-type{ &__sheet-type{
text-align: center; text-align: center;
margin: 0;
font-size: 15px;
font-weight: 400;
color: #666;
margin-top: 6px;
} }
&__date{ &__date{
text-align: center; text-align: center;
color: #888;
margin-top: 6px;
} }
&__table{ &__table{
margin-top: 24px;
.tbody{
.code.td{
color: #777;
}
}
} }
&__accounting-basis{ &__accounting-basis{
} }
&--trial-balance{
min-width: 720px;
}
} }

View File

@@ -74,7 +74,7 @@ factory.define('manual_journal', 'manual_journals', async () => {
}); });
factory.define('item_category', 'items_categories', () => ({ factory.define('item_category', 'items_categories', () => ({
label: faker.name.firstName(), name: faker.name.firstName(),
description: faker.lorem.text(), description: faker.lorem.text(),
parent_category_id: null, parent_category_id: null,
})); }));

View File

@@ -414,10 +414,10 @@ export default {
query('basis').optional(), query('basis').optional(),
query('from_date').optional().isISO8601(), query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(), query('to_date').optional().isISO8601(),
query('number_format.no_cents').optional().isBoolean(), query('number_format.no_cents').optional().isBoolean().toBoolean(),
query('number_format.1000_divide').optional().isBoolean(), query('number_format.1000_divide').optional().isBoolean().toBoolean(),
query('basis').optional(), query('basis').optional(),
query('none_zero').optional(), query('none_zero').optional().isBoolean().toBoolean(),
], ],
async handler(req, res) { async handler(req, res) {
const validationErrors = validationResult(req); const validationErrors = validationResult(req);
@@ -462,6 +462,7 @@ export default {
const trial = journalEntries.getTrialBalance(account.id); const trial = journalEntries.getTrialBalance(account.id);
return { return {
account_id: account.id, account_id: account.id,
name: account.name,
code: account.code, code: account.code,
accountNormal: account.type.normal, accountNormal: account.type.normal,
credit: balanceFormatter(trial.credit), credit: balanceFormatter(trial.credit),
@@ -471,7 +472,7 @@ export default {
}); });
return res.status(200).send({ return res.status(200).send({
query: { ...filter }, query: { ...filter },
items: [...items], accounts: [...items],
}); });
}, },
}, },