311 lines
13 KiB
TypeScript
311 lines
13 KiB
TypeScript
![]() |
"use client"
|
||
|
|
||
|
import { useState } from "react"
|
||
|
import Link from "next/link"
|
||
|
import { ArrowRight, ArrowUpDown, Download, FileText, Filter, Search, SortAsc, SortDesc } from "lucide-react"
|
||
|
import { Button } from "@/components/ui/button"
|
||
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||
|
import { Badge } from "@/components/ui/badge"
|
||
|
import { Separator } from "@/components/ui/separator"
|
||
|
import { Input } from "@/components/ui/input"
|
||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||
|
import {
|
||
|
DropdownMenu,
|
||
|
DropdownMenuContent,
|
||
|
DropdownMenuItem,
|
||
|
DropdownMenuLabel,
|
||
|
DropdownMenuSeparator,
|
||
|
DropdownMenuTrigger,
|
||
|
} from "@/components/ui/dropdown-menu"
|
||
|
|
||
|
// Mock data for resolutions
|
||
|
const mockResolutions = [
|
||
|
{
|
||
|
id: 12,
|
||
|
title: "Education and Community Health Improvements",
|
||
|
description: "A comprehensive set of recommendations for improving education access and community health services",
|
||
|
date: "2024-02-15",
|
||
|
contributions: 8,
|
||
|
categories: ["Education", "Healthcare"],
|
||
|
status: "active",
|
||
|
},
|
||
|
{
|
||
|
id: 11,
|
||
|
title: "Environmental Protection and Green Spaces",
|
||
|
description: "Recommendations for preserving and expanding green spaces and environmental protections",
|
||
|
date: "2024-01-10",
|
||
|
contributions: 6,
|
||
|
categories: ["Environmental Policy"],
|
||
|
status: "active",
|
||
|
},
|
||
|
{
|
||
|
id: 10,
|
||
|
title: "Public Transportation and Infrastructure",
|
||
|
description: "Proposals for improving public transportation access and infrastructure maintenance",
|
||
|
date: "2023-12-05",
|
||
|
contributions: 7,
|
||
|
categories: ["Infrastructure"],
|
||
|
status: "archived",
|
||
|
},
|
||
|
]
|
||
|
|
||
|
// Add state for search, filter, and sort
|
||
|
export default function ResolutionsPage() {
|
||
|
const [searchQuery, setSearchQuery] = useState("")
|
||
|
const [categoryFilter, setCategoryFilter] = useState("all")
|
||
|
const [statusFilter, setStatusFilter] = useState("all")
|
||
|
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc")
|
||
|
const [sortBy, setSortBy] = useState<"contributions" | "date">("date")
|
||
|
|
||
|
// Filter and sort the resolutions
|
||
|
const filteredResolutions = mockResolutions.filter((resolution) => {
|
||
|
// Filter by search query
|
||
|
if (
|
||
|
searchQuery &&
|
||
|
!resolution.title.toLowerCase().includes(searchQuery.toLowerCase()) &&
|
||
|
!resolution.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||
|
)
|
||
|
return false
|
||
|
|
||
|
// Filter by category
|
||
|
if (categoryFilter !== "all" && !resolution.categories.includes(categoryFilter)) return false
|
||
|
|
||
|
// Filter by status
|
||
|
if (statusFilter !== "all" && resolution.status !== statusFilter) return false
|
||
|
|
||
|
return true
|
||
|
})
|
||
|
|
||
|
// Sort the filtered resolutions
|
||
|
const sortedResolutions = [...filteredResolutions].sort((a, b) => {
|
||
|
if (sortBy === "contributions") {
|
||
|
return sortOrder === "desc" ? b.contributions - a.contributions : a.contributions - b.contributions
|
||
|
} else {
|
||
|
const dateA = new Date(a.date).getTime()
|
||
|
const dateB = new Date(b.date).getTime()
|
||
|
return sortOrder === "desc" ? dateB - dateA : dateA - dateB
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// Group resolutions by status
|
||
|
const activeResolutions = sortedResolutions.filter((r) => r.status === "active")
|
||
|
const archivedResolutions = sortedResolutions.filter((r) => r.status === "archived")
|
||
|
|
||
|
// Add the search, filter, and sort UI
|
||
|
return (
|
||
|
<div className="container mx-auto px-4 py-8">
|
||
|
<div className="mb-8">
|
||
|
<h1 className="text-3xl font-bold">Resolutions</h1>
|
||
|
<p className="mt-2 text-muted-foreground">
|
||
|
Final policy recommendations compiled from validated community contributions.
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
{/* Filters and Search */}
|
||
|
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||
|
<div className="relative w-full md:w-72">
|
||
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||
|
<Input
|
||
|
placeholder="Search resolutions..."
|
||
|
className="pl-8"
|
||
|
value={searchQuery}
|
||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||
|
/>
|
||
|
</div>
|
||
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||
|
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
|
||
|
<SelectTrigger className="w-full sm:w-[180px]">
|
||
|
<Filter className="mr-2 h-4 w-4" />
|
||
|
<SelectValue placeholder="Filter by category" />
|
||
|
</SelectTrigger>
|
||
|
<SelectContent>
|
||
|
<SelectItem value="all">All Categories</SelectItem>
|
||
|
<SelectItem value="Education">Education</SelectItem>
|
||
|
<SelectItem value="Healthcare">Healthcare</SelectItem>
|
||
|
<SelectItem value="Environmental Policy">Environmental</SelectItem>
|
||
|
<SelectItem value="Infrastructure">Infrastructure</SelectItem>
|
||
|
</SelectContent>
|
||
|
</Select>
|
||
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||
|
<SelectTrigger className="w-full sm:w-[180px]">
|
||
|
<SelectValue placeholder="Filter by status" />
|
||
|
</SelectTrigger>
|
||
|
<SelectContent>
|
||
|
<SelectItem value="all">All Statuses</SelectItem>
|
||
|
<SelectItem value="active">Active</SelectItem>
|
||
|
<SelectItem value="archived">Archived</SelectItem>
|
||
|
</SelectContent>
|
||
|
</Select>
|
||
|
<DropdownMenu>
|
||
|
<DropdownMenuTrigger asChild>
|
||
|
<Button variant="outline" className="flex items-center gap-2">
|
||
|
<ArrowUpDown className="h-4 w-4" />
|
||
|
Sort by {sortBy === "contributions" ? "Contributions" : "Date"}
|
||
|
{sortOrder === "desc" ? <SortDesc className="ml-1 h-4 w-4" /> : <SortAsc className="ml-1 h-4 w-4" />}
|
||
|
</Button>
|
||
|
</DropdownMenuTrigger>
|
||
|
<DropdownMenuContent align="end">
|
||
|
<DropdownMenuLabel>Sort by</DropdownMenuLabel>
|
||
|
<DropdownMenuSeparator />
|
||
|
<DropdownMenuItem onClick={() => setSortBy("contributions")}>
|
||
|
Contributions {sortBy === "contributions" && "✓"}
|
||
|
</DropdownMenuItem>
|
||
|
<DropdownMenuItem onClick={() => setSortBy("date")}>Date {sortBy === "date" && "✓"}</DropdownMenuItem>
|
||
|
<DropdownMenuSeparator />
|
||
|
<DropdownMenuItem onClick={() => setSortOrder(sortOrder === "desc" ? "asc" : "desc")}>
|
||
|
{sortOrder === "desc" ? "Ascending" : "Descending"}
|
||
|
</DropdownMenuItem>
|
||
|
</DropdownMenuContent>
|
||
|
</DropdownMenu>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
{filteredResolutions.length === 0 ? (
|
||
|
<div className="mt-12 text-center">
|
||
|
<p className="text-lg font-medium">No resolutions match your filters</p>
|
||
|
<p className="mt-2 text-muted-foreground">Try adjusting your search or filters</p>
|
||
|
<Button
|
||
|
variant="outline"
|
||
|
className="mt-4"
|
||
|
onClick={() => {
|
||
|
setSearchQuery("")
|
||
|
setCategoryFilter("all")
|
||
|
setStatusFilter("all")
|
||
|
}}
|
||
|
>
|
||
|
Clear All Filters
|
||
|
</Button>
|
||
|
</div>
|
||
|
) : (
|
||
|
<>
|
||
|
{statusFilter !== "archived" && activeResolutions.length > 0 && (
|
||
|
<>
|
||
|
<div className="mb-8">
|
||
|
<h2 className="text-xl font-semibold">Active Resolutions</h2>
|
||
|
<p className="text-sm text-muted-foreground">
|
||
|
Current resolutions available for policymakers and the public
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||
|
{activeResolutions.map((resolution) => (
|
||
|
<Card key={resolution.id}>
|
||
|
<CardHeader>
|
||
|
<div className="flex items-start justify-between">
|
||
|
<div>
|
||
|
<CardTitle className="flex items-center gap-2">
|
||
|
<FileText className="h-5 w-5 text-primary" />
|
||
|
Resolution #{resolution.id}
|
||
|
</CardTitle>
|
||
|
<CardDescription className="mt-1">{resolution.title}</CardDescription>
|
||
|
</div>
|
||
|
<Badge>Active</Badge>
|
||
|
</div>
|
||
|
</CardHeader>
|
||
|
<CardContent>
|
||
|
<p className="text-sm text-muted-foreground">{resolution.description}</p>
|
||
|
|
||
|
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
|
||
|
<div>
|
||
|
<p className="font-medium">Published</p>
|
||
|
<p className="text-muted-foreground">{new Date(resolution.date).toLocaleDateString()}</p>
|
||
|
</div>
|
||
|
<div>
|
||
|
<p className="font-medium">Contributions</p>
|
||
|
<p className="text-muted-foreground">{resolution.contributions} included</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div className="mt-4">
|
||
|
<p className="text-sm font-medium">Categories</p>
|
||
|
<div className="mt-2 flex flex-wrap gap-2">
|
||
|
{resolution.categories.map((category) => (
|
||
|
<Badge key={category} variant="outline">
|
||
|
{category}
|
||
|
</Badge>
|
||
|
))}
|
||
|
</div>
|
||
|
</div>
|
||
|
</CardContent>
|
||
|
<CardFooter className="flex flex-col gap-4 sm:flex-row">
|
||
|
<Button variant="outline" className="w-full sm:w-auto" asChild>
|
||
|
<Link href={`/resolutions/${resolution.id}`}>
|
||
|
View Details
|
||
|
<ArrowRight className="ml-2 h-4 w-4" />
|
||
|
</Link>
|
||
|
</Button>
|
||
|
<Button className="w-full sm:w-auto">
|
||
|
<Download className="mr-2 h-4 w-4" />
|
||
|
Download PDF
|
||
|
</Button>
|
||
|
</CardFooter>
|
||
|
</Card>
|
||
|
))}
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
|
||
|
{statusFilter !== "active" && archivedResolutions.length > 0 && (
|
||
|
<>
|
||
|
<Separator className="my-8" />
|
||
|
|
||
|
<div className="mb-8">
|
||
|
<h2 className="text-xl font-semibold">Archive</h2>
|
||
|
<p className="text-sm text-muted-foreground">
|
||
|
Past resolutions that have been delivered to policymakers
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||
|
{archivedResolutions.map((resolution) => (
|
||
|
<Card key={resolution.id} className="opacity-80">
|
||
|
<CardHeader>
|
||
|
<div className="flex items-start justify-between">
|
||
|
<div>
|
||
|
<CardTitle className="flex items-center gap-2">
|
||
|
<FileText className="h-5 w-5" />
|
||
|
Resolution #{resolution.id}
|
||
|
</CardTitle>
|
||
|
<CardDescription className="mt-1">{resolution.title}</CardDescription>
|
||
|
</div>
|
||
|
<Badge variant="outline">Archived</Badge>
|
||
|
</div>
|
||
|
</CardHeader>
|
||
|
<CardContent>
|
||
|
<p className="text-sm text-muted-foreground">{resolution.description}</p>
|
||
|
|
||
|
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
|
||
|
<div>
|
||
|
<p className="font-medium">Published</p>
|
||
|
<p className="text-muted-foreground">{new Date(resolution.date).toLocaleDateString()}</p>
|
||
|
</div>
|
||
|
<div>
|
||
|
<p className="font-medium">Contributions</p>
|
||
|
<p className="text-muted-foreground">{resolution.contributions} included</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
</CardContent>
|
||
|
<CardFooter className="flex flex-col gap-4 sm:flex-row">
|
||
|
<Button variant="outline" className="w-full sm:w-auto" asChild>
|
||
|
<Link href={`/resolutions/${resolution.id}`}>
|
||
|
View Details
|
||
|
<ArrowRight className="ml-2 h-4 w-4" />
|
||
|
</Link>
|
||
|
</Button>
|
||
|
<Button variant="secondary" className="w-full sm:w-auto">
|
||
|
<Download className="mr-2 h-4 w-4" />
|
||
|
Download PDF
|
||
|
</Button>
|
||
|
</CardFooter>
|
||
|
</Card>
|
||
|
))}
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
</>
|
||
|
)}
|
||
|
</div>
|
||
|
)
|
||
|
}
|
||
|
|