Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Datagrid): remove filter tags individually from FilterSummary #3582

Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export let Datagrid = React.forwardRef(
};

return (
<FilterProvider filters={filters}>
<FilterProvider filters={filters} filterProps={filterProps}>
<InlineEditProvider>
<div
{...rest}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import React, { useContext, useEffect, useRef } from 'react';
import { Table, TableContainer } from '@carbon/react';
import { carbon, pkg } from '../../../settings';

import { CLEAR_FILTERS } from './addons/Filtering/constants';
import {
CLEAR_FILTERS,
CLEAR_SINGLE_FILTER,
} from './addons/Filtering/constants';
import DatagridBody from './DatagridBody';
import DatagridHead from './DatagridHead';
import DatagridToolbar from './DatagridToolbar';
Expand All @@ -23,6 +26,8 @@ import { handleGridKeyPress } from './addons/InlineEdit/handleGridKeyPress';
import { px } from '@carbon/layout';
import { useClickOutside } from '../../../global/js/hooks';
import { useMultipleKeyTracking } from '../../DataSpreadsheet/hooks';
import { useSubscribeToEventEmitter } from './addons/Filtering/hooks';
import { clearSingleFilter } from './addons/Filtering/FilterProvider';

const blockClass = `${pkg.prefix}--datagrid`;

Expand All @@ -49,6 +54,7 @@ export const DatagridContent = ({ datagridState, title }) => {
DatagridActions,
totalColumnsWidth,
gridRef,
setAllFilters,
state,
page,
rows,
Expand Down Expand Up @@ -141,6 +147,10 @@ export const DatagridContent = ({ datagridState, title }) => {
}
}, [withInlineEdit, tableId, totalColumnsWidth, datagridState, gridActive]);

useSubscribeToEventEmitter(CLEAR_SINGLE_FILTER, (id) =>
clearSingleFilter(id, setAllFilters, state)
);

const renderFilterSummary = () =>
state.filters.length > 0 && (
<FilterSummary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ const FilterPanel = ({
/** State */
const [showDividerLine, setShowDividerLine] = useState(false);

/** Context */
const { panelOpen, setPanelOpen } = useContext(FilterContext);

const {
filtersState,
prevFiltersObjectArrayRef,
Expand All @@ -71,6 +74,7 @@ const FilterPanel = ({
variation: PANEL,
reactTableFiltersState,
onCancel,
panelOpen,
});

/** Refs */
Expand All @@ -90,9 +94,6 @@ const FilterPanel = ({
/** Memos */
const showActionSet = useMemo(() => updateMethod === BATCH, [updateMethod]);

/** Context */
const { panelOpen, setPanelOpen } = useContext(FilterContext);

/** Methods */
const closePanel = () => {
cancel();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/**
* Copyright IBM Corp. 2022, 2022
* Copyright IBM Corp. 2022, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { createContext, useState } from 'react';
import PropTypes from 'prop-types';
import { DATE, DROPDOWN, NUMBER, RADIO, CHECKBOX } from './constants';
import {
DATE,
DROPDOWN,
NUMBER,
RADIO,
CHECKBOX,
CLEAR_SINGLE_FILTER,
} from './constants';

export const FilterContext = createContext();

Expand All @@ -27,22 +34,102 @@ const EventEmitter = {
},
};

const prepareFiltersForTags = (filters) => {
const removeFilterItem = (state, index) => state.splice(index, 1);

const updateFilterState = (state, type, value) => {
if (type === CHECKBOX) {
return;
}
if (type === DATE) {
const filterTagIndex = state.findIndex(
(val) =>
formatDateRange(val.value[0], val.value[1]) ===
formatDateRange(value[0], value[1])
);
return removeFilterItem(state, filterTagIndex);
}
const filterTagIndex = state.findIndex((val) => val.value === value);
return removeFilterItem(state, filterTagIndex);
};

export const clearSingleFilter = ({ key, value }, setAllFilters, state) => {
const tempState = [...state.filters];
tempState.forEach((f, filterIndex) => {
if (f.id === key) {
matthewgallo marked this conversation as resolved.
Show resolved Hide resolved
const filterValues = f.value;
const filterType = f.type;
updateFilterState(tempState, filterType, value);
if (filterType === CHECKBOX) {
/**
When all checkboxes of a group are all unselected the value still exists in the filtersObjectArray
This checks if all the checkboxes are selected = false and removes it from the array
*/
const valueIndex = filterValues.findIndex((val) => val.id === value);
filterValues[valueIndex].selected = false;
const updatedFilterObject = {
...f,
value: [...filterValues],
};
tempState[filterIndex] = updatedFilterObject;
const index = tempState.findIndex((filter) => filter.id === key);

// If all the selected state is false remove from array
const shouldRemoveFromArray = tempState[index].value.every(
(val) => val.selected === false
);

if (shouldRemoveFromArray) {
removeFilterItem(tempState, index);
}
}
}
});
setAllFilters(tempState);
};

const handleSingleFilterRemoval = (key, value) => {
EventEmitter.dispatch(CLEAR_SINGLE_FILTER, { key, value });
};

const formatDateRange = (startDate, endDate) => {
const startDateObj = new Date(startDate);
const endDateObj = new Date(endDate);
return `${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()}`;
};

const prepareFiltersForTags = (filters, renderDateLabel) => {
const tags = [];

filters.forEach(({ id, type, value }) => {
const sharedFilterProps = {
filter: true,
onClose: () => handleSingleFilterRemoval(id, value),
};

if (type === DROPDOWN || type === RADIO || type === NUMBER) {
tags.push({ key: id, value });
tags.push({
key: id,
value,
...sharedFilterProps,
});
} else if (type === DATE) {
const [startDate, endDate] = value;
tags.push({
key: id,
value: `${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`,
value:
renderDateLabel?.(startDate, endDate) ??
formatDateRange(startDate, endDate),
...sharedFilterProps,
});
} else if (type === CHECKBOX) {
value.forEach((checkbox) => {
if (checkbox.selected) {
tags.push({ key: id, value: checkbox.value });
tags.push({
key: id,
value: checkbox.value,
...sharedFilterProps,
onClose: () => handleSingleFilterRemoval(id, checkbox.value),
});
}
});
}
Expand All @@ -51,8 +138,9 @@ const prepareFiltersForTags = (filters) => {
return tags;
};

export const FilterProvider = ({ children, filters }) => {
const filterTags = prepareFiltersForTags(filters);
export const FilterProvider = ({ children, filters, filterProps }) => {
const { renderDateLabel } = filterProps || {};
const filterTags = prepareFiltersForTags(filters, renderDateLabel);
const [panelOpen, setPanelOpen] = useState(false);

const value = { filterTags, EventEmitter, panelOpen, setPanelOpen };
Expand All @@ -67,5 +155,6 @@ FilterProvider.propTypes = {
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
filterProps: PropTypes.object,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const DROPDOWN = 'dropdown';

/** Constants for event emitters */
export const CLEAR_FILTERS = 'clearFilters';
export const CLEAR_SINGLE_FILTER = 'clearSingleFilter';

/** Constants for panel dimensions */
export const PANEL_WIDTH = 320;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* US Government Users Restricted Rights - Use, duplication or disclosure
* restricted by GSA ADP Schedule Contract with IBM Corp.
*/
import React, { useState, useRef, useEffect } from 'react';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import {
Checkbox,
DatePicker,
Expand All @@ -28,6 +28,7 @@ import {
PANEL,
} from '../constants';
import { getInitialStateFromFilters } from '../utils';
import { usePreviousValue } from '../../../../../../global/js/hooks';

const useFilters = ({
updateMethod,
Expand All @@ -36,6 +37,7 @@ const useFilters = ({
variation,
reactTableFiltersState,
onCancel = () => {},
panelOpen,
}) => {
/** State */
const [filtersState, setFiltersState] = useState(
Expand All @@ -46,21 +48,37 @@ const useFilters = ({
reactTableFiltersState
);

const previousState = usePreviousValue({ panelOpen });

// When using batch actions we have to store the filters to then apply them later
const prevFiltersRef = useRef(JSON.stringify(filtersState));
const lastAppliedFilters = useRef(JSON.stringify(reactTableFiltersState));
const prevFiltersObjectArrayRef = useRef(JSON.stringify(filtersObjectArray));

const holdingPrevFiltersRef = useRef();
const holdingLastAppliedFiltersRef = useRef([]);
const holdingPrevFiltersObjectArrayRef = useRef([]);

/** Methods */
// If the user decides to cancel or click outside the flyout, it reverts back to the filters that were
// there when they opened the flyout
const revertToPreviousFilters = () => {
setFiltersState(JSON.parse(prevFiltersRef.current));
setFiltersObjectArray(JSON.parse(prevFiltersObjectArrayRef.current));
setAllFilters(JSON.parse(lastAppliedFilters.current));

// Set the temp prev refs, these will be used to populate the prev values once the
// panel opens again
holdingPrevFiltersRef.current = JSON.parse(prevFiltersRef.current);
holdingLastAppliedFiltersRef.current = JSON.parse(
prevFiltersObjectArrayRef.current
);
holdingPrevFiltersObjectArrayRef.current = JSON.parse(
lastAppliedFilters.current
);
};

const reset = () => {
const reset = useCallback(() => {
// When we reset we want the "initialFilters" to be an empty array
const resetFiltersArray = [];

Expand All @@ -82,7 +100,7 @@ const useFilters = ({
prevFiltersObjectArrayRef.current = JSON.stringify(
initialFiltersObjectArray
);
};
}, [filters, setAllFilters, variation]);

const applyFilters = ({ column, value, type }) => {
// If no end date is selected return because we need the end date to do computations
Expand Down Expand Up @@ -331,6 +349,35 @@ const useFilters = ({
return <React.Fragment key={column}>{filter}</React.Fragment>;
};

/** This useEffect will properly handle the previous filters when the panel closes
* 1. If the panel closes we need to call the reset fn but also store the
* previous filters in a (new) temporary place.
* 2. When the panel opens again, take the values from the temporary place
* and populate the filter state with them
*/
useEffect(() => {
if (!panelOpen && previousState?.panelOpen) {
setAllFilters(holdingLastAppliedFiltersRef.current);
}
if (panelOpen && !previousState?.panelOpen) {
if (
holdingPrevFiltersRef.current &&
holdingLastAppliedFiltersRef.current &&
holdingPrevFiltersObjectArrayRef.current
) {
setFiltersState(holdingPrevFiltersRef.current);
setFiltersObjectArray(holdingLastAppliedFiltersRef.current);
setAllFilters(JSON.parse(prevFiltersObjectArrayRef.current));
}
}
}, [
panelOpen,
previousState,
previousState?.panelOpen,
reset,
setAllFilters,
]);

const cancel = () => {
// Reverting to previous filters only applies when using batch actions
if (updateMethod === BATCH) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,11 @@ export const PanelBatch = prepareStory(FilteringTemplateWrapper, {
onPanelOpen: action('onPanelOpen'),
onPanelClose: action('onPanelClose'),
panelTitle: 'Filter',
renderDateLabel: (start, end) => {
const startDateObj = new Date(start);
const endDateObj = new Date(end);
return `${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()}`;
},
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ const useFiltering = (hooks) => {
date: (rows, id, [startDate, endDate]) => {
return rows.filter((row) => {
const rowValue = row.values[id];
const startDateObj =
typeof startDate === 'object' ? startDate : new Date(startDate);
const endDateObj =
typeof endDate === 'object' ? endDate : new Date(endDate);
if (
rowValue.getTime() <= endDate.getTime() &&
rowValue.getTime() >= startDate.getTime()
rowValue.getTime() <= endDateObj.getTime() &&
rowValue.getTime() >= startDateObj.getTime()
) {
// In date range
return true;
Expand Down
Loading