Skip to content

Commit

Permalink
implement filtering and sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
usavkov-epam committed Dec 7, 2023
1 parent 7a51c29 commit f7c8e3d
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 24 deletions.
44 changes: 26 additions & 18 deletions lib/FindLocation/FindLocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,23 @@ export const FindLocation = (props) => {
const eventEmitter = useEventEmitter();

const [selectedLocations, setSelectedLocations] = useState({});
const [filters, setFilters] = useState({});

const { institutions, isLoading: isInstitutionsLoading } = useInstitutions();
const { campuses, isLoading: isCampusesLoading } = useCampuses();
const { libraries, isLoading: isLibrariesLoading } = useLibraries();
const { locations, isLoading: isLocationsLoading } = useLocationsRecords();

const institutionsMap = useMemo(() => keyBy(institutions, 'id'), [institutions]);
const campusessMap = useMemo(() => keyBy(campuses, 'id'), [campuses]);
const librariesMap = useMemo(() => keyBy(libraries, 'id'), [libraries]);

const { locations, isLoading: isLocationsLoading } = useLocationsRecords({
filters,
selectedLocations,
institutionsMap,
campusessMap,
librariesMap,
});

useEffect(() => {
const eventType = EVENT_EMMITER_EVENTS.FIND_RECORDS_SELECTED_CHANGED;
Expand All @@ -62,10 +74,6 @@ export const FindLocation = (props) => {
)
}), [isMultiSelect]);

const institutionsMap = useMemo(() => keyBy(institutions, 'id'), [institutions]);
const campusessMap = useMemo(() => keyBy(campuses, 'id'), [campuses]);
const librariesMap = useMemo(() => keyBy(libraries, 'id'), [libraries]);

const resultsFormatter = useMemo(() => ({
[COLUMN_NAMES.name]: ({ name }) => name,
[COLUMN_NAMES.code]: ({ code }) => code,
Expand All @@ -85,10 +93,20 @@ export const FindLocation = (props) => {
selectedLocations,
]);

const refreshRecords = useCallback((filters) => setFilters(filters), []);

const isLoading = (
isLocationsLoading
|| isInstitutionsLoading
|| isCampusesLoading
|| isLibrariesLoading
);

const renderFilters = useCallback((activeFilters, applyFilters) => (
<FindLocationFilters
activeFilters={activeFilters}
applyFilters={applyFilters}
disabled={isLoading}
isMultiSelect={isMultiSelect}
institutions={institutions}
campuses={campuses}
Expand All @@ -98,20 +116,10 @@ export const FindLocation = (props) => {
campuses,
institutions,
isMultiSelect,
isLoading,
libraries,
]);

const refreshRecords = useCallback((data) => {
console.log('refresh', data)
}, []);

const isLoading = (
isInstitutionsLoading
|| isCampusesLoading
|| isLibrariesLoading
|| isLocationsLoading
);

return (
<ColumnManager
id="find-locations"
Expand Down Expand Up @@ -146,9 +154,9 @@ export const FindLocation = (props) => {
FindLocation.propTypes = {
idPrefix: PropTypes.string,
isMultiSelect: PropTypes.bool,
modalLabel: PropTypes.oneOf([PropTypes.element, PropTypes.string]),
modalLabel: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
onRecordsSelect: PropTypes.func.isRequired,
resultsPaneTitle: PropTypes.oneOf([PropTypes.element, PropTypes.string]),
resultsPaneTitle: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
sortableColumns: PropTypes.arrayOf(PropTypes.string),
visibleColumns: PropTypes.arrayOf(PropTypes.string),
};
Expand Down
35 changes: 34 additions & 1 deletion lib/FindLocation/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const COLUMN_MAPPING = {
export const SORTABLE_COLUMNS = [
COLUMN_NAMES.name,
COLUMN_NAMES.code,
COLUMN_NAMES.isActive,
COLUMN_NAMES.institution,
COLUMN_NAMES.campus,
COLUMN_NAMES.library,
];

export const NON_TOGGLEABLE_COLUMNS = [
Expand All @@ -46,3 +48,34 @@ export const ASSIGNED_FILTER_OPTIONS = [
label: <FormattedMessage id="stripes-acq-components.filter.assignment.unassigned" />,
},
];

export const FILTERS_CONFIG = {
/*
Matching between filter's name and related field's name in a location object.
*/
filterMap: {
institutions: 'institutionId',
campuses: 'campusId',
libraries: 'libraryId',
},
/*
Matching between sorting column's name and related field's name in a location object.
The fields 'institution', 'campus', and 'library' are hydrated on filtering stage.
*/
sortingMap: {
[COLUMN_NAMES.institution]: 'institution.name',
[COLUMN_NAMES.campus]: 'campus.name',
[COLUMN_NAMES.library]: 'library.name',
},
/*
The names of the fields in a location object that are used for keyword search.
The fields 'institution', 'campus', and 'library' are hydrated on filtering stage.
*/
queryIndexes: [
'name',
'code',
'institution.name',
'campus.name',
'library.name',
],
};
54 changes: 54 additions & 0 deletions lib/FindLocation/filterAndSort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import isNil from 'lodash/isNil';
import get from 'lodash/get';

import { SEARCH_PARAMETER } from '../AcqList';

const normalizeValue = (value) => value.toString().trim().toLowerCase();

/**
* Sorts and filters an array of objects.
* @param { Object } config - Configuration for mapping and query fields.
* @param { Object } activeFilters - Filters to apply to the array elements.
* @param { Array } items - Array of objects to filter and sort.
* @returns { Array } - Filtered and sorted array of objects.
*/
export const filterAndSort = (config, activeFilters, items) => {
const {
sorting,
sortingDirection,
...filters
} = activeFilters;

const filteredItems = items.filter(item => {
return Object.entries(filters)
.filter(entry => !isNil(entry[1]))
.every(([key, value]) => {
const itemKey = config.filterMap?.[key] || key;

if (Array.isArray(value)) {
return value.includes(item[itemKey]);
}

if (key === SEARCH_PARAMETER) {
return config.queryIndexes.some(field => (
normalizeValue(get(item, field, '')).includes(normalizeValue(value))
));
}

return get(item, itemKey, '').includes(value);
});
});

if (sorting) {
filteredItems.sort((a, b) => {
const pathToField = config.sortingMap?.[sorting] || sorting;

const valueA = normalizeValue(get(a, pathToField, ''));
const valueB = normalizeValue(get(b, pathToField, ''));

return sortingDirection === 'descending' ? valueB.localeCompare(valueA) : valueA.localeCompare(valueB);
});
}

return filteredItems;
};
59 changes: 54 additions & 5 deletions lib/FindLocation/useLocationsRecords.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
import omit from 'lodash/omit';
import { useEffect, useState } from 'react';

import { useLocations } from '../hooks';
import { FILTERS_CONFIG } from './configs';
import { filterAndSort } from './filterAndSort';

const hydrateLocations = (
locations,
selectedLocations,
institutionsMap,
campusessMap,
librariesMap,
) => {
return locations.map(location => ({
...location,
isAssigned: Boolean(selectedLocations[location.id]),
institution: institutionsMap[location.institutionId],
campus: campusessMap[location.campusId],
library: librariesMap[location.libraryId],
}));
};

const dehydrateLocations = (hydratedLocations) => {
return hydratedLocations.map((location) => omit(location, ['isAssigned']));
};

export const useLocationsRecords = ({
filters,
selectedLocations,
institutionsMap,
campusessMap,
librariesMap,
}) => {
const [isProcessing, setIsProcessing] = useState(false);
const [locationRecords, setLocationRecords] = useState([]);
const { locations, isLoading: isLocationsLoading } = useLocations();

export const useLocationsRecords = () => {
const {
useEffect(() => {
Promise.resolve(true)
.then(setIsProcessing)
.then(() => hydrateLocations(locations, selectedLocations, institutionsMap, campusessMap, librariesMap))
.then((items) => filterAndSort(FILTERS_CONFIG, filters, items))
.then(dehydrateLocations)
.then(setLocationRecords)
.then(() => setIsProcessing(false));
}, [
filters,
locations,
isLoading,
} = useLocations();
selectedLocations,
institutionsMap,
campusessMap,
librariesMap,
]);

const isLoading = isLocationsLoading || isProcessing;

return {
locations,
locations: locationRecords,
isLoading,
}
}

0 comments on commit f7c8e3d

Please sign in to comment.