@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.
Files changed (109) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/es/index.js +124 -4
  3. package/es/lib/ActionList/ActionList.js +7 -2
  4. package/es/lib/ActionList/ActionListFieldArray.js +49 -10
  5. package/es/lib/CustomProperties/Config/CustomPropertiesSettings.js +2 -2
  6. package/es/lib/CustomProperties/Config/{CustomPropertiesView.js → CustomPropertyView.js} +5 -5
  7. package/es/lib/CustomProperties/Config/index.js +6 -4
  8. package/es/lib/CustomProperties/Edit/CustomPropertiesEdit.js +72 -0
  9. package/es/lib/CustomProperties/Edit/CustomPropertiesEditCtx.js +133 -0
  10. package/es/lib/CustomProperties/Edit/CustomPropertiesListField.js +279 -0
  11. package/es/lib/CustomProperties/Edit/CustomPropertyField.js +370 -0
  12. package/es/lib/CustomProperties/Edit/CustomPropertyFormCard.js +156 -0
  13. package/es/lib/CustomProperties/Edit/index.js +51 -0
  14. package/es/lib/CustomProperties/Filter/CustomPropertiesFilter.js +216 -0
  15. package/es/lib/CustomProperties/Filter/CustomPropertiesFilterField.js +236 -0
  16. package/es/lib/CustomProperties/Filter/CustomPropertiesFilterFieldArray.js +159 -0
  17. package/es/lib/CustomProperties/Filter/CustomPropertiesFilterForm.js +119 -0
  18. package/es/lib/CustomProperties/Filter/CustomPropertiesRule.js +173 -0
  19. package/es/lib/CustomProperties/Filter/index.js +59 -0
  20. package/es/lib/CustomProperties/Filter/useOperators.js +138 -0
  21. package/es/lib/CustomProperties/Filter/useParseActiveFilterStrings.js +97 -0
  22. package/es/lib/CustomProperties/Filter/useValueProps.js +101 -0
  23. package/es/lib/CustomProperties/View/CustomPropertiesView.js +73 -0
  24. package/es/lib/CustomProperties/View/CustomPropertiesViewCtx.js +187 -0
  25. package/es/lib/CustomProperties/View/CustomPropertyCard.js +204 -0
  26. package/es/lib/CustomProperties/View/index.js +35 -0
  27. package/es/lib/CustomProperties/index.js +125 -0
  28. package/es/lib/EditableRefdataList/EditableRefdataList.js +12 -16
  29. package/es/lib/FormModal/FormModal.js +18 -4
  30. package/es/lib/QueryTypedown/QueryTypedown.js +9 -4
  31. package/es/lib/constants/customProperties.js +4 -1
  32. package/es/lib/hooks/index.js +16 -0
  33. package/es/lib/hooks/typedownHooks/useTypedownData.js +9 -2
  34. package/es/lib/hooks/useAvailableCustomProperties.js +106 -0
  35. package/es/lib/hooks/useInvalidateRefdata.js +53 -0
  36. package/es/lib/hooks/useMutateRefdataValue.js +11 -6
  37. package/es/lib/hooks/useRefdata.js +1 -3
  38. package/es/lib/utils/groupCustomPropertiesByCtx.js +69 -0
  39. package/es/lib/utils/index.js +24 -0
  40. package/es/lib/utils/refdataQueryKey.js +48 -0
  41. package/es/lib/utils/typedownQueryKey.js +48 -0
  42. package/es/lib/utils/validators.js +60 -1
  43. package/git_translate.sh +8 -0
  44. package/package.json +1 -1
  45. package/src/index.js +27 -3
  46. package/src/lib/ActionList/ActionList.js +5 -2
  47. package/src/lib/ActionList/ActionListFieldArray.js +31 -8
  48. package/src/lib/ActionList/README.md +23 -20
  49. package/src/lib/CustomProperties/Config/CustomPropertiesSettings.js +2 -2
  50. package/src/lib/CustomProperties/Config/{CustomPropertiesView.js → CustomPropertyView.js} +3 -3
  51. package/src/lib/CustomProperties/Config/index.js +1 -1
  52. package/src/lib/CustomProperties/Edit/CustomPropertiesEdit.js +35 -0
  53. package/src/lib/CustomProperties/Edit/CustomPropertiesEditCtx.js +85 -0
  54. package/src/lib/CustomProperties/Edit/CustomPropertiesListField.js +194 -0
  55. package/src/lib/CustomProperties/Edit/CustomPropertyField.js +299 -0
  56. package/src/lib/CustomProperties/Edit/CustomPropertyFormCard.js +131 -0
  57. package/src/lib/CustomProperties/Edit/index.js +5 -0
  58. package/src/lib/CustomProperties/Filter/CustomPropertiesFilter.js +125 -0
  59. package/src/lib/CustomProperties/Filter/CustomPropertiesFilterField.js +148 -0
  60. package/src/lib/CustomProperties/Filter/CustomPropertiesFilterFieldArray.js +113 -0
  61. package/src/lib/CustomProperties/Filter/CustomPropertiesFilterForm.js +74 -0
  62. package/src/lib/CustomProperties/Filter/CustomPropertiesRule.js +122 -0
  63. package/src/lib/CustomProperties/Filter/index.js +6 -0
  64. package/src/lib/CustomProperties/Filter/useOperators.js +55 -0
  65. package/src/lib/CustomProperties/Filter/useParseActiveFilterStrings.js +35 -0
  66. package/src/lib/CustomProperties/Filter/useValueProps.js +45 -0
  67. package/src/lib/CustomProperties/View/CustomPropertiesView.js +36 -0
  68. package/src/lib/CustomProperties/View/CustomPropertiesViewCtx.js +112 -0
  69. package/src/lib/CustomProperties/View/CustomPropertyCard.js +177 -0
  70. package/src/lib/CustomProperties/View/index.js +3 -0
  71. package/src/lib/CustomProperties/index.js +30 -0
  72. package/src/lib/EditableRefdataList/EditableRefdataList.js +13 -10
  73. package/src/lib/FormModal/FormModal.js +37 -17
  74. package/src/lib/QueryTypedown/QueryTypedown.js +3 -1
  75. package/src/lib/constants/customProperties.js +1 -0
  76. package/src/lib/hooks/index.js +2 -0
  77. package/src/lib/hooks/typedownHooks/useTypedownData.js +9 -3
  78. package/src/lib/hooks/useAvailableCustomProperties.js +40 -0
  79. package/src/lib/hooks/useInvalidateRefdata.js +11 -0
  80. package/src/lib/hooks/useMutateRefdataValue.js +7 -3
  81. package/src/lib/hooks/useRefdata.js +2 -3
  82. package/src/lib/utils/groupCustomPropertiesByCtx.js +13 -0
  83. package/src/lib/utils/index.js +5 -0
  84. package/src/lib/utils/refdataQueryKey.js +9 -0
  85. package/src/lib/utils/typedownQueryKey.js +9 -0
  86. package/src/lib/utils/validators.js +40 -0
  87. package/translate.sh +63 -0
  88. package/translations/stripes-kint-components/ar.json +105 -0
  89. package/translations/stripes-kint-components/ca.json +1 -0
  90. package/translations/stripes-kint-components/cs_CZ.json +105 -0
  91. package/translations/stripes-kint-components/da.json +1 -0
  92. package/translations/stripes-kint-components/de.json +105 -0
  93. package/translations/stripes-kint-components/en.json +54 -2
  94. package/translations/stripes-kint-components/es.json +105 -0
  95. package/translations/stripes-kint-components/fr.json +105 -0
  96. package/translations/stripes-kint-components/he.json +1 -0
  97. package/translations/stripes-kint-components/hi_IN.json +105 -0
  98. package/translations/stripes-kint-components/hu.json +105 -0
  99. package/translations/stripes-kint-components/it_IT.json +105 -0
  100. package/translations/stripes-kint-components/ja.json +105 -0
  101. package/translations/stripes-kint-components/ko.json +105 -0
  102. package/translations/stripes-kint-components/nb.json +1 -0
  103. package/translations/stripes-kint-components/nn.json +1 -0
  104. package/translations/stripes-kint-components/pl.json +105 -0
  105. package/translations/stripes-kint-components/pt_PT.json +105 -0
  106. package/translations/stripes-kint-components/ru.json +105 -0
  107. package/translations/stripes-kint-components/sv.json +105 -0
  108. package/translations/stripes-kint-components/ur.json +1 -0
  109. 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;