Add sprint support to data model and API

- Added Sprint type and interface
- Updated Task to include sprintId
- Added sprint actions to task store (addSprint, updateSprint, deleteSprint, selectSprint)
- Updated API to handle sprints
- Updated all sync calls to include sprints
This commit is contained in:
OpenClaw Bot 2026-02-19 17:21:21 -06:00
parent 584cabaf07
commit 7710167eb2
6 changed files with 113 additions and 22 deletions

BIN
comps/01-statuses.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
comps/02-workflow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
comps/03-gannt-board.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
comps/04-backlog-screen.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

View File

@ -12,6 +12,7 @@ interface Task {
status: 'backlog' | 'in-progress' | 'review' | 'done' | 'archived';
priority: 'low' | 'medium' | 'high' | 'urgent';
projectId: string;
sprintId?: string;
createdAt: string;
updatedAt: string;
dueDate?: string;
@ -27,9 +28,21 @@ interface Project {
createdAt: string;
}
interface Sprint {
id: string;
name: string;
goal?: string;
startDate: string;
endDate: string;
status: 'planning' | 'active' | 'completed';
projectId: string;
createdAt: string;
}
interface DataStore {
projects: Project[];
tasks: Task[];
sprints: Sprint[];
lastUpdated: number;
}
@ -40,6 +53,7 @@ const defaultData: DataStore = {
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
],
tasks: [],
sprints: [],
lastUpdated: Date.now(),
};
@ -64,16 +78,16 @@ function saveData(data: DataStore) {
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
// GET - fetch all tasks and projects
// GET - fetch all tasks, projects, and sprints
export async function GET() {
const data = getData();
return NextResponse.json(data);
}
// POST - create or update a task
// POST - create or update tasks, projects, or sprints
export async function POST(request: Request) {
try {
const { task, projects } = await request.json();
const { task, projects, sprints } = await request.json();
const data = getData();
// Update projects if provided
@ -81,6 +95,11 @@ export async function POST(request: Request) {
data.projects = projects;
}
// Update sprints if provided
if (sprints) {
data.sprints = sprints;
}
// Update or add task
if (task) {
const existingIndex = data.tasks.findIndex((t) => t.id === task.id);

View File

@ -4,6 +4,18 @@ import { persist } from 'zustand/middleware'
export type TaskType = 'idea' | 'task' | 'bug' | 'research' | 'plan'
export type TaskStatus = 'backlog' | 'in-progress' | 'review' | 'done' | 'archived'
export type Priority = 'low' | 'medium' | 'high' | 'urgent'
export type SprintStatus = 'planning' | 'active' | 'completed'
export interface Sprint {
id: string
name: string
goal?: string
startDate: string
endDate: string
status: SprintStatus
projectId: string
createdAt: string
}
export interface Comment {
id: string
@ -20,6 +32,7 @@ export interface Task {
status: TaskStatus
priority: Priority
projectId: string
sprintId?: string
createdAt: string
updatedAt: string
dueDate?: string
@ -38,8 +51,10 @@ export interface Project {
interface TaskStore {
projects: Project[]
tasks: Task[]
sprints: Sprint[]
selectedProjectId: string | null
selectedTaskId: string | null
selectedSprintId: string | null
isLoading: boolean
lastSynced: number | null
@ -59,6 +74,13 @@ interface TaskStore {
deleteTask: (id: string) => void
selectTask: (id: string | null) => void
// Sprint actions
addSprint: (sprint: Omit<Sprint, 'id' | 'createdAt'>) => void
updateSprint: (id: string, updates: Partial<Sprint>) => void
deleteSprint: (id: string) => void
selectSprint: (id: string | null) => void
getTasksBySprint: (sprintId: string) => Task[]
// Comment actions
addComment: (taskId: string, text: string, author: 'user' | 'assistant') => void
deleteComment: (taskId: string, commentId: string) => void
@ -325,12 +347,12 @@ const defaultTasks: Task[] = [
]
// Helper to sync to server
async function syncToServer(projects: Project[], tasks: Task[]) {
async function syncToServer(projects: Project[], tasks: Task[], sprints: Sprint[]) {
try {
await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ projects, tasks }),
body: JSON.stringify({ projects, tasks, sprints }),
})
} catch (error) {
console.error('Failed to sync to server:', error)
@ -342,8 +364,10 @@ export const useTaskStore = create<TaskStore>()(
(set, get) => ({
projects: defaultProjects,
tasks: defaultTasks,
sprints: [],
selectedProjectId: '1',
selectedTaskId: null,
selectedSprintId: null,
isLoading: false,
lastSynced: null,
@ -353,10 +377,11 @@ export const useTaskStore = create<TaskStore>()(
const res = await fetch('/api/tasks')
if (res.ok) {
const data = await res.json()
if (data.tasks?.length > 0 || data.projects?.length > 0) {
if (data.tasks?.length > 0 || data.projects?.length > 0 || data.sprints?.length > 0) {
set({
projects: data.projects || get().projects,
tasks: data.tasks || get().tasks,
sprints: data.sprints || get().sprints,
lastSynced: Date.now(),
})
}
@ -370,8 +395,8 @@ export const useTaskStore = create<TaskStore>()(
},
syncToServer: async () => {
const { projects, tasks } = get()
await syncToServer(projects, tasks)
const { projects, tasks, sprints } = get()
await syncToServer(projects, tasks, sprints)
set({ lastSynced: Date.now() })
},
@ -387,7 +412,7 @@ export const useTaskStore = create<TaskStore>()(
set((state) => {
const newState = { projects: [...state.projects, newProject] }
// Sync to server
syncToServer(newState.projects, state.tasks)
syncToServer(newState.projects, state.tasks, state.sprints)
return newState
})
},
@ -395,7 +420,7 @@ export const useTaskStore = create<TaskStore>()(
updateProject: (id, updates) => {
set((state) => {
const newProjects = state.projects.map((p) => (p.id === id ? { ...p, ...updates } : p))
syncToServer(newProjects, state.tasks)
syncToServer(newProjects, state.tasks, state.sprints)
return { projects: newProjects }
})
},
@ -404,16 +429,18 @@ export const useTaskStore = create<TaskStore>()(
set((state) => {
const newProjects = state.projects.filter((p) => p.id !== id)
const newTasks = state.tasks.filter((t) => t.projectId !== id)
syncToServer(newProjects, newTasks)
const newSprints = state.sprints.filter((s) => s.projectId !== id)
syncToServer(newProjects, newTasks, newSprints)
return {
projects: newProjects,
tasks: newTasks,
sprints: newSprints,
selectedProjectId: state.selectedProjectId === id ? null : state.selectedProjectId,
}
})
},
selectProject: (id) => set({ selectedProjectId: id, selectedTaskId: null }),
selectProject: (id) => set({ selectedProjectId: id, selectedTaskId: null, selectedSprintId: null }),
addTask: (task) => {
const newTask: Task = {
@ -425,7 +452,7 @@ export const useTaskStore = create<TaskStore>()(
}
set((state) => {
const newTasks = [...state.tasks, newTask]
syncToServer(state.projects, newTasks)
syncToServer(state.projects, newTasks, state.sprints)
return { tasks: newTasks }
})
},
@ -435,7 +462,7 @@ export const useTaskStore = create<TaskStore>()(
const newTasks = state.tasks.map((t) =>
t.id === id ? { ...t, ...updates, updatedAt: new Date().toISOString() } : t
)
syncToServer(state.projects, newTasks)
syncToServer(state.projects, newTasks, state.sprints)
return { tasks: newTasks }
})
},
@ -443,7 +470,7 @@ export const useTaskStore = create<TaskStore>()(
deleteTask: (id) => {
set((state) => {
const newTasks = state.tasks.filter((t) => t.id !== id)
syncToServer(state.projects, newTasks)
syncToServer(state.projects, newTasks, state.sprints)
return {
tasks: newTasks,
selectedTaskId: state.selectedTaskId === id ? null : state.selectedTaskId,
@ -453,6 +480,51 @@ export const useTaskStore = create<TaskStore>()(
selectTask: (id) => set({ selectedTaskId: id }),
// Sprint actions
addSprint: (sprint) => {
const newSprint: Sprint = {
...sprint,
id: Date.now().toString(),
createdAt: new Date().toISOString(),
}
set((state) => {
const newSprints = [...state.sprints, newSprint]
syncToServer(state.projects, state.tasks, newSprints)
return { sprints: newSprints }
})
},
updateSprint: (id, updates) => {
set((state) => {
const newSprints = state.sprints.map((s) =>
s.id === id ? { ...s, ...updates } : s
)
syncToServer(state.projects, state.tasks, newSprints)
return { sprints: newSprints }
})
},
deleteSprint: (id) => {
set((state) => {
const newSprints = state.sprints.filter((s) => s.id !== id)
const newTasks = state.tasks.map((t) =>
t.sprintId === id ? { ...t, sprintId: undefined } : t
)
syncToServer(state.projects, newTasks, newSprints)
return {
sprints: newSprints,
tasks: newTasks,
selectedSprintId: state.selectedSprintId === id ? null : state.selectedSprintId,
}
})
},
selectSprint: (id) => set({ selectedSprintId: id }),
getTasksBySprint: (sprintId) => {
return get().tasks.filter((t) => t.sprintId === sprintId)
},
addComment: (taskId, text, author) => {
const newComment: Comment = {
id: Date.now().toString(),
@ -466,7 +538,7 @@ export const useTaskStore = create<TaskStore>()(
? { ...t, comments: [...t.comments, newComment], updatedAt: new Date().toISOString() }
: t
)
syncToServer(state.projects, newTasks)
syncToServer(state.projects, newTasks, state.sprints)
return { tasks: newTasks }
})
},
@ -478,7 +550,7 @@ export const useTaskStore = create<TaskStore>()(
? { ...t, comments: t.comments.filter((c) => c.id !== commentId) }
: t
)
syncToServer(state.projects, newTasks)
syncToServer(state.projects, newTasks, state.sprints)
return { tasks: newTasks }
})
},