'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useCreateStory, useUpdateStory } from '@/lib/hooks/use-stories'; import { useEpics } from '@/lib/hooks/use-epics'; import type { Story, WorkItemPriority } from '@/types/project'; import { toast } from 'sonner'; import { Loader2 } from 'lucide-react'; import { useAuthStore } from '@/stores/authStore'; const storySchema = z.object({ epicId: z.string().min(1, 'Parent Epic is required'), title: z .string() .min(1, 'Title is required') .max(200, 'Title must be less than 200 characters'), description: z .string() .max(2000, 'Description must be less than 2000 characters') .optional(), priority: z.enum(['Low', 'Medium', 'High', 'Critical']), estimatedHours: z .number() .min(0, 'Estimated hours must be positive') .optional() .or(z.literal('')), }); type StoryFormValues = z.infer; interface StoryFormProps { story?: Story; epicId?: string; projectId?: string; onSuccess?: () => void; onCancel?: () => void; } export function StoryForm({ story, epicId, projectId, onSuccess, onCancel, }: StoryFormProps) { const isEditing = !!story; const user = useAuthStore((state) => state.user); const createStory = useCreateStory(); const updateStory = useUpdateStory(); // Fetch epics for parent epic selection const { data: epics = [], isLoading: epicsLoading } = useEpics(projectId); const form = useForm({ resolver: zodResolver(storySchema), defaultValues: { epicId: story?.epicId || epicId || '', title: story?.title || '', description: story?.description || '', priority: story?.priority || 'Medium', estimatedHours: story?.estimatedHours || ('' as any), }, }); async function onSubmit(data: StoryFormValues) { try { if (isEditing && story) { await updateStory.mutateAsync({ id: story.id, data: { title: data.title, description: data.description, priority: data.priority, estimatedHours: typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined, }, }); toast.success('Story updated successfully'); } else { if (!user?.id) { toast.error('User not authenticated'); return; } if (!projectId) { toast.error('Project ID is required'); return; } await createStory.mutateAsync({ epicId: data.epicId, projectId, title: data.title, description: data.description, priority: data.priority, estimatedHours: typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined, createdBy: user.id, }); toast.success('Story created successfully'); } onSuccess?.(); } catch (error) { const message = error instanceof Error ? error.message : 'Operation failed'; toast.error(message); } } const isLoading = createStory.isPending || updateStory.isPending; return (
( Parent Epic * {isEditing ? 'Parent epic cannot be changed' : 'Select the parent epic'} )} /> ( Story Title * A clear, concise title for this story )} /> ( Description