gohorsejobs/frontend/src/app/dashboard/support/tickets/page.tsx
Redbull Deployer 1a449b7824 UI: Fix ticket dialogs, i18n interpolation, and Add Job button
- Place Category and Priority side by side in create ticket dialogs
- Fix pagination showing literal {1} instead of actual values
- Fix double-brace interpolation in en.json (tickets, companies)
- Replace Add Job modal on dashboard with link to /dashboard/jobs/new
2026-03-04 15:34:32 -06:00

215 lines
11 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Ticket, ticketsApi } from "@/lib/api"
import { toast } from "sonner"
import Link from "next/link"
import { Plus, MessageSquare } from "lucide-react"
import { format } from "date-fns"
export default function TicketsPage() {
const [tickets, setTickets] = useState<Ticket[]>([])
const [loading, setLoading] = useState(true)
const [isCreateOpen, setIsCreateOpen] = useState(false)
const [newTicket, setNewTicket] = useState({ subject: "", category: "support", priority: "medium", message: "" })
const fetchTickets = async () => {
try {
const data = await ticketsApi.list()
setTickets(data)
setLoading(false)
} catch (error) {
console.error("Failed to fetch tickets", error)
toast.error("Failed to fetch tickets")
setLoading(false)
}
}
useEffect(() => {
fetchTickets()
}, [])
const handleCreateTicket = async () => {
if (!newTicket.subject || !newTicket.message) return
try {
await ticketsApi.create(newTicket)
toast.success("Ticket created successfully")
setIsCreateOpen(false)
setNewTicket({ subject: "", category: "support", priority: "medium", message: "" })
fetchTickets() // Refresh
} catch (error) {
console.error("Failed to create ticket", error)
toast.error("Failed to create ticket")
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "open": return <Badge variant="default" className="bg-green-500">Open</Badge>
case "in_progress": return <Badge variant="secondary" className="bg-yellow-500 text-white">In Progress</Badge>
case "closed": return <Badge variant="outline">Closed</Badge>
default: return <Badge variant="outline">{status}</Badge>
}
}
const getPriorityBadge = (priority: string) => {
switch (priority) {
case "high": return <Badge variant="destructive">High</Badge>
case "medium": return <Badge variant="secondary">Medium</Badge>
case "low": return <Badge variant="outline">Low</Badge>
default: return <Badge variant="outline">{priority}</Badge>
}
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Support Tickets</h1>
<p className="text-muted-foreground">Manage your support requests and inquiries.</p>
</div>
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="mr-2 h-4 w-4" />
New Ticket
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Ticket</DialogTitle>
<DialogDescription>Describe your issue and priority.</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-1.5">
<Label htmlFor="subject">Subject</Label>
<Input
id="subject"
placeholder="Describe your issue briefly"
value={newTicket.subject}
onChange={(e) => setNewTicket({ ...newTicket, subject: e.target.value })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label htmlFor="category">Category</Label>
<Select
value={newTicket.category}
onValueChange={(val) => setNewTicket({ ...newTicket, category: val })}
>
<SelectTrigger>
<SelectValue placeholder="Select category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="bug">Bug</SelectItem>
<SelectItem value="feature">Feature Request</SelectItem>
<SelectItem value="support">Support</SelectItem>
<SelectItem value="billing">Billing</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label htmlFor="priority">Priority</Label>
<Select
value={newTicket.priority}
onValueChange={(val) => setNewTicket({ ...newTicket, priority: val })}
>
<SelectTrigger>
<SelectValue placeholder="Select priority" />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-1.5">
<Label htmlFor="message">Message</Label>
<Textarea
id="message"
placeholder="Describe your request in detail"
value={newTicket.message}
onChange={(e) => setNewTicket({ ...newTicket, message: e.target.value })}
rows={4}
/>
</div>
</div>
<DialogFooter>
<Button onClick={handleCreateTicket}>Create Ticket</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
<Card>
<CardHeader>
<CardTitle>My Tickets</CardTitle>
<CardDescription>A list of your recent support tickets.</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Subject</TableHead>
<TableHead>Status</TableHead>
<TableHead>Priority</TableHead>
<TableHead>Created</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">Loading...</TableCell>
</TableRow>
) : tickets.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-4">No tickets found</TableCell>
</TableRow>
) : (
tickets.map((ticket) => (
<TableRow key={ticket.id}>
<TableCell className="font-mono text-xs max-w-[80px] truncate">{ticket.id.substring(0, 8)}...</TableCell>
<TableCell className="font-medium">{ticket.subject}</TableCell>
<TableCell>{getStatusBadge(ticket.status)}</TableCell>
<TableCell>{getPriorityBadge(ticket.priority)}</TableCell>
<TableCell>{format(new Date(ticket.createdAt), "MMM d, yyyy")}</TableCell>
<TableCell className="text-right">
<Link href={`./tickets/${ticket.id}`}>
<Button variant="ghost" size="sm">
<MessageSquare className="h-4 w-4 mr-2" />
View
</Button>
</Link>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}