diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx index f53b7ed..7ad275c 100644 --- a/app/(dashboard)/dashboard/page.tsx +++ b/app/(dashboard)/dashboard/page.tsx @@ -17,8 +17,8 @@ export default function DashboardPage() { // Calculate statistics const stats = { totalProjects: projects?.length || 0, - activeProjects: projects?.filter(p => p.status === 'Active').length || 0, - archivedProjects: projects?.filter(p => p.status === 'Archived').length || 0, + activeProjects: projects?.length || 0, // TODO: Add status field to Project model + archivedProjects: 0, // TODO: Add status field to Project model }; // Get recent projects (sort by creation time, take first 5) @@ -142,12 +142,10 @@ export default function DashboardPage() {

{project.name}

- - {project.status} - + {project.key}

- {project.key} • {project.description || 'No description'} + {project.description || 'No description'}

diff --git a/components/features/projects/CreateProjectDialog.tsx b/components/features/projects/CreateProjectDialog.tsx index d7256ee..d7efd3c 100644 --- a/components/features/projects/CreateProjectDialog.tsx +++ b/components/features/projects/CreateProjectDialog.tsx @@ -27,7 +27,7 @@ import type { CreateProjectDto } from '@/types/project'; const projectSchema = z.object({ name: z.string().min(1, 'Project name is required').max(200, 'Project name cannot exceed 200 characters'), - description: z.string().max(2000, 'Description cannot exceed 2000 characters'), + description: z.string().max(2000, 'Description cannot exceed 2000 characters').optional(), key: z .string() .min(2, 'Project key must be at least 2 characters') diff --git a/components/features/projects/EditProjectDialog.tsx b/components/features/projects/EditProjectDialog.tsx index c764c61..c589233 100644 --- a/components/features/projects/EditProjectDialog.tsx +++ b/components/features/projects/EditProjectDialog.tsx @@ -31,9 +31,15 @@ const updateProjectSchema = z.object({ .string() .min(1, 'Project name is required') .max(200, 'Project name cannot exceed 200 characters'), + key: z + .string() + .min(2, 'Project key must be at least 2 characters') + .max(10, 'Project key cannot exceed 10 characters') + .regex(/^[A-Z]+$/, 'Project key must contain only uppercase letters'), description: z .string() - .max(2000, 'Description cannot exceed 2000 characters'), + .max(2000, 'Description cannot exceed 2000 characters') + .optional(), }); type UpdateProjectFormData = z.infer; @@ -55,6 +61,7 @@ export function EditProjectDialog({ resolver: zodResolver(updateProjectSchema), defaultValues: { name: project.name, + key: project.key, description: project.description, }, }); @@ -99,6 +106,29 @@ export function EditProjectDialog({ )} /> + ( + + Project Key + + { + field.onChange(e.target.value.toUpperCase()); + }} + /> + + + A unique identifier for the project (2-10 uppercase letters). + + + + )} + /> + ; + +interface EpicFormProps { + epic?: Epic; + projectId?: string; + onSuccess?: () => void; + onCancel?: () => void; +} + +export function EpicForm({ epic, projectId, onSuccess, onCancel }: EpicFormProps) { + const isEditing = !!epic; + const createEpic = useCreateEpic(); + const updateEpic = useUpdateEpic(); + + const form = useForm({ + resolver: zodResolver(epicSchema), + defaultValues: { + projectId: epic?.projectId || projectId || '', + title: epic?.title || '', + description: epic?.description || '', + priority: epic?.priority || 'Medium', + estimatedHours: epic?.estimatedHours || ('' as any), + }, + }); + + async function onSubmit(data: EpicFormValues) { + try { + if (isEditing && epic) { + await updateEpic.mutateAsync({ + id: epic.id, + data: { + title: data.title, + description: data.description, + priority: data.priority, + estimatedHours: + typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined, + }, + }); + toast.success('Epic updated successfully'); + } else { + await createEpic.mutateAsync({ + projectId: data.projectId, + title: data.title, + description: data.description, + priority: data.priority, + estimatedHours: + typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined, + }); + toast.success('Epic created successfully'); + } + onSuccess?.(); + } catch (error) { + const message = error instanceof Error ? error.message : 'Operation failed'; + toast.error(message); + } + } + + const isLoading = createEpic.isPending || updateEpic.isPending; + + return ( +
+ + ( + + Epic Title * + + + + A clear, concise title for this epic + + + )} + /> + + ( + + Description + +