Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2주차 기본/심화 과제] 루밍이의 가계부 💸 #5

Merged
merged 52 commits into from
Nov 13, 2023

Conversation

Arooming
Copy link
Contributor

@Arooming Arooming commented Oct 27, 2023

✨ 구현 기능 명세

🧩 기본 과제

  • 최초 데이터

    1. 상수로 INIT_BALANCE, HISTORY_LIST 데이터를 가집니다. (상수명은 자유)

      • INIT_BALANCE = 0
      • HISTORY_LIST : 입출금 내역 리스트 (4개)
    2. 최초 실행시 위 상수 데이터들 이용해 렌더링합니다. (즉, html에 직접 박아놓는 하드코딩 ❌)

      → 나의 자산은 INIT_BALANCE로부터 4개의 입출금 내역 리스트를 반영하여 보여줍니다.

  • 총수입 / 총지출

    1. 마찬가지로 최초에 HISTORY_LIST에 있는 수입 내역과 지출 내역을 계산해서 총수입, 총지출을 보여줍니다.
  • 수입/지출 필터링

    1. 수입, 지출 선택에 따라 내역 리스트가 필터링됩니다.
  • 리스트 삭제

    1. 각 리스트의 X 버튼을 누르면 해당 리스트가 삭제됩니다.
    2. 리스트 삭제시 나의 자산에 반영됩니다.
    3. 리스트 삭제시 총 수입 / 총 지출에 반영됩니다.
  • 리스트 추가

    하단 footer의 + 버튼을 누르면 리스트 추가 모달이 나타납니다.

    1. 수입 지출 버튼
      • default ⇒ 수입
      • 하나를 선택하면 다른 항목은 자동으로 선택이 풀립니다.
    2. 카테고리를 선택
      • 수입을 선택하면 수입 관련된 항목들이, 지출을 선택하면 종류에 지출 관련된 항목들이 나옵니다.
      • 카테고리는 수입, 지출 각각 2개 이상씩 있습니다.
    3. 금액내용 입력 input
    4. 저장하기 버튼
      • 저장하기 버튼 클릭시 입력한 내용들로 이뤄진 리스트가 추가됩니다.
      • 이에 따라 나의 자산(잔액), 총수입, 총지출도 알맞게 변경됩니다.
      • 저장 성공시 alert 메시지를 띄웁니다.
      • 저장하기를 눌러도 모달이 닫히지 않습니다.
    5. 닫기 버튼
      • 클릭하면 모달이 사라집니다.

🧩 심화 과제

  • 리스트 삭제 모달

    1. x 버튼 클릭시 삭제 모달이 나타납니다.

      클릭시 삭제를 진행합니다.

      취소 클릭시 모달이 사라집니다.

  • 리스트 추가

    1. 카테고리, 금액, 내용 중 입력하지 않은 항목이 있는데 저장하기를 누른 경우, alert를 띄워 막습니다.
    2. 금액에 숫자가 아닌 문자를 입력시 alert를 띄워 막습니다.
  • 모달 백그라운드 & 애니메이션 (삭제, 추가)

    1. 백그라운드 : 모달 외부 부분을 어둡게 처리합니다.
    2. 애니메이션 : + 클릭시 추가 모달이 아래에서 위로 올라옵니다.
  • 카테고리 추가

    **localStorage**를 활용합니다.

    1. 상수로 최초 카테고리를 저장한 후, 렌더링시 추가 모달의 드롭다운 option들을 동적으로 렌더링합니다.
    2. 우측 하단 버튼을 누르면 <카테고리 관리> 페이지로 이동합니다.
    3. 수입 카테고리와 지출 카테고리에 현재 카테고리들이 있습니다.
    4. input 작성 후 Enter키를 누르면 카테고리가 추가됩니다.
    5. 다시 home으로 돌아가서 내역 추가 모달을 키면 option에 새로운 카테고리가 추가되어 있습니다.
    6. 새로고침을 해도 카테고리는 계속해서 유지됩니다.
  • 금액

    1. 모든 금액에 세개 단위로 , 로 표시됩니다. (나의 자산, 총수입/지출, 내역 리스트, 리스트 추가 input)

💎 PR Point

🍟 최초 데이터

  1. 최초 데이터를 상수로 저장해주었습니다. (html에 박아놓는 하드코딩 ❌)
const HISTORY_LIST = [
  {
    id: 1,
    category: "과외비",
    place: "10월 월급",
    history: 500000,
  },
  {
    id: 2,
    category: "식비",
    place: "닭도리탕 문정역점",
    history: -30000,
  },
  {
    id: 3,
    category: "용돈",
    place: "용돈 획득",
    history: 100000,
  },
  {
    id: 4,
    category: "식비",
    place: "버거킹 햄버거 단품",
    history: -10000,
  },
];

🍟 수입/ 지출 필터링

  1. 수입, 지출 내역을 필터링 하는 함수를 만들어주었습니다.
  2. 매개변수만 다르게 넣어주면 하나의 함수로 수입과 지출 모두에 적용할 수 있도록 구현했습니다.
// 수입, 지출 필터링 함수
function filterList(checkbox, notFilteredList) {
  notFilteredList.forEach((element) => {
    element.parentNode.style.display =
      checkbox.checked === true ? "flex" : "none";
  });
}

// 수입 필터링
incomeBox.addEventListener("click", () => {
  const incomeList = accountArticle.querySelectorAll(".income.history");
  filterList(incomeBox, incomeList);
});

// 지출 필터링
expensesBox.addEventListener("click", () => {
  const expensesList = accountArticle.querySelectorAll(".expenses.history");
  filterList(expensesBox, expensesList);
});

🍟 리스트 추가

  1. 체크된 값이 수입인지, 지출인지에 따라 다른 객체를 생성해주었습니다.
  2. input 값 중 하나라도 값이 들어오지 않았다면, alert를 띄워주었습니다.
const newObj = checkedInput === "income" ? {
    id: HISTORY_LIST.length + 1,
    category: selectedOption,
    place: additionalContents,
    history: Number(additionalPrice),
  } : {
    id: HISTORY_LIST.length + 1,
    category: selectedOption,
    place: additionalContents,
    history: -additionalPrice,
  };

  saveNewList(newObj);
  calcMyAccount();
// 추가 리스트를 저장하는 함수
function saveNewList(newObj) {
  if (newObj.place.length === 0 || newObj.history === 0) {
    alert("모든 칸을 채워주세요.");
  } else {
    HISTORY_LIST.push({ ...newObj });
    createList({ ...newObj });
    alert("저장 성공!");
  }
}

🍟 리스트 삭제 모달

  1. 클릭한 리스트의 금액 값을 배열에 넣어주었습니다. 이 값을 수입/ 지출 값에서 빼는 방식으로 화면에서 자산이 달라지도록 구현했습니다.
  2. 모달 내부 삭제 버튼 클릭 시, 모달이 사라지고 화면에 보이는 자산이 달라지며 기존에 있던 리스트를 삭제해주었습니다.
  3. 모달 내부 취소 버튼 클릭 시, 모달이 사라지고 클릭한 금액의 값이 담겨있는 배열에 해당 값을 넣는 방식으로 구현했습니다.
delBtn.addEventListener("click", () => {
      const modal = document.querySelector(".modal.delete");
      const listDelBtn = modal.querySelector(".listDelBtn");
      const cancelBtn = modal.querySelector(".cancelBtn");
      const clickedHistory = li.querySelector(".history");
      let clickedArr = [];

      displayModal(modal);

      // "삭제" 클릭 시, 모달이 화면에서 사라지고 "x" 버튼 클릭했던 리스트 삭제
      listDelBtn.addEventListener("click", () => {
        deleteModal(modal);
        reflectAccount(clickedArr, clickedHistory);
        li.remove();
      });

      // "취소" 클릭 시, 모달만 화면에서 사라짐
      cancelBtn.addEventListener("click", () => {
        deleteModal(modal);
        clickedArr.push(clickedHistory.innerHTML);
      });
// 자산을 화면에 반영하는 함수
function reflectAccount(clickedArr, clickedHistory) {
  clickedArr.push(clickedHistory.innerHTML);
  if (clickedArr.length === 1) {
    // 계산할 수 있도록 "," 빼줌
    const clickedNum = clickedArr[0].replaceAll(",", "");
    INIT_BALANCE -= clickedNum;

    clickedNum < 0 ? (expenses -= clickedNum) : (income -= clickedNum);
  }

  incomeValue.innerHTML = income.toLocaleString();
  expensesValue.innerHTML = Math.abs(expenses).toLocaleString();
  assetValue.innerHTML = INIT_BALANCE.toLocaleString();
}

🥺 소요 시간, 어려웠던 점

  • 5 ~ 6h
  • 리스트의 x 버튼을 눌렀을 때 제대로 동작하게 만드는 게 꽤 어려웠던 것 같아요!
  • 지금의 방법이 최선은 절대 아니라고 생각해서, 더 나은 방법이 있다면 리뷰 마구마구 남겨주시면 감사하겠습니당!!

🌈 구현 결과물

1.mp4
2.mp4
3.mp4
4.mp4
5.mp4
6.mp4

@Arooming Arooming self-assigned this Oct 27, 2023
@Arooming Arooming changed the title [ 2주차 기본/심화 과제 ] 루밍이의 가계부 💸 [2주차 기본/심화 과제] 루밍이의 가계부 💸 Oct 27, 2023
@ExceptAnyone
Copy link

2주차 과제까지 수고했어 아름! 나는 거의 9일~10일 동안 작업을 진행했는데, 너는 이걸 어떻게 6시간만에 했는지..... 역시 우리 조장님이야. 리스펙이 한가득이다 지금 ㅋㅋㅋㅋ 추후에 같이 리팩토링 진행해보자. 가감없는 피드백 부탁해 :)

Copy link

@moondda moondda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

결국 생일이슈에도 불구하고 심화 다 뿌신 아름이...이 코드 보고 더럽다고 한 걸 보니 내 코드 보고는 혼절하겠다ㅎ 주석 적재적소에 잘 넣은거 넘 좋아용

Comment on lines +315 to +324
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translate3d(0, 100%, 0);
}
100% {
opacity: 1;
transform: translateZ(0);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오홍 이건 모지??

Copy link
Contributor Author

@Arooming Arooming Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하단 모달이 부드럽게 올라오도록 애니메이션을 구현한 부분이야!
0%는 애니메이션의 시작으로, 투명도는 0이고 transform 속성을 활용해서 수직으로 100% 아래로 내려가게 구현했어!
100%는 애니메이션의 종료 지점으로, 투명도는 1이고 transform 속성을 활용해서 요소를 translateZ(0) 상태 요소가 움직이지 않는 상태로 만들어줬어!

아래 .bottomModal에 animation: fadeInUp 0.7s;을 넣어줘서 0.7초동안 해당 에니메이션이 실행되도록 했고,
결국 0.7초동안 0%에서 100%로 가면서 하단 모달이 부드럽게 올라오게 보인다고 생각하면 될 것 같아!


<div id="detailContainer">
<div id="plusContainer">
<p class="detailSign">✚</p>
<p class="detailValue">300,000</p>
<p class="income detailValue"></p>
</div>
<div id="minusContainer">
<p class="detailSign"><b>&nbsp;−&nbsp;</b></p>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기 이렇게 공백으로 align을 맞춰주면 반응형 대응이 정말 어렵고, 나중에 유지 보수할 때도 복잡해질 수 있으니 요런 표현은 사용하지 않는게 좋아 ! by 1주차의 서아름

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b 태그 처음본다 간단하게 쓰기 좋을듯~!

Copy link
Contributor Author

@Arooming Arooming Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기 이렇게 공백으로 align을 맞춰주면 반응형 대응이 정말 어렵고, 나중에 유지 보수할 때도 복잡해질 수 있으니 요런 표현은 사용하지 않는게 좋아 ! by 1주차의 서아름

음 이건 뭔가 align을 맞춘다기 보단 - 기호 주변으로 백그라운드의 너비를 넓히기 위해 공백을 주려고 했던건데
생각해보니까 요것도 그냥 padding으로 넣는게 더 맞겠다 ! 알려줘서 고마옹

Comment on lines +108 to +109
<button class="modalBtn yellowBackground save">저장하기</button>
<button class="modalBtn pinkBackground close">닫기</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save이랑 close 클래스는 css가 없는데???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요거는 css 지정이 아니라, 아래 코드에서 보면 js에서 활용하기 위해서 넣어준 class야 !!

deleteOptions();
});

/* -------------- 함수 모음 -------------- */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 이런 주석 좋아요

const clickedNum = clickedArr[0].replaceAll(",", "");
INIT_BALANCE -= clickedNum;

clickedNum < 0 ? (expenses -= clickedNum) : (income -= clickedNum);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삼항연산자 알차다 알차

Comment on lines +285 to +286
// replaceChildren(): 아무런 파라미터도 넘겨주지 않으면 모든 자식 노드 삭제
categorySelect.replaceChildren();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 나도 이렇게 구현해봐야겠어!!

}

incomeValue.innerHTML = income.toLocaleString();
expensesValue.innerHTML = Math.abs(expenses).toLocaleString();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 절댓값으로 했구나 굳굳!!

Comment on lines +256 to +263
function displayModal(modal) {
modal.style.display = "flex";
}

// 모달을 화면에서 보이지 않게 하는 함수
function deleteModal(modal) {
modal.style.display = "none";
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나는 html코드를 늘리지 않겠다는 이상한 똥고집때문에 모달창 만드는 로직을 아예 다른 파일로 빼서 작업했는데, 이렇게 보니까 이게 훨씬 깔끔해보이네

Copy link
Contributor Author

@Arooming Arooming Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오옹 마자 모달은 큰 의미를 갖는다기 보단 단순 알림창 느낌이니까 화면 상에 보여지는 것만 조절해주면 된다고 생각해서,
모달 만들때 요런 식으로 화면에서 보이게/ 안보이게 구현 해주는 편인 것 같아 !

Comment on lines +310 to +327
if (isNaN(value)) {
alert("숫자를 입력해주세요!");
additionalInputPrice.value = 0;
} else {
const formatValue = value.toLocaleString();
additionalInputPrice.value = formatValue;
}
}

// 추가 리스트를 저장하는 함수
function saveNewList(newObj) {
if (newObj.place.length === 0 || newObj.history === 0) {
alert("모든 칸을 채워주세요.");
} else {
HISTORY_LIST.push({ ...newObj });
createList({ ...newObj });
alert("저장 성공!");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나도 나중에 리팩토링 할거지만, 이 메세지들을 따로 한 곳에 상수화해서 관리하면 더 좋지않을까라는 생각이 들더라고??
추후에 같이 리팩토링에 반영해보자

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 그렇게 하면 더 깔끔하게 정리할 수 있겠다 ! 좋아 !!

Comment on lines +29 to +46
let INIT_BALANCE = 0;
let income = 0;
let expenses = 0;
const incomeValue = document.querySelector(".income.detailValue");
const expensesValue = document.querySelector(".expenses.detailValue");
const accountArticle = document.querySelector(".accountArticle");
const accountUl = document.createElement("ul");
const assetValue = document.querySelector(".assetValue");
const addBtn = document.querySelector("footer button");
const modalCloseBtn = document.querySelector(".modalBtn.close");
const listSaveBtn = document.querySelector(".modalBtn.save");
const modalInput = document.querySelectorAll(".modalInput");
const checkedInputContainer = document.querySelector(".bottomModal");
const additionalInputPrice = checkedInputContainer.querySelector(
".additionalInput.price"
);
const incomeBox = accountArticle.querySelector("#incomeBox");
const expensesBox = accountArticle.querySelector("#expensesBox");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘네도 다른 파일로 따로 빼서 한 곳에 정리해놓으면 조금 더 깔끔해 보일 것 같음 !!

Comment on lines +172 to +177
createdLi.appendChild(createdCategory);
createdLi.appendChild(createdPlace);
createdLi.appendChild(createdHistory);
createdLi.appendChild(createdDelBtn);

accountUl.appendChild(createdLi);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번에는 appendChild 방식으로 해봤으니, 다음번에는 템플릿 형식으로 해보는 것도 좋을듯 ㅎㅎ!!

function deleteOptions() {
const categorySelect = document.querySelector(".categorySelect");
// replaceChildren(): 아무런 파라미터도 넘겨주지 않으면 모든 자식 노드 삭제
categorySelect.replaceChildren();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaceChildren 메서드는 처음보네. 덕분에 또 하나 배워가~

Comment on lines +1 to +26
const HISTORY_LIST = [
{
id: 1,
category: "과외비",
place: "10월 월급",
history: 500000,
},
{
id: 2,
category: "식비",
place: "닭도리탕 문정역점",
history: -30000,
},
{
id: 3,
category: "용돈",
place: "용돈 획득",
history: 100000,
},
{
id: 4,
category: "식비",
place: "버거킹 햄버거 단품",
history: -10000,
},
];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘를 constants.js 같은 파일 만들어서 아래에서 말했던 메세지와 함께 빼면 더 좋을거같애!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어어 마자 지금은 하나의 자스파일에 다 들어가있어서 가독성이 많이 떨어진다고 생각해 !ㅠ
리팩토링 때 반영해볼게 !!

Comment on lines 32 to 62
HISTORY_LIST.map((list) => {
INIT_BALANCE += list.history;

const accountLi = document.createElement("li");
const listCategory = document.createElement("p");
listCategory.innerHTML = list.category;
listCategory.classList.add("category");

const listPlace = document.createElement("p");
listPlace.innerHTML = list.place;
listPlace.classList.add("place");

const listHistory = document.createElement("p");
// toLocaleString(): 숫자 자릿수에 따라 콤마(,) 추가
listHistory.innerHTML = list.history;

// 수입인 경우, "className = income"
// 지출인 경우, "className = expenses"
list.history < 0
? listHistory.classList.add("expenses")
: listHistory.classList.add("income");

// 수입, 지출 모두 공통적으로 "history"라는 클래스 명 부여
listHistory.classList.add("history");

accountLi.appendChild(listCategory);
accountLi.appendChild(listPlace);
accountLi.appendChild(listHistory);

accountUl.appendChild(accountLi);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 일반 반복을 도는 것 같은데, foreach말고 map을 써준 이유가 있을까??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

움 그러게 여기는 배열 순회 후에 새 배열을 얻어야 하는 것도 아니고,
단순히 배열을 순회하는 부분이니까 map()보단 forEach()가 더 적합하겠다 ..!
요 부분 수정해놓을게 !!

Comment on lines 106 to 107
history: -additionalPrice,
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 굉장히 신박하네. 훨씬 간결하고 좋은디??

Comment on lines 204 to 217
function checkedOnlyOne(input) {
const modalIncomeBox = document.getElementById("modalIncomeBox");
const modalExpensesBtn = document.getElementById("modalExpensesBtn");

input.addEventListener("click", () => {
if (input.checked) {
if (input === modalIncomeBox) {
modalExpensesBtn.checked = false;
} else if (input === modalExpensesBtn) {
modalIncomeBox.checked = false;
}
}
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 과제에서는 이 함수가 한 번 사용되니까 괜찮은데, 조금 더 재사용성 있게 하기 위해서 이렇게 바꿔 보는건 어떨까?

function checkedOnlyOne(input,checkbox1,checkbox2) {
input.addEventListener("click", () => {
    if (input.checked) {
      if (input === checkbox1) {
        checkbox2.checked = false;
      } else if (input === checkbox2) {
        checkbox1.checked = false;
      }
    }
  });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이것도 좋은 방법이다! 안 그래도 이번 과제를 너무 촉박하게 하느라 함수화를 제대로 못하거나 코드를 제대로 정리 못한 부분이 많은 것 같아서 리팩토링 때 잡아야겠다고 생각했어! 고마옹 !!

@Arooming Arooming merged commit 9a1baad into week1Account Nov 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants