@@ -10,26 +10,21 @@ import {
1010import { Button } from '@/components/ui/button' ;
1111import { useNotifications } from '@/components/notifications' ;
1212import { exportEEGData , downloadCSV } from '@/lib/eeg-api' ;
13+ import { ExitIcon } from '@radix-ui/react-icons' ;
1314
1415type ExportDialogProps = {
1516 open : boolean ;
1617 sessionId : number | null ;
1718 onOpenChange : ( open : boolean ) => void ;
1819} ;
1920
20- const ExportIcon = ( ) => (
21- < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "currentColor" stroke = "none" className = "mr-3 h-6 w-6" >
22- < path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z" />
23- < path fill = "#fff" stroke = "#fff" strokeWidth = "2" strokeLinecap = "square" strokeLinejoin = "miter" d = "M8 12h8M12 8l4 4-4 4" />
24- </ svg >
25- ) ;
26-
2721export default function ExportDialog ( {
2822 open,
2923 sessionId,
3024 onOpenChange,
3125} : ExportDialogProps ) {
3226 const notifications = useNotifications ( ) ;
27+ const [ exportMode , setExportMode ] = useState < 'range' | 'all' > ( 'range' ) ;
3328 const [ durationValue , setDurationValue ] = useState ( '30' ) ;
3429 const [ durationUnit , setDurationUnit ] = useState ( 'Minutes' ) ;
3530 const [ isExporting , setIsExporting ] = useState ( false ) ;
@@ -49,29 +44,30 @@ export default function ExportDialog({
4944 return ;
5045 }
5146
52- const value = parseFloat ( durationValue ) ;
53- if ( isNaN ( value ) || value <= 0 ) {
54- notifications . error ( {
55- title : 'Invalid duration' ,
56- description : 'Please enter a valid number greater than 0.' ,
57- } ) ;
58- return ;
59- }
47+ const options : Record < string , string > = { } ;
6048
61- setIsExporting ( true ) ;
62- try {
63- const options : Record < string , string > = { } ;
49+ if ( exportMode === 'range' ) {
50+ const value = parseFloat ( durationValue ) ;
51+ if ( isNaN ( value ) || value <= 0 ) {
52+ notifications . error ( {
53+ title : 'Invalid duration' ,
54+ description : 'Please enter a valid number greater than 0.' ,
55+ } ) ;
56+ return ;
57+ }
6458
65- let multiplier = 1000 ; // default to seconds
59+ let multiplier = 1000 ;
6660 if ( durationUnit === 'Minutes' ) multiplier = 60 * 1000 ;
6761 if ( durationUnit === 'Hours' ) multiplier = 60 * 60 * 1000 ;
6862 if ( durationUnit === 'Days' ) multiplier = 24 * 60 * 60 * 1000 ;
6963
70- const durationMs = value * multiplier ;
7164 const now = new Date ( ) ;
72-
73- options . start_time = new Date ( now . getTime ( ) - durationMs ) . toISOString ( ) ;
65+ options . start_time = new Date ( now . getTime ( ) - value * multiplier ) . toISOString ( ) ;
7466 options . end_time = now . toISOString ( ) ;
67+ }
68+
69+ setIsExporting ( true ) ;
70+ try {
7571
7672 const csvContent = await exportEEGData ( sessionId , options ) ;
7773 downloadCSV ( csvContent , sessionId ) ;
@@ -92,47 +88,84 @@ export default function ExportDialog({
9288 < Dialog open = { open } onOpenChange = { handleClose } >
9389 < DialogContent className = "sm:max-w-[400px] p-8" >
9490 < DialogHeader className = "mb-4 text-left" >
95- < DialogTitle className = "flex items-center text-2xl font-bold mb-0 " >
96- < ExportIcon />
91+ < DialogTitle className = "flex items-center text-2xl font-bold mb-2 " >
92+ < ExitIcon className = "mr-2" width = { 24 } height = { 24 } />
9793 Export Data
9894 </ DialogTitle >
9995 </ DialogHeader >
10096
10197 < div className = "py-2" >
102- < p className = "text-sm font-medium text-gray-900 mb-2" >
103- Export data from the last:
104- </ p >
105-
106- < div className = "flex gap-3 mb-4" >
107- < input
108- type = "number"
109- min = "1"
110- value = { durationValue }
111- onChange = { ( e ) => setDurationValue ( e . target . value ) }
98+ { /* Mode toggle */ }
99+ < div className = "flex rounded-lg border border-gray-200 overflow-hidden mb-4" >
100+ < button
101+ type = "button"
102+ onClick = { ( ) => setExportMode ( 'range' ) }
112103 disabled = { isExporting }
113- 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"
114- />
115- < div className = "relative flex-1" >
116- < select
117- value = { durationUnit }
118- onChange = { ( e ) => setDurationUnit ( e . target . value ) }
119- disabled = { isExporting }
120- className = "appearance-none flex h-10 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 pr-8 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
121- >
122- < option value = "Seconds" > Seconds</ option >
123- < option value = "Minutes" > Minutes</ option >
124- < option value = "Hours" > Hours</ option >
125- < option value = "Days" > Days</ option >
126- </ select >
127- < div className = "pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-500" >
128- < svg className = "h-4 w-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
129- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M19 9l-7 7-7-7" />
130- </ svg >
131- </ div >
132- </ div >
104+ className = { `flex-1 px-3 py-2 text-sm font-medium transition-colors ${ exportMode === 'range' ? 'bg-gray-900 text-white' : 'bg-transparent text-gray-600 hover:bg-gray-50' } disabled:cursor-not-allowed disabled:opacity-50` }
105+ >
106+ Last duration
107+ </ button >
108+ < button
109+ type = "button"
110+ onClick = { ( ) => setExportMode ( 'all' ) }
111+ disabled = { isExporting }
112+ className = { `flex-1 px-3 py-2 text-sm font-medium transition-colors ${ exportMode === 'all' ? 'bg-gray-900 text-white' : 'bg-transparent text-gray-600 hover:bg-gray-50' } disabled:cursor-not-allowed disabled:opacity-50` }
113+ >
114+ All data
115+ </ button >
116+ </ div >
117+
118+ < div className = "h-20" >
119+ { exportMode === 'range' && (
120+ < >
121+ < p className = "text-sm font-medium text-gray-900 mb-2" >
122+ Export data from the last:
123+ </ p >
124+ < div className = "flex gap-3" >
125+ < input
126+ type = "number"
127+ min = "1"
128+ value = { durationValue }
129+ onChange = { ( e ) => setDurationValue ( e . target . value ) }
130+ disabled = { isExporting }
131+ 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"
132+ />
133+ < div className = "relative flex-1" >
134+ < select
135+ value = { durationUnit }
136+ onChange = { ( e ) => setDurationUnit ( e . target . value ) }
137+ disabled = { isExporting }
138+ className = "appearance-none flex h-10 w-full rounded-lg border border-gray-300 bg-transparent px-3 py-2 pr-8 text-sm shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
139+ >
140+ < option value = "Seconds" > Seconds</ option >
141+ < option value = "Minutes" > Minutes</ option >
142+ < option value = "Hours" > Hours</ option >
143+ < option value = "Days" > Days</ option >
144+ </ select >
145+ < div className = "pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-gray-500" >
146+ < svg className = "h-4 w-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
147+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M19 9l-7 7-7-7" />
148+ </ svg >
149+ </ div >
150+ </ div >
151+ </ div >
152+ </ >
153+ ) }
154+
155+ { exportMode === 'all' && (
156+ < p className = "text-sm text-gray-500" >
157+ Exports all recorded data for this session, from the earliest timestamp to now.
158+ </ p >
159+ ) }
133160 </ div >
134161
135- < hr className = "my-6 border-gray-100" />
162+ < hr className = "my-4 border-gray-100" />
163+
164+ { sessionId === null && (
165+ < p className = "text-sm text-red-600 font-medium mb-3" >
166+ No active session - please start or load a session before exporting.
167+ </ p >
168+ ) }
136169
137170 < p className = "text-sm text-gray-400 font-medium mb-3" >
138171 Data will be exported as CSV format.
0 commit comments