@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
@@ -3,21 +3,28 @@
3
3
  import { useMemo } from "react";
4
4
 
5
5
  import { Alternate } from "@uniai-fe/uds-primitives";
6
- import useWeatherKoreaAlert from "../../hooks/useWeatherKoreaAlert";
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 isKoreaProvider = isKoreaWeatherLocale(locale);
18
- const { alert, isFetching } = useWeatherKoreaAlert({
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 { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
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 isKoreaProvider = isKoreaWeatherLocale(locale);
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
- koreaWeather={koreaWeather}
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 { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
4
+ import { useWeather } from "../../hooks";
6
5
  import type { WeatherPageHeaderForecastProps } from "../../types";
7
6
  import {
8
- getOpenWeatherMapNextDays,
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
- koreaWeather,
18
- openWeatherMapWeather,
24
+ weather,
19
25
  }: WeatherPageHeaderForecastProps = {}) {
20
- const isKoreaProvider = isKoreaWeatherLocale(locale);
21
- const koreaWeatherFallback = useWeatherKorea({
22
- enabled: !koreaWeather && isKoreaProvider,
23
- });
24
- const openWeatherMapWeatherFallback = useOpenWeatherMap({
26
+ const weatherFallback = useWeather({
25
27
  locale,
26
- enabledNow: false,
27
- enabledForecast: !openWeatherMapWeather && !isKoreaProvider,
28
+ enabled: !weather,
28
29
  });
29
- const korea = koreaWeather ?? koreaWeatherFallback;
30
- const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
30
+ const currentWeather = weather ?? weatherFallback;
31
31
  const texts = resolveWeatherPageHeaderTexts(textOverrides);
32
- const openWeatherMapNextDays = useMemo(
33
- () => getOpenWeatherMapNextDays(openWeatherMap.forecast),
34
- [openWeatherMap.forecast],
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 (isFetching) {
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={forecastDays?.day_1}
50
+ data={forecast?.day_1}
58
51
  texts={textOverrides}
59
52
  />
60
53
  <WeatherPageHeaderNextDays
61
54
  title={texts.dayAfterTomorrowLabel}
62
- data={forecastDays?.day_2}
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,
@@ -7,187 +7,115 @@ import { Alternate } from "@uniai-fe/uds-primitives";
7
7
  import WeatherIcon from "../icon/Weather";
8
8
  import WeatherPageHeaderAlert from "./Alert";
9
9
  import type { WeatherPageHeaderTodayProps } from "../../types";
10
- import { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
10
+ import { useWeather } from "../../hooks";
11
11
  import {
12
- getOpenWeatherMapConditionCode,
13
- getOpenWeatherMapTodayForecast,
14
- isKoreaWeatherLocale,
12
+ isWeatherForecastResponse,
13
+ isWeatherNowResponse,
15
14
  resolveWeatherPageHeaderTexts,
16
15
  } from "../../utils";
17
16
 
17
+ /**
18
+ * Weather Page Header; 표시 가능한 숫자값으로 변환.
19
+ * @util
20
+ * @desc weather API가 숫자 또는 문자열로 내려주는 수치 값을 number/null로 정규화한다.
21
+ * @param {number|string|null|undefined} value 변환할 날씨 수치 값
22
+ * @return {number | null} 표시 가능한 숫자 값
23
+ */
24
+ const toWeatherNumber = (
25
+ value: number | string | null | undefined,
26
+ ): number | null => {
27
+ if (value === null || typeof value === "undefined" || value === "") {
28
+ return null;
29
+ }
30
+
31
+ const nextValue = Number(value);
32
+ return Number.isNaN(nextValue) ? null : nextValue;
33
+ };
34
+
35
+ /**
36
+ * Weather Page Header; 현재 날씨와 당일 최고/최저/습도를 표시하는 컴포넌트.
37
+ * @component
38
+ * @desc 통합 weather now/forecast 응답을 조합해 현재 기온, 최고/최저 기온, 습도를 표시한다.
39
+ * @param {WeatherPageHeaderTodayProps} [props] today props
40
+ * @param {string} [props.locale] 표시 언어 코드
41
+ * @param {Partial<WeatherPageHeaderTexts>} [props.texts] 서비스에서 주입한 표시 문구
42
+ * @param {WeatherPageHeaderWeather} [props.weather] 상위 container에서 준비한 통합 weather query 결과
43
+ */
18
44
  export default function WeatherPageHeaderToday({
19
45
  locale,
20
46
  texts: textOverrides,
21
- koreaWeather,
22
- openWeatherMapWeather,
47
+ weather,
23
48
  }: WeatherPageHeaderTodayProps = {}) {
24
- const isKoreaProvider = isKoreaWeatherLocale(locale);
25
- const koreaWeatherFallback = useWeatherKorea({
26
- enabled: !koreaWeather && isKoreaProvider,
27
- });
28
- const openWeatherMapWeatherFallback = useOpenWeatherMap({
49
+ const weatherFallback = useWeather({
29
50
  locale,
30
- enabledNow: !openWeatherMapWeather && !isKoreaProvider,
31
- enabledForecast: !openWeatherMapWeather && !isKoreaProvider,
51
+ enabled: !weather,
32
52
  });
33
- const korea = koreaWeather ?? koreaWeatherFallback;
34
- const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
53
+ const currentWeather = weather ?? weatherFallback;
35
54
  const texts = resolveWeatherPageHeaderTexts(textOverrides);
36
- const openWeatherMapToday = useMemo(
37
- () => getOpenWeatherMapTodayForecast(openWeatherMap.forecast),
38
- [openWeatherMap.forecast],
55
+ const now = useMemo(
56
+ () =>
57
+ isWeatherNowResponse(currentWeather.now) ? currentWeather.now : undefined,
58
+ [currentWeather.now],
59
+ );
60
+ const forecast = useMemo(
61
+ () =>
62
+ isWeatherForecastResponse(currentWeather.forecast)
63
+ ? currentWeather.forecast
64
+ : undefined,
65
+ [currentWeather.forecast],
39
66
  );
40
67
 
41
- const isFetchingAlternate = useMemo(() => {
42
- if (!isKoreaProvider)
43
- return openWeatherMap.isFetchingNow || openWeatherMap.isFetchingForecast;
44
- if (korea.isFetchingNow) return true;
45
- return false;
46
- }, [
47
- isKoreaProvider,
48
- korea.isFetchingNow,
49
- openWeatherMap.isFetchingForecast,
50
- openWeatherMap.isFetchingNow,
51
- ]);
52
-
53
- const iconCode = useMemo(() => {
54
- if (isKoreaProvider) {
55
- const kma_now = korea.now?.today?.condition;
56
- if (kma_now && !kma_now.endsWith("null")) return kma_now;
57
-
58
- const kma_forecast = korea.forecast?.today?.condition;
59
- if (kma_forecast && !kma_forecast.endsWith("null")) return kma_forecast;
60
- }
61
-
62
- return getOpenWeatherMapConditionCode(
63
- openWeatherMap.now?.weather?.[0]?.icon,
64
- );
65
- }, [
66
- isKoreaProvider,
67
- korea.forecast?.today?.condition,
68
- korea.now?.today?.condition,
69
- openWeatherMap.now?.weather,
70
- ]);
71
-
72
- const weatherStateName = useMemo(() => {
73
- if (isKoreaProvider) {
74
- const kma_now = korea.now?.today?.conditionName;
75
- if (kma_now && !kma_now.endsWith("null"))
76
- return kma_now || texts.conditionFallbackLabel;
77
-
78
- const kma_forecast = korea.forecast?.today?.conditionName;
79
- if (kma_forecast && !kma_forecast.endsWith("null"))
80
- return kma_forecast || texts.conditionFallbackLabel;
81
- }
82
-
83
- return (
84
- openWeatherMap.now?.weather?.[0]?.description ||
85
- texts.conditionFallbackLabel
86
- );
87
- }, [
88
- isKoreaProvider,
89
- korea.forecast?.today?.conditionName,
90
- korea.now?.today?.conditionName,
91
- openWeatherMap.now?.weather,
92
- texts.conditionFallbackLabel,
93
- ]);
94
-
95
- const temperature = useMemo(() => {
96
- if (isKoreaProvider) {
97
- const kma_now = korea.now?.today?.temperature;
98
- if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
99
-
100
- const kma_forecast = korea.forecast?.today?.temperature;
101
- if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
102
- return Number(kma_forecast);
103
- }
104
-
105
- const alt_now = openWeatherMap.now?.main?.temp;
106
- return typeof alt_now === "number" ? Math.round(alt_now) : null;
107
- }, [
108
- isKoreaProvider,
109
- korea.forecast?.today?.temperature,
110
- korea.now?.today?.temperature,
111
- openWeatherMap.now?.main?.temp,
112
- ]);
113
-
114
- const humidity = useMemo(() => {
115
- if (isKoreaProvider) {
116
- const kma_now = korea.now?.today?.humidity;
117
- if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
118
-
119
- const kma_forecast = korea.forecast?.today?.humidity;
120
- if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
121
- return Number(kma_forecast);
122
- }
123
-
124
- const alt_now = openWeatherMap.now?.main?.humidity;
125
- return typeof alt_now === "number" ? Math.round(alt_now) : null;
126
- }, [
127
- isKoreaProvider,
128
- korea.forecast?.today?.humidity,
129
- korea.now?.today?.humidity,
130
- openWeatherMap.now?.main?.humidity,
131
- ]);
132
-
133
- const maxTemperature = useMemo(() => {
134
- if (isKoreaProvider) {
135
- const kma_forecast = korea.forecast?.today?.max_temperature;
136
- if (
137
- kma_forecast !== null &&
138
- kma_forecast !== undefined &&
139
- !Number.isNaN(Number(kma_forecast))
140
- )
141
- return Number(kma_forecast);
142
- }
68
+ const iconCode = useMemo(
69
+ () => now?.today.condition || forecast?.today.condition || "sky-1",
70
+ [forecast?.today.condition, now?.today.condition],
71
+ );
143
72
 
144
- const alt_forecast = openWeatherMapToday?.max_temperature;
145
- if (
146
- alt_forecast !== null &&
147
- alt_forecast !== undefined &&
148
- !Number.isNaN(Number(alt_forecast))
149
- )
150
- return Number(alt_forecast);
73
+ const weatherStateName = useMemo(
74
+ () =>
75
+ now?.today.conditionName ||
76
+ forecast?.today.conditionName ||
77
+ texts.conditionFallbackLabel,
78
+ [
79
+ forecast?.today.conditionName,
80
+ now?.today.conditionName,
81
+ texts.conditionFallbackLabel,
82
+ ],
83
+ );
151
84
 
152
- return null;
153
- }, [
154
- isKoreaProvider,
155
- korea.forecast?.today?.max_temperature,
156
- openWeatherMapToday?.max_temperature,
157
- ]);
85
+ const temperature = useMemo(
86
+ () =>
87
+ toWeatherNumber(now?.today.temperature) ??
88
+ toWeatherNumber(forecast?.today.temperature),
89
+ [forecast?.today.temperature, now?.today.temperature],
90
+ );
158
91
 
159
- const minTemperature = useMemo(() => {
160
- if (isKoreaProvider) {
161
- const kma_forecast = korea.forecast?.today?.min_temperature;
162
- if (
163
- kma_forecast !== null &&
164
- kma_forecast !== undefined &&
165
- !Number.isNaN(Number(kma_forecast))
166
- )
167
- return Number(kma_forecast);
168
- }
92
+ const humidity = useMemo(
93
+ () =>
94
+ toWeatherNumber(now?.today.humidity) ??
95
+ toWeatherNumber(forecast?.today.humidity),
96
+ [forecast?.today.humidity, now?.today.humidity],
97
+ );
169
98
 
170
- const alt_forecast = openWeatherMapToday?.min_temperature;
171
- if (
172
- alt_forecast !== null &&
173
- alt_forecast !== undefined &&
174
- !Number.isNaN(Number(alt_forecast))
175
- )
176
- return Number(alt_forecast);
99
+ const maxTemperature = useMemo(
100
+ () => toWeatherNumber(forecast?.today.max_temperature),
101
+ [forecast?.today.max_temperature],
102
+ );
177
103
 
178
- return null;
179
- }, [
180
- isKoreaProvider,
181
- korea.forecast?.today?.min_temperature,
182
- openWeatherMapToday?.min_temperature,
183
- ]);
104
+ const minTemperature = useMemo(
105
+ () => toWeatherNumber(forecast?.today.min_temperature),
106
+ [forecast?.today.min_temperature],
107
+ );
184
108
  const alert = (
185
- <WeatherPageHeaderAlert locale={locale} texts={textOverrides} />
109
+ <WeatherPageHeaderAlert
110
+ locale={locale}
111
+ texts={textOverrides}
112
+ weather={currentWeather}
113
+ />
186
114
  );
187
115
 
188
116
  return (
189
117
  <div className="weather-today-container">
190
- {isFetchingAlternate ? (
118
+ {currentWeather.isFetchingNow ? (
191
119
  <>
192
120
  {alert}
193
121
  <Alternate.LoadingDefault direction="horizontal">
@@ -6,6 +6,11 @@ import WeatherPageHeaderNextDays from "./NextDays";
6
6
  import WeatherPageHeaderToday from "./Today";
7
7
 
8
8
  // 기존 Container 호출 계약을 유지하면서, 서비스 재조합용 하위 조각 접근도 함께 연다.
9
+ /**
10
+ * Weather Page Header; 조합형 page-header namespace 컴포넌트.
11
+ * @component
12
+ * @desc 기존 container 호출 계약을 유지하고 서비스 재조합용 하위 조각을 정적 속성으로 노출한다.
13
+ */
9
14
  export const WeatherPageHeader = Object.assign(WeatherPageHeaderContainer, {
10
15
  Container: WeatherPageHeaderContainer,
11
16
  Address: WeatherPageHeaderAddress,
@@ -1,24 +1,6 @@
1
- import type { API_Res_WeatherKoreaBase } from "../types";
1
+ import type { API_Res_WeatherBase } from "../types";
2
2
 
3
- const API_RES_RAW = {
4
- response: {
5
- header: {
6
- resultCode: "",
7
- resultMsg: "",
8
- },
9
- body: {
10
- dataType: "",
11
- items: {
12
- item: [],
13
- },
14
- pageNo: 0,
15
- numOfRows: 0,
16
- totalCount: 0,
17
- },
18
- },
19
- };
20
-
21
- const API_RES_BASE: API_Res_WeatherKoreaBase = {
3
+ const API_RES_BASE: API_Res_WeatherBase = {
22
4
  sky: null,
23
5
  drop: null,
24
6
  rainAmount: null,
@@ -28,9 +10,8 @@ const API_RES_BASE: API_Res_WeatherKoreaBase = {
28
10
  conditionName: null,
29
11
  };
30
12
 
31
- const WEATHER_KOREA_RESPONSE = {
32
- API_RES_RAW,
13
+ const WEATHER_RESPONSE = {
33
14
  API_RES_BASE,
34
15
  };
35
16
 
36
- export default WEATHER_KOREA_RESPONSE;
17
+ export default WEATHER_RESPONSE;
@@ -1,4 +1,4 @@
1
- import useOpenWeatherMap from "./useOpenWeatherMap";
2
- import useWeatherKorea from "./useWeatherKorea";
1
+ import useWeather from "./useWeather";
2
+ import useWeatherAlert from "./useWeatherAlert";
3
3
 
4
- export { useWeatherKorea, useOpenWeatherMap };
4
+ export { useWeather, useWeatherAlert };
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useAtomValue } from "jotai";
5
+ import { useQueryWeatherSummary } from "../apis/client";
6
+ import { weatherCoordinate, weatherFarmIdx } from "../jotai";
7
+ import type {
8
+ API_Req_Weather,
9
+ UseWeatherOptions,
10
+ UseWeatherReturn,
11
+ } from "../types";
12
+
13
+ /**
14
+ * Weather Hook; 통합 weather summary API를 조회한다.
15
+ * @hook
16
+ * @desc weatherCoordinate/weatherFarmIdx atom 값을 통합 summary route 요청 파라미터로 변환해 조회한다.
17
+ * @param {UseWeatherOptions} [options] weather 조회 옵션
18
+ * @param {string} [options.locale] 요청 언어 코드
19
+ * @param {boolean} [options.enabled] query 실행 여부
20
+ * @return {UseWeatherReturn} 통합 weather query 결과
21
+ */
22
+ export default function useWeather({
23
+ locale,
24
+ enabled = true,
25
+ }: UseWeatherOptions = {}): UseWeatherReturn {
26
+ const coordinate = useAtomValue(weatherCoordinate);
27
+ const farm_idx = useAtomValue(weatherFarmIdx);
28
+ const params = useMemo<API_Req_Weather>(
29
+ () => ({
30
+ lat: coordinate.lat,
31
+ lng: coordinate.lng,
32
+ ...(farm_idx ? { farm_idx } : {}),
33
+ ...(locale ? { locale } : {}),
34
+ }),
35
+ [coordinate.lat, coordinate.lng, farm_idx, locale],
36
+ );
37
+ const { data, isFetching } = useQueryWeatherSummary(params, { enabled });
38
+
39
+ const alert = useMemo(() => data?.alert?.alerts || [], [data]);
40
+
41
+ return {
42
+ params,
43
+ coordinate,
44
+ farm_idx,
45
+ ...(data ? { summary: data, now: data.now, forecast: data.forecast } : {}),
46
+ alert,
47
+ isFetching,
48
+ isFetchingNow: isFetching,
49
+ isFetchingForecast: isFetching,
50
+ isFetchingAlert: isFetching,
51
+ };
52
+ }
@@ -0,0 +1,35 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import { useAtomValue } from "jotai";
5
+ import { useQueryWeatherAlert } from "../apis/client";
6
+ import { weatherFarmIdx } from "../jotai";
7
+ import type { UseWeatherAlertOptions, UseWeatherAlertReturn } from "../types";
8
+
9
+ /**
10
+ * Weather Hook; 통합 weather alert API를 조회한다.
11
+ * @hook
12
+ * @desc weatherFarmIdx atom 값을 통합 alert route 요청 파라미터로 변환해 조회한다.
13
+ * @param {UseWeatherAlertOptions} [options] 특보 조회 옵션
14
+ * @param {boolean} [options.enabled] query 실행 여부
15
+ * @return {UseWeatherAlertReturn} 특보 query 결과
16
+ */
17
+ export default function useWeatherAlert({
18
+ enabled = true,
19
+ }: UseWeatherAlertOptions = {}): UseWeatherAlertReturn {
20
+ const farm_idx = useAtomValue(weatherFarmIdx);
21
+ const { data, isFetching } = useQueryWeatherAlert(
22
+ {
23
+ farm_idx,
24
+ },
25
+ { enabled },
26
+ );
27
+
28
+ const alert = useMemo(() => data?.alerts || [], [data]);
29
+
30
+ return {
31
+ farm_idx,
32
+ alert,
33
+ isFetching,
34
+ };
35
+ }
@@ -2,12 +2,12 @@
2
2
  import "./index.scss";
3
3
 
4
4
  /**
5
- * Weather; page-header 템플릿과 api/hook/jotai 도구를 함께 제공하는 엔트리
5
+ * Weather; page-header 템플릿과 normalized API/hook/jotai 도구를 제공하는 엔트리
6
6
  */
7
7
  export * from "./utils";
8
8
  export * from "./apis";
9
- export * from "./jotai";
10
9
  export * from "./hooks";
10
+ export * from "./jotai";
11
11
  export * from "./types";
12
12
  export { default as WeatherComponents } from "./components";
13
13
  export { WeatherPageHeader } from "./components";
@@ -3,6 +3,10 @@
3
3
  import type { WeatherCoordinate } from "../types";
4
4
  import { atomWithReset } from "jotai/utils";
5
5
 
6
+ /**
7
+ * Weather Jotai; 현재 지역 좌표 atom.
8
+ * @desc service app에서 주입한 주소와 위경도를 weather template 전역 상태로 보관한다.
9
+ */
6
10
  export const weatherCoordinate = atomWithReset<WeatherCoordinate>({
7
11
  address: null,
8
12
  lat: null,
@@ -2,4 +2,8 @@
2
2
 
3
3
  import { atomWithReset } from "jotai/utils";
4
4
 
5
+ /**
6
+ * Weather Jotai; 현재 농장 식별자 atom.
7
+ * @desc weather alert 조회에 사용할 farm_idx를 weather template 전역 상태로 보관한다.
8
+ */
5
9
  export const weatherFarmIdx = atomWithReset<number | null>(null);