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:
parent
584cabaf07
commit
7710167eb2
BIN
comps/01-statuses.png
Executable file
BIN
comps/01-statuses.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
comps/02-workflow.png
Executable file
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
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
BIN
comps/04-backlog-screen.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
@ -12,6 +12,7 @@ interface Task {
|
|||||||
status: 'backlog' | 'in-progress' | 'review' | 'done' | 'archived';
|
status: 'backlog' | 'in-progress' | 'review' | 'done' | 'archived';
|
||||||
priority: 'low' | 'medium' | 'high' | 'urgent';
|
priority: 'low' | 'medium' | 'high' | 'urgent';
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
sprintId?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
dueDate?: string;
|
dueDate?: string;
|
||||||
@ -27,9 +28,21 @@ interface Project {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Sprint {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
goal?: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
|
status: 'planning' | 'active' | 'completed';
|
||||||
|
projectId: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface DataStore {
|
interface DataStore {
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
|
sprints: Sprint[];
|
||||||
lastUpdated: number;
|
lastUpdated: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +53,7 @@ const defaultData: DataStore = {
|
|||||||
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
|
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
|
||||||
],
|
],
|
||||||
tasks: [],
|
tasks: [],
|
||||||
|
sprints: [],
|
||||||
lastUpdated: Date.now(),
|
lastUpdated: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,16 +78,16 @@ function saveData(data: DataStore) {
|
|||||||
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
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() {
|
export async function GET() {
|
||||||
const data = getData();
|
const data = getData();
|
||||||
return NextResponse.json(data);
|
return NextResponse.json(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST - create or update a task
|
// POST - create or update tasks, projects, or sprints
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const { task, projects } = await request.json();
|
const { task, projects, sprints } = await request.json();
|
||||||
const data = getData();
|
const data = getData();
|
||||||
|
|
||||||
// Update projects if provided
|
// Update projects if provided
|
||||||
@ -81,6 +95,11 @@ export async function POST(request: Request) {
|
|||||||
data.projects = projects;
|
data.projects = projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update sprints if provided
|
||||||
|
if (sprints) {
|
||||||
|
data.sprints = sprints;
|
||||||
|
}
|
||||||
|
|
||||||
// Update or add task
|
// Update or add task
|
||||||
if (task) {
|
if (task) {
|
||||||
const existingIndex = data.tasks.findIndex((t) => t.id === task.id);
|
const existingIndex = data.tasks.findIndex((t) => t.id === task.id);
|
||||||
|
|||||||
@ -4,6 +4,18 @@ import { persist } from 'zustand/middleware'
|
|||||||
export type TaskType = 'idea' | 'task' | 'bug' | 'research' | 'plan'
|
export type TaskType = 'idea' | 'task' | 'bug' | 'research' | 'plan'
|
||||||
export type TaskStatus = 'backlog' | 'in-progress' | 'review' | 'done' | 'archived'
|
export type TaskStatus = 'backlog' | 'in-progress' | 'review' | 'done' | 'archived'
|
||||||
export type Priority = 'low' | 'medium' | 'high' | 'urgent'
|
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 {
|
export interface Comment {
|
||||||
id: string
|
id: string
|
||||||
@ -20,6 +32,7 @@ export interface Task {
|
|||||||
status: TaskStatus
|
status: TaskStatus
|
||||||
priority: Priority
|
priority: Priority
|
||||||
projectId: string
|
projectId: string
|
||||||
|
sprintId?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
dueDate?: string
|
dueDate?: string
|
||||||
@ -38,8 +51,10 @@ export interface Project {
|
|||||||
interface TaskStore {
|
interface TaskStore {
|
||||||
projects: Project[]
|
projects: Project[]
|
||||||
tasks: Task[]
|
tasks: Task[]
|
||||||
|
sprints: Sprint[]
|
||||||
selectedProjectId: string | null
|
selectedProjectId: string | null
|
||||||
selectedTaskId: string | null
|
selectedTaskId: string | null
|
||||||
|
selectedSprintId: string | null
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
lastSynced: number | null
|
lastSynced: number | null
|
||||||
|
|
||||||
@ -59,6 +74,13 @@ interface TaskStore {
|
|||||||
deleteTask: (id: string) => void
|
deleteTask: (id: string) => void
|
||||||
selectTask: (id: string | null) => 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
|
// Comment actions
|
||||||
addComment: (taskId: string, text: string, author: 'user' | 'assistant') => void
|
addComment: (taskId: string, text: string, author: 'user' | 'assistant') => void
|
||||||
deleteComment: (taskId: string, commentId: string) => void
|
deleteComment: (taskId: string, commentId: string) => void
|
||||||
@ -325,12 +347,12 @@ const defaultTasks: Task[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// Helper to sync to server
|
// Helper to sync to server
|
||||||
async function syncToServer(projects: Project[], tasks: Task[]) {
|
async function syncToServer(projects: Project[], tasks: Task[], sprints: Sprint[]) {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/tasks', {
|
await fetch('/api/tasks', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ projects, tasks }),
|
body: JSON.stringify({ projects, tasks, sprints }),
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to sync to server:', error)
|
console.error('Failed to sync to server:', error)
|
||||||
@ -342,8 +364,10 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
projects: defaultProjects,
|
projects: defaultProjects,
|
||||||
tasks: defaultTasks,
|
tasks: defaultTasks,
|
||||||
|
sprints: [],
|
||||||
selectedProjectId: '1',
|
selectedProjectId: '1',
|
||||||
selectedTaskId: null,
|
selectedTaskId: null,
|
||||||
|
selectedSprintId: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastSynced: null,
|
lastSynced: null,
|
||||||
|
|
||||||
@ -353,10 +377,11 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
const res = await fetch('/api/tasks')
|
const res = await fetch('/api/tasks')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
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({
|
set({
|
||||||
projects: data.projects || get().projects,
|
projects: data.projects || get().projects,
|
||||||
tasks: data.tasks || get().tasks,
|
tasks: data.tasks || get().tasks,
|
||||||
|
sprints: data.sprints || get().sprints,
|
||||||
lastSynced: Date.now(),
|
lastSynced: Date.now(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -370,8 +395,8 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
syncToServer: async () => {
|
syncToServer: async () => {
|
||||||
const { projects, tasks } = get()
|
const { projects, tasks, sprints } = get()
|
||||||
await syncToServer(projects, tasks)
|
await syncToServer(projects, tasks, sprints)
|
||||||
set({ lastSynced: Date.now() })
|
set({ lastSynced: Date.now() })
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -387,7 +412,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
const newState = { projects: [...state.projects, newProject] }
|
const newState = { projects: [...state.projects, newProject] }
|
||||||
// Sync to server
|
// Sync to server
|
||||||
syncToServer(newState.projects, state.tasks)
|
syncToServer(newState.projects, state.tasks, state.sprints)
|
||||||
return newState
|
return newState
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -395,7 +420,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
updateProject: (id, updates) => {
|
updateProject: (id, updates) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newProjects = state.projects.map((p) => (p.id === id ? { ...p, ...updates } : p))
|
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 }
|
return { projects: newProjects }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -404,16 +429,18 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
set((state) => {
|
set((state) => {
|
||||||
const newProjects = state.projects.filter((p) => p.id !== id)
|
const newProjects = state.projects.filter((p) => p.id !== id)
|
||||||
const newTasks = state.tasks.filter((t) => t.projectId !== 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 {
|
return {
|
||||||
projects: newProjects,
|
projects: newProjects,
|
||||||
tasks: newTasks,
|
tasks: newTasks,
|
||||||
|
sprints: newSprints,
|
||||||
selectedProjectId: state.selectedProjectId === id ? null : state.selectedProjectId,
|
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) => {
|
addTask: (task) => {
|
||||||
const newTask: Task = {
|
const newTask: Task = {
|
||||||
@ -425,7 +452,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
}
|
}
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newTasks = [...state.tasks, newTask]
|
const newTasks = [...state.tasks, newTask]
|
||||||
syncToServer(state.projects, newTasks)
|
syncToServer(state.projects, newTasks, state.sprints)
|
||||||
return { tasks: newTasks }
|
return { tasks: newTasks }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -435,7 +462,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
const newTasks = state.tasks.map((t) =>
|
const newTasks = state.tasks.map((t) =>
|
||||||
t.id === id ? { ...t, ...updates, updatedAt: new Date().toISOString() } : t
|
t.id === id ? { ...t, ...updates, updatedAt: new Date().toISOString() } : t
|
||||||
)
|
)
|
||||||
syncToServer(state.projects, newTasks)
|
syncToServer(state.projects, newTasks, state.sprints)
|
||||||
return { tasks: newTasks }
|
return { tasks: newTasks }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -443,7 +470,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
deleteTask: (id) => {
|
deleteTask: (id) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newTasks = state.tasks.filter((t) => t.id !== id)
|
const newTasks = state.tasks.filter((t) => t.id !== id)
|
||||||
syncToServer(state.projects, newTasks)
|
syncToServer(state.projects, newTasks, state.sprints)
|
||||||
return {
|
return {
|
||||||
tasks: newTasks,
|
tasks: newTasks,
|
||||||
selectedTaskId: state.selectedTaskId === id ? null : state.selectedTaskId,
|
selectedTaskId: state.selectedTaskId === id ? null : state.selectedTaskId,
|
||||||
@ -453,6 +480,51 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
|
|
||||||
selectTask: (id) => set({ selectedTaskId: id }),
|
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) => {
|
addComment: (taskId, text, author) => {
|
||||||
const newComment: Comment = {
|
const newComment: Comment = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
@ -466,7 +538,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
? { ...t, comments: [...t.comments, newComment], updatedAt: new Date().toISOString() }
|
? { ...t, comments: [...t.comments, newComment], updatedAt: new Date().toISOString() }
|
||||||
: t
|
: t
|
||||||
)
|
)
|
||||||
syncToServer(state.projects, newTasks)
|
syncToServer(state.projects, newTasks, state.sprints)
|
||||||
return { tasks: newTasks }
|
return { tasks: newTasks }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -478,7 +550,7 @@ export const useTaskStore = create<TaskStore>()(
|
|||||||
? { ...t, comments: t.comments.filter((c) => c.id !== commentId) }
|
? { ...t, comments: t.comments.filter((c) => c.id !== commentId) }
|
||||||
: t
|
: t
|
||||||
)
|
)
|
||||||
syncToServer(state.projects, newTasks)
|
syncToServer(state.projects, newTasks, state.sprints)
|
||||||
return { tasks: newTasks }
|
return { tasks: newTasks }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user