mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
WIP Financial statements.
This commit is contained in:
67
client/src/components/AccountsMultiSelect.js
Normal file
67
client/src/components/AccountsMultiSelect.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, {useMemo, useCallback, useState} from 'react';
|
||||||
|
import {omit} from 'lodash';
|
||||||
|
import {
|
||||||
|
MenuItem,
|
||||||
|
Button
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
// import {Select} from '@blueprintjs/select';
|
||||||
|
import MultiSelect from 'components/MultiSelect';
|
||||||
|
|
||||||
|
export default function AccountsMultiSelect({
|
||||||
|
accounts,
|
||||||
|
onAccountSelected,
|
||||||
|
}) {
|
||||||
|
const [selectedAccounts, setSelectedAccounts] = useState({});
|
||||||
|
|
||||||
|
const isAccountSelect = useCallback((accountId) => {
|
||||||
|
return 'undefined' !== typeof selectedAccounts[accountId];
|
||||||
|
}, [selectedAccounts]);
|
||||||
|
|
||||||
|
// Account item of select accounts field.
|
||||||
|
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon={isAccountSelect(item.id) ? "tick" : "blank"}
|
||||||
|
text={item.name}
|
||||||
|
label={item.code}
|
||||||
|
key={item.id}
|
||||||
|
onClick={handleClick} />
|
||||||
|
);
|
||||||
|
}, [isAccountSelect]);
|
||||||
|
|
||||||
|
const countSelectedAccounts = useMemo(() =>
|
||||||
|
Object.values(selectedAccounts).length,
|
||||||
|
[selectedAccounts]);
|
||||||
|
|
||||||
|
const onAccountSelect = useCallback((account) => {
|
||||||
|
const selected = {
|
||||||
|
...(!isAccountSelect(account.id)) ? {
|
||||||
|
...selectedAccounts,
|
||||||
|
[account.id]: true,
|
||||||
|
} : {
|
||||||
|
...omit(selectedAccounts, [account.id])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setSelectedAccounts({ ...selected });
|
||||||
|
onAccountSelected && onAccountSelected(selected);
|
||||||
|
}, [setSelectedAccounts, selectedAccounts, isAccountSelect, onAccountSelected]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
items={accounts}
|
||||||
|
noResults={<MenuItem disabled={true} text='No results.' />}
|
||||||
|
itemRenderer={accountItem}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
filterable={true}
|
||||||
|
onItemSelect={onAccountSelect}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
rightIcon='caret-down'
|
||||||
|
text={countSelectedAccounts === 0 ?
|
||||||
|
'All accounts' :
|
||||||
|
`(${countSelectedAccounts}) Selected accounts`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</MultiSelect>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -34,7 +34,8 @@ export default function DataTable({
|
|||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
manualSortBy = 'false',
|
manualSortBy = 'false',
|
||||||
selectionColumn = false,
|
selectionColumn = false,
|
||||||
className
|
className,
|
||||||
|
noResults = 'This report does not contain any data.'
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
@@ -144,7 +145,8 @@ export default function DataTable({
|
|||||||
</div>
|
</div>
|
||||||
<div {...getTableBodyProps()} className="tbody">
|
<div {...getTableBodyProps()} className="tbody">
|
||||||
{page.map((row, i) => {
|
{page.map((row, i) => {
|
||||||
prepareRow(row)
|
prepareRow(row);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...row.getRowProps()} className="tr">
|
<div {...row.getRowProps()} className="tr">
|
||||||
{row.cells.map((cell) => {
|
{row.cells.map((cell) => {
|
||||||
@@ -154,6 +156,12 @@ export default function DataTable({
|
|||||||
})}
|
})}
|
||||||
</div>)
|
</div>)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{ (page.length === 0) && (
|
||||||
|
<div className={'tr no-results'}>
|
||||||
|
<div class="td">{ noResults }</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function FinancialSheet({
|
|||||||
<LoadingIndicator loading={loading}>
|
<LoadingIndicator loading={loading}>
|
||||||
<h1 class="financial-sheet__title">{ companyTitle }</h1>
|
<h1 class="financial-sheet__title">{ companyTitle }</h1>
|
||||||
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
|
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
|
||||||
<div class="financial-sheet__date">As of { formattedDate }</div>
|
<div class="financial-sheet__date">From { formattedDate } | To { formattedDate }</div>
|
||||||
|
|
||||||
<div class="financial-sheet__table">
|
<div class="financial-sheet__table">
|
||||||
{ children }
|
{ children }
|
||||||
|
|||||||
177
client/src/components/MultiSelect.js
Normal file
177
client/src/components/MultiSelect.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import classNames from "classnames";
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
DISPLAYNAME_PREFIX,
|
||||||
|
Popover,
|
||||||
|
Position,
|
||||||
|
TagInput,
|
||||||
|
Utils,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
|
import {
|
||||||
|
QueryList,
|
||||||
|
} from '@blueprintjs/select';
|
||||||
|
|
||||||
|
// export interface IMultiSelectProps<T> extends IListItemsProps<T> {
|
||||||
|
// /**
|
||||||
|
// * Whether the component should take up the full width of its container.
|
||||||
|
// * This overrides `popoverProps.fill` and `tagInputProps.fill`.
|
||||||
|
// */
|
||||||
|
// fill?: boolean;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Whether the popover opens on key down or when `TagInput` is focused.
|
||||||
|
// * @default false
|
||||||
|
// */
|
||||||
|
// openOnKeyDown?: boolean;
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Input placeholder text. Shorthand for `tagInputProps.placeholder`.
|
||||||
|
// * @default "Search..."
|
||||||
|
// */
|
||||||
|
// placeholder?: string;
|
||||||
|
|
||||||
|
// /** Props to spread to `Popover`. Note that `content` cannot be changed. */
|
||||||
|
// popoverProps?: Partial<IPopoverProps> & object;
|
||||||
|
|
||||||
|
// /** Controlled selected values. */
|
||||||
|
// selectedItems?: T[];
|
||||||
|
|
||||||
|
// /** Props to spread to `TagInput`. Use `query` and `onQueryChange` to control the input. */
|
||||||
|
// tagInputProps?: Partial<ITagInputProps> & object;
|
||||||
|
|
||||||
|
// /** Custom renderer to transform an item into tag content. */
|
||||||
|
// tagRenderer: (item: T) => React.ReactNode;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface IMultiSelectState {
|
||||||
|
// isOpen: boolean;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default class MultiSelect extends React.Component {
|
||||||
|
static get displayName() {
|
||||||
|
return `${DISPLAYNAME_PREFIX}.MultiSelect`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get defaultProps() {
|
||||||
|
return {
|
||||||
|
fill: false,
|
||||||
|
placeholder: "Search...",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = { isOpen: (this.props.popoverProps && this.props.popoverProps.isOpen) || false };
|
||||||
|
this.input = null;
|
||||||
|
this.queryList = null;
|
||||||
|
this.listRef = React.createRef();
|
||||||
|
|
||||||
|
this.refHandlers = {
|
||||||
|
queryList: (ref) => {
|
||||||
|
this.queryList = ref;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// omit props specific to this component, spread the rest.
|
||||||
|
const { openOnKeyDown, popoverProps, tagInputProps, ...restProps } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryList
|
||||||
|
{...restProps}
|
||||||
|
onItemSelect={this.handleItemSelect.bind(this)}
|
||||||
|
onQueryChange={this.handleQueryChange.bind(this)}
|
||||||
|
ref={this.refHandlers.queryList}
|
||||||
|
renderer={this.renderQueryList.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQueryList(listProps) {
|
||||||
|
const { fill, tagInputProps = {}, popoverProps = {} } = this.props;
|
||||||
|
const { handleKeyDown, handleKeyUp } = listProps;
|
||||||
|
|
||||||
|
if (fill) {
|
||||||
|
popoverProps.fill = true;
|
||||||
|
tagInputProps.fill = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add our own inputProps.className so that we can reference it in event handlers
|
||||||
|
const { inputProps = {} } = tagInputProps;
|
||||||
|
inputProps.className = classNames(inputProps.className, Classes.MULTISELECT_TAG_INPUT_INPUT);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
autoFocus={false}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
enforceFocus={false}
|
||||||
|
isOpen={this.state.isOpen}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
{...popoverProps}
|
||||||
|
className={classNames(listProps.className, popoverProps.className)}
|
||||||
|
onInteraction={this.handlePopoverInteraction.bind(this)}
|
||||||
|
popoverClassName={classNames(Classes.MULTISELECT_POPOVER, popoverProps.popoverClassName)}
|
||||||
|
onOpened={this.handlePopoverOpened.bind(this)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onKeyDown={this.state.isOpen ? handleKeyDown : this.handleTargetKeyDown}
|
||||||
|
onKeyUp={this.state.isOpen ? handleKeyUp : undefined}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
<div onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} ref={this.listRef}>
|
||||||
|
{listProps.itemList}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleItemSelect(item, evt) {
|
||||||
|
if (this.input != null) {
|
||||||
|
this.input.focus();
|
||||||
|
}
|
||||||
|
Utils.safeInvoke(this.props.onItemSelect, item, evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleQueryChange(query, evt) {
|
||||||
|
this.setState({ isOpen: query.length > 0 || !this.props.openOnKeyDown });
|
||||||
|
Utils.safeInvoke(this.props.onQueryChange, query, evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePopoverInteraction = (isOpen, e) => {
|
||||||
|
if (e && this.listRef.current && this.listRef.current.contains(e.target)) {
|
||||||
|
this.setState({ isOpen: true })
|
||||||
|
} else {
|
||||||
|
this.setState({ isOpen });
|
||||||
|
}
|
||||||
|
Utils.safeInvokeMember(this.props.popoverProps, "onInteraction", isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handlePopoverOpened(node) {
|
||||||
|
if (this.queryList != null) {
|
||||||
|
// scroll active item into view after popover transition completes and all dimensions are stable.
|
||||||
|
this.queryList.scrollActiveItemIntoView();
|
||||||
|
}
|
||||||
|
Utils.safeInvokeMember(this.props.popoverProps, "onOpened", node);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
17
client/src/components/SelectList.js
Normal file
17
client/src/components/SelectList.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React, {useState, useMemo, useCallback} from 'react';
|
||||||
|
import {Button} from '@blueprintjs/core';
|
||||||
|
import {Select} from '@blueprintjs/select';
|
||||||
|
|
||||||
|
export default function SelectList(props) {
|
||||||
|
const {buttonLabel, ...rest} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select {...rest}>
|
||||||
|
<Button
|
||||||
|
rightIcon="caret-down"
|
||||||
|
fill={true}>
|
||||||
|
{ buttonLabel }
|
||||||
|
</Button>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
client/src/connectors/GeneralLedgerSheet.connect.js
Normal file
20
client/src/connectors/GeneralLedgerSheet.connect.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {
|
||||||
|
fetchGeneralLedger,
|
||||||
|
} from 'store/financialStatement/financialStatements.actions';
|
||||||
|
import {
|
||||||
|
getFinancialSheetIndexByQuery,
|
||||||
|
getFinancialSheet,
|
||||||
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
|
export const mapStateToProps = (state, props) => ({
|
||||||
|
getGeneralLedgerSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.generalLedger.sheets, query),
|
||||||
|
getGeneralLedgerSheet: (index) => getFinancialSheet(state.financialStatements.generalLedger.sheets, index),
|
||||||
|
generalLedgerSheetLoading: state.financialStatements.generalLedger.loading,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
fetchGeneralLedger: (query = {}) => dispatch(fetchGeneralLedger({ query })),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps);
|
||||||
@@ -8,8 +8,9 @@ import {
|
|||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
export const mapStateToProps = (state, props) => ({
|
||||||
getJournalSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.journalSheets, query),
|
getJournalSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.journal.sheets, query),
|
||||||
getJournalSheet: (index) => getFinancialSheet(state.financialStatements.journalSheets, index),
|
getJournalSheet: (index) => getFinancialSheet(state.financialStatements.journal.sheets, index),
|
||||||
|
journalSheetLoading: state.financialStatements.journal.loading,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|||||||
@@ -3,17 +3,13 @@ import {
|
|||||||
fetchProfitLossSheet,
|
fetchProfitLossSheet,
|
||||||
} from 'store/financialStatement/financialStatements.actions';
|
} from 'store/financialStatement/financialStatements.actions';
|
||||||
import {
|
import {
|
||||||
getProfitLossSheetIndex,
|
getFinancialSheetIndexByQuery,
|
||||||
getProfitLossSheet,
|
getFinancialSheet,
|
||||||
getProfitLossSheetColumns,
|
|
||||||
getProfitLossSheetAccounts,
|
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
export const mapStateToProps = (state, props) => ({
|
||||||
getProfitLossSheetIndex: (query) => getProfitLossSheetIndex(state.financialStatements.profitLossSheets, query),
|
getProfitLossSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.profitLoss.sheets, query),
|
||||||
getProfitLossSheet: (index) => getProfitLossSheet(state.financialStatements.profitLossSheets, index),
|
getProfitLossSheet: (index) => getFinancialSheet(state.financialStatements.profitLoss.sheets, index),
|
||||||
getProfitLossSheetColumns: (index) => getProfitLossSheetColumns(state.financialStatements.profitLossSheets, index),
|
|
||||||
getProfitLossSheetAccounts: (index) => getProfitLossSheetAccounts(state.financialStatements.profitLossSheets, index),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import BalanceSheetHeader from './BalanceSheetHeader';
|
|||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
import BalanceSheetTable from './BalanceSheetTable';
|
import BalanceSheetTable from './BalanceSheetTable';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import BalanceSheetActionsBar from './BalanceSheetActionsBar';
|
||||||
|
|
||||||
function BalanceSheet({
|
function BalanceSheet({
|
||||||
fetchBalanceSheet,
|
fetchBalanceSheet,
|
||||||
@@ -22,20 +25,15 @@ function BalanceSheet({
|
|||||||
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: 'cash',
|
||||||
display_columns_by: 'total',
|
display_columns_type: 'total',
|
||||||
|
display_columns_by: '',
|
||||||
none_zero: false,
|
none_zero: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [reload, setReload] = useState(false);
|
const fetchHook = useAsync(async (query = filter) => {
|
||||||
|
|
||||||
const fetchHook = useAsync(async () => {
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchBalanceSheet({
|
fetchBalanceSheet({ ...query }),
|
||||||
...filter,
|
|
||||||
display_columns_type: 'total',
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
setReload(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle fetch the data of balance sheet.
|
// Handle fetch the data of balance sheet.
|
||||||
@@ -57,15 +55,20 @@ function BalanceSheet({
|
|||||||
|
|
||||||
// Handle re-fetch balance sheet after filter change.
|
// Handle re-fetch balance sheet after filter change.
|
||||||
const handleFilterSubmit = useCallback((filter) => {
|
const handleFilterSubmit = useCallback((filter) => {
|
||||||
setFilter({
|
const _filter = {
|
||||||
...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({ ..._filter });
|
||||||
}, [setFilter]);
|
fetchHook.execute(_filter);
|
||||||
|
}, [setFilter, fetchHook]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DashboardInsider>
|
||||||
|
<BalanceSheetActionsBar />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
<div class="financial-statement">
|
<div class="financial-statement">
|
||||||
<BalanceSheetHeader
|
<BalanceSheetHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
@@ -81,6 +84,8 @@ function BalanceSheet({
|
|||||||
</LoadingIndicator>
|
</LoadingIndicator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarHeading,
|
||||||
|
NavbarDivider,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
|
||||||
|
export default function JournalActionsBar({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const filterDropdown = FilterDropdown({
|
||||||
|
fields: [],
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon='cog' />}
|
||||||
|
text='Customize Report'
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
content={filterDropdown}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text="Filter"
|
||||||
|
icon={ <Icon icon="filter" /> } />
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Print'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Export'
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,233 +1,108 @@
|
|||||||
import React, {useState, useMemo, useEffect} from 'react';
|
import React, {useMemo, 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 {
|
||||||
Button,
|
Button,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Position,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
HTMLSelect,
|
|
||||||
Intent,
|
|
||||||
Popover,
|
|
||||||
Classes,
|
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import {Select} from '@blueprintjs/select';
|
import SelectList from 'components/SelectList';
|
||||||
import {DateInput} from '@blueprintjs/datetime';
|
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
import {
|
|
||||||
momentFormatter,
|
|
||||||
handleStringChange,
|
|
||||||
parseDateRangeQuery,
|
|
||||||
} from 'utils';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
|
||||||
export default function BalanceSheetHeader({
|
export default function BalanceSheetHeader({
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
pageFilter,
|
pageFilter,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
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({
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
|
basis: 'cash',
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
from_date: moment(pageFilter.from_date).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate()
|
to_date: moment(pageFilter.to_date).toDate(),
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
from_date: Yup.date().required(),
|
||||||
|
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
||||||
|
}),
|
||||||
|
onSubmit: (values, actions) => {
|
||||||
|
onSubmitFilter(values);
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
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.
|
// Handle item select of `display columns by` field.
|
||||||
const onItemSelectDisplayColumns = (item) => {
|
const onItemSelectDisplayColumns = useCallback((item) => {
|
||||||
setFilterByKey('display_columns_by', item.key);
|
formik.setFieldValue('display_columns_type', item.type);
|
||||||
};
|
formik.setFieldValue('display_columns_by', item.by);
|
||||||
|
}, []);
|
||||||
// Handle any date change.
|
|
||||||
const handleDateChange = (name) => (date) => {
|
|
||||||
setReportDateRange('custom');
|
|
||||||
setFilterByKey(name, date);
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle submit filter submit button.
|
// handle submit filter submit button.
|
||||||
const handleSubmitClick = () => {
|
const handleSubmitClick = useCallback(() => {
|
||||||
onSubmitFilter(filter);
|
formik.submitForm();
|
||||||
};
|
}, [formik]);
|
||||||
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 filterAccountsOptions = useMemo(() => [
|
||||||
|
{key: '', name: 'Accounts with Zero Balance'},
|
||||||
|
{key: 'all-trans', name: 'All Transactions' },
|
||||||
|
], []);
|
||||||
|
|
||||||
const onClickActiveRowsColumnsBtn = () => {
|
const filterAccountRenderer = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
setActiveRowsColumns(!activeRowsColumns);
|
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const activeRowsColumnsPopover = (
|
const infoIcon = useMemo(() =>
|
||||||
<div>
|
(<Icon icon="info-circle" iconSize={12} />), []);
|
||||||
<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>
|
|
||||||
);
|
|
||||||
|
|
||||||
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
const handleAccountingBasisChange = useCallback((value) => {
|
||||||
|
formik.setFieldValue('basis', value);
|
||||||
|
}, [formik]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader>
|
<FinancialStatementHeader>
|
||||||
<Row>
|
<FinancialStatementDateRange formik={formik} />
|
||||||
<Col sm={3}>
|
|
||||||
<FormGroup
|
|
||||||
label={intl.formatMessage({'id': 'report_date_range'})}
|
|
||||||
labelInfo={infoIcon}
|
|
||||||
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'})}
|
|
||||||
labelInfo={infoIcon}
|
|
||||||
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'})}
|
|
||||||
labelInfo={infoIcon}
|
|
||||||
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>
|
<Row>
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<FormGroup
|
<SelectDisplayColumnsBy onItemSelect={onItemSelectDisplayColumns} />
|
||||||
label={'Display report columns'}
|
|
||||||
labelInfo={infoIcon}
|
|
||||||
className="form-group-display-columns-by form-group--select-list bp3-fill"
|
|
||||||
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>
|
||||||
|
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={'Show non-zero or active only'}
|
label={'Filter Accounts'}
|
||||||
className="form-group--select-list bp3-fill"
|
className="form-group--select-list bp3-fill"
|
||||||
inline={false}>
|
inline={false}>
|
||||||
|
|
||||||
<Popover
|
<SelectList
|
||||||
isOpen={activeRowsColumns}
|
items={filterAccountsOptions}
|
||||||
content={activeRowsColumnsPopover}
|
itemRenderer={filterAccountRenderer}
|
||||||
minimal={true}
|
onItemSelect={onItemSelectDisplayColumns}
|
||||||
position={Position.BOTTOM}>
|
popoverProps={{ minimal: true }}
|
||||||
|
filterable={false} />
|
||||||
<Button
|
|
||||||
rightIcon="caret-down"
|
|
||||||
fill={true}
|
|
||||||
text="Active rows/Columns Active"
|
|
||||||
onClick={onClickActiveRowsColumnsBtn} />
|
|
||||||
</Popover>
|
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<RadioGroup
|
<RadiosAccountingBasis
|
||||||
inline={true}
|
selectedValue={formik.values.basis}
|
||||||
label={intl.formatMessage({'id': 'accounting_basis'})}
|
onChange={handleAccountingBasisChange} />
|
||||||
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>
|
||||||
|
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<Button intent={Intent.PRIMARY} type="submit" onClick={handleSubmitClick}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmitClick}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
className={'button--submit-filter'}>
|
||||||
{ 'Calculate Report' }
|
{ 'Calculate Report' }
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import React, {useState, useCallback, useMemo} from 'react';
|
||||||
|
import {Row, Col} from 'react-grid-system';
|
||||||
|
import {momentFormatter} from 'utils';
|
||||||
|
import {DateInput} from '@blueprintjs/datetime';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {
|
||||||
|
HTMLSelect,
|
||||||
|
FormGroup,
|
||||||
|
Intent,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import {
|
||||||
|
parseDateRangeQuery
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
|
export default function FinancialStatementDateRange({
|
||||||
|
formik,
|
||||||
|
}) {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [reportDateRange, setReportDateRange] = useState('this_year');
|
||||||
|
|
||||||
|
const dateRangeOptions = useMemo(() => [
|
||||||
|
{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 = useCallback((name) => (date) => {
|
||||||
|
setReportDateRange('custom');
|
||||||
|
formik.setFieldValue(name, date);
|
||||||
|
}, [setReportDateRange, formik]);
|
||||||
|
|
||||||
|
// Handles date range field change.
|
||||||
|
const handleDateRangeChange = useCallback((e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value !== 'custom') {
|
||||||
|
const dateRange = parseDateRangeQuery(value);
|
||||||
|
if (dateRange) {
|
||||||
|
formik.setFieldValue('from_date', dateRange.from_date);
|
||||||
|
formik.setFieldValue('to_date', dateRange.to_date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setReportDateRange(value);
|
||||||
|
}, [formik]);
|
||||||
|
|
||||||
|
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col sm={3}>
|
||||||
|
<FormGroup
|
||||||
|
label={intl.formatMessage({'id': 'report_date_range'})}
|
||||||
|
labelInfo={infoIcon}
|
||||||
|
minimal={true}
|
||||||
|
fill={true}>
|
||||||
|
|
||||||
|
<HTMLSelect
|
||||||
|
fill={true}
|
||||||
|
options={dateRangeOptions}
|
||||||
|
value={reportDateRange}
|
||||||
|
onChange={handleDateRangeChange} />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col sm={3}>
|
||||||
|
<FormGroup
|
||||||
|
label={intl.formatMessage({'id': 'from_date'})}
|
||||||
|
labelInfo={infoIcon}
|
||||||
|
minimal={true}
|
||||||
|
fill={true}
|
||||||
|
intent={formik.errors.from_date && Intent.DANGER}>
|
||||||
|
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
value={formik.values.from_date}
|
||||||
|
onChange={handleDateChange('from_date')}
|
||||||
|
popoverProps={{ position: Position.BOTTOM }}
|
||||||
|
fill={true} />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col sm={3}>
|
||||||
|
<FormGroup
|
||||||
|
label={intl.formatMessage({'id': 'to_date'})}
|
||||||
|
labelInfo={infoIcon}
|
||||||
|
minimal={true}
|
||||||
|
fill={true}
|
||||||
|
intent={formik.errors.to_date && Intent.DANGER}>
|
||||||
|
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
value={formik.values.to_date}
|
||||||
|
onChange={handleDateChange('to_date')}
|
||||||
|
popoverProps={{ position: Position.BOTTOM }}
|
||||||
|
fill={true}
|
||||||
|
intent={formik.errors.to_date && Intent.DANGER} />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import GeneralLedgerTable from 'containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable';
|
||||||
|
import useAsync from 'hooks/async';
|
||||||
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
|
import GeneralLedgerConnect from 'connectors/GeneralLedgerSheet.connect';
|
||||||
|
import GeneralLedgerHeader from './GeneralLedgerHeader';
|
||||||
|
import {compose} from 'utils';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider'
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
import DashboardActionsBar from 'components/Accounts/AccountsActionsBar'
|
||||||
|
import GeneralLedgerActionsBar from './GeneralLedgerActionsBar';
|
||||||
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
|
|
||||||
|
function GeneralLedger({
|
||||||
|
changePageTitle,
|
||||||
|
getGeneralLedgerSheetIndex,
|
||||||
|
getGeneralLedgerSheet,
|
||||||
|
fetchGeneralLedger,
|
||||||
|
generalLedgerSheetLoading,
|
||||||
|
fetchAccounts,
|
||||||
|
}) {
|
||||||
|
const [filter, setFilter] = useState({
|
||||||
|
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
|
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
|
basis: 'accural',
|
||||||
|
none_zero: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change page title of the dashboard.
|
||||||
|
useEffect(() => {
|
||||||
|
changePageTitle('General Ledger');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchHook = useAsync(() => {
|
||||||
|
return Promise.all([
|
||||||
|
fetchAccounts(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchSheet = useAsync((query = filter) => {
|
||||||
|
return Promise.all([
|
||||||
|
fetchGeneralLedger(query),
|
||||||
|
]);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
const generalLedgerSheetIndex = useMemo(() =>
|
||||||
|
getGeneralLedgerSheetIndex(filter),
|
||||||
|
[getGeneralLedgerSheetIndex, filter]);
|
||||||
|
|
||||||
|
const generalLedgerSheet = useMemo(() =>
|
||||||
|
getGeneralLedgerSheet(generalLedgerSheetIndex),
|
||||||
|
[generalLedgerSheetIndex, getGeneralLedgerSheet])
|
||||||
|
|
||||||
|
// Handle fetch data of trial balance table.
|
||||||
|
const handleFetchData = useCallback(() => { fetchSheet.execute() }, [fetchSheet]);
|
||||||
|
|
||||||
|
// Handle financial statement filter change.
|
||||||
|
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'),
|
||||||
|
};
|
||||||
|
setFilter(parsedFilter);
|
||||||
|
fetchSheet.execute(parsedFilter);
|
||||||
|
}, [setFilter, fetchSheet]);
|
||||||
|
|
||||||
|
const handleFilterChanged = () => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider>
|
||||||
|
<GeneralLedgerActionsBar onFilterChanged={handleFilterChanged} />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
|
<div class="financial-statement financial-statement--general-ledger">
|
||||||
|
<GeneralLedgerHeader
|
||||||
|
pageFilter={filter}
|
||||||
|
onSubmitFilter={handleFilterSubmit} />
|
||||||
|
|
||||||
|
<div class="financial-statement__table">
|
||||||
|
<GeneralLedgerTable
|
||||||
|
loading={generalLedgerSheetLoading}
|
||||||
|
data={[
|
||||||
|
... (generalLedgerSheet) ?
|
||||||
|
generalLedgerSheet.tableRows : [],
|
||||||
|
]}
|
||||||
|
onFetchData={handleFetchData} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
DashboardConnect,
|
||||||
|
AccountsConnect,
|
||||||
|
GeneralLedgerConnect,
|
||||||
|
)(GeneralLedger);
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarHeading,
|
||||||
|
NavbarDivider,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
export default function GeneralLedgerActionsBar({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const filterDropdown = FilterDropdown({
|
||||||
|
fields: [],
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon='cog' />}
|
||||||
|
text='Customize Report'
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
content={filterDropdown}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text="Filter"
|
||||||
|
icon={ <Icon icon="filter" /> } />
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Print'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Export'
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React, {useState, useMemo, useEffect, useCallback} from 'react';
|
||||||
|
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
Classes,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import {Row, Col} from 'react-grid-system';
|
||||||
|
import {
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
import AccountsConnect from 'connectors/Accounts.connector'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import AccountsMultiSelect from 'components/AccountsMultiSelect';
|
||||||
|
import {useFormik} from 'formik';
|
||||||
|
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
|
||||||
|
|
||||||
|
function GeneralLedgerHeader({
|
||||||
|
onSubmitFilter,
|
||||||
|
pageFilter,
|
||||||
|
accounts,
|
||||||
|
}) {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
...pageFilter,
|
||||||
|
from_date: moment(pageFilter.from_date).toDate(),
|
||||||
|
to_date: moment(pageFilter.to_date).toDate()
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
from_date: Yup.date().required(),
|
||||||
|
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
||||||
|
}),
|
||||||
|
onSubmit(values, actions) {
|
||||||
|
onSubmitFilter(values);
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle submit filter submit button.
|
||||||
|
const handleSubmitClick = useCallback(() => {
|
||||||
|
formik.submitForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onAccountSelected = useCallback((selectedAccounts) => {
|
||||||
|
console.log(selectedAccounts);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAccountingBasisChange = useCallback((value) => {
|
||||||
|
formik.setFieldValue('basis', value);
|
||||||
|
}, [formik]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FinancialStatementHeader>
|
||||||
|
<FinancialStatementDateRange formik={formik} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col sm={3}>
|
||||||
|
<FormGroup
|
||||||
|
label={'Specific Accounts'}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<AccountsMultiSelect
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={onAccountSelected} />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
<Col sm={3}>
|
||||||
|
<RadiosAccountingBasis
|
||||||
|
onChange={handleAccountingBasisChange}
|
||||||
|
selectedValue={formik.values.basis} />
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col sm={3}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmitClick}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
className={'button--submit-filter'}>
|
||||||
|
{ 'Calculate Report' }
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</FinancialStatementHeader>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
AccountsConnect
|
||||||
|
)(GeneralLedgerHeader);
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import React, {useEffect, useState, useCallback, useMemo} from 'react';
|
||||||
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
|
import DataTable from 'components/DataTable';
|
||||||
|
import Money from 'components/Money';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const ROW_TYPE = {
|
||||||
|
CLOSING_BALANCE: 'closing_balance',
|
||||||
|
OPENING_BALANCE: 'opening_balance',
|
||||||
|
ACCOUNT: 'account_name',
|
||||||
|
TRANSACTION: 'transaction',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GeneralLedgerTable({
|
||||||
|
onFetchData,
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
}) {
|
||||||
|
// Account name column accessor.
|
||||||
|
const accountNameAccessor = useCallback((row) => {
|
||||||
|
switch(row.rowType) {
|
||||||
|
case ROW_TYPE.OPENING_BALANCE:
|
||||||
|
return 'Opening Balance';
|
||||||
|
case ROW_TYPE.CLOSING_BALANCE:
|
||||||
|
return 'Closing Balance';
|
||||||
|
default:
|
||||||
|
return row.name;
|
||||||
|
}
|
||||||
|
}, [ROW_TYPE]);
|
||||||
|
|
||||||
|
// Date accessor.
|
||||||
|
const dateAccessor = useCallback((row) => {
|
||||||
|
const TYPES = [
|
||||||
|
ROW_TYPE.OPENING_BALANCE,
|
||||||
|
ROW_TYPE.CLOSING_BALANCE,
|
||||||
|
ROW_TYPE.TRANSACTION];
|
||||||
|
|
||||||
|
return (TYPES.indexOf(row.rowType) !== -1)
|
||||||
|
? moment(row.date).format('DD-MM-YYYY') : '';
|
||||||
|
}, [moment, ROW_TYPE]);
|
||||||
|
|
||||||
|
// Amount cell
|
||||||
|
const amountCell = useCallback(({ cell }) => {
|
||||||
|
const transaction = cell.row.original
|
||||||
|
|
||||||
|
if (transaction.rowType === ROW_TYPE.ACCOUNT) {
|
||||||
|
return (!cell.row.isExpanded) ?
|
||||||
|
(<Money amount={transaction.closing.amount} currency={"USD"} />) : '';
|
||||||
|
}
|
||||||
|
return (<Money amount={transaction.amount} currency={"USD"} />);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const referenceLink = useCallback((row) => {
|
||||||
|
return (<a href="">{ row.referenceId }</a>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
// Build our expander column
|
||||||
|
id: 'expander', // Make sure it has an ID
|
||||||
|
className: 'expander',
|
||||||
|
Header: ({
|
||||||
|
getToggleAllRowsExpandedProps,
|
||||||
|
isAllRowsExpanded
|
||||||
|
}) => (
|
||||||
|
<span {...getToggleAllRowsExpandedProps()} className="toggle">
|
||||||
|
{isAllRowsExpanded ?
|
||||||
|
(<span class="arrow-down" />) :
|
||||||
|
(<span class="arrow-right" />)
|
||||||
|
}
|
||||||
|
</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`,
|
||||||
|
},
|
||||||
|
className: 'toggle',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{row.isExpanded ?
|
||||||
|
(<span class="arrow-down" />) :
|
||||||
|
(<span class="arrow-right" />)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
) : null,
|
||||||
|
width: 20,
|
||||||
|
disableResizing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Account Name',
|
||||||
|
accessor: accountNameAccessor,
|
||||||
|
className: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Date',
|
||||||
|
accessor: dateAccessor,
|
||||||
|
className: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Transaction Type',
|
||||||
|
accessor: 'referenceType',
|
||||||
|
className: 'transaction_type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Trans. NUM',
|
||||||
|
accessor: referenceLink,
|
||||||
|
className: 'transaction_number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Description',
|
||||||
|
accessor: 'note',
|
||||||
|
className: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Amount',
|
||||||
|
Cell: amountCell,
|
||||||
|
className: 'amount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Balance',
|
||||||
|
Cell: amountCell,
|
||||||
|
className: 'balance',
|
||||||
|
},
|
||||||
|
], []);
|
||||||
|
|
||||||
|
const handleFetchData = useCallback(() => {
|
||||||
|
onFetchData && onFetchData();
|
||||||
|
}, [onFetchData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FinancialSheet
|
||||||
|
companyTitle={'Facebook, Incopration'}
|
||||||
|
sheetType={'General Ledger Sheet'}
|
||||||
|
date={new Date()}
|
||||||
|
name="general-ledger"
|
||||||
|
loading={loading}>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
className="bigcapital-datatable--financial-report"
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
onFetchData={handleFetchData} />
|
||||||
|
</FinancialSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useState, useEffect, useMemo} from 'react';
|
import React, {useState, useCallback, useEffect, useMemo} from 'react';
|
||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
import JournalConnect from 'connectors/Journal.connect';
|
import JournalConnect from 'connectors/Journal.connect';
|
||||||
@@ -8,61 +8,91 @@ import {useIntl} from 'react-intl';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import JournalTable from './JournalTable';
|
import JournalTable from './JournalTable';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
|
import JournalActionsBar from './JournalActionsBar';
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
function Journal({
|
function Journal({
|
||||||
fetchJournalSheet,
|
fetchJournalSheet,
|
||||||
getJournalSheet,
|
getJournalSheet,
|
||||||
getJournalSheetIndex,
|
getJournalSheetIndex,
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
journalSheetLoading,
|
||||||
}) {
|
}) {
|
||||||
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: 'accural'
|
||||||
const [reload, setReload] = useState(false);
|
|
||||||
|
|
||||||
const fetchHook = useAsync(async () => {
|
|
||||||
await Promise.all([
|
|
||||||
fetchJournalSheet(filter),
|
|
||||||
]);
|
|
||||||
setReload(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Journal');
|
changePageTitle('Journal Sheet');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchHook = useAsync((query = filter) => {
|
||||||
if (reload) {
|
return Promise.all([
|
||||||
fetchHook.execute();
|
fetchJournalSheet(query),
|
||||||
}
|
]);
|
||||||
}, [reload, fetchHook]);
|
}, false);
|
||||||
|
|
||||||
const journalSheetIndex = useMemo(() => {
|
// Retrieve journal sheet index by the given filter query.
|
||||||
return getJournalSheetIndex(filter);
|
const journalSheetIndex = useMemo(() =>
|
||||||
}, [filter, getJournalSheetIndex]);
|
getJournalSheetIndex(filter),
|
||||||
|
[getJournalSheetIndex, filter]);
|
||||||
|
|
||||||
const handleFilterSubmit = (filter) => {
|
// Retrieve journal sheet by the given sheet index.
|
||||||
setFilter({
|
const journalSheet = useMemo(() =>
|
||||||
|
getJournalSheet(journalSheetIndex),
|
||||||
|
[getJournalSheet, journalSheetIndex]);
|
||||||
|
|
||||||
|
// Handle financial statement filter change.
|
||||||
|
const handleFilterSubmit = useCallback((filter) => {
|
||||||
|
const _filter = {
|
||||||
...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(_filter);
|
||||||
|
fetchHook.execute(_filter);
|
||||||
|
}, [fetchHook]);
|
||||||
|
|
||||||
|
const handlePrintClick = useCallback(() => {
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleExportClick = useCallback(() => {
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFetchData = useCallback(({ sortBy, pageIndex, pageSize }) => {
|
||||||
|
fetchHook.execute();
|
||||||
|
}, [fetchHook]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="financial-statement">
|
<DashboardInsider>
|
||||||
|
<JournalActionsBar
|
||||||
|
onFilterChanged={() => {}}
|
||||||
|
onPrintClick={handlePrintClick}
|
||||||
|
onExportClick={handleExportClick} />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
|
<div class="financial-statement financial-statement--journal">
|
||||||
<JournalHeader
|
<JournalHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
onSubmitFilter={handleFilterSubmit} />
|
onSubmitFilter={handleFilterSubmit} />
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__table">
|
||||||
<LoadingIndicator loading={fetchHook.pending}>
|
|
||||||
<JournalTable
|
<JournalTable
|
||||||
journalIndex={journalSheetIndex} />
|
data={[
|
||||||
</LoadingIndicator>
|
...(journalSheet && journalSheet.tableRows)
|
||||||
|
? journalSheet.tableRows : []
|
||||||
|
]}
|
||||||
|
loading={journalSheetLoading}
|
||||||
|
onFetchData={handleFetchData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</DashboardInsider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarHeading,
|
||||||
|
NavbarDivider,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
|
||||||
|
export default function JournalActionsBar({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const filterDropdown = FilterDropdown({
|
||||||
|
fields: [],
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon='cog' />}
|
||||||
|
text='Customize Report'
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
content={filterDropdown}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
|
text="Filter"
|
||||||
|
icon={ <Icon icon="filter" /> } />
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Print'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Export'
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,115 +1,53 @@
|
|||||||
import React, {useState, useMemo, useEffect} from 'react';
|
import React, {useCallback} from 'react';
|
||||||
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
|
||||||
import {Row, Col} from 'react-grid-system';
|
import {Row, Col} from 'react-grid-system';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormGroup,
|
|
||||||
Position,
|
|
||||||
HTMLSelect,
|
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import {DateInput} from '@blueprintjs/datetime';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {useFormik} from 'formik';
|
||||||
momentFormatter,
|
|
||||||
parseDateRangeQuery,
|
|
||||||
} from 'utils';
|
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
||||||
|
|
||||||
|
|
||||||
export default function JournalHeader({
|
export default function JournalHeader({
|
||||||
pageFilter,
|
pageFilter,
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const formik = useFormik({
|
||||||
const [filter, setFilter] = useState({
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
from_date: moment(pageFilter.from_date).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate()
|
to_date: moment(pageFilter.to_date).toDate()
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
from_date: Yup.date().required(),
|
||||||
|
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
||||||
|
}),
|
||||||
|
onSubmit: (values, actions) => {
|
||||||
|
onSubmitFilter(values);
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const setFilterByKey = (name, value) => {
|
const handleSubmitClick = useCallback(() => {
|
||||||
setFilter({ ...filter, [name]: value });
|
formik.submitForm();
|
||||||
};
|
}, [formik]);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (reportDateRange === 'custom') { return; }
|
|
||||||
const dateRange = parseDateRangeQuery(reportDateRange);
|
|
||||||
|
|
||||||
if (dateRange) {
|
|
||||||
setFilter((filter) => ({ ...filter, ...dateRange }));
|
|
||||||
}
|
|
||||||
}, [reportDateRange]);
|
|
||||||
|
|
||||||
const handleSubmitClick = () => { onSubmitFilter(filter); };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader>
|
<FinancialStatementHeader>
|
||||||
<Row>
|
<FinancialStatementDateRange formik={formik} />
|
||||||
<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>
|
<Row>
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmitClick}>
|
onClick={handleSubmitClick}
|
||||||
|
class={'button--submit-filter'}>
|
||||||
{ 'Run Report' }
|
{ 'Run Report' }
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useState, useEffect, useMemo} from 'react';
|
import React, {useState, useEffect, 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 {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
@@ -8,82 +8,87 @@ import {
|
|||||||
getFinancialSheet,
|
getFinancialSheet,
|
||||||
} from 'store/financialStatement/financialStatements.selectors';
|
} from 'store/financialStatement/financialStatements.selectors';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
import Money from 'components/Money';
|
||||||
|
|
||||||
function JournalSheetTable({
|
function JournalSheetTable({
|
||||||
journalIndex,
|
onFetchData,
|
||||||
journalTableData,
|
data,
|
||||||
|
loading,
|
||||||
}) {
|
}) {
|
||||||
|
const rowTypeFilter = (rowType, value, types) => {
|
||||||
|
return (types.indexOf(rowType) === -1) ? '' : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const exceptRowTypes = (rowType, value, types) => {
|
||||||
|
return (types.indexOf(rowType) !== -1) ? '' : value;
|
||||||
|
};
|
||||||
const columns = useMemo(() => [
|
const columns = useMemo(() => [
|
||||||
{
|
{
|
||||||
Header: 'Date',
|
Header: 'Date',
|
||||||
accessor: r => moment(r.date).format('YYYY/MM/DD'),
|
accessor: r => rowTypeFilter(r.rowType, moment(r.date).format('YYYY/MM/DD'), ['first_entry']),
|
||||||
className: 'date',
|
className: 'date',
|
||||||
},
|
width: 85,
|
||||||
{
|
|
||||||
Header: 'Account Name',
|
|
||||||
accessor: 'account.name',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Transaction Type',
|
Header: 'Transaction Type',
|
||||||
accessor: 'transaction_type',
|
accessor: r => rowTypeFilter(r.rowType, r.transaction_type, ['first_entry']),
|
||||||
className: "transaction_type",
|
className: "transaction_type",
|
||||||
|
width: 145,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Num.',
|
Header: 'Num.',
|
||||||
accessor: 'reference_id',
|
accessor: r => rowTypeFilter(r.rowType, r.reference_id, ['first_entry']),
|
||||||
className: 'reference_id',
|
className: 'reference_id',
|
||||||
|
width: 70,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Note',
|
Header: 'Description',
|
||||||
accessor: 'note',
|
accessor: 'note',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Header: 'Acc. Code',
|
||||||
|
accessor: 'account.code',
|
||||||
|
width: 120,
|
||||||
|
className: 'account_code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Account',
|
||||||
|
accessor: 'account.name',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Header: 'Credit',
|
Header: 'Credit',
|
||||||
accessor: 'credit',
|
accessor: r => exceptRowTypes(
|
||||||
|
r.rowType, (<Money amount={r.credit} currency={'USD'} />), ['space_entry']),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Debit',
|
Header: 'Debit',
|
||||||
accessor: 'debit',
|
accessor: r => exceptRowTypes(
|
||||||
|
r.rowType, (<Money amount={r.debit} currency={'USD'} />), ['space_entry']),
|
||||||
},
|
},
|
||||||
], []);
|
], []);
|
||||||
|
|
||||||
|
const handleFetchData = useCallback((...args) => {
|
||||||
|
onFetchData && onFetchData(...args)
|
||||||
|
}, [onFetchData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialSheet
|
<FinancialSheet
|
||||||
companyTitle={'Facebook, Incopration'}
|
companyTitle={'Facebook, Incopration'}
|
||||||
sheetType={'Balance Sheet'}
|
sheetType={'Journal Sheet'}
|
||||||
date={[]}>
|
date={new Date()}
|
||||||
|
name="journal"
|
||||||
|
loading={loading}>
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
|
className="bigcapital-datatable--financial-report"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={journalTableData} />
|
data={data}
|
||||||
|
onFetchData={handleFetchData}
|
||||||
|
noResults={"This report does not contain any data."} />
|
||||||
</FinancialSheet>
|
</FinancialSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
|
||||||
const journalTableData = [];
|
|
||||||
const journalSheet = getFinancialSheet(state.financialStatements.journalSheets, props.journalIndex);
|
|
||||||
|
|
||||||
if (journalSheet && journalSheet.journal) {
|
|
||||||
journalSheet.journal.forEach((journal) => {
|
|
||||||
journal.entries.forEach((entry, index) => {
|
|
||||||
journalTableData.push({ ...entry, index });
|
|
||||||
});
|
|
||||||
journalTableData.push({
|
|
||||||
credit: journal.credit,
|
|
||||||
debit: journal.debit,
|
|
||||||
total: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
journalSheet,
|
|
||||||
journalTableData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
connect(mapStateToProps),
|
|
||||||
JournalConnect,
|
JournalConnect,
|
||||||
)(JournalSheetTable);
|
)(JournalSheetTable);
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarHeading,
|
||||||
|
NavbarDivider,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
export default function ProfitLossActionsBar({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const filterDropdown = FilterDropdown({
|
||||||
|
fields: [],
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon='cog' />}
|
||||||
|
text='Customize Report'
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Print'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Export'
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,74 +1,81 @@
|
|||||||
import React, {useState, useEffect, useMemo} from 'react';
|
import React, {useState, useMemo, useCallback, useEffect} from 'react';
|
||||||
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
|
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
|
||||||
import ProfitLossSheetTable from './ProfitLossSheetTable';
|
import ProfitLossSheetTable from './ProfitLossSheetTable';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
import { useAsync } from 'react-use';
|
import useAsync from 'hooks/async';
|
||||||
import moment from 'moment';
|
|
||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
import ProfitLossSheetConnect from 'connectors/ProfitLossSheet.connect';
|
import ProfitLossSheetConnect from 'connectors/ProfitLossSheet.connect';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider'
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'
|
||||||
|
import ProfitLossActionsBar from './ProfitLossActionsBar';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
function ProfitLossSheet({
|
function ProfitLossSheet({
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
fetchProfitLossSheet,
|
fetchProfitLossSheet,
|
||||||
|
|
||||||
getProfitLossSheetIndex,
|
getProfitLossSheetIndex,
|
||||||
getProfitLossSheet,
|
getProfitLossSheet,
|
||||||
|
|
||||||
getProfitLossSheetAccounts,
|
|
||||||
getProfitLossSheetColumns,
|
|
||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = useState({});
|
const [filter, setFilter] = useState({
|
||||||
const [reload, setReload] = useState(false);
|
from_date: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
|
to_date: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
|
||||||
// Change page title of the dashboard.
|
// Change page title of the dashboard.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Trial Balance Sheet');
|
changePageTitle('Profit/Loss Sheet');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchHook = useAsync(async () => {
|
// Fetches profit/loss sheet.
|
||||||
await Promise.all([
|
const fetchHook = useAsync((query = filter) => {
|
||||||
fetchProfitLossSheet(filter),
|
return Promise.all([
|
||||||
|
fetchProfitLossSheet(query),
|
||||||
]);
|
]);
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
const handleFilterSubmit = (filter) => {
|
const profitLossSheetIndex = useMemo(() =>
|
||||||
setFilter({
|
getProfitLossSheetIndex(filter),
|
||||||
|
[getProfitLossSheetIndex, filter])
|
||||||
|
|
||||||
|
const profitLossSheet = useMemo(() =>
|
||||||
|
getProfitLossSheet(profitLossSheetIndex),
|
||||||
|
[getProfitLossSheet, profitLossSheetIndex]);
|
||||||
|
|
||||||
|
const handleSubmitFilter = useCallback((filter) => {
|
||||||
|
const _filter = {
|
||||||
...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(_filter);
|
||||||
}
|
fetchHook.execute(_filter);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const profitLossSheetIndex = useMemo(() => {
|
// Handle fetch data of profit/loss sheet table.
|
||||||
return getProfitLossSheetIndex(filter);
|
const handleFetchData = useCallback(() => {
|
||||||
}, [filter, getProfitLossSheetIndex]);
|
fetchHook.execute();
|
||||||
|
}, [fetchHook]);
|
||||||
const profitLossSheetAccounts = useMemo(() => {
|
|
||||||
return getProfitLossSheetAccounts(profitLossSheetIndex);
|
|
||||||
}, [profitLossSheetIndex, getProfitLossSheet]);
|
|
||||||
|
|
||||||
const profitLossSheetColumns = useMemo(() => {
|
|
||||||
return getProfitLossSheetColumns(profitLossSheetIndex);
|
|
||||||
}, [profitLossSheetIndex])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DashboardInsider>
|
||||||
|
<ProfitLossActionsBar />
|
||||||
|
|
||||||
<div class="financial-statement">
|
<div class="financial-statement">
|
||||||
<ProfitLossSheetHeader
|
<ProfitLossSheetHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
onSubmitFilter={handleFilterSubmit} />
|
onSubmitFilter={handleSubmitFilter} />
|
||||||
|
|
||||||
<div class="financial-statement__body">
|
<div class="financial-statement__body">
|
||||||
<LoadingIndicator loading={fetchHook.pending}>
|
<LoadingIndicator loading={false}>
|
||||||
|
|
||||||
<ProfitLossSheetTable
|
<ProfitLossSheetTable
|
||||||
accounts={profitLossSheetAccounts}
|
data={[]}
|
||||||
columns={profitLossSheetColumns} />
|
onFetchData={handleFetchData} />
|
||||||
</LoadingIndicator>
|
</LoadingIndicator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,227 +1,78 @@
|
|||||||
import React, {useState, useMemo, useEffect} from 'react';
|
import React, {useCallback} from 'react';
|
||||||
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
|
||||||
import {Row, Col} from 'react-grid-system';
|
import {Row, Col} from 'react-grid-system';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormGroup,
|
} from '@blueprintjs/core';
|
||||||
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';
|
import moment from 'moment';
|
||||||
|
import {useFormik} from 'formik';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader';
|
||||||
|
import SelectsListColumnsBy from '../SelectDisplayColumnsBy';
|
||||||
|
import RadiosAccountingBasis from '../RadiosAccountingBasis';
|
||||||
|
|
||||||
export default function ProfitLossSheetHeader({
|
|
||||||
onSubmitFilter,
|
export default function JournalHeader({
|
||||||
pageFilter,
|
pageFilter,
|
||||||
|
onSubmitFilter,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const displayColumnsByOptions = [
|
const formik = useFormik({
|
||||||
{key: 'total', name: 'Total'},
|
enableReinitialize: true,
|
||||||
{key: 'year', name: 'Year'},
|
initialValues: {
|
||||||
{key: 'month', name: 'Month'},
|
|
||||||
{key: 'week', name: 'Week'},
|
|
||||||
{key: 'day', name: 'Day'},
|
|
||||||
{key: 'quarter', name: 'Quarter'},
|
|
||||||
];
|
|
||||||
|
|
||||||
const [filter, setFilter] = useState({
|
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
from_date: moment(pageFilter.from_date).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate()
|
to_date: moment(pageFilter.to_date).toDate()
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
from_date: Yup.date().required(),
|
||||||
|
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
||||||
|
}),
|
||||||
|
onSubmit: (values, actions) => {
|
||||||
|
onSubmitFilter(values);
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
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.
|
// Handle item select of `display columns by` field.
|
||||||
const onItemSelectDisplayColumns = (item) => {
|
const handleItemSelectDisplayColumns = useCallback((item) => {
|
||||||
setFilterByKey('display_columns_by', item.key);
|
formik.setFieldValue('display_columns_type', item.type);
|
||||||
};
|
formik.setFieldValue('display_columns_by', item.by);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Handle any date change.
|
const handleSubmitClick = useCallback(() => {
|
||||||
const handleDateChange = (name) => (date) => {
|
formik.submitForm();
|
||||||
setReportDateRange('custom');
|
}, [formik]);
|
||||||
setFilterByKey(name, date);
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle submit filter submit button.
|
const handleAccountingBasisChange = useCallback((value) => {
|
||||||
const handleSubmitClick = () => {
|
formik.setFieldValue('basis', value);
|
||||||
onSubmitFilter(filter);
|
}, [formik]);
|
||||||
};
|
|
||||||
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 (
|
return (
|
||||||
<FinancialStatementHeader>
|
<FinancialStatementHeader>
|
||||||
<Row>
|
<FinancialStatementDateRange formik={formik} />
|
||||||
<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>
|
<Row>
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<FormGroup
|
<SelectsListColumnsBy onItemSelect={handleItemSelectDisplayColumns} />
|
||||||
label={'Display report columns'}
|
</Col>
|
||||||
className="{'form-group-display-columns-by'}"
|
|
||||||
inline={false}>
|
|
||||||
|
|
||||||
<Select
|
<Col sm={3}>
|
||||||
items={displayColumnsByOptions}
|
<RadiosAccountingBasis
|
||||||
noResults={<MenuItem disabled={true} text="No results." />}
|
selectedValue={formik.values.basis}
|
||||||
filterable={false}
|
onChange={handleAccountingBasisChange} />
|
||||||
itemRenderer={accountTypeItem}
|
</Col>
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
onItemSelect={onItemSelectDisplayColumns}>
|
<Col sm={3}>
|
||||||
<Button
|
<Button
|
||||||
rightIcon="caret-down"
|
type="submit"
|
||||||
fill={true}
|
onClick={handleSubmitClick}
|
||||||
text={selectedDisplayColumnOpt ? selectedDisplayColumnOpt.name : 'Select'} />
|
className={'button--submit-filter'}>
|
||||||
</Select>
|
{ 'Run Report' }
|
||||||
</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>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</FinancialStatementHeader>
|
</FinancialStatementHeader>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React, {useState, useMemo, useCallback} from 'react';
|
||||||
|
import FinancialSheet from 'components/FinancialSheet';
|
||||||
|
import DataTable from 'components/DataTable';
|
||||||
|
import Money from 'components/Money';
|
||||||
|
|
||||||
|
|
||||||
|
export default function ProfitLossSheetTable({
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
onFetchData,
|
||||||
|
}) {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
Header: 'Account Name',
|
||||||
|
accessor: 'name',
|
||||||
|
className: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Acc. Code',
|
||||||
|
accessor: 'code',
|
||||||
|
className: "account_code",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleFetchData = useCallback((...args) => {
|
||||||
|
onFetchData && onFetchData(...args);
|
||||||
|
}, [onFetchData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FinancialSheet
|
||||||
|
companyTitle={'Facebook, Incopration'}
|
||||||
|
sheetType={'Profit/Loss Sheet'}
|
||||||
|
date={new Date()}
|
||||||
|
name="profit-loss-sheet"
|
||||||
|
loading={loading}>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
className="bigcapital-datatable--financial-report"
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
onFetchData={handleFetchData} />
|
||||||
|
</FinancialSheet>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {handleStringChange} from 'utils';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {
|
||||||
|
RadioGroup,
|
||||||
|
Radio,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
|
|
||||||
|
|
||||||
|
export default function RadiosAccountingBasis(props) {
|
||||||
|
const { onChange, ...rest } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
inline={true}
|
||||||
|
label={intl.formatMessage({'id': 'accounting_basis'})}
|
||||||
|
name="basis"
|
||||||
|
onChange={handleStringChange((value) => {
|
||||||
|
onChange && onChange(value);
|
||||||
|
})}
|
||||||
|
{...rest}>
|
||||||
|
<Radio label="Cash" value="cash" />
|
||||||
|
<Radio label="Accural" value="accural" />
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import React, {useMemo, useCallback} from 'react';
|
||||||
|
import SelectList from 'components/SelectList';
|
||||||
|
import {
|
||||||
|
FormGroup,
|
||||||
|
MenuItem,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export default function SelectsListColumnsBy(props) {
|
||||||
|
const { formGroupProps, selectListProps } = props;
|
||||||
|
|
||||||
|
const displayColumnsByOptions = useMemo(() => [
|
||||||
|
{key: 'total', name: 'Total', type: 'total', by: '', },
|
||||||
|
{key: 'year', name: 'Year', type: 'date', by: 'year'},
|
||||||
|
{key: 'month', name: 'Month', type: 'date', by: 'month'},
|
||||||
|
{key: 'week', name: 'Week', type: 'date', by: 'month'},
|
||||||
|
{key: 'day', name: 'Day', type: 'date', by: 'day'},
|
||||||
|
{key: 'quarter', name: 'Quarter', type: 'date', by: 'quarter'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const itemRenderer = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
|
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup
|
||||||
|
label={'Display report columns'}
|
||||||
|
className="form-group-display-columns-by form-group--select-list bp3-fill"
|
||||||
|
inline={false}
|
||||||
|
{...formGroupProps}>
|
||||||
|
|
||||||
|
<SelectList
|
||||||
|
items={displayColumnsByOptions}
|
||||||
|
noResults={<MenuItem disabled={true} text="No results." />}
|
||||||
|
filterable={false}
|
||||||
|
itemRenderer={itemRenderer}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
buttonLabel={'Select...'}
|
||||||
|
{...selectListProps} />
|
||||||
|
</FormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
NavbarGroup,
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
NavbarHeading,
|
||||||
|
NavbarDivider,
|
||||||
|
Intent,
|
||||||
|
Popover,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
export default function GeneralLedgerActionsBar({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const filterDropdown = FilterDropdown({
|
||||||
|
fields: [],
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
|
icon={<Icon icon='cog' />}
|
||||||
|
text='Customize Report'
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Print'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='file-export' />}
|
||||||
|
text='Export'
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,10 @@ import moment from 'moment';
|
|||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
|
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
|
import TrialBalanceActionsBar from './TrialBalanceActionsBar';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
|
||||||
|
|
||||||
function TrialBalanceSheet({
|
function TrialBalanceSheet({
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
@@ -57,6 +61,10 @@ function TrialBalanceSheet({
|
|||||||
}, [setFilter, fetchHook]);
|
}, [setFilter, fetchHook]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<DashboardInsider>
|
||||||
|
<TrialBalanceActionsBar />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
<div class="financial-statement">
|
<div class="financial-statement">
|
||||||
<TrialBalanceSheetHeader
|
<TrialBalanceSheetHeader
|
||||||
pageFilter={filter}
|
pageFilter={filter}
|
||||||
@@ -70,6 +78,8 @@ function TrialBalanceSheet({
|
|||||||
loading={trialBalanceSheetLoading} />
|
loading={trialBalanceSheetLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</DashboardInsider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useState, useCallback} from 'react';
|
import React, {useState, useCallback, useMemo} 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 {
|
||||||
@@ -12,98 +12,50 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
Popover,
|
Popover,
|
||||||
} from "@blueprintjs/core";
|
} from "@blueprintjs/core";
|
||||||
import {DateInput} from '@blueprintjs/datetime';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {momentFormatter} from 'utils';
|
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange';
|
||||||
|
|
||||||
export default function TrialBalanceSheetHeader({
|
export default function TrialBalanceSheetHeader({
|
||||||
pageFilter,
|
pageFilter,
|
||||||
onSubmitFilter,
|
onSubmitFilter,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [filter, setFilter] = useState({
|
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
...pageFilter,
|
...pageFilter,
|
||||||
from_date: moment(pageFilter.from_date).toDate(),
|
from_date: moment(pageFilter.from_date).toDate(),
|
||||||
to_date: moment(pageFilter.to_date).toDate()
|
to_date: moment(pageFilter.to_date).toDate()
|
||||||
})
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
from_date: Yup.date().required(),
|
||||||
|
to_date: Yup.date().min(Yup.ref('from_date')).required(),
|
||||||
|
}),
|
||||||
|
onSubmit: (values, actions) => {
|
||||||
|
onSubmitFilter(values);
|
||||||
|
actions.setSubmitting(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const setFilterByKey = (name, value) => {
|
const handleSubmitClick = useCallback(() => {
|
||||||
setFilter({ ...filter, [name]: value });
|
formik.submitForm();
|
||||||
};
|
}, [formik]);
|
||||||
|
|
||||||
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 = useCallback(() => { onSubmitFilter(filter); }, [filter]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancialStatementHeader>
|
<FinancialStatementHeader>
|
||||||
<Row>
|
<FinancialStatementDateRange formik={formik} />
|
||||||
<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>
|
<Row>
|
||||||
<Col sm={3}>
|
<Col sm={3}>
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmitClick}>
|
onClick={handleSubmitClick}
|
||||||
|
disabled={formik.isSubmitting}>
|
||||||
{ 'Run Report' }
|
{ 'Run Report' }
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default [
|
|||||||
name: 'dashboard.accounting.general.ledger',
|
name: 'dashboard.accounting.general.ledger',
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
loader: () =>
|
loader: () =>
|
||||||
import('containers/Dashboard/FinancialStatements/LedgerSheet')
|
import('containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -90,5 +90,9 @@ export default {
|
|||||||
"info-circle": {
|
"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'],
|
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'
|
viewBox: '0 0 512 512'
|
||||||
}
|
},
|
||||||
|
"cog": {
|
||||||
|
path: ['M452.515 237l31.843-18.382c9.426-5.441 13.996-16.542 11.177-27.054-11.404-42.531-33.842-80.547-64.058-110.797-7.68-7.688-19.575-9.246-28.985-3.811l-31.785 18.358a196.276 196.276 0 0 0-32.899-19.02V39.541a24.016 24.016 0 0 0-17.842-23.206c-41.761-11.107-86.117-11.121-127.93-.001-10.519 2.798-17.844 12.321-17.844 23.206v36.753a196.276 196.276 0 0 0-32.899 19.02l-31.785-18.358c-9.41-5.435-21.305-3.877-28.985 3.811-30.216 30.25-52.654 68.265-64.058 110.797-2.819 10.512 1.751 21.613 11.177 27.054L59.485 237a197.715 197.715 0 0 0 0 37.999l-31.843 18.382c-9.426 5.441-13.996 16.542-11.177 27.054 11.404 42.531 33.842 80.547 64.058 110.797 7.68 7.688 19.575 9.246 28.985 3.811l31.785-18.358a196.202 196.202 0 0 0 32.899 19.019v36.753a24.016 24.016 0 0 0 17.842 23.206c41.761 11.107 86.117 11.122 127.93.001 10.519-2.798 17.844-12.321 17.844-23.206v-36.753a196.34 196.34 0 0 0 32.899-19.019l31.785 18.358c9.41 5.435 21.305 3.877 28.985-3.811 30.216-30.25 52.654-68.266 64.058-110.797 2.819-10.512-1.751-21.613-11.177-27.054L452.515 275c1.22-12.65 1.22-25.35 0-38zm-52.679 63.019l43.819 25.289a200.138 200.138 0 0 1-33.849 58.528l-43.829-25.309c-31.984 27.397-36.659 30.077-76.168 44.029v50.599a200.917 200.917 0 0 1-67.618 0v-50.599c-39.504-13.95-44.196-16.642-76.168-44.029l-43.829 25.309a200.15 200.15 0 0 1-33.849-58.528l43.819-25.289c-7.63-41.299-7.634-46.719 0-88.038l-43.819-25.289c7.85-21.229 19.31-41.049 33.849-58.529l43.829 25.309c31.984-27.397 36.66-30.078 76.168-44.029V58.845a200.917 200.917 0 0 1 67.618 0v50.599c39.504 13.95 44.196 16.642 76.168 44.029l43.829-25.309a200.143 200.143 0 0 1 33.849 58.529l-43.819 25.289c7.631 41.3 7.634 46.718 0 88.037zM256 160c-52.935 0-96 43.065-96 96s43.065 96 96 96 96-43.065 96-96-43.065-96-96-96zm0 144c-26.468 0-48-21.532-48-48 0-26.467 21.532-48 48-48s48 21.533 48 48c0 26.468-21.532 48-48 48'],
|
||||||
|
viewBox: '0 0 512 512',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
@@ -48,10 +48,20 @@ export const fetchTrialBalanceSheet = ({ query }) => {
|
|||||||
|
|
||||||
export const fetchProfitLossSheet = ({ query }) => {
|
export const fetchProfitLossSheet = ({ query }) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
ApiService.get('/financial_statements/profit_loss_sheet', { params: query }).then((response) => {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.PROFIT_LOSS_STATEMENT_SET,
|
type: t.PROFIT_LOSS_SHEET_LOADING,
|
||||||
data: response.data,
|
loading: false,
|
||||||
|
});
|
||||||
|
ApiService.get('/financial_statements/profit_loss_sheet').then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.PROFIT_LOSS_SHEET_SET,
|
||||||
|
profitLoss: response.data.profitLoss,
|
||||||
|
columns: response.data.columns,
|
||||||
|
query: response.data.query,
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.PROFIT_LOSS_SHEET_LOADING,
|
||||||
|
loading: false,
|
||||||
});
|
});
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
}).catch((error) => { reject(error); });
|
}).catch((error) => { reject(error); });
|
||||||
@@ -60,12 +70,20 @@ export const fetchProfitLossSheet = ({ query }) => {
|
|||||||
|
|
||||||
export const fetchJournalSheet = ({ query }) => {
|
export const fetchJournalSheet = ({ query }) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.JOURNAL_SHEET_LOADING,
|
||||||
|
loading: true,
|
||||||
|
});
|
||||||
ApiService.get('/financial_statements/journal', { params: query }).then((response) => {
|
ApiService.get('/financial_statements/journal', { params: query }).then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.JOURNAL_SHEET_SET,
|
type: t.JOURNAL_SHEET_SET,
|
||||||
data: response.data,
|
data: response.data,
|
||||||
query: response.data.query,
|
query: response.data.query,
|
||||||
});
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.JOURNAL_SHEET_LOADING,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
}).catch(error => { reject(error); });
|
}).catch(error => { reject(error); });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getFinancialSheetIndexByQuery,
|
getFinancialSheetIndexByQuery,
|
||||||
// getFinancialSheetIndexByQuery,
|
// getFinancialSheetIndexByQuery,
|
||||||
} from './financialStatements.selectors';
|
} from './financialStatements.selectors';
|
||||||
|
import {omit} from 'lodash';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
balanceSheets: [],
|
balanceSheets: [],
|
||||||
@@ -12,10 +13,65 @@ const initialState = {
|
|||||||
sheets: [],
|
sheets: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
generalLedger: [],
|
generalLedger: {
|
||||||
journalSheets: [],
|
sheets: [],
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
journal: {
|
||||||
|
sheets: [],
|
||||||
|
loading: false,
|
||||||
|
tableRows: [],
|
||||||
|
},
|
||||||
|
profitLoss: {
|
||||||
|
sheets: [],
|
||||||
|
loading: false,
|
||||||
|
tableRows: [],
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapGeneralLedgerAccountsToRows = (accounts) => {
|
||||||
|
return accounts.reduce((tableRows, account) => {
|
||||||
|
const children = [];
|
||||||
|
children.push({
|
||||||
|
...account.opening, rowType: 'opening_balance',
|
||||||
|
});
|
||||||
|
account.transactions.map((transaction) => {
|
||||||
|
children.push({
|
||||||
|
...transaction, ...omit(account, ['transactions']),
|
||||||
|
rowType: 'transaction'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
children.push({
|
||||||
|
...account.closing, rowType: 'closing_balance',
|
||||||
|
});
|
||||||
|
tableRows.push({
|
||||||
|
...omit(account, ['transactions']), children,
|
||||||
|
rowType: 'account_name',
|
||||||
|
});
|
||||||
|
return tableRows;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapJournalTableRows = (journal) => {
|
||||||
|
return journal.reduce((rows, journal) => {
|
||||||
|
journal.entries.forEach((entry, index) => {
|
||||||
|
rows.push({
|
||||||
|
...entry,
|
||||||
|
rowType: (index === 0) ? 'first_entry' : 'entry'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
rows.push({
|
||||||
|
credit: journal.credit,
|
||||||
|
debit: journal.debit,
|
||||||
|
rowType: 'entries_total',
|
||||||
|
});
|
||||||
|
rows.push({
|
||||||
|
rowType: 'space_entry',
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export default createReducer(initialState, {
|
export default createReducer(initialState, {
|
||||||
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
|
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
|
||||||
const index = getBalanceSheetIndexByQuery(state.balanceSheets, action.query);
|
const index = getBalanceSheetIndexByQuery(state.balanceSheets, action.query);
|
||||||
@@ -50,17 +106,55 @@ export default createReducer(initialState, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[t.JOURNAL_SHEET_SET]: (state, action) => {
|
[t.JOURNAL_SHEET_SET]: (state, action) => {
|
||||||
const index = getFinancialSheetIndexByQuery(state.journalSheets, action.query);
|
const index = getFinancialSheetIndexByQuery(state.journal.sheets, action.query);
|
||||||
console.log(index, 'INDEX');
|
|
||||||
|
|
||||||
const journal = {
|
const journal = {
|
||||||
query: action.data.query,
|
query: action.data.query,
|
||||||
journal: action.data.journal,
|
journal: action.data.journal,
|
||||||
|
tableRows: mapJournalTableRows(action.data.journal),
|
||||||
};
|
};
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.journalSheets[index] = journal;
|
state.journal.sheets[index] = journal;
|
||||||
} else {
|
} else {
|
||||||
state.journalSheets.push(journal);
|
state.journal.sheets.push(journal);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.JOURNAL_SHEET_LOADING]: (state, action) => {
|
||||||
|
state.journal.loading = !!action.loading;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.GENERAL_LEDGER_STATEMENT_SET]: (state, action) => {
|
||||||
|
const index = getFinancialSheetIndexByQuery(state.generalLedger.sheets, action.query);
|
||||||
|
|
||||||
|
const generalLedger = {
|
||||||
|
query: action.data.query,
|
||||||
|
accounts: action.data.accounts,
|
||||||
|
tableRows: mapGeneralLedgerAccountsToRows(action.data.accounts),
|
||||||
|
};
|
||||||
|
if (index !== -1) {
|
||||||
|
state.generalLedger.sheets[index] = generalLedger;
|
||||||
|
} else {
|
||||||
|
state.generalLedger.sheets.push(generalLedger);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.PROFIT_LOSS_SHEET_SET]: (state, action) => {
|
||||||
|
const index = getFinancialSheetIndexByQuery(state.profitLoss.sheets, action.query);
|
||||||
|
|
||||||
|
const profitLossSheet = {
|
||||||
|
query: action.query,
|
||||||
|
profitLoss: action.profitLoss,
|
||||||
|
columns: action.columns,
|
||||||
|
};
|
||||||
|
if (index !== -1) {
|
||||||
|
state.profitLoss.sheets[index] = profitLossSheet;
|
||||||
|
} else {
|
||||||
|
state.profitLoss.sheets.push(profitLossSheet);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.PROFIT_LOSS_SHEET_LOADING]: (state, action) => {
|
||||||
|
state.profitLoss.loading = !!action.loading;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
@@ -6,4 +6,8 @@ export default {
|
|||||||
TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET',
|
TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET',
|
||||||
TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING',
|
TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING',
|
||||||
JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET',
|
JOURNAL_SHEET_SET: 'JOURNAL_SHEET_SET',
|
||||||
|
JOURNAL_SHEET_LOADING: 'JOURNAL_SHEET_LOADING',
|
||||||
|
|
||||||
|
PROFIT_LOSS_SHEET_SET: 'PROFIT_LOSS_SHEET_SET',
|
||||||
|
PROFIT_LOSS_SHEET_LOADING: 'PROFIT_LOSS_SHEET_LOADING',
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import resources from './resources/resource.types';
|
|||||||
import users from './users/users.types';
|
import users from './users/users.types';
|
||||||
import financialStatements from './financialStatement/financialStatements.types';
|
import financialStatements from './financialStatement/financialStatements.types';
|
||||||
import itemCategories from './itemCategories/itemsCategory.type';
|
import itemCategories from './itemCategories/itemsCategory.type';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...authentication,
|
...authentication,
|
||||||
...accounts,
|
...accounts,
|
||||||
|
|||||||
@@ -138,6 +138,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-results{
|
||||||
|
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.td{
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--financial-report{
|
&--financial-report{
|
||||||
|
|
||||||
@@ -147,11 +157,10 @@
|
|||||||
border-top: 1px solid #666;
|
border-top: 1px solid #666;
|
||||||
border-bottom: 1px solid #666;
|
border-bottom: 1px solid #666;
|
||||||
|
|
||||||
padding: 10px 1.5rem;
|
padding: 10px 0.4rem;
|
||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
.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{
|
.form-group--select-list.bp3-fill{
|
||||||
|
|
||||||
.bp3-popover-wrapper{
|
.bp3-popover-wrapper{
|
||||||
@@ -41,9 +25,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-button{
|
.bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal){
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
|
background-color: #E6EFFB;
|
||||||
|
color: #555555;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
|
||||||
|
.form-group--select-list &{
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:hover{
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-button{
|
||||||
|
|
||||||
&.bp3-intent-primary,
|
&.bp3-intent-primary,
|
||||||
&.bp3-intent-success,
|
&.bp3-intent-success,
|
||||||
@@ -56,3 +57,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--secondary{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -37,11 +37,15 @@ label{
|
|||||||
&.#{$ns}-intent-danger{
|
&.#{$ns}-intent-danger{
|
||||||
select{
|
select{
|
||||||
box-shadow: 0 0 0 0 rgba(219, 55, 55, 0),
|
box-shadow: 0 0 0 0 rgba(219, 55, 55, 0),
|
||||||
0 0 0 0 rgba(219, 55, 55, 0),
|
0 0 0 0 rgba(2, 2, 2, 0),
|
||||||
inset 0 0 0 1px #db3737,
|
inset 0 0 0 1px #db3737,
|
||||||
inset 0 0 0 1px rgba(16, 22, 26, 0.15),
|
inset 0 0 0 1px rgba(16, 22, 26, 0.15),
|
||||||
inset 0 1px 1px rgba(16, 22, 26, 0.2);
|
inset 0 1px 1px rgba(16, 22, 26, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-input{
|
||||||
|
border-color: #db3737;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-label{
|
.#{$ns}-label{
|
||||||
|
|||||||
@@ -24,11 +24,9 @@
|
|||||||
border: 1px solid #E2E2E2;
|
border: 1px solid #E2E2E2;
|
||||||
min-width: 640px;
|
min-width: 640px;
|
||||||
width: 0;
|
width: 0;
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-top: 30px;
|
padding-top: 30px;
|
||||||
margin-top: 35px;
|
margin: 35px auto;
|
||||||
|
|
||||||
&__title{
|
&__title{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -67,4 +65,35 @@
|
|||||||
min-width: 720px;
|
min-width: 720px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--general-ledger,
|
||||||
|
&--journal{
|
||||||
|
width: auto;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
border-color: #EEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--journal{
|
||||||
|
|
||||||
|
.financial-sheet__table{
|
||||||
|
|
||||||
|
.tbody{
|
||||||
|
.tr .td{
|
||||||
|
padding: 0.4rem;
|
||||||
|
color: #444;
|
||||||
|
border-bottom-color: #F0F0F0;
|
||||||
|
min-height: 32px;
|
||||||
|
border-left: 1px solid #F0F0F0;
|
||||||
|
|
||||||
|
&:first-of-type{
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
&.account_code{
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ export default {
|
|||||||
const formatNumber = formatNumberClosure(filter.number_format);
|
const formatNumber = formatNumberClosure(filter.number_format);
|
||||||
|
|
||||||
const journalGrouped = groupBy(accountsJournalEntries, (entry) => {
|
const journalGrouped = groupBy(accountsJournalEntries, (entry) => {
|
||||||
return `${entry.id}-${entry.referenceType}`;
|
return `${entry.referenceId}-${entry.referenceType}`;
|
||||||
});
|
});
|
||||||
const journal = Object.keys(journalGrouped).map((key) => {
|
const journal = Object.keys(journalGrouped).map((key) => {
|
||||||
const transactionsGroup = journalGrouped[key];
|
const transactionsGroup = journalGrouped[key];
|
||||||
@@ -251,11 +251,11 @@ export default {
|
|||||||
],
|
],
|
||||||
opening: {
|
opening: {
|
||||||
date: filter.from_date,
|
date: filter.from_date,
|
||||||
balance: opeingBalanceCollection.getClosingBalance(account.id),
|
amount: opeingBalanceCollection.getClosingBalance(account.id),
|
||||||
},
|
},
|
||||||
closing: {
|
closing: {
|
||||||
date: filter.to_date,
|
date: filter.to_date,
|
||||||
balance: closingBalanceCollection.getClosingBalance(account.id),
|
amount: closingBalanceCollection.getClosingBalance(account.id),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -666,9 +666,11 @@ export default {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
query: { ...filter },
|
query: { ...filter },
|
||||||
columns: [...dateRangeSet],
|
columns: [...dateRangeSet],
|
||||||
|
profitLoss: {
|
||||||
income: incomeResponse,
|
income: incomeResponse,
|
||||||
expenses: expenseResponse,
|
expenses: expenseResponse,
|
||||||
net_income: netIncomeResponse,
|
net_income: netIncomeResponse,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user