2021/02/08 - [๐ฉ๐ป/Vue.js] - [Vue-todo-list] #2 ์นดํ ๊ณ ๋ฆฌ๊ฐ ์๋ Todo List ๋ง๋ค๊ธฐ
todolist ์์ ์ ๋ง์ง๋ง ๋จ๊ณ๋ chart.js ์ฐ์ต์ ์ํด์ Pie chart์ line chart ๋ ๊ฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ Daily Report๋ก ๊พธ๋ช๋ค. Line chart์ ๊ฒฝ์ฐ localStorage์ ์ ์ฅ๋๋ todo ๊ฐ์ฒด์ createdAt Date ๊ฐ์ฒด์ ์๋ฃ๊ฐ ๋ ๋ ์์ฑ๋๋ completedAt Date ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ง์ ๋ ์ง์ ํด๋นํ๋ todo ๊ฐ์ฒด์ length์ ๋ฐ๋ผ ๋ ๊ฐ์ Line์ ๋น๊ตํด์ฃผ๋ ์ฐจํธ๋ก ๋ง๋ค์๋ค.
ํ๋์ ๋ผ์ธ์ ํด๋น ๋ ์ง์ ์์ฑ๋ todo์ ๊ฐฏ์, ๋นจ๊ฐ์ ๋ผ์ธ์ ์๋ฃํ todo์ ๊ฐ์์ธ๋ฐ, ์ค๋ ๋ ์ง๊ฐ ๋ผ๋ฒจ์์ ๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ์์นํ๋๋ก ํด์ ์ต๊ทผ 7์ผ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์๋๋ก ํ๋ค.
๋ค๋ง ํ์ฌ๋ ์ฌ์ฉ์๊ฐ todo done ๋ชฉ๋ก์ ์๋ todo๋ฅผ ์ญ์ ํ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๊ฐ ์ฐ๋๋์ด ์๋ Calendar์ Chart ๋ชจ๋์์๋ ์๊ฐํ๋ ์๋ฃ๊ฐ ์์ด ๋ํ๋์ง ์๋ ๋ฌธ์ ๊ฐ ์๊ธด ํ๋ค.
npm์ผ๋ก vue-chart.js๋ฅผ ์ถ๊ฐํด์ค ํ, LineChart.js์ import ํด์ฃผ์๋ค.
// LineChart.js
import { Vue } from "vue-property-decorator";
import { Line } from "vue-chartjs";
์ต๊ทผ์ 7์ผ ๊ฐ์ todo๋ฅผ filterํ latestTodos ๋ฐฐ์ด, ๋น๊ต๊ตฐ์ด ๋ ์ต๊ทผ 7์ผ ๊ฐ์ ์๋ฃ๋ todos, ๊ทธ๋ฆฌ๊ณ todos์ ๊ฐฏ์๋ฅผ ๋ฐํํด์ ๊ณ์ฐ ์์ ์ฌ์ฉํ๊ฒ ๋ temporary array์ธ numDaysLabels๋ก ๋ฐ์ดํฐ๋ฅผ ์ก์์ฃผ์๋ค.
chartdata๋ chart.js guide document์์ ์ ์ํ ๋๋ก ํ์์ ์ง์ผ ์์ฑํด์ค๋ค.
๋๋ ๋ ๊ฐ์ ๋ผ์ธ์ ๊ฐ๋ ์ฐจํธ๋ฅผ ๊ทธ๋ฆด ์์ ์ด์์ผ๋ datasets์ ๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ จํ๊ณ , sub label์๋ ๊ฐ๊ฐ ์ด๋ฆ์ ๋ถ์ฌ์ฃผ์์ง๋ง, ์์ธ ์ญํ ์ ํ๋ chartdata์ labels๋ ๋น์๋๊ณ ๋์ ์ผ๋ก ์์ฑํด์ฃผ๊ธฐ๋ก ํ๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก datasets์ ๊ฐ ๊ฐ์ฒด์ data ์์ฑ๋ ๋น ๋ฐฐ์ด๋ก ์ด๊ธฐํ๋ง ํด๋์๋ค.
data: () => ({
latestTodos: [],
latestDoneTodos: [],
numDaysLabels: [],
chartdata: {
labels: [],
datasets: [
{
label: "๐ฏ Created Todos",
pointBackgroundColor: "#007bff",
borderWidth: 1,
pointBorderColor: "#007bff",
borderColor: "#007bff",
data: []
},
{
label: "โจ Completed Todos",
pointBackgroundColor: "#E74D4E",
borderWidth: 1,
pointBorderColor: "#dc3545",
borderColor: "#E74D4E",
data: []
}
]
},
options: {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
}
],
xAxes: [
{
grindLines: {
display: false
}
}
]
},
legend: { display: true },
responsive: true,
maintainAspectRatio: false,
hoverBorderWidth: 20
}
}),
chart.js ์ ๊ณต์ ๋ฌธ์์์๋ mounted() ํ ์์ this.renderChart(this.chartdata, this.options)๋ก ๋ฐ์ดํฐ๊ฐ ๊ทธ๋ ค์ง๋๋ฐ props๋ก ์ข ๋ ํ์ฉ๋๋ฅผ ๋์ผ ์ ์๋ค๊ณ ๋์ ์์์ง๋ง, ์ปดํฌ๋ํธ ํ๋์์๋ง ์ฐ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ต์ ์์ data๋ render ์ฉ ๋ฉ์๋๋ ๋ค ํ ๋ฒ์ ์จ๋์๋ค.
๋ฐ์ดํฐ๋ค์ mounted ๋๊ธฐ ์ created() ํ ์์ ๋ชจ๋ ์ ์๋๊ณ , ์กฐ์ ๋ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ created() ํ ์์ ์๋ ๊ฐ์ด ๋ฉ์๋๋ฅผ ํธ์ถํด์ฃผ์๋ค.
created() {
this.chartdata.labels = this.locateLabelsOfDays();
this.latestTodos = this.getLatestTodos();
this.latestDoneTodos = this.getLatestDoneTodos();
this.chartdata.datasets[0].data = this.getTodosOfDay();
this.chartdata.datasets[1].data = this.getDoneTodosOfDay();
},
์์ธ ์ญํ ์ ํ๋ ์์ผ ๋ชฉ๋ก๋ค์ '์ค๋ ๋ ์ง'๊ฐ ๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ์์นํ๋๋ก ๋งค์ผ ๋งค์ผ ๋ฌ๋ผ์ ธ์ผ ํ๋ฏ๋ก, locateLabelsOfDays() ๋ฉ์๋๋ฅผ ํตํด ์ ์ํ๊ณ , ์ต๊ทผ todos ๋ ๊ฐ๋ฅผ ๋ง๋ค์ด์ค ๋ค, ๊ฐ todos์ ๊ฐฏ์๋ฅผ ์์ผ์ index์ ์์ํ๋๋ก ๋ง๋ ๋ฐฐ์ด ๋ฐ์ดํฐ๋ฅผ chartdata.datasets์ data์ ๊ฐ๊ฐ ์ฃผ์ ํด์ฃผ์๋ค.
๋์ ์ผ๋ก ๋ผ๋ฒจ ์์ฑํ๊ธฐ
locateLabelsOfDays() {
let labels = [];
let numLabels = [];
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth();
let day = today.getDate();
while (labels.length < 7) {
const labelOfDay = new Intl.DateTimeFormat("en-US", {
weekday: "short"
}).format(new Date(year, month, day));
labels.unshift(labelOfDay);
const numLabelOfDay = new Date(year, month, day).getDay();
numLabels.unshift(numLabelOfDay);
day--;
}
this.numDaysLabels = numLabels;
return labels;
},
Sun, Mon, Tue .. ๋ฑ์ string์ ์ด์ฉํด label์ ์์ฑํ ์๋ ์์๊ฒ ์ง๋ง, ๋ค์ํ Date ๊ฐ์ฒด ๊ด๋ จ ๋ฉ์๋๋ฅผ ์จ๋ณด๊ณ ์ถ์ด์
new Intl.DateTimeFormat() ๋ฉ์๋๋ฅผ ์ฐพ์๋ค. ํ์ค ๋ด์ฅ ๊ฐ์ฒด๋ก Intl.DateTimeFormat์ ์ธ์ด ์ค์ ์ ๋ฐ๋ผ ๋ ์ง์ ์๊ฐ ์์์ ์ง์ํ๋ค. ์ต์ ์ผ๋ก ์ธ๋ฐํ๊ฒ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ํ์ฑํ ์ ์๋ค. ๋๋ ์๋ฌธ์ผ๋ก short ๋ฒ์ ์ ์์ผ ์ด๋ฆ์ ์ป๊ธฐ ์ํด "en-US", short ๋ฑ์ ์ต์ ์ ๊ฐ์ก์ง๋ง, ํ๊ธ ์์ผ ์ด๋ฆ๋ ๋ฌผ๋ก ๊ฐ์ง ์ ์๊ณ , long, medium ๋ฑ์ ์ต์ ์ผ๋ก ๋ณด๋ค ๊ธด ์ด๋ฆ์ ์ถ์ถํ ์๋ ์๋ค.
์ค๋ ๋ ์ง๊ฐ ์ผ์์ผ์ด๋ผ๋ฉด, [์ํ์๋ชฉ๊ธํ ์ผ] ์์ผ๋ก ์ต๊ทผ ๊ณผ๊ฑฐ ์์ผ์ด ๋ฉ์๋ก ์ผ์ชฝ์ ์์นํด์ผ ํ๊ธฐ ๋๋ฌธ์ unshift()๋ก ๋ฐฐ์ด์ ์์๋ฅผ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ๋ฒ์ ์ผ๋ค.
์ต๊ทผ ๋ ์ง์ todos ๊ฐ๊ณ ์ค๊ธฐ
์์์๋ created() ์ฌ์ดํด์์ getLatesTodos๋ฅผ ๊ฐ์ง๊ณ ์ค๋ ๋ฐฉ๋ฒ์ ์ฝ๊ฐ ์์ ํด ์๋์ฒ๋ผ ๊ฐ์ ํ๋ค.
this.latestTodos = this.getLatesTodos("created");
this.latestDoneTodos = this.getLatesTodos("completed");
๋๊ฐ์ด ์ฐ์ธ ์ฝ๋๋ฅผ ์ ๋ฆฌํด ํ๋๋ก ์ฌ์ฌ์ฉํ๋ค. ๊ตฌ๋ถ์ String ์ธ์๋ก ๋์๋ค.
getLatesTodos(param) {
let d = new Date();
let day = d.getDate();
const lastDateOfWeek = new Date(d.setDate(day - 7));
if (param === "created") {
return this.$store.state.todos.filter(
todo =>
new Date(todo.createdAt) <= new Date() &&
new Date(todo.createdAt) >= lastDateOfWeek
);
} else {
return this.$store.state.todos.filter(
todo =>
new Date(todo.completedAt) <= new Date() &&
new Date(todo.completedAt) >= lastDateOfWeek
);
}
},
์ผ์ฃผ์ผ ์ ์ ๋ ์ง๋ฅผ ๋ณํํ๋ ๋ฐฉ๋ฒ์ ์๊ฐ๋ณด๋ค ๊ฐ๋จํ๋๋ฐ setDate()๋ฅผ ์ด์ฉํด์ ํด๊ฒฐํ ์ ์์๋ค.
์ค๋ ๋ ์ง๋ฅผ ์ป๊ธฐ ์ํด new Date() ๊ฐ์ฒด๋ฅผ ๋ณ์ d์ ๋ด๊ณ , ์์ผ์ ์ป์ด day์ ๋ด๋๋ค.
Date ๊ฐ์ฒด ๊ฐ๋ผ๋ฆฌ์ ๋น๊ต๋ฅผ ์ํด ์์ฒ๋ผ ๋ฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฃ์ด Date ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. Date๋ ๋น๊ต ์ฐ์ฐ์๋ฅผ ํตํด ๋น๊ต ๊ฐ๋ฅํ๋ค.
? operand
์ด๋ todo๊ฐ done ํ์๊ฐ ๋๊ธฐ๋ง ํ๋ฉด completedAt ๋ฐ์ดํฐ๊ฐ ์๊ฒจ๋ฒ๋ ค์, ์ฌ์ฉ์๊ฐ ์ทจ์ํ๋ค๊ฐ ๋ค์ pending todo ์๋ฆฌ๋ก ์ฎ๊ฒผ์ ๋์๋ ์ฌ์ ํ ํด๋น ๋ฐ์ดํฐ๊ฐ ์์กดํ๋ ๋ฌธ์ ๊ฐ ์์๋ค. ์ด๋๋ object์ completedAt ์์ฑ์ delete๋ก ์ง์์ฃผ๊ธฐ๋ง ํ๋ฉด ๋๋๋ฐ, ์๋์ฒ๋ผ typescript ์๋ฌ๊ฐ ๋ฌ๋ค.
the operand of 'delete' operator must be optional
์๊ณ ๋ณด๋ Todo๋ ์๋์ฒ๋ผ ํ์ ์ด ์ ์ธ๋ ์ํ์๋๋ฐ, completedAt์ด ๋น ๊ฐ์ผ ๊ฒฝ์ฐ๊ฐ ๊ณ ๋ ค๊ฐ ์ ๋์ด ์์ด์์๋ค.
interface Todo {
title: string;
isDone: boolean;
isEditable: boolean;
cate: string;
createdAt: Date;
completedAt: Date;
}
์ ์ฝ๋์์ completedAt ์์ฑ๋ง ์๋์ฒ๋ผ ๋ฐ๊ฟ์ค๋ค. ํด๋น ์ค๋ฅ๊ฐ ์ฌ๋ผ์ง๋ค.
completedAt?: Date;
์ด ๋ฌธ์ ๋ ์ด ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ค.
datasets์ date ์ฑ์ฐ๊ธฐ
์ฌ๊ธฐ์๋ ์๊น์ฒ๋ผ ๋ฐ๋ณต๋๋ ํจ์๊ฐ ์์ด์ ์ฝ๋ ์ ๋ฆฌ๋ฅผ ํ ๋ฒ ํ๋ค.
this.chartdata.datasets[0].data = this.getTodosOfDay("createdAt");
this.chartdata.datasets[1].data = this.getTodosOfDay("completedAt");
์ธ์๋ก ์ค string์ ๋์ค์ todo์ ์์ฑ ์ด๋ฆ์ผ๋ก ๊ทธ๋๋ก ์ฐ์ผ ๊ฒ์ด๋ฏ๋ก ๋ง์ถ๋ ๊ฒ์ด ์ข๋ค.
getTodosOfDay(param) {
let LengthOftodos = this.latestTodos;
let localNums = this.numDaysLabels.slice();
for (let i = 0; i < localNums.length; i++) {
let cnt = 0;
LengthOftodos.forEach(todo => {
const dayOfTodo = new Date(todo[param]).getDay();
if (dayOfTodo === localNums[i]) cnt++;
});
localNums[i] = cnt;
}
return localNums;
}
[param]์ผ๋ก ๋ ๋ถ๋ถ๋ง ์ ์ธํ๋ฉด ๋์ผํ ์ฝ๋๋ ๋ง์ฐฌ๊ฐ์ง์๋ค. Object์ ์์ฑ์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ dot ์ผ๋ก ๊ตฌ๋ถํด์ ์จ์ฃผ๊ฑฐ๋ Object["property"]๋ก ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. param์ด string ํ์ ์ผ๋ก ๋ค์ด์ค๋ฏ๋ก todo[param]์ผ๋ก ์ ์ด์ฃผ๋ฉด ์กฐ๊ฑด์ผ๋ก ๋ถ๊ธฐํ ํ์ ์์ด ์ฝ๋๊ฐ ๋์ฑ ๊ฐ๊ฒฐํด์ง๋ค.
์๋๋ ํจ์๋ฅผ ๋ ๊ฐ๋ก ๋๋ ์ฐ๋ฉฐ ์์ ๋ฐฐ์ด ์ญํ ์ ํ๋ numDaysLabels๋ฅผ ๋ ๋ฒ์ ๋๋ ์ฐ๊ธฐ ์ํด slice() ํจ์๋ก ๋ฐฐ์ด์ ๋ณต์ฌํ๋ ๋ฐฉ๋ฒ์ ์ผ๋๋ฐ, ์ฌ๊ธฐ์๋ ์ฌ์ค ๊ทธ๋ด ํ์๊ฐ ์์ด์ slice() ๋ฉ์๋๋ฅผ ์ญ์ ํด๋ ๋ฌด๋ฐฉํ๋ค.
์ ๋ฉ์๋์์ ์ค์ํ ์ ์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์์ ๋๋ 0 ๊ฐ์ ๋ฌด์กฐ๊ฑด ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค๋ ์ ! ๊ทธ๋ํ๊ฐ ๋ง๊ฐ์ง์ง ์๊ฒ ๋ฐ์ดํฐ ๋ณด์ ์ ์ํด์์ด๋ค.
์๋์ ์ผ๋ก ๊ฐ๋จํ Pie chart์ ๋นํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ ์ผ์ด ๋ง์๋ ๋ถ๋ถ์ด๋ผ ๊ณต๋ถ๊ฐ ๋ง์ด ๋๋ค. ์๋๋ ์ง์ ๋ผ์ธ ์ฐจํธ๋ฅผ ๊ตฌํํด์ ํ๋ ๊ฒ๊น์ง๊ฐ ๋ชฉํ์๋๋ฐ, ์ด๋ฒ ํ๋ก์ ํธ์์๋ ๋ชปํด์ ์์ฌ์ ๋ค. ๋ฐ์ดํฐ ์๊ฐํ์ ๋ํ ๊ด์ฌ์ด ๊ณ์ ๋์์ ธ์, ์ฐจํธ๋ ์ ๋๋ฉ์ด์ ๋ฑ์ ์ง์ ๊ตฌํํ๋ ๊ณต๋ถ๋ ์์ผ๋ก๋ ๊ณ์ ๊ด์ฌ์๊ฒ ํด๋๊ฐ ๊ฒ ๊ฐ๋ค.
๐ LineChart.js ์ปดํฌ๋ํธ์ ๋ํ ์ ์ฒด ์์ค๋ ์ด๊ณณ์์