222 lines
8.1 KiB
TypeScript
222 lines
8.1 KiB
TypeScript
'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>
|
|
);
|
|
}
|