feat: Add CSV download functionality for filtered dataset entries

This commit is contained in:
averel10
2026-03-20 07:37:28 +01:00
parent 7d58abd30f
commit 3aee6c10a8
3 changed files with 128 additions and 1 deletions

View File

@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { downloadFilteredEntries } from '@/app/actions/download-filtered-entries';
import { requireAdmin } from '@/lib/auth';
interface FilterParams {
speakerId?: string;
modelName?: string;
dialect?: string;
iteration?: number;
utteranceId?: string;
}
export async function POST(request: NextRequest) {
try {
const resultAdmin = await requireAdmin();
if (!resultAdmin.authenticated || !resultAdmin.admin) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const body = await request.json();
const { datasetId, filters } = body;
if (!datasetId) {
return NextResponse.json(
{ error: 'datasetId is required' },
{ status: 400 }
);
}
// Get filtered entries data from server action
const result = await downloadFilteredEntries(datasetId, filters || {});
const fileName = `dataset-${datasetId}-metadata-${new Date().toISOString().split('T')[0]}.csv`;
return new NextResponse(result.csvContent, {
status: 200,
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="${fileName}"`,
'Content-Length': Buffer.byteLength(result.csvContent).toString(),
},
});
} catch (error) {
console.error('Error downloading CSV:', error);
return NextResponse.json(
{ error: 'Failed to download CSV file' },
{ status: 500 }
);
}
}

View File

@@ -5,7 +5,7 @@ import Link from 'next/link';
import AudioPlayer from './AudioPlayer'; import AudioPlayer from './AudioPlayer';
import { getDatasetEntries } from '@/app/actions/get-dataset-entries'; import { getDatasetEntries } from '@/app/actions/get-dataset-entries';
import { getFilterOptions } from '@/app/actions/get-filter-options'; import { getFilterOptions } from '@/app/actions/get-filter-options';
import { downloadFilteredEntriesAsZip } from '@/lib/download-utils'; import { downloadFilteredEntriesAsZip, downloadFilteredEntriesAsCsv } from '@/lib/download-utils';
interface DatasetEntriesListProps { interface DatasetEntriesListProps {
datasetId: number; datasetId: number;
@@ -45,6 +45,7 @@ export default function DatasetEntriesList({
const [entries, setEntries] = useState<DatasetEntry[]>([]); const [entries, setEntries] = useState<DatasetEntry[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [downloading, setDownloading] = useState(false); const [downloading, setDownloading] = useState(false);
const [csvDownloading, setCsvDownloading] = useState(false);
const [hasMore, setHasMore] = useState(true); const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
@@ -197,6 +198,26 @@ export default function DatasetEntriesList({
} }
} }
async function handleDownloadFilteredCsv() {
setCsvDownloading(true);
try {
const filterParams = {
speakerId: filters.speakerId || undefined,
modelName: filters.modelName || undefined,
dialect: filters.dialect || undefined,
iteration: filters.iteration ? parseInt(filters.iteration, 10) : undefined,
utteranceId: filters.utteranceId || undefined,
};
await downloadFilteredEntriesAsCsv(datasetId, filterParams);
} catch (error) {
console.error('Error downloading CSV:', error);
alert('Failed to download CSV. Please try again.');
} finally {
setCsvDownloading(false);
}
}
if (entries.length === 0 && !loading) { if (entries.length === 0 && !loading) {
return ( return (
<div className="bg-white rounded-lg shadow p-6"> <div className="bg-white rounded-lg shadow p-6">
@@ -207,6 +228,13 @@ export default function DatasetEntriesList({
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-700">Filters</h3> <h3 className="text-lg font-semibold text-gray-700">Filters</h3>
<div className="flex gap-3"> <div className="flex gap-3">
<button
onClick={handleDownloadFilteredCsv}
disabled={csvDownloading || total === 0}
className="text-sm px-3 py-1 bg-purple-500 text-white rounded hover:bg-purple-600 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{csvDownloading ? 'Downloading...' : 'Download CSV'}
</button>
<button <button
onClick={handleDownloadFiltered} onClick={handleDownloadFiltered}
disabled={downloading || total === 0} disabled={downloading || total === 0}
@@ -325,6 +353,13 @@ export default function DatasetEntriesList({
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-700">Filters</h3> <h3 className="text-lg font-semibold text-gray-700">Filters</h3>
<div className="flex gap-3"> <div className="flex gap-3">
<button
onClick={handleDownloadFilteredCsv}
disabled={csvDownloading || total === 0}
className="text-sm px-3 py-1 bg-purple-500 text-white rounded hover:bg-purple-600 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{csvDownloading ? 'Downloading...' : 'Download CSV'}
</button>
<button <button
onClick={handleDownloadFiltered} onClick={handleDownloadFiltered}
disabled={downloading || total === 0} disabled={downloading || total === 0}

View File

@@ -44,3 +44,42 @@ export async function downloadFilteredEntriesAsZip(
throw error; throw error;
} }
} }
export async function downloadFilteredEntriesAsCsv(
datasetId: number,
filters: FilterParams
) {
try {
const response = await fetch('/api/download-metadata-csv', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
datasetId,
filters,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to download CSV');
}
// Get the CSV blob from response
const blob = await response.blob();
// Trigger download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `dataset-${datasetId}-metadata-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading CSV:', error);
throw error;
}
}