@@ -5,6 +5,7 @@ import Button from "@/components/ui/Button";
import ModalWindow from "@/components/ui/Modal" ;
import { withTitle } from "@/constructors/Component" ;
import { UrlsTitle } from "@/enums/urls" ;
import { cn } from "@/utils/class-merge" ;
import { PlusIcon } from "@heroicons/react/20/solid" ;
import {
BookmarkIcon ,
@@ -12,13 +13,15 @@ import {
CalendarDaysIcon ,
DocumentDuplicateIcon ,
FunnelIcon ,
InboxArrowDownIcon ,
MagnifyingGlassIcon ,
PencilIcon ,
} from "@heroicons/react/24/outline" ;
import { FunctionComponent } from "preact" ;
import { useEffect , useMemo , useRef , useState } from "preact/hooks" ;
import { useEffect , useMemo , useState } from "preact/hooks" ;
import { Nullable } from "primereact/ts-helpers" ;
import { ITask } from "./profile_tasks.dto ";
import { SubmitHandler , useForm } from "react-hook-form ";
import { ITask , ITaskForm } from "./profile_tasks.dto" ;
import classes from "./profile_tasks.module.scss" ;
const example_tasks : ITask [ ] = [
@@ -49,6 +52,13 @@ const example_tasks: ITask[] = [
] ;
const ProfileTasks : FunctionComponent = ( ) = > {
const [ openModal , setIsOpen ] = useState ( false ) ; // Открыта модалка
const [ openModalCalendar , setOpenModalCalendar ] = useState ( false ) ; // Открыта модалка календаря
const [ isEdit , setIsEdit ] = useState ( false ) ; // Открыта задача
const [ isEditModal , setIsEditModal ] = useState ( false ) ; // Включено редактирование задачи
const [ isCreating , setIsCreating ] = useState ( false ) ; // Включено создание задачи
const [ editContent , setEditContent ] = useState < ITask | null > ( null ) ; // Содержимое редактируемой задачи
const [ calendarDate , setCalendarDate ] = useState < Nullable < Date > > ( ) ; // Выбранная в календаре дата
const getDate = useMemo ( ( ) = > {
const date = new Date ( ) ;
const formatter = new Intl . DateTimeFormat ( "ru-RU" , { month : "long" , day : "numeric" } ) ;
@@ -64,23 +74,53 @@ const ProfileTasks: FunctionComponent = () => {
useEffect ( ( ) = > {
localStorage . setItem ( "tasks" , JSON . stringify ( tasks ) ) ;
} , [ tasks ] ) ;
const [ openModal , setIsOpen ] = useState ( false ) ;
const [ openModalCalendar , setOpenModalCalendar ] = useState ( false ) ;
const [ isEdit , setIsEdit ] = useState ( false ) ;
const [ isCreating , setIsCreating ] = useState ( false ) ;
const [ editContent , setEditContent ] = useState < ITask | null > ( null ) ;
const taskNameRef = u seRef < HTMLInputElement > ( null ) ;
const taskDescriptionRef = useRef < HTMLTextAreaElement > ( null ) ;
const [ calendarDate , setCalendarDate ] = useState < Nullable < Date > > ( ) ;
const {
handleSubmit ,
register ,
reset ,
setError ,
formState : { errors } ,
} = useForm < ITaskForm > ( {
defaultValues : {
tags : [ ] ,
} ,
} ) ;
const saveTask : SubmitHandler < ITaskForm > = ( data ) = > {
if ( ! calendarDate ) {
setError ( "date" , { message : "Выберите дату" } ) ;
return ;
}
const eTask : ITask = { . . . data , date : calendarDate } ;
if ( isCreating ) setTasks ( [ . . . tasks , eTask ] ) ;
else setTasks ( tasks . map ( ( task ) = > ( task . id === eTask . id ? eTask : task ) ) ) ;
if ( isCreating ) setIsOpen ( false ) ;
} ;
useEffect ( ( ) = > {
if ( editContent ) reset ( { . . . editContent , date : editContent.date.toISOString ( ) . slice ( 0 , 16 ) } ) ;
else reset ( ) ;
} , [ editContent ] ) ;
useEffect ( ( ) = > {
if ( calendarDate && editContent ) setEditContent ( { . . . editContent , date : calendarDate } ) ;
} , [ calendarDate ] ) ;
useEffect ( ( ) = > {
reset ( {
name : "" ,
description : "" ,
date : "" ,
tags : [ "Т е г 1" , "Т е г 2" ] ,
checked : false ,
} ) ;
} , [ isCreating ] ) ;
return (
< div class = { classes . container } >
< ModalCalendar
isOpen = { openModalCalendar }
setIsOpen = { setOpenModalCalendar }
onClose = { ( ) = > {
if ( isEdit ) setCalendarDate ( null ) ;
if ( isEdit && ! isEditModal ) setCalendarDate ( null ) ;
} }
onChange = { ( e ) = > isCreating && setCalendarDate ( e . value ) }
onChange = { ( e ) = > ( isCreating || isEditModal ) && setCalendarDate ( e . value ) }
value = { calendarDate ! }
/ >
< ModalWindow
@@ -90,25 +130,78 @@ const ProfileTasks: FunctionComponent = () => {
setIsEdit ( false ) ;
setEditContent ( null ) ;
setIsCreating ( false ) ;
setIsEditModal ( false ) ;
setCalendarDate ( null ) ;
} }
>
{ isEdit && editContent && (
< div class = "flex h-full w-full flex-col items-start justify-between" >
< form
class = "flex h-full w-full flex-col items-start justify-between"
onSubmit = { ( e ) = > {
e . preventDefault ( ) ;
if ( isEditModal ) handleSubmit ( saveTask ) ( ) ;
else setIsEditModal ( ! isEditModal ) ;
} }
>
< div class = "flex w-full flex-row items-start justify-between" >
< div class = "flex flex-col " >
< p class = "text-2xl" > { editContent . name } < / p >
< p > { editContent . description } < / p >
< div class = "flex flex-1 flex-col gap-1 pe-2 " >
< input
class = "w-full text-2xl outline-0"
disabled = { ! isEditModal }
placeholder = "Название"
{ ...register ( "name" , {
required : "Заполните название" ,
maxLength : { value : 20 , message : "Максимум 20 символов в названии" } ,
} ) }
/ >
< textarea
class = "h-[5rem] w-full resize-none outline-0"
disabled = { ! isEditModal }
placeholder = { isEditModal ? "Описание" : "" }
{ ...register ( "description" , {
maxLength : { value : 200 , message : "Максимум 200 символов в описании" } ,
} ) }
/ >
< input
type = "datetime-local"
value = { calendarDate ? calendarDate . toISOString ( ) . slice ( 0 , 16 ) : "" }
hidden
{ ...register ( "date" ) }
/ >
< input type = "checkbox" hidden { ...register ( "checked" ) } / >
< / div >
< div className = "flex cursor-pointer flex-col items-center gap-3" >
< div
className = "flex cursor-pointer flex-col items-center gap-3"
onClick = { ( ) = > {
if ( isEditModal ) {
handleSubmit ( saveTask ) ( ) ;
setIsEditModal ( ! isEditModal ) ;
} else setIsEditModal ( ! isEditModal ) ;
} }
>
{ isEditModal ? (
< >
< InboxArrowDownIcon class = "size-6" / >
< p class = "text-[0.7rem]" > С о х р а н и т ь < / p >
< / >
) : (
< >
{ " " }
< PencilIcon class = "size-6" / >
< p class = "text-[0.7rem]" > Р е д а к т и р о в а т ь < / p >
< / >
) }
< / div >
< / div >
{ errors . name && < p class = "text-red-500" > { errors . name . message } < / p > }
{ errors . description && < p class = "text-red-500" > { errors . description . message } < / p > }
< div class = "flex flex-col items-center gap-6 self-center md:flex-row md:justify-start md:self-start" >
< div
class = "flex h-full flex-row items-center gap-1 rounded-2xl bg-[rgba(251,194,199,0.38)] px-2 py-1"
class = { cn ( "flex h-full flex-row items-center gap-1 rounded-2xl bg-[rgba(251,194,199,0.38)] px-2 py-1", {
"cursor-pointer" : isEditModal ,
} ) }
onClick = { ( ) = > {
if ( ! isEditModal ) return ;
setOpenModalCalendar ( true ) ;
setCalendarDate ( editContent . date ) ;
} }
@@ -135,20 +228,39 @@ const ProfileTasks: FunctionComponent = () => {
< / p >
< / div >
< / div >
< / div >
< / form >
) }
{ isCreating && (
< div class = "flex h-full w-full flex-col items-start justify-between" >
< div class = "flex w-full flex-row items-start justify-between" >
< div class = "me-4 flex flex-1 flex-col gap-1" >
< input class = "text-2xl outline-0" maxLength = { 20 } placeholder = "Название" ref = { taskNameRef } / >
< textarea
class = "h-[5rem] w-full resize-none outline-0"
maxLength = { 200 }
placeholder = "Описание"
ref = { taskDescriptionRef }
< form
class = "flex h-full w-full flex-col items-start justify-between"
onSubmit = { ( e ) = > {
e . preventDefault ( ) ;
handleSubmit ( ( data ) = > saveTask ( { . . . data , id : tasks.length + 1 } ) ) ( ) ;
} }
>
< div class = "flex w-full flex-1 flex-row items-start justify-between" >
< div class = "me-4 flex h-full flex-1 flex-col gap-1" >
< input
class = "text-2xl outline-0"
maxLength = { 20 }
placeholder = "Название"
{ ...register ( "name" , { required : "Заполните название" } ) }
/ >
< textarea
class = "mb-10 w-full flex-1 resize-none outline-0"
placeholder = "Описание"
maxLength = { 200 }
{ ...register ( "description" ) }
/ >
< input
type = "datetime-local"
value = { calendarDate ? calendarDate . toISOString ( ) . slice ( 0 , 16 ) : "" }
hidden
{ ...register ( "date" ) }
/ >
< input type = "checkbox" checked = { false } hidden { ...register ( "checked" ) } / >
< / div >
< div class = "flex flex-row gap-3 self-start" >
< CalendarDaysIcon
class = "size-10 cursor-pointer"
onClick = { ( ) = > {
@@ -158,7 +270,10 @@ const ProfileTasks: FunctionComponent = () => {
/ >
< BookmarkIcon class = "ms-4 size-10 cursor-pointer" / >
< / div >
< div className = "mb-8 flex h-16 flex-col items-center gap-6 self-center md:mb-0 md:flex-row" >
< / div >
{ errors . name && < p class = "text-red-500" > { errors . name . message } < / p > }
{ errors . date && < p class = "text-red-500" > { errors . date . message } < / p > }
< div className = "mb-8 flex h-16 flex-col items-center gap-6 self-center md:mb-0 md:flex-row md:self-start" >
< Button
className = "text-sm"
onClick = { ( ) = > {
@@ -167,36 +282,11 @@ const ProfileTasks: FunctionComponent = () => {
>
О т м е н а
< / Button >
< Button
color = "red"
onClick = { ( ) = > {
if ( taskNameRef . current && taskDescriptionRef . current ) {
if ( ! taskNameRef . current . value || ! taskDescriptionRef . current . value ) {
alert ( "Заполните все поля" ) ;
return ;
}
if ( ! calendarDate ) {
alert ( "Заполните дату и время" ) ;
return ;
}
const task : ITask = {
id : tasks.length + 1 ,
name : taskNameRef.current.value ,
description : taskDescriptionRef.current.value ,
date : calendarDate ,
checked : false ,
tags : [ "Математика" , "Домашнее задание" ] ,
} ;
setTasks ( [ . . . tasks , task ] ) ;
setIsOpen ( false ) ;
setCalendarDate ( null ) ;
}
} }
>
< Button color = "red" type = "submit" >
Д о б а в и т ь з а д а ч у
< / Button >
< / div >
< / div >
< / form >
) }
< / ModalWindow >
{ tasks . length > 0 ? (
@@ -214,11 +304,15 @@ const ProfileTasks: FunctionComponent = () => {
setIsOpen ( true ) ;
setIsEdit ( true ) ;
setEditContent ( task ) ;
setCalendarDate ( task . date ) ;
} }
onMarkClick = { ( ) = > {
setTasks ( tasks . map ( ( t ) = > ( t . id === task . id ? { . . . t , checked : ! t . checked } : t ) ) ) ;
} }
/ >
) ) }
< / div >
< div class = "group fixed right-[22rem] bottom-4 hidden flex-row items-center justify-start space-x-3 overflow-x-hidden py-2 md:flex " >
< div class = "group fixed right-1 bottom-16 flex flex-row items-center justify-start space-x-3 overflow-x-hidden py-2 md:right-[22rem] md:bottom-4 " >
< div
class = "flex aspect-square h-20 cursor-pointer items-center justify-center rounded-full bg-[rgb(251,194,199,0.53)] text-9xl text-white shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)] transition-all duration-300 ease-out group-hover:ml-[12rem] hover:bg-[rgb(251,194,199,0.7)]"
onClick = { ( ) = > {
@@ -228,7 +322,7 @@ const ProfileTasks: FunctionComponent = () => {
>
< PlusIcon / >
< / div >
< div class = "absolute left-0 my-auto flex flex-row space-x-3 opacity-0 transition-opacity duration-100 group-hover:opacity-100" >
< div class = "absolute left-0 my-auto hidden flex-row space-x-3 opacity-0 transition-opacity duration-100 group-hover:opacity-100 md:flex " >
< div class = "pointer-events-none flex aspect-square h-20 cursor-pointer flex-col items-center justify-center rounded-full bg-[rgba(206,232,251,0.7)] text-xl text-gray-600 shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)] group-hover:pointer-events-auto hover:bg-[rgba(206,232,251,0.9)]" >
< MagnifyingGlassIcon class = "size-12" / >
< / div >