mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
WIP financial statements.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"accounting": "^0.4.1",
|
||||
"axios": "^0.19.2",
|
||||
"babel-eslint": "10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
@@ -45,6 +46,7 @@
|
||||
"jest-environment-jsdom-fourteen": "1.0.1",
|
||||
"jest-resolve": "24.9.0",
|
||||
"jest-watch-typeahead": "0.4.2",
|
||||
"js-money": "^0.6.3",
|
||||
"lodash": "^4.17.15",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"moment": "^2.24.0",
|
||||
|
||||
@@ -131,7 +131,8 @@ function AccountsDataTable({
|
||||
columns={columns}
|
||||
data={accounts}
|
||||
onFetchData={handleDatatableFetchData}
|
||||
manualSortBy={true} />
|
||||
manualSortBy={true}
|
||||
selectionColumn={true} />
|
||||
</LoadingIndicator>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import {Checkbox} from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'components/Icon';
|
||||
// import { FixedSizeList } from 'react-window'
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
@@ -23,9 +22,7 @@ const IndeterminateCheckbox = React.forwardRef(
|
||||
resolvedRef.current.indeterminate = indeterminate
|
||||
}, [resolvedRef, indeterminate])
|
||||
|
||||
return (
|
||||
<Checkbox ref={resolvedRef} {...rest} />
|
||||
);
|
||||
return (<Checkbox ref={resolvedRef} {...rest} />);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -35,7 +32,9 @@ export default function DataTable({
|
||||
loading,
|
||||
onFetchData,
|
||||
onSelectedRowsChange,
|
||||
manualSortBy = 'false'
|
||||
manualSortBy = 'false',
|
||||
selectionColumn = false,
|
||||
className
|
||||
}) {
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -51,8 +50,8 @@ export default function DataTable({
|
||||
nextPage,
|
||||
previousPage,
|
||||
setPageSize,
|
||||
|
||||
selectedFlatRows,
|
||||
|
||||
// Get the state from the instance
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = useTable(
|
||||
@@ -77,7 +76,7 @@ export default function DataTable({
|
||||
hooks => {
|
||||
hooks.visibleColumns.push(columns => [
|
||||
// Let's make a column for selection
|
||||
{
|
||||
...(selectionColumn) ? [{
|
||||
id: 'selection',
|
||||
disableResizing: true,
|
||||
minWidth: 35,
|
||||
@@ -97,23 +96,19 @@ export default function DataTable({
|
||||
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}] : [],
|
||||
...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!
|
||||
useEffect(() => {
|
||||
onFetchDataDebounced({ pageIndex, pageSize, sortBy })
|
||||
}, []);
|
||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
||||
}, [pageIndex, pageSize, sortBy]);
|
||||
|
||||
return (
|
||||
<div className={'bigcapital-datatable'}>
|
||||
<div className={classnames('bigcapital-datatable', className)}>
|
||||
<div {...getTableProps()} className="table">
|
||||
<div className="thead">
|
||||
{headerGroups.map(headerGroup => (
|
||||
@@ -157,8 +152,7 @@ export default function DataTable({
|
||||
className: classnames(cell.column.className || '', 'td'),
|
||||
})}>{ cell.render('Cell') }</div>
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
</div>)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
import React, { Children } from 'react';
|
||||
|
||||
import moment from 'moment';
|
||||
import classnames from 'classnames';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
|
||||
export default function FinancialSheet({
|
||||
companyTitle,
|
||||
sheetType,
|
||||
date,
|
||||
children,
|
||||
accountingBasis
|
||||
accountingBasis,
|
||||
name,
|
||||
loading,
|
||||
}) {
|
||||
const formattedDate = moment(date).format('DD MMMM YYYY')
|
||||
const nameModifer = name ? `financial-sheet--${name}` : '';
|
||||
|
||||
return (
|
||||
<div class="financial-sheet">
|
||||
<h1 class="financial-sheet__title">{ companyTitle }</h1>
|
||||
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
|
||||
<span class="financial-sheet__date">{ date }</span>
|
||||
<div className={classnames('financial-sheet', nameModifer)}>
|
||||
<LoadingIndicator loading={loading}>
|
||||
<h1 class="financial-sheet__title">{ companyTitle }</h1>
|
||||
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
|
||||
<div class="financial-sheet__date">As of { formattedDate }</div>
|
||||
|
||||
<div class="financial-sheet__table">
|
||||
{ children }
|
||||
</div>
|
||||
<div class="financial-sheet__table">
|
||||
{ children }
|
||||
</div>
|
||||
|
||||
<div class="financial-sheet__accounting-basis">
|
||||
{ accountingBasis }
|
||||
</div>
|
||||
<div class="financial-sheet__accounting-basis">
|
||||
{ accountingBasis }
|
||||
</div>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import { Spinner } from '@blueprintjs/core';
|
||||
export default function LoadingIndicator({
|
||||
loading,
|
||||
spinnerSize = 40,
|
||||
children
|
||||
children,
|
||||
mount = true,
|
||||
}) {
|
||||
const [rendered, setRendered] = useState(false);
|
||||
const [rendered, setRendered] = useState(mount);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) { setRendered(true); }
|
||||
|
||||
16
client/src/components/Money.js
Normal file
16
client/src/components/Money.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,15 @@ import {
|
||||
getTrialBalanceSheetIndex,
|
||||
getTrialBalanceAccounts,
|
||||
getTrialBalanceQuery,
|
||||
getFinancialSheetIndexByQuery,
|
||||
} from 'store/financialStatement/financialStatements.selectors';
|
||||
|
||||
export const mapStateToProps = (state, props) => ({
|
||||
getTrialBalanceSheetIndex: (query) => getTrialBalanceSheetIndex(state.financialStatements.trialBalanceSheets, query),
|
||||
getTrialBalanceAccounts: (sheetIndex) => getTrialBalanceAccounts(state.financialStatements.trialBalanceSheets, sheetIndex),
|
||||
getTrialBalanceQuery: (sheetIndex) => getTrialBalanceQuery(state.financialStatements.trialBalanceSheets, sheetIndex),
|
||||
getTrialBalanceSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.trialBalance.sheets, query),
|
||||
getTrialBalanceAccounts: (sheetIndex) => getTrialBalanceAccounts(state.financialStatements.trialBalance.sheets, sheetIndex),
|
||||
getTrialBalanceQuery: (sheetIndex) => getTrialBalanceQuery(state.financialStatements.trialBalance.sheets, sheetIndex),
|
||||
|
||||
trialBalanceSheetLoading: state.financialStatements.trialBalance.loading,
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -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 {compose} from 'utils';
|
||||
import useAsync from 'hooks/async';
|
||||
@@ -30,39 +30,41 @@ function BalanceSheet({
|
||||
|
||||
const fetchHook = useAsync(async () => {
|
||||
await Promise.all([
|
||||
fetchBalanceSheet(filter),
|
||||
fetchBalanceSheet({
|
||||
...filter,
|
||||
display_columns_type: 'total',
|
||||
}),
|
||||
]);
|
||||
setReload(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!reload) { return; }
|
||||
fetchHook.execute();
|
||||
}, [reload]);
|
||||
// Handle fetch the data of balance sheet.
|
||||
const handleFetchData = useCallback(() => { fetchHook.execute(); }, [fetchHook]);
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle('Balance Sheet');
|
||||
}, []);
|
||||
|
||||
// Retrieve balance sheet index by the given filter query.
|
||||
const balanceSheetIndex = useMemo(() => {
|
||||
return getBalanceSheetIndexByQuery(filter);
|
||||
}, [filter, balanceSheets]);
|
||||
const balanceSheetIndex = useMemo(() =>
|
||||
getBalanceSheetIndexByQuery(filter),
|
||||
[filter, getBalanceSheetIndexByQuery]);
|
||||
|
||||
// Retreive balance sheet by the given sheet index.
|
||||
const balanceSheet = useMemo(() => {
|
||||
return getBalanceSheetByIndex(balanceSheetIndex);
|
||||
}, [balanceSheetIndex, balanceSheets]);
|
||||
const balanceSheet = useMemo(() =>
|
||||
getBalanceSheetByIndex(balanceSheetIndex),
|
||||
[balanceSheetIndex, getBalanceSheetByIndex]);
|
||||
|
||||
// Handle re-fetch balance sheet after filter change.
|
||||
const handleFilterSubmit = (filter) => {
|
||||
const handleFilterSubmit = useCallback((filter) => {
|
||||
setFilter({
|
||||
...filter,
|
||||
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
|
||||
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
|
||||
});
|
||||
setReload(true);
|
||||
};
|
||||
}, [setFilter]);
|
||||
|
||||
return (
|
||||
<div class="financial-statement">
|
||||
<BalanceSheetHeader
|
||||
@@ -73,7 +75,9 @@ function BalanceSheet({
|
||||
<LoadingIndicator loading={fetchHook.pending}>
|
||||
<BalanceSheetTable
|
||||
balanceSheet={balanceSheet}
|
||||
balanceSheetIndex={balanceSheetIndex} />
|
||||
balanceSheetIndex={balanceSheetIndex}
|
||||
onFetchData={handleFetchData}
|
||||
asDate={new Date()} />
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
HTMLSelect,
|
||||
Intent,
|
||||
Popover,
|
||||
Classes,
|
||||
} from "@blueprintjs/core";
|
||||
import {Select} from '@blueprintjs/select';
|
||||
import {DateInput} from '@blueprintjs/datetime';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
parseDateRangeQuery,
|
||||
} from 'utils';
|
||||
import moment from 'moment';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
export default function BalanceSheetHeader({
|
||||
onSubmitFilter,
|
||||
@@ -111,12 +113,16 @@ export default function BalanceSheetHeader({
|
||||
</RadioGroup>
|
||||
</div>
|
||||
);
|
||||
|
||||
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader>
|
||||
<Row>
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'report_date_range'})}
|
||||
labelInfo={infoIcon}
|
||||
minimal={true}
|
||||
fill={true}>
|
||||
|
||||
@@ -131,6 +137,7 @@ export default function BalanceSheetHeader({
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'from_date'})}
|
||||
labelInfo={infoIcon}
|
||||
minimal={true}
|
||||
fill={true}>
|
||||
|
||||
@@ -146,6 +153,7 @@ export default function BalanceSheetHeader({
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'to_date'})}
|
||||
labelInfo={infoIcon}
|
||||
minimal={true}
|
||||
fill={true}>
|
||||
|
||||
@@ -163,7 +171,8 @@ export default function BalanceSheetHeader({
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
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}>
|
||||
|
||||
<Select
|
||||
@@ -184,6 +193,7 @@ export default function BalanceSheetHeader({
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={'Show non-zero or active only'}
|
||||
className="form-group--select-list bp3-fill"
|
||||
inline={false}>
|
||||
|
||||
<Popover
|
||||
|
||||
@@ -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 DataTable from 'components/DataTable';
|
||||
import FinancialStatementConnect from 'connectors/FinancialStatements.connector';
|
||||
import {compose} from 'utils';
|
||||
import moment from 'moment';
|
||||
import Money from 'components/Money';
|
||||
|
||||
function BalanceSheetTable({
|
||||
balanceSheet,
|
||||
@@ -14,25 +15,32 @@ function BalanceSheetTable({
|
||||
getBalanceSheetLiabilitiesAccounts,
|
||||
|
||||
getBalanceSheetQuery,
|
||||
}) {
|
||||
const balanceSheetColumns = useMemo(() => {
|
||||
return getBalanceSheetColumns(balanceSheetIndex);
|
||||
}, [getBalanceSheetColumns]);
|
||||
|
||||
const balanceSheetQuery = useMemo(() => {
|
||||
return getBalanceSheetQuery(balanceSheetIndex);
|
||||
}, [getBalanceSheetQuery])
|
||||
onFetchData,
|
||||
asDate,
|
||||
}) {
|
||||
const balanceSheetColumns = useMemo(() =>
|
||||
getBalanceSheetColumns(balanceSheetIndex),
|
||||
[getBalanceSheetColumns, balanceSheetIndex]);
|
||||
|
||||
const balanceSheetQuery = useMemo(() =>
|
||||
getBalanceSheetQuery(balanceSheetIndex),
|
||||
[getBalanceSheetQuery, balanceSheetIndex])
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
// Build our expander column
|
||||
id: 'expander', // Make sure it has an ID
|
||||
className: 'expander',
|
||||
Header: ({
|
||||
getToggleAllRowsExpandedProps,
|
||||
isAllRowsExpanded
|
||||
}) => (
|
||||
<span {...getToggleAllRowsExpandedProps()}>
|
||||
{isAllRowsExpanded ? '👇' : '👉'}
|
||||
<span {...getToggleAllRowsExpandedProps()} className="toggle">
|
||||
{isAllRowsExpanded ?
|
||||
(<span class="arrow-down" />) :
|
||||
(<span class="arrow-right" />)
|
||||
}
|
||||
</span>
|
||||
),
|
||||
Cell: ({ row }) =>
|
||||
@@ -47,30 +55,41 @@ function BalanceSheetTable({
|
||||
// of the row
|
||||
paddingLeft: `${row.depth * 2}rem`,
|
||||
},
|
||||
className: 'toggle',
|
||||
})}
|
||||
>
|
||||
{row.isExpanded ? '👇' : '👉'}
|
||||
{row.isExpanded ?
|
||||
(<span class="arrow-down" />) :
|
||||
(<span class="arrow-right" />)
|
||||
}
|
||||
</span>
|
||||
) : null,
|
||||
width: 20,
|
||||
disableResizing: true,
|
||||
},
|
||||
{
|
||||
Header: 'Account Name',
|
||||
accessor: 'name',
|
||||
className: "actions",
|
||||
className: "account_name",
|
||||
},
|
||||
{
|
||||
Header: 'Code',
|
||||
accessor: 'code',
|
||||
className: "note",
|
||||
className: "code",
|
||||
},
|
||||
...(balanceSheetQuery &&
|
||||
balanceSheetQuery.display_columns_by === 'total') ? [
|
||||
{
|
||||
Header: 'Total',
|
||||
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",
|
||||
}
|
||||
]: (balanceSheetColumns.map((column, index) => ({
|
||||
] : (balanceSheetColumns.map((column, index) => ({
|
||||
Header: column,
|
||||
accessor: (row) => {
|
||||
if (row.periods_balance && row.periods_balance[index]) {
|
||||
@@ -87,33 +106,32 @@ function BalanceSheetTable({
|
||||
setData([
|
||||
{
|
||||
name: 'Assets',
|
||||
code: '',
|
||||
children: [
|
||||
...getBalanceSheetAssetsAccounts(balanceSheetIndex),
|
||||
],
|
||||
children: getBalanceSheetAssetsAccounts(balanceSheetIndex),
|
||||
},
|
||||
{
|
||||
name: 'Liabilies & Equity',
|
||||
code: '',
|
||||
children: [
|
||||
...getBalanceSheetLiabilitiesAccounts(balanceSheetIndex),
|
||||
]
|
||||
children: getBalanceSheetLiabilitiesAccounts(balanceSheetIndex),
|
||||
}
|
||||
])
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
const handleFetchData = useCallback(() => {
|
||||
onFetchData && onFetchData();
|
||||
}, [onFetchData]);
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
companyTitle={'Facebook, Incopration'}
|
||||
sheetType={'Balance Sheet'}
|
||||
date={''}>
|
||||
date={asDate}>
|
||||
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
columns={columns}
|
||||
data={data} />
|
||||
|
||||
data={data}
|
||||
onFetchData={handleFetchData} />
|
||||
</FinancialSheet>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import TrialBalanceSheetHeader from "./TrialBalanceSheetHeader";
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import TrialBalanceSheetTable from './TrialBalanceSheetTable';
|
||||
import { useAsync } from 'react-use';
|
||||
import useAsync from 'hooks/async';
|
||||
import moment from 'moment';
|
||||
import {compose} from 'utils';
|
||||
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
|
||||
@@ -13,44 +13,48 @@ function TrialBalanceSheet({
|
||||
fetchTrialBalanceSheet,
|
||||
getTrialBalanceSheetIndex,
|
||||
getTrialBalanceAccounts,
|
||||
trialBalanceSheetLoading,
|
||||
}) {
|
||||
const [filter, setFilter] = useState({
|
||||
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||
basis: 'cash',
|
||||
basis: 'accural',
|
||||
none_zero: false,
|
||||
});
|
||||
const [reload, setReload] = useState(false);
|
||||
|
||||
const fetchHook = useAsync(async () => {
|
||||
await Promise.all([
|
||||
fetchTrialBalanceSheet(),
|
||||
const fetchHook = useAsync((query = filter) => {
|
||||
return Promise.all([
|
||||
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.
|
||||
const trialBalanceSheetIndex = useMemo(() => {
|
||||
return getTrialBalanceSheetIndex(filter);
|
||||
}, [getTrialBalanceSheetIndex, filter]);
|
||||
const trialBalanceSheetIndex = useMemo(() =>
|
||||
getTrialBalanceSheetIndex(filter),
|
||||
[getTrialBalanceSheetIndex, filter]);
|
||||
|
||||
// Retrieve balance sheet accounts bu the given sheet index.
|
||||
const trialBalanceAccounts = useMemo(() => {
|
||||
return getTrialBalanceAccounts(trialBalanceSheetIndex);
|
||||
}, [trialBalanceSheetIndex]);
|
||||
const trialBalanceAccounts = useMemo(() =>
|
||||
getTrialBalanceAccounts(trialBalanceSheetIndex),
|
||||
[getTrialBalanceAccounts, trialBalanceSheetIndex]);
|
||||
|
||||
// Change page title of the dashboard.
|
||||
useEffect(() => {
|
||||
changePageTitle('Trial Balance Sheet');
|
||||
}, []);
|
||||
|
||||
const handleFilterSubmit = (filter) => {
|
||||
setFilter({
|
||||
const handleFilterSubmit = useCallback((filter) => {
|
||||
const parsedFilter = {
|
||||
...filter,
|
||||
from_date: moment(filter.from_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 (
|
||||
<div class="financial-statement">
|
||||
@@ -59,11 +63,11 @@ function TrialBalanceSheet({
|
||||
onSubmitFilter={handleFilterSubmit} />
|
||||
|
||||
<div class="financial-statement__body">
|
||||
<LoadingIndicator loading={fetchHook.pending}>
|
||||
<TrialBalanceSheetTable
|
||||
trialBalanceSheetAccounts={trialBalanceAccounts}
|
||||
trialBalanceSheetIndex={trialBalanceSheetIndex} />
|
||||
</LoadingIndicator>
|
||||
<TrialBalanceSheetTable
|
||||
trialBalanceSheetAccounts={trialBalanceAccounts}
|
||||
trialBalanceSheetIndex={trialBalanceSheetIndex}
|
||||
onFetchData={handleFetchData}
|
||||
loading={trialBalanceSheetLoading} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useState, useCallback} from 'react';
|
||||
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
||||
import {Row, Col} from 'react-grid-system';
|
||||
import {
|
||||
@@ -48,7 +48,7 @@ export default function TrialBalanceSheetHeader({
|
||||
setFilterByKey(name, date);
|
||||
};
|
||||
|
||||
const handleSubmitClick = () => { onSubmitFilter(filter); };
|
||||
const handleSubmitClick = useCallback(() => { onSubmitFilter(filter); }, [filter]);
|
||||
|
||||
return (
|
||||
<FinancialStatementHeader>
|
||||
|
||||
@@ -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 DataTable from 'components/DataTable';
|
||||
|
||||
import Money from 'components/Money';
|
||||
|
||||
export default function TrialBalanceSheetTable({
|
||||
trialBalanceSheetAccounts,
|
||||
trialBalanceSheetIndex,
|
||||
onFetchData,
|
||||
loading,
|
||||
}) {
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
@@ -13,12 +15,16 @@ export default function TrialBalanceSheetTable({
|
||||
{
|
||||
// Build our expander column
|
||||
id: 'expander', // Make sure it has an ID
|
||||
className: 'expander',
|
||||
Header: ({
|
||||
getToggleAllRowsExpandedProps,
|
||||
isAllRowsExpanded
|
||||
}) => (
|
||||
<span {...getToggleAllRowsExpandedProps()}>
|
||||
{isAllRowsExpanded ? '👇' : '👉'}
|
||||
<span {...getToggleAllRowsExpandedProps()} className="toggle">
|
||||
{isAllRowsExpanded ?
|
||||
(<span class="arrow-down" />) :
|
||||
(<span class="arrow-right" />)
|
||||
}
|
||||
</span>
|
||||
),
|
||||
Cell: ({ row }) =>
|
||||
@@ -33,48 +39,66 @@ export default function TrialBalanceSheetTable({
|
||||
// of the row
|
||||
paddingLeft: `${row.depth * 2}rem`,
|
||||
},
|
||||
className: 'toggle',
|
||||
})}
|
||||
>
|
||||
{row.isExpanded ? '👇' : '👉'}
|
||||
{row.isExpanded ?
|
||||
(<span class="arrow-down" />) :
|
||||
(<span class="arrow-right" />)
|
||||
}
|
||||
</span>
|
||||
) : null,
|
||||
width: 20,
|
||||
disableResizing: true,
|
||||
},
|
||||
{
|
||||
Header: 'Account Name',
|
||||
accessor: 'name',
|
||||
className: "actions",
|
||||
className: "name",
|
||||
},
|
||||
{
|
||||
Header: 'Code',
|
||||
accessor: 'code',
|
||||
className: "note",
|
||||
className: "code",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: 'Credit',
|
||||
accessor: 'credit',
|
||||
accessor: r => (<Money amount={r.credit} currency="USD" />),
|
||||
className: 'credit',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: 'debit',
|
||||
accessor: 'debit',
|
||||
Header: 'Debit',
|
||||
accessor: r => (<Money amount={r.debit} currency="USD" />),
|
||||
className: 'debit',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: 'Balance',
|
||||
accessor: 'balance',
|
||||
accessor: r => (<Money amount={r.balance} currency="USD" />),
|
||||
className: 'balance',
|
||||
width: 120,
|
||||
}
|
||||
], []);
|
||||
|
||||
const handleFetchData = useCallback(() => {
|
||||
onFetchData && onFetchData();
|
||||
}, [onFetchData]);
|
||||
|
||||
return (
|
||||
<FinancialSheet
|
||||
companyTitle={'Facebook, Incopration'}
|
||||
sheetType={'Trial Balance Sheet'}
|
||||
date={''}>
|
||||
date={new Date()}
|
||||
name="trial-balance"
|
||||
loading={loading}>
|
||||
|
||||
<DataTable
|
||||
className="bigcapital-datatable--financial-report"
|
||||
columns={columns}
|
||||
data={trialBalanceSheetAccounts} />
|
||||
data={trialBalanceSheetAccounts}
|
||||
onFetchData={handleFetchData} />
|
||||
</FinancialSheet>
|
||||
);
|
||||
}
|
||||
@@ -9,12 +9,12 @@ const useAsync = (asyncFunction, immediate = true) => {
|
||||
// handles setting state for pending, value, and error.
|
||||
// useCallback ensures the below useEffect is not called
|
||||
// on every render, but only if asyncFunction changes.
|
||||
const execute = useCallback(() => {
|
||||
const execute = useCallback((...args) => {
|
||||
setPending(true);
|
||||
setValue(null);
|
||||
setError(null);
|
||||
|
||||
return asyncFunction()
|
||||
return asyncFunction(...args)
|
||||
.then(response => setValue(response))
|
||||
.catch(error => setError(error))
|
||||
.finally(() => setPending(false));
|
||||
|
||||
@@ -86,5 +86,9 @@ export default {
|
||||
"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'],
|
||||
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'
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,19 @@ export const fetchBalanceSheet = ({ query }) => {
|
||||
|
||||
export const fetchTrialBalanceSheet = ({ query }) => {
|
||||
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) => {
|
||||
dispatch({
|
||||
type: t.TRAIL_BALANCE_STATEMENT_SET,
|
||||
data: response.data,
|
||||
});
|
||||
dispatch({
|
||||
type: t.TRIAL_BALANCE_SHEET_LOADING,
|
||||
loading: false,
|
||||
});
|
||||
resolve(response.data);
|
||||
}).catch((error) => { reject(error); })
|
||||
})
|
||||
|
||||
@@ -2,13 +2,16 @@ import { createReducer } from '@reduxjs/toolkit';
|
||||
import t from 'store/types';
|
||||
import {
|
||||
getBalanceSheetIndexByQuery,
|
||||
getTrialBalanceSheetIndex,
|
||||
getFinancialSheetIndexByQuery,
|
||||
// getFinancialSheetIndexByQuery,
|
||||
} from './financialStatements.selectors';
|
||||
|
||||
const initialState = {
|
||||
balanceSheets: [],
|
||||
trialBalanceSheets: [],
|
||||
trialBalance: {
|
||||
sheets: [],
|
||||
loading: false,
|
||||
},
|
||||
generalLedger: [],
|
||||
journalSheets: [],
|
||||
};
|
||||
@@ -30,19 +33,22 @@ export default createReducer(initialState, {
|
||||
},
|
||||
|
||||
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
|
||||
const index = getTrialBalanceSheetIndex(state.trialBalanceSheets, action.query);
|
||||
|
||||
const index = getFinancialSheetIndexByQuery(state.trialBalance.sheets, action.query);
|
||||
const trailBalanceSheet = {
|
||||
accounts: action.data.accounts,
|
||||
query: action.data.query,
|
||||
};
|
||||
if (index !== -1) {
|
||||
state.trialBalanceSheets[index] = trailBalanceSheet;
|
||||
state.trialBalance.sheets[index] = trailBalanceSheet;
|
||||
} 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) => {
|
||||
const index = getFinancialSheetIndexByQuery(state.journalSheets, action.query);
|
||||
console.log(index, 'INDEX');
|
||||
|
||||
@@ -4,5 +4,6 @@ export default {
|
||||
GENERAL_LEDGER_STATEMENT_SET: 'GENERAL_LEDGER_STATEMENT_SET',
|
||||
BALANCE_SHEET_STATEMENT_SET: 'BALANCE_SHEET_STATEMENT_SET',
|
||||
TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET',
|
||||
TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING',
|
||||
JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET',
|
||||
}
|
||||
@@ -20,6 +20,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
// Objects
|
||||
@import "objects/form";
|
||||
@import "objects/typography";
|
||||
@import "objects/buttons";
|
||||
|
||||
// Components
|
||||
@import "components/data-table";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
|
||||
.bigcapital-datatable{
|
||||
|
||||
.bigcapital-datatable{
|
||||
display: block;
|
||||
// max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
@@ -18,8 +16,6 @@
|
||||
overflow-x: hidden;
|
||||
|
||||
.th{
|
||||
|
||||
|
||||
padding: 1rem 1.5rem;
|
||||
background: #F8FAFA;
|
||||
font-size: 14px;
|
||||
@@ -78,7 +74,7 @@
|
||||
.inner-resizer{
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: #ececec;
|
||||
border-left: 1px solid #ececec;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -104,6 +100,58 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
58
client/src/style/objects/buttons.scss
Normal file
58
client/src/style/objects/buttons.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,22 @@ label{
|
||||
}
|
||||
|
||||
.#{$ns}-input{
|
||||
box-shadow: 0 0 0 0 rgba(19, 124, 189, 0),
|
||||
0 0 0 0 rgba(19, 124, 189, 0),
|
||||
inset 0 0 0 1px rgba(16, 22, 26, 0.1),
|
||||
inset 0 1px 1px rgba(16, 22, 26, 0.15);
|
||||
box-shadow: 0 0 0;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0;
|
||||
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{
|
||||
margin-bottom: 20px;
|
||||
|
||||
&.#{$ns}-intent-danger{
|
||||
select{
|
||||
@@ -36,4 +43,51 @@ label{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
|
||||
|
||||
body{
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.#{$ns}-heading{
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
.financial-statement{
|
||||
|
||||
&__header{
|
||||
padding: 30px;
|
||||
padding: 25px 26px 25px;
|
||||
background: #FDFDFD;
|
||||
|
||||
.bp3-form-group .bp3-label{
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
&__body{
|
||||
@@ -21,28 +27,44 @@
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 20px;
|
||||
margin-top: 40px;
|
||||
padding-top: 30px;
|
||||
margin-top: 35px;
|
||||
|
||||
&__title{
|
||||
margin: 0;
|
||||
font-weight: 200;
|
||||
font-size: 22px;
|
||||
color: #222;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__sheet-type{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
&__date{
|
||||
text-align: center;
|
||||
color: #888;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
&__table{
|
||||
margin-top: 24px;
|
||||
|
||||
.tbody{
|
||||
.code.td{
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__accounting-basis{
|
||||
|
||||
}
|
||||
|
||||
&--trial-balance{
|
||||
min-width: 720px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,7 +74,7 @@ factory.define('manual_journal', 'manual_journals', async () => {
|
||||
});
|
||||
|
||||
factory.define('item_category', 'items_categories', () => ({
|
||||
label: faker.name.firstName(),
|
||||
name: faker.name.firstName(),
|
||||
description: faker.lorem.text(),
|
||||
parent_category_id: null,
|
||||
}));
|
||||
|
||||
@@ -414,10 +414,10 @@ export default {
|
||||
query('basis').optional(),
|
||||
query('from_date').optional().isISO8601(),
|
||||
query('to_date').optional().isISO8601(),
|
||||
query('number_format.no_cents').optional().isBoolean(),
|
||||
query('number_format.1000_divide').optional().isBoolean(),
|
||||
query('number_format.no_cents').optional().isBoolean().toBoolean(),
|
||||
query('number_format.1000_divide').optional().isBoolean().toBoolean(),
|
||||
query('basis').optional(),
|
||||
query('none_zero').optional(),
|
||||
query('none_zero').optional().isBoolean().toBoolean(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
@@ -462,6 +462,7 @@ export default {
|
||||
const trial = journalEntries.getTrialBalance(account.id);
|
||||
return {
|
||||
account_id: account.id,
|
||||
name: account.name,
|
||||
code: account.code,
|
||||
accountNormal: account.type.normal,
|
||||
credit: balanceFormatter(trial.credit),
|
||||
@@ -471,7 +472,7 @@ export default {
|
||||
});
|
||||
return res.status(200).send({
|
||||
query: { ...filter },
|
||||
items: [...items],
|
||||
accounts: [...items],
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user