gantt-board/src/stores/useTaskStore.ts
OpenClaw Bot 659c7c7c63 Add 3 new tasks to Kanban board from user
Task 4: Redesign Heartbeat Monitor to match UptimeRobot (high priority)
Task 5: Fix Blog Backup links to be clickable (medium priority)
Task 6: Fix monitoring schedule - sites are down (urgent bug)
2026-02-18 13:56:53 -06:00

278 lines
10 KiB
TypeScript

import { create } from 'zustand'
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 interface Comment {
id: string
text: string
createdAt: string
author: 'user' | 'assistant'
}
export interface Task {
id: string
title: string
description?: string
type: TaskType
status: TaskStatus
priority: Priority
projectId: string
createdAt: string
updatedAt: string
dueDate?: string
comments: Comment[]
tags: string[]
}
export interface Project {
id: string
name: string
description?: string
color: string
createdAt: string
}
interface TaskStore {
projects: Project[]
tasks: Task[]
selectedProjectId: string | null
selectedTaskId: string | null
// Project actions
addProject: (name: string, description?: string) => void
updateProject: (id: string, updates: Partial<Project>) => void
deleteProject: (id: string) => void
selectProject: (id: string | null) => void
// Task actions
addTask: (task: Omit<Task, 'id' | 'createdAt' | 'updatedAt' | 'comments'>) => void
updateTask: (id: string, updates: Partial<Task>) => void
deleteTask: (id: string) => void
selectTask: (id: string | null) => void
// Comment actions
addComment: (taskId: string, text: string, author: 'user' | 'assistant') => void
deleteComment: (taskId: string, commentId: string) => void
// Filters
getTasksByProject: (projectId: string) => Task[]
getTasksByStatus: (status: TaskStatus) => Task[]
getTaskById: (id: string) => Task | undefined
}
const defaultProjects: Project[] = [
{ id: '1', name: 'OpenClaw iOS', description: 'Main iOS app development', color: '#8b5cf6', createdAt: new Date().toISOString() },
{ id: '2', name: 'Web Projects', description: 'Web tools and dashboards', color: '#3b82f6', createdAt: new Date().toISOString() },
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
]
const defaultTasks: Task[] = [
{
id: '1',
title: 'Redesign Gantt Board',
description: 'Make it actually work with proper notes system',
type: 'task',
status: 'in-progress',
priority: 'high',
projectId: '2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [
{ id: 'c1', text: 'Need 1-to-many notes, not one big text field', createdAt: new Date().toISOString(), author: 'user' },
{ id: 'c2', text: 'Agreed - will rebuild with proper comment threads', createdAt: new Date().toISOString(), author: 'assistant' },
],
tags: ['ui', 'rewrite']
},
{
id: '2',
title: 'MoodWeave App Idea',
description: 'Social mood tracking with woven visualizations',
type: 'idea',
status: 'backlog',
priority: 'medium',
projectId: '1',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [],
tags: ['ios', 'social']
},
{
id: '3',
title: 'Set up Gitea integration for code pushes',
description: 'Create bot account on Gitea (192.168.1.128:3000) and configure git remotes for all OpenClaw projects. Decide on account name, permissions, and auth method (SSH vs token). User prefers dedicated bot account over using their personal account for audit trail.',
type: 'task',
status: 'done',
priority: 'medium',
projectId: '2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [
{ id: 'c3', text: 'User has local Gitea at http://192.168.1.128:3000', createdAt: new Date().toISOString(), author: 'assistant' },
{ id: 'c4', text: 'Options: 1) Create dedicated bot account (recommended), 2) Use existing account', createdAt: new Date().toISOString(), author: 'assistant' },
{ id: 'c5', text: 'Account created: mbruce@topdoglabs.com / !7883Gitea (username: ai-agent)', createdAt: new Date().toISOString(), author: 'user' },
{ id: 'c6', text: 'Git configured for all 3 projects. Gitea remotes added.', createdAt: new Date().toISOString(), author: 'assistant' },
{ id: 'c7', text: '✅ All 3 repos created and pushed to Gitea: gantt-board, blog-backup, heartbeat-monitor', createdAt: new Date().toISOString(), author: 'assistant' }
],
tags: ['gitea', 'git', 'automation', 'infrastructure']
},
{
id: '4',
title: 'Redesign Heartbeat Monitor to match UptimeRobot',
description: 'Completely redesign the Heartbeat Monitor website to be a competitor to https://uptimerobot.com. Study their design, layout, color scheme, typography, and functionality. Match their look, feel, and style as closely as possible. Include: modern dashboard, status pages, uptime charts, incident history, public status pages.',
type: 'task',
status: 'backlog',
priority: 'high',
projectId: '2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [
{ id: 'c8', text: 'Reference: https://uptimerobot.com - study their homepage, dashboard, and status page designs', createdAt: new Date().toISOString(), author: 'user' },
{ id: 'c9', text: 'Focus on: clean modern UI, blue/green color scheme, card-based layouts, uptime percentage displays, incident timelines', createdAt: new Date().toISOString(), author: 'user' }
],
tags: ['ui', 'ux', 'redesign', 'dashboard', 'monitoring']
},
{
id: '5',
title: 'Fix Blog Backup links to be clickable',
description: 'Make links in the Daily Digest clickable in the blog backup UI. Currently links are just text that require copy-paste. Need to render markdown links properly so users can click directly. Consider different formatting for Telegram vs Blog - Telegram gets plain text summary with "Read more at [link]", Blog gets full formatted content with clickable links.',
type: 'task',
status: 'backlog',
priority: 'medium',
projectId: '2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [
{ id: 'c10', text: 'Blog should show: [headline](url) as clickable markdown links', createdAt: new Date().toISOString(), author: 'user' },
{ id: 'c11', text: 'Telegram gets summary + "Full digest at: http://localhost:3003"', createdAt: new Date().toISOString(), author: 'user' }
],
tags: ['blog', 'ui', 'markdown', 'links']
},
{
id: '6',
title: 'Fix monitoring schedule - 2 of 3 sites are down',
description: 'The cron job running every 10 minutes to check heartbeat website is failing. Currently 2 of 3 websites are down and not being auto-restarted. Debug and fix the monitoring schedule to ensure all 3 sites (gantt-board, blog-backup, heartbeat-monitor) are checked and auto-restarted properly.',
type: 'bug',
status: 'in-progress',
priority: 'urgent',
projectId: '2',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [
{ id: 'c12', text: 'Issue: Cron job exists but sites are still going down without restart', createdAt: new Date().toISOString(), author: 'user' },
{ id: 'c13', text: 'Need to verify: cron is running, checks all 3 ports, restart logic works, permissions correct', createdAt: new Date().toISOString(), author: 'user' }
],
tags: ['monitoring', 'cron', 'bug', 'infrastructure', 'urgent']
}
]
export const useTaskStore = create<TaskStore>()(
persist(
(set, get) => ({
projects: defaultProjects,
tasks: defaultTasks,
selectedProjectId: '1',
selectedTaskId: null,
addProject: (name, description) => {
const colors = ['#8b5cf6', '#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#ec4899', '#06b6d4']
const newProject: Project = {
id: Date.now().toString(),
name,
description,
color: colors[Math.floor(Math.random() * colors.length)],
createdAt: new Date().toISOString(),
}
set((state) => ({ projects: [...state.projects, newProject] }))
},
updateProject: (id, updates) => {
set((state) => ({
projects: state.projects.map((p) => (p.id === id ? { ...p, ...updates } : p)),
}))
},
deleteProject: (id) => {
set((state) => ({
projects: state.projects.filter((p) => p.id !== id),
tasks: state.tasks.filter((t) => t.projectId !== id),
selectedProjectId: state.selectedProjectId === id ? null : state.selectedProjectId,
}))
},
selectProject: (id) => set({ selectedProjectId: id, selectedTaskId: null }),
addTask: (task) => {
const newTask: Task = {
...task,
id: Date.now().toString(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
comments: [],
}
set((state) => ({ tasks: [...state.tasks, newTask] }))
},
updateTask: (id, updates) => {
set((state) => ({
tasks: state.tasks.map((t) =>
t.id === id ? { ...t, ...updates, updatedAt: new Date().toISOString() } : t
),
}))
},
deleteTask: (id) => {
set((state) => ({
tasks: state.tasks.filter((t) => t.id !== id),
selectedTaskId: state.selectedTaskId === id ? null : state.selectedTaskId,
}))
},
selectTask: (id) => set({ selectedTaskId: id }),
addComment: (taskId, text, author) => {
const newComment: Comment = {
id: Date.now().toString(),
text,
createdAt: new Date().toISOString(),
author,
}
set((state) => ({
tasks: state.tasks.map((t) =>
t.id === taskId
? { ...t, comments: [...t.comments, newComment], updatedAt: new Date().toISOString() }
: t
),
}))
},
deleteComment: (taskId, commentId) => {
set((state) => ({
tasks: state.tasks.map((t) =>
t.id === taskId
? { ...t, comments: t.comments.filter((c) => c.id !== commentId) }
: t
),
}))
},
getTasksByProject: (projectId) => {
return get().tasks.filter((t) => t.projectId === projectId)
},
getTasksByStatus: (status) => {
return get().tasks.filter((t) => t.status === status)
},
getTaskById: (id) => {
return get().tasks.find((t) => t.id === id)
},
}),
{
name: 'task-store',
}
)
)