feat: MySQL integration, Docker setup, drag-and-drop kanban
This commit is contained in:
150
src/App.tsx
150
src/App.tsx
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { SEED_TASKS } from './data';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { apiFetchTasks, apiFetchUsers, apiCreateTask, apiUpdateTask, apiAddActivity } from './api';
|
||||
import type { Task, User, Status } from './data';
|
||||
import { STATUS_LABELS } from './data';
|
||||
import { LoginPage } from './Login';
|
||||
import { Sidebar } from './Sidebar';
|
||||
import { TopNavbar, BottomToggleBar } from './NavBars';
|
||||
@@ -24,7 +25,8 @@ const VIEW_PAGES = ['calendar', 'kanban', 'list'];
|
||||
export default function App() {
|
||||
const now = new Date();
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [tasks, setTasks] = useState<Task[]>(SEED_TASKS);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [activePage, setActivePage] = useState('calendar');
|
||||
const [activeView, setActiveView] = useState('calendar');
|
||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||
@@ -36,6 +38,20 @@ export default function App() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [quickAddDay, setQuickAddDay] = useState<{ date: string; rect: { top: number; left: number } } | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Load data from API when user logs in
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
setLoading(true);
|
||||
Promise.all([apiFetchTasks(), apiFetchUsers()])
|
||||
.then(([fetchedTasks, fetchedUsers]) => {
|
||||
setTasks(fetchedTasks);
|
||||
setUsers(fetchedUsers);
|
||||
})
|
||||
.catch(err => console.error('Failed to load data:', err))
|
||||
.finally(() => setLoading(false));
|
||||
}, [currentUser]);
|
||||
|
||||
if (!currentUser) return <LoginPage onLogin={u => { setCurrentUser(u); setActivePage('calendar'); setActiveView('calendar'); }} />;
|
||||
|
||||
@@ -57,29 +73,91 @@ export default function App() {
|
||||
setQuickAddDay({ date, rect: { top: rect.bottom, left: rect.left } });
|
||||
};
|
||||
|
||||
const handleQuickAdd = (partial: Partial<Task>) => {
|
||||
const task: Task = {
|
||||
id: `t${Date.now()}`, title: partial.title || '', description: partial.description || '',
|
||||
status: (partial.status || 'todo') as Status, priority: partial.priority || 'medium',
|
||||
assignee: partial.assignee || 'u1', reporter: currentUser.id, dueDate: partial.dueDate || '',
|
||||
tags: partial.tags || [], subtasks: partial.subtasks || [], comments: partial.comments || [],
|
||||
activity: [{ id: `a${Date.now()}`, text: '📝 Task created', timestamp: new Date().toISOString() }],
|
||||
};
|
||||
setTasks(prev => [...prev, task]);
|
||||
setQuickAddDay(null);
|
||||
const handleQuickAdd = async (partial: Partial<Task>) => {
|
||||
try {
|
||||
const created = await apiCreateTask({
|
||||
title: partial.title || '',
|
||||
description: partial.description || '',
|
||||
status: partial.status || 'todo',
|
||||
priority: partial.priority || 'medium',
|
||||
assignee: partial.assignee || currentUser.id,
|
||||
reporter: currentUser.id,
|
||||
dueDate: partial.dueDate || '',
|
||||
tags: partial.tags || [],
|
||||
});
|
||||
setTasks(prev => [...prev, created]);
|
||||
setQuickAddDay(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to quick-add task:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddTask = (task: Task) => setTasks(prev => [...prev, { ...task, reporter: currentUser.id }]);
|
||||
const handleAddTask = async (task: Task) => {
|
||||
try {
|
||||
const created = await apiCreateTask({
|
||||
title: task.title,
|
||||
description: task.description,
|
||||
status: task.status,
|
||||
priority: task.priority,
|
||||
assignee: task.assignee,
|
||||
reporter: currentUser.id,
|
||||
dueDate: task.dueDate,
|
||||
tags: task.tags,
|
||||
});
|
||||
setTasks(prev => [...prev, created]);
|
||||
} catch (err) {
|
||||
console.error('Failed to add task:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateTask = (updated: Task) => {
|
||||
setTasks(prev => prev.map(t => t.id === updated.id ? updated : t));
|
||||
setActiveTask(updated);
|
||||
const handleUpdateTask = async (updated: Task) => {
|
||||
try {
|
||||
const result = await apiUpdateTask(updated.id, {
|
||||
title: updated.title,
|
||||
description: updated.description,
|
||||
status: updated.status,
|
||||
priority: updated.priority,
|
||||
assignee: updated.assignee,
|
||||
reporter: updated.reporter,
|
||||
dueDate: updated.dueDate,
|
||||
tags: updated.tags,
|
||||
});
|
||||
setTasks(prev => prev.map(t => t.id === result.id ? result : t));
|
||||
setActiveTask(result);
|
||||
} catch (err) {
|
||||
console.error('Failed to update task:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNewTask = () => { setAddModalDefaults({}); setShowAddModal(true); };
|
||||
const handleKanbanAdd = (status: Status) => { setAddModalDefaults({ status }); setShowAddModal(true); };
|
||||
const handleToggleDone = (taskId: string) => {
|
||||
setTasks(prev => prev.map(t => t.id === taskId ? { ...t, status: t.status === 'done' ? 'todo' : 'done' as Status } : t));
|
||||
const handleToggleDone = async (taskId: string) => {
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) return;
|
||||
const newStatus = task.status === 'done' ? 'todo' : 'done';
|
||||
try {
|
||||
const result = await apiUpdateTask(taskId, { status: newStatus });
|
||||
await apiAddActivity(taskId, `🔄 ${currentUser.name} changed status to ${newStatus}`);
|
||||
setTasks(prev => prev.map(t => t.id === taskId ? result : t));
|
||||
} catch (err) {
|
||||
console.error('Failed to toggle done:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveTask = async (taskId: string, newStatus: Status) => {
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task || task.status === newStatus) return;
|
||||
// Optimistic update
|
||||
setTasks(prev => prev.map(t => t.id === taskId ? { ...t, status: newStatus } : t));
|
||||
try {
|
||||
const result = await apiUpdateTask(taskId, { status: newStatus });
|
||||
await apiAddActivity(taskId, `🔄 ${currentUser.name} moved task to ${STATUS_LABELS[newStatus]}`);
|
||||
setTasks(prev => prev.map(t => t.id === taskId ? result : t));
|
||||
} catch (err) {
|
||||
console.error('Failed to move task:', err);
|
||||
// Revert on failure
|
||||
setTasks(prev => prev.map(t => t.id === taskId ? task : t));
|
||||
}
|
||||
};
|
||||
|
||||
const displayPage = VIEW_PAGES.includes(activePage) ? activeView : activePage;
|
||||
@@ -87,37 +165,45 @@ export default function App() {
|
||||
|
||||
const pageTitle = PAGE_TITLES[displayPage] || 'Calendar';
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="app-shell" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
|
||||
<p style={{ color: '#818cf8', fontSize: 18 }}>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<TopNavbar title={pageTitle} filterUser={filterUser} onFilterChange={setFilterUser}
|
||||
searchQuery={searchQuery} onSearch={setSearchQuery} onNewTask={handleNewTask}
|
||||
onOpenSidebar={() => setSidebarOpen(true)} />
|
||||
onOpenSidebar={() => setSidebarOpen(true)} users={users} />
|
||||
<div className="app-body">
|
||||
<Sidebar currentUser={currentUser} activePage={activePage} onNavigate={handleNavigate}
|
||||
onSignOut={() => { setCurrentUser(null); setActivePage('calendar'); setActiveView('calendar'); setSidebarOpen(false); }}
|
||||
isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||
isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} users={users} />
|
||||
<div className="main-content">
|
||||
{displayPage === 'calendar' && (
|
||||
<CalendarView tasks={tasks} currentUser={currentUser} calMonth={calMonth} calView={calView}
|
||||
onMonthChange={setCalMonth} onViewChange={setCalView} onTaskClick={handleTaskClick}
|
||||
onDayClick={handleDayClick} filterUser={filterUser} searchQuery={searchQuery} />
|
||||
onDayClick={handleDayClick} filterUser={filterUser} searchQuery={searchQuery} users={users} />
|
||||
)}
|
||||
{displayPage === 'kanban' && (
|
||||
<KanbanBoard tasks={tasks} currentUser={currentUser} onTaskClick={handleTaskClick}
|
||||
onAddTask={handleKanbanAdd} filterUser={filterUser} searchQuery={searchQuery} />
|
||||
onAddTask={handleKanbanAdd} onMoveTask={handleMoveTask} filterUser={filterUser} searchQuery={searchQuery} users={users} />
|
||||
)}
|
||||
{displayPage === 'list' && (
|
||||
<ListView tasks={tasks} currentUser={currentUser} onTaskClick={handleTaskClick}
|
||||
filterUser={filterUser} searchQuery={searchQuery} onToggleDone={handleToggleDone} />
|
||||
filterUser={filterUser} searchQuery={searchQuery} onToggleDone={handleToggleDone} users={users} />
|
||||
)}
|
||||
{displayPage === 'dashboard' && <DashboardPage tasks={tasks} currentUser={currentUser} />}
|
||||
{displayPage === 'dashboard' && <DashboardPage tasks={tasks} currentUser={currentUser} users={users} />}
|
||||
{displayPage === 'mytasks' && (
|
||||
<ListView tasks={filteredMyTasks} currentUser={currentUser} onTaskClick={handleTaskClick}
|
||||
filterUser={null} searchQuery={searchQuery} onToggleDone={handleToggleDone} />
|
||||
filterUser={null} searchQuery={searchQuery} onToggleDone={handleToggleDone} users={users} />
|
||||
)}
|
||||
{displayPage === 'teamtasks' && <TeamTasksPage tasks={tasks} currentUser={currentUser} />}
|
||||
{displayPage === 'reports' && <ReportsPage tasks={tasks} />}
|
||||
{displayPage === 'members' && <MembersPage tasks={tasks} />}
|
||||
{displayPage === 'teamtasks' && <TeamTasksPage tasks={tasks} currentUser={currentUser} users={users} />}
|
||||
{displayPage === 'reports' && <ReportsPage tasks={tasks} users={users} />}
|
||||
{displayPage === 'members' && <MembersPage tasks={tasks} users={users} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,8 +211,8 @@ export default function App() {
|
||||
<BottomToggleBar activeView={activeView} onViewChange={handleViewChange} />
|
||||
)}
|
||||
|
||||
{activeTask && <TaskDrawer task={activeTask} currentUser={currentUser} onClose={() => setActiveTask(null)} onUpdate={handleUpdateTask} />}
|
||||
{showAddModal && <AddTaskModal onClose={() => setShowAddModal(false)} onAdd={handleAddTask} defaultDate={addModalDefaults.date} defaultStatus={addModalDefaults.status} />}
|
||||
{activeTask && <TaskDrawer task={activeTask} currentUser={currentUser} onClose={() => setActiveTask(null)} onUpdate={handleUpdateTask} users={users} />}
|
||||
{showAddModal && <AddTaskModal onClose={() => setShowAddModal(false)} onAdd={handleAddTask} defaultDate={addModalDefaults.date} defaultStatus={addModalDefaults.status} users={users} currentUser={currentUser} />}
|
||||
|
||||
{quickAddDay && (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 199 }} onClick={() => setQuickAddDay(null)}>
|
||||
@@ -134,7 +220,7 @@ export default function App() {
|
||||
onClick={e => e.stopPropagation()}>
|
||||
<QuickAddPanel date={quickAddDay.date} onAdd={handleQuickAdd}
|
||||
onOpenFull={() => { setAddModalDefaults({ date: quickAddDay.date }); setShowAddModal(true); setQuickAddDay(null); }}
|
||||
onClose={() => setQuickAddDay(null)} />
|
||||
onClose={() => setQuickAddDay(null)} users={users} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user