mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: fix accounts issue.
This commit is contained in:
@@ -8,15 +8,10 @@ import DialogsContainer from 'components/DialogsContainer';
|
|||||||
import PreferencesContent from 'components/Preferences/PreferencesContent';
|
import PreferencesContent from 'components/Preferences/PreferencesContent';
|
||||||
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
||||||
import Search from 'containers/GeneralSearch/Search';
|
import Search from 'containers/GeneralSearch/Search';
|
||||||
import withDashboard from 'containers/Dashboard/withDashboard';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
export default function Dashboard() {
|
||||||
|
|
||||||
function Dashboard({ sidebarExpended }) {
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('dashboard', {
|
<div className={classNames('dashboard')}>
|
||||||
'has-mini-sidebar': !sidebarExpended,
|
|
||||||
})}>
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/preferences">
|
<Route path="/preferences">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
@@ -35,9 +30,3 @@ function Dashboard({ sidebarExpended }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withDashboard(({ sidebarExpended }) => ({
|
|
||||||
sidebarExpended,
|
|
||||||
})),
|
|
||||||
)(Dashboard);
|
|
||||||
|
|||||||
79
client/src/components/Dashboard/DashboardViewsTabs.js
Normal file
79
client/src/components/Dashboard/DashboardViewsTabs.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
|
||||||
|
import { If, Icon } from 'components';
|
||||||
|
|
||||||
|
export default function DashboardViewsTabs({
|
||||||
|
tabs,
|
||||||
|
allTab = true,
|
||||||
|
newViewTab = true,
|
||||||
|
onNewViewTabClick,
|
||||||
|
onChange,
|
||||||
|
onTabClick,
|
||||||
|
}) {
|
||||||
|
const [currentView, setCurrentView] = useState(0);
|
||||||
|
|
||||||
|
const handleClickNewView = () => {
|
||||||
|
onNewViewTabClick && onNewViewTabClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabClick = (viewId) => {
|
||||||
|
onTabClick && onTabClick(viewId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappedTabs = useMemo(
|
||||||
|
() => tabs.map((tab) => ({ ...tab, onTabClick: handleTabClick })),
|
||||||
|
[tabs],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleViewLinkClick = () => {
|
||||||
|
onNewViewTabClick && onNewViewTabClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabsChange = (viewId) => {
|
||||||
|
setCurrentView(viewId);
|
||||||
|
onChange && onChange(viewId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
id="navbar"
|
||||||
|
large={true}
|
||||||
|
selectedTabId={currentView}
|
||||||
|
className="tabs--dashboard-views"
|
||||||
|
onChange={handleTabsChange}
|
||||||
|
>
|
||||||
|
{allTab && (
|
||||||
|
<Tab id={0} title={<T id={'all'} />} onClick={handleViewLinkClick} />
|
||||||
|
)}
|
||||||
|
{mappedTabs.map((tab) => (
|
||||||
|
<Tab id={tab.id} title={tab.name} onClick={handleTabClick} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<If condition={newViewTab}>
|
||||||
|
<Tooltip
|
||||||
|
content={<T id={'create_a_new_view'} />}
|
||||||
|
position={Position.RIGHT}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="button--new-view"
|
||||||
|
icon={<Icon icon="plus" />}
|
||||||
|
onClick={handleClickNewView}
|
||||||
|
minimal={true}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</If>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DashboardViewsTabs.propTypes = {
|
||||||
|
tabs: PropTypes.array.isRequired,
|
||||||
|
allTab: PropTypes.bool,
|
||||||
|
newViewTab: PropTypes.bool,
|
||||||
|
|
||||||
|
onNewViewTabClick: PropTypes.func,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onTabClick: PropTypes.func,
|
||||||
|
};
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { isDialogOpen, getDialogPayload } from 'store/dashboard/dashboard.selectors';
|
||||||
|
|
||||||
export default (Dialog) => {
|
export default (Dialog) => {
|
||||||
|
|
||||||
function DialogReduxConnect(props) {
|
function DialogReduxConnect(props) {
|
||||||
return (<Dialog {...props} />);
|
return (<Dialog {...props} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const dialogs = state.dashboard.dialogs;
|
return {
|
||||||
|
isOpen: isDialogOpen(state, props),
|
||||||
if (dialogs && dialogs.hasOwnProperty['name'] && dialogs[props.name]) {
|
payload: getDialogPayload(state, props),
|
||||||
const { isOpen, payload } = dialogs[props.name];
|
};
|
||||||
return { isOpen, payload };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return connect(
|
return connect(
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { HTMLSelect, Classes } from '@blueprintjs/core';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { getConditionTypeCompatators } from './DynamicFilterCompatators';
|
||||||
|
|
||||||
|
export default function DynamicFilterCompatatorField({
|
||||||
|
dataType,
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const options = useMemo(
|
||||||
|
() => getConditionTypeCompatators(dataType).map(comp => ({
|
||||||
|
value: comp.value, label: formatMessage({ id: comp.label_id }),
|
||||||
|
})),
|
||||||
|
[dataType]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HTMLSelect
|
||||||
|
options={options}
|
||||||
|
className={Classes.FILL}
|
||||||
|
{...{ ...restProps }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export const BooleanCompatators = [
|
||||||
|
{ value: 'is', label_id: 'is' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TextCompatators = [
|
||||||
|
{ value: 'contain', label_id: 'contain' },
|
||||||
|
{ value: 'not_contain', label_id: 'not_contain' },
|
||||||
|
{ value: 'equals', label_id: 'equals' },
|
||||||
|
{ value: 'not_equal', label_id: 'not_equals' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const DateCompatators = [
|
||||||
|
{ value: 'in', label_id: 'in' },
|
||||||
|
{ value: 'after', label_id: 'after' },
|
||||||
|
{ value: 'before', label_id: 'before' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OptionsCompatators = [
|
||||||
|
{ value: 'is', label_id: 'is' },
|
||||||
|
{ value: 'is_not', label_id: 'is_not' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getConditionTypeCompatators = (dataType) => {
|
||||||
|
return [
|
||||||
|
...(dataType === 'options'
|
||||||
|
? [...OptionsCompatators]
|
||||||
|
: dataType === 'date'
|
||||||
|
? [...DateCompatators]
|
||||||
|
: dataType === 'boolean'
|
||||||
|
? [...BooleanCompatators]
|
||||||
|
: [...TextCompatators]),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConditionDefaultCompatator = (dataType) => {
|
||||||
|
const compatators = getConditionTypeCompatators(dataType);
|
||||||
|
return compatators[0];
|
||||||
|
};
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
||||||
import { FormGroup, MenuItem, InputGroup, Position, Spinner } from '@blueprintjs/core';
|
import {
|
||||||
|
FormGroup,
|
||||||
|
MenuItem,
|
||||||
|
InputGroup,
|
||||||
|
Position,
|
||||||
|
Checkbox,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
@@ -13,35 +19,42 @@ import { If, Choose, ListSelect, MODIFIER } from 'components';
|
|||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getConditionTypeCompatators,
|
||||||
|
getConditionDefaultCompatator,
|
||||||
|
} from './DynamicFilterCompatators';
|
||||||
|
|
||||||
import { compose, momentFormatter } from 'utils';
|
import { compose, momentFormatter } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic filter fields.
|
* Dynamic filter fields.
|
||||||
*/
|
*/
|
||||||
function DynamicFilterValueField({
|
function DynamicFilterValueField({
|
||||||
fieldMeta,
|
dataType,
|
||||||
value,
|
value,
|
||||||
|
|
||||||
initialValue,
|
initialValue,
|
||||||
error,
|
error,
|
||||||
// fieldkey,
|
// fieldkey,
|
||||||
// resourceKey,
|
// resourceKey,
|
||||||
|
|
||||||
|
// #withResourceDetail
|
||||||
resourceName,
|
resourceName,
|
||||||
resourceData,
|
resourceData,
|
||||||
|
|
||||||
requestResourceData,
|
requestResourceData,
|
||||||
|
|
||||||
onChange,
|
onChange,
|
||||||
|
rosourceKey,
|
||||||
|
|
||||||
inputDebounceWait = 500,
|
inputDebounceWait = 500,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [localValue, setLocalValue] = useState();
|
const [localValue, setLocalValue] = useState();
|
||||||
|
|
||||||
|
|
||||||
const fetchResourceData = useQuery(
|
const fetchResourceData = useQuery(
|
||||||
['resource-data', resourceName],
|
resourceName && ['resource-data', resourceName],
|
||||||
() => requestResourceData(resourceName),
|
(k, resName) => requestResourceData(resName),
|
||||||
{ manual: true },
|
{ manual: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -57,7 +70,7 @@ function DynamicFilterValueField({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBtnClick = () => {
|
const handleBtnClick = () => {
|
||||||
fetchResourceData.refetch({ force: true });
|
fetchResourceData.refetch({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const listOptions = useMemo(() => Object.values(resourceData), [
|
const listOptions = useMemo(() => Object.values(resourceData), [
|
||||||
@@ -85,75 +98,85 @@ function DynamicFilterValueField({
|
|||||||
onChange && onChange(value);
|
onChange && onChange(value);
|
||||||
}, inputDebounceWait),
|
}, inputDebounceWait),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
setLocalValue(e.currentTarget.value);
|
if (e.currentTarget.type === 'checkbox') {
|
||||||
|
setLocalValue(e.currentTarget.checked);
|
||||||
|
} else {
|
||||||
|
setLocalValue(e.currentTarget.value);
|
||||||
|
}
|
||||||
handleInputChangeThrottled.current(e.currentTarget.value);
|
handleInputChangeThrottled.current(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCheckboxChange = (e) => {
|
||||||
|
const value = !!e.currentTarget.checked;
|
||||||
|
setLocalValue(value);
|
||||||
|
onChange && onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
const handleDateChange = (date) => {
|
const handleDateChange = (date) => {
|
||||||
setLocalValue(date);
|
setLocalValue(date);
|
||||||
onChange && onChange(date);
|
onChange && onChange(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformDateValue = (value) => {
|
const transformDateValue = (value) => {
|
||||||
return moment(value || new Date()).toDate();
|
return value ? moment(value || new Date()).toDate() : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup className={'form-group--value'}>
|
<FormGroup className={'form-group--value'}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={true}>
|
<Choose.When condition={dataType === 'options'}>
|
||||||
<Spinner size={18} />
|
<ListSelect
|
||||||
|
className={classNames(
|
||||||
|
'list-select--filter-dropdown',
|
||||||
|
'form-group--select-list',
|
||||||
|
MODIFIER.SELECT_LIST_FILL_POPOVER,
|
||||||
|
MODIFIER.SELECT_LIST_FILL_BUTTON,
|
||||||
|
)}
|
||||||
|
items={listOptions}
|
||||||
|
itemRenderer={menuItem}
|
||||||
|
loading={fetchResourceData.isFetching}
|
||||||
|
itemPredicate={filterItems}
|
||||||
|
popoverProps={{
|
||||||
|
inline: true,
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
popoverClassName: 'popover--list-select-filter-dropdown',
|
||||||
|
}}
|
||||||
|
onItemSelect={onItemSelect}
|
||||||
|
selectedItem={value}
|
||||||
|
selectedItemProp={'id'}
|
||||||
|
defaultText={<T id={'select_account_type'} />}
|
||||||
|
labelProp={'name'}
|
||||||
|
buttonProps={{ onClick: handleBtnClick }}
|
||||||
|
/>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.When condition={dataType === 'date'}>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
value={transformDateValue(localValue)}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
popoverProps={{
|
||||||
|
minimal: true,
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
}}
|
||||||
|
shortcuts={true}
|
||||||
|
placeholder={'Select date'}
|
||||||
|
/>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.When condition={dataType === 'boolean'}>
|
||||||
|
<Checkbox value={localValue} onChange={handleCheckboxChange} />
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Choose>
|
<InputGroup
|
||||||
<Choose.When condition={fieldMeta.data_type === 'options'}>
|
placeholder={formatMessage({ id: 'value' })}
|
||||||
<ListSelect
|
onChange={handleInputChange}
|
||||||
className={classNames(
|
value={localValue}
|
||||||
'list-select--filter-dropdown',
|
/>
|
||||||
'form-group--select-list',
|
|
||||||
MODIFIER.SELECT_LIST_FILL_POPOVER,
|
|
||||||
MODIFIER.SELECT_LIST_FILL_BUTTON,
|
|
||||||
)}
|
|
||||||
items={listOptions}
|
|
||||||
itemRenderer={menuItem}
|
|
||||||
loading={fetchResourceData.isFetching}
|
|
||||||
itemPredicate={filterItems}
|
|
||||||
popoverProps={{
|
|
||||||
inline: true,
|
|
||||||
minimal: true,
|
|
||||||
captureDismiss: true,
|
|
||||||
popoverClassName: 'popover--list-select-filter-dropdown',
|
|
||||||
}}
|
|
||||||
onItemSelect={onItemSelect}
|
|
||||||
selectedItem={value}
|
|
||||||
selectedItemProp={'id'}
|
|
||||||
defaultText={<T id={'select_account_type'} />}
|
|
||||||
labelProp={'name'}
|
|
||||||
buttonProps={{ onClick: handleBtnClick }}
|
|
||||||
/>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.When condition={fieldMeta.data_type === 'date'}>
|
|
||||||
<DateInput
|
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
|
||||||
value={transformDateValue(localValue)}
|
|
||||||
onChange={handleDateChange}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: true,
|
|
||||||
position: Position.BOTTOM,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
|
||||||
<InputGroup
|
|
||||||
placeholder={formatMessage({ id: 'value' })}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
value={localValue}
|
|
||||||
/>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
@@ -161,7 +184,7 @@ function DynamicFilterValueField({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
resourceName: props.fieldMeta.resource_key || 'account_type',
|
resourceName: props.dataResource,
|
||||||
});
|
});
|
||||||
|
|
||||||
const withResourceFilterValueField = connect(mapStateToProps);
|
const withResourceFilterValueField = connect(mapStateToProps);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @flow
|
||||||
import React, { useEffect, useMemo, useCallback, useRef } from 'react';
|
import React, { useEffect, useMemo, useCallback, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -7,25 +8,39 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual, last } from 'lodash';
|
||||||
import { usePrevious } from 'react-use';
|
import { usePrevious } from 'react-use';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { checkRequiredProperties } from 'utils';
|
import { checkRequiredProperties, uniqueMultiProps } from 'utils';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { DynamicFilterValueField } from 'components';
|
import {
|
||||||
|
DynamicFilterValueField,
|
||||||
|
DynamicFilterCompatatorField,
|
||||||
|
} from 'components';
|
||||||
import Toaster from 'components/AppToaster';
|
import Toaster from 'components/AppToaster';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import {
|
||||||
|
getConditionTypeCompatators,
|
||||||
|
getConditionDefaultCompatator
|
||||||
|
} from './DynamicFilter/DynamicFilterCompatators';
|
||||||
|
|
||||||
let limitToast;
|
let limitToast;
|
||||||
|
|
||||||
|
type InitialCondition = {
|
||||||
|
fieldKey: string,
|
||||||
|
comparator: string,
|
||||||
|
value: string,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter popover content.
|
* Filter popover content.
|
||||||
*/
|
*/
|
||||||
export default function FilterDropdown({
|
export default function FilterDropdown({
|
||||||
fields,
|
fields,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
refetchDebounceWait = 250,
|
refetchDebounceWait = 10,
|
||||||
|
initialCondition,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const fieldsKeyMapped = new Map(fields.map((field) => [field.key, field]));
|
const fieldsKeyMapped = new Map(fields.map((field) => [field.key, field]));
|
||||||
@@ -37,6 +52,7 @@ export default function FilterDropdown({
|
|||||||
],
|
],
|
||||||
[formatMessage],
|
[formatMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const resourceFields = useMemo(
|
const resourceFields = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...fields.map((field) => ({
|
...fields.map((field) => ({
|
||||||
@@ -46,23 +62,13 @@ export default function FilterDropdown({
|
|||||||
],
|
],
|
||||||
[fields],
|
[fields],
|
||||||
);
|
);
|
||||||
const compatatorsItems = useMemo(
|
|
||||||
() => [
|
|
||||||
{ value: '', label: formatMessage({ id: 'comparator' }) },
|
|
||||||
{ value: 'equals', label: formatMessage({ id: 'equals' }) },
|
|
||||||
{ value: 'not_equal', label: formatMessage({ id: 'not_equal' }) },
|
|
||||||
{ value: 'contain', label: formatMessage({ id: 'contain' }) },
|
|
||||||
{ value: 'not_contain', label: formatMessage({ id: 'not_contain' }) },
|
|
||||||
],
|
|
||||||
[formatMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultFilterCondition = useMemo(
|
const defaultFilterCondition = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
condition: 'and',
|
condition: 'and',
|
||||||
field_key: fields.length > 0 ? fields[0].key : '',
|
field_key: initialCondition.fieldKey,
|
||||||
compatator: 'equals',
|
comparator: initialCondition.comparator,
|
||||||
value: '',
|
value: initialCondition.value,
|
||||||
}),
|
}),
|
||||||
[fields],
|
[fields],
|
||||||
);
|
);
|
||||||
@@ -86,17 +92,17 @@ export default function FilterDropdown({
|
|||||||
} else {
|
} else {
|
||||||
setFieldValue('conditions', [
|
setFieldValue('conditions', [
|
||||||
...values.conditions,
|
...values.conditions,
|
||||||
defaultFilterCondition,
|
last(values.conditions),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [values, defaultFilterCondition, setFieldValue]);
|
}, [values, defaultFilterCondition, setFieldValue]);
|
||||||
|
|
||||||
const filteredFilterConditions = useMemo(() => {
|
const filteredFilterConditions = useMemo(() => {
|
||||||
const requiredProps = ['field_key', 'condition', 'compatator', 'value'];
|
const requiredProps = ['field_key', 'condition', 'comparator', 'value'];
|
||||||
|
const conditions = values.conditions.filter(
|
||||||
return values.conditions.filter(
|
|
||||||
(condition) => !checkRequiredProperties(condition, requiredProps),
|
(condition) => !checkRequiredProperties(condition, requiredProps),
|
||||||
);
|
);
|
||||||
|
return uniqueMultiProps(conditions, requiredProps);
|
||||||
}, [values.conditions]);
|
}, [values.conditions]);
|
||||||
|
|
||||||
const prevConditions = usePrevious(filteredFilterConditions);
|
const prevConditions = usePrevious(filteredFilterConditions);
|
||||||
@@ -109,7 +115,7 @@ export default function FilterDropdown({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
||||||
onFilterChangeThrottled.current(filteredFilterConditions);
|
onFilterChange && onFilterChange(filteredFilterConditions);
|
||||||
}
|
}
|
||||||
}, [filteredFilterConditions, prevConditions]);
|
}, [filteredFilterConditions, prevConditions]);
|
||||||
|
|
||||||
@@ -124,7 +130,7 @@ export default function FilterDropdown({
|
|||||||
setFieldValue('conditions', [...conditions]);
|
setFieldValue('conditions', [...conditions]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// transform dynamic value field.
|
// Transform dynamic value field.
|
||||||
const transformValueField = (value) => {
|
const transformValueField = (value) => {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return moment(value).format('YYYY-MM-DD');
|
return moment(value).format('YYYY-MM-DD');
|
||||||
@@ -145,27 +151,52 @@ export default function FilterDropdown({
|
|||||||
const currentField = fieldsKeyMapped.get(
|
const currentField = fieldsKeyMapped.get(
|
||||||
values.conditions[index].field_key,
|
values.conditions[index].field_key,
|
||||||
);
|
);
|
||||||
const prevField = fieldsKeyMapped.get(e.currentTarget.value);
|
const nextField = fieldsKeyMapped.get(e.currentTarget.value);
|
||||||
|
|
||||||
if (currentField.data_type !== prevField.data_type) {
|
if (currentField.data_type !== nextField.data_type) {
|
||||||
setFieldValue(`conditions[${index}].value`, '');
|
setFieldValue(`conditions[${index}].value`, '');
|
||||||
}
|
}
|
||||||
|
const comparatorsObs = getConditionTypeCompatators(nextField.data_type);
|
||||||
|
const currentCompatator = values.conditions[index].comparator;
|
||||||
|
|
||||||
|
if (!currentCompatator || comparatorsObs.map(c => c.value).indexOf(currentCompatator) === -1) {
|
||||||
|
const defaultCompatator = getConditionDefaultCompatator(nextField.data_type);
|
||||||
|
setFieldValue(`conditions[${index}].comparator`, defaultCompatator.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override.onChange(e);
|
override.onChange(e);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Value field props.
|
// Compatator field props.
|
||||||
const valueFieldProps = (name, index) => ({
|
const comparatorFieldProps = (name, index) => {
|
||||||
...fieldProps(name, index),
|
const condition = values.conditions[index];
|
||||||
onChange: (value) => {
|
const field = fieldsKeyMapped.get(condition.field_key);
|
||||||
const transformedValue = transformValueField(value);
|
|
||||||
setFieldValue(`conditions[${index}].${name}`, transformedValue);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(values.conditions, 'XX');
|
return {
|
||||||
|
...fieldProps(name, index),
|
||||||
|
dataType: field.data_type,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Value field props.
|
||||||
|
const valueFieldProps = (name, index) => {
|
||||||
|
const condition = values.conditions[index];
|
||||||
|
const field = fieldsKeyMapped.get(condition.field_key);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...fieldProps(name, index),
|
||||||
|
dataType: field.data_type,
|
||||||
|
resourceKey: field.resource_key,
|
||||||
|
options: field.options,
|
||||||
|
dataResource: field.data_resource,
|
||||||
|
onChange: (value) => {
|
||||||
|
const transformedValue = transformValueField(value);
|
||||||
|
setFieldValue(`conditions[${index}].${name}`, transformedValue);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
@@ -190,18 +221,15 @@ export default function FilterDropdown({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup className={'form-group--compatator'}>
|
<FormGroup className={'form-group--comparator'}>
|
||||||
<HTMLSelect
|
<DynamicFilterCompatatorField
|
||||||
options={compatatorsItems}
|
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
{...fieldProps('compatator', index)}
|
{...comparatorFieldProps('comparator', index)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<DynamicFilterValueField
|
<DynamicFilterValueField {...valueFieldProps('value', index)} />
|
||||||
fieldMeta={fieldsKeyMapped.get(condition.field_key)}
|
|
||||||
{...valueFieldProps('value', index)}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
icon={<Icon icon="times" iconSize={14} />}
|
icon={<Icon icon="times" iconSize={14} />}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Scrollbar } from 'react-scrollbars-custom';
|
import { Scrollbar } from 'react-scrollbars-custom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
@@ -17,6 +17,10 @@ function SidebarContainer({
|
|||||||
// #withDashboard
|
// #withDashboard
|
||||||
sidebarExpended,
|
sidebarExpended,
|
||||||
}) {
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.toggle('has-mini-sidebar', !sidebarExpended);
|
||||||
|
}, [sidebarExpended]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('sidebar', {
|
className={classNames('sidebar', {
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import For from './Utils/For';
|
|||||||
import ListSelect from './ListSelect';
|
import ListSelect from './ListSelect';
|
||||||
import FinancialStatement from './FinancialStatement';
|
import FinancialStatement from './FinancialStatement';
|
||||||
import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField';
|
import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField';
|
||||||
|
import DynamicFilterCompatatorField from './DynamicFilter/DynamicFilterCompatatorField';
|
||||||
import ErrorMessage from './ErrorMessage';
|
import ErrorMessage from './ErrorMessage';
|
||||||
import MODIFIER from './modifiers';
|
import MODIFIER from './modifiers';
|
||||||
import FieldHint from './FieldHint';
|
import FieldHint from './FieldHint';
|
||||||
import MenuItemLabel from './MenuItemLabel';
|
import MenuItemLabel from './MenuItemLabel';
|
||||||
import Pagination from './Pagination';
|
import Pagination from './Pagination';
|
||||||
|
import DashboardViewsTabs from './Dashboard/DashboardViewsTabs';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -23,11 +25,13 @@ export {
|
|||||||
FinancialStatement,
|
FinancialStatement,
|
||||||
Choose,
|
Choose,
|
||||||
DynamicFilterValueField,
|
DynamicFilterValueField,
|
||||||
|
DynamicFilterCompatatorField,
|
||||||
MODIFIER,
|
MODIFIER,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
FieldHint,
|
FieldHint,
|
||||||
Hint,
|
Hint,
|
||||||
MenuItemLabel,
|
MenuItemLabel,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
DashboardViewsTabs,
|
||||||
// For,
|
// For,
|
||||||
};
|
};
|
||||||
@@ -10,11 +10,12 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Intent
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
@@ -28,11 +29,9 @@ import withManualJournalsActions from 'containers/Accounting/withManualJournalsA
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function ManualJournalActionsBar({
|
function ManualJournalActionsBar({
|
||||||
// #withResourceDetail
|
// #withResourceDetail
|
||||||
resourceName = 'manual_journal',
|
resourceName = 'manual_journals',
|
||||||
resourceFields,
|
resourceFields,
|
||||||
|
|
||||||
// #withManualJournals
|
// #withManualJournals
|
||||||
@@ -43,12 +42,12 @@ function ManualJournalActionsBar({
|
|||||||
|
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
onBulkDelete
|
onBulkDelete,
|
||||||
}) {
|
}) {
|
||||||
const { path } = useRouteMatch();
|
const { path } = useRouteMatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const viewsMenuItems = manualJournalsViews.map(view => {
|
const viewsMenuItems = manualJournalsViews.map((view) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
|
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
|
||||||
);
|
);
|
||||||
@@ -60,18 +59,25 @@ function ManualJournalActionsBar({
|
|||||||
|
|
||||||
const filterDropdown = FilterDropdown({
|
const filterDropdown = FilterDropdown({
|
||||||
fields: resourceFields,
|
fields: resourceFields,
|
||||||
onFilterChange: filterConditions => {
|
initialCondition: {
|
||||||
|
fieldKey: 'journal_number',
|
||||||
|
compatator: 'contains',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
addManualJournalsTableQueries({
|
addManualJournalsTableQueries({
|
||||||
filter_roles: filterConditions || ''
|
filter_roles: filterConditions || '',
|
||||||
});
|
});
|
||||||
onFilterChanged && onFilterChanged(filterConditions);
|
onFilterChanged && onFilterChanged(filterConditions);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
||||||
|
selectedRows,
|
||||||
|
]);
|
||||||
|
|
||||||
// Handle delete button click.
|
// Handle delete button click.
|
||||||
const handleBulkDelete = useCallback(() => {
|
const handleBulkDelete = useCallback(() => {
|
||||||
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
|
||||||
}, [onBulkDelete, selectedRows]);
|
}, [onBulkDelete, selectedRows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -85,8 +91,8 @@ function ManualJournalActionsBar({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||||
icon={<Icon icon='table-16' iconSize={16} />}
|
icon={<Icon icon="table-16" iconSize={16} />}
|
||||||
text={<T id={'table_views'}/>}
|
text={<T id={'table_views'} />}
|
||||||
rightIcon={'caret-down'}
|
rightIcon={'caret-down'}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -95,27 +101,28 @@ function ManualJournalActionsBar({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='plus' />}
|
icon={<Icon icon="plus" />}
|
||||||
text={<T id={'new_journal'}/>}
|
text={<T id={'new_journal'} />}
|
||||||
onClick={onClickNewManualJournal}
|
onClick={onClickNewManualJournal}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<Popover
|
||||||
|
minimal={true}
|
||||||
content={filterDropdown}
|
content={filterDropdown}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}
|
position={Position.BOTTOM_LEFT}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||||
text='Filter'
|
text="Filter"
|
||||||
icon={<Icon icon='filter-16' iconSize={16} />}
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<If condition={hasSelectedRows}>
|
<If condition={hasSelectedRows}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='trash-16' iconSize={16} />}
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
text={<T id={'delete'}/>}
|
text={<T id={'delete'} />}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
/>
|
/>
|
||||||
@@ -123,20 +130,27 @@ function ManualJournalActionsBar({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='file-import-16' iconSize={16} />}
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
text={<T id={'import'}/>}
|
text={<T id={'import'} />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='file-export-16' iconSize={16} />}
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
text={<T id={'export'}/>}
|
text={<T id={'export'} />}
|
||||||
/>
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
resourceName: 'manual_journals',
|
||||||
|
});
|
||||||
|
|
||||||
|
const withManualJournalsActionsBar = connect(mapStateToProps);
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
|
withManualJournalsActionsBar,
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withResourceDetail(({ resourceFields }) => ({
|
withResourceDetail(({ resourceFields }) => ({
|
||||||
resourceFields,
|
resourceFields,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import withManualJournals from 'containers/Accounting/withManualJournals';
|
|||||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||||
import withViewsActions from 'containers/Views/withViewsActions';
|
import withViewsActions from 'containers/Views/withViewsActions';
|
||||||
import withRouteActions from 'containers/Router/withRouteActions';
|
import withRouteActions from 'containers/Router/withRouteActions';
|
||||||
|
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -30,6 +31,9 @@ function ManualJournalsTable({
|
|||||||
// #withViewsActions
|
// #withViewsActions
|
||||||
requestFetchResourceViews,
|
requestFetchResourceViews,
|
||||||
|
|
||||||
|
// #withResourceActions
|
||||||
|
requestFetchResourceFields,
|
||||||
|
|
||||||
// #withManualJournals
|
// #withManualJournals
|
||||||
manualJournalsTableQuery,
|
manualJournalsTableQuery,
|
||||||
|
|
||||||
@@ -50,10 +54,15 @@ function ManualJournalsTable({
|
|||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const fetchViews = useQuery('journals-resource-views', () => {
|
const fetchViews = useQuery('manual-journals-resource-views', () => {
|
||||||
return requestFetchResourceViews('manual_journals');
|
return requestFetchResourceViews('manual_journals');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fetchResourceFields = useQuery(
|
||||||
|
'manual-journals-resource-fields',
|
||||||
|
() => requestFetchResourceFields('manual_journals'),
|
||||||
|
);
|
||||||
|
|
||||||
const fetchManualJournals = useQuery(
|
const fetchManualJournals = useQuery(
|
||||||
['manual-journals-table', manualJournalsTableQuery],
|
['manual-journals-table', manualJournalsTableQuery],
|
||||||
(key, q) => requestFetchManualJournalsTable(q),
|
(key, q) => requestFetchManualJournalsTable(q),
|
||||||
@@ -108,8 +117,8 @@ function ManualJournalsTable({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage(
|
message: formatMessage(
|
||||||
{ id: 'the_journals_has_been_successfully_deleted', },
|
{ id: 'the_journals_has_been_successfully_deleted' },
|
||||||
{ count: selectedRowsCount, },
|
{ count: selectedRowsCount },
|
||||||
),
|
),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
@@ -189,7 +198,7 @@ function ManualJournalsTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={fetchViews.isFetching}
|
loading={fetchViews.isFetching || fetchResourceFields.isFetching}
|
||||||
name={'manual-journals'}
|
name={'manual-journals'}
|
||||||
>
|
>
|
||||||
<ManualJournalsActionsBar
|
<ManualJournalsActionsBar
|
||||||
@@ -265,6 +274,7 @@ export default compose(
|
|||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withManualJournalsActions,
|
withManualJournalsActions,
|
||||||
withViewsActions,
|
withViewsActions,
|
||||||
|
withResourceActions,
|
||||||
withManualJournals(({ manualJournalsTableQuery }) => ({
|
withManualJournals(({ manualJournalsTableQuery }) => ({
|
||||||
manualJournalsTableQuery,
|
manualJournalsTableQuery,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default (mapState) => {
|
|||||||
manualJournalsTableQuery.page,
|
manualJournalsTableQuery.page,
|
||||||
),
|
),
|
||||||
manualJournalsTableQuery,
|
manualJournalsTableQuery,
|
||||||
manualJournalsViews: getResourceViews(state, 'manual_journals'),
|
manualJournalsViews: getResourceViews(state, props, 'manual_journals'),
|
||||||
manualJournalsItems: state.manualJournals.items,
|
manualJournalsItems: state.manualJournals.items,
|
||||||
|
|
||||||
manualJournalsPagination: state.manualJournals.paginationMeta,
|
manualJournalsPagination: state.manualJournals.paginationMeta,
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ function AccountsActionsBar({
|
|||||||
|
|
||||||
const filterDropdown = FilterDropdown({
|
const filterDropdown = FilterDropdown({
|
||||||
fields: resourceFields,
|
fields: resourceFields,
|
||||||
|
initialCondition: {
|
||||||
|
fieldKey: 'name',
|
||||||
|
compatator: 'contains',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
onFilterChange: (filterConditions) => {
|
onFilterChange: (filterConditions) => {
|
||||||
setFilterCount(filterConditions.length || 0);
|
setFilterCount(filterConditions.length || 0);
|
||||||
addAccountsTableQueries({
|
addAccountsTableQueries({
|
||||||
@@ -125,7 +130,7 @@ function AccountsActionsBar({
|
|||||||
filterCount <= 0 ? (
|
filterCount <= 0 ? (
|
||||||
<T id={'filter'} />
|
<T id={'filter'} />
|
||||||
) : (
|
) : (
|
||||||
`${filterCount} filters applied`
|
<T id={'count_filters_applied'} values={{ count: filterCount }} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ function AccountsChart({
|
|||||||
<AccountsViewsTabs onViewChanged={handleViewChanged} />
|
<AccountsViewsTabs onViewChanged={handleViewChanged} />
|
||||||
|
|
||||||
<AccountsDataTable
|
<AccountsDataTable
|
||||||
|
loading={fetchAccountsHook.isFetching}
|
||||||
onDeleteAccount={handleDeleteAccount}
|
onDeleteAccount={handleDeleteAccount}
|
||||||
onInactiveAccount={handleInactiveAccount}
|
onInactiveAccount={handleInactiveAccount}
|
||||||
onActivateAccount={handleActivateAccount}
|
onActivateAccount={handleActivateAccount}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useState, useMemo } from 'react';
|
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
@@ -24,6 +25,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentView from 'containers/Views/withCurrentView';
|
||||||
|
|
||||||
import { If } from 'components';
|
import { If } from 'components';
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ function AccountsDataTable({
|
|||||||
// #withDialog.
|
// #withDialog.
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|
||||||
|
currentViewId,
|
||||||
|
|
||||||
// own properties
|
// own properties
|
||||||
loading,
|
loading,
|
||||||
onFetchData,
|
onFetchData,
|
||||||
@@ -43,14 +47,18 @@ function AccountsDataTable({
|
|||||||
onInactiveAccount,
|
onInactiveAccount,
|
||||||
onActivateAccount,
|
onActivateAccount,
|
||||||
}) {
|
}) {
|
||||||
const [initialMount, setInitialMount] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(false);
|
||||||
|
}, [currentViewId]);
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
if (!accountsLoading) {
|
if (!accountsLoading) {
|
||||||
setInitialMount(true);
|
setIsMounted(true);
|
||||||
}
|
}
|
||||||
}, [accountsLoading, setInitialMount]);
|
}, [accountsLoading, setIsMounted]);
|
||||||
|
|
||||||
const handleEditAccount = useCallback(
|
const handleEditAccount = useCallback(
|
||||||
(account) => () => {
|
(account) => () => {
|
||||||
@@ -132,21 +140,21 @@ function AccountsDataTable({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
className: 'account_name',
|
className: 'account_name',
|
||||||
width: 300,
|
width: 220,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'code',
|
id: 'code',
|
||||||
Header: formatMessage({ id: 'code' }),
|
Header: formatMessage({ id: 'code' }),
|
||||||
accessor: 'code',
|
accessor: 'code',
|
||||||
className: 'code',
|
className: 'code',
|
||||||
width: 100,
|
width: 125,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'type',
|
id: 'type',
|
||||||
Header: formatMessage({ id: 'type' }),
|
Header: formatMessage({ id: 'type' }),
|
||||||
accessor: 'type.name',
|
accessor: 'type.name',
|
||||||
className: 'type',
|
className: 'type',
|
||||||
width: 120,
|
width: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'normal',
|
id: 'normal',
|
||||||
@@ -168,7 +176,7 @@ function AccountsDataTable({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
className: 'normal',
|
className: 'normal',
|
||||||
width: 75,
|
width: 115,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
@@ -207,9 +215,9 @@ function AccountsDataTable({
|
|||||||
|
|
||||||
const selectionColumn = useMemo(
|
const selectionColumn = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
minWidth: 50,
|
minWidth: 40,
|
||||||
width: 50,
|
width: 40,
|
||||||
maxWidth: 50,
|
maxWidth: 40,
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -227,7 +235,7 @@ function AccountsDataTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingIndicator loading={loading} mount={false}>
|
<LoadingIndicator loading={loading && !isMounted} mount={false}>
|
||||||
<DataTable
|
<DataTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -239,7 +247,7 @@ function AccountsDataTable({
|
|||||||
treeGraph={true}
|
treeGraph={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
loading={accountsLoading && !initialMount}
|
loading={accountsLoading && !isMounted}
|
||||||
spinnerProps={{ size: 30 }}
|
spinnerProps={{ size: 30 }}
|
||||||
rowContextMenu={rowContextMenu}
|
rowContextMenu={rowContextMenu}
|
||||||
/>
|
/>
|
||||||
@@ -248,6 +256,8 @@ function AccountsDataTable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
withCurrentView,
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
@@ -7,14 +7,15 @@ import {
|
|||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
Button
|
Button,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useParams, withRouter } from 'react-router-dom';
|
import { useParams, withRouter } from 'react-router-dom';
|
||||||
import Icon from 'components/Icon';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { pick, debounce } from 'lodash';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import {useUpdateEffect} from 'hooks';
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
import { DashboardViewsTabs } from 'components';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||||
@@ -48,7 +49,7 @@ function AccountsViewsTabs({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changeAccountsCurrentView(customViewId || -1);
|
changeAccountsCurrentView(customViewId || -1);
|
||||||
setTopbarEditView(customViewId);
|
setTopbarEditView(customViewId);
|
||||||
changePageSubtitle((customViewId && viewItem) ? viewItem.name : '');
|
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||||
|
|
||||||
addAccountsTableQueries({
|
addAccountsTableQueries({
|
||||||
custom_view_id: customViewId,
|
custom_view_id: customViewId,
|
||||||
@@ -57,7 +58,7 @@ function AccountsViewsTabs({
|
|||||||
return () => {
|
return () => {
|
||||||
setTopbarEditView(null);
|
setTopbarEditView(null);
|
||||||
changePageSubtitle('');
|
changePageSubtitle('');
|
||||||
changeAccountsCurrentView(null)
|
changeAccountsCurrentView(null);
|
||||||
};
|
};
|
||||||
}, [customViewId]);
|
}, [customViewId]);
|
||||||
|
|
||||||
@@ -71,52 +72,37 @@ function AccountsViewsTabs({
|
|||||||
history.push('/custom_views/accounts/new');
|
history.push('/custom_views/accounts/new');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle view tab link click.
|
const tabs = accountsViews.map((view) => ({
|
||||||
const handleViewLinkClick = () => {
|
...pick(view, ['name', 'id']),
|
||||||
setTopbarEditView(customViewId);
|
}));
|
||||||
|
|
||||||
|
const debounceChangeHistory = useRef(
|
||||||
|
debounce((toUrl) => {
|
||||||
|
history.push(toUrl);
|
||||||
|
}, 250),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabsChange = (viewId) => {
|
||||||
|
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||||
|
debounceChangeHistory.current(`/accounts/${toPath}`);
|
||||||
|
setTopbarEditView(viewId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = accountsViews.map((view) => {
|
|
||||||
const baseUrl = '/accounts';
|
|
||||||
|
|
||||||
const link = (
|
|
||||||
<Link
|
|
||||||
to={`${baseUrl}/${view.id}/custom_view`}
|
|
||||||
onClick={handleViewLinkClick}
|
|
||||||
>{ view.name }</Link>
|
|
||||||
);
|
|
||||||
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className='navbar--dashboard-views'>
|
<Navbar className="navbar--dashboard-views">
|
||||||
<NavbarGroup align={Alignment.LEFT}>
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
<Tabs
|
<DashboardViewsTabs
|
||||||
id='navbar'
|
baseUrl={'/accounts'}
|
||||||
large={true}
|
tabs={tabs}
|
||||||
selectedTabId={customViewId ? `custom_view_${customViewId}` : 'all'}
|
onNewViewTabClick={handleClickNewView}
|
||||||
className='tabs--dashboard-views'
|
onChange={handleTabsChange}
|
||||||
>
|
/>
|
||||||
<Tab
|
|
||||||
id={'all'}
|
|
||||||
title={<Link to={`/accounts`}><T id={'all'}/></Link>}
|
|
||||||
onClick={handleViewLinkClick}
|
|
||||||
/>
|
|
||||||
{ tabs }
|
|
||||||
<Button
|
|
||||||
className='button--new-view'
|
|
||||||
icon={<Icon icon='plus' />}
|
|
||||||
onClick={handleClickNewView}
|
|
||||||
minimal={true}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
// Mapping view id from matched route params.
|
|
||||||
viewId: ownProps.match.params.custom_view_id,
|
viewId: ownProps.match.params.custom_view_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,5 +116,5 @@ export default compose(
|
|||||||
accountsViews,
|
accountsViews,
|
||||||
})),
|
})),
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
withViewDetail
|
withViewDetail,
|
||||||
)(AccountsViewsTabs);
|
)(AccountsViewsTabs);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
accountsViews: getResourceViews(state, 'accounts'),
|
accountsViews: getResourceViews(state, props, 'accounts'),
|
||||||
accounts: getAccountsItems(state, state.accounts.currentViewId),
|
accounts: getAccountsItems(state, props),
|
||||||
accountsTypes: state.accounts.accountsTypes,
|
accountsTypes: state.accounts.accountsTypes,
|
||||||
|
|
||||||
accountsTableQuery: state.accounts.tableQuery,
|
accountsTableQuery: state.accounts.tableQuery,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { getCurrenciesList } from 'store/currencies/currencies.selector';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
currencies: state.currencies.data,
|
currencies: state.currencies.data,
|
||||||
currenciesList: Object.values(state.currencies.data),
|
currenciesList: getCurrenciesList(state, props),
|
||||||
currenciesLoading: state.currencies.loading,
|
currenciesLoading: state.currencies.loading,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default (mapState) => {
|
|||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
customersViews: getResourceViews(state, 'customers'),
|
customersViews: getResourceViews(state, props, 'customers'),
|
||||||
customersItems: Object.values(state.customers.items),
|
customersItems: Object.values(state.customers.items),
|
||||||
customers: getCustomersItems(state, state.customers.currentViewId),
|
customers: getCustomersItems(state, state.customers.currentViewId),
|
||||||
customersLoading: state.customers.loading,
|
customersLoading: state.customers.loading,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default (mapState) => {
|
|||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
expenses: getExpensesItems(state, state.expenses.currentViewId),
|
expenses: getExpensesItems(state, state.expenses.currentViewId),
|
||||||
expensesViews: getResourceViews(state, 'expenses'),
|
expensesViews: getResourceViews(state, props, 'expenses'),
|
||||||
expensesItems: state.expenses.items,
|
expensesItems: state.expenses.items,
|
||||||
expensesTableQuery: state.expenses.tableQuery,
|
expensesTableQuery: state.expenses.tableQuery,
|
||||||
expensesLoading: state.expenses.loading,
|
expensesLoading: state.expenses.loading,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default (mapState) => {
|
|||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const viewPages = getViewPages(state.items.views, state.items.currentViewId);
|
const viewPages = getViewPages(state.items.views, state.items.currentViewId);
|
||||||
const mapped = {
|
const mapped = {
|
||||||
itemsViews: getResourceViews(state, 'items'),
|
itemsViews: getResourceViews(state, props, 'items'),
|
||||||
itemsCurrentPage: getCurrentPageResults(
|
itemsCurrentPage: getCurrentPageResults(
|
||||||
state.items.items,
|
state.items.items,
|
||||||
viewPages,
|
viewPages,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default (mapState) => {
|
|||||||
const { resourceName } = props;
|
const { resourceName } = props;
|
||||||
const mapped = {
|
const mapped = {
|
||||||
resourceData: getResourceData(state, resourceName),
|
resourceData: getResourceData(state, resourceName),
|
||||||
resourceFields: getResourceFields(state, resourceName),
|
resourceFields: getResourceFields(state, props),
|
||||||
resourceColumns: getResourceColumns(state, resourceName),
|
resourceColumns: getResourceColumns(state, resourceName),
|
||||||
resourceMetadata: getResourceMetadata(state, resourceName),
|
resourceMetadata: getResourceMetadata(state, resourceName),
|
||||||
};
|
};
|
||||||
|
|||||||
7
client/src/containers/Views/withCurrentView.js
Normal file
7
client/src/containers/Views/withCurrentView.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
currentViewId: props.match.params.custom_view_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps);
|
||||||
@@ -6,11 +6,9 @@ import {
|
|||||||
|
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => {
|
export const mapStateToProps = (state, props) => {
|
||||||
const { viewId } = props;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewMeta: getViewMeta(state, viewId),
|
viewMeta: getViewMeta(state, props),
|
||||||
viewItem: getViewItem(state, viewId),
|
viewItem: getViewItem(state, props),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -501,5 +501,13 @@ export default {
|
|||||||
once_delete_this_customer_you_will_able_to_restore_it: `Once you delete this customer, you won\'t be able to restore it later. Are you sure you want to delete this cusomter?`,
|
once_delete_this_customer_you_will_able_to_restore_it: `Once you delete this customer, you won\'t be able to restore it later. Are you sure you want to delete this cusomter?`,
|
||||||
once_delete_these_customers_you_will_not_able_restore_them:
|
once_delete_these_customers_you_will_not_able_restore_them:
|
||||||
"Once you delete these customers, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
"Once you delete these customers, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
||||||
financial_accounting: 'Financial accounting'
|
financial_accounting: 'Financial accounting',
|
||||||
|
after: 'After',
|
||||||
|
before: 'Before',
|
||||||
|
count_filters_applied: '{count} filters applied',
|
||||||
|
is: 'Is',
|
||||||
|
is_not: 'Is Not',
|
||||||
|
create_a_new_view: 'Create a new view',
|
||||||
|
in: 'In',
|
||||||
|
not_equals: 'Not Equals',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
import { pickItemsFromIds } from 'store/selectors';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
|
||||||
export const getAccountsItems = (state, viewId) => {
|
const accountsViewsSelector = (state) => state.accounts.views;
|
||||||
const accountsView = state.accounts.views[viewId || -1];
|
const accountsDataSelector = (state) => state.accounts.items;
|
||||||
const accountsItems = state.accounts.items;
|
const accountsCurrentViewSelector = (state) => state.accounts.currentViewId;
|
||||||
|
|
||||||
return typeof accountsView === 'object'
|
export const getAccountsItems = createSelector(
|
||||||
? pickItemsFromIds(accountsItems, accountsView.ids) || []
|
accountsViewsSelector,
|
||||||
: [];
|
accountsDataSelector,
|
||||||
};
|
accountsCurrentViewSelector,
|
||||||
|
(accountsViews, accountsItems, viewId) => {
|
||||||
|
const accountsView = accountsViews[viewId || -1];
|
||||||
|
|
||||||
|
return typeof accountsView === 'object'
|
||||||
|
? pickItemsFromIds(accountsItems, accountsView.ids) || []
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
const currenciesItemsSelector = state => state.currencies.data;
|
||||||
|
|
||||||
|
export const getCurrenciesList = createSelector(
|
||||||
|
currenciesItemsSelector,
|
||||||
|
(currencies) => {
|
||||||
|
return Object.values(currencies);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const getCurrencyById = (currencies: Object, id: Integer) => {
|
export const getCurrencyById = (currencies: Object, id: Integer) => {
|
||||||
return Object.values(currencies).find(c => c.id == id) || null;
|
return Object.values(currencies).find(c => c.id == id) || null;
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import {pickItemsFromIds} from 'store/selectors';
|
import { createSelector } from 'reselect';
|
||||||
import {getResourceColumn } from 'store/resources/resources.reducer';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
import { getResourceColumn } from 'store/resources/resources.reducer';
|
||||||
|
|
||||||
export const getResourceViews = (state, resourceName) => {
|
const resourceViewsIdsSelector = (state, props, resourceName) =>
|
||||||
const resourceViewsIds = state.views.resourceViews[resourceName] || [];
|
state.views.resourceViews[resourceName] || [];
|
||||||
return pickItemsFromIds(state.views.views, resourceViewsIds);
|
|
||||||
};
|
const viewsSelector = (state) => state.views.views;
|
||||||
|
const viewByIdSelector = (state, props) => state.views.viewsMeta[props.viewId] || {};
|
||||||
|
|
||||||
|
export const getResourceViews = createSelector(
|
||||||
|
resourceViewsIdsSelector,
|
||||||
|
viewsSelector,
|
||||||
|
(resourceViewsIds, views) => {
|
||||||
|
return pickItemsFromIds(views, resourceViewsIds);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const getViewMeta = (state, viewId) => {
|
export const getViewMeta = (state, viewId) => {
|
||||||
const view = { ...state.views.viewsMeta[viewId] } || {};
|
const view = { ...state.views.viewsMeta[viewId] } || {};
|
||||||
@@ -24,6 +34,7 @@ export const getViewItem = (state, viewId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getViewPages = (resourceViews, viewId) => {
|
export const getViewPages = (resourceViews, viewId) => {
|
||||||
return (typeof resourceViews[viewId] === 'undefined') ?
|
return typeof resourceViews[viewId] === 'undefined'
|
||||||
{} : resourceViews[viewId].pages;
|
? {}
|
||||||
|
: resourceViews[viewId].pages;
|
||||||
};
|
};
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
import { pickItemsFromIds } from 'store/selectors';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
export const getCustomersItems = (state, viewId) => {
|
|
||||||
|
|
||||||
const customersView = state.customers.views[viewId || -1];
|
const customersViewsSelector = state => state.customers.views;
|
||||||
const customersItems = state.customers.items;
|
const customersItemsSelector = state => state.customers.items;
|
||||||
|
const customersCurrentViewSelector = state => state.customers.currentViewId;
|
||||||
|
|
||||||
return typeof customersView === 'object'
|
export const getCustomersItems = createSelector(
|
||||||
? pickItemsFromIds(customersItems, customersView.ids) || []
|
customersViewsSelector,
|
||||||
: [];
|
customersItemsSelector,
|
||||||
};
|
customersCurrentViewSelector,
|
||||||
|
(customersViews, customersItems, currentViewId) => {
|
||||||
|
const customersView = customersViews[currentViewId || -1];
|
||||||
|
|
||||||
|
return (typeof customersView === 'object')
|
||||||
|
? pickItemsFromIds(customersItems, customersView.ids) || []
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
);
|
||||||
18
client/src/store/dashboard/dashboard.selectors.js
Normal file
18
client/src/store/dashboard/dashboard.selectors.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { createSelector } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
|
||||||
|
const dialogByNameSelector = (state, props) => state.dashboard.dialogs[props.name];
|
||||||
|
|
||||||
|
export const isDialogOpen = createSelector(
|
||||||
|
dialogByNameSelector,
|
||||||
|
(dialog) => {
|
||||||
|
return dialog && dialog.isOpen;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getDialogPayload = createSelector(
|
||||||
|
dialogByNameSelector,
|
||||||
|
(dialog) => {
|
||||||
|
return dialog?.payload;
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { pickItemsFromIds } from 'store/selectors';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
|
||||||
export const getExpensesItems = (state, viewId) => {
|
const expensesViewsSelector = state => state.expenses.views;
|
||||||
const accountsView = state.expenses.views[viewId || -1];
|
const expensesItemsSelector = state => state.expenses.items;
|
||||||
const accountsItems = state.expenses.items;
|
const expensesCurrentViewSelector = state => state.expenses.currentViewId;
|
||||||
|
|
||||||
return typeof accountsView === 'object'
|
export const getExpensesItems = createSelector(
|
||||||
? pickItemsFromIds(accountsItems, accountsView.ids) || []
|
expensesViewsSelector,
|
||||||
: [];
|
expensesItemsSelector,
|
||||||
};
|
expensesCurrentViewSelector,
|
||||||
|
(expensesViews, expensesItems, currentViewId) => {
|
||||||
|
const expensesView = expensesViews[currentViewId || -1];
|
||||||
|
|
||||||
|
return (typeof expensesView === 'object')
|
||||||
|
? (pickItemsFromIds(expensesItems, expensesView.ids) || [])
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createReducer } from "@reduxjs/toolkit";
|
import { createReducer } from "@reduxjs/toolkit";
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { pickItemsFromIds } from 'store/selectors'
|
import { pickItemsFromIds } from 'store/selectors'
|
||||||
|
|
||||||
@@ -61,17 +62,23 @@ export default createReducer(initialState, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const resourceFieldsIdsSelector = (state, props) => state.resources.resourceFields[props.resourceName];
|
||||||
|
const resourceFieldsItemsSelector = (state) => state.resources.fields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource fields of the given resource slug.
|
* Retrieve resource fields of the given resource slug.
|
||||||
* @param {Object} state
|
* @param {Object} state
|
||||||
* @param {String} resourceSlug
|
* @param {String} resourceSlug
|
||||||
* @return {Array}
|
* @return {Array}
|
||||||
*/
|
*/
|
||||||
export const getResourceFields = (state, resourceSlug) => {
|
export const getResourceFields = createSelector(
|
||||||
const resourceIds = state.resources.resourceFields[resourceSlug];
|
resourceFieldsIdsSelector,
|
||||||
const items = state.resources.fields;
|
resourceFieldsItemsSelector,
|
||||||
return pickItemsFromIds(items, resourceIds);
|
(fieldsIds, fieldsItems) => {
|
||||||
};
|
return pickItemsFromIds(fieldsItems, fieldsIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource columns of the given resource slug.
|
* Retrieve resource columns of the given resource slug.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
.th{
|
.th{
|
||||||
padding: 0.8rem 0.5rem;
|
padding: 0.75rem 0.5rem;
|
||||||
background: #F8FAFA;
|
background: #F8FAFA;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #444;
|
color: #444;
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
&--desc{
|
&--desc{
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
border-right: 4px solid transparent;
|
border-right: 4px solid transparent;
|
||||||
border-bottom: 6px solid #888;
|
border-bottom: 6px solid #666;
|
||||||
}
|
}
|
||||||
&--asc{
|
&--asc{
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
border-right: 4px solid transparent;
|
border-right: 4px solid transparent;
|
||||||
border-top: 6px solid #888;
|
border-top: 6px solid #666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +115,7 @@
|
|||||||
.tr .td{
|
.tr .td{
|
||||||
border-bottom: 1px solid #E8E8E8;
|
border-bottom: 1px solid #E8E8E8;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
color: #252833;
|
||||||
|
|
||||||
.placeholder{
|
.placeholder{
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
body{
|
body{
|
||||||
color: #333;
|
color: #1f3255;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-heading{
|
.#{$ns}-heading{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
.bigcapital-datatable{
|
.bigcapital-datatable{
|
||||||
.normal{
|
.normal{
|
||||||
.#{$ns}-icon{
|
.#{$ns}-icon{
|
||||||
color: #aaa;
|
color: #838d9b;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,9 +22,6 @@
|
|||||||
border-bottom-color: #c6c6c6;
|
border-bottom-color: #c6c6c6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.code{
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.normal{
|
.normal{
|
||||||
.bp3-popover-wrapper{
|
.bp3-popover-wrapper{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
&-sidebar-toggle{
|
&-sidebar-toggle{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: 4px;
|
margin-left: 2px;
|
||||||
|
|
||||||
.#{$ns}-button{
|
.#{$ns}-button{
|
||||||
color: #8E8E8E;
|
color: #6B8193;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
&:hover,
|
&:hover,
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
|
|
||||||
&,
|
&,
|
||||||
&-group{
|
&-group{
|
||||||
height: 44px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-navbar-divider{
|
.#{$ns}-navbar-divider{
|
||||||
@@ -124,17 +124,17 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
.#{$ns}-button{
|
.#{$ns}-button{
|
||||||
color: #5C5C5C;
|
color: #4d4d4d;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
background: rgba(167, 182, 194, 0.12);
|
background: rgba(167, 182, 194, 0.12);
|
||||||
color: #5C5C5C;
|
color: #4d4d4d;
|
||||||
}
|
}
|
||||||
&.bp3-minimal:active,
|
&.bp3-minimal:active,
|
||||||
&.bp3-minimal.bp3-active{
|
&.bp3-minimal.bp3-active{
|
||||||
background: rgba(167, 182, 194, 0.12);
|
background: rgba(167, 182, 194, 0.12);
|
||||||
color: #5C5C5C;
|
color: #4d4d4d;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-active-filters{
|
&.has-active-filters{
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.#{$ns}-icon{
|
.#{$ns}-icon{
|
||||||
color: #666;
|
color: #4d4d4d;
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
}
|
}
|
||||||
&.#{$ns}-minimal.#{$ns}-intent-danger{
|
&.#{$ns}-minimal.#{$ns}-intent-danger{
|
||||||
@@ -193,12 +193,12 @@
|
|||||||
&__title{
|
&__title{
|
||||||
align-items: center;;
|
align-items: center;;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: 6px;
|
margin-left: 4px;
|
||||||
|
|
||||||
h1{
|
h1{
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: #4d4c4c;
|
color: #393939;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
h3{
|
h3{
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
.tbody{
|
.tbody{
|
||||||
.th.selection,
|
.th.selection,
|
||||||
.td.selection{
|
.td.selection{
|
||||||
padding-left: 18px;
|
padding-left: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,16 +294,17 @@
|
|||||||
.tabs--dashboard-views{
|
.tabs--dashboard-views{
|
||||||
|
|
||||||
.#{$ns}-tab{
|
.#{$ns}-tab{
|
||||||
color: #5C5C5C;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
padding-left: 14px;
|
||||||
|
padding-right: 14px;
|
||||||
|
|
||||||
> a{
|
&[aria-selected='true'] {
|
||||||
padding-left: 14px;
|
color: #0052cc;
|
||||||
padding-right: 14px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
.#{$ns}-menu-item {
|
.#{$ns}-menu-item {
|
||||||
color: $sidebar-menu-item-color;
|
color: $sidebar-menu-item-color;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 9px 16px;
|
padding: 8px 16px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
@@ -86,10 +86,10 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
}
|
}
|
||||||
&-label{
|
&-label{
|
||||||
display: block;
|
display: block;
|
||||||
color: rgba(255, 255, 255, 0.45);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 4px 16px;
|
padding: 6px 16px;
|
||||||
margin-top: 6px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
color: $sidebar-menu-item-color;
|
color: $sidebar-menu-item-color;
|
||||||
}
|
}
|
||||||
.#{$ns}-menu-divider {
|
.#{$ns}-menu-divider {
|
||||||
border-top-color: rgba(255, 255, 255, 0.1);
|
border-top-color: rgba(255, 255, 255, 0.15);
|
||||||
color: #6b708c;
|
color: #6b708c;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,6 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--mini-sidebar{
|
&--mini-sidebar{
|
||||||
|
|||||||
@@ -23,12 +23,13 @@
|
|||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-html-select select,
|
.bp3-html-select select,
|
||||||
.bp3-select select{
|
.bp3-select select{
|
||||||
padding: 0 20px 0 6px;
|
padding: 0 15px 0 4px;
|
||||||
|
|
||||||
&:after{
|
&:after{
|
||||||
margin-right: 10px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bp3-input{
|
.bp3-input{
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
.bp3-html-select::after,
|
.bp3-html-select::after,
|
||||||
.form-group--select-list .bp3-button::after{
|
.form-group--select-list .bp3-button::after{
|
||||||
border-top-color: #aaa;
|
border-top-color: #aaa;
|
||||||
margin-right: 10px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,19 +56,24 @@
|
|||||||
}
|
}
|
||||||
.form-group{
|
.form-group{
|
||||||
&--condition{
|
&--condition{
|
||||||
width: 70px;
|
width: 65px;
|
||||||
min-width: 70px;
|
min-width: 65px;
|
||||||
}
|
}
|
||||||
&--field{
|
&--field{
|
||||||
width: 45%;
|
width: 40%;
|
||||||
}
|
}
|
||||||
&--compatator{
|
&--comparator{
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
width: 120px;
|
width: 100px;
|
||||||
max-width: 120px;
|
max-width: 100px;
|
||||||
}
|
}
|
||||||
&--value{
|
&--value{
|
||||||
width: 55%;
|
width: 50%;
|
||||||
|
|
||||||
|
.bp3-control{
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 1.8rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +107,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bp3-input-group{
|
.bp3-input-group{
|
||||||
padding: 8px;
|
margin: 8px 8px 0;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
|
|
||||||
.bp3-input:not(:first-child){
|
.bp3-input:not(:first-child){
|
||||||
|
|||||||
@@ -182,3 +182,10 @@ export const firstLettersArgs = (...args) => {
|
|||||||
});
|
});
|
||||||
return letters.join('').toUpperCase();
|
return letters.join('').toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const uniqueMultiProps = (items, props) => {
|
||||||
|
return _.uniqBy(items, (item) => {
|
||||||
|
return JSON.stringify(_.pick(item, props));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -40,6 +40,11 @@ export default {
|
|||||||
},
|
},
|
||||||
'created_at': {
|
'created_at': {
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
column: 'active',
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ exports.up = function (knex) {
|
|||||||
table.boolean('builtin').defaultTo(false);
|
table.boolean('builtin').defaultTo(false);
|
||||||
table.boolean('columnable');
|
table.boolean('columnable');
|
||||||
table.integer('index');
|
table.integer('index');
|
||||||
|
table.string('data_resource');
|
||||||
table.json('options');
|
table.json('options');
|
||||||
table.integer('resource_id').unsigned();
|
table.integer('resource_id').unsigned();
|
||||||
}).raw('ALTER TABLE `RESOURCE_FIELDS` AUTO_INCREMENT = 1000').then(() => {
|
}).raw('ALTER TABLE `RESOURCE_FIELDS` AUTO_INCREMENT = 1000').then(() => {
|
||||||
|
|||||||
@@ -1,15 +1,51 @@
|
|||||||
|
exports.seed = function (knex) {
|
||||||
exports.seed = function(knex) {
|
|
||||||
// Deletes ALL existing entries
|
// Deletes ALL existing entries
|
||||||
return knex('resource_fields').del()
|
return knex('resource_fields')
|
||||||
|
.del()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('resource_fields').insert([
|
return knex('resource_fields').insert([
|
||||||
{ id: 1, label_name: 'Name', key: 'name', data_type: '', active: 1, predefined: 1 },
|
{
|
||||||
{ id: 2, label_name: 'Code', key: 'code', data_type: '', active: 1, predefined: 1 },
|
id: 1,
|
||||||
{ id: 3, label_name: 'Account Type', key: 'type', data_type: '', active: 1, predefined: 1 },
|
label_name: 'Name',
|
||||||
{ id: 4, label_name: 'Description', key: 'description', data_type: '', active: 1, predefined: 1 },
|
key: 'name',
|
||||||
{ id: 5, label_name: 'Account Normal', key: 'normal', data_type: 'string', active: 1, predefined: 1 },
|
data_type: '',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label_name: 'Code',
|
||||||
|
key: 'code',
|
||||||
|
data_type: '',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
label_name: 'Account Type',
|
||||||
|
key: 'type',
|
||||||
|
data_type: '',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
data_resource_id: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
label_name: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
data_type: '',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
label_name: 'Account Normal',
|
||||||
|
key: 'normal',
|
||||||
|
data_type: 'string',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
label_name: 'Root Account Type',
|
label_name: 'Root Account Type',
|
||||||
@@ -18,6 +54,14 @@ exports.seed = function(knex) {
|
|||||||
active: 1,
|
active: 1,
|
||||||
predefined: 1,
|
predefined: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
label_name: 'Active',
|
||||||
|
key: 'active',
|
||||||
|
data_type: 'boolean',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ exports.seed = (knex) => {
|
|||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('resources').insert([
|
return knex('resources').insert([
|
||||||
{ id: 1, name: 'accounts' },
|
{ id: 1, name: 'accounts' },
|
||||||
|
{ id: 8, name: 'accounts_types' },
|
||||||
|
|
||||||
{ id: 2, name: 'items' },
|
{ id: 2, name: 'items' },
|
||||||
{ id: 3, name: 'expenses' },
|
{ id: 3, name: 'expenses' },
|
||||||
{ id: 4, name: 'manual_journals' },
|
{ id: 4, name: 'manual_journals' },
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ exports.seed = (knex) => {
|
|||||||
data_type: 'options',
|
data_type: 'options',
|
||||||
predefined: 1,
|
predefined: 1,
|
||||||
columnable: true,
|
columnable: true,
|
||||||
|
data_resource: 'accounts_types',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
@@ -58,6 +59,15 @@ exports.seed = (knex) => {
|
|||||||
predefined: 1,
|
predefined: 1,
|
||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
resource_id: 1,
|
||||||
|
data_type: 'boolean',
|
||||||
|
label_name: 'Active',
|
||||||
|
key: 'active',
|
||||||
|
predefined: 1,
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Expenses
|
// Expenses
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ export default {
|
|||||||
errors: [{ type: 'NOT_EXIST_ACCOUNT_TYPE', code: 200 }],
|
errors: [{ type: 'NOT_EXIST_ACCOUNT_TYPE', code: 200 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await Account.query().insert({ ...form });
|
const insertedAccount = await Account.query().insertAndFetch({ ...form });
|
||||||
|
|
||||||
return res.status(200).send({ item: { } });
|
return res.status(200).send({ account: { ...insertedAccount } });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export default {
|
|||||||
check('name').exists().isLength({ min: 3 })
|
check('name').exists().isLength({ min: 3 })
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
check('code').exists().isLength({ max: 10 })
|
check('code').optional().isLength({ max: 10 })
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
check('account_type_id').exists().isNumeric().toInt(),
|
check('account_type_id').exists().isNumeric().toInt(),
|
||||||
@@ -157,28 +157,30 @@ export default {
|
|||||||
if (!account) {
|
if (!account) {
|
||||||
return res.boom.notFound();
|
return res.boom.notFound();
|
||||||
}
|
}
|
||||||
const foundAccountCodePromise = (form.code && form.code !== account.code)
|
const errorReasons = [];
|
||||||
? Account.query().where('code', form.code).whereNot('id', account.id) : null;
|
|
||||||
|
|
||||||
const foundAccountTypePromise = (form.account_type_id !== account.account_type_id)
|
// Validate the account type is not changed.
|
||||||
? AccountType.query().where('id', form.account_type_id) : null;
|
if (account.account_type_id != form.accountTypeId) {
|
||||||
|
errorReasons.push({
|
||||||
const [foundAccountCode, foundAccountType] = await Promise.all([
|
type: 'NOT.ALLOWED.TO.CHANGE.ACCOUNT.TYPE', code: 100,
|
||||||
foundAccountCodePromise, foundAccountTypePromise,
|
|
||||||
]);
|
|
||||||
if (foundAccountCode.length > 0 && foundAccountCodePromise) {
|
|
||||||
return res.boom.badRequest(null, {
|
|
||||||
errors: [{ type: 'NOT_UNIQUE_CODE', code: 100 }],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (foundAccountType.length <= 0 && foundAccountTypePromise) {
|
// Validate the account code not exists on the storage.
|
||||||
return res.boom.badRequest(null, {
|
if (form.code && form.code !== account.code) {
|
||||||
errors: [{ type: 'NOT_EXIST_ACCOUNT_TYPE', code: 110 }],
|
const foundAccountCode = await Account.query().where('code', form.code).whereNot('id', account.id);
|
||||||
});
|
|
||||||
}
|
|
||||||
await account.patch({ ...form });
|
|
||||||
|
|
||||||
return res.status(200).send();
|
if (foundAccountCode.length > 0) {
|
||||||
|
errorReasons.push({ type: 'NOT_UNIQUE_CODE', code: 200 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorReasons.length > 0) {
|
||||||
|
return res.status(400).send({ error: errorReasons });
|
||||||
|
}
|
||||||
|
// Update the account on the storage.
|
||||||
|
const updatedAccount = await Account.query().patchAndFetchById(account.id, { ...form });
|
||||||
|
|
||||||
|
return res.status(200).send({ account: { ...updatedAccount } });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -268,7 +270,6 @@ export default {
|
|||||||
if (filter.stringified_filter_roles) {
|
if (filter.stringified_filter_roles) {
|
||||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Resource, Account, View } = req.models;
|
const { Resource, Account, View } = req.models;
|
||||||
const errorReasons = [];
|
const errorReasons = [];
|
||||||
|
|
||||||
@@ -338,14 +339,31 @@ export default {
|
|||||||
if (errorReasons.length > 0) {
|
if (errorReasons.length > 0) {
|
||||||
return res.status(400).send({ errors: errorReasons });
|
return res.status(400).send({ errors: errorReasons });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const query = Account.query()
|
||||||
|
// .remember()
|
||||||
|
.onBuild((builder) => {
|
||||||
|
builder.modify('filterAccountTypes', filter.account_types);
|
||||||
|
builder.withGraphFetched('type');
|
||||||
|
builder.withGraphFetched('balance');
|
||||||
|
|
||||||
|
dynamicFilter.buildQuery()(builder);
|
||||||
|
|
||||||
|
// console.log(builder.toKnexQuery().toSQL());
|
||||||
|
}).toKnexQuery().toSQL();
|
||||||
|
|
||||||
|
console.log(query);
|
||||||
|
|
||||||
const accounts = await Account.query()
|
const accounts = await Account.query()
|
||||||
.remember()
|
// .remember()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.modify('filterAccountTypes', filter.account_types);
|
builder.modify('filterAccountTypes', filter.account_types);
|
||||||
builder.withGraphFetched('type');
|
builder.withGraphFetched('type');
|
||||||
builder.withGraphFetched('balance');
|
builder.withGraphFetched('balance');
|
||||||
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
|
|
||||||
|
// console.log(builder.toKnexQuery().toSQL());
|
||||||
});
|
});
|
||||||
|
|
||||||
const nestedAccounts = Account.toNestedArray(accounts);
|
const nestedAccounts = Account.toNestedArray(accounts);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
import { Lexer } from '@/lib/LogicEvaluation/Lexer';
|
import { Lexer } from '@/lib/LogicEvaluation/Lexer';
|
||||||
import Parser from '@/lib/LogicEvaluation/Parser';
|
import Parser from '@/lib/LogicEvaluation/Parser';
|
||||||
import QueryParser from '@/lib/LogicEvaluation/QueryParser';
|
import QueryParser from '@/lib/LogicEvaluation/QueryParser';
|
||||||
@@ -12,25 +13,8 @@ import resourceFieldsKeys from '@/data/ResourceFieldsKeys';
|
|||||||
// index: Number,
|
// index: Number,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* Get field column metadata and its relation with other tables.
|
|
||||||
* @param {String} tableName - Table name of target column.
|
|
||||||
* @param {String} columnKey - Target column key that stored in resource field.
|
|
||||||
*/
|
|
||||||
export function getRoleFieldColumn(tableName, columnKey) {
|
|
||||||
const tableFields = resourceFieldsKeys[tableName];
|
|
||||||
return (tableFields[columnKey]) ? tableFields[columnKey] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds roles queries.
|
|
||||||
* @param {String} tableName -
|
|
||||||
* @param {Object} role -
|
|
||||||
*/
|
|
||||||
export function buildRoleQuery(tableName, role) {
|
|
||||||
const fieldRelation = getRoleFieldColumn(tableName, role.columnKey);
|
|
||||||
const comparatorColumn = fieldRelation.relationColumn || `${tableName}.${fieldRelation.column}`;
|
|
||||||
|
|
||||||
|
const textRoleQueryBuilder = (role, comparatorColumn) => {
|
||||||
switch (role.comparator) {
|
switch (role.comparator) {
|
||||||
case 'equals':
|
case 'equals':
|
||||||
default:
|
default:
|
||||||
@@ -47,6 +31,81 @@ export function buildRoleQuery(tableName, role) {
|
|||||||
return (builder) => {
|
return (builder) => {
|
||||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||||
};
|
};
|
||||||
|
case 'not_contain':
|
||||||
|
case 'not_contains':
|
||||||
|
return (builder) => {
|
||||||
|
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateQueryBuilder = (role, comparatorColumn) => {
|
||||||
|
switch(role.comparator) {
|
||||||
|
case 'after':
|
||||||
|
case 'before':
|
||||||
|
return (builder) => {
|
||||||
|
const comparator = role.comparator === 'before' ? '<' : '>';
|
||||||
|
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
||||||
|
const targetDate = moment(role.value);
|
||||||
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||||
|
|
||||||
|
if (!hasTimeFormat) {
|
||||||
|
if (role.comparator === 'before') {
|
||||||
|
targetDate.startOf('day');
|
||||||
|
} else {
|
||||||
|
targetDate.endOf('day');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const comparatorValue = targetDate.format(dateFormat);
|
||||||
|
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||||
|
};
|
||||||
|
case 'in':
|
||||||
|
return (builder) => {
|
||||||
|
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
||||||
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||||
|
|
||||||
|
if (hasTimeFormat) {
|
||||||
|
const targetDateTime = moment(role.value).format(dateFormat);
|
||||||
|
builder.where(comparatorColumn, '=', targetDateTime);
|
||||||
|
} else {
|
||||||
|
const startDate = moment(role.value).startOf('day');
|
||||||
|
const endDate = moment(role.value).endOf('day');
|
||||||
|
|
||||||
|
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||||
|
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get field column metadata and its relation with other tables.
|
||||||
|
* @param {String} tableName - Table name of target column.
|
||||||
|
* @param {String} columnKey - Target column key that stored in resource field.
|
||||||
|
*/
|
||||||
|
export function getRoleFieldColumn(tableName, columnKey) {
|
||||||
|
const tableFields = resourceFieldsKeys[tableName];
|
||||||
|
return (tableFields[columnKey]) ? tableFields[columnKey] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds roles queries.
|
||||||
|
* @param {String} tableName -
|
||||||
|
* @param {Object} role -
|
||||||
|
*/
|
||||||
|
export function buildRoleQuery(tableName, role) {
|
||||||
|
const fieldRelation = getRoleFieldColumn(tableName, role.columnKey);
|
||||||
|
const comparatorColumn = fieldRelation.relationColumn || `${tableName}.${fieldRelation.column}`;
|
||||||
|
|
||||||
|
switch (fieldRelation.columnType) {
|
||||||
|
case 'date':
|
||||||
|
return dateQueryBuilder(role, comparatorColumn);
|
||||||
|
case 'text':
|
||||||
|
case 'varchar':
|
||||||
|
default:
|
||||||
|
return textRoleQueryBuilder(role, comparatorColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user