Skip to content

baegofda/Project-GOC

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Project GOC


ย ๊ตญ๋‚ด์™ธ ์ฝ”๋กœ๋‚˜์—๋Œ€ํ•œ ์ •๋ณด์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์‚ฌ์ดํŠธ์ž…๋‹ˆ๋‹ค.


๋žœ๋”ฉํŽ˜์ด์ง€


ํ”„๋กœ์ ํŠธ ๊ตฌ๊ฒฝ๊ฐ€๊ธฐ


โœจ ๋ชฉํ‘œ


  • API๋ฅผ ํ™œ์šฉํ•œ ๊ตญ๋‚ด์™ธ ์ฝ”๋กœ๋‚˜ ํ†ต๊ณ„ ์ž๋ฃŒ ์ œ๊ณต
    • ๊ตญ๋‚ด
      • ๊ตญ๋‚ด ์ข…ํ•ฉ ํ˜„ํ™ฉ
      • ์‹œ๋„๋ณ„ ํ˜„ํ™ฉ
      • ๊ฑฐ๋ฆฌ๋‘๊ธฐ ์ •๋ณด (๋งํฌ)
      • ๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ ์ •๋ณด
      • ๊ตญ๋‚ด ์ฃผ์š” ์†Œ์‹ ('์ฝ”๋กœ๋‚˜ ๋ฐฑ์‹ ' ๋„ค์ด๋ฒ„ ๋‰ด์Šค, ๋‹ค์Œ ์›น๋ฌธ์„œ ๊ฒฐ๊ณผ)
    • ํ•ด์™ธ
      • ํ•ด์™ธ ์ข…ํ•ฉํ˜„ํ™ฉ
      • ์ฃผ๋ณ€ ๊ตญ๊ฐ€๋ณ„ ํ˜„ํ™ฉ
      • ์ „ ์„ธ๊ณ„ ๋Œ€์‹œ๋ณด๋“œ
  • ๋ฐ˜์‘ํ˜• ์›น ๊ตฌ์ถ• (Mobile First)

๐Ÿงฐ ์‚ฌ์šฉ๊ธฐ์ˆ 


  • HTML, CSS(PostCSS), Reactjs, Nodejs(Expressjs)
  • ํ˜ธ์ŠคํŒ…์„œ๋ฒ„ : ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„(Netlify), ๋ฐฑ์—”๋“œ ์„œ๋ฒ„(heroku)
  • ๊ณต๊ณต๋ฐ์ดํ„ฐํฌํ„ธ OPEN API (๊ตญ๋‚ด ์ข…ํ•ฉํ˜„ํ™ฉ, ์‹œ๋„๋ณ„ํ˜„ํ™ฉ, ๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ)
  • ์นด์นด์˜ค๋งต API (๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ ์œ„์น˜์ œ๊ณต)
  • ๋„ค์ด๋ฒ„ ๋‰ด์Šค API (์ฃผ์š”์†Œ์‹ - ๋„ค์ด๋ฒ„ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ)
  • ๋‹ค์Œ ๊ฒ€์ƒ‰ API (์ฃผ์š”์†Œ์‹ - ๋‹ค์Œ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ)
  • axios, xmltojson
  • momentjs(๋‚ ์งœ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ), Chartjs , sweetalert2

๐Ÿ“… ์†Œ์š”๊ธฐ๊ฐ„


  • 7์ผ

๐Ÿ‘€ ์ฃผ์š”๊ธฐ๋Šฅ & ๋ถ€๋ถ„ ์ฝ”๋“œ


๐Ÿ’ก ์ฃผ์š”๊ธฐ๋Šฅ

- ๊ตญ๋‚ด ์ฝ”๋กœ๋‚˜ ๋ฐ์ดํ„ฐ ์ •๋ณด ์ œ๊ณต
- ํ•ด์™ธ ์ฝ”๋กœ๋‚˜ ๋ฐ์ดํ„ฐ ์ •๋ณด ์ œ๊ณต
- ๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ ์ •๋ณด ์ œ๊ณต
- '์ฝ”๋กœ๋‚˜ ๋ฐฑ์‹ ' ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ œ๊ณต
- ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ๋ฐ ์—๋ŸฌํŽ˜์ด์ง€ ๊ตฌ์ถ•

ย ํŒŒ์ผ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.



1. ๊ตญ๋‚ด ์ฝ”๋กœ๋‚˜ ๋ฐ์ดํ„ฐ ์ •๋ณด ์ œ๊ณต


๐Ÿ’ป ์ฝ”๋“œ์‚ดํŽด๋ณด๊ธฐ (๊ตญ๋‚ด ์ข…ํ•ฉ ํ˜„ํ™ฉ)


๊ณต๊ณต๋ฐ์ดํ„ฐํฌํ„ธ OPEN API๋ฅผ ํ†ตํ•˜์—ฌ ๊ตญ๋‚ด ์ข…ํ•ฉ ํ˜„ํ™ฉ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค.



๐Ÿ“‚ server>Router>KoreaAllRoute.js



ย ํ•ด๋‹น 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>src>components>KoreaData>KoreaAllData>KoreaAllData.jsx



ย ํŒŒ์‹ฑ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์€ 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๋ฅผ ํ†ตํ•˜์—ฌ ๊ตญ๋‚ด ์‹œ๋„๋ณ„ ํ˜„ํ™ฉ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค.



๐Ÿ“‚ server>Router>KoreaCityRoute.js



ย ํ•ด๋‹น 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>src>components>KoreaData>KoreaCityData>KoreaCityData.jsx



ย ํŒŒ์‹ฑ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์€ 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);
  });

2. ํ•ด์™ธ ์ฝ”๋กœ๋‚˜ ๋ฐ์ดํ„ฐ ์ •๋ณด ์ œ๊ณต


๐Ÿ’ป ์ฝ”๋“œ์‚ดํŽด๋ณด๊ธฐ (์ฃผ๋ณ€ ๊ตญ๊ฐ€๋ณ„ ํ˜„ํ™ฉ)


NovelCOVID API๋ฅผ ํ†ตํ•˜์—ฌ ์ฃผ๋ณ€ ๊ตญ๊ฐ€๋ณ„ ํ˜„ํ™ฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“‚ client>src>components>OverseasData>OverseasCountryData>OverseasCountryData.jsx



ย ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ 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);
  });

3. ๊ตญ๋‚ด ์ฝ”๋กœ๋‚˜ ๊ด€๋ จ ์ •๋ณด์ œ๊ณต


๐Ÿ’ป ์ฝ”๋“œ์‚ดํŽด๋ณด๊ธฐ (๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ ์ •๋ณด)


์นด์นด์˜ค๋งต API์™€ ๊ณต๊ณต๋ฐ์ดํ„ฐํฌํ„ธ์„ ํ†ตํ•˜์—ฌ ๋ฐฑ์‹  ์ ‘์ข… ์„ผํ„ฐ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค.



๐Ÿ“‚ client>src>components>Center>Center.jsx



ย ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ผํ„ฐ์˜ ์œ„์น˜๋ฅผ ์นด์นด์˜ค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๋ฅผ ํ™œ์šฉํ•˜์—ฌ '์ฝ”๋กœ๋‚˜ ๋ฐฑ์‹ '๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.



๐Ÿ“‚ client>src>components>News>News.jsx



ย ๋‘๊ฐœ์˜ 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);
  });

4. ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ๋ฐ ์—๋ŸฌํŽ˜์ด์ง€ ๊ตฌ์ถ•


๐Ÿ’ป ์ฝ”๋“œ์‚ดํŽด๋ณด๊ธฐ (๋กœ๋”ฉ์Šคํ”ผ๋„ˆ)


state ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋กœ๋“œ์ „ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.



๐Ÿ“‚ client>src>components>Loading>Loading.jsx



ย ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋ฅผ ๋ณด์—ฌ์ค€ ํ›„ ๋กœ๋“œ๊ฐ€ ๋˜๋ฉด 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 />
    )}
  </>
);

๐Ÿ’ป ์ฝ”๋“œ์‚ดํŽด๋ณด๊ธฐ (Error))


API์„œ๋ฒ„์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๊ฒฝ์šฐ alert๊ณผ ํ•จ๊ป˜ ๋ฌธ๊ตฌ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.



๐Ÿ“‚ client>src>components>Err>Err.jsx



ย 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 Error))


์ž˜๋ชป๋œ ๊ฒฝ๋กœ๋กœ ์ ‘๊ทผ์‹œ 404 ํŽ˜์ด์ง€๋กœ ๋ผ์šฐํŠธ ๋ฉ๋‹ˆ๋‹ค.



๐Ÿ“‚ client>src>components>NotFound>NotFound.jsx



ย 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๋˜ํ•œ ์—ฐ๋™ํ•˜๊ณ ์‹ถ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published