WIP Financial statements.

This commit is contained in:
Ahmed Bouhuolia
2020-04-03 01:17:17 +02:00
parent cf5f56ae32
commit 4227f2f9a8
40 changed files with 1750 additions and 761 deletions

View 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>
);
}

View File

@@ -34,7 +34,8 @@ export default function DataTable({
onSelectedRowsChange,
manualSortBy = 'false',
selectionColumn = false,
className
className,
noResults = 'This report does not contain any data.'
}) {
const {
getTableProps,
@@ -144,7 +145,8 @@ export default function DataTable({
</div>
<div {...getTableBodyProps()} className="tbody">
{page.map((row, i) => {
prepareRow(row)
prepareRow(row);
return (
<div {...row.getRowProps()} className="tr">
{row.cells.map((cell) => {
@@ -154,6 +156,12 @@ export default function DataTable({
})}
</div>)
})}
{ (page.length === 0) && (
<div className={'tr no-results'}>
<div class="td">{ noResults }</div>
</div>
)}
</div>
</div>
</div>

View File

@@ -20,7 +20,7 @@ export default function FinancialSheet({
<LoadingIndicator loading={loading}>
<h1 class="financial-sheet__title">{ companyTitle }</h1>
<h6 class="financial-sheet__sheet-type">{ sheetType }</h6>
<div class="financial-sheet__date">As of { formattedDate }</div>
<div class="financial-sheet__date">From { formattedDate } | To { formattedDate }</div>
<div class="financial-sheet__table">
{ children }

View 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);
};
}

View 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>
)
}

View 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);

View File

@@ -8,8 +8,9 @@ import {
} from 'store/financialStatement/financialStatements.selectors';
export const mapStateToProps = (state, props) => ({
getJournalSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.journalSheets, query),
getJournalSheet: (index) => getFinancialSheet(state.financialStatements.journalSheets, index),
getJournalSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.journal.sheets, query),
getJournalSheet: (index) => getFinancialSheet(state.financialStatements.journal.sheets, index),
journalSheetLoading: state.financialStatements.journal.loading,
});
export const mapDispatchToProps = (dispatch) => ({

View File

@@ -3,17 +3,13 @@ import {
fetchProfitLossSheet,
} from 'store/financialStatement/financialStatements.actions';
import {
getProfitLossSheetIndex,
getProfitLossSheet,
getProfitLossSheetColumns,
getProfitLossSheetAccounts,
getFinancialSheetIndexByQuery,
getFinancialSheet,
} from 'store/financialStatement/financialStatements.selectors';
export const mapStateToProps = (state, props) => ({
getProfitLossSheetIndex: (query) => getProfitLossSheetIndex(state.financialStatements.profitLossSheets, query),
getProfitLossSheet: (index) => getProfitLossSheet(state.financialStatements.profitLossSheets, index),
getProfitLossSheetColumns: (index) => getProfitLossSheetColumns(state.financialStatements.profitLossSheets, index),
getProfitLossSheetAccounts: (index) => getProfitLossSheetAccounts(state.financialStatements.profitLossSheets, index),
getProfitLossSheetIndex: (query) => getFinancialSheetIndexByQuery(state.financialStatements.profitLoss.sheets, query),
getProfitLossSheet: (index) => getFinancialSheet(state.financialStatements.profitLoss.sheets, index),
});
export const mapDispatchToProps = (dispatch) => ({

View File

@@ -8,6 +8,9 @@ import BalanceSheetHeader from './BalanceSheetHeader';
import LoadingIndicator from 'components/LoadingIndicator';
import BalanceSheetTable from './BalanceSheetTable';
import moment from 'moment';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import BalanceSheetActionsBar from './BalanceSheetActionsBar';
function BalanceSheet({
fetchBalanceSheet,
@@ -22,20 +25,15 @@ function BalanceSheet({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
basis: 'cash',
display_columns_by: 'total',
display_columns_type: 'total',
display_columns_by: '',
none_zero: false,
});
const [reload, setReload] = useState(false);
const fetchHook = useAsync(async () => {
const fetchHook = useAsync(async (query = filter) => {
await Promise.all([
fetchBalanceSheet({
...filter,
display_columns_type: 'total',
}),
fetchBalanceSheet({ ...query }),
]);
setReload(false);
});
// Handle fetch the data of balance sheet.
@@ -57,30 +55,37 @@ function BalanceSheet({
// Handle re-fetch balance sheet after filter change.
const handleFilterSubmit = useCallback((filter) => {
setFilter({
const _filter = {
...filter,
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
});
setReload(true);
}, [setFilter]);
};
setFilter({ ..._filter });
fetchHook.execute(_filter);
}, [setFilter, fetchHook]);
return (
<div class="financial-statement">
<BalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<DashboardInsider>
<BalanceSheetActionsBar />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<BalanceSheetTable
balanceSheet={balanceSheet}
balanceSheetIndex={balanceSheetIndex}
onFetchData={handleFetchData}
asDate={new Date()} />
</LoadingIndicator>
</div>
</div>
<DashboardPageContent>
<div class="financial-statement">
<BalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<BalanceSheetTable
balanceSheet={balanceSheet}
balanceSheetIndex={balanceSheetIndex}
onFetchData={handleFetchData}
asDate={new Date()} />
</LoadingIndicator>
</div>
</div>
</DashboardPageContent>
</DashboardInsider>
);
}

View File

@@ -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>
)
}

View File

@@ -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 {Row, Col} from 'react-grid-system';
import {
Button,
FormGroup,
Position,
MenuItem,
RadioGroup,
Radio,
HTMLSelect,
Intent,
Popover,
Classes,
} from "@blueprintjs/core";
import {Select} from '@blueprintjs/select';
import {DateInput} from '@blueprintjs/datetime';
import SelectList from 'components/SelectList';
import {useIntl} from 'react-intl';
import {
momentFormatter,
handleStringChange,
parseDateRangeQuery,
} from 'utils';
import moment from 'moment';
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({
onSubmitFilter,
pageFilter,
}) {
const intl = useIntl();
const displayColumnsByOptions = [
{key: 'total', name: 'Total'},
{key: 'year', name: 'Year'},
{key: 'month', name: 'Month'},
{key: 'week', name: 'Week'},
{key: 'day', name: 'Day'},
{key: 'quarter', name: 'Quarter'},
];
const [filter, setFilter] = useState({
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate()
const formik = useFormik({
enableReinitialize: true,
initialValues: {
...pageFilter,
basis: 'cash',
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);
},
});
const setFilterByKey = (name, value) => {
setFilter({ ...filter, [name]: value });
};
const [reportDateRange, setReportDateRange] = useState('this_year');
useEffect(() => {
if (reportDateRange === 'custom') { return; }
const dateRange = parseDateRangeQuery(reportDateRange);
if (dateRange) {
setFilter((filter) => ({ ...filter, ...dateRange, }));
}
}, [reportDateRange])
const selectedDisplayColumnOpt = useMemo(() => {
return displayColumnsByOptions.find(o => o.key === filter.display_columns_by);
}, [filter.display_columns_by, displayColumnsByOptions]);
// Account type item of select filed.
const accountTypeItem = (item, { handleClick, modifiers, query }) => {
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
};
// Handle item select of `display columns by` field.
const onItemSelectDisplayColumns = (item) => {
setFilterByKey('display_columns_by', item.key);
};
// Handle any date change.
const handleDateChange = (name) => (date) => {
setReportDateRange('custom');
setFilterByKey(name, date);
};
const onItemSelectDisplayColumns = useCallback((item) => {
formik.setFieldValue('display_columns_type', item.type);
formik.setFieldValue('display_columns_by', item.by);
}, []);
// handle submit filter submit button.
const handleSubmitClick = () => {
onSubmitFilter(filter);
};
const dateRangeOptions = [
{value: 'today', label: 'Today', },
{value: 'this_week', label: 'This Week'},
{value: 'this_month', label: 'This Month'},
{value: 'this_quarter', label: 'This Quarter'},
{value: 'this_year', label: 'This Year'},
{value: 'custom', label: 'Custom Range'},
];
const handleSubmitClick = useCallback(() => {
formik.submitForm();
}, [formik]);
const [activeRowsColumns, setActiveRowsColumns] = useState(false);
const filterAccountsOptions = useMemo(() => [
{key: '', name: 'Accounts with Zero Balance'},
{key: 'all-trans', name: 'All Transactions' },
], []);
const onClickActiveRowsColumnsBtn = () => {
setActiveRowsColumns(!activeRowsColumns);
};
const filterAccountRenderer = useCallback((item, { handleClick, modifiers, query }) => {
return (<MenuItem text={item.name} key={item.id} onClick={handleClick} />);
}, []);
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>
);
const infoIcon = useMemo(() =>
(<Icon icon="info-circle" iconSize={12} />), []);
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
const handleAccountingBasisChange = useCallback((value) => {
formik.setFieldValue('basis', value);
}, [formik]);
return (
<FinancialStatementHeader>
<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={(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>
<FinancialStatementDateRange formik={formik} />
<Row>
<Col sm={3}>
<FormGroup
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>
<SelectDisplayColumnsBy onItemSelect={onItemSelectDisplayColumns} />
</Col>
<Col sm={3}>
<FormGroup
label={'Show non-zero or active only'}
label={'Filter Accounts'}
className="form-group--select-list bp3-fill"
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>
<SelectList
items={filterAccountsOptions}
itemRenderer={filterAccountRenderer}
onItemSelect={onItemSelectDisplayColumns}
popoverProps={{ minimal: true }}
filterable={false} />
</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>
<RadiosAccountingBasis
selectedValue={formik.values.basis}
onChange={handleAccountingBasisChange} />
</Col>
<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' }
</Button>
</Col>

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -1,4 +1,4 @@
import React, {useState, useEffect, useMemo} from 'react';
import React, {useState, useCallback, useEffect, useMemo} from 'react';
import {compose} from 'utils';
import LoadingIndicator from 'components/LoadingIndicator';
import JournalConnect from 'connectors/Journal.connect';
@@ -8,61 +8,91 @@ import {useIntl} from 'react-intl';
import moment from 'moment';
import JournalTable from './JournalTable';
import DashboardConnect from 'connectors/Dashboard.connector';
import JournalActionsBar from './JournalActionsBar';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
function Journal({
fetchJournalSheet,
getJournalSheet,
getJournalSheetIndex,
changePageTitle,
journalSheetLoading,
}) {
const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
});
const [reload, setReload] = useState(false);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchJournalSheet(filter),
]);
setReload(false);
basis: 'accural'
});
useEffect(() => {
changePageTitle('Journal');
changePageTitle('Journal Sheet');
}, []);
useEffect(() => {
if (reload) {
fetchHook.execute();
}
}, [reload, fetchHook]);
const fetchHook = useAsync((query = filter) => {
return Promise.all([
fetchJournalSheet(query),
]);
}, false);
const journalSheetIndex = useMemo(() => {
return getJournalSheetIndex(filter);
}, [filter, getJournalSheetIndex]);
// Retrieve journal sheet index by the given filter query.
const journalSheetIndex = useMemo(() =>
getJournalSheetIndex(filter),
[getJournalSheetIndex, filter]);
const handleFilterSubmit = (filter) => {
setFilter({
// Retrieve journal sheet by the given sheet index.
const journalSheet = useMemo(() =>
getJournalSheet(journalSheetIndex),
[getJournalSheet, journalSheetIndex]);
// Handle financial statement filter change.
const handleFilterSubmit = useCallback((filter) => {
const _filter = {
...filter,
from_date: moment(filter.from_date).format('YYYY-MM-DD'),
to_date: moment(filter.to_date).format('YYYY-MM-DD'),
});
setReload(true);
};
return (
<div class="financial-statement">
<JournalHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
};
setFilter(_filter);
fetchHook.execute(_filter);
}, [fetchHook]);
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<JournalTable
journalIndex={journalSheetIndex} />
</LoadingIndicator>
</div>
</div>
const handlePrintClick = useCallback(() => {
}, []);
const handleExportClick = useCallback(() => {
}, []);
const handleFetchData = useCallback(({ sortBy, pageIndex, pageSize }) => {
fetchHook.execute();
}, [fetchHook]);
return (
<DashboardInsider>
<JournalActionsBar
onFilterChanged={() => {}}
onPrintClick={handlePrintClick}
onExportClick={handleExportClick} />
<DashboardPageContent>
<div class="financial-statement financial-statement--journal">
<JournalHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__table">
<JournalTable
data={[
...(journalSheet && journalSheet.tableRows)
? journalSheet.tableRows : []
]}
loading={journalSheetLoading}
onFetchData={handleFetchData} />
</div>
</div>
</DashboardPageContent>
</DashboardInsider>
)
}

View File

@@ -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>
)
}

View File

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

View File

@@ -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 DataTable from 'components/DataTable';
import {compose} from 'utils';
@@ -8,82 +8,87 @@ import {
getFinancialSheet,
} from 'store/financialStatement/financialStatements.selectors';
import {connect} from 'react-redux';
import Money from 'components/Money';
function JournalSheetTable({
journalIndex,
journalTableData,
onFetchData,
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(() => [
{
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',
},
{
Header: 'Account Name',
accessor: 'account.name',
width: 85,
},
{
Header: 'Transaction Type',
accessor: 'transaction_type',
accessor: r => rowTypeFilter(r.rowType, r.transaction_type, ['first_entry']),
className: "transaction_type",
width: 145,
},
{
Header: 'Num.',
accessor: 'reference_id',
accessor: r => rowTypeFilter(r.rowType, r.reference_id, ['first_entry']),
className: 'reference_id',
width: 70,
},
{
Header: 'Note',
Header: 'Description',
accessor: 'note',
},
{
Header: 'Acc. Code',
accessor: 'account.code',
width: 120,
className: 'account_code',
},
{
Header: 'Account',
accessor: 'account.name',
},
{
Header: 'Credit',
accessor: 'credit',
accessor: r => exceptRowTypes(
r.rowType, (<Money amount={r.credit} currency={'USD'} />), ['space_entry']),
},
{
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 (
<FinancialSheet
companyTitle={'Facebook, Incopration'}
sheetType={'Balance Sheet'}
date={[]}>
sheetType={'Journal Sheet'}
date={new Date()}
name="journal"
loading={loading}>
<DataTable
className="bigcapital-datatable--financial-report"
columns={columns}
data={journalTableData} />
data={data}
onFetchData={handleFetchData}
noResults={"This report does not contain any data."} />
</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(
connect(mapStateToProps),
JournalConnect,
)(JournalSheetTable);

View File

@@ -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>
);
}

View File

@@ -1,74 +1,81 @@
import React, {useState, useEffect, useMemo} from 'react';
import React, {useState, useMemo, useCallback, useEffect} from 'react';
import ProfitLossSheetHeader from './ProfitLossSheetHeader';
import ProfitLossSheetTable from './ProfitLossSheetTable';
import LoadingIndicator from 'components/LoadingIndicator';
import { useAsync } from 'react-use';
import moment from 'moment';
import useAsync from 'hooks/async';
import {compose} from 'utils';
import DashboardConnect from 'connectors/Dashboard.connector';
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({
changePageTitle,
fetchProfitLossSheet,
getProfitLossSheetIndex,
getProfitLossSheet,
getProfitLossSheetAccounts,
getProfitLossSheetColumns,
}) {
const [filter, setFilter] = useState({});
const [reload, setReload] = useState(false);
const [filter, setFilter] = useState({
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
});
// Change page title of the dashboard.
useEffect(() => {
changePageTitle('Trial Balance Sheet');
changePageTitle('Profit/Loss Sheet');
}, []);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchProfitLossSheet(filter),
// Fetches profit/loss sheet.
const fetchHook = useAsync((query = filter) => {
return Promise.all([
fetchProfitLossSheet(query),
]);
});
}, false);
const handleFilterSubmit = (filter) => {
setFilter({
const profitLossSheetIndex = useMemo(() =>
getProfitLossSheetIndex(filter),
[getProfitLossSheetIndex, filter])
const profitLossSheet = useMemo(() =>
getProfitLossSheet(profitLossSheetIndex),
[getProfitLossSheet, profitLossSheetIndex]);
const handleSubmitFilter = useCallback((filter) => {
const _filter = {
...filter,
from_date: moment(filter.from_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(() => {
return getProfitLossSheetIndex(filter);
}, [filter, getProfitLossSheetIndex]);
const profitLossSheetAccounts = useMemo(() => {
return getProfitLossSheetAccounts(profitLossSheetIndex);
}, [profitLossSheetIndex, getProfitLossSheet]);
const profitLossSheetColumns = useMemo(() => {
return getProfitLossSheetColumns(profitLossSheetIndex);
}, [profitLossSheetIndex])
// Handle fetch data of profit/loss sheet table.
const handleFetchData = useCallback(() => {
fetchHook.execute();
}, [fetchHook]);
return (
<div class="financial-statement">
<ProfitLossSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<DashboardInsider>
<ProfitLossActionsBar />
<div class="financial-statement__body">
<LoadingIndicator loading={fetchHook.pending}>
<div class="financial-statement">
<ProfitLossSheetHeader
pageFilter={filter}
onSubmitFilter={handleSubmitFilter} />
<ProfitLossSheetTable
accounts={profitLossSheetAccounts}
columns={profitLossSheetColumns} />
</LoadingIndicator>
<div class="financial-statement__body">
<LoadingIndicator loading={false}>
<ProfitLossSheetTable
data={[]}
onFetchData={handleFetchData} />
</LoadingIndicator>
</div>
</div>
</div>
</DashboardInsider>
);
}

View File

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

View File

@@ -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>
)
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -7,6 +7,10 @@ import moment from 'moment';
import {compose} from 'utils';
import TrialBalanceSheetConnect from 'connectors/TrialBalanceSheet.connect';
import DashboardConnect from 'connectors/Dashboard.connector';
import TrialBalanceActionsBar from './TrialBalanceActionsBar';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
function TrialBalanceSheet({
changePageTitle,
@@ -57,19 +61,25 @@ function TrialBalanceSheet({
}, [setFilter, fetchHook]);
return (
<div class="financial-statement">
<TrialBalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<DashboardInsider>
<TrialBalanceActionsBar />
<div class="financial-statement__body">
<TrialBalanceSheetTable
trialBalanceSheetAccounts={trialBalanceAccounts}
trialBalanceSheetIndex={trialBalanceSheetIndex}
onFetchData={handleFetchData}
loading={trialBalanceSheetLoading} />
</div>
</div>
<DashboardPageContent>
<div class="financial-statement">
<TrialBalanceSheetHeader
pageFilter={filter}
onSubmitFilter={handleFilterSubmit} />
<div class="financial-statement__body">
<TrialBalanceSheetTable
trialBalanceSheetAccounts={trialBalanceAccounts}
trialBalanceSheetIndex={trialBalanceSheetIndex}
onFetchData={handleFetchData}
loading={trialBalanceSheetLoading} />
</div>
</div>
</DashboardPageContent>
</DashboardInsider>
)
}

View File

@@ -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 {Row, Col} from 'react-grid-system';
import {
@@ -12,98 +12,50 @@ import {
Intent,
Popover,
} from "@blueprintjs/core";
import {DateInput} from '@blueprintjs/datetime';
import moment from 'moment';
import {momentFormatter} from 'utils';
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({
pageFilter,
onSubmitFilter,
}) {
const intl = useIntl();
const [filter, setFilter] = useState({
...pageFilter,
from_date: moment(pageFilter.from_date).toDate(),
to_date: moment(pageFilter.to_date).toDate()
})
const setFilterByKey = (name, value) => {
setFilter({ ...filter, [name]: value });
};
const 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);
}
});
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]);
const handleSubmitClick = useCallback(() => {
formik.submitForm();
}, [formik]);
return (
<FinancialStatementHeader>
<Row>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'report_date_range'})}
minimal={true}
fill={true}>
<HTMLSelect
fill={true}
options={dateRangeOptions}
value={reportDateRange}
onChange={(event) => setReportDateRange(event.target.value)} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'from_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.from_date}
onChange={handleDateChange('from_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
<Col sm={3}>
<FormGroup
label={intl.formatMessage({'id': 'to_date'})}
minimal={true}
fill={true}>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={filter.to_date}
onChange={handleDateChange('to_date')}
popoverProps={{ position: Position.BOTTOM }}
fill={true} />
</FormGroup>
</Col>
</Row>
<FinancialStatementDateRange formik={formik} />
<Row>
<Col sm={3}>
<Button
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitClick}>
onClick={handleSubmitClick}
disabled={formik.isSubmitting}>
{ 'Run Report' }
</Button>
</Col>

View File

@@ -92,7 +92,7 @@ export default [
name: 'dashboard.accounting.general.ledger',
component: LazyLoader({
loader: () =>
import('containers/Dashboard/FinancialStatements/LedgerSheet')
import('containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger')
})
},
{

View File

@@ -90,5 +90,9 @@ export default {
"info-circle": {
path: ['M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z'],
viewBox: '0 0 512 512'
}
},
"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',
},
}

View File

@@ -48,10 +48,20 @@ export const fetchTrialBalanceSheet = ({ query }) => {
export const fetchProfitLossSheet = ({ query }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.get('/financial_statements/profit_loss_sheet', { params: query }).then((response) => {
dispatch({
type: t.PROFIT_LOSS_SHEET_LOADING,
loading: false,
});
ApiService.get('/financial_statements/profit_loss_sheet').then((response) => {
dispatch({
type: t.PROFIT_LOSS_STATEMENT_SET,
data: response.data,
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);
}).catch((error) => { reject(error); });
@@ -60,12 +70,20 @@ export const fetchProfitLossSheet = ({ query }) => {
export const fetchJournalSheet = ({ query }) => {
return (dispatch) => new Promise((resolve, reject) => {
dispatch({
type: t.JOURNAL_SHEET_LOADING,
loading: true,
});
ApiService.get('/financial_statements/journal', { params: query }).then((response) => {
dispatch({
type: t.JOURNAL_SHEET_SET,
data: response.data,
query: response.data.query,
});
dispatch({
type: t.JOURNAL_SHEET_LOADING,
loading: false,
});
resolve(response.data);
}).catch(error => { reject(error); });
});

View File

@@ -5,6 +5,7 @@ import {
getFinancialSheetIndexByQuery,
// getFinancialSheetIndexByQuery,
} from './financialStatements.selectors';
import {omit} from 'lodash';
const initialState = {
balanceSheets: [],
@@ -12,10 +13,65 @@ const initialState = {
sheets: [],
loading: false,
},
generalLedger: [],
journalSheets: [],
generalLedger: {
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, {
[t.BALANCE_SHEET_STATEMENT_SET]: (state, action) => {
const index = getBalanceSheetIndexByQuery(state.balanceSheets, action.query);
@@ -50,17 +106,55 @@ export default createReducer(initialState, {
},
[t.JOURNAL_SHEET_SET]: (state, action) => {
const index = getFinancialSheetIndexByQuery(state.journalSheets, action.query);
console.log(index, 'INDEX');
const index = getFinancialSheetIndexByQuery(state.journal.sheets, action.query);
const journal = {
query: action.data.query,
journal: action.data.journal,
tableRows: mapJournalTableRows(action.data.journal),
};
if (index !== -1) {
state.journalSheets[index] = journal;
state.journal.sheets[index] = journal;
} 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;
},
});

View File

@@ -6,4 +6,8 @@ export default {
TRAIL_BALANCE_STATEMENT_SET: 'TRAIL_BALANCE_STATEMENT_SET',
TRIAL_BALANCE_SHEET_LOADING: 'TRIAL_BALANCE_SHEET_LOADING',
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',
}

View File

@@ -11,6 +11,7 @@ import resources from './resources/resource.types';
import users from './users/users.types';
import financialStatements from './financialStatement/financialStatements.types';
import itemCategories from './itemCategories/itemsCategory.type';
export default {
...authentication,
...accounts,

View File

@@ -138,6 +138,16 @@
}
}
.no-results{
color: #666;
.td{
padding-top: 20px;
padding-bottom: 20px;
width: 100%;
}
}
&--financial-report{
@@ -147,11 +157,10 @@
border-top: 1px solid #666;
border-bottom: 1px solid #666;
padding: 10px 1.5rem;
padding: 10px 0.4rem;
color: #222;
}
}
}
}
}

View File

@@ -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{
.bp3-popover-wrapper{
@@ -41,18 +25,39 @@
}
}
.bp3-button{
.bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal){
min-width: 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-success,
&.bp3-intent-danger,
&.bp3-intent-warning{
&,
&:hover{
box-shadow: 0 0 0;
}
}
}
}
.button--secondary{
}

View File

@@ -37,11 +37,15 @@ label{
&.#{$ns}-intent-danger{
select{
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 rgba(16, 22, 26, 0.15),
inset 0 1px 1px rgba(16, 22, 26, 0.2);
}
.bp3-input{
border-color: #db3737;
}
}
.#{$ns}-label{

View File

@@ -24,11 +24,9 @@
border: 1px solid #E2E2E2;
min-width: 640px;
width: 0;
margin-left: auto;
margin-right: auto;
padding: 20px;
padding-top: 30px;
margin-top: 35px;
margin: 35px auto;
&__title{
margin: 0;
@@ -62,9 +60,40 @@
&__accounting-basis{
}
&--trial-balance{
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;
}
}
}
}
}
}