@uniai-fe/uds-templates 0.5.9 → 0.5.11
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 +7 -0
- package/dist/styles.css +139 -79
- package/package.json +1 -1
- package/src/cctv/components/pagination/list/Item.tsx +9 -0
- package/src/weather/apis/korea/client.ts +50 -15
- package/src/weather/apis/korea/server.ts +2 -2
- package/src/weather/apis/open-weather-map/client.ts +45 -15
- package/src/weather/apis/open-weather-map/server.ts +8 -2
- package/src/weather/components/icon/Address.tsx +7 -6
- package/src/weather/components/icon/Weather.tsx +4 -5
- package/src/weather/components/page-header/Address.tsx +36 -2
- package/src/weather/components/page-header/Alert.tsx +43 -16
- package/src/weather/components/page-header/Container.tsx +33 -5
- package/src/weather/components/page-header/Forecast.tsx +48 -7
- package/src/weather/components/page-header/NextDays.tsx +25 -22
- package/src/weather/components/page-header/Today.tsx +134 -91
- package/src/weather/hooks/useOpenWeatherMap.ts +22 -3
- package/src/weather/hooks/useWeatherKorea.ts +16 -4
- package/src/weather/hooks/useWeatherKoreaAlert.ts +13 -4
- package/src/weather/img/marker.svg +4 -0
- package/src/weather/index.scss +1 -0
- package/src/weather/styles/variables.scss +30 -0
- package/src/weather/styles/weather.scss +116 -109
- package/src/weather/types/base.ts +20 -0
- package/src/weather/types/index.ts +2 -0
- package/src/weather/types/page-header.ts +277 -0
- package/src/weather/types/provider.ts +34 -0
- package/src/weather/utils/index.ts +1 -0
- package/src/weather/utils/locale.ts +110 -0
- package/src/weather/utils/weather.ts +112 -0
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
3
4
|
import { useAtomValue } from "jotai";
|
|
4
5
|
|
|
5
6
|
import { weatherCoordinate } from "../../jotai";
|
|
7
|
+
import type { WeatherPageHeaderAddressProps } from "../../types";
|
|
8
|
+
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
6
9
|
import WeatherAddressIcon from "../icon/Address";
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
const getRenderedAt = (): string => {
|
|
12
|
+
const now = new Date();
|
|
13
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
14
|
+
const date = String(now.getDate()).padStart(2, "0");
|
|
15
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
16
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
17
|
+
|
|
18
|
+
return `${now.getFullYear()}-${month}-${date} ${hours}:${minutes}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function WeatherPageHeaderAddress({
|
|
22
|
+
texts: textOverrides,
|
|
23
|
+
}: WeatherPageHeaderAddressProps = {}) {
|
|
9
24
|
const { address } = useAtomValue(weatherCoordinate);
|
|
25
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
26
|
+
const [renderedAt, setRenderedAt] = useState(getRenderedAt);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const intervalId = window.setInterval(() => {
|
|
30
|
+
setRenderedAt(prevRenderedAt => {
|
|
31
|
+
const nextRenderedAt = getRenderedAt();
|
|
32
|
+
|
|
33
|
+
return prevRenderedAt === nextRenderedAt
|
|
34
|
+
? prevRenderedAt
|
|
35
|
+
: nextRenderedAt;
|
|
36
|
+
});
|
|
37
|
+
}, 1000);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
window.clearInterval(intervalId);
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
10
43
|
|
|
11
44
|
if (!address) return null;
|
|
12
45
|
|
|
13
46
|
return (
|
|
14
47
|
<div className="weather-address">
|
|
15
|
-
<WeatherAddressIcon />
|
|
48
|
+
<WeatherAddressIcon alt={texts.addressIconAlt} />
|
|
16
49
|
<p className="weather-address-text">
|
|
17
50
|
<span>{address}</span>
|
|
51
|
+
<span className="weather-address-date">({renderedAt})</span>
|
|
18
52
|
</p>
|
|
19
53
|
</div>
|
|
20
54
|
);
|
|
@@ -2,40 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
|
|
5
|
-
import useWeatherKoreaAlert from "../../hooks/useWeatherKoreaAlert";
|
|
6
5
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
6
|
+
import useWeatherKoreaAlert from "../../hooks/useWeatherKoreaAlert";
|
|
7
|
+
import type { WeatherPageHeaderAlertProps } from "../../types";
|
|
8
|
+
import {
|
|
9
|
+
isKoreaWeatherLocale,
|
|
10
|
+
resolveWeatherPageHeaderTexts,
|
|
11
|
+
} from "../../utils";
|
|
7
12
|
|
|
8
|
-
export default function WeatherPageHeaderAlert(
|
|
9
|
-
|
|
13
|
+
export default function WeatherPageHeaderAlert({
|
|
14
|
+
locale,
|
|
15
|
+
texts: textOverrides,
|
|
16
|
+
}: WeatherPageHeaderAlertProps = {}) {
|
|
17
|
+
const isKoreaProvider = isKoreaWeatherLocale(locale);
|
|
18
|
+
const { alert, isFetching } = useWeatherKoreaAlert({
|
|
19
|
+
enabled: isKoreaProvider,
|
|
20
|
+
});
|
|
21
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
10
22
|
|
|
11
23
|
const notice = useMemo((): string => {
|
|
12
24
|
if (alert.length === 0 || !alert?.[0]?.alert_type) return "";
|
|
13
25
|
|
|
14
26
|
const { alert_type, alert_level, alert_command } = alert?.[0] || {};
|
|
15
27
|
const level = () => {
|
|
16
|
-
if (alert_level.startsWith("예비"))
|
|
17
|
-
|
|
28
|
+
if (alert_level.startsWith("예비"))
|
|
29
|
+
return texts.alertPreliminaryLevelLabel;
|
|
30
|
+
if (alert_level.startsWith("주의")) return texts.alertWatchLevelLabel;
|
|
18
31
|
return alert_level;
|
|
19
32
|
};
|
|
20
|
-
const command = alert_command?.includes("해제")
|
|
33
|
+
const command = alert_command?.includes("해제")
|
|
34
|
+
? texts.alertCancelCommandLabel
|
|
35
|
+
: texts.alertIssueCommandLabel;
|
|
21
36
|
return `${alert_type}${level()} ${command}`;
|
|
22
|
-
}, [
|
|
37
|
+
}, [
|
|
38
|
+
alert,
|
|
39
|
+
texts.alertCancelCommandLabel,
|
|
40
|
+
texts.alertIssueCommandLabel,
|
|
41
|
+
texts.alertPreliminaryLevelLabel,
|
|
42
|
+
texts.alertWatchLevelLabel,
|
|
43
|
+
]);
|
|
23
44
|
|
|
24
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
!isKoreaProvider ||
|
|
47
|
+
(!isFetching && (alert.length === 0 || !alert?.[0]?.alert_type))
|
|
48
|
+
) {
|
|
25
49
|
return null;
|
|
26
50
|
}
|
|
27
51
|
|
|
52
|
+
if (isFetching) {
|
|
53
|
+
return (
|
|
54
|
+
<div className="weather-alert-loading">
|
|
55
|
+
<Alternate.LoadingDefault direction="horizontal">
|
|
56
|
+
{texts.alertLoading}
|
|
57
|
+
</Alternate.LoadingDefault>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
return (
|
|
29
63
|
<div className="weather-alert">
|
|
30
64
|
<div className="weather-alert-text">
|
|
31
|
-
{
|
|
32
|
-
// 특보 조회 중에도 영역을 확보해 레이아웃이 흔들리지 않도록 로딩 표시를 유지한다.
|
|
33
|
-
<Alternate.LoadingDefault direction="horizontal">
|
|
34
|
-
특보 불러오는 중...
|
|
35
|
-
</Alternate.LoadingDefault>
|
|
36
|
-
) : (
|
|
37
|
-
<span>{notice}</span>
|
|
38
|
-
)}
|
|
65
|
+
<span>{notice}</span>
|
|
39
66
|
</div>
|
|
40
67
|
</div>
|
|
41
68
|
);
|
|
@@ -1,14 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import WeatherPageHeaderToday from "./Today";
|
|
2
4
|
import WeatherPageHeaderForecast from "./Forecast";
|
|
3
5
|
import WeatherPageHeaderAddress from "./Address";
|
|
4
6
|
import PageHeaderUtilityItem from "../../../page-frame/desktop/components/header/util/Item";
|
|
7
|
+
import type { WeatherPageHeaderContainerProps } from "../../types";
|
|
8
|
+
import { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
|
|
9
|
+
import { isKoreaWeatherLocale } from "../../utils";
|
|
10
|
+
|
|
11
|
+
export default function WeatherPageHeaderContainer({
|
|
12
|
+
locale,
|
|
13
|
+
texts,
|
|
14
|
+
}: 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
|
+
});
|
|
5
24
|
|
|
6
|
-
export default function WeatherPageHeaderContainer() {
|
|
7
25
|
return (
|
|
8
|
-
<PageHeaderUtilityItem>
|
|
9
|
-
<WeatherPageHeaderAddress />
|
|
10
|
-
<WeatherPageHeaderToday
|
|
11
|
-
|
|
26
|
+
<PageHeaderUtilityItem className="weather-page-header">
|
|
27
|
+
<WeatherPageHeaderAddress locale={locale} texts={texts} />
|
|
28
|
+
<WeatherPageHeaderToday
|
|
29
|
+
locale={locale}
|
|
30
|
+
texts={texts}
|
|
31
|
+
koreaWeather={koreaWeather}
|
|
32
|
+
openWeatherMapWeather={openWeatherMapWeather}
|
|
33
|
+
/>
|
|
34
|
+
<WeatherPageHeaderForecast
|
|
35
|
+
locale={locale}
|
|
36
|
+
texts={texts}
|
|
37
|
+
koreaWeather={koreaWeather}
|
|
38
|
+
openWeatherMapWeather={openWeatherMapWeather}
|
|
39
|
+
/>
|
|
12
40
|
</PageHeaderUtilityItem>
|
|
13
41
|
);
|
|
14
42
|
}
|
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useMemo } from "react";
|
|
3
4
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
4
|
-
import { useWeatherKorea } from "../../hooks";
|
|
5
|
+
import { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
|
|
6
|
+
import type { WeatherPageHeaderForecastProps } from "../../types";
|
|
7
|
+
import {
|
|
8
|
+
getOpenWeatherMapNextDays,
|
|
9
|
+
isKoreaWeatherLocale,
|
|
10
|
+
resolveWeatherPageHeaderTexts,
|
|
11
|
+
} from "../../utils";
|
|
5
12
|
import WeatherPageHeaderNextDays from "./NextDays";
|
|
6
13
|
|
|
7
|
-
export default function WeatherPageHeaderForecast(
|
|
8
|
-
|
|
14
|
+
export default function WeatherPageHeaderForecast({
|
|
15
|
+
locale,
|
|
16
|
+
texts: textOverrides,
|
|
17
|
+
koreaWeather,
|
|
18
|
+
openWeatherMapWeather,
|
|
19
|
+
}: WeatherPageHeaderForecastProps = {}) {
|
|
20
|
+
const isKoreaProvider = isKoreaWeatherLocale(locale);
|
|
21
|
+
const koreaWeatherFallback = useWeatherKorea({
|
|
22
|
+
enabled: !koreaWeather && isKoreaProvider,
|
|
23
|
+
});
|
|
24
|
+
const openWeatherMapWeatherFallback = useOpenWeatherMap({
|
|
25
|
+
locale,
|
|
26
|
+
enabledNow: false,
|
|
27
|
+
enabledForecast: !openWeatherMapWeather && !isKoreaProvider,
|
|
28
|
+
});
|
|
29
|
+
const korea = koreaWeather ?? koreaWeatherFallback;
|
|
30
|
+
const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
|
|
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;
|
|
9
42
|
|
|
10
|
-
if (
|
|
43
|
+
if (isFetching) {
|
|
11
44
|
return (
|
|
12
45
|
<div className="weather-next-days-container">
|
|
13
46
|
<Alternate.LoadingDefault direction="horizontal">
|
|
14
|
-
|
|
47
|
+
{texts.forecastWeatherLoading}
|
|
15
48
|
</Alternate.LoadingDefault>
|
|
16
49
|
</div>
|
|
17
50
|
);
|
|
@@ -19,8 +52,16 @@ export default function WeatherPageHeaderForecast() {
|
|
|
19
52
|
|
|
20
53
|
return (
|
|
21
54
|
<>
|
|
22
|
-
<WeatherPageHeaderNextDays
|
|
23
|
-
|
|
55
|
+
<WeatherPageHeaderNextDays
|
|
56
|
+
title={texts.tomorrowLabel}
|
|
57
|
+
data={forecastDays?.day_1}
|
|
58
|
+
texts={textOverrides}
|
|
59
|
+
/>
|
|
60
|
+
<WeatherPageHeaderNextDays
|
|
61
|
+
title={texts.dayAfterTomorrowLabel}
|
|
62
|
+
data={forecastDays?.day_2}
|
|
63
|
+
texts={textOverrides}
|
|
64
|
+
/>
|
|
24
65
|
</>
|
|
25
66
|
);
|
|
26
67
|
}
|
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lengthFormat } from "@uniai-fe/util-functions";
|
|
2
|
+
|
|
3
|
+
import type { WeatherPageHeaderNextDaysProps } from "../../types";
|
|
4
|
+
import { resolveWeatherPageHeaderTexts } from "../../utils";
|
|
2
5
|
import WeatherIcon from "../icon/Weather";
|
|
3
6
|
|
|
4
7
|
export default function WeatherPageHeaderNextDays({
|
|
5
8
|
title,
|
|
6
9
|
data,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
texts: textOverrides,
|
|
11
|
+
}: WeatherPageHeaderNextDaysProps) {
|
|
12
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
13
|
+
|
|
11
14
|
return (
|
|
12
15
|
<div className="weather-next-days-container">
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</
|
|
16
|
+
<WeatherIcon
|
|
17
|
+
code={data?.condition}
|
|
18
|
+
name={data?.conditionName}
|
|
19
|
+
alt={texts.weatherIconAlt}
|
|
20
|
+
/>
|
|
21
|
+
<p className="weather-forecast-text">
|
|
22
|
+
<span className="weather-label">{title}</span>
|
|
23
|
+
<span className="weather-value">
|
|
24
|
+
{lengthFormat(data?.min_temperature, 0)}
|
|
25
|
+
</span>
|
|
26
|
+
<span className="weather-unit">℃</span>
|
|
27
|
+
<span className="weather-range">~</span>
|
|
28
|
+
<span className="weather-value">
|
|
29
|
+
{lengthFormat(data?.max_temperature, 0)}
|
|
30
|
+
</span>
|
|
31
|
+
<span className="weather-unit">℃</span>
|
|
32
|
+
</p>
|
|
30
33
|
</div>
|
|
31
34
|
);
|
|
32
35
|
}
|
|
@@ -6,127 +6,170 @@ import { lengthFormat } from "@uniai-fe/util-functions";
|
|
|
6
6
|
import { Alternate } from "@uniai-fe/uds-primitives";
|
|
7
7
|
import WeatherIcon from "../icon/Weather";
|
|
8
8
|
import WeatherPageHeaderAlert from "./Alert";
|
|
9
|
+
import type { WeatherPageHeaderTodayProps } from "../../types";
|
|
9
10
|
import { useOpenWeatherMap, useWeatherKorea } from "../../hooks";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import {
|
|
12
|
+
getOpenWeatherMapConditionCode,
|
|
13
|
+
isKoreaWeatherLocale,
|
|
14
|
+
resolveWeatherPageHeaderTexts,
|
|
15
|
+
} from "../../utils";
|
|
16
|
+
|
|
17
|
+
export default function WeatherPageHeaderToday({
|
|
18
|
+
locale,
|
|
19
|
+
texts: textOverrides,
|
|
20
|
+
koreaWeather,
|
|
21
|
+
openWeatherMapWeather,
|
|
22
|
+
}: WeatherPageHeaderTodayProps = {}) {
|
|
23
|
+
const isKoreaProvider = isKoreaWeatherLocale(locale);
|
|
24
|
+
const koreaWeatherFallback = useWeatherKorea({
|
|
25
|
+
enabled: !koreaWeather && isKoreaProvider,
|
|
26
|
+
});
|
|
27
|
+
const openWeatherMapWeatherFallback = useOpenWeatherMap({
|
|
28
|
+
locale,
|
|
29
|
+
enabledNow: !openWeatherMapWeather && !isKoreaProvider,
|
|
30
|
+
enabledForecast: false,
|
|
31
|
+
});
|
|
32
|
+
const korea = koreaWeather ?? koreaWeatherFallback;
|
|
33
|
+
const openWeatherMap = openWeatherMapWeather ?? openWeatherMapWeatherFallback;
|
|
34
|
+
const texts = resolveWeatherPageHeaderTexts(textOverrides);
|
|
14
35
|
|
|
15
36
|
const isFetchingAlternate = useMemo(() => {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
const kma_forecast = forecast?.today?.condition;
|
|
19
|
-
if (
|
|
20
|
-
!kma_now ||
|
|
21
|
-
kma_now.endsWith("null") ||
|
|
22
|
-
!kma_forecast ||
|
|
23
|
-
kma_forecast.endsWith("null")
|
|
24
|
-
) {
|
|
25
|
-
return isFetchingAlt;
|
|
26
|
-
}
|
|
37
|
+
if (!isKoreaProvider) return openWeatherMap.isFetchingNow;
|
|
38
|
+
if (korea.isFetchingNow) return true;
|
|
27
39
|
return false;
|
|
28
|
-
}, [
|
|
29
|
-
forecast?.today?.condition,
|
|
30
|
-
isFetchingAlt,
|
|
31
|
-
isFetchingNow,
|
|
32
|
-
now?.today?.condition,
|
|
33
|
-
]);
|
|
40
|
+
}, [isKoreaProvider, korea.isFetchingNow, openWeatherMap.isFetchingNow]);
|
|
34
41
|
|
|
35
42
|
const iconCode = useMemo(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const alt_now = nowAlt?.weather?.[0]?.icon;
|
|
43
|
-
switch (alt_now) {
|
|
44
|
-
case "01d":
|
|
45
|
-
return "sky-1";
|
|
46
|
-
case "02d":
|
|
47
|
-
case "03d":
|
|
48
|
-
case "04d":
|
|
49
|
-
return "sky-2";
|
|
50
|
-
case "09d":
|
|
51
|
-
return "drop-rain-3";
|
|
52
|
-
case "10d":
|
|
53
|
-
return "drop-rain-1";
|
|
54
|
-
case "11d":
|
|
55
|
-
return "drop-rain-thunder";
|
|
56
|
-
case "13d":
|
|
57
|
-
return "drop-snow";
|
|
58
|
-
case "50d":
|
|
59
|
-
return "sky-4";
|
|
60
|
-
default:
|
|
61
|
-
return "sky-1";
|
|
43
|
+
if (isKoreaProvider) {
|
|
44
|
+
const kma_now = korea.now?.today?.condition;
|
|
45
|
+
if (kma_now && !kma_now.endsWith("null")) return kma_now;
|
|
46
|
+
|
|
47
|
+
const kma_forecast = korea.forecast?.today?.condition;
|
|
48
|
+
if (kma_forecast && !kma_forecast.endsWith("null")) return kma_forecast;
|
|
62
49
|
}
|
|
63
|
-
}, [forecast?.today?.condition, now?.today?.condition, nowAlt?.weather]);
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
return getOpenWeatherMapConditionCode(
|
|
52
|
+
openWeatherMap.now?.weather?.[0]?.icon,
|
|
53
|
+
);
|
|
54
|
+
}, [
|
|
55
|
+
isKoreaProvider,
|
|
56
|
+
korea.forecast?.today?.condition,
|
|
57
|
+
korea.now?.today?.condition,
|
|
58
|
+
openWeatherMap.now?.weather,
|
|
59
|
+
]);
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
if (
|
|
61
|
+
const weatherStateName = useMemo(() => {
|
|
62
|
+
if (isKoreaProvider) {
|
|
63
|
+
const kma_now = korea.now?.today?.conditionName;
|
|
64
|
+
if (kma_now && !kma_now.endsWith("null"))
|
|
65
|
+
return kma_now || texts.conditionFallbackLabel;
|
|
66
|
+
|
|
67
|
+
const kma_forecast = korea.forecast?.today?.conditionName;
|
|
68
|
+
if (kma_forecast && !kma_forecast.endsWith("null"))
|
|
69
|
+
return kma_forecast || texts.conditionFallbackLabel;
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
return
|
|
72
|
+
return (
|
|
73
|
+
openWeatherMap.now?.weather?.[0]?.description ||
|
|
74
|
+
texts.conditionFallbackLabel
|
|
75
|
+
);
|
|
73
76
|
}, [
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
isKoreaProvider,
|
|
78
|
+
korea.forecast?.today?.conditionName,
|
|
79
|
+
korea.now?.today?.conditionName,
|
|
80
|
+
openWeatherMap.now?.weather,
|
|
81
|
+
texts.conditionFallbackLabel,
|
|
77
82
|
]);
|
|
78
83
|
|
|
79
84
|
const temperature = useMemo(() => {
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
if (isKoreaProvider) {
|
|
86
|
+
const kma_now = korea.now?.today?.temperature;
|
|
87
|
+
if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
const kma_forecast = korea.forecast?.today?.temperature;
|
|
90
|
+
if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
|
|
91
|
+
return Number(kma_forecast);
|
|
92
|
+
}
|
|
86
93
|
|
|
87
|
-
const alt_now =
|
|
94
|
+
const alt_now = openWeatherMap.now?.main?.temp;
|
|
88
95
|
return typeof alt_now === "number" ? Math.round(alt_now) : null;
|
|
89
96
|
}, [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
isKoreaProvider,
|
|
98
|
+
korea.forecast?.today?.temperature,
|
|
99
|
+
korea.now?.today?.temperature,
|
|
100
|
+
openWeatherMap.now?.main?.temp,
|
|
93
101
|
]);
|
|
94
102
|
|
|
95
103
|
const humidity = useMemo(() => {
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
if (isKoreaProvider) {
|
|
105
|
+
const kma_now = korea.now?.today?.humidity;
|
|
106
|
+
if (kma_now && !Number.isNaN(Number(kma_now))) return Number(kma_now);
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
const kma_forecast = korea.forecast?.today?.humidity;
|
|
109
|
+
if (kma_forecast && !Number.isNaN(Number(kma_forecast)))
|
|
110
|
+
return Number(kma_forecast);
|
|
111
|
+
}
|
|
102
112
|
|
|
103
|
-
const alt_now =
|
|
113
|
+
const alt_now = openWeatherMap.now?.main?.humidity;
|
|
104
114
|
return typeof alt_now === "number" ? Math.round(alt_now) : null;
|
|
105
|
-
}, [
|
|
115
|
+
}, [
|
|
116
|
+
isKoreaProvider,
|
|
117
|
+
korea.forecast?.today?.humidity,
|
|
118
|
+
korea.now?.today?.humidity,
|
|
119
|
+
openWeatherMap.now?.main?.humidity,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const maxTemperature = null;
|
|
123
|
+
const minTemperature = null;
|
|
124
|
+
const alert = (
|
|
125
|
+
<WeatherPageHeaderAlert locale={locale} texts={textOverrides} />
|
|
126
|
+
);
|
|
106
127
|
|
|
107
128
|
return (
|
|
108
129
|
<div className="weather-today-container">
|
|
109
130
|
{isFetchingAlternate ? (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
<>
|
|
132
|
+
{alert}
|
|
133
|
+
<Alternate.LoadingDefault direction="horizontal">
|
|
134
|
+
{texts.currentWeatherLoading}
|
|
135
|
+
</Alternate.LoadingDefault>
|
|
136
|
+
</>
|
|
113
137
|
) : (
|
|
114
138
|
<>
|
|
115
|
-
<WeatherIcon
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
<WeatherIcon
|
|
140
|
+
code={iconCode}
|
|
141
|
+
name={weatherStateName}
|
|
142
|
+
alt={texts.weatherIconAlt}
|
|
143
|
+
/>
|
|
144
|
+
<div className="weather-today-text">
|
|
145
|
+
{alert}
|
|
146
|
+
<p className="weather-temperature-text">
|
|
147
|
+
<span className="weather-label">{texts.currentLabel}</span>
|
|
148
|
+
<span className="weather-value">
|
|
149
|
+
{lengthFormat(temperature, 0)}
|
|
150
|
+
</span>
|
|
151
|
+
<span className="weather-unit">℃</span>
|
|
152
|
+
</p>
|
|
153
|
+
<p className="weather-temperature-text">
|
|
154
|
+
<span className="weather-label">{texts.maxTemperatureLabel}</span>
|
|
155
|
+
<span className="weather-value">
|
|
156
|
+
{lengthFormat(maxTemperature, 0)}
|
|
157
|
+
</span>
|
|
158
|
+
<span className="weather-unit">℃</span>
|
|
159
|
+
</p>
|
|
160
|
+
<p className="weather-temperature-text">
|
|
161
|
+
<span className="weather-label">{texts.minTemperatureLabel}</span>
|
|
162
|
+
<span className="weather-value">
|
|
163
|
+
{lengthFormat(minTemperature, 0)}
|
|
164
|
+
</span>
|
|
165
|
+
<span className="weather-unit">℃</span>
|
|
166
|
+
</p>
|
|
167
|
+
<p className="weather-humidity-text">
|
|
168
|
+
<span className="weather-label">{texts.humidityLabel}</span>
|
|
169
|
+
<span className="weather-value">
|
|
170
|
+
{lengthFormat(humidity, 0)}%
|
|
171
|
+
</span>
|
|
172
|
+
</p>
|
|
130
173
|
</div>
|
|
131
174
|
</>
|
|
132
175
|
)}
|
|
@@ -6,14 +6,33 @@ import {
|
|
|
6
6
|
useQueryWeatherOpenWeatherMapForecast,
|
|
7
7
|
useQueryWeatherOpenWeatherMapNow,
|
|
8
8
|
} from "../apis/open-weather-map/client";
|
|
9
|
+
import type {
|
|
10
|
+
WeatherApiLocaleOptions,
|
|
11
|
+
WeatherOpenWeatherMapParams,
|
|
12
|
+
} from "../types";
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
type UseOpenWeatherMapOptions = WeatherApiLocaleOptions & {
|
|
15
|
+
enabledNow?: boolean;
|
|
16
|
+
enabledForecast?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function useOpenWeatherMap({
|
|
20
|
+
locale,
|
|
21
|
+
enabledNow = true,
|
|
22
|
+
enabledForecast = true,
|
|
23
|
+
}: UseOpenWeatherMapOptions = {}) {
|
|
11
24
|
const coordinate = useAtomValue(weatherCoordinate);
|
|
25
|
+
const params: WeatherOpenWeatherMapParams = {
|
|
26
|
+
...coordinate,
|
|
27
|
+
...(locale ? { locale } : {}),
|
|
28
|
+
};
|
|
12
29
|
|
|
13
30
|
const { data: now, isFetching: isFetchingNow } =
|
|
14
|
-
useQueryWeatherOpenWeatherMapNow(
|
|
31
|
+
useQueryWeatherOpenWeatherMapNow(params, { enabled: enabledNow });
|
|
15
32
|
const { data: forecast, isFetching: isFetchingForecast } =
|
|
16
|
-
useQueryWeatherOpenWeatherMapForecast(
|
|
33
|
+
useQueryWeatherOpenWeatherMapForecast(params, {
|
|
34
|
+
enabled: enabledForecast,
|
|
35
|
+
});
|
|
17
36
|
|
|
18
37
|
return {
|
|
19
38
|
coordinate,
|
|
@@ -11,7 +11,13 @@ import { weatherCoordinate } from "../jotai/coordinate";
|
|
|
11
11
|
import { getWeatherBaseMoments } from "../utils/date-time";
|
|
12
12
|
import { getWeatherGridLocation } from "../utils/location";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
type UseWeatherKoreaOptions = {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function useWeatherKorea({
|
|
19
|
+
enabled = true,
|
|
20
|
+
}: UseWeatherKoreaOptions = {}) {
|
|
15
21
|
const coordinate = useAtomValue(weatherCoordinate);
|
|
16
22
|
const [params, setParams] = useState<{
|
|
17
23
|
[categoryKey: string]: API_Req_WeatherKorea;
|
|
@@ -21,7 +27,8 @@ export default function useWeatherKorea() {
|
|
|
21
27
|
});
|
|
22
28
|
|
|
23
29
|
useEffect(() => {
|
|
24
|
-
if (typeof window === "undefined") return;
|
|
30
|
+
if (typeof window === "undefined" || !enabled) return;
|
|
31
|
+
let isActive = true;
|
|
25
32
|
|
|
26
33
|
const hasInjectedCoordinate =
|
|
27
34
|
typeof coordinate.address === "string" ||
|
|
@@ -38,6 +45,7 @@ export default function useWeatherKorea() {
|
|
|
38
45
|
|
|
39
46
|
async function updater() {
|
|
40
47
|
const grid = await getWeatherGridLocation(coordinate);
|
|
48
|
+
if (!isActive) return;
|
|
41
49
|
setParams({
|
|
42
50
|
now: {
|
|
43
51
|
...grid,
|
|
@@ -51,14 +59,18 @@ export default function useWeatherKorea() {
|
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
updater();
|
|
54
|
-
|
|
62
|
+
return () => {
|
|
63
|
+
isActive = false;
|
|
64
|
+
};
|
|
65
|
+
}, [coordinate, enabled]);
|
|
55
66
|
|
|
56
67
|
const { data: now, isFetching: isFetchingNow } = useQueryWeatherKoreaNow(
|
|
57
68
|
params.now,
|
|
69
|
+
{ enabled },
|
|
58
70
|
);
|
|
59
71
|
|
|
60
72
|
const { data: forecast, isFetching: isFetchingForecast } =
|
|
61
|
-
useQueryWeatherKoreaForecast(params.forecast);
|
|
73
|
+
useQueryWeatherKoreaForecast(params.forecast, { enabled });
|
|
62
74
|
|
|
63
75
|
return {
|
|
64
76
|
params,
|