From 1d5da2dd8bcb493a04f11b9305e8d1271846ae2e Mon Sep 17 00:00:00 2001 From: vidane Date: Sat, 2 May 2026 18:03:43 -0400 Subject: [PATCH] Add src/components/AppProvider.tsx --- src/components/AppProvider.tsx | 143 +++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/components/AppProvider.tsx diff --git a/src/components/AppProvider.tsx b/src/components/AppProvider.tsx new file mode 100644 index 0000000..8e33874 --- /dev/null +++ b/src/components/AppProvider.tsx @@ -0,0 +1,143 @@ +'use client'; + +import { createContext, useContext, useState, useCallback, type ReactNode, type Dispatch, type SetStateAction } from 'react'; + +export type ViewType = 'list' | 'kanban' | 'calendar'; + +export interface Task { + id: string; + projectId: string | null; + title: string; + description: string; + completed: boolean; + priority: 'low' | 'medium' | 'high' | 'urgent'; + dueDate: string | null; + status: 'todo' | 'in_progress' | 'done'; + parentTaskId: string | null; + recurrenceRule: 'none' | 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'yearly'; + recurrenceInterval: number; + nextOccurrence: string | null; + sortOrder: number; + createdAt: string; + updatedAt: string; +} + +export interface Project { + id: string; + name: string; + description: string; + color: string; + sortOrder: number; + createdAt: string; + updatedAt: string; +} + +export interface AppState { + view: ViewType; + selectedProject: string | null; + selectedTask: Task | null; + searchQuery: string; + sidebarOpen: boolean; + showNewTaskModal: boolean; + showEditTaskModal: boolean; + showNewProjectModal: boolean; + showRecurrenceModal: boolean; + editingTask: Task | null; + filterStatus: string; + filterPriority: string; + filterDueBefore: string; + filterDueAfter: string; + filterCompleted: string; + tasks: Task[]; + projects: Project[]; + setView: (view: ViewType) => void; + setSelectedProject: (project: string | null) => void; + setSelectedTask: (task: Task | null) => void; + setSearchQuery: (query: string) => void; + setSidebarOpen: (open: boolean) => void; + setShowNewTaskModal: (show: boolean) => void; + setShowEditTaskModal: (show: boolean) => void; + setShowNewProjectModal: (show: boolean) => void; + setShowRecurrenceModal: (show: boolean) => void; + setEditingTask: (task: Task | null) => void; + setFilterStatus: (status: string) => void; + setFilterPriority: (priority: string) => void; + setFilterDueBefore: (date: string) => void; + setFilterDueAfter: (date: string) => void; + setFilterCompleted: (completed: string) => void; + setTasks: Dispatch>; + setProjects: Dispatch>; + refreshTasks: () => Promise; + refreshProjects: () => Promise; +} + +const AppContext = createContext(null); + +export function useApp() { + const context = useContext(AppContext); + if (!context) throw new Error('useApp must be used within AppProvider'); + return context; +} + +export default function AppProvider({ children }: { children: ReactNode }) { + const [view, setView] = useState('list'); + const [selectedProject, setSelectedProject] = useState(null); + const [selectedTask, setSelectedTask] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [sidebarOpen, setSidebarOpen] = useState(true); + const [showNewTaskModal, setShowNewTaskModal] = useState(false); + const [showEditTaskModal, setShowEditTaskModal] = useState(false); + const [showNewProjectModal, setShowNewProjectModal] = useState(false); + const [showRecurrenceModal, setShowRecurrenceModal] = useState(false); + const [editingTask, setEditingTask] = useState(null); + const [filterStatus, setFilterStatus] = useState(''); + const [filterPriority, setFilterPriority] = useState(''); + const [filterDueBefore, setFilterDueBefore] = useState(''); + const [filterDueAfter, setFilterDueAfter] = useState(''); + const [filterCompleted, setFilterCompleted] = useState(''); + const [tasks, setTasks] = useState([]); + const [projects, setProjects] = useState([]); + + const refreshTasks = useCallback(async () => { + try { + const params = new URLSearchParams(); + if (selectedProject) params.set('project', selectedProject); + if (searchQuery) params.set('search', searchQuery); + if (filterStatus) params.set('status', filterStatus); + if (filterPriority) params.set('priority', filterPriority); + if (filterDueBefore) params.set('due_before', filterDueBefore); + if (filterDueAfter) params.set('due_after', filterDueAfter); + if (filterCompleted) params.set('completed', filterCompleted); + + const res = await fetch(`/api/tasks?${params}`); + const data = await res.json(); + setTasks(data); + } catch (error) { + console.error('Failed to refresh tasks:', error); + } + }, [selectedProject, searchQuery, filterStatus, filterPriority, filterDueBefore, filterDueAfter, filterCompleted]); + + const refreshProjects = useCallback(async () => { + try { + const res = await fetch('/api/projects'); + const data = await res.json(); + setProjects(data); + } catch (error) { + console.error('Failed to refresh projects:', error); + } + }, []); + + const value: AppState = { + view, selectedProject, selectedTask, searchQuery, sidebarOpen, + showNewTaskModal, showEditTaskModal, showNewProjectModal, showRecurrenceModal, + editingTask, filterStatus, filterPriority, filterDueBefore, filterDueAfter, filterCompleted, + tasks, projects, + setView, setSelectedProject, setSelectedTask, setSearchQuery, setSidebarOpen, + setShowNewTaskModal, setShowEditTaskModal, setShowNewProjectModal, setShowRecurrenceModal, + setEditingTask, setFilterStatus, setFilterPriority, setFilterDueBefore, setFilterDueAfter, + setFilterCompleted, setTasks, setProjects, + refreshTasks, refreshProjects, + }; + + return {children}; +}