import React, { useState, useEffect, useImperativeHandle, forwardRef, } from 'react'; import _ from 'lodash'; import i18next from 'i18next'; import { ThemeProvider } from 'styled-components'; import { getTheme } from '../utils/theme'; import { Container, ContainerList, ContainerView, ContentContainer, Header, LoadingRowItem, Box, ItemContainer, EmptyStateContainer, } from './styledComponents'; import { Props } from './interfaces'; import { filterAndSortListBy, getPaginatedData, sortListBy } from './utils'; import { ORDER_TYPE, DEFAULT_PROPS, NB_EMPTY_ROWS_LOADING_STATE, } from './constants'; import ListOptions from './components/ListOptions'; import ListViewContent from './components/ListViewContent'; import ListViewHeader from './components/ListViewHeader'; import Paginator from './components/Paginator'; function renderLoadingState(columns) { return ( <> {_.times(NB_EMPTY_ROWS_LOADING_STATE, (index) => ( {columns && columns.map(({ name, large, minWidth }) => ( ))} ))} ); } const ListView = forwardRef((props: Props, ref): JSX.Element => { const { columns, data, isLoading, minWidth, defaultCurrentPage, handleCurrentPageChange, defaultOrderBy, handleOrderByChange, defaultOrderType, handleOrderTypeChange, defaultMaxPerPage, handleMaxPerPageChange, maxPerPageOptions, hideAllPerPageOption, hideSearchbar, defaultSearchInput, handleSearchInputChange, actions, actionsOnHover, rowActions, renderEmptyState, maxActionsInLine, exportFunction, actionOnClick, placeholderShape, placeholderShapePlural, padding, margin, setSelectedItems, renderFilterButton, languageCode, forceEnableSelection, disableSelection, theme, disableListOptions, disableMultipleSelection, disableFullSelection, disableFooter, disableResetSelectionOnNavigation, // Dynamic fetch queryParams, onQueryParamsChange, countElements, markerConfiguration, allSelectedByDefault, minActionsInActionsDropdown, } = props; const updatedTheme = getTheme(theme, 'listView'); const [orderBy, setOrderBy] = useState(defaultOrderBy); const [orderType, setOrderType] = useState<'asc' | 'desc'>(defaultOrderType); const [maxPerPage, setMaxPerPage] = useState(defaultMaxPerPage); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [filteredData, setFilteredData] = useState([]); const [selectedCount, setSelectedCount] = useState(0); const [currentPage, setCurrentPage] = useState(defaultCurrentPage); const [searchInput, setSearchInput] = useState(defaultSearchInput); useEffect(() => { setSearchInput(defaultSearchInput); }, [defaultSearchInput]); useEffect(() => { if (Number.isInteger(defaultMaxPerPage) && defaultMaxPerPage > 0) { return setMaxPerPage(defaultMaxPerPage); } return setMaxPerPage(DEFAULT_PROPS.maxPerPage); }, [defaultMaxPerPage]); useEffect(() => { const columnPropertyKeys = columns.map((column) => column.propertyKey); if (columnPropertyKeys.includes(defaultOrderBy)) { return setOrderBy(defaultOrderBy); } return setOrderBy(columnPropertyKeys[0]); }, [columns, defaultOrderBy]); useEffect(() => { if ( defaultOrderType === ORDER_TYPE.ASCENDING || defaultOrderType === ORDER_TYPE.DESCENDING ) { return setOrderType(defaultOrderType); } return setOrderType(ORDER_TYPE.ASCENDING); }, [defaultOrderType]); const [filteredDataCount, setFilteredDataCount] = useState( queryParams ? countElements : filteredData.length ); const [filterablePropertyKeys, setFilterablePropertyKeys] = useState< string[] >([]); const [paginatedData, setPaginatedData] = useState([[]]); const [notSelectableItemsCount, setNotSelectableItemsCount] = useState(0); const selectAllRows = () => { const items = disableFooter ? filteredData : paginatedData[ onQueryParamsChange && !!currentPage ? 0 : currentPage - 1 ]; const areRowsSelected = selectedCount > 0; const areAllRowsSelected = !!items && selectedCount === items.length; const selectedItems: unknown[] = []; // eslint-disable-next-line no-nested-ternary const startPaginatedIndex = disableFooter ? 0 : onQueryParamsChange ? 0 : (currentPage - 1) * maxPerPage; // eslint-disable-next-line no-nested-ternary const endPaginatedIndex = disableFooter ? (items || []).length : onQueryParamsChange ? maxPerPage : currentPage * maxPerPage; const updatedFilteredData = filteredData.map((item, index) => { if (item.isNotSelectable) { return item; } if (index < startPaginatedIndex || index >= endPaginatedIndex) { return { ...item, isRowSelected: false, }; } if (areRowsSelected && !areAllRowsSelected) { return { ...item, isRowSelected: false, }; } if (!areAllRowsSelected) { selectedItems.push(item); } return { ...item, isRowSelected: !areAllRowsSelected, }; }); setFilteredData(updatedFilteredData); setSelectedCount(selectedItems.length); if (setSelectedItems) { setSelectedItems(selectedItems); } }; const resetSelection = () => { if (disableResetSelectionOnNavigation) { return; } const startPaginatedIndex = (currentPage - 1) * maxPerPage; const endPaginatedIndex = currentPage * maxPerPage; const updatedFilteredData = filteredData.map((item, index) => { if (index >= startPaginatedIndex && index <= endPaginatedIndex) { return { ...item, isRowSelected: false, }; } return item; }); setFilteredData(updatedFilteredData); if (setSelectedItems) { setSelectedItems([]); } setSelectedCount(0); }; useEffect(() => { i18next.changeLanguage(languageCode); }, [languageCode]); useEffect(() => { const propertyKeys = columns .filter((column) => !column.excludeFromSearch) .map((column) => column.propertyKey); if (!_.isEqual(propertyKeys, filterablePropertyKeys)) { setFilterablePropertyKeys(propertyKeys); } }, [columns, filterablePropertyKeys]); useEffect(() => { if (onQueryParamsChange) { setFilteredData(data); return; } const filteredAndSortedData = filterAndSortListBy( _.cloneDeep(data), orderBy, orderType, filterablePropertyKeys, searchInput ); setFilteredData(filteredAndSortedData); // eslint-disable-next-line react-hooks/exhaustive-deps }, [filterablePropertyKeys, searchInput, data, onQueryParamsChange]); useEffect(() => { if (!data || !data.length || !filteredData.length) { return; } if (allSelectedByDefault) { selectAllRows(); return; } if (!queryParams) { const alreadySelectedItems = data.filter((item) => item.isRowSelected); setSelectedCount(alreadySelectedItems.length); if (setSelectedItems) { setSelectedItems(alreadySelectedItems); } const itemsNotSelectable = data.filter((item) => item.isNotSelectable); setNotSelectableItemsCount(itemsNotSelectable.length); return; } if (setSelectedItems) { setSelectedItems([]); } setSelectedCount(0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [data]); useEffect(() => { if (currentPage === 1 && onQueryParamsChange) { onQueryParamsChange({ ...queryParams, }); } const itemsCount = onQueryParamsChange ? countElements : filteredDataCount; setCurrentPage( defaultCurrentPage > 0 && defaultCurrentPage <= Math.ceil(itemsCount / maxPerPage) ? defaultCurrentPage : DEFAULT_PROPS.currentPage ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ countElements, filteredDataCount, maxPerPage, searchInput, orderBy, orderType, ]); // Order list if no dynamic pagination is set useEffect(() => { if (!onQueryParamsChange) { const sortedFilteredData = sortListBy(filteredData, orderBy, orderType); setFilteredData(sortedFilteredData); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [orderBy, orderType]); useEffect(() => { if (queryParams && onQueryParamsChange) { onQueryParamsChange({ ...queryParams, }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentPage]); useEffect(() => { if (filteredDataCount !== filteredData.length) { setFilteredDataCount( onQueryParamsChange ? countElements : filteredData.length ); const alreadySelectedItems = filteredData.filter( (item) => item.isRowSelected ); setSelectedCount(alreadySelectedItems.length); const itemsNotSelectable = filteredData.filter( (item) => item.isNotSelectable ); setNotSelectableItemsCount(itemsNotSelectable.length); } setPaginatedData(getPaginatedData(filteredData, maxPerPage)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ countElements, filteredData, maxPerPage, filteredDataCount, setFilteredDataCount, ]); useImperativeHandle(ref, () => ({ resetPagination() { if (currentPage !== 1) { setCurrentPage(1); } }, getFilteredData() { return filteredData; }, })); const setOrder = (columnId) => { let updatedOrderType: 'asc' | 'desc' = 'asc'; if (orderBy === columnId) { updatedOrderType = orderType === ORDER_TYPE.ASCENDING ? ORDER_TYPE.DESCENDING : ORDER_TYPE.ASCENDING; } else { setOrderBy(columnId); if (handleOrderByChange && handleCurrentPageChange) { handleOrderByChange(columnId); handleCurrentPageChange(1); } } setOrderType(updatedOrderType); if (handleOrderTypeChange && handleCurrentPageChange) { handleOrderTypeChange(updatedOrderType); handleCurrentPageChange(1); } }; const selectRow = (_row, index) => { const indexInFilteredData = !currentPage || onQueryParamsChange ? index : (currentPage - 1) * maxPerPage + index; if (!filteredData[indexInFilteredData]) { return; } if (disableMultipleSelection) { const { isRowSelected } = filteredData[indexInFilteredData]; const updatedFilteredData = filteredData.map((item) => { return { ...item, isRowSelected: false }; }); updatedFilteredData[indexInFilteredData].isRowSelected = !isRowSelected; if (!isRowSelected) { setSelectedCount(1); } else { setSelectedCount(0); } setFilteredData(updatedFilteredData); if (setSelectedItems) { const selectedRows = updatedFilteredData.filter( (item) => item.isRowSelected ); setSelectedItems(selectedRows); } return; } const updatedFilteredData = _.cloneDeep(filteredData); const { isRowSelected } = updatedFilteredData[indexInFilteredData]; updatedFilteredData[indexInFilteredData].isRowSelected = !isRowSelected; setFilteredData(updatedFilteredData); if (!isRowSelected) { setSelectedCount((count) => count + 1); } else { setSelectedCount((count) => count - 1); } if (setSelectedItems) { const selectedRows = updatedFilteredData.filter( (item) => item.isRowSelected ); setSelectedItems(selectedRows); } }; const renderContent = (): JSX.Element => { if (!filteredData.length && !isLoading) { return (
{renderEmptyState && (renderEmptyState() as string)}
); } // eslint-disable-next-line no-nested-ternary const isSelectable = forceEnableSelection ? true : disableSelection ? false : !!actions?.length; return (
{isLoading && renderLoadingState(columns)} {!isLoading && ( )}
); }; return ( {!disableListOptions && ( { resetSelection(); setSearchInput(input); if (handleSearchInputChange && handleCurrentPageChange) { handleSearchInputChange(input); handleCurrentPageChange(1); } }} maxActionsInLine={maxActionsInLine || 0} itemsCount={ onQueryParamsChange ? countElements : filteredDataCount } selectedRows={filteredData.filter((row) => row.isRowSelected)} exportFunction={exportFunction} placeholderShape={placeholderShape} placeholderShapePlural={placeholderShapePlural} renderFilterButton={renderFilterButton} minActionsInActionsDropdown={minActionsInActionsDropdown} /> )} {renderContent()} {!disableFooter && filteredData.length > 0 && ( { resetSelection(); setMaxPerPage(option); if (handleMaxPerPageChange && handleCurrentPageChange) { handleMaxPerPageChange(option); handleCurrentPageChange(1); } }} currentPage={currentPage} setCurrentPage={(indexCurrentPage) => { resetSelection(); setCurrentPage(indexCurrentPage); if (handleCurrentPageChange) { handleCurrentPageChange(indexCurrentPage); } }} isLoading={isLoading} maxPerPageOptions={maxPerPageOptions} hideAllPerPageOption={hideAllPerPageOption} /> )} ); }); ListView.defaultProps = { isLoading: false, minWidth: 800, countElements: 0, defaultCurrentPage: DEFAULT_PROPS.currentPage, handleCurrentPageChange: undefined, defaultOrderBy: undefined, handleOrderByChange: undefined, defaultOrderType: ORDER_TYPE.ASCENDING, handleOrderTypeChange: undefined, defaultMaxPerPage: DEFAULT_PROPS.maxPerPage, handleMaxPerPageChange: undefined, maxPerPageOptions: [10, 20, 50, 100], hideAllPerPageOption: false, hideSearchbar: false, defaultSearchInput: '', handleSearchInputChange: undefined, actions: undefined, actionsOnHover: undefined, rowActions: undefined, renderEmptyState: undefined, maxActionsInLine: 2, exportFunction: undefined, renderFilterButton: undefined, actionOnClick: undefined, placeholderShape: undefined, placeholderShapePlural: undefined, padding: '0', margin: null, setSelectedItems: undefined, queryParams: null, onQueryParamsChange: undefined, languageCode: 'fr', forceEnableSelection: false, disableSelection: false, disableMultipleSelection: false, disableResetSelectionOnNavigation: false, theme: null, disableListOptions: false, disableFooter: false, markerConfiguration: undefined, allSelectedByDefault: false, minActionsInActionsDropdown: 2, }; export default ListView;