From 70ce1d55537709326f91b6497082d6f183454481 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 16 Nov 2025 08:33:32 +0100 Subject: [PATCH] feat(projects): allow sorting project table --- .../src/common/hooks-query/useProjectList.ts | 31 ++++++++++----- .../panel/project-panel/ProjectList.tsx | 38 ++++++++++++++++--- .../project-panel/ProjectPanel.module.scss | 8 +++- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/apps/client/src/common/hooks-query/useProjectList.ts b/apps/client/src/common/hooks-query/useProjectList.ts index 329bba44b..fe35fbc46 100644 --- a/apps/client/src/common/hooks-query/useProjectList.ts +++ b/apps/client/src/common/hooks-query/useProjectList.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { ProjectFileListResponse } from 'ontime-types'; +import { ProjectFile, ProjectFileList, ProjectFileListResponse } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { PROJECT_LIST } from '../api/constants'; @@ -24,22 +24,33 @@ function useProjectList() { return { data: data ?? placeholderProjectList, status, refetch }; } -export function useOrderedProjectList() { +export type ProjectSortMode = 'alphabetical-asc' | 'alphabetical-desc' | 'modified-asc' | 'modified-desc'; +type SortComparator = (a: ProjectFile, b: ProjectFile) => number; +const sortComparators: Record = { + 'alphabetical-asc': (a, b) => a.filename.localeCompare(b.filename), + 'alphabetical-desc': (a, b) => b.filename.localeCompare(a.filename), + 'modified-asc': (a, b) => new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(), + 'modified-desc': (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), +}; + +export function useOrderedProjectList(sort: ProjectSortMode = 'modified-desc') { const response = useProjectList(); const { files, lastLoadedProject } = response.data; - const reorderedProjectFiles = useMemo(() => { + const reorderedProjectFiles: ProjectFileList = useMemo(() => { if (!files.length) return []; - const currentlyLoadedIndex = files.findIndex((project) => project.filename === lastLoadedProject); + const sorted = [...files].sort(sortComparators[sort]); - if (currentlyLoadedIndex === -1) return files; + // keep loaded always on top + const currentlyLoadedIndex = sorted.findIndex((project) => project.filename === lastLoadedProject); + if (currentlyLoadedIndex > 0) { + const [loaded] = sorted.splice(currentlyLoadedIndex, 1); + sorted.unshift(loaded); + } - const projectFiles = [...files]; - const current = projectFiles.splice(currentlyLoadedIndex, 1)[0]; - - return [current, ...projectFiles]; - }, [files, lastLoadedProject]); + return sorted; + }, [files, lastLoadedProject, sort]); return { ...response, data: { reorderedProjectFiles, lastLoadedProject: response.data.lastLoadedProject } }; } diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx index b62151af8..2295f44fe 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectList.tsx @@ -1,18 +1,22 @@ import { useState } from 'react'; +import { IoArrowDown, IoArrowUp } from 'react-icons/io5'; import Info from '../../../../common/components/info/Info'; -import { useOrderedProjectList } from '../../../../common/hooks-query/useProjectList'; +import { ProjectSortMode, useOrderedProjectList } from '../../../../common/hooks-query/useProjectList'; import * as Panel from '../../panel-utils/PanelUtils'; import ProjectListItem, { EditMode } from './ProjectListItem'; import style from './ProjectPanel.module.scss'; -export default function ProjectList() { - const { data, refetch, status } = useOrderedProjectList(); +type SortParameter = 'alphabetical' | 'modified'; +export default function ProjectList() { const [editingMode, setEditingMode] = useState(null); const [editingFilename, setEditingFilename] = useState(null); + const [sortMode, setSortMode] = useState('modified-desc'); + + const { data, refetch, status } = useOrderedProjectList(sortMode); const handleToggleEditMode = (editMode: EditMode, filename: string | null) => { setEditingMode((prev) => (prev === editMode && filename === editingFilename ? null : editMode)); @@ -28,6 +32,13 @@ export default function ProjectList() { await refetch(); }; + const handleSort = (sortParameter: SortParameter) => { + setSortMode((current) => { + const isAscending = current === `${sortParameter}-asc`; + return `${sortParameter}-${isAscending ? 'desc' : 'asc'}` as ProjectSortMode; + }); + }; + if (status === 'pending') { return (
@@ -48,8 +59,18 @@ export default function ProjectList() { - File Name - Last Used + handleSort('alphabetical')}> + + File Name + + + + handleSort('modified')}> + + Last Used + + + @@ -72,3 +93,10 @@ export default function ProjectList() { ); } + +function SortIcon({ sortMode, type }: { sortMode: ProjectSortMode; type: SortParameter }) { + const prefix = `${type}-`; + if (sortMode === `${prefix}asc`) return ; + if (sortMode === `${prefix}desc`) return ; + return null; +} diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss index 9d95819e3..9335d861a 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss @@ -23,7 +23,13 @@ } .containCell { - max-width: 400px; + max-width: 50%; +} + +.sortableHeader { + display: inline-flex; + align-items: center; + gap: 0.5em; } .fullWidth {