feat: new tasks calendar

This commit is contained in:
2025-04-27 14:04:51 +03:00
parent ef4d1be2bc
commit a2fe9bcfd7

View File

@@ -17,8 +17,10 @@ import {
InboxArrowDownIcon,
CalendarDaysIcon,
BookOpenIcon,
DocumentDuplicateIcon
DocumentDuplicateIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import Dialog from "@/components/ui/Dialog";
const example_tags: { first: string[]; second: string[] } = {
first: ["Программирование", "Информатика", "Физика", "Математика"],
@@ -40,8 +42,9 @@ const calendarStyles = {
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)]"
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 = () => {
@@ -56,6 +59,8 @@ const ProfileCalendar: FunctionComponent = () => {
const [editContent, setEditContent] = useState<ITask | null>(null);
const [calendarDate, setCalendarDate] = useState<Nullable<Date>>();
const [tags, setTags] = useState<ITags>({ first: "", second: "" });
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const {
handleSubmit,
@@ -72,35 +77,31 @@ const ProfileCalendar: FunctionComponent = () => {
useEffect(() => {
const storedTasks = localStorage.getItem("tasks");
if (storedTasks) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parsedTasks: ITask[] = JSON.parse(storedTasks).map((task: any) => ({
...task,
date: new Date(task.date)
date: new Date(task.date),
}));
setTasks(parsedTasks);
}
const handleStorageChange = (e: StorageEvent) => {
if (e.key === "tasks" && e.newValue) {
const updatedTasks: ITask[] = JSON.parse(e.newValue).map((task: any) => ({
...task,
date: new Date(task.date)
}));
setTasks(updatedTasks);
}
};
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, []);
const handleDeleteTask = () => {
if (!editContent) return;
setTasks(tasks.filter((task) => task.id !== editContent.id));
setIsOpen(false);
setShowDeleteDialog(false);
};
useEffect(() => {
if (!currentDate) return;
const tasksForDate = tasks.filter(task => {
const tasksForDate = tasks.filter((task) => {
const taskDate = task.date;
return taskDate.getDate() === currentDate.getDate() &&
return (
taskDate.getDate() === currentDate.getDate() &&
taskDate.getMonth() === currentDate.getMonth() &&
taskDate.getFullYear() === currentDate.getFullYear();
taskDate.getFullYear() === currentDate.getFullYear()
);
});
setSelectedTasks(tasksForDate);
@@ -110,6 +111,9 @@ const ProfileCalendar: FunctionComponent = () => {
if (editContent) reset({ ...editContent, date: editContent.date.toISOString().slice(0, 16) });
else reset();
}, [editContent]);
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
useEffect(() => {
if (!editContent) return;
@@ -120,43 +124,45 @@ const ProfileCalendar: FunctionComponent = () => {
}, [tags]);
const hasTasksOnDate = (date: CalendarDateTemplateEvent) => {
return tasks.some(task => {
return tasks.some((task) => {
const taskDate = task.date;
return taskDate.getDate() === date.day &&
taskDate.getMonth() === date.month &&
taskDate.getFullYear() === date.year;
return (
taskDate.getDate() === date.day && taskDate.getMonth() === date.month && taskDate.getFullYear() === date.year
);
});
};
const dateTemplate = (date: CalendarDateTemplateEvent) => {
const isHighlighted = hasTasksOnDate(date);
const isSelected = currentDate &&
const isSelected =
currentDate &&
currentDate.getDate() === date.day &&
currentDate.getMonth() === date.month &&
currentDate.getFullYear() === date.year;
const isToday = new Date().getDate() === date.day &&
const isToday =
new Date().getDate() === date.day &&
new Date().getMonth() === date.month &&
new Date().getFullYear() === date.year;
return (
<div className={cn(
"relative w-full h-20 flex items-start justify-start p-2 cursor-pointer rounded-lg",
<div
className={cn(
"relative flex h-20 w-full cursor-pointer items-start justify-start rounded-lg p-2",
"hover:bg-[rgba(251,194,199,0.1)]",
{
"bg-[rgba(251,194,199,0.4)]": isSelected,
"bg-[rgba(251,194,199,0.2)]": isToday && !isSelected,
}
)}>
)}
>
<span>{date.day}</span>
{isHighlighted && <span className="absolute top-2 right-2 w-2 h-2 rounded-full bg-pink-400" />}
{isHighlighted && <span className="absolute top-2 right-2 h-2 w-2 rounded-full bg-pink-400" />}
</div>
);
};
const handleTaskCheck = (taskId: string) => {
const updatedTasks = tasks.map(task =>
task.id === taskId ? { ...task, checked: !task.checked } : task
);
const updatedTasks = tasks.map((task) => (task.id === taskId ? { ...task, checked: !task.checked } : task));
setTasks(updatedTasks);
localStorage.setItem("tasks", JSON.stringify(updatedTasks));
};
@@ -165,7 +171,7 @@ const ProfileCalendar: FunctionComponent = () => {
return new Intl.DateTimeFormat("ru-RU", {
day: "numeric",
month: "long",
year: "numeric"
year: "numeric",
}).format(date);
};
@@ -205,7 +211,7 @@ const ProfileCalendar: FunctionComponent = () => {
monthPicker: { className: calendarStyles.monthPicker },
yearPicker: { className: calendarStyles.yearPicker },
month: { className: calendarStyles.month },
year: { className: calendarStyles.year }
year: { className: calendarStyles.year },
};
return (
@@ -276,6 +282,7 @@ const ProfileCalendar: FunctionComponent = () => {
/>
<input type="checkbox" hidden {...register("checked")} />
</div>
<div class="flex flex-row gap-4">
<div
className="flex cursor-pointer flex-col items-center gap-3"
onClick={() => {
@@ -297,6 +304,14 @@ const ProfileCalendar: FunctionComponent = () => {
</>
)}
</div>
<div
className="flex cursor-pointer flex-col items-center gap-3"
onClick={() => setShowDeleteDialog(true)}
>
<TrashIcon class="size-6" />
<p class="text-[0.7rem]">Удалить</p>
</div>
</div>
</div>
{errors.name && <p class="text-red-500">{errors.name.message}</p>}
{errors.description && <p class="text-red-500">{errors.description.message}</p>}
@@ -347,23 +362,29 @@ const ProfileCalendar: FunctionComponent = () => {
</form>
)}
</ModalWindow>
<Dialog
isOpen={showDeleteDialog}
setIsOpen={setShowDeleteDialog}
title="Удаление задачи"
content="Вы уверены, что хотите удалить эту задачу?"
onConfirm={handleDeleteTask}
confirmText="Удалить"
cancelText="Отмена"
/>
<Calendar
value={currentDate}
onChange={(e) => setCurrentDate(e ? e.value! : new Date())}
inline
pt={pt}
dateTemplate={dateTemplate}
className="[&_.p-datepicker-calendar]:border-separate [&_.p-datepicker-calendar]:border-spacing-2 [&_td]:border [&_td]:border-[rgba(251,194,199,0.38)] [&_td]:rounded-lg [&_.p-datepicker]:!border-0"
className="[&_.p-datepicker]:!border-0 [&_.p-datepicker-calendar]:border-separate [&_.p-datepicker-calendar]:border-spacing-2 [&_td]:rounded-lg [&_td]:border [&_td]:border-[rgba(251,194,199,0.38)]"
/>
<div class="mt-8 w-full px-4">
<h2 class="mb-4 text-2xl font-semibold">
Задачи на {formatDate(currentDate)}
</h2>
<h2 class="mb-4 text-2xl font-semibold">Задачи на {formatDate(currentDate)}</h2>
{selectedTasks.length > 0 ? (
<div class="flex flex-col gap-4">
{selectedTasks.map(task => (
{selectedTasks.map((task) => (
<Task
key={task.id}
name={task.name}
@@ -379,9 +400,7 @@ const ProfileCalendar: FunctionComponent = () => {
))}
</div>
) : (
<div class="rounded-lg bg-[rgba(251,194,199,0.2)] p-4 text-center">
На этот день задач нет
</div>
<div class="rounded-lg bg-[rgba(251,194,199,0.2)] p-4 text-center">На этот день задач нет</div>
)}
</div>
</div>