diff --git a/client/src/components/AccountsMultiSelect.js b/client/src/components/AccountsMultiSelect.js new file mode 100644 index 000000000..5fcfa0b24 --- /dev/null +++ b/client/src/components/AccountsMultiSelect.js @@ -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 ( + + ); + }, [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 ( + } + itemRenderer={accountItem} + popoverProps={{ minimal: true }} + filterable={true} + onItemSelect={onAccountSelect} + > + diff --git a/client/src/containers/Dashboard/FinancialStatements/FinancialStatementDateRange.js b/client/src/containers/Dashboard/FinancialStatements/FinancialStatementDateRange.js new file mode 100644 index 000000000..34479a3c2 --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/FinancialStatementDateRange.js @@ -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(() => (), []); + + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger.js b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger.js index e69de29bb..3a70351ce 100644 --- a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger.js +++ b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger.js @@ -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 ( + + + + +
+ + +
+ +
+
+
+
+ ); +} + +export default compose( + DashboardConnect, + AccountsConnect, + GeneralLedgerConnect, +)(GeneralLedger); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js new file mode 100644 index 000000000..2c8563677 --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js @@ -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 ( + + + + + + + ) +} + +export default compose( + AccountsConnect +)(GeneralLedgerHeader); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js index e69de29bb..2ba961b16 100644 --- a/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable.js @@ -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) ? + () : ''; + } + return (); + }, []); + + const referenceLink = useCallback((row) => { + return ({ row.referenceId }); + }); + + const columns = useMemo(() => [ + { + // Build our expander column + id: 'expander', // Make sure it has an ID + className: 'expander', + Header: ({ + getToggleAllRowsExpandedProps, + isAllRowsExpanded + }) => ( + + {isAllRowsExpanded ? + () : + () + } + + ), + Cell: ({ row }) => + // Use the row.canExpand and row.getToggleRowExpandedProps prop getter + // to build the toggle for expanding a row + row.canExpand ? ( + + {row.isExpanded ? + () : + () + } + + ) : 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 ( + + + + + ); +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/Journal/Journal.js b/client/src/containers/Dashboard/FinancialStatements/Journal/Journal.js index 1b3517dac..4992891f1 100644 --- a/client/src/containers/Dashboard/FinancialStatements/Journal/Journal.js +++ b/client/src/containers/Dashboard/FinancialStatements/Journal/Journal.js @@ -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 ( -
- + }; + setFilter(_filter); + fetchHook.execute(_filter); + }, [fetchHook]); -
- - - -
-
+ const handlePrintClick = useCallback(() => { + + }, []); + + const handleExportClick = useCallback(() => { + + }, []); + + const handleFetchData = useCallback(({ sortBy, pageIndex, pageSize }) => { + fetchHook.execute(); + }, [fetchHook]); + + return ( + + {}} + onPrintClick={handlePrintClick} + onExportClick={handleExportClick} /> + + +
+ + +
+ +
+
+
+
) } diff --git a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalActionsBar.js b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalActionsBar.js new file mode 100644 index 000000000..9e792d07d --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalActionsBar.js @@ -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 ( + + + diff --git a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js index dfb914245..28485da84 100644 --- a/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/Journal/JournalTable.js @@ -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, (), ['space_entry']), }, { Header: 'Debit', - accessor: 'debit', + accessor: r => exceptRowTypes( + r.rowType, (), ['space_entry']), }, ], []); + const handleFetchData = useCallback((...args) => { + onFetchData && onFetchData(...args) + }, [onFetchData]); + return ( + sheetType={'Journal Sheet'} + date={new Date()} + name="journal" + loading={loading}> - + data={data} + onFetchData={handleFetchData} + noResults={"This report does not contain any data."} /> ); } -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); \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js new file mode 100644 index 000000000..23ff45d4c --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js @@ -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 ( + + + - ) + ); } \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js index e69de29bb..a72a6a739 100644 --- a/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js +++ b/client/src/containers/Dashboard/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js @@ -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 ( + + + + + ) +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js b/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js new file mode 100644 index 000000000..a957a8781 --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/RadiosAccountingBasis.js @@ -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 ( + { + onChange && onChange(value); + })} + {...rest}> + + + + ); +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js b/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js new file mode 100644 index 000000000..0faf79071 --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/SelectDisplayColumnsBy.js @@ -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 (); + }, []); + + return ( + + + } + filterable={false} + itemRenderer={itemRenderer} + popoverProps={{ minimal: true }} + buttonLabel={'Select...'} + {...selectListProps} /> + + ); +} \ No newline at end of file diff --git a/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceActionsBar.js b/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceActionsBar.js new file mode 100644 index 000000000..9bcec4b82 --- /dev/null +++ b/client/src/containers/Dashboard/FinancialStatements/TrialBalanceSheet/TrialBalanceActionsBar.js @@ -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 ( + + + diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 916821eca..e918d57c3 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -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') }) }, { diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js index c7728204a..198c0f8d0 100644 --- a/client/src/static/json/icons.js +++ b/client/src/static/json/icons.js @@ -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', + }, } \ No newline at end of file diff --git a/client/src/store/financialStatement/financialStatements.actions.js b/client/src/store/financialStatement/financialStatements.actions.js index bb394f80d..6f884c455 100644 --- a/client/src/store/financialStatement/financialStatements.actions.js +++ b/client/src/store/financialStatement/financialStatements.actions.js @@ -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); }); }); diff --git a/client/src/store/financialStatement/financialStatements.reducer.js b/client/src/store/financialStatement/financialStatements.reducer.js index aaa8d14b7..fce244cd4 100644 --- a/client/src/store/financialStatement/financialStatements.reducer.js +++ b/client/src/store/financialStatement/financialStatements.reducer.js @@ -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; + }, }); \ No newline at end of file diff --git a/client/src/store/financialStatement/financialStatements.types.js b/client/src/store/financialStatement/financialStatements.types.js index 898fcbab5..0804852b3 100644 --- a/client/src/store/financialStatement/financialStatements.types.js +++ b/client/src/store/financialStatement/financialStatements.types.js @@ -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', } \ No newline at end of file diff --git a/client/src/store/types.js b/client/src/store/types.js index 16604a866..31359f5be 100644 --- a/client/src/store/types.js +++ b/client/src/store/types.js @@ -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, diff --git a/client/src/style/components/data-table.scss b/client/src/style/components/data-table.scss index 76cc3a26c..ec124e6c3 100644 --- a/client/src/style/components/data-table.scss +++ b/client/src/style/components/data-table.scss @@ -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; } - } - + } } } diff --git a/client/src/style/objects/buttons.scss b/client/src/style/objects/buttons.scss index a7b76b654..4fc099465 100644 --- a/client/src/style/objects/buttons.scss +++ b/client/src/style/objects/buttons.scss @@ -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{ + } \ No newline at end of file diff --git a/client/src/style/objects/form.scss b/client/src/style/objects/form.scss index c6879981a..15efd2542 100644 --- a/client/src/style/objects/form.scss +++ b/client/src/style/objects/form.scss @@ -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{ diff --git a/client/src/style/pages/financial-statements.scss b/client/src/style/pages/financial-statements.scss index 964266086..8963cfef4 100644 --- a/client/src/style/pages/financial-statements.scss +++ b/client/src/style/pages/financial-statements.scss @@ -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; + } + } + } + } + } } \ No newline at end of file diff --git a/server/src/http/controllers/FinancialStatements.js b/server/src/http/controllers/FinancialStatements.js index 32ed2c93b..be387033c 100644 --- a/server/src/http/controllers/FinancialStatements.js +++ b/server/src/http/controllers/FinancialStatements.js @@ -120,7 +120,7 @@ export default { const formatNumber = formatNumberClosure(filter.number_format); const journalGrouped = groupBy(accountsJournalEntries, (entry) => { - return `${entry.id}-${entry.referenceType}`; + return `${entry.referenceId}-${entry.referenceType}`; }); const journal = Object.keys(journalGrouped).map((key) => { const transactionsGroup = journalGrouped[key]; @@ -251,11 +251,11 @@ export default { ], opening: { date: filter.from_date, - balance: opeingBalanceCollection.getClosingBalance(account.id), + amount: opeingBalanceCollection.getClosingBalance(account.id), }, closing: { 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({ query: { ...filter }, columns: [...dateRangeSet], - income: incomeResponse, - expenses: expenseResponse, - net_income: netIncomeResponse, + profitLoss: { + income: incomeResponse, + expenses: expenseResponse, + net_income: netIncomeResponse, + }, }); }, },