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