From 08909aa8841a9e766f1af0101fc12a4998699005 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Thu, 16 Apr 2026 03:31:21 +0000 Subject: [PATCH] feat(tickets): add AiTicketParseForm and NewTicketModal with two-tab creation flow Co-Authored-By: Claude Sonnet 4.6 --- .../components/tickets/AiTicketParseForm.tsx | 62 +++++ .../src/components/tickets/NewTicketModal.tsx | 259 +++++++++++++++++- 2 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/tickets/AiTicketParseForm.tsx diff --git a/frontend/src/components/tickets/AiTicketParseForm.tsx b/frontend/src/components/tickets/AiTicketParseForm.tsx new file mode 100644 index 00000000..df4b1324 --- /dev/null +++ b/frontend/src/components/tickets/AiTicketParseForm.tsx @@ -0,0 +1,62 @@ +import { useState } from 'react' +import { Sparkles, Loader2 } from 'lucide-react' +import { ticketsApi } from '@/api/tickets' +import type { AiParseResponse, TicketCreationPayload } from '@/types/tickets' + +interface Props { + initialHint?: string + onParsed: (values: Partial, parseResponse: AiParseResponse) => void +} + +export function AiTicketParseForm({ initialHint = '', onParsed }: Props) { + const [prompt, setPrompt] = useState(initialHint) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + async function handleParse() { + if (!prompt.trim()) return + setLoading(true) + setError(null) + try { + const result = await ticketsApi.aiParse(prompt) + const values: Partial = { + summary: result.summary ?? undefined, + company_id: result.company_id, + board_id: result.board_id, + status_id: result.status_id, + priority_id: result.priority_id, + assigned_member_id: result.assigned_member_id, + description: result.description ?? undefined, + } + onParsed(values, result) + } catch { + setError('AI parsing failed. Please try again or use the full form.') + } finally { + setLoading(false) + } + } + + return ( +
+

+ Describe the ticket in plain language — who, what, which client, and priority. +

+