@uniai-fe/uds-templates 0.5.28 → 0.6.0
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/README.md +3 -6
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/cctv/styles/variables.scss +1 -1
- package/src/weather/_legacy/apis/index.ts +4 -0
- package/src/weather/_legacy/data/response.ts +36 -0
- package/src/weather/_legacy/hooks/index.ts +5 -0
- package/src/weather/{hooks → _legacy/hooks}/useOpenWeatherMap.ts +1 -1
- package/src/weather/{hooks → _legacy/hooks}/useWeatherKorea.ts +4 -7
- package/src/weather/{hooks → _legacy/hooks}/useWeatherKoreaAlert.ts +2 -4
- package/src/weather/_legacy/types/api.ts +221 -0
- package/src/weather/_legacy/types/base.ts +70 -0
- package/src/weather/_legacy/types/index.ts +4 -0
- package/src/weather/_legacy/utils/index.ts +5 -0
- package/src/weather/_legacy/utils/locale.ts +28 -0
- package/src/weather/_legacy/utils/location.ts +139 -0
- package/src/weather/_legacy/utils/weather.ts +460 -0
- package/src/weather/apis/client.ts +339 -0
- package/src/weather/apis/index.ts +2 -4
- package/src/weather/apis/server.ts +264 -0
- package/src/weather/components/icon/Address.tsx +7 -0
- package/src/weather/components/icon/Weather.tsx +7 -6
- package/src/weather/components/page-header/Address.tsx +14 -0
- package/src/weather/components/page-header/Alert.tsx +17 -13
- package/src/weather/components/page-header/Container.tsx +12 -19
- package/src/weather/components/page-header/Forecast.tsx +21 -28
- package/src/weather/components/page-header/NextDays.tsx +10 -0
- package/src/weather/components/page-header/Today.tsx +86 -158
- package/src/weather/components/page-header/index.ts +5 -0
- package/src/weather/data/response.ts +4 -23
- package/src/weather/hooks/index.ts +3 -3
- package/src/weather/hooks/useWeather.ts +52 -0
- package/src/weather/hooks/useWeatherAlert.ts +35 -0
- package/src/weather/index.tsx +2 -2
- package/src/weather/jotai/coordinate.ts +4 -0
- package/src/weather/jotai/farm-idx.ts +4 -0
- package/src/weather/types/api.ts +393 -114
- package/src/weather/types/base.ts +15 -32
- package/src/weather/types/index.ts +0 -3
- package/src/weather/types/page-header.ts +118 -68
- package/src/weather/utils/index.ts +6 -4
- package/src/weather/utils/locale.ts +7 -69
- package/src/weather/utils/location.ts +6 -141
- package/src/weather/utils/weather.ts +53 -456
- package/src/weather/data/alert-regions-meta.json +0 -1286
- package/src/weather/data/weather-regions-meta.json +0 -9833
- package/src/weather/types/provider.ts +0 -34
- package/src/weather/utils/alert.ts +0 -30
- /package/src/weather/{apis → _legacy/apis}/korea/client.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/korea/server.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/open-weather-map/client.ts +0 -0
- /package/src/weather/{apis → _legacy/apis}/open-weather-map/server.ts +0 -0
- /package/src/weather/{types → _legacy/types}/korea.ts +0 -0
- /package/src/weather/{types → _legacy/types}/open-weather-map.ts +0 -0
- /package/src/weather/{utils → _legacy/utils}/date-time.ts +0 -0
- /package/src/weather/{utils → _legacy/utils}/validate.ts +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
|
4
|
+
import { getQueryString } from "@uniai-fe/util-api";
|
|
5
|
+
import { isValidNumber } from "@uniai-fe/util-functions";
|
|
6
|
+
import type {
|
|
7
|
+
API_Req_Weather,
|
|
8
|
+
API_Req_WeatherAlert,
|
|
9
|
+
API_Res_WeatherAlert,
|
|
10
|
+
API_Res_WeatherForecast,
|
|
11
|
+
API_Res_WeatherNow,
|
|
12
|
+
API_Res_WeatherSummary,
|
|
13
|
+
WeatherClientQueryOptions,
|
|
14
|
+
WeatherClientRouteOptions,
|
|
15
|
+
} from "../types";
|
|
16
|
+
|
|
17
|
+
const WEATHER_CLIENT_ROUTE_PATH = {
|
|
18
|
+
now: "/api/weather/now",
|
|
19
|
+
forecast: "/api/weather/forecast",
|
|
20
|
+
summary: "/api/weather/summary",
|
|
21
|
+
alert: "/api/weather/alert",
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Weather Client; query string에 포함 가능한 값인지 확인.
|
|
26
|
+
* @param {unknown} value 확인할 query 값
|
|
27
|
+
* @return {boolean} query 포함 가능 여부
|
|
28
|
+
*/
|
|
29
|
+
const hasQueryValue = (value: unknown): boolean =>
|
|
30
|
+
value !== null && typeof value !== "undefined" && value !== "";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Weather Client; backend weather route query로 전달할 값만 정리.
|
|
34
|
+
* @param {API_Req_Weather} params 날씨 요청 파라미터
|
|
35
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
36
|
+
* @param {number|null} [params.lat] 위도
|
|
37
|
+
* @param {number|null} [params.lng] 경도
|
|
38
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
39
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
40
|
+
* @return {API_Req_Weather} local route query 파라미터
|
|
41
|
+
*/
|
|
42
|
+
export const getWeatherClientParams = ({
|
|
43
|
+
farm_idx,
|
|
44
|
+
lat,
|
|
45
|
+
lng,
|
|
46
|
+
locale,
|
|
47
|
+
include_raw,
|
|
48
|
+
}: API_Req_Weather): API_Req_Weather => ({
|
|
49
|
+
...(hasQueryValue(farm_idx) ? { farm_idx } : {}),
|
|
50
|
+
...(hasQueryValue(lat) ? { lat } : {}),
|
|
51
|
+
...(hasQueryValue(lng) ? { lng } : {}),
|
|
52
|
+
...(locale ? { locale } : {}),
|
|
53
|
+
...(typeof include_raw === "boolean" ? { include_raw } : {}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Weather Client; route override 또는 기본 local route 경로 선택.
|
|
58
|
+
* @param {string} [routePath] service app의 local weather route 경로
|
|
59
|
+
* @param {string} fallbackRoutePath 기본 local weather route 경로
|
|
60
|
+
* @return {string} fetch에 사용할 local route 경로
|
|
61
|
+
*/
|
|
62
|
+
const getWeatherRoutePath = (
|
|
63
|
+
routePath: string | undefined,
|
|
64
|
+
fallbackRoutePath: string,
|
|
65
|
+
): string => routePath || fallbackRoutePath;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Weather Client; 현재/예보/summary 조회가 가능한 위치 입력인지 확인.
|
|
69
|
+
* @param {API_Req_Weather} params 날씨 요청 파라미터
|
|
70
|
+
* @param {number|null} [params.lat] 위도
|
|
71
|
+
* @param {number|null} [params.lng] 경도
|
|
72
|
+
* @return {boolean} 위치 기반 weather query 실행 가능 여부
|
|
73
|
+
*/
|
|
74
|
+
const isWeatherLocationAvailable = ({ lat, lng }: API_Req_Weather): boolean =>
|
|
75
|
+
lat !== null &&
|
|
76
|
+
lng !== null &&
|
|
77
|
+
typeof lat !== "undefined" &&
|
|
78
|
+
typeof lng !== "undefined" &&
|
|
79
|
+
isValidNumber(lat) &&
|
|
80
|
+
isValidNumber(lng);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Weather Client; 특보 조회가 가능한 농장 식별자인지 확인.
|
|
84
|
+
* @param {API_Req_WeatherAlert} params 특보 요청 파라미터
|
|
85
|
+
* @param {number|string|null} params.farm_idx 농장 식별자
|
|
86
|
+
* @return {boolean} 특보 query 실행 가능 여부
|
|
87
|
+
*/
|
|
88
|
+
const isWeatherFarmAvailable = ({ farm_idx }: API_Req_WeatherAlert): boolean =>
|
|
89
|
+
hasQueryValue(farm_idx) && Number(farm_idx) > 0;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Weather Client; React Query key에 사용할 weather 요청 값을 정렬.
|
|
93
|
+
* @param {API_Req_Weather} params 날씨 요청 파라미터
|
|
94
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
95
|
+
* @param {number|null} [params.lat] 위도
|
|
96
|
+
* @param {number|null} [params.lng] 경도
|
|
97
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
98
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
99
|
+
* @return {readonly unknown[]} React Query key 값
|
|
100
|
+
*/
|
|
101
|
+
const getWeatherQueryKeyValues = (params: API_Req_Weather) => {
|
|
102
|
+
const { farm_idx, lat, lng, locale, include_raw } =
|
|
103
|
+
getWeatherClientParams(params);
|
|
104
|
+
|
|
105
|
+
return [farm_idx, lat, lng, locale, include_raw] as const;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Weather Client; 현재 날씨 fetch.
|
|
110
|
+
* @method GET
|
|
111
|
+
* @param {API_Req_WeatherNow} params 현재 날씨 요청 파라미터
|
|
112
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
113
|
+
* @param {number|null} [params.lat] 위도
|
|
114
|
+
* @param {number|null} [params.lng] 경도
|
|
115
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
116
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
117
|
+
* @param {WeatherClientRouteOptions} [options] local route 옵션
|
|
118
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
119
|
+
* @return {Promise<Data>} 현재 날씨 응답
|
|
120
|
+
*/
|
|
121
|
+
export const getWeatherNow = async <Data = API_Res_WeatherNow>(
|
|
122
|
+
params: API_Req_Weather,
|
|
123
|
+
options: WeatherClientRouteOptions = {},
|
|
124
|
+
): Promise<Data> =>
|
|
125
|
+
await (
|
|
126
|
+
await fetch(
|
|
127
|
+
`${getWeatherRoutePath(
|
|
128
|
+
options.routePath,
|
|
129
|
+
WEATHER_CLIENT_ROUTE_PATH.now,
|
|
130
|
+
)}${getQueryString(getWeatherClientParams(params))}`,
|
|
131
|
+
)
|
|
132
|
+
).json();
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Weather Client; 예보 날씨 fetch.
|
|
136
|
+
* @method GET
|
|
137
|
+
* @param {API_Req_WeatherForecast} params 예보 날씨 요청 파라미터
|
|
138
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
139
|
+
* @param {number|null} [params.lat] 위도
|
|
140
|
+
* @param {number|null} [params.lng] 경도
|
|
141
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
142
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
143
|
+
* @param {WeatherClientRouteOptions} [options] local route 옵션
|
|
144
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
145
|
+
* @return {Promise<Data>} 예보 날씨 응답
|
|
146
|
+
*/
|
|
147
|
+
export const getWeatherForecast = async <Data = API_Res_WeatherForecast>(
|
|
148
|
+
params: API_Req_Weather,
|
|
149
|
+
options: WeatherClientRouteOptions = {},
|
|
150
|
+
): Promise<Data> =>
|
|
151
|
+
await (
|
|
152
|
+
await fetch(
|
|
153
|
+
`${getWeatherRoutePath(
|
|
154
|
+
options.routePath,
|
|
155
|
+
WEATHER_CLIENT_ROUTE_PATH.forecast,
|
|
156
|
+
)}${getQueryString(getWeatherClientParams(params))}`,
|
|
157
|
+
)
|
|
158
|
+
).json();
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Weather Client; summary fetch.
|
|
162
|
+
* @method GET
|
|
163
|
+
* @param {API_Req_WeatherSummary} params summary 요청 파라미터
|
|
164
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
165
|
+
* @param {number|null} [params.lat] 위도
|
|
166
|
+
* @param {number|null} [params.lng] 경도
|
|
167
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
168
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
169
|
+
* @param {WeatherClientRouteOptions} [options] local route 옵션
|
|
170
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
171
|
+
* @return {Promise<Data>} summary 응답
|
|
172
|
+
*/
|
|
173
|
+
export const getWeatherSummary = async <Data = API_Res_WeatherSummary>(
|
|
174
|
+
params: API_Req_Weather,
|
|
175
|
+
options: WeatherClientRouteOptions = {},
|
|
176
|
+
): Promise<Data> =>
|
|
177
|
+
await (
|
|
178
|
+
await fetch(
|
|
179
|
+
`${getWeatherRoutePath(
|
|
180
|
+
options.routePath,
|
|
181
|
+
WEATHER_CLIENT_ROUTE_PATH.summary,
|
|
182
|
+
)}${getQueryString(getWeatherClientParams(params))}`,
|
|
183
|
+
)
|
|
184
|
+
).json();
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Weather Client; 특보 fetch.
|
|
188
|
+
* @method GET
|
|
189
|
+
* @param {API_Req_WeatherAlert} params 특보 요청 파라미터
|
|
190
|
+
* @param {number|string|null} params.farm_idx 농장 식별자
|
|
191
|
+
* @param {WeatherClientRouteOptions} [options] local route 옵션
|
|
192
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
193
|
+
* @return {Promise<Data>} 특보 응답
|
|
194
|
+
*/
|
|
195
|
+
export const getWeatherAlert = async <Data = API_Res_WeatherAlert>(
|
|
196
|
+
params: API_Req_WeatherAlert,
|
|
197
|
+
options: WeatherClientRouteOptions = {},
|
|
198
|
+
): Promise<Data> =>
|
|
199
|
+
await (
|
|
200
|
+
await fetch(
|
|
201
|
+
`${getWeatherRoutePath(
|
|
202
|
+
options.routePath,
|
|
203
|
+
WEATHER_CLIENT_ROUTE_PATH.alert,
|
|
204
|
+
)}${getQueryString(params)}`,
|
|
205
|
+
)
|
|
206
|
+
).json();
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Weather Client; 현재 날씨 react query.
|
|
210
|
+
* @method GET
|
|
211
|
+
* @param {API_Req_WeatherNow} params 현재 날씨 요청 파라미터
|
|
212
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
213
|
+
* @param {number|null} [params.lat] 위도
|
|
214
|
+
* @param {number|null} [params.lng] 경도
|
|
215
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
216
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
217
|
+
* @param {WeatherClientQueryOptions} [options] query 옵션
|
|
218
|
+
* @param {boolean} [options.enabled] query 실행 여부
|
|
219
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
220
|
+
* @return {UseQueryResult<Data>} 현재 날씨 query 결과
|
|
221
|
+
*/
|
|
222
|
+
export const useQueryWeatherNow = <Data = API_Res_WeatherNow>(
|
|
223
|
+
params: API_Req_Weather,
|
|
224
|
+
options: WeatherClientQueryOptions = {},
|
|
225
|
+
): UseQueryResult<Data> => {
|
|
226
|
+
const routePath = getWeatherRoutePath(
|
|
227
|
+
options.routePath,
|
|
228
|
+
WEATHER_CLIENT_ROUTE_PATH.now,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return useQuery({
|
|
232
|
+
queryKey: ["weather_now", routePath, ...getWeatherQueryKeyValues(params)],
|
|
233
|
+
queryFn: () => getWeatherNow<Data>(params, { routePath }),
|
|
234
|
+
enabled: (options.enabled ?? true) && isWeatherLocationAvailable(params),
|
|
235
|
+
staleTime: 10 * 60 * 1000,
|
|
236
|
+
refetchInterval: 5 * 60 * 1000,
|
|
237
|
+
refetchOnWindowFocus: true,
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Weather Client; 예보 날씨 react query.
|
|
243
|
+
* @method GET
|
|
244
|
+
* @param {API_Req_WeatherForecast} params 예보 날씨 요청 파라미터
|
|
245
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
246
|
+
* @param {number|null} [params.lat] 위도
|
|
247
|
+
* @param {number|null} [params.lng] 경도
|
|
248
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
249
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
250
|
+
* @param {WeatherClientQueryOptions} [options] query 옵션
|
|
251
|
+
* @param {boolean} [options.enabled] query 실행 여부
|
|
252
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
253
|
+
* @return {UseQueryResult<Data>} 예보 날씨 query 결과
|
|
254
|
+
*/
|
|
255
|
+
export const useQueryWeatherForecast = <Data = API_Res_WeatherForecast>(
|
|
256
|
+
params: API_Req_Weather,
|
|
257
|
+
options: WeatherClientQueryOptions = {},
|
|
258
|
+
): UseQueryResult<Data> => {
|
|
259
|
+
const routePath = getWeatherRoutePath(
|
|
260
|
+
options.routePath,
|
|
261
|
+
WEATHER_CLIENT_ROUTE_PATH.forecast,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return useQuery({
|
|
265
|
+
queryKey: [
|
|
266
|
+
"weather_forecast",
|
|
267
|
+
routePath,
|
|
268
|
+
...getWeatherQueryKeyValues(params),
|
|
269
|
+
],
|
|
270
|
+
queryFn: () => getWeatherForecast<Data>(params, { routePath }),
|
|
271
|
+
enabled: (options.enabled ?? true) && isWeatherLocationAvailable(params),
|
|
272
|
+
staleTime: 30 * 60 * 1000,
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Weather Client; summary react query.
|
|
278
|
+
* @method GET
|
|
279
|
+
* @param {API_Req_WeatherSummary} params summary 요청 파라미터
|
|
280
|
+
* @param {number|string|null} [params.farm_idx] 농장 식별자
|
|
281
|
+
* @param {number|null} [params.lat] 위도
|
|
282
|
+
* @param {number|null} [params.lng] 경도
|
|
283
|
+
* @param {string} [params.locale] 요청 언어 코드
|
|
284
|
+
* @param {boolean} [params.include_raw] 원본 응답 포함 여부
|
|
285
|
+
* @param {WeatherClientQueryOptions} [options] query 옵션
|
|
286
|
+
* @param {boolean} [options.enabled] query 실행 여부
|
|
287
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
288
|
+
* @return {UseQueryResult<Data>} summary query 결과
|
|
289
|
+
*/
|
|
290
|
+
export const useQueryWeatherSummary = <Data = API_Res_WeatherSummary>(
|
|
291
|
+
params: API_Req_Weather,
|
|
292
|
+
options: WeatherClientQueryOptions = {},
|
|
293
|
+
): UseQueryResult<Data> => {
|
|
294
|
+
const routePath = getWeatherRoutePath(
|
|
295
|
+
options.routePath,
|
|
296
|
+
WEATHER_CLIENT_ROUTE_PATH.summary,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return useQuery({
|
|
300
|
+
queryKey: [
|
|
301
|
+
"weather_summary",
|
|
302
|
+
routePath,
|
|
303
|
+
...getWeatherQueryKeyValues(params),
|
|
304
|
+
],
|
|
305
|
+
queryFn: () => getWeatherSummary<Data>(params, { routePath }),
|
|
306
|
+
enabled: (options.enabled ?? true) && isWeatherLocationAvailable(params),
|
|
307
|
+
staleTime: 10 * 60 * 1000,
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Weather Client; 특보 react query.
|
|
313
|
+
* @method GET
|
|
314
|
+
* @param {API_Req_WeatherAlert} params 특보 요청 파라미터
|
|
315
|
+
* @param {number|string|null} params.farm_idx 농장 식별자
|
|
316
|
+
* @param {WeatherClientQueryOptions} [options] query 옵션
|
|
317
|
+
* @param {boolean} [options.enabled] query 실행 여부
|
|
318
|
+
* @param {string} [options.routePath] service app의 local weather route 경로
|
|
319
|
+
* @return {UseQueryResult<Data>} 특보 query 결과
|
|
320
|
+
*/
|
|
321
|
+
export const useQueryWeatherAlert = <Data = API_Res_WeatherAlert>(
|
|
322
|
+
params: API_Req_WeatherAlert,
|
|
323
|
+
options: WeatherClientQueryOptions = {},
|
|
324
|
+
): UseQueryResult<Data> => {
|
|
325
|
+
const farmIdx = params.farm_idx;
|
|
326
|
+
const routePath = getWeatherRoutePath(
|
|
327
|
+
options.routePath,
|
|
328
|
+
WEATHER_CLIENT_ROUTE_PATH.alert,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return useQuery({
|
|
332
|
+
queryKey: ["weather_alert", routePath, farmIdx],
|
|
333
|
+
queryFn: () => getWeatherAlert<Data>({ farm_idx: farmIdx }, { routePath }),
|
|
334
|
+
enabled:
|
|
335
|
+
(options.enabled ?? true) &&
|
|
336
|
+
isWeatherFarmAvailable({ farm_idx: farmIdx }),
|
|
337
|
+
staleTime: 60 * 60 * 1000,
|
|
338
|
+
});
|
|
339
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { generateQueryUrl, nextAPILog } from "@uniai-fe/util-api";
|
|
4
|
+
import { setDebugResponseHeaders } from "@uniai-fe/util-next";
|
|
5
|
+
import type { NextResponse } from "next/server";
|
|
6
|
+
import WEATHER_RESPONSE from "../data/response";
|
|
7
|
+
import type {
|
|
8
|
+
API_Res_WeatherAlert,
|
|
9
|
+
API_Res_WeatherForecast,
|
|
10
|
+
API_Res_WeatherNextDays,
|
|
11
|
+
API_Res_WeatherNow,
|
|
12
|
+
API_Res_WeatherSummary,
|
|
13
|
+
API_Res_WeatherToday,
|
|
14
|
+
WeatherBackendFetchParams,
|
|
15
|
+
WeatherServerRouteParams,
|
|
16
|
+
} from "../types";
|
|
17
|
+
|
|
18
|
+
const QUERY_URL = {
|
|
19
|
+
now: "/avic/weather/now",
|
|
20
|
+
forecast: "/avic/weather/forecast",
|
|
21
|
+
summary: "/avic/weather/summary",
|
|
22
|
+
alert: "/avic/weather/alert",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const { API_RES_BASE } = WEATHER_RESPONSE;
|
|
26
|
+
const BACKEND_QUERY_KEYS = [
|
|
27
|
+
"farm_idx",
|
|
28
|
+
"lat",
|
|
29
|
+
"lng",
|
|
30
|
+
"locale",
|
|
31
|
+
"include_raw",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Weather backend; client route query를 backend 허용 query로 정리.
|
|
36
|
+
* @param {URLSearchParams} searchParams service app local route query
|
|
37
|
+
* @param {string} [searchParams.farm_idx] 농장 식별자
|
|
38
|
+
* @param {string} [searchParams.lat] 위도
|
|
39
|
+
* @param {string} [searchParams.lng] 경도
|
|
40
|
+
* @param {string} [searchParams.locale] 요청 언어 코드
|
|
41
|
+
* @param {string} [searchParams.include_raw] 원본 응답 포함 여부
|
|
42
|
+
* @return {URLSearchParams} backend 전달 query
|
|
43
|
+
*/
|
|
44
|
+
const getBackendSearchParams = (
|
|
45
|
+
searchParams: URLSearchParams,
|
|
46
|
+
): URLSearchParams => {
|
|
47
|
+
const params = new URLSearchParams();
|
|
48
|
+
|
|
49
|
+
BACKEND_QUERY_KEYS.forEach(key => {
|
|
50
|
+
const value = searchParams.get(key);
|
|
51
|
+
if (value !== null && value !== "") params.set(key, value);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return params;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Weather backend; 실패 시 사용할 현재 날씨 기본값 생성.
|
|
59
|
+
* @return {API_Res_WeatherToday} 현재 날씨 기본값
|
|
60
|
+
*/
|
|
61
|
+
const getWeatherTodayDefault = (): API_Res_WeatherToday => ({
|
|
62
|
+
...API_RES_BASE,
|
|
63
|
+
temperature: null,
|
|
64
|
+
max_temperature: null,
|
|
65
|
+
min_temperature: null,
|
|
66
|
+
humidity: null,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Weather backend; 실패 시 사용할 내일/모레 예보 기본값 생성.
|
|
71
|
+
* @return {API_Res_WeatherNextDays} 내일/모레 예보 기본값
|
|
72
|
+
*/
|
|
73
|
+
const getWeatherNextDaysDefault = (): API_Res_WeatherNextDays => ({
|
|
74
|
+
...API_RES_BASE,
|
|
75
|
+
max_temperature: null,
|
|
76
|
+
min_temperature: null,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Weather backend; 실패 시 사용할 현재 날씨 응답 생성.
|
|
81
|
+
* @return {API_Res_WeatherNow} 현재 날씨 fallback 응답
|
|
82
|
+
*/
|
|
83
|
+
const getWeatherNowDefault = (): API_Res_WeatherNow => ({
|
|
84
|
+
today: getWeatherTodayDefault(),
|
|
85
|
+
source: null,
|
|
86
|
+
fallback_used: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Weather backend; 실패 시 사용할 예보 날씨 응답 생성.
|
|
91
|
+
* @return {API_Res_WeatherForecast} 예보 날씨 fallback 응답
|
|
92
|
+
*/
|
|
93
|
+
const getWeatherForecastDefault = (): API_Res_WeatherForecast => ({
|
|
94
|
+
today: getWeatherTodayDefault(),
|
|
95
|
+
day_1: getWeatherNextDaysDefault(),
|
|
96
|
+
day_2: getWeatherNextDaysDefault(),
|
|
97
|
+
source: null,
|
|
98
|
+
fallback_used: true,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Weather backend; 실패 시 사용할 특보 응답 생성.
|
|
103
|
+
* @return {API_Res_WeatherAlert} 특보 fallback 응답
|
|
104
|
+
*/
|
|
105
|
+
const getWeatherAlertDefault = (): API_Res_WeatherAlert => ({
|
|
106
|
+
api_announcement_time: null,
|
|
107
|
+
total_count: 0,
|
|
108
|
+
alerts: [],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Weather backend; 실패 시 사용할 summary 응답 생성.
|
|
113
|
+
* @return {API_Res_WeatherSummary} summary fallback 응답
|
|
114
|
+
*/
|
|
115
|
+
const getWeatherSummaryDefault = (): API_Res_WeatherSummary => ({
|
|
116
|
+
now: getWeatherNowDefault(),
|
|
117
|
+
forecast: getWeatherForecastDefault(),
|
|
118
|
+
alert: getWeatherAlertDefault(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Weather backend; backend weather route 호출과 fallback 응답 처리.
|
|
123
|
+
* @param {WeatherBackendFetchParams<Data>} params route 호출 옵션
|
|
124
|
+
* @param {string} params.domain backend API domain
|
|
125
|
+
* @param {string} params.routeUrl local route URL
|
|
126
|
+
* @param {string} params.queryUrl backend weather route URL
|
|
127
|
+
* @param {URLSearchParams} params.searchParams client route query
|
|
128
|
+
* @param {Data} params.fallback 실패 시 반환할 fallback 응답
|
|
129
|
+
* @return {Promise<Data>} backend 응답 또는 fallback
|
|
130
|
+
*/
|
|
131
|
+
const fetchWeatherBackend = async <Data>({
|
|
132
|
+
domain,
|
|
133
|
+
routeUrl,
|
|
134
|
+
queryUrl,
|
|
135
|
+
searchParams,
|
|
136
|
+
fallback,
|
|
137
|
+
}: WeatherBackendFetchParams<Data>): Promise<Data> => {
|
|
138
|
+
if (!domain) {
|
|
139
|
+
nextAPILog("GET", routeUrl, queryUrl, {
|
|
140
|
+
error: "환경변수를 확인하세요.",
|
|
141
|
+
});
|
|
142
|
+
return fallback;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const backendParams = getBackendSearchParams(searchParams);
|
|
146
|
+
const url = generateQueryUrl({
|
|
147
|
+
domain,
|
|
148
|
+
routeUrl,
|
|
149
|
+
queryUrl,
|
|
150
|
+
searchParams: backendParams,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const res = await fetch(url);
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
nextAPILog("GET", routeUrl, url, {
|
|
157
|
+
status: res.status,
|
|
158
|
+
statusText: res.statusText,
|
|
159
|
+
});
|
|
160
|
+
return fallback;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (await res.json()) as Data;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
nextAPILog("GET", routeUrl, url, { error });
|
|
166
|
+
return fallback;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Weather backend; 현재 날씨 proxy.
|
|
172
|
+
* @method GET
|
|
173
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
174
|
+
* @param {string} params.domain backend API domain
|
|
175
|
+
* @param {string} params.routeUrl service app local route URL
|
|
176
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
177
|
+
* @return {Promise<API_Res_WeatherNow>} 현재 날씨 응답
|
|
178
|
+
*/
|
|
179
|
+
export const routeWeatherNow = async ({
|
|
180
|
+
domain,
|
|
181
|
+
routeUrl,
|
|
182
|
+
searchParams,
|
|
183
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherNow> =>
|
|
184
|
+
await fetchWeatherBackend({
|
|
185
|
+
domain,
|
|
186
|
+
routeUrl,
|
|
187
|
+
queryUrl: QUERY_URL.now,
|
|
188
|
+
searchParams,
|
|
189
|
+
fallback: getWeatherNowDefault(),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Weather backend; 예보 날씨 proxy.
|
|
194
|
+
* @method GET
|
|
195
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
196
|
+
* @param {string} params.domain backend API domain
|
|
197
|
+
* @param {string} params.routeUrl service app local route URL
|
|
198
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
199
|
+
* @return {Promise<API_Res_WeatherForecast>} 예보 날씨 응답
|
|
200
|
+
*/
|
|
201
|
+
export const routeWeatherForecast = async ({
|
|
202
|
+
domain,
|
|
203
|
+
routeUrl,
|
|
204
|
+
searchParams,
|
|
205
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherForecast> =>
|
|
206
|
+
await fetchWeatherBackend({
|
|
207
|
+
domain,
|
|
208
|
+
routeUrl,
|
|
209
|
+
queryUrl: QUERY_URL.forecast,
|
|
210
|
+
searchParams,
|
|
211
|
+
fallback: getWeatherForecastDefault(),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Weather backend; 현재/예보/특보 summary proxy.
|
|
216
|
+
* @method GET
|
|
217
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
218
|
+
* @param {string} params.domain backend API domain
|
|
219
|
+
* @param {string} params.routeUrl service app local route URL
|
|
220
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
221
|
+
* @return {Promise<API_Res_WeatherSummary>} summary 응답
|
|
222
|
+
*/
|
|
223
|
+
export const routeWeatherSummary = async ({
|
|
224
|
+
domain,
|
|
225
|
+
routeUrl,
|
|
226
|
+
searchParams,
|
|
227
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherSummary> =>
|
|
228
|
+
await fetchWeatherBackend({
|
|
229
|
+
domain,
|
|
230
|
+
routeUrl,
|
|
231
|
+
queryUrl: QUERY_URL.summary,
|
|
232
|
+
searchParams,
|
|
233
|
+
fallback: getWeatherSummaryDefault(),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Weather backend; 특보 proxy.
|
|
238
|
+
* @method GET
|
|
239
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
240
|
+
* @param {string} params.domain backend API domain
|
|
241
|
+
* @param {string} params.routeUrl service app local route URL
|
|
242
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
243
|
+
* @return {Promise<NextResponse<API_Res_WeatherAlert>>} 특보 응답
|
|
244
|
+
*/
|
|
245
|
+
export const routeWeatherAlert = async ({
|
|
246
|
+
domain,
|
|
247
|
+
routeUrl,
|
|
248
|
+
searchParams,
|
|
249
|
+
}: WeatherServerRouteParams): Promise<NextResponse<API_Res_WeatherAlert>> => {
|
|
250
|
+
const res = await fetchWeatherBackend({
|
|
251
|
+
domain,
|
|
252
|
+
routeUrl,
|
|
253
|
+
queryUrl: QUERY_URL.alert,
|
|
254
|
+
searchParams,
|
|
255
|
+
fallback: getWeatherAlertDefault(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return setDebugResponseHeaders<API_Res_WeatherAlert>({
|
|
259
|
+
res,
|
|
260
|
+
domain,
|
|
261
|
+
queryUrl: QUERY_URL.alert,
|
|
262
|
+
searchParams: getBackendSearchParams(searchParams),
|
|
263
|
+
});
|
|
264
|
+
};
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
import type { WeatherAddressIconProps } from "../../types";
|
|
4
4
|
import MarkerIcon from "../../img/marker.svg";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Weather Address Icon; 위치 아이콘 컴포넌트.
|
|
8
|
+
* @component
|
|
9
|
+
* @desc weather address 앞에 표시할 marker SVG를 접근성 label과 함께 렌더한다.
|
|
10
|
+
* @param {WeatherAddressIconProps} [props] 위치 아이콘 props
|
|
11
|
+
* @param {string} [props.alt] 아이콘 대체 텍스트
|
|
12
|
+
*/
|
|
6
13
|
export default function WeatherAddressIcon({
|
|
7
14
|
alt,
|
|
8
15
|
}: WeatherAddressIconProps = {}) {
|
|
@@ -9,12 +9,13 @@ import type { WeatherIconProps } from "../../types";
|
|
|
9
9
|
// Storybook의 next/image mock과 동일한 client boundary에서 weather icon을 렌더한다.
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
12
|
+
* Weather Icon; 날씨 상태 아이콘 컴포넌트.
|
|
13
|
+
* @component
|
|
14
|
+
* @desc normalized weather condition code를 asset icon으로 렌더하고 상태 이름을 대체 텍스트로 사용한다.
|
|
15
|
+
* @param {WeatherIconProps} [props] 날씨 아이콘 props
|
|
16
|
+
* @param {string | null} [props.code] 날씨 아이콘 코드
|
|
17
|
+
* @param {string | null} [props.name] 날씨 상태 이름
|
|
18
|
+
* @param {string} [props.alt] 아이콘 대체 텍스트 fallback
|
|
18
19
|
*/
|
|
19
20
|
export default function WeatherIcon({
|
|
20
21
|
code,
|
|
@@ -8,6 +8,12 @@ import type { WeatherPageHeaderAddressProps } from "../../types";
|
|
|
8
8
|
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
9
9
|
import WeatherAddressIcon from "../icon/Address";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Weather Page Header; 현재 렌더링 시각을 표시 문자열로 변환.
|
|
13
|
+
* @util
|
|
14
|
+
* @desc 클라이언트 시각을 weather address 보조 텍스트 형식으로 변환한다.
|
|
15
|
+
* @return {string} yyyy-mm-dd hh:mm 형식의 렌더링 시각
|
|
16
|
+
*/
|
|
11
17
|
const getRenderedAt = (): string => {
|
|
12
18
|
const now = new Date();
|
|
13
19
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
@@ -18,6 +24,14 @@ const getRenderedAt = (): string => {
|
|
|
18
24
|
return `${now.getFullYear()}-${month}-${date} ${hours}:${minutes}`;
|
|
19
25
|
};
|
|
20
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Weather Page Header; 주소와 렌더링 시각을 표시하는 컴포넌트.
|
|
29
|
+
* @component
|
|
30
|
+
* @desc weatherCoordinate atom에 주입된 주소와 클라이언트 렌더링 시각을 표시한다.
|
|
31
|
+
* @param {WeatherPageHeaderAddressProps} [props] address props
|
|
32
|
+
* @param {string} [props.locale] 표시 언어 코드
|
|
33
|
+
* @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
|
|
34
|
+
*/
|
|
21
35
|
export default function WeatherPageHeaderAddress({
|
|
22
36
|
texts: textOverrides,
|
|
23
37
|
}: WeatherPageHeaderAddressProps = {}) {
|