Add src/components/Header.tsx
This commit is contained in:
111
src/components/Header.tsx
Normal file
111
src/components/Header.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useApp } from './AppProvider';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const {
|
||||||
|
view, selectedProject, projects, searchQuery, setSearchQuery,
|
||||||
|
showNewTaskModal, setShowNewTaskModal,
|
||||||
|
filterStatus, setFilterStatus, filterPriority, setFilterPriority,
|
||||||
|
filterDueBefore, setFilterDueBefore, filterDueAfter, setFilterDueAfter,
|
||||||
|
filterCompleted, setFilterCompleted,
|
||||||
|
refreshTasks,
|
||||||
|
} = useApp();
|
||||||
|
|
||||||
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
const searchRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
searchRef.current?.focus();
|
||||||
|
}
|
||||||
|
if (e.key === 'n' && (e.metaKey || e.ctrlKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowNewTaskModal(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', handler);
|
||||||
|
return () => window.removeEventListener('keydown', handler);
|
||||||
|
}, [setShowNewTaskModal]);
|
||||||
|
|
||||||
|
const selectedProjectName = projects.find(p => p.id === selectedProject)?.name || 'All Projects';
|
||||||
|
|
||||||
|
const handleSearch = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
refreshTasks();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearFilters = () => {
|
||||||
|
setFilterStatus('');
|
||||||
|
setFilterPriority('');
|
||||||
|
setFilterDueBefore('');
|
||||||
|
setFilterDueAfter('');
|
||||||
|
setFilterCompleted('');
|
||||||
|
setSearchQuery('');
|
||||||
|
refreshTasks();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasActiveFilters = filterStatus || filterPriority || filterDueBefore || filterDueAfter || filterCompleted || searchQuery;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="bg-card border-b border-border px-4 py-3 flex items-center gap-4">
|
||||||
|
{/* Search */}
|
||||||
|
<form onSubmit={handleSearch} className="flex-1 max-w-md">
|
||||||
|
<div className="relative">
|
||||||
|
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm">🔍</span>
|
||||||
|
<input
|
||||||
|
ref={searchRef}
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search tasks... (⌘K)"
|
||||||
|
className="w-full bg-content border border-border rounded-lg pl-9 pr-4 py-2 text-sm focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20 transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Project name */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-text-secondary">
|
||||||
|
<span>📋</span>
|
||||||
|
<span className="font-medium">{selectedProjectName}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter toggle */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm transition-colors ${
|
||||||
|
showFilters || hasActiveFilters
|
||||||
|
? 'bg-accent/10 text-accent'
|
||||||
|
: 'text-text-secondary hover:bg-content'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>⚙</span>
|
||||||
|
Filters
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<span className="w-2 h-2 bg-accent rounded-full" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Clear filters */}
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<button
|
||||||
|
onClick={handleClearFilters}
|
||||||
|
className="text-xs text-text-secondary hover:text-red-500 transition-colors"
|
||||||
|
>
|
||||||
|
Clear all
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* New task button (mobile) */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewTaskModal(true)}
|
||||||
|
className="bg-accent hover:bg-accent-hover text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
|
+ New Task
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user