feat: new tasks calendar
This commit is contained in:
@@ -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.getMonth() === currentDate.getMonth() &&
|
taskDate.getDate() === currentDate.getDate() &&
|
||||||
taskDate.getFullYear() === currentDate.getFullYear();
|
taskDate.getMonth() === currentDate.getMonth() &&
|
||||||
|
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(
|
||||||
"hover:bg-[rgba(251,194,199,0.1)]",
|
"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,
|
"bg-[rgba(251,194,199,0.4)]": 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,7 +197,7 @@ const ProfileCalendar: FunctionComponent = () => {
|
|||||||
|
|
||||||
const pt = {
|
const pt = {
|
||||||
root: { className: calendarStyles.root },
|
root: { className: calendarStyles.root },
|
||||||
input: { root: { className: calendarStyles.input }},
|
input: { root: { className: calendarStyles.input } },
|
||||||
panel: { className: calendarStyles.panel },
|
panel: { className: calendarStyles.panel },
|
||||||
header: { className: calendarStyles.header },
|
header: { className: calendarStyles.header },
|
||||||
title: { className: calendarStyles.title },
|
title: { className: calendarStyles.title },
|
||||||
@@ -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,26 +282,35 @@ const ProfileCalendar: FunctionComponent = () => {
|
|||||||
/>
|
/>
|
||||||
<input type="checkbox" hidden {...register("checked")} />
|
<input type="checkbox" hidden {...register("checked")} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="flex flex-row gap-4">
|
||||||
className="flex cursor-pointer flex-col items-center gap-3"
|
<div
|
||||||
onClick={() => {
|
className="flex cursor-pointer flex-col items-center gap-3"
|
||||||
if (isEditModal) {
|
onClick={() => {
|
||||||
handleSubmit(saveTask)();
|
if (isEditModal) {
|
||||||
setIsEditModal(!isEditModal);
|
handleSubmit(saveTask)();
|
||||||
} else setIsEditModal(!isEditModal);
|
setIsEditModal(!isEditModal);
|
||||||
}}
|
} else setIsEditModal(!isEditModal);
|
||||||
>
|
}}
|
||||||
{isEditModal ? (
|
>
|
||||||
<>
|
{isEditModal ? (
|
||||||
<InboxArrowDownIcon class="size-6" />
|
<>
|
||||||
<p class="text-[0.7rem]">Сохранить</p>
|
<InboxArrowDownIcon class="size-6" />
|
||||||
</>
|
<p class="text-[0.7rem]">Сохранить</p>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<PencilIcon class="size-6" />
|
<>
|
||||||
<p class="text-[0.7rem]">Редактировать</p>
|
<PencilIcon class="size-6" />
|
||||||
</>
|
<p class="text-[0.7rem]">Редактировать</p>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
</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>
|
</div>
|
||||||
{errors.name && <p class="text-red-500">{errors.name.message}</p>}
|
{errors.name && <p class="text-red-500">{errors.name.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>
|
||||||
|
|||||||
Reference in New Issue
Block a user