import { Box, Tab, Tabs } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { useListContext, useResourceContext } from 'react-admin'; import { ReactElement, useCallback, useEffect, useMemo, useRef } from 'react'; import { useListViewContext } from './ListViewProvider'; import { useSessionStorage } from '@/hooks'; import { useSearchParams } from 'react-router-dom'; import _ from 'lodash'; type ListTabToolbarConfig = { label: string; key: string; icon?: ReactElement; filter: Record; default?: boolean; }; interface ListTabsToolbarProps { tabs?: ListTabToolbarConfig[]; } interface TabFilterState { filterValues: Record; displayedFilters: Record; } function useListTabs(tabs: ListTabToolbarConfig[]) { const [searchParams] = useSearchParams(); const resource = useResourceContext(); const { setCurrentTabKey } = useListViewContext(); const { filterValues, displayedFilters, setFilters } = useListContext(); const resourceKey = useMemo(() => resource.replace(/^entities\//, ''), [resource]); const tabGroupKey = `${resourceKey}-tab-group`; const tabFiltersKey = `${resourceKey}-tab-filters`; const defaultTabIndex = useMemo(() => tabs.findIndex((tab) => tab.default) ?? 0, [tabs]); const [currentTab, setCurrentTab] = useSessionStorage(tabGroupKey, defaultTabIndex) as [number, (v: number) => void]; const [tabFilterStates, setTabFilterStates] = useSessionStorage(tabFiltersKey, {}) as [ Record, (v: any) => void ]; const initialized = useRef(false); const switchingTab = useRef(false); const handleTabChange = useCallback( (index: number) => { const tab = tabs[index]; if (!tab) return; setCurrentTab(index); setCurrentTabKey?.(tab.key); const savedState = tabFilterStates?.[tab.key]; const nextFilters = savedState?.filterValues ?? tab.filter; const nextDisplayed = savedState?.displayedFilters ?? {}; if (!_.isEqual(nextFilters, filterValues) || !_.isEqual(nextDisplayed, displayedFilters)) { switchingTab.current = true; setFilters(nextFilters, nextDisplayed); } }, [tabs, tabFilterStates, filterValues, displayedFilters, setCurrentTab, setCurrentTabKey, setFilters] ); useEffect(() => { if (initialized.current) return; initialized.current = true; const param = searchParams.get(tabGroupKey); const initialIndex = param != null ? Number(param) : Number(currentTab); const hasUrlFilter = searchParams.get('filter') !== null; if (hasUrlFilter) { setCurrentTab(initialIndex); setCurrentTabKey?.(tabs[initialIndex]?.key); } else { handleTabChange(initialIndex); } }, [searchParams, tabGroupKey, currentTab, setCurrentTab, setCurrentTabKey, handleTabChange, tabs]); useEffect(() => { if (!initialized.current || switchingTab.current) { switchingTab.current = false; return; } const currentTabKey = tabs[Number(currentTab)]?.key; if (!currentTabKey) return; setTabFilterStates((prev: Record) => { const prevState = prev?.[currentTabKey]; if ( _.isEqual(prevState?.filterValues, filterValues) && _.isEqual(prevState?.displayedFilters, displayedFilters) ) { return prev; } return { ...prev, [currentTabKey]: { filterValues, displayedFilters } }; }); }, [filterValues, displayedFilters, currentTab, tabs, setTabFilterStates]); return { currentTab, handleTabChange: (_: any, val: number) => handleTabChange(val) }; } function ListTabsToolbar({ tabs = [] }: ListTabsToolbarProps) { const theme = useTheme(); const { currentTab, handleTabChange } = useListTabs(tabs); if (tabs.length > 0 && tabs.some((tab) => !tab.key)) { throw new Error('ListTabsToolbar: Each tab must have a unique key.'); } if (tabs.length === 0) return null; return ( {tabs.map((tab) => ( ))} ); } export { type ListTabToolbarConfig, ListTabsToolbar };