This commit is contained in:
Ahmed Bouhuolia
2020-03-19 17:02:37 +02:00
parent 73711384f6
commit ab81f4be40
57 changed files with 3734 additions and 515 deletions

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { useTable, usePagination } from 'react-table'
import { useTable, useExpanded, usePagination } from 'react-table'
export default function DataTable({
columns,
@@ -32,8 +32,10 @@ export default function DataTable({
// This means we'll also have to provide our own
// pageCount.
// pageCount: controlledPageCount,
getSubRows: row => row.children,
},
usePagination
useExpanded,
usePagination,
);
return (

View File

@@ -116,6 +116,10 @@ export default [
text: 'Balance Sheet',
href: '/dashboard/accounting/balance-sheet',
},
{
text: 'Trial Balance Sheet',
href: '/dashboard/accounting/trial-balance-sheet',
},
{
divider: true,
},

View File

@@ -3,10 +3,29 @@ import {
fetchGeneralLedger,
fetchBalanceSheet,
} from 'store/financialStatement/financialStatements.actions';
import t from 'store/types';
import {
getBalanceSheetByQuery,
getBalanceSheetColumns,
getBalanceSheetIndexByQuery,
getBalanceSheetByIndex,
getBalanceSheetAssetsAccounts,
getBalanceSheetLiabilitiesAccounts,
getBalanceSheetQuery,
} from 'store/financialStatement/financialStatements.selectors';
export const mapStateToProps = (state, props) => ({
generalLedeger: state.financialStatements.generalLedger,
balanceSheets: state.financialStatements.balanceSheets,
getBalanceSheetByQuery: (query) => getBalanceSheetByQuery(state.financialStatements.balanceSheets, query),
getBalanceSheetColumns: (sheetIndex) => getBalanceSheetColumns(state.financialStatements.balanceSheets, sheetIndex),
getBalanceSheetIndexByQuery: (query) => getBalanceSheetIndexByQuery(state.financialStatements.balanceSheets, query),
getBalanceSheetByIndex: (sheetIndex) => getBalanceSheetByIndex(state.financialStatements.balanceSheets, sheetIndex),
getBalanceSheetAssetsAccounts: (sheetIndex) => getBalanceSheetAssetsAccounts(state.financialStatements.balanceSheets, sheetIndex),
getBalanceSheetLiabilitiesAccounts: (sheetIndex) => getBalanceSheetLiabilitiesAccounts(state.financialStatements.balanceSheets, sheetIndex),
getBalanceSheetQuery: (sheetIndex) => getBalanceSheetQuery(state.financialStatements.balanceSheets, sheetIndex),
});
export const mapDispatchToProps = (dispatch) => ({

View File

@@ -0,0 +1,23 @@
import {connect} from 'react-redux';
import {
fetchProfitLossSheet,
} from 'store/financialStatement/financialStatements.actions';
import {
getProfitLossSheetIndex,
getProfitLossSheet,
getProfitLossSheetColumns,
getProfitLossSheetAccounts,
} from 'store/financialStatement/financialStatements.selectors';
export const mapStateToProps = (state, props) => ({
getProfitLossSheetIndex: (query) => getProfitLossSheetIndex(state.financialStatements.profitLossSheets, query),
getProfitLossSheet: (index) => getProfitLossSheet(state.financialStatements.profitLossSheets, index),
getProfitLossSheetColumns: (index) => getProfitLossSheetColumns(state.financialStatements.profitLossSheets, index),
getProfitLossSheetAccounts: (index) => getProfitLossSheetAccounts(state.financialStatements.profitLossSheets, index),
});
export const mapDispatchToProps = (dispatch) => ({
fetchProfitLossSheet: (query = {}) => dispatch(fetchProfitLossSheet({ query })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -0,0 +1,21 @@
import {connect} from 'react-redux';
import {
fetchTrialBalanceSheet
} from 'store/financialStatement/financialStatements.actions';
import {
getTrialBalanceSheetIndex,
getTrialBalanceAccounts,
getTrialBalanceQuery,
} 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),
});
export const mapDispatchToProps = (dispatch) => ({
fetchTrialBalanceSheet: (query = {}) => dispatch(fetchTrialBalanceSheet({ query })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,48 +0,0 @@
import React, {useEffect} from 'react';
import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils';
import useAsync from 'hooks/async';
import FinancialStatementConnect from 'connectors/FinancialStatements.connector';
import {useIntl} from 'react-intl';
import BalanceSheetHeader from './BalanceSheet/BalanceSheetHeader';
import LoadingIndicator from 'components/LoadingIndicator';
import BalanceSheetTable from './BalanceSheet/BalanceSheetTable';
function BalanceSheet({
fetchBalanceSheet,
changePageTitle,
}) {
const intl = useIntl();
const handleDateChange = () => {};
const fetchHook = useAsync(async () => {
await Promise.all([
fetchBalanceSheet({}),
]);
});
useEffect(() => {
changePageTitle('Balance Sheet');
}, []);
const handleFilterSubmit = (filter) => {
};
return (
<div class="financial-statement">
<BalanceSheetHeader onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<BalanceSheetTable />
</LoadingIndicator>
</div>
</div>
);
}
export default compose(
DashboardConnect,
FinancialStatementConnect,
)(BalanceSheet);

View File

@@ -0,0 +1,86 @@
import React, {useEffect, useMemo, useState} from 'react';
import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils';
import useAsync from 'hooks/async';
import FinancialStatementConnect from 'connectors/FinancialStatements.connector';
import {useIntl} from 'react-intl';
import BalanceSheetHeader from './BalanceSheetHeader';
import LoadingIndicator from 'components/LoadingIndicator';
import BalanceSheetTable from './BalanceSheetTable';
import moment from 'moment';
function BalanceSheet({
fetchBalanceSheet,
changePageTitle,
getBalanceSheetByQuery,
getBalanceSheetIndexByQuery,
getBalanceSheetByIndex,
balanceSheets
}) {
const intl = useIntl();
const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
display_columns_by: 'total',
none_zero: false,
});
const [reload, setReload] = useState(false);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchBalanceSheet(filter),
]);
setReload(false);
});
useEffect(() => {
if (!reload) { return; }
fetchHook.execute();
}, [reload]);
useEffect(() => {
changePageTitle('Balance Sheet');
}, []);
// Retrieve balance sheet index by the given filter query.
const balanceSheetIndex = useMemo(() => {
return getBalanceSheetIndexByQuery(filter);
}, [filter, balanceSheets]);
// Retreive balance sheet by the given sheet index.
const balanceSheet = useMemo(() => {
return getBalanceSheetByIndex(balanceSheetIndex);
}, [balanceSheetIndex, balanceSheets]);
// Handle re-fetch balance sheet after filter change.
const handleFilterSubmit = (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);
};
return (
<div class="financial-statement">
<BalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<BalanceSheetTable
balanceSheet={balanceSheet}
balanceSheetIndex={balanceSheetIndex} />
</LoadingIndicator>
</div>
</div>
);
}
export default compose(
DashboardConnect,
FinancialStatementConnect,
)(BalanceSheet);

View File

@@ -1,4 +1,4 @@
import React, {useState, useMemo} from 'react';
import React, {useState, useMemo, useEffect} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system';
import {
@@ -10,44 +10,53 @@ import {
Radio,
HTMLSelect,
Intent,
Popover,
} from "@blueprintjs/core";
import {Select} from '@blueprintjs/select';
import {DateInput} from '@blueprintjs/datetime';
import {useIntl} from 'react-intl';
import {momentFormatter, handleStringChange} from 'utils';
import {
momentFormatter,
handleStringChange,
parseDateRangeQuery,
} from 'utils';
import moment from 'moment';
export default function BalanceSheetHeader({
onSubmitFilter,
pageFilter,
}) {
const intl = useIntl();
const [filter, setFilter] = useState({
from_date: null,
to_date: null,
accounting_basis: 'cash',
display_columns_by: 'total',
});
const setFilterByName = (name, value) => {
setFilter({
...filter,
[name]: value,
});
};
const handleFieldChange = (event) => {
setFilterByName(event.target.name, event.target.value);
};
const displayColumnsByOptions = [
{key: 'total', name: 'Total'},
{key: 'year', name: 'Year'},
{key: 'month', name: 'Month'},
{key: 'week', name: 'Week'},
{key: 'day', name: 'Day'},
{key: 'quarter', name: 'Quarter'}
{key: 'quarter', name: 'Quarter'},
];
const [filter, setFilter] = useState({
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate()
});
const setFilterByKey = (name, value) => {
setFilter({ ...filter, [name]: value });
};
const [reportDateRange, setReportDateRange] = useState('this_year');
useEffect(() => {
if (reportDateRange === 'custom') { return; }
const dateRange = parseDateRangeQuery(reportDateRange);
if (dateRange) {
setFilter((filter) => ({ ...filter, ...dateRange, }));
}
}, [reportDateRange])
const selectedDisplayColumnOpt = useMemo(() => {
return displayColumnsByOptions.find(o => o.key === filter.display_columns_by);
}, [filter.display_columns_by, displayColumnsByOptions]);
@@ -56,31 +65,56 @@ export default function BalanceSheetHeader({
const accountTypeItem = (item, { handleClick, modifiers, query }) => {
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
};
// Handle item select of `display columns by` field.
const onItemSelectDisplayColumns = (item) => {
setFilterByName('display_columns_by', item.key);
setFilterByKey('display_columns_by', item.key);
};
// Handle any date change.
const handleDateChange = (name) => (date) => {
setFilterByName(name, moment(date).format('YYYY-MM-DD'));
setReportDateRange('custom');
setFilterByKey(name, date);
};
// handle submit filter submit button.
const handleSubmitClick = () => {
onSubmitFilter(filter);
};
const dateRangeOptions = [
{value: 'today', label: 'Today', },
{value: 'this_week', label: 'This Week'},
{value: 'this_month', label: 'This Month'},
{value: 'this_quarter', label: 'This Quarter'},
{value: 'this_year', label: 'This Year'},
{value: 'custom', label: 'Custom Range'},
];
const [activeRowsColumns, setActiveRowsColumns] = useState(false);
const onClickActiveRowsColumnsBtn = () => {
setActiveRowsColumns(!activeRowsColumns);
};
const activeRowsColumnsPopover = (
<div>
<h5>Columns</h5>
<RadioGroup
name="none_zero"
selectedValue={filter.none_zero}
onChange={handleStringChange((value) => {
setFilterByKey('none_zero', value);
})}
>
<Radio label="All" value="0" />
<Radio label="Non-Zero" value="1" />
</RadioGroup>
</div>
);
return (
<FinancialStatementHeader>
<Row>
<Col sm={4}>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'report_date_range'})}
minimal={true}
@@ -88,11 +122,13 @@ export default function BalanceSheetHeader({
<HTMLSelect
fill={true}
options={dateRangeOptions} />
options={dateRangeOptions}
value={reportDateRange}
onChange={(event) => setReportDateRange(event.target.value)} />
</FormGroup>
</Col>
<Col sm={4}>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'from_date'})}
minimal={true}
@@ -100,14 +136,14 @@ export default function BalanceSheetHeader({
<DateInput
{...momentFormatter('YYYY/MM/DD')}
defaultValue={new Date()}
value={filter.from_date}
onChange={handleDateChange('from_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
<Col sm={4}>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'to_date'})}
minimal={true}
@@ -115,7 +151,7 @@ export default function BalanceSheetHeader({
<DateInput
{...momentFormatter('YYYY/MM/DD')}
defaultValue={new Date()}
value={filter.to_date}
onChange={handleDateChange('to_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
@@ -124,11 +160,11 @@ export default function BalanceSheetHeader({
</Row>
<Row>
<Col sm={4}>
<Col sm={3}>
<FormGroup
label={'Display report columns'}
className="{'form-group-display-columns-by'}"
inline={true}>
inline={false}>
<Select
items={displayColumnsByOptions}
@@ -145,14 +181,34 @@ export default function BalanceSheetHeader({
</FormGroup>
</Col>
<Col sm={4}>
<Col sm={3}>
<FormGroup
label={'Show non-zero or active only'}
inline={false}>
<Popover
isOpen={activeRowsColumns}
content={activeRowsColumnsPopover}
minimal={true}
position={Position.BOTTOM}>
<Button
rightIcon="caret-down"
fill={true}
text="Active rows/Columns Active"
onClick={onClickActiveRowsColumnsBtn} />
</Popover>
</FormGroup>
</Col>
<Col sm={3}>
<RadioGroup
inline={true}
label={intl.formatMessage({'id': 'accounting_basis'})}
name="accounting_bahandleRadioChangesis"
selectedValue={filter.accounting_basis}
onChange={handleStringChange((value) => {
setFilterByName('accounting_basis', value);
setFilterByKey('accounting_basis', value);
})}
>
<Radio label="Cash" value="cash" />
@@ -160,10 +216,10 @@ export default function BalanceSheetHeader({
</RadioGroup>
</Col>
<Col sm={4}>
<Button intent={Intent.PRIMARY} type="submit" onClick={handleSubmitClick}>
{ 'Calculate Report' }
</Button>
<Col sm={3}>
<Button intent={Intent.PRIMARY} type="submit" onClick={handleSubmitClick}>
{ 'Calculate Report' }
</Button>
</Col>
</Row>
</FinancialStatementHeader>

View File

@@ -1,31 +1,114 @@
import React, {useMemo, useState} from 'react';
import React, {useMemo, useState, 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';
export default function BalanceSheetTable({
function BalanceSheetTable({
balanceSheet,
balanceSheetIndex,
getBalanceSheetColumns,
getBalanceSheetAssetsAccounts,
getBalanceSheetLiabilitiesAccounts,
getBalanceSheetQuery,
}) {
const balanceSheetColumns = useMemo(() => {
return getBalanceSheetColumns(balanceSheetIndex);
}, [getBalanceSheetColumns]);
const balanceSheetQuery = useMemo(() => {
return getBalanceSheetQuery(balanceSheetIndex);
}, [getBalanceSheetQuery])
const columns = useMemo(() => [
{
// Build our expander column
id: 'expander', // Make sure it has an ID
Header: ({
getToggleAllRowsExpandedProps,
isAllRowsExpanded
}) => (
<span {...getToggleAllRowsExpandedProps()}>
{isAllRowsExpanded ? '👇' : '👉'}
</span>
),
Cell: ({ row }) =>
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
// to build the toggle for expanding a row
row.canExpand ? (
<span
{...row.getToggleRowExpandedProps({
style: {
// We can even use the row.depth property
// and paddingLeft to indicate the depth
// of the row
paddingLeft: `${row.depth * 2}rem`,
},
})}
>
{row.isExpanded ? '👇' : '👉'}
</span>
) : null,
},
{
Header: 'Account Name',
accessor: 'index',
accessor: 'name',
className: "actions",
},
{
Header: 'Code',
accessor: 'note',
accessor: 'code',
className: "note",
},
{
Header: 'Total',
accessor: 'total',
className: "credit",
},
]);
...(balanceSheetQuery &&
balanceSheetQuery.display_columns_by === 'total') ? [
{
Header: 'Total',
accessor: 'balance.formatted_amount',
className: "credit",
}
]: (balanceSheetColumns.map((column, index) => ({
Header: column,
accessor: (row) => {
if (row.periods_balance && row.periods_balance[index]) {
return row.periods_balance[index].formatted_amount;
}
},
}))),
], [balanceSheetColumns]);
const [data, setData] = useState([]);
useEffect(() => {
if (!balanceSheet) { return; }
setData([
{
name: 'Assets',
code: '',
children: [
...getBalanceSheetAssetsAccounts(balanceSheetIndex),
],
},
{
name: 'Liabilies & Equity',
code: '',
children: [
...getBalanceSheetLiabilitiesAccounts(balanceSheetIndex),
]
}
])
}, [])
// if (balanceSheets.length > 0) {
// setData(balanceSheets[0].balance_sheet);
// }
return (
<FinancialSheet
companyTitle={'Facebook, Incopration'}
@@ -38,4 +121,8 @@ export default function BalanceSheetTable({
</FinancialSheet>
)
}
}
export default compose(
FinancialStatementConnect,
)(BalanceSheetTable);

View File

@@ -0,0 +1,78 @@
import React, {useState, useEffect, useMemo} from 'react';
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
import ProfitLossSheetTable from './ProfitLossSheetTable';
import LoadingIndicator from 'components/LoadingIndicator';
import { useAsync } from 'react-use';
import moment from 'moment';
import {compose} from 'utils';
import DashboardConnect from 'connectors/Dashboard.connector';
import ProfitLossSheetConnect from 'connectors/ProfitLossSheet.connect';
function ProfitLossSheet({
changePageTitle,
fetchProfitLossSheet,
getProfitLossSheetIndex,
getProfitLossSheet,
getProfitLossSheetAccounts,
getProfitLossSheetColumns,
}) {
const [filter, setFilter] = useState({});
const [reload, setReload] = useState(false);
// Change page title of the dashboard.
useEffect(() => {
changePageTitle('Trial Balance Sheet');
}, []);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchProfitLossSheet(filter),
]);
});
const handleFilterSubmit = (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);
}
const profitLossSheetIndex = useMemo(() => {
return getProfitLossSheetIndex(filter);
}, [filter, getProfitLossSheetIndex]);
const profitLossSheetAccounts = useMemo(() => {
return getProfitLossSheetAccounts(profitLossSheetIndex);
}, [profitLossSheetIndex, getProfitLossSheet]);
const profitLossSheetColumns = useMemo(() => {
return getProfitLossSheetColumns(profitLossSheetIndex);
}, [profitLossSheetIndex])
return (
<div class="financial-statement">
<ProfitLossSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<ProfitLossSheetTable
accounts={profitLossSheetAccounts}
columns={profitLossSheetColumns} />
</LoadingIndicator>
</div>
</div>
);
}
export default compose(
DashboardConnect,
ProfitLossSheetConnect
)(ProfitLossSheet);

View File

@@ -0,0 +1,227 @@
import React, {useState, useMemo, useEffect} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system';
import {
Button,
FormGroup,
Position,
MenuItem,
RadioGroup,
Radio,
HTMLSelect,
Intent,
Popover,
} from "@blueprintjs/core";
import {Select} from '@blueprintjs/select';
import {DateInput} from '@blueprintjs/datetime';
import {useIntl} from 'react-intl';
import {
momentFormatter,
handleStringChange,
parseDateRangeQuery,
} from 'utils';
import moment from 'moment';
export default function ProfitLossSheetHeader({
onSubmitFilter,
pageFilter,
}) {
const intl = useIntl();
const displayColumnsByOptions = [
{key: 'total', name: 'Total'},
{key: 'year', name: 'Year'},
{key: 'month', name: 'Month'},
{key: 'week', name: 'Week'},
{key: 'day', name: 'Day'},
{key: 'quarter', name: 'Quarter'},
];
const [filter, setFilter] = useState({
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate()
});
const setFilterByKey = (name, value) => {
setFilter({ ...filter, [name]: value });
};
const [reportDateRange, setReportDateRange] = useState('this_year');
useEffect(() => {
if (reportDateRange === 'custom') { return; }
const dateRange = parseDateRangeQuery(reportDateRange);
if (dateRange) {
setFilter((filter) => ({ ...filter, ...dateRange, }));
}
}, [reportDateRange])
const selectedDisplayColumnOpt = useMemo(() => {
return displayColumnsByOptions.find(o => o.key === filter.display_columns_by);
}, [filter.display_columns_by, displayColumnsByOptions]);
// Account type item of select filed.
const accountTypeItem = (item, { handleClick, modifiers, query }) => {
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
};
// Handle item select of `display columns by` field.
const onItemSelectDisplayColumns = (item) => {
setFilterByKey('display_columns_by', item.key);
};
// Handle any date change.
const handleDateChange = (name) => (date) => {
setReportDateRange('custom');
setFilterByKey(name, date);
};
// handle submit filter submit button.
const handleSubmitClick = () => {
onSubmitFilter(filter);
};
const dateRangeOptions = [
{value: 'today', label: 'Today', },
{value: 'this_week', label: 'This Week'},
{value: 'this_month', label: 'This Month'},
{value: 'this_quarter', label: 'This Quarter'},
{value: 'this_year', label: 'This Year'},
{value: 'custom', label: 'Custom Range'},
];
const [activeRowsColumns, setActiveRowsColumns] = useState(false);
const onClickActiveRowsColumnsBtn = () => {
setActiveRowsColumns(!activeRowsColumns);
};
const activeRowsColumnsPopover = (
<div>
<h5>Columns</h5>
<RadioGroup
name="none_zero"
selectedValue={filter.none_zero}
onChange={handleStringChange((value) => {
setFilterByKey('none_zero', value);
})}
>
<Radio label="All" value="0" />
<Radio label="Non-Zero" value="1" />
</RadioGroup>
</div>
);
return (
<FinancialStatementHeader>
<Row>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'report_date_range'})}
minimal={true}
fill={true}>
<HTMLSelect
fill={true}
options={dateRangeOptions}
value={reportDateRange}
onChange={(event) => setReportDateRange(event.target.value)} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'from_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.from_date}
onChange={handleDateChange('from_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'to_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.to_date}
onChange={handleDateChange('to_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
</Row>
<Row>
<Col sm={3}>
<FormGroup
label={'Display report columns'}
className="{'form-group-display-columns-by'}"
inline={false}>
<Select
items={displayColumnsByOptions}
noResults={<MenuItem disabled={true} text="No results." />}
filterable={false}
itemRenderer={accountTypeItem}
popoverProps={{ minimal: true }}
onItemSelect={onItemSelectDisplayColumns}>
<Button
rightIcon="caret-down"
fill={true}
text={selectedDisplayColumnOpt ? selectedDisplayColumnOpt.name : 'Select'} />
</Select>
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={'Show non-zero or active only'}
inline={false}>
<Popover
isOpen={activeRowsColumns}
content={activeRowsColumnsPopover}
minimal={true}
position={Position.BOTTOM}>
<Button
rightIcon="caret-down"
fill={true}
text="Active rows/Columns Active"
onClick={onClickActiveRowsColumnsBtn} />
</Popover>
</FormGroup>
</Col>
<Col sm={3}>
<RadioGroup
inline={true}
label={intl.formatMessage({'id': 'accounting_basis'})}
name="accounting_bahandleRadioChangesis"
selectedValue={filter.accounting_basis}
onChange={handleStringChange((value) => {
setFilterByKey('accounting_basis', value);
})}
>
<Radio label="Cash" value="cash" />
<Radio label="Accural" value="accural" />
</RadioGroup>
</Col>
<Col sm={3}>
<Button intent={Intent.PRIMARY} type="submit" onClick={handleSubmitClick}>
{ 'Calculate Report' }
</Button>
</Col>
</Row>
</FinancialStatementHeader>
)
}

View File

@@ -0,0 +1,75 @@
import React, { useEffect, useState, useMemo } from 'react';
import TrialBalanceSheetHeader from "./TrialBalanceSheetHeader";
import LoadingIndicator from 'components/LoadingIndicator';
import TrialBalanceSheetTable from './TrialBalanceSheetTable';
import { useAsync } from 'react-use';
import moment from 'moment';
import {compose} from 'utils';
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
import DashboardConnect from 'connectors/Dashboard.connector';
function TrialBalanceSheet({
changePageTitle,
fetchTrialBalanceSheet,
getTrialBalanceSheetIndex,
getTrialBalanceAccounts,
}) {
const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
none_zero: false,
});
const [reload, setReload] = useState(false);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchTrialBalanceSheet(),
]);
});
// Retrieve balance sheet index by the given filter query.
const trialBalanceSheetIndex = useMemo(() => {
return getTrialBalanceSheetIndex(filter);
}, [getTrialBalanceSheetIndex, filter]);
// Retrieve balance sheet accounts bu the given sheet index.
const trialBalanceAccounts = useMemo(() => {
return getTrialBalanceAccounts(trialBalanceSheetIndex);
}, [trialBalanceSheetIndex]);
// Change page title of the dashboard.
useEffect(() => {
changePageTitle('Trial Balance Sheet');
}, []);
const handleFilterSubmit = (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);
};
return (
<div class="financial-statement">
<TrialBalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<TrialBalanceSheetTable
trialBalanceSheetAccounts={trialBalanceAccounts}
trialBalanceSheetIndex={trialBalanceSheetIndex} />
</LoadingIndicator>
</div>
</div>
)
}
export default compose(
DashboardConnect,
TrialBalanceSheetConnect,
)(TrialBalanceSheet);

View File

@@ -0,0 +1,113 @@
import React, {useState} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system';
import {
Button,
FormGroup,
Position,
MenuItem,
RadioGroup,
Radio,
HTMLSelect,
Intent,
Popover,
} from "@blueprintjs/core";
import {DateInput} from '@blueprintjs/datetime';
import moment from 'moment';
import {momentFormatter} from 'utils';
import {useIntl} from 'react-intl';
export default function TrialBalanceSheetHeader({
pageFilter,
onSubmitFilter,
}) {
const intl = useIntl();
const [filter, setFilter] = useState({
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate()
})
const setFilterByKey = (name, value) => {
setFilter({ ...filter, [name]: value });
};
const [reportDateRange, setReportDateRange] = useState('this_year');
const dateRangeOptions = [
{value: 'today', label: 'Today', },
{value: 'this_week', label: 'This Week'},
{value: 'this_month', label: 'This Month'},
{value: 'this_quarter', label: 'This Quarter'},
{value: 'this_year', label: 'This Year'},
{value: 'custom', label: 'Custom Range'},
];
const handleDateChange = (name) => (date) => {
setReportDateRange('custom');
setFilterByKey(name, date);
};
const handleSubmitClick = () => { onSubmitFilter(filter); };
return (
<FinancialStatementHeader>
<Row>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'report_date_range'})}
minimal={true}
fill={true}>
<HTMLSelect
fill={true}
options={dateRangeOptions}
value={reportDateRange}
onChange={(event) => setReportDateRange(event.target.value)} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'from_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.from_date}
onChange={handleDateChange('from_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'to_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.to_date}
onChange={handleDateChange('to_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
</Row>
<Row>
<Col sm={3}>
<Button
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitClick}>
{ 'Run Report' }
</Button>
</Col>
</Row>
</FinancialStatementHeader>
);
}

View File

@@ -0,0 +1,80 @@
import React, {useEffect, useState, useMemo} from 'react';
import FinancialSheet from 'components/FinancialSheet';
import DataTable from 'components/DataTable';
export default function TrialBalanceSheetTable({
trialBalanceSheetAccounts,
trialBalanceSheetIndex,
}) {
const [data, setData] = useState([]);
const columns = useMemo(() => [
{
// Build our expander column
id: 'expander', // Make sure it has an ID
Header: ({
getToggleAllRowsExpandedProps,
isAllRowsExpanded
}) => (
<span {...getToggleAllRowsExpandedProps()}>
{isAllRowsExpanded ? '👇' : '👉'}
</span>
),
Cell: ({ row }) =>
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
// to build the toggle for expanding a row
row.canExpand ? (
<span
{...row.getToggleRowExpandedProps({
style: {
// We can even use the row.depth property
// and paddingLeft to indicate the depth
// of the row
paddingLeft: `${row.depth * 2}rem`,
},
})}
>
{row.isExpanded ? '👇' : '👉'}
</span>
) : null,
},
{
Header: 'Account Name',
accessor: 'name',
className: "actions",
},
{
Header: 'Code',
accessor: 'code',
className: "note",
},
{
Header: 'Credit',
accessor: 'credit',
className: 'credit',
},
{
Header: 'debit',
accessor: 'debit',
className: 'debit',
},
{
Header: 'Balance',
accessor: 'balance',
className: 'balance',
}
], []);
return (
<FinancialSheet
companyTitle={'Facebook, Incopration'}
sheetType={'Trial Balance Sheet'}
date={''}>
<DataTable
columns={columns}
data={trialBalanceSheetAccounts} />
</FinancialSheet>
);
}

View File

@@ -75,18 +75,25 @@ export default [
loader: () => import('containers/Dashboard/FinancialStatements/LedgerSheet')
}),
},
{
path: `${BASE_URL}/accounting/trial-balance`,
name: 'dashboard.accounting.trial.balance',
component: LazyLoader({
loader: () => import('containers/Dashboard/FinancialStatements/TrialBalanceSheet')
}),
},
{
path: `${BASE_URL}/accounting/balance-sheet`,
name: 'dashboard.accounting.balance.sheet',
component: LazyLoader({
loader: () => import('containers/Dashboard/FinancialStatements/BalanceSheet')
loader: () => import('containers/Dashboard/FinancialStatements/BalanceSheet/BalanceSheet')
}),
},
{
path: `${BASE_URL}/accounting/trial-balance-sheet`,
name: 'dashboard.accounting.trial.balance',
component: LazyLoader({
loader: () => import('containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet')
}),
},
{
path: `${BASE_URL}/accounting/profit-loss-sheet`,
name: 'dashboard.accounting.profit.loss.sheet',
component: LazyLoader({
loader: () => import('containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheet')
}),
}
];

View File

@@ -19,8 +19,33 @@ export const fetchBalanceSheet = ({ query }) => {
dispatch({
type: t.BALANCE_SHEET_STATEMENT_SET,
data: response.data,
query: query,
});
resolve(response);
}).catch((error) => { reject(error); });
});
};
export const fetchTrialBalanceSheet = ({ query }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get('/financial_statements/trial_balance_sheet').then((response) => {
dispatch({
type: t.TRAIL_BALANCE_STATEMENT_SET,
data: response.data,
});
resolve(response.data);
}).catch((error) => { reject(error); })
})
};
export const fetchProfitLossSheet = ({ query }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get('/financial_statements/profit_loss_sheet').then((response) => {
dispatch({
type: t.PROFIT_LOSS_STATEMENT_SET,
data: response.data,
});
resolve(response.data);
}).catch((error) => { reject(error); });
})
};

View File

@@ -1,14 +1,41 @@
import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types';
import {getBalanceSheetIndexByQuery, getTrialBalanceSheetIndex} from './financialStatements.selectors';
import { actionComplete } from '@syncfusion/ej2-react-grids';
const initialState = {
balanceSheet: [],
balanceSheets: [],
trialBalanceSheets: [],
generalLedger: [],
trialBalance: [],
};
export default createReducer(initialState, {
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
state.balanceSheet.push(action.data);
const index = getBalanceSheetIndexByQuery(state.balanceSheets, action.query);
const balanceSheet = {
balances: action.data.balance_sheet,
columns: Object.values(action.data.columns),
query: action.data.query,
};
if (index !== -1) {
state.balanceSheets[index] = balanceSheet;
} else {
state.balanceSheets.push(balanceSheet);
}
},
[t.TRAIL_BALANCE_STATEMENT_SET]: (state, action) => {
const index = getTrialBalanceSheetIndex(state.trialBalanceSheets, action.query);
const trailBalanceSheet = {
accounts: action.data.accounts,
query: action.data.query,
};
if (index !== -1) {
state.trialBalanceSheets[index] = trailBalanceSheet;
} else {
state.trailBalanceSheet.push(trailBalanceSheet);
}
}
});

View File

@@ -0,0 +1,91 @@
import {getObjectDiff} from 'utils';
// Balance Sheet.
export const getBalanceSheetByQuery = (balanceSheets, query) => {
return balanceSheets.find(balanceSheet => {
return getObjectDiff(query, balanceSheet.query).length === 0;
});
};
export const getBalanceSheetIndexByQuery = (balanceSheets, query) => {
return balanceSheets.findIndex((balanceSheet) => {
return getObjectDiff(query, balanceSheet.query).length === 0;
});
};
export const getBalanceSheetByIndex = (balanceSheets, sheetIndex) => {
return balanceSheets[sheetIndex];
};
export const getBalanceSheetQuery = (balanceSheets, sheetIndex) => {
if (typeof balanceSheets[sheetIndex] === 'object') {
return balanceSheets[sheetIndex].query || {};
}
return {};
};
export const getBalanceSheetAssetsAccounts = (balanceSheets, sheetIndex) => {
if (typeof balanceSheets[sheetIndex] === 'object') {
return balanceSheets[sheetIndex].balances.assets.accounts || [];
}
return [];
};
export const getBalanceSheetLiabilitiesAccounts = (balanceSheets, sheetIndex) => {
if (typeof balanceSheets[sheetIndex] === 'object') {
return balanceSheets[sheetIndex].balances.liabilities_equity.accounts || [];
}
return [];
};
export const getBalanceSheetColumns = (balanceSheets, sheetIndex) => {
if (typeof balanceSheets[sheetIndex] === 'object') {
return balanceSheets[sheetIndex].columns;
}
return [];
};
// Trial Balance Sheet.
export const getTrialBalanceSheetIndex = (trialBalanceSheets, query) => {
return trialBalanceSheets.find((trialBalanceSheet) => {
return getObjectDiff(query, trialBalanceSheet.query).length === 0;
});
};
export const getTrialBalanceAccounts = (trialBalanceSheets, sheetIndex) => {
if (typeof trialBalanceSheets[sheetIndex] === 'object') {
return trialBalanceSheets[sheetIndex].accounts;
}
return [];
};
export const getTrialBalanceQuery = (trialBalanceSheets, sheetIndex) => {
if (typeof trialBalanceSheets[sheetIndex] === 'object') {
return trialBalanceSheets[sheetIndex].query;
}
return [];
};
// Profit/Loss Sheet selectors.
export const getProfitLossSheetIndex = (profitLossSheets, query) => {
return profitLossSheets.find((profitLossSheet) => {
return getObjectDiff(query, profitLossSheet.query).length === 0;
});
}
export const getProfitLossSheet = (profitLossSheets, index) => {
return (typeof profitLossSheets[index] !== 'undefined') ?
profitLossSheets[index] : null;
};
export const getProfitLossSheetColumns = (profitLossSheets, index) => {
const sheet = getProfitLossSheet(profitLossSheets, index);
return (sheet) ? sheet.columns : [];
};
export const getProfitLossSheetAccounts = (profitLossSheets, index) => {
const sheet = getProfitLossSheet(profitLossSheets, index);
return (sheet) ? sheet.accounts : [];
};

View File

@@ -3,4 +3,5 @@
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',
}

View File

@@ -1,4 +1,5 @@
import moment from 'moment';
import _ from 'lodash';
export function removeEmptyFromObject(obj) {
obj = Object.assign({}, obj);
@@ -76,4 +77,38 @@ export const objectKeysTransform = (obj, transform) => {
};
export const compose = (...funcs) =>
funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
export const getObjectDiff = (a, b) => {
return _.reduce(a, (result, value, key) => {
return _.isEqual(value, b[key]) ?
result : result.concat(key);
}, []);
}
export const parseDateRangeQuery = (keyword) => {
const queries = {
'today': {
range: 'day',
},
'this_year': {
range: 'year',
},
'this_month': {
range: 'month'
},
'this_week': {
range: 'week'
}
};
if (typeof queries[keyword] === 'undefined') {
throw new Error(`The date range query ${keyword} is not defined.`);
}
const query = queries[keyword];
return {
from_date: moment().startOf(query.range).toDate(),
to_date: moment().endOf(query.range).toDate(),
};
};