fix(frontend): Align Epic field names with backend API
Fix frontend-backend API field mismatches for Epic entity by: 1. Changed Epic.title to Epic.name in type definitions 2. Added Epic.createdBy field (required by backend) 3. Updated all Epic references from epic.title to epic.name 4. Fixed Epic form to use name field and include createdBy Files modified: - types/project.ts: Updated Epic, CreateEpicDto, UpdateEpicDto interfaces - components/epics/epic-form.tsx: Fixed defaultValues to use epic.name - components/projects/hierarchy-tree.tsx: Replaced epic.title with epic.name - components/projects/story-form.tsx: Fixed epic dropdown to show epic.name - app/(dashboard)/projects/[id]/epics/page.tsx: Display epic.name in list - app/(dashboard)/projects/[id]/page.tsx: Display epic.name in preview - app/(dashboard)/api-test/page.tsx: Display epic.name in test page This resolves the 400 Bad Request error when creating Epics caused by missing 'Name' field (was sending 'title' instead) and missing 'CreatedBy' field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,7 @@ export default function ApiTestPage() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{epics.map((epic) => (
|
{epics.map((epic) => (
|
||||||
<Card key={epic.id} className="p-4">
|
<Card key={epic.id} className="p-4">
|
||||||
<h3 className="font-semibold">{epic.title}</h3>
|
<h3 className="font-semibold">{epic.name}</h3>
|
||||||
{epic.description && (
|
{epic.description && (
|
||||||
<p className="text-sm text-gray-600 mt-1">{epic.description}</p>
|
<p className="text-sm text-gray-600 mt-1">{epic.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export default function EpicsPage({ params }: EpicsPageProps) {
|
|||||||
className="block hover:underline"
|
className="block hover:underline"
|
||||||
>
|
>
|
||||||
<CardTitle className="line-clamp-2 text-lg">
|
<CardTitle className="line-clamp-2 text-lg">
|
||||||
{epic.title}
|
{epic.name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ export default function ProjectDetailPage({ params }: ProjectDetailPageProps) {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="space-y-1 flex-1">
|
<div className="space-y-1 flex-1">
|
||||||
<p className="text-sm font-medium line-clamp-1">{epic.title}</p>
|
<p className="text-sm font-medium line-clamp-1">{epic.name}</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{epic.status}
|
{epic.status}
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ import { useCreateEpic, useUpdateEpic } from '@/lib/hooks/use-epics';
|
|||||||
import type { Epic, WorkItemPriority } from '@/types/project';
|
import type { Epic, WorkItemPriority } from '@/types/project';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
import { useAuthStore } from '@/stores/authStore';
|
||||||
|
|
||||||
const epicSchema = z.object({
|
const epicSchema = z.object({
|
||||||
title: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, 'Title is required')
|
.min(1, 'Title is required')
|
||||||
.max(200, 'Title must be less than 200 characters'),
|
.max(200, 'Title must be less than 200 characters'),
|
||||||
@@ -57,11 +58,12 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
|
|||||||
const isEditing = !!epic;
|
const isEditing = !!epic;
|
||||||
const createEpic = useCreateEpic();
|
const createEpic = useCreateEpic();
|
||||||
const updateEpic = useUpdateEpic();
|
const updateEpic = useUpdateEpic();
|
||||||
|
const user = useAuthStore((state) => state.user);
|
||||||
|
|
||||||
const form = useForm<EpicFormValues>({
|
const form = useForm<EpicFormValues>({
|
||||||
resolver: zodResolver(epicSchema),
|
resolver: zodResolver(epicSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
title: epic?.title || '',
|
name: epic?.name || '', // Fixed: use 'name' instead of 'title'
|
||||||
description: epic?.description || '',
|
description: epic?.description || '',
|
||||||
priority: epic?.priority || 'Medium',
|
priority: epic?.priority || 'Medium',
|
||||||
estimatedHours: epic?.estimatedHours || ('' as any),
|
estimatedHours: epic?.estimatedHours || ('' as any),
|
||||||
@@ -70,6 +72,11 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
|
|||||||
|
|
||||||
async function onSubmit(data: EpicFormValues) {
|
async function onSubmit(data: EpicFormValues) {
|
||||||
try {
|
try {
|
||||||
|
if (!user?.id) {
|
||||||
|
toast.error('User not authenticated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...data,
|
...data,
|
||||||
estimatedHours: data.estimatedHours || undefined,
|
estimatedHours: data.estimatedHours || undefined,
|
||||||
@@ -83,6 +90,7 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
|
|||||||
} else {
|
} else {
|
||||||
await createEpic.mutateAsync({
|
await createEpic.mutateAsync({
|
||||||
projectId,
|
projectId,
|
||||||
|
createdBy: user.id,
|
||||||
...payload,
|
...payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,7 +115,7 @@ export function EpicForm({ projectId, epic, onSuccess, onCancel }: EpicFormProps
|
|||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="title"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Epic Title *</FormLabel>
|
<FormLabel>Epic Title *</FormLabel>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function EpicNode({ epic, onEpicClick, onStoryClick, onTaskClick }: EpicNodeProp
|
|||||||
onEpicClick?.(epic);
|
onEpicClick?.(epic);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{epic.title}
|
{epic.name}
|
||||||
</span>
|
</span>
|
||||||
<StatusBadge status={epic.status} />
|
<StatusBadge status={epic.status} />
|
||||||
<PriorityBadge priority={epic.priority} />
|
<PriorityBadge priority={epic.priority} />
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function StoryForm({
|
|||||||
) : (
|
) : (
|
||||||
epics.map((epic) => (
|
epics.map((epic) => (
|
||||||
<SelectItem key={epic.id} value={epic.id}>
|
<SelectItem key={epic.id} value={epic.id}>
|
||||||
{epic.title}
|
{epic.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export interface UpdateProjectDto {
|
|||||||
// ==================== Epic ====================
|
// ==================== Epic ====================
|
||||||
export interface Epic {
|
export interface Epic {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
name: string; // Changed from 'title' to match backend API
|
||||||
description?: string;
|
description?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
status: WorkItemStatus;
|
status: WorkItemStatus;
|
||||||
@@ -36,6 +36,7 @@ export interface Epic {
|
|||||||
estimatedHours?: number;
|
estimatedHours?: number;
|
||||||
actualHours?: number;
|
actualHours?: number;
|
||||||
assigneeId?: string;
|
assigneeId?: string;
|
||||||
|
createdBy: string; // Added to match backend API (required field)
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -43,14 +44,15 @@ export interface Epic {
|
|||||||
|
|
||||||
export interface CreateEpicDto {
|
export interface CreateEpicDto {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
title: string;
|
name: string; // Changed from 'title' to match backend API
|
||||||
description?: string;
|
description?: string;
|
||||||
priority: WorkItemPriority;
|
priority: WorkItemPriority;
|
||||||
estimatedHours?: number;
|
estimatedHours?: number;
|
||||||
|
createdBy: string; // Added to match backend API (required field)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateEpicDto {
|
export interface UpdateEpicDto {
|
||||||
title?: string;
|
name?: string; // Changed from 'title' to match backend API
|
||||||
description?: string;
|
description?: string;
|
||||||
priority?: WorkItemPriority;
|
priority?: WorkItemPriority;
|
||||||
estimatedHours?: number;
|
estimatedHours?: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user