ship-gis/src/weather/components/TyphoonInfo.jsx

191 lines
7.2 KiB
React
Raw Normal View 히스토리

import { useState, useCallback } from 'react';
import { fetchTyphoonList, fetchTyphoonDetail } from '@/api/weatherApi';
const LIMIT = 10;
const currentYear = new Date().getFullYear();
const yearOptions = Array.from({ length: currentYear - 2000 + 1 }, (_, i) => currentYear - i);
const monthOptions = Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, '0'));
export default function TyphoonInfo() {
const [year, setYear] = useState(String(currentYear));
const [month, setMonth] = useState('');
const [list, setList] = useState([]);
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [expandedIndex, setExpandedIndex] = useState(null);
const [detailData, setDetailData] = useState([]);
const [isDetailLoading, setIsDetailLoading] = useState(false);
const search = useCallback(async (targetPage) => {
if (!year) return;
setIsLoading(true);
setError(null);
setExpandedIndex(null);
try {
const result = await fetchTyphoonList({
typhoonBeginningYear: year,
typhoonBeginningMonth: month,
page: targetPage,
limit: LIMIT,
});
setList(result.list);
setTotalPage(result.totalPage);
setPage(targetPage);
} catch (err) {
setError('태풍정보 조회 중 오류가 발생했습니다.');
setList([]);
setTotalPage(0);
} finally {
setIsLoading(false);
}
}, [year, month]);
const handleSearch = () => {
search(1);
};
const handlePageChange = (newPage) => {
search(newPage);
};
const handleToggle = useCallback(async (idx, item) => {
if (expandedIndex === idx) {
setExpandedIndex(null);
return;
}
setExpandedIndex(idx);
setIsDetailLoading(true);
setDetailData([]);
try {
const data = await fetchTyphoonDetail({
typhoonSequence: item.typhoonSequence,
year: item.typhoonBeginningYear || year,
});
setDetailData(data);
} catch {
setDetailData([]);
} finally {
setIsDetailLoading(false);
}
}, [expandedIndex, year]);
return (
<div className="tabWrap is-active">
<div className="tabTop">
<div className="title">태풍정보</div>
<div className="formGroup">
<ul>
<li>
<label>
<span>연도</span>
<select value={year} onChange={(e) => setYear(e.target.value)}>
{yearOptions.map((y) => (
<option key={y} value={y}>{y}</option>
))}
</select>
</label>
<label>
<span></span>
<select value={month} onChange={(e) => setMonth(e.target.value)}>
<option value="">전체</option>
{monthOptions.map((m) => (
<option key={m} value={m}>{m}</option>
))}
</select>
</label>
</li>
<li className="fgBtn">
<button type="button" className="schBtn" onClick={handleSearch}>검색</button>
</li>
</ul>
</div>
</div>
<div className="tabBtm">
{isLoading && <div className="loading">조회 ...</div>}
{error && <div className="error">{error}</div>}
{!isLoading && !error && list.length === 0 && (
<div className="empty">검색 결과가 없습니다.</div>
)}
{!isLoading && list.length > 0 && (
<>
<ul className="colList lineSB">
{list.map((item, idx) => {
const isExpanded = expandedIndex === idx;
const status = item.typhoonEndTime ? '종료' : '진행중';
return (
<li key={idx} style={isExpanded ? { flexDirection: 'column', alignItems: 'stretch' } : undefined}>
<a
href="#"
onClick={(e) => {
e.preventDefault();
handleToggle(idx, item);
}}
>
<span className="title">
{(page - 1) * LIMIT + idx + 1}. {item.typhoonName} ({status})
</span>
<span className="meta">
발생일시 {item.typhoonBeginningTime} / 종료일시 {item.typhoonEndTime || '-'}
</span>
</a>
{isExpanded && (
<div className={`acdListBox${isExpanded ? ' open' : ''}`}>
{isDetailLoading && <div className="loading">상세 조회 ...</div>}
{!isDetailLoading && detailData.length === 0 && (
<div className="empty">진행정보가 없습니다.</div>
)}
{!isDetailLoading && detailData.length > 0 && (
<ul className="acdList">
{detailData.map((d, dIdx) => (
<li key={dIdx}>
<span>발표순서: {d.presentationSequence}</span>
<span>시간간격: {d.timeInterval}</span>
<span>발표시간: {d.presentationTime}</span>
</li>
))}
</ul>
)}
</div>
)}
</li>
);
})}
</ul>
{totalPage > 1 && (
<div className="pagination">
<button type="button" className={page <= 1 ? 'disabled' : ''} disabled={page <= 1} onClick={() => handlePageChange(page - 1)}>&lt;</button>
{page > 3 && <button type="button" onClick={() => handlePageChange(1)}>1</button>}
{page > 4 && <span className="ellipsis">...</span>}
{Array.from({ length: 5 }, (_, i) => page - 2 + i)
.filter((p) => p >= 1 && p <= totalPage)
.map((p) => (
<button key={p} type="button" className={p === page ? 'on' : ''} onClick={() => handlePageChange(p)}>{p}</button>
))}
{page < totalPage - 3 && <span className="ellipsis">...</span>}
{page < totalPage - 2 && <button type="button" onClick={() => handlePageChange(totalPage)}>{totalPage}</button>}
<button type="button" className={page >= totalPage ? 'disabled' : ''} disabled={page >= totalPage} onClick={() => handlePageChange(page + 1)}>&gt;</button>
</div>
)}
</>
)}
</div>
</div>
);
}