ย ๊ตญ๋ด์ธ ์ฝ๋ก๋์๋ํ ์ ๋ณด์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ์ฌ์ดํธ์ ๋๋ค.
- API๋ฅผ ํ์ฉํ ๊ตญ๋ด์ธ ์ฝ๋ก๋ ํต๊ณ ์๋ฃ ์ ๊ณต
- ๊ตญ๋ด
- ๊ตญ๋ด ์ข ํฉ ํํฉ
- ์๋๋ณ ํํฉ
- ๊ฑฐ๋ฆฌ๋๊ธฐ ์ ๋ณด (๋งํฌ)
- ๋ฐฑ์ ์ ์ข ์ผํฐ ์ ๋ณด
- ๊ตญ๋ด ์ฃผ์ ์์ ('์ฝ๋ก๋ ๋ฐฑ์ ' ๋ค์ด๋ฒ ๋ด์ค, ๋ค์ ์น๋ฌธ์ ๊ฒฐ๊ณผ)
- ํด์ธ
- ํด์ธ ์ข ํฉํํฉ
- ์ฃผ๋ณ ๊ตญ๊ฐ๋ณ ํํฉ
- ์ ์ธ๊ณ ๋์๋ณด๋
- ๊ตญ๋ด
- ๋ฐ์ํ ์น ๊ตฌ์ถ (Mobile First)
- HTML, CSS(PostCSS), Reactjs, Nodejs(Expressjs)
- ํธ์คํ ์๋ฒ : ํ๋ก ํธ์๋ ์๋ฒ(Netlify), ๋ฐฑ์๋ ์๋ฒ(heroku)
- ๊ณต๊ณต๋ฐ์ดํฐํฌํธ OPEN API (๊ตญ๋ด ์ข ํฉํํฉ, ์๋๋ณํํฉ, ๋ฐฑ์ ์ ์ข ์ผํฐ)
- ์นด์นด์ค๋งต API (๋ฐฑ์ ์ ์ข ์ผํฐ ์์น์ ๊ณต)
- ๋ค์ด๋ฒ ๋ด์ค API (์ฃผ์์์ - ๋ค์ด๋ฒ ๊ฒ์๊ฒฐ๊ณผ)
- ๋ค์ ๊ฒ์ API (์ฃผ์์์ - ๋ค์ ๊ฒ์๊ฒฐ๊ณผ)
- axios, xmltojson
- momentjs(๋ ์ง๋ผ์ด๋ธ๋ฌ๋ฆฌ), Chartjs , sweetalert2
- 7์ผ
๐ก ์ฃผ์๊ธฐ๋ฅ
- ๊ตญ๋ด ์ฝ๋ก๋ ๋ฐ์ดํฐ ์ ๋ณด ์ ๊ณต
- ํด์ธ ์ฝ๋ก๋ ๋ฐ์ดํฐ ์ ๋ณด ์ ๊ณต
- ๋ฐฑ์ ์ ์ข
์ผํฐ ์ ๋ณด ์ ๊ณต
- '์ฝ๋ก๋ ๋ฐฑ์ ' ๊ฒ์ ๊ฒฐ๊ณผ ์ ๊ณต
- ๋ก๋ฉ ์คํผ๋ ๋ฐ ์๋ฌํ์ด์ง ๊ตฌ์ถ
ย ํ์ผ ๊ตฌ์กฐ๋ ์๋์ ๊ฐ์ต๋๋ค.
๊ณต๊ณต๋ฐ์ดํฐํฌํธ OPEN API๋ฅผ ํตํ์ฌ ๊ตญ๋ด ์ข ํฉ ํํฉ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
ย ํด๋น API๋ xml๋ฐ์ดํฐ ํ์ ์ ์ ๊ณตํ๊ธฐ์ JSONํ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด route ํ์ผ์์ xmltojson ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ json๋ฐ์ดํฐ๋ก ํ์ฑ ํ ๋ฐ์ดํฐ๋ฅผ client๋ก response ํด์ฃผ๊ณ ์์ต๋๋ค.
// ๋ ์ง ๊ตฌํ๊ธฐ ์ํจ (7~8์ผ๊ฐ์ ๋ฐ์ดํฐ)
const express = require("express");
const router = express.Router();
const request = require("request");
const converter = require("xml-js");
const config = require("../config/key");
const moment = require("moment");
// ๋ ์ง ๊ตฌํ๊ธฐ ์ํจ
const today = moment().format("YYYYMMDD");
const week = moment(moment().subtract(7, "day")).format("YYYYMMDD");
const options = {
method: "GET",
url: `http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson?serviceKey=${config.OPENAPI_KEY}&pageNo=1&numOfRows=10&startCreateDt=${week}&endCreateDt=${today}`,
headers: {},
};
router.get("/", (req, res) => {
request(options, (error, response, body) => {
if (error) throw new Error(error);
const xmlToJson = converter.xml2json(body);
res.send(xmlToJson);
});
});
module.exports = router;
ย ํ์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ client์์๋ ์ผ์ผ ์ง์ญ๋ฐ์ ๋ฐ ํด์ธ์ ์ ๊ณผ ์ข ํฉ ํ์ง์, ์น๋ฃ์ค, ์์น์, ์ฌ๋ง์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ state์ ๋ฐ์ดํฐ๋ฅผ setํด์ฃผ๊ณ ์์ต๋๋ค.
// ์ผ์ผ ํํฉ ๋ฐ์ดํฐ์ ๊ณต์ ์ํ ํด๋น ๋ฐ์ดํฐ ๊ฐ๊ณต
const cardsDataHandler = (totalData, yesterDayData) => {
const totalDefCnt = totalData[2].elements[0].text;
const totalIngCnt = totalData[8].elements[0].text;
const totalClearCnt = totalData[7].elements[0].text;
const totalDeathCnt = totalData[1].elements[0].text;
const yesterDayDefCnt = yesterDayData[2].elements[0].text;
const yesterDayIngCnt = yesterDayData[8].elements[0].text;
const yesterDayClearCnt = yesterDayData[7].elements[0].text;
const yesterDayDeathCnt = yesterDayData[1].elements[0].text;
//ํ์ง์
const newDefCnt = totalDefCnt - yesterDayDefCnt;
//๊ฒ์ฌ์งํ
const newIngCnt = totalIngCnt - yesterDayIngCnt;
//๊ฒฉ๋ฆฌํด์
const newClearCnt = totalClearCnt - yesterDayClearCnt;
//์ฌ๋ง์
const newDeathCnt = totalDeathCnt - yesterDayDeathCnt;
// ์ฒ๋จ์ ์ฝค๋ง๋ฅผ ์ํด toLocaleString() ์ฌ์ฉ
setCardsData([
{
id: 0,
title: "ํ์ง์ ์",
count: Number(totalDefCnt).toLocaleString(),
new: Number(newDefCnt).toLocaleString(),
},
{
id: 1,
title: "์น๋ฃ ์ค",
count: Number(totalIngCnt).toLocaleString(),
new: Number(newIngCnt).toLocaleString(),
},
{
id: 2,
title: "์์น์ ์",
count: Number(totalClearCnt).toLocaleString(),
new: Number(newClearCnt).toLocaleString(),
},
{
id: 3,
title: "์ฌ๋ง์ ์",
count: Number(totalDeathCnt).toLocaleString(),
new: Number(newDeathCnt).toLocaleString(),
},
]);
};
// ์ฐจํธ๋ฐ์ดํฐ ์ ๊ณต์ ์ํ reduce๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ ๊ฐ๊ณต
const cardsDataHandler = (totalData, yesterDayData) => {
const totalDefCnt = totalData[2].elements[0].text;
const totalIngCnt = totalData[8].elements[0].text;
const totalClearCnt = totalData[7].elements[0].text;
const totalDeathCnt = totalData[1].elements[0].text;
const yesterDayDefCnt = yesterDayData[2].elements[0].text;
const yesterDayIngCnt = yesterDayData[8].elements[0].text;
const yesterDayClearCnt = yesterDayData[7].elements[0].text;
const yesterDayDeathCnt = yesterDayData[1].elements[0].text;
//ํ์ง์
const newDefCnt = totalDefCnt - yesterDayDefCnt;
//๊ฒ์ฌ์งํ
const newIngCnt = totalIngCnt - yesterDayIngCnt;
//๊ฒฉ๋ฆฌํด์
const newClearCnt = totalClearCnt - yesterDayClearCnt;
//์ฌ๋ง์
const newDeathCnt = totalDeathCnt - yesterDayDeathCnt;
setCardsData([
{
id: 0,
title: "ํ์ง์ ์",
count: Number(totalDefCnt).toLocaleString(),
new: Number(newDefCnt).toLocaleString(),
},
{
id: 1,
title: "์น๋ฃ ์ค",
count: Number(totalIngCnt).toLocaleString(),
new: Number(newIngCnt).toLocaleString(),
},
{
id: 2,
title: "์์น์ ์",
count: Number(totalClearCnt).toLocaleString(),
new: Number(newClearCnt).toLocaleString(),
},
{
id: 3,
title: "์ฌ๋ง์ ์",
count: Number(totalDeathCnt).toLocaleString(),
new: Number(newDeathCnt).toLocaleString(),
},
]);
};
const chartDataHandler = (items) => {
const arr = items.reduce((prev, curr) => {
const currDate = curr.elements[13].elements[0].text;
const split = currDate.split(" ").slice(1, 3);
const month = split[0].split("์").slice(0, 1);
const day = split[1].split("์ผ").slice(0, 1);
const date = `${month}.${day}`;
const confirmed = curr.elements[6].elements[0].text;
const localData = curr.elements[9].elements[0].text;
const overFlowData = curr.elements[10].elements[0].text;
const category = curr.elements[3].elements[0].text;
prev.push({ confirmed, localData, overFlowData, date, category });
return prev;
}, []);
// ๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ํ์ง์
const otherObjs = arr.slice(0, 9).map((item) => {
return item;
});
const testConfirmed = otherObjs.map((obj) => {
return obj.confirmed;
});
const otherConfirmed = testConfirmed.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
// ๋๋ ์ฐจํธ ๋ฐ์ดํฐ
const doughnutObjs = arr.slice(9, 18).map((item) => {
return item;
});
const category = doughnutObjs.map((obj) => {
return obj.category;
});
const confirmed = doughnutObjs.map((obj) => {
return obj.confirmed;
});
const totalCategory = [...category, "๊ธฐํ"];
const totalConfirmed = [...confirmed, otherConfirmed];
const total = totalConfirmed.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
//๋ง๋ ์ฐจํธ ๋ฐ์ดํฐ
const barObjs = arr
.filter((item) => item.category === "ํฉ๊ณ")
.map((item) => {
return item;
});
const reBarObjs = barObjs.reverse();
const date = reBarObjs.map((obj) => {
return obj.date;
});
const localData = reBarObjs.map((obj) => {
return Number(obj.localData).toLocaleString();
});
const overFlowData = reBarObjs.map((obj) => {
return Number(obj.overFlowData).toLocaleString();
});
setDoughnutData({
// setDoughnutData
});
setBarData({
// setBarData
});
};
//7์ผ๊ฐ์ ๋ฐ์ดํฐ๋ง Handler์ธ์๊ฐ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
axios
.get("https://projectgoc.herokuapp.com/api")
.then((res) => {
const data = res.data.elements[0].elements[1].elements[0].elements;
const items = data.slice(0, 133);
const totalData = items[18].elements;
const yesterDayData = items[37].elements;
panelDataHandler(totalData);
cardsDataHandler(totalData, yesterDayData);
chartDataHandler(items);
setIsLoading(false);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
๊ณต๊ณต๋ฐ์ดํฐํฌํธ OPEN API๋ฅผ ํตํ์ฌ ๊ตญ๋ด ์๋๋ณ ํํฉ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
ย ํด๋น API๋ xmltojson ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
const express = require("express");
const router = express.Router();
const request = require("request");
const converter = require("xml-js");
const config = require("../config/key");
const moment = require("moment");
const today = moment().format("YYYYMMDD");
const week = moment(moment().subtract(2, "day")).format("YYYYMMDD");
const options = {
method: "GET",
url: `http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson?serviceKey=${config.OPENAPI_KEY}&pageNo=1&numOfRows=10&startCreateDt=${week}&endCreateDt=${today}`,
headers: {},
};
router.get("/", (req, res) => {
request(options, (error, response, body) => {
if (error) throw new Error(error);
const xmlToJson = converter.xml2json(body);
res.send(xmlToJson);
});
});
module.exports = router;
ย ํ์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ client์์๋ ์๋๋ณ ํ์ง์, ์น๋ฃ์ค, ์์น์, ์ฌ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
// ์๋๋ณ ์ข
ํฉ ๋ฐ์ดํฐ ๊ฐ๊ณต
const dataHandler = (items) => {
const arr = items.reduce((prev, curr) => {
const deathCnt = curr.elements[1].elements[0].text;
const confirmeCnt = curr.elements[2].elements[0].text;
const clearCnt = curr.elements[7].elements[0].text;
const ingCnt = curr.elements[8].elements[0].text;
const category = curr.elements[3].elements[0].text;
prev.push({ deathCnt, confirmeCnt, clearCnt, ingCnt, category });
return prev;
}, []);
// ๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ๋ฐฐ์ด
const otherObjs = arr.slice(0, 9).map((item) => {
return item;
});
// ๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ํ์ง์
const confirmeCnt = otherObjs.map((obj) => {
return obj.confirmeCnt;
});
const otherConfirmeCnt = confirmeCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
// ๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ์น๋ฃ์ค
const ingCnt = otherObjs.map((obj) => {
return obj.ingCnt;
});
const otherIngCnt = ingCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
// ๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ์์น์
const clearCnt = otherObjs.map((obj) => {
return obj.clearCnt;
});
const otherClearCnt = clearCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
//๊ธฐํ ์นดํ
๊ณ ๋ฆฌ ์ฌ๋ง์
const deathCnt = otherObjs.map((obj) => {
return obj.deathCnt;
});
const otherDeathCnt = deathCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
//์ฃผ์ ๋ฐ์ดํฐ ๋ฐฐ์ด
const mainObjs = arr.slice(9, 18).map((item) => {
return item;
});
//์ฃผ์ ๋ฐ์ดํฐ ํ์ง์
const mainConfirmeCnt = mainObjs.map((obj) => {
return Number(obj.confirmeCnt);
});
//์ฃผ์ ๋ฐ์ดํฐ ์น๋ฃ์ค
const mainIngCnt = mainObjs.map((obj) => {
return Number(obj.ingCnt);
});
//์ฃผ์ ๋ฐ์ดํฐ ์์น์
const mainClearCnt = mainObjs.map((obj) => {
return Number(obj.clearCnt);
});
//์ฃผ์ ๋ฐ์ดํฐ ์ฌ๋ง์
const mainDeathCnt = mainObjs.map((obj) => {
return Number(obj.deathCnt);
});
//์ฃผ์ ๋ฐ์ดํฐ ์นดํ
๊ณ ๋ฆฌ
const category = mainObjs.map((obj) => {
return obj.category;
});
//ํตํฉ ๋ฐ์ดํฐ
const totalCategory = [...category, "๊ธฐํ"];
const totalConfirmeCnt = [...mainConfirmeCnt, otherConfirmeCnt];
const totalIngCnt = [...mainIngCnt, otherIngCnt];
const totalClearCnt = [...mainClearCnt, otherClearCnt];
const totalDeathCnt = [...mainDeathCnt, otherDeathCnt];
const sumFirmeCnt = totalConfirmeCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
const sumIngCnt = totalIngCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
const sumClearCnt = totalClearCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
const sumDeathCnt = totalDeathCnt.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
setData([
// setData
]);
};
//๊ฐ์ฅ ์ต๊ทผ์ ๋ฐ์ดํฐ๋ง Handler์ธ์๊ฐ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
axios
.get("https://projectgoc.herokuapp.com/api/city")
.then((res) => {
const data = res.data.elements[0].elements[1].elements[0].elements;
const items = data.slice(0, 19);
dataHandler(items);
setIsLoading(false);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
NovelCOVID API๋ฅผ ํตํ์ฌ ์ฃผ๋ณ ๊ตญ๊ฐ๋ณ ํํฉ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
ย ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ client์์๋ ์ฃผ๋ณ ๊ตญ๊ฐ๋ณ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
// ์ฃผ๋ณ ๊ตญ๊ฐ๋ณ ๋ฐ์ดํฐ ๊ฐ๊ณต
const OverseasCountryDataHandler = (items) => {
const arr = items.reduce((prev, curr) => {
const country = curr.country;
const cases = curr.timeline.cases[Object.keys(curr.timeline.cases)];
const deaths = curr.timeline.deaths[Object.keys(curr.timeline.deaths)];
const recovered =
curr.timeline.recovered[Object.keys(curr.timeline.recovered)];
prev.push({
country: country || 0,
cases: cases || 0,
deaths: deaths || 0,
recovered: recovered || 0,
});
return prev;
}, []);
const reArr = arr.reverse();
const country = reArr.map((item) => {
return item.country;
});
const cases = reArr.map((item) => {
return item.cases;
});
const deaths = reArr.map((item) => {
return item.deaths;
});
const recovered = reArr.map((item) => {
return item.recovered;
});
const sumFirmeCnt = cases.reduce((prev, curr) => Number(prev) + Number(curr));
const sumDeathsCnt = deaths.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
const sumClearCnt = recovered.reduce(
(prev, curr) => Number(prev) + Number(curr)
);
setData([
// setData
]);
};
axios
.get("https://projectgoc.herokuapp.com/api/country")
.then((res) => {
const items = res.data;
OverseasCountryDataHandler(items);
setIsLoading(false);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
์นด์นด์ค๋งต API์ ๊ณต๊ณต๋ฐ์ดํฐํฌํธ์ ํตํ์ฌ ๋ฐฑ์ ์ ์ข ์ผํฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
ย ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ผํฐ์ ์์น๋ฅผ ์นด์นด์คmap์ผ๋ก ์ ๋ฌํ์ฌ ๋ณด์ฌ์ค๋๋ค.
// ์นด์นด์ค๋งต API ํธ์ถ
const kakaoMaps = (centers) => {
const mapContainer = document.getElementById("map"); // ์ง๋๋ฅผ ํ์ํ div
const mapOption = {
center: new kakao.maps.LatLng(33.450701, 126.570667), // ์ง๋์ ์ค์ฌ์ขํ
level: 10, // ์ง๋์ ํ๋ ๋ ๋ฒจ
};
const map = new kakao.maps.Map(mapContainer, mapOption); // ์ง๋๋ฅผ ์์ฑํฉ๋๋ค
// ------------------- ๋ค์ค๋ง์ปค ์์ฑ
centers.forEach((center) => {
//๋ง์ปค ์ด๋ฏธ์ง url
const imageSrc =
"https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png";
// ๋ง์ปค ์ด๋ฏธ์ง์ ์ด๋ฏธ์ง ํฌ๊ธฐ ์
๋๋ค
const imageSize = new kakao.maps.Size(24, 35);
// ๋ง์ปค ์ด๋ฏธ์ง๋ฅผ ์์ฑํฉ๋๋ค
const markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize);
// ๋ง์ปค๋ฅผ ์์ฑํฉ๋๋ค
const marker = new kakao.maps.Marker({
map: map, // ๋ง์ปค๋ฅผ ํ์ํ ์ง๋
position: center.latlng, // ๋ง์ปค๋ฅผ ํ์ํ ์์น
clickable: true,
image: markerImage, // ๋ง์ปค ์ด๋ฏธ์ง
});
// ๋ง์ปค๋ฅผ ์ง๋์ ํ์ํฉ๋๋ค.
marker.setMap(map);
const iwRemoveable = true;
// ์ธํฌ์๋์ฐ๋ฅผ ์์ฑํฉ๋๋ค
const infowindow = new kakao.maps.InfoWindow({
content: center.info,
removable: iwRemoveable,
});
// ๋ง์ปค์ ํด๋ฆญ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํฉ๋๋ค
kakao.maps.event.addListener(marker, "click", () => {
// ๋ง์ปค ์์ ์ธํฌ์๋์ฐ๋ฅผ ํ์ํฉ๋๋ค
infowindow.open(map, marker);
});
});
// --------------------------- ์ฌ์ฉ์ ์์น ์ธ์
// HTML5์ geolocation์ผ๋ก ์ฌ์ฉํ ์ ์๋์ง ํ์ธํฉ๋๋ค
if (navigator.geolocation) {
// GeoLocation์ ์ด์ฉํด์ ์ ์ ์์น๋ฅผ ์ป์ด์ต๋๋ค
navigator.geolocation.getCurrentPosition((position) => {
let lat = position.coords.latitude; // ์๋
let lon = position.coords.longitude; // ๊ฒฝ๋
let locPosition = new kakao.maps.LatLng(lat, lon); // ๋ง์ปค๊ฐ ํ์๋ ์์น๋ฅผ geolocation์ผ๋ก ์ป์ด์จ ์ขํ๋ก ์์ฑํฉ๋๋ค
const message = '<div style="padding:5px;">์ฌ๊ธฐ์ ๊ณ์ ๊ฐ์?!</div>'; // ์ธํฌ์๋์ฐ์ ํ์๋ ๋ด์ฉ์
๋๋ค
// ๋ง์ปค์ ์ธํฌ์๋์ฐ๋ฅผ ํ์ํฉ๋๋ค
displayMarker(locPosition, message);
});
} else {
// HTML5์ GeoLocation์ ์ฌ์ฉํ ์ ์์๋ ๋ง์ปค ํ์ ์์น์ ์ธํฌ์๋์ฐ ๋ด์ฉ์ ์ค์ ํฉ๋๋ค
const locPosition = new kakao.maps.LatLng(33.450701, 126.570667);
const message = "geolocation์ ์ฌ์ฉํ ์ ์์ด์..";
displayMarker(locPosition, message);
}
// ์ง๋์ ๋ง์ปค์ ์ธํฌ์๋์ฐ๋ฅผ ํ์ํ๋ ํจ์์
๋๋ค
function displayMarker(locPosition, message) {
// ๋ง์ปค๋ฅผ ์์ฑํฉ๋๋ค
const marker = new kakao.maps.Marker({
map: map,
position: locPosition,
});
const iwContent = message; // ์ธํฌ์๋์ฐ์ ํ์ํ ๋ด์ฉ
const iwRemoveable = true;
// ์ธํฌ์๋์ฐ๋ฅผ ์์ฑํฉ๋๋ค
const infowindow = new kakao.maps.InfoWindow({
content: iwContent,
removable: iwRemoveable,
});
// ์ธํฌ์๋์ฐ๋ฅผ ๋ง์ปค์์ ํ์ํฉ๋๋ค
infowindow.open(map, marker);
// ์ง๋ ์ค์ฌ์ขํ๋ฅผ ์ ์์์น๋ก ๋ณ๊ฒฝํฉ๋๋ค
map.setCenter(locPosition);
}
};
// ๋ฐ์ ๋ฐ์ดํฐ ๊ฐ๊ณต ๋ฐ state set
const centersDataHandler = (items) => {
const arr = items.map((item) => {
const name = item.centerName;
const sp = name.split("์ฝ๋ก๋19")[1];
const center = {
id: item.id,
centerName: sp,
orgName: item.org,
centerType: item.centerType,
facilityName: item.facilityName,
address: item.address,
sido: item.sido,
sigungu: item.sigungu,
zipCode: item.zipCode,
url: `https://map.kakao.com/link/map/${item.org || sp},${item.lng},${
item.lat
}`,
latlng: new kakao.maps.LatLng(item.lng, item.lat),
info: `<div style="width:200px; padding:5px; font-size:12px;">${
item.org || sp
} <br><a href="https://map.kakao.com/link/map/${item.org || sp},${
item.lng
},${
item.lat
}" style="color:blue" target="_blank">ํฐ์ง๋๋ณด๊ธฐ</a> <a href="https://map.kakao.com/link/to/${
item.org || sp
},${item.lng},${
item.lat
}" style="color:blue" target="_blank">๊ธธ์ฐพ๊ธฐ</a></div>`,
};
return center;
});
setCenters(arr);
kakaoMaps(arr);
};
axios
.get("https://projectgoc.herokuapp.com/api/center")
.then((res) => {
const items = res.data.data;
setIsLoading(false);
centersDataHandler(items);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
๋ค์ด๋ฒ ๋ด์ค API์ ๋ค์ ๊ฒ์ API๋ฅผ ํ์ฉํ์ฌ '์ฝ๋ก๋ ๋ฐฑ์ '๊ด๋ จ ๋ฌธ์๋ฅผ ๋ณด์ฌ์ค๋๋ค.
ย ๋๊ฐ์ Router์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ Axios์ Multiple Request๊ธฐ๋ฅ์ ์ฌ์ฉํ์์ต๋๋ค.
// ๋ฐ์์จ ๋ฌธ์๋ฐ์ดํฐ ๊ฐ๊ณต
const newsDataHandler = (items) => {
const arr = items.reduce((prev, curr) => {
// ๋ฌธ์๋ค์ title๊ณผ desc์ ๋ถํ์ํ ํน์๋ฌธ์๋ฅผ ์ ๊ฑฐํ๊ธฐ์ํ ์ ๊ท์
const reg = /[<b>|</b>|&qout|amp|lt|gt;]/g;
const regTitle = curr.title
.replace(reg, "")
.replace(/#39/g, "'")
.replace(/#34/g, '"');
const itemDesc = curr.desc;
// ๋ฌธ์ ๋ ์ง์ ๋ฐ์ดํฐ ํฌ๋งท ๋ณ๊ฒฝ์ ์ํ momentjs๋ผ์ด๋ธ๋ฌ๋ฆฌ
const articleDate = moment(curr.date).format("YYYY.MM.DD");
const articleDesc = itemDesc
.replace(reg, "")
.replace(/#39/g, "'")
.replace(/#34/g, '"');
const articleUrl = curr.url;
const articleSite = curr.site;
prev.push({
id: i++,
title: regTitle,
desc: articleDesc,
url: articleUrl,
date: articleDate,
site: articleSite,
});
return prev;
}, []);
setNews({
// setNews
});
};
// ๋๊ฐ์ request๋ฅผ ์ํ Axios Multiple Request๊ธฐ๋ฅ ์ฌ์ฉ
axios
.all([
axios.get("https://projectgoc.herokuapp.com/api/news/naver"),
axios.get("https://projectgoc.herokuapp.com/api/news/daum"),
])
.then(
axios.spread((res1, res2) => {
const naverArr = res1.data.items.reduce((prev, curr) => {
prev.push({
title: curr.title,
desc: curr.description,
url: curr.link,
date: curr.pubDate,
site: "naver",
});
return prev;
}, []);
const daumArr = res2.data.documents.reduce((prev, curr) => {
prev.push({
title: curr.title,
desc: curr.contents,
url: curr.url,
date: curr.datetime,
site: "daum",
});
return prev;
}, []);
const arr = [...naverArr, ...daumArr];
newsDataHandler(arr);
setIsLoading(false);
})
)
.catch((err) => {
setIsStatus(false);
console.log(err);
});
state ๊ด๋ฆฌ๋ฅผ ํตํ์ฌ ๋ฐ์ดํฐ ๋ก๋์ ๋ก๋ฉ ์คํผ๋๋ฅผ ๋ณด์ฌ์ค๋๋ค.
ย ๋ฐ์ดํฐ๊ฐ ๋ก๋๋๊ธฐ ์ ์ ๋ก๋ฉ ์คํผ๋๋ฅผ ๋ณด์ฌ์ค ํ ๋ก๋๊ฐ ๋๋ฉด state๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ์คํผ๋๊ฐ ์ฌ๋ผ์ง๋๋ค.
const [isLoading, setIsLoading] = useState(true);
axios
.get("https://projectgoc.herokuapp.com/api/country")
.then((res) => {
const items = res.data;
OverseasCountryDataHandler(items);
setIsLoading(false);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
return (
<>
{isStatus ? (
<>
{isLoading ? (
<Loading />
) : (
<>
<ContentTitle data={title} />
<section className={styles.wrap}>
<DoughnutChart data={data} options={options} />
</section>
</>
)}
</>
) : (
<Err />
)}
</>
);
API์๋ฒ์ ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๊ฒฝ์ฐ alert๊ณผ ํจ๊ป ๋ฌธ๊ตฌ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
ย API์๋ฒ์ ๋ฌธ์ ๊ฐ ์๊ฒจ ๋ฐ์ดํฐ๋ก๋๊ฐ ๋ถ๊ฐ๋ฅํ๋ค๋ฉด .catch๋ฌธ์์ state๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
const [isStatus, setIsStatus] = useState(true);
axios
.get("https://projectgoc.herokuapp.com/api/country")
.then((res) => {
const items = res.data;
OverseasCountryDataHandler(items);
setIsLoading(false);
})
.catch((err) => {
setIsStatus(false);
console.log(err);
});
return (
<>
{status ? (
<>
{isLoading ? (
<Loading />
) : (
<>
<ContentTitle data={title} />
<section className={styles.wrap}>
<DoughnutChart data={data} options={options} />
</section>
</>
)}
</>
) : (
<Err />
)}
</>
);
์๋ชป๋ ๊ฒฝ๋ก๋ก ์ ๊ทผ์ 404 ํ์ด์ง๋ก ๋ผ์ฐํธ ๋ฉ๋๋ค.
ย react-router-dom์ Route๋ฅผ ์ด์ฉํ์ฌ 404 ํ์ด์ง๋ก ๋ผ์ฐํธ ๋ฉ๋๋ค.
const App = () => {
return (
<>
<Header />
<main className="main">
<Switch>
<Route exact path={"/"} component={KoreaAllData} />
<Route exact path={"/city"} component={KoreaCityData} />
<Route exact path={"/all"} component={OverseasAllData} />
<Route exact path={"/country"} component={OverseasCountryData} />
<Route exact path={"/dashboard"} component={JHUDashboard} />
<Route exact path={"/center"} component={Center} />
<Route exact path={"/news"} component={News} />
<Route exact path={"/other"} component={Other} />
<Route to={"/404"} component={NotFound} />
</Switch>
</main>
<Footer />
</>
);
};
๋ฆฌ์กํธ ํ์ต ์ดํ ์ฒ์ ๋ง๋ค์ด๋ณธ ์ฌ์ดํธ์๋ค.
Expressjs์ ์ฐ๋์ด ์ ๋๋ก ๋์ง์์ Proxy์ ๊ดํด ์ข๋ ์๊ฐํด๋ณด๊ณ API ๋ฐ์ดํฐ ๊ฐ๊ณต๋ฒ, ๋ฐ์ดํฐ ์๊ฐํ๋ฅผ ์ํ Chartjs, momentjs ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ ํด๋ณด๋ฉฐ API์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ๋ง์ฐํ ๋๋ ค์ ๋ํ ์์จ ์ ์์๊ณ ํ๋ก์ ํธ ๊ตฌ์ฑ๋ถํฐ ์ปดํฌ๋ํธ ๋ถ๋ฆฌ, ๋ฐฑ์๋ ์๋ฒ ๊ตฌ์ถ ๋ฐ ์ฐ๋, ๊ณต๊ณต๋ฐ์ดํฐ API ์ฌ์ฉ๋ฒ, ์๋ฒ ํธ์คํ ๊น์ง ํด๋ณด๋ฉฐ ์ ์ฒด์ ์ธ ํ๋ก์ฐ๋ฅผ ์ดํดํ๋๋ฐ ๋ง์ ๋์์ด ๋๊ฒ ๊ฐ๋ค.
๋ํ ํธ์คํ ํ ์ปค๋ฎค๋ํฐ์ ์ง์ธ๋ค์ ํผ๋๋ฐฑ์ ํตํ์ฌ ๋ฏธ์ณ ์๊ฐ์น๋ชปํ๋ ๋ถ๋ถ๋ค์ ๋ํด ์์๊ฐ๊ณ ์ฝ๋๋ฅผ ํ๋ฒ ๋ ๋ณด๋ฉฐ ์ ์ ์ ๊ฒฝํ๊ณผ ๋ฆฌํฉํ ๋ง์๋ํ ์ค์์ฑ ๋ํ ๋๋ ์ ์์๋ค.
์ถํ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๋ฉด ts๋ฅผ ๋์ ํด๋ณด๊ณ ์ถ๊ณ ๋ก๊ทธ์ธ๊ธฐ๋ฅ๋ฑ๊ณผ ๋ฆฌ๋์ค, db๋ํ ์ฐ๋ํ๊ณ ์ถ๋ค.