Project Management
A powerful team collaboration tool with task boards, timelines, file sharing, and integrated team chat. Built for productivity.
Overview
Keep Your Team in Sync
Everything your team needs to plan, track, and deliver projects on time. Visualize work with kanban boards, communicate in real-time, and never miss a deadline with smart calendar integration.
Kanban Task Boards
Drag-and-drop task management with customizable workflows
Team Communication
Built-in chat with channels, threads, and file sharing
Calendar Integration
Timeline views, milestones, and deadline tracking
File Management
Upload, organize, and share files with your team
Live Demo
Task Board
To Do
2Design system updates
API integration
In Progress
2User testing
Bug fixes
Review
1Documentation
Done
1Performance audit
Implementation Guide
Define Project Structure
Set up the data models for projects, tasks, and team members.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// types/project.ts export interface Project { id: string; name: string; description?: string; color: string; members: TeamMember[]; createdAt: Date; } export interface Task { id: string; title: string; description?: string; status: "todo" | "in-progress" | "review" | "done"; priority: "low" | "medium" | "high" | "urgent"; assignee?: TeamMember; dueDate?: Date; tags: string[]; projectId: string; comments: Comment[]; attachments: Attachment[]; } export interface TeamMember { id: string; name: string; email: string; avatar?: string; role: "owner" | "admin" | "member" | "viewer"; } export interface Comment { id: string; content: string; author: TeamMember; createdAt: Date; } export interface Attachment { id: string; name: string; url: string; type: string; size: number; uploadedBy: TeamMember; uploadedAt: Date; }
Build the Task Board
Create an interactive kanban board with drag-and-drop functionality.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263function TaskBoard({ projectId }: { projectId: string }) { const [tasks, setTasks] = useState<Task[]>([]); const [draggedTask, setDraggedTask] = useState<Task | null>(null); const columns = [ { id: "todo", title: "To Do", color: "bg-slate-500" }, { id: "in-progress", title: "In Progress", color: "bg-amber-500" }, { id: "review", title: "Review", color: "bg-purple-500" }, { id: "done", title: "Done", color: "bg-emerald-500" }, ]; const handleDragStart = (task: Task) => setDraggedTask(task); const handleDrop = (status: Task["status"]) => { if (draggedTask) { setTasks(prev => prev.map(t => t.id === draggedTask.id ? { ...t, status } : t) ); // Sync with backend updateTaskStatus(draggedTask.id, status); setDraggedTask(null); } }; return ( <div className="flex gap-4 overflow-x-auto pb-4 h-full"> {columns.map(column => { const columnTasks = tasks.filter(t => t.status === column.id); return ( <div key={column.id} onDragOver={e => e.preventDefault()} onDrop={() => handleDrop(column.id as Task["status"])} className="flex-shrink-0 w-80 flex flex-col" > <div className="flex items-center gap-2 mb-4"> <div className={`w-3 h-3 rounded-full ${column.color}`} /> <h3 className="font-semibold">{column.title}</h3> <span className="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 rounded-full"> {columnTasks.length} </span> </div> <div className="space-y-3 flex-1"> {columnTasks.map(task => ( <TaskCard key={task.id} task={task} onDragStart={() => handleDragStart(task)} /> ))} </div> <button className="mt-4 w-full py-2 border-2 border-dashed border-slate-200 dark:border-slate-700 rounded-xl text-slate-500 hover:border-amber-500 hover:text-amber-500"> + Add Task </button> </div> ); })} </div> ); }
Implement Team Chat
Add real-time messaging with channels and direct messages.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485function TeamChat({ projectId }: { projectId: string }) { const [messages, setMessages] = useState<Message[]>([]); const [newMessage, setNewMessage] = useState(""); const [activeChannel, setActiveChannel] = useState("general"); const messagesEndRef = useRef<HTMLDivElement>(null); // Subscribe to real-time messages useEffect(() => { const unsubscribe = subscribeToChannel(projectId, activeChannel, (message) => { setMessages(prev => [...prev, message]); }); return () => unsubscribe(); }, [projectId, activeChannel]); // Scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const sendMessage = async () => { if (!newMessage.trim()) return; await createMessage({ content: newMessage, channelId: activeChannel, projectId, }); setNewMessage(""); }; return ( <div className="flex h-full"> {/* Channels sidebar */} <div className="w-64 border-r border-slate-200 dark:border-slate-800 p-4"> <h3 className="font-semibold mb-4">Channels</h3> <div className="space-y-1"> {["general", "design", "development", "random"].map(channel => ( <button key={channel} onClick={() => setActiveChannel(channel)} className={`w-full px-3 py-2 text-left rounded-lg ${ activeChannel === channel ? "bg-amber-100 text-amber-700" : "hover:bg-slate-100 dark:hover:bg-slate-800" }`} > # {channel} </button> ))} </div> </div> {/* Messages area */} <div className="flex-1 flex flex-col"> <div className="flex-1 overflow-y-auto p-4 space-y-4"> {messages.map(message => ( <MessageBubble key={message.id} message={message} /> ))} <div ref={messagesEndRef} /> </div> {/* Message input */} <div className="p-4 border-t border-slate-200 dark:border-slate-800"> <div className="flex gap-2"> <input type="text" value={newMessage} onChange={e => setNewMessage(e.target.value)} onKeyDown={e => e.key === "Enter" && sendMessage()} placeholder={`Message #${activeChannel}`} className="flex-1 px-4 py-2 bg-slate-100 dark:bg-slate-800 rounded-xl" /> <button onClick={sendMessage} className="px-4 py-2 bg-amber-500 text-white rounded-xl hover:bg-amber-600" > Send </button> </div> </div> </div> </div> ); }
Add Calendar View
Display tasks and deadlines on a visual calendar.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109function ProjectCalendar({ projectId }: { projectId: string }) { const [currentMonth, setCurrentMonth] = useState(new Date()); const [tasks, setTasks] = useState<Task[]>([]); const daysInMonth = new Date( currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0 ).getDate(); const firstDayOfMonth = new Date( currentMonth.getFullYear(), currentMonth.getMonth(), 1 ).getDay(); const getTasksForDay = (day: number) => { return tasks.filter(task => { if (!task.dueDate) return false; const dueDate = new Date(task.dueDate); return ( dueDate.getDate() === day && dueDate.getMonth() === currentMonth.getMonth() && dueDate.getFullYear() === currentMonth.getFullYear() ); }); }; return ( <div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6"> {/* Calendar header */} <div className="flex items-center justify-between mb-6"> <h2 className="text-xl font-semibold"> {currentMonth.toLocaleDateString("en-US", { month: "long", year: "numeric" })} </h2> <div className="flex gap-2"> <button onClick={() => setCurrentMonth(prev => new Date(prev.getFullYear(), prev.getMonth() - 1))} className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg" > ← </button> <button onClick={() => setCurrentMonth(new Date())} className="px-3 py-1 text-sm bg-slate-100 dark:bg-slate-800 rounded-lg" > Today </button> <button onClick={() => setCurrentMonth(prev => new Date(prev.getFullYear(), prev.getMonth() + 1))} className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg" > → </button> </div> </div> {/* Days header */} <div className="grid grid-cols-7 mb-2"> {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(day => ( <div key={day} className="text-center text-sm font-medium text-slate-500 py-2"> {day} </div> ))} </div> {/* Calendar grid */} <div className="grid grid-cols-7 gap-1"> {/* Empty cells for days before month starts */} {Array.from({ length: firstDayOfMonth }).map((_, i) => ( <div key={`empty-${i}`} className="h-24" /> ))} {/* Day cells */} {Array.from({ length: daysInMonth }).map((_, i) => { const day = i + 1; const dayTasks = getTasksForDay(day); const isToday = new Date().toDateString() === new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day).toDateString(); return ( <div key={day} className={`h-24 p-2 border border-slate-100 dark:border-slate-800 rounded-lg ${ isToday ? "bg-amber-50 dark:bg-amber-900/20" : "" }`} > <span className={`text-sm ${isToday ? "font-bold text-amber-600" : ""}`}> {day} </span> <div className="mt-1 space-y-1"> {dayTasks.slice(0, 2).map(task => ( <div key={task.id} className="px-1.5 py-0.5 text-xs bg-amber-100 text-amber-700 rounded truncate" > {task.title} </div> ))} {dayTasks.length > 2 && ( <span className="text-xs text-slate-500">+{dayTasks.length - 2} more</span> )} </div> </div> ); })} </div> </div> ); }
Full Source Code
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273"use client"; import { useState, useRef, useEffect } from "react"; // Types interface Task { id: string; title: string; description?: string; status: "todo" | "in-progress" | "review" | "done"; priority: "low" | "medium" | "high" | "urgent"; assignee?: string; dueDate?: Date; } interface Message { id: string; author: string; avatar: string; content: string; time: string; } // Main Project Management Dashboard export default function ProjectManagement() { const [activeView, setActiveView] = useState<"board" | "chat" | "calendar">("board"); return ( <div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex"> <Sidebar activeView={activeView} onViewChange={setActiveView} /> <main className="flex-1 flex flex-col"> <Header view={activeView} /> <div className="flex-1 overflow-auto p-6"> {activeView === "board" && <TaskBoard />} {activeView === "chat" && <TeamChat />} {activeView === "calendar" && <ProjectCalendar />} </div> </main> </div> ); } // Task Board with Drag and Drop function TaskBoard() { const columns = [ { id: "todo", title: "To Do", color: "bg-slate-500" }, { id: "in-progress", title: "In Progress", color: "bg-amber-500" }, { id: "review", title: "Review", color: "bg-purple-500" }, { id: "done", title: "Done", color: "bg-emerald-500" }, ]; const [tasks, setTasks] = useState<Task[]>([ { id: "1", title: "Design system updates", status: "todo", priority: "high", assignee: "EJ" }, { id: "2", title: "API integration", status: "in-progress", priority: "medium", assignee: "MC" }, // More tasks... ]); const [draggedTask, setDraggedTask] = useState<Task | null>(null); const handleDragStart = (task: Task) => setDraggedTask(task); const handleDrop = (status: Task["status"]) => { if (draggedTask) { setTasks(prev => prev.map(t => t.id === draggedTask.id ? { ...t, status } : t) ); setDraggedTask(null); } }; return ( <div className="flex gap-4 overflow-x-auto pb-4 h-full"> {columns.map((column) => { const columnTasks = tasks.filter(t => t.status === column.id); return ( <div key={column.id} onDragOver={e => e.preventDefault()} onDrop={() => handleDrop(column.id as Task["status"])} className="flex-shrink-0 w-72 flex flex-col" > <div className="flex items-center gap-2 mb-4"> <div className={`w-3 h-3 rounded-full ${column.color}`} /> <h3 className="font-semibold">{column.title}</h3> <span className="px-2 py-0.5 text-xs bg-slate-100 rounded-full"> {columnTasks.length} </span> </div> <div className="space-y-3 flex-1"> {columnTasks.map(task => ( <div key={task.id} draggable onDragStart={() => handleDragStart(task)} className="p-4 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 cursor-grab active:cursor-grabbing" > <PriorityBadge priority={task.priority} /> <h4 className="font-medium mt-2">{task.title}</h4> {task.assignee && ( <div className="mt-3"> <Avatar initials={task.assignee} /> </div> )} </div> ))} </div> <button className="mt-4 w-full py-2 border-2 border-dashed border-slate-200 rounded-xl text-slate-500 hover:border-amber-500 hover:text-amber-500"> + Add Task </button> </div> ); })} </div> ); } // Team Chat Component function TeamChat() { const [messages, setMessages] = useState<Message[]>([ { id: "1", author: "Emily", avatar: "EJ", content: "Hey team!", time: "10:30 AM" }, ]); const [newMessage, setNewMessage] = useState(""); const messagesEndRef = useRef<HTMLDivElement>(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const sendMessage = () => { if (!newMessage.trim()) return; setMessages(prev => [...prev, { id: Date.now().toString(), author: "You", avatar: "YO", content: newMessage, time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), }]); setNewMessage(""); }; return ( <div className="flex h-full bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden"> {/* Channels sidebar */} <div className="w-56 border-r border-slate-200 dark:border-slate-800 p-4"> <p className="text-xs font-semibold text-slate-400 uppercase mb-3">Channels</p> <div className="space-y-1"> {["general", "design", "development"].map((channel, i) => ( <button key={channel} className={`w-full px-3 py-2 text-left text-sm rounded-lg ${ i === 0 ? "bg-amber-100 text-amber-700" : "hover:bg-slate-100" }`} > # {channel} </button> ))} </div> </div> {/* Messages */} <div className="flex-1 flex flex-col"> <div className="flex-1 p-4 space-y-4 overflow-y-auto"> {messages.map(msg => ( <div key={msg.id} className="flex gap-3"> <Avatar initials={msg.avatar} /> <div> <div className="flex items-center gap-2 mb-1"> <span className="font-medium text-sm">{msg.author}</span> <span className="text-xs text-slate-500">{msg.time}</span> </div> <p className="text-sm text-slate-600 dark:text-slate-300">{msg.content}</p> </div> </div> ))} <div ref={messagesEndRef} /> </div> <div className="p-4 border-t border-slate-200 dark:border-slate-800"> <div className="flex gap-2"> <input type="text" value={newMessage} onChange={e => setNewMessage(e.target.value)} onKeyDown={e => e.key === "Enter" && sendMessage()} placeholder="Message #general" className="flex-1 px-4 py-2 bg-slate-100 dark:bg-slate-800 rounded-xl text-sm" /> <button onClick={sendMessage} className="px-4 py-2 bg-amber-600 text-white rounded-xl text-sm font-medium" > Send </button> </div> </div> </div> </div> ); } // Project Calendar function ProjectCalendar() { const [currentMonth] = useState(new Date()); return ( <div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6"> <div className="flex items-center justify-between mb-6"> <h2 className="text-xl font-semibold"> {currentMonth.toLocaleDateString("en-US", { month: "long", year: "numeric" })} </h2> <div className="flex gap-2"> <button className="p-2 hover:bg-slate-100 rounded-lg">←</button> <button className="px-3 py-1 bg-slate-100 rounded-lg text-sm">Today</button> <button className="p-2 hover:bg-slate-100 rounded-lg">→</button> </div> </div> <div className="grid grid-cols-7 gap-1"> {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(day => ( <div key={day} className="text-center text-sm font-medium text-slate-500 py-2"> {day} </div> ))} {/* Calendar days would be rendered here */} </div> </div> ); } // Helper Components function Avatar({ initials }: { initials: string }) { return ( <div className="w-8 h-8 rounded-full bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center text-white text-xs font-semibold"> {initials} </div> ); } function PriorityBadge({ priority }: { priority: Task["priority"] }) { const colors = { urgent: "bg-red-100 text-red-700", high: "bg-orange-100 text-orange-700", medium: "bg-amber-100 text-amber-700", low: "bg-slate-100 text-slate-600", }; return ( <span className={`px-2 py-0.5 text-xs font-medium rounded ${colors[priority]}`}> {priority} </span> ); } function Sidebar({ activeView, onViewChange }: { activeView: string; onViewChange: (view: "board" | "chat" | "calendar") => void; }) { return ( <aside className="w-64 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800"> {/* Sidebar content */} </aside> ); } function Header({ view }: { view: string }) { return ( <header className="h-16 bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 flex items-center justify-between px-6"> <h1 className="text-lg font-semibold capitalize">{view.replace("-", " ")}</h1> </header> ); }
Customization Guide
Custom Task Statuses
Configure your own workflow stages.
const customStatuses = [
{ id: "backlog", title: "Backlog", color: "bg-slate-400" },
{ id: "ready", title: "Ready", color: "bg-blue-500" },
{ id: "in-progress", title: "In Progress", color: "bg-amber-500" },
{ id: "testing", title: "Testing", color: "bg-purple-500" },
{ id: "staging", title: "Staging", color: "bg-cyan-500" },
{ id: "deployed", title: "Deployed", color: "bg-emerald-500" },
];Priority Levels
Define priority levels with colors and icons.
const priorities = {
urgent: {
label: "Urgent",
color: "bg-red-100 text-red-700",
icon: "🔴",
},
high: {
label: "High",
color: "bg-orange-100 text-orange-700",
icon: "🟠",
},
medium: {
label: "Medium",
color: "bg-amber-100 text-amber-700",
icon: "🟡",
},
low: {
label: "Low",
color: "bg-slate-100 text-slate-700",
icon: "⚪",
},
};Team Roles
Configure team member roles and permissions.
const roles = {
owner: {
label: "Owner",
permissions: ["all"],
},
admin: {
label: "Admin",
permissions: ["manage_members", "manage_settings", "manage_tasks"],
},
member: {
label: "Member",
permissions: ["create_tasks", "update_own_tasks", "comment"],
},
viewer: {
label: "Viewer",
permissions: ["view_tasks", "comment"],
},
};Notification Settings
Customize notification triggers and delivery.
const notificationSettings = {
task_assigned: { email: true, push: true, inApp: true },
task_completed: { email: false, push: true, inApp: true },
comment_added: { email: true, push: true, inApp: true },
due_date_reminder: { email: true, push: true, inApp: true },
mention: { email: true, push: true, inApp: true },
};
// Configure reminder timing
const reminderTiming = [
{ label: "1 day before", hours: 24 },
{ label: "3 hours before", hours: 3 },
{ label: "1 hour before", hours: 1 },
];Ready to Boost Your Team's Productivity?
Copy this template and start managing projects effectively. Customize workflows to match your team.