@uniai-fe/uds-templates 0.5.29 → 0.6.1
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/package.json +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 +459 -0
- package/src/weather/apis/index.ts +2 -4
- package/src/weather/apis/server.ts +373 -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 +442 -114
- package/src/weather/types/base.ts +31 -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 +47 -102
- 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,373 @@
|
|
|
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 { convertWeatherCoordinateToGrid } from "../utils";
|
|
8
|
+
import type {
|
|
9
|
+
API_Res_WeatherAlert,
|
|
10
|
+
API_Res_WeatherForecast,
|
|
11
|
+
API_Res_WeatherNextDays,
|
|
12
|
+
API_Res_WeatherNow,
|
|
13
|
+
API_Res_WeatherSummary,
|
|
14
|
+
API_Res_WeatherToday,
|
|
15
|
+
WeatherBackendFetchParams,
|
|
16
|
+
WeatherServerRouteParams,
|
|
17
|
+
} from "../types";
|
|
18
|
+
|
|
19
|
+
const QUERY_URL = {
|
|
20
|
+
now: "/avic/weather/now",
|
|
21
|
+
forecast: "/avic/weather/forecast",
|
|
22
|
+
summary: "/avic/weather/summary",
|
|
23
|
+
alert: "/avic/weather/alert",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const { API_RES_BASE } = WEATHER_RESPONSE;
|
|
27
|
+
type WeatherBackendRouteKind = keyof typeof QUERY_URL;
|
|
28
|
+
|
|
29
|
+
const BACKEND_COMMON_QUERY_KEYS = [
|
|
30
|
+
"farm_idx",
|
|
31
|
+
"lat",
|
|
32
|
+
"lng",
|
|
33
|
+
"locale",
|
|
34
|
+
"include_raw",
|
|
35
|
+
] as const;
|
|
36
|
+
const BACKEND_DATE_TIME_QUERY_PAIRS = {
|
|
37
|
+
now: [["base_date", "base_time"]],
|
|
38
|
+
forecast: [["base_date", "base_time"]],
|
|
39
|
+
summary: [
|
|
40
|
+
["now_base_date", "now_base_time"],
|
|
41
|
+
["forecast_base_date", "forecast_base_time"],
|
|
42
|
+
],
|
|
43
|
+
alert: [],
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Weather backend; query 값 존재 여부 확인.
|
|
48
|
+
* @param {string | null} value 확인할 query 값
|
|
49
|
+
* @return {boolean} query 값 존재 여부
|
|
50
|
+
*/
|
|
51
|
+
const hasSearchParamValue = (value: string | null): value is string =>
|
|
52
|
+
value !== null && value !== "";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Weather backend; query 값을 숫자로 변환한다.
|
|
56
|
+
* @param {URLSearchParams} searchParams service app local route query
|
|
57
|
+
* @param {string} key 숫자로 읽을 query key
|
|
58
|
+
* @return {number | null} 변환된 숫자 또는 null
|
|
59
|
+
*/
|
|
60
|
+
const getSearchParamNumber = (
|
|
61
|
+
searchParams: URLSearchParams,
|
|
62
|
+
key: string,
|
|
63
|
+
): number | null => {
|
|
64
|
+
const value = searchParams.get(key);
|
|
65
|
+
|
|
66
|
+
if (!hasSearchParamValue(value)) return null;
|
|
67
|
+
|
|
68
|
+
const parsed = Number(value);
|
|
69
|
+
|
|
70
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Weather backend; 재현용 date/time query를 pair 단위로 전달한다.
|
|
75
|
+
* @param {URLSearchParams} params backend 전달 query
|
|
76
|
+
* @param {URLSearchParams} searchParams service app local route query
|
|
77
|
+
* @param {WeatherBackendRouteKind} routeKind backend weather route 종류
|
|
78
|
+
*/
|
|
79
|
+
const setBackendDateTimeSearchParams = (
|
|
80
|
+
params: URLSearchParams,
|
|
81
|
+
searchParams: URLSearchParams,
|
|
82
|
+
routeKind: WeatherBackendRouteKind,
|
|
83
|
+
): void => {
|
|
84
|
+
BACKEND_DATE_TIME_QUERY_PAIRS[routeKind].forEach(([dateKey, timeKey]) => {
|
|
85
|
+
const date = searchParams.get(dateKey);
|
|
86
|
+
const time = searchParams.get(timeKey);
|
|
87
|
+
|
|
88
|
+
if (!hasSearchParamValue(date) || !hasSearchParamValue(time)) return;
|
|
89
|
+
|
|
90
|
+
params.set(dateKey, date);
|
|
91
|
+
params.set(timeKey, time);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Weather backend; nx/ny query를 직접 값 또는 lat/lng 변환값으로 전달한다.
|
|
97
|
+
* @param {URLSearchParams} params backend 전달 query
|
|
98
|
+
* @param {URLSearchParams} searchParams service app local route query
|
|
99
|
+
*/
|
|
100
|
+
const setBackendGridSearchParams = (
|
|
101
|
+
params: URLSearchParams,
|
|
102
|
+
searchParams: URLSearchParams,
|
|
103
|
+
): void => {
|
|
104
|
+
const providedNx = getSearchParamNumber(searchParams, "nx");
|
|
105
|
+
const providedNy = getSearchParamNumber(searchParams, "ny");
|
|
106
|
+
const { nx, ny } =
|
|
107
|
+
providedNx !== null && providedNy !== null
|
|
108
|
+
? { nx: providedNx, ny: providedNy }
|
|
109
|
+
: convertWeatherCoordinateToGrid({
|
|
110
|
+
lat: getSearchParamNumber(searchParams, "lat"),
|
|
111
|
+
lng: getSearchParamNumber(searchParams, "lng"),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (nx !== null && nx > 0) params.set("nx", String(nx));
|
|
115
|
+
if (ny !== null && ny > 0) params.set("ny", String(ny));
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Weather backend; client route query를 backend 허용 query로 정리.
|
|
120
|
+
* @param {WeatherBackendRouteKind} routeKind backend weather route 종류
|
|
121
|
+
* @param {URLSearchParams} searchParams service app local route query
|
|
122
|
+
* @param {string} [searchParams.farm_idx] 농장 식별자
|
|
123
|
+
* @param {string} [searchParams.lat] 위도
|
|
124
|
+
* @param {string} [searchParams.lng] 경도
|
|
125
|
+
* @param {string} [searchParams.nx] backend 격자 X
|
|
126
|
+
* @param {string} [searchParams.ny] backend 격자 Y
|
|
127
|
+
* @param {string} [searchParams.base_date] 현재/예보 단일 route 재현용 날짜
|
|
128
|
+
* @param {string} [searchParams.base_time] 현재/예보 단일 route 재현용 시각
|
|
129
|
+
* @param {string} [searchParams.now_base_date] summary 현재 날씨 재현용 날짜
|
|
130
|
+
* @param {string} [searchParams.now_base_time] summary 현재 날씨 재현용 시각
|
|
131
|
+
* @param {string} [searchParams.forecast_base_date] summary 예보 날씨 재현용 날짜
|
|
132
|
+
* @param {string} [searchParams.forecast_base_time] summary 예보 날씨 재현용 시각
|
|
133
|
+
* @param {string} [searchParams.locale] 요청 언어 코드
|
|
134
|
+
* @param {string} [searchParams.include_raw] 원본 응답 포함 여부
|
|
135
|
+
* @return {URLSearchParams} backend 전달 query
|
|
136
|
+
*/
|
|
137
|
+
const getBackendSearchParams = (
|
|
138
|
+
routeKind: WeatherBackendRouteKind,
|
|
139
|
+
searchParams: URLSearchParams,
|
|
140
|
+
): URLSearchParams => {
|
|
141
|
+
const params = new URLSearchParams();
|
|
142
|
+
|
|
143
|
+
if (routeKind === "alert") {
|
|
144
|
+
const farmIdx = searchParams.get("farm_idx");
|
|
145
|
+
if (hasSearchParamValue(farmIdx)) params.set("farm_idx", farmIdx);
|
|
146
|
+
return params;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
BACKEND_COMMON_QUERY_KEYS.forEach(key => {
|
|
150
|
+
const value = searchParams.get(key);
|
|
151
|
+
if (hasSearchParamValue(value)) params.set(key, value);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
setBackendGridSearchParams(params, searchParams);
|
|
155
|
+
setBackendDateTimeSearchParams(params, searchParams, routeKind);
|
|
156
|
+
|
|
157
|
+
return params;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Weather backend; 실패 시 사용할 현재 날씨 기본값 생성.
|
|
162
|
+
* @return {API_Res_WeatherToday} 현재 날씨 기본값
|
|
163
|
+
*/
|
|
164
|
+
const getWeatherTodayDefault = (): API_Res_WeatherToday => ({
|
|
165
|
+
...API_RES_BASE,
|
|
166
|
+
temperature: null,
|
|
167
|
+
max_temperature: null,
|
|
168
|
+
min_temperature: null,
|
|
169
|
+
humidity: null,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Weather backend; 실패 시 사용할 내일/모레 예보 기본값 생성.
|
|
174
|
+
* @return {API_Res_WeatherNextDays} 내일/모레 예보 기본값
|
|
175
|
+
*/
|
|
176
|
+
const getWeatherNextDaysDefault = (): API_Res_WeatherNextDays => ({
|
|
177
|
+
...API_RES_BASE,
|
|
178
|
+
max_temperature: null,
|
|
179
|
+
min_temperature: null,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Weather backend; 실패 시 사용할 현재 날씨 응답 생성.
|
|
184
|
+
* @return {API_Res_WeatherNow} 현재 날씨 fallback 응답
|
|
185
|
+
*/
|
|
186
|
+
const getWeatherNowDefault = (): API_Res_WeatherNow => ({
|
|
187
|
+
today: getWeatherTodayDefault(),
|
|
188
|
+
source: null,
|
|
189
|
+
fallback_used: true,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Weather backend; 실패 시 사용할 예보 날씨 응답 생성.
|
|
194
|
+
* @return {API_Res_WeatherForecast} 예보 날씨 fallback 응답
|
|
195
|
+
*/
|
|
196
|
+
const getWeatherForecastDefault = (): API_Res_WeatherForecast => ({
|
|
197
|
+
today: getWeatherTodayDefault(),
|
|
198
|
+
day_1: getWeatherNextDaysDefault(),
|
|
199
|
+
day_2: getWeatherNextDaysDefault(),
|
|
200
|
+
source: null,
|
|
201
|
+
fallback_used: true,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Weather backend; 실패 시 사용할 특보 응답 생성.
|
|
206
|
+
* @return {API_Res_WeatherAlert} 특보 fallback 응답
|
|
207
|
+
*/
|
|
208
|
+
const getWeatherAlertDefault = (): API_Res_WeatherAlert => ({
|
|
209
|
+
api_announcement_time: null,
|
|
210
|
+
total_count: 0,
|
|
211
|
+
alerts: [],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Weather backend; 실패 시 사용할 summary 응답 생성.
|
|
216
|
+
* @return {API_Res_WeatherSummary} summary fallback 응답
|
|
217
|
+
*/
|
|
218
|
+
const getWeatherSummaryDefault = (): API_Res_WeatherSummary => ({
|
|
219
|
+
now: getWeatherNowDefault(),
|
|
220
|
+
forecast: getWeatherForecastDefault(),
|
|
221
|
+
alert: getWeatherAlertDefault(),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Weather backend; backend weather route 호출과 fallback 응답 처리.
|
|
226
|
+
* @param {WeatherBackendFetchParams<Data>} params route 호출 옵션
|
|
227
|
+
* @param {string} params.domain backend API domain
|
|
228
|
+
* @param {string} params.routeUrl local route URL
|
|
229
|
+
* @param {string} params.queryUrl backend weather route URL
|
|
230
|
+
* @param {"now"|"forecast"|"summary"|"alert"} params.routeKind backend weather route 종류
|
|
231
|
+
* @param {URLSearchParams} params.searchParams client route query
|
|
232
|
+
* @param {Data} params.fallback 실패 시 반환할 fallback 응답
|
|
233
|
+
* @return {Promise<Data>} backend 응답 또는 fallback
|
|
234
|
+
*/
|
|
235
|
+
const fetchWeatherBackend = async <Data>({
|
|
236
|
+
domain,
|
|
237
|
+
routeUrl,
|
|
238
|
+
queryUrl,
|
|
239
|
+
routeKind,
|
|
240
|
+
searchParams,
|
|
241
|
+
fallback,
|
|
242
|
+
}: WeatherBackendFetchParams<Data>): Promise<Data> => {
|
|
243
|
+
if (!domain) {
|
|
244
|
+
nextAPILog("GET", routeUrl, queryUrl, {
|
|
245
|
+
error: "환경변수를 확인하세요.",
|
|
246
|
+
});
|
|
247
|
+
return fallback;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const backendParams = getBackendSearchParams(routeKind, searchParams);
|
|
251
|
+
const url = generateQueryUrl({
|
|
252
|
+
domain,
|
|
253
|
+
routeUrl,
|
|
254
|
+
queryUrl,
|
|
255
|
+
searchParams: backendParams,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetch(url);
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
nextAPILog("GET", routeUrl, url, {
|
|
262
|
+
status: res.status,
|
|
263
|
+
statusText: res.statusText,
|
|
264
|
+
});
|
|
265
|
+
return fallback;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return (await res.json()) as Data;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
nextAPILog("GET", routeUrl, url, { error });
|
|
271
|
+
return fallback;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Weather backend; 현재 날씨 proxy.
|
|
277
|
+
* @method GET
|
|
278
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
279
|
+
* @param {string} params.domain backend API domain
|
|
280
|
+
* @param {string} params.routeUrl service app local route URL
|
|
281
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
282
|
+
* @return {Promise<API_Res_WeatherNow>} 현재 날씨 응답
|
|
283
|
+
*/
|
|
284
|
+
export const routeWeatherNow = async ({
|
|
285
|
+
domain,
|
|
286
|
+
routeUrl,
|
|
287
|
+
searchParams,
|
|
288
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherNow> =>
|
|
289
|
+
await fetchWeatherBackend({
|
|
290
|
+
domain,
|
|
291
|
+
routeUrl,
|
|
292
|
+
queryUrl: QUERY_URL.now,
|
|
293
|
+
routeKind: "now",
|
|
294
|
+
searchParams,
|
|
295
|
+
fallback: getWeatherNowDefault(),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Weather backend; 예보 날씨 proxy.
|
|
300
|
+
* @method GET
|
|
301
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
302
|
+
* @param {string} params.domain backend API domain
|
|
303
|
+
* @param {string} params.routeUrl service app local route URL
|
|
304
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
305
|
+
* @return {Promise<API_Res_WeatherForecast>} 예보 날씨 응답
|
|
306
|
+
*/
|
|
307
|
+
export const routeWeatherForecast = async ({
|
|
308
|
+
domain,
|
|
309
|
+
routeUrl,
|
|
310
|
+
searchParams,
|
|
311
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherForecast> =>
|
|
312
|
+
await fetchWeatherBackend({
|
|
313
|
+
domain,
|
|
314
|
+
routeUrl,
|
|
315
|
+
queryUrl: QUERY_URL.forecast,
|
|
316
|
+
routeKind: "forecast",
|
|
317
|
+
searchParams,
|
|
318
|
+
fallback: getWeatherForecastDefault(),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Weather backend; 현재/예보/특보 summary proxy.
|
|
323
|
+
* @method GET
|
|
324
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
325
|
+
* @param {string} params.domain backend API domain
|
|
326
|
+
* @param {string} params.routeUrl service app local route URL
|
|
327
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
328
|
+
* @return {Promise<API_Res_WeatherSummary>} summary 응답
|
|
329
|
+
*/
|
|
330
|
+
export const routeWeatherSummary = async ({
|
|
331
|
+
domain,
|
|
332
|
+
routeUrl,
|
|
333
|
+
searchParams,
|
|
334
|
+
}: WeatherServerRouteParams): Promise<API_Res_WeatherSummary> =>
|
|
335
|
+
await fetchWeatherBackend({
|
|
336
|
+
domain,
|
|
337
|
+
routeUrl,
|
|
338
|
+
queryUrl: QUERY_URL.summary,
|
|
339
|
+
routeKind: "summary",
|
|
340
|
+
searchParams,
|
|
341
|
+
fallback: getWeatherSummaryDefault(),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Weather backend; 특보 proxy.
|
|
346
|
+
* @method GET
|
|
347
|
+
* @param {WeatherServerRouteParams} params route 호출 옵션
|
|
348
|
+
* @param {string} params.domain backend API domain
|
|
349
|
+
* @param {string} params.routeUrl service app local route URL
|
|
350
|
+
* @param {URLSearchParams} params.searchParams service app local route query
|
|
351
|
+
* @return {Promise<NextResponse<API_Res_WeatherAlert>>} 특보 응답
|
|
352
|
+
*/
|
|
353
|
+
export const routeWeatherAlert = async ({
|
|
354
|
+
domain,
|
|
355
|
+
routeUrl,
|
|
356
|
+
searchParams,
|
|
357
|
+
}: WeatherServerRouteParams): Promise<NextResponse<API_Res_WeatherAlert>> => {
|
|
358
|
+
const res = await fetchWeatherBackend({
|
|
359
|
+
domain,
|
|
360
|
+
routeUrl,
|
|
361
|
+
queryUrl: QUERY_URL.alert,
|
|
362
|
+
routeKind: "alert",
|
|
363
|
+
searchParams,
|
|
364
|
+
fallback: getWeatherAlertDefault(),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return setDebugResponseHeaders<API_Res_WeatherAlert>({
|
|
368
|
+
res,
|
|
369
|
+
domain,
|
|
370
|
+
queryUrl: QUERY_URL.alert,
|
|
371
|
+
searchParams: getBackendSearchParams("alert", searchParams),
|
|
372
|
+
});
|
|
373
|
+
};
|
|
@@ -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 = {}) {
|
|
@@ -3,21 +3,28 @@
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
|
|
5
5
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
6
|
-
import
|
|
6
|
+
import { useWeatherAlert } from "../../hooks";
|
|
7
7
|
import type { WeatherPageHeaderAlertProps } from "../../types";
|
|
8
|
-
import {
|
|
9
|
-
isKoreaWeatherLocale,
|
|
10
|
-
resolveWeatherPageHeaderTexts,
|
|
11
|
-
} from "../../utils";
|
|
8
|
+
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
12
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Weather Page Header; 기상 특보를 표시하는 컴포넌트.
|
|
12
|
+
* @component
|
|
13
|
+
* @desc 통합 weather alert query 결과 또는 상위 weather props의 특보 목록을 표시한다.
|
|
14
|
+
* @param {WeatherPageHeaderAlertProps} [props] alert props
|
|
15
|
+
* @param {string} [props.locale] 표시 언어 코드
|
|
16
|
+
* @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
|
|
17
|
+
* @param {WeatherPageHeaderWeather} [props.weather] 상위 container에서 준비한 통합 weather query 결과
|
|
18
|
+
*/
|
|
13
19
|
export default function WeatherPageHeaderAlert({
|
|
14
|
-
locale,
|
|
15
20
|
texts: textOverrides,
|
|
21
|
+
weather,
|
|
16
22
|
}: WeatherPageHeaderAlertProps = {}) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
enabled: isKoreaProvider,
|
|
23
|
+
const alertFallback = useWeatherAlert({
|
|
24
|
+
enabled: !weather,
|
|
20
25
|
});
|
|
26
|
+
const alert = weather?.alert ?? alertFallback.alert;
|
|
27
|
+
const isFetching = weather?.isFetchingAlert ?? alertFallback.isFetching;
|
|
21
28
|
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
22
29
|
|
|
23
30
|
const notice = useMemo((): string => {
|
|
@@ -42,10 +49,7 @@ export default function WeatherPageHeaderAlert({
|
|
|
42
49
|
texts.alertWatchLevelLabel,
|
|
43
50
|
]);
|
|
44
51
|
|
|
45
|
-
if (
|
|
46
|
-
!isKoreaProvider ||
|
|
47
|
-
(!isFetching && (alert.length === 0 || !alert?.[0]?.alert_type))
|
|
48
|
-
) {
|
|
52
|
+
if (!isFetching && (alert.length === 0 || !alert?.[0]?.alert_type)) {
|
|
49
53
|
return null;
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -5,37 +5,30 @@ import WeatherPageHeaderForecast from "./Forecast";
|
|
|
5
5
|
import WeatherPageHeaderAddress from "./Address";
|
|
6
6
|
import PageHeaderUtilityItem from "../../../page-frame/desktop/components/header/util/Item";
|
|
7
7
|
import type { WeatherPageHeaderContainerProps } from "../../types";
|
|
8
|
-
import {
|
|
9
|
-
import { isKoreaWeatherLocale } from "../../utils";
|
|
8
|
+
import { useWeather } from "../../hooks";
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Weather Page Header; address/today/forecast를 조합하는 container 컴포넌트.
|
|
12
|
+
* @component
|
|
13
|
+
* @desc 통합 weather summary query를 한 번 실행하고 address, today, forecast leaf에 전달한다.
|
|
14
|
+
* @param {WeatherPageHeaderContainerProps} [props] container props
|
|
15
|
+
* @param {string} [props.locale] 표시 언어 코드
|
|
16
|
+
* @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
|
|
17
|
+
*/
|
|
11
18
|
export default function WeatherPageHeaderContainer({
|
|
12
19
|
locale,
|
|
13
20
|
texts,
|
|
14
21
|
}: WeatherPageHeaderContainerProps = {}) {
|
|
15
|
-
const
|
|
16
|
-
const koreaWeather = useWeatherKorea({
|
|
17
|
-
enabled: isKoreaProvider,
|
|
18
|
-
});
|
|
19
|
-
const openWeatherMapWeather = useOpenWeatherMap({
|
|
20
|
-
locale,
|
|
21
|
-
enabledNow: !isKoreaProvider,
|
|
22
|
-
enabledForecast: !isKoreaProvider,
|
|
23
|
-
});
|
|
22
|
+
const weather = useWeather({ locale });
|
|
24
23
|
|
|
25
24
|
return (
|
|
26
25
|
<PageHeaderUtilityItem className="weather-page-header">
|
|
27
26
|
<WeatherPageHeaderAddress locale={locale} texts={texts} />
|
|
28
|
-
<WeatherPageHeaderToday
|
|
29
|
-
locale={locale}
|
|
30
|
-
texts={texts}
|
|
31
|
-
koreaWeather={koreaWeather}
|
|
32
|
-
openWeatherMapWeather={openWeatherMapWeather}
|
|
33
|
-
/>
|
|
27
|
+
<WeatherPageHeaderToday locale={locale} texts={texts} weather={weather} />
|
|
34
28
|
<WeatherPageHeaderForecast
|
|
35
29
|
locale={locale}
|
|
36
30
|
texts={texts}
|
|
37
|
-
|
|
38
|
-
openWeatherMapWeather={openWeatherMapWeather}
|
|
31
|
+
weather={weather}
|
|
39
32
|
/>
|
|
40
33
|
</PageHeaderUtilityItem>
|
|
41
34
|
);
|
|
@@ -1,46 +1,39 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo } from "react";
|
|
4
3
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
5
|
-
import {
|
|
4
|
+
import { useWeather } from "../../hooks";
|
|
6
5
|
import type { WeatherPageHeaderForecastProps } from "../../types";
|
|
7
6
|
import {
|
|
8
|
-
|
|
9
|
-
isKoreaWeatherLocale,
|
|
7
|
+
isWeatherForecastResponse,
|
|
10
8
|
resolveWeatherPageHeaderTexts,
|
|
11
9
|
} from "../../utils";
|
|
12
10
|
import WeatherPageHeaderNextDays from "./NextDays";
|
|
13
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Weather Page Header; 내일/모레 예보를 표시하는 컴포넌트.
|
|
14
|
+
* @component
|
|
15
|
+
* @desc 통합 weather forecast 응답에서 day_1/day_2를 추출해 하루 단위 예보로 표시한다.
|
|
16
|
+
* @param {WeatherPageHeaderForecastProps} [props] forecast props
|
|
17
|
+
* @param {string} [props.locale] 표시 언어 코드
|
|
18
|
+
* @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
|
|
19
|
+
* @param {WeatherPageHeaderWeather} [props.weather] 상위 container에서 준비한 통합 weather query 결과
|
|
20
|
+
*/
|
|
14
21
|
export default function WeatherPageHeaderForecast({
|
|
15
22
|
locale,
|
|
16
23
|
texts: textOverrides,
|
|
17
|
-
|
|
18
|
-
openWeatherMapWeather,
|
|
24
|
+
weather,
|
|
19
25
|
}: WeatherPageHeaderForecastProps = {}) {
|
|
20
|
-
const
|
|
21
|
-
const koreaWeatherFallback = useWeatherKorea({
|
|
22
|
-
enabled: !koreaWeather && isKoreaProvider,
|
|
23
|
-
});
|
|
24
|
-
const openWeatherMapWeatherFallback = useOpenWeatherMap({
|
|
26
|
+
const weatherFallback = useWeather({
|
|
25
27
|
locale,
|
|
26
|
-
|
|
27
|
-
enabledForecast: !openWeatherMapWeather && !isKoreaProvider,
|
|
28
|
+
enabled: !weather,
|
|
28
29
|
});
|
|
29
|
-
const
|
|
30
|
-
const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
|
|
30
|
+
const currentWeather = weather ?? weatherFallback;
|
|
31
31
|
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
36
|
-
const forecastDays = isKoreaProvider
|
|
37
|
-
? korea.forecast
|
|
38
|
-
: openWeatherMapNextDays;
|
|
39
|
-
const isFetching = isKoreaProvider
|
|
40
|
-
? korea.isFetchingForecast
|
|
41
|
-
: openWeatherMap.isFetchingForecast;
|
|
32
|
+
const forecast = isWeatherForecastResponse(currentWeather.forecast)
|
|
33
|
+
? currentWeather.forecast
|
|
34
|
+
: undefined;
|
|
42
35
|
|
|
43
|
-
if (
|
|
36
|
+
if (currentWeather.isFetchingForecast) {
|
|
44
37
|
return (
|
|
45
38
|
<div className="weather-next-days-container">
|
|
46
39
|
<Alternate.LoadingDefault direction="horizontal">
|
|
@@ -54,12 +47,12 @@ export default function WeatherPageHeaderForecast({
|
|
|
54
47
|
<>
|
|
55
48
|
<WeatherPageHeaderNextDays
|
|
56
49
|
title={texts.tomorrowLabel}
|
|
57
|
-
data={
|
|
50
|
+
data={forecast?.day_1}
|
|
58
51
|
texts={textOverrides}
|
|
59
52
|
/>
|
|
60
53
|
<WeatherPageHeaderNextDays
|
|
61
54
|
title={texts.dayAfterTomorrowLabel}
|
|
62
|
-
data={
|
|
55
|
+
data={forecast?.day_2}
|
|
63
56
|
texts={textOverrides}
|
|
64
57
|
/>
|
|
65
58
|
</>
|
|
@@ -4,6 +4,16 @@ import type { WeatherPageHeaderNextDaysProps } from "../../types";
|
|
|
4
4
|
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
5
5
|
import WeatherIcon from "../icon/Weather";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Weather Page Header; 하루 단위 예보를 표시하는 컴포넌트.
|
|
9
|
+
* @component
|
|
10
|
+
* @desc title과 normalized forecast day 데이터를 조합해 날씨 아이콘과 최저/최고 기온을 표시한다.
|
|
11
|
+
* @param {WeatherPageHeaderNextDaysProps} props next days props
|
|
12
|
+
* @param {string} props.title 날짜 라벨
|
|
13
|
+
* @param {API_Res_WeatherNextDays} [props.data] 예보 데이터
|
|
14
|
+
* @param {string} [props.locale] 표시 언어 코드
|
|
15
|
+
* @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
|
|
16
|
+
*/
|
|
7
17
|
export default function WeatherPageHeaderNextDays({
|
|
8
18
|
title,
|
|
9
19
|
data,
|