본문 바로가기
개인 공부/게시판

8. 일기 수정 모달 창

by 갱생angel 2024. 2. 15.

<추가/수정 사항>

※수정 버튼 클릭 시 모달 창이 나오도록 구현

※pointer-events CSS를 이용해 모달 창 개/폐 여부에 따라 페이지 버튼 클릭 이벤트 허용/방지

  -모달 창이 열리면 모달 창 외부 버튼을 클릭이 안되며 그대로 모달 창 닫힘

※이벤트 리스너를 이용해 모달 창 외부를 클릭하면 모달 창이 닫힘

  -mousedown : HTML 요소 위에 마우스를 클릭 시 이벤트 발생

  -.current.contains(event.target) : 특정 영역 외 클릭을 감지

※제목/내용/카테고리 수정

※수정 모달 창을 열 시 , 이미 저장된 제목/내용/카테고리를 기본 value로 지정

 

component

  -Input.JSX

  -Button.JSX

  -Textarea.JSX

  -DiaryWrite.JSX

  -DiaryBlock.JSX

  -Category.JSX

  -CategoryButton.JSX

  -Header.JSX

  -Sidebar.JSX

  -EditDiary.JSX

module

  -Diary.JSX

css

  -Diary.css

  -Category.css

 

component - <EditDiary.JSX> : 일기 수정 모달 창

import React, {useEffect} from 'react'
import '../css/Diary.css'
import Input from './Input'
import Textarea from './Textarea'
import Button from './Button'
import Category from './Category'

function EditDiary({
  editTitle,
  editContent,
  changeEditTitle,
  changeEditContent,
  closeEditModal,
  editCategory,
  changeEditCategory,
  submitEditDiary
}) {
  useEffect(() => {
    document.body.style = `pointer-events: none` //모달 창 열면 페이지 전체 클릭 이벤트 방지
    return () => (document.body.style = `pointer-events: auto`) //모달 창 닫으면 페이지 전체 클릭 이벤트 허용
  })

  return (
    <div className="writeDiv">
      <Button className={'closeWriteDiary'} onClick={closeEditModal} name={'X'} />
      <h1 className="writeMain">Today Diary</h1>
      <form>
        <div>
          <label>카테고리</label>
          <Category value={editCategory} onChange={changeEditCategory} />
        </div>
        <div className="writeTitleDiv">
          <label>제목</label>
          <Input
            className={'writeTitleInput'}
            type={'text'}
            name={'title'}
            value={editTitle}
            onChange={changeEditTitle}
          />
        </div>
        <div className="writeContentDiv">
          <label>내용</label>
          <Textarea
            className={'writeContentTextarea'}
            name={'content'}
            value={editContent}
            onChange={changeEditContent}
          />
        </div>
        <Button
          className={'submitButton'}
          type={'submit'}
          name={'저장'}
          onClick={submitEditDiary}
        />
      </form>
    </div>
  )
}

export default EditDiary

 

component - <DiaryBlock.JSX > : 이벤트 리스너를 통해 모달 창 외부 클릭 감지

import React, {useEffect, useRef, useState} from 'react'
import '../css/Diary.css'
import Button from './Button'
import EditDiary from './EditDiary'

function DiaryBlock({title, content, id, name, date, deleteDiary, editDiary, modalBool}) {
  const [editTitle, setEditTitle] = useState(title) //수정 모달 창에 저장된 제목을 value로 지정
  const [editContent, setEditContent] = useState(content)//수정 모달 창에 저장된 내용을 value로 지정
  const [editCategory, setEditCategory] = useState(name) //수정 모달 창에 저장된 카테고리를을 value로 지정
  const [editBool, setEditBool] = useState(false)

  const modalWindow = useRef() //문서에서 모달 창 영역을 지정

  const changeEditTitle = event => {
    setEditTitle(event.target.value)
  }
  const changeEditContent = event => {
    setEditContent(event.target.value)
  }
  const changeEditCategory = event => {
    setEditCategory(event.target.value)
  }

  const handleDelete = () => {
    deleteDiary(id)
  }
  const handleEdit = () => {
    setEditBool(true)
  }

  const submitEditDiary = event => {
    event.preventDefault()
    if (editBool === false) {
      setEditBool(true)
    } else {
      setEditBool(false)
      editDiary(id, editTitle, editContent, editCategory)
      //id, 수정한 제목/내용/카테고리를 상위 컴포넌트로 넘김
    }
  }

  const closeEditModal = () => {
    setEditBool(false)
  }

  useEffect(() => {
    const modalOutClick = event => {
      if (!modalWindow.current || modalWindow.current.contains(event.target)) return
      //모달창이 존재하지 않거나 모달창 내부를 클릭할 경우, 함수 실행 안함
      setEditBool(false) //모달창 외부를 클릭할 경우 모달창 꺼짐는 함수 실행
    }
    document.addEventListener('mousedown', modalOutClick)
    //마우스 다운 이벤트가 발생할 때 modalOutClick 함수를 실행하는 이벤트 리스너를 문서 전체에 등록
    return () => {
      document.removeEventListener('mousedown', modalOutClick)
    } //컴포넌트가 언마운트되거나 업데이트될 때 실행 때 마우스 다운 이벤트 리스너를 제거
  }, [modalWindow])

  return (
    <div className="blockDiv" key={id} name={name}>
      <div className="blockTitleDiv">
        <h3 className="blockTitle">{title}</h3>
        <Button
          className={'deleteButton'}
          onClick={modalBool ? null : handleDelete}
          name={'삭제'}
        />
        <Button
          className={'editButton'}
          onClick={modalBool ? null : handleEdit}
          name={'수정'}
        />
      </div>
      <div className="blockDateDiv">
        <p>{date}</p>
      </div>
      <p>----------------------------------------------------------------------</p>
      <div ref={modalWindow}>
        {editBool ? (
          <EditDiary
            editTitle={editTitle}
            editContent={editContent}
            editCategory={editCategory}
            changeEditTitle={changeEditTitle}
            changeEditContent={changeEditContent}
            changeEditCategory={changeEditCategory}
            submitEditDiary={submitEditDiary}
            closeEditModal={closeEditModal}
          />
        ) : (
          <p>{content}</p>
        )}
      </div>
    </div>
  )
}

export default DiaryBlock

 

module - <Diary.JSX> : 일기 제목/내용/카테고리 수정

import React, {useEffect, useRef, useState} from 'react'
import '../css/Diary.css'
import DiaryWrite from '../component/DiaryWrite'
import DiaryBlock from '../component/DiaryBlock'
import Button from '../component/Button'
import Header from '../component/Header'
import Sidebar from '../component/Sidebar'

function Diary() {
  const [diary, setDiary] = useState(JSON.parse(localStorage.getItem('Diary')) || [])
  const [categoryDiary, setCategoryDiary] = useState(diary)
  const [categoryValue, setCategoryValue] = useState('none')
  const [modalBool, setModalBool] = useState(false)

  const diaryKey = useRef(parseInt(localStorage.getItem('DiaryKey')) || 0)

  const changeCategoryValue = event => {
    setCategoryValue(event.target.value)
  }

  const addDiary = (title, content) => {
    if (categoryValue === 'none') {
      alert('카테고리를 지정해주세요.')
    } else {
      const newDiary = {
        title,
        content,
        name: categoryValue,
        id: diaryKey.current,
        date: new Date().toLocaleString()
      }
      diaryKey.current += 1
      setDiary([newDiary, ...diary])
      setCategoryValue('none')
      setModalBool(false)
    }
  }
  const deleteDiary = id => {
    const remainDiary = diary.filter(remove => remove.id !== id)
    setDiary(remainDiary)
  }
  const editDiary = (id, title, content, name) => { //제목, 내용, 카테고리 수정
    setDiary(
      diary.map(edit =>
        edit.id === id ? {...edit, title: title, content: content, name: name} : edit
        //지정한 id의 제목, 내용, 카테고리만 수정
      )
    )
  }

  const allDivDiaryBtn = () => {
    setCategoryDiary(diary)
  }
  const routineDivDiaryBtn = () => {
    setCategoryDiary(diary.filter(divide => divide.name === 'routine'))
  }
  const studyDivDiaryBtn = () => {
    setCategoryDiary(diary.filter(divide => divide.name === 'study'))
  }
  const travelDivDiaryBtn = () => {
    setCategoryDiary(diary.filter(divide => divide.name === 'travel'))
  }
  const gameDivDiaryBtn = () => {
    setCategoryDiary(diary.filter(divide => divide.name === 'game'))
  }

  const openModal = event => {
    setModalBool(true)
    event.stopPropagation()
  }
  const closeModal = () => {
    setModalBool(false)
    setCategoryValue('none')
  }

  useEffect(() => {
    localStorage.setItem('Diary', JSON.stringify(diary))
    localStorage.setItem('DiaryKey', parseInt(diaryKey.current))
    setCategoryDiary(diary)
  }, [diary])

  return (
    <div className={modalBool ? 'diaryDiv' : null} onClick={closeModal}>
      <div>
        <Header />
      </div>
      <div>
        <div>
          <Sidebar
            allDivDiaryBtn={modalBool ? null : allDivDiaryBtn}
            routineDivDiaryBtn={modalBool ? null : routineDivDiaryBtn}
            studyDivDiaryBtn={modalBool ? null : studyDivDiaryBtn}
            travelDivDiaryBtn={modalBool ? null : travelDivDiaryBtn}
            gameDivDiaryBtn={modalBool ? null : gameDivDiaryBtn}
          />
        </div>
        <div className="mainDiv">
          <div>
            <Button
              className="openWriteDiary"
              name={'글쓰기 +'}
              onClick={modalBool ? null : openModal}
            />
          </div>
          <div onClick={event => event.stopPropagation()}>
            {modalBool ? (
              <DiaryWrite
                addDiary={addDiary}
                closeModal={closeModal}
                categoryValue={categoryValue}
                changeCategory={changeCategoryValue}
              />
            ) : null}
          </div>
          <div className="gridDiv">
            {categoryDiary.map(diaryData => (
              <DiaryBlock
                key={diaryData.id}
                name={diaryData.name}
                date={diaryData.date}
                {...diaryData}
                deleteDiary={deleteDiary}
                editDiary={editDiary}
                modalBool={modalBool}
              />
            ))}
          </div>
        </div>
      </div>
    </div>
  )
}

export default Diary

 

Diary.css : pointer-events로 모달 창 내부만 클릭 허용

.headerDiv {
  display: flex;
  border-bottom: 1px solid black;
  top: 0;
  width: 100%;
  background-color: white;
  position: fixed;
  z-index: 1;
}

.headerTitle {
  margin: 20px 0px 20px 70px;
  padding: 0;
  font-size: 40px;
}

.sidebarDiv {
  position: fixed;
  border-right: 1px solid black;
  height: 100vh;
  width: 270px;
  padding-top: 100px;
  top: 0;
}

.mainDiv {
  margin: 120px 0px 20px 300px;
}

.gridDiv {
  display: grid;
  grid-template-columns: 500px 500px;
}

.writeDiv {
  border: 1px solid black;
  display: inline-block;
  padding: 20px;
  position: absolute;
  background-color: white;
  left: 37%;
  top: 10%;
  box-shadow: rgba(0, 0, 0, 0.7) 0 0 0 9999px;
  z-index: 1;
  pointer-events: auto; //모달 창 열 시 해당 위치(모달창)만 포인트 이벤트 동작
}

.writeMain {
  text-align: center;
  margin-left: 30px;
}

.writeTitleDiv {
  margin-top: 20px;
}

.writeTitleInput {
  width: 400px;
  height: 30px;
  font-size: 15px;
}

.writeContentDiv {
  margin-top: 20px;
}

.writeContentTextarea {
  width: 400px;
  height: 100px;
  resize: none;
  font-size: 15px;
}

.blockDiv {
  border: 1px solid rgb(211, 211, 211);
  background-color: rgb(211, 211, 211);
  width: 450px;
  padding: 10px;
  margin-top: 20px;
}

.blockTitleDiv {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}

.blockTitle {
  margin-right: auto;
  margin-top: 10px;
  margin-bottom: 0;
}

.blockDateDiv {
  font-size: 15px;
  font-style: italic;
}

.diaryDiv {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

.openWriteDiary {
  margin-top: 10px 20px 0px 0px;
  padding: 7px 15px 7px 15px;
  background-color: black;
  color: white;
  border: none;
}

.closeWriteDiary {
  float: right;
  background: none;
  border: none;
  font-size: 20px;
}

.submitButton {
  margin-top: 20px;
  width: 400px;
  height: 40px;
  font-size: 20px;
}

.deleteButton {
  font-size: 15px;
  padding: 3px 10px 3px 10px;
  margin-right: 10px;
  margin-top: 10px;
  background-color: rgb(211, 211, 211);
}

.editButton {
  font-size: 15px;
  padding: 3px 10px 3px 10px;
  margin-right: 30px;
  margin-top: 10px;
  background-color: rgb(211, 211, 211);
}

 

 

'개인 공부 > 게시판' 카테고리의 다른 글

9. 페이지네이션  (0) 2024.02.19
7. 페이지 구성  (0) 2024.02.13
6. 모달 창 구현  (0) 2024.02.10
5. 카테고리, 작성 날짜  (0) 2024.01.31
4. EditDiary  (0) 2024.01.29