11'use client' ;
22
3- import { useState , useEffect } from 'react' ;
3+ import { useState , useEffect , useRef } from 'react' ;
44import Link from 'next/link' ;
5+ import { generateSurveyPDF } from '@/lib/pdfGenerator' ;
6+ import { surveyData } from './index' ;
57
68const AVAILABLE_SURVEYS = [
79 {
@@ -15,11 +17,118 @@ const AVAILABLE_SURVEYS = [
1517
1618export default function SurveyListingPage ( ) {
1719 const [ isClient , setIsClient ] = useState ( false ) ;
20+ const [ showAnalytics , setShowAnalytics ] = useState ( false ) ;
21+ const [ isDragOver , setIsDragOver ] = useState ( false ) ;
22+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
23+ const [ error , setError ] = useState < string | null > ( null ) ;
24+ const [ uploadedData , setUploadedData ] = useState < Record < string , unknown > | null > ( null ) ;
25+ const [ success , setSuccess ] = useState < string | null > ( null ) ;
26+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
1827
1928 useEffect ( ( ) => {
2029 setIsClient ( true ) ;
2130 } , [ ] ) ;
2231
32+ const handleFileSelect = ( file : File ) => {
33+ if ( file . type !== 'application/json' ) {
34+ setError ( 'Please select a valid JSON file.' ) ;
35+ return ;
36+ }
37+
38+ setIsProcessing ( true ) ;
39+ setError ( null ) ;
40+ setSuccess ( null ) ;
41+
42+ const reader = new FileReader ( ) ;
43+ reader . onload = ( e ) => {
44+ try {
45+ const jsonContent = e . target ?. result as string ;
46+ const data = JSON . parse ( jsonContent ) ;
47+
48+ // Handle both single submission and array of submissions
49+ const submissions = Array . isArray ( data ) ? data : [ data ] ;
50+
51+ // Validate that this looks like survey data
52+ const validSubmissions = submissions . filter ( submission => {
53+ if ( ! submission || typeof submission !== 'object' ) return false ;
54+
55+ // Check for some expected survey fields
56+ return Object . keys ( submission ) . some ( key =>
57+ key . includes ( 'library-name' ) ||
58+ key . includes ( 'contact-' ) ||
59+ key . includes ( 'matrix-' ) ||
60+ key . includes ( 'eigenvalue' ) ||
61+ key . includes ( 'cholesky' ) ||
62+ key . includes ( 'qr-' )
63+ ) ;
64+ } ) ;
65+
66+ if ( validSubmissions . length === 0 ) {
67+ throw new Error ( 'No valid ExaNLA survey responses found in the file.' ) ;
68+ }
69+
70+ // For now, just process the first submission for PDF generation
71+ const firstSubmission = validSubmissions [ 0 ] ;
72+ setUploadedData ( firstSubmission ) ;
73+
74+ // Generate PDF immediately
75+ generateSurveyPDF ( firstSubmission , surveyData ) ;
76+ setSuccess ( `PDF generated successfully! Processed ${ validSubmissions . length } submission(s).` ) ;
77+
78+ } catch ( err ) {
79+ console . error ( 'Error processing JSON:' , err ) ;
80+ setError ( err instanceof Error ? err . message : 'Error processing JSON file.' ) ;
81+ } finally {
82+ setIsProcessing ( false ) ;
83+ }
84+ } ;
85+
86+ reader . onerror = ( ) => {
87+ setError ( 'Error reading file.' ) ;
88+ setIsProcessing ( false ) ;
89+ } ;
90+
91+ reader . readAsText ( file ) ;
92+ } ;
93+
94+ const handleDrop = ( e : React . DragEvent ) => {
95+ e . preventDefault ( ) ;
96+ setIsDragOver ( false ) ;
97+
98+ const files = Array . from ( e . dataTransfer . files ) ;
99+ if ( files . length > 0 ) {
100+ handleFileSelect ( files [ 0 ] ) ;
101+ }
102+ } ;
103+
104+ const handleDragOver = ( e : React . DragEvent ) => {
105+ e . preventDefault ( ) ;
106+ setIsDragOver ( true ) ;
107+ } ;
108+
109+ const handleDragLeave = ( e : React . DragEvent ) => {
110+ e . preventDefault ( ) ;
111+ setIsDragOver ( false ) ;
112+ } ;
113+
114+ const handleFileInputChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
115+ const files = e . target . files ;
116+ if ( files && files . length > 0 ) {
117+ handleFileSelect ( files [ 0 ] ) ;
118+ }
119+ } ;
120+
121+ const handleClick = ( ) => {
122+ fileInputRef . current ?. click ( ) ;
123+ } ;
124+
125+ const resetAnalytics = ( ) => {
126+ setUploadedData ( null ) ;
127+ setShowAnalytics ( false ) ;
128+ setError ( null ) ;
129+ setSuccess ( null ) ;
130+ } ;
131+
23132 if ( ! isClient ) {
24133 return < div > Loading...</ div > ;
25134 }
@@ -38,25 +147,180 @@ export default function SurveyListingPage() {
38147
39148 < div className = "grid gap-6 md:grid-cols-2 lg:grid-cols-3" >
40149 { AVAILABLE_SURVEYS . map ( survey => (
41- < Link
42- key = { survey . id }
43- href = { survey . path }
44- className = "block"
45- >
46- < div className = "h-full bg-white rounded-lg shadow-lg p-6 hover:shadow-xl transition-shadow border border-gray-200 cursor-pointer" >
47- < h2 className = "text-xl font-semibold text-gray-900 mb-3" >
48- { survey . title }
49- </ h2 >
50- < p className = "text-gray-600" >
51- { survey . description }
52- </ p >
53- < div className = "mt-4 text-blue-600 font-medium" >
150+ < div key = { survey . id } className = "h-full bg-white rounded-lg shadow-lg p-6 border border-gray-200" >
151+ < h2 className = "text-xl font-semibold text-gray-900 mb-3" >
152+ { survey . title }
153+ </ h2 >
154+ < p className = "text-gray-600 mb-6" >
155+ { survey . description }
156+ </ p >
157+
158+ < div className = "space-y-3" >
159+ < Link
160+ href = { survey . path }
161+ className = "block w-full bg-blue-600 text-white text-center py-3 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium"
162+ >
54163 Start Survey →
55- </ div >
164+ </ Link >
165+
166+ < button
167+ onClick = { ( ) => setShowAnalytics ( true ) }
168+ className = "w-full bg-gray-100 text-gray-700 text-center py-3 px-4 rounded-lg hover:bg-gray-200 transition-colors font-medium"
169+ >
170+ Analytics & Data Upload
171+ </ button >
56172 </ div >
57- </ Link >
173+ </ div >
58174 ) ) }
59175 </ div >
176+
177+ { /* Analytics Modal */ }
178+ { showAnalytics && (
179+ < div className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" >
180+ < div className = "bg-white rounded-2xl shadow-xl max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto" >
181+ < div className = "p-8" >
182+ < div className = "flex items-center justify-between mb-6" >
183+ < h2 className = "text-2xl font-semibold text-gray-900" >
184+ Generate PDF from Survey Data
185+ </ h2 >
186+ < button
187+ onClick = { resetAnalytics }
188+ className = "text-gray-400 hover:text-gray-600 transition-colors"
189+ >
190+ < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
191+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" />
192+ </ svg >
193+ </ button >
194+ </ div >
195+
196+ { /* Development Warning */ }
197+ < div className = "mb-6 p-4 bg-amber-50 border border-amber-200 rounded-lg" >
198+ < div className = "flex" >
199+ < svg className = "w-5 h-5 text-amber-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
200+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
201+ </ svg >
202+ < div className = "ml-3" >
203+ < h3 className = "text-sm font-medium text-amber-800" > Under Development</ h3 >
204+ < p className = "text-sm text-amber-700 mt-1" >
205+ This feature is currently limited to PDF generation from JSON data.
206+ Advanced analytics and visualizations will be available once sufficient survey data is collected.
207+ </ p >
208+ </ div >
209+ </ div >
210+ </ div >
211+
212+ { ! uploadedData ? (
213+ /* Upload Section */
214+ < div className = "max-w-2xl mx-auto" >
215+ < p className = "text-gray-600 mb-6 text-center" >
216+ Upload a JSON file from a previous survey submission to generate a PDF report.
217+ </ p >
218+
219+ < div
220+ className = { `border-2 border-dashed rounded-lg p-12 text-center cursor-pointer transition-colors ${
221+ isDragOver
222+ ? 'border-blue-500 bg-blue-50'
223+ : 'border-gray-300 hover:border-gray-400'
224+ } ${ isProcessing ? 'opacity-50 cursor-not-allowed' : '' } `}
225+ onDrop = { handleDrop }
226+ onDragOver = { handleDragOver }
227+ onDragLeave = { handleDragLeave }
228+ onClick = { ! isProcessing ? handleClick : undefined }
229+ >
230+ < input
231+ ref = { fileInputRef }
232+ type = "file"
233+ accept = ".json,application/json"
234+ onChange = { handleFileInputChange }
235+ className = "hidden"
236+ disabled = { isProcessing }
237+ />
238+
239+ { isProcessing ? (
240+ < div className = "flex flex-col items-center" >
241+ < svg className = "w-12 h-12 text-blue-500 animate-spin mb-4" fill = "none" viewBox = "0 0 24 24" >
242+ < circle className = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" strokeWidth = "4" > </ circle >
243+ < path className = "opacity-75" fill = "currentColor" d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" > </ path >
244+ </ svg >
245+ < p className = "text-lg text-gray-600" > Processing survey data...</ p >
246+ </ div >
247+ ) : (
248+ < div className = "flex flex-col items-center" >
249+ < svg className = "w-16 h-16 text-gray-400 mb-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
250+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
251+ </ svg >
252+ < p className = "text-lg text-gray-600 mb-2" >
253+ < span className = "font-medium text-blue-600" > Click to upload</ span > or drag and drop
254+ </ p >
255+ < p className = "text-sm text-gray-500 mb-4" > JSON file(s) containing survey responses</ p >
256+ < div className = "text-xs text-gray-400 space-y-1" >
257+ < p > • Single submission JSON</ p >
258+ < p > • Array of submissions JSON</ p >
259+ < p > • Database export JSON</ p >
260+ </ div >
261+ </ div >
262+ ) }
263+ </ div >
264+
265+ { error && (
266+ < div className = "mt-6 p-4 bg-red-50 border border-red-200 rounded-md" >
267+ < div className = "flex" >
268+ < svg className = "w-5 h-5 text-red-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
269+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
270+ </ svg >
271+ < div className = "ml-2" >
272+ < p className = "text-sm text-red-800" > { error } </ p >
273+ </ div >
274+ </ div >
275+ </ div >
276+ ) }
277+
278+ { success && (
279+ < div className = "mt-6 p-4 bg-green-50 border border-green-200 rounded-md" >
280+ < div className = "flex" >
281+ < svg className = "w-5 h-5 text-green-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
282+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M5 13l4 4L19 7" />
283+ </ svg >
284+ < div className = "ml-2" >
285+ < p className = "text-sm text-green-800" > { success } </ p >
286+ </ div >
287+ </ div >
288+ </ div >
289+ ) }
290+ </ div >
291+ ) : (
292+ /* Success Section */
293+ < div className = "max-w-2xl mx-auto text-center" >
294+ < div className = "mb-6" >
295+ < svg className = "w-16 h-16 text-green-500 mx-auto mb-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
296+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
297+ </ svg >
298+ < h3 className = "text-xl font-semibold text-gray-900 mb-2" > PDF Generated Successfully!</ h3 >
299+ < p className = "text-gray-600 mb-4" >
300+ Your survey data has been processed and a PDF report has been generated and downloaded.
301+ </ p >
302+ </ div >
303+
304+ < div className = "space-y-3" >
305+ < button
306+ onClick = { resetAnalytics }
307+ className = "w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium"
308+ >
309+ Upload Another File
310+ </ button >
311+ < button
312+ onClick = { resetAnalytics }
313+ className = "w-full bg-gray-100 text-gray-700 py-3 px-4 rounded-lg hover:bg-gray-200 transition-colors font-medium"
314+ >
315+ Close
316+ </ button >
317+ </ div >
318+ </ div >
319+ ) }
320+ </ div >
321+ </ div >
322+ </ div >
323+ ) }
60324 </ div >
61325 </ div >
62326 ) ;
0 commit comments