Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/weather poc #39

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ module.exports = {
"plugin:prettier/recommended",
"plugin:storybook/recommended",
],
rules: {
"react-hooks/exhaustive-deps": "off",
},
};
82 changes: 82 additions & 0 deletions pages/api/weather.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import axios from "axios";
import { LOCATION_OBJECT } from "create/WeatherBox/const";
import { getCorrectBaseTime } from "create/WeatherBox/helper";
import { WeatherItem, WeatherResponse } from "create/WeatherBox/interfaces";
import { isEmpty, isUndefined } from "lodash";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { date, locationCode } = req.query;
if (isEmpty(date) || isEmpty(locationCode)) return res.status(400).end();
const { x, y } = LOCATION_OBJECT[locationCode as string];

const params = {
serviceKey: process.env.NEXT_PUBLIC_WEATHER_API_KEY ?? "",
numOfRows: "846",
pageNo: "1",
nx: x,
ny: y,
dataType: "JSON",
...getCorrectBaseTime(),
};

const result = await axios.get(
"http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst",
{ params },
);

const items = result.data.response.body.items.item as WeatherItem[];
const newWeatherObject = items.reduce((acc, cur) => {
const date = cur.fcstDate;
if (isUndefined(acc[date])) {
acc[date] = {
TMP: Array(24),
SKY: Array(24),
PTY: Array(24),
};
}

switch (cur.category) {
case "TMP":
case "SKY":
case "PTY":
const time = Number(cur.fcstTime.slice(0, 2));
acc[date][cur.category][time] = Number(cur.fcstValue);
break;
case "TMN":
case "TMX":
acc[date][cur.category] = Number(cur.fcstValue);
break;
}
return acc;
}, {} as WeatherResponse);

res.json(newWeatherObject);
}

/**
*
* 하늘상태(SKY) 코드 : 맑음(1), 구름많음(3), 흐림(4)
* 강수형태(PTY) 코드 : 없음(0), 비(1), 비/눈(2), 눈(3), 소나기(4)
*
* 단기예보 제공 시간대는 0200, 0500, 0800, 1100, 1400, 1700, 2000, 2300
* API 제공 시간(~이후) : 02:10, 05:10, 08:10, 11:10, 14:10, 17:10, 20:10, 23:10
*
* POP 강수확률
* PTY 강수형태
* PCP 1시간 강수량
* REH 습도
* SNO 1시간 신적설
* SKY 하늘상태
* TMP 1시간 기온
* TMN 일 최저기온
* TMX 일 최고기온
* UUU 풍속(동서성분)
* VVV 풍속(남북성분)
* WAV 파고
* VEC 풍향
* WSD 풍속
*/
57 changes: 57 additions & 0 deletions pages/test/weather-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import WeatherBox from "create/WeatherBox";
import {
getLoc1Arrays,
getLoc2Arrays,
getLocationCode,
} from "create/WeatherBox/helper";
import { ChangeEvent, useEffect, useState } from "react";
import SelectBox from "@shared/components/SelectBox";

const WeatherTest = () => {
const [loc1Arrays] = useState(getLoc1Arrays());
const [loc2Arrays, setLoc2Arrays] = useState<string[]>([]);

const [selectedLoc1, setSelectedLoc1] = useState("");
const [selectedLoc2, setSelectedLoc2] = useState("");

const [date, setDate] = useState("");

useEffect(() => {
setLoc2Arrays(getLoc2Arrays(selectedLoc1));
setSelectedLoc2("");
}, [selectedLoc1]);

const handleChangeSelectBoxLoc1 = (e: ChangeEvent<HTMLSelectElement>) => {
setSelectedLoc1(e.target.value);
};

const handleChangeSelectBoxLoc2 = (e: ChangeEvent<HTMLSelectElement>) => {
setSelectedLoc2(e.target.value);
};

const handleChangeDate = (e: ChangeEvent<HTMLInputElement>) => {
setDate(e.target.value);
};

return (
<div className="flex items-center justify-center w-screen h-screen">
<input type="date" value={date} onChange={handleChangeDate} />
<SelectBox
options={loc1Arrays}
onChange={handleChangeSelectBoxLoc1}
defaultLabel="시 선택"
/>
<SelectBox
options={loc2Arrays}
onChange={handleChangeSelectBoxLoc2}
defaultLabel="군구 선택"
/>
<WeatherBox
date={date}
locationCode={getLocationCode(selectedLoc1, selectedLoc2)}
/>
</div>
);
};

export default WeatherTest;
Binary file added public/assets/weather/hailstone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/weather/rainy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/weather/snow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/weather/storm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/weather/sunny.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/weather/windy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
269 changes: 269 additions & 0 deletions src/create/WeatherBox/const.ts

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions src/create/WeatherBox/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { BASE_TIMES, LOCATION_OBJECT } from "./const";

/**
* @description loc1 배열을 반환
*/
export const getLoc1Arrays = () => {
return Object.values(LOCATION_OBJECT).reduce<string[]>((prev, curr) => {
if (!prev.includes(curr.loc1)) return [...prev, curr.loc1];
return prev;
}, []);
};

/**
* @description loc1에 해당하는 loc2 배열을 반환
*/
export const getLoc2Arrays = (loc1: string) => {
return Object.values(LOCATION_OBJECT).reduce<string[]>((prev, curr) => {
if (curr.loc1 === loc1 && !prev.includes(curr.loc2))
return [...prev, curr.loc2];
return prev;
}, []);
};

/**
* @description loc1, loc2에 해당하는 locationCode를 반환
*/
export const getLocationCode = (loc1: string, loc2: string) => {
if (loc1 === "" || loc2 === "") return "";
const selectedLoc = Object.entries(LOCATION_OBJECT).find(
([_, value]) => value.loc1 === loc1 && value.loc2 === loc2,
);
return selectedLoc?.[0] ?? "";
};

/**
* @description Date 데이터를 YYYYMMDD 형식으로 반환
*/
export const formatDate = (dateString?: string | number) => {
const date = new Date(dateString ?? Date.now());
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}${month}${day}`;
};

/**
* @description 현재 시간에 맞는 base_date, base_time을 반환
*/
export const getCorrectBaseTime = () => {
const today = new Date();
const currentHour = today.getHours();
const currentMinute = today.getMinutes();

if (currentHour < 2 || (currentHour === 2 && currentMinute < 10))
return {
base_date: formatDate(today.setDate(today.getDate() - 1)),
base_time: "2300",
};

for (let i = 1; i < BASE_TIMES.length; i++) {
const baseTime = BASE_TIMES[i - 1];
if (
currentHour < i * 3 + 2 ||
(currentHour === i * 3 + 2 && currentMinute < 10)
) {
return {
base_date: formatDate(),
base_time: baseTime,
};
}
}
};
68 changes: 68 additions & 0 deletions src/create/WeatherBox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import axios from "axios";
import Image from "next/image";
import { useEffect, useState } from "react";
import { LOCATION_OBJECT } from "./const";
import { WeatherBoxProps, WeatherObject, WeatherResponse } from "./interfaces";
import Sunny from "/public/assets/weather/sunny.png";

const WeatherBox = (props: WeatherBoxProps) => {
const locObj = LOCATION_OBJECT[props.locationCode];
const [weatherObject, setWeatherObject] = useState<WeatherObject>();

useEffect(() => {
axios
.get<WeatherResponse>("/api/weather", { params: props })
.then((res) => {
const rawDate = props.date.replace(/-/g, "");
if (res.data[rawDate]) {
console.log(res.data[rawDate]);
setWeatherObject(res.data[rawDate]);
}
return;
})
.catch((err) => {});
}, [props.locationCode]);

const getWeatherText = () => {
return "";
};

return (
<div className="bg-gradient-to-b from-c-pink to-[#FFCED1] rounded-[10px] w-[338px] h-[110px]">
<div className="flex items-center justify-around h-full">
<div>
<div className="text-base text-white font-extralight">
{locObj?.loc1}
</div>
<div className="text-[28px] text-white font-medium">
{locObj?.loc2}
</div>
<div className="text-sm text-white font-extralight">
{getWeatherText()}
{/* TODO: 하늘상태, 강수상태에 따른 텍스트 변경 기능 추가 */}
</div>
</div>
<div className="flex items-center ">
{weatherObject?.TMX && weatherObject?.TMN ? (
<>
<div>
<span className="text-4xl text-white font-extralight ">
{weatherObject?.TMN} / {weatherObject?.TMX}
</span>
<span className="text-xl text-white font-extralight">°C</span>
</div>
<div>
<Image src={Sunny} height={100} width={100} />
{/* TODO: 하늘상태, 강수상태에 따른 아이콘 변경 기능 추가 */}
</div>
</>
) : (
<div className="text-sm text-white">예상 날씨를 알 수 없어요</div>
)}
</div>
</div>
</div>
);
};

export default WeatherBox;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 코드 패치는 Next.js 프로젝트에서 사용되는 WeatherBox 컴포넌트입니다. 약간의 개선과 버그 위험을 제안해드리겠습니다:

  1. axios 라이브러리를 import 하기 전에 현재 프로젝트에 이 라이브러리가 설치되었는지 확인하세요.
  2. const [weatherObject, setWeatherObject] = useState<WeatherObject>(); 를 초기화할 때 초기 값을 null로 설정하는 것이 좋습니다.
  3. .catch((err) => {}) 에서 오류 처리를 추가하는 것이 좋습니다. 적어도 로깅을 통해 에러를 파악할 수 있습니다.
  4. getWeatherText 함수가 현재 항상 빈 문자열을 반환하고 있는데, 원하는 동작을 구현해야 합니다.
  5. <div className="text-sm text-white">예상 날씨를 알 수 없어요</div>를 더욱 명확한 메시지로 변경하는 것이 좋을 수 있습니다.
  6. 에러 처리 및 예외 상황에 대한 주석을 추가하는 것이 유용합니다.

이 외에도 코드 리뷰에는 다양한 관점이 있을 수 있으며, 기준은 프로젝트 요구 사항과 코딩 스타일 가이드에 따릅니다.

46 changes: 46 additions & 0 deletions src/create/WeatherBox/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export interface WeatherBoxProps {
date: string;
locationCode: string;
}
export interface WeatherItem {
baseDate: string;
baseTime: string;
category: WCategory;
fcstDate: string;
fcstTime: string;
fcstValue: string;
nx: number;
ny: number;
}
export interface LocationProps {
loc1: string;
loc2: string;
x: number;
y: number;
}
export type WCategory =
| "POP"
| "PTY"
| "PCP"
| "REH"
| "SNO"
| "SKY"
| "TMP"
| "TMN"
| "TMX"
| "UUU"
| "VVV"
| "WAV"
| "VEC"
| "WSD";

export interface WeatherObject {
TMP: number[];
SKY: number[];
PTY: number[];
TMN?: number;
TMX?: number;
}
export interface WeatherResponse {
[key: string]: WeatherObject;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 코드 패치를 요약적으로 검토하였습니다. 아래는 피드백입니다:

  1. 인터페이스 및 타입 정의는 명확하고 일관성 있어 보입니다. 각 객체 및 속성에 대한 설명이 제공되어 가독성을 높일 수 있습니다.

  2. 오류 리스크: 현재 코드에서 큰 문제점은 보이지 않습니다. 그러나 실제 구현과 연관된 다른 코드 부분이 없으므로 완전한 분석은 어렵습니다.

  3. 개선 제안:

  • WeatherObject 인터페이스의 TMP, SKY, PTY 속성의 데이터 유형이 number[]로 정의되어 있습니다. 이 데이터 유형이 어떤 것을 의미하는지 주석 및 설명을 추가하는 것이 도움이 될 수 있습니다.

  • 일부 인터페이스의 속성 이름은 알기 어려울 수 있습니다. 더 명확한 속성 이름을 선택하여 가독성을 높일 수 있습니다.

이상입니다! 위 코드 패치는 초기 검토에서 주요한 문제가 보이지 않았으며, 인터페이스 및 타입 정의도 일관성이 있어 보입니다. 그러나 전체 시스템 구조와 함께 사용되는 코드인지, 추가적인 설정 또는 요구사항이 있는지 등을 고려하여 보다 완벽한 검토를 수행하는 것이 좋습니다.

26 changes: 26 additions & 0 deletions src/shared/components/SelectBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
interface SelectBoxProps extends React.HTMLAttributes<HTMLSelectElement> {
options: string[];
defaultLabel?: string;
}
/**
* @param options
* @returns SelectBox
* @description 셀렉트 박스
*/
const SelectBox = (props: SelectBoxProps) => {
return (
<select
className={`h-14 w-[300px] px-5 py-4 border border-[#E0E0E0] rounded-[5px] ${props.className}`}
{...props}
>
<option>{props.defaultLabel ?? "선택"}</option>
{props.options.map((option, index) => (
<option key={index} value={option}>
{option}
</option>
))}
</select>
);
};

export default SelectBox;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 코드는 셀렉트 박스 컴포넌트를 정의하는 코드입니다. 이 코드의 주요 특징과 개선 사항을 아래에 설명하겠습니다:

  1. SelectBoxProps 인터페이스:

    • options 속성은 문자열 배열입니다. 이는 셀렉트 박스의 옵션으로 사용됩니다.
    • defaultLabel 속성은 선택하지 않았을 때 보여질 기본 라벨입니다. 이 값이 없으면 "선택"이라는 기본값을 사용합니다.
  2. SelectBox 함수 컴포넌트:

    • SelectBoxProps를 매개변수로 받습니다.
    • CSS 클래스를 동적으로 생성하여 셀렉트 박스 스타일을 설정합니다.
    • 기본 라벨을 옵션으로 추가합니다.
    • 옵션 배열을 순회하며 각각의 옵션을 <option> 요소로 렌더링합니다.
  3. 개선 사항:

    • 현재 코드에서 큰 문제나 버그는 발견되지 않습니다.
    • 다음 사항들을 고려하여 코드를 개선할 수 있습니다:
      • 사용자 정의 스타일을 적용하기 위해 className 속성을 사용하는데, 이를 좀 더 명확한 용도를 가진 속성으로 변경하는 것이 좋을 수 있습니다. 예를 들어, style 속성을 통해 스타일 객체를 전달받을 수 있도록 하는 것이 일반적입니다.
      • defaultLabel 속성을 사용할 경우 번역 가능한 메시지에 대한 지원을 고려해 볼 수 있습니다.
      • <select> 요소의 길이를 인라인 스타일로 고정시키는 대신 컴포넌트의 속성으로 받아서 동적으로 설정할 수 있도록 변경할 수 있습니다.

이 코드는 안전하게 작동할 것으로 보이며, 개선 사항은 주로 코드 가독성과 유지 관리 측면에서 제안된 내용입니다.

13 changes: 13 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ module.exports = {
],
theme: {
extend: {
colors: {
"c-accept": "#00D179", // green. 확인용 색상
"c-accept-disabled": "#00D17975", // green-disabled
"c-warn": "#FACD49", // yellow. 경고용 색상
"c-warn-disabled": "#FACD4975", // yellow-disabled
"c-danger": "#FF2330", // red. 에러용 색상
"c-danger-disabled": "#FFA4A475", // red-disabled
"c-pink": "#F864A1",
"c-orange": "#F5A200",
"c-purple": "#A564F8",
"c-gray": "#BABABA",
"c-black": "#222222",
},
backgroundImage: {
stayLoggedIn: "url(/images/samples/StayLoggedIn.svg)",
idLabel: "url(/images/samples/IdLabel.svg)",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치는 색상과 배경 이미지 확장을 위한 수정입니다. 주요 변경사항은 다음과 같습니다:

  1. colors 객체에 새로운 색상 값을 추가했습니다. 각 색상은 주석으로 설명되어 있습니다.
  2. backgroundImage 객체에 두 개의 새로운 배경 이미지 값을 추가했습니다. 이 이미지들은 주어진 경로에서 로드됩니다.

버그 리스크에 대해서는 주어진 코드만으로는 파악하기 어렵습니다. 그러나 새로운 색상과 배경 이미지를 추가하는 것은 일반적으로 크게 작용하지 않을 것으로 예상됩니다. 개발 환경과 사용 목적에 따라서 해당 코드의 테스트와 검증이 필요할 수 있습니다.

개선 제안:

  • 문법, 스타일 및 가독성을 개선하려면 코드에 들여쓰기 및 공백을 일관되게 적용하세요.
  • 색상 값의 명확성을 위해 변수 또는 상수로 정의하고 사용하세요. 마법 숫자 대신에 이러한 변수 또는 상수를 사용하면 코드가 더 이해하기 쉬워집니다.

Expand Down