Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[첫주차 팀프로젝트] Members 페이지 만들면서 겪은 문제점들과 그 해결법 1 본문

프론트엔드 부트캠프

[첫주차 팀프로젝트] Members 페이지 만들면서 겪은 문제점들과 그 해결법 1

heyday2024 2024. 10. 4. 21:15

우선 앞서 올린 게시물처럼 나는 Members 페이지를 만드는 역할을 담당했다.

간단하게 만들어진 와이어프레임은 아래와 같다.

정리하자면, 내가 해야할 일은

1) firestore에 저장된 데이터를 가져와서 bootstrap을 이용한 보기 좋은 디자인을 가진 cards 생성하기

2) 각각의 카드 클릭 시 각 맴버들의 상세 설명 내용 보여주기(이 데이터들도 firestore에 저장되어있음)

3) delete 버튼 클릭 시 삭제 확인을 다시 묻는 모달창 생성하기

4) 입력한 비밀번호와 저장된 비밀번호가 같은 경우 관련데이터를 firestore에서 삭제하고, 해당 카드도 삭제하기

5) 비밀번호가 틀리면 Members_delete_failed, 맞으면 Mebers_delete_successed 모달창 보여주기

 

과정을 요약하자면, 각각의 popup 창과 modal 그리고 카드의 배치와 디자인을 위해 html, css를 이용해서 각 요소를 만들어 놓고 버튼, 데이터를 불러오는 기능들을 하나씩 js파일에 코드를 추가하여 만들었다.

알록달록 아름답다 ㅎ.ㅎ

 

결과 코드들....(html, css, js 순/ 기본 defaultStyles.css는 공통 css 내용이라 올리지 않음.)

 

    <html lang="ko"></html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>삼조 맛집 맴버들</title>
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link
            href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
            rel="stylesheet"> 
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">                                                                                                                                                                                                                        
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
        <link rel="stylesheet" href="./styles.css">
        <link rel="stylesheet" href="./defaultStyles.css">
    </head>

    <body>
        <!-- 네비게이션 -->
        <header class="navbar">
            <div>
                <img class="logo" src="./../images/logo.png" alt="삼조 맛집 로고">
            </div>
            <nav class="menuNav">
                <div id="menuUnderLine"></div>
                <ul class="menuUl">
                    <li class="menuLi"><a href="../../index.html">Team</a></li>
                    <li class="menuLi"><a href="members.html">Members</a></li>
                    <li class="menuLi"><a href="./../join/join.html">Join</a></li>
                </ul>
            </nav>
            <hr class="hr1">
        </header>
        <!-- 네비게이션 -->

        <!-- 콘텐츠 -->
        <div class="container">
            <div class="row" id="memCard">
                <!-- <div class="col col-md-4">
                    <div class="card">
                        <img
                            src="https://i.namu.wiki/i/R0AhIJhNi8fkU2Al72pglkrT8QenAaCJd1as-d_iY6MC8nub1iI5VzIqzJlLa-1uzZm--TkB-KHFiT-P-t7bEg.webp"
                            class="card-img-top"
                            alt="..."
                        />
                        <div class="card-body">
                            <div class="textArea">
                                <h5 class="card-name">테스트</h5>
                                <div class="material-symbols-outlined" id="deleteBtn">delete</div>
                            </div>
                            <h7 class="card-position">테스트</h7>
                        </div>
                    </div>
                </div> -->
            </div>    
        </div>
        <!-- 콘텐츠 -->

        <!-- 팝업창 -->
        <div class="overlay" id="popUp" style="display: none;">
        <div class="popup-box">
            <div class="photo">
                <img src="" class="popUpPhoto" alt="프로필 사진 이미지">
            </div>
            <div class="popUpCloseBtn material-symbols-outlined">
                    close
            </div>
            <div class="introArea">
                <div class="compound">
                    <div class="popUpTitle">이름</div>
                    <div class="bold memName"></div>
                </div>                
                <div class="compound">
                    <div class="popUpTitle">MBTI</div>
                    <div class="bold memMbti"></div>
                </div>
                <div class="compound">
                    <div class="popUpTitle">좋아하는 것</div>
                    <div class="bold memFavorites"></div>  
                </div>        
                <div class="compound">
                    <div class="popUpTitle">깃헙</div>
                    <a class="bold memGithub"></a>
                </div>                 
                <div class="compound">
                    <div class="popUpTitle">블로그</div>
                    <a class="bold memBlog"></a>
                </div>                
                <div class="compound">
                    <div class="popUpTitle">장점</div>
                    <div class="bold memMerit"></div>
                </div>

                <div class="compound">
                    <div class="popUpTitle">협업스타일</div>
                    <div class="bold memStyle"></div>
                </div>                             
            </div>
        </div>
        </div>
        <!-- 팝업창 -->

        <!-- 삭제 모달창-->
        <div class="overlay" id="member-delete" style="display: none;">
            <div class="modal">
                <div class="delete-title">정말 삭제하시겠습니까?</div>
                <div class="delete-description">맴버카드 삭제를 원하시면, 맴버 가입 시 생성했던 본인의 비밀번호를 입력해주시고, 삭제하기 버튼을 눌러주세요.</div>
                <div class="inputArea">
                    <div class="pw-name">비밀번호</div>
                    <input type="password" class="input-pw" placeholder="비밀번호를 입력해주세요." id="password">
                </div>
                <div class="btnArea">
                    <button class="deleteCardBtn yellowBtn">삭제하기</button>
                    <button class="cancelBtn1 cancelBtn">취소하기</button>
                </div>
            </div>
        </div>
        <div class="overlay" id="member-delete-successed" style="display: none;">
            <div class="modal" >
                <div class="shape">
                    <div class="result-title">맴버카드가 성공적으로</div>
                    <div class="result-title">삭제되었습니다!</div>
                </div>
                <button class="okayBtn yellowBtn">확인</button>
            </div>
        </div>
        <div class="overlay" id="member-delete-failed"  style="display: none;">
            <div class="modal">
                <div class="shape">
                    <div class="result-title">비밀번호가 틀렸습니다.</div>
                    <div class="result-title">다시 시도해주세요.</div>
                </div>
                <div class="btnArea">
                    <button class="tryAgainBtn yellowBtn">다시 입력하기</button>
                    <button class="cancelBtn2 cancelBtn">취소하기</button>
                </div>
            </div>
        </div>

        <!-- 푸터 -->
        <footer class="footer">
            <img class="gitCat" src="../images/git_cat_60^60.png" alt="Github Logo">
            <a class="footerA" href="https://github.com/heftyCornerstone/samjo-matjib">Team GitHub Link</a>
        </footer>
        <!-- 푸터 -->

        <script src="./script.js"></script>
        <script type="module">
            
            // Firebase SDK 라이브러리 가져오기
            import { initializeApp } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-app.js";
            import {
            getFirestore,
            collection,
            getDocs,
            doc,
            deleteDoc,
            } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-firestore.js";

            // Firebase 구성 정보 설정
            const firebaseConfig = {
				보안상 지움...
            };

            // Initialize Firebase
            const app = initializeApp(firebaseConfig);
            const db = getFirestore(app);

            async function loadMembers() {
            // firebase에서 데이터 가져오기
            let docs = await getDocs(collection(db, "members"));

            docs.forEach((docInfo) => {
                let row = docInfo.data();
                let name = row["name"];
                let photo = row["photo"];
                let mbti = row["mbti"];
                let merit = row["merit"];
                let coStyle = row["coStyle"];
                let fav = row["favorites"];
                let blog = row["blog"];
                let github = row["github"];
                const pw = row["pw"];

                let position = name === "박산하" ? "팀장" : "팀원"; // 팀장 조건

                // HTML 문자열 생성
                let temp_html = `
                <div class="col col-md-4">
                    <div class="card">
                    <img src="${photo}" class="card-img-top" alt="${name} 이미지" />
                    <div class="card-body">
                        <div class="textArea">
                        <h5 class="card-name">${name}</h5>
                        <div class="material-symbols-outlined deleteBtn">delete</div>
                        </div>
                        <h7 class="card-position">${position}</h7>
                    </div>
                    </div>
                </div>`;

                // temp_html 맴카드에 추가하기
                let card = $(temp_html).appendTo("#memCard");

                // 카드 클릭 시 팝업창에 데이터 삽입
                card.find(".card-img-top").on("click", function () {
                // 팝업창에 데이터 삽입
                $(".memName").text(name);
                $(".popUpPhoto").attr("src", photo);
                $(".memMbti").text(mbti);
                $(".memFavorites").text(fav);
                $(".memGithub").text(github);
                $(".memBlog").text(blog);
                $(".memGithub").attr("href", github);
                $(".memBlog").attr("href", blog);
                $(".memMerit").text(merit);
                $(".memStyle").text(coStyle);

                // 팝업창 서서히 자연스럽게 보이게하기
                $("#popUp").fadeIn();
                });

                // 닫기 버튼 클릭 시 자연스럽게 팝업창 닫기
                $(".popUpCloseBtn")
                .off("click")
                .on("click", function () {
                    $("#popUp").fadeOut();
                });

                // delete 기능 구현
                card.find(".deleteBtn").on("click", async function () {
                // 모달을 표시
                $("#member-delete").css("display", "flex");

                // 삭제 확인 모달에서 삭제 버튼 클릭 시
                $(".deleteCardBtn").on("click", async function () {
                    const enteredPassword = $("#password").val(); // 입력한 비밀번호 가져오기

                    if (enteredPassword === pw) {
                    const docId = docInfo.id;
                    try {
                        await deleteDoc(doc(db, "members", docId));

                        card.remove();

                        // 삭제 성공 모달 표시
                        $("#member-delete").css("display", "none");
                        $("#member-failed").css("display", "none");
                        $("#member-delete-successed").css("display", "flex");
                    } catch (error) {
                        console.error("에러메세지: ", error);
                        alert("카드 삭제에 실패했습니다.");
                    }
                    } else {
                    // 비밀번호가 틀린 경우 실패 모달을 띄움
                    $("#member-delete-successed").css("display", "none");
                    $("#member-delete").css("display", "none");
                    $("#member-delete-failed").css("display", "flex");
                    }
                });

                // 취소1 버튼을 눌렀을 때 삭제 확인 모달 닫기
                $(".cancelBtn1").on("click", function () {
                    $("#member-delete").css("display", "none");
                });

                // 삭제 성공 모달에서 확인 버튼을 눌렀을 때
                $(".okayBtn").on("click", function () {
                    $("#member-delete-successed").css("display", "none");
                });

                // 삭제 실패 모달에서 다시 입력 버튼을 눌렀을 때
                $(".tryAgainBtn").on("click", function () {
                    $("#member-delete-failed").css("display", "none");
                    $("#member-delete").css("display", "flex");
                });

                // 실패 모달에서 취소 버튼을 눌렀을 때
                $(".cancelBtn2").on("click", function () {
                    $("#member-delete-failed").css("display", "none");
                });
                });
            });
        }

        // 비동기 함수 호출
        loadMembers().catch(console.error);
        // 호출 시 문제 생기면 에러 확실히 알기 위해 catch 사용함...

        </script>
    </body>
</html>

 

/*콘텐츠*/

/* bootstrap 과 기본 css 스타일링 충돌 해결 위함. */
header {
  display: block !important;
}

.container {
  display: flex;
  align-items: center;
  padding: 30px 0;
  width: 50%;
  justify-content: center;
}

.card {
  width: auto;
  margin-bottom: 40px; /* 줄 간격을 위해 아래 여백 추가 */
  border-radius: 10px;
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
  /* 그림자 추가 */
}
.card-img-top {
  height: 250px;
  object-fit: cover;
  /* 사진 사이즈를 일정하게 하고 싶었음 */
  border-radius: 10px 10px 0 0;
  /* 사진 윗부분만 뭉툭하게 만들었음 */
  cursor: pointer;
}

.textArea {
  display: flex;
  justify-content: space-between;
}

h5 {
  font-weight: 700;
}

.deleteBtn {
  cursor: pointer;
}
.deleteBtn:hover {
  color: red;
}
/*콘텐츠*/

/* /////////////////////////////////////////////////////// */
/* 여기부터 popup창 스타일링입니다요~~ ////////////////////*/
/* //////////////////////////////////////////////////////// */
/* 다크 배경 및 팝업 스타일 */
.overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  /* visibility: hidden;  */
  /* 기본으로 보이지 않게 설정 */
}

.popup-box {
  display: flex;
  flex-direction: row;
  background-color: white;
  padding: 0;
  width: 1000px;
  border-radius: 16px;
  height: 500px;
  position: relative;
}

.popUpPhoto {
  height: 100%;
  width: 350px;
  object-fit: cover;
  border-radius: 16px 0 0 16px;
  /* 사진 윗부분만 뭉툭하게 만들었음 */
}

.introArea {
  /* background-color: aqua; */
  margin: 50px 15px 30px 20px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  overflow: auto;
}

.compound {
  display: flex;
  flex-direction: column;
  margin-bottom: 30px;
  margin-right: 15px;
  gap: 5px;
}

.popUpTitle {
  font-size: 16px;
  color: rgb(152, 152, 152);
}
.bold {
  font-size: 18px;
  font-weight: 700;
}

.popUpCloseBtn {
  position: absolute;
  right: 8px;
  top: 10px;
  cursor: pointer;
}

/* 스크롤바 디자인 */
.introArea::-webkit-scrollbar {
  width: 10px;
}
.introArea::-webkit-scrollbar-thumb {
  background-color: #f9e33a;
  border-radius: 10px;
  cursor: pointer;
}
.introArea::-webkit-scrollbar-track {
  background-color: rgb(128, 128, 128);
  border-radius: 10px;
  box-shadow: inset 0px 0px 5px white;
}

/* //////////////////////////////////////////////////////////////////////////////// */
/* Members_delete 삭제 모달창 스타일링///////////////////////////////////////////////*/
/* //////////////////////////////////////////////////////////////////////////////// */

.modal {
  display: flex;
  flex-direction: row;
  background-color: white;
  padding: 0;
  width: 600px;
  border-radius: 16px;
  height: 500px;
  position: relative;
  flex-direction: column;
  padding: 50px;
  justify-content: center;
  align-items: center;
  gap: 50px;
}

.delete-title {
  font-weight: 700;
  font-size: 30px;
}

.delete-description {
  font-size: 18px;
}
.inputArea {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.input-pw {
  height: 40px;
  width: 100%;
  border-radius: 10px;
  border: solid 3px #f9e33a;
  text-indent: 10px;
}
.input-pw:focus {
  border: solid 3px rgb(62, 62, 248);
}

.btnArea {
  width: 100%;
  display: flex;
  justify-content: space-around;
}

.yellowBtn {
  font-weight: 700;
  padding: 15px 40px 15px 40px;
  background-color: #f9e33a;
  border: none;
  border-radius: 10px;
}
.cancelBtn {
  padding: 15px 40px 15px 40px;
  background-color: #d3d3d2;
  border: none;
  border-radius: 10px;
  font-weight: 700;
}

.shape {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.result-title {
  font-size: 30px;
  font-weight: 700;
}

 

// 네비게이션
document.addEventListener("DOMContentLoaded", () => {
  const underLine = document.getElementById("menuUnderLine");
  const menuItems = document.querySelectorAll(".menuUl .menuLi a");

  // 언더라인 위치 업데이트 함수
  const updateUnderLine = (item) => {
    const left =
      item.getBoundingClientRect().left -
      item.parentElement.getBoundingClientRect().left;
    const width = item.offsetWidth;
    const itemHeight = item.offsetHeight;

    underLine.style.width = `${width}px`;
    underLine.style.transform = `translate(${left}px, ${itemHeight - 2}px)`; // 언더라인을 텍스트 바로 아래로 이동
    underLine.style.visibility = "visible";
  };

  // 현재 URL에 맞는 메뉴 항목을 활성화시키는 함수
  const setActiveMenu = () => {
    const currentPage = window.location.pathname;

    menuItems.forEach((link) => {
      const menuItem = link.parentElement; // <li> 요소

      // 현재 URL이랑 일치하는 메뉴에 active 상태 적용
      if (currentPage.endsWith(link.getAttribute("href"))) {
        link.classList.add("active");
        link.style.color = "black"; // 활성화된 메뉴는 검정색
        updateUnderLine(menuItem); // 언더라인 위치 업데이트
      } else {
        link.classList.remove("active");
        link.style.color = "#7d7d7d"; // 비활성화된 메뉴는 회색
      }
    });
  };

  // 메뉴 클릭 시 언더라인과 색상 업데이트
  menuItems.forEach((link) => {
    const menuItem = link.parentElement; // <li> 요소

    link.addEventListener("click", (e) => {
      e.preventDefault(); // 페이지 이동 중단

      // 모든 메뉴의 active 상태를 해제하고 색상을 초기화
      menuItems.forEach((el) => {
        el.classList.remove("active");
        el.style.color = "#7d7d7d"; // 비활성화된 메뉴는 회색
      });

      // 클릭한 메뉴를 active 상태로 만들고 언더라인 이동
      link.classList.add("active");
      link.style.color = "black"; // 클릭한 메뉴는 검정색
      updateUnderLine(menuItem);

      // 페이지 이동
      setTimeout(() => {
        window.location.href = link.getAttribute("href");
      }, 300); // 애니메이션 끝나고 이동
    });
  });

  // 페이지 로드 시 초기 메뉴 설정
  setActiveMenu();

  // 언더라인 애니메이션 비활성화 (초기화 방지)
  underLine.style.transition = "none";

  // 페이지 로드 완료 후 애니메이션 활성화
  setTimeout(() => {
    underLine.style.transition = "width 0.3s ease, transform 0.3s ease";
  }, 100); // 로드 후 일정 시간 뒤 애니메이션 활성화

  // 브라우저 크기 변경 시 언더라인 재조정
  window.addEventListener("resize", () => {
    const activeLink = document.querySelector(".menuLi a.active");
    if (activeLink) {
      updateUnderLine(activeLink.parentElement);
    }
  });
});
// 네비게이션

 


 

코딩하다 생긴 문제점과 해결방안 모음....

(이게 다 살이되고 피가되는 것 아니겠습니까? ㅎ.ㅎ)

 

1. bootstrap cards 와 grid

    • 부트스트랩에서 cards 가져와 쓰고, cards가 여러개 생성될 것을 생각해서 display에 flex 값을 주고 줄바꿈 처리를 했다 (wrap). 요소들의 양쪽 끝 여백과 위아래 여백을 이쁘게 디자인 하기 위해 justify-content, align-items에 center를 주고 아 이쁘다 하고 있던 중...  줄바꿈이 일어날 떄 조금 어색한 배치가 되어있던 것을 발견했다.

내가 생각한 느낌의 줄바꿈

 

 

This is 현실..

 

    • 쉽고 간결한 방법으로 해결하고 싶어서 부트스트랩의 gird 스타일링을 가져와 적용시켰다. 
      • Grid? Grid(그리드)는 웹 레이아웃을 만들기 위한 시스템으로, 페이지를 일정한 칸으로 나누어 요소들을 정렬하고 배치하는 방법. 
      • Bootstrap의 Grid 시스템은 12개의 열로 이루어진 구조를 기반으로 하며, 이를 통해 다양한 크기의 기기에서 레이아웃을 자동으로 조정할 수 있음. 그리드는 행(row)과 열(column)로 구성되며, 각 열은 총합이 12가 되도록 크기를 나눌 수 있음.
        <div class="row" id="memCard">
                        <!-- <div class="col col-md-4">
                            <div class="card">
                                <img
                                    class="card-img-top"
                                    alt="..."
                                />
                                <div class="card-body">
                                    <div class="textArea">
                                        <h5 class="card-name">테스트</h5>
                                        <div class="material-symbols-outlined" id="deleteBtn">delete</div>
                                    </div>
                                    <h7 class="card-position">테스트</h7>
                                </div>
                            </div>
                        </div> -->
                    </div>
  •  

https://getbootstrap.com/docs/3.4/css/#grid

 

CSS · Bootstrap

For basic styling—light padding and only horizontal dividers—add the base class .table to any . It may seem super redundant, but given the widespread use of tables for other plugins like calendars and date pickers, we've opted to isolate our custom tab

getbootstrap.com

  • 한 줄에 카드 세 개씩으로 정하고, bootstrap grid 스타일링을 가져다가 사용함으로 문제 해결!!!!

3줄씩 참 정갈함 ㅎ.ㅎ ㅎ.ㅎ

 

2. bootstrap 충돌 문제

  • cards를 위해 부트스트랩을 사용했고, github에서 다른 팀원 분들이 작업한 코드를 가져오니(pull) 그 팀원분이 짠 css 코드와 나의 bootstrap 스타일링 코드 사이에 충돌이 일어났다...
  •  위의 부트스트랩 cdn 링크 태그를 지우면 그 분의 스타일링이 적용되었지만, 나의 cards는 더 이상 카드가 아니게 되버렸고, 반대로 cdn 링크 태그를 냅두면 header 의 네비게이션 바(팀원분이 짠 스타일링 코드)가 이상한 배치가 되어있었다.
  • 검색을 해보니 부트스트랩과 css 스타일 간의 충돌은 크게 3가지와 같은 이유로 발생할 수 있었다.
    • 1) css 우선순위 차이: 특정 css 선택자의 우선 순위가 부트스트랩에서 정의한 스타일보다 낮으면, 부트스트랩의 스타일이 적용되어 사용자 정의 css가 무시될 수 있음 --> 이 경우 더 높은 우선순위를 가진 선택자를 사용하거나 !important를 사용해 우선순위 높일 수 있음.
    • 2) CSS 리셋: 부트스트랩은 HTML 요소에 기본 스타일을 설정하거나 초기화하는 CSS 리셋을 포함하기 떄문에 사용자 정의 CSS가 의도한대로 적용되지 않을 수 있음.
    • 3) 미디어 쿼리: 부트스트랩은 반응형 디자인을 지원하기 위해 다양한 화면 크기에서 작동하는 미디어 쿼리를 사용. 특정 화면 크기에서 사용자 정의 CSS가 부트스트랩의 미디어 쿼리로 덮어씌워질 수 있음.
  • 팀원분들과 함께 이 문제에 대해서 의논하고, 개발자 도구를 이용해서 문제의 원인을 찾아보았다. 문제의 원인은 CSS 우선순위 차이 였다. 부트스트랩을 사용하면, navigation 바의 display: block 부분이 무시되고 있었던 것이다.
  • Solution: 팀장님의 도움으로 display:block 을 찾아냈고, display: block !important;를 적용시켜서 문제를 해결했다. 

navigation 바의 배치가 잘 되었고, 내가 만든 카드들이 잘 보인다.

 

3. Firebase deletedoc 문제

  • 생각보다 스타일링에 시간이 더 들고 어려웠다... 의외로 js파일에서 firebase를 이용해 데이터 정보를 가져오고 사용하는 것은 어렵지 않았다.
  • 그. 러. 나. 카드를 삭제하는 로직을 짜는 데 문제가 생겼다. deleteDoc 함수를 써서 삭제하려는 카드의 document ID를 가져와서 해당 데이터를 firestore에서 삭제하는 이 개념자체는 어렵지 않았고, 코드도 알맞게 짰다고 생각했다. 하지만, 작동이 안되었다. (근데 왜 안되냐고????!!!!!) 
 // Firebase SDK 라이브러리 가져오기
            import { initializeApp } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-app.js";
            import {
            getFirestore,
            collection,
            getDocs,
            doc,
            deleteDoc,
            } from "https://www.gstatic.com/firebasejs/10.14.0/firebase-firestore.js";

위 처럼, 분명 import로 삭제 시 필요한 함수인 deleteDoc, doc, collection 모두 잘 가져왔다.

 

 

아래의 코드는 수정 전 코드다. 딱 하나의 변수만 바꿨다. 

무엇이 문제였을까?

 // Initialize Firebase
            const app = initializeApp(firebaseConfig);
            const db = getFirestore(app);

            async function loadMembers() {
            // firebase에서 데이터 가져오기
            let docs = await getDocs(collection(db, "members"));

            docs.forEach((doc) => {
                let row = doc.data();
                let name = row["name"];
                let photo = row["photo"];
                let mbti = row["mbti"];
                let merit = row["merit"];
                let coStyle = row["coStyle"];
                let fav = row["favorites"];
                let blog = row["blog"];
                let github = row["github"];
                const pw = row["pw"];

                let position = name === "박산하" ? "팀장" : "팀원"; // 팀장 조건

                // HTML 문자열 생성
                let temp_html = `
                <div class="col col-md-4">
                    <div class="card">
                    <img src="${photo}" class="card-img-top" alt="${name} 이미지" />
                    <div class="card-body">
                        <div class="textArea">
                        <h5 class="card-name">${name}</h5>
                        <div class="material-symbols-outlined deleteBtn">delete</div>
                        </div>
                        <h7 class="card-position">${position}</h7>
                    </div>
                    </div>
                </div>`;

                // temp_html 맴카드에 추가하기
                let card = $(temp_html).appendTo("#memCard");

                // 카드 클릭 시 팝업창에 데이터 삽입
                card.find(".card-img-top").on("click", function () {
                // 팝업창에 데이터 삽입
                $(".memName").text(name);
                $(".popUpPhoto").attr("src", photo);
                $(".memMbti").text(mbti);
                $(".memFavorites").text(fav);
                $(".memGithub").text(github);
                $(".memBlog").text(blog);
                $(".memGithub").attr("href", github);
                $(".memBlog").attr("href", blog);
                $(".memMerit").text(merit);
                $(".memStyle").text(coStyle);

                // 팝업창 서서히 자연스럽게 보이게하기
                $("#popUp").fadeIn();
                });

                // 닫기 버튼 클릭 시 자연스럽게 팝업창 닫기
                $(".popUpCloseBtn")
                .off("click")
                .on("click", function () {
                    $("#popUp").fadeOut();
                });

                // delete 기능 구현
                card.find(".deleteBtn").on("click", async function () {
                // 모달을 표시
                $("#member-delete").css("display", "flex");

                // 삭제 확인 모달에서 삭제 버튼 클릭 시
                $(".deleteCardBtn").on("click", async function () {
                    const enteredPassword = $("#password").val(); // 입력한 비밀번호 가져오기

                    if (enteredPassword === pw) {
                    const docId = docInfo.id;
                    try {
                        await deleteDoc(doc(db, "members", docId));

                        card.remove();

                        // 삭제 성공 모달 표시
                        $("#member-delete").css("display", "none");
                        $("#member-failed").css("display", "none");
                        $("#member-delete-successed").css("display", "flex");
                    } catch (error) {
                        console.error("에러메세지: ", error);
                        alert("카드 삭제에 실패했습니다.");
                    }
                    } else {
                    // 비밀번호가 틀린 경우 실패 모달을 띄움
                    $("#member-delete-successed").css("display", "none");
                    $("#member-delete").css("display", "none");
                    $("#member-delete-failed").css("display", "flex");
                    }
                });

                // 취소1 버튼을 눌렀을 때 삭제 확인 모달 닫기
                $(".cancelBtn1").on("click", function () {
                    $("#member-delete").css("display", "none");
                });

                // 삭제 성공 모달에서 확인 버튼을 눌렀을 때
                $(".okayBtn").on("click", function () {
                    $("#member-delete-successed").css("display", "none");
                });

                // 삭제 실패 모달에서 다시 입력 버튼을 눌렀을 때
                $(".tryAgainBtn").on("click", function () {
                    $("#member-delete-failed").css("display", "none");
                    $("#member-delete").css("display", "flex");
                });

                // 실패 모달에서 취소 버튼을 눌렀을 때
                $(".cancelBtn2").on("click", function () {
                    $("#member-delete-failed").css("display", "none");
                });
                });
            });
        }

        // 비동기 함수 호출
        loadMembers().catch(console.error);
        // 호출 시 문제 생기면 에러 확실히 알기 위해 catch 사용함...

 

  • 나는 아주 기초적이고, 바보같은 실수를 한 것이다. 바로, doc이라는 함수명과 doc이라는 변수명이 같다. 이 두 doc은 같은 스코프 내에서 선언되어서 예기치 못한 결과를 만들었다. (즉, 이 코드에서 컴퓨터는 doc을 함수로 받아드린 것이 아닌 변수로 해석한 것이다.)
  • Solution: 그래서 나는 변수 doc을 docInfo로 바꿨고, 바꾸자마자 보기 좋게 로직을 잘 작동되었다.
  • error가 났을 때 무엇이 원인인지를 컴퓨터가 알려주면, 디버깅하는데 훨씬 도움이 많이 된다. 그래서 나는 try ~ catch 구문으로 어떤 에러가 났는지 확인을 하면서 이 문제를 해결했다. 또한 함수 중간 중간 alert 값을 주어 어디부터 함수가 실행이 안되는 지도 파악했다. 
    • 참고로 try-catch 구문은 예외처리를 위한 구조.
      • try 블록
        • 이 안에 작성된 코드는 오류가 발생할 가능성이 있는 코드. try 블록 내의 코드에서 오류가 발생하면 즉시 catch 블록으로 이동하며, 이후의 코드는 실행되지 않음.
      • catch 블록
        • try 블록에서 오류가 발생했을 때 실행되는 코드. catch 블록에는 오류 객체가 자동으로 전달되며, 오류에 대한 정보를 확인할 수 있음.
        • 오류 객체(error)에는 name(오류의 종류)과 message(오류 메시지) 같은 속성들이 포함됨.
      • finally 블록 (선택사항)
        • finally 블록은 try 또는 catch 블록이 끝난 후 항상 실행됨. 오류 발생 여부와 관계없이 실행되므로, 리소스 정리(예: 파일 닫기, 연결 종료 등)에 자주 사용.

 

나머지 문제는 다음 TIL 때 이어서 작성하겠음...