feat: add ticket workflow pages
This commit is contained in:
88
frontend/src/features/tickets/TicketCreatePage.tsx
Normal file
88
frontend/src/features/tickets/TicketCreatePage.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { AppField } from '@/components/app/AppField'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
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 { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { createTicket } from './api'
|
||||
import type { TicketInput } from './types'
|
||||
|
||||
const initialForm: TicketInput = {
|
||||
title: '',
|
||||
description: '',
|
||||
priority: 'medium',
|
||||
impact_scope: '',
|
||||
environment: '',
|
||||
related_system: ''
|
||||
}
|
||||
|
||||
export function TicketCreatePage() {
|
||||
const [form, setForm] = React.useState<TicketInput>(initialForm)
|
||||
const [error, setError] = React.useState('')
|
||||
|
||||
function updateField<Key extends keyof TicketInput>(field: Key, value: TicketInput[Key]) {
|
||||
setForm((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
|
||||
async function handleSubmit(event: React.FormEvent) {
|
||||
event.preventDefault()
|
||||
setError('')
|
||||
try {
|
||||
const ticket = await createTicket(form)
|
||||
window.location.hash = `/tickets/${ticket.id}`
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : '创建失败')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-background p-6 text-foreground">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<AppPageHeader title="Create Ticket" description="Record a confirmed support request." />
|
||||
<Card className="mt-4">
|
||||
<CardContent>
|
||||
<form className="grid gap-4" onSubmit={handleSubmit}>
|
||||
<AppField label="Title">
|
||||
<Input required value={form.title} onChange={(event) => updateField('title', event.target.value)} />
|
||||
</AppField>
|
||||
<AppField label="Description">
|
||||
<Textarea
|
||||
required
|
||||
value={form.description}
|
||||
onChange={(event) => updateField('description', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AppField label="Priority">
|
||||
<Select value={form.priority} onChange={(event) => updateField('priority', event.target.value as TicketInput['priority'])}>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
</Select>
|
||||
</AppField>
|
||||
<AppField label="Related System">
|
||||
<Input value={form.related_system} onChange={(event) => updateField('related_system', event.target.value)} />
|
||||
</AppField>
|
||||
<AppField label="Impact Scope">
|
||||
<Input value={form.impact_scope} onChange={(event) => updateField('impact_scope', event.target.value)} />
|
||||
</AppField>
|
||||
<AppField label="Environment">
|
||||
<Input value={form.environment} onChange={(event) => updateField('environment', event.target.value)} />
|
||||
</AppField>
|
||||
</div>
|
||||
{error ? <p className="text-sm text-danger">{error}</p> : null}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">Create</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user