diff --git a/client/src/components/JournalEntry/ManualJournalActionsBar.js b/client/src/components/JournalEntry/ManualJournalActionsBar.js
new file mode 100644
index 000000000..77d03defe
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalActionsBar.js
@@ -0,0 +1,112 @@
+import React, { useMemo, useState } from 'react';
+import Icon from 'components/Icon';
+import {
+ Button,
+ NavbarGroup,
+ Classes,
+ NavbarDivider,
+ MenuItem,
+ Menu,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ Intent
+} from '@blueprintjs/core';
+import classNames from 'classnames';
+import { useRouteMatch, useHistory } from 'react-router-dom';
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import DialogConnect from 'connectors/Dialog.connector';
+import { compose } from 'utils';
+import FilterDropdown from 'components/FilterDropdown';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import ResourceConnect from 'connectors/Resource.connector';
+
+function ManualJournalActionsBar({
+ openDialog,
+ views,
+ getResourceFields,
+ addManualJournalsTableQueries,
+ onFilterChanged
+}) {
+ const { path } = useRouteMatch();
+ const history = useHistory();
+
+ const manualJournalFields = getResourceFields('manual_journals');
+ const viewsMenuItems = views.map(view => {
+ return (
+
+ );
+ });
+
+ const onClickNewManualJournal = () => {
+ history.push('/dashboard/accounting/make-journal-entry');
+ };
+ const filterDropdown = FilterDropdown({
+ fields: manualJournalFields,
+ onFilterChange: filterConditions => {
+ addManualJournalsTableQueries({
+ filter_roles: filterConditions || ''
+ });
+ onFilterChanged && onFilterChanged(filterConditions);
+ }
+ });
+ return (
+
+
+ {viewsMenuItems}}
+ minimal={true}
+ interactionKind={PopoverInteractionKind.HOVER}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ text='Table Views'
+ rightIcon={'caret-down'}
+ />
+
+
+ }
+ text='New Journal'
+ onClick={onClickNewManualJournal}
+ />
+
+ }
+ />
+
+ }
+ text='Delete'
+ intent={Intent.DANGER}
+ />
+ }
+ text='Import'
+ />
+ }
+ text='Export'
+ />
+
+
+ );
+}
+
+export default compose(
+ DialogConnect,
+ ManualJournalsConnect,
+ ResourceConnect
+)(ManualJournalActionsBar);
diff --git a/client/src/components/JournalEntry/ManualJournalsDataTable.js b/client/src/components/JournalEntry/ManualJournalsDataTable.js
new file mode 100644
index 000000000..36e74c1a3
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalsDataTable.js
@@ -0,0 +1,140 @@
+import React, { useEffect, useCallback, useState, useMemo } from 'react';
+import {
+ Button,
+ Popover,
+ Menu,
+ MenuItem,
+ MenuDivider,
+ Position,
+} from '@blueprintjs/core';
+import { useParams, useHistory } from 'react-router-dom';
+import Icon from 'components/Icon';
+import { compose } from 'utils';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DialogConnect from 'connectors/Dialog.connector';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import ViewConnect from 'connectors/View.connector';
+import LoadingIndicator from 'components/LoadingIndicator';
+import DataTable from 'components/DataTable';
+
+function ManualJournalsDataTable({
+ manual_journals,
+ changeCurrentView,
+ changePageSubtitle,
+ getViewItem,
+ setTopbarEditView,
+ manualJournalsLoading,
+ onFetchData,
+}) {
+ const { custom_view_id: customViewId } = useParams();
+ useEffect(() => {
+ const viewMeta = getViewItem(customViewId);
+
+ if (customViewId) {
+ changeCurrentView(customViewId);
+ setTopbarEditView(customViewId);
+ }
+ changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
+ }, [customViewId]);
+
+ useEffect(
+ () => () => {
+ changePageSubtitle('');
+ },
+ []
+ );
+
+ const history = useHistory();
+ const handleClickNewView = () => {
+ history.push('/dashboard/accounting/make-journal-entry');
+ };
+
+ console.log(manual_journals, 'Manual_journals');
+
+ const actionMenuList = (manualJournal) => (
+
+ );
+
+ const columns = useMemo(
+ () => [
+ {
+ id: 'date',
+ Header: 'Date',
+ accessor: 'date',
+ },
+ {
+ id: 'amount',
+ Header: 'Amount',
+ accessor: 'amount',
+ },
+ {
+ id: 'journal_number',
+ Header: 'Journal Number',
+ accessor: 'journal_number',
+ },
+ {
+ id: 'status',
+ Header: 'Status',
+ accessor: (r) => {
+ return r.status ? 'Published' : 'Draft';
+ },
+ },
+ {
+ id: 'note',
+ Header: 'Note',
+ accessor: 'note',
+ },
+ {
+ id: 'transaction_type',
+ Header: 'Transaction type ',
+ },
+ {
+ id: 'actions',
+ Header: '',
+ Cell: ({ cell }) => (
+
+ } />
+
+ ),
+ className: 'actions',
+ width: 50,
+ },
+ ],
+ []
+ );
+ const handleDataTableFetchData = useCallback(() => {
+ onFetchData && onFetchData();
+ }, []);
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ ManualJournalsConnect,
+ DialogConnect,
+ DashboardConnect,
+ ViewConnect
+)(ManualJournalsDataTable);
diff --git a/client/src/components/JournalEntry/ManualJournalsViewTabs.js b/client/src/components/JournalEntry/ManualJournalsViewTabs.js
new file mode 100644
index 000000000..376dfc6b6
--- /dev/null
+++ b/client/src/components/JournalEntry/ManualJournalsViewTabs.js
@@ -0,0 +1,98 @@
+import React, { useEffect } from 'react';
+import { useHistory } from 'react-router';
+import {
+ Alignment,
+ Navbar,
+ NavbarGroup,
+ Tabs,
+ Tab,
+ Button,
+} from '@blueprintjs/core';
+import { useParams } from 'react-router-dom';
+import Icon from 'components/Icon';
+import { Link } from 'react-router-dom';
+import { compose } from 'utils';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import { useUpdateEffect } from 'hooks';
+
+function ManualJournalsViewTabs({
+ views,
+ manual_journals,
+ setTopbarEditView,
+ customViewChanged,
+ addManualJournalsTableQueries,
+ onViewChanged,
+}) {
+ const history = useHistory();
+ const { custom_view_id: customViewId } = useParams();
+
+ const handleClickNewView = () => {
+ setTopbarEditView(null);
+ history.push('/dashboard/custom_views/manual_journals/new');
+ };
+ const handleViewLinkClick = () => {
+ setTopbarEditView(customViewId);
+ };
+
+ useUpdateEffect(() => {
+ customViewChanged && customViewChanged(customViewId);
+
+ addManualJournalsTableQueries({
+ custom_view_id: customViewId || null,
+ });
+ onViewChanged && onViewChanged(customViewId);
+ }, [customViewId]);
+
+ useEffect(() => {
+ addManualJournalsTableQueries({
+ custom_view_id: customViewId,
+ });
+ }, [customViewId]);
+
+ const tabs = views.map((view) => {
+ //FIXME: dashboard/accounting/make-journal-entry
+
+ const baseUrl = '/dashboard/accounting/manual-journals';
+ const link = (
+
+ {view.name}
+
+ );
+ return ;
+ });
+
+ return (
+
+
+
+ All
+ }
+ />
+ {tabs}
+ }
+ onClick={handleClickNewView}
+ />
+
+
+
+ );
+}
+
+export default compose(
+ ManualJournalsConnect,
+ DashboardConnect
+)(ManualJournalsViewTabs);
diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js
index 522fa6077..2a8304e05 100644
--- a/client/src/config/sidebarMenu.js
+++ b/client/src/config/sidebarMenu.js
@@ -46,6 +46,10 @@ export default [
{
text: 'Make Journal',
href: '/dashboard/accounting/make-journal-entry'
+ },
+ {
+ text: 'Manual Journal',
+ href: '/dashboard/accounting/manual-journals'
}
]
},
diff --git a/client/src/connectors/ManualJournals.connect.js b/client/src/connectors/ManualJournals.connect.js
new file mode 100644
index 000000000..d89d1e321
--- /dev/null
+++ b/client/src/connectors/ManualJournals.connect.js
@@ -0,0 +1,38 @@
+import { connect } from 'react-redux';
+import {
+ deleteManualJournal,
+ // fetchManualJournalsList,
+ fetchManualJournalsTable,
+} from 'store/accounting/accounting.actions';
+import { getResourceViews } from 'store/customViews/customViews.selectors';
+import { getManualJournals } from 'store/accounting/accounting.selectors';
+import t from 'store/types';
+
+const mapStateToProps = (state, props) => ({
+ views: getResourceViews(state, 'manual_journals'),
+ // manual_journals: state.manual_journals,
+ manual_journals: getManualJournals(
+ state,
+ state.manual_journals.currentViewId
+ ),
+ tableQuery: state.manual_journals.tableQuery,
+ manualJournalsLoading: state.manual_journals.loading,
+});
+
+const mapActionsToProps = (dispatch) => ({
+ requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
+ changeCurrentView: (id) =>
+ dispatch({
+ type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
+ currentViewId: parseInt(id, 10),
+ }),
+ addManualJournalsTableQueries: (queries) =>
+ dispatch({
+ type: 'MANUAL_JOURNALS_TABLE_QUERIES_ADD',
+ queries,
+ }),
+ fetchManualJournalsTable: (query = {}) =>
+ dispatch(fetchManualJournalsTable({ query: { ...query } })),
+});
+
+export default connect(mapStateToProps, mapActionsToProps);
diff --git a/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js b/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js
new file mode 100644
index 000000000..2489154af
--- /dev/null
+++ b/client/src/containers/Dashboard/Accounting/ManualJournalsTable.js
@@ -0,0 +1,99 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { Route, Switch, useParams, useRouteMatch } from 'react-router-dom';
+import useAsync from 'hooks/async';
+import { Alert, Intent } from '@blueprintjs/core';
+import AppToaster from 'components/AppToaster';
+import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import ManualJournalsViewTabs from 'components/JournalEntry/ManualJournalsViewTabs';
+import ManualJournalsDataTable from 'components/JournalEntry/ManualJournalsDataTable';
+import DashboardActionsBar from 'components/JournalEntry/ManualJournalActionsBar';
+import ManualJournalsConnect from 'connectors/ManualJournals.connect';
+import DashboardConnect from 'connectors/Dashboard.connector';
+import CustomViewConnect from 'connectors/CustomView.connector';
+import ResourceConnect from 'connectors/Resource.connector';
+import { compose } from 'utils';
+
+function ManualJournalsTable({
+ changePageTitle,
+ fetchResourceViews,
+ fetchManualJournalsTable,
+ requestDeleteManualJournal,
+}) {
+ const [deleteManualJournal, setDeleteManualJournal] = useState(false);
+
+ const fetchHook = useAsync(async () => {
+ await Promise.all([fetchResourceViews('manual_journals')]);
+ });
+
+ const fetchManualJournalsHook = useAsync(async () => {
+ await Promise.all([fetchManualJournalsTable()]);
+ }, false);
+
+ useEffect(() => {
+ changePageTitle('Manual Journals');
+ }, []);
+
+ const handleCancelManualJournalDelete = () => {
+ setDeleteManualJournal(false);
+ };
+
+ const handleConfirmManualJournalDelete = useCallback(() => {
+ requestDeleteManualJournal(deleteManualJournal.id).then(() => {
+ setDeleteManualJournal(false);
+ fetchManualJournalsHook.execute();
+ AppToaster.show({ message: 'the_manual_Journal_has_been_deleted' });
+ });
+ }, [deleteManualJournal]);
+ const handleFilterChanged = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+ const handleViewChanged = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+ const handleFetchData = useCallback(() => {
+ fetchManualJournalsHook.execute();
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ Are you sure you want to move filename to Trash? You will be
+ able to restore it later, but it will become private to you.
+
+
+
+
+ );
+}
+
+export default compose(
+ ManualJournalsConnect,
+ CustomViewConnect,
+ ResourceConnect,
+ DashboardConnect
+)(ManualJournalsTable);
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index 3ab430c4b..4873bc665 100644
--- a/client/src/routes/dashboard.js
+++ b/client/src/routes/dashboard.js
@@ -65,6 +65,14 @@ export default [
}),
text: 'Make Journal Entry'
},
+ {
+ path: `${BASE_URL}/accounting/manual-journals`,
+ component: LazyLoader({
+ loader: () =>
+ import('containers/Dashboard/Accounting/ManualJournalsTable')
+ }),
+ text: 'Manual Journals'
+ },
// Items
{
@@ -86,14 +94,16 @@ export default [
loader: () => import('containers/Dashboard/Items/ItemsCategoryList')
})
},
- ,
+
// Financial Reports.
{
path: `${BASE_URL}/accounting/general-ledger`,
name: 'dashboard.accounting.general.ledger',
component: LazyLoader({
loader: () =>
- import('containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger')
+ import(
+ 'containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedger'
+ )
})
},
{
diff --git a/client/src/store/accounting/accounting.actions.js b/client/src/store/accounting/accounting.actions.js
index c985dbb8f..b7ad041ee 100644
--- a/client/src/store/accounting/accounting.actions.js
+++ b/client/src/store/accounting/accounting.actions.js
@@ -2,9 +2,82 @@ import ApiService from 'services/ApiService';
import t from 'store/types';
export const makeJournalEntries = ({ form }) => {
- return (dispatch) => new Promise((resolve, reject) => {
- ApiService.post('accounting/make-journal-entries', form).then((response) => {
- resolve(response);
- }).catch((error) => { reject(error); });
- });
-}
\ No newline at end of file
+ return (dispatch) =>
+ new Promise((resolve, reject) => {
+ ApiService.post('accounting/make-journal-entries', form)
+ .then((response) => {
+ resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+};
+
+export const deleteManualJournal = ({ id }) => {
+ return (dispatch) =>
+ new Promise((resolve, reject) => {
+ ApiService.delete(`accounting/manual-journals/${id}`)
+ .then((response) => {
+ dispatch({
+ type: t.MANUAL_JOURNAL_DELETE,
+ id,
+ });
+ resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+};
+
+export const fetchManualJournalsTable = ({ query } = {}) => {
+ return (dispatch, getState) =>
+ new Promise((resolve, reject) => {
+ const pageQuery = getState().manual_journals.tableQuery;
+
+ dispatch({
+ type: t.MANUAL_JOURNALS_TABLE_LOADING,
+ loading: true,
+ });
+ ApiService.get('accounting/manual-journals', {
+ params: { ...pageQuery, ...query },
+ })
+ .then((response) => {
+
+ dispatch({
+ type: t.MANUAL_JOURNALS_PAGE_SET,
+ manual_journals: response.data.manualJournals,
+ customViewId: response.data.customViewId,
+ });
+ dispatch({
+ type: t.MANUAL_JOURNALS_ITEMS_SET,
+ manual_journals: response.data.manualJournals,
+ });
+ dispatch({
+ type: t.MANUAL_JOURNALS_TABLE_LOADING,
+ loading: false,
+ });
+ resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+};
+
+export const fetchManualJournalsDataTable = ({ query }) => {
+ return (dispatch) =>
+ new Promise((resolve, reject) => {
+ ApiService.get('accounting/manual-journals')
+ .then((response) => {
+ dispatch({
+ type: t.MANUAL_JOURNALS_DATA_TABLE,
+ data: response.data,
+ });
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+};
diff --git a/client/src/store/accounting/accounting.reducers.js b/client/src/store/accounting/accounting.reducers.js
index e69de29bb..04ac8cb3d 100644
--- a/client/src/store/accounting/accounting.reducers.js
+++ b/client/src/store/accounting/accounting.reducers.js
@@ -0,0 +1,54 @@
+import t from 'store/types';
+import { createReducer, combineReducers } from '@reduxjs/toolkit';
+import { createTableQueryReducers } from 'store/queryReducers';
+
+const initialState = {
+ manual_journals: {},
+ views: {},
+ manualJournalById: {},
+ dataTableQuery: {},
+ currentViewId: -1,
+ selectedRows: [],
+ loading: false,
+};
+
+// MANUAL_JOURNALS
+const manualJournalsReducer = createReducer(initialState, {
+ [t.MANUAL_JOURNALS_ITEMS_SET]: (state, action) => {
+ const _manual_journals = {};
+
+ action.manual_journals.forEach((manual_journal) => {
+ _manual_journals[manual_journal.id] = manual_journal;
+ });
+
+ state.manual_journals = {
+ ...state.manual_journals,
+ ..._manual_journals,
+ };
+ },
+ [t.MANUAL_JOURNALS_PAGE_SET]: (state, action) => {
+ const viewId = action.customViewId || -1;
+ const view = state.views[viewId] || {};
+
+ state.views[viewId] = {
+ ...view,
+ ids: action.manual_journals.map((i) => i.id),
+ };
+ // state.manual_journals = action.manual_journals;
+ },
+ [t.MANUAL_JOURNALS_TABLE_LOADING]: (state, action) => {
+ state.loading = action.loading;
+ },
+ [t.MANUAL_JOURNALS_SET_CURRENT_VIEW]: (state, action) => {
+ state.currentViewId = action.currentViewId;
+ },
+});
+
+export default createTableQueryReducers(
+ 'manual_journals',
+ manualJournalsReducer
+);
+
+export const getManualJournalById = (state, id) => {
+ return state.manual_journals.manualJournalById[id];
+};
diff --git a/client/src/store/accounting/accounting.selectors.js b/client/src/store/accounting/accounting.selectors.js
new file mode 100644
index 000000000..18b9f84ac
--- /dev/null
+++ b/client/src/store/accounting/accounting.selectors.js
@@ -0,0 +1,14 @@
+import { pickItemsFromIds } from 'store/selectors';
+
+export const getManualJournals = (state, viewId) => {
+ const manualJournalView = state.manual_journals.views[-1];
+ const manualJournalsItems = state.manual_journals.manual_journals;
+
+ console.log(manualJournalView, 'Message');
+
+ return typeof manualJournalView === 'object' &&
+ manualJournalView.ids &&
+ manualJournalsItems
+ ? pickItemsFromIds(manualJournalsItems, manualJournalView.ids) || []
+ : [];
+};
diff --git a/client/src/store/accounting/accountsing.types.js b/client/src/store/accounting/accountsing.types.js
index f10249128..0ce8224eb 100644
--- a/client/src/store/accounting/accountsing.types.js
+++ b/client/src/store/accounting/accountsing.types.js
@@ -1,5 +1,10 @@
-
-
export default {
MAKE_JOURNAL_ENTRIES: 'MAKE_JOURNAL_ENTRIES',
-}
\ No newline at end of file
+ MANUAL_JOURNALS_TABLE_LOADING: 'MANUAL_JOURNALS_TABLE_LOADING',
+ MANUAL_JOURNALS_PAGE_SET: 'MANUAL_JOURNALS_PAGE_SET',
+ MANUAL_JOURNALS_DATA_TABLE: 'MANUAL_JOURNALS_DATA_TABLE',
+ MANUAL_JOURNALS_ITEMS_SET: 'MANUAL_JOURNALS_ITEMS_SET',
+ MANUAL_JOURNALS_SET_CURRENT_VIEW: 'MANUAL_JOURNALS_SET_CURRENT_VIEW',
+ MANUAL_JOURNALS_TABLE_QUERIES_ADD: 'MANUAL_JOURNALS_TABLE_QUERIES_ADD',
+ MANUAL_JOURNAL_DELETE: 'MANUAL_JOURNAL_DELETE',
+};
diff --git a/client/src/store/accounts/accounts.selectors.js b/client/src/store/accounts/accounts.selectors.js
index 56ff4c07f..6192243a2 100644
--- a/client/src/store/accounts/accounts.selectors.js
+++ b/client/src/store/accounts/accounts.selectors.js
@@ -1,9 +1,11 @@
-import {pickItemsFromIds} from 'store/selectors';
+import { pickItemsFromIds } from 'store/selectors';
export const getAccountsItems = (state, viewId) => {
- const accountsView = state.accounts.views[(viewId || -1)];
+
+ const accountsView = state.accounts.views[viewId || -1];
const accountsItems = state.accounts.items;
- return (typeof accountsView === 'object')
- ? (pickItemsFromIds(accountsItems, accountsView.ids) || []) : [];
-}
\ No newline at end of file
+ return typeof accountsView === 'object'
+ ? pickItemsFromIds(accountsItems, accountsView.ids) || []
+ : [];
+};
diff --git a/client/src/store/reducers.js b/client/src/store/reducers.js
index d3efde8b0..282a6ea6c 100644
--- a/client/src/store/reducers.js
+++ b/client/src/store/reducers.js
@@ -11,9 +11,9 @@ import expenses from './expenses/expenses.reducer';
import currencies from './currencies/currencies.reducer';
import resources from './resources/resources.reducer';
import financialStatements from './financialStatement/financialStatements.reducer';
-import itemCategories from './itemCategories/itemsCateory.reducer';
+import itemCategories from './itemCategories/itemsCategory.reducer';
import settings from './settings/settings.reducer';
-
+import manual_journals from './accounting/accounting.reducers';
export default combineReducers({
authentication,
dashboard,
@@ -28,4 +28,5 @@ export default combineReducers({
items,
itemCategories,
settings,
+ manual_journals,
});
diff --git a/client/src/store/types.js b/client/src/store/types.js
index 307f75f93..d70feb172 100644
--- a/client/src/store/types.js
+++ b/client/src/store/types.js
@@ -12,7 +12,7 @@ import users from './users/users.types';
import financialStatements from './financialStatement/financialStatements.types';
import itemCategories from './itemCategories/itemsCategory.type';
import settings from './settings/settings.type';
-
+import manualJournals from './accounting/accountsing.types';
export default {
...authentication,
...accounts,
@@ -27,5 +27,6 @@ export default {
...users,
...financialStatements,
...itemCategories,
- ...settings
+ ...settings,
+ ...manualJournals
};