- Fork elmeg-demo codebase for multi-band support - Add data importer infrastructure with base class - Create band-specific importers: - phish.py: Phish.net API v5 - grateful_dead.py: Grateful Stats API - setlistfm.py: Dead & Company, Billy Strings (Setlist.fm) - Add spec-kit configuration for Gemini - Update README with supported bands and architecture
143 lines
5.8 KiB
TypeScript
143 lines
5.8 KiB
TypeScript
"use client"
|
|
|
|
/**
|
|
* Known Issues Page - Public bugs/feature requests
|
|
*/
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { getApiUrl } from "@/lib/api-config"
|
|
import Link from "next/link"
|
|
import { ArrowLeft, ThumbsUp, Bug, Lightbulb, Loader2 } from "lucide-react"
|
|
|
|
interface Ticket {
|
|
id: number
|
|
ticket_number: string
|
|
type: string
|
|
status: string
|
|
priority: string
|
|
title: string
|
|
description: string
|
|
upvotes: number
|
|
created_at: string
|
|
}
|
|
|
|
const TYPE_ICONS: Record<string, typeof Bug> = {
|
|
bug: Bug,
|
|
feature: Lightbulb,
|
|
}
|
|
|
|
export default function KnownIssuesPage() {
|
|
const [tickets, setTickets] = useState<Ticket[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
fetchKnownIssues()
|
|
}, [])
|
|
|
|
const fetchKnownIssues = async () => {
|
|
try {
|
|
const res = await fetch(`${getApiUrl()}/tickets/known-issues`)
|
|
if (res.ok) {
|
|
setTickets(await res.json())
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to fetch known issues", e)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleUpvote = async (ticketNumber: string, e: React.MouseEvent) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
try {
|
|
await fetch(`${getApiUrl()}/tickets/${ticketNumber}/upvote`, {
|
|
method: "POST",
|
|
})
|
|
// Optimistic update
|
|
setTickets(tickets.map(t =>
|
|
t.ticket_number === ticketNumber
|
|
? { ...t, upvotes: t.upvotes + 1 }
|
|
: t
|
|
))
|
|
} catch (e) {
|
|
console.error("Failed to upvote", e)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="container max-w-4xl py-8">
|
|
<div className="flex items-center gap-4 mb-8">
|
|
<Link href="/bugs">
|
|
<Button variant="ghost" size="icon">
|
|
<ArrowLeft className="h-4 w-4" />
|
|
</Button>
|
|
</Link>
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Known Issues</h1>
|
|
<p className="text-muted-foreground">Active bugs and feature requests</p>
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
) : tickets.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="py-12 text-center">
|
|
<p className="text-muted-foreground">No known issues at this time.</p>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{tickets.map((ticket) => {
|
|
const Icon = TYPE_ICONS[ticket.type] || Bug
|
|
return (
|
|
<Link key={ticket.id} href={`/bugs/ticket/${ticket.ticket_number}`}>
|
|
<Card className="hover:border-primary transition-colors cursor-pointer">
|
|
<CardContent className="py-4">
|
|
<div className="flex items-start gap-4">
|
|
<div className="p-2 rounded-lg bg-muted">
|
|
<Icon className="h-5 w-5 text-muted-foreground" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="font-mono text-sm text-muted-foreground">
|
|
{ticket.ticket_number}
|
|
</span>
|
|
<Badge variant={ticket.status === "in_progress" ? "secondary" : "default"}>
|
|
{ticket.status === "in_progress" ? "In Progress" : "Open"}
|
|
</Badge>
|
|
</div>
|
|
<h3 className="font-semibold">{ticket.title}</h3>
|
|
{ticket.description && (
|
|
<p className="text-sm text-muted-foreground line-clamp-2 mt-1">
|
|
{ticket.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="gap-2 shrink-0"
|
|
onClick={(e) => handleUpvote(ticket.ticket_number, e)}
|
|
>
|
|
<ThumbsUp className="h-4 w-4" />
|
|
{ticket.upvotes}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|