diff --git a/week2/assign2/data/HISTORY_LIST.js b/week2/assign2/data/HISTORY_LIST.js new file mode 100644 index 0000000..77b8cc9 --- /dev/null +++ b/week2/assign2/data/HISTORY_LIST.js @@ -0,0 +1,30 @@ +export const HISTORY_LIST_DATA = [ + { + id: 1, + category: "식비", + contents: "맥도날드 안암점", + type: "expense", + amount: 10800, + }, + { + id: 2, + category: "취미", + contents: "하루필름 강남2호점", + type: "expense", + amount: 5000, + }, + { + id: 3, + category: "월급", + contents: "이번 달 알바비", + type: "income", + amount: 300000, + }, + { + id: 4, + category: "쇼핑", + contents: "오렌즈 강남역 지하쇼핑역점", + type: "expense", + amount: 30000, + }, +]; diff --git a/week2/assign2/index.html b/week2/assign2/index.html new file mode 100644 index 0000000..48c253c --- /dev/null +++ b/week2/assign2/index.html @@ -0,0 +1,158 @@ + + + + + + + 연또의 가계부 + + +
+

💰 연또의 가계부 💰

+
+ +
+
+
+

나의 자산

+

+
+

+ + +

+

+ - +

+
+
+
+ +
+
+ +

10월 1일

+ +
+ +
+

내역 리스트

+
+ + + + +
+
+ +
+
    + +
+
+
+
+ + + +
+
+
+

내역 추가

+
+
+ + + + +
+
+

종류

+ +
+
+

금액

+ +
+
+

내용

+ +
+ + +
+
+ + + + diff --git a/week2/assign2/reset.css b/week2/assign2/reset.css new file mode 100644 index 0000000..644951d --- /dev/null +++ b/week2/assign2/reset.css @@ -0,0 +1,133 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +* { + box-sizing: border-box; +} + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + /* font: inherit; */ + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/week2/assign2/script.js b/week2/assign2/script.js new file mode 100644 index 0000000..3dd1896 --- /dev/null +++ b/week2/assign2/script.js @@ -0,0 +1,228 @@ +import { HISTORY_LIST_DATA } from "./data/HISTORY_LIST.js"; + +const $ = (selector) => document.querySelector(selector); +const $$ = (selector) => document.querySelectorAll(selector); + +let INIT_BALANCE = 0; +let SUM_EXPENSE = 0; +let SUM_INCOME = 0; + +//**** 데이터로부터 초기 렌더링 구현 ***** +// 내역 리스트 렌더링 함수 +const renderHistory = () => { + const historyContainer = $(".history__list__container"); + + HISTORY_LIST_DATA.forEach((list) => { + const { id, category, contents, type, amount } = list; + + const historyBox = document.createElement("li"); + historyBox.classList.add(`history__list__box`); + historyBox.id = id; + historyBox.innerHTML = ` + ${category} + ${contents} + ${amount.toLocaleString()} + `; + + historyContainer.appendChild(historyBox); + }); +}; + +// 총 자산, 수입, 지출 렌더링 함수 +const renderTotalBalance = () => { + const incomeAmounts = [...$$(".history-amount.income")].map((history) => { + return Number(history.innerHTML.replaceAll(",", "")); + }); + const expenseAmounts = [...$$(".history-amount.expense")].map((history) => { + return Number(history.innerHTML.replaceAll(",", "")); + }); + + SUM_INCOME = incomeAmounts.reduce((sum, curr) => { + return sum + curr; + }, 0); + + SUM_EXPENSE = expenseAmounts.reduce((sum, curr) => { + return sum + curr; + }, 0); + + const totalAmount = $(".asset__box__total-amount"); + totalAmount.innerHTML = ( + INIT_BALANCE + + SUM_INCOME - + SUM_EXPENSE + ).toLocaleString(); + + const totalExpense = $(".detail-amount__num__minus"); + totalExpense.innerHTML = SUM_EXPENSE.toLocaleString(); + + const totalIncome = $(".detail-amount__num__plus"); + totalIncome.innerHTML = SUM_INCOME.toLocaleString(); +}; + +// 초기 데이터 렌더링 함수 +const handleRenderInitData = () => { + renderHistory(); //내역 리스트와 + renderTotalBalance(); // 총 수입, 지출, 자산을 데이터로부터 가져와 보여준다 +}; + +//*******체크 박스 선택에 따른 필터링 구현********** +const incomeCheckbox = $("#checkbox-income"); +const expenseCheckbox = $("#checkbox-expense"); + +//리스트 필터 함수 +const filterList = () => { + const incomeLists = $$(".history-amount.income"); + const expenseLists = $$(".history-amount.expense"); + + incomeCheckbox.checked + ? incomeLists.forEach((list) => (list.parentNode.style.display = "flex")) + : incomeLists.forEach((list) => (list.parentNode.style.display = "none")); + + expenseCheckbox.checked + ? expenseLists.forEach((list) => (list.parentNode.style.display = "flex")) + : expenseLists.forEach((list) => (list.parentNode.style.display = "none")); +}; + +//체크 박스 이벤트에 의한 필터링 헨들링 함수 +const handleFilterCheckbox = () => { + incomeCheckbox.addEventListener("change", filterList); + expenseCheckbox.addEventListener("change", filterList); +}; + +//***** 리스트 삭제 구현 ***** +//리스트 삭제 함수 +const delData = (event) => { + //이벤트 전파 방지 조건문 + if (event.target.className === "history-del-btn") { + event.target.parentNode.remove(); //리스트 삭제 + } + + //총 자산에도 반영 + renderTotalBalance(); +}; + +// 리스트 삭제 버튼 클릭시 삭제 구현 핸들링 함수 +const handleDelList = () => { + const delBtns = $$(".history-del-btn"); + + delBtns.forEach(() => addEventListener("click", delData)); +}; + +// ***** 리스트 추가 모달 구현 ***** +const addListModal = $(".add-list-modal__background"); + +// 리스트 추가 모달 나타나게 하는 핸들러 함수 +const handleOpenListAddModal = () => { + const openBtn = $(".footer__add-btn"); + + openBtn.addEventListener("click", () => { + addListModal.style.display = "block"; + + // 초기 값 세팅 + $(".add-amounts__input").value = ""; + $(".add-contents__input").value = ""; + $( + ".add-category__select" + ).innerHTML = ` + `; + }); +}; + +// type 버튼 이벤트 감지 핸들러 함수 +const handleChangeType = () => { + const radioInput = $(".add-list-modal__radio-input"); + radioInput.addEventListener("change", renderOptions); +}; + +// select의 option을 type에 따라 렌더해주는 함수 +const renderOptions = (event) => { + let targetType = "income"; //default 값 + + if (event.target.id === "modal__radio__expense") { + targetType = "expense"; + } + + const categorySelect = $(".add-category__select"); + + targetType === "income" + ? (categorySelect.innerHTML = ` + + `) + : (categorySelect.innerHTML = ` + + `); +}; + +//숫자만 입력하도록 구현하는 함수 +const checkNumber = (event) => { + if (isNaN(event.key)) { + alert("숫자만 입력하세요"); + } + event.target.value = Number( + event.target.value.replace(/[^0-9]/g, "") + ).toLocaleString(); +}; + +// 금액에 숫자만 입력하도록 하는 핸들러 함수 +const handleEnterAmount = () => { + const newAmount = $(".add-amounts__input"); + + newAmount.addEventListener("keyup", checkNumber); +}; + +// 리스트 추가 함수 +const addNewList = () => { + const newType = $('input[name="type"]:checked').value; + + const categories = $(".add-category__select"); + const newCategory = categories.options[categories.selectedIndex].innerHTML; + + const newAmount = $(".add-amounts__input").value; + + const newContents = $(".add-contents__input").value; + + const historyContainer = $(".history__list__container"); + const historyBox = document.createElement("li"); + historyBox.classList.add(`history__list__box`); + historyBox.innerHTML = ` + ${newCategory} + ${newContents} + ${newAmount.toLocaleString()} + `; + + if (newCategory && newAmount && newContents) { + historyContainer.appendChild(historyBox); + alert("저장 되었습니다."); + } else { + alert("아직 입력되지 않은 항목이 있습니다."); + } +}; + +// 모달에서 저장 버튼 클릭시 리스트 추사하는 핸들러 함수 +const handleAddList = () => { + const saveBtn = $(".add-list-modal__save-btn"); + + saveBtn.addEventListener("click", addNewList); +}; + +// 모달을 닫아주는 핸들러 함수 +const handleCloseListAddModal = () => { + const closeBtn = $(".add-list-modal__close-btn"); + closeBtn.addEventListener("click", () => { + addListModal.style.display = "none"; + }); +}; + +// ***** 최종 실행 함수들 ***** +handleRenderInitData(); + +handleFilterCheckbox(); + +handleDelList(); + +handleOpenListAddModal(); +handleChangeType(); +handleAddList(); +handleCloseListAddModal(); + +handleEnterAmount(); diff --git a/week2/assign2/style.css b/week2/assign2/style.css new file mode 100644 index 0000000..0dbb328 --- /dev/null +++ b/week2/assign2/style.css @@ -0,0 +1,442 @@ +@import url("./reset.css"); + +@font-face { + font-family: "DungGeunMo"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_six@1.2/DungGeunMo.woff") + format("woff"); + font-weight: normal; + font-style: normal; +} + +body { + display: flex; + flex-direction: column; + + /* 전체 스크롤 방지 */ + overflow: hidden; + touch-action: none; + + font-family: "DungGeunMo"; +} + +/* 버튼 기본 스타일링 제거, 포인터 설정*/ +button { + border: none; + background-color: transparent; + + cursor: pointer; +} + +/*전역 적용*/ +.income { + color: blue; +} + +.expense { + color: red; +} + +/*header*/ +.header { + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: 3.5rem; + + background-color: black; + color: white; + + font-size: 2rem; +} + +/* 나의 자산 */ +.asset { + display: flex; + justify-content: center; + align-items: center; + + padding: 1rem 2rem; + border: solid lightgray; + border-width: 0.2rem 0; +} + +.asset__box { + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; + height: fit-content; + padding: 1rem 0; + gap: 1.2rem; + + border: 0.3rem solid black; + border-radius: 0.5rem; + + background-color: #ddd; +} + +.asset__box__title { + font-size: 1.8rem; +} + +.asset__box__total-amount { + font-size: 1.5rem; +} + +.asset__box__total-amount::before { + content: "💲 "; + + font-size: 1.3rem; +} + +.asset__box__detail-amount { + display: flex; + gap: 3rem; + + font-size: 1.4rem; +} + +.detail-amount__plus, +.detail-amount__minus { + display: flex; + gap: 0.5rem; +} + +.amount-sign { + display: flex; + justify-content: center; + align-items: center; + + width: 1.5rem; + height: 1.5rem; + + border-radius: 1rem; + + background-color: white; +} + +.history { + display: flex; + flex-direction: column; + + width: 100%; +} + +.history__top { + display: flex; + justify-content: center; + align-items: center; + + padding: 0.5rem 0; + + font-size: 1.3rem; +} + +.history__top > button { + font-size: 1.5rem; +} + +.history__top > h2 { + padding: 0 2rem; +} + +.history__detail { + display: flex; + justify-content: space-between; + align-items: center; + + padding: 0.5rem 2rem; + + font-size: 1rem; + + border-bottom: 0.1rem solid lightgray; +} + +.history__detail__checkbox { + display: flex; + gap: 1rem; +} + +/* checkbox input 커스텀 */ +input[type="checkbox"], +input[type="radio"] { + display: none; +} + +input[id="checkbox-income"] + label, +input[id="checkbox-expense"] + label { + display: flex; + justify-content: center; + align-items: center; + + width: 5rem; + height: 2rem; + + border-radius: 0.5rem; + + cursor: pointer; +} + +input[id="checkbox-income"] + label { + background-color: transparent; + border: 0.1rem solid blue; +} +input[id="checkbox-income"]:checked + label { + background-color: blue; + color: white; +} + +input[id="checkbox-expense"] + label { + background-color: transparent; + border: 0.1rem solid red; +} +input[id="checkbox-expense"]:checked + label { + background-color: red; + color: white; +} + +/* 내역 리스트 영역 */ + +/*리스트 내 세로 스크롤*/ +.history__list { + height: 42vh; + overflow-y: scroll; + + margin-bottom: 3rem; +} + +.history__list > ul { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + width: 100%; + padding: 1rem 2rem 0 2rem; + gap: 1rem; +} + +.history__list__box { + display: flex; + align-items: center; + + width: 100%; + height: 3.5rem; + padding: 1rem 1.5rem; + + background-color: #a6b7da; + color: white; + + border-radius: 0.5rem; +} + +.history-category { + width: fit-content; + padding-right: 1.5rem; + + color: #444; + + font-size: 0.8rem; + text-align: center; +} + +.history-contents { + width: 8.5rem; + + /* 말줄임표 */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + color: #242526; + + font-size: 1rem; +} +.history-amount { + width: fit-content; + padding-right: 0.5rem; + margin-left: auto; + + font-size: 1.1rem; +} + +/*수입, 지출에 따라 부호 붙여주기*/ +.history__list__box > .income::before { + content: "+"; +} + +.history__list__box > .expense::before { + content: "-"; +} + +.history-del-btn { + width: 1.3rem; + height: 1.3rem; + border-radius: 0.7rem; + + font-size: 0.5rem; + + color: #242526; +} + +.history-del-btn:hover { + background-color: white; +} + +/* footer 영역 */ +.footer { + display: flex; + justify-content: center; + align-items: center; + + position: fixed; + bottom: 0; + + width: 100%; + height: 3rem; + + background-color: #ddd; +} + +.footer__add-btn { + width: 2rem; + height: 2rem; + + background-color: #a6b7da; + color: white; + + border-radius: 1rem; + + font-size: 1.5rem; +} + +/*내역 추가 모달*/ +@keyframes fadeInUp { + 0% { + opacity: 0.5; + transform: translate3d(0, 100%, 0); + } + to { + opacity: 1; + transform: translateZ(0); + } +} + +.add-list-modal__background { + position: absolute; + top: 0; + left: 0; + + display: none; + + width: 100%; + height: 100dvh; + background-color: rgba(0, 0, 0, 0.4); + z-index: 10; +} + +.add-list-modal { + position: absolute; + bottom: 0; + + display: flex; + flex-direction: column; + + width: 100%; + height: 70%; + gap: 1.3rem; + padding: 1.3rem 2rem; + + background-color: #a6b7da; + color: black; + + z-index: 100; + + animation: fadeInUp 0.5s; +} + +.add-list-modal__header { + align-self: center; + + font-size: 1.7rem; + + color: white; +} + +.add-list-modal__radio-input { + display: flex; + justify-content: center; + + gap: 2rem; +} + +input[id="modal__radio__income"] + label, +input[id="modal__radio__expense"] + label { + padding: 0.5rem 2.7rem; + + border-radius: 0.5rem; + + font-size: 1.3rem; + + background-color: whitesmoke; +} + +input[id="modal__radio__income"]:checked + label, +input[id="modal__radio__expense"]:checked + label { + background-color: grey; + color: white; +} + +.add-list-modal__category, +.add-list-modal__contents, +.add-list-modal__amounts { + display: flex; + flex-direction: column; + gap: 0.5rem; + + width: 100%; +} + +.add-category__title, +.add-contents__title, +.add-amounts__title { + font-size: 1.3rem; + width: 100%; +} + +.add-category__select { + width: 100%; + padding: 0.5rem; + + background: whitesmoke; + border: 0.1rem solid #999; + border-radius: 0.5rem; + + -webkit-appearance: none; /* 네이티브 외형 감추기 */ + -moz-appearance: none; + appearance: none; +} + +.add-contents__input, +.add-amounts__input { + width: 100%; + padding: 0.5rem; + + border: none; + border-radius: 0.5rem; +} + +.add-list-modal__save-btn, +.add-list-modal__close-btn { + align-self: center; + + width: 100%; + padding: 0.5rem; + + border-radius: 0.5rem; + + background-color: whitesmoke; +}