diff --git a/client/package.json b/client/package.json index c4dcc5020..43e6e59e3 100644 --- a/client/package.json +++ b/client/package.json @@ -138,6 +138,7 @@ "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "@types/react-router-dom": "^5.1.8", + "@types/yup": "^0.29.13", "@welldone-software/why-did-you-render": "^6.0.0-rc.1", "compression-webpack-plugin": "^6.1.0", "http-proxy-middleware": "^1.0.0", diff --git a/client/src/components/AdvancedFilter/AdvancedFilter.schema.js b/client/src/components/AdvancedFilter/AdvancedFilter.schema.js new file mode 100644 index 000000000..b40e41a90 --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilter.schema.js @@ -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(), + }), + ), + }); diff --git a/client/src/components/AdvancedFilter/AdvancedFilter.schema.ts b/client/src/components/AdvancedFilter/AdvancedFilter.schema.ts deleted file mode 100644 index 6efa7d699..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilter.schema.ts +++ /dev/null @@ -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), - }), - ), -}); diff --git a/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.js b/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.js new file mode 100644 index 000000000..9170c5dd8 --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.js @@ -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 ( + + ); +} diff --git a/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.tsx b/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.tsx deleted file mode 100644 index 7dffe6f0e..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilterCompatatorField.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/client/src/components/AdvancedFilter/AdvancedFilterDropdown.js b/client/src/components/AdvancedFilter/AdvancedFilterDropdown.js new file mode 100644 index 000000000..867bf06ba --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilterDropdown.js @@ -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 ( + + {condition.label} + {condition.text} + > + } + key={condition.value} + onClick={handleClick} + /> + ); +} + +/** + * Filter condition field. + */ +function FilterConditionField() { + const conditionalsOptions = getConditionalsOptions(); + const { conditionIndex, getConditionFieldPath } = useFilterCondition(); + + const conditionFieldPath = getConditionFieldPath('condition'); + + return ( + + {({ form, field }) => ( + + + + + + + + { + form.setFieldValue(conditionFieldPath, option.value); + }} + popoverProps={{ + inline: true, + minimal: true, + captureDismiss: true, + }} + itemRenderer={ConditionItemRenderer} + /> + + + + )} + + ); +} + +/** + * Compatator field. + */ +function FilterCompatatorFilter() { + const { getConditionFieldPath, fieldMeta } = useFilterCondition(); + + const comparatorFieldPath = getConditionFieldPath('comparator'); + const fieldType = get(fieldMeta, 'fieldType'); + + return ( + + {({ form, field }) => ( + + { + form.setFieldValue(comparatorFieldPath, option.value); + }} + /> + + )} + + ); +} + +/** + * 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 ( + + {({ field, form }) => ( + + { + 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, + }} + /> + + )} + + ); +} + +/** + * 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 ( + + {({ form: { setFieldValue }, field }) => ( + + { + setFieldValue(valueFieldPath, value); + }} + /> + + )} + + ); +} + +/** + * Advanced filter condition line. + */ +function AdvancedFilterDropdownCondition({ conditionIndex, onRemoveClick }) { + // Handle click remove condition. + const handleClickRemoveCondition = () => { + onRemoveClick && onRemoveClick(conditionIndex); + }; + + return ( + + + + + + + + } + minimal={true} + onClick={handleClickRemoveCondition} + className={'button--remove'} + /> + + + ); +} + +/** + * 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 ( + + + {form.values.conditions.map((condition, index) => ( + + ))} + + + + ); +} + +/** + * Advanced filter dropdown form. + */ +function AdvancedFilterDropdownForm() { + // Advanced filter auto-save. + useAdvancedFilterAutoSubmit(); + + return ( + + ( + + )} + /> + + ); +} + +/** + * Advanced filter dropdown footer. + */ +function AdvancedFilterDropdownFooter({ onClick }) { + // Handle new filter condition button click. + const onClickNewFilter = (event) => { + onClick && onClick(event); + }; + return ( + + + + + + ); +} + +/** + * 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 ( + + + + + + ); +} diff --git a/client/src/components/AdvancedFilter/AdvancedFilterDropdown.tsx b/client/src/components/AdvancedFilter/AdvancedFilterDropdown.tsx deleted file mode 100644 index d18d23958..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilterDropdown.tsx +++ /dev/null @@ -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 }) => ( - - - - )} - - ); -} - -/** - * Compatator field. - */ -function FilterCompatatorFilter() { - const { conditionIndex } = useFilterCondition(); - - return ( - - {({ field }) => ( - - - - )} - - ); -} - -/** - * Resource fields field. - */ -function FilterFieldsField() { - const { conditionIndex } = useFilterCondition(); - const { fields } = useAdvancedFilterContext(); - - return ( - - {({ 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 ( - - {({ form: { setFieldValue } }) => ( - - { - setFieldValue(valueFieldPath, value); - }} - /> - - )} - - ); -} - -/** - * Advanced filter condition line. - */ -function AdvancedFilterDropdownCondition({ - conditionIndex, - onRemoveClick, -}: IAdvancedFilterDropdownCondition) { - // Handle click remove condition. - const handleClickRemoveCondition = () => { - onRemoveClick && onRemoveClick(conditionIndex); - }; - - return ( - - - - - - - - } - minimal={true} - onClick={handleClickRemoveCondition} - /> - - - ); -} - -/** - * 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 ( - - - {form.values.conditions.map((condition: IFilterRole, index: number) => ( - - ))} - - - - ); -} - -/** - * Advanced filter dropdown form. - */ -function AdvancedFilterDropdownForm() { - // - useAdvancedFilterAutoSubmit(); - - return ( - - ( - - )} - /> - - ); -} - -/** - * Advanced filter dropdown footer. - */ -function AdvancedFilterDropdownFooter({ - onClick, -}: IAdvancedFilterDropdownFooter) { - // Handle new filter condition button click. - const onClickNewFilter = (event) => { - onClick && onClick(event); - }; - - return ( - - - - - - ); -} - -/** - * 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 ( - - - - - - ); -} diff --git a/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.js b/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.js new file mode 100644 index 000000000..6e32f8116 --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.js @@ -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 ; +} + +/** + * 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 ; +} + +const useFilterCondition = () => useContext(FilterConditionContext); +const useAdvancedFilterContext = () => useContext(AdvancedFilterContext); + +export { + AdvancedFilterDropdownProvider, + FilterConditionProvider, + useAdvancedFilterContext, + useFilterCondition, +}; diff --git a/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.tsx b/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.tsx deleted file mode 100644 index e6e53654a..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilterDropdownContext.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { createContext, useContext } from 'react'; -import { keyBy } from 'lodash'; -import { - IAdvancedFilterContextProps, - IFilterConditionContextProps, - IAdvancedFilterProviderProps, - IFilterConditionProviderProps, -} from './interfaces'; - -const AdvancedFilterContext = createContext({}); -const FilterConditionContext = createContext({}); - -/** - * Advanced filter dropdown context provider. - */ -function AdvancedFilterDropdownProvider({ - initialCondition, - fields, - ...props -}: IAdvancedFilterProviderProps) { - const fieldsByKey = keyBy(fields, 'key'); - // Provider payload. - const provider = { initialCondition, fields, fieldsByKey }; - return ; -} - -/** - * Filter condition row context provider. - */ -function FilterConditionProvider({ - conditionIndex, - ...props -}: IFilterConditionProviderProps) { - // Provider payload. - const provider = { conditionIndex }; - return ; -} - -const useFilterCondition = () => useContext(FilterConditionContext); -const useAdvancedFilterContext = () => useContext(AdvancedFilterContext); - -export { - AdvancedFilterDropdownProvider, - FilterConditionProvider, - useAdvancedFilterContext, - useFilterCondition, -}; diff --git a/client/src/components/AdvancedFilter/AdvancedFilterPopover.js b/client/src/components/AdvancedFilter/AdvancedFilterPopover.js new file mode 100644 index 000000000..902af70a0 --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilterPopover.js @@ -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 ( + + } + interactionKind={PopoverInteractionKind.CLICK} + position={Position.BOTTOM_LEFT} + canOutsideClickClose={true} + modifiers={{ + offset: { offset: '0, 4' }, + }} + {...popoverProps} + > + {children} + + ); +} diff --git a/client/src/components/AdvancedFilter/AdvancedFilterValueField.js b/client/src/components/AdvancedFilter/AdvancedFilterValueField.js new file mode 100644 index 000000000..04b8fd9f9 --- /dev/null +++ b/client/src/components/AdvancedFilter/AdvancedFilterValueField.js @@ -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 ( + + ); +} + +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 ( + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/client/src/components/AdvancedFilter/AdvancedFilterValueField.tsx b/client/src/components/AdvancedFilter/AdvancedFilterValueField.tsx deleted file mode 100644 index 10a9877ad..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilterValueField.tsx +++ /dev/null @@ -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 (); - }; - - // 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 ( - - - - - - - - - - - - - - - - - - - - ); -} - -const mapStateToProps = (state, props) => ({ - resourceName: props.optionsResource, -}); - -const withResourceFilterValueField = connect(mapStateToProps); - -export default compose( - withResourceFilterValueField, - withResourceDetail(({ resourceData }) => ({ resourceData })), - withResourceActions, -)(DynamicFilterValueField); diff --git a/client/src/components/AdvancedFilter/AdvancedFilterValueField2.tsx b/client/src/components/AdvancedFilter/AdvancedFilterValueField2.tsx deleted file mode 100644 index c30a07e8a..000000000 --- a/client/src/components/AdvancedFilter/AdvancedFilterValueField2.tsx +++ /dev/null @@ -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 ( - - ); -} - -/** - * Advanced filter value field detarminer. - */ -export default function AdvancedFilterValueField2({ - fieldType, - options, - onChange, -}: IAdvancedFilterValueField) { - const [localValue, setLocalValue] = React.useState(''); - - 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 ( - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/client/src/components/AdvancedFilter/components.tsx b/client/src/components/AdvancedFilter/components.js similarity index 88% rename from client/src/components/AdvancedFilter/components.tsx rename to client/src/components/AdvancedFilter/components.js index 57b78fd3c..4e139ca65 100644 --- a/client/src/components/AdvancedFilter/components.tsx +++ b/client/src/components/AdvancedFilter/components.js @@ -9,7 +9,7 @@ const DEBOUNCE_MS = 100; */ export function useAdvancedFilterAutoSubmit() { const { submitForm, values } = useFormikContext(); - const [isSubmit, setIsSubmit] = React.useState(false); + const [isSubmit, setIsSubmit] = React.useState(false); const debouncedSubmit = React.useCallback( debounce(() => { diff --git a/client/src/components/AdvancedFilter/interfaces.ts b/client/src/components/AdvancedFilter/interfaces.ts index 6ee8b0a7b..8fe9d705b 100644 --- a/client/src/components/AdvancedFilter/interfaces.ts +++ b/client/src/components/AdvancedFilter/interfaces.ts @@ -1,4 +1,5 @@ import { ArrayHelpers } from 'formik'; +import { IPopoverProps } from '@blueprintjs/core'; export type IResourceFieldType = 'text' | 'number' | 'enumeration' | 'boolean'; @@ -10,9 +11,12 @@ export interface IResourceField { export interface IAdvancedFilterDropdown { fields: IResourceField[]; + conditions?: IFilterRole[]; defaultFieldKey: string; defaultComparator?: string; defaultValue?: string; + defaultCondition?: string; + onFilterChange?: (filterRoles: IFilterRole[]) => void; } export interface IAdvancedFilterDropdownFooter { @@ -69,6 +73,7 @@ export interface IFilterOption { export interface IAdvancedFilterValueField { fieldType: string; + value?: string; key: string; label: string; options?: IFilterOption[]; @@ -82,3 +87,25 @@ export enum IFieldType { ENUMERATION = 'enumeration', 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; +} diff --git a/client/src/components/AdvancedFilter/utils.js b/client/src/components/AdvancedFilter/utils.js new file mode 100644 index 000000000..441864897 --- /dev/null +++ b/client/src/components/AdvancedFilter/utils.js @@ -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) + ); +}; \ No newline at end of file diff --git a/client/src/components/AdvancedFilter/utils.ts b/client/src/components/AdvancedFilter/utils.ts deleted file mode 100644 index a01044726..000000000 --- a/client/src/components/AdvancedFilter/utils.ts +++ /dev/null @@ -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); -}; diff --git a/client/src/components/Dashboard/DashboardFilterButton.js b/client/src/components/Dashboard/DashboardFilterButton.js new file mode 100644 index 000000000..973431d08 --- /dev/null +++ b/client/src/components/Dashboard/DashboardFilterButton.js @@ -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 ( + 0, + })} + text={ + conditionsCount > 0 ? ( + intl.get('count_filters_applied', { count: conditionsCount }) + ) : ( + + ) + } + icon={} + /> + ); +} diff --git a/client/src/components/Dashboard/DashboardRowsHeightButton.js b/client/src/components/Dashboard/DashboardRowsHeightButton.js new file mode 100644 index 000000000..85c72f749 --- /dev/null +++ b/client/src/components/Dashboard/DashboardRowsHeightButton.js @@ -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 ( + + + + + + } + placement="bottom-start" + modifiers={{ + offset: { offset: '0, 4' }, + }} + interactionKind={PopoverInteractionKind.CLICK} + > + } + /> + + ); +} diff --git a/client/src/components/FilterDropdown2.js b/client/src/components/FilterDropdown2.js deleted file mode 100644 index 4bdd172a9..000000000 --- a/client/src/components/FilterDropdown2.js +++ /dev/null @@ -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 ( - - - {values.conditions.map((condition, index) => ( - - - 1} - {...fieldProps('condition', index)} - /> - - - - - - - - - - - - - } - minimal={true} - onClick={onClickRemoveCondition(index)} - /> - - ))} - - - - - ); -} diff --git a/client/src/components/ListSelect.js b/client/src/components/ListSelect.js index 1035d184f..dbfb4d7ab 100644 --- a/client/src/components/ListSelect.js +++ b/client/src/components/ListSelect.js @@ -1,3 +1,4 @@ + import React, { useState, useMemo, useEffect } from 'react'; import { Button, MenuItem } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; @@ -66,10 +67,23 @@ export default function ListSelect({ 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 ( ); diff --git a/client/src/components/index.js b/client/src/components/index.js index b6aa9dd50..d0d5904ce 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -62,6 +62,10 @@ import Card from './Card'; import { ItemsMultiSelect } from './Items'; export * from './Menu'; +export * from './AdvancedFilter/AdvancedFilterDropdown'; +export * from './AdvancedFilter/AdvancedFilterPopover'; +export * from './Dashboard/DashboardFilterButton'; +export * from './Dashboard/DashboardRowsHeightButton'; const Hint = FieldHint; diff --git a/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js b/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js index 1491775e8..023be03b9 100644 --- a/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js +++ b/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js @@ -5,24 +5,26 @@ import { NavbarGroup, Classes, NavbarDivider, - Popover, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; 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 { useManualJournalsContext } from './ManualJournalsListProvider'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; + import withDialogActions from 'containers/Dialog/withDialogActions'; +import withManualJournalsActions from './withManualJournalsActions'; +import withManualJournals from './withManualJournals'; import { If, DashboardActionViewsList } from 'components'; -import withManualJournalsActions from './withManualJournalsActions'; import { compose } from 'utils'; /** @@ -31,12 +33,15 @@ import { compose } from 'utils'; function ManualJournalActionsBar({ // #withManualJournalsActions setManualJournalsTableState, + + // #withManualJournals + manualJournalsFilterConditions, }) { // History context. const history = useHistory(); // Manual journals context. - const { journalsViews } = useManualJournalsContext(); + const { journalsViews, fields } = useManualJournalsContext(); // Manual journals refresh action. const { refresh } = useRefreshJournals(); @@ -45,7 +50,6 @@ function ManualJournalActionsBar({ const onClickNewManualJournal = () => { history.push('/make-journal-entry'); }; - // Handle delete button click. const handleBulkDelete = () => {}; @@ -53,11 +57,10 @@ function ManualJournalActionsBar({ const handleTabChange = (customView) => { setManualJournalsTableState({ customViewId: customView.id || null }); }; - // Handle click a refresh Journals - const handleRefreshBtnClick = () => { - refresh(); - }; + const handleRefreshBtnClick = () => { refresh(); }; + + console.log(manualJournalsFilterConditions, fields, 'XXX'); return ( @@ -75,20 +78,20 @@ function ManualJournalActionsBar({ text={} onClick={onClickNewManualJournal} /> - { + setManualJournalsTableState({ filterRoles: filterConditions }); + }, + }} > - } - icon={} + - + ({ + manualJournalsFilterConditions: manualJournalsTableState.filterRoles, + })) )(ManualJournalActionsBar); diff --git a/client/src/containers/Accounting/JournalsLanding/ManualJournalsListProvider.js b/client/src/containers/Accounting/JournalsLanding/ManualJournalsListProvider.js index d9f512714..b26fbb5e8 100644 --- a/client/src/containers/Accounting/JournalsLanding/ManualJournalsListProvider.js +++ b/client/src/containers/Accounting/JournalsLanding/ManualJournalsListProvider.js @@ -1,27 +1,36 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useJournals } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useResourceViews, useResourceMeta, useJournals } from 'hooks/query'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; const ManualJournalsContext = createContext(); function ManualJournalsListProvider({ query, ...props }) { // Fetches accounts resource views and fields. - const { data: journalsViews, isLoading: isViewsLoading } = useResourceViews( - 'manual_journals', - ); + const { data: journalsViews, isLoading: isViewsLoading } = + useResourceViews('manual_journals'); // Fetches the manual journals transactions with pagination meta. const { data: { manualJournals, pagination, filterMeta }, isLoading: isManualJournalsLoading, - isFetching: isManualJournalsFetching + isFetching: isManualJournalsFetching, } = useJournals(query, { keepPreviousData: true }); + // Fetch the accounts resource fields. + const { + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('manual_journals'); + // Detarmines the datatable empty status. - const isEmptyStatus = isTableEmptyStatus({ - data: manualJournals, pagination, filterMeta, - }) && !isManualJournalsFetching; + const isEmptyStatus = + isTableEmptyStatus({ + data: manualJournals, + pagination, + filterMeta, + }) && !isManualJournalsFetching; // Global state. const state = { @@ -29,15 +38,21 @@ function ManualJournalsListProvider({ query, ...props }) { pagination, journalsViews, + resourceMeta, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + isManualJournalsLoading, isManualJournalsFetching, isViewsLoading, - isEmptyStatus + isEmptyStatus, }; + const isPageLoading = + isManualJournalsLoading || isViewsLoading || isResourceMetaLoading; + return ( - + ); diff --git a/client/src/containers/Accounts/AccountsActionsBar.js b/client/src/containers/Accounts/AccountsActionsBar.js index 8204c9436..80781d57d 100644 --- a/client/src/containers/Accounts/AccountsActionsBar.js +++ b/client/src/containers/Accounts/AccountsActionsBar.js @@ -6,20 +6,20 @@ import { NavbarGroup, Classes, NavbarDivider, - Popover, - PopoverInteractionKind, - Position, Intent, Switch, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; + 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 AdvancedFilterDropdown from 'components/AdvancedFilter/AdvancedFilterDropdown.tsx'; import { useRefreshAccounts } from 'hooks/query/accounts'; import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider'; @@ -30,46 +30,6 @@ import withAccountsTableActions from './withAccountsTableActions'; 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. */ @@ -80,6 +40,7 @@ function AccountsActionsBar({ // #withAccounts accountsSelectedRows, accountsInactiveMode, + accountsFilterConditions, // #withAlertActions openAlert, @@ -90,7 +51,7 @@ function AccountsActionsBar({ // #ownProps onFilterChanged, }) { - const { resourceViews } = useAccountsChartContext(); + const { resourceViews, fields } = useAccountsChartContext(); const onClickNewAccount = () => { openDialog('account-form', {}); @@ -148,34 +109,22 @@ function AccountsActionsBar({ text={} onClick={onClickNewAccount} /> - { - console.log(filterConditions, 'XXX'); - }} /> - } - interactionKind={PopoverInteractionKind.CLICK} - position={Position.BOTTOM_LEFT} - canOutsideClickClose={true} + { + setAccountsTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - intl.get('count_filters_applied', { count: 0 }) - ) - } - icon={} + - + + + ({ accountsSelectedRows, accountsInactiveMode: accountsTableState.inactiveMode, + accountsFilterConditions: accountsTableState.filterRoles, })), withAccountsTableActions, )(AccountsActionsBar); diff --git a/client/src/containers/Accounts/AccountsChart.js b/client/src/containers/Accounts/AccountsChart.js index bcfd3ca8f..35b1dc85d 100644 --- a/client/src/containers/Accounts/AccountsChart.js +++ b/client/src/containers/Accounts/AccountsChart.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import 'style/pages/Accounts/List.scss'; import { DashboardPageContent, DashboardContentTable } from 'components'; @@ -14,6 +14,7 @@ import withAccounts from 'containers/Accounts/withAccounts'; import { compose } from 'utils'; import { transformAccountsStateToQuery } from './utils'; +import withAccountsTableActions from './withAccountsTableActions'; /** * Accounts chart list. @@ -21,7 +22,22 @@ import { transformAccountsStateToQuery } from './utils'; function AccountsChart({ // #withAccounts accountsTableState, + + // #withAccountsActions + setAccountsTableState, }) { + // Resets the accounts table state once the page unmount. + useEffect( + () => () => { + setAccountsTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setAccountsTableState], + ); + return ( ({ accountsTableState })), + withAccountsTableActions, )(AccountsChart); diff --git a/client/src/containers/Accounts/AccountsChartProvider.js b/client/src/containers/Accounts/AccountsChartProvider.js index ff9985afb..03353ead0 100644 --- a/client/src/containers/Accounts/AccountsChartProvider.js +++ b/client/src/containers/Accounts/AccountsChartProvider.js @@ -1,6 +1,7 @@ import React, { createContext } from 'react'; 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(); @@ -9,41 +10,42 @@ const AccountsChartContext = createContext(); */ function AccountsChartProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: resourceViews, isLoading: isViewsLoading } = useResourceViews( - 'accounts', - ); + const { data: resourceViews, isLoading: isViewsLoading } = + useResourceViews('accounts'); // Fetch the accounts resource fields. const { - data: resourceFields, - isLoading: isFieldsLoading, - } = useResourceFields('accounts'); + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('accounts'); // Fetch accounts list according to the given custom view id. const { data: accounts, isFetching: isAccountsFetching, - isLoading: isAccountsLoading - } = useAccounts( - query, - { keepPreviousData: true } - ); + isLoading: isAccountsLoading, + } = useAccounts(query, { keepPreviousData: true }); // Provider payload. const provider = { accounts, - resourceFields, + + resourceMeta, resourceViews, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + isAccountsLoading, isAccountsFetching, - isFieldsLoading, + isResourceMetaFetching, + isResourceMetaLoading, isViewsLoading, }; return ( diff --git a/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js b/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js index cf5d3615d..17696c68f 100644 --- a/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js +++ b/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js @@ -1,23 +1,24 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { NavbarGroup, NavbarDivider, Button, Classes, Intent, - Popover, - Position, - PopoverInteractionKind, Switch, Alignment, } from '@blueprintjs/core'; import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; 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 { useRefreshCustomers } from 'hooks/query/customers'; @@ -34,6 +35,7 @@ import { compose } from 'utils'; function CustomerActionsBar({ // #withCustomers customersSelectedRows = [], + customersFilterConditions, // #withCustomersActions setCustomersTableState, @@ -46,7 +48,7 @@ function CustomerActionsBar({ const history = useHistory(); // Customers list context. - const { customersViews } = useCustomersListContext(); + const { customersViews, fields } = useCustomersListContext(); // Customers refresh action. const { refresh } = useRefreshCustomers(); @@ -72,9 +74,7 @@ function CustomerActionsBar({ }; // Handle click a refresh customers - const handleRefreshBtnClick = () => { - refresh(); - }; + const handleRefreshBtnClick = () => { refresh(); }; return ( @@ -93,17 +93,21 @@ function CustomerActionsBar({ onClick={onClickNewCustomer} /> - { + setCustomersTableState({ filterRoles: filterConditions }); + }, + }} > - } + - + ({ customersSelectedRows, accountsInactiveMode: customersTableState.inactiveMode, + customersFilterConditions: customersTableState.filterRoles, })), withAlertActions, )(CustomerActionsBar); diff --git a/client/src/containers/Customers/CustomersLanding/CustomersList.js b/client/src/containers/Customers/CustomersLanding/CustomersList.js index 936ebdb5e..0e56bb645 100644 --- a/client/src/containers/Customers/CustomersLanding/CustomersList.js +++ b/client/src/containers/Customers/CustomersLanding/CustomersList.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import 'style/pages/Customers/List.scss'; @@ -11,6 +11,8 @@ import CustomersAlerts from 'containers/Customers/CustomersAlerts'; import { CustomersListProvider } from './CustomersListProvider'; import withCustomers from './withCustomers'; +import withCustomersActions from './withCustomersActions'; + import { compose } from 'utils'; /** @@ -19,7 +21,22 @@ import { compose } from 'utils'; function CustomersList({ // #withCustomers customersTableState, + + // #withCustomersActions + setCustomersTableState }) { + // Resets the accounts table state once the page unmount. + useEffect( + () => () => { + setCustomersTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setCustomersTableState], + ); + return ( @@ -38,4 +55,5 @@ function CustomersList({ export default compose( withCustomers(({ customersTableState }) => ({ customersTableState })), + withCustomersActions )(CustomersList); diff --git a/client/src/containers/Customers/CustomersLanding/CustomersListProvider.js b/client/src/containers/Customers/CustomersLanding/CustomersListProvider.js index 36af7b2e0..94508abfb 100644 --- a/client/src/containers/Customers/CustomersLanding/CustomersListProvider.js +++ b/client/src/containers/Customers/CustomersLanding/CustomersListProvider.js @@ -1,8 +1,8 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useCustomers } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useResourceMeta, useResourceViews, useCustomers } from 'hooks/query'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; import { transformCustomersStateToQuery } from './utils'; const CustomersListContext = createContext(); @@ -12,10 +12,15 @@ function CustomersListProvider({ tableState, ...props }) { const tableQuery = transformCustomersStateToQuery(tableState); // Fetch customers resource views and fields. + const { data: customersViews, isLoading: isViewsLoading } = + useResourceViews('customers'); + + // Fetch the customers resource fields. const { - data: customersViews, - isLoading: isCustomersViewsLoading, - } = useResourceViews('customers'); + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('customers'); // Fetches customers data with pagination meta. const { @@ -25,16 +30,26 @@ function CustomersListProvider({ tableState, ...props }) { } = useCustomers(tableQuery, { keepPreviousData: true }); // Detarmines the datatable empty status. - const isEmptyStatus = isTableEmptyStatus({ - data: customers, pagination, filterMeta, - }) && !isCustomersFetching && !tableState.inactiveMode; + const isEmptyStatus = + isTableEmptyStatus({ + data: customers, + pagination, + filterMeta, + }) && + !isCustomersFetching && + !tableState.inactiveMode; const state = { customersViews, customers, pagination, - isCustomersViewsLoading, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + resourceMeta, + isResourceMetaLoading, + isResourceMetaFetching, + + isViewsLoading, isCustomersLoading, isCustomersFetching, @@ -42,7 +57,10 @@ function CustomersListProvider({ tableState, ...props }) { }; return ( - + ); diff --git a/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js b/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js index 4e41b3275..955d0a9d8 100644 --- a/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js +++ b/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js @@ -1,28 +1,31 @@ -import React, { useCallback, useState } from 'react'; +import React from 'react'; import Icon from 'components/Icon'; import { Button, NavbarGroup, Classes, NavbarDivider, - Popover, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { FormattedMessage as T } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; 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 { useExpensesListContext } from './ExpensesListProvider'; + import withExpensesActions from './withExpensesActions'; +import withExpenses from './withExpenses'; import { compose } from 'utils'; @@ -32,14 +35,15 @@ import { compose } from 'utils'; function ExpensesActionsBar({ //#withExpensesActions setExpensesTableState, -}) { - const [filterCount, setFilterCount] = useState(0); + // #withExpenses + expensesFilterConditions +}) { // History context. const history = useHistory(); // Expenses list context. - const { expensesViews } = useExpensesListContext(); + const { expensesViews, fields } = useExpensesListContext(); // Expenses refresh action. const { refresh } = useRefreshExpenses(); @@ -79,20 +83,20 @@ function ExpensesActionsBar({ text={} onClick={onClickNewExpense} /> - { + setExpensesTableState({ filterRoles: filterConditions }); + }, + }} > - 0, - })} - text={} - icon={} + - + ({ + expensesFilterConditions: expensesTableState.filterRoles, + })) )(ExpensesActionsBar); diff --git a/client/src/containers/Expenses/ExpensesLanding/ExpensesList.js b/client/src/containers/Expenses/ExpensesLanding/ExpensesList.js index edaaa4783..689d12be5 100644 --- a/client/src/containers/Expenses/ExpensesLanding/ExpensesList.js +++ b/client/src/containers/Expenses/ExpensesLanding/ExpensesList.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import 'style/pages/Expense/List.scss'; @@ -10,6 +10,7 @@ import ExpenseDataTable from './ExpenseDataTable'; import ExpensesAlerts from '../ExpensesAlerts'; import withExpenses from './withExpenses'; +import withExpensesActions from './withExpensesActions'; import { compose, transformTableStateToQuery } from 'utils'; import { ExpensesListProvider } from './ExpensesListProvider'; @@ -20,7 +21,22 @@ import { ExpensesListProvider } from './ExpensesListProvider'; function ExpensesList({ // #withExpenses expensesTableState, + + // #withExpensesActions + setExpensesTableState, }) { + // Resets the accounts table state once the page unmount. + useEffect( + () => () => { + setExpensesTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setExpensesTableState], + ); + return ( ({ expensesTableState })), + withExpensesActions, )(ExpensesList); diff --git a/client/src/containers/Expenses/ExpensesLanding/ExpensesListProvider.js b/client/src/containers/Expenses/ExpensesLanding/ExpensesListProvider.js index cbef2e579..53f3ae0b9 100644 --- a/client/src/containers/Expenses/ExpensesLanding/ExpensesListProvider.js +++ b/client/src/containers/Expenses/ExpensesLanding/ExpensesListProvider.js @@ -1,7 +1,7 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useExpenses, useResourceViews } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useExpenses, useResourceMeta, useResourceViews } from 'hooks/query'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; const ExpensesListContext = createContext(); @@ -10,9 +10,8 @@ const ExpensesListContext = createContext(); */ function ExpensesListProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: expensesViews, isLoading: isViewsLoading } = useResourceViews( - 'expenses', - ); + const { data: expensesViews, isLoading: isViewsLoading } = + useResourceViews('expenses'); // Fetches the expenses with pagination meta. const { @@ -21,10 +20,20 @@ function ExpensesListProvider({ query, ...props }) { isFetching: isExpensesFetching, } = useExpenses(query, { keepPreviousData: true }); + // Fetch the expenses resource fields. + const { + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('expenses'); + // Detarmines the datatable empty status. - const isEmptyStatus = isTableEmptyStatus({ - data: expenses, pagination, filterMeta, - }) && !isExpensesFetching; + const isEmptyStatus = + isTableEmptyStatus({ + data: expenses, + pagination, + filterMeta, + }) && !isExpensesFetching; // Provider payload. const provider = { @@ -32,16 +41,21 @@ function ExpensesListProvider({ query, ...props }) { expenses, pagination, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + resourceMeta, + isResourceMetaLoading, + isResourceMetaFetching, + isViewsLoading, isExpensesLoading, isExpensesFetching, - isEmptyStatus + isEmptyStatus, }; return ( @@ -49,7 +63,6 @@ function ExpensesListProvider({ query, ...props }) { ); } -const useExpensesListContext = () => - React.useContext(ExpensesListContext); +const useExpensesListContext = () => React.useContext(ExpensesListContext); export { ExpensesListProvider, useExpensesListContext }; diff --git a/client/src/containers/Expenses/ExpensesLanding/withExpenses.js b/client/src/containers/Expenses/ExpensesLanding/withExpenses.js index 6268995cc..6255c9b77 100644 --- a/client/src/containers/Expenses/ExpensesLanding/withExpenses.js +++ b/client/src/containers/Expenses/ExpensesLanding/withExpenses.js @@ -10,6 +10,5 @@ export default (mapState) => { }; return mapState ? mapState(mapped, state, props) : mapped; }; - return connect(mapStateToProps); }; diff --git a/client/src/containers/Items/ItemsActionsBar.js b/client/src/containers/Items/ItemsActionsBar.js index 44634c25e..fa3a4c409 100644 --- a/client/src/containers/Items/ItemsActionsBar.js +++ b/client/src/containers/Items/ItemsActionsBar.js @@ -1,24 +1,23 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; -import classNames from 'classnames'; import { - Popover, NavbarGroup, NavbarDivider, - PopoverInteractionKind, - Position, Button, Classes, Intent, Switch, Alignment, } from '@blueprintjs/core'; -import { Tooltip2 } from '@blueprintjs/popover2'; import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import Icon from 'components/Icon'; -import { If, DashboardActionViewsList } from 'components'; +import { + If, + DashboardActionViewsList, + AdvancedFilterPopover, + DashboardFilterButton, +} from 'components'; import { useItemsListContext } from './ItemsListProvider'; import { useRefreshItems } from 'hooks/query/items'; @@ -35,6 +34,7 @@ import { compose } from 'utils'; function ItemsActionsBar({ // #withItems itemsSelectedRows, + itemsFilterRoles, // #withItemActions setItemsTableState, @@ -44,7 +44,7 @@ function ItemsActionsBar({ openAlert, }) { // Items list context. - const { itemsViews } = useItemsListContext(); + const { itemsViews, fields } = useItemsListContext(); // Items refresh action. const { refresh } = useRefreshItems(); @@ -93,19 +93,21 @@ function ItemsActionsBar({ text={} onClick={onClickNewItem} /> - - { + setItemsTableState({ filterRoles: filterConditions }); + }, + }} > - } - /> - + + + + ({ itemsSelectedRows, itemsInactiveMode: itemsTableState.inactiveMode, + itemsFilterRoles: itemsTableState.filterRoles, })), withItemsActions, withAlertActions, diff --git a/client/src/containers/Items/ItemsListProvider.js b/client/src/containers/Items/ItemsListProvider.js index 05c755906..e44fe0c25 100644 --- a/client/src/containers/Items/ItemsListProvider.js +++ b/client/src/containers/Items/ItemsListProvider.js @@ -1,62 +1,74 @@ import React, { createContext } from 'react'; -import { transformTableQueryToParams, isTableEmptyStatus } from 'utils'; +import { + getFieldsFromResourceMeta, + transformTableQueryToParams, + isTableEmptyStatus, +} from 'utils'; import { transformItemsTableState } from './utils'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useResourceFields, useItems } from 'hooks/query'; +import { useResourceViews, useResourceMeta, useItems } from 'hooks/query'; const ItemsContext = createContext(); /** * Items list provider. */ -function ItemsListProvider({ - tableState, - ...props -}) { +function ItemsListProvider({ tableState, ...props }) { const tableQuery = transformItemsTableState(tableState); // Fetch accounts resource views and fields. - const { data: itemsViews, isLoading: isViewsLoading } = useResourceViews( - 'items', - ); + const { data: itemsViews, isLoading: isViewsLoading } = + useResourceViews('items'); // Fetch the accounts resource fields. - const { data: itemsFields, isLoading: isFieldsLoading } = useResourceFields( - 'items', - ); + const { + data: resourceMeta, + isLoading: isResourceLoading, + isFetching: isResourceFetching, + } = useResourceMeta('items'); // Handle fetching the items table based on the given query. const { data: { items, pagination, filterMeta }, isFetching: isItemsFetching, isLoading: isItemsLoading, - } = useItems({ - ...transformTableQueryToParams(tableQuery) - }, { keepPreviousData: true }); + } = useItems( + { + ...transformTableQueryToParams(tableQuery), + }, + { keepPreviousData: true }, + ); // Detarmines the datatable empty status. - const isEmptyStatus = isTableEmptyStatus({ - data: items, pagination, filterMeta, - }) && !isItemsFetching && !tableState.inactiveMode; + const isEmptyStatus = + isTableEmptyStatus({ + data: items, + pagination, + filterMeta, + }) && + !isItemsFetching && + !tableState.inactiveMode; const state = { itemsViews, - itemsFields, items, pagination, + + fields: getFieldsFromResourceMeta(resourceMeta.fields), + isViewsLoading, isItemsLoading, isItemsFetching: isItemsFetching, + isResourceLoading, + isResourceFetching, + isEmptyStatus, }; return ( - + ); diff --git a/client/src/containers/ItemsCategories/ItemCategoriesList.js b/client/src/containers/ItemsCategories/ItemCategoriesList.js index d0220a575..630a94b0c 100644 --- a/client/src/containers/ItemsCategories/ItemCategoriesList.js +++ b/client/src/containers/ItemsCategories/ItemCategoriesList.js @@ -1,4 +1,6 @@ import React from 'react'; +import * as R from 'ramda'; + import 'style/pages/ItemsCategories/List.scss'; import { DashboardContentTable, DashboardPageContent } from 'components'; @@ -8,12 +10,17 @@ import ItemsCategoryActionsBar from './ItemsCategoryActionsBar'; import { ItemsCategoriesProvider } from './ItemsCategoriesProvider'; import ItemCategoriesTable from './ItemCategoriesTable'; +import withItemsCategories from './withItemCategories'; + /** * Item categories list. */ -export default function ItemCategoryList() { +function ItemCategoryList({ + // #withItemsCategories + itemsCategoriesTableState +}) { return ( - + @@ -25,3 +32,9 @@ export default function ItemCategoryList() { ); } + +export default R.compose( + withItemsCategories(({ itemsCategoriesTableState }) => ({ + itemsCategoriesTableState, + })), +)(ItemCategoryList); diff --git a/client/src/containers/ItemsCategories/ItemsCategoriesProvider.js b/client/src/containers/ItemsCategories/ItemsCategoriesProvider.js index f6995bb9b..5b05b8ed8 100644 --- a/client/src/containers/ItemsCategories/ItemsCategoriesProvider.js +++ b/client/src/containers/ItemsCategories/ItemsCategoriesProvider.js @@ -1,31 +1,50 @@ import React, { createContext } from 'react'; 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(); /** * 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 { data: { itemsCategories, pagination }, isFetching: isCategoriesFetching, isLoading: isCategoriesLoading, - } = useItemsCategories(); + } = useItemsCategories(query, { keepPreviousData: true }); + + // Fetch the accounts resource fields. + const { + data: resourceMeta, + isLoading: isResourceLoading, + isFetching: isResourceFetching, + } = useResourceMeta('item_category'); const state = { isCategoriesFetching, isCategoriesLoading, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + resourceMeta, + isResourceLoading, + isResourceFetching, + itemsCategories, pagination, - query, }; return ( - + ); @@ -34,7 +53,4 @@ function ItemsCategoriesProvider({ query, ...props }) { const useItemsCategoriesContext = () => React.useContext(ItemsCategoriesContext); -export { - ItemsCategoriesProvider, - useItemsCategoriesContext, -}; +export { ItemsCategoriesProvider, useItemsCategoriesContext }; diff --git a/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js b/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js index e47942012..011228175 100644 --- a/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js +++ b/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js @@ -5,21 +5,20 @@ import { Button, Classes, Intent, - Popover, - Position, - PopoverInteractionKind, } from '@blueprintjs/core'; import { FormattedMessage as T } from 'components'; -import classNames from 'classnames'; import { If, Icon } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import { AdvancedFilterPopover, DashboardFilterButton } from 'components'; 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 { compose } from 'utils'; +import { useItemsCategoriesContext } from './ItemsCategoriesProvider'; /** * Items categories actions bar. @@ -27,6 +26,10 @@ import { compose } from 'utils'; function ItemsCategoryActionsBar({ // #withItemCategories itemCategoriesSelectedRows = [], + categoriesFilterConditions, + + // + setItemsCategoriesTableState, // #withDialog openDialog, @@ -34,6 +37,8 @@ function ItemsCategoryActionsBar({ // #withAlertActions openAlert, }) { + const { fields } = useItemsCategoriesContext(); + const onClickNewCategory = () => { openDialog('item-category-form', {}); }; @@ -44,7 +49,9 @@ function ItemsCategoryActionsBar({ itemCategoriesIds: itemCategoriesSelectedRows, }); }; - + + console.log(fields, categoriesFilterConditions, 'XXXX'); + return ( @@ -56,25 +63,20 @@ function ItemsCategoryActionsBar({ /> - { + setItemsCategoriesTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - `${0} filters applied` - ) - } - icon={} + - + ({ - // itemCategoriesSelectedRows, - // })), + withItemCategories( + ({ itemCategoriesSelectedRows, itemsCategoriesTableState }) => ({ + itemCategoriesSelectedRows, + categoriesFilterConditions: itemsCategoriesTableState.filterRoles, + }), + ), withAlertActions, + withItemCategoriesActions )(ItemsCategoryActionsBar); diff --git a/client/src/containers/ItemsCategories/withItemCategories.js b/client/src/containers/ItemsCategories/withItemCategories.js index 8b33e841d..a5ffc694f 100644 --- a/client/src/containers/ItemsCategories/withItemCategories.js +++ b/client/src/containers/ItemsCategories/withItemCategories.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import { getItemsCategoriesTableStateFactory, -} from 'store/itemCategories/itemsCategories.selectors'; +} from 'store/itemCategories/ItemsCategories.selectors'; export default (mapState) => { const getItemsCategoriesTableState = getItemsCategoriesTableStateFactory(); diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js index 96d13aab0..684fba3d9 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js @@ -3,24 +3,25 @@ import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; -import { If, DashboardActionViewsList } from 'components'; +import { + If, + DashboardActionViewsList, + DashboardFilterButton, + AdvancedFilterPopover, +} from 'components'; import withBillsActions from './withBillsActions'; +import withBills from './withBills'; import { useBillsListContext } from './BillsListProvider'; import { useRefreshBills } from 'hooks/query/bills'; import { compose } from 'utils'; @@ -31,6 +32,9 @@ import { compose } from 'utils'; function BillActionsBar({ // #withBillActions setBillsTableState, + + // #withBills + billsConditionsRoles }) { const history = useHistory(); @@ -38,9 +42,7 @@ function BillActionsBar({ const { refresh } = useRefreshBills(); // Bills list context. - const { billsViews } = useBillsListContext(); - - const [filterCount] = useState(0); + const { billsViews, fields } = useBillsListContext(); // Handle click a new bill. const handleClickNewBill = () => { @@ -53,11 +55,8 @@ function BillActionsBar({ customViewId: customView.id || null, }); }; - // Handle click a refresh bills - const handleRefreshBtnClick = () => { - refresh(); - }; + const handleRefreshBtnClick = () => { refresh(); }; return ( @@ -74,24 +73,21 @@ function BillActionsBar({ text={} onClick={handleClickNewBill} /> - { + setBillsTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - `${filterCount} ${intl.get('filters_applied')}` - ) - } - icon={} + - + + ({ + billsConditionsRoles: billsTableState.filterRoles + })) +)(BillActionsBar); diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsList.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsList.js index e1723e8c4..1f15c0f97 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsList.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsList.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { DashboardContentTable, DashboardPageContent } from 'components'; import 'style/pages/Bills/List.scss'; @@ -11,6 +11,7 @@ import BillsViewsTabs from './BillsViewsTabs'; import BillsTable from './BillsTable'; import withBills from './withBills'; +import withBillsActions from './withBillsActions'; import { transformTableStateToQuery, compose } from 'utils'; @@ -20,7 +21,22 @@ import { transformTableStateToQuery, compose } from 'utils'; function BillsList({ // #withBills billsTableState, + + // #withBillsActions + setBillsTableState }) { + // Resets the accounts table state once the page unmount. + useEffect( + () => () => { + setBillsTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setBillsTableState], + ); + return ( @@ -40,4 +56,5 @@ function BillsList({ export default compose( withBills(({ billsTableState }) => ({ billsTableState })), + withBillsActions )(BillsList); diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js index 432dea87e..562c0b6f1 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsListProvider.js @@ -1,7 +1,7 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useResourceFields, useBills } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useResourceViews, useResourceMeta, useBills } from 'hooks/query'; +import { getFieldsFromResourceMeta, isTableEmptyStatus } from 'utils'; const BillsListContext = createContext(); @@ -10,15 +10,15 @@ const BillsListContext = createContext(); */ function BillsListProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: billsViews, isLoading: isViewsLoading } = useResourceViews( - 'bills', - ); + const { data: billsViews, isLoading: isViewsLoading } = + useResourceViews('bills'); // Fetch the accounts resource fields. const { - data: billsFields, - isLoading: isFieldsLoading, - } = useResourceFields('bills'); + data: resourceMeta, + isLoading: isResourceLoading, + isFetching: isResourceFetching, + } = useResourceMeta('bills'); // Fetch accounts list according to the given custom view id. const { @@ -28,27 +28,33 @@ function BillsListProvider({ query, ...props }) { } = useBills(query, { keepPreviousData: true }); // Detarmines the datatable empty status. - const isEmptyStatus = isTableEmptyStatus({ - data: bills, pagination, filterMeta, - }) && !isBillsFetching; + const isEmptyStatus = + isTableEmptyStatus({ + data: bills, + pagination, + filterMeta, + }) && !isBillsFetching; // Provider payload. const provider = { bills, pagination, - billsFields, billsViews, + resourceMeta, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + isResourceLoading, + isResourceFetching, + isBillsLoading, isBillsFetching, - isFieldsLoading, isViewsLoading, - isEmptyStatus + isEmptyStatus, }; return ( diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js index efb2ba30d..e7ad53d85 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js @@ -3,26 +3,29 @@ import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; 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 { usePaymentMadesListContext } from './PaymentMadesListProvider'; import { useRefreshPaymentMades } from 'hooks/query/paymentMades'; + import { compose } from 'utils'; /** @@ -31,11 +34,14 @@ import { compose } from 'utils'; function PaymentMadeActionsBar({ // #withPaymentMadesActions setPaymentMadesTableState, + + // #withPaymentMades + paymentMadesFilterConditions }) { const history = useHistory(); // Payment receives list context. - const { paymentMadesViews } = usePaymentMadesListContext(); + const { paymentMadesViews, fields } = usePaymentMadesListContext(); // Handle new payment made button click. const handleClickNewPaymentMade = () => { @@ -69,20 +75,21 @@ function PaymentMadeActionsBar({ text={} onClick={handleClickNewPaymentMade} /> - { + setPaymentMadesTableState({ filterRoles: filterConditions }); + }, + }} > - : `${0} ${intl.get('filters_applied')}` - } - icon={} + - + + ({ + paymentMadesFilterConditions: paymentMadesTableState.filterRoles, + })), +)(PaymentMadeActionsBar); diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeList.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeList.js index fa5ee8b18..3ff9662ae 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeList.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeList.js @@ -10,6 +10,7 @@ import { PaymentMadesListProvider } from './PaymentMadesListProvider'; import PaymentMadeViewTabs from './PaymentMadeViewTabs'; import withPaymentMades from './withPaymentMade'; +import withPaymentMadeActions from './withPaymentMadeActions'; import { compose, transformTableStateToQuery } from 'utils'; @@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils'; function PaymentMadeList({ // #withPaymentMades paymentMadesTableState, + + // #withPaymentMadeActions + setPaymentMadesTableState }) { + // Resets the invoices table state once the page unmount. + React.useEffect( + () => () => { + setPaymentMadesTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setPaymentMadesTableState], + ); + return ( ({ paymentMadesTableState, })), + withPaymentMadeActions )(PaymentMadeList); diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesListProvider.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesListProvider.js index 23050b8ab..53250b9fb 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesListProvider.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesListProvider.js @@ -2,10 +2,10 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { useResourceViews, - useResourceFields, usePaymentMades, + useResourceMeta } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; const PaymentMadesListContext = createContext(); @@ -21,9 +21,10 @@ function PaymentMadesListProvider({ query, ...props }) { // Fetch the accounts resource fields. const { - data: paymentMadesFields, - isLoading: isFieldsLoading, - } = useResourceFields('bill_payments'); + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('bill_payments'); // Fetch accounts list according to the given custom view id. const { @@ -45,19 +46,22 @@ function PaymentMadesListProvider({ query, ...props }) { paymentMades, pagination, filterMeta, - paymentMadesFields, paymentMadesViews, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + resourceMeta, + isResourceMetaLoading, + isResourceMetaFetching, + isPaymentsLoading, isPaymentsFetching, - isFieldsLoading, isViewsLoading, isEmptyStatus }; return ( diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMdesListProvider.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMdesListProvider.js index dded1da20..4753c0aeb 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMdesListProvider.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMdesListProvider.js @@ -9,20 +9,20 @@ const PaymentMadesContext = createContext(); */ function PaymentMadesProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: paymentsViews, isFetching: isViewsLoading } = useResourceViews( + const { data: paymentsViews, isLoading: isViewsLoading } = useResourceViews( 'bill_payments', ); // Fetch the accounts resource fields. const { data: paymentsFields, - isFetching: isFieldsLoading, + isLoading: isFieldsLoading, } = useResourceFields('bill_payments'); // Fetch accounts list according to the given custom view id. const { data: { paymentMades, pagination }, - isFetching: isPaymentsLoading, + isLoading: isPaymentsLoading, } = usePaymentMades(query); // Provider payload. @@ -39,7 +39,7 @@ function PaymentMadesProvider({ query, ...props }) { return ( diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js index b2358cf1e..3cca7dff5 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js @@ -1,25 +1,27 @@ -import React, { useState } from 'react'; +import React from 'react'; import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; 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 withEstimatesActions from './withEstimatesActions'; +import withEstimates from './withEstimates'; + import { useEstimatesListContext } from './EstimatesListProvider'; import { useRefreshEstimates } from 'hooks/query/estimates'; @@ -31,13 +33,14 @@ import { compose } from 'utils'; function EstimateActionsBar({ // #withEstimateActions setEstimatesTableState, + + // #withEstimates + estimatesFilterRoles }) { const history = useHistory(); - const [filterCount, setFilterCount] = useState(0); - // Estimates list context. - const { estimatesViews } = useEstimatesListContext(); + const { estimatesViews, fields } = useEstimatesListContext(); // Handle click a new sale estimate. const onClickNewEstimate = () => { @@ -55,9 +58,7 @@ function EstimateActionsBar({ }; // Handle click a refresh sale estimates - const handleRefreshBtnClick = () => { - refresh(); - }; + const handleRefreshBtnClick = () => { refresh(); }; return ( @@ -74,23 +75,21 @@ function EstimateActionsBar({ text={} onClick={onClickNewEstimate} /> - { + setEstimatesTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - `${filterCount} ${intl.get('filters_applied')}` - ) - } - icon={} + - + + ({ + estimatesFilterRoles: estimatesTableState.filterRoles, + })), +)(EstimateActionsBar); diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js index 016216290..f093409ee 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js @@ -9,6 +9,7 @@ import EstimatesViewTabs from './EstimatesViewTabs'; import EstimatesDataTable from './EstimatesDataTable'; import withEstimates from './withEstimates'; +import withEstimatesActions from './withEstimatesActions'; import { EstimatesListProvider } from './EstimatesListProvider'; import { compose, transformTableStateToQuery } from 'utils'; @@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils'; function EstimatesList({ // #withEstimate estimatesTableState, + + // #withEstimatesActions + setEstimatesTableState }) { + // Resets the estimates table state once the page unmount. + React.useEffect( + () => () => { + setEstimatesTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setEstimatesTableState], + ); + return ( ({ estimatesTableState })), + withEstimatesActions )(EstimatesList); diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesListProvider.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesListProvider.js index 4c05b9b20..6d1ae9b7e 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesListProvider.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesListProvider.js @@ -1,7 +1,9 @@ import React, { createContext } from 'react'; + 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(); @@ -10,17 +12,17 @@ const EstimatesListContext = createContext(); */ function EstimatesListProvider({ query, ...props }) { // Fetches estimates resource views and fields. - const { data: estimatesViews, isLoading: isViewsLoading } = useResourceViews( - 'sale_estimates', - ); + const { data: estimatesViews, isLoading: isViewsLoading } = + useResourceViews('sale_estimates'); // Fetches the estimates resource fields. const { - data: estimatesFields, - isLoading: isFieldsLoading, - } = useResourceFields('sale_estimates'); + data: resourceMeta, + isLoading: isResourceLoading, + 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 { data: { estimates, pagination, filterMeta }, isLoading: isEstimatesLoading, @@ -39,12 +41,15 @@ function EstimatesListProvider({ query, ...props }) { const provider = { estimates, pagination, - estimatesFields, + + fields: getFieldsFromResourceMeta(resourceMeta.fields), estimatesViews, + isResourceLoading, + isResourceFetching, + isEstimatesLoading, isEstimatesFetching, - isFieldsLoading, isViewsLoading, isEmptyStatus, @@ -52,7 +57,7 @@ function EstimatesListProvider({ query, ...props }) { return ( diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js index 10953864b..7f4f8577d 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js @@ -1,21 +1,19 @@ -import React, { useState } from 'react'; +import React from 'react'; import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; - -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; +import { + FormattedMessage as T, + AdvancedFilterPopover, + DashboardFilterButton, +} from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -23,7 +21,9 @@ import { If, DashboardActionViewsList } from 'components'; import { useRefreshInvoices } from 'hooks/query/invoices'; import { useInvoicesListContext } from './InvoicesListProvider'; + import withInvoiceActions from './withInvoiceActions'; +import withInvoices from './withInvoices'; import { compose } from 'utils'; @@ -33,13 +33,14 @@ import { compose } from 'utils'; function InvoiceActionsBar({ // #withInvoiceActions setInvoicesTableState, + + // #withInvoices + invoicesFilterRoles, }) { const history = useHistory(); - const [filterCount, setFilterCount] = useState(0); - // Sale invoices list context. - const { invoicesViews } = useInvoicesListContext(); + const { invoicesViews, invoicesFields } = useInvoicesListContext(); // Handle new invoice button click. const handleClickNewInvoice = () => { @@ -74,31 +75,27 @@ function InvoiceActionsBar({ text={} onClick={handleClickNewInvoice} /> - { + setInvoicesTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - `${filterCount} ${intl.get('filters_applied')}` - ) - } - icon={} - /> - + + + + + } text={} intent={Intent.DANGER} - // onClick={handleBulkDelete} /> ({ + invoicesFilterRoles: invoicesTableState.filterRoles, + })), +)(InvoiceActionsBar); diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesList.js b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesList.js index a00709247..a55858f6f 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesList.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesList.js @@ -11,6 +11,7 @@ import InvoicesDataTable from './InvoicesDataTable'; import InvoicesAlerts from '../InvoicesAlerts'; import withInvoices from './withInvoices'; +import withInvoiceActions from './withInvoiceActions'; import withAlertsActions from 'containers/Alert/withAlertActions'; import { transformTableStateToQuery, compose } from 'utils'; @@ -21,7 +22,22 @@ import { transformTableStateToQuery, compose } from 'utils'; function InvoicesList({ // #withInvoice invoicesTableState, + + // #withInvoicesActions + setInvoicesTableState }) { + // Resets the invoices table state once the page unmount. + React.useEffect( + () => () => { + setInvoicesTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setInvoicesTableState], + ); + return ( ({ invoicesTableState })), + withInvoiceActions, withAlertsActions, )(InvoicesList); diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesListProvider.js b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesListProvider.js index 8e9b3ecd5..15d09f749 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesListProvider.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesListProvider.js @@ -1,7 +1,7 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useResourceFields, useInvoices } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useResourceViews, useResourceMeta, useInvoices } from 'hooks/query'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; const InvoicesListContext = createContext(); @@ -10,15 +10,15 @@ const InvoicesListContext = createContext(); */ function InvoicesListProvider({ query, ...props }) { // Fetch accounts resource views and fields. - const { data: invoicesViews, isLoading: isViewsLoading } = useResourceViews( - 'sale_invoices', - ); + const { data: invoicesViews, isLoading: isViewsLoading } = + useResourceViews('sale_invoices'); // Fetch the accounts resource fields. const { - data: invoicesFields, - isLoading: isFieldsLoading, - } = useResourceFields('sale_invoices'); + data: resourceMeta, + isLoading: isResourceLoading, + isFetching: isResourceFetching, + } = useResourceMeta('sale_invoices'); // Fetch accounts list according to the given custom view id. const { @@ -39,20 +39,22 @@ function InvoicesListProvider({ query, ...props }) { const provider = { invoices, pagination, - invoicesFields, + + invoicesFields: getFieldsFromResourceMeta(resourceMeta.fields), invoicesViews, isInvoicesLoading, isInvoicesFetching, - isFieldsLoading, + isResourceFetching, + isResourceLoading, isViewsLoading, - isEmptyStatus + isEmptyStatus, }; return ( diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js index 51042b245..8a8c0ecf1 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js @@ -2,10 +2,10 @@ import React, { createContext, useContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { useResourceViews, - useResourceFields, + useResourceMeta, usePaymentReceives, } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; const PaymentReceivesListContext = createContext(); @@ -21,9 +21,10 @@ function PaymentReceivesListProvider({ query, ...props }) { // Fetch the payment receives resource fields. const { - data: paymentReceivesFields, - isFetching: isFieldsLoading, - } = useResourceFields('payment_receives'); + data: resourceMeta, + isLoading: isResourceLoading, + isFetching: isResourceFetching, + } = useResourceMeta('payment_receives'); // Fetch payment receives list according to the given custom view id. const { @@ -44,19 +45,23 @@ function PaymentReceivesListProvider({ query, ...props }) { const state = { paymentReceives, pagination, - paymentReceivesFields, + + resourceMeta, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + paymentReceivesViews, isPaymentReceivesLoading, isPaymentReceivesFetching, - isFieldsLoading, + isResourceFetching, + isResourceLoading, isViewsLoading, isEmptyStatus, }; return ( diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js index c34a75052..81ec2d599 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js @@ -3,18 +3,18 @@ import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; 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'; @@ -33,12 +33,15 @@ import { useRefreshPaymentReceive } from 'hooks/query/paymentReceives'; function PaymentReceiveActionsBar({ // #withPaymentReceivesActions setPaymentReceivesTableState, + + // #withPaymentReceives + paymentFilterConditions }) { // History context. const history = useHistory(); // Payment receives list context. - const { paymentReceivesViews } = usePaymentReceivesListContext(); + const { paymentReceivesViews, fields } = usePaymentReceivesListContext(); // Handle new payment button click. const handleClickNewPaymentReceive = () => { @@ -58,6 +61,8 @@ function PaymentReceiveActionsBar({ refresh(); }; + console.log(fields, 'fields'); + return ( @@ -73,18 +78,21 @@ function PaymentReceiveActionsBar({ text={} onClick={handleClickNewPaymentReceive} /> - { + setPaymentReceivesTableState({ filterRoles: filterConditions }); + }, + }} > - } - icon={} + - + + ({ paymentReceivesTableState, + paymentFilterConditions: paymentReceivesTableState.filterRoles, })), )(PaymentReceiveActionsBar); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js index 852dff346..d6133c4e2 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js @@ -10,6 +10,7 @@ import PaymentReceiveViewTabs from './PaymentReceiveViewTabs'; import PaymentReceivesTable from './PaymentReceivesTable'; import withPaymentReceives from './withPaymentReceives'; +import withPaymentReceivesActions from './withPaymentReceivesActions'; import { compose, transformTableStateToQuery } from 'utils'; @@ -19,7 +20,22 @@ import { compose, transformTableStateToQuery } from 'utils'; function PaymentReceiveList({ // #withPaymentReceives paymentReceivesTableState, + + // #withPaymentReceivesActions + setPaymentReceivesTableState }) { + // Resets the payment receives table state once the page unmount. + React.useEffect( + () => () => { + setPaymentReceivesTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setPaymentReceivesTableState], + ); + return ( ({ paymentReceivesTableState, })), + withPaymentReceivesActions, )(PaymentReceiveList); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js index bb7fa2487..a281aa5d1 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js @@ -2,9 +2,10 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { useResourceViews, - useResourceFields, + useResourceMeta, usePaymentReceives, } from 'hooks/query'; +import { getFieldsFromResourceMeta } from 'utils'; const PaymentReceivesListContext = createContext(); @@ -20,9 +21,10 @@ function PaymentReceivesListProvider({ query, ...props }) { // Fetch the accounts resource fields. const { - data: paymentReceivesFields, - isFetching: isFieldsLoading, - } = useResourceFields('payment_receives'); + data: resourceMeta, + isFetching: isResourceFetching, + isLoading: isResourceLoading, + } = useResourceMeta('payment_receives'); // Fetch accounts list according to the given custom view id. const { @@ -35,18 +37,21 @@ function PaymentReceivesListProvider({ query, ...props }) { const provider = { paymentReceives, paymentReceivesViews, - paymentReceivesFields, pagination, + resourceMeta, + + fields: getFieldsFromResourceMeta(resourceMeta.fields), isViewsLoading, - isFieldsLoading, + isResourceFetching, + isResourceLoading, isPaymentReceivesLoading, isPaymentReceivesFetching }; return ( diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js index 552720af1..91f974565 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js @@ -3,24 +3,25 @@ import Icon from 'components/Icon'; import { Button, Classes, - Popover, NavbarDivider, NavbarGroup, - PopoverInteractionKind, - Position, Intent, Alignment, } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; +import { + AdvancedFilterPopover, + DashboardFilterButton, + FormattedMessage as T, +} from 'components'; import { If, DashboardActionViewsList } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import withReceiptsActions from './withReceiptsActions'; +import withReceipts from './withReceipts'; + import { useReceiptsListContext } from './ReceiptsListProvider'; import { useRefreshReceipts } from 'hooks/query/receipts'; import { compose } from 'utils'; @@ -31,13 +32,14 @@ import { compose } from 'utils'; function ReceiptActionsBar({ // #withReceiptsActions setReceiptsTableState, + + // #withReceipts + receiptsFilterConditions, }) { const history = useHistory(); - const [filterCount, setFilterCount] = useState(0); - // Sale receipts list context. - const { receiptsViews } = useReceiptsListContext(); + const { receiptsViews, fields } = useReceiptsListContext(); // Handle new receipt button click. const onClickNewReceipt = () => { @@ -54,9 +56,9 @@ function ReceiptActionsBar({ }; // Handle click a refresh sale estimates - const handleRefreshBtnClick = () => { - refresh(); - }; + const handleRefreshBtnClick = () => { refresh(); }; + + console.log(receiptsFilterConditions, fields, 'XXX'); return ( @@ -74,24 +76,21 @@ function ReceiptActionsBar({ text={} onClick={onClickNewReceipt} /> - { + setReceiptsTableState({ filterRoles: filterConditions }); + }, + }} > - - ) : ( - `${filterCount} ${intl.get('filters_applied')}` - ) - } - icon={} + - + + ({ + receiptsFilterConditions: receiptTableState.filterRoles, + })), +)(ReceiptActionsBar); diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js index 673c64268..169a4bf2c 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js @@ -9,6 +9,7 @@ import ReceiptsAlerts from '../ReceiptsAlerts'; import ReceiptsTable from './ReceiptsTable'; import withReceipts from './withReceipts'; +import withReceiptsActions from './withReceiptsActions'; import { ReceiptsListProvider } from './ReceiptsListProvider'; import { transformTableStateToQuery, compose } from 'utils'; @@ -19,7 +20,22 @@ import { transformTableStateToQuery, compose } from 'utils'; function ReceiptsList({ // #withReceipts receiptTableState, + + // #withReceiptsActions + setReceiptsTableState, }) { + // Resets the receipts table state once the page unmount. + React.useEffect( + () => () => { + setReceiptsTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setReceiptsTableState], + ); + return ( @@ -43,4 +59,5 @@ export default compose( withReceipts(({ receiptTableState }) => ({ receiptTableState, })), + withReceiptsActions, )(ReceiptsList); diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js index edbccc319..cc7dac755 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsListProvider.js @@ -1,29 +1,30 @@ import React, { createContext } from 'react'; 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(); // Receipts list provider. function ReceiptsListProvider({ query, ...props }) { // Fetch receipts resource views and fields. - const { data: receiptsViews, isLoading: isViewsLoading } = useResourceViews( - 'sale_receipt', - ); + const { data: receiptsViews, isLoading: isViewsLoading } = + useResourceViews('sale_receipt'); // Fetches the sale receipts resource fields. - // const { - // data: receiptsFields, - // isFetching: isFieldsLoading, - // } = useResourceFields('sale_receipt'); + const { + data: resourceMeta, + isFetching: isResourceFetching, + isLoading: isResourceLoading, + } = useResourceMeta('sale_receipt'); const { data: { receipts, pagination, filterMeta }, isLoading: isReceiptsLoading, isFetching: isReceiptsFetching, } = useReceipts(query, { keepPreviousData: true }); - + // Detarmines the datatable empty status. const isEmptyStatus = isTableEmptyStatus({ @@ -35,20 +36,22 @@ function ReceiptsListProvider({ query, ...props }) { const provider = { receipts, pagination, - // receiptsFields, + receiptsViews, isViewsLoading, - // isFieldsLoading, + + resourceMeta, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + isResourceFetching, + isResourceLoading, + isReceiptsLoading, isReceiptsFetching, - isEmptyStatus + isEmptyStatus, }; return ( - + ); diff --git a/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js b/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js index 10d05c93d..ee143530f 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js @@ -5,19 +5,19 @@ import { Button, Classes, Intent, - Popover, - Position, - PopoverInteractionKind, Switch, Alignment, } from '@blueprintjs/core'; import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; -import classNames from 'classnames'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import Icon from 'components/Icon'; -import { If, DashboardActionViewsList } from 'components'; +import { + If, + DashboardActionViewsList, + DashboardFilterButton, + AdvancedFilterPopover, +} from 'components'; import { useRefreshVendors } from 'hooks/query/vendors'; import { useVendorsListContext } from './VendorsListProvider'; @@ -32,6 +32,9 @@ import { compose } from 'utils'; * Vendors actions bar. */ function VendorActionsBar({ + // #withVendors + vendorsFilterConditions, + // #withVendorActions setVendorsTableState, vendorsInactiveMode, @@ -39,7 +42,7 @@ function VendorActionsBar({ const history = useHistory(); // Vendors list context. - const { vendorsViews } = useVendorsListContext(); + const { vendorsViews, fields } = useVendorsListContext(); // Handles new vendor button click. const onClickNewVendor = () => { @@ -81,19 +84,21 @@ function VendorActionsBar({ onClick={onClickNewVendor} /> - { + setVendorsTableState({ filterRoles: filterConditions }); + }, + }} > - : `${9} ${intl.get('filters_applied')}` - } - icon={} + - + + ({ vendorsInactiveMode: vendorsTableState.inactiveMode, + vendorsFilterConditions: vendorsTableState.filterRoles, })), )(VendorActionsBar); diff --git a/client/src/containers/Vendors/VendorsLanding/VendorsList.js b/client/src/containers/Vendors/VendorsLanding/VendorsList.js index 7eb934f45..4836434f4 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorsList.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorsList.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import 'style/pages/Vendors/List.scss'; @@ -11,6 +11,7 @@ import VendorsAlerts from '../VendorsAlerts'; import VendorsTable from './VendorsTable'; import withVendors from './withVendors'; +import withVendorsActions from './withVendorsActions'; import { compose } from 'utils'; @@ -20,7 +21,22 @@ import { compose } from 'utils'; function VendorsList({ // #withVendors vendorsTableState, + + // #withVendorsActions + setVendorsTableState }) { + // Resets the vendors table state once the page unmount. + useEffect( + () => () => { + setVendorsTableState({ + filterRoles: [], + viewSlug: '', + pageIndex: 0, + }); + }, + [setVendorsTableState], + ); + return ( @@ -40,4 +56,5 @@ function VendorsList({ export default compose( withVendors(({ vendorsTableState }) => ({ vendorsTableState })), + withVendorsActions )(VendorsList); diff --git a/client/src/containers/Vendors/VendorsLanding/VendorsListProvider.js b/client/src/containers/Vendors/VendorsLanding/VendorsListProvider.js index 163f9395d..10e493851 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorsListProvider.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorsListProvider.js @@ -1,8 +1,8 @@ import React, { createContext } from 'react'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; -import { useResourceViews, useVendors } from 'hooks/query'; -import { isTableEmptyStatus } from 'utils'; +import { useResourceMeta, useResourceViews, useVendors } from 'hooks/query'; +import { isTableEmptyStatus, getFieldsFromResourceMeta } from 'utils'; import { transformVendorsStateToQuery } from './utils'; const VendorsListContext = createContext(); @@ -22,6 +22,13 @@ function VendorsListProvider({ tableState, ...props }) { const { data: vendorsViews, isLoading: isVendorsViewsLoading } = useResourceViews('vendors'); + // Fetch the customers resource fields. + const { + data: resourceMeta, + isLoading: isResourceMetaLoading, + isFetching: isResourceMetaFetching, + } = useResourceMeta('customers'); + // Detarmines the datatable empty status. const isEmptyStatus = isTableEmptyStatus({ @@ -37,15 +44,23 @@ function VendorsListProvider({ tableState, ...props }) { pagination, vendorsViews, - isVendorsLoading, - isVendorsFetching, + fields: getFieldsFromResourceMeta(resourceMeta.fields), + resourceMeta, + isResourceMetaLoading, + isResourceMetaFetching, + isVendorsViewsLoading, + isVendorsLoading, + isVendorsFetching, isEmptyStatus, }; return ( - + ); diff --git a/client/src/hooks/query/views.js b/client/src/hooks/query/views.js index 1f16e3918..42e2b4893 100644 --- a/client/src/hooks/query/views.js +++ b/client/src/hooks/query/views.js @@ -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. */ -export function useResourceFields(resourceSlug, props) { +export function useResourceMeta(resourceSlug, props) { return useRequestQuery( - ['RESOURCE_FIELDS', resourceSlug], - { method: 'get', url: `resources/${resourceSlug}/fields` }, + ['RESOURCE_META', resourceSlug], + { method: 'get', url: `resources/${resourceSlug}/meta` }, { - select: (res) => res.data.resource_fields, - defaultData: [], + select: (res) => res.data.resource_meta, + defaultData: { + fields: {}, + }, }, - props + props, ); -} +} \ No newline at end of file diff --git a/client/src/hooks/useRequest.js b/client/src/hooks/useRequest.js index 5993d7f67..6f15ea272 100644 --- a/client/src/hooks/useRequest.js +++ b/client/src/hooks/useRequest.js @@ -6,10 +6,12 @@ import { useSetGlobalErrors, useAuthToken, } from './state'; +import { useAppIntlContext } from '../components/AppIntlProvider'; export default function useApiRequest() { const setGlobalErrors = useSetGlobalErrors(); const { setLogout } = useAuthActions(); + const { currentLocale } = useAppIntlContext(); // Authentication token. const token = useAuthToken(); @@ -24,7 +26,7 @@ export default function useApiRequest() { // Request interceptors. instance.interceptors.request.use( (request) => { - const locale = 'ar'; + const locale = currentLocale; if (token) { request.headers.common['X-Access-Token'] = token; diff --git a/client/src/lang/en/index.json b/client/src/lang/en/index.json index 9f42b9557..a55aed880 100644 --- a/client/src/lang/en/index.json +++ b/client/src/lang/en/index.json @@ -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?", "after": "After", "before": "Before", - "count_filters_applied": "{count} filters applied", + "count_filters_applied": "{count} Filters applied", "is": "Is", "is_not": "Is Not", "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", "publish_adjustment": "Publish adjustment", "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" } \ No newline at end of file diff --git a/client/src/store/Bills/bills.reducer.js b/client/src/store/Bills/bills.reducer.js index 0d58b54c5..71e538182 100644 --- a/client/src/store/Bills/bills.reducer.js +++ b/client/src/store/Bills/bills.reducer.js @@ -8,6 +8,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [] }, }; @@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:bills'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/Estimate/estimates.reducer.js b/client/src/store/Estimate/estimates.reducer.js index 7ddcdba12..e61cbf3e2 100644 --- a/client/src/store/Estimate/estimates.reducer.js +++ b/client/src/store/Estimate/estimates.reducer.js @@ -10,6 +10,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [], }, }; @@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:estimates'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/Invoice/invoices.reducer.js b/client/src/store/Invoice/invoices.reducer.js index e6492a545..a7b037ac4 100644 --- a/client/src/store/Invoice/invoices.reducer.js +++ b/client/src/store/Invoice/invoices.reducer.js @@ -10,6 +10,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [] }, }; @@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:invoices'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/PaymentMades/paymentMades.reducer.js b/client/src/store/PaymentMades/paymentMades.reducer.js index c1531fec0..bc63df771 100644 --- a/client/src/store/PaymentMades/paymentMades.reducer.js +++ b/client/src/store/PaymentMades/paymentMades.reducer.js @@ -10,6 +10,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [], sortBy: [], }, }; diff --git a/client/src/store/PaymentReceives/paymentReceives.reducer.js b/client/src/store/PaymentReceives/paymentReceives.reducer.js index 2bddc3138..a49afa76d 100644 --- a/client/src/store/PaymentReceives/paymentReceives.reducer.js +++ b/client/src/store/PaymentReceives/paymentReceives.reducer.js @@ -10,6 +10,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [], }, }; @@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:paymentReceives'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/accounts/accounts.reducer.js b/client/src/store/accounts/accounts.reducer.js index e486b131d..deb7d15ba 100644 --- a/client/src/store/accounts/accounts.reducer.js +++ b/client/src/store/accounts/accounts.reducer.js @@ -5,14 +5,18 @@ import { createTableStateReducers } from 'store/tableState.reducer'; import t from 'store/types'; const initialState = { - tableState: {}, + tableState: { + pageSize: 12, + pageIndex: 0, + filterRoles: [], + }, }; const STORAGE_KEY = 'bigcapital:accounts'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/customers/customers.reducer.js b/client/src/store/customers/customers.reducer.js index eaf82cff0..93b68a76a 100644 --- a/client/src/store/customers/customers.reducer.js +++ b/client/src/store/customers/customers.reducer.js @@ -8,6 +8,7 @@ const initialState = { pageSize: 12, pageIndex: 0, inactiveMode: false, + filterRoles: [] }, }; @@ -20,7 +21,7 @@ const STORAGE_KEY = 'bigcapital:estimates'; export default persistReducer( { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }, reducerInstance, diff --git a/client/src/store/expenses/expenses.reducer.js b/client/src/store/expenses/expenses.reducer.js index 318360938..473102d95 100644 --- a/client/src/store/expenses/expenses.reducer.js +++ b/client/src/store/expenses/expenses.reducer.js @@ -8,6 +8,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [], }, }; @@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:expenses'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/itemCategories/itemsCategory.reducer.js b/client/src/store/itemCategories/itemsCategory.reducer.js index fb0d90b3a..5445ef7f6 100644 --- a/client/src/store/itemCategories/itemsCategory.reducer.js +++ b/client/src/store/itemCategories/itemsCategory.reducer.js @@ -8,14 +8,16 @@ import t from 'store/types'; // Initial state. const initialState = { - tableState: {}, + tableState: { + filterRoles: [] + }, }; const STORAGE_KEY = 'bigcapital:itemCategories'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/itemCategories/itemsCateory.reducer.js b/client/src/store/itemCategories/itemsCateory.reducer.js index a484472d4..ed6011b8c 100644 --- a/client/src/store/itemCategories/itemsCateory.reducer.js +++ b/client/src/store/itemCategories/itemsCateory.reducer.js @@ -2,6 +2,9 @@ import t from 'store/types'; import { createReducer } from '@reduxjs/toolkit'; const initialState = { + tableState: { + filterRoles: [], + }, categories: {}, loading: false, }; diff --git a/client/src/store/items/items.reducer.js b/client/src/store/items/items.reducer.js index 41bb7d3a4..234301292 100644 --- a/client/src/store/items/items.reducer.js +++ b/client/src/store/items/items.reducer.js @@ -8,7 +8,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, - filters: [], + filterRoles: [], inactiveMode: false, }, selectedRows: [], diff --git a/client/src/store/manualJournals/manualJournals.reducers.js b/client/src/store/manualJournals/manualJournals.reducers.js index b041a63f4..058d1fb60 100644 --- a/client/src/store/manualJournals/manualJournals.reducers.js +++ b/client/src/store/manualJournals/manualJournals.reducers.js @@ -8,6 +8,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [], }, }; @@ -15,7 +16,7 @@ const STORAGE_KEY = 'bigcapital:manualJournals'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/receipts/receipts.reducer.js b/client/src/store/receipts/receipts.reducer.js index 5401eb97a..9490c12f4 100644 --- a/client/src/store/receipts/receipts.reducer.js +++ b/client/src/store/receipts/receipts.reducer.js @@ -10,6 +10,7 @@ const initialState = { tableState: { pageSize: 12, pageIndex: 0, + filterRoles: [] }, }; @@ -17,7 +18,7 @@ const STORAGE_KEY = 'bigcapital:receipts'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/store/vendors/vendors.reducer.js b/client/src/store/vendors/vendors.reducer.js index c1f5bca8b..a89aa1007 100644 --- a/client/src/store/vendors/vendors.reducer.js +++ b/client/src/store/vendors/vendors.reducer.js @@ -9,6 +9,7 @@ const initialState = { pageSize: 12, pageIndex: 0, inactiveMode: false, + filterRoles: [], }, }; @@ -16,7 +17,7 @@ const STORAGE_KEY = 'bigcapital:vendors'; const CONFIG = { key: STORAGE_KEY, - whitelist: ['tableState'], + whitelist: [], storage, }; diff --git a/client/src/style/objects/buttons.scss b/client/src/style/objects/buttons.scss index 44b18a6be..b8c0f2086 100644 --- a/client/src/style/objects/buttons.scss +++ b/client/src/style/objects/buttons.scss @@ -7,6 +7,10 @@ padding-right: 12px; } +.bp3-button:not([class*='bp3-intent-']) { + color: #33304a; +} + .bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) { color: #555555; box-shadow: 0 0 0 transparent; diff --git a/client/src/style/views/filter-dropdown.scss b/client/src/style/views/filter-dropdown.scss index d4f4555a2..d3a7d9eac 100644 --- a/client/src/style/views/filter-dropdown.scss +++ b/client/src/style/views/filter-dropdown.scss @@ -1,121 +1,115 @@ .filter-dropdown{ - width: 550px; + width: 600px; - &__body{ - padding: 12px; + &__form{ + + } + + + &__conditions-wrap{ + + } + + &__conditions{ + padding: 6px 0 } &__condition{ 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{ padding: 0 6px; } - .bp3-html-select select, - .bp3-select select, - .bp3-input-group .bp3-input{ - height: 32px; - border-radius: 3px; - border-color: #dbd8d8; - } + .form-group--select-list .bp3-popover-target .bp3-button{ + padding-left: 6px; + } .bp3-html-select::after, .form-group--select-list .bp3-button::after{ - border-top-color: #aaa; - margin-right: 8px; + margin-right: 6px; + 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{ - border-top: 1px solid #e8e8e8; - padding: 5px 10px; - } - .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; - } - } + padding: 10px 12px; + margin-top: -6px; } } -.list-select--filter-dropdown{ +.bp3-popover{ - .bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal), - .bp3-button:not([class*="bp3-intent-"]):not(.bp3-minimal){ - &, - &:hover{ - background-color: #E6EFFB; - border: 0; - border-radius: 3px; - } - - &:after{ - border-top-color: #afb9d0; - } + &, + & .bp3-popover-content{ + border-radius: 5px; } } -.popover--list-select-filter-dropdown{ - - .bp3-popover-content{ - max-width: 200px; +.bp3-menu-item{ + .text-hint{ + font-size: 11px; + 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; - } - } } \ No newline at end of file diff --git a/client/src/utils.js b/client/src/utils.js index f5e1243e7..2b6b38291 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -512,6 +512,10 @@ export function getPagesCountFromPaginationMeta(pagination) { return Math.ceil(total / pageSize); } +function transformFilterRoles(filterRoles) { + return JSON.stringify(filterRoles); +} + /** * Transformes the table state to url query. */ @@ -521,6 +525,13 @@ export function transformTableStateToQuery(tableState) { const query = { pageSize, page: pageIndex + 1, + ...(tableState.filterRoles + ? { + stringified_filter_roles: transformFilterRoles( + tableState.filterRoles, + ), + } + : {}), ...(viewSlug ? { viewSlug } : {}), ...(Array.isArray(sortBy) && sortBy.length > 0 ? { @@ -691,10 +702,14 @@ export function nestedArrayToflatten( parseItem = (a, level) => a, level = 1, ) { - const parseObject = (obj) => parseItem({ - ..._.omit(obj, [property]), - level, - }, level); + const parseObject = (obj) => + parseItem( + { + ..._.omit(obj, [property]), + level, + }, + level, + ); return collection.reduce((items, currentValue, index) => { let localItems = [...items]; @@ -713,3 +728,27 @@ export function nestedArrayToflatten( 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', + +} \ No newline at end of file diff --git a/server/.env.example b/server/.env.example index cc54ba76b..fa7aed49b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -16,7 +16,7 @@ TENANT_DB_CLIENT=mysql TENANT_DB_NAME_PERFIX=bigcapital_tenant_ TENANT_DB_HOST=127.0.0.1 TENANT_DB_PASSWORD=root -TEANNT_DB_USER=root +TENANT_DB_USER=root TENANT_DB_CHARSET=utf8 TENANT_MIGRATIONS_DIR=src/database/migrations TENANT_SEEDS_DIR=src/database/seeds/core diff --git a/server/src/api/controllers/Resources.ts b/server/src/api/controllers/Resources.ts index f10b4cb53..5d679f37f 100644 --- a/server/src/api/controllers/Resources.ts +++ b/server/src/api/controllers/Resources.ts @@ -1,7 +1,6 @@ import { Service, Inject } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; -import { param, query } from 'express-validator'; -import asyncMiddleware from 'api/middleware/asyncMiddleware'; +import { param } from 'express-validator'; import BaseController from './BaseController'; import { ServiceError } from 'exceptions'; import ResourceService from 'services/Resource/ResourceService'; @@ -19,32 +18,15 @@ export default class ResourceController extends BaseController { router.get( '/:resource_model/meta', - [...this.resourceModelParamSchema], + [ + param('resource_model').exists().trim().escape() + ], this.asyncMiddleware(this.resourceMeta.bind(this)), 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; } - get resourceModelParamSchema() { - return [param('resource_model').exists().trim().escape()]; - } - /** * Retrieve resource model meta. * @param {Request} req - @@ -52,7 +34,7 @@ export default class ResourceController extends BaseController { * @param {NextFunction} next - * @returns {Response} */ - private resourceMeta = ( + public resourceMeta = ( req: Request, res: Response, 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. * @param {Error} error diff --git a/server/src/models/Account.Settings.ts b/server/src/models/Account.Settings.ts index 5494a7abb..61a2f6caf 100644 --- a/server/src/models/Account.Settings.ts +++ b/server/src/models/Account.Settings.ts @@ -23,6 +23,7 @@ export default { column: 'slug', fieldType: 'text', columnable: false, + filterable: false, }, code: { name: 'Account code', @@ -76,6 +77,7 @@ export default { name: 'Currency', column: 'currency_code', fieldType: 'text', + filterable: false, }, created_at: { name: 'Created at', diff --git a/server/src/models/Bill.Settings.ts b/server/src/models/Bill.Settings.ts index 94d27fd58..bb67c7416 100644 --- a/server/src/models/Bill.Settings.ts +++ b/server/src/models/Bill.Settings.ts @@ -48,12 +48,12 @@ export default { fieldType: 'enumeration', columnable: true, options: [ - { name: 'Paid', key: 'paid' }, - { name: 'Partially paid', key: 'partially-paid' }, - { name: 'Overdue', key: 'overdue' }, - { name: 'Unpaid', key: 'unpaid' }, - { name: 'Opened', key: 'opened' }, - { name: 'Draft', key: 'draft' }, + { label: 'Paid', key: 'paid' }, + { label: 'Partially paid', key: 'partially-paid' }, + { label: 'Overdue', key: 'overdue' }, + { label: 'Unpaid', key: 'unpaid' }, + { label: 'Opened', key: 'opened' }, + { label: 'Draft', key: 'draft' }, ], filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, diff --git a/server/src/models/Customer.Settings.ts b/server/src/models/Customer.Settings.ts index 59e26cf57..c709e9b33 100644 --- a/server/src/models/Customer.Settings.ts +++ b/server/src/models/Customer.Settings.ts @@ -43,6 +43,7 @@ export default { created_at: { name: 'Created at', column: 'created_at', + fieldType: 'date', }, balance: { name: 'Balance', @@ -61,11 +62,13 @@ export default { fieldType: 'date', }, currency_code: { + name: 'Curreny', column: 'currency_code', fieldType: 'text', }, status: { - label: 'Status', + name: 'Status', + fieldType: 'enumeration', options: [ { key: 'active', label: 'Active' }, { key: 'inactive', label: 'Inactive' }, diff --git a/server/src/models/Expense.Settings.ts b/server/src/models/Expense.Settings.ts index 091c9b6cd..4f7a91575 100644 --- a/server/src/models/Expense.Settings.ts +++ b/server/src/models/Expense.Settings.ts @@ -48,8 +48,8 @@ export default { name: 'Status', fieldType: 'enumeration', options: [ - { key: 'draft', name: 'Draft' }, - { key: 'published', name: 'Published' }, + { key: 'draft', label: 'Draft' }, + { key: 'published', label: 'Published' }, ], filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, diff --git a/server/src/models/ItemCategory.Settings.ts b/server/src/models/ItemCategory.Settings.ts index c47a075c8..bad8956ef 100644 --- a/server/src/models/ItemCategory.Settings.ts +++ b/server/src/models/ItemCategory.Settings.ts @@ -6,23 +6,23 @@ export default { }, fields: { name: { - label: 'Name', + name: 'Name', column: 'name', fieldType: 'text', }, description: { - label: 'Description', + name: 'Description', column: 'description', fieldType: 'text', }, count: { - label: 'Count', + name: 'Count', column: 'count', fieldType: 'number', virtualColumn: true, }, created_at: { - label: 'Created at', + name: 'Created at', column: 'created_at', columnType: 'date', }, diff --git a/server/src/models/ManualJournal.Settings.ts b/server/src/models/ManualJournal.Settings.ts index 8579d90dc..f506e1626 100644 --- a/server/src/models/ManualJournal.Settings.ts +++ b/server/src/models/ManualJournal.Settings.ts @@ -6,43 +6,47 @@ export default { }, fields: { 'date': { - label: 'Date', + name: 'Date', column: 'date', fieldType: 'date', }, 'journal_number': { - label: 'Journal number', + name: 'Journal number', column: 'journal_number', fieldType: 'text', }, 'reference': { - label: 'Reference No.', + name: 'Reference No.', column: 'reference', fieldType: 'text', }, 'journal_type': { - label: 'Journal type', + name: 'Journal type', column: 'journal_type', fieldType: 'text', }, 'amount': { - label: 'Amount', + name: 'Amount', column: 'amount', - columnType: 'number', + fieldType: 'number', }, 'description': { - label: 'Description', + name: 'Description', column: 'description', fieldType: 'text', }, 'status': { - label: 'Status', + name: 'Status', column: 'status', fieldType: 'enumeration', + options: [ + { key: 'draft', label: 'Draft' }, + { key: 'published', label: 'published' } + ], sortCustomQuery: StatusFieldSortQuery, }, 'created_at': { - label: 'Created at', + name: 'Created at', column: 'created_at', fieldType: 'date', }, diff --git a/server/src/models/ModelSetting.ts b/server/src/models/ModelSetting.ts index 60b8196ff..697b8af6e 100644 --- a/server/src/models/ModelSetting.ts +++ b/server/src/models/ModelSetting.ts @@ -26,8 +26,8 @@ export default (Model) => * @param {string} key * @returns */ - public static getMeta(key: string) { - return get(this.meta, key); + public static getMeta(key?: string) { + return key ? get(this.meta, key): this.meta; } /** diff --git a/server/src/models/SaleEstimate.Settings.ts b/server/src/models/SaleEstimate.Settings.ts index 59f011850..7efdca4bb 100644 --- a/server/src/models/SaleEstimate.Settings.ts +++ b/server/src/models/SaleEstimate.Settings.ts @@ -55,11 +55,11 @@ export default { name: 'Status', fieldType: 'enumeration', options: [ - { name: 'Delivered', key: 'delivered' }, - { name: 'Rejected', key: 'rejected' }, - { name: 'Approved', key: 'approved' }, - { name: 'Delivered', key: 'delivered' }, - { name: 'Draft', key: 'draft' }, + { label: 'Delivered', key: 'delivered' }, + { label: 'Rejected', key: 'rejected' }, + { label: 'Approved', key: 'approved' }, + { label: 'Delivered', key: 'delivered' }, + { label: 'Draft', key: 'draft' }, ], filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, diff --git a/server/src/models/SaleInvoice.Settings.ts b/server/src/models/SaleInvoice.Settings.ts index d87634d43..3e3334bd6 100644 --- a/server/src/models/SaleInvoice.Settings.ts +++ b/server/src/models/SaleInvoice.Settings.ts @@ -64,15 +64,14 @@ export default { }, status: { name: 'Status', - columnable: true, fieldType: 'enumeration', options: [ - { key: 'draft', name: 'Draft' }, - { key: 'delivered', name: 'Delivered' }, - { key: 'unpaid', name: 'Unpaid' }, - { key: 'overdue', name: 'Overdue' }, - { key: 'partially-paid', name: 'Partially paid' }, - { key: 'paid', name: 'Paid' }, + { key: 'draft', label: 'Draft' }, + { key: 'delivered', label: 'Delivered' }, + { key: 'unpaid', label: 'Unpaid' }, + { key: 'overdue', label: 'Overdue' }, + { key: 'partially-paid', label: 'Partially paid' }, + { key: 'paid', label: 'Paid' }, ], filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, @@ -97,4 +96,4 @@ function StatusFieldFilterQuery(query, role) { */ function StatusFieldSortQuery(query, role) { query.modify('sortByStatus', role.order); -} \ No newline at end of file +} diff --git a/server/src/models/SaleReceipt.Settings.ts b/server/src/models/SaleReceipt.Settings.ts index 023a1c684..098e86554 100644 --- a/server/src/models/SaleReceipt.Settings.ts +++ b/server/src/models/SaleReceipt.Settings.ts @@ -22,8 +22,8 @@ export default { relationEntityKey: 'slug', }, 'customer': { - name: 'Customer', - column: 'customer_id', + label: 'Customer', + name: 'customer_id', fieldType: 'relation', relationType: 'enumeration', @@ -67,8 +67,8 @@ export default { name: 'Status', fieldType: 'enumeration', options: [ - { key: 'draft', name: 'Draft' }, - { key: 'closed', name: 'Closed' }, + { key: 'draft', label: 'Draft' }, + { key: 'closed', label: 'Closed' }, ], filterCustomQuery: StatusFieldFilterQuery, sortCustomQuery: StatusFieldSortQuery, diff --git a/server/src/models/Vendor.Settings.ts b/server/src/models/Vendor.Settings.ts index d9186b8c1..5bd8b48d6 100644 --- a/server/src/models/Vendor.Settings.ts +++ b/server/src/models/Vendor.Settings.ts @@ -71,7 +71,8 @@ export default { fieldType: 'text', }, 'status': { - label: 'Status', + name: 'Status', + type: 'enumeration', options: [ { key: 'overdue', label: 'Overdue' }, { key: 'unpaid', label: 'Unpaid' }, diff --git a/server/src/services/Resource/ResourceService.ts b/server/src/services/Resource/ResourceService.ts index f672e8e90..ba4a71b0d 100644 --- a/server/src/services/Resource/ResourceService.ts +++ b/server/src/services/Resource/ResourceService.ts @@ -3,9 +3,6 @@ import { camelCase, upperFirst } from 'lodash'; import pluralize from 'pluralize'; import { buildFilter } from 'objection-filter'; import { IModel, IModelMeta } from 'interfaces'; -import { - getModelFields, -} from 'lib/ViewRolesBuilder' import TenancyService from 'services/Tenancy/TenancyService'; import { ServiceError } from 'exceptions'; @@ -20,62 +17,12 @@ export default class ResourceService { /** * Transform resource to model name. - * @param {string} resourceName + * @param {string} resourceName */ private resourceToModelName(resourceName: string): string { 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. * @param {number} tenantId - @@ -91,29 +38,18 @@ export default class ResourceService { 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. - * @param {number} tenantId - * @param {string} modelName + * @param {number} tenantId + * @param {string} modelName * @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 settings = resourceModel.meta(); - - return settings; + return resourceModel.getMeta(metakey); } -} \ No newline at end of file +}