diff --git a/src/app/actions/get-dataset-entries.ts b/src/app/actions/get-dataset-entries.ts index 7104e98..a54a06f 100644 --- a/src/app/actions/get-dataset-entries.ts +++ b/src/app/actions/get-dataset-entries.ts @@ -2,7 +2,7 @@ import db from '@/lib/db'; import { dataset_entry } from '@/lib/model/dataset_entry'; -import { count, eq, and, like } from 'drizzle-orm'; +import { count, eq, and, like, asc, desc, SQL } from 'drizzle-orm'; const ENTRIES_PER_PAGE = 20; @@ -14,10 +14,16 @@ interface FilterParams { utteranceId?: string; } +interface SortParams { + sortBy?: keyof typeof dataset_entry; + sortOrder?: 'asc' | 'desc'; +} + export async function getDatasetEntries( datasetId: number, page: number = 1, - filters?: FilterParams + filters?: FilterParams, + sort?: SortParams ) { const offset = (page - 1) * ENTRIES_PER_PAGE; @@ -46,10 +52,21 @@ export async function getDatasetEntries( const whereClause = and(...conditions); + // Build sort clause + let sortColumn: SQL | undefined; + const sortFn = sort?.sortOrder === 'desc' ? desc : asc; + + if (sort?.sortBy && (sort.sortBy in dataset_entry)) { + sortColumn = sortFn(dataset_entry[sort.sortBy as keyof typeof dataset_entry]); + } else { + sortColumn = asc(dataset_entry.externalId); // Default sort + } + const entries = await db .select() .from(dataset_entry) .where(whereClause) + .orderBy(sortColumn) .limit(ENTRIES_PER_PAGE) .offset(offset); diff --git a/src/components/DatasetEntriesList.tsx b/src/components/DatasetEntriesList.tsx index 5e007b0..e1a8dc7 100644 --- a/src/components/DatasetEntriesList.tsx +++ b/src/components/DatasetEntriesList.tsx @@ -67,6 +67,10 @@ export default function DatasetEntriesList({ utteranceIds: [], }); + // Sort state + const [sortBy, setSortBy] = useState('externalId'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + // Load filter options on mount useEffect(() => { async function loadFilterOptions() { @@ -83,7 +87,7 @@ export default function DatasetEntriesList({ useEffect(() => { setPage(1); loadEntries(1); - }, [filters]); + }, [filters, sortBy, sortOrder]); async function loadEntries(pageNum: number = page) { setLoading(true); @@ -96,7 +100,12 @@ export default function DatasetEntriesList({ utteranceId: filters.utteranceId || undefined, }; - const result = await getDatasetEntries(datasetId, pageNum, filterParams); + const sortParams = { + sortBy: sortBy as keyof DatasetEntry, + sortOrder: sortOrder, + }; + + const result = await getDatasetEntries(datasetId, pageNum, filterParams, sortParams); if (pageNum === 1) { setEntries(result.entries); } else { @@ -123,7 +132,12 @@ export default function DatasetEntriesList({ utteranceId: filters.utteranceId || undefined, }; - const result = await getDatasetEntries(datasetId, page + 1, filterParams); + const sortParams = { + sortBy: sortBy as keyof DatasetEntry, + sortOrder: sortOrder, + }; + + const result = await getDatasetEntries(datasetId, page + 1, filterParams, sortParams); setEntries((prev) => [...prev, ...result.entries]); setHasMore(result.hasMore); setTotal(result.total); @@ -152,6 +166,17 @@ export default function DatasetEntriesList({ }); } + function handleSort(column: keyof DatasetEntry) { + if (sortBy === column) { + // Toggle sort order if clicking the same column + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + // Set new column to sort by, default to ascending + setSortBy(column); + setSortOrder('asc'); + } + } + async function handleDownloadFiltered() { setDownloading(true); try { @@ -415,17 +440,39 @@ export default function DatasetEntriesList({ Play - External ID - Speaker ID - Model Name - Utterance ID - Dialect - Iteration - Duration (ms) - RMS Value - Longest Pause (s) - UTMOS Score - WER Score + handleSort('externalId')}> + External ID {sortBy === 'externalId' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('speakerId')}> + Speaker ID {sortBy === 'speakerId' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('modelName')}> + Model Name {sortBy === 'modelName' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('utteranceId')}> + Utterance ID {sortBy === 'utteranceId' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('dialect')}> + Dialect {sortBy === 'dialect' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('iteration')}> + Iteration {sortBy === 'iteration' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('durationMs')}> + Duration (ms) {sortBy === 'durationMs' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('rmsValue')}> + RMS Value {sortBy === 'rmsValue' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('longestPause')}> + Longest Pause (s) {sortBy === 'longestPause' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('utmosScore')}> + UTMOS Score {sortBy === 'utmosScore' && (sortOrder === 'asc' ? '↑' : '↓')} + + handleSort('werScore')}> + WER Score {sortBy === 'werScore' && (sortOrder === 'asc' ? '↑' : '↓')} +