diff --git a/frontend/components/ui-header/app-header.tsx b/frontend/components/ui-header/app-header.tsx index 5f14ac1..9b31ead 100644 --- a/frontend/components/ui-header/app-header.tsx +++ b/frontend/components/ui-header/app-header.tsx @@ -1,7 +1,7 @@ 'use client'; import { Button } from '@/components/ui/button'; import { ChevronUpIcon } from '@radix-ui/react-icons'; -import { MoveUpRight } from 'lucide-react'; +import { ArrowUpRight } from 'lucide-react'; import { DropdownMenu, @@ -22,7 +22,7 @@ export default function AppHeader() { const { activeSessionId } = useGlobalContext(); return ( -
+
{/* logo */}
- {/* update, issues, import, export, help */} + {/* import, export, help */}
- - {/* Export Data */} {/* Import Data */} {/* help */} diff --git a/frontend/components/ui/export-dialog.tsx b/frontend/components/ui/export-dialog.tsx index ede5335..2213b8d 100644 --- a/frontend/components/ui/export-dialog.tsx +++ b/frontend/components/ui/export-dialog.tsx @@ -10,6 +10,7 @@ import { import { Button } from '@/components/ui/button'; import { useNotifications } from '@/components/notifications'; import { exportEEGData, downloadCSV } from '@/lib/eeg-api'; +import { ExitIcon } from '@radix-ui/react-icons'; type ExportDialogProps = { open: boolean; @@ -17,19 +18,13 @@ type ExportDialogProps = { onOpenChange: (open: boolean) => void; }; -const ExportIcon = () => ( - - - - -); - export default function ExportDialog({ open, sessionId, onOpenChange, }: ExportDialogProps) { const notifications = useNotifications(); + const [exportMode, setExportMode] = useState<'range' | 'all'>('range'); const [durationValue, setDurationValue] = useState('30'); const [durationUnit, setDurationUnit] = useState('Minutes'); const [isExporting, setIsExporting] = useState(false); @@ -49,29 +44,30 @@ export default function ExportDialog({ return; } - const value = parseFloat(durationValue); - if (isNaN(value) || value <= 0) { - notifications.error({ - title: 'Invalid duration', - description: 'Please enter a valid number greater than 0.', - }); - return; - } + const options: Record = {}; - setIsExporting(true); - try { - const options: Record = {}; + if (exportMode === 'range') { + const value = parseFloat(durationValue); + if (isNaN(value) || value <= 0) { + notifications.error({ + title: 'Invalid duration', + description: 'Please enter a valid number greater than 0.', + }); + return; + } - let multiplier = 1000; // default to seconds + let multiplier = 1000; if (durationUnit === 'Minutes') multiplier = 60 * 1000; if (durationUnit === 'Hours') multiplier = 60 * 60 * 1000; if (durationUnit === 'Days') multiplier = 24 * 60 * 60 * 1000; - const durationMs = value * multiplier; const now = new Date(); - - options.start_time = new Date(now.getTime() - durationMs).toISOString(); + options.start_time = new Date(now.getTime() - value * multiplier).toISOString(); options.end_time = now.toISOString(); + } + + setIsExporting(true); + try { const csvContent = await exportEEGData(sessionId, options); downloadCSV(csvContent, sessionId); @@ -92,47 +88,84 @@ export default function ExportDialog({ - - + + Export Data
-

- Export data from the last: -

- -
- setDurationValue(e.target.value)} + {/* Mode toggle */} +
+ + +
+ +
+ {exportMode === 'range' && ( + <> +

+ Export data from the last: +

+
+ setDurationValue(e.target.value)} + disabled={isExporting} + className="flex h-10 w-24 rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50" + /> +
+ +
+ + + +
+
+
+ + )} + + {exportMode === 'all' && ( +

+ Exports all recorded data for this session, from the earliest timestamp to now. +

+ )}
-
+
+ + {sessionId === null && ( +

+ No active session - please start or load a session before exporting. +

+ )}

Data will be exported as CSV format. diff --git a/frontend/components/ui/import-dialog.tsx b/frontend/components/ui/import-dialog.tsx index 224a082..3a24bbb 100644 --- a/frontend/components/ui/import-dialog.tsx +++ b/frontend/components/ui/import-dialog.tsx @@ -11,6 +11,8 @@ import { import { useNotifications } from '@/components/notifications'; import { importEEGData } from '@/lib/eeg-api'; import { Folder } from 'lucide-react'; +import { EnterIcon, InfoCircledIcon } from '@radix-ui/react-icons'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; type ImportDialogProps = { open: boolean; @@ -20,13 +22,6 @@ type ImportDialogProps = { onImportSuccess?: () => void; }; -const ImportIcon = () => ( - - - - -); - export default function ImportDialog({ open, sessionId, @@ -119,11 +114,26 @@ export default function ImportDialog({ - + Import Data - + Only CSV files are accepted. + + + + + +

Expected CSV format

+

The file must have a header row followed by data rows in this shape:

+ + Time,Channel1,Channel2,Channel3,Channel4 + +

The Time column must be in RFC 3339 format (e.g. 2024-01-15T13:45:00Z).

+ +