Feat/datatable core enhancements (#13193)
**What** This PR adds core DataTable enhancements to support view configurations in the admin dashboard. This is part of a set of stacked PRs to add the feature to Medusa. - Puts handlers in place to update the visible columns in a table and the order in which they appear. - Adds a ViewPills component for displaying and switching between saved view configurations - Integrated view configuration hooks (useViewConfigurations) with the DataTable Note: Column drag-and-drop reordering and the column visibility UI controls are not included in this PR as they require additional UI library updates - which will come in the next PR. Example of what this looks like with the feature flag turned on - note the view pills with "default" in the top. This will expand when the data table behavior adds configuration. <img width="2492" height="758" alt="CleanShot 2025-08-13 at 2 31 35@2x" src="https://github.com/user-attachments/assets/ee770f1c-dae1-49da-b255-1a6d615789de" />
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { useState, useCallback, useMemo, useEffect, useRef } from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import type { ViewConfiguration } from "../../../hooks/use-view-configurations"
|
||||
|
||||
interface UseColumnStateReturn {
|
||||
visibleColumns: Record<string, boolean>
|
||||
columnOrder: string[]
|
||||
currentColumns: {
|
||||
visible: string[]
|
||||
order: string[]
|
||||
}
|
||||
setVisibleColumns: (visibility: Record<string, boolean>) => void
|
||||
setColumnOrder: (order: string[]) => void
|
||||
handleColumnVisibilityChange: (visibility: Record<string, boolean>) => void
|
||||
handleViewChange: (view: ViewConfiguration | null, apiColumns: HttpTypes.AdminViewColumn[]) => void
|
||||
initializeColumns: (apiColumns: HttpTypes.AdminViewColumn[]) => void
|
||||
}
|
||||
|
||||
export function useColumnState(
|
||||
apiColumns: HttpTypes.AdminViewColumn[] | undefined,
|
||||
activeView?: ViewConfiguration | null
|
||||
): UseColumnStateReturn {
|
||||
// Initialize state lazily to avoid unnecessary re-renders
|
||||
const [visibleColumns, setVisibleColumns] = useState<Record<string, boolean>>(() => {
|
||||
if (apiColumns?.length && activeView) {
|
||||
// If there's an active view, initialize with its configuration
|
||||
const visibility: Record<string, boolean> = {}
|
||||
apiColumns.forEach(column => {
|
||||
visibility[column.field] = activeView.configuration.visible_columns.includes(column.field)
|
||||
})
|
||||
return visibility
|
||||
} else if (apiColumns?.length) {
|
||||
return getInitialColumnVisibility(apiColumns)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const [columnOrder, setColumnOrder] = useState<string[]>(() => {
|
||||
if (activeView) {
|
||||
// If there's an active view, use its column order
|
||||
return activeView.configuration.column_order || []
|
||||
} else if (apiColumns?.length) {
|
||||
return getInitialColumnOrder(apiColumns)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const currentColumns = useMemo(() => {
|
||||
const visible = Object.entries(visibleColumns)
|
||||
.filter(([_, isVisible]) => isVisible)
|
||||
.map(([field]) => field)
|
||||
|
||||
return {
|
||||
visible,
|
||||
order: columnOrder,
|
||||
}
|
||||
}, [visibleColumns, columnOrder])
|
||||
|
||||
const handleColumnVisibilityChange = useCallback((visibility: Record<string, boolean>) => {
|
||||
setVisibleColumns(visibility)
|
||||
}, [])
|
||||
|
||||
const handleViewChange = useCallback((
|
||||
view: ViewConfiguration | null,
|
||||
apiColumns: HttpTypes.AdminViewColumn[]
|
||||
) => {
|
||||
if (view) {
|
||||
// Apply view configuration
|
||||
const newVisibility: Record<string, boolean> = {}
|
||||
apiColumns.forEach(column => {
|
||||
newVisibility[column.field] = view.configuration.visible_columns.includes(column.field)
|
||||
})
|
||||
setVisibleColumns(newVisibility)
|
||||
setColumnOrder(view.configuration.column_order)
|
||||
} else {
|
||||
// Reset to default visibility when no view is selected
|
||||
setVisibleColumns(getInitialColumnVisibility(apiColumns))
|
||||
setColumnOrder(getInitialColumnOrder(apiColumns))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const initializeColumns = useCallback((apiColumns: HttpTypes.AdminViewColumn[]) => {
|
||||
// Only initialize if we don't already have column state
|
||||
if (Object.keys(visibleColumns).length === 0) {
|
||||
setVisibleColumns(getInitialColumnVisibility(apiColumns))
|
||||
}
|
||||
if (columnOrder.length === 0) {
|
||||
setColumnOrder(getInitialColumnOrder(apiColumns))
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Track previous active view to detect changes
|
||||
const prevActiveViewRef = useRef<ViewConfiguration | null | undefined>()
|
||||
|
||||
// Sync local state when active view updates (e.g., after saving)
|
||||
useEffect(() => {
|
||||
if (apiColumns?.length && activeView && prevActiveViewRef.current) {
|
||||
// Check if the active view has been updated (same ID but different updated_at)
|
||||
if (
|
||||
prevActiveViewRef.current.id === activeView.id &&
|
||||
prevActiveViewRef.current.updated_at !== activeView.updated_at
|
||||
) {
|
||||
// Sync local state with the updated view configuration
|
||||
const newVisibility: Record<string, boolean> = {}
|
||||
apiColumns.forEach(column => {
|
||||
newVisibility[column.field] = activeView.configuration.visible_columns.includes(column.field)
|
||||
})
|
||||
setVisibleColumns(newVisibility)
|
||||
setColumnOrder(activeView.configuration.column_order)
|
||||
}
|
||||
}
|
||||
|
||||
prevActiveViewRef.current = activeView
|
||||
}, [activeView, apiColumns])
|
||||
|
||||
return {
|
||||
visibleColumns,
|
||||
columnOrder,
|
||||
currentColumns,
|
||||
setVisibleColumns,
|
||||
setColumnOrder,
|
||||
handleColumnVisibilityChange,
|
||||
handleViewChange,
|
||||
initializeColumns,
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
const DEFAULT_COLUMN_ORDER = 500
|
||||
|
||||
/**
|
||||
* Gets the initial column visibility state from API columns
|
||||
*/
|
||||
function getInitialColumnVisibility(
|
||||
apiColumns: HttpTypes.AdminViewColumn[]
|
||||
): Record<string, boolean> {
|
||||
const visibility: Record<string, boolean> = {}
|
||||
|
||||
apiColumns.forEach(column => {
|
||||
visibility[column.field] = column.default_visible
|
||||
})
|
||||
|
||||
return visibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initial column order from API columns
|
||||
*/
|
||||
function getInitialColumnOrder(
|
||||
apiColumns: HttpTypes.AdminViewColumn[]
|
||||
): string[] {
|
||||
const sortedColumns = [...apiColumns].sort((a, b) => {
|
||||
const orderA = a.default_order ?? DEFAULT_COLUMN_ORDER
|
||||
const orderB = b.default_order ?? DEFAULT_COLUMN_ORDER
|
||||
return orderA - orderB
|
||||
})
|
||||
|
||||
return sortedColumns.map(col => col.field)
|
||||
}
|
||||
Reference in New Issue
Block a user