From 6facc4637b1d795d3e0cbfd448fece791712044a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 15:06:27 +0000 Subject: [PATCH 1/3] Initial plan From e895b8bd4bb0cf35f9864564e94ad1aa9c30645d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 15:14:05 +0000 Subject: [PATCH 2/3] Add task structure to milestones with UI components Co-authored-by: 0xdevcollins <90073781+0xdevcollins@users.noreply.github.com> --- .../[id]/milestone/[milestoneId]/page.tsx | 8 +- .../project-milestone/index.tsx | 17 ++ .../project-milestone/milestone-card.tsx | 23 ++ .../milestone-details/MilestoneDetails.tsx | 19 +- .../milestone-details/TaskList.tsx | 210 ++++++++++++++++++ components/ui/timeline/TimelineItem.tsx | 1 + components/ui/timeline/types.ts | 4 + types/milestone.ts | 19 +- types/project.ts | 12 + 9 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 components/project-details/project-milestone/milestone-details/TaskList.tsx diff --git a/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx b/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx index 01590836..fc87fae4 100644 --- a/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx +++ b/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx @@ -31,14 +31,12 @@ const MilestonePage = async ({ params }: MilestonePageProps) => { // Transform milestone to match component expectations milestone = foundMilestone - ? { + ? ({ + ...foundMilestone, _id: foundMilestone.id || foundMilestone.name, title: foundMilestone.name, - description: foundMilestone.description, - status: foundMilestone.status, dueDate: foundMilestone.endDate, - amount: foundMilestone.amount, - } + } as any) : null; } catch { // Handle error silently diff --git a/components/project-details/project-milestone/index.tsx b/components/project-details/project-milestone/index.tsx index e9ca5189..6631cae9 100644 --- a/components/project-details/project-milestone/index.tsx +++ b/components/project-details/project-milestone/index.tsx @@ -91,6 +91,22 @@ const ProjectMilestone = ({ crowdfund }: ProjectMilestoneProps) => { const mappedStatus = mapStatus(milestone.status); + // Calculate task progress + const taskProgress = + milestone.tasks && milestone.tasks.length > 0 + ? { + completed: milestone.tasks.filter( + task => task.status === 'completed' + ).length, + total: milestone.tasks.length, + } + : milestone.deliverables && milestone.deliverables.length > 0 + ? { + completed: 0, // Legacy deliverables don't have status + total: milestone.deliverables.length, + } + : undefined; + return { id: milestone.name, title: milestone.name, @@ -99,6 +115,7 @@ const ProjectMilestone = ({ crowdfund }: ProjectMilestoneProps) => { amount: milestone.amount, percentage, status: mappedStatus, + taskProgress, ...(milestone.status === 'in-review' && { feedbackDays: 3 }), ...(milestone.status === 'rejected' && { deadline: dueDate }), ...(milestone.status === 'approved' && { isUnlocked: true }), diff --git a/components/project-details/project-milestone/milestone-card.tsx b/components/project-details/project-milestone/milestone-card.tsx index 6cbf2c07..00d92ec0 100644 --- a/components/project-details/project-milestone/milestone-card.tsx +++ b/components/project-details/project-milestone/milestone-card.tsx @@ -24,6 +24,10 @@ interface MilestoneCardProps { isUnlocked?: boolean; onClick?: () => void; isClickable?: boolean; + taskProgress?: { + completed: number; + total: number; + }; } //status badge colors @@ -60,6 +64,7 @@ const MilestoneCard = ({ feedbackDays, onClick, isClickable = false, + taskProgress, }: MilestoneCardProps) => { // Generate dynamic header text based on status const getHeaderText = () => { @@ -126,6 +131,24 @@ const MilestoneCard = ({

{description}

+ + {/* Task Progress */} + {taskProgress && taskProgress.total > 0 && ( +
+
+
+
+ + {taskProgress.completed}/{taskProgress.total} tasks + +
+ )} +
+ {/* Tasks & Deliverables */} + {milestone && (milestone.tasks || milestone.deliverables) && ( +
+ +
+ )} + {/* Video Media Showcase */} {project?.demoVideo && (
diff --git a/components/project-details/project-milestone/milestone-details/TaskList.tsx b/components/project-details/project-milestone/milestone-details/TaskList.tsx new file mode 100644 index 00000000..b58ee455 --- /dev/null +++ b/components/project-details/project-milestone/milestone-details/TaskList.tsx @@ -0,0 +1,210 @@ +'use client'; + +import React from 'react'; +import { Task } from '@/types/milestone'; +import { CheckCircle2, Circle, Clock } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Badge } from '@/components/ui/badge'; + +interface TaskListProps { + tasks?: Task[]; + deliverables?: string[]; // Legacy support +} + +const TaskList = ({ tasks, deliverables }: TaskListProps) => { + // If no tasks provided but deliverables exist, convert them to simple task objects + const displayTasks: Task[] = React.useMemo(() => { + if (tasks && tasks.length > 0) { + return tasks.sort((a, b) => a.order - b.order); + } + + // Convert legacy deliverables to task format for display + if (deliverables && deliverables.length > 0) { + return deliverables.map((deliverable, index) => ({ + id: `deliverable-${index}`, + title: deliverable, + status: 'todo' as const, + order: index, + })); + } + + return []; + }, [tasks, deliverables]); + + if (displayTasks.length === 0) { + return null; + } + + const completedCount = displayTasks.filter( + task => task.status === 'completed' + ).length; + const totalCount = displayTasks.length; + const progressPercentage = Math.round((completedCount / totalCount) * 100); + + const getStatusIcon = (status: Task['status']) => { + switch (status) { + case 'completed': + return ; + case 'in_progress': + return ; + case 'todo': + default: + return ; + } + }; + + const getStatusBadge = (status: Task['status']) => { + switch (status) { + case 'completed': + return ( + + Completed + + ); + case 'in_progress': + return ( + + In Progress + + ); + case 'todo': + default: + return ( + + To Do + + ); + } + }; + + const getPriorityColor = (priority?: Task['priority']) => { + switch (priority) { + case 'high': + return 'text-red-400'; + case 'medium': + return 'text-yellow-400'; + case 'low': + return 'text-blue-400'; + default: + return 'text-gray-400'; + } + }; + + return ( +
+
+

+ Tasks & Deliverables +

+
+ + {completedCount} / {totalCount} completed + + + {progressPercentage}% + +
+
+ + {/* Progress Bar */} +
+
+
+ + {/* Task List */} +
+ {displayTasks.map(task => ( +
+
{getStatusIcon(task.status)}
+
+
+
+

+ {task.title} +

+ {task.description && ( +

+ {task.description} +

+ )} +
+
+ {task.priority && ( + + {task.priority} + + )} + {getStatusBadge(task.status)} +
+
+
+ {task.assignee && ( +
+ Assigned to: + {task.assignee} +
+ )} + {task.dueDate && ( +
+ Due: + + {new Date(task.dueDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + +
+ )} + {task.completedAt && ( +
+ Completed: + + {new Date(task.completedAt).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + })} + +
+ )} +
+
+
+ ))} +
+
+ ); +}; + +export default TaskList; diff --git a/components/ui/timeline/TimelineItem.tsx b/components/ui/timeline/TimelineItem.tsx index e4f5ffc9..2bd1273a 100644 --- a/components/ui/timeline/TimelineItem.tsx +++ b/components/ui/timeline/TimelineItem.tsx @@ -66,6 +66,7 @@ const TimelineItem: React.FC = ({ isUnlocked={item.isUnlocked} onClick={handleMilestoneClick} isClickable={isClickable} + taskProgress={item.taskProgress} />
diff --git a/components/ui/timeline/types.ts b/components/ui/timeline/types.ts index 39d2a6e4..023ee9ad 100644 --- a/components/ui/timeline/types.ts +++ b/components/ui/timeline/types.ts @@ -20,6 +20,10 @@ export interface TimelineItem { deadline?: string; feedbackDays?: number; isUnlocked?: boolean; + taskProgress?: { + completed: number; + total: number; + }; } export interface TimelineProps { diff --git a/types/milestone.ts b/types/milestone.ts index cf3eec5a..2d8a2fee 100644 --- a/types/milestone.ts +++ b/types/milestone.ts @@ -1,3 +1,15 @@ +export interface Task { + id: string; + title: string; + description?: string; + status: 'todo' | 'in_progress' | 'completed'; + priority?: 'low' | 'medium' | 'high'; + assignee?: string; + dueDate?: string; + completedAt?: string; + order: number; +} + export interface Milestone { id: string; title: string; @@ -7,7 +19,8 @@ export interface Milestone { endDate: string; budget: number; currency: string; - deliverables: string[]; + deliverables: string[]; // Legacy field, kept for backward compatibility + tasks?: Task[]; // New structured task list demoVideo?: string; attachments?: Array<{ name: string; @@ -29,3 +42,7 @@ export type MilestoneStatus = | 'in_progress' | 'completed' | 'cancelled'; + +export type TaskStatus = 'todo' | 'in_progress' | 'completed'; + +export type TaskPriority = 'low' | 'medium' | 'high'; diff --git a/types/project.ts b/types/project.ts index aed298fb..d79eada0 100644 --- a/types/project.ts +++ b/types/project.ts @@ -35,6 +35,18 @@ export interface Milestone { endDate: string; startDate: string; description: string; + deliverables?: string[]; // Legacy field for backward compatibility + tasks?: Array<{ + id: string; + title: string; + description?: string; + status: 'todo' | 'in_progress' | 'completed'; + priority?: 'low' | 'medium' | 'high'; + assignee?: string; + dueDate?: string; + completedAt?: string; + order: number; + }>; } export interface User { From 0a36c8eb24b87ab6a558bba8bbbe42841da42312 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 15:17:40 +0000 Subject: [PATCH 3/3] Address code review feedback: remove type duplication and fix type assertions Co-authored-by: 0xdevcollins <90073781+0xdevcollins@users.noreply.github.com> --- .../[id]/milestone/[milestoneId]/page.tsx | 23 +-- .../milestone-details/MilestoneLinks.tsx | 11 +- .../milestone-details/MilstoneOverview.tsx | 11 +- docs/TASK_STRUCTURE.md | 153 ++++++++++++++++++ types/milestone.ts | 2 +- types/project.ts | 14 +- 6 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 docs/TASK_STRUCTURE.md diff --git a/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx b/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx index fc87fae4..aa4d9151 100644 --- a/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx +++ b/app/(landing)/projects/[id]/milestone/[milestoneId]/page.tsx @@ -4,7 +4,7 @@ import MilestoneDetails from '@/components/project-details/project-milestone/mil import MilestoneLinks from '@/components/project-details/project-milestone/milestone-details/MilestoneLinks'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { getCrowdfundingProject } from '@/lib/api/project'; -import { Crowdfunding } from '@/types/project'; +import { Crowdfunding, Milestone } from '@/types/project'; interface MilestonePageProps { params: Promise<{ @@ -13,6 +13,18 @@ interface MilestonePageProps { }>; } +// Helper function to transform milestone data for the component +function transformMilestoneForDisplay( + foundMilestone: Milestone +): Milestone & { _id: string; title?: string; dueDate?: string } { + return { + ...foundMilestone, + _id: foundMilestone.id || foundMilestone.name, + title: foundMilestone.name, + dueDate: foundMilestone.endDate, + }; +} + const MilestonePage = async ({ params }: MilestonePageProps) => { const { id: projectId, milestoneId } = await params; @@ -30,14 +42,7 @@ const MilestonePage = async ({ params }: MilestonePageProps) => { ); // Transform milestone to match component expectations - milestone = foundMilestone - ? ({ - ...foundMilestone, - _id: foundMilestone.id || foundMilestone.name, - title: foundMilestone.name, - dueDate: foundMilestone.endDate, - } as any) - : null; + milestone = foundMilestone ? transformMilestoneForDisplay(foundMilestone) : null; } catch { // Handle error silently } diff --git a/components/project-details/project-milestone/milestone-details/MilestoneLinks.tsx b/components/project-details/project-milestone/milestone-details/MilestoneLinks.tsx index 3410f860..7437e812 100644 --- a/components/project-details/project-milestone/milestone-details/MilestoneLinks.tsx +++ b/components/project-details/project-milestone/milestone-details/MilestoneLinks.tsx @@ -2,17 +2,14 @@ import React from 'react'; import Link from 'next/link'; import { Github } from 'lucide-react'; import Image from 'next/image'; -import { CrowdfundingProject } from '@/types/project'; +import { CrowdfundingProject, Milestone } from '@/types/project'; interface MilestoneLinksProps { project?: CrowdfundingProject | null; - milestone?: { + milestone?: Milestone & { _id: string; - title: string; - description: string; - status: string; - dueDate: string; - amount: number; + title?: string; + dueDate?: string; }; } diff --git a/components/project-details/project-milestone/milestone-details/MilstoneOverview.tsx b/components/project-details/project-milestone/milestone-details/MilstoneOverview.tsx index b792f4a5..01c33bd2 100644 --- a/components/project-details/project-milestone/milestone-details/MilstoneOverview.tsx +++ b/components/project-details/project-milestone/milestone-details/MilstoneOverview.tsx @@ -4,17 +4,14 @@ import { Separator } from '@/components/ui/separator'; import Link from 'next/link'; import { Github } from 'lucide-react'; import Image from 'next/image'; -import { CrowdfundingProject } from '@/types/project'; +import { CrowdfundingProject, Milestone } from '@/types/project'; interface MilstoneOverviewProps { project?: CrowdfundingProject | null; - milestone?: { + milestone?: Milestone & { _id: string; - title: string; - description: string; - status: string; - dueDate: string; - amount: number; + title?: string; + dueDate?: string; }; } diff --git a/docs/TASK_STRUCTURE.md b/docs/TASK_STRUCTURE.md new file mode 100644 index 00000000..518262cc --- /dev/null +++ b/docs/TASK_STRUCTURE.md @@ -0,0 +1,153 @@ +# Task Structure Improvement + +This document describes the improvements made to the milestone task structure in the Boundless platform. + +## Overview + +Previously, milestones only had a simple `deliverables: string[]` field which was too simplistic for proper project management. This update adds a comprehensive task tracking system to milestones. + +## New Task Type + +```typescript +export interface Task { + id: string; + title: string; + description?: string; + status: 'todo' | 'in_progress' | 'completed'; + priority?: 'low' | 'medium' | 'high'; + assignee?: string; + dueDate?: string; + completedAt?: string; + order: number; +} +``` + +## Updated Milestone Interface + +The Milestone interface now includes: +- `tasks?: Task[]` - New structured task list with full metadata +- `deliverables: string[]` - Legacy field kept for backward compatibility + +## UI Components + +### TaskList Component + +A new `TaskList` component displays tasks with: +- Progress bar showing completion percentage +- Task status indicators (todo, in progress, completed) +- Priority badges (low, medium, high) +- Assignee information +- Due dates and completion dates + +### Milestone Card Enhancements + +Milestone cards now show: +- Task progress bar when tasks are present +- "X/Y tasks" completion count +- Visual progress indicator + +## Example Usage + +### Backend API Response + +```json +{ + "milestones": [ + { + "id": "milestone-1", + "name": "Initial Development", + "description": "Set up the basic infrastructure", + "status": "in-progress", + "amount": 10000, + "startDate": "2024-01-01", + "endDate": "2024-02-01", + "tasks": [ + { + "id": "task-1", + "title": "Set up repository", + "description": "Create GitHub repo and initial structure", + "status": "completed", + "priority": "high", + "assignee": "john@example.com", + "dueDate": "2024-01-05", + "completedAt": "2024-01-04", + "order": 1 + }, + { + "id": "task-2", + "title": "Configure CI/CD pipeline", + "status": "in_progress", + "priority": "high", + "assignee": "jane@example.com", + "dueDate": "2024-01-10", + "order": 2 + }, + { + "id": "task-3", + "title": "Write documentation", + "status": "todo", + "priority": "medium", + "dueDate": "2024-01-15", + "order": 3 + } + ] + } + ] +} +``` + +## Backward Compatibility + +The system maintains backward compatibility with existing milestones that use the `deliverables` field: + +- If a milestone has `tasks`, they will be displayed with full functionality +- If a milestone only has `deliverables`, they will be displayed as simple task items +- Both fields can coexist during the migration period + +## Benefits + +1. **Better Project Tracking**: Track individual tasks with status, priority, and assignment +2. **Visual Progress**: See at-a-glance task completion in milestone cards +3. **Accountability**: Assign tasks to specific team members +4. **Prioritization**: Mark tasks with priority levels for better planning +5. **Timeline Management**: Set and track due dates for individual tasks +6. **Historical Data**: Track when tasks were completed + +## Migration Path + +For existing milestones with deliverables: + +```typescript +// Old format +{ + deliverables: [ + "Complete user authentication", + "Build dashboard UI", + "Deploy to production" + ] +} + +// Can be migrated to new format +{ + tasks: [ + { + id: "1", + title: "Complete user authentication", + status: "completed", + order: 1 + }, + { + id: "2", + title: "Build dashboard UI", + status: "in_progress", + order: 2 + }, + { + id: "3", + title: "Deploy to production", + status: "todo", + order: 3 + } + ] +} +``` diff --git a/types/milestone.ts b/types/milestone.ts index 2d8a2fee..8ccf3694 100644 --- a/types/milestone.ts +++ b/types/milestone.ts @@ -19,7 +19,7 @@ export interface Milestone { endDate: string; budget: number; currency: string; - deliverables: string[]; // Legacy field, kept for backward compatibility + deliverables?: string[]; // Legacy field, kept for backward compatibility tasks?: Task[]; // New structured task list demoVideo?: string; attachments?: Array<{ diff --git a/types/project.ts b/types/project.ts index d79eada0..71e24cf7 100644 --- a/types/project.ts +++ b/types/project.ts @@ -1,4 +1,6 @@ // Crowdfunding types +import { Task } from './milestone'; + export interface Contributor { date: string; amount: number; @@ -36,17 +38,7 @@ export interface Milestone { startDate: string; description: string; deliverables?: string[]; // Legacy field for backward compatibility - tasks?: Array<{ - id: string; - title: string; - description?: string; - status: 'todo' | 'in_progress' | 'completed'; - priority?: 'low' | 'medium' | 'high'; - assignee?: string; - dueDate?: string; - completedAt?: string; - order: number; - }>; + tasks?: Task[]; } export interface User {