mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: WIP advanced filter.
This commit is contained in:
@@ -138,6 +138,7 @@
|
|||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
|
"@types/yup": "^0.29.13",
|
||||||
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
||||||
"compression-webpack-plugin": "^6.1.0",
|
"compression-webpack-plugin": "^6.1.0",
|
||||||
"http-proxy-middleware": "^1.0.0",
|
"http-proxy-middleware": "^1.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
export const getFilterDropdownSchema = () =>
|
||||||
|
Yup.object().shape({
|
||||||
|
conditions: Yup.array().of(
|
||||||
|
Yup.object().shape({
|
||||||
|
fieldKey: Yup.string(),
|
||||||
|
value: Yup.string().nullable(),
|
||||||
|
condition: Yup.string().nullable(),
|
||||||
|
comparator: Yup.string().nullable(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import * as Yup from 'yup';
|
|
||||||
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
|
||||||
|
|
||||||
export const getFilterDropdownSchema = () => Yup.object().shape({
|
|
||||||
conditions: Yup.array().of(
|
|
||||||
Yup.object().shape({
|
|
||||||
fieldKey: Yup.string().max(DATATYPES_LENGTH.TEXT),
|
|
||||||
value: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
|
||||||
condition: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
|
||||||
comparator: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
import ListSelect from '../ListSelect';
|
||||||
|
import { getConditionTypeCompatators } from './utils';
|
||||||
|
|
||||||
|
export default function DynamicFilterCompatatorField({
|
||||||
|
dataType,
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const options = getConditionTypeCompatators(dataType);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListSelect
|
||||||
|
textProp={'label'}
|
||||||
|
selectedItemProp={'value'}
|
||||||
|
items={options}
|
||||||
|
className={Classes.FILL}
|
||||||
|
filterable={false}
|
||||||
|
popoverProps={{
|
||||||
|
inline: true,
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
}}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import React, { useMemo } from 'react';
|
|
||||||
import { HTMLSelect, Classes } from '@blueprintjs/core';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { getConditionTypeCompatators } from './utils';
|
|
||||||
|
|
||||||
export default function DynamicFilterCompatatorField({
|
|
||||||
dataType,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
const options = useMemo(
|
|
||||||
() => getConditionTypeCompatators(dataType).map(comp => ({
|
|
||||||
value: comp.value, label: intl.get(comp.label),
|
|
||||||
})),
|
|
||||||
[dataType]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HTMLSelect
|
|
||||||
options={options}
|
|
||||||
className={Classes.FILL}
|
|
||||||
{...{ ...restProps }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
397
client/src/components/AdvancedFilter/AdvancedFilterDropdown.js
Normal file
397
client/src/components/AdvancedFilter/AdvancedFilterDropdown.js
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik, FastField, FieldArray, useFormikContext } from 'formik';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
Classes,
|
||||||
|
InputGroup,
|
||||||
|
MenuItem,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { get, first, defaultTo, isEqual, isEmpty } from 'lodash';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Choose, Icon, FormattedMessage as T, ListSelect } from 'components';
|
||||||
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
import {
|
||||||
|
AdvancedFilterDropdownProvider,
|
||||||
|
FilterConditionProvider,
|
||||||
|
useFilterCondition,
|
||||||
|
useAdvancedFilterContext,
|
||||||
|
} from './AdvancedFilterDropdownContext';
|
||||||
|
import AdvancedFilterCompatatorField from './AdvancedFilterCompatatorField';
|
||||||
|
import AdvancedFilterValueField from './AdvancedFilterValueField';
|
||||||
|
import {
|
||||||
|
filterConditionRoles,
|
||||||
|
getConditionalsOptions,
|
||||||
|
transformFieldsToOptions,
|
||||||
|
shouldFilterValueFieldUpdate,
|
||||||
|
getConditionTypeCompatators,
|
||||||
|
} from './utils';
|
||||||
|
import { getFilterDropdownSchema } from './AdvancedFilter.schema';
|
||||||
|
import { useAdvancedFilterAutoSubmit } from './components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condition item list renderer.
|
||||||
|
*/
|
||||||
|
function ConditionItemRenderer(condition, { handleClick, modifiers, query }) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<div>{condition.label}</div>
|
||||||
|
<span className="text-hint">{condition.text}</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
key={condition.value}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter condition field.
|
||||||
|
*/
|
||||||
|
function FilterConditionField() {
|
||||||
|
const conditionalsOptions = getConditionalsOptions();
|
||||||
|
const { conditionIndex, getConditionFieldPath } = useFilterCondition();
|
||||||
|
|
||||||
|
const conditionFieldPath = getConditionFieldPath('condition');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FastField name={conditionFieldPath}>
|
||||||
|
{({ form, field }) => (
|
||||||
|
<FormGroup className={'form-group--condition'}>
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={conditionIndex === 0}>
|
||||||
|
<InputGroup disabled value={intl.get('filter.when')} />
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<ListSelect
|
||||||
|
selectedItem={field.value}
|
||||||
|
textProp={'label'}
|
||||||
|
selectedItemProp={'value'}
|
||||||
|
labelProp={'text'}
|
||||||
|
items={conditionalsOptions}
|
||||||
|
className={Classes.FILL}
|
||||||
|
filterable={false}
|
||||||
|
onItemSelect={(option) => {
|
||||||
|
form.setFieldValue(conditionFieldPath, option.value);
|
||||||
|
}}
|
||||||
|
popoverProps={{
|
||||||
|
inline: true,
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
}}
|
||||||
|
itemRenderer={ConditionItemRenderer}
|
||||||
|
/>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatator field.
|
||||||
|
*/
|
||||||
|
function FilterCompatatorFilter() {
|
||||||
|
const { getConditionFieldPath, fieldMeta } = useFilterCondition();
|
||||||
|
|
||||||
|
const comparatorFieldPath = getConditionFieldPath('comparator');
|
||||||
|
const fieldType = get(fieldMeta, 'fieldType');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FastField name={comparatorFieldPath}>
|
||||||
|
{({ form, field }) => (
|
||||||
|
<FormGroup className={'form-group--comparator'}>
|
||||||
|
<AdvancedFilterCompatatorField
|
||||||
|
dataType={fieldType}
|
||||||
|
className={Classes.FILL}
|
||||||
|
selectedItem={field.value}
|
||||||
|
onItemSelect={(option) => {
|
||||||
|
form.setFieldValue(comparatorFieldPath, option.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes default value of comparator field in the condition row once the
|
||||||
|
* field option changing.
|
||||||
|
*/
|
||||||
|
function useDefaultComparatorFieldValue({
|
||||||
|
getConditionValue,
|
||||||
|
setConditionValue,
|
||||||
|
fieldMeta,
|
||||||
|
}) {
|
||||||
|
const fieldKeyValue = getConditionValue('fieldKey');
|
||||||
|
|
||||||
|
const comparatorsOptions = React.useMemo(
|
||||||
|
() => getConditionTypeCompatators(fieldMeta.fieldType),
|
||||||
|
[fieldMeta.fieldType],
|
||||||
|
);
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
if (fieldKeyValue) {
|
||||||
|
const defaultValue = get(first(comparatorsOptions), 'value');
|
||||||
|
setConditionValue('comparator', defaultValue);
|
||||||
|
}
|
||||||
|
}, [fieldKeyValue, setConditionValue, comparatorsOptions]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource fields field.
|
||||||
|
*/
|
||||||
|
function FilterFieldsField() {
|
||||||
|
const {
|
||||||
|
getConditionFieldPath,
|
||||||
|
getConditionValue,
|
||||||
|
setConditionValue,
|
||||||
|
fieldMeta,
|
||||||
|
} = useFilterCondition();
|
||||||
|
|
||||||
|
const { fields } = useAdvancedFilterContext();
|
||||||
|
|
||||||
|
const fieldPath = getConditionFieldPath('fieldKey');
|
||||||
|
const valueFieldPath = getConditionFieldPath('value');
|
||||||
|
|
||||||
|
useDefaultComparatorFieldValue({
|
||||||
|
getConditionValue,
|
||||||
|
setConditionValue,
|
||||||
|
fieldMeta,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FastField name={fieldPath}>
|
||||||
|
{({ field, form }) => (
|
||||||
|
<FormGroup className={'form-group--fieldKey'}>
|
||||||
|
<ListSelect
|
||||||
|
selectedItem={field.value}
|
||||||
|
textProp={'label'}
|
||||||
|
selectedItemProp={'value'}
|
||||||
|
items={transformFieldsToOptions(fields)}
|
||||||
|
className={Classes.FILL}
|
||||||
|
onItemSelect={(option) => {
|
||||||
|
form.setFieldValue(fieldPath, option.value);
|
||||||
|
|
||||||
|
// Resets the value field to empty once the field option changing.
|
||||||
|
form.setFieldValue(valueFieldPath, '');
|
||||||
|
}}
|
||||||
|
popoverProps={{
|
||||||
|
inline: true,
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter value field.
|
||||||
|
*/
|
||||||
|
function FilterValueField() {
|
||||||
|
const { conditionIndex, fieldMeta, getConditionFieldPath } =
|
||||||
|
useFilterCondition();
|
||||||
|
|
||||||
|
// Can't continue if the given field key is not selected yet.
|
||||||
|
if (!fieldMeta) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Field meta type, name and options.
|
||||||
|
const fieldType = get(fieldMeta, 'fieldType');
|
||||||
|
const fieldName = get(fieldMeta, 'name');
|
||||||
|
const options = get(fieldMeta, 'options');
|
||||||
|
|
||||||
|
const valueFieldPath = getConditionFieldPath('value');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FastField
|
||||||
|
name={valueFieldPath}
|
||||||
|
fieldKey={fieldType} // Pass to shouldUpdate function.
|
||||||
|
shouldUpdate={shouldFilterValueFieldUpdate}
|
||||||
|
>
|
||||||
|
{({ form: { setFieldValue }, field }) => (
|
||||||
|
<FormGroup className={'form-group--value'}>
|
||||||
|
<AdvancedFilterValueField
|
||||||
|
isFocus={conditionIndex === 0}
|
||||||
|
value={field.value}
|
||||||
|
key={'name'}
|
||||||
|
label={fieldName}
|
||||||
|
fieldType={fieldType}
|
||||||
|
options={options}
|
||||||
|
onChange={(value) => {
|
||||||
|
setFieldValue(valueFieldPath, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter condition line.
|
||||||
|
*/
|
||||||
|
function AdvancedFilterDropdownCondition({ conditionIndex, onRemoveClick }) {
|
||||||
|
// Handle click remove condition.
|
||||||
|
const handleClickRemoveCondition = () => {
|
||||||
|
onRemoveClick && onRemoveClick(conditionIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="filter-dropdown__condition">
|
||||||
|
<FilterConditionProvider conditionIndex={conditionIndex}>
|
||||||
|
<FilterConditionField />
|
||||||
|
<FilterFieldsField />
|
||||||
|
<FilterCompatatorFilter />
|
||||||
|
<FilterValueField />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon={<Icon icon="times" iconSize={14} />}
|
||||||
|
minimal={true}
|
||||||
|
onClick={handleClickRemoveCondition}
|
||||||
|
className={'button--remove'}
|
||||||
|
/>
|
||||||
|
</FilterConditionProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter dropdown condition.
|
||||||
|
*/
|
||||||
|
function AdvancedFilterDropdownConditions({ push, remove, replace, form }) {
|
||||||
|
const { initialCondition } = useAdvancedFilterContext();
|
||||||
|
|
||||||
|
// Handle remove condition.
|
||||||
|
const handleClickRemoveCondition = (conditionIndex) => {
|
||||||
|
if (form.values.conditions.length > 1) {
|
||||||
|
remove(conditionIndex);
|
||||||
|
} else {
|
||||||
|
replace(0, { ...initialCondition });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle new condition button click.
|
||||||
|
const handleNewConditionBtnClick = (index) => {
|
||||||
|
push({ ...initialCondition });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="filter-dropdonw__conditions-wrap">
|
||||||
|
<div className={'filter-dropdown__conditions'}>
|
||||||
|
{form.values.conditions.map((condition, index) => (
|
||||||
|
<AdvancedFilterDropdownCondition
|
||||||
|
conditionIndex={index}
|
||||||
|
onRemoveClick={handleClickRemoveCondition}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<AdvancedFilterDropdownFooter onClick={handleNewConditionBtnClick} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter dropdown form.
|
||||||
|
*/
|
||||||
|
function AdvancedFilterDropdownForm() {
|
||||||
|
// Advanced filter auto-save.
|
||||||
|
useAdvancedFilterAutoSubmit();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="filter-dropdown__form">
|
||||||
|
<FieldArray
|
||||||
|
name={'conditions'}
|
||||||
|
render={({ ...fieldArrayProps }) => (
|
||||||
|
<AdvancedFilterDropdownConditions {...fieldArrayProps} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter dropdown footer.
|
||||||
|
*/
|
||||||
|
function AdvancedFilterDropdownFooter({ onClick }) {
|
||||||
|
// Handle new filter condition button click.
|
||||||
|
const onClickNewFilter = (event) => {
|
||||||
|
onClick && onClick(event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="filter-dropdown__footer">
|
||||||
|
<Button minimal={true} onClick={onClickNewFilter}>
|
||||||
|
<T id={'new_conditional'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter dropdown.
|
||||||
|
*/
|
||||||
|
export function AdvancedFilterDropdown({
|
||||||
|
fields,
|
||||||
|
conditions,
|
||||||
|
defaultFieldKey,
|
||||||
|
defaultComparator,
|
||||||
|
defaultValue,
|
||||||
|
defaultCondition,
|
||||||
|
onFilterChange,
|
||||||
|
}) {
|
||||||
|
// Initial condition.
|
||||||
|
const initialCondition = {
|
||||||
|
fieldKey: defaultFieldKey,
|
||||||
|
comparator: defaultTo(defaultComparator, 'contain'),
|
||||||
|
condition: defaultTo(defaultCondition, 'or'),
|
||||||
|
value: defaultTo(defaultValue, ''),
|
||||||
|
};
|
||||||
|
// Initial conditions.
|
||||||
|
const initialConditions = !isEmpty(conditions)
|
||||||
|
? conditions
|
||||||
|
: [initialCondition, initialCondition];
|
||||||
|
|
||||||
|
const [prevConditions, setPrevConditions] = React.useState(initialConditions);
|
||||||
|
|
||||||
|
// Handle the filter dropdown form submit.
|
||||||
|
const handleFitlerDropdownSubmit = (values) => {
|
||||||
|
const conditions = filterConditionRoles(values.conditions);
|
||||||
|
|
||||||
|
// Campare the current conditions with previous conditions, if they were equal
|
||||||
|
// there is no need to execute `onFilterChange` function.
|
||||||
|
if (!isEqual(prevConditions, conditions)) {
|
||||||
|
onFilterChange && onFilterChange(conditions);
|
||||||
|
setPrevConditions(conditions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Filter dropdown validation schema.
|
||||||
|
const validationSchema = getFilterDropdownSchema();
|
||||||
|
|
||||||
|
// Initial values.
|
||||||
|
const initialValues = {
|
||||||
|
conditions: initialConditions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="filter-dropdown">
|
||||||
|
<AdvancedFilterDropdownProvider
|
||||||
|
initialCondition={initialCondition}
|
||||||
|
fields={fields}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
component={AdvancedFilterDropdownForm}
|
||||||
|
onSubmit={handleFitlerDropdownSubmit}
|
||||||
|
/>
|
||||||
|
</AdvancedFilterDropdownProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Formik, Field, FieldArray, useFormikContext } from 'formik';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Intent,
|
|
||||||
FormGroup,
|
|
||||||
Classes,
|
|
||||||
HTMLSelect,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { get, defaultTo, isEqual } from 'lodash';
|
|
||||||
import { Icon, FormattedMessage as T } from 'components';
|
|
||||||
import {
|
|
||||||
AdvancedFilterDropdownProvider,
|
|
||||||
FilterConditionProvider,
|
|
||||||
useFilterCondition,
|
|
||||||
useAdvancedFilterContext,
|
|
||||||
} from './AdvancedFilterDropdownContext';
|
|
||||||
import AdvancedFilterCompatatorField from './AdvancedFilterCompatatorField';
|
|
||||||
import AdvancedFilterValueField from './AdvancedFilterValueField2';
|
|
||||||
import {
|
|
||||||
filterConditionRoles,
|
|
||||||
getConditionalsOptions,
|
|
||||||
transformFieldsToOptions,
|
|
||||||
} from './utils';
|
|
||||||
import { getFilterDropdownSchema } from './AdvancedFilter.schema';
|
|
||||||
import {
|
|
||||||
IAdvancedFilterDropdown,
|
|
||||||
IAdvancedFilterDropdownFooter,
|
|
||||||
IFilterDropdownFormikValues,
|
|
||||||
IAdvancedFilterDropdownConditionsProps,
|
|
||||||
IAdvancedFilterDropdownCondition,
|
|
||||||
IFilterRole,
|
|
||||||
} from './interfaces';
|
|
||||||
import { useAdvancedFilterAutoSubmit } from './components';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter condition field.
|
|
||||||
*/
|
|
||||||
function FilterConditionField() {
|
|
||||||
const conditionalsOptions = getConditionalsOptions();
|
|
||||||
const { conditionIndex } = useFilterCondition();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Field name={`conditions[${conditionIndex}].condition`}>
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup className={'form-group--condition'}>
|
|
||||||
<HTMLSelect
|
|
||||||
options={conditionalsOptions}
|
|
||||||
className={Classes.FILL}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compatator field.
|
|
||||||
*/
|
|
||||||
function FilterCompatatorFilter() {
|
|
||||||
const { conditionIndex } = useFilterCondition();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Field name={`conditions[${conditionIndex}].comparator`}>
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup className={'form-group--comparator'}>
|
|
||||||
<AdvancedFilterCompatatorField
|
|
||||||
className={Classes.FILL}
|
|
||||||
dataType={'text'}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource fields field.
|
|
||||||
*/
|
|
||||||
function FilterFieldsField() {
|
|
||||||
const { conditionIndex } = useFilterCondition();
|
|
||||||
const { fields } = useAdvancedFilterContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Field name={`conditions[${conditionIndex}].fieldKey`}>
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup className={'form-group--fieldKey'}>
|
|
||||||
<HTMLSelect
|
|
||||||
options={transformFieldsToOptions(fields)}
|
|
||||||
value={1}
|
|
||||||
className={Classes.FILL}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter value field.
|
|
||||||
*/
|
|
||||||
function FilterValueField(): JSX.Element | null {
|
|
||||||
const { values } = useFormikContext();
|
|
||||||
const { conditionIndex } = useFilterCondition();
|
|
||||||
const { fieldsByKey } = useAdvancedFilterContext();
|
|
||||||
|
|
||||||
// Current condition field key.
|
|
||||||
const conditionFieldKey = get(
|
|
||||||
values.conditions,
|
|
||||||
`[${conditionIndex}].fieldKey`,
|
|
||||||
);
|
|
||||||
// Field meta.
|
|
||||||
const fieldMeta = fieldsByKey[conditionFieldKey];
|
|
||||||
|
|
||||||
// Can't continue if the given field key is not selected yet.
|
|
||||||
if (!conditionFieldKey || !fieldMeta) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Field meta type, name and options.
|
|
||||||
const fieldType = get(fieldMeta, 'fieldType');
|
|
||||||
const fieldName = get(fieldMeta, 'name');
|
|
||||||
const options = get(fieldMeta, 'options');
|
|
||||||
|
|
||||||
const valueFieldPath = `conditions[${conditionIndex}].value`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Field name={valueFieldPath}>
|
|
||||||
{({ form: { setFieldValue } }) => (
|
|
||||||
<FormGroup className={'form-group--value'}>
|
|
||||||
<AdvancedFilterValueField
|
|
||||||
key={'name'}
|
|
||||||
label={fieldName}
|
|
||||||
fieldType={fieldType}
|
|
||||||
options={options}
|
|
||||||
onChange={(value) => {
|
|
||||||
setFieldValue(valueFieldPath, value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter condition line.
|
|
||||||
*/
|
|
||||||
function AdvancedFilterDropdownCondition({
|
|
||||||
conditionIndex,
|
|
||||||
onRemoveClick,
|
|
||||||
}: IAdvancedFilterDropdownCondition) {
|
|
||||||
// Handle click remove condition.
|
|
||||||
const handleClickRemoveCondition = () => {
|
|
||||||
onRemoveClick && onRemoveClick(conditionIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="filter-dropdown__condition">
|
|
||||||
<FilterConditionProvider conditionIndex={conditionIndex}>
|
|
||||||
<FilterConditionField />
|
|
||||||
<FilterCompatatorFilter />
|
|
||||||
<FilterFieldsField />
|
|
||||||
<FilterValueField />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
icon={<Icon icon="times" iconSize={14} />}
|
|
||||||
minimal={true}
|
|
||||||
onClick={handleClickRemoveCondition}
|
|
||||||
/>
|
|
||||||
</FilterConditionProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter dropdown condition.
|
|
||||||
*/
|
|
||||||
function AdvancedFilterDropdownConditions({
|
|
||||||
push,
|
|
||||||
remove,
|
|
||||||
form,
|
|
||||||
}: IAdvancedFilterDropdownConditionsProps) {
|
|
||||||
const { initialCondition } = useAdvancedFilterContext();
|
|
||||||
|
|
||||||
// Handle remove condition.
|
|
||||||
const handleClickRemoveCondition = (conditionIndex: number) => {
|
|
||||||
remove(conditionIndex);
|
|
||||||
};
|
|
||||||
// Handle new condition button click.
|
|
||||||
const handleNewConditionBtnClick = (index: number) => {
|
|
||||||
push({ ...initialCondition });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="filter-dropdonw__conditions-wrap">
|
|
||||||
<div className={'filter-dropdown__conditions'}>
|
|
||||||
{form.values.conditions.map((condition: IFilterRole, index: number) => (
|
|
||||||
<AdvancedFilterDropdownCondition
|
|
||||||
conditionIndex={index}
|
|
||||||
onRemoveClick={handleClickRemoveCondition}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<AdvancedFilterDropdownFooter onClick={handleNewConditionBtnClick} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter dropdown form.
|
|
||||||
*/
|
|
||||||
function AdvancedFilterDropdownForm() {
|
|
||||||
//
|
|
||||||
useAdvancedFilterAutoSubmit();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="filter-dropdown__form">
|
|
||||||
<FieldArray
|
|
||||||
name={'conditions'}
|
|
||||||
render={({ ...fieldArrayProps }) => (
|
|
||||||
<AdvancedFilterDropdownConditions {...fieldArrayProps} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter dropdown footer.
|
|
||||||
*/
|
|
||||||
function AdvancedFilterDropdownFooter({
|
|
||||||
onClick,
|
|
||||||
}: IAdvancedFilterDropdownFooter) {
|
|
||||||
// Handle new filter condition button click.
|
|
||||||
const onClickNewFilter = (event) => {
|
|
||||||
onClick && onClick(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="filter-dropdown__footer">
|
|
||||||
<Button minimal={true} intent={Intent.PRIMARY} onClick={onClickNewFilter}>
|
|
||||||
<T id={'new_conditional'} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter dropdown.
|
|
||||||
*/
|
|
||||||
export default function AdvancedFilterDropdown({
|
|
||||||
fields,
|
|
||||||
defaultFieldKey,
|
|
||||||
defaultComparator,
|
|
||||||
defaultValue,
|
|
||||||
onFilterChange,
|
|
||||||
}: IAdvancedFilterDropdown) {
|
|
||||||
const [prevConditions, setPrevConditions] = React.useState({});
|
|
||||||
|
|
||||||
// Handle the filter dropdown form submit.
|
|
||||||
const handleFitlerDropdownSubmit = (values: IFilterDropdownFormikValues) => {
|
|
||||||
const conditions = filterConditionRoles(values.conditions);
|
|
||||||
|
|
||||||
// Campare the current conditions with previous conditions, if they were equal
|
|
||||||
// there is no need to execute `onFilterChange` function.
|
|
||||||
if (!isEqual(prevConditions, conditions) && conditions.length > 0) {
|
|
||||||
onFilterChange && onFilterChange(conditions);
|
|
||||||
|
|
||||||
setPrevConditions(conditions);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter dropdown validation schema.
|
|
||||||
const validationSchema = getFilterDropdownSchema();
|
|
||||||
|
|
||||||
// Initial condition.
|
|
||||||
const initialCondition = {
|
|
||||||
fieldKey: defaultFieldKey,
|
|
||||||
comparator: defaultTo(defaultComparator, 'equals'),
|
|
||||||
condition: '',
|
|
||||||
value: defaultTo(defaultValue, ''),
|
|
||||||
};
|
|
||||||
// Initial values.
|
|
||||||
const initialValues: IFilterDropdownFormikValues = {
|
|
||||||
conditions: [initialCondition],
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="filter-dropdown">
|
|
||||||
<AdvancedFilterDropdownProvider
|
|
||||||
initialCondition={initialCondition}
|
|
||||||
fields={fields}
|
|
||||||
>
|
|
||||||
<Formik
|
|
||||||
initialValues={initialValues}
|
|
||||||
validationSchema={validationSchema}
|
|
||||||
component={AdvancedFilterDropdownForm}
|
|
||||||
onSubmit={handleFitlerDropdownSubmit}
|
|
||||||
/>
|
|
||||||
</AdvancedFilterDropdownProvider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
import { get, keyBy } from 'lodash';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
const AdvancedFilterContext = createContext({});
|
||||||
|
const FilterConditionContext = createContext({});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter dropdown context provider.
|
||||||
|
*/
|
||||||
|
function AdvancedFilterDropdownProvider({
|
||||||
|
initialCondition,
|
||||||
|
fields,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const fieldsByKey = keyBy(fields, 'key');
|
||||||
|
|
||||||
|
// Retrieve field meta by the given field key.
|
||||||
|
const getFieldMetaByKey = React.useCallback(
|
||||||
|
(key) => get(fieldsByKey, key),
|
||||||
|
[fieldsByKey],
|
||||||
|
);
|
||||||
|
// Provider payload.
|
||||||
|
const provider = { initialCondition, fields, fieldsByKey, getFieldMetaByKey };
|
||||||
|
|
||||||
|
return <AdvancedFilterContext.Provider value={provider} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter condition row context provider.
|
||||||
|
*/
|
||||||
|
function FilterConditionProvider({ conditionIndex, ...props }) {
|
||||||
|
const { setFieldValue, values } = useFormikContext();
|
||||||
|
const { getFieldMetaByKey } = useAdvancedFilterContext();
|
||||||
|
|
||||||
|
// Condition value path.
|
||||||
|
const conditionPath = `conditions[${conditionIndex}]`;
|
||||||
|
|
||||||
|
// Sets conditions value.
|
||||||
|
const setConditionValue = React.useCallback(
|
||||||
|
(field, value) => {
|
||||||
|
return setFieldValue(`${conditionPath}.${field}`, value);
|
||||||
|
},
|
||||||
|
[conditionPath, setFieldValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve condition field value.
|
||||||
|
const getConditionValue = React.useCallback(
|
||||||
|
(field) => get(values, `${conditionPath}.${field}`),
|
||||||
|
[conditionPath, values],
|
||||||
|
);
|
||||||
|
|
||||||
|
// The current condition field meta.
|
||||||
|
const fieldMeta = React.useMemo(
|
||||||
|
() => getFieldMetaByKey(getConditionValue('fieldKey')),
|
||||||
|
[getFieldMetaByKey, getConditionValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve the condition field path.
|
||||||
|
const getConditionFieldPath = React.useCallback(
|
||||||
|
(field) => `${conditionPath}.${field}`,
|
||||||
|
[conditionPath],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
fieldMeta,
|
||||||
|
conditionIndex,
|
||||||
|
getConditionValue,
|
||||||
|
getConditionFieldPath,
|
||||||
|
setConditionValue,
|
||||||
|
};
|
||||||
|
return <FilterConditionContext.Provider value={provider} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useFilterCondition = () => useContext(FilterConditionContext);
|
||||||
|
const useAdvancedFilterContext = () => useContext(AdvancedFilterContext);
|
||||||
|
|
||||||
|
export {
|
||||||
|
AdvancedFilterDropdownProvider,
|
||||||
|
FilterConditionProvider,
|
||||||
|
useAdvancedFilterContext,
|
||||||
|
useFilterCondition,
|
||||||
|
};
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import React, { createContext, useContext } from 'react';
|
|
||||||
import { keyBy } from 'lodash';
|
|
||||||
import {
|
|
||||||
IAdvancedFilterContextProps,
|
|
||||||
IFilterConditionContextProps,
|
|
||||||
IAdvancedFilterProviderProps,
|
|
||||||
IFilterConditionProviderProps,
|
|
||||||
} from './interfaces';
|
|
||||||
|
|
||||||
const AdvancedFilterContext = createContext<IAdvancedFilterContextProps>({});
|
|
||||||
const FilterConditionContext = createContext<IFilterConditionContextProps>({});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter dropdown context provider.
|
|
||||||
*/
|
|
||||||
function AdvancedFilterDropdownProvider({
|
|
||||||
initialCondition,
|
|
||||||
fields,
|
|
||||||
...props
|
|
||||||
}: IAdvancedFilterProviderProps) {
|
|
||||||
const fieldsByKey = keyBy(fields, 'key');
|
|
||||||
// Provider payload.
|
|
||||||
const provider = { initialCondition, fields, fieldsByKey };
|
|
||||||
return <AdvancedFilterContext.Provider value={provider} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter condition row context provider.
|
|
||||||
*/
|
|
||||||
function FilterConditionProvider({
|
|
||||||
conditionIndex,
|
|
||||||
...props
|
|
||||||
}: IFilterConditionProviderProps) {
|
|
||||||
// Provider payload.
|
|
||||||
const provider = { conditionIndex };
|
|
||||||
return <FilterConditionContext.Provider value={provider} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFilterCondition = () => useContext(FilterConditionContext);
|
|
||||||
const useAdvancedFilterContext = () => useContext(AdvancedFilterContext);
|
|
||||||
|
|
||||||
export {
|
|
||||||
AdvancedFilterDropdownProvider,
|
|
||||||
FilterConditionProvider,
|
|
||||||
useAdvancedFilterContext,
|
|
||||||
useFilterCondition,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Popover, PopoverInteractionKind, Position } from '@blueprintjs/core';
|
||||||
|
import { AdvancedFilterDropdown } from './AdvancedFilterDropdown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced filter popover.
|
||||||
|
*/
|
||||||
|
export function AdvancedFilterPopover({
|
||||||
|
popoverProps,
|
||||||
|
advancedFilterProps,
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
minimal={true}
|
||||||
|
content={
|
||||||
|
<AdvancedFilterDropdown
|
||||||
|
{...advancedFilterProps}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
canOutsideClickClose={true}
|
||||||
|
modifiers={{
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
}}
|
||||||
|
{...popoverProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
131
client/src/components/AdvancedFilter/AdvancedFilterValueField.js
Normal file
131
client/src/components/AdvancedFilter/AdvancedFilterValueField.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Position, Checkbox, InputGroup } from '@blueprintjs/core';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import moment from 'moment';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { Choose, ListSelect } from 'components';
|
||||||
|
import { momentFormatter } from 'utils';
|
||||||
|
|
||||||
|
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
||||||
|
return (
|
||||||
|
<ListSelect
|
||||||
|
items={options}
|
||||||
|
selectedItem={value}
|
||||||
|
popoverProps={{
|
||||||
|
fill: true,
|
||||||
|
inline: true,
|
||||||
|
minimal: true,
|
||||||
|
captureDismiss: true,
|
||||||
|
}}
|
||||||
|
defaultText={`Select an option`}
|
||||||
|
textProp={'label'}
|
||||||
|
selectedItemProp={'key'}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IFieldType = {
|
||||||
|
ENUMERATION: 'enumeration',
|
||||||
|
BOOLEAN: 'boolean',
|
||||||
|
NUMBER: 'number',
|
||||||
|
DATE: 'date',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function tansformDateValue(date, defaultValue = null) {
|
||||||
|
return date ? moment(date).toDate() : defaultValue;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Advanced filter value field detarminer.
|
||||||
|
*/
|
||||||
|
export default function AdvancedFilterValueField2({
|
||||||
|
value,
|
||||||
|
fieldType,
|
||||||
|
options,
|
||||||
|
onChange,
|
||||||
|
isFocus
|
||||||
|
}) {
|
||||||
|
const [localValue, setLocalValue] = React.useState(value);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (localValue !== value && !isUndefined(value)) {
|
||||||
|
setLocalValue(value)
|
||||||
|
}
|
||||||
|
}, [localValue, value]);
|
||||||
|
|
||||||
|
// Input field reference.
|
||||||
|
const valueRef = useAutofocus(isFocus);
|
||||||
|
|
||||||
|
const triggerOnChange = (value) => onChange && onChange(value);
|
||||||
|
|
||||||
|
// Handle input change.
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
if (e.currentTarget.type === 'checkbox') {
|
||||||
|
setLocalValue(e.currentTarget.checked);
|
||||||
|
triggerOnChange(e.currentTarget.checked);
|
||||||
|
} else {
|
||||||
|
setLocalValue(e.currentTarget.value);
|
||||||
|
triggerOnChange(e.currentTarget.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle enumeration field type change.
|
||||||
|
const handleEnumerationChange = (option) => {
|
||||||
|
setLocalValue(option.key);
|
||||||
|
triggerOnChange(option.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle date field change.
|
||||||
|
const handleDateChange = (date) => {
|
||||||
|
const formattedDate = moment(date).format('YYYY/MM/DD');
|
||||||
|
|
||||||
|
setLocalValue(formattedDate);
|
||||||
|
triggerOnChange(formattedDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={fieldType === IFieldType.ENUMERATION}>
|
||||||
|
<AdvancedFilterEnumerationField
|
||||||
|
options={options}
|
||||||
|
value={localValue}
|
||||||
|
onItemSelect={handleEnumerationChange}
|
||||||
|
/>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.When condition={fieldType === IFieldType.DATE}>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
value={tansformDateValue(localValue)}
|
||||||
|
onChange={handleDateChange}
|
||||||
|
popoverProps={{
|
||||||
|
minimal: true,
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
}}
|
||||||
|
shortcuts={true}
|
||||||
|
placeholder={'Enter date'}
|
||||||
|
fill={true}
|
||||||
|
inputProps={{
|
||||||
|
fill: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.When condition={fieldType === IFieldType.BOOLEAN}>
|
||||||
|
<Checkbox value={localValue} onChange={handleInputChange} />
|
||||||
|
</Choose.When>
|
||||||
|
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<InputGroup
|
||||||
|
placeholder={intl.get('value')}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={localValue}
|
||||||
|
inputRef={valueRef}
|
||||||
|
/>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import React, { useMemo, useRef, useEffect, useState } from 'react';
|
|
||||||
import {
|
|
||||||
FormGroup,
|
|
||||||
MenuItem,
|
|
||||||
InputGroup,
|
|
||||||
Position,
|
|
||||||
Checkbox,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
import { Choose, ListSelect, MODIFIER } from 'components';
|
|
||||||
|
|
||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
|
||||||
|
|
||||||
import { compose, momentFormatter } from 'utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamic filter fields.
|
|
||||||
*/
|
|
||||||
function DynamicFilterValueField({
|
|
||||||
|
|
||||||
// #withResourceDetail
|
|
||||||
resourceName,
|
|
||||||
resourceData = [],
|
|
||||||
|
|
||||||
requestResourceData,
|
|
||||||
|
|
||||||
// #ownProps
|
|
||||||
fieldType,
|
|
||||||
fieldName,
|
|
||||||
value,
|
|
||||||
initialValue,
|
|
||||||
error,
|
|
||||||
optionsResource,
|
|
||||||
optionsKey = 'key',
|
|
||||||
optionsLabel = 'label',
|
|
||||||
options,
|
|
||||||
onChange,
|
|
||||||
inputDebounceWait = 250,
|
|
||||||
}) {
|
|
||||||
|
|
||||||
const [localValue, setLocalValue] = useState();
|
|
||||||
|
|
||||||
// Makes `localValue` controlled mode from `value`.
|
|
||||||
useEffect(() => {
|
|
||||||
if (value !== localValue) {
|
|
||||||
setLocalValue(value);
|
|
||||||
}
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
// Fetches resource data.
|
|
||||||
const fetchResourceData = useQuery(
|
|
||||||
['resource-data', resourceName],
|
|
||||||
(key, _resourceName) => requestResourceData(_resourceName),
|
|
||||||
{
|
|
||||||
enabled: resourceName,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Account type item of select filed.
|
|
||||||
const menuItem = (item, { handleClick, modifiers, query }) => {
|
|
||||||
return (<MenuItem
|
|
||||||
text={item[optionsLabel]}
|
|
||||||
key={item[optionsKey]}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle list button click.
|
|
||||||
const handleBtnClick = () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const listOptions = useMemo(() => [
|
|
||||||
...(resourceData || []),
|
|
||||||
...(options || []),
|
|
||||||
], [
|
|
||||||
resourceData, options,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Filters accounts types items.
|
|
||||||
const filterItems = (query, item, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = item.label.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return normalizedTitle.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle list item selected.
|
|
||||||
const onItemSelect = (item) => {
|
|
||||||
onChange && onChange(item[optionsKey]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChangeThrottled = useRef(
|
|
||||||
debounce((value) => { onChange && onChange(value); }, inputDebounceWait),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle input change.
|
|
||||||
const handleInputChange = (e) => {
|
|
||||||
if (e.currentTarget.type === 'checkbox') {
|
|
||||||
setLocalValue(e.currentTarget.checked);
|
|
||||||
} else {
|
|
||||||
setLocalValue(e.currentTarget.value);
|
|
||||||
}
|
|
||||||
handleInputChangeThrottled.current(e.currentTarget.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle checkbox field change.
|
|
||||||
const handleCheckboxChange = (e) => {
|
|
||||||
const value = !!e.currentTarget.checked;
|
|
||||||
setLocalValue(value);
|
|
||||||
onChange && onChange(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle date field change.
|
|
||||||
const handleDateChange = (date) => {
|
|
||||||
setLocalValue(date);
|
|
||||||
onChange && onChange(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const transformDateValue = (value) => {
|
|
||||||
return value ? moment(value || new Date()).toDate() : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormGroup className={'form-group--value'}>
|
|
||||||
<Choose>
|
|
||||||
<Choose.When condition={fieldType === '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={optionsKey}
|
|
||||||
defaultText={`Select an option`}
|
|
||||||
textProp={optionsLabel}
|
|
||||||
buttonProps={{
|
|
||||||
onClick: handleBtnClick
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.When condition={fieldType === '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={fieldType === 'checkbox'}>
|
|
||||||
<Checkbox value={localValue} onChange={handleCheckboxChange} />
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
|
||||||
<InputGroup
|
|
||||||
placeholder={intl.get('value')}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
value={localValue}
|
|
||||||
/>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
resourceName: props.optionsResource,
|
|
||||||
});
|
|
||||||
|
|
||||||
const withResourceFilterValueField = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withResourceFilterValueField,
|
|
||||||
withResourceDetail(({ resourceData }) => ({ resourceData })),
|
|
||||||
withResourceActions,
|
|
||||||
)(DynamicFilterValueField);
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Position, Checkbox, InputGroup, FormGroup } from '@blueprintjs/core';
|
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
|
||||||
import moment from 'moment';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { Choose, ListSelect } from 'components';
|
|
||||||
import { momentFormatter, tansformDateValue } from 'utils';
|
|
||||||
import { IFieldType, IAdvancedFilterValueField } from './interfaces';
|
|
||||||
|
|
||||||
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
|
||||||
return (
|
|
||||||
<ListSelect
|
|
||||||
items={options}
|
|
||||||
selectedItem={value}
|
|
||||||
popoverProps={{
|
|
||||||
inline: true,
|
|
||||||
minimal: true,
|
|
||||||
captureDismiss: true,
|
|
||||||
}}
|
|
||||||
defaultText={`Select an option`}
|
|
||||||
textProp={'label'}
|
|
||||||
selectedItemProp={'key'}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced filter value field detarminer.
|
|
||||||
*/
|
|
||||||
export default function AdvancedFilterValueField2({
|
|
||||||
fieldType,
|
|
||||||
options,
|
|
||||||
onChange,
|
|
||||||
}: IAdvancedFilterValueField) {
|
|
||||||
const [localValue, setLocalValue] = React.useState<string>('');
|
|
||||||
|
|
||||||
const triggerOnChange = (value: string) => onChange && onChange(value);
|
|
||||||
|
|
||||||
// Handle input change.
|
|
||||||
const handleInputChange = (e) => {
|
|
||||||
if (e.currentTarget.type === 'checkbox') {
|
|
||||||
setLocalValue(e.currentTarget.checked);
|
|
||||||
triggerOnChange(e.currentTarget.checked);
|
|
||||||
} else {
|
|
||||||
setLocalValue(e.currentTarget.value);
|
|
||||||
triggerOnChange(e.currentTarget.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle enumeration field type change.
|
|
||||||
const handleEnumerationChange = (option) => {
|
|
||||||
setLocalValue(option.key);
|
|
||||||
triggerOnChange(option.key);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle date field change.
|
|
||||||
const handleDateChange = (date: Date) => {
|
|
||||||
const formattedDate: string = moment(date).format('YYYY/MM/DD');
|
|
||||||
|
|
||||||
setLocalValue(formattedDate);
|
|
||||||
triggerOnChange(formattedDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormGroup className={'form-group--value'}>
|
|
||||||
<Choose>
|
|
||||||
<Choose.When condition={fieldType === IFieldType.ENUMERATION}>
|
|
||||||
<AdvancedFilterEnumerationField
|
|
||||||
options={options}
|
|
||||||
value={localValue}
|
|
||||||
onItemSelect={handleEnumerationChange}
|
|
||||||
/>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.When condition={fieldType === IFieldType.DATE}>
|
|
||||||
<DateInput
|
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
|
||||||
value={tansformDateValue(localValue)}
|
|
||||||
onChange={handleDateChange}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: true,
|
|
||||||
position: Position.BOTTOM,
|
|
||||||
}}
|
|
||||||
shortcuts={true}
|
|
||||||
placeholder={'Select date'}
|
|
||||||
/>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.When condition={fieldType === IFieldType.BOOLEAN}>
|
|
||||||
<Checkbox value={localValue} onChange={handleInputChange} />
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
|
||||||
<InputGroup
|
|
||||||
placeholder={intl.get('value')}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
value={localValue}
|
|
||||||
/>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ const DEBOUNCE_MS = 100;
|
|||||||
*/
|
*/
|
||||||
export function useAdvancedFilterAutoSubmit() {
|
export function useAdvancedFilterAutoSubmit() {
|
||||||
const { submitForm, values } = useFormikContext();
|
const { submitForm, values } = useFormikContext();
|
||||||
const [isSubmit, setIsSubmit] = React.useState<boolean>(false);
|
const [isSubmit, setIsSubmit] = React.useState(false);
|
||||||
|
|
||||||
const debouncedSubmit = React.useCallback(
|
const debouncedSubmit = React.useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ArrayHelpers } from 'formik';
|
import { ArrayHelpers } from 'formik';
|
||||||
|
import { IPopoverProps } from '@blueprintjs/core';
|
||||||
|
|
||||||
export type IResourceFieldType = 'text' | 'number' | 'enumeration' | 'boolean';
|
export type IResourceFieldType = 'text' | 'number' | 'enumeration' | 'boolean';
|
||||||
|
|
||||||
@@ -10,9 +11,12 @@ export interface IResourceField {
|
|||||||
|
|
||||||
export interface IAdvancedFilterDropdown {
|
export interface IAdvancedFilterDropdown {
|
||||||
fields: IResourceField[];
|
fields: IResourceField[];
|
||||||
|
conditions?: IFilterRole[];
|
||||||
defaultFieldKey: string;
|
defaultFieldKey: string;
|
||||||
defaultComparator?: string;
|
defaultComparator?: string;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
defaultCondition?: string;
|
||||||
|
onFilterChange?: (filterRoles: IFilterRole[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAdvancedFilterDropdownFooter {
|
export interface IAdvancedFilterDropdownFooter {
|
||||||
@@ -69,6 +73,7 @@ export interface IFilterOption {
|
|||||||
|
|
||||||
export interface IAdvancedFilterValueField {
|
export interface IAdvancedFilterValueField {
|
||||||
fieldType: string;
|
fieldType: string;
|
||||||
|
value?: string;
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
options?: IFilterOption[];
|
options?: IFilterOption[];
|
||||||
@@ -82,3 +87,25 @@ export enum IFieldType {
|
|||||||
ENUMERATION = 'enumeration',
|
ENUMERATION = 'enumeration',
|
||||||
BOOLEAN = 'boolean',
|
BOOLEAN = 'boolean',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IConditionTypeOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IConditionOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAdvancedFilterPopover {
|
||||||
|
popoverProps?: IPopoverProps;
|
||||||
|
advancedFilterProps: IAdvancedFilterDropdown;
|
||||||
|
children: JSX.Element | JSX.Element[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IDynamicFilterCompatatorFieldProps {
|
||||||
|
dataType: string;
|
||||||
|
}
|
||||||
|
|||||||
110
client/src/components/AdvancedFilter/utils.js
Normal file
110
client/src/components/AdvancedFilter/utils.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import {
|
||||||
|
defaultFastFieldShouldUpdate,
|
||||||
|
uniqueMultiProps,
|
||||||
|
checkRequiredProperties,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
|
// Conditions options.
|
||||||
|
export const getConditionalsOptions = () => [
|
||||||
|
{
|
||||||
|
value: 'and',
|
||||||
|
label: intl.get('and'),
|
||||||
|
text: intl.get('filter.all_filters_must_match'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'or',
|
||||||
|
label: intl.get('or'),
|
||||||
|
text: intl.get('filter.atleast_one_filter_must_match'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getBooleanCompatators = () => [
|
||||||
|
{ value: 'is', label: intl.get('is') },
|
||||||
|
{ value: 'is_not', label: intl.get('is_not') },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getTextCompatators = () => [
|
||||||
|
{ value: 'contain', label: intl.get('contain') },
|
||||||
|
{ value: 'not_contain', label: intl.get('not_contain') },
|
||||||
|
{ value: 'equal', label: intl.get('equals') },
|
||||||
|
{ value: 'not_equal', label: intl.get('not_equals') },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getDateCompatators = () => [
|
||||||
|
{ value: 'in', label: intl.get('in') },
|
||||||
|
{ value: 'after', label: intl.get('after') },
|
||||||
|
{ value: 'before', label: intl.get('before') },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getOptionsCompatators = () => [
|
||||||
|
{ value: 'is', label: intl.get('is') },
|
||||||
|
{ value: 'is_not', label: intl.get('is_not') },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getNumberCampatators = () => [
|
||||||
|
{ value: 'equal', label: intl.get('equals') },
|
||||||
|
{ value: 'not_equal', label: intl.get('not_equal') },
|
||||||
|
{ value: 'bigger_than', label: intl.get('bigger_than') },
|
||||||
|
{ value: 'bigger_or_equal', label: intl.get('bigger_or_equals') },
|
||||||
|
{ value: 'smaller_than', label: intl.get('smaller_than') },
|
||||||
|
{ value: 'smaller_or_equal', label: intl.get('smaller_or_equals') },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getConditionTypeCompatators = (
|
||||||
|
dataType,
|
||||||
|
) => {
|
||||||
|
return [
|
||||||
|
...(dataType === 'enumeration'
|
||||||
|
? [...getOptionsCompatators()]
|
||||||
|
: dataType === 'date'
|
||||||
|
? [...getDateCompatators()]
|
||||||
|
: dataType === 'boolean'
|
||||||
|
? [...getBooleanCompatators()]
|
||||||
|
: dataType === 'number'
|
||||||
|
? [...getNumberCampatators()]
|
||||||
|
: [...getTextCompatators()]),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConditionDefaultCompatator = (
|
||||||
|
dataType,
|
||||||
|
) => {
|
||||||
|
const compatators = getConditionTypeCompatators(dataType);
|
||||||
|
return compatators[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformFieldsToOptions = (fields) =>
|
||||||
|
fields.map((field) => ({
|
||||||
|
value: field.key,
|
||||||
|
label: field.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtered conditions that don't contain atleast on required fields or
|
||||||
|
* fileds keys that not exists.
|
||||||
|
* @param {IFilterRole[]} conditions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const filterConditionRoles = (
|
||||||
|
conditions,
|
||||||
|
) => {
|
||||||
|
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
||||||
|
|
||||||
|
const filteredConditions = conditions.filter(
|
||||||
|
(condition) =>
|
||||||
|
!checkRequiredProperties(condition, requiredProps),
|
||||||
|
);
|
||||||
|
return uniqueMultiProps(filteredConditions, requiredProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines the value field when should update.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const shouldFilterValueFieldUpdate = (newProps, oldProps) => {
|
||||||
|
return (
|
||||||
|
newProps.fieldKey !== oldProps.fieldKey ||
|
||||||
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import intl from 'react-intl-universal';
|
|
||||||
import { IResourceField, IFilterRole } from './interfaces';
|
|
||||||
import { uniqueMultiProps, checkRequiredProperties } from 'utils';
|
|
||||||
|
|
||||||
interface IConditionOption {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conditions options.
|
|
||||||
export const getConditionalsOptions = (): IConditionOption[] => [
|
|
||||||
{ value: 'and', label: intl.get('and') },
|
|
||||||
{ value: 'or', label: intl.get('or') },
|
|
||||||
];
|
|
||||||
|
|
||||||
interface IConditionTypeOption {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getBooleanCompatators = (): IConditionTypeOption[] => [
|
|
||||||
{ value: 'is', label: 'is' },
|
|
||||||
{ value: 'is_not', label: 'is_not' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getTextCompatators = (): IConditionTypeOption[] => [
|
|
||||||
{ value: 'contain', label: 'contain' },
|
|
||||||
{ value: 'not_contain', label: 'not_contain' },
|
|
||||||
{ value: 'equals', label: 'equals' },
|
|
||||||
{ value: 'not_equal', label: 'not_equals' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getDateCompatators = (): IConditionTypeOption[] => [
|
|
||||||
{ value: 'in', label: 'in' },
|
|
||||||
{ value: 'after', label: 'after' },
|
|
||||||
{ value: 'before', label: 'before' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getOptionsCompatators = (): IConditionTypeOption[] => [
|
|
||||||
{ value: 'is', label: 'is' },
|
|
||||||
{ value: 'is_not', label: 'is_not' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getNumberCampatators = (): IConditionTypeOption[] => [
|
|
||||||
{ value: 'equals', label: 'equals' },
|
|
||||||
{ value: 'not_equal', label: 'not_equal' },
|
|
||||||
{ value: 'bigger_than', label: 'bigger_than' },
|
|
||||||
{ value: 'bigger_or_equals', label: 'bigger_or_equals' },
|
|
||||||
{ value: 'smaller_than', label: 'smaller_than' },
|
|
||||||
{ value: 'smaller_or_equals', label: 'smaller_or_equals' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getConditionTypeCompatators = (
|
|
||||||
dataType: string,
|
|
||||||
): IConditionTypeOption[] => {
|
|
||||||
return [
|
|
||||||
...(dataType === 'options'
|
|
||||||
? [...getOptionsCompatators()]
|
|
||||||
: dataType === 'date'
|
|
||||||
? [...getDateCompatators()]
|
|
||||||
: dataType === 'boolean'
|
|
||||||
? [...getBooleanCompatators()]
|
|
||||||
: dataType === 'number'
|
|
||||||
? [...getNumberCampatators()]
|
|
||||||
: [...getTextCompatators()]),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getConditionDefaultCompatator = (
|
|
||||||
dataType: string,
|
|
||||||
): IConditionTypeOption => {
|
|
||||||
const compatators = getConditionTypeCompatators(dataType);
|
|
||||||
return compatators[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformFieldsToOptions = (fields: IResourceField[]) =>
|
|
||||||
fields.map((field) => ({
|
|
||||||
value: field.key,
|
|
||||||
label: field.name,
|
|
||||||
}));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filtered conditions that don't contain atleast on required fields or
|
|
||||||
* fileds keys that not exists.
|
|
||||||
* @param {IFilterRole[]} conditions
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const filterConditionRoles = (
|
|
||||||
conditions: IFilterRole[],
|
|
||||||
): IFilterRole[] => {
|
|
||||||
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
|
||||||
|
|
||||||
const filteredConditions = conditions.filter(
|
|
||||||
(condition: IFilterRole) =>
|
|
||||||
!checkRequiredProperties(condition, requiredProps),
|
|
||||||
);
|
|
||||||
return uniqueMultiProps(filteredConditions, requiredProps);
|
|
||||||
};
|
|
||||||
26
client/src/components/Dashboard/DashboardFilterButton.js
Normal file
26
client/src/components/Dashboard/DashboardFilterButton.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import intl from "react-intl-universal";
|
||||||
|
import { Classes, Button } from '@blueprintjs/core';
|
||||||
|
import { T, Icon } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard advanced filter button.
|
||||||
|
*/
|
||||||
|
export function DashboardFilterButton({ conditionsCount }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={classNames(Classes.MINIMAL, 'button--filter', {
|
||||||
|
'has-active-filters': conditionsCount > 0,
|
||||||
|
})}
|
||||||
|
text={
|
||||||
|
conditionsCount > 0 ? (
|
||||||
|
intl.get('count_filters_applied', { count: conditionsCount })
|
||||||
|
) : (
|
||||||
|
<T id={'filter'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
client/src/components/Dashboard/DashboardRowsHeightButton.js
Normal file
36
client/src/components/Dashboard/DashboardRowsHeightButton.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuDivider,
|
||||||
|
Classes
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
export function DashboardRowsHeightButton() {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
minimal={true}
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuDivider title={'Rows height'} />
|
||||||
|
<MenuItem text="Compact" />
|
||||||
|
<MenuItem text="Medium" />
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
placement="bottom-start"
|
||||||
|
modifiers={{
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
}}
|
||||||
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="rows-height" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import React, { useEffect, useMemo, useCallback, useState } from 'react';
|
|
||||||
import {
|
|
||||||
FormGroup,
|
|
||||||
Classes,
|
|
||||||
HTMLSelect,
|
|
||||||
Button,
|
|
||||||
Intent,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import { isEqual, last } from 'lodash';
|
|
||||||
import { usePrevious } from 'react-use';
|
|
||||||
import Icon from 'components/Icon';
|
|
||||||
import { checkRequiredProperties, uniqueMultiProps } from 'utils';
|
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
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,
|
|
||||||
initialCondition,
|
|
||||||
initialConditions,
|
|
||||||
}) {
|
|
||||||
|
|
||||||
|
|
||||||
// Fields key -> metadata table.
|
|
||||||
const fieldsKeyMapped = useMemo(() =>
|
|
||||||
new Map(fields.map((field) => [field.key, field])),
|
|
||||||
[fields]
|
|
||||||
);
|
|
||||||
// Conditions options.
|
|
||||||
const conditionalsOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
{ value: '&&', label: intl.get('and') },
|
|
||||||
{ value: '||', label: intl.get('or') },
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
// Resources fileds options for fields options.
|
|
||||||
const resourceFieldsOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
...fields.map((field) => ({
|
|
||||||
value: field.key,
|
|
||||||
label: field.label,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
[fields],
|
|
||||||
);
|
|
||||||
// Default filter conition.
|
|
||||||
const defaultFilterCondition = useMemo(
|
|
||||||
() => ({
|
|
||||||
condition: '&&',
|
|
||||||
fieldKey: initialCondition.fieldKey,
|
|
||||||
comparator: initialCondition.comparator,
|
|
||||||
value: initialCondition.value,
|
|
||||||
}),
|
|
||||||
[initialCondition],
|
|
||||||
);
|
|
||||||
// Formik for validation purposes.
|
|
||||||
const { setFieldValue, getFieldProps, values } = useFormik({
|
|
||||||
initialValues: {
|
|
||||||
conditions: [
|
|
||||||
...((initialConditions && initialConditions.length) ?
|
|
||||||
[...initialConditions] : [defaultFilterCondition]),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle click a new filter row.
|
|
||||||
const onClickNewFilter = useCallback(() => {
|
|
||||||
if (values.conditions.length >= 12) {
|
|
||||||
limitToast = Toaster.show(
|
|
||||||
{
|
|
||||||
message: intl.get('you_reached_conditions_limit'),
|
|
||||||
intent: Intent.WARNING,
|
|
||||||
},
|
|
||||||
limitToast,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setFieldValue('conditions', [
|
|
||||||
...values.conditions,
|
|
||||||
defaultFilterCondition
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}, [values, setFieldValue, defaultFilterCondition]);
|
|
||||||
|
|
||||||
// Filtered conditions that filters conditions that don't contain atleast
|
|
||||||
// on required fields or fileds keys that not exists.
|
|
||||||
const filteredFilterConditions = useMemo(() => {
|
|
||||||
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
|
||||||
|
|
||||||
const conditions = values.conditions
|
|
||||||
.filter(
|
|
||||||
(condition) => !checkRequiredProperties(condition, requiredProps),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
(condition) => !!fieldsKeyMapped.get(condition.fieldKey),
|
|
||||||
);
|
|
||||||
return uniqueMultiProps(conditions, requiredProps);
|
|
||||||
}, [values.conditions, fieldsKeyMapped]);
|
|
||||||
|
|
||||||
// Previous filtered conditions.
|
|
||||||
const prevConditions = usePrevious(filteredFilterConditions);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Campare the current conditions with previous conditions, if they were equal
|
|
||||||
// there is no need to execute `onFilterChange` function.
|
|
||||||
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
|
||||||
onFilterChange && onFilterChange(filteredFilterConditions);
|
|
||||||
}
|
|
||||||
}, [filteredFilterConditions, prevConditions, onFilterChange]);
|
|
||||||
|
|
||||||
// Handle click remove condition.
|
|
||||||
const onClickRemoveCondition = (index) => () => {
|
|
||||||
if (values.conditions.length === 1) {
|
|
||||||
setFieldValue('conditions', [defaultFilterCondition]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const conditions = [...values.conditions];
|
|
||||||
conditions.splice(index, 1);
|
|
||||||
setFieldValue('conditions', [...conditions]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform dynamic value field.
|
|
||||||
const transformValueField = (value) => {
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return moment(value).format('YYYY-MM-DD');
|
|
||||||
} else if (typeof value === 'object') {
|
|
||||||
return value.id;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
// Override getFieldProps for conditions fields.
|
|
||||||
const fieldProps = (name, index) => {
|
|
||||||
const override = {
|
|
||||||
...getFieldProps(`conditions[${index}].${name}`),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...override,
|
|
||||||
onChange: (e) => {
|
|
||||||
if (name === 'fieldKey') {
|
|
||||||
const currentField = fieldsKeyMapped.get(
|
|
||||||
values.conditions[index].fieldKey,
|
|
||||||
);
|
|
||||||
const nextField = fieldsKeyMapped.get(e.currentTarget.value);
|
|
||||||
|
|
||||||
if (currentField.field_type !== nextField.field_type) {
|
|
||||||
setFieldValue(`conditions[${index}].value`, '');
|
|
||||||
}
|
|
||||||
const comparatorsObs = getConditionTypeCompatators(
|
|
||||||
nextField.field_type,
|
|
||||||
);
|
|
||||||
const currentCompatator = values.conditions[index].comparator;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!currentCompatator ||
|
|
||||||
comparatorsObs.map((c) => c.value).indexOf(currentCompatator) === -1
|
|
||||||
) {
|
|
||||||
const defaultCompatator = getConditionDefaultCompatator(
|
|
||||||
nextField.field_type,
|
|
||||||
);
|
|
||||||
setFieldValue(
|
|
||||||
`conditions[${index}].comparator`,
|
|
||||||
defaultCompatator.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override.onChange(e);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compatator field props.
|
|
||||||
const comparatorFieldProps = (name, index) => {
|
|
||||||
const condition = values.conditions[index];
|
|
||||||
const field = fieldsKeyMapped.get(condition.fieldKey);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...fieldProps(name, index),
|
|
||||||
dataType: field.field_type,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Value field props.
|
|
||||||
const valueFieldProps = (name, index) => {
|
|
||||||
const condition = values.conditions[index];
|
|
||||||
const field = fieldsKeyMapped.get(condition.fieldKey);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...fieldProps(name, index),
|
|
||||||
fieldName: field.label,
|
|
||||||
fieldType: field.field_type,
|
|
||||||
options: field.options,
|
|
||||||
optionsResource: field.options_resource,
|
|
||||||
onChange: (value) => {
|
|
||||||
const transformedValue = transformValueField(value);
|
|
||||||
setFieldValue(`conditions[${index}].${name}`, transformedValue);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="filter-dropdown">
|
|
||||||
<div class="filter-dropdown__body">
|
|
||||||
{values.conditions.map((condition, index) => (
|
|
||||||
<div class="filter-dropdown__condition">
|
|
||||||
<FormGroup className={'form-group--condition'}>
|
|
||||||
<HTMLSelect
|
|
||||||
options={conditionalsOptions}
|
|
||||||
className={Classes.FILL}
|
|
||||||
disabled={index > 1}
|
|
||||||
{...fieldProps('condition', index)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup className={'form-group--field'}>
|
|
||||||
<HTMLSelect
|
|
||||||
options={resourceFieldsOptions}
|
|
||||||
value={1}
|
|
||||||
className={Classes.FILL}
|
|
||||||
{...fieldProps('fieldKey', index)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup className={'form-group--comparator'}>
|
|
||||||
<DynamicFilterCompatatorField
|
|
||||||
className={Classes.FILL}
|
|
||||||
{...comparatorFieldProps('comparator', index)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<DynamicFilterValueField
|
|
||||||
{...valueFieldProps('value', index)} />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
icon={<Icon icon="times" iconSize={14} />}
|
|
||||||
minimal={true}
|
|
||||||
onClick={onClickRemoveCondition(index)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-dropdown__footer">
|
|
||||||
<Button
|
|
||||||
minimal={true}
|
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
onClick={onClickNewFilter}
|
|
||||||
>
|
|
||||||
<T id={'new_conditional'} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { Button, MenuItem } from '@blueprintjs/core';
|
import { Button, MenuItem } from '@blueprintjs/core';
|
||||||
import { Select } from '@blueprintjs/select';
|
import { Select } from '@blueprintjs/select';
|
||||||
@@ -66,10 +67,23 @@ export default function ListSelect({
|
|||||||
onItemSelect && onItemSelect(_item);
|
onItemSelect && onItemSelect(_item);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filters accounts types items.
|
||||||
|
const filterItems = (query, item, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = item[textProp].toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return normalizedTitle.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
itemRenderer={itemRenderer}
|
itemRenderer={itemRenderer}
|
||||||
onItemSelect={handleItemSelect}
|
onItemSelect={handleItemSelect}
|
||||||
|
itemPredicate={filterItems}
|
||||||
{...selectProps}
|
{...selectProps}
|
||||||
noResults={noResults}
|
noResults={noResults}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -83,6 +97,7 @@ export default function ListSelect({
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ import Card from './Card';
|
|||||||
import { ItemsMultiSelect } from './Items';
|
import { ItemsMultiSelect } from './Items';
|
||||||
|
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
|
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||||
|
export * from './AdvancedFilter/AdvancedFilterPopover';
|
||||||
|
export * from './Dashboard/DashboardFilterButton';
|
||||||
|
export * from './Dashboard/DashboardRowsHeightButton';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
|
|||||||
@@ -5,24 +5,26 @@ import {
|
|||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Classes,
|
Classes,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
Popover,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import {
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
DashboardFilterButton,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useRefreshJournals } from 'hooks/query/manualJournals';
|
import { useRefreshJournals } from 'hooks/query/manualJournals';
|
||||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withManualJournalsActions from './withManualJournalsActions';
|
||||||
|
import withManualJournals from './withManualJournals';
|
||||||
|
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import { If, DashboardActionViewsList } from 'components';
|
||||||
|
|
||||||
import withManualJournalsActions from './withManualJournalsActions';
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,12 +33,15 @@ import { compose } from 'utils';
|
|||||||
function ManualJournalActionsBar({
|
function ManualJournalActionsBar({
|
||||||
// #withManualJournalsActions
|
// #withManualJournalsActions
|
||||||
setManualJournalsTableState,
|
setManualJournalsTableState,
|
||||||
|
|
||||||
|
// #withManualJournals
|
||||||
|
manualJournalsFilterConditions,
|
||||||
}) {
|
}) {
|
||||||
// History context.
|
// History context.
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Manual journals context.
|
// Manual journals context.
|
||||||
const { journalsViews } = useManualJournalsContext();
|
const { journalsViews, fields } = useManualJournalsContext();
|
||||||
|
|
||||||
// Manual journals refresh action.
|
// Manual journals refresh action.
|
||||||
const { refresh } = useRefreshJournals();
|
const { refresh } = useRefreshJournals();
|
||||||
@@ -45,7 +50,6 @@ function ManualJournalActionsBar({
|
|||||||
const onClickNewManualJournal = () => {
|
const onClickNewManualJournal = () => {
|
||||||
history.push('/make-journal-entry');
|
history.push('/make-journal-entry');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete button click.
|
// Handle delete button click.
|
||||||
const handleBulkDelete = () => {};
|
const handleBulkDelete = () => {};
|
||||||
|
|
||||||
@@ -53,11 +57,10 @@ function ManualJournalActionsBar({
|
|||||||
const handleTabChange = (customView) => {
|
const handleTabChange = (customView) => {
|
||||||
setManualJournalsTableState({ customViewId: customView.id || null });
|
setManualJournalsTableState({ customViewId: customView.id || null });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh Journals
|
// Handle click a refresh Journals
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => { refresh(); };
|
||||||
refresh();
|
|
||||||
};
|
console.log(manualJournalsFilterConditions, fields, 'XXX');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -75,20 +78,20 @@ function ManualJournalActionsBar({
|
|||||||
text={<T id={'journal_entry'} />}
|
text={<T id={'journal_entry'} />}
|
||||||
onClick={onClickNewManualJournal}
|
onClick={onClickNewManualJournal}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: manualJournalsFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'journal_number',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setManualJournalsTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
conditionsCount={manualJournalsFilterConditions.length}
|
||||||
'has-active-filters': false,
|
|
||||||
})}
|
|
||||||
text={<T id={'filter'} />}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
@@ -130,4 +133,7 @@ function ManualJournalActionsBar({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withManualJournalsActions,
|
withManualJournalsActions,
|
||||||
|
withManualJournals(({ manualJournalsTableState }) => ({
|
||||||
|
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
|
||||||
|
}))
|
||||||
)(ManualJournalActionsBar);
|
)(ManualJournalActionsBar);
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useJournals } from 'hooks/query';
|
import { useResourceViews, useResourceMeta, useJournals } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const ManualJournalsContext = createContext();
|
const ManualJournalsContext = createContext();
|
||||||
|
|
||||||
function ManualJournalsListProvider({ query, ...props }) {
|
function ManualJournalsListProvider({ query, ...props }) {
|
||||||
// Fetches accounts resource views and fields.
|
// Fetches accounts resource views and fields.
|
||||||
const { data: journalsViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: journalsViews, isLoading: isViewsLoading } =
|
||||||
'manual_journals',
|
useResourceViews('manual_journals');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches the manual journals transactions with pagination meta.
|
// Fetches the manual journals transactions with pagination meta.
|
||||||
const {
|
const {
|
||||||
data: { manualJournals, pagination, filterMeta },
|
data: { manualJournals, pagination, filterMeta },
|
||||||
isLoading: isManualJournalsLoading,
|
isLoading: isManualJournalsLoading,
|
||||||
isFetching: isManualJournalsFetching
|
isFetching: isManualJournalsFetching,
|
||||||
} = useJournals(query, { keepPreviousData: true });
|
} = useJournals(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Fetch the accounts resource fields.
|
||||||
|
const {
|
||||||
|
data: resourceMeta,
|
||||||
|
isLoading: isResourceMetaLoading,
|
||||||
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('manual_journals');
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus = isTableEmptyStatus({
|
const isEmptyStatus =
|
||||||
data: manualJournals, pagination, filterMeta,
|
isTableEmptyStatus({
|
||||||
}) && !isManualJournalsFetching;
|
data: manualJournals,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
}) && !isManualJournalsFetching;
|
||||||
|
|
||||||
// Global state.
|
// Global state.
|
||||||
const state = {
|
const state = {
|
||||||
@@ -29,15 +38,21 @@ function ManualJournalsListProvider({ query, ...props }) {
|
|||||||
pagination,
|
pagination,
|
||||||
journalsViews,
|
journalsViews,
|
||||||
|
|
||||||
|
resourceMeta,
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
|
||||||
isManualJournalsLoading,
|
isManualJournalsLoading,
|
||||||
isManualJournalsFetching,
|
isManualJournalsFetching,
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
|
|
||||||
isEmptyStatus
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPageLoading =
|
||||||
|
isManualJournalsLoading || isViewsLoading || isResourceMetaLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={isViewsLoading} name={'manual-journals'}>
|
<DashboardInsider loading={isPageLoading} name={'manual-journals'}>
|
||||||
<ManualJournalsContext.Provider value={state} {...props} />
|
<ManualJournalsContext.Provider value={state} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ import {
|
|||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Classes,
|
Classes,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
Popover,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Switch,
|
Switch,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
import {
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
AdvancedFilterPopover,
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import AdvancedFilterDropdown from 'components/AdvancedFilter/AdvancedFilterDropdown.tsx';
|
|
||||||
|
|
||||||
import { useRefreshAccounts } from 'hooks/query/accounts';
|
import { useRefreshAccounts } from 'hooks/query/accounts';
|
||||||
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
|
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
|
||||||
@@ -30,46 +30,6 @@ import withAccountsTableActions from './withAccountsTableActions';
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const FIELDS = [
|
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Account code',
|
|
||||||
key: 'code',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Balance',
|
|
||||||
key: 'balance',
|
|
||||||
fieldType: 'number'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Active',
|
|
||||||
key: 'active',
|
|
||||||
fieldType: 'boolean'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Created at',
|
|
||||||
key: 'created_at',
|
|
||||||
fieldType: 'date'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Root type',
|
|
||||||
key: 'root_type',
|
|
||||||
fieldType: 'enumeration',
|
|
||||||
options: [
|
|
||||||
{ key: 'asset', label: 'Asset' },
|
|
||||||
{ key: 'liability', label: 'Liability' },
|
|
||||||
{ key: 'equity', label: 'Equity' },
|
|
||||||
{ key: 'Income', label: 'Income' },
|
|
||||||
{ key: 'expense', label: 'Expense' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts actions bar.
|
* Accounts actions bar.
|
||||||
*/
|
*/
|
||||||
@@ -80,6 +40,7 @@ function AccountsActionsBar({
|
|||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsSelectedRows,
|
accountsSelectedRows,
|
||||||
accountsInactiveMode,
|
accountsInactiveMode,
|
||||||
|
accountsFilterConditions,
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
openAlert,
|
openAlert,
|
||||||
@@ -90,7 +51,7 @@ function AccountsActionsBar({
|
|||||||
// #ownProps
|
// #ownProps
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
}) {
|
}) {
|
||||||
const { resourceViews } = useAccountsChartContext();
|
const { resourceViews, fields } = useAccountsChartContext();
|
||||||
|
|
||||||
const onClickNewAccount = () => {
|
const onClickNewAccount = () => {
|
||||||
openDialog('account-form', {});
|
openDialog('account-form', {});
|
||||||
@@ -148,34 +109,22 @@ function AccountsActionsBar({
|
|||||||
text={<T id={'new_account'} />}
|
text={<T id={'new_account'} />}
|
||||||
onClick={onClickNewAccount}
|
onClick={onClickNewAccount}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
content={
|
conditions: accountsFilterConditions,
|
||||||
<AdvancedFilterDropdown
|
defaultFieldKey: 'name',
|
||||||
defaultFieldKey={'name'}
|
fields: fields,
|
||||||
fields={FIELDS}
|
onFilterChange: (filterConditions) => {
|
||||||
onFilterChange={(filterConditions) => {
|
setAccountsTableState({ filterRoles: filterConditions });
|
||||||
console.log(filterConditions, 'XXX');
|
},
|
||||||
}} />
|
}}
|
||||||
}
|
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
|
||||||
position={Position.BOTTOM_LEFT}
|
|
||||||
canOutsideClickClose={true}
|
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
conditionsCount={accountsFilterConditions.length}
|
||||||
'has-active-filters': false,
|
|
||||||
})}
|
|
||||||
text={
|
|
||||||
true ? (
|
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
intl.get('count_filters_applied', { count: 0 })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={!isEmpty(accountsSelectedRows)}>
|
<If condition={!isEmpty(accountsSelectedRows)}>
|
||||||
<Button
|
<Button
|
||||||
@@ -237,6 +186,7 @@ export default compose(
|
|||||||
withAccounts(({ accountsSelectedRows, accountsTableState }) => ({
|
withAccounts(({ accountsSelectedRows, accountsTableState }) => ({
|
||||||
accountsSelectedRows,
|
accountsSelectedRows,
|
||||||
accountsInactiveMode: accountsTableState.inactiveMode,
|
accountsInactiveMode: accountsTableState.inactiveMode,
|
||||||
|
accountsFilterConditions: accountsTableState.filterRoles,
|
||||||
})),
|
})),
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
)(AccountsActionsBar);
|
)(AccountsActionsBar);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import 'style/pages/Accounts/List.scss';
|
import 'style/pages/Accounts/List.scss';
|
||||||
import { DashboardPageContent, DashboardContentTable } from 'components';
|
import { DashboardPageContent, DashboardContentTable } from 'components';
|
||||||
@@ -14,6 +14,7 @@ import withAccounts from 'containers/Accounts/withAccounts';
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import { transformAccountsStateToQuery } from './utils';
|
import { transformAccountsStateToQuery } from './utils';
|
||||||
|
import withAccountsTableActions from './withAccountsTableActions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts chart list.
|
* Accounts chart list.
|
||||||
@@ -21,7 +22,22 @@ import { transformAccountsStateToQuery } from './utils';
|
|||||||
function AccountsChart({
|
function AccountsChart({
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsTableState,
|
accountsTableState,
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
setAccountsTableState,
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the accounts table state once the page unmount.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setAccountsTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setAccountsTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountsChartProvider
|
<AccountsChartProvider
|
||||||
query={transformAccountsStateToQuery(accountsTableState)}
|
query={transformAccountsStateToQuery(accountsTableState)}
|
||||||
@@ -43,4 +59,5 @@ function AccountsChart({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAccounts(({ accountsTableState }) => ({ accountsTableState })),
|
withAccounts(({ accountsTableState }) => ({ accountsTableState })),
|
||||||
|
withAccountsTableActions,
|
||||||
)(AccountsChart);
|
)(AccountsChart);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useResourceFields, useAccounts } from 'hooks/query';
|
import { useResourceViews, useResourceMeta, useAccounts } from 'hooks/query';
|
||||||
|
import { getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const AccountsChartContext = createContext();
|
const AccountsChartContext = createContext();
|
||||||
|
|
||||||
@@ -9,41 +10,42 @@ const AccountsChartContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function AccountsChartProvider({ query, ...props }) {
|
function AccountsChartProvider({ query, ...props }) {
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: resourceViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: resourceViews, isLoading: isViewsLoading } =
|
||||||
'accounts',
|
useResourceViews('accounts');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: resourceFields,
|
data: resourceMeta,
|
||||||
isLoading: isFieldsLoading,
|
isLoading: isResourceMetaLoading,
|
||||||
} = useResourceFields('accounts');
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('accounts');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
data: accounts,
|
data: accounts,
|
||||||
isFetching: isAccountsFetching,
|
isFetching: isAccountsFetching,
|
||||||
isLoading: isAccountsLoading
|
isLoading: isAccountsLoading,
|
||||||
} = useAccounts(
|
} = useAccounts(query, { keepPreviousData: true });
|
||||||
query,
|
|
||||||
{ keepPreviousData: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
accounts,
|
accounts,
|
||||||
resourceFields,
|
|
||||||
|
resourceMeta,
|
||||||
resourceViews,
|
resourceViews,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
isAccountsFetching,
|
isAccountsFetching,
|
||||||
isFieldsLoading,
|
isResourceMetaFetching,
|
||||||
|
isResourceMetaLoading,
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceMetaLoading}
|
||||||
name={'accounts-chart'}
|
name={'accounts-chart'}
|
||||||
>
|
>
|
||||||
<AccountsChartContext.Provider value={provider} {...props} />
|
<AccountsChartContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Intent,
|
Intent,
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Switch,
|
Switch,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import { If, Icon, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
Icon,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
DashboardFilterButton,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useCustomersListContext } from './CustomersListProvider';
|
import { useCustomersListContext } from './CustomersListProvider';
|
||||||
import { useRefreshCustomers } from 'hooks/query/customers';
|
import { useRefreshCustomers } from 'hooks/query/customers';
|
||||||
@@ -34,6 +35,7 @@ import { compose } from 'utils';
|
|||||||
function CustomerActionsBar({
|
function CustomerActionsBar({
|
||||||
// #withCustomers
|
// #withCustomers
|
||||||
customersSelectedRows = [],
|
customersSelectedRows = [],
|
||||||
|
customersFilterConditions,
|
||||||
|
|
||||||
// #withCustomersActions
|
// #withCustomersActions
|
||||||
setCustomersTableState,
|
setCustomersTableState,
|
||||||
@@ -46,7 +48,7 @@ function CustomerActionsBar({
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Customers list context.
|
// Customers list context.
|
||||||
const { customersViews } = useCustomersListContext();
|
const { customersViews, fields } = useCustomersListContext();
|
||||||
|
|
||||||
// Customers refresh action.
|
// Customers refresh action.
|
||||||
const { refresh } = useRefreshCustomers();
|
const { refresh } = useRefreshCustomers();
|
||||||
@@ -72,9 +74,7 @@ function CustomerActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh customers
|
// Handle click a refresh customers
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => { refresh(); };
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -93,17 +93,21 @@ function CustomerActionsBar({
|
|||||||
onClick={onClickNewCustomer}
|
onClick={onClickNewCustomer}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
<Popover
|
|
||||||
// content={filterDropdown}
|
<AdvancedFilterPopover
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
advancedFilterProps={{
|
||||||
position={Position.BOTTOM_LEFT}
|
conditions: customersFilterConditions,
|
||||||
|
defaultFieldKey: 'display_name',
|
||||||
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setCustomersTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
conditionsCount={customersFilterConditions.length}
|
||||||
text={`${intl.get('filter')}`}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={customersSelectedRows.length}>
|
<If condition={customersSelectedRows.length}>
|
||||||
<Button
|
<Button
|
||||||
@@ -146,6 +150,7 @@ export default compose(
|
|||||||
withCustomers(({ customersSelectedRows, customersTableState }) => ({
|
withCustomers(({ customersSelectedRows, customersTableState }) => ({
|
||||||
customersSelectedRows,
|
customersSelectedRows,
|
||||||
accountsInactiveMode: customersTableState.inactiveMode,
|
accountsInactiveMode: customersTableState.inactiveMode,
|
||||||
|
customersFilterConditions: customersTableState.filterRoles,
|
||||||
})),
|
})),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
)(CustomerActionsBar);
|
)(CustomerActionsBar);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import 'style/pages/Customers/List.scss';
|
import 'style/pages/Customers/List.scss';
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ import CustomersAlerts from 'containers/Customers/CustomersAlerts';
|
|||||||
import { CustomersListProvider } from './CustomersListProvider';
|
import { CustomersListProvider } from './CustomersListProvider';
|
||||||
|
|
||||||
import withCustomers from './withCustomers';
|
import withCustomers from './withCustomers';
|
||||||
|
import withCustomersActions from './withCustomersActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +21,22 @@ import { compose } from 'utils';
|
|||||||
function CustomersList({
|
function CustomersList({
|
||||||
// #withCustomers
|
// #withCustomers
|
||||||
customersTableState,
|
customersTableState,
|
||||||
|
|
||||||
|
// #withCustomersActions
|
||||||
|
setCustomersTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the accounts table state once the page unmount.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setCustomersTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setCustomersTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomersListProvider tableState={customersTableState}>
|
<CustomersListProvider tableState={customersTableState}>
|
||||||
<CustomersActionsBar />
|
<CustomersActionsBar />
|
||||||
@@ -38,4 +55,5 @@ function CustomersList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withCustomers(({ customersTableState }) => ({ customersTableState })),
|
withCustomers(({ customersTableState }) => ({ customersTableState })),
|
||||||
|
withCustomersActions
|
||||||
)(CustomersList);
|
)(CustomersList);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useCustomers } from 'hooks/query';
|
import { useResourceMeta, useResourceViews, useCustomers } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
import { transformCustomersStateToQuery } from './utils';
|
import { transformCustomersStateToQuery } from './utils';
|
||||||
|
|
||||||
const CustomersListContext = createContext();
|
const CustomersListContext = createContext();
|
||||||
@@ -12,10 +12,15 @@ function CustomersListProvider({ tableState, ...props }) {
|
|||||||
const tableQuery = transformCustomersStateToQuery(tableState);
|
const tableQuery = transformCustomersStateToQuery(tableState);
|
||||||
|
|
||||||
// Fetch customers resource views and fields.
|
// Fetch customers resource views and fields.
|
||||||
|
const { data: customersViews, isLoading: isViewsLoading } =
|
||||||
|
useResourceViews('customers');
|
||||||
|
|
||||||
|
// Fetch the customers resource fields.
|
||||||
const {
|
const {
|
||||||
data: customersViews,
|
data: resourceMeta,
|
||||||
isLoading: isCustomersViewsLoading,
|
isLoading: isResourceMetaLoading,
|
||||||
} = useResourceViews('customers');
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('customers');
|
||||||
|
|
||||||
// Fetches customers data with pagination meta.
|
// Fetches customers data with pagination meta.
|
||||||
const {
|
const {
|
||||||
@@ -25,16 +30,26 @@ function CustomersListProvider({ tableState, ...props }) {
|
|||||||
} = useCustomers(tableQuery, { keepPreviousData: true });
|
} = useCustomers(tableQuery, { keepPreviousData: true });
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus = isTableEmptyStatus({
|
const isEmptyStatus =
|
||||||
data: customers, pagination, filterMeta,
|
isTableEmptyStatus({
|
||||||
}) && !isCustomersFetching && !tableState.inactiveMode;
|
data: customers,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
}) &&
|
||||||
|
!isCustomersFetching &&
|
||||||
|
!tableState.inactiveMode;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
customersViews,
|
customersViews,
|
||||||
customers,
|
customers,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
||||||
isCustomersViewsLoading,
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
resourceMeta,
|
||||||
|
isResourceMetaLoading,
|
||||||
|
isResourceMetaFetching,
|
||||||
|
|
||||||
|
isViewsLoading,
|
||||||
isCustomersLoading,
|
isCustomersLoading,
|
||||||
isCustomersFetching,
|
isCustomersFetching,
|
||||||
|
|
||||||
@@ -42,7 +57,10 @@ function CustomersListProvider({ tableState, ...props }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={isCustomersViewsLoading} name={'customers-list'}>
|
<DashboardInsider
|
||||||
|
loading={isViewsLoading || isResourceMetaLoading || isCustomersLoading}
|
||||||
|
name={'customers-list'}
|
||||||
|
>
|
||||||
<CustomersListContext.Provider value={state} {...props} />
|
<CustomersListContext.Provider value={state} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Classes,
|
Classes,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
Popover,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useRefreshExpenses } from 'hooks/query/expenses';
|
import { useRefreshExpenses } from 'hooks/query/expenses';
|
||||||
import { useExpensesListContext } from './ExpensesListProvider';
|
import { useExpensesListContext } from './ExpensesListProvider';
|
||||||
|
|
||||||
import withExpensesActions from './withExpensesActions';
|
import withExpensesActions from './withExpensesActions';
|
||||||
|
import withExpenses from './withExpenses';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -32,14 +35,15 @@ import { compose } from 'utils';
|
|||||||
function ExpensesActionsBar({
|
function ExpensesActionsBar({
|
||||||
//#withExpensesActions
|
//#withExpensesActions
|
||||||
setExpensesTableState,
|
setExpensesTableState,
|
||||||
}) {
|
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
|
||||||
|
|
||||||
|
// #withExpenses
|
||||||
|
expensesFilterConditions
|
||||||
|
}) {
|
||||||
// History context.
|
// History context.
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Expenses list context.
|
// Expenses list context.
|
||||||
const { expensesViews } = useExpensesListContext();
|
const { expensesViews, fields } = useExpensesListContext();
|
||||||
|
|
||||||
// Expenses refresh action.
|
// Expenses refresh action.
|
||||||
const { refresh } = useRefreshExpenses();
|
const { refresh } = useRefreshExpenses();
|
||||||
@@ -79,20 +83,20 @@ function ExpensesActionsBar({
|
|||||||
text={<T id={'new_expense'} />}
|
text={<T id={'new_expense'} />}
|
||||||
onClick={onClickNewExpense}
|
onClick={onClickNewExpense}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
content={''}
|
conditions: expensesFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'reference_no',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setExpensesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
conditionsCount={expensesFilterConditions.length}
|
||||||
'has-active-filters': filterCount > 0,
|
|
||||||
})}
|
|
||||||
text={<T id={'filter'} />}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
@@ -134,4 +138,7 @@ function ExpensesActionsBar({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withExpensesActions,
|
withExpensesActions,
|
||||||
|
withExpenses(({ expensesTableState }) => ({
|
||||||
|
expensesFilterConditions: expensesTableState.filterRoles,
|
||||||
|
}))
|
||||||
)(ExpensesActionsBar);
|
)(ExpensesActionsBar);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import 'style/pages/Expense/List.scss';
|
import 'style/pages/Expense/List.scss';
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ import ExpenseDataTable from './ExpenseDataTable';
|
|||||||
import ExpensesAlerts from '../ExpensesAlerts';
|
import ExpensesAlerts from '../ExpensesAlerts';
|
||||||
|
|
||||||
import withExpenses from './withExpenses';
|
import withExpenses from './withExpenses';
|
||||||
|
import withExpensesActions from './withExpensesActions';
|
||||||
|
|
||||||
import { compose, transformTableStateToQuery } from 'utils';
|
import { compose, transformTableStateToQuery } from 'utils';
|
||||||
import { ExpensesListProvider } from './ExpensesListProvider';
|
import { ExpensesListProvider } from './ExpensesListProvider';
|
||||||
@@ -20,7 +21,22 @@ import { ExpensesListProvider } from './ExpensesListProvider';
|
|||||||
function ExpensesList({
|
function ExpensesList({
|
||||||
// #withExpenses
|
// #withExpenses
|
||||||
expensesTableState,
|
expensesTableState,
|
||||||
|
|
||||||
|
// #withExpensesActions
|
||||||
|
setExpensesTableState,
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the accounts table state once the page unmount.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setExpensesTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setExpensesTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpensesListProvider
|
<ExpensesListProvider
|
||||||
query={transformTableStateToQuery(expensesTableState)}
|
query={transformTableStateToQuery(expensesTableState)}
|
||||||
@@ -42,4 +58,5 @@ function ExpensesList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withExpenses(({ expensesTableState }) => ({ expensesTableState })),
|
withExpenses(({ expensesTableState }) => ({ expensesTableState })),
|
||||||
|
withExpensesActions,
|
||||||
)(ExpensesList);
|
)(ExpensesList);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useExpenses, useResourceViews } from 'hooks/query';
|
import { useExpenses, useResourceMeta, useResourceViews } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const ExpensesListContext = createContext();
|
const ExpensesListContext = createContext();
|
||||||
|
|
||||||
@@ -10,9 +10,8 @@ const ExpensesListContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function ExpensesListProvider({ query, ...props }) {
|
function ExpensesListProvider({ query, ...props }) {
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: expensesViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: expensesViews, isLoading: isViewsLoading } =
|
||||||
'expenses',
|
useResourceViews('expenses');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches the expenses with pagination meta.
|
// Fetches the expenses with pagination meta.
|
||||||
const {
|
const {
|
||||||
@@ -21,10 +20,20 @@ function ExpensesListProvider({ query, ...props }) {
|
|||||||
isFetching: isExpensesFetching,
|
isFetching: isExpensesFetching,
|
||||||
} = useExpenses(query, { keepPreviousData: true });
|
} = useExpenses(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Fetch the expenses resource fields.
|
||||||
|
const {
|
||||||
|
data: resourceMeta,
|
||||||
|
isLoading: isResourceMetaLoading,
|
||||||
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('expenses');
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus = isTableEmptyStatus({
|
const isEmptyStatus =
|
||||||
data: expenses, pagination, filterMeta,
|
isTableEmptyStatus({
|
||||||
}) && !isExpensesFetching;
|
data: expenses,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
}) && !isExpensesFetching;
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
@@ -32,16 +41,21 @@ function ExpensesListProvider({ query, ...props }) {
|
|||||||
expenses,
|
expenses,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
resourceMeta,
|
||||||
|
isResourceMetaLoading,
|
||||||
|
isResourceMetaFetching,
|
||||||
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isExpensesLoading,
|
isExpensesLoading,
|
||||||
isExpensesFetching,
|
isExpensesFetching,
|
||||||
|
|
||||||
isEmptyStatus
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isExpensesLoading}
|
loading={isViewsLoading || isExpensesLoading || isResourceMetaLoading}
|
||||||
name={'expenses'}
|
name={'expenses'}
|
||||||
>
|
>
|
||||||
<ExpensesListContext.Provider value={provider} {...props} />
|
<ExpensesListContext.Provider value={provider} {...props} />
|
||||||
@@ -49,7 +63,6 @@ function ExpensesListProvider({ query, ...props }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useExpensesListContext = () =>
|
const useExpensesListContext = () => React.useContext(ExpensesListContext);
|
||||||
React.useContext(ExpensesListContext);
|
|
||||||
|
|
||||||
export { ExpensesListProvider, useExpensesListContext };
|
export { ExpensesListProvider, useExpensesListContext };
|
||||||
|
|||||||
@@ -10,6 +10,5 @@ export default (mapState) => {
|
|||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|
||||||
return connect(mapStateToProps);
|
return connect(mapStateToProps);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import classNames from 'classnames';
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Intent,
|
Intent,
|
||||||
Switch,
|
Switch,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Tooltip2 } from '@blueprintjs/popover2';
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
DashboardFilterButton,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useItemsListContext } from './ItemsListProvider';
|
import { useItemsListContext } from './ItemsListProvider';
|
||||||
import { useRefreshItems } from 'hooks/query/items';
|
import { useRefreshItems } from 'hooks/query/items';
|
||||||
@@ -35,6 +34,7 @@ import { compose } from 'utils';
|
|||||||
function ItemsActionsBar({
|
function ItemsActionsBar({
|
||||||
// #withItems
|
// #withItems
|
||||||
itemsSelectedRows,
|
itemsSelectedRows,
|
||||||
|
itemsFilterRoles,
|
||||||
|
|
||||||
// #withItemActions
|
// #withItemActions
|
||||||
setItemsTableState,
|
setItemsTableState,
|
||||||
@@ -44,7 +44,7 @@ function ItemsActionsBar({
|
|||||||
openAlert,
|
openAlert,
|
||||||
}) {
|
}) {
|
||||||
// Items list context.
|
// Items list context.
|
||||||
const { itemsViews } = useItemsListContext();
|
const { itemsViews, fields } = useItemsListContext();
|
||||||
|
|
||||||
// Items refresh action.
|
// Items refresh action.
|
||||||
const { refresh } = useRefreshItems();
|
const { refresh } = useRefreshItems();
|
||||||
@@ -93,19 +93,21 @@ function ItemsActionsBar({
|
|||||||
text={<T id={'new_item'} />}
|
text={<T id={'new_item'} />}
|
||||||
onClick={onClickNewItem}
|
onClick={onClickNewItem}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
|
||||||
|
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
content={''}
|
advancedFilterProps={{
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
conditions: itemsFilterRoles,
|
||||||
position={Position.BOTTOM_LEFT}
|
defaultFieldKey: 'name',
|
||||||
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setItemsTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton conditionsCount={itemsFilterRoles.length} />
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
</AdvancedFilterPopover>
|
||||||
text={`${intl.get('filter')}`}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
<NavbarDivider />
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<If condition={itemsSelectedRows.length}>
|
<If condition={itemsSelectedRows.length}>
|
||||||
<Button
|
<Button
|
||||||
@@ -149,6 +151,7 @@ export default compose(
|
|||||||
withItems(({ itemsSelectedRows, itemsTableState }) => ({
|
withItems(({ itemsSelectedRows, itemsTableState }) => ({
|
||||||
itemsSelectedRows,
|
itemsSelectedRows,
|
||||||
itemsInactiveMode: itemsTableState.inactiveMode,
|
itemsInactiveMode: itemsTableState.inactiveMode,
|
||||||
|
itemsFilterRoles: itemsTableState.filterRoles,
|
||||||
})),
|
})),
|
||||||
withItemsActions,
|
withItemsActions,
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
|
|||||||
@@ -1,62 +1,74 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
import { transformTableQueryToParams, isTableEmptyStatus } from 'utils';
|
import {
|
||||||
|
getFieldsFromResourceMeta,
|
||||||
|
transformTableQueryToParams,
|
||||||
|
isTableEmptyStatus,
|
||||||
|
} from 'utils';
|
||||||
import { transformItemsTableState } from './utils';
|
import { transformItemsTableState } from './utils';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useResourceFields, useItems } from 'hooks/query';
|
import { useResourceViews, useResourceMeta, useItems } from 'hooks/query';
|
||||||
|
|
||||||
const ItemsContext = createContext();
|
const ItemsContext = createContext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Items list provider.
|
* Items list provider.
|
||||||
*/
|
*/
|
||||||
function ItemsListProvider({
|
function ItemsListProvider({ tableState, ...props }) {
|
||||||
tableState,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const tableQuery = transformItemsTableState(tableState);
|
const tableQuery = transformItemsTableState(tableState);
|
||||||
|
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: itemsViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: itemsViews, isLoading: isViewsLoading } =
|
||||||
'items',
|
useResourceViews('items');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const { data: itemsFields, isLoading: isFieldsLoading } = useResourceFields(
|
const {
|
||||||
'items',
|
data: resourceMeta,
|
||||||
);
|
isLoading: isResourceLoading,
|
||||||
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('items');
|
||||||
|
|
||||||
// Handle fetching the items table based on the given query.
|
// Handle fetching the items table based on the given query.
|
||||||
const {
|
const {
|
||||||
data: { items, pagination, filterMeta },
|
data: { items, pagination, filterMeta },
|
||||||
isFetching: isItemsFetching,
|
isFetching: isItemsFetching,
|
||||||
isLoading: isItemsLoading,
|
isLoading: isItemsLoading,
|
||||||
} = useItems({
|
} = useItems(
|
||||||
...transformTableQueryToParams(tableQuery)
|
{
|
||||||
}, { keepPreviousData: true });
|
...transformTableQueryToParams(tableQuery),
|
||||||
|
},
|
||||||
|
{ keepPreviousData: true },
|
||||||
|
);
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus = isTableEmptyStatus({
|
const isEmptyStatus =
|
||||||
data: items, pagination, filterMeta,
|
isTableEmptyStatus({
|
||||||
}) && !isItemsFetching && !tableState.inactiveMode;
|
data: items,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
}) &&
|
||||||
|
!isItemsFetching &&
|
||||||
|
!tableState.inactiveMode;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
itemsViews,
|
itemsViews,
|
||||||
itemsFields,
|
|
||||||
items,
|
items,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isItemsLoading,
|
isItemsLoading,
|
||||||
isItemsFetching: isItemsFetching,
|
isItemsFetching: isItemsFetching,
|
||||||
|
isResourceLoading,
|
||||||
|
isResourceFetching,
|
||||||
|
|
||||||
isEmptyStatus,
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider loading={isItemsLoading || isResourceLoading} name={'items-list'}>
|
||||||
loading={isFieldsLoading}
|
|
||||||
name={'items-list'}
|
|
||||||
>
|
|
||||||
<ItemsContext.Provider value={state} {...props} />
|
<ItemsContext.Provider value={state} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
import 'style/pages/ItemsCategories/List.scss';
|
import 'style/pages/ItemsCategories/List.scss';
|
||||||
|
|
||||||
import { DashboardContentTable, DashboardPageContent } from 'components';
|
import { DashboardContentTable, DashboardPageContent } from 'components';
|
||||||
@@ -8,12 +10,17 @@ import ItemsCategoryActionsBar from './ItemsCategoryActionsBar';
|
|||||||
import { ItemsCategoriesProvider } from './ItemsCategoriesProvider';
|
import { ItemsCategoriesProvider } from './ItemsCategoriesProvider';
|
||||||
import ItemCategoriesTable from './ItemCategoriesTable';
|
import ItemCategoriesTable from './ItemCategoriesTable';
|
||||||
|
|
||||||
|
import withItemsCategories from './withItemCategories';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item categories list.
|
* Item categories list.
|
||||||
*/
|
*/
|
||||||
export default function ItemCategoryList() {
|
function ItemCategoryList({
|
||||||
|
// #withItemsCategories
|
||||||
|
itemsCategoriesTableState
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<ItemsCategoriesProvider>
|
<ItemsCategoriesProvider tableState={itemsCategoriesTableState}>
|
||||||
<ItemsCategoryActionsBar />
|
<ItemsCategoryActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
@@ -25,3 +32,9 @@ export default function ItemCategoryList() {
|
|||||||
</ItemsCategoriesProvider>
|
</ItemsCategoriesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withItemsCategories(({ itemsCategoriesTableState }) => ({
|
||||||
|
itemsCategoriesTableState,
|
||||||
|
})),
|
||||||
|
)(ItemCategoryList);
|
||||||
|
|||||||
@@ -1,31 +1,50 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useItemsCategories } from 'hooks/query';
|
import { useItemsCategories, useResourceMeta } from 'hooks/query';
|
||||||
|
import { transformTableStateToQuery, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const ItemsCategoriesContext = createContext();
|
const ItemsCategoriesContext = createContext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Items categories provider.
|
* Items categories provider.
|
||||||
*/
|
*/
|
||||||
function ItemsCategoriesProvider({ query, ...props }) {
|
function ItemsCategoriesProvider({ tableState, ...props }) {
|
||||||
|
// Transformes the table state to query.
|
||||||
|
const query = transformTableStateToQuery(tableState);
|
||||||
|
|
||||||
|
// Items categories list.
|
||||||
const {
|
const {
|
||||||
data: { itemsCategories, pagination },
|
data: { itemsCategories, pagination },
|
||||||
isFetching: isCategoriesFetching,
|
isFetching: isCategoriesFetching,
|
||||||
isLoading: isCategoriesLoading,
|
isLoading: isCategoriesLoading,
|
||||||
} = useItemsCategories();
|
} = useItemsCategories(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Fetch the accounts resource fields.
|
||||||
|
const {
|
||||||
|
data: resourceMeta,
|
||||||
|
isLoading: isResourceLoading,
|
||||||
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('item_category');
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
isCategoriesFetching,
|
isCategoriesFetching,
|
||||||
isCategoriesLoading,
|
isCategoriesLoading,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
resourceMeta,
|
||||||
|
isResourceLoading,
|
||||||
|
isResourceFetching,
|
||||||
|
|
||||||
itemsCategories,
|
itemsCategories,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
||||||
query,
|
query,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider name={'items-categories-list'}>
|
<DashboardInsider
|
||||||
|
isLoading={isResourceLoading}
|
||||||
|
name={'items-categories-list'}
|
||||||
|
>
|
||||||
<ItemsCategoriesContext.Provider value={state} {...props} />
|
<ItemsCategoriesContext.Provider value={state} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
@@ -34,7 +53,4 @@ function ItemsCategoriesProvider({ query, ...props }) {
|
|||||||
const useItemsCategoriesContext = () =>
|
const useItemsCategoriesContext = () =>
|
||||||
React.useContext(ItemsCategoriesContext);
|
React.useContext(ItemsCategoriesContext);
|
||||||
|
|
||||||
export {
|
export { ItemsCategoriesProvider, useItemsCategoriesContext };
|
||||||
ItemsCategoriesProvider,
|
|
||||||
useItemsCategoriesContext,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -5,21 +5,20 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Intent,
|
Intent,
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { If, Icon } from 'components';
|
import { If, Icon } from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
import { AdvancedFilterPopover, DashboardFilterButton } from 'components';
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
// import withItemCategories from './withItemCategories';
|
import withItemCategories from './withItemCategories';
|
||||||
|
import withItemCategoriesActions from './withItemCategoriesActions';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
import { useItemsCategoriesContext } from './ItemsCategoriesProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Items categories actions bar.
|
* Items categories actions bar.
|
||||||
@@ -27,6 +26,10 @@ import { compose } from 'utils';
|
|||||||
function ItemsCategoryActionsBar({
|
function ItemsCategoryActionsBar({
|
||||||
// #withItemCategories
|
// #withItemCategories
|
||||||
itemCategoriesSelectedRows = [],
|
itemCategoriesSelectedRows = [],
|
||||||
|
categoriesFilterConditions,
|
||||||
|
|
||||||
|
//
|
||||||
|
setItemsCategoriesTableState,
|
||||||
|
|
||||||
// #withDialog
|
// #withDialog
|
||||||
openDialog,
|
openDialog,
|
||||||
@@ -34,6 +37,8 @@ function ItemsCategoryActionsBar({
|
|||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
openAlert,
|
openAlert,
|
||||||
}) {
|
}) {
|
||||||
|
const { fields } = useItemsCategoriesContext();
|
||||||
|
|
||||||
const onClickNewCategory = () => {
|
const onClickNewCategory = () => {
|
||||||
openDialog('item-category-form', {});
|
openDialog('item-category-form', {});
|
||||||
};
|
};
|
||||||
@@ -44,7 +49,9 @@ function ItemsCategoryActionsBar({
|
|||||||
itemCategoriesIds: itemCategoriesSelectedRows,
|
itemCategoriesIds: itemCategoriesSelectedRows,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(fields, categoriesFilterConditions, 'XXXX');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -56,25 +63,20 @@ function ItemsCategoryActionsBar({
|
|||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: categoriesFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'name',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
canOutsideClickClose={true}
|
onFilterChange: (filterConditions) => {
|
||||||
|
setItemsCategoriesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
conditionsCount={categoriesFilterConditions.length}
|
||||||
text={
|
|
||||||
true ? (
|
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
`${0} filters applied`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={itemCategoriesSelectedRows.length}>
|
<If condition={itemCategoriesSelectedRows.length}>
|
||||||
<Button
|
<Button
|
||||||
@@ -103,8 +105,12 @@ function ItemsCategoryActionsBar({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
// withItemCategories(({ itemCategoriesSelectedRows }) => ({
|
withItemCategories(
|
||||||
// itemCategoriesSelectedRows,
|
({ itemCategoriesSelectedRows, itemsCategoriesTableState }) => ({
|
||||||
// })),
|
itemCategoriesSelectedRows,
|
||||||
|
categoriesFilterConditions: itemsCategoriesTableState.filterRoles,
|
||||||
|
}),
|
||||||
|
),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
|
withItemCategoriesActions
|
||||||
)(ItemsCategoryActionsBar);
|
)(ItemsCategoryActionsBar);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getItemsCategoriesTableStateFactory,
|
getItemsCategoriesTableStateFactory,
|
||||||
} from 'store/itemCategories/itemsCategories.selectors';
|
} from 'store/itemCategories/ItemsCategories.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const getItemsCategoriesTableState = getItemsCategoriesTableStateFactory();
|
const getItemsCategoriesTableState = getItemsCategoriesTableStateFactory();
|
||||||
|
|||||||
@@ -3,24 +3,25 @@ import Icon from 'components/Icon';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import withBillsActions from './withBillsActions';
|
import withBillsActions from './withBillsActions';
|
||||||
|
import withBills from './withBills';
|
||||||
import { useBillsListContext } from './BillsListProvider';
|
import { useBillsListContext } from './BillsListProvider';
|
||||||
import { useRefreshBills } from 'hooks/query/bills';
|
import { useRefreshBills } from 'hooks/query/bills';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -31,6 +32,9 @@ import { compose } from 'utils';
|
|||||||
function BillActionsBar({
|
function BillActionsBar({
|
||||||
// #withBillActions
|
// #withBillActions
|
||||||
setBillsTableState,
|
setBillsTableState,
|
||||||
|
|
||||||
|
// #withBills
|
||||||
|
billsConditionsRoles
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -38,9 +42,7 @@ function BillActionsBar({
|
|||||||
const { refresh } = useRefreshBills();
|
const { refresh } = useRefreshBills();
|
||||||
|
|
||||||
// Bills list context.
|
// Bills list context.
|
||||||
const { billsViews } = useBillsListContext();
|
const { billsViews, fields } = useBillsListContext();
|
||||||
|
|
||||||
const [filterCount] = useState(0);
|
|
||||||
|
|
||||||
// Handle click a new bill.
|
// Handle click a new bill.
|
||||||
const handleClickNewBill = () => {
|
const handleClickNewBill = () => {
|
||||||
@@ -53,11 +55,8 @@ function BillActionsBar({
|
|||||||
customViewId: customView.id || null,
|
customViewId: customView.id || null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh bills
|
// Handle click a refresh bills
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => { refresh(); };
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -74,24 +73,21 @@ function BillActionsBar({
|
|||||||
text={<T id={'new_bill'} />}
|
text={<T id={'new_bill'} />}
|
||||||
onClick={handleClickNewBill}
|
onClick={handleClickNewBill}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
content={[]}
|
conditions: billsConditionsRoles,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'bill_number',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setBillsTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL)}
|
conditionsCount={billsConditionsRoles.length}
|
||||||
text={
|
|
||||||
true ? (
|
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
`${filterCount} ${intl.get('filters_applied')}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -128,4 +124,9 @@ function BillActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withBillsActions)(BillActionsBar);
|
export default compose(
|
||||||
|
withBillsActions,
|
||||||
|
withBills(({ billsTableState }) => ({
|
||||||
|
billsConditionsRoles: billsTableState.filterRoles
|
||||||
|
}))
|
||||||
|
)(BillActionsBar);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { DashboardContentTable, DashboardPageContent } from 'components';
|
import { DashboardContentTable, DashboardPageContent } from 'components';
|
||||||
|
|
||||||
import 'style/pages/Bills/List.scss';
|
import 'style/pages/Bills/List.scss';
|
||||||
@@ -11,6 +11,7 @@ import BillsViewsTabs from './BillsViewsTabs';
|
|||||||
import BillsTable from './BillsTable';
|
import BillsTable from './BillsTable';
|
||||||
|
|
||||||
import withBills from './withBills';
|
import withBills from './withBills';
|
||||||
|
import withBillsActions from './withBillsActions';
|
||||||
|
|
||||||
import { transformTableStateToQuery, compose } from 'utils';
|
import { transformTableStateToQuery, compose } from 'utils';
|
||||||
|
|
||||||
@@ -20,7 +21,22 @@ import { transformTableStateToQuery, compose } from 'utils';
|
|||||||
function BillsList({
|
function BillsList({
|
||||||
// #withBills
|
// #withBills
|
||||||
billsTableState,
|
billsTableState,
|
||||||
|
|
||||||
|
// #withBillsActions
|
||||||
|
setBillsTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the accounts table state once the page unmount.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setBillsTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setBillsTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BillsListProvider query={transformTableStateToQuery(billsTableState)}>
|
<BillsListProvider query={transformTableStateToQuery(billsTableState)}>
|
||||||
<BillsActionsBar />
|
<BillsActionsBar />
|
||||||
@@ -40,4 +56,5 @@ function BillsList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withBills(({ billsTableState }) => ({ billsTableState })),
|
withBills(({ billsTableState }) => ({ billsTableState })),
|
||||||
|
withBillsActions
|
||||||
)(BillsList);
|
)(BillsList);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useResourceFields, useBills } from 'hooks/query';
|
import { useResourceViews, useResourceMeta, useBills } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { getFieldsFromResourceMeta, isTableEmptyStatus } from 'utils';
|
||||||
|
|
||||||
const BillsListContext = createContext();
|
const BillsListContext = createContext();
|
||||||
|
|
||||||
@@ -10,15 +10,15 @@ const BillsListContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function BillsListProvider({ query, ...props }) {
|
function BillsListProvider({ query, ...props }) {
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: billsViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: billsViews, isLoading: isViewsLoading } =
|
||||||
'bills',
|
useResourceViews('bills');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: billsFields,
|
data: resourceMeta,
|
||||||
isLoading: isFieldsLoading,
|
isLoading: isResourceLoading,
|
||||||
} = useResourceFields('bills');
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('bills');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
@@ -28,27 +28,33 @@ function BillsListProvider({ query, ...props }) {
|
|||||||
} = useBills(query, { keepPreviousData: true });
|
} = useBills(query, { keepPreviousData: true });
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus = isTableEmptyStatus({
|
const isEmptyStatus =
|
||||||
data: bills, pagination, filterMeta,
|
isTableEmptyStatus({
|
||||||
}) && !isBillsFetching;
|
data: bills,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
}) && !isBillsFetching;
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
bills,
|
bills,
|
||||||
pagination,
|
pagination,
|
||||||
billsFields,
|
|
||||||
billsViews,
|
billsViews,
|
||||||
|
|
||||||
|
resourceMeta,
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
isResourceLoading,
|
||||||
|
isResourceFetching,
|
||||||
|
|
||||||
isBillsLoading,
|
isBillsLoading,
|
||||||
isBillsFetching,
|
isBillsFetching,
|
||||||
isFieldsLoading,
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isEmptyStatus
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceLoading}
|
||||||
name={'bills'}
|
name={'bills'}
|
||||||
>
|
>
|
||||||
<BillsListContext.Provider value={provider} {...props} />
|
<BillsListContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -3,26 +3,29 @@ import Icon from 'components/Icon';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import withPaymentMade from './withPaymentMade';
|
||||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||||
|
|
||||||
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
|
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
|
||||||
import { useRefreshPaymentMades } from 'hooks/query/paymentMades';
|
import { useRefreshPaymentMades } from 'hooks/query/paymentMades';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,11 +34,14 @@ import { compose } from 'utils';
|
|||||||
function PaymentMadeActionsBar({
|
function PaymentMadeActionsBar({
|
||||||
// #withPaymentMadesActions
|
// #withPaymentMadesActions
|
||||||
setPaymentMadesTableState,
|
setPaymentMadesTableState,
|
||||||
|
|
||||||
|
// #withPaymentMades
|
||||||
|
paymentMadesFilterConditions
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Payment receives list context.
|
// Payment receives list context.
|
||||||
const { paymentMadesViews } = usePaymentMadesListContext();
|
const { paymentMadesViews, fields } = usePaymentMadesListContext();
|
||||||
|
|
||||||
// Handle new payment made button click.
|
// Handle new payment made button click.
|
||||||
const handleClickNewPaymentMade = () => {
|
const handleClickNewPaymentMade = () => {
|
||||||
@@ -69,20 +75,21 @@ function PaymentMadeActionsBar({
|
|||||||
text={<T id={'new_payment_made'} />}
|
text={<T id={'new_payment_made'} />}
|
||||||
onClick={handleClickNewPaymentMade}
|
onClick={handleClickNewPaymentMade}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: paymentMadesFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'payment_number',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setPaymentMadesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL)}
|
conditionsCount={paymentMadesFilterConditions.length}
|
||||||
text={
|
|
||||||
true ? <T id={'filter'} /> : `${0} ${intl.get('filters_applied')}`
|
|
||||||
}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -119,4 +126,9 @@ function PaymentMadeActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withPaymentMadeActions)(PaymentMadeActionsBar);
|
export default compose(
|
||||||
|
withPaymentMadeActions,
|
||||||
|
withPaymentMade(({ paymentMadesTableState }) => ({
|
||||||
|
paymentMadesFilterConditions: paymentMadesTableState.filterRoles,
|
||||||
|
})),
|
||||||
|
)(PaymentMadeActionsBar);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { PaymentMadesListProvider } from './PaymentMadesListProvider';
|
|||||||
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
|
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
|
||||||
|
|
||||||
import withPaymentMades from './withPaymentMade';
|
import withPaymentMades from './withPaymentMade';
|
||||||
|
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||||
|
|
||||||
import { compose, transformTableStateToQuery } from 'utils';
|
import { compose, transformTableStateToQuery } from 'utils';
|
||||||
|
|
||||||
@@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils';
|
|||||||
function PaymentMadeList({
|
function PaymentMadeList({
|
||||||
// #withPaymentMades
|
// #withPaymentMades
|
||||||
paymentMadesTableState,
|
paymentMadesTableState,
|
||||||
|
|
||||||
|
// #withPaymentMadeActions
|
||||||
|
setPaymentMadesTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the invoices table state once the page unmount.
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
|
setPaymentMadesTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setPaymentMadesTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaymentMadesListProvider
|
<PaymentMadesListProvider
|
||||||
query={transformTableStateToQuery(paymentMadesTableState)}
|
query={transformTableStateToQuery(paymentMadesTableState)}
|
||||||
@@ -43,4 +59,5 @@ export default compose(
|
|||||||
withPaymentMades(({ paymentMadesTableState }) => ({
|
withPaymentMades(({ paymentMadesTableState }) => ({
|
||||||
paymentMadesTableState,
|
paymentMadesTableState,
|
||||||
})),
|
})),
|
||||||
|
withPaymentMadeActions
|
||||||
)(PaymentMadeList);
|
)(PaymentMadeList);
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React, { createContext } from 'react';
|
|||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import {
|
import {
|
||||||
useResourceViews,
|
useResourceViews,
|
||||||
useResourceFields,
|
|
||||||
usePaymentMades,
|
usePaymentMades,
|
||||||
|
useResourceMeta
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const PaymentMadesListContext = createContext();
|
const PaymentMadesListContext = createContext();
|
||||||
|
|
||||||
@@ -21,9 +21,10 @@ function PaymentMadesListProvider({ query, ...props }) {
|
|||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: paymentMadesFields,
|
data: resourceMeta,
|
||||||
isLoading: isFieldsLoading,
|
isLoading: isResourceMetaLoading,
|
||||||
} = useResourceFields('bill_payments');
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('bill_payments');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
@@ -45,19 +46,22 @@ function PaymentMadesListProvider({ query, ...props }) {
|
|||||||
paymentMades,
|
paymentMades,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta,
|
filterMeta,
|
||||||
paymentMadesFields,
|
|
||||||
paymentMadesViews,
|
paymentMadesViews,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
resourceMeta,
|
||||||
|
isResourceMetaLoading,
|
||||||
|
isResourceMetaFetching,
|
||||||
|
|
||||||
isPaymentsLoading,
|
isPaymentsLoading,
|
||||||
isPaymentsFetching,
|
isPaymentsFetching,
|
||||||
isFieldsLoading,
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isEmptyStatus
|
isEmptyStatus
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceMetaLoading}
|
||||||
name={'payment-mades-list'}
|
name={'payment-mades-list'}
|
||||||
>
|
>
|
||||||
<PaymentMadesListContext.Provider value={provider} {...props} />
|
<PaymentMadesListContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ const PaymentMadesContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function PaymentMadesProvider({ query, ...props }) {
|
function PaymentMadesProvider({ query, ...props }) {
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: paymentsViews, isFetching: isViewsLoading } = useResourceViews(
|
const { data: paymentsViews, isLoading: isViewsLoading } = useResourceViews(
|
||||||
'bill_payments',
|
'bill_payments',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: paymentsFields,
|
data: paymentsFields,
|
||||||
isFetching: isFieldsLoading,
|
isLoading: isFieldsLoading,
|
||||||
} = useResourceFields('bill_payments');
|
} = useResourceFields('bill_payments');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
data: { paymentMades, pagination },
|
data: { paymentMades, pagination },
|
||||||
isFetching: isPaymentsLoading,
|
isLoading: isPaymentsLoading,
|
||||||
} = usePaymentMades(query);
|
} = usePaymentMades(query);
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
@@ -39,7 +39,7 @@ function PaymentMadesProvider({ query, ...props }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isFieldsLoading || isPaymentsLoading}
|
||||||
name={'payment_made'}
|
name={'payment_made'}
|
||||||
>
|
>
|
||||||
<PaymentMadesContext.Provider value={provider} {...props} />
|
<PaymentMadesContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
|
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
} from 'components';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import withEstimatesActions from './withEstimatesActions';
|
import withEstimatesActions from './withEstimatesActions';
|
||||||
|
import withEstimates from './withEstimates';
|
||||||
|
|
||||||
import { useEstimatesListContext } from './EstimatesListProvider';
|
import { useEstimatesListContext } from './EstimatesListProvider';
|
||||||
import { useRefreshEstimates } from 'hooks/query/estimates';
|
import { useRefreshEstimates } from 'hooks/query/estimates';
|
||||||
|
|
||||||
@@ -31,13 +33,14 @@ import { compose } from 'utils';
|
|||||||
function EstimateActionsBar({
|
function EstimateActionsBar({
|
||||||
// #withEstimateActions
|
// #withEstimateActions
|
||||||
setEstimatesTableState,
|
setEstimatesTableState,
|
||||||
|
|
||||||
|
// #withEstimates
|
||||||
|
estimatesFilterRoles
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
|
||||||
|
|
||||||
// Estimates list context.
|
// Estimates list context.
|
||||||
const { estimatesViews } = useEstimatesListContext();
|
const { estimatesViews, fields } = useEstimatesListContext();
|
||||||
|
|
||||||
// Handle click a new sale estimate.
|
// Handle click a new sale estimate.
|
||||||
const onClickNewEstimate = () => {
|
const onClickNewEstimate = () => {
|
||||||
@@ -55,9 +58,7 @@ function EstimateActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh sale estimates
|
// Handle click a refresh sale estimates
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => { refresh(); };
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -74,23 +75,21 @@ function EstimateActionsBar({
|
|||||||
text={<T id={'new_estimate'} />}
|
text={<T id={'new_estimate'} />}
|
||||||
onClick={onClickNewEstimate}
|
onClick={onClickNewEstimate}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
conditions: estimatesFilterRoles,
|
||||||
position={Position.BOTTOM_LEFT}
|
defaultFieldKey: 'estimate_number',
|
||||||
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setEstimatesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
conditionsCount={estimatesFilterRoles.length}
|
||||||
text={
|
|
||||||
filterCount <= 0 ? (
|
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
`${filterCount} ${intl.get('filters_applied')}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -128,4 +127,9 @@ function EstimateActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withEstimatesActions)(EstimateActionsBar);
|
export default compose(
|
||||||
|
withEstimatesActions,
|
||||||
|
withEstimates(({ estimatesTableState }) => ({
|
||||||
|
estimatesFilterRoles: estimatesTableState.filterRoles,
|
||||||
|
})),
|
||||||
|
)(EstimateActionsBar);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import EstimatesViewTabs from './EstimatesViewTabs';
|
|||||||
import EstimatesDataTable from './EstimatesDataTable';
|
import EstimatesDataTable from './EstimatesDataTable';
|
||||||
|
|
||||||
import withEstimates from './withEstimates';
|
import withEstimates from './withEstimates';
|
||||||
|
import withEstimatesActions from './withEstimatesActions';
|
||||||
|
|
||||||
import { EstimatesListProvider } from './EstimatesListProvider';
|
import { EstimatesListProvider } from './EstimatesListProvider';
|
||||||
import { compose, transformTableStateToQuery } from 'utils';
|
import { compose, transformTableStateToQuery } from 'utils';
|
||||||
@@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils';
|
|||||||
function EstimatesList({
|
function EstimatesList({
|
||||||
// #withEstimate
|
// #withEstimate
|
||||||
estimatesTableState,
|
estimatesTableState,
|
||||||
|
|
||||||
|
// #withEstimatesActions
|
||||||
|
setEstimatesTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the estimates table state once the page unmount.
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
|
setEstimatesTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setEstimatesTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EstimatesListProvider
|
<EstimatesListProvider
|
||||||
query={transformTableStateToQuery(estimatesTableState)}
|
query={transformTableStateToQuery(estimatesTableState)}
|
||||||
@@ -41,4 +57,5 @@ function EstimatesList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withEstimates(({ estimatesTableState }) => ({ estimatesTableState })),
|
withEstimates(({ estimatesTableState }) => ({ estimatesTableState })),
|
||||||
|
withEstimatesActions
|
||||||
)(EstimatesList);
|
)(EstimatesList);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useResourceFields, useEstimates } from 'hooks/query';
|
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { useResourceViews, useResourceMeta, useEstimates } from 'hooks/query';
|
||||||
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const EstimatesListContext = createContext();
|
const EstimatesListContext = createContext();
|
||||||
|
|
||||||
@@ -10,17 +12,17 @@ const EstimatesListContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function EstimatesListProvider({ query, ...props }) {
|
function EstimatesListProvider({ query, ...props }) {
|
||||||
// Fetches estimates resource views and fields.
|
// Fetches estimates resource views and fields.
|
||||||
const { data: estimatesViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: estimatesViews, isLoading: isViewsLoading } =
|
||||||
'sale_estimates',
|
useResourceViews('sale_estimates');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches the estimates resource fields.
|
// Fetches the estimates resource fields.
|
||||||
const {
|
const {
|
||||||
data: estimatesFields,
|
data: resourceMeta,
|
||||||
isLoading: isFieldsLoading,
|
isLoading: isResourceLoading,
|
||||||
} = useResourceFields('sale_estimates');
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('sale_estimates');
|
||||||
|
|
||||||
// Fetch estimates list according to the given custom view id.
|
// Fetches estimates list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
data: { estimates, pagination, filterMeta },
|
data: { estimates, pagination, filterMeta },
|
||||||
isLoading: isEstimatesLoading,
|
isLoading: isEstimatesLoading,
|
||||||
@@ -39,12 +41,15 @@ function EstimatesListProvider({ query, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
estimates,
|
estimates,
|
||||||
pagination,
|
pagination,
|
||||||
estimatesFields,
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
estimatesViews,
|
estimatesViews,
|
||||||
|
|
||||||
|
isResourceLoading,
|
||||||
|
isResourceFetching,
|
||||||
|
|
||||||
isEstimatesLoading,
|
isEstimatesLoading,
|
||||||
isEstimatesFetching,
|
isEstimatesFetching,
|
||||||
isFieldsLoading,
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
|
|
||||||
isEmptyStatus,
|
isEmptyStatus,
|
||||||
@@ -52,7 +57,7 @@ function EstimatesListProvider({ query, ...props }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceLoading}
|
||||||
name={'sale_estimate'}
|
name={'sale_estimate'}
|
||||||
>
|
>
|
||||||
<EstimatesListContext.Provider value={provider} {...props} />
|
<EstimatesListContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import {
|
||||||
import intl from 'react-intl-universal';
|
FormattedMessage as T,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
DashboardFilterButton,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
@@ -23,7 +21,9 @@ import { If, DashboardActionViewsList } from 'components';
|
|||||||
|
|
||||||
import { useRefreshInvoices } from 'hooks/query/invoices';
|
import { useRefreshInvoices } from 'hooks/query/invoices';
|
||||||
import { useInvoicesListContext } from './InvoicesListProvider';
|
import { useInvoicesListContext } from './InvoicesListProvider';
|
||||||
|
|
||||||
import withInvoiceActions from './withInvoiceActions';
|
import withInvoiceActions from './withInvoiceActions';
|
||||||
|
import withInvoices from './withInvoices';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -33,13 +33,14 @@ import { compose } from 'utils';
|
|||||||
function InvoiceActionsBar({
|
function InvoiceActionsBar({
|
||||||
// #withInvoiceActions
|
// #withInvoiceActions
|
||||||
setInvoicesTableState,
|
setInvoicesTableState,
|
||||||
|
|
||||||
|
// #withInvoices
|
||||||
|
invoicesFilterRoles,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
|
||||||
|
|
||||||
// Sale invoices list context.
|
// Sale invoices list context.
|
||||||
const { invoicesViews } = useInvoicesListContext();
|
const { invoicesViews, invoicesFields } = useInvoicesListContext();
|
||||||
|
|
||||||
// Handle new invoice button click.
|
// Handle new invoice button click.
|
||||||
const handleClickNewInvoice = () => {
|
const handleClickNewInvoice = () => {
|
||||||
@@ -74,31 +75,27 @@ function InvoiceActionsBar({
|
|||||||
text={<T id={'new_invoice'} />}
|
text={<T id={'new_invoice'} />}
|
||||||
onClick={handleClickNewInvoice}
|
onClick={handleClickNewInvoice}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: invoicesFilterRoles,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'invoice_no',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: invoicesFields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setInvoicesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton conditionsCount={invoicesFilterRoles.length} />
|
||||||
className={classNames(Classes.MINIMAL)}
|
</AdvancedFilterPopover>
|
||||||
text={
|
|
||||||
filterCount <= 0 ? (
|
<NavbarDivider />
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
`${filterCount} ${intl.get('filters_applied')}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<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}
|
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<Button
|
<Button
|
||||||
@@ -128,4 +125,9 @@ function InvoiceActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withInvoiceActions)(InvoiceActionsBar);
|
export default compose(
|
||||||
|
withInvoiceActions,
|
||||||
|
withInvoices(({ invoicesTableState }) => ({
|
||||||
|
invoicesFilterRoles: invoicesTableState.filterRoles,
|
||||||
|
})),
|
||||||
|
)(InvoiceActionsBar);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import InvoicesDataTable from './InvoicesDataTable';
|
|||||||
import InvoicesAlerts from '../InvoicesAlerts';
|
import InvoicesAlerts from '../InvoicesAlerts';
|
||||||
|
|
||||||
import withInvoices from './withInvoices';
|
import withInvoices from './withInvoices';
|
||||||
|
import withInvoiceActions from './withInvoiceActions';
|
||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
import { transformTableStateToQuery, compose } from 'utils';
|
import { transformTableStateToQuery, compose } from 'utils';
|
||||||
@@ -21,7 +22,22 @@ import { transformTableStateToQuery, compose } from 'utils';
|
|||||||
function InvoicesList({
|
function InvoicesList({
|
||||||
// #withInvoice
|
// #withInvoice
|
||||||
invoicesTableState,
|
invoicesTableState,
|
||||||
|
|
||||||
|
// #withInvoicesActions
|
||||||
|
setInvoicesTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the invoices table state once the page unmount.
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
|
setInvoicesTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setInvoicesTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InvoicesListProvider
|
<InvoicesListProvider
|
||||||
query={transformTableStateToQuery(invoicesTableState)}
|
query={transformTableStateToQuery(invoicesTableState)}
|
||||||
@@ -43,5 +59,6 @@ function InvoicesList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withInvoices(({ invoicesTableState }) => ({ invoicesTableState })),
|
withInvoices(({ invoicesTableState }) => ({ invoicesTableState })),
|
||||||
|
withInvoiceActions,
|
||||||
withAlertsActions,
|
withAlertsActions,
|
||||||
)(InvoicesList);
|
)(InvoicesList);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useResourceFields, useInvoices } from 'hooks/query';
|
import { useResourceViews, useResourceMeta, useInvoices } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const InvoicesListContext = createContext();
|
const InvoicesListContext = createContext();
|
||||||
|
|
||||||
@@ -10,15 +10,15 @@ const InvoicesListContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function InvoicesListProvider({ query, ...props }) {
|
function InvoicesListProvider({ query, ...props }) {
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const { data: invoicesViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: invoicesViews, isLoading: isViewsLoading } =
|
||||||
'sale_invoices',
|
useResourceViews('sale_invoices');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: invoicesFields,
|
data: resourceMeta,
|
||||||
isLoading: isFieldsLoading,
|
isLoading: isResourceLoading,
|
||||||
} = useResourceFields('sale_invoices');
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('sale_invoices');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
@@ -39,20 +39,22 @@ function InvoicesListProvider({ query, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
invoices,
|
invoices,
|
||||||
pagination,
|
pagination,
|
||||||
invoicesFields,
|
|
||||||
|
invoicesFields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
invoicesViews,
|
invoicesViews,
|
||||||
|
|
||||||
isInvoicesLoading,
|
isInvoicesLoading,
|
||||||
isInvoicesFetching,
|
isInvoicesFetching,
|
||||||
isFieldsLoading,
|
isResourceFetching,
|
||||||
|
isResourceLoading,
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
|
|
||||||
isEmptyStatus
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceLoading}
|
||||||
name={'sales-invoices-list'}
|
name={'sales-invoices-list'}
|
||||||
>
|
>
|
||||||
<InvoicesListContext.Provider value={provider} {...props} />
|
<InvoicesListContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React, { createContext, useContext } from 'react';
|
|||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import {
|
import {
|
||||||
useResourceViews,
|
useResourceViews,
|
||||||
useResourceFields,
|
useResourceMeta,
|
||||||
usePaymentReceives,
|
usePaymentReceives,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const PaymentReceivesListContext = createContext();
|
const PaymentReceivesListContext = createContext();
|
||||||
|
|
||||||
@@ -21,9 +21,10 @@ function PaymentReceivesListProvider({ query, ...props }) {
|
|||||||
|
|
||||||
// Fetch the payment receives resource fields.
|
// Fetch the payment receives resource fields.
|
||||||
const {
|
const {
|
||||||
data: paymentReceivesFields,
|
data: resourceMeta,
|
||||||
isFetching: isFieldsLoading,
|
isLoading: isResourceLoading,
|
||||||
} = useResourceFields('payment_receives');
|
isFetching: isResourceFetching,
|
||||||
|
} = useResourceMeta('payment_receives');
|
||||||
|
|
||||||
// Fetch payment receives list according to the given custom view id.
|
// Fetch payment receives list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
@@ -44,19 +45,23 @@ function PaymentReceivesListProvider({ query, ...props }) {
|
|||||||
const state = {
|
const state = {
|
||||||
paymentReceives,
|
paymentReceives,
|
||||||
pagination,
|
pagination,
|
||||||
paymentReceivesFields,
|
|
||||||
|
resourceMeta,
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
|
||||||
paymentReceivesViews,
|
paymentReceivesViews,
|
||||||
|
|
||||||
isPaymentReceivesLoading,
|
isPaymentReceivesLoading,
|
||||||
isPaymentReceivesFetching,
|
isPaymentReceivesFetching,
|
||||||
isFieldsLoading,
|
isResourceFetching,
|
||||||
|
isResourceLoading,
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isEmptyStatus,
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceLoading}
|
||||||
name={'payment-receives-list'}
|
name={'payment-receives-list'}
|
||||||
>
|
>
|
||||||
<PaymentReceivesListContext.Provider value={state} {...props} />
|
<PaymentReceivesListContext.Provider value={state} {...props} />
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import Icon from 'components/Icon';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import {
|
||||||
|
DashboardFilterButton,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
@@ -33,12 +33,15 @@ import { useRefreshPaymentReceive } from 'hooks/query/paymentReceives';
|
|||||||
function PaymentReceiveActionsBar({
|
function PaymentReceiveActionsBar({
|
||||||
// #withPaymentReceivesActions
|
// #withPaymentReceivesActions
|
||||||
setPaymentReceivesTableState,
|
setPaymentReceivesTableState,
|
||||||
|
|
||||||
|
// #withPaymentReceives
|
||||||
|
paymentFilterConditions
|
||||||
}) {
|
}) {
|
||||||
// History context.
|
// History context.
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Payment receives list context.
|
// Payment receives list context.
|
||||||
const { paymentReceivesViews } = usePaymentReceivesListContext();
|
const { paymentReceivesViews, fields } = usePaymentReceivesListContext();
|
||||||
|
|
||||||
// Handle new payment button click.
|
// Handle new payment button click.
|
||||||
const handleClickNewPaymentReceive = () => {
|
const handleClickNewPaymentReceive = () => {
|
||||||
@@ -58,6 +61,8 @@ function PaymentReceiveActionsBar({
|
|||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(fields, 'fields');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -73,18 +78,21 @@ function PaymentReceiveActionsBar({
|
|||||||
text={<T id={'new_payment_receive'} />}
|
text={<T id={'new_payment_receive'} />}
|
||||||
onClick={handleClickNewPaymentReceive}
|
onClick={handleClickNewPaymentReceive}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: paymentFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'payment_receive_no',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setPaymentReceivesTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL)}
|
conditionsCount={paymentFilterConditions.length}
|
||||||
text={<T id={'filter'} />}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -125,5 +133,6 @@ export default compose(
|
|||||||
withPaymentReceivesActions,
|
withPaymentReceivesActions,
|
||||||
withPaymentReceives(({ paymentReceivesTableState }) => ({
|
withPaymentReceives(({ paymentReceivesTableState }) => ({
|
||||||
paymentReceivesTableState,
|
paymentReceivesTableState,
|
||||||
|
paymentFilterConditions: paymentReceivesTableState.filterRoles,
|
||||||
})),
|
})),
|
||||||
)(PaymentReceiveActionsBar);
|
)(PaymentReceiveActionsBar);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import PaymentReceiveViewTabs from './PaymentReceiveViewTabs';
|
|||||||
import PaymentReceivesTable from './PaymentReceivesTable';
|
import PaymentReceivesTable from './PaymentReceivesTable';
|
||||||
|
|
||||||
import withPaymentReceives from './withPaymentReceives';
|
import withPaymentReceives from './withPaymentReceives';
|
||||||
|
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||||
|
|
||||||
import { compose, transformTableStateToQuery } from 'utils';
|
import { compose, transformTableStateToQuery } from 'utils';
|
||||||
|
|
||||||
@@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils';
|
|||||||
function PaymentReceiveList({
|
function PaymentReceiveList({
|
||||||
// #withPaymentReceives
|
// #withPaymentReceives
|
||||||
paymentReceivesTableState,
|
paymentReceivesTableState,
|
||||||
|
|
||||||
|
// #withPaymentReceivesActions
|
||||||
|
setPaymentReceivesTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the payment receives table state once the page unmount.
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
|
setPaymentReceivesTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setPaymentReceivesTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaymentReceivesListProvider
|
<PaymentReceivesListProvider
|
||||||
query={transformTableStateToQuery(paymentReceivesTableState)}
|
query={transformTableStateToQuery(paymentReceivesTableState)}
|
||||||
@@ -43,4 +59,5 @@ export default compose(
|
|||||||
withPaymentReceives(({ paymentReceivesTableState }) => ({
|
withPaymentReceives(({ paymentReceivesTableState }) => ({
|
||||||
paymentReceivesTableState,
|
paymentReceivesTableState,
|
||||||
})),
|
})),
|
||||||
|
withPaymentReceivesActions,
|
||||||
)(PaymentReceiveList);
|
)(PaymentReceiveList);
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import React, { createContext } from 'react';
|
|||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import {
|
import {
|
||||||
useResourceViews,
|
useResourceViews,
|
||||||
useResourceFields,
|
useResourceMeta,
|
||||||
usePaymentReceives,
|
usePaymentReceives,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
|
import { getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const PaymentReceivesListContext = createContext();
|
const PaymentReceivesListContext = createContext();
|
||||||
|
|
||||||
@@ -20,9 +21,10 @@ function PaymentReceivesListProvider({ query, ...props }) {
|
|||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch the accounts resource fields.
|
||||||
const {
|
const {
|
||||||
data: paymentReceivesFields,
|
data: resourceMeta,
|
||||||
isFetching: isFieldsLoading,
|
isFetching: isResourceFetching,
|
||||||
} = useResourceFields('payment_receives');
|
isLoading: isResourceLoading,
|
||||||
|
} = useResourceMeta('payment_receives');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const {
|
const {
|
||||||
@@ -35,18 +37,21 @@ function PaymentReceivesListProvider({ query, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
paymentReceives,
|
paymentReceives,
|
||||||
paymentReceivesViews,
|
paymentReceivesViews,
|
||||||
paymentReceivesFields,
|
|
||||||
pagination,
|
pagination,
|
||||||
|
resourceMeta,
|
||||||
|
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
isFieldsLoading,
|
isResourceFetching,
|
||||||
|
isResourceLoading,
|
||||||
isPaymentReceivesLoading,
|
isPaymentReceivesLoading,
|
||||||
isPaymentReceivesFetching
|
isPaymentReceivesFetching
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={isViewsLoading || isFieldsLoading}
|
loading={isViewsLoading || isResourceLoading}
|
||||||
name={'payment_receives'}
|
name={'payment_receives'}
|
||||||
>
|
>
|
||||||
<PaymentReceivesListContext.Provider value={provider} {...props} />
|
<PaymentReceivesListContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -3,24 +3,25 @@ import Icon from 'components/Icon';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Popover,
|
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
PopoverInteractionKind,
|
|
||||||
Position,
|
|
||||||
Intent,
|
Intent,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'components';
|
import {
|
||||||
import intl from 'react-intl-universal';
|
AdvancedFilterPopover,
|
||||||
|
DashboardFilterButton,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import { If, DashboardActionViewsList } from 'components';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
import withReceiptsActions from './withReceiptsActions';
|
import withReceiptsActions from './withReceiptsActions';
|
||||||
|
import withReceipts from './withReceipts';
|
||||||
|
|
||||||
import { useReceiptsListContext } from './ReceiptsListProvider';
|
import { useReceiptsListContext } from './ReceiptsListProvider';
|
||||||
import { useRefreshReceipts } from 'hooks/query/receipts';
|
import { useRefreshReceipts } from 'hooks/query/receipts';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -31,13 +32,14 @@ import { compose } from 'utils';
|
|||||||
function ReceiptActionsBar({
|
function ReceiptActionsBar({
|
||||||
// #withReceiptsActions
|
// #withReceiptsActions
|
||||||
setReceiptsTableState,
|
setReceiptsTableState,
|
||||||
|
|
||||||
|
// #withReceipts
|
||||||
|
receiptsFilterConditions,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
|
||||||
|
|
||||||
// Sale receipts list context.
|
// Sale receipts list context.
|
||||||
const { receiptsViews } = useReceiptsListContext();
|
const { receiptsViews, fields } = useReceiptsListContext();
|
||||||
|
|
||||||
// Handle new receipt button click.
|
// Handle new receipt button click.
|
||||||
const onClickNewReceipt = () => {
|
const onClickNewReceipt = () => {
|
||||||
@@ -54,9 +56,9 @@ function ReceiptActionsBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle click a refresh sale estimates
|
// Handle click a refresh sale estimates
|
||||||
const handleRefreshBtnClick = () => {
|
const handleRefreshBtnClick = () => { refresh(); };
|
||||||
refresh();
|
|
||||||
};
|
console.log(receiptsFilterConditions, fields, 'XXX');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -74,24 +76,21 @@ function ReceiptActionsBar({
|
|||||||
text={<T id={'new_receipt'} />}
|
text={<T id={'new_receipt'} />}
|
||||||
onClick={onClickNewReceipt}
|
onClick={onClickNewReceipt}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
minimal={true}
|
advancedFilterProps={{
|
||||||
// content={filterDropdown}
|
conditions: receiptsFilterConditions,
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
defaultFieldKey: 'reference_no',
|
||||||
position={Position.BOTTOM_LEFT}
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setReceiptsTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
conditionsCount={receiptsFilterConditions.length}
|
||||||
text={
|
|
||||||
filterCount <= 0 ? (
|
|
||||||
<T id={'filter'} />
|
|
||||||
) : (
|
|
||||||
`${filterCount} ${intl.get('filters_applied')}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -128,4 +127,9 @@ function ReceiptActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withReceiptsActions)(ReceiptActionsBar);
|
export default compose(
|
||||||
|
withReceiptsActions,
|
||||||
|
withReceipts(({ receiptTableState }) => ({
|
||||||
|
receiptsFilterConditions: receiptTableState.filterRoles,
|
||||||
|
})),
|
||||||
|
)(ReceiptActionsBar);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ReceiptsAlerts from '../ReceiptsAlerts';
|
|||||||
import ReceiptsTable from './ReceiptsTable';
|
import ReceiptsTable from './ReceiptsTable';
|
||||||
|
|
||||||
import withReceipts from './withReceipts';
|
import withReceipts from './withReceipts';
|
||||||
|
import withReceiptsActions from './withReceiptsActions';
|
||||||
|
|
||||||
import { ReceiptsListProvider } from './ReceiptsListProvider';
|
import { ReceiptsListProvider } from './ReceiptsListProvider';
|
||||||
import { transformTableStateToQuery, compose } from 'utils';
|
import { transformTableStateToQuery, compose } from 'utils';
|
||||||
@@ -19,7 +20,22 @@ import { transformTableStateToQuery, compose } from 'utils';
|
|||||||
function ReceiptsList({
|
function ReceiptsList({
|
||||||
// #withReceipts
|
// #withReceipts
|
||||||
receiptTableState,
|
receiptTableState,
|
||||||
|
|
||||||
|
// #withReceiptsActions
|
||||||
|
setReceiptsTableState,
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the receipts table state once the page unmount.
|
||||||
|
React.useEffect(
|
||||||
|
() => () => {
|
||||||
|
setReceiptsTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setReceiptsTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReceiptsListProvider query={transformTableStateToQuery(receiptTableState)}>
|
<ReceiptsListProvider query={transformTableStateToQuery(receiptTableState)}>
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
@@ -43,4 +59,5 @@ export default compose(
|
|||||||
withReceipts(({ receiptTableState }) => ({
|
withReceipts(({ receiptTableState }) => ({
|
||||||
receiptTableState,
|
receiptTableState,
|
||||||
})),
|
})),
|
||||||
|
withReceiptsActions,
|
||||||
)(ReceiptsList);
|
)(ReceiptsList);
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useReceipts } from 'hooks/query';
|
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { useResourceMeta, useResourceViews, useReceipts } from 'hooks/query';
|
||||||
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
|
|
||||||
const ReceiptsListContext = createContext();
|
const ReceiptsListContext = createContext();
|
||||||
|
|
||||||
// Receipts list provider.
|
// Receipts list provider.
|
||||||
function ReceiptsListProvider({ query, ...props }) {
|
function ReceiptsListProvider({ query, ...props }) {
|
||||||
// Fetch receipts resource views and fields.
|
// Fetch receipts resource views and fields.
|
||||||
const { data: receiptsViews, isLoading: isViewsLoading } = useResourceViews(
|
const { data: receiptsViews, isLoading: isViewsLoading } =
|
||||||
'sale_receipt',
|
useResourceViews('sale_receipt');
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches the sale receipts resource fields.
|
// Fetches the sale receipts resource fields.
|
||||||
// const {
|
const {
|
||||||
// data: receiptsFields,
|
data: resourceMeta,
|
||||||
// isFetching: isFieldsLoading,
|
isFetching: isResourceFetching,
|
||||||
// } = useResourceFields('sale_receipt');
|
isLoading: isResourceLoading,
|
||||||
|
} = useResourceMeta('sale_receipt');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { receipts, pagination, filterMeta },
|
data: { receipts, pagination, filterMeta },
|
||||||
isLoading: isReceiptsLoading,
|
isLoading: isReceiptsLoading,
|
||||||
isFetching: isReceiptsFetching,
|
isFetching: isReceiptsFetching,
|
||||||
} = useReceipts(query, { keepPreviousData: true });
|
} = useReceipts(query, { keepPreviousData: true });
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus =
|
const isEmptyStatus =
|
||||||
isTableEmptyStatus({
|
isTableEmptyStatus({
|
||||||
@@ -35,20 +36,22 @@ function ReceiptsListProvider({ query, ...props }) {
|
|||||||
const provider = {
|
const provider = {
|
||||||
receipts,
|
receipts,
|
||||||
pagination,
|
pagination,
|
||||||
// receiptsFields,
|
|
||||||
receiptsViews,
|
receiptsViews,
|
||||||
isViewsLoading,
|
isViewsLoading,
|
||||||
// isFieldsLoading,
|
|
||||||
|
resourceMeta,
|
||||||
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
|
isResourceFetching,
|
||||||
|
isResourceLoading,
|
||||||
|
|
||||||
isReceiptsLoading,
|
isReceiptsLoading,
|
||||||
isReceiptsFetching,
|
isReceiptsFetching,
|
||||||
isEmptyStatus
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider loading={isViewsLoading} name={'sales_receipts'}>
|
||||||
loading={isViewsLoading}
|
|
||||||
name={'sales_receipts'}
|
|
||||||
>
|
|
||||||
<ReceiptsListContext.Provider value={provider} {...props} />
|
<ReceiptsListContext.Provider value={provider} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Classes,
|
Classes,
|
||||||
Intent,
|
Intent,
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
PopoverInteractionKind,
|
|
||||||
Switch,
|
Switch,
|
||||||
Alignment,
|
Alignment,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import {
|
||||||
|
If,
|
||||||
|
DashboardActionViewsList,
|
||||||
|
DashboardFilterButton,
|
||||||
|
AdvancedFilterPopover,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useRefreshVendors } from 'hooks/query/vendors';
|
import { useRefreshVendors } from 'hooks/query/vendors';
|
||||||
import { useVendorsListContext } from './VendorsListProvider';
|
import { useVendorsListContext } from './VendorsListProvider';
|
||||||
@@ -32,6 +32,9 @@ import { compose } from 'utils';
|
|||||||
* Vendors actions bar.
|
* Vendors actions bar.
|
||||||
*/
|
*/
|
||||||
function VendorActionsBar({
|
function VendorActionsBar({
|
||||||
|
// #withVendors
|
||||||
|
vendorsFilterConditions,
|
||||||
|
|
||||||
// #withVendorActions
|
// #withVendorActions
|
||||||
setVendorsTableState,
|
setVendorsTableState,
|
||||||
vendorsInactiveMode,
|
vendorsInactiveMode,
|
||||||
@@ -39,7 +42,7 @@ function VendorActionsBar({
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Vendors list context.
|
// Vendors list context.
|
||||||
const { vendorsViews } = useVendorsListContext();
|
const { vendorsViews, fields } = useVendorsListContext();
|
||||||
|
|
||||||
// Handles new vendor button click.
|
// Handles new vendor button click.
|
||||||
const onClickNewVendor = () => {
|
const onClickNewVendor = () => {
|
||||||
@@ -81,19 +84,21 @@ function VendorActionsBar({
|
|||||||
onClick={onClickNewVendor}
|
onClick={onClickNewVendor}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
<Popover
|
<AdvancedFilterPopover
|
||||||
// content={}
|
advancedFilterProps={{
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
conditions: vendorsFilterConditions,
|
||||||
position={Position.BOTTOM_LEFT}
|
defaultFieldKey: 'display_name',
|
||||||
|
fields: fields,
|
||||||
|
onFilterChange: (filterConditions) => {
|
||||||
|
setVendorsTableState({ filterRoles: filterConditions });
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<DashboardFilterButton
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
conditionsCount={vendorsFilterConditions.length}
|
||||||
text={
|
|
||||||
true ? <T id={'filter'} /> : `${9} ${intl.get('filters_applied')}`
|
|
||||||
}
|
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
@@ -133,5 +138,6 @@ export default compose(
|
|||||||
withVendorsActions,
|
withVendorsActions,
|
||||||
withVendors(({ vendorsTableState }) => ({
|
withVendors(({ vendorsTableState }) => ({
|
||||||
vendorsInactiveMode: vendorsTableState.inactiveMode,
|
vendorsInactiveMode: vendorsTableState.inactiveMode,
|
||||||
|
vendorsFilterConditions: vendorsTableState.filterRoles,
|
||||||
})),
|
})),
|
||||||
)(VendorActionsBar);
|
)(VendorActionsBar);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import 'style/pages/Vendors/List.scss';
|
import 'style/pages/Vendors/List.scss';
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ import VendorsAlerts from '../VendorsAlerts';
|
|||||||
import VendorsTable from './VendorsTable';
|
import VendorsTable from './VendorsTable';
|
||||||
|
|
||||||
import withVendors from './withVendors';
|
import withVendors from './withVendors';
|
||||||
|
import withVendorsActions from './withVendorsActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -20,7 +21,22 @@ import { compose } from 'utils';
|
|||||||
function VendorsList({
|
function VendorsList({
|
||||||
// #withVendors
|
// #withVendors
|
||||||
vendorsTableState,
|
vendorsTableState,
|
||||||
|
|
||||||
|
// #withVendorsActions
|
||||||
|
setVendorsTableState
|
||||||
}) {
|
}) {
|
||||||
|
// Resets the vendors table state once the page unmount.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
setVendorsTableState({
|
||||||
|
filterRoles: [],
|
||||||
|
viewSlug: '',
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setVendorsTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VendorsListProvider tableState={vendorsTableState}>
|
<VendorsListProvider tableState={vendorsTableState}>
|
||||||
<VendorActionsBar />
|
<VendorActionsBar />
|
||||||
@@ -40,4 +56,5 @@ function VendorsList({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withVendors(({ vendorsTableState }) => ({ vendorsTableState })),
|
withVendors(({ vendorsTableState }) => ({ vendorsTableState })),
|
||||||
|
withVendorsActions
|
||||||
)(VendorsList);
|
)(VendorsList);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { useResourceViews, useVendors } from 'hooks/query';
|
import { useResourceMeta, useResourceViews, useVendors } from 'hooks/query';
|
||||||
import { isTableEmptyStatus } from 'utils';
|
import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils';
|
||||||
import { transformVendorsStateToQuery } from './utils';
|
import { transformVendorsStateToQuery } from './utils';
|
||||||
|
|
||||||
const VendorsListContext = createContext();
|
const VendorsListContext = createContext();
|
||||||
@@ -22,6 +22,13 @@ function VendorsListProvider({ tableState, ...props }) {
|
|||||||
const { data: vendorsViews, isLoading: isVendorsViewsLoading } =
|
const { data: vendorsViews, isLoading: isVendorsViewsLoading } =
|
||||||
useResourceViews('vendors');
|
useResourceViews('vendors');
|
||||||
|
|
||||||
|
// Fetch the customers resource fields.
|
||||||
|
const {
|
||||||
|
data: resourceMeta,
|
||||||
|
isLoading: isResourceMetaLoading,
|
||||||
|
isFetching: isResourceMetaFetching,
|
||||||
|
} = useResourceMeta('customers');
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus =
|
const isEmptyStatus =
|
||||||
isTableEmptyStatus({
|
isTableEmptyStatus({
|
||||||
@@ -37,15 +44,23 @@ function VendorsListProvider({ tableState, ...props }) {
|
|||||||
pagination,
|
pagination,
|
||||||
vendorsViews,
|
vendorsViews,
|
||||||
|
|
||||||
isVendorsLoading,
|
fields: getFieldsFromResourceMeta(resourceMeta.fields),
|
||||||
isVendorsFetching,
|
resourceMeta,
|
||||||
|
isResourceMetaLoading,
|
||||||
|
isResourceMetaFetching,
|
||||||
|
|
||||||
isVendorsViewsLoading,
|
isVendorsViewsLoading,
|
||||||
|
|
||||||
|
isVendorsLoading,
|
||||||
|
isVendorsFetching,
|
||||||
isEmptyStatus,
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={isVendorsViewsLoading} name={'vendors-list'}>
|
<DashboardInsider
|
||||||
|
loading={isResourceMetaLoading || isVendorsLoading}
|
||||||
|
name={'vendors-list'}
|
||||||
|
>
|
||||||
<VendorsListContext.Provider value={provider} {...props} />
|
<VendorsListContext.Provider value={provider} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,33 +14,21 @@ export function useResourceViews(resourceSlug) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the resource columns.
|
|
||||||
* @param {string} resourceSlug - Resource slug.
|
|
||||||
*/
|
|
||||||
export function useResourceColumns(resourceSlug) {
|
|
||||||
return useRequestQuery(
|
|
||||||
['RESOURCE_COLUMNS', resourceSlug],
|
|
||||||
{ method: 'get', url: `resources/${resourceSlug}/columns` },
|
|
||||||
{
|
|
||||||
defaultData: [],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the resource fields.
|
* Retrieve the resource meta.
|
||||||
* @param {string} resourceSlug - Resource slug.
|
* @param {string} resourceSlug - Resource slug.
|
||||||
*/
|
*/
|
||||||
export function useResourceFields(resourceSlug, props) {
|
export function useResourceMeta(resourceSlug, props) {
|
||||||
return useRequestQuery(
|
return useRequestQuery(
|
||||||
['RESOURCE_FIELDS', resourceSlug],
|
['RESOURCE_META', resourceSlug],
|
||||||
{ method: 'get', url: `resources/${resourceSlug}/fields` },
|
{ method: 'get', url: `resources/${resourceSlug}/meta` },
|
||||||
{
|
{
|
||||||
select: (res) => res.data.resource_fields,
|
select: (res) => res.data.resource_meta,
|
||||||
defaultData: [],
|
defaultData: {
|
||||||
|
fields: {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
props
|
props,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,12 @@ import {
|
|||||||
useSetGlobalErrors,
|
useSetGlobalErrors,
|
||||||
useAuthToken,
|
useAuthToken,
|
||||||
} from './state';
|
} from './state';
|
||||||
|
import { useAppIntlContext } from '../components/AppIntlProvider';
|
||||||
|
|
||||||
export default function useApiRequest() {
|
export default function useApiRequest() {
|
||||||
const setGlobalErrors = useSetGlobalErrors();
|
const setGlobalErrors = useSetGlobalErrors();
|
||||||
const { setLogout } = useAuthActions();
|
const { setLogout } = useAuthActions();
|
||||||
|
const { currentLocale } = useAppIntlContext();
|
||||||
|
|
||||||
// Authentication token.
|
// Authentication token.
|
||||||
const token = useAuthToken();
|
const token = useAuthToken();
|
||||||
@@ -24,7 +26,7 @@ export default function useApiRequest() {
|
|||||||
// Request interceptors.
|
// Request interceptors.
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(request) => {
|
(request) => {
|
||||||
const locale = 'ar';
|
const locale = currentLocale;
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
request.headers.common['X-Access-Token'] = token;
|
request.headers.common['X-Access-Token'] = token;
|
||||||
|
|||||||
@@ -435,7 +435,7 @@
|
|||||||
"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_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?",
|
||||||
"after": "After",
|
"after": "After",
|
||||||
"before": "Before",
|
"before": "Before",
|
||||||
"count_filters_applied": "{count} filters applied",
|
"count_filters_applied": "{count} Filters applied",
|
||||||
"is": "Is",
|
"is": "Is",
|
||||||
"is_not": "Is Not",
|
"is_not": "Is Not",
|
||||||
"create_a_new_view": "Create a new view",
|
"create_a_new_view": "Create a new view",
|
||||||
@@ -1194,5 +1194,8 @@
|
|||||||
"are_sure_to_activate_this_contact": "Are you sure you want to activate this contact ? You will be able to inactivate it later",
|
"are_sure_to_activate_this_contact": "Are you sure you want to activate this contact ? You will be able to inactivate it later",
|
||||||
"publish_adjustment": "Publish adjustment",
|
"publish_adjustment": "Publish adjustment",
|
||||||
"inactivate_customer": "Inactivate customer",
|
"inactivate_customer": "Inactivate customer",
|
||||||
"activate_customer": "Activate customer"
|
"activate_customer": "Activate customer",
|
||||||
|
"filter.all_filters_must_match": "Atleast one filter must match",
|
||||||
|
"filter.atleast_one_filter_must_match": "Atleast one filter must match",
|
||||||
|
"filter.when": "When"
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: []
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:bills';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:estimates';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: []
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:invoices';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
sortBy: [],
|
sortBy: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:paymentReceives';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ import { createTableStateReducers } from 'store/tableState.reducer';
|
|||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: {},
|
tableState: {
|
||||||
|
pageSize: 12,
|
||||||
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:accounts';
|
const STORAGE_KEY = 'bigcapital:accounts';
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const initialState = {
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
|
filterRoles: []
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ const STORAGE_KEY = 'bigcapital:estimates';
|
|||||||
export default persistReducer(
|
export default persistReducer(
|
||||||
{
|
{
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
},
|
},
|
||||||
reducerInstance,
|
reducerInstance,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:expenses';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import t from 'store/types';
|
|||||||
|
|
||||||
// Initial state.
|
// Initial state.
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: {},
|
tableState: {
|
||||||
|
filterRoles: []
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:itemCategories';
|
const STORAGE_KEY = 'bigcapital:itemCategories';
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import t from 'store/types';
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
tableState: {
|
||||||
|
filterRoles: [],
|
||||||
|
},
|
||||||
categories: {},
|
categories: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
filters: [],
|
filterRoles: [],
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
},
|
},
|
||||||
selectedRows: [],
|
selectedRows: [],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:manualJournals';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const initialState = {
|
|||||||
tableState: {
|
tableState: {
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
|
filterRoles: []
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:receipts';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
3
client/src/store/vendors/vendors.reducer.js
vendored
3
client/src/store/vendors/vendors.reducer.js
vendored
@@ -9,6 +9,7 @@ const initialState = {
|
|||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
|
filterRoles: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ const STORAGE_KEY = 'bigcapital:vendors';
|
|||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
key: STORAGE_KEY,
|
key: STORAGE_KEY,
|
||||||
whitelist: ['tableState'],
|
whitelist: [],
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-button:not([class*='bp3-intent-']) {
|
||||||
|
color: #33304a;
|
||||||
|
}
|
||||||
|
|
||||||
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
|
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
|
||||||
color: #555555;
|
color: #555555;
|
||||||
box-shadow: 0 0 0 transparent;
|
box-shadow: 0 0 0 transparent;
|
||||||
|
|||||||
@@ -1,121 +1,115 @@
|
|||||||
|
|
||||||
.filter-dropdown{
|
.filter-dropdown{
|
||||||
width: 550px;
|
width: 600px;
|
||||||
|
|
||||||
&__body{
|
&__form{
|
||||||
padding: 12px;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&__conditions-wrap{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__conditions{
|
||||||
|
padding: 6px 0
|
||||||
}
|
}
|
||||||
|
|
||||||
&__condition{
|
&__condition{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
padding: 6px 12px;
|
||||||
|
|
||||||
&:not(:first-of-type) {
|
|
||||||
padding-top: 8px;
|
|
||||||
border-top: 1px solid #e6e6e6;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-form-group{
|
|
||||||
padding-right: 4px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
&:not(:last-of-type) {
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-html-select select,
|
|
||||||
.bp3-select select{
|
|
||||||
padding: 0 15px 0 4px;
|
|
||||||
|
|
||||||
&:after{
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp3-input{
|
.bp3-input{
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
}
|
}
|
||||||
.bp3-html-select select,
|
.form-group--select-list .bp3-popover-target .bp3-button{
|
||||||
.bp3-select select,
|
padding-left: 6px;
|
||||||
.bp3-input-group .bp3-input{
|
}
|
||||||
height: 32px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border-color: #dbd8d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
margin-right: 6px;
|
||||||
margin-right: 8px;
|
border-top-color: #c5c5c5;
|
||||||
|
}
|
||||||
|
.bp3-html-select,
|
||||||
|
.bp3-input,
|
||||||
|
.form-group--select-list .bp3-button{
|
||||||
|
|
||||||
|
&:not(:focus){
|
||||||
|
border-color: #d3d9de;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--select-list .bp3-button{
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
.bp3-form-group{
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
.bp3-control.bp3-checkbox{
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-popover-wrapper{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-button{
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: visible;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-group--condition{
|
||||||
|
width: 65px;
|
||||||
|
|
||||||
|
input[disabled] {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-group--comparator{
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.form-group--fieldKey{
|
||||||
|
width: 145px;
|
||||||
|
}
|
||||||
|
.form-group--value{
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--remove{
|
||||||
|
margin-left: -6px;
|
||||||
|
|
||||||
|
.bp3-icon{
|
||||||
|
color: #929aa0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer{
|
&__footer{
|
||||||
border-top: 1px solid #e8e8e8;
|
padding: 10px 12px;
|
||||||
padding: 5px 10px;
|
margin-top: -6px;
|
||||||
}
|
|
||||||
.form-group{
|
|
||||||
&--condition{
|
|
||||||
width: 65px;
|
|
||||||
min-width: 65px;
|
|
||||||
}
|
|
||||||
&--field{
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
&--comparator{
|
|
||||||
min-width: 100px;
|
|
||||||
width: 100px;
|
|
||||||
max-width: 100px;
|
|
||||||
}
|
|
||||||
&--value{
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
.bp3-control{
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 1.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-select--filter-dropdown{
|
.bp3-popover{
|
||||||
|
|
||||||
.bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal),
|
&,
|
||||||
.bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal){
|
& .bp3-popover-content{
|
||||||
&,
|
border-radius: 5px;
|
||||||
&:hover{
|
|
||||||
background-color: #E6EFFB;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after{
|
|
||||||
border-top-color: #afb9d0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover--list-select-filter-dropdown{
|
.bp3-menu-item{
|
||||||
|
.text-hint{
|
||||||
.bp3-popover-content{
|
font-size: 11px;
|
||||||
max-width: 200px;
|
line-height: 1.3;
|
||||||
|
display: block;
|
||||||
|
color: #5c7080;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-menu{
|
|
||||||
max-height: 250px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-input-group{
|
|
||||||
margin: 8px 8px 0;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
|
|
||||||
.bp3-input:not(:first-child){
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-icon-search{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -512,6 +512,10 @@ export function getPagesCountFromPaginationMeta(pagination) {
|
|||||||
return Math.ceil(total / pageSize);
|
return Math.ceil(total / pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transformFilterRoles(filterRoles) {
|
||||||
|
return JSON.stringify(filterRoles);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the table state to url query.
|
* Transformes the table state to url query.
|
||||||
*/
|
*/
|
||||||
@@ -521,6 +525,13 @@ export function transformTableStateToQuery(tableState) {
|
|||||||
const query = {
|
const query = {
|
||||||
pageSize,
|
pageSize,
|
||||||
page: pageIndex + 1,
|
page: pageIndex + 1,
|
||||||
|
...(tableState.filterRoles
|
||||||
|
? {
|
||||||
|
stringified_filter_roles: transformFilterRoles(
|
||||||
|
tableState.filterRoles,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
...(viewSlug ? { viewSlug } : {}),
|
...(viewSlug ? { viewSlug } : {}),
|
||||||
...(Array.isArray(sortBy) && sortBy.length > 0
|
...(Array.isArray(sortBy) && sortBy.length > 0
|
||||||
? {
|
? {
|
||||||
@@ -691,10 +702,14 @@ export function nestedArrayToflatten(
|
|||||||
parseItem = (a, level) => a,
|
parseItem = (a, level) => a,
|
||||||
level = 1,
|
level = 1,
|
||||||
) {
|
) {
|
||||||
const parseObject = (obj) => parseItem({
|
const parseObject = (obj) =>
|
||||||
..._.omit(obj, [property]),
|
parseItem(
|
||||||
level,
|
{
|
||||||
}, level);
|
..._.omit(obj, [property]),
|
||||||
|
level,
|
||||||
|
},
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
|
||||||
return collection.reduce((items, currentValue, index) => {
|
return collection.reduce((items, currentValue, index) => {
|
||||||
let localItems = [...items];
|
let localItems = [...items];
|
||||||
@@ -713,3 +728,27 @@ export function nestedArrayToflatten(
|
|||||||
return localItems;
|
return localItems;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFieldsFromResourceMeta(resourceFields) {
|
||||||
|
const fields = Object.keys(resourceFields)
|
||||||
|
.map((fieldKey) => {
|
||||||
|
const field = resourceFields[fieldKey];
|
||||||
|
return {
|
||||||
|
...transformToCamelCase(field),
|
||||||
|
key: fieldKey,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((field) => field.filterable !== false);
|
||||||
|
|
||||||
|
return _.orderBy(fields, ['label']);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFilterableFieldsFromFields(fields) {
|
||||||
|
return fields.filter((field) => field.filterable !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RESORUCE_TYPE = {
|
||||||
|
ACCOUNTS: 'account',
|
||||||
|
ITEMS: 'items',
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ TENANT_DB_CLIENT=mysql
|
|||||||
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
TENANT_DB_NAME_PERFIX=bigcapital_tenant_
|
||||||
TENANT_DB_HOST=127.0.0.1
|
TENANT_DB_HOST=127.0.0.1
|
||||||
TENANT_DB_PASSWORD=root
|
TENANT_DB_PASSWORD=root
|
||||||
TEANNT_DB_USER=root
|
TENANT_DB_USER=root
|
||||||
TENANT_DB_CHARSET=utf8
|
TENANT_DB_CHARSET=utf8
|
||||||
TENANT_MIGRATIONS_DIR=src/database/migrations
|
TENANT_MIGRATIONS_DIR=src/database/migrations
|
||||||
TENANT_SEEDS_DIR=src/database/seeds/core
|
TENANT_SEEDS_DIR=src/database/seeds/core
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { param, query } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
|
||||||
import BaseController from './BaseController';
|
import BaseController from './BaseController';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import ResourceService from 'services/Resource/ResourceService';
|
import ResourceService from 'services/Resource/ResourceService';
|
||||||
@@ -19,32 +18,15 @@ export default class ResourceController extends BaseController {
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:resource_model/meta',
|
'/:resource_model/meta',
|
||||||
[...this.resourceModelParamSchema],
|
[
|
||||||
|
param('resource_model').exists().trim().escape()
|
||||||
|
],
|
||||||
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:resource_model/fields',
|
|
||||||
[...this.resourceModelParamSchema],
|
|
||||||
this.validationResult,
|
|
||||||
asyncMiddleware(this.resourceFields.bind(this)),
|
|
||||||
this.handleServiceErrors
|
|
||||||
);
|
|
||||||
router.get(
|
|
||||||
'/:resource_model/data',
|
|
||||||
[...this.resourceModelParamSchema],
|
|
||||||
this.validationResult,
|
|
||||||
asyncMiddleware(this.resourceData.bind(this)),
|
|
||||||
this.handleServiceErrors
|
|
||||||
);
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
get resourceModelParamSchema() {
|
|
||||||
return [param('resource_model').exists().trim().escape()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource model meta.
|
* Retrieve resource model meta.
|
||||||
* @param {Request} req -
|
* @param {Request} req -
|
||||||
@@ -52,7 +34,7 @@ export default class ResourceController extends BaseController {
|
|||||||
* @param {NextFunction} next -
|
* @param {NextFunction} next -
|
||||||
* @returns {Response}
|
* @returns {Response}
|
||||||
*/
|
*/
|
||||||
private resourceMeta = (
|
public resourceMeta = (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
@@ -73,56 +55,6 @@ export default class ResourceController extends BaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource fields of the given resource.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
resourceFields(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { resource_model: resourceModel } = req.params;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// const resourceFields = this.resourcesService.getResourceFields(
|
|
||||||
// tenantId,
|
|
||||||
// resourceModel
|
|
||||||
// );
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
resource_fields: [],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource data of the give resource based on the given query.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {NextFunction} next
|
|
||||||
*/
|
|
||||||
async resourceData(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { resource_model: resourceModel } = req.params;
|
|
||||||
const filter = req.query;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resourceData = await this.resourcesService.getResourceData(
|
|
||||||
tenantId,
|
|
||||||
resourceModel,
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
resource_data: this.transfromToResponse(resourceData),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default {
|
|||||||
column: 'slug',
|
column: 'slug',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
columnable: false,
|
columnable: false,
|
||||||
|
filterable: false,
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
name: 'Account code',
|
name: 'Account code',
|
||||||
@@ -76,6 +77,7 @@ export default {
|
|||||||
name: 'Currency',
|
name: 'Currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
|
filterable: false,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
name: 'Created at',
|
name: 'Created at',
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ export default {
|
|||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
columnable: true,
|
columnable: true,
|
||||||
options: [
|
options: [
|
||||||
{ name: 'Paid', key: 'paid' },
|
{ label: 'Paid', key: 'paid' },
|
||||||
{ name: 'Partially paid', key: 'partially-paid' },
|
{ label: 'Partially paid', key: 'partially-paid' },
|
||||||
{ name: 'Overdue', key: 'overdue' },
|
{ label: 'Overdue', key: 'overdue' },
|
||||||
{ name: 'Unpaid', key: 'unpaid' },
|
{ label: 'Unpaid', key: 'unpaid' },
|
||||||
{ name: 'Opened', key: 'opened' },
|
{ label: 'Opened', key: 'opened' },
|
||||||
{ name: 'Draft', key: 'draft' },
|
{ label: 'Draft', key: 'draft' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export default {
|
|||||||
created_at: {
|
created_at: {
|
||||||
name: 'Created at',
|
name: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
name: 'Balance',
|
name: 'Balance',
|
||||||
@@ -61,11 +62,13 @@ export default {
|
|||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
currency_code: {
|
currency_code: {
|
||||||
|
name: 'Curreny',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
label: 'Status',
|
name: 'Status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'active', label: 'Active' },
|
{ key: 'active', label: 'Active' },
|
||||||
{ key: 'inactive', label: 'Inactive' },
|
{ key: 'inactive', label: 'Inactive' },
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export default {
|
|||||||
name: 'Status',
|
name: 'Status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'draft', name: 'Draft' },
|
{ key: 'draft', label: 'Draft' },
|
||||||
{ key: 'published', name: 'Published' },
|
{ key: 'published', label: 'Published' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
label: 'Name',
|
name: 'Name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
name: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
label: 'Count',
|
name: 'Count',
|
||||||
column: 'count',
|
column: 'count',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
virtualColumn: true,
|
virtualColumn: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
name: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,43 +6,47 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
'date': {
|
'date': {
|
||||||
label: 'Date',
|
name: 'Date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
'journal_number': {
|
'journal_number': {
|
||||||
label: 'Journal number',
|
name: 'Journal number',
|
||||||
column: 'journal_number',
|
column: 'journal_number',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'reference': {
|
'reference': {
|
||||||
label: 'Reference No.',
|
name: 'Reference No.',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'journal_type': {
|
'journal_type': {
|
||||||
label: 'Journal type',
|
name: 'Journal type',
|
||||||
column: 'journal_type',
|
column: 'journal_type',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'amount': {
|
'amount': {
|
||||||
label: 'Amount',
|
name: 'Amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
columnType: 'number',
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
'description': {
|
'description': {
|
||||||
label: 'Description',
|
name: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
'status': {
|
||||||
label: 'Status',
|
name: 'Status',
|
||||||
column: 'status',
|
column: 'status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'draft', label: 'Draft' },
|
||||||
|
{ key: 'published', label: 'published' }
|
||||||
|
],
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
},
|
},
|
||||||
'created_at': {
|
'created_at': {
|
||||||
label: 'Created at',
|
name: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export default (Model) =>
|
|||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public static getMeta(key: string) {
|
public static getMeta(key?: string) {
|
||||||
return get(this.meta, key);
|
return key ? get(this.meta, key): this.meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export default {
|
|||||||
name: 'Status',
|
name: 'Status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'Delivered', key: 'delivered' },
|
{ label: 'Delivered', key: 'delivered' },
|
||||||
{ name: 'Rejected', key: 'rejected' },
|
{ label: 'Rejected', key: 'rejected' },
|
||||||
{ name: 'Approved', key: 'approved' },
|
{ label: 'Approved', key: 'approved' },
|
||||||
{ name: 'Delivered', key: 'delivered' },
|
{ label: 'Delivered', key: 'delivered' },
|
||||||
{ name: 'Draft', key: 'draft' },
|
{ label: 'Draft', key: 'draft' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
|
|||||||
@@ -64,15 +64,14 @@ export default {
|
|||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
name: 'Status',
|
name: 'Status',
|
||||||
columnable: true,
|
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'draft', name: 'Draft' },
|
{ key: 'draft', label: 'Draft' },
|
||||||
{ key: 'delivered', name: 'Delivered' },
|
{ key: 'delivered', label: 'Delivered' },
|
||||||
{ key: 'unpaid', name: 'Unpaid' },
|
{ key: 'unpaid', label: 'Unpaid' },
|
||||||
{ key: 'overdue', name: 'Overdue' },
|
{ key: 'overdue', label: 'Overdue' },
|
||||||
{ key: 'partially-paid', name: 'Partially paid' },
|
{ key: 'partially-paid', label: 'Partially paid' },
|
||||||
{ key: 'paid', name: 'Paid' },
|
{ key: 'paid', label: 'Paid' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
@@ -97,4 +96,4 @@ function StatusFieldFilterQuery(query, role) {
|
|||||||
*/
|
*/
|
||||||
function StatusFieldSortQuery(query, role) {
|
function StatusFieldSortQuery(query, role) {
|
||||||
query.modify('sortByStatus', role.order);
|
query.modify('sortByStatus', role.order);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export default {
|
|||||||
relationEntityKey: 'slug',
|
relationEntityKey: 'slug',
|
||||||
},
|
},
|
||||||
'customer': {
|
'customer': {
|
||||||
name: 'Customer',
|
label: 'Customer',
|
||||||
column: 'customer_id',
|
name: 'customer_id',
|
||||||
fieldType: 'relation',
|
fieldType: 'relation',
|
||||||
|
|
||||||
relationType: 'enumeration',
|
relationType: 'enumeration',
|
||||||
@@ -67,8 +67,8 @@ export default {
|
|||||||
name: 'Status',
|
name: 'Status',
|
||||||
fieldType: 'enumeration',
|
fieldType: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'draft', name: 'Draft' },
|
{ key: 'draft', label: 'Draft' },
|
||||||
{ key: 'closed', name: 'Closed' },
|
{ key: 'closed', label: 'Closed' },
|
||||||
],
|
],
|
||||||
filterCustomQuery: StatusFieldFilterQuery,
|
filterCustomQuery: StatusFieldFilterQuery,
|
||||||
sortCustomQuery: StatusFieldSortQuery,
|
sortCustomQuery: StatusFieldSortQuery,
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export default {
|
|||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
'status': {
|
'status': {
|
||||||
label: 'Status',
|
name: 'Status',
|
||||||
|
type: 'enumeration',
|
||||||
options: [
|
options: [
|
||||||
{ key: 'overdue', label: 'Overdue' },
|
{ key: 'overdue', label: 'Overdue' },
|
||||||
{ key: 'unpaid', label: 'Unpaid' },
|
{ key: 'unpaid', label: 'Unpaid' },
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { camelCase, upperFirst } from 'lodash';
|
|||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { buildFilter } from 'objection-filter';
|
import { buildFilter } from 'objection-filter';
|
||||||
import { IModel, IModelMeta } from 'interfaces';
|
import { IModel, IModelMeta } from 'interfaces';
|
||||||
import {
|
|
||||||
getModelFields,
|
|
||||||
} from 'lib/ViewRolesBuilder'
|
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
|
|
||||||
@@ -20,62 +17,12 @@ export default class ResourceService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform resource to model name.
|
* Transform resource to model name.
|
||||||
* @param {string} resourceName
|
* @param {string} resourceName
|
||||||
*/
|
*/
|
||||||
private resourceToModelName(resourceName: string): string {
|
private resourceToModelName(resourceName: string): string {
|
||||||
return upperFirst(camelCase(pluralize.singular(resourceName)));
|
return upperFirst(camelCase(pluralize.singular(resourceName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve model fields.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {IModel} Model
|
|
||||||
*/
|
|
||||||
private getModelFields(tenantId: number, Model: IModel) {
|
|
||||||
const { __ } = this.tenancy.i18n(tenantId);
|
|
||||||
const fields = getModelFields(Model);
|
|
||||||
|
|
||||||
return fields.map((field) => ({
|
|
||||||
label: __(field.label),
|
|
||||||
key: field.key,
|
|
||||||
dataType: field.columnType,
|
|
||||||
fieldType: field.fieldType,
|
|
||||||
|
|
||||||
...(field.options) ? {
|
|
||||||
options: field.options.map((option) => ({
|
|
||||||
...option, label: __(option.label),
|
|
||||||
})),
|
|
||||||
} : {},
|
|
||||||
|
|
||||||
...(field.optionsResource) ? {
|
|
||||||
optionsResource: field.optionsResource,
|
|
||||||
optionsKey: field.optionsKey,
|
|
||||||
optionsLabel: field.optionsLabel,
|
|
||||||
} : {},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should model be resource-able or throw service error.
|
|
||||||
* @param {IModel} model
|
|
||||||
*/
|
|
||||||
private shouldModelBeResourceable(model: IModel) {
|
|
||||||
if (!model.resourceable) {
|
|
||||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource fields from resource model name.
|
|
||||||
* @param {string} resourceName
|
|
||||||
*/
|
|
||||||
public getResourceFields(tenantId: number, modelName: string) {
|
|
||||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
|
||||||
this.shouldModelBeResourceable(resourceModel);
|
|
||||||
|
|
||||||
return this.getModelFields(tenantId, resourceModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource model object.
|
* Retrieve resource model object.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -91,29 +38,18 @@ export default class ResourceService {
|
|||||||
return Models[modelName];
|
return Models[modelName];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource data from the storage based on the given query.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {string} modelName
|
|
||||||
*/
|
|
||||||
public async getResourceData(tenantId: number, modelName: string, filter: any) {
|
|
||||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
|
||||||
this.shouldModelBeResourceable(resourceModel);
|
|
||||||
|
|
||||||
return buildFilter(resourceModel).build(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the resource meta.
|
* Retrieve the resource meta.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {string} modelName
|
* @param {string} modelName
|
||||||
* @returns {IModelMeta}
|
* @returns {IModelMeta}
|
||||||
*/
|
*/
|
||||||
public getResourceMeta(tenantId: number, modelName: string): IModelMeta {
|
public getResourceMeta(
|
||||||
|
tenantId: number,
|
||||||
|
modelName: string,
|
||||||
|
metakey?: string
|
||||||
|
): IModelMeta {
|
||||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||||
|
return resourceModel.getMeta(metakey);
|
||||||
const settings = resourceModel.meta();
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user