import { useState, useMemo, useCallback, useEffect } from "react" import { useSearchParams } from "react-router-dom" import { HttpTypes } from "@medusajs/types" import { useViewConfigurations, useViewConfiguration, } from "../use-view-configurations" import { useEntityColumns } from "../api/views" import { useFeatureFlag } from "../../providers/feature-flag-provider" import { useColumnState } from "./columns/use-column-state" import { useQueryParams } from "../use-query-params" import { calculateRequiredFields } from "../../lib/table/field-utils" export interface TableConfiguration { filters: Record sorting: { id: string; desc: boolean } | null search: string visible_columns?: string[] column_order?: string[] } export interface UseTableConfigurationOptions { entity: string pageSize?: number queryPrefix?: string filters?: Array<{ id: string }> } export interface UseTableConfigurationReturn { activeView: any createView: any updateView: any isViewConfigEnabled: boolean visibleColumns: Record columnOrder: string[] currentColumns: { visible: string[] order: string[] } setColumnOrder: (order: string[]) => void handleColumnVisibilityChange: (visibility: Record) => void currentConfiguration: TableConfiguration hasConfigurationChanged: boolean handleClearConfiguration: () => void apiColumns: HttpTypes.AdminColumn[] | undefined isLoadingColumns: boolean queryParams: Record requiredFields: string } function parseSortingState(value: string) { return value.startsWith("-") ? { id: value.slice(1), desc: true } : { id: value, desc: false } } export function useTableConfiguration({ entity, queryPrefix = "", filters = [], }: UseTableConfigurationOptions): UseTableConfigurationReturn { const isViewConfigEnabled = useFeatureFlag("view_configurations") const [_, setSearchParams] = useSearchParams() const { activeView, createView } = useViewConfigurations(entity) const currentActiveView = activeView?.view_configuration || null const { updateView } = useViewConfiguration( entity, currentActiveView?.id || "" ) const { columns: apiColumns, isLoading: isLoadingColumns } = useEntityColumns( entity, { enabled: isViewConfigEnabled, } ) const queryParams = useQueryParams( ["q", "order", "offset", "limit", ...filters.map((f) => f.id)], queryPrefix ) // Column state const { visibleColumns, columnOrder, currentColumns, setColumnOrder, handleColumnVisibilityChange, handleViewChange: originalHandleViewChange, } = useColumnState(apiColumns, currentActiveView) // Sync view configuration with URL and column state useEffect(() => { if (!apiColumns) { return } originalHandleViewChange(currentActiveView, apiColumns) setSearchParams((prev) => { // Clear existing query params const keysToDelete = Array.from(prev.keys()).filter( (key) => key.startsWith(queryPrefix + "_") || key === queryPrefix + "_q" || key === queryPrefix + "_order" ) keysToDelete.forEach((key) => prev.delete(key)) // Apply view configuration if (currentActiveView) { const viewConfig = currentActiveView.configuration if (viewConfig.filters) { Object.entries(viewConfig.filters).forEach(([key, value]) => { prev.set(`${queryPrefix}_${key}`, JSON.stringify(value)) }) } if (viewConfig.sorting) { const sortValue = viewConfig.sorting.desc ? `-${viewConfig.sorting.id}` : viewConfig.sorting.id prev.set(`${queryPrefix}_order`, sortValue) } if (viewConfig.search) { prev.set(`${queryPrefix}_q`, viewConfig.search) } } return prev }) }, [currentActiveView, apiColumns]) // Current configuration from URL const currentConfiguration = useMemo(() => { const currentFilters: Record = {} filters.forEach((filter) => { if (queryParams[filter.id] !== undefined) { currentFilters[filter.id] = JSON.parse(queryParams[filter.id] || "") } }) return { filters: currentFilters, sorting: queryParams.order ? parseSortingState(queryParams.order) : null, search: queryParams.q || "", } }, [filters, queryParams]) // Check if configuration has changed from view const [debouncedHasConfigChanged, setDebouncedHasConfigChanged] = useState(false) const hasConfigurationChanged = useMemo(() => { const currentFilters = currentConfiguration.filters const currentSorting = currentConfiguration.sorting const currentSearch = currentConfiguration.search const currentVisibleColumns = Object.entries(visibleColumns) .filter(([_, isVisible]) => isVisible) .map(([field]) => field) .sort() if (currentActiveView) { const viewFilters = currentActiveView.configuration.filters || {} const viewSorting = currentActiveView.configuration.sorting const viewSearch = currentActiveView.configuration.search || "" const viewVisibleColumns = [ ...(currentActiveView.configuration.visible_columns || []), ].sort() const viewColumnOrder = currentActiveView.configuration.column_order || [] // Check filters const filterKeys = new Set([ ...Object.keys(currentFilters), ...Object.keys(viewFilters), ]) for (const key of filterKeys) { if ( JSON.stringify(currentFilters[key]) !== JSON.stringify(viewFilters[key]) ) { return true } } // Check sorting const normalizedCurrentSorting = currentSorting || undefined const normalizedViewSorting = viewSorting || undefined if ( JSON.stringify(normalizedCurrentSorting) !== JSON.stringify(normalizedViewSorting) ) { return true } // Check search if (currentSearch !== viewSearch) { return true } // Check visible columns if ( JSON.stringify(currentVisibleColumns) !== JSON.stringify(viewVisibleColumns) ) { return true } // Check column order if (JSON.stringify(columnOrder) !== JSON.stringify(viewColumnOrder)) { return true } } else { // Check against defaults if (Object.keys(currentFilters).length > 0) { return true } if (currentSorting !== null) { return true } if (currentSearch !== "") { return true } if (apiColumns) { const currentVisibleSet = new Set(currentVisibleColumns) const defaultVisibleSet = new Set( apiColumns .filter((col) => col.default_visible) .map((col) => col.field) ) if ( currentVisibleSet.size !== defaultVisibleSet.size || [...currentVisibleSet].some((field) => !defaultVisibleSet.has(field)) ) { return true } const defaultOrder = apiColumns .sort((a, b) => (a.default_order ?? 500) - (b.default_order ?? 500)) .map((col) => col.field) if (JSON.stringify(columnOrder) !== JSON.stringify(defaultOrder)) { return true } } } return false }, [ currentActiveView, visibleColumns, columnOrder, currentConfiguration, apiColumns, ]) // Debounce configuration change detection useEffect(() => { const timer = setTimeout(() => { setDebouncedHasConfigChanged(hasConfigurationChanged) }, 50) return () => clearTimeout(timer) }, [hasConfigurationChanged]) // Clear configuration handler const handleClearConfiguration = useCallback(() => { if (apiColumns) { originalHandleViewChange(currentActiveView, apiColumns) } setSearchParams((prev) => { const keysToDelete = Array.from(prev.keys()).filter( (key) => key.startsWith(queryPrefix + "_") || key === queryPrefix + "_q" || key === queryPrefix + "_order" ) keysToDelete.forEach((key) => prev.delete(key)) if (currentActiveView?.configuration) { const viewConfig = currentActiveView.configuration if (viewConfig.filters) { Object.entries(viewConfig.filters).forEach(([key, value]) => { prev.set(`${queryPrefix}_${key}`, JSON.stringify(value)) }) } if (viewConfig.sorting) { const sortValue = viewConfig.sorting.desc ? `-${viewConfig.sorting.id}` : viewConfig.sorting.id prev.set(`${queryPrefix}_order`, sortValue) } if (viewConfig.search) { prev.set(`${queryPrefix}_q`, viewConfig.search) } } return prev }) }, [currentActiveView, apiColumns, queryPrefix]) // Calculate required fields based on visible columns const requiredFields = useMemo(() => { return calculateRequiredFields(entity, apiColumns, visibleColumns) }, [entity, apiColumns, visibleColumns]) return { activeView: currentActiveView, createView, updateView, isViewConfigEnabled, visibleColumns, columnOrder, currentColumns, setColumnOrder, handleColumnVisibilityChange, currentConfiguration, hasConfigurationChanged: debouncedHasConfigChanged, handleClearConfiguration, apiColumns, isLoadingColumns, queryParams, requiredFields, } }