feat: fix accounts issue.

This commit is contained in:
Ahmed Bouhuolia
2020-06-25 13:43:47 +02:00
parent 6074134a53
commit 111aa83908
46 changed files with 797 additions and 345 deletions

View File

@@ -8,15 +8,10 @@ import DialogsContainer from 'components/DialogsContainer';
import PreferencesContent from 'components/Preferences/PreferencesContent';
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
import Search from 'containers/GeneralSearch/Search';
import withDashboard from 'containers/Dashboard/withDashboard';
import { compose } from 'utils';
function Dashboard({ sidebarExpended }) {
export default function Dashboard() {
return (
<div className={classNames('dashboard', {
'has-mini-sidebar': !sidebarExpended,
})}>
<div className={classNames('dashboard')}>
<Switch>
<Route path="/preferences">
<Sidebar />
@@ -35,9 +30,3 @@ function Dashboard({ sidebarExpended }) {
</div>
);
}
export default compose(
withDashboard(({ sidebarExpended }) => ({
sidebarExpended,
})),
)(Dashboard);

View 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,
};

View File

@@ -1,19 +1,17 @@
import React from 'react';
import { connect } from 'react-redux';
import { isDialogOpen, getDialogPayload } from 'store/dashboard/dashboard.selectors';
export default (Dialog) => {
function DialogReduxConnect(props) {
return (<Dialog {...props} />);
};
const mapStateToProps = (state, props) => {
const dialogs = state.dashboard.dialogs;
if (dialogs && dialogs.hasOwnProperty['name'] && dialogs[props.name]) {
const { isOpen, payload } = dialogs[props.name];
return { isOpen, payload };
}
return {
isOpen: isDialogOpen(state, props),
payload: getDialogPayload(state, props),
};
};
return connect(

View File

@@ -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 }}
/>
);
}

View File

@@ -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];
};

View File

@@ -1,5 +1,11 @@
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 { useQuery } from 'react-query';
import { DateInput } from '@blueprintjs/datetime';
@@ -13,35 +19,42 @@ import { If, Choose, ListSelect, MODIFIER } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withResourceActions from 'containers/Resources/withResourcesActions';
import {
getConditionTypeCompatators,
getConditionDefaultCompatator,
} from './DynamicFilterCompatators';
import { compose, momentFormatter } from 'utils';
/**
* Dynamic filter fields.
*/
function DynamicFilterValueField({
fieldMeta,
dataType,
value,
initialValue,
error,
// fieldkey,
// resourceKey,
// #withResourceDetail
resourceName,
resourceData,
requestResourceData,
onChange,
rosourceKey,
inputDebounceWait = 500,
}) {
const { formatMessage } = useIntl();
const [localValue, setLocalValue] = useState();
const fetchResourceData = useQuery(
['resource-data', resourceName],
() => requestResourceData(resourceName),
resourceName && ['resource-data', resourceName],
(k, resName) => requestResourceData(resName),
{ manual: true },
);
@@ -57,7 +70,7 @@ function DynamicFilterValueField({
};
const handleBtnClick = () => {
fetchResourceData.refetch({ force: true });
fetchResourceData.refetch({});
};
const listOptions = useMemo(() => Object.values(resourceData), [
@@ -85,75 +98,85 @@ function DynamicFilterValueField({
onChange && onChange(value);
}, inputDebounceWait),
);
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);
};
const handleCheckboxChange = (e) => {
const value = !!e.currentTarget.checked;
setLocalValue(value);
onChange && onChange(value);
}
const handleDateChange = (date) => {
setLocalValue(date);
onChange && onChange(date);
};
const transformDateValue = (value) => {
return moment(value || new Date()).toDate();
return value ? moment(value || new Date()).toDate() : null;
};
return (
<FormGroup className={'form-group--value'}>
<Choose>
<Choose.When condition={true}>
<Spinner size={18} />
<Choose.When condition={dataType === 'options'}>
<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.Otherwise>
<Choose>
<Choose.When condition={fieldMeta.data_type === 'options'}>
<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={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>
<InputGroup
placeholder={formatMessage({ id: 'value' })}
onChange={handleInputChange}
value={localValue}
/>
</Choose.Otherwise>
</Choose>
</FormGroup>
@@ -161,7 +184,7 @@ function DynamicFilterValueField({
}
const mapStateToProps = (state, props) => ({
resourceName: props.fieldMeta.resource_key || 'account_type',
resourceName: props.dataResource,
});
const withResourceFilterValueField = connect(mapStateToProps);

View File

@@ -1,3 +1,4 @@
// @flow
import React, { useEffect, useMemo, useCallback, useRef } from 'react';
import {
FormGroup,
@@ -7,25 +8,39 @@ import {
Intent,
} from '@blueprintjs/core';
import { useFormik } from 'formik';
import { isEqual } from 'lodash';
import { isEqual, last } from 'lodash';
import { usePrevious } from 'react-use';
import { debounce } from 'lodash';
import Icon from 'components/Icon';
import { checkRequiredProperties } from 'utils';
import { checkRequiredProperties, uniqueMultiProps } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { DynamicFilterValueField } from 'components';
import {
DynamicFilterValueField,
DynamicFilterCompatatorField,
} from 'components';
import Toaster from 'components/AppToaster';
import moment from 'moment';
import {
getConditionTypeCompatators,
getConditionDefaultCompatator
} from './DynamicFilter/DynamicFilterCompatators';
let limitToast;
type InitialCondition = {
fieldKey: string,
comparator: string,
value: string,
};
/**
* Filter popover content.
*/
export default function FilterDropdown({
fields,
onFilterChange,
refetchDebounceWait = 250,
refetchDebounceWait = 10,
initialCondition,
}) {
const { formatMessage } = useIntl();
const fieldsKeyMapped = new Map(fields.map((field) => [field.key, field]));
@@ -37,6 +52,7 @@ export default function FilterDropdown({
],
[formatMessage],
);
const resourceFields = useMemo(
() => [
...fields.map((field) => ({
@@ -46,23 +62,13 @@ export default function FilterDropdown({
],
[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(
() => ({
condition: 'and',
field_key: fields.length > 0 ? fields[0].key : '',
compatator: 'equals',
value: '',
field_key: initialCondition.fieldKey,
comparator: initialCondition.comparator,
value: initialCondition.value,
}),
[fields],
);
@@ -86,17 +92,17 @@ export default function FilterDropdown({
} else {
setFieldValue('conditions', [
...values.conditions,
defaultFilterCondition,
last(values.conditions),
]);
}
}, [values, defaultFilterCondition, setFieldValue]);
const filteredFilterConditions = useMemo(() => {
const requiredProps = ['field_key', 'condition', 'compatator', 'value'];
return values.conditions.filter(
const requiredProps = ['field_key', 'condition', 'comparator', 'value'];
const conditions = values.conditions.filter(
(condition) => !checkRequiredProperties(condition, requiredProps),
);
return uniqueMultiProps(conditions, requiredProps);
}, [values.conditions]);
const prevConditions = usePrevious(filteredFilterConditions);
@@ -109,7 +115,7 @@ export default function FilterDropdown({
useEffect(() => {
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
onFilterChangeThrottled.current(filteredFilterConditions);
onFilterChange && onFilterChange(filteredFilterConditions);
}
}, [filteredFilterConditions, prevConditions]);
@@ -124,7 +130,7 @@ export default function FilterDropdown({
setFieldValue('conditions', [...conditions]);
};
// transform dynamic value field.
// Transform dynamic value field.
const transformValueField = (value) => {
if (value instanceof Date) {
return moment(value).format('YYYY-MM-DD');
@@ -145,27 +151,52 @@ export default function FilterDropdown({
const currentField = fieldsKeyMapped.get(
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`, '');
}
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);
},
};
};
// Value field props.
const valueFieldProps = (name, index) => ({
...fieldProps(name, index),
onChange: (value) => {
const transformedValue = transformValueField(value);
setFieldValue(`conditions[${index}].${name}`, transformedValue);
},
});
// Compatator field props.
const comparatorFieldProps = (name, index) => {
const condition = values.conditions[index];
const field = fieldsKeyMapped.get(condition.field_key);
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 (
<div class="filter-dropdown">
@@ -190,18 +221,15 @@ export default function FilterDropdown({
/>
</FormGroup>
<FormGroup className={'form-group--compatator'}>
<HTMLSelect
options={compatatorsItems}
<FormGroup className={'form-group--comparator'}>
<DynamicFilterCompatatorField
className={Classes.FILL}
{...fieldProps('compatator', index)}
{...comparatorFieldProps('comparator', index)}
/>
</FormGroup>
<DynamicFilterValueField
fieldMeta={fieldsKeyMapped.get(condition.field_key)}
{...valueFieldProps('value', index)}
/>
<DynamicFilterValueField {...valueFieldProps('value', index)} />
<Button
icon={<Icon icon="times" iconSize={14} />}
minimal={true}

View File

@@ -1,4 +1,4 @@
import * as React from 'react';
import React, { useEffect } from 'react';
import { Scrollbar } from 'react-scrollbars-custom';
import classNames from 'classnames';
@@ -17,6 +17,10 @@ function SidebarContainer({
// #withDashboard
sidebarExpended,
}) {
useEffect(() => {
document.body.classList.toggle('has-mini-sidebar', !sidebarExpended);
}, [sidebarExpended]);
return (
<div
className={classNames('sidebar', {

View File

@@ -6,11 +6,13 @@ import For from './Utils/For';
import ListSelect from './ListSelect';
import FinancialStatement from './FinancialStatement';
import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField';
import DynamicFilterCompatatorField from './DynamicFilter/DynamicFilterCompatatorField';
import ErrorMessage from './ErrorMessage';
import MODIFIER from './modifiers';
import FieldHint from './FieldHint';
import MenuItemLabel from './MenuItemLabel';
import Pagination from './Pagination';
import DashboardViewsTabs from './Dashboard/DashboardViewsTabs';
const Hint = FieldHint;
@@ -23,11 +25,13 @@ export {
FinancialStatement,
Choose,
DynamicFilterValueField,
DynamicFilterCompatatorField,
MODIFIER,
ErrorMessage,
FieldHint,
Hint,
MenuItemLabel,
Pagination,
DashboardViewsTabs,
// For,
};