diff --git a/apps/studio/components/grid/components/menu/ColumnMenu.tsx b/apps/studio/components/grid/components/menu/ColumnMenu.tsx index 5ad77c71d28..7cf199e619e 100644 --- a/apps/studio/components/grid/components/menu/ColumnMenu.tsx +++ b/apps/studio/components/grid/components/menu/ColumnMenu.tsx @@ -1,10 +1,13 @@ -import { ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react' +import type { Sort } from 'components/grid/types' +import { ArrowDown, ArrowUp, ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react' import type { CalculatedColumn } from 'react-data-grid' +import { useTableSort } from 'components/grid/hooks/useTableSort' import { useTableEditorStateSnapshot } from 'state/table-editor' import { useTableEditorTableStateSnapshot } from 'state/table-editor-table' import { Button, + cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -23,8 +26,10 @@ interface ColumnMenuProps { const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => { const tableEditorSnap = useTableEditorStateSnapshot() const snap = useTableEditorTableStateSnapshot() + const { sorts, addOrUpdateSort, removeSort } = useTableSort() const columnKey = column.key + const columnName = column.name as string function onFreezeColumn() { snap.freezeColumn(columnKey) @@ -48,7 +53,22 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => { } } + function onSortColumn(ascending: boolean) { + if (!columnKey) return + const currentSort = sorts.find((s) => s.column === columnKey) + + if (currentSort && currentSort.ascending === ascending) { + // Clicked the currently active sort: Remove it + removeSort(columnKey) + } else { + // Clicked the inactive sort or column wasn't sorted: Add or update it + addOrUpdateSort(columnKey, ascending) + } + } + function renderMenu() { + const currentSort: Sort | undefined = sorts.find((s) => s.column === columnKey) + return ( <> {snap.editable && ( @@ -80,6 +100,29 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => { )} + + onSortColumn(true)} + > + +

Sort Ascending

+
+ onSortColumn(false)} + > + +

Sort Descending

+
{snap.editable && ( <> diff --git a/apps/studio/components/grid/hooks/useTableSort.ts b/apps/studio/components/grid/hooks/useTableSort.ts index b40352a25b4..9ba34d77d62 100644 --- a/apps/studio/components/grid/hooks/useTableSort.ts +++ b/apps/studio/components/grid/hooks/useTableSort.ts @@ -44,9 +44,58 @@ export function useTableSort() { [snap, setParams, saveSortsAndTriggerSideEffects] ) + /** + * Adds a new sort for a column or updates the direction of an existing one. + * New sorts are added to the beginning of the array (highest precedence). + * If the column already exists, its `ascending` direction is updated. + * Calls `onApplySorts` to update URL parameters and trigger side effects. + * + * @param columnKey The key/name of the column to sort. + * @param ascending The sort direction (true for ascending, false for descending). + */ + const addOrUpdateSort = useCallback( + (columnKey: string, ascending: boolean) => { + if (!tableName || !columnKey) return + + // Use the derived 'sorts' state from the hook + const existingSortIndex = sorts.findIndex((s) => s.column === columnKey) + let newSorts = [...sorts] // Create a mutable copy + + if (existingSortIndex !== -1) { + // Column already exists in sorts: Update the existing sort (toggle handled by removeSort) + newSorts[existingSortIndex] = { ...newSorts[existingSortIndex], ascending: ascending } + } else { + // Column doesn't exist in sorts: Add it to the beginning + newSorts.unshift({ table: tableName, column: columnKey, ascending: ascending }) + } + + onApplySorts(newSorts) + }, + [tableName, sorts, onApplySorts] // Depend on derived sorts and callback + ) + + /** + * Removes a sort criterion for a specific column. + * Calls `onApplySorts` with the filtered array to update URL parameters and trigger side effects. + * + * @param columnKey The key/name of the column to remove from sorting. + */ + const removeSort = useCallback( + (columnKey: string) => { + if (!tableName || !columnKey) return + + // Use the derived 'sorts' state from the hook + const newSorts = sorts.filter((s) => s.column !== columnKey) + onApplySorts(newSorts) + }, + [tableName, sorts, onApplySorts] // Depend on derived sorts and callback + ) + return { sorts, urlSorts, onApplySorts, + addOrUpdateSort, + removeSort, } }