feat: add ticket workflow pages
This commit is contained in:
68
frontend/src/features/tickets/TicketListPage.tsx
Normal file
68
frontend/src/features/tickets/TicketListPage.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
|
||||
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select } from '@/components/ui/select'
|
||||
import { formatDateTime } from '@/lib/format'
|
||||
|
||||
import { listTickets } from './api'
|
||||
import type { Ticket } from './types'
|
||||
|
||||
export function TicketListPage() {
|
||||
const [tickets, setTickets] = React.useState<Ticket[]>([])
|
||||
const [status, setStatus] = React.useState('')
|
||||
const [q, setQ] = React.useState('')
|
||||
const [error, setError] = React.useState('')
|
||||
|
||||
React.useEffect(() => {
|
||||
listTickets({ status, q })
|
||||
.then(setTickets)
|
||||
.catch((reason: unknown) => setError(reason instanceof Error ? reason.message : '加载失败'))
|
||||
}, [status, q])
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-background p-6 text-foreground">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-4">
|
||||
<AppPageHeader
|
||||
title="Tickets"
|
||||
description="Search, filter, and review support tickets."
|
||||
actions={<Button onClick={() => (window.location.hash = '/tickets/new')}>Create Ticket</Button>}
|
||||
/>
|
||||
<div className="grid gap-2 md:grid-cols-[1fr_180px]">
|
||||
<Input onChange={(event) => setQ(event.target.value)} placeholder="Search ticket no or title" value={q} />
|
||||
<Select onChange={(event) => setStatus(event.target.value)} value={status}>
|
||||
<option value="">All statuses</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="in_progress">In progress</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="closed">Closed</option>
|
||||
</Select>
|
||||
</div>
|
||||
{error ? <AppEmptyState title="Unable to load tickets" description={error} /> : null}
|
||||
{!error && tickets.length === 0 ? <AppEmptyState title="No tickets" description="Create the first support ticket." /> : null}
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
{tickets.map((ticket) => (
|
||||
<a
|
||||
className="grid gap-3 border-b border-border px-4 py-3 text-sm last:border-b-0 hover:bg-muted md:grid-cols-[150px_1fr_120px_100px_140px]"
|
||||
href={`#/tickets/${ticket.id}`}
|
||||
key={ticket.id}
|
||||
>
|
||||
<span className="font-medium text-foreground">{ticket.ticket_no}</span>
|
||||
<span>{ticket.title}</span>
|
||||
<AppStatusBadge status={ticket.status} />
|
||||
<AppPriorityBadge priority={ticket.priority} />
|
||||
<span className="text-muted-foreground">{formatDateTime(ticket.created_at)}</span>
|
||||
</a>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user