Add src/components/Sidebar.tsx

This commit is contained in:
2026-05-02 18:03:45 -04:00
parent 1d5da2dd8b
commit 24e94c050c

221
src/components/Sidebar.tsx Normal file
View File

@@ -0,0 +1,221 @@
'use client';
import { useApp } from './AppProvider';
import { useState } from 'react';
export default function Sidebar() {
const {
view, setView, selectedProject, setSelectedProject,
sidebarOpen, setSidebarOpen, showNewProjectModal, setShowNewProjectModal,
showNewTaskModal, setShowNewTaskModal, projects, setProjects, refreshTasks, refreshProjects,
} = useApp();
const [projectName, setProjectName] = useState('');
const [projectDescription, setProjectDescription] = useState('');
const [projectColor, setProjectColor] = useState('#3b82f6');
const [creatingProject, setCreatingProject] = useState(false);
const views: { key: typeof view; label: string; icon: string }[] = [
{ key: 'list', label: 'List View', icon: '☰' },
{ key: 'kanban', label: 'Kanban Board', icon: '▦' },
{ key: 'calendar', label: 'Calendar', icon: '📅' },
];
const handleNewProject = async () => {
if (!projectName.trim()) return;
try {
await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: projectName.trim(),
description: projectDescription.trim(),
color: projectColor,
}),
});
setProjectName('');
setProjectDescription('');
setProjectColor('#3b82f6');
setCreatingProject(false);
refreshProjects();
} catch (error) {
console.error('Failed to create project:', error);
}
};
const handleDeleteProject = async (id: string) => {
if (!confirm('Delete this project and all its tasks?')) return;
try {
await fetch(`/api/projects/${id}`, { method: 'DELETE' });
if (selectedProject === id) setSelectedProject(null);
refreshProjects();
refreshTasks();
} catch (error) {
console.error('Failed to delete project:', error);
}
};
return (
<div className={`flex flex-col h-full bg-sidebar text-white transition-all duration-300 ${sidebarOpen ? 'w-64' : 'w-16'}`}>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-white/10">
{sidebarOpen && (
<div className="flex items-center gap-2">
<span className="text-xl font-bold">VixTix</span>
</div>
)}
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="p-1.5 rounded-lg hover:bg-sidebar-hover transition-colors"
>
<span className="text-lg">{sidebarOpen ? '◀' : '▶'}</span>
</button>
</div>
{/* New Task Button */}
<div className="p-3">
<button
onClick={() => setShowNewTaskModal(true)}
className="w-full flex items-center justify-center gap-2 bg-accent hover:bg-accent-hover text-white py-2.5 px-4 rounded-lg transition-colors font-medium"
>
<span className="text-lg">+</span>
{sidebarOpen && <span>New Task</span>}
</button>
</div>
{/* Views */}
{sidebarOpen && (
<div className="px-3 mb-4">
<p className="text-xs text-gray-400 uppercase tracking-wider mb-2 px-2">Views</p>
<div className="space-y-1">
{views.map((v) => (
<button
key={v.key}
onClick={() => setView(v.key)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
view === v.key
? 'bg-sidebar-active text-white'
: 'text-gray-300 hover:bg-sidebar-hover hover:text-white'
}`}
>
<span className="text-base">{v.icon}</span>
{v.label}
</button>
))}
</div>
</div>
)}
{/* Projects */}
<div className="flex-1 overflow-y-auto px-3">
{sidebarOpen && (
<div className="flex items-center justify-between mb-2 px-2">
<p className="text-xs text-gray-400 uppercase tracking-wider">Projects</p>
<button
onClick={() => setCreatingProject(!creatingProject)}
className="text-gray-400 hover:text-white transition-colors"
title="New Project"
>
+
</button>
</div>
)}
{/* New project form */}
{creatingProject && sidebarOpen && (
<div className="mb-3 p-3 bg-sidebar-hover rounded-lg animate-slide-in">
<input
type="text"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
placeholder="Project name"
className="w-full bg-transparent border border-white/20 rounded px-2 py-1.5 text-sm text-white placeholder-gray-500 mb-2 focus:outline-none focus:border-accent"
autoFocus
/>
<input
type="text"
value={projectDescription}
onChange={(e) => setProjectDescription(e.target.value)}
placeholder="Description (optional)"
className="w-full bg-transparent border border-white/20 rounded px-2 py-1.5 text-sm text-white placeholder-gray-500 mb-2 focus:outline-none focus:border-accent"
/>
<div className="flex items-center gap-2 mb-2">
<span className="text-xs text-gray-400">Color:</span>
<input
type="color"
value={projectColor}
onChange={(e) => setProjectColor(e.target.value)}
className="w-8 h-8 rounded cursor-pointer border-0 bg-transparent"
/>
</div>
<div className="flex gap-2">
<button
onClick={handleNewProject}
className="flex-1 bg-accent hover:bg-accent-hover text-white py-1.5 rounded text-sm transition-colors"
>
Create
</button>
<button
onClick={() => { setCreatingProject(false); setProjectName(''); }}
className="flex-1 bg-gray-600 hover:bg-gray-500 text-white py-1.5 rounded text-sm transition-colors"
>
Cancel
</button>
</div>
</div>
)}
{/* Project list */}
<div className="space-y-0.5">
{/* All Projects option */}
<button
onClick={() => setSelectedProject(null)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
!selectedProject
? 'bg-sidebar-active text-white'
: 'text-gray-300 hover:bg-sidebar-hover hover:text-white'
}`}
>
<span className="text-base">📋</span>
{sidebarOpen && <span className="truncate">All Projects</span>}
</button>
{projects.map((project) => (
<div
key={project.id}
className={`group flex items-center gap-3 px-3 py-2 rounded-lg transition-colors text-sm ${
selectedProject === project.id
? 'bg-sidebar-active text-white'
: 'text-gray-300 hover:bg-sidebar-hover hover:text-white'
}`}
>
<span
className="w-3 h-3 rounded-full flex-shrink-0"
style={{ backgroundColor: project.color }}
/>
{sidebarOpen && (
<>
<span className="truncate flex-1 text-left">{project.name}</span>
<button
onClick={(e) => { e.stopPropagation(); handleDeleteProject(project.id); }}
className="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-400 transition-all"
title="Delete project"
>
</button>
</>
)}
</div>
))}
</div>
</div>
{/* Footer */}
{sidebarOpen && (
<div className="p-3 border-t border-white/10 text-xs text-gray-500 text-center">
VixTix v1.0
</div>
)}
</div>
);
}