My-Weekly-ToDo-List/src/components/SearchModal.tsx

129 lines
6.8 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect, useRef } from 'react';
import { format } from 'date-fns';
interface SearchModalProps {
isOpen: boolean;
onClose: () => void;
tasks: any[];
events: any[];
onSelectTask: (date: Date) => void;
}
export default function SearchModal({ isOpen, onClose, tasks, events, onSelectTask }: SearchModalProps) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<{ type: 'task' | 'event', item: any }[]>([]);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (isOpen && inputRef.current) {
inputRef.current.focus();
}
}, [isOpen]);
useEffect(() => {
if (!query.trim()) {
setResults([]);
return;
}
const lowerQuery = query.toLowerCase();
const filteredTasks = tasks.filter(t =>
t.title.toLowerCase().includes(lowerQuery) && !t.somedayListId
).map(t => ({ type: 'task' as const, item: t }));
const filteredEvents = events.filter(e =>
e.title.toLowerCase().includes(lowerQuery)
).map(e => ({ type: 'event' as const, item: e }));
setResults([...filteredTasks, ...filteredEvents].slice(0, 10));
}, [query, tasks, events]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-start justify-center pt-20" onClick={onClose}>
<div className="bg-white rounded-lg shadow-2xl w-[600px] max-w-[90%] overflow-hidden" onClick={e => e.stopPropagation()}>
<div className="p-4 border-b border-gray-100 flex items-center gap-3">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-400">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input
ref={inputRef}
type="text"
placeholder="Search tasks and events..."
className="flex-1 text-lg outline-none text-gray-700 placeholder-gray-400"
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={e => e.key === 'Escape' && onClose()}
/>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div className="max-h-[60vh] overflow-y-auto">
{results.length > 0 ? (
<div className="py-2">
{results.map((result, idx) => (
<div
key={`${result.type}-${result.item.id}-${idx}`}
className="px-4 py-3 hover:bg-gray-50 cursor-pointer flex items-center gap-3 border-b border-gray-50 last:border-0"
onClick={() => {
const date = result.type === 'task'
? (result.item.scheduledDate ? new Date(result.item.scheduledDate) : new Date())
: (new Date(result.item.startTime));
onSelectTask(date);
onClose();
}}
>
<div className={`p-2 rounded-full ${result.type === 'task' ? 'bg-blue-100 text-blue-600' : 'bg-teal-100 text-teal-600'}`}>
{result.type === 'task' ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path><path d="m9 12 2 2 4-4"></path></svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
)}
</div>
<div className="flex-1">
<div className="font-medium text-gray-800">{result.item.title}</div>
<div className="text-xs text-gray-500">
{format(
result.type === 'task'
? (result.item.scheduledDate ? new Date(result.item.scheduledDate) : new Date())
: new Date(result.item.startTime),
'PPP'
)}
</div>
</div>
{result.type === 'task' && result.item.completed && (
<span className="text-xs bg-gray-100 text-gray-500 px-2 py-1 rounded">Completed</span>
)}
</div>
))}
</div>
) : query.trim() ? (
<div className="p-8 text-center text-gray-400">
No Item found.
</div>
) : (
<div className="p-8 text-center text-gray-400">
Start typing to search...
</div>
)}
</div>
</div>
<style jsx>{`
.slide-in-from-top-2 { animation: slideIn 0.2s ease-out; }
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
}