diff --git a/client/package.json b/client/package.json index 42dc97aed..a73054c21 100644 --- a/client/package.json +++ b/client/package.json @@ -80,7 +80,7 @@ "react-query": "^2.4.6", "react-redux": "^7.1.3", "react-router-breadcrumbs-hoc": "^3.2.10", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", "react-scroll-sync": "^0.7.1", "react-scrollbars-custom": "^4.0.21", "react-sortablejs": "^2.0.11", @@ -89,7 +89,7 @@ "react-table-sticky": "^1.1.2", "react-transition-group": "^4.4.1", "react-use": "^13.26.1", - "react-window": "^1.8.5", + "react-virtualized": "^9.22.3", "redux": "^4.0.5", "redux-persist": "^6.0.0", "redux-thunk": "^2.3.0", diff --git a/client/src/common/classes.js b/client/src/common/classes.js index a6968d378..79e07b132 100644 --- a/client/src/common/classes.js +++ b/client/src/common/classes.js @@ -12,7 +12,8 @@ const CLASSES = { DASHBOARD_CONTENT: 'dashboard-content', DASHBOARD_CONTENT_PREFERENCES: 'dashboard-content--preferences', - + DASHBOARD_CONTENT_PANE: 'Pane2', + PAGE_FORM: 'page-form', PAGE_FORM_HEADER: 'page-form__header', PAGE_FORM_HEADER_PRIMARY: 'page-form__primary-section', diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js index 585d96b01..27abc0b6d 100644 --- a/client/src/components/AccountsSelectList.js +++ b/client/src/components/AccountsSelectList.js @@ -14,7 +14,7 @@ export default function AccountsSelectList({ onAccountSelected, disabled = false, popoverFill = false, - filterByRootTypes = [], + filterByParentTypes = [], filterByTypes = [], filterByNormal, buttonProps = {} @@ -23,23 +23,23 @@ export default function AccountsSelectList({ const filteredAccounts = useMemo(() => { let filteredAccounts = [...accounts]; - if (!isEmpty(filterByRootTypes)) { + if (!isEmpty(filterByParentTypes)) { filteredAccounts = filteredAccounts.filter( - (account) => filterByRootTypes.indexOf(account.type.root_type) !== -1, + (account) => filterByParentTypes.indexOf(account.account_parent_type) !== -1, ); } if (!isEmpty(filterByTypes)) { filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.type.key) !== -1, + (account) => filterByTypes.indexOf(account.account_type) !== -1, ); } if (!isEmpty(filterByNormal)) { filteredAccounts = filteredAccounts.filter( - (account) => filterByTypes.indexOf(account.type.normal) === filterByNormal, + (account) => filterByTypes.indexOf(account.account_normal) === filterByNormal, ); } return filteredAccounts; - }, [accounts, filterByRootTypes, filterByTypes, filterByNormal]); + }, [accounts, filterByParentTypes, filterByTypes, filterByNormal]); // Find initial account object to set it as default account in initial render. const initialAccount = useMemo( diff --git a/client/src/components/AccountsSuggestField.js b/client/src/components/AccountsSuggestField.js index c0757d8b0..f43cdb2f4 100644 --- a/client/src/components/AccountsSuggestField.js +++ b/client/src/components/AccountsSuggestField.js @@ -119,7 +119,7 @@ export default function AccountsSuggestField({ inputProps={{ placeholder: defaultSelectText }} resetOnClose={true} fill={true} - popoverProps={{ minimal: true }} + popoverProps={{ minimal: true, boundary: 'window' }} inputValueRenderer={handleInputValueRenderer} className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, diff --git a/client/src/components/AppToaster.js b/client/src/components/AppToaster.js index 5aa4cf810..917854d3f 100644 --- a/client/src/components/AppToaster.js +++ b/client/src/components/AppToaster.js @@ -1,7 +1,7 @@ import { Position, Toaster, Intent } from "@blueprintjs/core"; const AppToaster = Toaster.create({ - position: Position.TOP, + position: Position.RIGHT_BOTTOM, intent: Intent.WARNING, }); diff --git a/client/src/components/ContactsSuggestField.js b/client/src/components/ContactsSuggestField.js index 4269d21ff..6e165d2c8 100644 --- a/client/src/components/ContactsSuggestField.js +++ b/client/src/components/ContactsSuggestField.js @@ -94,8 +94,7 @@ export default function ContactsSuggestField({ selectedItem={selecetedContact} inputProps={{ placeholder: defaultTextSelect }} resetOnClose={true} - // fill={true} - popoverProps={{ minimal: true }} + popoverProps={{ minimal: true, boundary: 'window' }} inputValueRenderer={handleInputValueRenderer} className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, diff --git a/client/src/components/Dashboard/DashboardSplitePane.js b/client/src/components/Dashboard/DashboardSplitePane.js index a6a2445e4..0abb4ed8b 100644 --- a/client/src/components/Dashboard/DashboardSplitePane.js +++ b/client/src/components/Dashboard/DashboardSplitePane.js @@ -9,7 +9,7 @@ function DashboardSplitPane({ sidebarExpended, children }) { - const initialSize = 190; + const initialSize = 200; const [defaultSize, setDefaultSize] = useState( parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize, diff --git a/client/src/components/Dashboard/TopbarUser.js b/client/src/components/Dashboard/TopbarUser.js index 311496b49..e14988afb 100644 --- a/client/src/components/Dashboard/TopbarUser.js +++ b/client/src/components/Dashboard/TopbarUser.js @@ -6,6 +6,7 @@ import { MenuDivider, Button, Popover, + Position, } from '@blueprintjs/core'; import { FormattedMessage as T } from 'react-intl'; @@ -50,7 +51,7 @@ function DashboardTopbarUser({ requestLogout, user }) { ); return ( - + diff --git a/client/src/containers/Dialogs/AccountFormDialog/index.js b/client/src/containers/Dialogs/AccountFormDialog/index.js index b7f6b9a54..6956e1145 100644 --- a/client/src/containers/Dialogs/AccountFormDialog/index.js +++ b/client/src/containers/Dialogs/AccountFormDialog/index.js @@ -33,7 +33,7 @@ function AccountFormDialog({ accountId={payload.id} action={payload.action} parentAccountId={payload.parentAccountId} - accountTypeId={payload.accountTypeId} + accountType={payload.accountType} /> diff --git a/client/src/containers/Dialogs/AccountFormDialog/utils.js b/client/src/containers/Dialogs/AccountFormDialog/utils.js index cf672b5a0..8dda0baa8 100644 --- a/client/src/containers/Dialogs/AccountFormDialog/utils.js +++ b/client/src/containers/Dialogs/AccountFormDialog/utils.js @@ -14,11 +14,11 @@ export const transformApiErrors = (errors) => { export const transformAccountToForm = (account, { action, parentAccountId, - accountTypeId + accountType }) => { return { parent_account_id: action === 'new_child' ? parentAccountId : '', - account_type_id: action === 'new_child'? accountTypeId : '', + account_type: action === 'new_child'? accountType : '', subaccount: action === 'new_child' ? true : false, ...account, } diff --git a/client/src/containers/Router/withRoute.js b/client/src/containers/Router/withRoute.js new file mode 100644 index 000000000..a8b71e50b --- /dev/null +++ b/client/src/containers/Router/withRoute.js @@ -0,0 +1,6 @@ +import { connect } from "react-redux"; +import { withRouter } from "react-router-dom" + +export default (mapState) => { + return () => withRouter ; +}; \ No newline at end of file diff --git a/client/src/containers/Router/withRouteActions.js b/client/src/containers/Router/withRouteActions.js index 2c4664a3f..8bc63a774 100644 --- a/client/src/containers/Router/withRouteActions.js +++ b/client/src/containers/Router/withRouteActions.js @@ -28,4 +28,4 @@ const mapDispatchToProps = (dispatch, props) => { } } -export default connect(null, mapDispatchToProps) \ No newline at end of file +export default connect(null, mapDispatchToProps) \ No newline at end of file diff --git a/client/src/store/accounts/accounts.actions.js b/client/src/store/accounts/accounts.actions.js index 0661ae77e..6f7568608 100644 --- a/client/src/store/accounts/accounts.actions.js +++ b/client/src/store/accounts/accounts.actions.js @@ -1,3 +1,4 @@ +import { batch } from 'react-redux' import { omit } from 'lodash'; import ApiService from 'services/ApiService'; import t from 'store/types'; @@ -26,15 +27,17 @@ export const fetchAccountsList = () => { ApiService.get('accounts', { params: query }) .then((response) => { - dispatch({ - type: t.ACCOUNTS_ITEMS_SET, - accounts: response.data.accounts, - }); - dispatch({ - type: t.ACCOUNTS_LIST_SET, - payload: { + batch(() => { + dispatch({ + type: t.ACCOUNTS_ITEMS_SET, accounts: response.data.accounts, - } + }); + dispatch({ + type: t.ACCOUNTS_LIST_SET, + payload: { + accounts: response.data.accounts, + } + }); }); resolve(response); }) @@ -62,18 +65,20 @@ export const fetchAccountsTable = ({ query } = {}) => { }); ApiService.get('accounts', { params: { ...pageQuery, ...query } }) .then((response) => { - dispatch({ - type: t.ACCOUNTS_PAGE_SET, - accounts: response.data.accounts, - customViewId: response.data?.filter_meta?.view?.custom_view_id, - }); - dispatch({ - type: t.ACCOUNTS_ITEMS_SET, - accounts: response.data.accounts, - }); - dispatch({ - type: t.ACCOUNTS_TABLE_LOADING, - loading: false, + batch(() => { + dispatch({ + type: t.ACCOUNTS_PAGE_SET, + accounts: response.data.accounts, + customViewId: response.data?.filter_meta?.view?.custom_view_id, + }); + dispatch({ + type: t.ACCOUNTS_ITEMS_SET, + accounts: response.data.accounts, + }); + dispatch({ + type: t.ACCOUNTS_TABLE_LOADING, + loading: false, + }); }); resolve(response); }) @@ -243,3 +248,11 @@ export const fetchAccount = ({ id }) => { }); }); }; + + +export const setBulkAction = ({ action }) => { + return (dispatch) => dispatch({ + type: t.ACCOUNTS_BULK_ACTION, + payload: { action } + }); +} \ No newline at end of file diff --git a/client/src/store/accounts/accounts.reducer.js b/client/src/store/accounts/accounts.reducer.js index e385a1fa9..3c304cc89 100644 --- a/client/src/store/accounts/accounts.reducer.js +++ b/client/src/store/accounts/accounts.reducer.js @@ -75,11 +75,6 @@ const accountsReducer = createReducer(initialState, { } }, - [t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => { - const { ids } = action.payload; - state.selectedRows = []; - }, - [t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => { state.currentViewId = action.currentViewId; }, @@ -108,6 +103,11 @@ const accountsReducer = createReducer(initialState, { }); state.items = items; }, + + [t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => { + const { selectedRows } = action.payload; + state.selectedRows = selectedRows; + } }); export default createTableQueryReducers('accounts', accountsReducer); diff --git a/client/src/store/accounts/accounts.selectors.js b/client/src/store/accounts/accounts.selectors.js index 3b1a0af6a..48ea8e010 100644 --- a/client/src/store/accounts/accounts.selectors.js +++ b/client/src/store/accounts/accounts.selectors.js @@ -1,12 +1,15 @@ -import { createSelector } from 'reselect'; -import { repeat } from 'lodash'; +import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect'; +import { repeat, isEqual } from 'lodash'; import { pickItemsFromIds, getItemById, - paginationLocationQuery, } from 'store/selectors'; import { flatToNestedArray, treeToList } from 'utils'; +const createDeepEqualSelector = createSelectorCreator( + defaultMemoize, + isEqual +); const accountsViewsSelector = (state) => state.accounts.views; const accountsDataSelector = (state) => state.accounts.items; const accountsCurrentViewSelector = (state) => state.accounts.currentViewId; @@ -23,7 +26,7 @@ export const getAccountsTableQuery = createSelector( }, ); -export const getAccountsItems = createSelector( +export const getAccountsItems = createDeepEqualSelector( accountsViewsSelector, accountsDataSelector, accountsCurrentViewSelector, @@ -42,7 +45,7 @@ export const getAccountsItems = createSelector( ); export const getAccountsListFactory = () => - createSelector( + createDeepEqualSelector( accountsListSelector, accountsDataSelector, (accountsTree, accountsItems) => { diff --git a/client/src/store/accounts/accounts.types.js b/client/src/store/accounts/accounts.types.js index bdaa96383..283f6d194 100644 --- a/client/src/store/accounts/accounts.types.js +++ b/client/src/store/accounts/accounts.types.js @@ -8,6 +8,8 @@ export default { ACCOUNT_SET: 'ACCOUNT_SET', ACCOUNT_DELETE: 'ACCOUNT_DELETE', ACCOUNT_FORM_ERRORS: 'ACCOUNT_FORM_ERRORS', + ACCOUNTS_BULK_ACTION: 'ACCOUNTS_BULK_ACTION', + CLEAR_ACCOUNT_FORM_ERRORS: 'CLEAR_ACCOUNT_FORM_ERRORS', ACCOUNTS_SELECTED_ROWS_SET: 'ACCOUNTS_SELECTED_ROWS_SET', diff --git a/client/src/store/customViews/customViews.selectors.js b/client/src/store/customViews/customViews.selectors.js index eb83b77cd..52a3295dc 100644 --- a/client/src/store/customViews/customViews.selectors.js +++ b/client/src/store/customViews/customViews.selectors.js @@ -3,10 +3,10 @@ import { pickItemsFromIds } from 'store/selectors'; import { getResourceColumn } from 'store/resources/resources.reducer'; const resourceViewsIdsSelector = (state, props, resourceName) => - state.views.resourceViews[resourceName] || []; + state.views.resourceViews[resourceName]; const viewsSelector = (state) => state.views.views; -const viewByIdSelector = (state, props) => state.views.views[props.viewId] || {}; +const viewByIdSelector = (state, props) => state.views.views[props.viewId]; const viewColumnsSelector = (state, props) => { }; diff --git a/client/src/store/dashboard/dashboard.actions.js b/client/src/store/dashboard/dashboard.actions.js index 0a81cb8b4..2f186efac 100644 --- a/client/src/store/dashboard/dashboard.actions.js +++ b/client/src/store/dashboard/dashboard.actions.js @@ -13,5 +13,21 @@ export function closeDialog(name, payload) { type: t.CLOSE_DIALOG, name: name, payload: payload, - } + }; +} + +export function openAlert(name, payload) { + return { + type: t.OPEN_ALERT, + name, + payload, + }; +} + +export function closeAlert(name, payload) { + return { + type: t.CLOSE_ALERT, + name, + payload, + }; } \ No newline at end of file diff --git a/client/src/store/dashboard/dashboard.reducer.js b/client/src/store/dashboard/dashboard.reducer.js index f4565a424..ae50d8ac9 100644 --- a/client/src/store/dashboard/dashboard.reducer.js +++ b/client/src/store/dashboard/dashboard.reducer.js @@ -11,6 +11,7 @@ const initialState = { sidebarExpended: true, previousSidebarExpended: null, dialogs: {}, + alerts: {}, topbarEditViewId: null, requestsLoading: 0, backLink: false, @@ -47,6 +48,20 @@ const reducerInstance = createReducer(initialState, { }; }, + [t.OPEN_ALERT]: (state, action) => { + state.alerts[action.name] = { + isOpen: true, + payload: action.payload || {}, + }; + }, + + [t.CLOSE_ALERT]: (state, action) => { + state.alerts[action.name] = { + ...state.alerts[action.name], + isOpen: false, + }; + }, + [t.CLOSE_ALL_DIALOGS]: (state, action) => { }, diff --git a/client/src/store/dashboard/dashboard.selectors.js b/client/src/store/dashboard/dashboard.selectors.js index fa5c09445..6212193f3 100644 --- a/client/src/store/dashboard/dashboard.selectors.js +++ b/client/src/store/dashboard/dashboard.selectors.js @@ -14,4 +14,20 @@ export const getDialogPayloadFactory = () => createSelector( (dialog) => { return { ...dialog?.payload }; }, -); \ No newline at end of file +); + +const alertByNameSelector = (state, props) => state.dashboard.alerts?.[props.name]; + +export const isAlertOpenFactory = () => createSelector( + alertByNameSelector, + (alert) => { + return alert && alert.isOpen; + }, +); + +export const getAlertPayloadFactory = () => createSelector( + alertByNameSelector, + (alert) => { + return { ...alert?.payload }; + }, +); diff --git a/client/src/store/dashboard/dashboard.types.js b/client/src/store/dashboard/dashboard.types.js index d57336ce9..083446f75 100644 --- a/client/src/store/dashboard/dashboard.types.js +++ b/client/src/store/dashboard/dashboard.types.js @@ -1,10 +1,11 @@ - - export default { OPEN_DIALOG: 'OPEN_DIALOG', CLOSE_DIALOG: 'CLOSE_DIALOG', + OPEN_ALERT: 'OPEN_ALERT', + CLOSE_ALERT: 'CLOSE_ALERT', CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS', + CLOSE_ALL_ALERTS: 'CLOSE_ALL_ALERTS', CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE', CHANGE_DASHBOARD_PAGE_HINT: 'CHANGE_DASHBOARD_PAGE_HINT', CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE', diff --git a/client/src/style/components/DataTable/DataTable.scss b/client/src/style/components/DataTable/DataTable.scss index 1724bb489..ab26c0f13 100644 --- a/client/src/style/components/DataTable/DataTable.scss +++ b/client/src/style/components/DataTable/DataTable.scss @@ -142,7 +142,7 @@ .tr .td { border-bottom: 1px solid #e8e8e8; align-items: center; - color: #141720; + color: #101219; .placeholder { color: #a0a0a0; diff --git a/client/src/style/containers/Dashboard/Sidebar.scss b/client/src/style/containers/Dashboard/Sidebar.scss index 53b1d4e22..c89f276b9 100644 --- a/client/src/style/containers/Dashboard/Sidebar.scss +++ b/client/src/style/containers/Dashboard/Sidebar.scss @@ -84,7 +84,7 @@ display: block; color: $sidebar-menu-label-color; font-size: 11px; - padding: 8px 18px; + padding: 10px 18px; margin-top: 4px; text-transform: uppercase; font-weight: 500; diff --git a/client/src/style/pages/Dashboard/Dashboard.scss b/client/src/style/pages/Dashboard/Dashboard.scss index 602db1e89..f002dcf5b 100644 --- a/client/src/style/pages/Dashboard/Dashboard.scss +++ b/client/src/style/pages/Dashboard/Dashboard.scss @@ -60,7 +60,6 @@ .bp3-navbar-divider { margin: 0 8px; } - .form-group-quick-new-downDrop { .bp3-popover-target .bp3-button { color: #1552c8; @@ -146,7 +145,7 @@ } .#{$ns}-button { color: #32304a; - padding: 8px 10px; + padding: 8px 12px; &:hover { background: rgba(167, 182, 194, 0.12); @@ -323,7 +322,7 @@ flex: 1 0 0; flex-direction: column; background: #fff; - margin: 20px; + margin: 22px 32px; border: 1px solid #d2dce2; .bigcapital-datatable { @@ -425,15 +424,15 @@ &[aria-selected='true'] { color: #0052cc; + font-weight: 500; } } .#{$ns}-tab-indicator-wrapper { .#{$ns}-tab-indicator { - height: 3px; + height: 4px; } } - .button--new-view { margin: 0; height: 50px; diff --git a/client/src/style/variables.scss b/client/src/style/variables.scss index 22a405399..7eba241bf 100644 --- a/client/src/style/variables.scss +++ b/client/src/style/variables.scss @@ -38,7 +38,7 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62); $sidebar-menu-label-color: rgba(255, 255, 255, 0.45); $sidebar-submenu-item-color: rgba(255, 255, 255, 0.6); $sidebar-submenu-item-hover-color: rgba(255, 255, 255, 0.85); -$sidebar-logo-opacity: 0.55; +$sidebar-logo-opacity: 0.5; $sidebar-submenu-item-bg-color: #01287d; $form-check-input-checked-color: #fff; diff --git a/server/src/api/middleware/EnsureTenantIsInitialized.ts b/server/src/api/middleware/EnsureTenantIsInitialized.ts index e9e3203bc..2f546cb01 100644 --- a/server/src/api/middleware/EnsureTenantIsInitialized.ts +++ b/server/src/api/middleware/EnsureTenantIsInitialized.ts @@ -11,6 +11,7 @@ export default (req: Request, res: Response, next: Function) => { } if (!req.tenant.initializedAt) { Logger.info('[ensure_tenant_initialized_middleware] tenant database not initalized.'); + return res.boom.badRequest( 'Tenant database is not migrated with application schema yut.', { errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }] }, diff --git a/server/src/models/Account.js b/server/src/models/Account.js index 2c5761384..adef91d9b 100644 --- a/server/src/models/Account.js +++ b/server/src/models/Account.js @@ -31,7 +31,7 @@ export default class Account extends TenantModel { static get virtualAttributes() { return [ 'accountTypeLabel', - 'accountParentTypeLabel', + 'accountParentType', 'accountNormal', 'isBalanceSheetAccount', 'isPLSheet' diff --git a/server/src/services/ManualJournals/ManualJournalsService.ts b/server/src/services/ManualJournals/ManualJournalsService.ts index bf0568064..0aece462e 100644 --- a/server/src/services/ManualJournals/ManualJournalsService.ts +++ b/server/src/services/ManualJournals/ManualJournalsService.ts @@ -160,8 +160,7 @@ export default class ManualJournalsService implements IManualJournalsService { const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId); const accounts = await Account.query() - .whereIn('id', manualAccountsIds) - .withGraphFetched('type'); + .whereIn('id', manualAccountsIds); const storedAccountsIds = accounts.map((account) => account.id);