Skip to content

Commit 62c2f7d

Browse files
authored
Integrate analytics functionality into survey page with PDF generation (#47)
1 parent 1e2fe84 commit 62c2f7d

1 file changed

Lines changed: 280 additions & 16 deletions

File tree

app/survey/page.tsx

Lines changed: 280 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use client';
22

3-
import { useState, useEffect } from 'react';
3+
import { useState, useEffect, useRef } from 'react';
44
import Link from 'next/link';
5+
import { generateSurveyPDF } from '@/lib/pdfGenerator';
6+
import { surveyData } from './index';
57

68
const AVAILABLE_SURVEYS = [
79
{
@@ -15,11 +17,118 @@ const AVAILABLE_SURVEYS = [
1517

1618
export 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

Comments
 (0)