์ด๋ฒ์ฃผ ํ๋ก์ ํธ๋ 3์ฃผ๊ฐ ๐Jenny ๐งDong ๊ณผ ํจ๊ปํ๊ฒ ๋์๋ค
์ฒซ๋ ์ ์ญ์ BE,FE ์ ์ปจ๋ฒค์ ๊ณผ git repository๊ด๋ฆฌ ๋ฑ์ ์ ํ๊ณ
Jenny์๋ Component๊ตฌ์กฐ๋ฅผ ์งฐ๋ค.
โฌ๏ธ Component๊ตฌ์กฐ ์ค๊ณ๋
https://app.diagrams.net/#G13ElfK5XcWA_TtAZ3Bh6-GEVlhodBu7gh
์ผ๋จ ๋น์ฅ ๊ตฌํํ ํ์ด์ง๋ ๋ผ์ฐํฐ2๊ฐ๋ก ์ด๋ฃจ์ด ์ง์ง๋ง
๊ทธ ์์์ ๊ตฌํํ ๋์๋ค์ด ๋ง์์ component๊ตฌ์กฐ๊ฐ ๊ฑฐ๋ํด์ก๋ค.
+ update ์ต์ข ๋๋ ํ ๋ฆฌ ์ํฉ
ํํ๋ก์ด UI๊ตฌ์ฑ๋ถํฐ ์์!
์ด๋ฒํ๋ก์ ํธ์์ ๋ฐฐ์ ๋ ๊ฒ๋ค, ์ด๋ ค์ ๋ ์ ๋ค์ ์ฐจ๊ทผ์ฐจ๊ทผ ๊ธฐ๋กํด๊ฐ ์์ ์ ๋๋ค:)~~
๐ ๋ฐ์ํ์ CSS
์ฐฝ์ ํฌ๊ธฐ์ ๋ฐ๋ผ์ CSS ๊ฐ ๋ฐ์ํ๋๋ก ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ 2๊ฐ์ง๋ผ ์๊ฐํ๋ค.
1) Width ๋ฅผ % ๋ก ์กฐ์
2) ๋ฏธ๋์ด ์ฟผ๋ฆฌ (Width ํฌ๊ธฐ์ ๋ฐ๋ผ ๋ค๋ฅธ ์กฐ๊ฑด ์ง์ )
ํ์UI ๋ ํํ๋ก์ ํธ๋ฅผ ํ๋๋ผ๋ ๋๋ ์ ๋งก๊ธฐ ๋๋ฌธ์ ํ๋ ๋ฐฉ์์ผ๋ก๋ง ํด์ ๊ฐํ๊ธฐ ๋ง๋ จ์ธ๋ฐ,
Jenny์ ํจ๊ป ์ฌ์ํ๊ฒ ์๋๋๊ฑธ ๊ณ ์ณ๊ฐ๋ฉด์ ์์ํ๊ฑธ ๋ฐฐ์ ๋ค.
โ Width ๋ฅผ %๋ก ์กฐ์
const CityTour = () => {
return (
<CityTourDiv>
<CityTitle>๊ฐ๊น์ด ์ฌํ์ง ๋๋ฌ๋ณด๊ธฐ</CityTitle>
<CityCardFrame>
{cityData.map((cityCard, idx) => (
<CityCard
key={idx}
cityImg={cityCard.imgUrl}
cityName={cityCard.name}
cityDistance={cityCard.distance}
></CityCard>
))}
</CityCardFrame>
</CityTourDiv>
);
};
const CityTourDiv = styled.div`
margin: 0rem auto 5rem auto;
width: 88.88%;
`;
const CityCardFrame = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-gap: 1rem;
`;
์ ์ฒด Width : 1440px
CityTourDiv Width:1280px
1280 /1440 *100% ํด์ ๊ทธ ๋น์จ์ width๋ก ์ง์ ํ๊ณ
width: 88.88%
height: fit-content ๋ก ์ง์ ํ์ฌ ํ๋ฉด ํฌ๊ธฐ๊ฐ ์์์ง๋ฉด ๋ฐ์ํ๊ฒ ๋ง๋ค์๋ค.
๐react modal ์ด์ธ ์์ญ์ ๋๋ฅด๋ฉด ๋ชจ๋ฌ์ฐฝ ๋ซ๊ธฐ
์ผ๋จ ๋ชจ๋ฌ์ฐฝ์ ๋์ฐ๊ธฐ ์ ์ํฉ์ ๋ณด๋ฉด ,
Search ๋ฐ์ ์ด 4๊ฐ์ component( Checkin Checkout , Price, People) ์ด ์๊ณ
๊ฐ component๋ฅผ ํด๋ฆญํ๋ฉด ClickReducer์์ ํด๋ฆญํ ํ์ ์ ๋ง๋ modal์ฐฝ์ ๋ด๋ฆฌ๋ ๊ตฌ์กฐ
1๏ธโฃ ์ฒซ ์๋ - Modal ๊ฐ๊ฐ์ ref ์ฐ๊ฒฐ ๋ฐ
import React, { useReducer, useRef, useEffect } from 'react';
import styled from 'styled-components';
const Search = () => {
//modal reducer ์์ฑ
const isClickedReducer = (state, action) => {
switch (action.type) {
//...์ค๋ต
//modal ์ด์ธ ์์ญ ํด๋ฆญ์ ์ ๋ถ ๋ซํ๋ action type ์ ํจ
case 'CloseModal':
if (
(state.checkInOut || state.price || state.people) &&
!modalEl.current.contains
) {
}
return {
checkInOut: false,
price: false,
people: false,
};
default:
return;
}
};
//ref์ฐ๊ฒฐ ๋ฐ click ์ด๋ฒคํธ ๊ธฐ๋ฅ
const modalEl = useRef();
const [clicked, dispatch] = useReducer(isClickedReducer, {
checkInOut: false,
price: false,
people: false,
});
const { checkInOut, price, people } = clicked;
useEffect(() => {
window.addEventListener('click', dispatch({ type: 'CloseModal' }));
return () => {
window.removeEventListener('click', dispatch({ type: 'CloseModal' }));
};
}, []);
return (
<SearchDiv>
<SearchWrap>
{checkInOut && <CheckModal ref={modalEl} />}
{price && <PriceModal ref={modalEl} />}
{people && <PeopleModal ref={modalEl} />}
</SearchWrap>
</SearchDiv>
);
};
export default Search;
๊ฐ component์ ํด๋นํ๋ Modal์ฐฝ์ ref ์ฐ๊ฒฐ
dispatch์ 'CloseModal'์ถ๊ฐ
์ค๋ฅ ์ด์
: Modal์ฐฝ์ด ์๊ธฐ๋ ์์ div์ ref๋ฅผ ๊ฑธ์์ด์ผ ํจ
์ฒ์์ ๊ฐ Modal์ ์ํ๊ฐ false์ด๊ธฐ ๋๋ฌธ์ ref=modalEl ๊ฐ ์๋ ์ํฉ์ด ๋จ
2๏ธโฃ ์ฌ ์๋ - useEffect ์ฌ์ฉ
const Search = () => {
const modalElement = useRef();
//๋ชจ๋ฌ ์ธ๋ถ click ์ ๊ตฌํ ์กฐ๊ฑด
useEffect(() => {
// console.log('e : ', e);
// console.log('target :', e.target);
// console.log('modalElement.current:', modalElement.current);
const modalOff = (e) => {
if (modalElement.current && !modalElement.current.contains(e.target)) {
dispatch({ type: 'ModalOff' });
}
};
document.addEventListener('mousedown', modalOff);
return () => {
document.removeEventListener('mousedown', modalOff);
};
}, [modalElement]);
//modal click reducer ๊ตฌํ
const isClickedReducer = (state, action) => {
switch (action.type) {
//...์ค๋ต
case 'ModalOff':
return {
checkInOut: false,
price: false,
people: false,
};
default:
return;
}
};
const [clicked, dispatch] = useReducer(isClickedReducer, {
checkInOut: false,
price: false,
people: false,
});
const { checkInOut, price, people } = clicked;
return (
<SearchDiv>
<ModalDiv ref={modalElement}>
{checkInOut && <CheckModal />}
{price && <PriceModal />}
{people && <PeopleModal />}
</ModalDiv>
</SearchDiv>
);
};
๋ณ๊ฒฝํ ๋ถ๋ถ
1) useRef ์ ์ธ ์๋ก ์ฎ๊ธฐ๊ธฐ
: ์ฌ์ํ ๋ฌธ์ ์ด๊ธด ํ์ง๋ง useEffect์์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ ์ธ์ด ๋จผ์ ๋์ผ ํ์
2) Modal์ ๊ฐ์ธ๋ ์์ div ์์ฑ (ModalDiv)
: Modal์ด ์์ฑ๋๊ธฐ ์ ์๋ ์ํ๊ฐ ์์ด์ผ ํ๊ธฐ ๋๋ฌธ์ div๋ฅผ ์์ฑํ์
3) modal ์ธ๋ถ ํด๋ฆญ ์ ์กฐ๊ฑด ์ฌ์ค์
useEffect(() => {
const modalOff = (e) => {
if (modalElement.current && !modalElement.current.contains(e.target)) {
dispatch({ type: 'ModalOff' });
}
};
document.addEventListener('mousedown', modalOff);
return () => {
document.removeEventListener('mousedown', modalOff);
};
}, [modalElement]);
2๊ฐ์ง ์กฐ๊ฑด์ ํ์ธํ์ฌ ํด๋นํ๋ฉด modal์ด ๋ซํ๋๋ก ์ค์ ํ๋ค.
1) modalElement (์ฆ ref๋ก ์ฐ๊ฒฐํ modal component) ๊ฐ ์กด์ฌํ๋์ง
2) modalElement๊ฐ ๋ฐฉ๊ธ ํด๋ฆญํ ์์ (e.target)์ ํฌํจํ๊ณ ์์ง ์์์ง ( ๊ทธ๋์ผ modal ์ธ๋ถ ์์ญ)
modal ์ component๊ฐ unmount ๋๋ ์ฃผ๊ธฐ๊ฐ ๋ง์ผ๋ฏ๋ก useEffect ์ฝ๋ฐฑ ์์ ๋ฆฌํด์ ํตํด click ์ด๋ฒคํธ๋ฅผ ์ ๊ฑฐํด์ผ ํ๋ค.
๐ฎ ๋จ!
ref๋ฅผ document์ ์ฐ๊ฒฐํด ๋์ผ๋
๋ชจ๋ Component ์์์ forwardRef๊ฐ ๋ถ์๋ค.
์ด๊ฒ ๊ณผ์ฐ ๋ง๋ ์ผ์ผ๊น?
์ฐจ์ฐจ ์์๊ฐ๋๋ก ํ์...
๐ ์ฌ๋ผ์ด๋ Calendar (๋ฌ๋ ฅ) ๋ง๋ค๊ธฐ
๋๋ฌ์ฉ ์ฌ๋ผ์ด๋๋ก ๋์ด๊ฐ๋ ๋ฌ๋ ฅ์ ๋ง๋ค์ด์ผ ํ๋ค!!!
Jenny๊ฐ ๋๋ฌด ์น์ ํ๊ฒ๋ ์ฌ๋ผ์ด๋์ CSS๋ฅผ ์์๊ฒ ๊ตฌํํด ๋์๊ธฐ ๋๋ฌธ์ ๋ฌ๋ ฅ์ ๊ณ์ฐํด์ ๋ฃ์ด์ผ ํ๋ค
โญ๏ธ ๊ณํ
1) createMonthArray ํจ์๋ก ๊ฐ๋น ๋ฌ๋ ฅ ๋ฐฐ์ด ์์ฑ
2) ๋ฐฐ์ด์ ๋๋ฉฐ ๋ ๋๋งํ๊ณ
3) ๋ฒํผ์ ๋๋ฅผ๋๋ง๋ค year, month ์ ์ํ๊ฐ ๋ฐ๋๊ณ ๋ฆฌ๋ ๋๋ง ๋์ผ ํจ
โฌ ๏ธ
๋๋ต ์ด๋ฐ๋๋์ monthArray๋ฅผ ๋ง๋ค๊ณ
map์ผ๋ก ๋๋ ค ๋ ๋๋ง ํจ
๐พ ํด๊ฒฐ์ ํ์ ๋ค ..
1) ์ฒซ๋ฒ์งธ ์๋ - ๋ฐ๋๋ผ JS ์ด์ฉํ๊ธฐ
const setcalendarData = (year,month) =>{
let calHtml="";
//์ค๋๋ ๋ ์ง ๊ฐ์ฒด / ์ด๋ฒ๋ฌ 1์งธ๋ ์ง / ์ด๋ฒ๋ฌ 1์งธ์์ผ / ์ด๋ฒ๋ฌ ๋ง ๋ ์ง / ์ง๋๋ฌ ๋ง ๋ ์ง
const setDate = new Date(year,month-1,1);
//์ด๋ฒ๋ฌ 1์งธ๋ ์ง
const firstDay = setDate.getDate();
//์ด๋ฒ๋ฌ 1์งธ์์ผ
const firstDayName = setDate.getDay();
//์ด๋ฒ๋ฌ ๋ง ๋ ์ง
const lastDay = new Date(year,month,0).getDate();
//์ง๋๋ฌ ๋ง ๋ ์ง
const prevLastDay = new Date(year,month-1,0).getDate();
//์ด๋ฒ๋ฌ์ ์ผ์ ๊ตฌํ๊ธฐ
let startDayCount = 1;
let lastDayCount = 1;
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 7; j++) {
//์ด๋ฒ๋ฌ ์์์์ผ ์ด์
if (i == 0 && j < firstDayName) {
calHtml +=
`<div class='calendar__day borderHidden'> </div>`;
}
//์ด๋ฒ๋ฌ ์์ ์์ผ์ผ ๋
else if (i == 0 && j == firstDayName) {
if (j == 0) {
calHtml +=
`<div class='calendar__day horizontalGutter'><span>${startDayCount}</span><span id='${year}${month}${setFixDayCount(startDayCount++)}'></span></div>`;
} else if (j == 6) {
์ด์ ๋ฐ๋๋ผJS๋ก ๊ตฌํํ๋ calendar๋ฅผ ์ฌ์ฉ ๊ฐ์ ธ์์ ์ฌ์ฉํ๋ ค ํ๋ค.
์ฌ์ฉํ์ง ๋ชปํ ์ด์ :
์ด ํ์์ calHtml์ template literal๋ก ๊ณ์ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ์์ธ๋ฐ
React๋ jsxํํ๋ก ๋ง๋ค๊ณ ์์๊ณ , ์ํ๋ณ๊ฒฝ์ ๋ฐ๋ผ ์ฌ๋ ๋๋ง์ด ๋ฐ๋ณต๋๋ ๊ตฌ์กฐ์์
๋ฐ์ดํฐ ์์ฑ๊ณผ ๋ ๋๋ง์ด ํฉ์ณ์ ธ ์๋ ๊ตฌ์กฐ๋ ์ ์ ํ์ง ์์๋ค.
2) ๋ฐ์ดํฐ ์์ฑ๊ณผ ๋ ๋๋ง ๋ถ๋ฆฌ
createMonthArray๋ก ํด๋นํ๋ ๋ฌ์ ๋ ์ง๋ฅผ ์์ฑํ๊ณ
SingleCalendar Component๋ก ๋ฟ๋ ค์ค์ผ ๊ฒ ๋ค ์๊ฐ!
createMonthArray ํจ์
const createMonthArray = (range) => {
let monthArr = [];
let weekArr = [];
let dayCnt = 1;
const today = new Date(Date.now());
const [year, month] = [today.getFullYear(), range + today.getMonth()];
const firstDayName = new Date(year, month - 1, 1).getDay();
const lastDay = new Date(year, month, 0).getDate();
for (let i = 0; i < 7; i++) {
for (let j = 0; j < 7; j++) {
if (weekArr.length >= 7) {
monthArr.push(weekArr);
weekArr = [];
}
if (i === 0 && j < firstDayName) weekArr.push(' ');
else if (i === 0 && j >= firstDayName) {
weekArr.push(dayCnt);
dayCnt++;
}
else if (i > 0 && dayCnt <= lastDay) {
weekArr.push(dayCnt);
dayCnt++;
}
else if (dayCnt > lastDay && weekArr.length > 0) weekArr.push(' ');
}
}
SingleCalendar
const SingleCalendar = ({ range }) => {
const monthArr = createMonthArray(range);
return (
<SingleCalDiv>
<thead></thead>
<tbody>
{monthArr?.map((week, idx) => (
<DayTr key={idx}>
{week?.map((day, idx) => (
<DayTd key={idx}>{day}</DayTd>
))}
</DayTr>
))}
</tbody>
</SingleCalDiv>
);
};
์ด๋ ๊ฒ ์ํ๋ ๋ฌ์ ๋ฐ์ดํฐ๋ฅผ ์ด์ฐจ์๋ฐฐ์ด๋ก ๋ง๋ค์ด ๋๊ณ
SingleCalendar Component ์์๋ ๋ฟ๋ ค์ฃผ๊ธฐ๋ง ํ๋๋ก ๊ตฌ์ฑํ๋๋ ์ฑ๊ณต์ ์ผ๋ก calendar ๊ฐ ์์ฑ๋์๋ค.
++ ๋ฉฐ์น ํ ์ถ๊ฐ
์ ์ ์ฝ๋์์๋ year (์ฐ๋) , month(๋ฌ) ๋์ด๊ฐ๋ ๋ถ๋ถ์ ๊ณ ๋ คํ์ง ๋ชปํด 2022๋ 5์์ฏค๋ถํฐ ๊ณ์ฐ์ด ์๋ชป๋์๋ค.
createMonthArray ํจ์
const createMonthArray = (year, month) => {
let monthArr = [];
let weekArr = [];
let dayCnt = 1;
const firstDayName = new Date(year, month - 1, 1).getDay(); //6
//์ด๋ฒ๋ฌ ๋ง์ง๋ง ๋ ์ง
const lastDay = new Date(year, month, 0).getDate();
// i: 1์ฃผ j:์์ผ
for (let i = 0; i < 7; i++) {
for (let j = 0; j < 7; j++) {
if (weekArr.length >= 7) {
monthArr.push(weekArr);
weekArr = [];
}
// ์ด๋ฒ๋ฌ ์์ ์์ผ ์
if (i === 0 && j < firstDayName) weekArr.push(' ');
//์ด๋ฒ๋ฌ ์์ ์์ผ ์ฃผ
else if (i === 0 && j >= firstDayName) {
weekArr.push(dayCnt);
dayCnt++;
}
//์ด๋ฒ๋ฌ์ ๋ง์ง๋ง๋ , ํน์ ๊ทธ ์ด์ ์ผ ๋
else if (i > 0 && dayCnt <= lastDay) {
weekArr.push(dayCnt);
dayCnt++;
} else if (dayCnt > lastDay && weekArr.length > 0) weekArr.push(' ');
}
}
return monthArr;
};
export default createMonthArray;
calcMonth ํจ์
const calcMonth = (year, mon) => {
if (mon <= 0) {
const count = Math.floor(Math.abs(mon / 12)) + 1;
mon += 12 * count;
year = year - count;
} else if (mon >= 13) {
const count = Math.ceil(Math.abs(mon / 12)) - 1;
mon -= 12 * count;
year = year + count;
}
return [year, mon];
};
const today = new Date(Date.now());
const [year, month] = calcMonth(
today.getFullYear(),
today.getMonth() + 1 + range
);
const monthArr = createMonthArray(year, month);
๋ฐ๋ผ์ Jenny๊ฐ ์์ฑํด์ค clacMonth์์ ํ์ฌ ๋ ์ง๋ฅผ ์ด์ฉํด year,month๋ฅผ ๊ณ์ฐํด
๊ทธ ๊ฒฐ๊ณผ๋ฅผ createMonthArray ํจ์๊ฐ ๋ฐ์์ ๋ ์ง๋ฅผ ์์ฑํ๋ ๋ฐฉํฅ์ผ๋ก ๋ณ๊ฒฝํ๋ค.
์ฐธ๊ณ ๋ก mon์ range[-1,0,1,2]์ ํฉํด์ง ๊ฐ
range์ ํฉํ ๊ฐ์ผ๋ก ๋ฏธ๋์ ๋ฌ์ ๊ณ์ฐํ๊ธฐ ๋๋ฌธ์ 12๋ฅผ ๋์๋๋ถํฐ ๋๋ ์ ๊ณ์ฐํด์ผ ํ๋ค.
๐ 1์ฐจ ์ฝ๋๋ฆฌ๋ทฐ ํ ๋๋์
์ฝ๋๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๊ณ ํด๋น ์ฌํญ์ ๊ณ ์น๋ฉด์ ๋๋์ ์ ์ฌ์ํ์ง๋ง ์ ๊ฒฝ์จ์ผ ํ๋ ๋ถ๋ถ์ ์๊พธ ๊ฑด๋๋ฐ๋ฉด ๋ณต์กํ ์ฝ๋๊ฐ ๋๋ค๋ ์ ์ด๋ค.
๋ง์์ผ๋ก๋ ์ข์ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ๋ค๊ณ ํ์ง๋ง, ๋ง์ ์ฝ๋๋ฅผ ์งค ๋๋ ๊ตฌํ์ด ๋์ผ ํ๋ค๋ ์๊ฐ์
* ๋ณ์๋ช ๊ณ ๋ฏผ
* ๊ตฌ์กฐ๋ถํด ํ ๋น
* ๋ชจ๋ํ
๋ฅผ ๊ณ ๋ฏผํ์ง ์๊ณ ๊ฑฐ๋ํ ๋ชธ์ง๊ณผ ํ๋ํ ์กฐํฉ์ฒด๊ฐ ๋ง๋ค์ด์ง๋ค.
๐ฎ ๋ค์ ๋ฆฌ๋ทฐ๊น์ง ์ ๊ฒฝ์ธ ์
1. Component์ ์์ฑ์๋ ๋งค์ง๋๋ฒ,์คํธ๋ง์ด ๋ค์ด๊ฐ์ง ์๋๋ค.
: ์์ ๋ถ๋ถ์ด๋ผ ์๊ด ์์ ๊ฒ์ด๋ผ ์๊ฐํ์ง๋ง ์ด๋ฐ ์์ ๊ฒ๋ค์ด ์ฝ๋์ ๊ฐ๋ ์ฑ์ ๋จ์ด๋จ๋ฆฐ๋ค.
2 ์ง๊ธ ๋ฐ๋ก ์ฌ์ฉํ์ง ์์ ๋์์ ๋ฏธ๋ฆฌ ๋ง๋ค์ง ๋ง์.
import {useEffect , useRef... } ์ฒ๋ผ ์ฌ์ฉํ์ง ์์ ๊ฒ์ ์ฒ์๋ถํฐ ์ฐ์ง ์๋ ์ต๊ด!
3. ํจ์๋ ๋ณ์๋ก ๋ฏธ๋ฆฌ ๋ง๋ค์ด, ์ฐ์ฐ์ ์ธ๋ผ์ธ์ ๋์ง ์๋๋ค.
1.๋ฒ๊ณผ ๋น์ทํ ๋งฅ๋ฝ์ด์ง๋ง , ์๊พธ๋ง Component ๋ ๋๋ง ๋ถ๋ถ์ ์ฐ์ฐ์ ๋๋ ๋ฒ๋ฆ์ด ์์๋ค.
๊ฐ๋ ์ฑ์ด ํ์ ํ ๋จ์ด์ง๋ ํ์์ ์ด์ง ์์ผ๋ฉด ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ ๊ฐ์ ธ์ค์.
'๐ฟ Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SidePJ] React ๋ชจ๋ฌ ์์ญ ๋ฐ ํด๋ฆญ์ ๋ซ๊ธฐ (0) | 2021.06.03 |
---|---|
React๋ก Slider ๊ตฌํ & React์์ Canvas๋ก ๊ณก์ ๊ทธ๋ํ ๊ทธ๋ฆฌ๊ธฐ (+globalCompositeOperation ) (0) | 2021.06.02 |
[side PJ ] โพ๏ธ์ผ๊ตฌ๊ฒ์ - ๋ฌธ์ ์ํฉ๊ณผ ํด๊ฒฐ๊ณผ์ (0) | 2021.05.20 |
[Side PJ] ์ผ๊ตฌ๊ฒ์ - ์ฝ์ง๊ณผ ๋ฐฐ์ด์ ๐ (0) | 2021.05.20 |
todo List ํ๋ก์ ํธ (0) | 2021.04.10 |
๋๊ธ