From ce694f0be829b55011ad276687150c9a9ba3978d Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Wed, 7 May 2025 11:14:17 +0300 Subject: [PATCH] feat: show tasks and creating them --- src/pages/profile_tasks.dto.ts | 43 +++++++ src/pages/profile_tasks.tsx | 210 ++++++++++++++++++++++++--------- 2 files changed, 198 insertions(+), 55 deletions(-) diff --git a/src/pages/profile_tasks.dto.ts b/src/pages/profile_tasks.dto.ts index 6bc7b88..8c5d088 100644 --- a/src/pages/profile_tasks.dto.ts +++ b/src/pages/profile_tasks.dto.ts @@ -11,3 +11,46 @@ export interface ITask { export interface ITaskForm extends Omit { date: string; } + +export interface IApiTask { + id: number; + title: string; + description: string; + isCompleted: boolean; + due_date: string; + subject: string; + task_type: string; +} + +export interface IApiDay { + date: string; + name: string; + tasks: IApiTask[]; +} + +export interface IApiResponse { + profile: string; + days: IApiDay[]; + subject_choices: Record; + task_type_choices: Record; +} + +export interface ICreateTaskResponse { + success: boolean; + message: string; + profile: string; + task: { + id: number; + title: string; + description: string; + subject: string; + taskType: string; + dateTime_due: string; + isCompleted: boolean; + reminder?: { + remind_before_days: number; + repeat_interval: number; + reminder_time: string; + }; + }; +} diff --git a/src/pages/profile_tasks.tsx b/src/pages/profile_tasks.tsx index eff630e..69a24bb 100644 --- a/src/pages/profile_tasks.tsx +++ b/src/pages/profile_tasks.tsx @@ -28,13 +28,9 @@ import { Checkbox, CheckboxPassThroughMethodOptions } from "primereact/checkbox" import { Nullable } from "primereact/ts-helpers"; import { SubmitHandler, useForm } from "react-hook-form"; import { v4 as uuid } from "uuid"; -import { ITask, ITaskForm } from "./profile_tasks.dto"; +import { ITask, ITaskForm, IApiResponse, ICreateTaskResponse } from "./profile_tasks.dto"; import classes from "./profile_tasks.module.scss"; - -const example_tags: { first: string[]; second: string[] } = { - first: ["Программирование", "Информатика", "Физика", "Математика"], - second: ["Лабораторная работа", "Практическая работа", "Домашнее задание", "Экзамен"], -}; +import apiClient from "@/services/api"; const ProfileTasks: FunctionComponent = () => { const [openModal, setIsOpen] = useState(false); // Открыта модалка @@ -58,17 +54,44 @@ const ProfileTasks: FunctionComponent = () => { const formatter = new Intl.DateTimeFormat("ru-RU", { month: "long", day: "numeric" }); return formatter.format(date); }, []); - const init_tasks: ITask[] = localStorage.getItem("tasks") ? JSON.parse(localStorage.getItem("tasks") as string) : []; - let clear = false; - init_tasks.forEach((task) => { - clear = clear || (task.new == undefined ? true : false); - if (!clear) task.new = true; - task.date = new Date(task.date); - }); - const [tasks, setTasks] = useState(clear ? [] : init_tasks); + const [tasks, setTasks] = useState([]); + const [subjectChoices, setSubjectChoices] = useState>({}); + const [taskTypeChoices, setTaskTypeChoices] = useState>({}); + const [isLoading, setIsLoading] = useState(true); + useEffect(() => { - localStorage.setItem("tasks", JSON.stringify(tasks)); - }, [tasks]); + fetchTasks(); + }, []); + + const fetchTasks = async () => { + try { + setIsLoading(true); + const response = await apiClient("/api/tasks/view_tasks/"); + + // Update choices + setSubjectChoices(response.subject_choices); + setTaskTypeChoices(response.task_type_choices); + + // Convert API tasks to our format + const convertedTasks: ITask[] = response.days.flatMap((day) => + day.tasks.map((apiTask) => ({ + id: apiTask.id.toString(), + name: apiTask.title, + checked: apiTask.isCompleted, + date: new Date(apiTask.due_date), + description: apiTask.description, + tags: [apiTask.subject, apiTask.task_type], + new: false, + })) + ); + + setTasks(convertedTasks); + } catch (error) { + console.error("Failed to fetch tasks:", error); + } finally { + setIsLoading(false); + } + }; const { handleSubmit, @@ -81,27 +104,79 @@ const ProfileTasks: FunctionComponent = () => { tags: [], }, }); - const saveTask: SubmitHandler = (data) => { + + // Update example_tags to use the choices from the API + const example_tags = useMemo( + () => ({ + first: Object.keys(subjectChoices), + second: Object.keys(taskTypeChoices), + }), + [subjectChoices, taskTypeChoices] + ); + + const saveTask: SubmitHandler = async (data) => { if (!calendarDate) { setError("date", { message: "Выберите дату" }); return; } - console.log(tags); if ((!editContent?.tags[0] || !editContent.tags[1]) && (!tags.first || !tags.second)) { setError("tags", { message: "Выберите теги" }); return; } - const eTask: ITask = { - ...data, - date: calendarDate, - tags: editContent?.tags.length ? editContent.tags : [tags.first, tags.second], - new: true, - }; - if (isCreating) setTasks([...tasks, eTask]); - else setTasks(tasks.map((task) => (task.id === eTask.id ? eTask : task))); - if (isCreating) setIsOpen(false); - setTags({ first: "", second: "", overdue: false }); + + try { + const selectedSubject = editContent?.tags[0] || tags.first; + const selectedTaskType = editContent?.tags[1] || 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 (isCreating) { + const response = await apiClient("/api/tasks/create_task/", { + method: "POST", + body: JSON.stringify(taskData), + }); + + if (!response.success) { + throw new Error(response.message); + } + } else { + await apiClient(`/api/tasks/update_task/${editContent?.id}/`, { + method: "PUT", + body: JSON.stringify(taskData), + }); + } + + // Refresh tasks after saving + await fetchTasks(); + + if (isCreating) setIsOpen(false); + setTags({ first: "", second: "", overdue: false }); + } catch (error) { + console.error("Failed to save task:", error); + } }; + useEffect(() => { if (editContent) reset({ ...editContent, date: editContent.date.toISOString().slice(0, 16) }); else reset(); @@ -174,11 +249,37 @@ const ProfileTasks: FunctionComponent = () => { }).format(date); }; - const handleDeleteTask = () => { + const handleDeleteTask = async () => { if (!editContent) return; - setTasks(tasks.filter((task) => task.id !== editContent.id)); - setIsOpen(false); - setShowDeleteDialog(false); + + try { + await apiClient(`/api/delete_task/${editContent.id}/`, { + method: "DELETE", + }); + + // Refresh tasks after deletion + await fetchTasks(); + + setIsOpen(false); + setShowDeleteDialog(false); + } catch (error) { + console.error("Failed to delete task:", error); + } + }; + + // Update the task marking functionality + const handleMarkTask = async (taskId: string, isCompleted: boolean) => { + try { + await apiClient(`/api/update_task/${taskId}/`, { + method: "PATCH", + body: JSON.stringify({ isCompleted }), + }); + + // Refresh tasks after marking + await fetchTasks(); + } catch (error) { + console.error("Failed to mark task:", error); + } }; const filteredTasks = useMemo(() => { @@ -211,19 +312,26 @@ const ProfileTasks: FunctionComponent = () => { useEffect(() => { if (searchInputRef.current && openSearchModal) searchInputRef.current.focus(); }, [searchInputRef, openSearchModal]); + return (
- { - if (!isCreating) setTags({ first: "", second: "", overdue: false }); - }} - onChange={setTags} - /> + {isLoading ? ( +
+
Загрузка...
+
+ ) : ( + { + if (!isCreating) setTags({ first: "", second: "", overdue: false }); + }} + onChange={setTags} + /> + )} { setEditContent(task); setCalendarDate(task.date); }} - onMarkClick={() => { - setTasks(tasks.map((t) => (t.id === task.id ? { ...t, checked: !t.checked } : t))); - }} + onMarkClick={() => handleMarkTask(task.id, !task.checked)} /> ))}
@@ -503,9 +609,7 @@ const ProfileTasks: FunctionComponent = () => { setEditContent(task); setCalendarDate(task.date); }} - onMarkClick={() => { - setTasks(tasks.map((t) => (t.id === task.id ? { ...t, checked: !t.checked } : t))); - }} + onMarkClick={() => handleMarkTask(task.id, !task.checked)} /> ))} @@ -524,9 +628,7 @@ const ProfileTasks: FunctionComponent = () => { setEditContent(task); setCalendarDate(task.date); }} - onMarkClick={() => { - setTasks(tasks.map((t) => (t.id === task.id ? { ...t, checked: !t.checked } : t))); - }} + onMarkClick={() => handleMarkTask(task.id, !task.checked)} /> ))} @@ -578,9 +680,7 @@ const ProfileTasks: FunctionComponent = () => { setEditContent(task); setCalendarDate(task.date); }} - onMarkClick={() => { - setTasks(tasks.map((t) => (t.id === task.id ? { ...t, checked: !t.checked } : t))); - }} + onMarkClick={() => handleMarkTask(task.id, !task.checked)} /> )) ) : (