@uniai-fe/uds-templates 0.5.9 → 0.5.10

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.
@@ -0,0 +1,277 @@
1
+ import type {
2
+ API_Res_WeatherKoreaForecast,
3
+ API_Res_WeatherKoreaNextDays,
4
+ API_Res_WeatherKoreaNow,
5
+ } from "./api";
6
+ import type {
7
+ OWM_Res_Weather_Forecast,
8
+ OWM_Res_Weather_Now,
9
+ } from "./open-weather-map";
10
+
11
+ /**
12
+ * Weather Page Header; locale 옵션
13
+ * @property {string} [locale] 표시 언어 코드. 기본 지원값은 ko, en, ja이며 타입은 확장 가능한 string으로 유지한다.
14
+ */
15
+ export interface WeatherLocaleOptions {
16
+ /**
17
+ * 표시 언어 코드. 기본 지원값은 ko, en, ja이며 타입은 확장 가능한 string으로 유지한다.
18
+ */
19
+ locale?: string;
20
+ }
21
+
22
+ /**
23
+ * Weather Page Header; 텍스트 옵션
24
+ * @property {string} currentWeatherLoading 현재 날씨 로딩 문구
25
+ * @property {string} forecastWeatherLoading 예보 날씨 로딩 문구
26
+ * @property {string} alertLoading 특보 로딩 문구
27
+ * @property {string} currentLabel 현재 기온 라벨
28
+ * @property {string} maxTemperatureLabel 최고 기온 라벨
29
+ * @property {string} minTemperatureLabel 최저 기온 라벨
30
+ * @property {string} humidityLabel 습도 라벨
31
+ * @property {string} tomorrowLabel 내일 라벨
32
+ * @property {string} dayAfterTomorrowLabel 모레 라벨
33
+ * @property {string} weatherIconAlt 날씨 아이콘 기본 대체 텍스트
34
+ * @property {string} addressIconAlt 위치 아이콘 대체 텍스트
35
+ * @property {string} conditionFallbackLabel 날씨 상태 fallback 텍스트
36
+ * @property {string} alertPreliminaryLevelLabel 예비 특보 수준 라벨
37
+ * @property {string} alertWatchLevelLabel 주의보 수준 라벨
38
+ * @property {string} alertCancelCommandLabel 특보 해제 명령 라벨
39
+ * @property {string} alertIssueCommandLabel 특보 발령 명령 라벨
40
+ */
41
+ export interface WeatherPageHeaderTexts {
42
+ /**
43
+ * 현재 날씨 로딩 문구
44
+ */
45
+ currentWeatherLoading: string;
46
+ /**
47
+ * 예보 날씨 로딩 문구
48
+ */
49
+ forecastWeatherLoading: string;
50
+ /**
51
+ * 특보 로딩 문구
52
+ */
53
+ alertLoading: string;
54
+ /**
55
+ * 현재 기온 라벨
56
+ */
57
+ currentLabel: string;
58
+ /**
59
+ * 최고 기온 라벨
60
+ */
61
+ maxTemperatureLabel: string;
62
+ /**
63
+ * 최저 기온 라벨
64
+ */
65
+ minTemperatureLabel: string;
66
+ /**
67
+ * 습도 라벨
68
+ */
69
+ humidityLabel: string;
70
+ /**
71
+ * 내일 라벨
72
+ */
73
+ tomorrowLabel: string;
74
+ /**
75
+ * 모레 라벨
76
+ */
77
+ dayAfterTomorrowLabel: string;
78
+ /**
79
+ * 날씨 아이콘 기본 대체 텍스트
80
+ */
81
+ weatherIconAlt: string;
82
+ /**
83
+ * 위치 아이콘 대체 텍스트
84
+ */
85
+ addressIconAlt: string;
86
+ /**
87
+ * 날씨 상태 fallback 텍스트
88
+ */
89
+ conditionFallbackLabel: string;
90
+ /**
91
+ * 예비 특보 수준 라벨
92
+ */
93
+ alertPreliminaryLevelLabel: string;
94
+ /**
95
+ * 주의보 수준 라벨
96
+ */
97
+ alertWatchLevelLabel: string;
98
+ /**
99
+ * 특보 해제 명령 라벨
100
+ */
101
+ alertCancelCommandLabel: string;
102
+ /**
103
+ * 특보 발령 명령 라벨
104
+ */
105
+ alertIssueCommandLabel: string;
106
+ }
107
+
108
+ /**
109
+ * Weather Page Header; locale과 텍스트 override 조합 옵션
110
+ * @property {string} [locale] 표시 언어 코드
111
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
112
+ */
113
+ export interface WeatherPageHeaderLocaleOptions extends WeatherLocaleOptions {
114
+ /**
115
+ * 서비스에서 주입한 표시 문구
116
+ */
117
+ texts?: Partial<WeatherPageHeaderTexts>;
118
+ }
119
+
120
+ /**
121
+ * Weather Page Header; KMA weather query 결과
122
+ * @property {API_Res_WeatherKoreaNow} [now] 현재 날씨 응답
123
+ * @property {API_Res_WeatherKoreaForecast} [forecast] 예보 날씨 응답
124
+ * @property {boolean} isFetchingNow 현재 날씨 조회 상태
125
+ * @property {boolean} isFetchingForecast 예보 날씨 조회 상태
126
+ */
127
+ export interface WeatherPageHeaderKoreaWeather {
128
+ /**
129
+ * 현재 날씨 응답
130
+ */
131
+ now?: API_Res_WeatherKoreaNow;
132
+ /**
133
+ * 예보 날씨 응답
134
+ */
135
+ forecast?: API_Res_WeatherKoreaForecast;
136
+ /**
137
+ * 현재 날씨 조회 상태
138
+ */
139
+ isFetchingNow: boolean;
140
+ /**
141
+ * 예보 날씨 조회 상태
142
+ */
143
+ isFetchingForecast: boolean;
144
+ }
145
+
146
+ /**
147
+ * Weather Page Header; OpenWeatherMap query 결과
148
+ * @property {OWM_Res_Weather_Now} [now] 현재 날씨 응답
149
+ * @property {OWM_Res_Weather_Forecast} [forecast] 예보 날씨 응답
150
+ * @property {boolean} isFetchingNow 현재 날씨 조회 상태
151
+ * @property {boolean} isFetchingForecast 예보 날씨 조회 상태
152
+ */
153
+ export interface WeatherPageHeaderOpenWeatherMapWeather {
154
+ /**
155
+ * 현재 날씨 응답
156
+ */
157
+ now?: OWM_Res_Weather_Now;
158
+ /**
159
+ * 예보 날씨 응답
160
+ */
161
+ forecast?: OWM_Res_Weather_Forecast;
162
+ /**
163
+ * 현재 날씨 조회 상태
164
+ */
165
+ isFetchingNow: boolean;
166
+ /**
167
+ * 예보 날씨 조회 상태
168
+ */
169
+ isFetchingForecast: boolean;
170
+ }
171
+
172
+ /**
173
+ * Weather Page Header; 상위 container에서 준비한 weather query 결과
174
+ * @property {WeatherPageHeaderKoreaWeather} [koreaWeather] KMA weather query 결과
175
+ * @property {WeatherPageHeaderOpenWeatherMapWeather} [openWeatherMapWeather] OpenWeatherMap query 결과
176
+ */
177
+ export interface WeatherPageHeaderDataOptions {
178
+ /**
179
+ * KMA weather query 결과
180
+ */
181
+ koreaWeather?: WeatherPageHeaderKoreaWeather;
182
+ /**
183
+ * OpenWeatherMap query 결과
184
+ */
185
+ openWeatherMapWeather?: WeatherPageHeaderOpenWeatherMapWeather;
186
+ }
187
+
188
+ /**
189
+ * Weather Page Header; container props
190
+ * @property {string} [locale] 표시 언어 코드
191
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
192
+ */
193
+ export type WeatherPageHeaderContainerProps = WeatherPageHeaderLocaleOptions;
194
+
195
+ /**
196
+ * Weather Page Header; address props
197
+ * @property {string} [locale] 표시 언어 코드
198
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
199
+ */
200
+ export type WeatherPageHeaderAddressProps = WeatherPageHeaderLocaleOptions;
201
+
202
+ /**
203
+ * Weather Page Header; today props
204
+ * @property {string} [locale] 표시 언어 코드
205
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
206
+ * @property {WeatherPageHeaderKoreaWeather} [koreaWeather] KMA weather query 결과
207
+ * @property {WeatherPageHeaderOpenWeatherMapWeather} [openWeatherMapWeather] OpenWeatherMap query 결과
208
+ */
209
+ export type WeatherPageHeaderTodayProps = WeatherPageHeaderLocaleOptions &
210
+ WeatherPageHeaderDataOptions;
211
+
212
+ /**
213
+ * Weather Page Header; forecast props
214
+ * @property {string} [locale] 표시 언어 코드
215
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
216
+ * @property {WeatherPageHeaderKoreaWeather} [koreaWeather] KMA weather query 결과
217
+ * @property {WeatherPageHeaderOpenWeatherMapWeather} [openWeatherMapWeather] OpenWeatherMap query 결과
218
+ */
219
+ export type WeatherPageHeaderForecastProps = WeatherPageHeaderLocaleOptions &
220
+ WeatherPageHeaderDataOptions;
221
+
222
+ /**
223
+ * Weather Page Header; alert props
224
+ * @property {string} [locale] 표시 언어 코드
225
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
226
+ */
227
+ export type WeatherPageHeaderAlertProps = WeatherPageHeaderLocaleOptions;
228
+
229
+ /**
230
+ * Weather Page Header; next days props
231
+ * @property {string} title 날짜 라벨
232
+ * @property {API_Res_WeatherKoreaNextDays} [data] 예보 데이터
233
+ * @property {string} [locale] 표시 언어 코드
234
+ * @property {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 표시 문구
235
+ */
236
+ export interface WeatherPageHeaderNextDaysProps extends WeatherPageHeaderLocaleOptions {
237
+ /**
238
+ * 날짜 라벨
239
+ */
240
+ title: string;
241
+ /**
242
+ * 예보 데이터
243
+ */
244
+ data?: API_Res_WeatherKoreaNextDays;
245
+ }
246
+
247
+ /**
248
+ * Weather Icon; 날씨 아이콘 props
249
+ * @property {string | null} [code] 날씨 아이콘 코드
250
+ * @property {string | null} [name] 날씨 상태 이름
251
+ * @property {string} [alt] 아이콘 대체 텍스트 fallback
252
+ */
253
+ export interface WeatherIconProps {
254
+ /**
255
+ * 날씨 아이콘 코드
256
+ */
257
+ code?: string | null;
258
+ /**
259
+ * 날씨 상태 이름
260
+ */
261
+ name?: string | null;
262
+ /**
263
+ * 아이콘 대체 텍스트 fallback
264
+ */
265
+ alt?: string;
266
+ }
267
+
268
+ /**
269
+ * Weather Address Icon; 위치 아이콘 props
270
+ * @property {string} [alt] 아이콘 대체 텍스트
271
+ */
272
+ export interface WeatherAddressIconProps {
273
+ /**
274
+ * 아이콘 대체 텍스트
275
+ */
276
+ alt?: string;
277
+ }
@@ -2,3 +2,4 @@ export * from "./location";
2
2
  export * from "./date-time";
3
3
  export * from "./weather";
4
4
  export * from "./alert";
5
+ export * from "./locale";
@@ -0,0 +1,73 @@
1
+ import type { WeatherPageHeaderTexts } from "../types";
2
+
3
+ const WEATHER_PAGE_HEADER_DEFAULT_TEXTS: WeatherPageHeaderTexts = {
4
+ currentWeatherLoading: "현재 날씨 불러오는 중...",
5
+ forecastWeatherLoading: "예보 날씨 불러오는 중...",
6
+ alertLoading: "특보 불러오는 중...",
7
+ currentLabel: "현재",
8
+ maxTemperatureLabel: "최고:",
9
+ minTemperatureLabel: "최저:",
10
+ humidityLabel: "습도",
11
+ tomorrowLabel: "내일",
12
+ dayAfterTomorrowLabel: "모레",
13
+ weatherIconAlt: "날씨 아이콘",
14
+ addressIconAlt: "날씨 위치",
15
+ conditionFallbackLabel: "-",
16
+ alertPreliminaryLevelLabel: "예비특보",
17
+ alertWatchLevelLabel: "주의보",
18
+ alertCancelCommandLabel: "해제",
19
+ alertIssueCommandLabel: "발령",
20
+ };
21
+
22
+ const OPEN_WEATHER_MAP_LANG_BY_LOCALE: Record<string, string> = {
23
+ ko: "kr",
24
+ en: "en",
25
+ ja: "ja",
26
+ };
27
+
28
+ /**
29
+ * Weather Locale; locale 기준 키 정규화
30
+ * @param {string} [locale] locale 코드
31
+ * @return {string} 전달된 locale의 기본 언어 코드
32
+ * @example
33
+ * getWeatherLocaleKey("ko-KR")
34
+ */
35
+ export const getWeatherLocaleKey = (locale?: string): string =>
36
+ locale?.trim().toLowerCase().split(/[-_]/)[0] || "ko";
37
+
38
+ /**
39
+ * Weather Locale; KMA provider 사용 여부
40
+ * @param {string} [locale] locale 코드
41
+ * @return {boolean} KMA provider 대상 locale 여부
42
+ * @example
43
+ * isKoreaWeatherLocale("ko-KR")
44
+ */
45
+ export const isKoreaWeatherLocale = (locale?: string): boolean =>
46
+ getWeatherLocaleKey(locale) === "ko";
47
+
48
+ /**
49
+ * Weather Locale; page-header 텍스트 병합
50
+ * @param {Partial<WeatherPageHeaderTexts>} [texts] 서비스에서 주입한 텍스트
51
+ * @return {WeatherPageHeaderTexts} 기본 fallback과 서비스 주입 텍스트를 병합한 텍스트
52
+ * @example
53
+ * resolveWeatherPageHeaderTexts({ humidityLabel: "습도" })
54
+ */
55
+ export const resolveWeatherPageHeaderTexts = (
56
+ texts?: Partial<WeatherPageHeaderTexts>,
57
+ ): WeatherPageHeaderTexts => ({
58
+ ...WEATHER_PAGE_HEADER_DEFAULT_TEXTS,
59
+ ...texts,
60
+ });
61
+
62
+ /**
63
+ * Weather Locale; OpenWeatherMap API lang 값 변환
64
+ * @param {string} [locale] locale 코드
65
+ * @return {string} OpenWeatherMap lang 파라미터
66
+ * @example
67
+ * getOpenWeatherMapLang("ko")
68
+ */
69
+ export const getOpenWeatherMapLang = (locale?: string): string => {
70
+ const localeKey = getWeatherLocaleKey(locale);
71
+
72
+ return OPEN_WEATHER_MAP_LANG_BY_LOCALE[localeKey] ?? localeKey;
73
+ };
@@ -1,10 +1,13 @@
1
1
  import type {
2
2
  API_Res_WeatherKoreaBase,
3
3
  API_Res_WeatherKoreaForecast,
4
+ API_Res_WeatherKoreaNextDays,
4
5
  API_Res_WeatherKoreaNow,
5
6
  KMA_Res_WeatherItem,
6
7
  KMA_Res_WeatherForecast,
7
8
  KMA_Res_WeatherNow,
9
+ OWM_Res_Weather_Forecast,
10
+ OWM_Res_Weather_Forecast_Item,
8
11
  } from "../types";
9
12
  import { getMoments } from "./date-time";
10
13
 
@@ -217,6 +220,115 @@ const getWeatherCondition = ({
217
220
  };
218
221
  };
219
222
 
223
+ /**
224
+ * 날씨 도구; OpenWeatherMap 아이콘 코드를 weather icon 코드로 변환
225
+ * @param {string} [icon] OpenWeatherMap icon 코드
226
+ * @return {string} weather icon 코드
227
+ */
228
+ export const getOpenWeatherMapConditionCode = (icon?: string): string => {
229
+ switch (icon) {
230
+ case "01d":
231
+ case "01n":
232
+ return "sky-1";
233
+ case "02d":
234
+ case "02n":
235
+ case "03d":
236
+ case "03n":
237
+ case "04d":
238
+ case "04n":
239
+ return "sky-2";
240
+ case "09d":
241
+ case "09n":
242
+ return "drop-rain-3";
243
+ case "10d":
244
+ case "10n":
245
+ return "drop-rain-1";
246
+ case "11d":
247
+ case "11n":
248
+ return "drop-rain-thunder";
249
+ case "13d":
250
+ case "13n":
251
+ return "drop-snow";
252
+ case "50d":
253
+ case "50n":
254
+ return "sky-4";
255
+ default:
256
+ return "sky-1";
257
+ }
258
+ };
259
+
260
+ const getLocalDateKey = (date: Date): string => {
261
+ const month = String(date.getMonth() + 1).padStart(2, "0");
262
+ const day = String(date.getDate()).padStart(2, "0");
263
+
264
+ return `${date.getFullYear()}-${month}-${day}`;
265
+ };
266
+
267
+ const getForecastItemDateKey = (item: OWM_Res_Weather_Forecast_Item): string =>
268
+ item.dt_txt?.slice(0, 10) || getLocalDateKey(new Date(item.dt * 1000));
269
+
270
+ const toOpenWeatherMapNextDay = (
271
+ items: OWM_Res_Weather_Forecast_Item[],
272
+ ): API_Res_WeatherKoreaNextDays | undefined => {
273
+ if (items.length === 0) return undefined;
274
+
275
+ const conditionItem =
276
+ items.find(item => item.dt_txt?.includes("12:00")) ||
277
+ items[Math.floor(items.length / 2)];
278
+ const temperatures = items.flatMap(item => [
279
+ item.main.temp_min,
280
+ item.main.temp_max,
281
+ item.main.temp,
282
+ ]);
283
+
284
+ return {
285
+ sky: null,
286
+ drop: null,
287
+ rainAmount: null,
288
+ snowAmount: null,
289
+ windSpeed: null,
290
+ condition: getOpenWeatherMapConditionCode(
291
+ conditionItem?.weather?.[0]?.icon,
292
+ ),
293
+ conditionName: conditionItem?.weather?.[0]?.description || null,
294
+ max_temperature:
295
+ temperatures.length > 0 ? Math.round(Math.max(...temperatures)) : null,
296
+ min_temperature:
297
+ temperatures.length > 0 ? Math.round(Math.min(...temperatures)) : null,
298
+ };
299
+ };
300
+
301
+ /**
302
+ * 날씨 도구; OpenWeatherMap 예보 응답을 page-header next days 형태로 변환
303
+ * @param {OWM_Res_Weather_Forecast} [forecast] OpenWeatherMap 예보 응답
304
+ * @return {{ day_1?: API_Res_WeatherKoreaNextDays; day_2?: API_Res_WeatherKoreaNextDays }} 내일/모레 예보
305
+ */
306
+ export const getOpenWeatherMapNextDays = (
307
+ forecast?: OWM_Res_Weather_Forecast,
308
+ ): {
309
+ day_1?: API_Res_WeatherKoreaNextDays;
310
+ day_2?: API_Res_WeatherKoreaNextDays;
311
+ } => {
312
+ if (!Array.isArray(forecast?.list)) return {};
313
+
314
+ const todayKey = getLocalDateKey(new Date());
315
+ const grouped = forecast.list.reduce<
316
+ Record<string, OWM_Res_Weather_Forecast_Item[]>
317
+ >((days, item) => {
318
+ const dateKey = getForecastItemDateKey(item);
319
+ if (dateKey === todayKey) return days;
320
+
321
+ days[dateKey] = [...(days[dateKey] || []), item];
322
+ return days;
323
+ }, {});
324
+ const [day1Key, day2Key] = Object.keys(grouped).sort();
325
+
326
+ return {
327
+ day_1: day1Key ? toOpenWeatherMapNextDay(grouped[day1Key]) : undefined,
328
+ day_2: day2Key ? toOpenWeatherMapNextDay(grouped[day2Key]) : undefined,
329
+ };
330
+ };
331
+
220
332
  export function now(
221
333
  outputResponse: API_Res_WeatherKoreaNow,
222
334
  apiResponse: KMA_Res_WeatherNow,