From 42230fe64b067c07d840123e63dbdf2e9778d39a Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Tue, 16 Mar 2021 17:27:27 +0200 Subject: [PATCH] feat(FinancialReports): add loading progress bar. fix(preformance): Optimize preformance of virtualized list. fix(preformance): Optimize financial reports preformance. --- client/src/components/App.js | 2 +- client/src/components/Dashboard/Dashboard.js | 21 ++--- .../Dashboard/DashboardLoadingIndicator.js | 2 +- .../components/Dashboard/DashboardProvider.js | 16 ++++ .../src/components/Dashboard/PrivatePages.js | 40 ++------- .../Dashboard/PrivatePagesProvider.js | 17 ++++ client/src/components/Datatable/TableCell.js | 2 +- .../src/components/Datatable/TableHeader.js | 6 +- client/src/components/Datatable/TableRow.js | 86 ++++++++++++------- client/src/components/Datatable/TableRows.js | 4 +- .../Datatable/TableVirtualizedRows.js | 59 ++++++------- client/src/components/FinancialSheet.js | 77 ++++++++--------- .../Guards/EnsureOrganizationIsNotReady.js | 3 + client/src/components/Guards/PrivateRoute.js | 6 +- client/src/components/Utils/If.js | 2 +- client/src/components/index.js | 4 +- .../containers/Accounts/AccountsDataTable.js | 2 +- client/src/containers/Accounts/components.js | 6 +- .../APAgingSummary/APAgingSummary.js | 3 + .../APAgingSummary/APAgingSummaryProvider.js | 12 ++- .../APAgingSummary/APAgingSummaryTable.js | 4 +- .../APAgingSummary/components.js | 17 ++++ .../ARAgingSummary/ARAgingSummary.js | 3 + .../ARAgingSummary/ARAgingSummaryProvider.js | 9 +- .../ARAgingSummary/ARAgingSummaryTable.js | 4 +- .../ARAgingSummary/components.js | 17 ++++ .../BalanceSheet/BalanceSheet.js | 4 +- .../BalanceSheet/BalanceSheetProvider.js | 14 +-- .../BalanceSheet/components.js | 18 +++- .../FinancialLoadingBar.js | 13 +++ .../FinancialStatements/FinancialReports.js | 9 +- .../GeneralLedger/GeneralLedger.js | 6 ++ .../GeneralLedger/GeneralLedgerProvider.js | 18 ++-- .../GeneralLedger/GeneralLedgerTable.js | 26 +++--- .../GeneralLedger/components.js | 52 +++++++++++ .../FinancialStatements/Journal/Journal.js | 14 +-- .../Journal/JournalProvider.js | 23 +++-- .../Journal/JournalTable.js | 12 ++- .../FinancialStatements/Journal/components.js | 51 +++++++++++ .../ProfitLossSheet/ProfitLossProvider.js | 16 ++-- .../ProfitLossSheet/ProfitLossSheet.js | 23 +++-- .../ProfitLossSheet/components.js | 51 +++++++++++ .../TrialBalanceSheet/TrialBalanceProvider.js | 19 ++-- .../TrialBalanceSheet/TrialBalanceSheet.js | 19 +++- .../TrialBalanceSheet/components.js | 51 ++++++++++- .../src/containers/Items/ItemsListProvider.js | 4 +- .../Bills/BillsLanding/BillsListProvider.js | 4 +- .../PaymentForm/PaymentMadeFormProvider.js | 12 +-- .../EstimatesLanding/EstimatesListProvider.js | 4 +- .../InvoicesLanding/InvoicesListProvider.js | 4 +- .../PaymentReceiveFormProvider.js | 10 +-- .../ReceiptsLanding/ReceiptsListProvider.js | 4 +- client/src/hooks/query/items.js | 2 +- client/src/hooks/query/organization.js | 79 ++++++++++++++++- client/src/hooks/query/settings.js | 12 +-- client/src/hooks/query/types.js | 3 +- client/src/hooks/state/authentication.js | 11 ++- client/src/hooks/state/index.js | 5 +- client/src/hooks/state/organizations.js | 11 +++ client/src/hooks/state/settings.js | 12 +++ client/src/hooks/state/subscriptions.js | 14 +++ client/src/hooks/useRequest.js | 11 ++- .../authentication/authentication.reducer.js | 2 + .../organizations/organizations.actions.js | 9 ++ client/src/store/settings/settings.actions.js | 8 ++ .../subscription/subscription.actions.js | 12 ++- client/src/style/App.scss | 77 +++++++++++++++++ .../style/components/DataTable/DataTable.scss | 15 ++++ .../FinancialReportPage.scss | 10 ++- .../pages/FinancialStatements/Journal.scss | 4 + client/src/utils.js | 4 +- server/src/api/controllers/Organization.ts | 68 +++++++++------ server/src/services/Organization/index.ts | 15 ++++ 73 files changed, 969 insertions(+), 320 deletions(-) create mode 100644 client/src/components/Dashboard/DashboardProvider.js create mode 100644 client/src/components/Dashboard/PrivatePagesProvider.js create mode 100644 client/src/containers/FinancialStatements/FinancialLoadingBar.js create mode 100644 client/src/containers/FinancialStatements/ProfitLossSheet/components.js create mode 100644 client/src/hooks/state/organizations.js create mode 100644 client/src/hooks/state/settings.js create mode 100644 client/src/hooks/state/subscriptions.js diff --git a/client/src/components/App.js b/client/src/components/App.js index e1cfeb14b..9df37df3c 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -19,7 +19,7 @@ function App({ locale }) { const queryConfig = { defaultOptions: { queries: { - refetchOnWindowFocus: false, + refetchOnWindowFocus: true, staleTime: 30000, }, }, diff --git a/client/src/components/Dashboard/Dashboard.js b/client/src/components/Dashboard/Dashboard.js index 905506b10..c9039f7a5 100644 --- a/client/src/components/Dashboard/Dashboard.js +++ b/client/src/components/Dashboard/Dashboard.js @@ -4,8 +4,6 @@ import { useQuery } from 'react-query'; import 'style/pages/Dashboard/Dashboard.scss'; -import DashboardLoadingIndicator from './DashboardLoadingIndicator'; - import Sidebar from 'components/Sidebar/Sidebar'; import DashboardContent from 'components/Dashboard/DashboardContent'; import DialogsContainer from 'components/DialogsContainer'; @@ -13,22 +11,15 @@ import PreferencesPage from 'components/Preferences/PreferencesPage'; import Search from 'containers/GeneralSearch/Search'; import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane'; import GlobalHotkeys from './GlobalHotkeys'; -import withSettingsActions from 'containers/Settings/withSettingsActions'; +import DashboardProvider from './DashboardProvider'; import DrawersContainer from 'components/DrawersContainer'; -import { compose } from 'utils'; - /** * Dashboard page. */ -function Dashboard({ - // #withSettings - requestFetchOptions, -}) { - const fetchOptions = useQuery(['options'], () => requestFetchOptions()); - +export default function Dashboard() { return ( - + @@ -49,8 +40,6 @@ function Dashboard({ - + ); -} - -export default compose(withSettingsActions)(Dashboard); +} \ No newline at end of file diff --git a/client/src/components/Dashboard/DashboardLoadingIndicator.js b/client/src/components/Dashboard/DashboardLoadingIndicator.js index bbd981a43..e9e7a710b 100644 --- a/client/src/components/Dashboard/DashboardLoadingIndicator.js +++ b/client/src/components/Dashboard/DashboardLoadingIndicator.js @@ -10,7 +10,7 @@ export default function DashboardLoadingIndicator({ className, children, }) { - return ( + return ( diff --git a/client/src/components/Dashboard/DashboardProvider.js b/client/src/components/Dashboard/DashboardProvider.js new file mode 100644 index 000000000..ee2f0ed65 --- /dev/null +++ b/client/src/components/Dashboard/DashboardProvider.js @@ -0,0 +1,16 @@ +import React from 'react'; +import DashboardLoadingIndicator from './DashboardLoadingIndicator'; +import { useSettings } from 'hooks/query'; + +/** + * Dashboard provider. + */ +export default function DashboardProvider({ children }) { + const { isLoading } = useSettings(); + + return ( + + { children } + + ) +} \ No newline at end of file diff --git a/client/src/components/Dashboard/PrivatePages.js b/client/src/components/Dashboard/PrivatePages.js index 1534d4078..0d0b5d06c 100644 --- a/client/src/components/Dashboard/PrivatePages.js +++ b/client/src/components/Dashboard/PrivatePages.js @@ -1,46 +1,21 @@ import React from 'react'; import { Switch, Route } from 'react-router'; -import { useQuery } from 'react-query'; import Dashboard from 'components/Dashboard/Dashboard'; import SetupWizardPage from 'containers/Setup/WizardSetupPage'; -import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator'; - -import withOrganizationActions from 'containers/Organization/withOrganizationActions'; -import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions'; import EnsureOrganizationIsReady from 'components/Guards/EnsureOrganizationIsReady'; import EnsureOrganizationIsNotReady from 'components/Guards/EnsureOrganizationIsNotReady'; - -import { compose } from 'utils'; +import { PrivatePagesProvider } from './PrivatePagesProvider'; import 'style/pages/Dashboard/Dashboard.scss'; /** * Dashboard inner private pages. */ -function DashboardPrivatePages({ - // #withOrganizationActions - requestAllOrganizations, - - // #withSubscriptionsActions - requestFetchSubscriptions, -}) { - // Fetches all user's organizatins. - const fetchOrganizations = useQuery( - ['organizations'], () => requestAllOrganizations(), - ); - - // Fetches organization subscriptions. - const fetchSuscriptions = useQuery( - ['susbcriptions'], () => requestFetchSubscriptions(), - ) - +export default function DashboardPrivatePages() { return ( - + @@ -54,11 +29,6 @@ function DashboardPrivatePages({ - + ); -} - -export default compose( - withOrganizationActions, - withSubscriptionsActions, -)(DashboardPrivatePages); \ No newline at end of file +} \ No newline at end of file diff --git a/client/src/components/Dashboard/PrivatePagesProvider.js b/client/src/components/Dashboard/PrivatePagesProvider.js new file mode 100644 index 000000000..34db3a813 --- /dev/null +++ b/client/src/components/Dashboard/PrivatePagesProvider.js @@ -0,0 +1,17 @@ +import React from 'react'; +import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator'; +import { useCurrentOrganization } from '../../hooks/query/organization'; + +/** + * Private pages provider. + */ +export function PrivatePagesProvider({ children }) { + // Fetches the current user's organization. + const { isLoading } = useCurrentOrganization(); + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/client/src/components/Datatable/TableCell.js b/client/src/components/Datatable/TableCell.js index a1d2eac3e..46621de5e 100644 --- a/client/src/components/Datatable/TableCell.js +++ b/client/src/components/Datatable/TableCell.js @@ -62,7 +62,7 @@ export default function TableCell({ 'cell-inner', )} style={{ - 'padding-left': + paddingLeft: isExpandColumn && expandable ? `${depth * expandColumnSpace}rem` : '', diff --git a/client/src/components/Datatable/TableHeader.js b/client/src/components/Datatable/TableHeader.js index 28f87a877..b15c04617 100644 --- a/client/src/components/Datatable/TableHeader.js +++ b/client/src/components/Datatable/TableHeader.js @@ -66,7 +66,7 @@ function TableHeaderGroup({ headerGroup }) { return (
{headerGroup.headers.map((column, index) => ( - + ))}
); @@ -87,8 +87,8 @@ export default function TableHeader() { return (
- {headerGroups.map((headerGroup) => ( - + {headerGroups.map((headerGroup, index) => ( + ))} diff --git a/client/src/components/Datatable/TableRow.js b/client/src/components/Datatable/TableRow.js index c519dd116..4762b506e 100644 --- a/client/src/components/Datatable/TableRow.js +++ b/client/src/components/Datatable/TableRow.js @@ -1,23 +1,18 @@ -import React, { useContext } from 'react'; +import React, { useCallback, useContext } from 'react'; +import { ContextMenu } from 'components'; import classNames from 'classnames'; import useContextMenu from 'react-use-context-menu'; import TableContext from './TableContext'; -import { saveInvoke } from 'utils'; -import { ContextMenu } from 'components'; - +import { saveInvoke, ConditionalWrapper } from 'utils'; /** - * Table row. + * Table row context wrapper. */ -export default function TableRow({ row, className, style }) { +function TableRowContextMenu({ children, row }) { + // Table context. const { - props: { - TableCellRenderer, - rowContextMenu, - rowClassNames, - ContextMenu: ContextMenuContent, - }, + props: { ContextMenu: ContextMenuContent }, table, } = useContext(TableContext); @@ -32,33 +27,64 @@ export default function TableRow({ row, className, style }) { collect: () => 'Title', }); + const handleClose = useCallback(() => { + setVisible(false); + }, [setVisible]); + return ( -
- {row.cells.map((cell, index) => ( - - ))} +
+ {children} setVisible(false)} + onClosed={handleClose} >
); } + +/** + * Table row. + */ +export default function TableRow({ row, className, style }) { + const { + props: { + TableCellRenderer, + rowClassNames, + ContextMenu: ContextMenuContent, + }, + } = useContext(TableContext); + + return ( +
+ + {row.cells.map((cell, index) => ( + + ))} + +
+ ); +} diff --git a/client/src/components/Datatable/TableRows.js b/client/src/components/Datatable/TableRows.js index cd27400f0..976ccbe42 100644 --- a/client/src/components/Datatable/TableRows.js +++ b/client/src/components/Datatable/TableRows.js @@ -10,8 +10,8 @@ export default function TableRows() { props: { TableRowRenderer, TableCellRenderer }, } = useContext(TableContext); - return page.map((row) => { + return page.map((row, index) => { prepareRow(row); - return ; + return ; }); } \ No newline at end of file diff --git a/client/src/components/Datatable/TableVirtualizedRows.js b/client/src/components/Datatable/TableVirtualizedRows.js index 10c8e751e..e3689bb67 100644 --- a/client/src/components/Datatable/TableVirtualizedRows.js +++ b/client/src/components/Datatable/TableVirtualizedRows.js @@ -3,11 +3,13 @@ import { WindowScroller, AutoSizer, List } from 'react-virtualized'; import { CLASSES } from 'common/classes'; import TableContext from './TableContext'; +/** + * Table virtualized list row. + */ function TableVirtualizedListRow({ index, isScrolling, isVisible, - key, style, }) { const { @@ -18,7 +20,7 @@ function TableVirtualizedListRow({ const row = page[index]; prepareRow(row); - return ; + return (); } /** @@ -27,39 +29,38 @@ function TableVirtualizedListRow({ export default function TableVirtualizedListRows() { const { table: { page }, - props: { vListrowHeight, vListOverscanRowCount } + props: { vListrowHeight, vListOverscanRowCount }, } = useContext(TableContext); // Dashboard content pane. - const dashboardContentPane = document.querySelector( + const dashboardContentPane = React.useMemo(()=> document.querySelector( `.${CLASSES.DASHBOARD_CONTENT_PANE}`, - ); + ), []); + + const rowRenderer = React.useCallback(({ key, ...args }) => ( + + ), []); + return ( - {({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => ( -
- - {({ width }) => ( -
- { - return ; - }} - scrollTop={scrollTop} - width={width} - /> -
- )} -
-
+ {({ height, isScrolling, onChildScroll, scrollTop }) => ( + + {({ width }) => ( + + )} + )}
); diff --git a/client/src/components/FinancialSheet.js b/client/src/components/FinancialSheet.js index 181747f35..0f2373090 100644 --- a/client/src/components/FinancialSheet.js +++ b/client/src/components/FinancialSheet.js @@ -7,7 +7,6 @@ import 'style/pages/FinancialStatements/FinancialSheet.scss'; import { If, LoadingIndicator, MODIFIER } from 'components'; - export default function FinancialSheet({ companyName, sheetType, @@ -57,49 +56,47 @@ export default function FinancialSheet({ 'is-full-width': fullWidth, })} > - - -
- -

{companyName}

-
- - -
{sheetType}
-
- -
- - {formattedAsDate} + {loading ? ( + + ) : ( +
+ +

{companyName}

- - {formattedFromDate} | {' '} - {formattedToDate} + +
{sheetType}
+ +
+ + {formattedAsDate} + + + + {formattedFromDate} | {' '} + {formattedToDate} + +
+ +
{children}
+
{accountingBasis}
+ +
- -
{children}
-
{accountingBasis}
- - -
+ )}
); } diff --git a/client/src/components/Guards/EnsureOrganizationIsNotReady.js b/client/src/components/Guards/EnsureOrganizationIsNotReady.js index 33bfa6b53..cdbfee0cc 100644 --- a/client/src/components/Guards/EnsureOrganizationIsNotReady.js +++ b/client/src/components/Guards/EnsureOrganizationIsNotReady.js @@ -5,6 +5,9 @@ import { compose } from 'utils'; import withAuthentication from 'containers/Authentication/withAuthentication'; import withOrganization from 'containers/Organization/withOrganization'; +/** + * Ensures organization is not ready. + */ function EnsureOrganizationIsNotReady({ children, diff --git a/client/src/components/Guards/PrivateRoute.js b/client/src/components/Guards/PrivateRoute.js index e50904cd0..9fa112a1e 100644 --- a/client/src/components/Guards/PrivateRoute.js +++ b/client/src/components/Guards/PrivateRoute.js @@ -11,11 +11,7 @@ export default function PrivateRoute({ component: Component, ...rest }) { {isAuthenticated ? ( ) : ( - + )} ); diff --git a/client/src/components/Utils/If.js b/client/src/components/Utils/If.js index f6ae2c418..7ed0a6750 100644 --- a/client/src/components/Utils/If.js +++ b/client/src/components/Utils/If.js @@ -5,7 +5,7 @@ const If = props => props.condition ? (props.render ? props.render() : props.children) : null; If.propTypes = { - condition: PropTypes.bool.isRequired, + // condition: PropTypes.bool.isRequired, children: PropTypes.node, render: PropTypes.func }; diff --git a/client/src/components/index.js b/client/src/components/index.js index 4be74d6b4..25d53d033 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -53,6 +53,7 @@ import Drawer from './Drawer/Drawer'; import DrawerSuspense from './Drawer/DrawerSuspense'; import Postbox from './Postbox'; import AccountsSuggestField from './AccountsSuggestField'; +import MaterialProgressBar from './MaterialProgressBar'; const Hint = FieldHint; @@ -112,5 +113,6 @@ export { Drawer, DrawerSuspense, Postbox, - AccountsSuggestField + AccountsSuggestField, + MaterialProgressBar }; diff --git a/client/src/containers/Accounts/AccountsDataTable.js b/client/src/containers/Accounts/AccountsDataTable.js index 876c605bb..c89eba307 100644 --- a/client/src/containers/Accounts/AccountsDataTable.js +++ b/client/src/containers/Accounts/AccountsDataTable.js @@ -99,7 +99,7 @@ function AccountsDataTable({ // #TableVirtualizedListRows props. vListrowHeight={42} - vListOverscanRowCount={10} + vListOverscanRowCount={0} payload={{ onEdit: handleEditAccount, diff --git a/client/src/containers/Accounts/components.js b/client/src/containers/Accounts/components.js index 5c0721fab..1453e4fe6 100644 --- a/client/src/containers/Accounts/components.js +++ b/client/src/containers/Accounts/components.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo } from 'react'; import { Position, Classes, @@ -75,7 +75,7 @@ export function ActionsMenu({ /** * Actions cell. */ -export const ActionsCell = (props) => { +export function ActionsCell(props) { return ( { +
+
+ ); +} + +/** + * General ledger sheet loading bar. + */ +export function GeneralLedgerSheetLoadingBar() { + const { + isFetching, + } = useGeneralLedgerContext(); + + return ( + + + + ) +} diff --git a/client/src/containers/FinancialStatements/Journal/Journal.js b/client/src/containers/FinancialStatements/Journal/Journal.js index d238cfc85..5eeca928d 100644 --- a/client/src/containers/FinancialStatements/Journal/Journal.js +++ b/client/src/containers/FinancialStatements/Journal/Journal.js @@ -1,20 +1,21 @@ import React, { useState, useCallback, useEffect } from 'react'; import moment from 'moment'; -import { useIntl } from 'react-intl'; -import { compose } from 'utils'; +import 'style/pages/FinancialStatements/Journal.scss'; + +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; + import JournalTable from './JournalTable'; - import JournalHeader from './JournalHeader'; import JournalActionsBar from './JournalActionsBar'; -import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import { JournalSheetProvider } from './JournalProvider'; +import { JournalSheetLoadingBar, JournalSheetAlerts } from './components'; import withSettings from 'containers/Settings/withSettings'; import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withJournalActions from './withJournalActions'; -import 'style/pages/FinancialStatements/Journal.scss'; +import { compose } from 'utils'; /** * Journal sheet. @@ -60,6 +61,9 @@ function Journal({ onSubmitFilter={handleFilterSubmit} pageFilter={filter} /> + + +
+ - + ); } diff --git a/client/src/containers/FinancialStatements/Journal/JournalTable.js b/client/src/containers/FinancialStatements/Journal/JournalTable.js index 1d8ced6ea..6b53b78c2 100644 --- a/client/src/containers/FinancialStatements/Journal/JournalTable.js +++ b/client/src/containers/FinancialStatements/Journal/JournalTable.js @@ -4,7 +4,8 @@ import { useIntl } from 'react-intl'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; import { useJournalSheetContext } from './JournalProvider'; - +import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows'; +import TableFastCell from 'components/Datatable/TableFastCell'; import { defaultExpanderReducer } from 'utils'; import { useJournalTableColumns } from './components'; @@ -41,6 +42,8 @@ export default function JournalSheetTable({ }; }, []); + + return ( ); diff --git a/client/src/containers/FinancialStatements/Journal/components.js b/client/src/containers/FinancialStatements/Journal/components.js index 34d95feb3..c8667a724 100644 --- a/client/src/containers/FinancialStatements/Journal/components.js +++ b/client/src/containers/FinancialStatements/Journal/components.js @@ -1,6 +1,10 @@ import React from 'react'; import { useIntl } from 'react-intl'; import moment from 'moment'; +import { Button } from '@blueprintjs/core'; +import { Icon, If } from 'components'; +import { useJournalSheetContext } from './JournalProvider'; +import FinancialLoadingBar from '../FinancialLoadingBar'; /** * Retrieve the journal table columns. @@ -60,3 +64,50 @@ export const useJournalTableColumns = () => { [formatMessage], ); }; + +/** + * Journal sheet loading bar. + */ +export function JournalSheetLoadingBar() { + const { + isFetching + } = useJournalSheetContext(); + + return ( + + + + ) +} + +/** + * Journal sheet alerts. + */ + export function JournalSheetAlerts() { + const { + isLoading, + refetchSheet, + journalSheet, + } = useJournalSheetContext(); + + // Handle refetch the report sheet. + const handleRecalcReport = () => { + refetchSheet(); + }; + // Can't display any error if the report is loading. + if (isLoading) { return null; } + + return ( + +
+ Just a moment! We're + calculating your cost transactions and this doesn't take much time. + Please check after sometime.{' '} + + +
+
+ ); +} \ No newline at end of file diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossProvider.js b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossProvider.js index 71d0d8e87..81aa7a99c 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossProvider.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossProvider.js @@ -1,25 +1,31 @@ import React, { createContext, useContext } from 'react'; -import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import FinancialReportPage from '../FinancialReportPage'; import { useProfitLossSheet } from 'hooks/query'; import { transformFilterFormToQuery } from '../common'; const ProfitLossSheetContext = createContext(); function ProfitLossSheetProvider({ query, ...props }) { - const { data: profitLossSheet, isFetching, refetch } = useProfitLossSheet({ + const { + data: profitLossSheet, + isFetching, + isLoading, + refetch, + } = useProfitLossSheet({ ...transformFilterFormToQuery(query), }); const provider = { profitLossSheet, - isLoading: isFetching, + isLoading, + isFetching, sheetRefetch: refetch, }; return ( - + - + ); } diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js index e2fb6c2af..e65eba735 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet.js @@ -14,6 +14,10 @@ import withSettings from 'containers/Settings/withSettings'; import 'style/pages/FinancialStatements/ProfitLossSheet.scss'; import { ProfitLossSheetProvider } from './ProfitLossProvider'; +import { + ProfitLossSheetLoadingBar, + ProfitLossSheetAlerts +} from './components'; /** * Profit/Loss financial statement sheet. @@ -23,7 +27,7 @@ function ProfitLossSheet({ organizationName, // #withProfitLossActions - toggleProfitLossFilterDrawer: toggleDisplayFilterDrawer + toggleProfitLossFilterDrawer: toggleDisplayFilterDrawer, }) { const [filter, setFilter] = useState({ basis: 'cash', @@ -32,7 +36,7 @@ function ProfitLossSheet({ displayColumnsType: 'total', accountsFilter: 'all-accounts', }); - + // Handle submit filter. const handleSubmitFilter = (filter) => { const _filter = { @@ -52,9 +56,12 @@ function ProfitLossSheet({ }; // Hide the filter drawer once the page unmount. - React.useEffect(() => () => { - toggleDisplayFilterDrawer(false); - }, [toggleDisplayFilterDrawer]) + React.useEffect( + () => () => { + toggleDisplayFilterDrawer(false); + }, + [toggleDisplayFilterDrawer], + ); return ( @@ -62,6 +69,8 @@ function ProfitLossSheet({ numberFormat={filter.numberFormat} onNumberFormatSubmit={handleNumberFormatSubmit} /> + +
@@ -71,9 +80,7 @@ function ProfitLossSheet({ />
- +
diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/components.js b/client/src/containers/FinancialStatements/ProfitLossSheet/components.js new file mode 100644 index 000000000..6e69b4438 --- /dev/null +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/components.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { Button } from '@blueprintjs/core'; +import { Icon, If } from 'components'; +import { useProfitLossSheetContext } from './ProfitLossProvider'; +import FinancialLoadingBar from '../FinancialLoadingBar'; + +/** + * Profit/loss sheet loading bar. + */ +export function ProfitLossSheetLoadingBar() { + const { isFetching } = useProfitLossSheetContext(); + + return ( + + + + ); +} + +/** + * Balance sheet alerts. + */ +export function ProfitLossSheetAlerts() { + const { + isLoading, + sheetRefetch, + profitLossSheet, + } = useProfitLossSheetContext(); + + // Handle refetch the report sheet. + const handleRecalcReport = () => { + sheetRefetch(); + }; + // Can't display any error if the report is loading. + if (isLoading) { + return null; + } + + return ( + +
+ Just a moment! We're + calculating your cost transactions and this doesn't take much time. + Please check after sometime.{' '} + +
+
+ ); +} diff --git a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceProvider.js b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceProvider.js index 4446dbb46..72996193d 100644 --- a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceProvider.js +++ b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceProvider.js @@ -1,27 +1,36 @@ import React, { createContext, useContext } from 'react'; -import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import FinancialReportPage from '../FinancialReportPage'; import { useTrialBalanceSheet } from 'hooks/query'; import { transformFilterFormToQuery } from '../common'; const TrialBalanceSheetContext = createContext(); function TrialBalanceSheetProvider({ query, ...props }) { - const { data: trialBalanceSheet, isFetching, refetch } = useTrialBalanceSheet( + const { + data: trialBalanceSheet, + isFetching, + isLoading, + refetch, + } = useTrialBalanceSheet( { ...transformFilterFormToQuery(query), }, + { + keepPreviousData: true, + }, ); const provider = { trialBalanceSheet, - isLoading: isFetching, + isLoading, + isFetching, refetchSheet: refetch, }; return ( - + - + ); } diff --git a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.js b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.js index d2c149eb2..6bb6fce25 100644 --- a/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.js +++ b/client/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.js @@ -9,6 +9,11 @@ import TrialBalanceSheetHeader from './TrialBalanceSheetHeader'; import TrialBalanceSheetTable from './TrialBalanceSheetTable'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; +import { + TrialBalanceSheetAlerts, + TrialBalanceSheetLoadingBar, +} from './components'; + import withTrialBalanceActions from './withTrialBalanceActions'; import withSettings from 'containers/Settings/withSettings'; @@ -22,7 +27,7 @@ function TrialBalanceSheet({ organizationName, // #withTrialBalanceSheetActions - toggleTrialBalanceFilterDrawer: toggleFilterDrawer + toggleTrialBalanceFilterDrawer: toggleFilterDrawer, }) { const [filter, setFilter] = useState({ fromDate: moment().startOf('year').format('YYYY-MM-DD'), @@ -53,9 +58,12 @@ function TrialBalanceSheet({ }; // Hide the filter drawer once the page unmount. - useEffect(() => () => { - toggleFilterDrawer(false) - }, [toggleFilterDrawer]); + useEffect( + () => () => { + toggleFilterDrawer(false); + }, + [toggleFilterDrawer], + ); return ( @@ -63,6 +71,9 @@ function TrialBalanceSheet({ numberFormat={filter.numberFormat} onNumberFormatSubmit={handleNumberFormatSubmit} /> + + +
{ [tableRows, formatMessage], ); }; + +/** + * Trial balance sheet progress loading bar. + */ +export function TrialBalanceSheetLoadingBar() { + const { + isFetching + } = useTrialBalanceSheetContext(); + + return ( + + + + ) +} + +/** + * Trial balance sheet alerts. + */ +export function TrialBalanceSheetAlerts() { + const { + trialBalanceSheet: { meta }, + isLoading, + refetchSheet + } = useTrialBalanceSheetContext(); + + // Handle refetch the sheet. + const handleRecalcReport = () => { + refetchSheet(); + }; + // Can't display any error if the report is loading. + if (isLoading) { return null; } + + return ( + +
+ Just a moment! We're + calculating your cost transactions and this doesn't take much time. + Please check after sometime.{' '} + + +
+
+ ) +} \ No newline at end of file diff --git a/client/src/containers/Items/ItemsListProvider.js b/client/src/containers/Items/ItemsListProvider.js index d381264e4..a2b71ae64 100644 --- a/client/src/containers/Items/ItemsListProvider.js +++ b/client/src/containers/Items/ItemsListProvider.js @@ -17,12 +17,12 @@ function ItemsListProvider({ ...props }) { // Fetch accounts resource views and fields. - const { data: itemsViews, isFetching: isViewsLoading } = useResourceViews( + const { data: itemsViews, isLoading: isViewsLoading } = useResourceViews( 'items', ); // Fetch the accounts resource fields. - const { data: itemsFields, isFetching: isFieldsLoading } = useResourceFields( + const { data: itemsFields, isLoading: isFieldsLoading } = useResourceFields( 'items', ); diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js index 931bc50e5..432dea87e 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js @@ -10,14 +10,14 @@ const BillsListContext = createContext(); */ function BillsListProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: billsViews, isFetching: isViewsLoading } = useResourceViews( + const { data: billsViews, isLoading: isViewsLoading } = useResourceViews( 'bills', ); // Fetch the accounts resource fields. const { data: billsFields, - isFetching: isFieldsLoading, + isLoading: isFieldsLoading, } = useResourceFields('bills'); // Fetch accounts list according to the given custom view id. diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js index 3cc2eaec8..2ac1deaee 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js @@ -21,7 +21,7 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { const [paymentVendorId, setPaymentVendorId] = React.useState(null); // Handle fetch accounts data. - const { data: accounts, isFetching: isAccountsFetching } = useAccounts(); + const { data: accounts, isLoading: isAccountsLoading } = useAccounts(); // Handle fetch Items data table or list. const { @@ -33,7 +33,7 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { // Handle fetch venders data table or list. const { data: { vendors }, - isFetching: isVendorsFetching, + isLoading: isVendorsLoading, } = useVendors({ page_size: 10000 }); // Handle fetch specific payment made details. @@ -66,10 +66,10 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { paymentVendorId, isNewMode, - isAccountsFetching, + isAccountsLoading, isItemsFetching, isItemsLoading, - isVendorsFetching, + isVendorsLoading, isPaymentFetching, isPaymentLoading, @@ -83,9 +83,9 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { return ( diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js index 5c0668b5a..edbccc319 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js @@ -1,6 +1,6 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useResourceFields, useReceipts } from 'hooks/query'; +import { useResourceViews, useReceipts } from 'hooks/query'; import { isTableEmptyStatus } from 'utils'; const ReceiptsListContext = createContext(); @@ -8,7 +8,7 @@ const ReceiptsListContext = createContext(); // Receipts list provider. function ReceiptsListProvider({ query, ...props }) { // Fetch receipts resource views and fields. - const { data: receiptsViews, isFetching: isViewsLoading } = useResourceViews( + const { data: receiptsViews, isLoading: isViewsLoading } = useResourceViews( 'sale_receipt', ); diff --git a/client/src/hooks/query/items.js b/client/src/hooks/query/items.js index 8013949d1..3e417a44a 100644 --- a/client/src/hooks/query/items.js +++ b/client/src/hooks/query/items.js @@ -1,4 +1,4 @@ -import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useMutation, useQueryClient } from 'react-query'; import { transformPagination, transformResponse } from 'utils'; import { useQueryTenant } from '../useQueryTenant'; import useApiRequest from '../useRequest'; diff --git a/client/src/hooks/query/organization.js b/client/src/hooks/query/organization.js index 46bbfe988..8feeaf259 100644 --- a/client/src/hooks/query/organization.js +++ b/client/src/hooks/query/organization.js @@ -1,9 +1,13 @@ -import t from 'store/types'; +import { useMutation } from 'react-query'; +import t from './types'; import useApiRequest from '../useRequest'; import { useQueryTenant } from '../useQueryTenant'; +import { useEffect } from 'react'; +import { useSetOrganizations, useSetSubscriptions } from '../state'; +import { omit } from 'lodash'; /** - * Retrieve the contact duplicate. + * Retrieve organizations of the authenticated user. */ export function useOrganizations(props) { const apiRequest = useApiRequest(); @@ -23,3 +27,74 @@ export function useOrganizations(props) { }, ); } + +/** + * Retrieve the current organization metadata. + */ +export function useCurrentOrganization(props) { + const apiRequest = useApiRequest(); + const setOrganizations = useSetOrganizations(); + const setSubscriptions = useSetSubscriptions(); + + const query = useQueryTenant( + [t.ORGANIZATION_CURRENT], + () => apiRequest.get(`organization/current`), + { + select: (res) => res.data.organization, + initialDataUpdatedAt: 0, + initialData: { + data: { + organization: {}, + }, + }, + ...props, + }, + ); + + useEffect(() => { + if (query.isSuccess) { + const organization = omit(query.data, ['subscriptions']); + + // Sets organizations. + setOrganizations([organization]); + + // Sets subscriptions. + setSubscriptions(query.data.subscriptions); + } + }, [query.data, query.isSuccess, setOrganizations, setSubscriptions]); + + return query; +} + +/** + * Builds the current tenant. + */ +export function useBuildTenant(props) { + const apiRequest = useApiRequest(); + + return useMutation( + (values) => apiRequest.post('organization/build'), + { + onSuccess: (res, values) => { + + }, + ...props, + }, + ); +}; + +/** + * Seeds the current tenant + */ +export function useSeedTenant() { + const apiRequest = useApiRequest(); + + return useMutation( + (values) => apiRequest.post('organization/seed'), + { + onSuccess: (res) => { + + }, + } + ) +}; \ No newline at end of file diff --git a/client/src/hooks/query/settings.js b/client/src/hooks/query/settings.js index b91463b92..559052565 100644 --- a/client/src/hooks/query/settings.js +++ b/client/src/hooks/query/settings.js @@ -1,9 +1,9 @@ import { useEffect } from 'react'; import { useMutation, useQueryClient } from 'react-query'; import { useQueryTenant } from '../useQueryTenant'; -import { useDispatch } from 'react-redux'; import useApiRequest from '../useRequest'; -import t from 'store/types'; +import { useSetSettings } from 'hooks/state'; +import t from './types'; /** * Saves the settings. @@ -21,8 +21,8 @@ export function useSaveSettings(props) { } function useSettingsQuery(key, query, props) { - const dispatch = useDispatch(); const apiRequest = useApiRequest(); + const setSettings = useSetSettings(); const state = useQueryTenant( key, @@ -40,10 +40,10 @@ function useSettingsQuery(key, query, props) { ); useEffect(() => { - if (typeof state.data !== 'undefined') { - dispatch({ type: t.SETTING_SET, options: state.data }); + if (state.isSuccess) { + setSettings(state.data); } - }, [state.data, dispatch]); + }, [state.data, state.isSuccess, setSettings]); return state.data; } diff --git a/client/src/hooks/query/types.js b/client/src/hooks/query/types.js index 766230392..716b0015f 100644 --- a/client/src/hooks/query/types.js +++ b/client/src/hooks/query/types.js @@ -90,7 +90,8 @@ const SETTING = { }; const ORGANIZATIONS = { - ORGANIZATIONS: 'ORGANIZATIONS' + ORGANIZATIONS: 'ORGANIZATIONS', + ORGANIZATION_CURRENT: 'ORGANIZATION_CURRENT' }; export default { diff --git a/client/src/hooks/state/authentication.js b/client/src/hooks/state/authentication.js index bb74b8d94..5af99dfd5 100644 --- a/client/src/hooks/state/authentication.js +++ b/client/src/hooks/state/authentication.js @@ -2,13 +2,22 @@ import { useDispatch, useSelector } from 'react-redux'; import { useCallback } from 'react'; import { isAuthenticated } from 'store/authentication/authentication.reducer'; import { setLogin, setLogout } from 'store/authentication/authentication.actions'; +import { useQueryClient } from 'react-query'; export const useAuthActions = () => { const dispatch = useDispatch(); + const queryClient = useQueryClient(); return { setLogin: useCallback((login) => dispatch(setLogin(login)), [dispatch]), - setLogout: useCallback(() => dispatch(setLogout()), [dispatch]), + setLogout: useCallback(() => { + + // Logout action. + dispatch(setLogout()); + + // Remove all cached queries. + queryClient.removeQueries(); + }, [dispatch, queryClient]), }; }; diff --git a/client/src/hooks/state/index.js b/client/src/hooks/state/index.js index 4208d88aa..75580b504 100644 --- a/client/src/hooks/state/index.js +++ b/client/src/hooks/state/index.js @@ -1,3 +1,6 @@ export * from './dashboard'; export * from './authentication'; -export * from './globalErrors'; \ No newline at end of file +export * from './globalErrors'; +export * from './subscriptions'; +export * from './organizations'; +export * from './settings'; \ No newline at end of file diff --git a/client/src/hooks/state/organizations.js b/client/src/hooks/state/organizations.js new file mode 100644 index 000000000..56647c500 --- /dev/null +++ b/client/src/hooks/state/organizations.js @@ -0,0 +1,11 @@ +import { useCallback } from "react"; +import { useDispatch } from "react-redux"; +import { setOrganizations } from 'store/organizations/organizations.actions'; + +export const useSetOrganizations = () => { + const dispatch = useDispatch(); + + return useCallback((organizations) => { + dispatch(setOrganizations(organizations)) + }, [dispatch]); +}; \ No newline at end of file diff --git a/client/src/hooks/state/settings.js b/client/src/hooks/state/settings.js new file mode 100644 index 000000000..b26393c21 --- /dev/null +++ b/client/src/hooks/state/settings.js @@ -0,0 +1,12 @@ +import { useCallback } from "react"; +import { useDispatch } from "react-redux"; +import { setSettings } from 'store/settings/settings.actions'; + + +export const useSetSettings = () => { + const dispatch = useDispatch(); + + return useCallback((settings) => { + dispatch(setSettings(settings)); + }, [dispatch]); +}; \ No newline at end of file diff --git a/client/src/hooks/state/subscriptions.js b/client/src/hooks/state/subscriptions.js new file mode 100644 index 000000000..7da587908 --- /dev/null +++ b/client/src/hooks/state/subscriptions.js @@ -0,0 +1,14 @@ +import { useCallback } from "react" +import { useDispatch } from "react-redux"; +import { setSubscriptions } from 'store/subscription/subscription.actions'; + +/** + * Sets subscriptions. + */ +export const useSetSubscriptions = () => { + const dispatch = useDispatch(); + + return useCallback((subscriptions) => { + dispatch(setSubscriptions(subscriptions)); + }, [dispatch]); +} \ No newline at end of file diff --git a/client/src/hooks/useRequest.js b/client/src/hooks/useRequest.js index f3cc834c6..be5909ed0 100644 --- a/client/src/hooks/useRequest.js +++ b/client/src/hooks/useRequest.js @@ -18,10 +18,11 @@ export default function useApiRequest() { const organizationId = useAuthOrganizationId(); const http = React.useMemo(() => { - axios.create(); + // Axios instance. + const instance = axios.create(); // Request interceptors. - axios.interceptors.request.use( + instance.interceptors.request.use( (request) => { const locale = 'en'; @@ -40,9 +41,8 @@ export default function useApiRequest() { return Promise.reject(error); }, ); - // Response interceptors. - axios.interceptors.response.use( + instance.interceptors.response.use( (response) => response, (error) => { const { status } = error.response; @@ -57,8 +57,7 @@ export default function useApiRequest() { return Promise.reject(error); }, ); - - return axios; + return instance; }, [token, organizationId, setGlobalErrors, setLogout]); return { diff --git a/client/src/store/authentication/authentication.reducer.js b/client/src/store/authentication/authentication.reducer.js index 12db81283..eb7e66097 100644 --- a/client/src/store/authentication/authentication.reducer.js +++ b/client/src/store/authentication/authentication.reducer.js @@ -29,6 +29,8 @@ export default createReducer(initialState, { state.token = ''; state.user = {}; state.organization = ''; + state.organizationId = null; + state.tenant = {}; }, [t.LOGIN_CLEAR_ERRORS]: (state) => { diff --git a/client/src/store/organizations/organizations.actions.js b/client/src/store/organizations/organizations.actions.js index ec44a62d4..e49a23393 100644 --- a/client/src/store/organizations/organizations.actions.js +++ b/client/src/store/organizations/organizations.actions.js @@ -1,6 +1,15 @@ import ApiService from 'services/ApiService'; import t from 'store/types'; +export const setOrganizations = (organizations) => { + return { + type: t.ORGANIZATIONS_LIST_SET, + payload: { + organizations, + }, + }; +} + export const fetchOrganizations = () => (dispatch) => new Promise((resolve, reject) => { ApiService.get('organization/all').then((response) => { dispatch({ diff --git a/client/src/store/settings/settings.actions.js b/client/src/store/settings/settings.actions.js index f4560cd20..3eea4aefa 100644 --- a/client/src/store/settings/settings.actions.js +++ b/client/src/store/settings/settings.actions.js @@ -29,3 +29,11 @@ export const FetchOptions = ({ form }) => { }); }); }; + + +export const setSettings = (settings) => { + return { + type: t.SETTING_SET, + options: settings, + }; +} \ No newline at end of file diff --git a/client/src/store/subscription/subscription.actions.js b/client/src/store/subscription/subscription.actions.js index 28ef9d8f2..bd765a782 100644 --- a/client/src/store/subscription/subscription.actions.js +++ b/client/src/store/subscription/subscription.actions.js @@ -11,4 +11,14 @@ export const fetchSubscriptions = () => (dispatch) => new Promise((resolve, reje }); resolve(response); }).catch((error) => { reject(error); }) -}); \ No newline at end of file +}); + + +export const setSubscriptions = (subscriptions) => { + return { + type: t.SET_PLAN_SUBSCRIPTIONS_LIST, + payload: { + subscriptions, + }, + } +}; \ No newline at end of file diff --git a/client/src/style/App.scss b/client/src/style/App.scss index 8cebb9fcc..32073e3d9 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -100,3 +100,80 @@ body.hide-scrollbar .Pane2{ background-color: rgba(0,10,30, .7); } + +.ReactVirtualized__Collection { +} + +.ReactVirtualized__Collection__innerScrollContainer { +} + +/* Grid default theme */ + +.ReactVirtualized__Grid { +} + +.ReactVirtualized__Grid__innerScrollContainer { +} + +/* Table default theme */ + +.ReactVirtualized__Table { +} + +.ReactVirtualized__Table__Grid { +} + +.ReactVirtualized__Table__headerRow { + font-weight: 700; + text-transform: uppercase; + display: flex; + flex-direction: row; + align-items: center; +} +.ReactVirtualized__Table__row { + display: flex; + flex-direction: row; + align-items: center; +} + +.ReactVirtualized__Table__headerTruncatedText { + display: inline-block; + max-width: 100%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.ReactVirtualized__Table__headerColumn, +.ReactVirtualized__Table__rowColumn { + margin-right: 10px; + min-width: 0px; +} +.ReactVirtualized__Table__rowColumn { + text-overflow: ellipsis; + white-space: nowrap; +} + +.ReactVirtualized__Table__headerColumn:first-of-type, +.ReactVirtualized__Table__rowColumn:first-of-type { + margin-left: 10px; +} +.ReactVirtualized__Table__sortableHeaderColumn { + cursor: pointer; +} + +.ReactVirtualized__Table__sortableHeaderIconContainer { + display: flex; + align-items: center; +} +.ReactVirtualized__Table__sortableHeaderIcon { + flex: 0 0 24px; + height: 1em; + width: 1em; + fill: currentColor; +} + +/* List default theme */ + +.ReactVirtualized__List { +} \ No newline at end of file diff --git a/client/src/style/components/DataTable/DataTable.scss b/client/src/style/components/DataTable/DataTable.scss index 92b1de9a8..64ec85bf8 100644 --- a/client/src/style/components/DataTable/DataTable.scss +++ b/client/src/style/components/DataTable/DataTable.scss @@ -68,6 +68,11 @@ .bp3-context-menu-popover-target{ z-index: 100; } + + .tr-context{ + display: flex; + flex: 1 0 auto; + } } .th, @@ -256,6 +261,11 @@ } } } + + .tr-inner{ + display: flex; + width: 100%; + } } .no-results { @@ -314,4 +324,9 @@ overflow-x: hidden; } } + + + .ReactVirtualized__Grid{ + will-change: auto !important; + } } diff --git a/client/src/style/pages/FinancialStatements/FinancialReportPage.scss b/client/src/style/pages/FinancialStatements/FinancialReportPage.scss index 4172db4c0..213fcd485 100644 --- a/client/src/style/pages/FinancialStatements/FinancialReportPage.scss +++ b/client/src/style/pages/FinancialStatements/FinancialReportPage.scss @@ -9,7 +9,7 @@ border-radius: 2px; background-color: #fdecda; color: #342515; - font-size: 12px; + font-size: 13px; button{ font-size: 12px; @@ -29,4 +29,10 @@ fill: #975f19; } } -} + + .financial-progressbar{ + .progress-materializecss{ + top: -2px; + } + } +} \ No newline at end of file diff --git a/client/src/style/pages/FinancialStatements/Journal.scss b/client/src/style/pages/FinancialStatements/Journal.scss index f841d3a6c..d544ee1b7 100644 --- a/client/src/style/pages/FinancialStatements/Journal.scss +++ b/client/src/style/pages/FinancialStatements/Journal.scss @@ -36,6 +36,10 @@ font-weight: 600; } + .tr{ + height: 28px; + } + } } } diff --git a/client/src/utils.js b/client/src/utils.js index 75978d6d9..f6deeee19 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -178,8 +178,8 @@ export function formattedExchangeRate(amount, currency) { return formatter.format(amount); } -export const ConditionalWrapper = ({ condition, wrapper, children }) => - condition ? wrapper(children) : children; +export const ConditionalWrapper = ({ condition, wrapper, children, ...rest }) => + condition ? wrapper({ children, ...rest }) : children; export const checkRequiredProperties = (obj, properties) => { return properties.some((prop) => { diff --git a/server/src/api/controllers/Organization.ts b/server/src/api/controllers/Organization.ts index 4153837a8..e89de8750 100644 --- a/server/src/api/controllers/Organization.ts +++ b/server/src/api/controllers/Organization.ts @@ -1,6 +1,6 @@ import { Inject, Service } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; -import asyncMiddleware from "api/middleware/asyncMiddleware"; +import asyncMiddleware from 'api/middleware/asyncMiddleware'; import JWTAuth from 'api/middleware/jwtAuth'; import TenancyMiddleware from 'api/middleware/TenancyMiddleware'; import SubscriptionMiddleware from 'api/middleware/SubscriptionMiddleware'; @@ -12,7 +12,7 @@ import EnsureConfiguredMiddleware from 'api/middleware/EnsureConfiguredMiddlewar import SettingsMiddleware from 'api/middleware/SettingsMiddleware'; @Service() -export default class OrganizationController extends BaseController{ +export default class OrganizationController extends BaseController { @Inject() organizationService: OrganizationService; @@ -22,12 +22,12 @@ export default class OrganizationController extends BaseController{ router() { const router = Router(); - // Should before build tenant database the user be authorized and + // Should before build tenant database the user be authorized and // most important than that, should be subscribed to any plan. router.use(JWTAuth); router.use(AttachCurrentTenantUser); router.use(TenancyMiddleware); - + // Should to seed organization tenant be configured. router.use('/seed', SubscriptionMiddleware('main')); router.use('/seed', SettingsMiddleware); @@ -35,17 +35,12 @@ export default class OrganizationController extends BaseController{ router.use('/build', SubscriptionMiddleware('main')); - router.post( - '/build', - asyncMiddleware(this.build.bind(this)) - ); - router.post( - '/seed', - asyncMiddleware(this.seed.bind(this)), - ); + router.post('/build', asyncMiddleware(this.build.bind(this))); + router.post('/seed', asyncMiddleware(this.seed.bind(this))); + router.get('/all', asyncMiddleware(this.allOrganizations.bind(this))); router.get( - '/all', - asyncMiddleware(this.allOrganizations.bind(this)), + '/current', + asyncMiddleware(this.currentOrganization.bind(this)) ); return router; } @@ -54,19 +49,19 @@ export default class OrganizationController extends BaseController{ * Builds tenant database and migrate database schema. * @param {Request} req - Express request. * @param {Response} res - Express response. - * @param {NextFunction} next + * @param {NextFunction} next */ async build(req: Request, res: Response, next: Function) { const { organizationId } = req.tenant; const { user } = req; - + try { await this.organizationService.build(organizationId, user); return res.status(200).send({ type: 'success', code: 'ORGANIZATION.DATABASE.INITIALIZED', - message: 'The organization database has been initialized.' + message: 'The organization database has been initialized.', }); } catch (error) { if (error instanceof ServiceError) { @@ -87,9 +82,9 @@ export default class OrganizationController extends BaseController{ /** * Seeds initial data to tenant database. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async seed(req: Request, res: Response, next: Function) { const { organizationId } = req.tenant; @@ -100,7 +95,7 @@ export default class OrganizationController extends BaseController{ return res.status(200).send({ type: 'success', code: 'ORGANIZATION.DATABASE.SEED', - message: 'The organization database has been seeded.' + message: 'The organization database has been seeded.', }); } catch (error) { if (error instanceof ServiceError) { @@ -126,18 +121,39 @@ export default class OrganizationController extends BaseController{ /** * Listing all organizations that assocaited to the authorized user. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ async allOrganizations(req: Request, res: Response, next: NextFunction) { const { user } = req; try { - const organizations = await this.organizationService.listOrganizations(user); + const organizations = await this.organizationService.listOrganizations( + user + ); return res.status(200).send({ organizations }); } catch (error) { next(error); } } -} \ No newline at end of file + + /** + * Retrieve the current organization of the associated authenticated user. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async currentOrganization(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + + try { + const organization = await this.organizationService.currentOrganization( + tenantId + ); + return res.status(200).send({ organization }); + } catch (error) { + next(error); + } + } +} diff --git a/server/src/services/Organization/index.ts b/server/src/services/Organization/index.ts index 45e5e9b75..e81ed40d8 100644 --- a/server/src/services/Organization/index.ts +++ b/server/src/services/Organization/index.ts @@ -19,6 +19,7 @@ const ERRORS = { TENANT_ALREADY_SEEDED: 'tenant_already_seeded', TENANT_DB_NOT_BUILT: 'tenant_db_not_built', }; + @Service() export default class OrganizationService { @EventDispatcher() @@ -111,6 +112,20 @@ export default class OrganizationService { return [tenant]; } + /** + * Retrieve the current organization metadata. + * @param {number} tenantId + * @returns {Promise} + */ + public async currentOrganization(tenantId: number): Promise { + const { tenantRepository } = this.sysRepositories; + const tenant = await tenantRepository.findOneById(tenantId, ['subscriptions']); + + this.throwIfTenantNotExists(tenant); + + return tenant; + } + /** * Throws error in case the given tenant is undefined. * @param {ITenant} tenant