import { useState, useEffect, useMemo } from "react" import { Link, useParams, useNavigate } from "react-router" import { ArrowLeft, BarChart3, Calendar, Clock, FileText, MessageSquare, Users, } from "lucide-react" import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from "recharts" import { Navbar } from "@/components/shared/navbar" import { Footer } from "@/components/shared/footer" import { FormButton } from "@/components/shared/form-button" import { useAuth } from "@/app/context/auth-context" import { getFormById, getFormResponses } from "@/lib/api" import type { FormDetail, FormResponse, Question, QuestionType } from "@/lib/types" const CHART_COLORS = [ "hsl(221, 83%, 53%)", "hsl(142, 71%, 45%)", "hsl(38, 92%, 50%)", "hsl(0, 84%, 60%)", "hsl(262, 83%, 58%)", "hsl(172, 66%, 50%)", "hsl(326, 80%, 55%)", "hsl(25, 95%, 53%)", "hsl(199, 89%, 48%)", "hsl(47, 96%, 53%)", ] type TabType = "summary" | "individual" export default function FormResponsesPage() { const { id } = useParams() const navigate = useNavigate() const { user, loading: authLoading } = useAuth() const [form, setForm] = useState(null) const [responses, setResponses] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [activeTab, setActiveTab] = useState("summary") useEffect(() => { if (!authLoading && !user) { navigate("/login") } }, [user, authLoading, navigate]) useEffect(() => { if (!id || !user) return async function fetchData() { try { const [formData, responsesData] = await Promise.all([ getFormById(id!), getFormResponses(id!), ]) setForm(formData) setResponses(responsesData) } catch (err) { if (err instanceof Response && err.status === 404) { setError("Form not found") } else { setError("Failed to load responses. Please try again.") } } finally { setLoading(false) } } fetchData() }, [id, user]) if (authLoading || loading) { return (

Loading responses...

) } if (error || !form) { return (

{error ?? "Form not found"}

Back to forms
) } if (!user) return null return (
Back to form {/* Header */}

{form.title}

{form.description}

Preview
{/* Stats */}
} label="Responses" value={responses.length} /> } label="Questions" value={form.questions.length} /> } label="Created" value={new Date(form.created_at).toLocaleDateString()} /> } label="Last response" value={ responses.length > 0 ? new Date(responses[0].submitted_at).toLocaleDateString() : "—" } />
{/* Tabs */}
{responses.length === 0 ? (

No responses yet. Share this form to start collecting data.

View submission page
) : activeTab === "summary" ? ( ) : ( )}
) } function StatCard({ icon, label, value, }: { icon: React.ReactNode label: string value: string | number }) { return (
{icon} {label}

{value}

) } function SummaryTab({ form, responses, }: { form: FormDetail responses: FormResponse[] }) { return (
{form.questions.map((question, index) => ( ))}
) } function QuestionSummary({ question, index, responses, totalResponses, }: { question: Question index: number responses: FormResponse[] totalResponses: number }) { const answersForQuestion = useMemo(() => { return responses .map((r) => r.answers.find((a) => a.question_id === question.id)) .filter(Boolean) }, [responses, question.id]) const responseCount = answersForQuestion.length const skipped = totalResponses - responseCount return (
{index + 1}

{question.title} {question.required && ( * )}

{question.type.replace("_", " ")} · {responseCount} answers {skipped > 0 && ( <> · {skipped} skipped )}
{hasChartableType(question.type) ? ( ) : ( )}
) } function hasChartableType(type: QuestionType): boolean { return ["multiple_choice", "checkbox", "dropdown", "rating"].includes(type) } function ChartVisualization({ question, responses, }: { question: Question responses: FormResponse[] }) { const chartData = useMemo(() => { const counts: Record = {} if (["multiple_choice", "dropdown"].includes(question.type)) { question.options.forEach((opt) => { counts[opt.label] = 0 }) } if (question.type === "checkbox") { question.options.forEach((opt) => { counts[opt.label] = 0 }) } if (question.type === "rating") { for (let i = 1; i <= 5; i++) { counts[String(i)] = 0 } } responses.forEach((r) => { const answer = r.answers.find((a) => a.question_id === question.id) if (!answer) return if (question.type === "checkbox") { const selected = answer.answer.split(",").map((s) => s.trim()) selected.forEach((s) => { counts[s] = (counts[s] || 0) + 1 }) } else { counts[answer.answer] = (counts[answer.answer] || 0) + 1 } }) return Object.entries(counts).map(([name, value]) => ({ name, value, })) }, [question, responses]) const total = chartData.reduce((sum, d) => sum + d.value, 0) if (question.type === "rating") { const avgRating = useMemo(() => { let sum = 0 let count = 0 responses.forEach((r) => { const answer = r.answers.find((a) => a.question_id === question.id) if (answer) { sum += Number(answer.answer) count++ } }) return count > 0 ? (sum / count).toFixed(1) : "—" }, [responses, question.id]) return (
{avgRating} average rating out of 5
`${v} ★`} className="fill-muted-foreground" /> {chartData.map((_, i) => ( ))}
) } return (
{chartData.map((_, i) => ( ))} [ `${value ?? 0} (${total > 0 ? (((value ?? 0) / total) * 100).toFixed(0) : 0}%)`, "Responses", ]} />
{chartData.map((item, i) => (
{item.name} {item.value} {total > 0 ? ((item.value / total) * 100).toFixed(0) : 0}%
))}
) } function TextResponses({ questionId, responses, }: { questionId: string responses: FormResponse[] }) { const [showAll, setShowAll] = useState(false) const textAnswers = useMemo(() => { return responses .map((r) => { const answer = r.answers.find((a) => a.question_id === questionId) return answer ? { text: answer.answer, date: r.submitted_at } : null }) .filter(Boolean) as { text: string; date: string }[] }, [responses, questionId]) if (textAnswers.length === 0) { return (

No responses for this question.

) } const visible = showAll ? textAnswers : textAnswers.slice(0, 5) return (
{visible.map((item, i) => (

{item.text}

{new Date(item.date).toLocaleString()}

))}
{textAnswers.length > 5 && ( )}
) } function IndividualTab({ form, responses, }: { form: FormDetail responses: FormResponse[] }) { const [selectedIndex, setSelectedIndex] = useState(0) const sorted = useMemo( () => [...responses].sort( (a, b) => new Date(b.submitted_at).getTime() - new Date(a.submitted_at).getTime() ), [responses] ) const current = sorted[selectedIndex] if (!current) return null return (
{/* Navigation */}

Response {selectedIndex + 1} of {sorted.length}

{new Date(current.submitted_at).toLocaleString()}

{/* Answers */}
{form.questions.map((question, index) => { const answer = current.answers.find( (a) => a.question_id === question.id ) return (
{index + 1}

{question.title} {question.required && ( * )}

{question.type.replace("_", " ")}

{answer ? (

{answer.answer}

) : (

No answer (skipped)

)}
) })}
) }