feat: add sorting functionality to dataset entries list
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user