feat: add sorting functionality to dataset entries list

This commit is contained in:
averel10
2026-03-13 14:22:15 +01:00
parent c66835555d
commit 7cb9c26a15
2 changed files with 80 additions and 16 deletions

View File

@@ -2,7 +2,7 @@
import db from '@/lib/db'; import db from '@/lib/db';
import { dataset_entry } from '@/lib/model/dataset_entry'; 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; const ENTRIES_PER_PAGE = 20;
@@ -14,10 +14,16 @@ interface FilterParams {
utteranceId?: string; utteranceId?: string;
} }
interface SortParams {
sortBy?: keyof typeof dataset_entry;
sortOrder?: 'asc' | 'desc';
}
export async function getDatasetEntries( export async function getDatasetEntries(
datasetId: number, datasetId: number,
page: number = 1, page: number = 1,
filters?: FilterParams filters?: FilterParams,
sort?: SortParams
) { ) {
const offset = (page - 1) * ENTRIES_PER_PAGE; const offset = (page - 1) * ENTRIES_PER_PAGE;
@@ -46,10 +52,21 @@ export async function getDatasetEntries(
const whereClause = and(...conditions); 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 const entries = await db
.select() .select()
.from(dataset_entry) .from(dataset_entry)
.where(whereClause) .where(whereClause)
.orderBy(sortColumn)
.limit(ENTRIES_PER_PAGE) .limit(ENTRIES_PER_PAGE)
.offset(offset); .offset(offset);

View File

@@ -67,6 +67,10 @@ export default function DatasetEntriesList({
utteranceIds: [], utteranceIds: [],
}); });
// Sort state
const [sortBy, setSortBy] = useState<keyof DatasetEntry>('externalId');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
// Load filter options on mount // Load filter options on mount
useEffect(() => { useEffect(() => {
async function loadFilterOptions() { async function loadFilterOptions() {
@@ -83,7 +87,7 @@ export default function DatasetEntriesList({
useEffect(() => { useEffect(() => {
setPage(1); setPage(1);
loadEntries(1); loadEntries(1);
}, [filters]); }, [filters, sortBy, sortOrder]);
async function loadEntries(pageNum: number = page) { async function loadEntries(pageNum: number = page) {
setLoading(true); setLoading(true);
@@ -96,7 +100,12 @@ export default function DatasetEntriesList({
utteranceId: filters.utteranceId || undefined, 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) { if (pageNum === 1) {
setEntries(result.entries); setEntries(result.entries);
} else { } else {
@@ -123,7 +132,12 @@ export default function DatasetEntriesList({
utteranceId: filters.utteranceId || undefined, 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]); setEntries((prev) => [...prev, ...result.entries]);
setHasMore(result.hasMore); setHasMore(result.hasMore);
setTotal(result.total); 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() { async function handleDownloadFiltered() {
setDownloading(true); setDownloading(true);
try { try {
@@ -415,17 +440,39 @@ export default function DatasetEntriesList({
<thead> <thead>
<tr className="border-b border-gray-200"> <tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 font-semibold text-gray-700">Play</th> <th className="text-left py-3 px-4 font-semibold text-gray-700">Play</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">External ID</th> <th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('externalId')}>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Speaker ID</th> External ID {sortBy === 'externalId' && (sortOrder === 'asc' ? '↑' : '↓')}
<th className="text-left py-3 px-4 font-semibold text-gray-700">Model Name</th> </th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Utterance ID</th> <th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('speakerId')}>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Dialect</th> Speaker ID {sortBy === 'speakerId' && (sortOrder === 'asc' ? '↑' : '↓')}
<th className="text-left py-3 px-4 font-semibold text-gray-700">Iteration</th> </th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Duration (ms)</th> <th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('modelName')}>
<th className="text-left py-3 px-4 font-semibold text-gray-700">RMS Value</th> Model Name {sortBy === 'modelName' && (sortOrder === 'asc' ? '↑' : '↓')}
<th className="text-left py-3 px-4 font-semibold text-gray-700">Longest Pause (s)</th> </th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">UTMOS Score</th> <th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('utteranceId')}>
<th className="text-left py-3 px-4 font-semibold text-gray-700">WER Score</th> Utterance ID {sortBy === 'utteranceId' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('dialect')}>
Dialect {sortBy === 'dialect' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('iteration')}>
Iteration {sortBy === 'iteration' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('durationMs')}>
Duration (ms) {sortBy === 'durationMs' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('rmsValue')}>
RMS Value {sortBy === 'rmsValue' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('longestPause')}>
Longest Pause (s) {sortBy === 'longestPause' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('utmosScore')}>
UTMOS Score {sortBy === 'utmosScore' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700 cursor-pointer hover:bg-gray-100" onClick={() => handleSort('werScore')}>
WER Score {sortBy === 'werScore' && (sortOrder === 'asc' ? '↑' : '↓')}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>