mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 16:49:41 +00:00
feat(projects): allow sorting project table
This commit is contained in:
committed by
Carlos Valente
parent
9974ce24cd
commit
70ce1d5553
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { ProjectFileListResponse } from 'ontime-types';
|
import { ProjectFile, ProjectFileList, ProjectFileListResponse } from 'ontime-types';
|
||||||
|
|
||||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||||
import { PROJECT_LIST } from '../api/constants';
|
import { PROJECT_LIST } from '../api/constants';
|
||||||
@@ -24,22 +24,33 @@ function useProjectList() {
|
|||||||
return { data: data ?? placeholderProjectList, status, refetch };
|
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<ProjectSortMode, SortComparator> = {
|
||||||
|
'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 response = useProjectList();
|
||||||
const { files, lastLoadedProject } = response.data;
|
const { files, lastLoadedProject } = response.data;
|
||||||
|
|
||||||
const reorderedProjectFiles = useMemo(() => {
|
const reorderedProjectFiles: ProjectFileList = useMemo(() => {
|
||||||
if (!files.length) return [];
|
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];
|
return sorted;
|
||||||
const current = projectFiles.splice(currentlyLoadedIndex, 1)[0];
|
}, [files, lastLoadedProject, sort]);
|
||||||
|
|
||||||
return [current, ...projectFiles];
|
|
||||||
}, [files, lastLoadedProject]);
|
|
||||||
|
|
||||||
return { ...response, data: { reorderedProjectFiles, lastLoadedProject: response.data.lastLoadedProject } };
|
return { ...response, data: { reorderedProjectFiles, lastLoadedProject: response.data.lastLoadedProject } };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoArrowDown, IoArrowUp } from 'react-icons/io5';
|
||||||
|
|
||||||
import Info from '../../../../common/components/info/Info';
|
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 * as Panel from '../../panel-utils/PanelUtils';
|
||||||
|
|
||||||
import ProjectListItem, { EditMode } from './ProjectListItem';
|
import ProjectListItem, { EditMode } from './ProjectListItem';
|
||||||
|
|
||||||
import style from './ProjectPanel.module.scss';
|
import style from './ProjectPanel.module.scss';
|
||||||
|
|
||||||
export default function ProjectList() {
|
type SortParameter = 'alphabetical' | 'modified';
|
||||||
const { data, refetch, status } = useOrderedProjectList();
|
|
||||||
|
|
||||||
|
export default function ProjectList() {
|
||||||
const [editingMode, setEditingMode] = useState<EditMode | null>(null);
|
const [editingMode, setEditingMode] = useState<EditMode | null>(null);
|
||||||
const [editingFilename, setEditingFilename] = useState<string | null>(null);
|
const [editingFilename, setEditingFilename] = useState<string | null>(null);
|
||||||
|
const [sortMode, setSortMode] = useState<ProjectSortMode>('modified-desc');
|
||||||
|
|
||||||
|
const { data, refetch, status } = useOrderedProjectList(sortMode);
|
||||||
|
|
||||||
const handleToggleEditMode = (editMode: EditMode, filename: string | null) => {
|
const handleToggleEditMode = (editMode: EditMode, filename: string | null) => {
|
||||||
setEditingMode((prev) => (prev === editMode && filename === editingFilename ? null : editMode));
|
setEditingMode((prev) => (prev === editMode && filename === editingFilename ? null : editMode));
|
||||||
@@ -28,6 +32,13 @@ export default function ProjectList() {
|
|||||||
await refetch();
|
await refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (sortParameter: SortParameter) => {
|
||||||
|
setSortMode((current) => {
|
||||||
|
const isAscending = current === `${sortParameter}-asc`;
|
||||||
|
return `${sortParameter}-${isAscending ? 'desc' : 'asc'}` as ProjectSortMode;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (status === 'pending') {
|
if (status === 'pending') {
|
||||||
return (
|
return (
|
||||||
<div className={style.empty}>
|
<div className={style.empty}>
|
||||||
@@ -48,8 +59,18 @@ export default function ProjectList() {
|
|||||||
<Panel.Table>
|
<Panel.Table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={style.containCell}>File Name</th>
|
<th className={style.containCell} onClick={() => handleSort('alphabetical')}>
|
||||||
<th>Last Used</th>
|
<span className={style.sortableHeader}>
|
||||||
|
File Name
|
||||||
|
<SortIcon sortMode={sortMode} type='alphabetical' />
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th onClick={() => handleSort('modified')}>
|
||||||
|
<span className={style.sortableHeader}>
|
||||||
|
Last Used
|
||||||
|
<SortIcon sortMode={sortMode} type='modified' />
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
<th />
|
<th />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -72,3 +93,10 @@ export default function ProjectList() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SortIcon({ sortMode, type }: { sortMode: ProjectSortMode; type: SortParameter }) {
|
||||||
|
const prefix = `${type}-`;
|
||||||
|
if (sortMode === `${prefix}asc`) return <IoArrowDown />;
|
||||||
|
if (sortMode === `${prefix}desc`) return <IoArrowUp />;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.containCell {
|
.containCell {
|
||||||
max-width: 400px;
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortableHeader {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullWidth {
|
.fullWidth {
|
||||||
|
|||||||
Reference in New Issue
Block a user