2025-03-25 03:52:30 -04:00

499 lines
28 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { use } from "react"
import {
ArrowLeft,
CheckCircle,
Clock,
ExternalLink,
MessageCircle,
ThumbsUp,
Users,
FileText,
CalendarDays,
LineChart,
AlertCircle
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger
} from "@/components/ui/tabs"
import { Badge } from "@/components/ui/badge"
import { Separator } from "@/components/ui/separator"
import { Progress } from "@/components/ui/progress"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
// Mock data for a single contribution
const mockContributions = [
{
id: 1,
title: "After-School Programs",
description: "Create free after-school programs for K-8 students in public schools",
category: "Education",
votes: { yes: 312, no: 28 },
perspectives: 42,
dateCreated: "2023-10-05",
dateValidated: "2023-11-15",
status: "Validated",
resolutionId: 12,
resolutionTitle: "Education and Community Health Improvements",
metrics: {
sentimentScore: 0.87,
participationRate: 0.62,
consensusSpeed: "Fast",
implementationComplexity: "Medium",
},
sourceInsight: {
id: 3,
title: "After-School Programs",
description: "Create free after-school programs for K-8 students in public schools"
},
relatedPerspectives: [
{
id: 101,
username: "parent87",
content: "As a working parent, having free after-school programs would be life-changing for me and my children.",
date: "2023-09-15",
votes: 43,
},
{
id: 102,
username: "educator22",
content: "After-school programs provide crucial educational support and keep children engaged in positive activities.",
date: "2023-09-18",
votes: 38,
},
{
id: 103,
username: "communityLeader",
content: "These programs would reduce juvenile crime rates in neighborhoods where working parents can't be home until evening.",
date: "2023-09-22",
votes: 29,
},
{
id: 104,
username: "budgetAnalyst",
content: "The ROI is excellent - the cost of these programs is lower than dealing with the social issues that arise without them.",
date: "2023-09-25",
votes: 36,
},
{
id: 105,
username: "socialWorker42",
content: "Many families in underserved areas would greatly benefit from structured after-school care and enrichment.",
date: "2023-09-28",
votes: 41,
},
],
},
{
id: 2,
title: "Community Health Clinics",
description: "Establish walk-in clinics in underserved areas with sliding scale fees",
category: "Healthcare",
votes: { yes: 278, no: 42 },
perspectives: 36,
dateCreated: "2023-11-10",
dateValidated: "2023-12-03",
status: "Validated",
resolutionId: 12,
resolutionTitle: "Education and Community Health Improvements",
metrics: {
sentimentScore: 0.82,
participationRate: 0.58,
consensusSpeed: "Medium",
implementationComplexity: "High",
},
sourceInsight: {
id: 4,
title: "Community Health Clinics",
description: "Establish walk-in clinics in underserved areas with sliding scale fees"
},
relatedPerspectives: [
// Perspectives data would go here
]
},
// Other contributions would go here
]
export default function ContributionPage({ params }: { params: Promise<{ id: string }> }) {
// Properly handle the params with React.use()
const { id } = use(params)
const contributionId = parseInt(id)
const [activeTab, setActiveTab] = useState("overview")
// Find the contribution from mock data
const contribution = mockContributions.find(c => c.id === contributionId)
if (!contribution) {
return (
<div className="container mx-auto px-4 py-16 text-center">
<AlertCircle className="mx-auto h-16 w-16 text-muted-foreground mb-4" />
<h1 className="text-2xl font-bold mb-2">Contribution Not Found</h1>
<p className="text-muted-foreground mb-8">The contribution you're looking for doesn't exist or has been removed.</p>
<Button asChild>
<Link href="/contributions">
<ArrowLeft className="mr-2 h-4 w-4" />
Return to Contributions
</Link>
</Button>
</div>
)
}
// Approval percentage
const approvalPercentage = Math.round((contribution.votes.yes / (contribution.votes.yes + contribution.votes.no)) * 100)
return (
<div className="container mx-auto px-4 py-8">
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-8">
<div>
<Button variant="outline" size="sm" className="mb-4" asChild>
<Link href="/contributions">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Contributions
</Link>
</Button>
<div className="flex items-center gap-2 mb-1">
<Badge className="text-xs">{contribution.category}</Badge>
<Badge variant="outline" className="text-xs">ID: {contribution.id}</Badge>
</div>
<h1 className="text-3xl font-bold flex items-center gap-2">
<CheckCircle className="h-6 w-6 text-primary" />
{contribution.title}
</h1>
<p className="mt-2 text-muted-foreground">{contribution.description}</p>
</div>
<div className="flex flex-col gap-2 items-start md:items-end">
<Badge variant="secondary" className="mb-2">
{contribution.status}
</Badge>
<div className="flex items-center gap-1 text-sm">
<CalendarDays className="h-4 w-4 mr-1 text-muted-foreground" />
<span className="text-muted-foreground">Validated: </span>
<span>{new Date(contribution.dateValidated).toLocaleDateString()}</span>
</div>
<Button size="sm" asChild>
<Link href={`/resolutions/${contribution.resolutionId}`}>
<FileText className="mr-2 h-4 w-4" />
View in Resolution #{contribution.resolutionId}
</Link>
</Button>
</div>
</div>
<Tabs defaultValue="overview" value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="perspectives">Source Perspectives</TabsTrigger>
<TabsTrigger value="metrics">Metrics & Analysis</TabsTrigger>
</TabsList>
{/* Overview Tab */}
<TabsContent value="overview" className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="text-lg">Consensus Details</CardTitle>
<CardDescription>How this contribution reached consensus</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
<div>
<div className="flex justify-between mb-2 text-sm">
<span>Approval: {approvalPercentage}%</span>
<span>
<span className="text-green-600">{contribution.votes.yes} Yes</span> / <span className="text-red-600">{contribution.votes.no} No</span>
</span>
</div>
<Progress value={approvalPercentage} className="h-2" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm font-medium">Based On</p>
<p className="text-2xl font-bold">{contribution.perspectives}</p>
<p className="text-xs text-muted-foreground">unique perspectives</p>
</div>
<div>
<p className="text-sm font-medium">Consensus Speed</p>
<p className="text-2xl font-bold">{contribution.metrics.consensusSpeed}</p>
<p className="text-xs text-muted-foreground">relative to average</p>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Timeline</h4>
<div className="space-y-3">
<div className="flex gap-2">
<div className="flex flex-col items-center">
<div className="h-6 w-6 rounded-full bg-primary flex items-center justify-center text-primary-foreground">
<MessageCircle className="h-3 w-3" />
</div>
<div className="w-px h-full bg-muted-foreground/30 my-1"></div>
</div>
<div>
<p className="text-sm font-medium">Perspectives Collection</p>
<p className="text-xs text-muted-foreground">Started: {new Date('2023-09-01').toLocaleDateString()}</p>
</div>
</div>
<div className="flex gap-2">
<div className="flex flex-col items-center">
<div className="h-6 w-6 rounded-full bg-amber-500 flex items-center justify-center text-white">
<LineChart className="h-3 w-3" />
</div>
<div className="w-px h-full bg-muted-foreground/30 my-1"></div>
</div>
<div>
<p className="text-sm font-medium">Insight Generated</p>
<p className="text-xs text-muted-foreground">Date: {new Date(contribution.dateCreated).toLocaleDateString()}</p>
</div>
</div>
<div className="flex gap-2">
<div className="flex flex-col items-center">
<div className="h-6 w-6 rounded-full bg-green-500 flex items-center justify-center text-white">
<ThumbsUp className="h-3 w-3" />
</div>
</div>
<div>
<p className="text-sm font-medium">Consensus Reached</p>
<p className="text-xs text-muted-foreground">Date: {new Date(contribution.dateValidated).toLocaleDateString()}</p>
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg">Resolution Integration</CardTitle>
<CardDescription>How this contribution impacts policy</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
<div>
<p className="text-sm font-medium mb-2">Included in Resolution</p>
<div className="flex items-center gap-2 p-3 border rounded-md">
<FileText className="h-5 w-5 text-blue-500" />
<div>
<p className="font-medium">Resolution #{contribution.resolutionId}</p>
<p className="text-sm text-muted-foreground">{contribution.resolutionTitle}</p>
</div>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Implementation Complexity</h4>
<Badge variant={contribution.metrics.implementationComplexity === "Low" ? "outline" :
contribution.metrics.implementationComplexity === "Medium" ? "secondary" :
"destructive"}>
{contribution.metrics.implementationComplexity}
</Badge>
<p className="mt-2 text-sm text-muted-foreground">
This contribution has been assessed as having {contribution.metrics.implementationComplexity.toLowerCase()}
implementation complexity based on required resources, timeline, and regulatory considerations.
</p>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Source Insight</h4>
<Link href={`/insights/${contribution.sourceInsight.id}`} className="block p-3 border rounded-md hover:bg-accent transition-colors">
<div className="flex items-center justify-between">
<div>
<p className="font-medium">{contribution.sourceInsight.title}</p>
<p className="text-sm text-muted-foreground truncate">{contribution.sourceInsight.description}</p>
</div>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</div>
</Link>
</div>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
{/* Perspectives Tab */}
<TabsContent value="perspectives" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-lg">Source Perspectives</CardTitle>
<CardDescription>Original community input that led to this contribution</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-6">
{contribution.relatedPerspectives.map((perspective) => (
<div key={perspective.id} className="p-4 border rounded-lg">
<div className="flex justify-between items-start mb-3">
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<AvatarImage src={`https://api.dicebear.com/7.x/initials/svg?seed=${perspective.username}`} alt={perspective.username} />
<AvatarFallback>{perspective.username.substring(0, 2).toUpperCase()}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{perspective.username}</p>
<p className="text-xs text-muted-foreground">{new Date(perspective.date).toLocaleDateString()}</p>
</div>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1">
<ThumbsUp className="h-4 w-4 text-muted-foreground" />
<span className="text-sm">{perspective.votes}</span>
</div>
</TooltipTrigger>
<TooltipContent>
<p>Community upvotes</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<p className="text-sm">{perspective.content}</p>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
{/* Metrics Tab */}
<TabsContent value="metrics" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-lg">Metrics & Analysis</CardTitle>
<CardDescription>Statistical breakdown of community engagement</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-2">Sentiment Analysis</h4>
<div className="flex items-center gap-4">
<div className="h-16 w-16 rounded-full border-4 border-green-500 flex items-center justify-center">
<span className="text-xl font-bold">{contribution.metrics.sentimentScore * 10}</span>
</div>
<div>
<p className="font-medium">Positive Sentiment</p>
<p className="text-sm text-muted-foreground">
{contribution.metrics.sentimentScore > 0.8 ? "Very Positive" :
contribution.metrics.sentimentScore > 0.6 ? "Positive" :
contribution.metrics.sentimentScore > 0.4 ? "Neutral" : "Mixed"}
</p>
</div>
</div>
<p className="mt-2 text-sm text-muted-foreground">
AI analysis of perspective text shows strong support for this contribution.
</p>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Demographic Distribution</h4>
<div className="space-y-2">
<div>
<div className="flex justify-between mb-1 text-xs">
<span>Age Groups</span>
</div>
<div className="flex h-2">
<div className="h-full bg-blue-300" style={{ width: "15%" }}></div>
<div className="h-full bg-blue-400" style={{ width: "25%" }}></div>
<div className="h-full bg-blue-500" style={{ width: "35%" }}></div>
<div className="h-full bg-blue-600" style={{ width: "20%" }}></div>
<div className="h-full bg-blue-700" style={{ width: "5%" }}></div>
</div>
<div className="flex justify-between text-xs mt-1">
<span>18-24</span>
<span>25-34</span>
<span>35-44</span>
<span>45-64</span>
<span>65+</span>
</div>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-2">Participation Rate</h4>
<div className="flex items-center gap-4">
<div className="relative h-16 w-16">
<svg className="h-full w-full" viewBox="0 0 36 36">
<path
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#eee"
strokeWidth="3"
/>
<path
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#3b82f6"
strokeWidth="3"
strokeDasharray={`${contribution.metrics.participationRate * 100}, 100`}
/>
</svg>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm font-semibold">
{Math.round(contribution.metrics.participationRate * 100)}%
</div>
</div>
<div>
<p className="font-medium">Community Engagement</p>
<p className="text-sm text-muted-foreground">
{contribution.metrics.participationRate > 0.7 ? "Exceptional" :
contribution.metrics.participationRate > 0.5 ? "High" :
contribution.metrics.participationRate > 0.3 ? "Moderate" : "Low"}
</p>
</div>
</div>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Geographic Impact</h4>
<div className="p-3 border rounded-md">
<div className="flex items-center gap-2 mb-2">
<Users className="h-5 w-5 text-primary" />
<p className="font-medium">City-wide Relevance</p>
</div>
<p className="text-sm text-muted-foreground mb-2">
This contribution has been flagged as having city-wide relevance with particular
significance for underserved communities.
</p>
<div className="flex gap-2">
<Badge variant="outline">Downtown</Badge>
<Badge variant="outline">South Side</Badge>
<Badge variant="outline">West End</Badge>
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
<div className="mt-12 flex justify-center">
<Button variant="outline" asChild>
<Link href="/contributions">Return to All Contributions</Link>
</Button>
</div>
</div>
)
}