import ModalCalendar from "@/components/ModalCalendar"; import ModalTags, { ITags } from "@/components/ModalTags"; import Task from "@/components/task"; import Dialog from "@/components/ui/Dialog"; import ModalWindow from "@/components/ui/Modal"; import { withTitle } from "@/constructors/Component"; import { UrlsTitle } from "@/enums/urls"; import apiClient from "@/services/api"; import { cn } from "@/utils/class-merge"; import { BookOpenIcon, CalendarDaysIcon, DocumentDuplicateIcon, InboxArrowDownIcon, PencilIcon, TrashIcon, } from "@heroicons/react/24/outline"; import { FunctionComponent } from "preact"; import { useEffect, useMemo, useState } from "preact/hooks"; import { Calendar, CalendarDateTemplateEvent } from "primereact/calendar"; import { Nullable } from "primereact/ts-helpers"; import { SubmitHandler, useForm } from "react-hook-form"; import { IApiResponse, IAPITag, ICreateTaskResponse, IDeleteTaskResponse, IEditTaskResponse, ITask, ITaskDetails, ITaskForm, IViewTagsResponse, } from "./profile_tasks.dto"; const calendarStyles = { root: "inline-flex w-full relative", input: "font-sans text-base text-gray-600 bg-white p-3", panel: "bg-white min-w-full p-2", header: "flex items-center justify-between p-2 text-gray-700 font-semibold m-0 mb-4", title: "leading-8 mx-auto text-xl", previousButton: "flex items-center justify-center cursor-pointer w-8 h-8 text-gray-600", nextButton: "flex items-center justify-center cursor-pointer w-8 h-8 text-gray-600", table: "border-collapse w-full", tableHeaderCell: "p-2 text-center", weekDay: "text-gray-600 font-normal pb-4", monthTitle: "text-gray-700 font-semibold mr-2", yearTitle: "text-gray-700 font-semibold", monthPicker: "grid grid-cols-3 gap-2", yearPicker: "grid grid-cols-2 gap-2", month: "p-2 text-center cursor-pointer rounded-lg hover:bg-[rgba(251,194,199,0.1)] [&.p-highlight]:bg-[rgba(251,194,199,0.2)]", year: "p-2 text-center cursor-pointer rounded-lg hover:bg-[rgba(251,194,199,0.1)] [&.p-highlight]:bg-[rgba(251,194,199,0.2)]", }; const ProfileCalendar: FunctionComponent = () => { const [currentDate, setCurrentDate] = useState(new Date()); const [tasks, setTasks] = useState([]); const [selectedTasks, setSelectedTasks] = useState([]); const [openModal, setIsOpen] = useState(false); const [openModalCalendar, setOpenModalCalendar] = useState(false); const [openModalTags, setOpenModalTags] = useState(false); const [isEdit, setIsEdit] = useState(false); const [isEditModal, setIsEditModal] = useState(false); const [editContent, setEditContent] = useState(null); const [calendarDate, setCalendarDate] = useState>(); const [tags, setTags] = useState({ first: 0, second: 0, overdue: false }); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [subjectChoices, setSubjectChoices] = useState([]); const [taskTypeChoices, setTaskTypeChoices] = useState([]); const [isLoading, setIsLoading] = useState(true); const { handleSubmit, register, reset, setError, formState: { errors }, } = useForm({ defaultValues: { subject: { name: "", id: 0, }, taskType: { name: "", id: 0, }, }, }); useEffect(() => { fetchTasks(); fetchTags(); }, []); const fetchTags = async () => { try { const response = await apiClient("/api/tags/view_tags/"); setSubjectChoices(response.subjects); setTaskTypeChoices(response.taskTypes); } catch (error) { console.error("Failed to fetch tags:", error); } }; const fetchTasks = async () => { try { setIsLoading(true); const response = await apiClient("/api/tasks/view_tasks/"); const convertedTasks: ITask[] = response.tasks.map((apiTask) => ({ id: apiTask.id.toString(), name: apiTask.title, checked: apiTask.isCompleted, date: new Date(apiTask.due_date), description: apiTask.description, priority: apiTask.priority, subject: apiTask.subject, taskType: apiTask.taskType, new: false, })); setTasks(convertedTasks); } catch (error) { console.error("Failed to fetch tasks:", error); } finally { setIsLoading(false); } }; const example_tags = useMemo( () => ({ first: subjectChoices, second: taskTypeChoices, }), [subjectChoices, taskTypeChoices] ); const saveTask: SubmitHandler = async (data) => { if (!calendarDate) { setError("date", { message: "Выберите дату" }); return; } if ((!editContent?.subject.id || !editContent.taskType.id) && (!tags.first || !tags.second)) { setError("subject", { message: "Выберите теги" }); return; } try { const selectedSubject = editContent?.subject.id || tags.first; const selectedTaskType = editContent?.taskType.id || tags.second; // Format date to DD-MM-YYYYTHH:MM const formattedDate = calendarDate .toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", hour12: false, }) .replace(",", "T") .replace(/\//g, "-") .replace("T ", "T"); const taskData = { title: data.name, description: data.description || "", subject: selectedSubject, taskType: selectedTaskType, dateTime_due: formattedDate, telegram_notifications: false, }; if (!editContent) { const response = await apiClient("/api/tasks/create_task/", { method: "POST", body: JSON.stringify(taskData), }); if (!response.success) { throw new Error(response.message); } } else { const response = await apiClient(`/api/tasks/edit_task/${editContent.id}/`, { method: "PUT", body: JSON.stringify(taskData), }); if (!response.success) { throw new Error(response.message); } } await fetchTasks(); setIsOpen(false); setTags({ first: 0, second: 0, overdue: false }); } catch (error) { console.error("Failed to save task:", error); } }; const handleDeleteTask = async () => { if (!editContent) return; try { const response = await apiClient(`/api/tasks/delete_task/${editContent.id}/`, { method: "DELETE", }); if (!response.success) { throw new Error(response.message); } await fetchTasks(); setIsOpen(false); setShowDeleteDialog(false); } catch (error) { console.error("Failed to delete task:", error); } }; const handleTaskCheck = async (taskId: string) => { try { await apiClient(`/api/tasks/toggle_complete_task/${taskId}/`, { method: "PATCH", }); setTasks((prevTasks) => prevTasks.map((task) => (task.id === taskId ? { ...task, checked: !task.checked } : task)) ); } catch (error) { console.error("Failed to mark task:", error); } }; const handleViewTask = async (taskId: string) => { try { const taskDetails = await apiClient(`/api/tasks/view_task/${taskId}/`); const task: ITask = { id: taskId, name: taskDetails.title, checked: false, date: new Date(taskDetails.dateTime_due), description: taskDetails.description, subject: taskDetails.subject, taskType: taskDetails.task_type, priority: taskDetails.priority, }; setIsOpen(true); setIsEdit(true); setEditContent(task); setCalendarDate(task.date); setIsEditModal(false); } catch (error) { console.error("Failed to fetch task details:", error); } }; useEffect(() => { if (!currentDate) return; const tasksForDate = tasks.filter((task) => { const taskDate = task.date; return ( taskDate.getDate() === currentDate.getDate() && taskDate.getMonth() === currentDate.getMonth() && taskDate.getFullYear() === currentDate.getFullYear() ); }); setSelectedTasks(tasksForDate); }, [currentDate, tasks]); useEffect(() => { if (editContent) reset({ ...editContent, date: editContent.date.toISOString().slice(0, 16) }); else reset(); }, [editContent]); useEffect(() => { if (!editContent) return; const newEditContent = editContent; if (tags.first) newEditContent.subject = subjectChoices.find((tag) => tag.id === tags.first)!; if (tags.second) newEditContent.taskType = taskTypeChoices.find((tag) => tag.id === tags.second)!; setEditContent(newEditContent); }, [tags]); const tasksCount = (date: CalendarDateTemplateEvent) => { return tasks.filter((task) => { const taskDate = task.date; return ( taskDate.getDate() === date.day && taskDate.getMonth() === date.month && taskDate.getFullYear() === date.year ); }).length; }; const hasTasksOnDate = (date: CalendarDateTemplateEvent) => { return tasks.some((task) => { const taskDate = task.date; return ( taskDate.getDate() === date.day && taskDate.getMonth() === date.month && taskDate.getFullYear() === date.year ); }); }; const dateTemplate = (date: CalendarDateTemplateEvent) => { const isHighlighted = hasTasksOnDate(date); const countT = tasksCount(date); const isSelected = currentDate && currentDate.getDate() === date.day && currentDate.getMonth() === date.month && currentDate.getFullYear() === date.year; const isToday = new Date().getDate() === date.day && new Date().getMonth() === date.month && new Date().getFullYear() === date.year; return (
{date.day} {isHighlighted && (
{Array.from({ length: countT > 3 ? 3 : countT }).map((_, i) => ( ))} {countT > 3 && +}
)}
); }; const formatDate = (date: Date) => { return new Intl.DateTimeFormat("ru-RU", { day: "numeric", month: "long", year: "numeric", }).format(date); }; const pt = { root: { className: calendarStyles.root }, input: { root: { className: calendarStyles.input } }, panel: { className: calendarStyles.panel }, header: { className: calendarStyles.header }, title: { className: calendarStyles.title }, previousButton: { className: calendarStyles.previousButton }, nextButton: { className: calendarStyles.nextButton }, table: { className: calendarStyles.table }, tableHeaderCell: { className: calendarStyles.tableHeaderCell }, weekDay: { className: calendarStyles.weekDay }, monthTitle: { className: calendarStyles.monthTitle }, yearTitle: { className: calendarStyles.yearTitle }, monthPicker: { className: calendarStyles.monthPicker }, yearPicker: { className: calendarStyles.yearPicker }, month: { className: calendarStyles.month }, year: { className: calendarStyles.year }, }; return (
{isLoading ? (
Загрузка...
) : ( <> { setTags({ first: 0, second: 0, overdue: false }); }} onChange={setTags} /> { if (isEdit && !isEditModal) setCalendarDate(null); }} onChange={(e) => isEditModal && setCalendarDate(e.value)} value={calendarDate!} /> { setIsEdit(false); setEditContent(null); setIsEditModal(false); setTags({ first: 0, second: 0, overdue: false }); setCalendarDate(null); }} > {isEdit && editContent && (
{ e.preventDefault(); if (isEditModal) handleSubmit(saveTask)(); else setIsEditModal(!isEditModal); }} >