feat: show tasks and creating them

This commit is contained in:
2025-05-07 11:14:17 +03:00
parent 852ac9ad0d
commit ce694f0be8
2 changed files with 198 additions and 55 deletions

View File

@@ -11,3 +11,46 @@ export interface ITask {
export interface ITaskForm extends Omit<ITask, "date"> {
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<string, string>;
task_type_choices: Record<string, string>;
}
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;
};
};
}

View File

@@ -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<ITask[]>(clear ? [] : init_tasks);
const [tasks, setTasks] = useState<ITask[]>([]);
const [subjectChoices, setSubjectChoices] = useState<Record<string, string>>({});
const [taskTypeChoices, setTaskTypeChoices] = useState<Record<string, string>>({});
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
fetchTasks();
}, []);
const fetchTasks = async () => {
try {
setIsLoading(true);
const response = await apiClient<IApiResponse>("/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<ITaskForm> = (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<ITaskForm> = 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<ICreateTaskResponse>("/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 (
<div class={classes.container}>
<ModalTags
zIndex={70}
isOpen={openModalTags}
setIsOpen={setOpenModalTags}
tagsList={example_tags}
value={tags}
onClose={() => {
if (!isCreating) setTags({ first: "", second: "", overdue: false });
}}
onChange={setTags}
/>
{isLoading ? (
<div class="flex w-full flex-1 items-center justify-center">
<div class="text-2xl">Загрузка...</div>
</div>
) : (
<ModalTags
zIndex={70}
isOpen={openModalTags}
setIsOpen={setOpenModalTags}
tagsList={example_tags}
value={tags}
onClose={() => {
if (!isCreating) setTags({ first: "", second: "", overdue: false });
}}
onChange={setTags}
/>
)}
<ModalCalendar
zIndex={80}
isOpen={openModalCalendar}
@@ -480,9 +588,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)}
/>
))}
</div>
@@ -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)}
/>
))}
</div>
@@ -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)}
/>
))}
</div>
@@ -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)}
/>
))
) : (