@k-int/stripes-kint-components 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/es/index.js +124 -4
- package/es/lib/ActionList/ActionList.js +7 -2
- package/es/lib/ActionList/ActionListFieldArray.js +49 -10
- package/es/lib/CustomProperties/Config/CustomPropertiesSettings.js +2 -2
- package/es/lib/CustomProperties/Config/{CustomPropertiesView.js → CustomPropertyView.js} +5 -5
- package/es/lib/CustomProperties/Config/index.js +6 -4
- package/es/lib/CustomProperties/Edit/CustomPropertiesEdit.js +72 -0
- package/es/lib/CustomProperties/Edit/CustomPropertiesEditCtx.js +133 -0
- package/es/lib/CustomProperties/Edit/CustomPropertiesListField.js +279 -0
- package/es/lib/CustomProperties/Edit/CustomPropertyField.js +370 -0
- package/es/lib/CustomProperties/Edit/CustomPropertyFormCard.js +156 -0
- package/es/lib/CustomProperties/Edit/index.js +51 -0
- package/es/lib/CustomProperties/Filter/CustomPropertiesFilter.js +216 -0
- package/es/lib/CustomProperties/Filter/CustomPropertiesFilterField.js +236 -0
- package/es/lib/CustomProperties/Filter/CustomPropertiesFilterFieldArray.js +159 -0
- package/es/lib/CustomProperties/Filter/CustomPropertiesFilterForm.js +119 -0
- package/es/lib/CustomProperties/Filter/CustomPropertiesRule.js +173 -0
- package/es/lib/CustomProperties/Filter/index.js +59 -0
- package/es/lib/CustomProperties/Filter/useOperators.js +138 -0
- package/es/lib/CustomProperties/Filter/useParseActiveFilterStrings.js +97 -0
- package/es/lib/CustomProperties/Filter/useValueProps.js +101 -0
- package/es/lib/CustomProperties/View/CustomPropertiesView.js +73 -0
- package/es/lib/CustomProperties/View/CustomPropertiesViewCtx.js +187 -0
- package/es/lib/CustomProperties/View/CustomPropertyCard.js +204 -0
- package/es/lib/CustomProperties/View/index.js +35 -0
- package/es/lib/CustomProperties/index.js +125 -0
- package/es/lib/EditableRefdataList/EditableRefdataList.js +12 -16
- package/es/lib/FormModal/FormModal.js +18 -4
- package/es/lib/QueryTypedown/QueryTypedown.js +9 -4
- package/es/lib/constants/customProperties.js +4 -1
- package/es/lib/hooks/index.js +16 -0
- package/es/lib/hooks/typedownHooks/useTypedownData.js +9 -2
- package/es/lib/hooks/useAvailableCustomProperties.js +106 -0
- package/es/lib/hooks/useInvalidateRefdata.js +53 -0
- package/es/lib/hooks/useMutateRefdataValue.js +11 -6
- package/es/lib/hooks/useRefdata.js +1 -3
- package/es/lib/utils/groupCustomPropertiesByCtx.js +69 -0
- package/es/lib/utils/index.js +24 -0
- package/es/lib/utils/refdataQueryKey.js +48 -0
- package/es/lib/utils/typedownQueryKey.js +48 -0
- package/es/lib/utils/validators.js +60 -1
- package/git_translate.sh +8 -0
- package/package.json +1 -1
- package/src/index.js +27 -3
- package/src/lib/ActionList/ActionList.js +5 -2
- package/src/lib/ActionList/ActionListFieldArray.js +31 -8
- package/src/lib/ActionList/README.md +23 -20
- package/src/lib/CustomProperties/Config/CustomPropertiesSettings.js +2 -2
- package/src/lib/CustomProperties/Config/{CustomPropertiesView.js → CustomPropertyView.js} +3 -3
- package/src/lib/CustomProperties/Config/index.js +1 -1
- package/src/lib/CustomProperties/Edit/CustomPropertiesEdit.js +35 -0
- package/src/lib/CustomProperties/Edit/CustomPropertiesEditCtx.js +85 -0
- package/src/lib/CustomProperties/Edit/CustomPropertiesListField.js +194 -0
- package/src/lib/CustomProperties/Edit/CustomPropertyField.js +299 -0
- package/src/lib/CustomProperties/Edit/CustomPropertyFormCard.js +131 -0
- package/src/lib/CustomProperties/Edit/index.js +5 -0
- package/src/lib/CustomProperties/Filter/CustomPropertiesFilter.js +125 -0
- package/src/lib/CustomProperties/Filter/CustomPropertiesFilterField.js +148 -0
- package/src/lib/CustomProperties/Filter/CustomPropertiesFilterFieldArray.js +113 -0
- package/src/lib/CustomProperties/Filter/CustomPropertiesFilterForm.js +74 -0
- package/src/lib/CustomProperties/Filter/CustomPropertiesRule.js +122 -0
- package/src/lib/CustomProperties/Filter/index.js +6 -0
- package/src/lib/CustomProperties/Filter/useOperators.js +55 -0
- package/src/lib/CustomProperties/Filter/useParseActiveFilterStrings.js +35 -0
- package/src/lib/CustomProperties/Filter/useValueProps.js +45 -0
- package/src/lib/CustomProperties/View/CustomPropertiesView.js +36 -0
- package/src/lib/CustomProperties/View/CustomPropertiesViewCtx.js +112 -0
- package/src/lib/CustomProperties/View/CustomPropertyCard.js +177 -0
- package/src/lib/CustomProperties/View/index.js +3 -0
- package/src/lib/CustomProperties/index.js +30 -0
- package/src/lib/EditableRefdataList/EditableRefdataList.js +13 -10
- package/src/lib/FormModal/FormModal.js +37 -17
- package/src/lib/QueryTypedown/QueryTypedown.js +3 -1
- package/src/lib/constants/customProperties.js +1 -0
- package/src/lib/hooks/index.js +2 -0
- package/src/lib/hooks/typedownHooks/useTypedownData.js +9 -3
- package/src/lib/hooks/useAvailableCustomProperties.js +40 -0
- package/src/lib/hooks/useInvalidateRefdata.js +11 -0
- package/src/lib/hooks/useMutateRefdataValue.js +7 -3
- package/src/lib/hooks/useRefdata.js +2 -3
- package/src/lib/utils/groupCustomPropertiesByCtx.js +13 -0
- package/src/lib/utils/index.js +5 -0
- package/src/lib/utils/refdataQueryKey.js +9 -0
- package/src/lib/utils/typedownQueryKey.js +9 -0
- package/src/lib/utils/validators.js +40 -0
- package/translate.sh +63 -0
- package/translations/stripes-kint-components/ar.json +105 -0
- package/translations/stripes-kint-components/ca.json +1 -0
- package/translations/stripes-kint-components/cs_CZ.json +105 -0
- package/translations/stripes-kint-components/da.json +1 -0
- package/translations/stripes-kint-components/de.json +105 -0
- package/translations/stripes-kint-components/en.json +54 -2
- package/translations/stripes-kint-components/es.json +105 -0
- package/translations/stripes-kint-components/fr.json +105 -0
- package/translations/stripes-kint-components/he.json +1 -0
- package/translations/stripes-kint-components/hi_IN.json +105 -0
- package/translations/stripes-kint-components/hu.json +105 -0
- package/translations/stripes-kint-components/it_IT.json +105 -0
- package/translations/stripes-kint-components/ja.json +105 -0
- package/translations/stripes-kint-components/ko.json +105 -0
- package/translations/stripes-kint-components/nb.json +1 -0
- package/translations/stripes-kint-components/nn.json +1 -0
- package/translations/stripes-kint-components/pl.json +105 -0
- package/translations/stripes-kint-components/pt_PT.json +105 -0
- package/translations/stripes-kint-components/ru.json +105 -0
- package/translations/stripes-kint-components/sv.json +105 -0
- package/translations/stripes-kint-components/ur.json +1 -0
- package/translations/stripes-kint-components/zh_CN.json +105 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as CustomPropertiesEdit } from './CustomPropertiesEdit';
|
|
2
|
+
export { default as CustomPropertiesEditCtx } from './CustomPropertiesEditCtx';
|
|
3
|
+
export { default as CustomPropertiesListField } from './CustomPropertiesListField';
|
|
4
|
+
export { default as CustomPropertyFormCard } from './CustomPropertyFormCard';
|
|
5
|
+
export { default as CustomPropertyField } from './CustomPropertyField';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Accordion,
|
|
7
|
+
FilterAccordionHeader,
|
|
8
|
+
Layout,
|
|
9
|
+
Spinner
|
|
10
|
+
} from '@folio/stripes/components';
|
|
11
|
+
import { useCustomProperties } from '../../hooks';
|
|
12
|
+
import useParseActiveFilterStrings from './useParseActiveFilterStrings';
|
|
13
|
+
import CustomPropertiesFilterForm from './CustomPropertiesFilterForm';
|
|
14
|
+
|
|
15
|
+
const CustomPropertiesFilter = ({
|
|
16
|
+
activeFilters: {
|
|
17
|
+
customProperties: custPropFilters,
|
|
18
|
+
...restOfFilters
|
|
19
|
+
},
|
|
20
|
+
customPropertiesEndpoint,
|
|
21
|
+
filterHandlers,
|
|
22
|
+
labelOverrides
|
|
23
|
+
}) => {
|
|
24
|
+
const [editingFilters, setEditingFilters] = useState(false);
|
|
25
|
+
const openEditModal = () => setEditingFilters(true);
|
|
26
|
+
const closeEditModal = () => setEditingFilters(false);
|
|
27
|
+
|
|
28
|
+
const { data: custprops, isLoading } = useCustomProperties({
|
|
29
|
+
endpoint: customPropertiesEndpoint,
|
|
30
|
+
returnQueryObject: true,
|
|
31
|
+
options: {
|
|
32
|
+
sort: [
|
|
33
|
+
{ path: 'ctx' }, // Group by ctx
|
|
34
|
+
{ path: 'retired' }, // Place retired custprops at the end
|
|
35
|
+
{ path: 'label' } // Within those groups, sort by label
|
|
36
|
+
],
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const parsedFilterData = useParseActiveFilterStrings(custPropFilters || []);
|
|
40
|
+
|
|
41
|
+
const getFiltersApplied = () => {
|
|
42
|
+
if (labelOverrides.filtersApplied && typeof labelOverrides.filtersApplied === 'function') {
|
|
43
|
+
return labelOverrides.filtersApplied((parsedFilterData?.numberOfFilters ?? 0));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
labelOverrides.filtersApplied ??
|
|
48
|
+
<FormattedMessage
|
|
49
|
+
id="stripes-kint-components.customProperty.filtersApplied"
|
|
50
|
+
values={{ count: (parsedFilterData?.numberOfFilters ?? 0) }}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (isLoading) {
|
|
56
|
+
return (
|
|
57
|
+
<Accordion
|
|
58
|
+
closedByDefault
|
|
59
|
+
header={FilterAccordionHeader}
|
|
60
|
+
id="clickable-custprop-filter"
|
|
61
|
+
label={
|
|
62
|
+
labelOverrides.customProperties ??
|
|
63
|
+
<FormattedMessage id="stripes-kint-components.customProperties" />
|
|
64
|
+
}
|
|
65
|
+
separator={false}
|
|
66
|
+
>
|
|
67
|
+
<Spinner />
|
|
68
|
+
</Accordion>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleSubmit = values => {
|
|
73
|
+
const { filters = [] } = values;
|
|
74
|
+
|
|
75
|
+
const filterStrings = filters
|
|
76
|
+
.filter(filter => filter.rules)
|
|
77
|
+
.map(filter => filter.rules
|
|
78
|
+
.map(rule => `customProperties.${filter.customProperty}.value${rule.operator}${rule.value ?? ''}`)
|
|
79
|
+
.join('||'));
|
|
80
|
+
|
|
81
|
+
filterHandlers.state({ ...restOfFilters, customProperties: filterStrings });
|
|
82
|
+
setEditingFilters(false);
|
|
83
|
+
|
|
84
|
+
return Promise.resolve();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Accordion
|
|
89
|
+
closedByDefault
|
|
90
|
+
displayClearButton={(parsedFilterData?.numberOfFilters ?? 0) > 0}
|
|
91
|
+
header={FilterAccordionHeader}
|
|
92
|
+
id="clickable-custprop-filter"
|
|
93
|
+
label={
|
|
94
|
+
labelOverrides.customProperties ??
|
|
95
|
+
<FormattedMessage id="stripes-kint-components.customProperties" />
|
|
96
|
+
}
|
|
97
|
+
onClearFilter={() => filterHandlers.state({ ...restOfFilters, customProperties: [] })}
|
|
98
|
+
separator={false}
|
|
99
|
+
>
|
|
100
|
+
<Layout className="padding-bottom-gutter">
|
|
101
|
+
{getFiltersApplied()}
|
|
102
|
+
</Layout>
|
|
103
|
+
<CustomPropertiesFilterForm
|
|
104
|
+
customProperties={custprops}
|
|
105
|
+
editingFilters={editingFilters}
|
|
106
|
+
filters={parsedFilterData?.filters}
|
|
107
|
+
handlers={{
|
|
108
|
+
closeEditModal,
|
|
109
|
+
openEditModal
|
|
110
|
+
}}
|
|
111
|
+
labelOverrides={labelOverrides}
|
|
112
|
+
onSubmit={handleSubmit}
|
|
113
|
+
/>
|
|
114
|
+
</Accordion>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
CustomPropertiesFilter.propTypes = {
|
|
119
|
+
activeFilters: PropTypes.object,
|
|
120
|
+
customPropertiesEndpoint: PropTypes.string,
|
|
121
|
+
filterHandlers: PropTypes.object,
|
|
122
|
+
labelOverrides: PropTypes.object
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default CustomPropertiesFilter;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import { Field, useForm, useFormState } from 'react-final-form';
|
|
6
|
+
import { FieldArray } from 'react-final-form-arrays';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Button,
|
|
10
|
+
Col,
|
|
11
|
+
Label,
|
|
12
|
+
Row,
|
|
13
|
+
Select
|
|
14
|
+
} from '@folio/stripes/components';
|
|
15
|
+
|
|
16
|
+
import { groupCustomPropertiesByCtx } from '../../utils';
|
|
17
|
+
import { required as requiredValidator } from '../../utils/validators';
|
|
18
|
+
|
|
19
|
+
import CustomPropertiesRule from './CustomPropertiesRule';
|
|
20
|
+
|
|
21
|
+
const CustomPropertiesFilterField = ({
|
|
22
|
+
customProperties,
|
|
23
|
+
fields,
|
|
24
|
+
index,
|
|
25
|
+
labelOverrides = {},
|
|
26
|
+
name,
|
|
27
|
+
}) => {
|
|
28
|
+
const groupedCustomProperties = groupCustomPropertiesByCtx(customProperties);
|
|
29
|
+
const { change, mutators: { push } } = useForm();
|
|
30
|
+
const { values } = useFormState();
|
|
31
|
+
|
|
32
|
+
// Deal with all the possible label override options
|
|
33
|
+
const getRetiredName = (customProperty) => {
|
|
34
|
+
// Label override for default card title,
|
|
35
|
+
if (labelOverrides.retiredName && typeof labelOverrides.retiredName === 'function') {
|
|
36
|
+
return (
|
|
37
|
+
labelOverrides.retiredName(customProperty.label)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Label override for default title or finally built in default
|
|
42
|
+
return (
|
|
43
|
+
labelOverrides.retiredName ??
|
|
44
|
+
<FormattedMessage id="stripes-kint-components.customProperty.retiredName" values={{ name: customProperty.label }} />
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<Field
|
|
51
|
+
id={`input-custprop-${index}`}
|
|
52
|
+
label={labelOverrides.customProperty ?? <FormattedMessage id="stripes-kint-components.customProperty" />}
|
|
53
|
+
name={`${name}.customProperty`}
|
|
54
|
+
placeholder=" "
|
|
55
|
+
render={fieldProps => {
|
|
56
|
+
return (
|
|
57
|
+
<Select
|
|
58
|
+
{...fieldProps}
|
|
59
|
+
placeholder={null} // placeholder default causes issues
|
|
60
|
+
>
|
|
61
|
+
<option value=""> </option>
|
|
62
|
+
{
|
|
63
|
+
Object.entries(groupedCustomProperties)?.sort((a, b) => {
|
|
64
|
+
if (a[0] === 'isNull') return -1; // Make sure noContext is at top
|
|
65
|
+
|
|
66
|
+
if (a[0].toLowerCase() < b[0].toLowerCase()) return -1;
|
|
67
|
+
if (a[0].toLowerCase() > b[0].toLowerCase()) return 1;
|
|
68
|
+
return 0;
|
|
69
|
+
}).map(([key, value]) => {
|
|
70
|
+
return (
|
|
71
|
+
<optgroup
|
|
72
|
+
label={key === 'isNull' ? '-' : key}
|
|
73
|
+
>
|
|
74
|
+
{value.map(v => {
|
|
75
|
+
return (
|
|
76
|
+
<option
|
|
77
|
+
key={v.id}
|
|
78
|
+
value={v.name}
|
|
79
|
+
>
|
|
80
|
+
{v.retired ? getRetiredName(v) : v.label}
|
|
81
|
+
</option>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</optgroup>
|
|
85
|
+
);
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
</Select>
|
|
89
|
+
);
|
|
90
|
+
}}
|
|
91
|
+
required
|
|
92
|
+
validate={requiredValidator}
|
|
93
|
+
/>
|
|
94
|
+
{/* This next div is rendered so that it can be referred to using aria-labelledby */}
|
|
95
|
+
<div
|
|
96
|
+
id={`selected-custprop-name-${index}`}
|
|
97
|
+
style={{ display: 'none' }}
|
|
98
|
+
>
|
|
99
|
+
{customProperties.find(t => t.name === fields.value[index]?.customProperty)?.label ?? ''}
|
|
100
|
+
</div>
|
|
101
|
+
<Row>
|
|
102
|
+
<Col xs={2} />
|
|
103
|
+
<Col xs={4}>
|
|
104
|
+
<Label id="rule-column-header-comparator" required>
|
|
105
|
+
<FormattedMessage id="stripes-erm-components.customProperty.filters.comparator" />
|
|
106
|
+
</Label>
|
|
107
|
+
</Col>
|
|
108
|
+
<Col xs={4}>
|
|
109
|
+
<Label id="rule-column-header-value" required>
|
|
110
|
+
<FormattedMessage id="stripes-erm-components.customProperty.filters.value" />
|
|
111
|
+
</Label>
|
|
112
|
+
</Col>
|
|
113
|
+
<Col xs={2} />
|
|
114
|
+
</Row>
|
|
115
|
+
<FieldArray name={`${name}.rules`}>
|
|
116
|
+
{({ fields: ruleFields }) => ruleFields.map((ruleFieldName, ruleFieldIndex) => (
|
|
117
|
+
<CustomPropertiesRule
|
|
118
|
+
key={ruleFieldName}
|
|
119
|
+
ariaLabelledby={`selected-custprop-name-${index}`}
|
|
120
|
+
clearRuleValue={() => change(`filters[${index}].rules[${ruleFieldIndex}].value`, '')}
|
|
121
|
+
custPropDefinition={customProperties.find(t => t.name === fields.value[index].customProperty)}
|
|
122
|
+
index={ruleFieldIndex}
|
|
123
|
+
name={ruleFieldName}
|
|
124
|
+
onDelete={() => ruleFields.remove(ruleFieldIndex)}
|
|
125
|
+
value={values.filters[index]?.rules[ruleFieldIndex]}
|
|
126
|
+
/>
|
|
127
|
+
))}
|
|
128
|
+
</FieldArray>
|
|
129
|
+
<Button
|
|
130
|
+
data-test-add-rule-btn
|
|
131
|
+
disabled={!fields.value[index]?.customProperty}
|
|
132
|
+
onClick={() => push(`${name}.rules`)}
|
|
133
|
+
>
|
|
134
|
+
<FormattedMessage id="stripes-erm-components.customProperty.filters.addRule" />
|
|
135
|
+
</Button>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
CustomPropertiesFilterField.propTypes = {
|
|
141
|
+
customProperties: PropTypes.arrayOf(PropTypes.object),
|
|
142
|
+
fields: PropTypes.object,
|
|
143
|
+
index: PropTypes.number,
|
|
144
|
+
labelOverrides: PropTypes.object,
|
|
145
|
+
name: PropTypes.string
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default CustomPropertiesFilterField;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import { useForm } from 'react-final-form';
|
|
6
|
+
import { FieldArray } from 'react-final-form-arrays';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Button,
|
|
10
|
+
Card,
|
|
11
|
+
IconButton,
|
|
12
|
+
Layout,
|
|
13
|
+
Tooltip
|
|
14
|
+
} from '@folio/stripes/components';
|
|
15
|
+
|
|
16
|
+
import CustomPropertiesFilterField from './CustomPropertiesFilterField';
|
|
17
|
+
|
|
18
|
+
const CustomPropertiesFilterFieldArray = ({
|
|
19
|
+
customProperties,
|
|
20
|
+
labelOverrides,
|
|
21
|
+
}) => {
|
|
22
|
+
const { mutators: { push } } = useForm();
|
|
23
|
+
|
|
24
|
+
const getCardTitle = (index) => {
|
|
25
|
+
if (labelOverrides.customPropertyFilter && typeof labelOverrides.customPropertyFilter === 'function') {
|
|
26
|
+
return labelOverrides.customPropertyFilter(index);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
labelOverrides.customPropertyFilter ??
|
|
31
|
+
<FormattedMessage id="stripes-kint-components.customProperty.filterIndex" values={{ index: index + 1 }} />
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getTooltipText = (index) => {
|
|
36
|
+
if (labelOverrides.removeFilter && typeof labelOverrides.removeFilter === 'function') {
|
|
37
|
+
return labelOverrides.removeFilter(index);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
labelOverrides.removeFilter ??
|
|
42
|
+
<FormattedMessage
|
|
43
|
+
id="stripes-kint-components.customProperty.removeFilter"
|
|
44
|
+
values={{ number: index + 1 }}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<FieldArray name="filters">
|
|
51
|
+
{({ fields }) => fields.map((name, index) => {
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<Card
|
|
55
|
+
key={`custom-property-filter-card[${index}]`}
|
|
56
|
+
headerEnd={
|
|
57
|
+
<Tooltip
|
|
58
|
+
id={`custom-property-filter-card-delete-[${index}]-tooltip`}
|
|
59
|
+
text={getTooltipText(index)}
|
|
60
|
+
>
|
|
61
|
+
{({ ref, ariaIds }) => (
|
|
62
|
+
<IconButton
|
|
63
|
+
ref={ref}
|
|
64
|
+
aria-labelledby={ariaIds.text}
|
|
65
|
+
icon="trash"
|
|
66
|
+
id={`custom-property-filter-card-delete-[${index}]`}
|
|
67
|
+
onClick={() => fields.remove(index)}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</Tooltip>
|
|
71
|
+
}
|
|
72
|
+
headerStart={
|
|
73
|
+
<strong>
|
|
74
|
+
{getCardTitle(index)}
|
|
75
|
+
</strong>
|
|
76
|
+
}
|
|
77
|
+
marginBottom0={index !== fields.length - 1}
|
|
78
|
+
>
|
|
79
|
+
<CustomPropertiesFilterField
|
|
80
|
+
customProperties={customProperties}
|
|
81
|
+
fields={fields}
|
|
82
|
+
index={index}
|
|
83
|
+
labelOverrides={labelOverrides}
|
|
84
|
+
name={name}
|
|
85
|
+
/>
|
|
86
|
+
</Card>
|
|
87
|
+
{index < fields.value.length - 1 && (
|
|
88
|
+
<Layout className="textCentered">
|
|
89
|
+
<FormattedMessage id="stripes-kint-components.AND" />
|
|
90
|
+
</Layout>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</FieldArray>
|
|
96
|
+
<Button
|
|
97
|
+
onClick={() => push('filters', { rules: [{}] })}
|
|
98
|
+
>
|
|
99
|
+
{
|
|
100
|
+
labelOverrides.addFilter ??
|
|
101
|
+
<FormattedMessage id="stripes-kint-components.customProperty.addFilter" />
|
|
102
|
+
}
|
|
103
|
+
</Button>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
CustomPropertiesFilterFieldArray.propTypes = {
|
|
109
|
+
customProperties: PropTypes.arrayOf(PropTypes.object),
|
|
110
|
+
labelOverrides: PropTypes.object
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default CustomPropertiesFilterFieldArray;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import arrayMutators from 'final-form-arrays';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Button,
|
|
9
|
+
} from '@folio/stripes/components';
|
|
10
|
+
|
|
11
|
+
import FormModal from '../../FormModal';
|
|
12
|
+
import CustomPropertiesFilterFieldArray from './CustomPropertiesFilterFieldArray';
|
|
13
|
+
|
|
14
|
+
const CustomPropertyFiltersForm = ({
|
|
15
|
+
customProperties,
|
|
16
|
+
editingFilters,
|
|
17
|
+
filters,
|
|
18
|
+
handlers: {
|
|
19
|
+
closeEditModal,
|
|
20
|
+
openEditModal,
|
|
21
|
+
},
|
|
22
|
+
labelOverrides,
|
|
23
|
+
onSubmit,
|
|
24
|
+
}) => {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<Button
|
|
28
|
+
onClick={openEditModal}
|
|
29
|
+
>
|
|
30
|
+
{
|
|
31
|
+
labelOverrides.editCustomPropertyFilters ??
|
|
32
|
+
<FormattedMessage id="stripes-kint-components.customProperty.editCustomPropertyFilters" />
|
|
33
|
+
}
|
|
34
|
+
</Button>
|
|
35
|
+
<FormModal
|
|
36
|
+
initialValues={{ filters: filters.length ? filters : [{ rules: [{}] }] }}
|
|
37
|
+
labelOverrides={{
|
|
38
|
+
save: <FormattedMessage id="stripes-kint-components.apply" />
|
|
39
|
+
}}
|
|
40
|
+
modalProps={{
|
|
41
|
+
dismissible: true,
|
|
42
|
+
enforceFocus: false,
|
|
43
|
+
label:
|
|
44
|
+
labelOverrides.filterBuilder ??
|
|
45
|
+
<FormattedMessage id="stripes-kint-components.customProperty.filterbuilder" />,
|
|
46
|
+
onClose: closeEditModal,
|
|
47
|
+
open: editingFilters,
|
|
48
|
+
size: 'medium'
|
|
49
|
+
}}
|
|
50
|
+
mutators={{ ...arrayMutators }}
|
|
51
|
+
onSubmit={onSubmit}
|
|
52
|
+
>
|
|
53
|
+
<CustomPropertiesFilterFieldArray
|
|
54
|
+
customProperties={customProperties}
|
|
55
|
+
labelOverrides={labelOverrides}
|
|
56
|
+
/>
|
|
57
|
+
</FormModal>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
CustomPropertyFiltersForm.propTypes = {
|
|
63
|
+
customProperties: PropTypes.arrayOf(PropTypes.object),
|
|
64
|
+
editingFilters: PropTypes.bool,
|
|
65
|
+
filters: PropTypes.arrayOf(PropTypes.object),
|
|
66
|
+
handlers: PropTypes.shape({
|
|
67
|
+
closeEditModal: PropTypes.func.isRequired,
|
|
68
|
+
openEditModal: PropTypes.func.isRequired
|
|
69
|
+
}),
|
|
70
|
+
labelOverrides: PropTypes.object,
|
|
71
|
+
onSubmit: PropTypes.func.isRequired
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default CustomPropertyFiltersForm;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { uniqueId } from 'lodash';
|
|
4
|
+
import { FormattedMessage } from 'react-intl';
|
|
5
|
+
import { Field } from 'react-final-form';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Col,
|
|
9
|
+
IconButton,
|
|
10
|
+
Layout,
|
|
11
|
+
Row,
|
|
12
|
+
Select,
|
|
13
|
+
Tooltip,
|
|
14
|
+
} from '@folio/stripes/components';
|
|
15
|
+
|
|
16
|
+
import { required as requiredValidator } from '../../utils/validators';
|
|
17
|
+
|
|
18
|
+
import useValueProps from './useValueProps';
|
|
19
|
+
import useOperators from './useOperators';
|
|
20
|
+
|
|
21
|
+
const propTypes = {
|
|
22
|
+
ariaLabelledby: PropTypes.string.isRequired,
|
|
23
|
+
clearRuleValue: PropTypes.func.isRequired,
|
|
24
|
+
index: PropTypes.number.isRequired,
|
|
25
|
+
name: PropTypes.string.isRequired,
|
|
26
|
+
onDelete: PropTypes.func.isRequired,
|
|
27
|
+
custPropDefinition: PropTypes.shape({
|
|
28
|
+
type: PropTypes.string,
|
|
29
|
+
category: PropTypes.shape({
|
|
30
|
+
values: PropTypes.arrayOf(PropTypes.object),
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
value: PropTypes.shape({
|
|
34
|
+
operator: PropTypes.string,
|
|
35
|
+
value: PropTypes.string,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const CustomPropertiesRule = ({
|
|
40
|
+
ariaLabelledby,
|
|
41
|
+
clearRuleValue,
|
|
42
|
+
index,
|
|
43
|
+
name,
|
|
44
|
+
onDelete,
|
|
45
|
+
custPropDefinition = {},
|
|
46
|
+
value,
|
|
47
|
+
}) => {
|
|
48
|
+
const operators = useOperators(custPropDefinition.type);
|
|
49
|
+
const valueProps = useValueProps(custPropDefinition);
|
|
50
|
+
const selectedOperator = operators.find(o => o.value === value?.operator);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Row
|
|
54
|
+
key={name}
|
|
55
|
+
>
|
|
56
|
+
<Col xs={2}>
|
|
57
|
+
<Layout className="textCentered">
|
|
58
|
+
{index === 0 ? null : <FormattedMessage id="stripes-kint-components.OR" />}
|
|
59
|
+
</Layout>
|
|
60
|
+
</Col>
|
|
61
|
+
<Col xs={4}>
|
|
62
|
+
<Field
|
|
63
|
+
name={`${name}.operator`}
|
|
64
|
+
validate={requiredValidator}
|
|
65
|
+
>
|
|
66
|
+
{({ input, meta }) => (
|
|
67
|
+
<Select
|
|
68
|
+
{...input}
|
|
69
|
+
aria-labelledby={`${ariaLabelledby}-rule-column-header-comparator`}
|
|
70
|
+
dataOptions={operators}
|
|
71
|
+
error={meta?.touched && meta?.error}
|
|
72
|
+
onChange={e => {
|
|
73
|
+
input.onChange(e);
|
|
74
|
+
|
|
75
|
+
const newlySelectedOperator = operators.find(o => o.value === e.target.value);
|
|
76
|
+
if (newlySelectedOperator?.noValueAllowed) {
|
|
77
|
+
clearRuleValue();
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
placeholder=" "
|
|
81
|
+
required
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</Field>
|
|
85
|
+
</Col>
|
|
86
|
+
<Col xs={4}>
|
|
87
|
+
{ selectedOperator?.noValueAllowed ?
|
|
88
|
+
null
|
|
89
|
+
:
|
|
90
|
+
<Field
|
|
91
|
+
aria-labelledby={`${ariaLabelledby}-rule-column-header-value`}
|
|
92
|
+
name={`${name}.value`}
|
|
93
|
+
required
|
|
94
|
+
validate={requiredValidator}
|
|
95
|
+
{...valueProps}
|
|
96
|
+
/>
|
|
97
|
+
}
|
|
98
|
+
</Col>
|
|
99
|
+
<Col xs={2}>
|
|
100
|
+
{ index ? (
|
|
101
|
+
<Tooltip
|
|
102
|
+
id={uniqueId('delete-rule-btn')}
|
|
103
|
+
text={<FormattedMessage id="stripes-kint-components.customProperty.removeRule" values={{ number: index + 1 }} />}
|
|
104
|
+
>
|
|
105
|
+
{({ ref, ariaIds }) => (
|
|
106
|
+
<IconButton
|
|
107
|
+
ref={ref}
|
|
108
|
+
aria-labelledby={ariaIds.text}
|
|
109
|
+
icon="trash"
|
|
110
|
+
onClick={onDelete}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
</Tooltip>
|
|
114
|
+
) : null}
|
|
115
|
+
</Col>
|
|
116
|
+
</Row>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
CustomPropertiesRule.propTypes = propTypes;
|
|
121
|
+
|
|
122
|
+
export default CustomPropertiesRule;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as CustomPropertiesFilter } from './CustomPropertiesFilter';
|
|
2
|
+
export { default as CustomPropertiesFilterForm } from './CustomPropertiesFilterForm';
|
|
3
|
+
export { default as useOperators } from './useOperators';
|
|
4
|
+
export { default as useParseActiveFilterStrings } from './useParseActiveFilterStrings';
|
|
5
|
+
export { default as CustomPropertiesFilterField } from './CustomPropertiesFilterField';
|
|
6
|
+
export { default as CustomPropertiesFilterFieldArray } from './CustomPropertiesFilterFieldArray';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useIntl } from 'react-intl';
|
|
2
|
+
import * as CUSTPROP_TYPES from '../../constants/customProperties';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
REFDATA_CLASS_NAME: REFDATA,
|
|
6
|
+
DECIMAL_CLASS_NAME: DECIMAL,
|
|
7
|
+
INTEGER_CLASS_NAME: INTEGER,
|
|
8
|
+
DATE_CLASS_NAME: DATE,
|
|
9
|
+
TEXT_CLASS_NAME: TEXT,
|
|
10
|
+
} = CUSTPROP_TYPES;
|
|
11
|
+
|
|
12
|
+
const useOperators = (type) => {
|
|
13
|
+
const intl = useIntl();
|
|
14
|
+
const getLabel = id => intl.formatMessage({ id });
|
|
15
|
+
const operators = [
|
|
16
|
+
{ value: ' isSet', label: getLabel('stripes-kint-components.operator.isSet'), noValueAllowed: true },
|
|
17
|
+
{ value: ' isNotSet', label: getLabel('stripes-kint-components.operator.isNotSet'), noValueAllowed: true },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
if (!type || type === INTEGER || type === DECIMAL) {
|
|
21
|
+
operators.push(
|
|
22
|
+
{ value: '==', label: getLabel('stripes-kint-components.operator.equals') },
|
|
23
|
+
{ value: '!=', label: getLabel('stripes-kint-components.operator.doesNotEqual') },
|
|
24
|
+
{ value: '>=', label: getLabel('stripes-kint-components.operator.isGreaterThanOrEqual') },
|
|
25
|
+
{ value: '<=', label: getLabel('stripes-kint-components.operator.isLessThanOrEqual') },
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (type === DATE) { // Basically the same as the "number" types above, so no need to reassign if there is no type
|
|
30
|
+
operators.push(
|
|
31
|
+
{ value: '==', label: getLabel('stripes-kint-components.operator.equals') },
|
|
32
|
+
{ value: '!=', label: getLabel('stripes-kint-components.operator.doesNotEqual') },
|
|
33
|
+
{ value: '>=', label: getLabel('stripes-kint-components.operator.isOnOrAfter') },
|
|
34
|
+
{ value: '<=', label: getLabel('stripes-kint-components.operator.isOnOrBefore') },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!type || type === REFDATA) {
|
|
39
|
+
operators.push(
|
|
40
|
+
{ value: '==', label: getLabel('stripes-kint-components.operator.is') },
|
|
41
|
+
{ value: '!=', label: getLabel('stripes-kint-components.operator.isNot') },
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!type || type === TEXT) {
|
|
46
|
+
operators.push(
|
|
47
|
+
{ value: '=~', label: getLabel('stripes-kint-components.operator.contains') },
|
|
48
|
+
{ value: '!~', label: getLabel('stripes-kint-components.operator.doesNotContain') },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return operators;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default useOperators;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import useOperators from './useOperators';
|
|
2
|
+
|
|
3
|
+
const useParseActiveFilterStrings = (filterStrings) => {
|
|
4
|
+
const operators = useOperators().map(o => o.value);
|
|
5
|
+
|
|
6
|
+
let numberOfFilters = 0;
|
|
7
|
+
const filters = filterStrings.map(filter => {
|
|
8
|
+
let customProperty;
|
|
9
|
+
const rules = filter.split('||').map(ruleString => {
|
|
10
|
+
// ruleString is constructed in this.handleSubmit passed to CustomPropertyFiltersForm
|
|
11
|
+
// and has shape "customProperties.foo.value!=42"
|
|
12
|
+
const [customPropertyPath, rule] = ruleString.split('.value');
|
|
13
|
+
customProperty = customPropertyPath.replace('customProperties.', '');
|
|
14
|
+
|
|
15
|
+
const operator = operators.find(o => rule.startsWith(o)) ?? '';
|
|
16
|
+
const value = rule.substring(operator.length);
|
|
17
|
+
|
|
18
|
+
numberOfFilters += 1;
|
|
19
|
+
|
|
20
|
+
return { operator, value };
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
customProperty,
|
|
25
|
+
rules,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
filters,
|
|
31
|
+
numberOfFilters
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default useParseActiveFilterStrings;
|