@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
package/README.md
CHANGED
|
@@ -60,6 +60,12 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
60
60
|
- `weatherCoordinate`
|
|
61
61
|
- `useWeatherKorea`
|
|
62
62
|
- `useOpenWeatherMap`
|
|
63
|
+
- `resolveWeatherProvider`
|
|
64
|
+
- `WEATHER_PROVIDER_CAPABILITIES`
|
|
65
|
+
- `WeatherPageHeaderContainerProps`
|
|
66
|
+
- `WeatherPageHeaderTexts`
|
|
67
|
+
- `WeatherProviderKey`
|
|
68
|
+
- `WeatherProviderCapability`
|
|
63
69
|
- `/service-inquiry`
|
|
64
70
|
- `ServiceInquiry.Form`
|
|
65
71
|
- `ServiceInquiry.OpenButton`
|
|
@@ -130,6 +136,7 @@ Next.js 서비스에서 primitives와 동일한 방식으로 **Raw TypeScript**
|
|
|
130
136
|
- 로그인 후 `farm_name`, `contact`를 auto-fill + readonly로 보여야 할 때는 `formContextOptions.defaultValues`와 `farmNameField.mode`, `contactField.mode`를 함께 전달한다.
|
|
131
137
|
- `/weather/**`
|
|
132
138
|
- page-frame header utility에 결합되는 weather header 템플릿, page-header 조각 export, weather data hook/API 도구를 제공한다.
|
|
139
|
+
- page-header는 `locale?: string`을 받으며 기본 지원값은 `ko`, `en`, `ja`다. 기본 문구 외 특수 문구는 `texts` prop으로 주입한다.
|
|
133
140
|
- `/cctv/**`
|
|
134
141
|
- finder/viewer/video/pagination 조합과 rtc/company-list API helper를 제공한다.
|
|
135
142
|
- `/page-frame/**`
|
package/dist/styles.css
CHANGED
|
@@ -67,6 +67,30 @@
|
|
|
67
67
|
var(--spacing-padding-9, 32px) + env(safe-area-inset-top, 0px)
|
|
68
68
|
);
|
|
69
69
|
--auth-container-padding-bottom: var(--spacing-padding-10, 40px);
|
|
70
|
+
--weather-page-header-height: 2.4rem;
|
|
71
|
+
--weather-page-header-gap: var(--spacing-gap-6);
|
|
72
|
+
--weather-page-header-background: var(--color-surface-static-white);
|
|
73
|
+
--weather-icon-width: 2.5rem;
|
|
74
|
+
--weather-icon-height: 2.4rem;
|
|
75
|
+
--weather-address-icon-size: 1.6rem;
|
|
76
|
+
--weather-item-gap: var(--spacing-gap-4);
|
|
77
|
+
--weather-address-gap: var(--spacing-gap-2);
|
|
78
|
+
--weather-body-font-size: var(--font-body-xxsmall-size);
|
|
79
|
+
--weather-body-line-height: var(--font-body-xxsmall-line-height);
|
|
80
|
+
--weather-body-letter-spacing: var(--font-body-xxsmall-letter-spacing);
|
|
81
|
+
--weather-label-font-weight: 500;
|
|
82
|
+
--weather-label-value-gap: var(--spacing-gap-2);
|
|
83
|
+
--weather-value-font-weight: 600;
|
|
84
|
+
--weather-unit-font-size: 0.8385rem;
|
|
85
|
+
--weather-text-color: var(--color-label-standard);
|
|
86
|
+
--weather-label-color: var(--color-label-neutral);
|
|
87
|
+
--weather-alert-text-color: var(--color-primary-standard);
|
|
88
|
+
--weather-alert-background: var(--color-surface-static-blue);
|
|
89
|
+
--weather-alert-height: 2rem;
|
|
90
|
+
--weather-alert-padding-horizontal: var(--spacing-padding-3);
|
|
91
|
+
--weather-alert-radius: 0.6rem;
|
|
92
|
+
--weather-alert-font-size: var(--font-caption-medium-size);
|
|
93
|
+
--weather-alert-font-weight: 400;
|
|
70
94
|
/* Card layout */
|
|
71
95
|
--cctv-video-radius: 12px;
|
|
72
96
|
--cctv-list-gap: var(--spacing-gap-5);
|
|
@@ -1471,129 +1495,165 @@
|
|
|
1471
1495
|
letter-spacing: 0px;
|
|
1472
1496
|
}
|
|
1473
1497
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
.weather-page-header {
|
|
1476
1501
|
display: flex;
|
|
1477
1502
|
align-items: center;
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1503
|
+
gap: var(--weather-page-header-gap);
|
|
1504
|
+
width: fit-content;
|
|
1505
|
+
height: var(--weather-page-header-height);
|
|
1506
|
+
background: var(--weather-page-header-background);
|
|
1507
|
+
white-space: nowrap;
|
|
1481
1508
|
}
|
|
1482
1509
|
|
|
1483
1510
|
.weather-base-icon {
|
|
1484
|
-
width: 2.5rem;
|
|
1485
|
-
height: 2.4rem;
|
|
1486
1511
|
position: relative;
|
|
1512
|
+
width: var(--weather-icon-width);
|
|
1513
|
+
height: var(--weather-icon-height);
|
|
1514
|
+
flex: 0 0 auto;
|
|
1487
1515
|
}
|
|
1488
1516
|
|
|
1489
|
-
.weather-
|
|
1517
|
+
.weather-address {
|
|
1490
1518
|
display: flex;
|
|
1491
1519
|
align-items: center;
|
|
1520
|
+
gap: var(--weather-address-gap);
|
|
1521
|
+
width: fit-content;
|
|
1492
1522
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
.weather-base-text-info dt span, .weather-next-days-text dt span {
|
|
1498
|
-
color: var(--color-cool-gray-35);
|
|
1499
|
-
}
|
|
1500
|
-
.weather-base-text-info dd, .weather-next-days-text dd {
|
|
1501
|
-
font-size: 0;
|
|
1523
|
+
|
|
1524
|
+
.weather-address-icon {
|
|
1525
|
+
position: relative;
|
|
1502
1526
|
display: flex;
|
|
1503
|
-
align-items:
|
|
1527
|
+
align-items: center;
|
|
1528
|
+
justify-content: center;
|
|
1529
|
+
width: var(--weather-address-icon-size);
|
|
1530
|
+
height: var(--weather-address-icon-size);
|
|
1531
|
+
flex: 0 0 auto;
|
|
1504
1532
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1533
|
+
|
|
1534
|
+
.weather-address-text {
|
|
1535
|
+
display: flex;
|
|
1536
|
+
align-items: center;
|
|
1537
|
+
gap: var(--weather-address-gap);
|
|
1538
|
+
margin: 0;
|
|
1539
|
+
color: var(--weather-text-color);
|
|
1540
|
+
font-size: var(--weather-body-font-size);
|
|
1541
|
+
font-weight: var(--weather-label-font-weight);
|
|
1542
|
+
line-height: var(--weather-body-line-height);
|
|
1543
|
+
letter-spacing: var(--weather-body-letter-spacing);
|
|
1508
1544
|
}
|
|
1509
1545
|
|
|
1510
|
-
.weather-
|
|
1511
|
-
|
|
1512
|
-
height: 1.3rem;
|
|
1513
|
-
margin: 0 0.8rem;
|
|
1514
|
-
background: var(--color-cool-gray-85);
|
|
1546
|
+
.weather-address-date {
|
|
1547
|
+
color: var(--weather-label-color);
|
|
1515
1548
|
}
|
|
1516
1549
|
|
|
1517
|
-
.weather-
|
|
1550
|
+
.weather-today-container {
|
|
1518
1551
|
display: flex;
|
|
1519
1552
|
align-items: center;
|
|
1553
|
+
gap: var(--weather-item-gap);
|
|
1520
1554
|
width: fit-content;
|
|
1521
1555
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
}
|
|
1528
|
-
.weather-address-text {
|
|
1556
|
+
|
|
1557
|
+
.weather-today-text {
|
|
1558
|
+
display: flex;
|
|
1559
|
+
align-items: center;
|
|
1560
|
+
gap: var(--weather-item-gap);
|
|
1529
1561
|
width: fit-content;
|
|
1530
|
-
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
.weather-next-days-container {
|
|
1531
1565
|
display: flex;
|
|
1532
1566
|
align-items: center;
|
|
1567
|
+
gap: var(--weather-item-gap);
|
|
1568
|
+
width: fit-content;
|
|
1533
1569
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1570
|
+
|
|
1571
|
+
.weather-temperature-text {
|
|
1572
|
+
margin: 0;
|
|
1573
|
+
color: var(--weather-text-color);
|
|
1574
|
+
font-size: 0;
|
|
1575
|
+
font-weight: var(--weather-label-font-weight);
|
|
1576
|
+
line-height: var(--weather-body-line-height);
|
|
1577
|
+
letter-spacing: var(--weather-body-letter-spacing);
|
|
1540
1578
|
}
|
|
1541
1579
|
|
|
1542
|
-
.weather-
|
|
1543
|
-
|
|
1580
|
+
.weather-humidity-text {
|
|
1581
|
+
margin: 0;
|
|
1582
|
+
color: var(--weather-text-color);
|
|
1583
|
+
font-size: 0;
|
|
1584
|
+
font-weight: var(--weather-label-font-weight);
|
|
1585
|
+
line-height: var(--weather-body-line-height);
|
|
1586
|
+
letter-spacing: var(--weather-body-letter-spacing);
|
|
1544
1587
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1588
|
+
|
|
1589
|
+
.weather-forecast-text {
|
|
1590
|
+
margin: 0;
|
|
1591
|
+
color: var(--weather-text-color);
|
|
1592
|
+
font-size: 0;
|
|
1593
|
+
font-weight: var(--weather-label-font-weight);
|
|
1594
|
+
line-height: var(--weather-body-line-height);
|
|
1595
|
+
letter-spacing: var(--weather-body-letter-spacing);
|
|
1548
1596
|
}
|
|
1549
1597
|
|
|
1550
|
-
.weather-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1598
|
+
.weather-label {
|
|
1599
|
+
color: var(--weather-label-color);
|
|
1600
|
+
font-size: var(--weather-body-font-size);
|
|
1601
|
+
font-weight: var(--weather-label-font-weight);
|
|
1602
|
+
margin-right: var(--weather-label-value-gap);
|
|
1554
1603
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1604
|
+
|
|
1605
|
+
.weather-value {
|
|
1606
|
+
color: var(--weather-text-color);
|
|
1607
|
+
font-size: var(--weather-body-font-size);
|
|
1608
|
+
font-weight: var(--weather-value-font-weight);
|
|
1559
1609
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1610
|
+
|
|
1611
|
+
.weather-unit {
|
|
1612
|
+
color: var(--weather-text-color);
|
|
1613
|
+
font-size: var(--weather-unit-font-size);
|
|
1614
|
+
font-weight: var(--weather-value-font-weight);
|
|
1562
1615
|
}
|
|
1563
1616
|
|
|
1564
|
-
.weather-
|
|
1565
|
-
|
|
1617
|
+
.weather-range {
|
|
1618
|
+
color: var(--weather-text-color);
|
|
1619
|
+
font-size: var(--weather-body-font-size);
|
|
1620
|
+
font-weight: var(--weather-value-font-weight);
|
|
1566
1621
|
}
|
|
1567
1622
|
|
|
1568
1623
|
.weather-alert {
|
|
1624
|
+
display: flex;
|
|
1625
|
+
align-items: center;
|
|
1626
|
+
justify-content: center;
|
|
1569
1627
|
width: fit-content;
|
|
1570
|
-
height:
|
|
1571
|
-
padding: 0
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1628
|
+
height: var(--weather-alert-height);
|
|
1629
|
+
padding: 0 var(--weather-alert-padding-horizontal);
|
|
1630
|
+
border-radius: var(--weather-alert-radius);
|
|
1631
|
+
background-color: var(--weather-alert-background);
|
|
1632
|
+
box-sizing: border-box;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.weather-alert-loading {
|
|
1575
1636
|
display: flex;
|
|
1576
1637
|
align-items: center;
|
|
1638
|
+
width: fit-content;
|
|
1639
|
+
height: var(--weather-page-header-height);
|
|
1577
1640
|
}
|
|
1641
|
+
|
|
1578
1642
|
.weather-alert-text {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
font-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
line-height: 1.6;
|
|
1643
|
+
display: flex;
|
|
1644
|
+
align-items: center;
|
|
1645
|
+
color: var(--weather-alert-text-color);
|
|
1646
|
+
font-size: var(--weather-alert-font-size);
|
|
1647
|
+
font-weight: var(--weather-alert-font-weight);
|
|
1648
|
+
line-height: var(--weather-body-line-height);
|
|
1649
|
+
letter-spacing: var(--weather-body-letter-spacing);
|
|
1587
1650
|
}
|
|
1588
1651
|
|
|
1589
|
-
.weather-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
}
|
|
1595
|
-
.weather-next-days-text dd:nth-of-type(n + 2) .unit {
|
|
1596
|
-
font-size: 1rem;
|
|
1652
|
+
.weather-alert-text span {
|
|
1653
|
+
color: inherit;
|
|
1654
|
+
font-size: inherit;
|
|
1655
|
+
font-weight: inherit;
|
|
1656
|
+
line-height: inherit;
|
|
1597
1657
|
}
|
|
1598
1658
|
|
|
1599
1659
|
|
package/package.json
CHANGED
|
@@ -48,7 +48,16 @@ export default function CCTVPaginationListItem({
|
|
|
48
48
|
>
|
|
49
49
|
<CCTVVideoTemplate
|
|
50
50
|
ref={videoRef}
|
|
51
|
+
cam={cam}
|
|
51
52
|
className="cctv-pagination-list-video-container"
|
|
53
|
+
headerOptions={{
|
|
54
|
+
activeLiveState: true,
|
|
55
|
+
activeTitle: false,
|
|
56
|
+
isLive,
|
|
57
|
+
isShared:
|
|
58
|
+
typeof cam.cam_shared === "boolean" ? cam.cam_shared : true,
|
|
59
|
+
title: cam.cam_name,
|
|
60
|
+
}}
|
|
52
61
|
footerOptions={{ cam }}
|
|
53
62
|
{...{ isError, overlayMessage, isLive }}
|
|
54
63
|
/>
|
|
@@ -11,6 +11,22 @@ import type {
|
|
|
11
11
|
} from "../../types";
|
|
12
12
|
import { isValidGridCoordinate } from "../../utils/validate";
|
|
13
13
|
|
|
14
|
+
type WeatherQueryOptions = {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getWeatherKoreaParams = ({
|
|
19
|
+
nx,
|
|
20
|
+
ny,
|
|
21
|
+
base_date,
|
|
22
|
+
base_time,
|
|
23
|
+
}: API_Req_WeatherKorea): API_Req_WeatherKorea => ({
|
|
24
|
+
nx,
|
|
25
|
+
ny,
|
|
26
|
+
...(base_date ? { base_date } : {}),
|
|
27
|
+
...(base_time ? { base_time } : {}),
|
|
28
|
+
});
|
|
29
|
+
|
|
14
30
|
/**
|
|
15
31
|
* 기상청 API; 현재날씨 fetch
|
|
16
32
|
* @method GET
|
|
@@ -54,15 +70,22 @@ export const getWeatherKoreaAlert = async (params: API_Req_WeatherKoreaAlert) =>
|
|
|
54
70
|
*/
|
|
55
71
|
export const useQueryWeatherKoreaNow = (
|
|
56
72
|
params: API_Req_WeatherKorea,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
options: WeatherQueryOptions = {},
|
|
74
|
+
): UseQueryResult<API_Res_WeatherKoreaNow> => {
|
|
75
|
+
const { nx, ny, base_date, base_time } = params;
|
|
76
|
+
|
|
77
|
+
return useQuery({
|
|
78
|
+
queryKey: ["weather_korea_now", nx, ny, base_date, base_time],
|
|
79
|
+
queryFn: () =>
|
|
80
|
+
getWeatherKoreaNow(
|
|
81
|
+
getWeatherKoreaParams({ nx, ny, base_date, base_time }),
|
|
82
|
+
),
|
|
83
|
+
enabled: (options.enabled ?? true) && isValidGridCoordinate({ nx, ny }),
|
|
62
84
|
staleTime: 10 * 60 * 1000, // 10분
|
|
63
85
|
refetchInterval: 5 * 60 * 1000, // 5분
|
|
64
86
|
refetchOnWindowFocus: true,
|
|
65
87
|
});
|
|
88
|
+
};
|
|
66
89
|
|
|
67
90
|
/**
|
|
68
91
|
* 기상청 API; 내일/모레 예보 react query
|
|
@@ -70,13 +93,20 @@ export const useQueryWeatherKoreaNow = (
|
|
|
70
93
|
*/
|
|
71
94
|
export const useQueryWeatherKoreaForecast = (
|
|
72
95
|
params: API_Req_WeatherKorea,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
options: WeatherQueryOptions = {},
|
|
97
|
+
): UseQueryResult<API_Res_WeatherKoreaForecast> => {
|
|
98
|
+
const { nx, ny, base_date, base_time } = params;
|
|
99
|
+
|
|
100
|
+
return useQuery({
|
|
101
|
+
queryKey: ["weather_korea_forecast", nx, ny, base_date, base_time],
|
|
102
|
+
queryFn: () =>
|
|
103
|
+
getWeatherKoreaForecast(
|
|
104
|
+
getWeatherKoreaParams({ nx, ny, base_date, base_time }),
|
|
105
|
+
),
|
|
106
|
+
enabled: (options.enabled ?? true) && isValidGridCoordinate({ nx, ny }),
|
|
78
107
|
staleTime: 30 * 60 * 1000, // 30분
|
|
79
108
|
});
|
|
109
|
+
};
|
|
80
110
|
|
|
81
111
|
/**
|
|
82
112
|
* 기상청 API; 특보 react query
|
|
@@ -84,10 +114,15 @@ export const useQueryWeatherKoreaForecast = (
|
|
|
84
114
|
*/
|
|
85
115
|
export const useQueryWeatherKoreaAlert = (
|
|
86
116
|
params: API_Req_WeatherKoreaAlert,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
options: WeatherQueryOptions = {},
|
|
118
|
+
): UseQueryResult<API_Res_WeatherKoreaAlert> => {
|
|
119
|
+
const { farm_idx } = params;
|
|
120
|
+
|
|
121
|
+
return useQuery({
|
|
122
|
+
queryKey: ["weather_korea_alert", farm_idx],
|
|
123
|
+
queryFn: () => getWeatherKoreaAlert({ farm_idx }),
|
|
124
|
+
enabled:
|
|
125
|
+
(options.enabled ?? true) && typeof farm_idx === "number" && farm_idx > 0,
|
|
92
126
|
staleTime: 60 * 60 * 1000, // 1시간
|
|
93
127
|
});
|
|
128
|
+
};
|
|
@@ -147,8 +147,8 @@ export const routeWeatherKoreaForecast = async ({
|
|
|
147
147
|
const resDefault: API_Res_WeatherKoreaForecast = {
|
|
148
148
|
raw: API_RES_RAW as KMA_Res_WeatherForecast,
|
|
149
149
|
today: { ...API_RES_BASE, temperature: null, humidity: null },
|
|
150
|
-
day_1: FORECAST_DATA,
|
|
151
|
-
day_2: FORECAST_DATA,
|
|
150
|
+
day_1: { ...FORECAST_DATA },
|
|
151
|
+
day_2: { ...FORECAST_DATA },
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
if (!authKey) {
|
|
@@ -4,11 +4,25 @@ import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
|
|
4
4
|
import type {
|
|
5
5
|
OWM_Res_Weather_Forecast,
|
|
6
6
|
OWM_Res_Weather_Now,
|
|
7
|
-
|
|
7
|
+
WeatherOpenWeatherMapParams,
|
|
8
8
|
} from "../../types";
|
|
9
9
|
import { getQueryString } from "@uniai-fe/util-api";
|
|
10
10
|
import { isValidNumber } from "@uniai-fe/util-functions";
|
|
11
11
|
|
|
12
|
+
type WeatherQueryOptions = {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getOpenWeatherMapParams = ({
|
|
17
|
+
lat,
|
|
18
|
+
lng,
|
|
19
|
+
locale,
|
|
20
|
+
}: WeatherOpenWeatherMapParams): WeatherOpenWeatherMapParams => ({
|
|
21
|
+
lat,
|
|
22
|
+
lng,
|
|
23
|
+
...(locale ? { locale } : {}),
|
|
24
|
+
});
|
|
25
|
+
|
|
12
26
|
/**
|
|
13
27
|
* 글로벌 날씨 API; 현재 날씨 fetch
|
|
14
28
|
* @method GET
|
|
@@ -19,7 +33,7 @@ import { isValidNumber } from "@uniai-fe/util-functions";
|
|
|
19
33
|
* @param {number} params.lon - 경도
|
|
20
34
|
*/
|
|
21
35
|
export const getWeatherOpenWeatherMapNow = async (
|
|
22
|
-
params:
|
|
36
|
+
params: WeatherOpenWeatherMapParams,
|
|
23
37
|
): Promise<OWM_Res_Weather_Now> =>
|
|
24
38
|
await (
|
|
25
39
|
await fetch(`/api/weather/open-weather-map/now${getQueryString(params)}`)
|
|
@@ -35,7 +49,7 @@ export const getWeatherOpenWeatherMapNow = async (
|
|
|
35
49
|
* @param {number} params.lon - 경도
|
|
36
50
|
*/
|
|
37
51
|
export const getWeatherOpenWeatherMapForecast = async (
|
|
38
|
-
params:
|
|
52
|
+
params: WeatherOpenWeatherMapParams,
|
|
39
53
|
): Promise<OWM_Res_Weather_Forecast> =>
|
|
40
54
|
await (
|
|
41
55
|
await fetch(
|
|
@@ -48,27 +62,43 @@ export const getWeatherOpenWeatherMapForecast = async (
|
|
|
48
62
|
* @method GET
|
|
49
63
|
*/
|
|
50
64
|
export const useQueryWeatherOpenWeatherMapNow = (
|
|
51
|
-
params:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
params: WeatherOpenWeatherMapParams,
|
|
66
|
+
options: WeatherQueryOptions = {},
|
|
67
|
+
): UseQueryResult<OWM_Res_Weather_Now> => {
|
|
68
|
+
const { lat, lng, locale } = params;
|
|
69
|
+
|
|
70
|
+
return useQuery({
|
|
71
|
+
queryKey: ["weather_open_weather_map_now", lat, lng, locale],
|
|
72
|
+
queryFn: () =>
|
|
73
|
+
getWeatherOpenWeatherMapNow(
|
|
74
|
+
getOpenWeatherMapParams({ lat, lng, locale }),
|
|
75
|
+
),
|
|
76
|
+
enabled:
|
|
77
|
+
(options.enabled ?? true) && isValidNumber(lat) && isValidNumber(lng),
|
|
57
78
|
staleTime: 10 * 60 * 1000, // 10분
|
|
58
79
|
refetchInterval: 5 * 60 * 1000, // 5분
|
|
59
80
|
refetchOnWindowFocus: true,
|
|
60
81
|
});
|
|
82
|
+
};
|
|
61
83
|
|
|
62
84
|
/**
|
|
63
85
|
* 글로벌 날씨 API; 예보 날씨 (4 days) react query
|
|
64
86
|
* @method GET
|
|
65
87
|
*/
|
|
66
88
|
export const useQueryWeatherOpenWeatherMapForecast = (
|
|
67
|
-
params:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
params: WeatherOpenWeatherMapParams,
|
|
90
|
+
options: WeatherQueryOptions = {},
|
|
91
|
+
): UseQueryResult<OWM_Res_Weather_Forecast> => {
|
|
92
|
+
const { lat, lng, locale } = params;
|
|
93
|
+
|
|
94
|
+
return useQuery({
|
|
95
|
+
queryKey: ["weather_open_weather_map_forecast", lat, lng, locale],
|
|
96
|
+
queryFn: () =>
|
|
97
|
+
getWeatherOpenWeatherMapForecast(
|
|
98
|
+
getOpenWeatherMapParams({ lat, lng, locale }),
|
|
99
|
+
),
|
|
100
|
+
enabled:
|
|
101
|
+
(options.enabled ?? true) && isValidNumber(lat) && isValidNumber(lng),
|
|
73
102
|
staleTime: 30 * 60 * 1000, // 30분
|
|
74
103
|
});
|
|
104
|
+
};
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
OWM_Res_Weather_Forecast,
|
|
6
6
|
OWM_Res_Weather_Now,
|
|
7
7
|
} from "../../types";
|
|
8
|
+
import { getOpenWeatherMapLang } from "../../utils/locale";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 글로벌 날씨 API; 현재 날씨 URL
|
|
@@ -17,7 +18,6 @@ const API_BASE_FORECAST =
|
|
|
17
18
|
"https://pro.openweathermap.org/data/2.5/weather/forecast/hourly";
|
|
18
19
|
|
|
19
20
|
const COMMON_OPTIONS = {
|
|
20
|
-
lang: "kr",
|
|
21
21
|
units: "metric", // 섭씨 온도
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -63,6 +63,7 @@ export const routeOpenWeatherMapNow = async ({
|
|
|
63
63
|
lat,
|
|
64
64
|
lon,
|
|
65
65
|
appid: authKey,
|
|
66
|
+
lang: getOpenWeatherMapLang(searchParams.get("locale") || undefined),
|
|
66
67
|
...COMMON_OPTIONS,
|
|
67
68
|
}),
|
|
68
69
|
});
|
|
@@ -119,13 +120,18 @@ export const routeOpenWeatherMapForecast = async ({
|
|
|
119
120
|
lat,
|
|
120
121
|
lon,
|
|
121
122
|
appid: authKey,
|
|
123
|
+
lang: getOpenWeatherMapLang(searchParams.get("locale") || undefined),
|
|
122
124
|
...COMMON_OPTIONS,
|
|
123
125
|
}),
|
|
124
126
|
});
|
|
125
127
|
|
|
126
128
|
try {
|
|
127
129
|
const res = await (await fetch(url)).json();
|
|
128
|
-
nextAPILog("GET", routeUrl,
|
|
130
|
+
nextAPILog("GET", routeUrl, "Open Weather Map 예보날씨 API", {
|
|
131
|
+
searchParams,
|
|
132
|
+
count: Array.isArray(res?.list) ? res.list.length : 0,
|
|
133
|
+
cod: res?.cod,
|
|
134
|
+
});
|
|
129
135
|
return res;
|
|
130
136
|
} catch (error) {
|
|
131
137
|
nextAPILog("GET", routeUrl, url, { error });
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import type { WeatherAddressIconProps } from "../../types";
|
|
4
|
+
import MarkerIcon from "../../img/marker.svg";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export default function WeatherAddressIcon({
|
|
7
|
+
alt,
|
|
8
|
+
}: WeatherAddressIconProps = {}) {
|
|
8
9
|
return (
|
|
9
|
-
<figure className="weather-address-icon">
|
|
10
|
-
<
|
|
10
|
+
<figure className="weather-address-icon" role="img" aria-label={alt}>
|
|
11
|
+
<MarkerIcon width={16} height={16} viewBox="0 0 16 16" />
|
|
11
12
|
</figure>
|
|
12
13
|
);
|
|
13
14
|
}
|
|
@@ -4,6 +4,7 @@ import Image from "next/image";
|
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
|
|
6
6
|
import assetUrl from "../../../asset-url";
|
|
7
|
+
import type { WeatherIconProps } from "../../types";
|
|
7
8
|
|
|
8
9
|
// Storybook의 next/image mock과 동일한 client boundary에서 weather icon을 렌더한다.
|
|
9
10
|
|
|
@@ -18,16 +19,14 @@ import assetUrl from "../../../asset-url";
|
|
|
18
19
|
export default function WeatherIcon({
|
|
19
20
|
code,
|
|
20
21
|
name,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
name: string | null;
|
|
24
|
-
}>) {
|
|
22
|
+
alt,
|
|
23
|
+
}: WeatherIconProps = {}) {
|
|
25
24
|
return (
|
|
26
25
|
code && (
|
|
27
26
|
<figure className={clsx("weather-base-icon", "weather-icon")}>
|
|
28
27
|
<Image
|
|
29
28
|
src={`${assetUrl}/img/weather/${code}.svg`}
|
|
30
|
-
alt={name || "날씨 아이콘"}
|
|
29
|
+
alt={name || alt || "날씨 아이콘"}
|
|
31
30
|
fill
|
|
32
31
|
/>
|
|
33
32
|
</figure>
|