"use client"; import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core"; import { closestCenter, DndContext, DragOverlay, KeyboardSensor, PointerSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { format } from "date-fns"; import { faIR } from "date-fns/locale"; import { Bell, Calendar, ChevronDown, Filter, Flag, GripVertical, Plus, SendHorizonal, SortAsc, Star, X, } from "lucide-react"; import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Calendar as CalendarComponent } from "@/components/ui/calendar"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, } from "@/components/ui/input-group"; import { Item, ItemContent, ItemGroup, ItemMedia, ItemTitle, } from "@/components/ui/item"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; type Priority = "P1" | "P2" | "P3" | "P4"; type SortOption = "manual" | "title" | "priority"; interface Project { id: string; name: string; color: string; } interface PriorityOption { value: Priority; label: string; color: string; } interface Todo { id: string; title: string; completed: boolean; priority?: Priority; starred: boolean; projectId: string; dueDate?: Date; } interface Filters { starredOnly: boolean; priorities: Priority[]; projects: string[]; } interface Todo7Props { heading?: string; description?: string; projects?: Project[]; priorities?: PriorityOption[]; filterLabel?: string; filterByLabel?: string; starredOnlyLabel?: string; prioritySectionLabel?: string; projectsSectionLabel?: string; clearFiltersLabel?: string; sortLabel?: string; sortByLabel?: string; manualOrderLabel?: string; titleSortLabel?: string; prioritySortLabel?: string; addTaskLabel?: string; taskPlaceholder?: string; dateLabel?: string; priorityLabel?: string; remindersLabel?: string; selectProjectLabel?: string; cancelLabel?: string; submitLabel?: string; emptyFiltersMessage?: string; taskCountLabel?: (count: number) => string; sortBadges?: Record; starredAriaLabel?: string; initialTodos?: Todo[]; className?: string; } const defaultProjects: Project[] = [ { id: "personal", name: "کارهای شخصی", color: "#3b82f6" }, { id: "work", name: "پروژه‌های کاری", color: "#8b5cf6" }, { id: "shopping", name: "خرید", color: "#10b981" }, { id: "fitness", name: "سلامت و تناسب اندام", color: "#f59e0b" }, { id: "learning", name: "یادگیری", color: "#ec4899" }, ]; const defaultPriorities: PriorityOption[] = [ { value: "P1", label: "P1 - فوری", color: "text-red-500" }, { value: "P2", label: "P2 - بالا", color: "text-orange-500" }, { value: "P3", label: "P3 - متوسط", color: "text-yellow-500" }, { value: "P4", label: "P4 - کم", color: "text-blue-500" }, ]; const defaultSortBadges: Record = { manual: "د", title: "ع", priority: "ا", }; const defaultTodos: Todo[] = [ { id: "1", title: "تکمیل مستندات فنی پروژه", completed: false, priority: "P2", starred: true, projectId: "work", dueDate: new Date(2025, 9, 25), }, { id: "2", title: "بررسی و تأیید طراحی رابط کاربری", completed: false, priority: "P3", starred: false, projectId: "work", }, { id: "3", title: "آماده‌سازی گزارش هفتگی", completed: false, priority: "P4", starred: false, projectId: "shopping", }, ]; const formatDueDate = (date: Date) => format(date, "d MMM", { locale: faIR }); interface SortableTodoItemProps { todo: Todo; onToggle: (id: string) => void; projects: Project[]; priorities: PriorityOption[]; starredAriaLabel: string; } const SortableTodoItem = ({ todo, onToggle, projects, priorities, starredAriaLabel, }: SortableTodoItemProps) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: todo.id }); const style = { transform: CSS.Translate.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; const priority = priorities.find((p) => p.value === todo.priority); const project = projects.find((p) => p.id === todo.projectId); return (
onToggle(todo.id)} >
onToggle(todo.id)} onClick={(e) => e.stopPropagation()} aria-label={ todo.completed ? `علامت‌گذاری «${todo.title}» به‌عنوان انجام‌نشده` : `علامت‌گذاری «${todo.title}» به‌عنوان انجام‌شده` } />
{todo.title} {todo.starred && ( )} {priority && ( {priority.value} )} {project && ( {project.name} )} {todo.dueDate && ( {formatDueDate(todo.dueDate)} )}
); }; const Todo7 = ({ heading = "مدیریت پیشرفته وظایف", description = "وظیفه با تاریخ، اولویت، یادآور و پروژه بسازید. ترتیب را تغییر دهید و فیلتر و مرتب‌سازی کنید.", projects = defaultProjects, priorities = defaultPriorities, filterLabel = "فیلتر", filterByLabel = "فیلتر بر اساس", starredOnlyLabel = "فقط علاقه‌مندی‌ها", prioritySectionLabel = "اولویت", projectsSectionLabel = "پروژه‌ها", clearFiltersLabel = "پاک کردن فیلترها", sortLabel = "مرتب‌سازی", sortByLabel = "مرتب‌سازی بر اساس", manualOrderLabel = "ترتیب دستی", titleSortLabel = "عنوان (الف–ی)", prioritySortLabel = "اولویت", addTaskLabel = "افزودن وظیفه جدید", taskPlaceholder = "عنوان وظیفه", dateLabel = "تاریخ", priorityLabel = "اولویت", remindersLabel = "یادآورها", selectProjectLabel = "انتخاب پروژه", cancelLabel = "انصراف", submitLabel = "افزودن", emptyFiltersMessage = "هیچ وظیفه‌ای با فیلترهای شما مطابقت ندارد.", taskCountLabel = (count) => `${count} وظیفه`, sortBadges = defaultSortBadges, starredAriaLabel = "علاقه‌مندی", initialTodos = defaultTodos, className, }: Todo7Props) => { const [todos, setTodos] = useState(initialTodos); const [isFormExpanded, setIsFormExpanded] = useState(false); const [formData, setFormData] = useState({ title: "", dueDate: undefined as Date | undefined, priority: null as Priority | null, reminders: [] as Date[], projectId: projects[0]?.id ?? "personal", }); const [datePickerOpen, setDatePickerOpen] = useState(false); const [sortBy, setSortBy] = useState("manual"); const [filters, setFilters] = useState({ starredOnly: false, priorities: [], projects: [], }); const [activeTodo, setActiveTodo] = useState(null); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ); const toggleTodo = (id: string) => { setTodos((prevTodos) => prevTodos.map((todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo, ), ); }; const resetForm = () => ({ title: "", dueDate: undefined, priority: null as Priority | null, reminders: [] as Date[], projectId: projects[0]?.id ?? "personal", }); const handleAddTask = () => { if (formData.title.trim()) { const newTodo: Todo = { id: Date.now().toString(), title: formData.title.trim(), completed: false, priority: formData.priority ?? undefined, starred: false, projectId: formData.projectId, dueDate: formData.dueDate, }; setTodos((prevTodos) => [...prevTodos, newTodo]); setFormData(resetForm()); setIsFormExpanded(false); } }; const handleCancel = () => { setFormData(resetForm()); setIsFormExpanded(false); }; const togglePriorityFilter = (priority: Priority) => { setFilters((prev) => ({ ...prev, priorities: prev.priorities.includes(priority) ? prev.priorities.filter((p) => p !== priority) : [...prev.priorities, priority], })); }; const toggleProjectFilter = (projectId: string) => { setFilters((prev) => ({ ...prev, projects: prev.projects.includes(projectId) ? prev.projects.filter((p) => p !== projectId) : [...prev.projects, projectId], })); }; const clearFilters = () => { setFilters({ starredOnly: false, priorities: [], projects: [], }); }; const activeFilterCount = (filters.starredOnly ? 1 : 0) + filters.priorities.length + filters.projects.length; const filteredAndSortedTodos = useMemo(() => { let filtered = todos.filter((todo) => { if (filters.starredOnly && !todo.starred) return false; if ( filters.priorities.length > 0 && todo.priority && !filters.priorities.includes(todo.priority) ) return false; if ( filters.projects.length > 0 && !filters.projects.includes(todo.projectId) ) return false; return true; }); if (sortBy === "title") { filtered = [...filtered].sort((a, b) => a.title.localeCompare(b.title, "fa"), ); } else if (sortBy === "priority") { const priorityOrder: Record = { P1: 1, P2: 2, P3: 3, P4: 4, }; filtered = [...filtered].sort( (a, b) => (priorityOrder[(a.priority || "P4") as Priority] || 4) - (priorityOrder[(b.priority || "P4") as Priority] || 4), ); } return filtered; }, [todos, filters, sortBy]); const handleDragStart = (event: DragStartEvent) => { const { active } = event; const todo = filteredAndSortedTodos.find((t) => t.id === active.id); setActiveTodo(todo || null); if (sortBy !== "manual") { setSortBy("manual"); } }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { const oldIndex = filteredAndSortedTodos.findIndex( (item) => item.id === active.id, ); const newIndex = filteredAndSortedTodos.findIndex( (item) => item.id === over.id, ); if (oldIndex !== -1 && newIndex !== -1) { const reorderedFiltered = arrayMove( filteredAndSortedTodos, oldIndex, newIndex, ); const reorderedIds = reorderedFiltered.map((t) => t.id); setTodos((items) => { const sortedItems = [...items].sort((a, b) => { const aIndex = reorderedIds.indexOf(a.id); const bIndex = reorderedIds.indexOf(b.id); if (aIndex === -1) return 1; if (bIndex === -1) return -1; return aIndex - bIndex; }); return sortedItems; }); } } setActiveTodo(null); }; const selectedProject = projects.find((p) => p.id === formData.projectId); const selectedPriority = priorities.find( (p) => p.value === formData.priority, ); return (

{heading}

{description}

{filterByLabel} setFilters((prev) => ({ ...prev, starredOnly: checked, })) } > {starredOnlyLabel} {prioritySectionLabel} {priorities.map((priority) => ( togglePriorityFilter(priority.value) } > {priority.label} ))} {projectsSectionLabel} {projects.map((project) => ( toggleProjectFilter(project.id)} > {project.name} ))} {activeFilterCount > 0 && ( <> )} {sortByLabel} setSortBy(value as SortOption)} > {manualOrderLabel} {titleSortLabel} {prioritySortLabel}
{taskCountLabel(filteredAndSortedTodos.length)}
{!isFormExpanded ? ( ) : ( setFormData((prev) => ({ ...prev, title: e.target.value, })) } onKeyDown={(e) => { if (e.key === "Enter" && formData.title.trim()) { handleAddTask(); } if (e.key === "Escape") { handleCancel(); } }} className="py-3 text-base font-medium" aria-label={taskPlaceholder} /> {formData.dueDate ? ( {formatDueDate(formData.dueDate)} ) : ( dateLabel )} { setFormData((prev) => ({ ...prev, dueDate: date })); if (date) { setDatePickerOpen(false); } }} disabled={(date) => date < new Date()} /> {selectedPriority ? ( ) : ( )} {selectedPriority?.label || priorityLabel}
{priorities.map((priority) => ( ))}
0 ? "text-chart-2" : "" } aria-label={remindersLabel} > {formData.reminders.length > 0 ? `${formData.reminders.length}` : remindersLabel}
{selectedProject ? selectedProject.name : selectProjectLabel}
{projects.map((project) => ( ))}
{cancelLabel} {submitLabel}
)} t.id)} strategy={verticalListSortingStrategy} > {filteredAndSortedTodos.length === 0 ? (
{emptyFiltersMessage}
) : ( filteredAndSortedTodos.map((todo) => ( )) )}
{activeTodo ? (
{activeTodo.title} {activeTodo.starred && ( )} {activeTodo.priority && ( {activeTodo.priority} )}
) : null}
); }; export { Todo7 };