@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.
Files changed (56) hide show
  1. package/README.md +3 -6
  2. package/dist/styles.css +1 -1
  3. package/package.json +1 -1
  4. package/src/cctv/styles/variables.scss +1 -1
  5. package/src/weather/_legacy/apis/index.ts +4 -0
  6. package/src/weather/_legacy/data/response.ts +36 -0
  7. package/src/weather/_legacy/hooks/index.ts +5 -0
  8. package/src/weather/{hooks → _legacy/hooks}/useOpenWeatherMap.ts +1 -1
  9. package/src/weather/{hooks → _legacy/hooks}/useWeatherKorea.ts +4 -7
  10. package/src/weather/{hooks → _legacy/hooks}/useWeatherKoreaAlert.ts +2 -4
  11. package/src/weather/_legacy/types/api.ts +221 -0
  12. package/src/weather/_legacy/types/base.ts +70 -0
  13. package/src/weather/_legacy/types/index.ts +4 -0
  14. package/src/weather/_legacy/utils/index.ts +5 -0
  15. package/src/weather/_legacy/utils/locale.ts +28 -0
  16. package/src/weather/_legacy/utils/location.ts +139 -0
  17. package/src/weather/_legacy/utils/weather.ts +460 -0
  18. package/src/weather/apis/client.ts +339 -0
  19. package/src/weather/apis/index.ts +2 -4
  20. package/src/weather/apis/server.ts +264 -0
  21. package/src/weather/components/icon/Address.tsx +7 -0
  22. package/src/weather/components/icon/Weather.tsx +7 -6
  23. package/src/weather/components/page-header/Address.tsx +14 -0
  24. package/src/weather/components/page-header/Alert.tsx +17 -13
  25. package/src/weather/components/page-header/Container.tsx +12 -19
  26. package/src/weather/components/page-header/Forecast.tsx +21 -28
  27. package/src/weather/components/page-header/NextDays.tsx +10 -0
  28. package/src/weather/components/page-header/Today.tsx +86 -158
  29. package/src/weather/components/page-header/index.ts +5 -0
  30. package/src/weather/data/response.ts +4 -23
  31. package/src/weather/hooks/index.ts +3 -3
  32. package/src/weather/hooks/useWeather.ts +52 -0
  33. package/src/weather/hooks/useWeatherAlert.ts +35 -0
  34. package/src/weather/index.tsx +2 -2
  35. package/src/weather/jotai/coordinate.ts +4 -0
  36. package/src/weather/jotai/farm-idx.ts +4 -0
  37. package/src/weather/types/api.ts +393 -114
  38. package/src/weather/types/base.ts +15 -32
  39. package/src/weather/types/index.ts +0 -3
  40. package/src/weather/types/page-header.ts +118 -68
  41. package/src/weather/utils/index.ts +6 -4
  42. package/src/weather/utils/locale.ts +7 -69
  43. package/src/weather/utils/location.ts +6 -141
  44. package/src/weather/utils/weather.ts +53 -456
  45. package/src/weather/data/alert-regions-meta.json +0 -1286
  46. package/src/weather/data/weather-regions-meta.json +0 -9833
  47. package/src/weather/types/provider.ts +0 -34
  48. package/src/weather/utils/alert.ts +0 -30
  49. /package/src/weather/{apis → _legacy/apis}/korea/client.ts +0 -0
  50. /package/src/weather/{apis → _legacy/apis}/korea/server.ts +0 -0
  51. /package/src/weather/{apis → _legacy/apis}/open-weather-map/client.ts +0 -0
  52. /package/src/weather/{apis → _legacy/apis}/open-weather-map/server.ts +0 -0
  53. /package/src/weather/{types → _legacy/types}/korea.ts +0 -0
  54. /package/src/weather/{types → _legacy/types}/open-weather-map.ts +0 -0
  55. /package/src/weather/{utils → _legacy/utils}/date-time.ts +0 -0
  56. /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
+ };
@@ -1,4 +1,2 @@
1
- export * from "./korea/client";
2
- export * from "./korea/server";
3
- export * from "./open-weather-map/client";
4
- export * from "./open-weather-map/server";
1
+ export * from "./client";
2
+ export * from "./server";
@@ -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
- * @desc
14
- * - 기상청 API 허브
15
- * - 2024. 11. 28. 발효 기준
16
- *
17
- * @see https://apihub.kma.go.kr/getAttachFile.do?fileName=(20241128)%EB%8B%A8%EA%B8%B0%EC%98%88%EB%B3%B4%20%EC%84%9C%EB%B9%84%EC%8A%A4%20%EA%B0%9C%EC%84%A0%EC%97%90%20%EB%94%B0%EB%A5%B8%20API%20%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD.pdf
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 = {}) {