๋งจ ์ฒ์ Vue๋ฅผ ์ ํ์ ๋ ๊ณต์ ๋ฌธ์๋ฅผ ๋ณด๋ฉฐ ๋ง๋ค์๋ To do list ์์ ๊ฐ ์์๋ค. ์๊ณ ์ง๋ด๋ค๊ฐ ๋ช ์ฃผ ์ ์ ์ด ์์ ์์ TypeScript๋ CSS framework์ธ Tailwinds ๋ฑ์ ๊ณต๋ถํด๋ณด๊ณ ์ถ์ด์ ์ด๊ฒ์ ๊ฒ ์๊ฐ์ด ๋ ๋๋ง๋ค ์คํํ๋ฉฐ ๋ช ๊ฐ์ ๊ธฐ๋ฅ์ ๋ถ์ฌ๋๊ฐ๋ค. ๊ทธ๋ฌ๋ค Home ํ๋ฉด์ด ๋น์ด์ ๋ญ ํ ์ง ๊ณ ๋ฏผํ๋ค๊ฐ JavaScript๋ก Date ๊ฐ์ฒด๋ฅผ ๋ง์ด ์ ๋ค๋ค๋ด์ ์ด ๊ธฐํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋์ ์ง์ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํ๋ฉด ์ข์ ๊ฒ ๊ฐ์๋ค.
์ผ๋จ ๋ฌ๋ ฅ์ฒ๋ผ ์ด์ ๋ ์ง๋ก ๋์๊ฐ๊ธฐ, ์๋ ์ด๋ ๋ด๋ ๋ฑ์ผ๋ก ์ด๋ํ ์ ์๋๋ก ํ๋ค. ์ค๋ ๋ ์ง๋ก ๋๋์์ค๋ ๋ฒํผ๊ณผ ์ฌํด์ ํน์ ์๋ก ์ด๋ํ ์ ์๋ ๊ธฐ๋ฅ๋ ๋ฃ์๋ค.
๋ฌ๋ ฅ์ ์ค๋ ๋ ์ง๋ฅผ ํ์ํด์ฃผ๋ ๋งํฌ๊ฐ ๋ถ๊ณ , to do list์ ์ฐ๊ณ๋ ๋ฌ๋ ฅ์ด๋ ํด๋น ๋ ์ง์ ์์ฑํ to do๊ฐ ์๋ค๋ฉด ํ๋์ ๋น ์์ผ๋ก, ํด๋น ๋ ์ง์ ์๋ฃํ to do๊ฐ ์๋ค๋ฉด ์์ ์ฑ์ฐ๋๋ก ๊ฐ๋จํ๊ฒ ๊พธ๋ช๋ค.
๊ฐ์ฅ ๋จผ์ ๋ฌ๋ ฅ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์๋ ๊ธฐ์ค์ด ๋๋ ์ค๋ ๋ ์ง ๊ตฌํ๊ธฐ, ๋น์์ 1์ผ์ ์์ผ, ๋น์์ ๋ง์ง๋ง ๋ ์ง, ์ง์ ๋ฌ์ ๋ง์ง๋ง ๋ ์ง ๋ฑ์ด ํ์ํ๋ค.
์ด๋ฒ ๋ฌ์ ์ฒซ ๋ฒ์งธ ์์ผ ๊ตฌํ๊ธฐ
JavaScript์์๋ new Date()๋ก Date ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. data ์ต์ ์ year์ month๋ฅผ0์ผ๋ก ์ด๊ธฐํํด์ค ๋ค ์ต์ ์ฒ๋ฆฌ ๋ฑ์ด์ผ์ด๋๋ created() ํ ๋จ๊ณ์์ ์๋ ์ฝ๋์ฒ๋ผ init() ๋ฉ์๋๋ฅผ ์คํํ๋ค.
// Calendar.vue
data() {
return {
year: 0,
month: 0
}
}
// ..
created() {
this.init()
},
methods: {
init(param) {
if (param) {
this.year = param[0];
this.month = param[1];
this.calendarDate();
} else {
const date = new Date();
this.year = date.getFullYear();
this.month = date.getMonth() + 1;
this.calendarDate();
}
}
}
init()๋ param ์ด๋ผ๋ ์ธ์๋ฅผ ํ๋ ๋ฐ๋๋ฐ, ์ฑ์ด ๋งจ ์ฒ์ ์คํ๋ ๋๋ param์ด ์กด์ฌํ์ง ์์ผ๋ else๋ก ๋ค์ด๊ฐ ์ค๋ ๋ ์ง์ date ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ๊ทธ๋ก๋ถํฐ year์ month๋ฅผ ํ๋์ฉ ์ถ์ถํ๋ค. month์ 1์ ๋ํด์ฃผ๋ ๊ฒ์ getMonth()์ ๋ฐํ๊ฐ์ด ๋ฐฐ์ด์ฒ๋ผ 0-based index์ด๊ธฐ ๋๋ฌธ์ด๋ค.
init()์ด param์ ๋ฐ๋ ์ด์ ๋ ๋ฌ๋ ฅ์์ ๋ค๋ฅธ ๋ ์ง๋ก ์ด๋ํ๊ฑฐ๋, ๋ค๋ฅธ ํด๋ก ์ด๋ํ ์ ์๊ธฐ ๋๋ฌธ์ ๊ทธ๋๋ง๋ค ์๋ก ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํด์ฃผ์ด์ผ ํ ํ์๊ฐ ์์ด์๋ค.
getFirstDayLastDate(year, month) {
const firstDay = new Date(year, month - 1, 1).getDay();
const lastDate = new Date(year, month, 0).getDate();
let lastMonth = month - 1;
if (month === 1) {
lastMonth = 12;
year -= 1;
}
const prevLastDate = new Date(year, lastMonth, 0).getDate();
return [firstDay, lastDate, prevLastDate];
},
์๊น ๊ตฌํ month์ + 1์ ํด์ค ์ํ์ด๋, ์ด๋ฒ ๋ฌ์ ์์ผ์ ๊ตฌํ๊ธฐ ์ํด์๋ month ๊ฐ์ ๋ค์ ์กฐ์ ํด์ค๋ค. getDay() ๋ฉ์๋๋ฅผ ํตํด ์์ผ์ ๊ตฌํ ์ ์๋๋ฐ 0-based์ด๋ฏ๋ก 0์ด ์ถ๋ ฅ๋๋ค๋ฉด ์ผ์์ผ์ ์๋ฏธํ๋ค.
๋ง์ง๋ง ๋ ์ง ๊ตฌํ๊ธฐ
// 2์ 1์ผ์ ๋ ์ง
const date = new Date(2021, 1, 1);
// 2์ 28์ผ์ ๋ ์ง (๋ฌ์ ๋ง์ง๋ง ๋ ์ง)
const lastDate = new Date(2021, 1, 0)
// 2์ 27์ผ์ ๋ ์ง (๋ง์ผ์์ ํ๋ฃจ ์ )
const lastDateBefore = new Date(2021, 1, -1)
๋ง์ง๋ง ๋ ์ง์ ๊ฒฝ์ฐ new Date(year, month, 0).getDate()๋ก ๊ตฌํ ์ ์๋ค. ์๋ฅผ ๋ค์ด 2์ 1์ผ์ ๋ ์ง๋ฅผ ๊ตฌํ๊ณ ์ ํ๋ค๋ฉด new Date(2021, 1, 1), ๋ง์ง๋ง ๋ ์ง๋ฅผ ๊ตฌํ๊ธฐ ์ํด์๋ ์ธ ๋ฒ์งธ ์ธ์๋ฅผ 0์ผ๋ก ๋น์๋๋ค. ๋ง์ง๋ง ๋ ์ง์์ ํ๋ฃจ ์ ํน์ ์ดํ ์ ์ ๊ตฌํ๋ค ํ๋ฉด ์์๋ฅผ ์ด์ฉํด -1, -2๋ก ์ฝ๊ฒ ๊ตฌํ ์ ์๋ค.
์ด์ ๋ ์ง๋ฅผ ๊ตฌํ ๋๋ ํ์ฌ ๋ฌ์ด 1์์ธ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ๋ค. ์ฆ month === 1์ผ ๋ year์์ 1๋ฅผ ๋นผ ์กฐ์ ํ๊ณ , lastMonth๋ ์๋์ผ๋ก 12๊ฐ ๋๋ค.
์ง์ ๋ฌ์ ๋ง์ง๋ง ๋ ์ง๊น์ง ๊ตฌํ๊ณ , ๊ตฌํ๊ณ ์ ํ ์ธ ๊ฐ์ง ๊ฐ์ ๋ฐฐ์ด์ ๋ด์ ๋ฐํํ๋ค.
getFirstDayLastDate(year, month) {
const firstDay = new Date(year, month - 1, 1).getDay();
const lastDate = new Date(year, month, 0).getDate();
let lastMonth = month - 1;
if (month === 1) {
lastMonth = 12;
year -= 1;
}
const prevLastDate = new Date(year, lastMonth, 0).getDate();
return [firstDay, lastDate, prevLastDate];
},
์ด์ ๋ฌ์ ์ฒซ์งธ ๋ ๋ถํฐ ๋ง์ง๋ง ๋ ๊น์ง์ ๊ฐ๋ค์ 2์ฐจ์ ๋ฐฐ์ด์ ํํ๋ก ๋ด๊ธฐ๋ก ํ๋ค. ๋ฌ๋ ฅ์ table ํ๊ทธ๋ก ์ง์, ๊ฐ tr์ date ๋ฐฐ์ด์ v-for๋ก ๋๋ ค ์ถ๋ ฅํ๋ค.
date() {
dates: [],
year: 0,
month: 0,
}
dates๋ผ๋ ๋น ๋ฐฐ์ด์ ๋ง๋ค์ด์ค๋ค. ์ฌ๊ธฐ์ ๋ฐฐ์ด์ธ ์์๋ค์ด ํ๋์ฉ ์ฑ์์ง ๊ฒ์ด๋ค.
๋ฌ๋ ฅ์ ๋ ์ง ๊ตฌํ๊ธฐ (์ด์ ๋ ์ง, ํ์ฌ ๋ฌ์ ๋ ์ง, ๋ค์ ๋ฌ์ ๋ ์ง)
getDaysOfMonth(monthFirstDay, monthLastDate, prevMonthLastDate) {
let day = 1;
let prevDay = prevMonthLastDate - monthFirstDay + 1;
let dates = [];
let daysOfWeek = [];
while (day <= monthLastDate) {
if (day === 1) {
this.getPrevDates(monthFirstDay, daysOfWeek, prevDay);
this.padDates(daysOfWeek);
}
if (daysOfWeek.length === 7) {
dates.push(daysOfWeek);
day = daysOfWeek[daysOfWeek.length - 1];
daysOfWeek = [];
} else if (
daysOfWeek.length < 7 &&
daysOfWeek.indexOf(monthLastDate) > -1
) {
this.padDates(daysOfWeek);
dates.push(daysOfWeek);
break;
}
day++;
if (daysOfWeek.length <= 7) {
daysOfWeek.push(day);
}
}
return dates;
},
getDaysOfMont() ๋ฉ์๋์์๋ ํด๋น ๋ฌ์ ๋ ์ง๋ฟ๋ง ์๋๋ผ ์ด์ ๋ฌ์ ๋ ์ง์ ํ ์ด๋ธ์ ๋ง์ง๋ง ์นธ์ ๋ง์ง๋ง ๋ ์ง๊ฐ ์ฑ์ฐ์ง ๋ชปํ๋ฉด ๋ค์ ๋ฌ์ ๋ ์ง๋ค์ ๋ณผ ์ ์์ผ๋ฏ๋ก ๊ทธ ๋ ์ง๋ค๋ ๊ฐ์ด ๊ฐ์ง๊ณ ์ค๋๋ก ํ๋ค.
prevDay๋ ๋ฌ๋ ฅ์ด ์ด๋ค ์ซ์๋ถํฐ ์์ํด์ผ ํ๋์ง ๊ณ์ฐํด ๋ฃ๋ ๋ณ์์ด๋ค. ์ง์ ๋ฌ์ ๋ง์ง๋ง ๋ ์ง(4์์ ๊ฒฝ์ฐ 3์์ด๋ฏ๋ก 31์ผ)์์ ํด๋น ์์ ์ฒซ ๋ฒ์งธ ๋ ์ง์ ์์ผ (4์์ ๊ฒฝ์ฐ 1์ผ์ด ๋ชฉ์์ผ๋ก 4)์ ๋บ ํ 1์ ๋ํด์ค๋ค.
31 - 4 + 1 = 28์ผ๋ถํฐ ๋ฌ๋ ฅ์ด ์์ํ๋ค.
day๊ฐ ๋ง์ง๋ง ๋ ์ง์ผ ๋๊น์ง ๋ฐ๋ณตํ๋ while ๋ฐ๋ณต๋ฌธ์์ 1์ผ์ผ ๊ฒฝ์ฐ prevDay๋ถํฐ ์์ํด ๋ง์ผ๊น์ง์ ๋ ์ง๋ฅผ ๊ณ์ฐํ๋ getPrevDates ์ฌ์ฉ์ ํจ์๋ฅผ ํธ์ถํ๊ณ , 7์ผ์ด ๋ค ์ฑ์์ง์ง ์์์ผ๋ ์ด์ด์ 1์ผ๋ถํฐ ์์ํด length 7์ง๋ฆฌ daysOfWeek ๋ฐฐ์ด์ ์ฑ์ฐ๋ padDates ํจ์๋ฅผ ํธ์ถํ๋ค.
๋ง์ผ ๊ธธ์ด๊ฐ daysOfWeek์ ๊ธธ์ด๊ฐ 7์ ๋ค๋ค๋ฅด๋ฉด, dates ๊ฐ์ฒด๋ก pushํด์ฃผ๊ณ , ๋ค์ daysOfWeek๋ฅผ ๋น ๋ฐฐ์ด๋ก ์ด๊ธฐํํด์ฃผ์ด์ผ ํ๋ค. ์ด๋ day๋ push ํด์ฃผ๊ธฐ ์ daysOfWeek์ ๋ง์ง๋ง ์์์ ๋์ผํ ๊ฐ์ด๋ฏ๋ก day ๋ณ์์ ๋ฃ์ด์ค ํ, ์กฐ๊ฑด๋ฌธ์ด ํต๊ณผํ๊ณ ๋๋ฉด day++๋ก ์๋ง์ ์ซ์๊ฐ ์นด์ดํธ๋ ์ ์๋๋ก ํ๋ค. 1์ฉ ์ฆ๊ฐํ๋ day๋ daysOfWeek๊ฐ ๊ฝ ์ฑ์์ง ๋๊น์ง ๊ณ์ ๋ฐ๋ณตํ๊ณ day = 1์ธ ์ผ์ด์ค์ ๋ง์ฐฌ๊ฐ์ง๋ก day๊ฐ ๋ง์ง๋ง ๋ ์ง์ ๋ค๋ค๋์ ๋ date๋ฅผ padํด์ daysOfWeek ๋ฐฐ์ด์ ์ฑ์์ฃผ๋๋ก ํ๋ค.
getPrevDates(monthFirstDay, daysOfWeek, prevDay) {
for (let j = 0; j < monthFirstDay; j++) {
daysOfWeek.push(prevDay);
this.prevDate.push(prevDay);
prevDay += 1;
}
},
padDates(daysOfWeek) {
const len = daysOfWeek.length;
const leftDays = 7 - len;
if (len >= 0 && len < 7) {
for (let i = 1; i <= leftDays; i++) {
daysOfWeek.push(i);
if (this.previewDate.length < leftDays) this.previewDate.push(i);
}
}
},
์ด์ ๋ ์ง๋ฅผ padํด์ฃผ๋ ํจ์์ ๋ฌ์ ๋งจ ์๋ ์นธ์ ๋ ์ง๋ฅผ padํด์ฃผ๋ ๋ฉ์๋๋ฅผ ๊ฐ๊ฐ ๋ฐ๋ก ๋ง๋ค์๋ค.
calendarDate() {
const [
monthFirstDay,
monthLastDate,
prevMonthLastDate
] = this.getFirstDayLastDate(this.year, this.month);
this.dates = this.getDaysOfMonth(
monthFirstDay,
monthLastDate,
prevMonthLastDate
);
},
์ด๋ ๊ฒ ๊ตฌํ dates๋ ๋งจ ์ฒ์ ์ธ์คํด์ค ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํ ๋ ๋น ๋ฐฐ์ด๋ก ์ ์ธํด๋์๋ dates์ ๋ด๋๋ค.
๋ฌ๋ ฅ ์ด๋ํ๊ธฐ
ํ์ดํ ๋ชจ์ ์์ด์ฝ๊ณผ ์ค๋ฅธ์ชฝ์ side menu๋ฅผ ํตํด ๋ ์ง๋ฅผ ์ด๋ํ ์ ์๋๋ก ํ๋ค. controlMonth() ๋ฉ์๋๋ก ๋ฌ์, controlYear() ๋ฉ์๋๋ก ํด๋ฅผ ์ฎ๊ธฐ๋๋ก ํ๋ค. ์ธ์๋ฅผ ๋ค๋ฅด๊ฒ ์ค์ ๋ฉ์๋๋ฅผ ์ฌ์ฌ์ฉํ๋ค.
controlMonth(p) {
if (p === "prev") {
this.currentMonth = this.month - 1;
this.currentYear = this.year;
if (this.month === 1) {
this.currentMonth = 12;
this.currentYear = this.year -= 1;
}
} else {
this.currentMonth = this.month + 1;
this.currentYear = this.year;
if (this.month === 12) {
this.currentMonth = 1;
this.currentYear = this.year += 1;
}
}
const param = [this.currentYear, this.currentMonth];
this.init(param);
},
controlYear(p) {
if (p === "prev") {
this.currentYear = this.year - 1;
} else {
this.currentYear = this.year + 1;
}
const param = [this.currentYear, this.month];
this.init(param);
},
created() ํ ์์ ํ ๋ฒ ์ฌ์ฉ๋์๋ init์ ์ด ๋ฉ์๋๋ค์์๋ param์ ์ธ์๋ก ๊ฐ๊ณ ์คํ๋๋ค.
side menu๋ฅผ ํตํด ์ด๋ํ ๋๋ ์ค๋ ๋ ์ง๋ก ๋๋์๋๋ก ํด์ฃผ๋ ๋ฉ์๋ ํ๋, ๊ฐ ๋ฌ์ ํด๋ฆญํ์ ๋ ํด๋น ๋ฌ๋ก ์ด๋ํ๋ ๋ฉ์๋ ํ๋๋ฅผ ๋ง๋ค์๋ค. ๊ทธ๋ฌ๊ณ ๋ณด๋ ๋ฌ๋ ฅ์์ ์ฌ์ฉ์๊ฐ ํ์ฌ ๋ณด๊ณ ์๋ ์ฐ๋๊ฐ ํ์ฌ ์ฐ๋์ ๋ค๋ฅผ ๊ฒฝ์ฐ side menu๋ฅผ ์กฐ์ํ๋ฉด ๋ฌด์กฐ๊ฑด ์ฌํด ์์ ๋ ์ง๋ค๋ง ๋ณผ ์ ์๋๋ก ๋์ด ์๋ค. ์๊ฐ์ด ๋๋ฉด ์ฌ์ฉ์๊ฐ ์ค์ ํด๋ ํด ์์์ ๋ฌ์ ์ด๋ํ ์ ์๋๋ก ํ๋ฉด ์กฐ๊ธ ๋ ํธํ ๋ฏ์ถ๋ค.
skipBy(p) {
this.currentYear = new Date().getFullYear();
if (p === "today") {
this.currentMonth = new Date().getMonth() + 1;
} else {
this.currentMonth = p + 1;
}
const param = [this.currentYear, this.currentMonth];
this.init(param);
},
์ฌ๊ธฐ๊น์ง๊ฐ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ์ํ ๋ฉ์๋๋ค์ด๋ผ๋ฉด, CSS ์คํ์ผ๋ง์ ์ํด ์ถ๊ฐํ ๋ฉ์๋์ computed ์์ฑ์ด ๋ช ๊ฐ ์๋ค.
๋ฌ๋ ฅ ์คํ์ผ๋ง์ ์ํ ๋ฉ์๋
์ค๋ ๋ ์ง์ ์์ผ๋ก ๋งํฌ๋ฅผ ํ๊ธฐ ์ํด์ getMatchedTodos()๋ผ๋ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด boolean ํ์ ์ ๋ฐํํ๋๋ก ํ๊ณ , v-for๋ก ๋ ์ง <td> ํ๊ทธ๋ฅผ ๋ฐ๋ณตํ๋ฉด์ ๊ทธ ์ค ์ค๋ ๋ ์ง์ ์ผ์นํ๋ ํ๊ทธ์๋ง :class ๋ฐ์ธ๋ฉ์ ํตํด 'selected-date'๋ผ๋ ํด๋์ค๋ฅผ ๋ถ์ฌ์ฃผ์๋ค.
<tr v-for="(date, idx) in dates" :key="idx" class="flex w-full h-12 justify-around items-center mb-3">
<td v-for="(day, index) in date" :key="index" class="w-12 h-12 flex flex-col justify-center items-center hover-date rounded-full"
:class="{'selected-date': day === currentDate && isCurrentDate, 'prev-dates': isPrevDates(day, idx)}">
prev-dates ํด๋์ค๋ ์ด์ ๋ ์ง๋ค์ผ ๊ฒฝ์ฐ text color๋ฅผ ์ ์ ์์ผ๋ก ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์ถ๊ฐํ๋ค. ์ผํญ ์ฐ์ฐ์๋ฅผ ํตํด ์ ํ์ ์ผ๋ก class๋ฅผ ๋ฐ์ธ๋ฉํ๋ค. ์ฌ๋ฌ ํด๋์ค์ ๋ํ ์กฐ์์ด ํ์ํ ๋๋ ๊ฐ์ฒด๋ก ๋ฌถ๋๋ค.
์์ฃผ ์ฌ์ฉ๋๋ ํจ์๋ค์ ๋ฌถ์ด์ ๋ ์ ์ ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฐ์ตํ ์ ์์๋ ์์ ์๋ค. ๋ฌผ๋ก ์์ง ์ฝ๋ ๊ฐ๋ ์ฑ์ด ๋ฎ๊ฒ ๊ธธ๊ฒ ์์ ๋์ด ์์ด์ ์ชผ๊ฐค ํ์๊ฐ ์๊ธด ํ๋ค.
๋, Tailwinds๋ฅผ ์ ๋๋ก ์ฌ์ฉํด๋ณธ ์ฒซ ๋ฒ์งธ ์์ ์ด๊ธฐ๋ ํ๋ฐ, ๊ฐ๋ตํ prototyping์ figma๋ก ํ๊ณ , ๊ทธ๋ ๊ทธ๋ ์๊ฐ๋๋ ๊ฑธ ๋ง๋ถ์ด๋ ํํ๋ก ๋ง๋ค๋ค๋ณด๋ ์คํ์ผ๋ง์ ์๊ฐ์ ๋ง์ด ์ฐ๋ ๊ฒ ์ซ์๋๋ฐ, ๊ทธ๋ Tailwinds๋ฅผ ์ฌ์ฉํ๋ ์๊ฐ์ด ๋ฌด์ฒ ์ ์ฝ๋๋ค. ์์ฃผ ์ฐ๋ ์์ฑ๋ค์ ๊ฒน์น๋ค๋ณด๋ ๋์์ธ์ ํต์ผ์ฑ๋ ๋ถ์ฌ๋์ด์ ๋น ๋ฅด๊ฒ ๋น ๋ฅด๊ฒ ์์ ํ ๋๋ ๋ฌด์ฒ ๋ง์กฑ์ค๋ฌ์ ๋ค.
๐ Calendar.vue ์ปดํฌ๋ํธ์ ๋ํ ์ ์ฒด ์์ค๋ ์ด๊ณณ์์