import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; import { Chip } from "./Chip"; import { PageSelector } from "./Inputs/PageSelector"; import { Ico, Icon } from "./Ico"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { cJoin } from "helpers/className"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; interface Group { name: string; items: T[]; totalCount: number; } const defaultGroupSortingFunction = (a: Group, b: Group) => a.name.localeCompare(b.name); const defaultGroupCountingFunction = () => 1; const defaultFilteringFunction = () => true; const defaultSortingFunction = () => 0; const defaultGroupingFunction = () => [""]; interface Props { // Items items: T[]; getItemId: (item: T) => string; renderItem: (props: { item: T }) => JSX.Element; renderWhenEmpty?: () => JSX.Element; // Pagination paginationItemPerPage?: number; paginationSelectorTop?: boolean; paginationSelectorBottom?: boolean; paginationScroolTop?: boolean; // Searching searchingTerm?: string; searchingBy?: (item: T) => string; searchingCaseInsensitive?: boolean; // Grouping groupingFunction?: (item: T) => string[]; groupSortingFunction?: (a: Group, b: Group) => number; groupCountingFunction?: (item: T) => number; // Filtering filteringFunction?: (item: T) => boolean; // Sorting sortingFunction?: (a: T, b: T) => number; // Other className?: string; langui: AppStaticProps["langui"]; } export const SmartList = ({ items, getItemId, renderItem: RenderItem, renderWhenEmpty: RenderWhenEmpty, paginationItemPerPage = Infinity, paginationSelectorTop = true, paginationSelectorBottom = true, paginationScroolTop = true, searchingTerm, searchingBy, searchingCaseInsensitive = true, groupingFunction = defaultGroupingFunction, groupSortingFunction = defaultGroupSortingFunction, groupCountingFunction = defaultGroupCountingFunction, filteringFunction = defaultFilteringFunction, sortingFunction = defaultSortingFunction, className, langui, }: Props): JSX.Element => { const [page, setPage] = useState(0); useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop); useEffect( () => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction, items] ); const searchFilter = useCallback(() => { if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) { if (searchingCaseInsensitive) { return items.filter((item) => searchingBy(item).toLowerCase().includes(searchingTerm.toLowerCase()) ); } return items.filter((item) => searchingBy(item).includes(searchingTerm)); } return items; }, [items, searchingBy, searchingCaseInsensitive, searchingTerm]); const filteredItems = useMemo(() => { const filteredBySearch = searchFilter(); return filteredBySearch.filter(filteringFunction); }, [filteringFunction, searchFilter]); const sortedItem = useMemo( () => filteredItems.sort(sortingFunction), [filteredItems, sortingFunction] ); const groups = useMemo(() => { const memo: Group[] = []; sortedItem.forEach((item) => { groupingFunction(item).forEach((category) => { const index = memo.findIndex((group) => group.name === category); if (index === -1) { memo.push({ name: category, items: [item], totalCount: groupCountingFunction(item), }); } else { memo[index].items.push(item); memo[index].totalCount += groupCountingFunction(item); } }); }); return memo.sort(groupSortingFunction); }, [ groupCountingFunction, groupSortingFunction, groupingFunction, sortedItem, ]); const pages = useMemo(() => { const memo: Group[][] = []; let currentPage: Group[] = []; let remainingSlots = paginationItemPerPage; let loopSafeguard = 1000; const newPage = () => { memo.push(currentPage); currentPage = []; remainingSlots = paginationItemPerPage; }; for (const group of groups) { let remainingItems = group.items.length; while (remainingItems > 0 && loopSafeguard >= 0) { loopSafeguard--; const currentIndex = group.items.length - remainingItems; if ( remainingSlots <= 0 || (currentIndex === 0 && remainingItems > remainingSlots && remainingItems <= paginationItemPerPage) ) { newPage(); } const slicedGroup: Group = { name: group.name, items: group.items.slice(currentIndex, currentIndex + remainingSlots), totalCount: group.totalCount, }; remainingItems -= slicedGroup.items.length; remainingSlots -= slicedGroup.items.length; currentPage.push(slicedGroup); } } if (currentPage.length > 0) { newPage(); } return memo; }, [groups, paginationItemPerPage]); return ( <> {pages.length > 1 && paginationSelectorTop && ( )}
{pages[page]?.length > 0 ? ( pages[page]?.map( (group) => group.items.length > 0 && ( {group.name.length > 0 && (

{group.name}

)}
{group.items.map((item) => ( ))}
) ) ) : isDefined(RenderWhenEmpty) ? ( ) : ( )}
{pages.length > 1 && paginationSelectorBottom && ( )} ); }; /* * ╭──────────────────────╮ * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰────────────────────────────────────── */ interface DefaultRenderWhenEmptyProps { langui: AppStaticProps["langui"]; } const DefaultRenderWhenEmpty = ({ langui }: DefaultRenderWhenEmptyProps) => (

{langui.no_results_message}

);