Skip to content

Commit

Permalink
ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licens… (
Browse files Browse the repository at this point in the history
#645)

* ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licenses and amendments

* copy document filter components from ui-agreements
* add extra filter and rule component for supplementary documents, leave documents filter and rule for core documents
* add component for rule constants
* add translations

* ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licenses and amendments

* fix lint errors

* * fix naming and type errors

* ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licenses and amendments

* avoid duplicate code

* * fix translations
* improve constants component

* * remove comment

* * change atTypeValues to refdataValues, selectify atTypeValues in DocumentFilterRule

* * add comment

* ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licenses and amendments

* get atTypeValues like other refdata values are passed
* remove unnecessary selectify

* ERM-3056, ERM-3057, ERM-3058, ERM-3059 Filter for documents in licenses and amendments

* remove replaceAll function
  • Loading branch information
CalamityC authored Oct 30, 2023
1 parent e34922a commit 4d44236
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export { default as ActionMenu } from './lib/ActionMenu';
export { default as AlternativeNamesFieldArray } from './lib/AlternativeNamesFieldArray';
export { default as DocumentCard } from './lib/DocumentCard';
export { default as DocumentFilter } from './lib/DocumentFilter';
export { default as DocumentsFieldArray } from './lib/DocumentsFieldArray';
export { default as DuplicateModal } from './lib/DuplicateModal';
export { default as EditCard } from './lib/EditCard';
Expand Down
135 changes: 135 additions & 0 deletions lib/DocumentFilter/DocumentFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import PropTypes from 'prop-types';
import { useState } from 'react';
import {
Accordion,
FilterAccordionHeader,
Layout,
} from '@folio/stripes/components';
import { FormattedMessage } from 'react-intl';

import {
deparseKiwtQueryFilters,
parseKiwtQueryFilters,
} from '@k-int/stripes-kint-components';

import DocumentFilterForm from './DocumentFilterForm';

const DocumentFilter = ({ activeFilters, atTypeValues = [], filterHandlers }) => {
// atTypeValues are only passed for SupplementaryDocumentFilter
const filterType = atTypeValues.length > 0 ? 'supplementaryDocuments' : 'documents';
const [editingFilters, setEditingFilters] = useState(false);
const openEditModal = () => setEditingFilters(true);
const closeEditModal = () => setEditingFilters(false);

// Due to how filters are handled within SearchAndSortQuery the filter string needs to be parsed back into a usual object
const parseQueryString = (filterArray) => {
if (filterArray?.length) {
// Since the filters are grouped, the docuements filterstring will be within the first array element
const parsedFilters = parseKiwtQueryFilters(filterArray?.[0]);

// This reduce function removes all array elements that contain solely comparators so the initial value shape is returned
// ---
// Before parsing:
// [
// [{ path, comparator, value }],
// '||',
// [{ path, comparator, value }, '||', { path, comparator, value }],
// ];
// ---
// After: parsing
// [
// [{ path, comparator, value }],
// [
// { path, comparator, value },
// { path, comparator, value },
// ],
// ];
const filters = parsedFilters.reduce((acc, curr) => {
if (typeof curr === 'string') {
return [...acc];
}
return [...acc, { rules: curr.filter((e) => typeof e !== 'string') }];
}, []);
return filters;
}
return [];
};

const parsedFilterData = parseQueryString(activeFilters?.[filterType] || []);

const handleSubmit = (values) => {
// In order to convert the form values into the shape for them to be deparsed we do the inverse of the above
// Adding a || operator between all elements of the filters array and a && operator between all elements of the nested arrays
// With special logic to ensure that operators are not added infront of the first elements of any arrays, to ensure no grouping errors
const kiwtQueryShape = values?.filters?.reduce((acc, curr, index) => {
let newAcc = [...acc];

if (index !== 0) {
newAcc = [...newAcc, '||'];
}

newAcc = [
...newAcc,
curr.rules.reduce((a, c, i) => {
return [
...a,
i !== 0 ? '&&' : null, // Don't group on first entry
c,
].filter(Boolean);
}, []),
];

return newAcc;
}, []);

filterHandlers.state({
...activeFilters,
[filterType]: [
// Currently the deparse function returns a query string containing whitespace which leads to grouping errors
// This regex removes all whitespace from the querystring
deparseKiwtQueryFilters(kiwtQueryShape),
],
});
setEditingFilters(false);
};

return (
<Accordion
closedByDefault
displayClearButton={!!parsedFilterData?.length}
header={FilterAccordionHeader}
id={`clickable-agreement-${filterType}-filter`}
label={<FormattedMessage id={`stripes-erm-components.documentFilter.${filterType}`} />}
onClearFilter={() => filterHandlers.state({ ...activeFilters, [filterType]: [] })
}
separator={false}
>
{!!parsedFilterData?.length && (
<Layout className="padding-bottom-gutter">
<FormattedMessage
id="stripes-erm-components.documentFilter.filtersApplied"
values={{ filtersLength: parsedFilterData?.length }}
/>
</Layout>
)}
<DocumentFilterForm
atTypeValues={atTypeValues}
editingFilters={editingFilters}
filters={parsedFilterData}
handlers={{
closeEditModal,
openEditModal,
}}
onSubmit={handleSubmit}
/>
</Accordion>
);
};

DocumentFilter.propTypes = {
activeFilters: PropTypes.object,
atTypeValues: PropTypes.arrayOf(PropTypes.object),
filterHandlers: PropTypes.object,
};

export default DocumentFilter;
71 changes: 71 additions & 0 deletions lib/DocumentFilter/DocumentFilterField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import PropTypes from 'prop-types';
import { Button, Row, Col, Label } from '@folio/stripes/components';
import { useForm, useFormState } from 'react-final-form';
import { FieldArray } from 'react-final-form-arrays';
import { FormattedMessage } from 'react-intl';
import DocumentFilterRule from './DocumentFilterRule';

const DocumentFilterField = ({ atTypeValues, index, name }) => {
const {
mutators: { push },
} = useForm();
const { values } = useFormState();
const renderRuleComponent = (ruleFields, ruleFieldName, ruleFieldIndex) => {
return (
<DocumentFilterRule
key={ruleFieldName}
ariaLabelledby={`selected-document-item-name-${index}`}
atTypeValues={atTypeValues}
index={ruleFieldIndex}
name={ruleFieldName}
onDelete={() => ruleFields.remove(ruleFieldIndex)}
value={values.filters[index]?.rules[ruleFieldIndex]}
/>
);
};

return (
<>
<Row>
<Col xs={2} />
<Col xs={3}>
<Label id="rule-column-header-attribute" required>
<FormattedMessage id="stripes-erm-components.documentFilter.attribute" />
</Label>
</Col>
<Col xs={3}>
<Label id="rule-column-header-comparator" required>
<FormattedMessage id="stripes-erm-components.comparator" />
</Label>
</Col>
<Col xs={3}>
<Label id="rule-column-header-value" required>
<FormattedMessage id="stripes-erm-components.value" />
</Label>
</Col>
<Col xs={1} />
</Row>

<FieldArray name={`${name}.rules`}>
{({ fields: ruleFields }) => ruleFields.map((ruleFieldName, ruleFieldIndex) => (
renderRuleComponent(ruleFields, ruleFieldName, ruleFieldIndex)
))
}
</FieldArray>
<Button
data-test-add-rule-btn
onClick={() => push(`${name}.rules`)}
>
<FormattedMessage id="stripes-erm-components.documentFilter.addRule" />
</Button>
</>
);
};

DocumentFilterField.propTypes = {
atTypeValues: PropTypes.arrayOf(PropTypes.object),
index: PropTypes.number,
name: PropTypes.string,
};

export default DocumentFilterField;
90 changes: 90 additions & 0 deletions lib/DocumentFilter/DocumentFilterFieldArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useForm } from 'react-final-form';
import { FieldArray } from 'react-final-form-arrays';
import PropTypes from 'prop-types';

import {
Button,
Card,
IconButton,
Layout,
Tooltip,
} from '@folio/stripes/components';
import { FormattedMessage } from 'react-intl';
import DocumentFilterField from './DocumentFilterField';

const propTypes = {
atTypeValues: PropTypes.arrayOf(PropTypes.object),
};

const DocumentFilterFieldArray = ({ atTypeValues }) => {
const {
mutators: { push },
} = useForm();

return (
<>
<FieldArray name="filters">
{({ fields }) => fields.map((name, index) => {
return (
<>
<Card
key={`document-filter-card[${index}]`}
headerEnd={
fields?.length > 1 ? (
<Tooltip
id={`document-filter-card-delete-[${index}]-tooltip`}
text={
<FormattedMessage
id="stripes-erm-components.documentFilter.deleteFilterIndex"
values={{ number: index + 1 }}
/>
}
>
{({ ref, ariaIds }) => (
<IconButton
ref={ref}
aria-labelledby={ariaIds.text}
icon="trash"
id={`document-filter-card-delete-[${index}]`}
onClick={() => fields.remove(index)}
/>
)}
</Tooltip>
) : null
}
headerStart={
<strong>
<FormattedMessage
id="stripes-erm-components.documentFilter.documentFilterIndex"
values={{ number: index + 1 }}
/>
</strong>
}
marginBottom0={index !== fields.length - 1}
>
<DocumentFilterField
atTypeValues={atTypeValues}
fields={fields}
index={index}
name={name}
/>
</Card>
{index < fields.value.length - 1 && (
<Layout className="textCentered">
<FormattedMessage id="stripes-erm-components.OR" />
</Layout>
)}
</>
);
})
}
</FieldArray>
<Button onClick={() => push('filters', { rules: [{}] })}>
<FormattedMessage id="stripes-erm-components.documentFilter.addFilter" />
</Button>
</>
);
};

DocumentFilterFieldArray.propTypes = propTypes;
export default DocumentFilterFieldArray;
52 changes: 52 additions & 0 deletions lib/DocumentFilter/DocumentFilterForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import PropTypes from 'prop-types';
import { Button } from '@folio/stripes/components';
import arrayMutators from 'final-form-arrays';
import { FormModal } from '@k-int/stripes-kint-components';
import { FormattedMessage } from 'react-intl';
import DocumentFilterFieldArray from './DocumentFilterFieldArray';

const DocumentFilterForm = ({
atTypeValues,
editingFilters,
filters,
handlers: { openEditModal, closeEditModal },
onSubmit,
}) => {
const filterBuilder = atTypeValues.length > 0 ? 'supplementaryDocumentFilterBuilder' : 'coreDocumentFilterBuilder';
return (
<>
<Button onClick={openEditModal}>
<FormattedMessage id="stripes-erm-components.documentFilter.editDocumentFilters" />
</Button>
<FormModal
initialValues={{
filters: filters?.length ? filters : [{ rules: [{}] }],
}}
modalProps={{
dismissible: true,
enforceFocus: false,
label: <FormattedMessage id={`stripes-erm-components.documentFilter.${filterBuilder}`} />,
onClose: closeEditModal,
open: editingFilters,
size: 'medium',
}}
mutators={{ ...arrayMutators }}
onSubmit={onSubmit}
>
<DocumentFilterFieldArray atTypeValues={atTypeValues} />
</FormModal>
</>
);
};

DocumentFilterForm.propTypes = {
atTypeValues: PropTypes.arrayOf(PropTypes.object),
editingFilters: PropTypes.bool,
filters: PropTypes.arrayOf(PropTypes.object),
handlers: PropTypes.shape({
closeEditModal: PropTypes.func.isRequired,
openEditModal: PropTypes.func.isRequired,
}),
onSubmit: PropTypes.func,
};
export default DocumentFilterForm;
Loading

0 comments on commit 4d44236

Please sign in to comment.