@uniai-fe/uds-templates 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/styles.css +139 -10
- package/package.json +18 -11
- package/src/auth/common/complete/Template.tsx +3 -0
- package/src/auth/common/complete/types.ts +4 -1
- package/src/auth/common/find/markup/CodeStep.tsx +2 -1
- package/src/auth/common/find/markup/InfoStep.tsx +2 -1
- package/src/auth/common/find/styles/result.scss +1 -1
- package/src/auth/find-password/markup/StepResetPassword.tsx +2 -1
- package/src/auth/login/markup/FormField.tsx +2 -1
- package/src/auth/signup/markup/AccountForm.tsx +2 -1
- package/src/auth/signup/markup/Complete.tsx +2 -1
- package/src/auth/signup/markup/UserInfoForm.tsx +2 -1
- package/src/auth/signup/markup/VerificationForm.tsx +4 -2
- package/src/index.scss +1 -0
- package/src/index.tsx +1 -0
- package/src/modal/core/components/FooterButtons.tsx +4 -1
- package/src/modal/templates/Dialog.tsx +4 -2
- package/src/modal/types/footer.ts +4 -2
- package/src/page-frame/container/index.scss +14 -8
- package/src/page-frame/desktop/components/header/Container.tsx +22 -0
- package/src/page-frame/desktop/components/header/Section.tsx +20 -0
- package/src/page-frame/desktop/components/header/index.tsx +19 -0
- package/src/page-frame/desktop/components/header/util/Button.tsx +23 -0
- package/src/page-frame/desktop/components/header/util/Container.tsx +18 -0
- package/src/page-frame/desktop/components/header/util/Item.tsx +13 -0
- package/src/page-frame/desktop/components/header/util/Logout.tsx +26 -0
- package/src/page-frame/desktop/components/header/util/setting/Button.tsx +39 -0
- package/src/page-frame/desktop/components/index.tsx +15 -0
- package/src/page-frame/desktop/components/nav/Button.tsx +24 -0
- package/src/page-frame/desktop/components/nav/Container.tsx +70 -0
- package/src/page-frame/desktop/components/nav/Logo.tsx +46 -0
- package/src/page-frame/desktop/components/nav/index.tsx +5 -0
- package/src/page-frame/desktop/components/page/Container.tsx +13 -0
- package/src/page-frame/desktop/components/page/ServiceFrame.tsx +15 -0
- package/src/page-frame/desktop/components/page/ServiceMain.tsx +13 -0
- package/src/page-frame/desktop/components/page/ServiceMainWrapper.tsx +24 -0
- package/src/page-frame/desktop/components/page/index.tsx +14 -0
- package/src/page-frame/desktop/components/popup/Container.tsx +18 -0
- package/src/page-frame/desktop/components/popup/frame/Body.tsx +14 -0
- package/src/page-frame/desktop/components/popup/frame/Container.tsx +16 -0
- package/src/page-frame/desktop/components/popup/frame/Header.tsx +26 -0
- package/src/page-frame/desktop/components/popup/frame/index.tsx +11 -0
- package/src/page-frame/desktop/components/popup/index.tsx +9 -0
- package/src/page-frame/desktop/index.scss +5 -0
- package/src/page-frame/desktop/index.tsx +5 -0
- package/src/page-frame/desktop/styles/page/common.scss +40 -0
- package/src/page-frame/desktop/styles/page/header.scss +49 -0
- package/src/page-frame/desktop/styles/page/nav.scss +128 -0
- package/src/page-frame/desktop/styles/popup/popup.scss +45 -0
- package/src/page-frame/desktop/styles/variables.scss +28 -0
- package/src/page-frame/desktop/types/nav.ts +77 -0
- package/src/page-frame/index.tsx +2 -0
- package/src/page-frame/mobile/index.scss +1 -1
- package/src/page-frame/types/index.ts +69 -0
- package/src/weather/apis/index.ts +4 -0
- package/src/weather/apis/korea/client.ts +93 -0
- package/src/weather/apis/korea/server.ts +253 -0
- package/src/weather/apis/open-weather-map/client.ts +69 -0
- package/src/weather/apis/open-weather-map/server.ts +134 -0
- package/src/weather/components/icon/Address.tsx +13 -0
- package/src/weather/components/icon/Weather.tsx +32 -0
- package/src/weather/components/index.tsx +7 -0
- package/src/weather/components/page-header/Address.tsx +21 -0
- package/src/weather/components/page-header/Alert.tsx +42 -0
- package/src/weather/components/page-header/Container.tsx +14 -0
- package/src/weather/components/page-header/Forecast.tsx +26 -0
- package/src/weather/components/page-header/NextDays.tsx +32 -0
- package/src/weather/components/page-header/Today.tsx +135 -0
- package/src/weather/context/mock.tsx +45 -0
- package/src/weather/data/alert-regions-meta.json +1286 -0
- package/src/weather/data/response.ts +36 -0
- package/src/weather/data/weather-regions-meta.json +9833 -0
- package/src/weather/hooks/index.ts +4 -0
- package/src/weather/hooks/useOpenWeatherMap.ts +42 -0
- package/src/weather/hooks/useWeatherKorea.ts +78 -0
- package/src/weather/hooks/useWeatherKoreaAlert.ts +36 -0
- package/src/weather/index.tsx +11 -0
- package/src/weather/jotai/coordinate.ts +16 -0
- package/src/weather/jotai/farm-idx.ts +5 -0
- package/src/weather/jotai/index.ts +2 -0
- package/src/weather/styles/weather.scss +151 -0
- package/src/weather/types/api.ts +215 -0
- package/src/weather/types/base.ts +50 -0
- package/src/weather/types/index.ts +4 -0
- package/src/weather/types/korea.ts +228 -0
- package/src/weather/types/open-weather-map.ts +164 -0
- package/src/weather/utils/alert.ts +30 -0
- package/src/weather/utils/date-time.ts +65 -0
- package/src/weather/utils/index.ts +4 -0
- package/src/weather/utils/location.ts +161 -0
- package/src/weather/utils/validate.ts +9 -0
- package/src/weather/utils/weather.ts +304 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--uds-page-frame-background: var(--color-common-100);
|
|
3
|
+
|
|
4
|
+
--uds-page-header-height: 7.2rem;
|
|
5
|
+
--uds-page-header-border: var(--color-border-standard-cool-gray);
|
|
6
|
+
--uds-page-header-padding-horizontal: var(--spacing-padding-9, 2.8rem);
|
|
7
|
+
--uds-page-header-padding-vertical: var(--spacing-padding-5, 1.2rem);
|
|
8
|
+
--uds-page-header-background: var(--uds-page-frame-background);
|
|
9
|
+
--uds-page-header-item-gap: var(--spacing-padding-5, 1.2rem);
|
|
10
|
+
|
|
11
|
+
--uds-page-nav-width: 7.2rem;
|
|
12
|
+
--uds-page-nav-border: var(--color-border-standard-cool-gray);
|
|
13
|
+
--uds-page-nav-background: var(--uds-page-frame-background);
|
|
14
|
+
|
|
15
|
+
--uds-page-nav-logo-height: var(--uds-page-header-height, 7.2rem);
|
|
16
|
+
|
|
17
|
+
--uds-page-nav-list-padding-horizontal: 0rem;
|
|
18
|
+
--uds-page-nav-list-padding-vertical: var(--spacing-padding-9, 2.4rem);
|
|
19
|
+
--uds-page-nav-item-height: var(--theme-size-meduum-2, 4.8rem);
|
|
20
|
+
--uds-page-nav-item-gap: var(--spacing-gap-6, 1.6rem);
|
|
21
|
+
--uds-page-nav-item-color-default: var(--color-label-standard);
|
|
22
|
+
--uds-page-nav-item-color-active: var(--color-primary-default);
|
|
23
|
+
--uds-page-nav-icon-size: var(--theme-size-small-2, 2.4rem);
|
|
24
|
+
|
|
25
|
+
--uds-page-body-background: var(--uds-page-frame-background);
|
|
26
|
+
--uds-page-body-padding-horizontal: var(--spacing-padding-9, 2.8rem);
|
|
27
|
+
--uds-page-body-padding-vertical: var(--spacing-padding-9, 2.8rem);
|
|
28
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { SitemapDataType } from "../../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PageFrame; Desktop Nav Logo Props
|
|
5
|
+
*/
|
|
6
|
+
export interface PageFrameDesktopNavLogoProps {
|
|
7
|
+
/**
|
|
8
|
+
* 로고 클릭 시 이동할 링크 주소.
|
|
9
|
+
* 기본값은 "/" (홈) 입니다.
|
|
10
|
+
*/
|
|
11
|
+
href?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 로고 이미지 src 속성 값.
|
|
14
|
+
*/
|
|
15
|
+
src: string;
|
|
16
|
+
/**
|
|
17
|
+
* 로고 이미지 alt 속성 값.
|
|
18
|
+
*/
|
|
19
|
+
alt: string;
|
|
20
|
+
/**
|
|
21
|
+
* 로고 이미지 width 속성 값.
|
|
22
|
+
*/
|
|
23
|
+
width: number;
|
|
24
|
+
/**
|
|
25
|
+
* 로고 이미지 height 속성 값.
|
|
26
|
+
*/
|
|
27
|
+
height: number;
|
|
28
|
+
/**
|
|
29
|
+
* 로고 이미지 priority 속성 값.
|
|
30
|
+
*/
|
|
31
|
+
priority?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* 커스텀 로고 콘텐츠.
|
|
34
|
+
* 제공 시, src, alt, label props는 무시됩니다.
|
|
35
|
+
*/
|
|
36
|
+
children?: React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PageFrame; Desktop Nav Item Props
|
|
41
|
+
*/
|
|
42
|
+
export interface PageFrameDesktopNavItemProps {
|
|
43
|
+
/**
|
|
44
|
+
* 경로.
|
|
45
|
+
*/
|
|
46
|
+
path: string;
|
|
47
|
+
/**
|
|
48
|
+
* 아이콘.
|
|
49
|
+
*/
|
|
50
|
+
icon?: React.ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* 메뉴 이름.
|
|
53
|
+
*/
|
|
54
|
+
menuName: React.ReactNode;
|
|
55
|
+
/**
|
|
56
|
+
* 이름 숨김 여부.
|
|
57
|
+
*/
|
|
58
|
+
isHideName?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* PageFrame; Desktop Nav Props
|
|
63
|
+
*/
|
|
64
|
+
export interface PageFrameDesktopNavProps {
|
|
65
|
+
/**
|
|
66
|
+
* logo 옵션
|
|
67
|
+
*/
|
|
68
|
+
logo?: PageFrameDesktopNavLogoProps;
|
|
69
|
+
/**
|
|
70
|
+
* 사이트맵 데이터 배열.
|
|
71
|
+
*/
|
|
72
|
+
sitemap: SitemapDataType[];
|
|
73
|
+
/**
|
|
74
|
+
* 현재 경로.
|
|
75
|
+
*/
|
|
76
|
+
currentPath?: string;
|
|
77
|
+
}
|
package/src/page-frame/index.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.page-frame-mobile {
|
|
2
2
|
width: 100%;
|
|
3
3
|
height: 100%;
|
|
4
|
-
min-height: var(--page-frame-height, var(--frame-device-height, 812px));
|
|
4
|
+
min-height: var(--uds-page-frame-height, var(--frame-device-height, 812px));
|
|
5
5
|
max-height: 100dvh;
|
|
6
6
|
background-color: var(--color-common-100, #ffffff);
|
|
7
7
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, SyntheticEvent } from "react";
|
|
2
|
+
import type { SitemapBase } from "@uniai-fe/util-functions";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* page-frame 컴포넌트에서 공통으로 사용하는 기본 Props
|
|
6
|
+
*/
|
|
7
|
+
export type ComponentBaseProps<ClickElement extends Element = HTMLElement> = {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
} & Partial<{
|
|
10
|
+
className: string;
|
|
11
|
+
clickEvent: (event?: SyntheticEvent<ClickElement>) => void;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 커스텀 CSS 변수를 명시적으로 다루기 위한 스타일 타입
|
|
16
|
+
*/
|
|
17
|
+
export type CSSVarStyle<Variable extends string> = CSSProperties & {
|
|
18
|
+
[Key in Variable]?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 페이지 프레임에서 사용하는 네비게이션 옵셔널 속성
|
|
23
|
+
*/
|
|
24
|
+
export type SitemapPageOptionsType = {
|
|
25
|
+
popup?: boolean;
|
|
26
|
+
auth?: string[];
|
|
27
|
+
pageStyle?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sidebar/ Breadcrumb 등을 구성하는 기본 노드 타입
|
|
32
|
+
*/
|
|
33
|
+
export type SitemapDataType = SitemapPageOptionsType &
|
|
34
|
+
SitemapBase & {
|
|
35
|
+
routeKey: string;
|
|
36
|
+
name: string;
|
|
37
|
+
depth: SitemapDataType[];
|
|
38
|
+
icon?: ReactNode;
|
|
39
|
+
navDepthDisabled?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Breadcrumb 매칭 결과 구조
|
|
44
|
+
*/
|
|
45
|
+
export type SitemapMatchDataType = {
|
|
46
|
+
category: SitemapDataType | null;
|
|
47
|
+
depth: SitemapDataType | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 헤더 프로필 선택 옵션
|
|
52
|
+
*/
|
|
53
|
+
export interface SelectOptionType<OptionDataType = unknown> {
|
|
54
|
+
key: string;
|
|
55
|
+
value: string | number;
|
|
56
|
+
optionName: string | ReactNode;
|
|
57
|
+
selected: boolean;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
data?: OptionDataType;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 헤더 알림 항목 데이터
|
|
64
|
+
*/
|
|
65
|
+
export type PageHeaderNotifyDataType = {
|
|
66
|
+
key: string;
|
|
67
|
+
content: ReactNode;
|
|
68
|
+
timestamp: string | Date;
|
|
69
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
|
4
|
+
import { getQueryString } from "@uniai-fe/util-functions";
|
|
5
|
+
import type {
|
|
6
|
+
API_Req_WeatherKorea,
|
|
7
|
+
API_Req_WeatherKoreaAlert,
|
|
8
|
+
API_Res_WeatherKoreaAlert,
|
|
9
|
+
API_Res_WeatherKoreaForecast,
|
|
10
|
+
API_Res_WeatherKoreaNow,
|
|
11
|
+
} from "../../types";
|
|
12
|
+
import { isValidGridCoordinate } from "../../utils/validate";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 기상청 API; 현재날씨 fetch
|
|
16
|
+
* @method GET
|
|
17
|
+
* @param {API_Req_WeatherKorea} params - API 요청 파라미터
|
|
18
|
+
* @param {number} params.nx - X 좌표
|
|
19
|
+
* @param {number} params.ny - Y 좌표
|
|
20
|
+
* @param {string} [params.base_date] - 기준 날짜
|
|
21
|
+
* @param {string} [params.base_time] - 기준 시간
|
|
22
|
+
*/
|
|
23
|
+
export const getWeatherKoreaNow = async (params: API_Req_WeatherKorea) =>
|
|
24
|
+
await (await fetch(`/api/weather/korea/now${getQueryString(params)}`)).json();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 기상청 API; 내일/모레 예보 fetch
|
|
28
|
+
* @method GET
|
|
29
|
+
* @param {API_Req_WeatherKorea} params - API 요청 파라미터
|
|
30
|
+
* @param {number} params.nx - X 좌표
|
|
31
|
+
* @param {string} params.ny - Y 좌표
|
|
32
|
+
* @param {string} [params.base_date] - 기준 날짜
|
|
33
|
+
* @param {string} [params.base_time] - 기준 시간
|
|
34
|
+
*/
|
|
35
|
+
export const getWeatherKoreaForecast = async (params: API_Req_WeatherKorea) =>
|
|
36
|
+
await (
|
|
37
|
+
await fetch(`/api/weather/korea/forecast${getQueryString(params)}`)
|
|
38
|
+
).json();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 기상청 API; 특보 fetch
|
|
42
|
+
* @method GET
|
|
43
|
+
* @param {API_Req_WeatherKoreaAlert} params - API 요청 파라미터
|
|
44
|
+
* @param {number} params.farm_idx - 농장 ID
|
|
45
|
+
*/
|
|
46
|
+
export const getWeatherKoreaAlert = async (params: API_Req_WeatherKoreaAlert) =>
|
|
47
|
+
await (
|
|
48
|
+
await fetch(`/api/weather/korea/alert${getQueryString(params)}`)
|
|
49
|
+
).json();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 기상청 API; 현재 날씨 react query
|
|
53
|
+
* @method GET
|
|
54
|
+
*/
|
|
55
|
+
export const useQueryWeatherKoreaNow = (
|
|
56
|
+
params: API_Req_WeatherKorea,
|
|
57
|
+
): UseQueryResult<API_Res_WeatherKoreaNow> =>
|
|
58
|
+
useQuery({
|
|
59
|
+
queryKey: ["weather_korea_now", ...Object.values(params)],
|
|
60
|
+
queryFn: () => getWeatherKoreaNow(params),
|
|
61
|
+
enabled: isValidGridCoordinate({ nx: params.nx, ny: params.ny }),
|
|
62
|
+
staleTime: 10 * 60 * 1000, // 10분
|
|
63
|
+
refetchInterval: 5 * 60 * 1000, // 5분
|
|
64
|
+
refetchOnWindowFocus: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 기상청 API; 내일/모레 예보 react query
|
|
69
|
+
* @method GET
|
|
70
|
+
*/
|
|
71
|
+
export const useQueryWeatherKoreaForecast = (
|
|
72
|
+
params: API_Req_WeatherKorea,
|
|
73
|
+
): UseQueryResult<API_Res_WeatherKoreaForecast> =>
|
|
74
|
+
useQuery({
|
|
75
|
+
queryKey: ["weather_korea_forecast", ...Object.values(params)],
|
|
76
|
+
queryFn: () => getWeatherKoreaForecast(params),
|
|
77
|
+
enabled: isValidGridCoordinate({ nx: params.nx, ny: params.ny }),
|
|
78
|
+
staleTime: 30 * 60 * 1000, // 30분
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 기상청 API; 특보 react query
|
|
83
|
+
* @method GET
|
|
84
|
+
*/
|
|
85
|
+
export const useQueryWeatherKoreaAlert = (
|
|
86
|
+
params: API_Req_WeatherKoreaAlert,
|
|
87
|
+
): UseQueryResult<API_Res_WeatherKoreaAlert> =>
|
|
88
|
+
useQuery({
|
|
89
|
+
queryKey: ["weather_korea_alert", ...Object.values(params)],
|
|
90
|
+
queryFn: () => getWeatherKoreaAlert(params),
|
|
91
|
+
enabled: typeof params.farm_idx === "number" && params.farm_idx > 0,
|
|
92
|
+
staleTime: 60 * 60 * 1000, // 1시간
|
|
93
|
+
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { setDebugResponseHeaders } from "@uniai-fe/util-next";
|
|
4
|
+
import type {
|
|
5
|
+
API_Res_WeatherKoreaAlert,
|
|
6
|
+
API_Res_WeatherKoreaForecast,
|
|
7
|
+
API_Res_WeatherKoreaNextDays,
|
|
8
|
+
API_Res_WeatherKoreaNow,
|
|
9
|
+
API_Res_WeatherKoreaToday,
|
|
10
|
+
KMA_Req_Weather,
|
|
11
|
+
KMA_Res_WeatherForecast,
|
|
12
|
+
KMA_Res_WeatherNow,
|
|
13
|
+
} from "../../types";
|
|
14
|
+
import WEATHER_KOREA_RESPONSE from "../../data/response";
|
|
15
|
+
import { getWeatherBaseMoments } from "../../utils/date-time";
|
|
16
|
+
import { extractWeatherSummary } from "../../utils";
|
|
17
|
+
import type { NextResponse } from "next/server";
|
|
18
|
+
import {
|
|
19
|
+
generateBackendQueryUrl_GET,
|
|
20
|
+
nextAPILog,
|
|
21
|
+
} from "@uniai-fe/util-functions";
|
|
22
|
+
|
|
23
|
+
// const { generateBackendQueryUrl_GET } = weatherApi;
|
|
24
|
+
// const weatherLog = weatherApi.logger;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 기상청 API; base URL
|
|
28
|
+
* "본 저작물은 기상청에서 'OO년'작성하여 공공누리 제1유형으로 개방한 '저작물명(작성자:OOO)'을 이용하였으며, 해당 저작물은 '기상청 API허브(apihub.kma.go.kr)에서 무료로 다운받으실 수 있습니다."
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const API_BASE = "https://apihub.kma.go.kr/api";
|
|
32
|
+
/**
|
|
33
|
+
* 기상청 API; 날씨정보 URL
|
|
34
|
+
*/
|
|
35
|
+
const API_WEATHER = "/typ02/openApi/VilageFcstInfoService_2.0";
|
|
36
|
+
|
|
37
|
+
const QUERY_URL = {
|
|
38
|
+
forecast: `${API_WEATHER}/getVilageFcst`,
|
|
39
|
+
now: `${API_WEATHER}/getUltraSrtFcst`,
|
|
40
|
+
alert: `/avic/weather-alert`,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const { API_RES_RAW, API_RES_BASE } = WEATHER_KOREA_RESPONSE;
|
|
44
|
+
|
|
45
|
+
const getWeatherParams = ({
|
|
46
|
+
authKey,
|
|
47
|
+
category,
|
|
48
|
+
searchParams,
|
|
49
|
+
}: {
|
|
50
|
+
authKey: string;
|
|
51
|
+
category: "now" | "forecast";
|
|
52
|
+
searchParams: URLSearchParams;
|
|
53
|
+
}): KMA_Req_Weather => {
|
|
54
|
+
const { base_date, base_time } = getWeatherBaseMoments(category);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
pageNo: "1",
|
|
58
|
+
numOfRows: "1000",
|
|
59
|
+
authKey,
|
|
60
|
+
base_date,
|
|
61
|
+
base_time,
|
|
62
|
+
dataType: "JSON",
|
|
63
|
+
nx: 0,
|
|
64
|
+
ny: 0,
|
|
65
|
+
...Object.fromEntries(searchParams.entries()),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 기상청 API; 현재 날씨
|
|
71
|
+
* @method GET
|
|
72
|
+
*/
|
|
73
|
+
export const routeWeatherKoreaNow = async ({
|
|
74
|
+
domain = API_BASE,
|
|
75
|
+
authKey,
|
|
76
|
+
routeUrl,
|
|
77
|
+
searchParams,
|
|
78
|
+
}: {
|
|
79
|
+
authKey: string;
|
|
80
|
+
routeUrl: string;
|
|
81
|
+
searchParams: URLSearchParams;
|
|
82
|
+
} & Partial<{
|
|
83
|
+
domain: string;
|
|
84
|
+
}>): Promise<API_Res_WeatherKoreaNow> => {
|
|
85
|
+
const NOW_DATA: API_Res_WeatherKoreaToday = {
|
|
86
|
+
...API_RES_BASE,
|
|
87
|
+
temperature: null,
|
|
88
|
+
humidity: null,
|
|
89
|
+
};
|
|
90
|
+
const resDefault: API_Res_WeatherKoreaNow = {
|
|
91
|
+
raw: API_RES_RAW as KMA_Res_WeatherNow,
|
|
92
|
+
today: NOW_DATA,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (!authKey) {
|
|
96
|
+
nextAPILog("GET", routeUrl, "기상청 현재날씨 API", {
|
|
97
|
+
error: "API Key를 확인하세요.",
|
|
98
|
+
});
|
|
99
|
+
return resDefault;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const queryParams = Object.fromEntries(
|
|
103
|
+
Object.entries(
|
|
104
|
+
getWeatherParams({ authKey, category: "now", searchParams }),
|
|
105
|
+
).map(([key, value]) => [key, String(value)]),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// 요청 URL 구성
|
|
109
|
+
const url = generateBackendQueryUrl_GET({
|
|
110
|
+
domain,
|
|
111
|
+
routeUrl,
|
|
112
|
+
queryUrl: QUERY_URL.now,
|
|
113
|
+
searchParams: new URLSearchParams(queryParams),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// nextAPILog("GET", routeUrl, url, { searchParams, queryParams });
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const res: KMA_Res_WeatherNow = await (await fetch(url)).json();
|
|
120
|
+
resDefault.raw = res;
|
|
121
|
+
return extractWeatherSummary.now(resDefault, res);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
nextAPILog("GET", routeUrl, url, { error });
|
|
124
|
+
|
|
125
|
+
return resDefault;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 기상청 API; 내일, 모래 예보
|
|
131
|
+
* @method GET
|
|
132
|
+
*/
|
|
133
|
+
export const routeWeatherKoreaForecast = async ({
|
|
134
|
+
domain = API_BASE,
|
|
135
|
+
authKey,
|
|
136
|
+
routeUrl,
|
|
137
|
+
searchParams,
|
|
138
|
+
}: {
|
|
139
|
+
authKey: string;
|
|
140
|
+
routeUrl: string;
|
|
141
|
+
searchParams: URLSearchParams;
|
|
142
|
+
} & Partial<{
|
|
143
|
+
domain: string;
|
|
144
|
+
}>): Promise<API_Res_WeatherKoreaForecast> => {
|
|
145
|
+
const FORECAST_DATA: API_Res_WeatherKoreaNextDays = {
|
|
146
|
+
...API_RES_BASE,
|
|
147
|
+
max_temperature: null,
|
|
148
|
+
min_temperature: null,
|
|
149
|
+
};
|
|
150
|
+
const resDefault: API_Res_WeatherKoreaForecast = {
|
|
151
|
+
raw: API_RES_RAW as KMA_Res_WeatherForecast,
|
|
152
|
+
today: { ...API_RES_BASE, temperature: null, humidity: null },
|
|
153
|
+
day_1: FORECAST_DATA,
|
|
154
|
+
day_2: FORECAST_DATA,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (!authKey) {
|
|
158
|
+
nextAPILog("GET", routeUrl, "기상청 예보날씨 API", {
|
|
159
|
+
error: "API Key를 확인하세요.",
|
|
160
|
+
});
|
|
161
|
+
return resDefault;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const queryParams = Object.fromEntries(
|
|
165
|
+
Object.entries(
|
|
166
|
+
getWeatherParams({ authKey, category: "forecast", searchParams }),
|
|
167
|
+
).map(([key, value]) => [key, String(value)]),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// 요청 URL 구성
|
|
171
|
+
const url = generateBackendQueryUrl_GET({
|
|
172
|
+
domain,
|
|
173
|
+
routeUrl,
|
|
174
|
+
queryUrl: QUERY_URL.forecast,
|
|
175
|
+
searchParams: new URLSearchParams(queryParams),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// nextAPILog("GET", routeUrl, url, { searchParams, queryParams });
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const res: KMA_Res_WeatherForecast = await (await fetch(url)).json();
|
|
182
|
+
resDefault.raw = res;
|
|
183
|
+
return extractWeatherSummary.forecast(resDefault, res);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
nextAPILog("GET", routeUrl, url, { error });
|
|
186
|
+
|
|
187
|
+
return resDefault;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 기상청 API; 특보 - 구역코드 조회
|
|
193
|
+
*/
|
|
194
|
+
// export const getWeatherKoreaAlertRegionCodes = async ({
|
|
195
|
+
// routeUrl,
|
|
196
|
+
// searchParams,
|
|
197
|
+
// }: {
|
|
198
|
+
// routeUrl: string;
|
|
199
|
+
// searchParams: URLSearchParams;
|
|
200
|
+
// }) => {
|
|
201
|
+
// const params = new URLSearchParams({
|
|
202
|
+
// authKey,
|
|
203
|
+
// dataType: "JSON",
|
|
204
|
+
// pageNo: "1",
|
|
205
|
+
// numOfRows: "1000",
|
|
206
|
+
// korName: "",
|
|
207
|
+
// });
|
|
208
|
+
// const url = `${QUERY_URL.alert}?${params.toString()}`;
|
|
209
|
+
// nextAPILog("GET", routeUrl, url, { params, searchParams });
|
|
210
|
+
|
|
211
|
+
// const regions = await (await fetch(url)).json();
|
|
212
|
+
|
|
213
|
+
// return regions;
|
|
214
|
+
// };
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 기상청 API; 특보
|
|
218
|
+
*/
|
|
219
|
+
export const routeWeatherKoreaAlert = async ({
|
|
220
|
+
domain,
|
|
221
|
+
routeUrl,
|
|
222
|
+
searchParams,
|
|
223
|
+
}: {
|
|
224
|
+
domain: string;
|
|
225
|
+
routeUrl: string;
|
|
226
|
+
searchParams: URLSearchParams;
|
|
227
|
+
}): Promise<NextResponse<API_Res_WeatherKoreaAlert>> => {
|
|
228
|
+
const params = new URLSearchParams({
|
|
229
|
+
// authKey,
|
|
230
|
+
// dataType: "JSON",
|
|
231
|
+
// reg: "",
|
|
232
|
+
// wrn: "",
|
|
233
|
+
// disp: "0",
|
|
234
|
+
// fe: "f",
|
|
235
|
+
// tm: "",
|
|
236
|
+
});
|
|
237
|
+
const url = generateBackendQueryUrl_GET({
|
|
238
|
+
domain,
|
|
239
|
+
routeUrl,
|
|
240
|
+
queryUrl: QUERY_URL.alert,
|
|
241
|
+
searchParams,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
nextAPILog("GET", routeUrl, url, { params, searchParams });
|
|
245
|
+
const info = await (await fetch(url)).json();
|
|
246
|
+
|
|
247
|
+
return setDebugResponseHeaders<API_Res_WeatherKoreaAlert>({
|
|
248
|
+
res: info,
|
|
249
|
+
domain,
|
|
250
|
+
queryUrl: QUERY_URL.alert,
|
|
251
|
+
searchParams,
|
|
252
|
+
});
|
|
253
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
|
4
|
+
import type { OWM_Res_Weather_Now, WeatherGeoCoordinate } from "../../types";
|
|
5
|
+
import { getQueryString, isValidNumber } from "@uniai-fe/util-functions";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 글로벌 날씨 API; 현재 날씨 fetch
|
|
9
|
+
* @method GET
|
|
10
|
+
* @route https://api.openweathermap.org/data/2.5/weather
|
|
11
|
+
* @see https://openweathermap.org/current
|
|
12
|
+
* @param {WeatherGeoCoordinate} params - API 요청 파라미터
|
|
13
|
+
* @param {number} params.lat - 위도
|
|
14
|
+
* @param {number} params.lon - 경도
|
|
15
|
+
*/
|
|
16
|
+
export const getWeatherOpenWeatherMapNow = async (
|
|
17
|
+
params: WeatherGeoCoordinate,
|
|
18
|
+
) =>
|
|
19
|
+
await (
|
|
20
|
+
await fetch(`/api/weather/open-weather-map/now${getQueryString(params)}`)
|
|
21
|
+
).json();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 글로벌 날씨 API; 예보 날씨 (4 days) fetch
|
|
25
|
+
* @method GET
|
|
26
|
+
* @route https://pro.openweathermap.org/data/2.5/weather/forecast/hourly
|
|
27
|
+
* @see https://openweathermap.org/api/hourly-forecast
|
|
28
|
+
* @param {WeatherGeoCoordinate} params - API 요청 파라미터
|
|
29
|
+
* @param {number} params.lat - 위도
|
|
30
|
+
* @param {number} params.lon - 경도
|
|
31
|
+
*/
|
|
32
|
+
export const getWeatherOpenWeatherMapForecast = async (
|
|
33
|
+
params: WeatherGeoCoordinate,
|
|
34
|
+
) =>
|
|
35
|
+
await (
|
|
36
|
+
await fetch(
|
|
37
|
+
`/api/weather/open-weather-map/forecast${getQueryString(params)}`,
|
|
38
|
+
)
|
|
39
|
+
).json();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 글로벌 날씨 API; 현재 날씨 react query
|
|
43
|
+
* @method GET
|
|
44
|
+
*/
|
|
45
|
+
export const useQueryWeatherOpenWeatherMapNow = (
|
|
46
|
+
params: WeatherGeoCoordinate,
|
|
47
|
+
): UseQueryResult<OWM_Res_Weather_Now> =>
|
|
48
|
+
useQuery({
|
|
49
|
+
queryKey: ["weather_open_weather_map_now", ...Object.values(params)],
|
|
50
|
+
queryFn: () => getWeatherOpenWeatherMapNow(params),
|
|
51
|
+
enabled: isValidNumber(params.lat) && isValidNumber(params.lng),
|
|
52
|
+
staleTime: 10 * 60 * 1000, // 10분
|
|
53
|
+
refetchInterval: 5 * 60 * 1000, // 5분
|
|
54
|
+
refetchOnWindowFocus: true,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 글로벌 날씨 API; 예보 날씨 (4 days) react query
|
|
59
|
+
* @method GET
|
|
60
|
+
*/
|
|
61
|
+
export const useQueryWeatherOpenWeatherMapForecast = (
|
|
62
|
+
params: WeatherGeoCoordinate,
|
|
63
|
+
) =>
|
|
64
|
+
useQuery({
|
|
65
|
+
queryKey: ["weather_open_weather_map_forecast", ...Object.values(params)],
|
|
66
|
+
queryFn: () => getWeatherOpenWeatherMapForecast(params),
|
|
67
|
+
enabled: isValidNumber(params.lat) && isValidNumber(params.lng),
|
|
68
|
+
staleTime: 30 * 60 * 1000, // 30분
|
|
69
|
+
});
|