chrone: sperate client and server to different repos.

This commit is contained in:
a.bouhuolia
2021-09-21 17:13:53 +02:00
parent e011b2a82b
commit 18df5530c7
10015 changed files with 17686 additions and 97524 deletions

View File

@@ -0,0 +1,45 @@
import intl from 'react-intl-universal';
import { RESOURCES_TYPES } from '../../common/resourcesTypes';
import withDrawerActions from '../Drawer/withDrawerActions';
function AccountUniversalSearchItemSelectComponent({
// #ownProps
resourceType,
resourceId,
onAction,
// #withDrawerActions
openDrawer,
}) {
if (resourceType === RESOURCES_TYPES.ACCOUNT) {
openDrawer('account-drawer', { accountId: resourceId });
onAction && onAction();
}
return null;
}
export const AccountUniversalSearchItemSelect = withDrawerActions(
AccountUniversalSearchItemSelectComponent,
);
/**
* Transformes account item to search item.
* @param {*} account
* @returns
*/
const accountToSearch = (account) => ({
id: account.id,
text: `${account.name} - ${account.code}`,
label: account.formatted_amount,
reference: account,
});
/**
* Binds universal search account configure.
*/
export const universalSearchAccountBind = () => ({
resourceType: RESOURCES_TYPES.ACCOUNT,
optionItemLabel: intl.get('accounts'),
selectItemAction: AccountUniversalSearchItemSelect,
itemSelect: accountToSearch,
});

View File

@@ -0,0 +1,194 @@
import React from 'react';
import Icon from 'components/Icon';
import { isEmpty } from 'lodash';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
Switch,
Alignment,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import {
AdvancedFilterPopover,
If,
DashboardActionViewsList,
DashboardFilterButton,
} from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useRefreshAccounts } from 'hooks/query/accounts';
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAccounts from 'containers/Accounts/withAccounts';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAccountsTableActions from './withAccountsTableActions';
import { compose } from 'utils';
/**
* Accounts actions bar.
*/
function AccountsActionsBar({
// #withDialogActions
openDialog,
// #withAccounts
accountsSelectedRows,
accountsInactiveMode,
accountsFilterConditions,
// #withAlertActions
openAlert,
// #withAccountsTableActions
setAccountsTableState,
// #ownProps
onFilterChanged,
}) {
const { resourceViews, fields } = useAccountsChartContext();
const onClickNewAccount = () => {
openDialog('account-form', {});
};
// Accounts refresh action.
const { refresh } = useRefreshAccounts();
// Handle bulk accounts delete.
const handleBulkDelete = () => {
openAlert('accounts-bulk-delete', { accountsIds: accountsSelectedRows });
};
// Handle bulk accounts activate.
const handelBulkActivate = () => {
openAlert('accounts-bulk-activate', { accountsIds: accountsSelectedRows });
};
// Handle bulk accounts inactivate.
const handelBulkInactive = () => {
openAlert('accounts-bulk-inactivate', {
accountsIds: accountsSelectedRows,
});
};
// Handle tab changing.
const handleTabChange = (view) => {
setAccountsTableState({ viewSlug: view ? view.slug : null });
};
// Handle inactive switch changing.
const handleInactiveSwitchChange = (event) => {
const checked = event.target.checked;
setAccountsTableState({ inactiveMode: checked });
};
// Handle click a refresh accounts
const handleRefreshBtnClick = () => {
refresh();
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'accounts'}
allMenuItem={true}
allMenuItemText={<T id={'all_accounts'} />}
views={resourceViews}
onChange={handleTabChange}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="plus" />}
text={<T id={'new_account'} />}
onClick={onClickNewAccount}
/>
<AdvancedFilterPopover
advancedFilterProps={{
conditions: accountsFilterConditions,
defaultFieldKey: 'name',
fields: fields,
onFilterChange: (filterConditions) => {
setAccountsTableState({ filterRoles: filterConditions });
},
}}
>
<DashboardFilterButton
conditionsCount={accountsFilterConditions.length}
/>
</AdvancedFilterPopover>
<NavbarDivider />
<If condition={!isEmpty(accountsSelectedRows)}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="play-16" iconSize={16} />}
text={<T id={'activate'} />}
onClick={handelBulkActivate}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pause-16" iconSize={16} />}
text={<T id={'inactivate'} />}
onClick={handelBulkInactive}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleBulkDelete}
/>
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />}
/>
<Switch
labelElement={<T id={'inactive'} />}
defaultChecked={accountsInactiveMode}
onChange={handleInactiveSwitchChange}
/>
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />}
onClick={handleRefreshBtnClick}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withAlertActions,
withAccounts(({ accountsSelectedRows, accountsTableState }) => ({
accountsSelectedRows,
accountsInactiveMode: accountsTableState.inactiveMode,
accountsFilterConditions: accountsTableState.filterRoles,
})),
withAccountsTableActions,
)(AccountsActionsBar);

View File

@@ -0,0 +1,26 @@
import React from 'react';
import AccountDeleteAlert from 'containers/Alerts/AccountDeleteAlert';
import AccountInactivateAlert from 'containers/Alerts/AccountInactivateAlert';
import AccountActivateAlert from 'containers/Alerts/AccountActivateAlert';
// import AccountBulkDeleteAlert from 'containers/Alerts/AccountBulkDeleteAlert';
// import AccountBulkInactivateAlert from 'containers/Alerts/AccountBulkInactivateAlert';
// import AccountBulkActivateAlert from 'containers/Alerts/AccountBulkActivateAlert';
/**
* Accounts alert.
*/
export default function AccountsAlerts({
}) {
return (
<div class="accounts-alerts">
<AccountDeleteAlert name={'account-delete'} />
<AccountInactivateAlert name={'account-inactivate'} />
<AccountActivateAlert name={'account-activate'} />
{/* <AccountBulkDeleteAlert name={'accounts-bulk-delete'} />
<AccountBulkInactivateAlert name={'accounts-bulk-inactivate'} />
<AccountBulkActivateAlert name={'accounts-bulk-activate'} /> */}
</div>
)
}

View File

@@ -0,0 +1,64 @@
import React, { useEffect } from 'react';
import 'style/pages/Accounts/List.scss';
import { DashboardPageContent, DashboardContentTable } from 'components';
import { AccountsChartProvider } from './AccountsChartProvider';
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
import AccountsAlerts from './AccountsAlerts';
import AccountsDataTable from './AccountsDataTable';
import withAccounts from 'containers/Accounts/withAccounts';
import { compose } from 'utils';
import { transformAccountsStateToQuery } from './utils';
import withAccountsTableActions from './withAccountsTableActions';
/**
* Accounts chart list.
*/
function AccountsChart({
// #withAccounts
accountsTableState,
accountsTableStateChanged,
// #withAccountsActions
resetAccountsTableState,
}) {
// Resets the accounts table state once the page unmount.
useEffect(
() => () => {
resetAccountsTableState();
},
[resetAccountsTableState],
);
return (
<AccountsChartProvider
query={transformAccountsStateToQuery(accountsTableState)}
tableStateChanged={accountsTableStateChanged}
>
<AccountsActionsBar />
<DashboardPageContent>
<AccountsViewsTabs />
<DashboardContentTable>
<AccountsDataTable />
</DashboardContentTable>
</DashboardPageContent>
<AccountsAlerts />
</AccountsChartProvider>
);
}
export default compose(
withAccounts(({ accountsTableState, accountsTableStateChanged }) => ({
accountsTableState,
accountsTableStateChanged,
})),
withAccountsTableActions,
)(AccountsChart);

View File

@@ -0,0 +1,58 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceMeta, useAccounts } from 'hooks/query';
import { getFieldsFromResourceMeta } from 'utils';
const AccountsChartContext = createContext();
/**
* Accounts chart data provider.
*/
function AccountsChartProvider({ query, tableStateChanged,...props }) {
// Fetch accounts resource views and fields.
const { data: resourceViews, isLoading: isViewsLoading } =
useResourceViews('accounts');
// Fetch the accounts resource fields.
const {
data: resourceMeta,
isLoading: isResourceMetaLoading,
isFetching: isResourceMetaFetching,
} = useResourceMeta('accounts');
// Fetch accounts list according to the given custom view id.
const {
data: accounts,
isFetching: isAccountsFetching,
isLoading: isAccountsLoading,
} = useAccounts(query, { keepPreviousData: true });
// Provider payload.
const provider = {
accounts,
resourceMeta,
resourceViews,
fields: getFieldsFromResourceMeta(resourceMeta.fields),
isAccountsLoading,
isAccountsFetching,
isResourceMetaFetching,
isResourceMetaLoading,
isViewsLoading,
};
return (
<DashboardInsider
loading={isViewsLoading || isResourceMetaLoading}
name={'accounts-chart'}
>
<AccountsChartContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useAccountsChartContext = () => React.useContext(AccountsChartContext);
export { AccountsChartProvider, useAccountsChartContext };

View File

@@ -0,0 +1,126 @@
import React from 'react';
import { TableFastCell, DataTable } from 'components';
import { compose } from 'utils';
import { useAccountsTableColumns, rowClassNames } from './utils';
import { ActionsMenu } from './components';
import { TABLES } from 'common/tables';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { useAccountsChartContext } from './AccountsChartProvider';
import { useMemorizedColumnsWidths } from '../../hooks';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
/**
* Accounts data-table.
*/
function AccountsDataTable({
// #withAlertsDialog
openAlert,
// #withDial
openDialog,
// #withDrawerActions
openDrawer,
}) {
const { isAccountsLoading, isAccountsFetching, accounts } =
useAccountsChartContext();
// Retrieve accounts table columns.
const columns = useAccountsTableColumns();
// Handle delete action account.
const handleDeleteAccount = (account) => {
openAlert('account-delete', { accountId: account.id });
};
// Handle activate action account.
const handleActivateAccount = (account) => {
openAlert('account-activate', { accountId: account.id });
};
// Handle inactivate action account.
const handleInactivateAccount = (account) => {
openAlert('account-inactivate', { accountId: account.id });
};
// Handle edit account action.
const handleEditAccount = (account) => {
openDialog('account-form', { action: 'edit', id: account.id });
};
// Handle view detail account.
const handleViewDetailAccount = ({ id }) => {
openDrawer('account-drawer', { accountId: id });
};
// Handle new child button click.
const handleNewChildAccount = (account) => {
openDialog('account-form', {
action: 'new_child',
parentAccountId: account.id,
accountType: account.account_type,
});
};
// Handle cell click.
const handleCellClick = (cell, event) => {
openDrawer('account-drawer', { accountId: cell.row.original.id });
};
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.ACCOUNTS);
return (
<DataTable
noInitialFetch={true}
columns={columns}
data={accounts}
selectionColumn={true}
expandable={true}
sticky={true}
loading={isAccountsLoading}
headerLoading={isAccountsLoading}
progressBarLoading={isAccountsFetching}
rowClassNames={rowClassNames}
autoResetExpanded={false}
autoResetSortBy={false}
autoResetSelectedRows={false}
expandColumnSpace={1}
expandToggleColumn={2}
selectionColumnWidth={45}
TableCellRenderer={TableFastCell}
TableRowsRenderer={TableVirtualizedListRows}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
// #TableVirtualizedListRows props.
vListrowHeight={42}
vListOverscanRowCount={0}
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
payload={{
onEdit: handleEditAccount,
onDelete: handleDeleteAccount,
onActivate: handleActivateAccount,
onInactivate: handleInactivateAccount,
onNewChild: handleNewChildAccount,
onViewDetails: handleViewDetailAccount,
}}
/>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
withDialogActions,
)(AccountsDataTable);

View File

@@ -0,0 +1,59 @@
import React, { useCallback } from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { DashboardViewsTabs } from 'components';
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
import withAccountsTableActions from './withAccountsTableActions';
import withAccounts from './withAccounts';
import { compose, transfromViewsToTabs } from 'utils';
/**
* Accounts views tabs.
*/
function AccountsViewsTabs({
// #withAccountsTableActions
setAccountsTableState,
// #withAccounts
accountsCurrentView
}) {
// Accounts chart context.
const { resourceViews } = useAccountsChartContext();
// Handles the tab change.
const handleTabChange = useCallback(
(viewSlug) => {
setAccountsTableState({
viewSlug: viewSlug || null,
});
},
[setAccountsTableState],
);
// Transfromes the accounts views to tabs.
const tabs = transfromViewsToTabs(resourceViews);
return (
<Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
defaultTabText={intl.get('all_accounts_')}
currentViewSlug={accountsCurrentView}
resourceName={'accounts'}
onChange={handleTabChange}
tabs={tabs}
/>
</NavbarGroup>
</Navbar>
);
}
export default compose(
withAccountsTableActions,
withAccounts(({ accountsTableState }) => ({
accountsCurrentView: accountsTableState.viewSlug
}))
)(AccountsViewsTabs);

View File

@@ -0,0 +1,109 @@
import React from 'react';
import {
Position,
Classes,
Tooltip,
MenuItem,
Menu,
MenuDivider,
Intent,
} from '@blueprintjs/core';
import { Icon, Money, If } from 'components';
import intl from 'react-intl-universal';
import { safeCallback } from 'utils';
/**
* Accounts table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: {
onEdit,
onViewDetails,
onDelete,
onNewChild,
onActivate,
onInactivate,
// onDrawer,
},
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={intl.get('view_details')}
onClick={safeCallback(onViewDetails, original)}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_account')}
onClick={safeCallback(onEdit, original)}
/>
<MenuItem
icon={<Icon icon="plus" />}
text={intl.get('new_child_account')}
onClick={safeCallback(onNewChild, original)}
/>
<MenuDivider />
<If condition={original.active}>
<MenuItem
text={intl.get('inactivate_account')}
icon={<Icon icon="pause-16" iconSize={16} />}
onClick={safeCallback(onInactivate, original)}
/>
</If>
<If condition={!original.active}>
<MenuItem
text={intl.get('activate_account')}
icon={<Icon icon="play-16" iconSize={16} />}
onClick={safeCallback(onActivate, original)}
/>
</If>
<MenuItem
text={intl.get('delete_account')}
icon={<Icon icon="trash-16" iconSize={16} />}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
/**
* Normal cell.
*/
export function NormalCell({ cell: { value } }) {
const arrowDirection = value === 'credit' ? 'down' : 'up';
// Can't continue if the value is not `credit` or `debit`.
if (['credit', 'debit'].indexOf(value) === -1) {
return '';
}
return (
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={intl.get(value)}
position={Position.RIGHT}
hoverOpenDelay={100}
>
<Icon icon={`arrow-${arrowDirection}`} />
</Tooltip>
);
}
/**
* Balance cell.
*/
export function BalanceCell({ cell }) {
const account = cell.row.original;
return account.amount !== null ? (
<span>
{account.formatted_amount}
{/* <Money amount={account.amount} currency={account.currency_code} /> */}
</span>
) : (
<span class="placeholder"></span>
);
}

View File

@@ -0,0 +1,124 @@
import React from 'react';
import { Intent, Tag } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { CLASSES } from '../../common/classes';
import { If, AppToaster } from 'components';
import { NormalCell, BalanceCell } from './components';
import { transformTableStateToQuery, isBlank } from 'utils';
/**
* Account name accessor.
*/
export const accountNameAccessor = (account) => {
return (
<span>
<span class={'account-name'}>{account.name}</span>
<If condition={account.description}>
<span class={'account-desc'}>{account.description}</span>
</If>
</span>
);
};
/**
* Handle delete errors in bulk and singular.
*/
export const handleDeleteErrors = (errors) => {
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
AppToaster.show({
message: intl.get('you_could_not_delete_predefined_accounts'),
intent: Intent.DANGER,
});
}
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
AppToaster.show({
message: intl.get('cannot_delete_account_has_associated_transactions'),
intent: Intent.DANGER,
});
}
};
export const AccountCodeAccessor = (row) =>
!isBlank(row.code) ? (
<Tag minimal={true} round={true} intent={Intent.NONE}>
{row.code}
</Tag>
) : null;
/**
* Accounts table columns.
*/
export const useAccountsTableColumns = () => {
return React.useMemo(
() => [
{
id: 'name',
Header: intl.get('account_name'),
accessor: 'name',
className: 'account_name',
width: 200,
clickable: true,
textOverview: true,
},
{
id: 'code',
Header: intl.get('code'),
accessor: AccountCodeAccessor,
className: 'code',
width: 80,
clickable: true,
},
{
id: 'type',
Header: intl.get('type'),
accessor: 'account_type_label',
className: 'type',
width: 140,
clickable: true,
textOverview: true,
},
{
id: 'normal',
Header: intl.get('account_normal'),
Cell: NormalCell,
accessor: 'account_normal',
className: 'normal',
width: 80,
clickable: true,
},
{
id: 'currency',
Header: intl.get('currency'),
accessor: 'currency_code',
width: 75,
clickable: true,
},
{
id: 'balance',
Header: intl.get('balance'),
accessor: 'amount',
Cell: BalanceCell,
width: 150,
clickable: true,
align: 'right',
},
],
[],
);
};
export const rowClassNames = (row) => ({
inactive: !row.original.active,
});
/**
* Transformes the table state to list query.
*/
export const transformAccountsStateToQuery = (tableState) => {
return {
...transformTableStateToQuery(tableState),
inactive_mode: tableState.inactiveMode,
}
}

View File

@@ -0,0 +1,20 @@
import { connect } from 'react-redux';
import {
getAccountsTableStateFactory,
accountsTableStateChangedFactory,
} from 'store/accounts/accounts.selectors';
export default (mapState) => {
const getAccountsTableState = getAccountsTableStateFactory();
const accountsTableStateChanged = accountsTableStateChangedFactory();
const mapStateToProps = (state, props) => {
const mapped = {
accountsTableState: getAccountsTableState(state, props),
accountsTableStateChanged: accountsTableStateChanged(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import {
setAccountsTableState,
resetAccountsTableState,
} from 'store/accounts/accounts.actions';
const mapActionsToProps = (dispatch) => ({
setAccountsTableState: (queries) => dispatch(setAccountsTableState(queries)),
resetAccountsTableState: () => dispatch(resetAccountsTableState()),
});
export default connect(null, mapActionsToProps);