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)
278 lines
10 KiB
TypeScript
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',
|
|
}
|
|
)
|
|
)
|