Merge commit '1fe8955c4685b5b7da13f775ee8aaf289cdacd9b' into iipbs/wr/dev
iipbs_wr / build (push) Waiting to run
iipbs_wr / build (push) Waiting to run
This commit is contained in:
+255
-191
@@ -2,12 +2,11 @@ import { useWrStore } from "@/app/store";
|
||||
import { btnClass } from "@/app/store/common/code";
|
||||
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
|
||||
import { excelDownload } from "@/components/lib/func/commom";
|
||||
import { Title, UiTable, UITab, ContentsDiv } from '@common/business_components';
|
||||
import { Title, UiTable, UITab } from '@common/business_components';
|
||||
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
|
||||
import { Button } from "@common/components/ui";
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import NewWindowPortal from '@/app/map/newWindowPortal';
|
||||
import PbnstPrfmncSttnPopup from "@/app/map/pbnstPrfmncSttnPopup";
|
||||
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
|
||||
import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type";
|
||||
import type { chgBuilding, customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
|
||||
@@ -16,6 +15,7 @@ import RswtrPrfmncsSttnPopup from "@/app/map/rswtrPrfmncSttnPopup";
|
||||
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
|
||||
import HtdflPrfmncSttnPopup from "@/app/map/htdflPrfmncSttnPopup";
|
||||
import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
|
||||
import { getFetch } from "@/components/lib/api";
|
||||
|
||||
interface NewWindowPopupProps {
|
||||
featureData: any;
|
||||
@@ -24,13 +24,6 @@ interface NewWindowPopupProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// 탭
|
||||
const TABS = [
|
||||
{ val: "0", text: "재이용시설1" },
|
||||
{ val: "1", text: "재이용시설2" },
|
||||
{ val: "2", text: "재이용시설n" },
|
||||
];
|
||||
|
||||
// 데이터의 속성(key)을 사용자에게 보여줄 한글 이름으로 변환하기 위한 맵
|
||||
// 실제 데이터에 맞게 이 부분을 확장해야 합니다.
|
||||
const propertyNameMap: { [key: string]: string } = {
|
||||
@@ -47,9 +40,10 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
checkedLayersData = [],
|
||||
onClose
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "0");
|
||||
const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<string>('0');
|
||||
// swcho : useState의 true, false 값 변경하여 목록, 상태 테스트 하였음
|
||||
const [showDetailView, setShowDetailView] = useState(true); // 상세정보 화면 표시 상태 추가
|
||||
const [showDetailView, setShowDetailView] = useState(false); // 상세정보 화면 표시 상태 추가
|
||||
|
||||
// onClose 함수를 useCallback으로 메모이제이션
|
||||
const memoizedOnClose = useCallback(() => {
|
||||
@@ -67,6 +61,7 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
} = useWrStore((state) => state);
|
||||
const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>();
|
||||
const [pageNation, setPageNation] = useState(1);
|
||||
const [pbnstRuseFcltPk, setPbnstRuseFcltPk] = useState<string>('');
|
||||
const [pbnDetaileData, setPbnDetaileData] = useState<{
|
||||
isModal: boolean;
|
||||
data: customPbnst;
|
||||
@@ -107,10 +102,6 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. RuseMngTable에 필요한 tableData를 생성합니다. (단일 행 데이터)
|
||||
const tableData = [featureData];
|
||||
console.log("tableData", tableData);
|
||||
|
||||
// 물재이용시설 cellClick 이벤트 (상세정보)
|
||||
const pbnstCellClick = (index: number | undefined) => {
|
||||
if (
|
||||
@@ -118,20 +109,93 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
pbnstSttn.pbnstSttnRes &&
|
||||
pbnstSttn.pbnstSttnRes[index]
|
||||
) {
|
||||
const pbnstRuseFcltPk = pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk;
|
||||
getPbnStnDetail(pbnstRuseFcltPk);
|
||||
setPbnstRuseFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk);
|
||||
fetchpbnstDetail(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk)
|
||||
setActiveTab(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk)
|
||||
setShowDetailView(true); // 상세정보 화면 표시 상태 변경
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBdstTable(pbnstSttn.pbnstSttnRes);
|
||||
}, [pbnstSttn.pbnstSttnRes]);
|
||||
useEffect(() => {
|
||||
if (!pbnstSttn.isInit) {
|
||||
pbnSttnInit("1", "13",{
|
||||
partValue: "A",
|
||||
sidoValue: "0",
|
||||
sigunValue: "",
|
||||
regionValue: "",
|
||||
unitWaterShedValue: "",
|
||||
middleRegionValue: "",
|
||||
envPartValue: "",
|
||||
sidoText: "",
|
||||
sigunText: ""
|
||||
}, String(new Date().getFullYear()));
|
||||
}
|
||||
}, [pbnstSttn.isInit]);
|
||||
|
||||
useEffect(() => {
|
||||
setBdstTable(pbnstSttn.pbnstSttnRes);
|
||||
}, [pbnstSttn.pbnstSttnRes]);
|
||||
|
||||
// 재이용시설 상세정보
|
||||
const setTabDataFunc = (res:any, pk:string) => {
|
||||
setTabData((prev) => {
|
||||
if (prev.some((tab) => tab.id === pk)) {
|
||||
return prev; // 이미 존재하는 탭이면 추가하지 않음
|
||||
}
|
||||
return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }];
|
||||
});
|
||||
}
|
||||
// 재이용시설 조회
|
||||
const fetchpbnstDetail = async (featureCd: string) => {
|
||||
try {
|
||||
const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`);
|
||||
// 중복 데이터 추가 방지
|
||||
setTabDataFunc(response, featureCd);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
// 재이용시설 페이징처리 관련 함수
|
||||
const onPageChangeHandler = useCallback((page: number) => {
|
||||
pbnstSttnSearch(
|
||||
String(page),
|
||||
String(13),
|
||||
{
|
||||
partValue: "A",
|
||||
sidoValue: "0",
|
||||
sigunValue: "",
|
||||
regionValue: "",
|
||||
unitWaterShedValue: "",
|
||||
middleRegionValue: "",
|
||||
envPartValue: "",
|
||||
sidoText: "",
|
||||
sigunText: "",
|
||||
},
|
||||
"",
|
||||
String(new Date().getFullYear())
|
||||
);
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// if (featureData) {
|
||||
// const selectedVal = featureData.getProperties();
|
||||
// const featureCd = selectedVal?.swtrmt_fclt_cd;
|
||||
// if (featureCd && featureCd !== pbnstRuseFcltPk) {
|
||||
// setPbnstRuseFcltPk(featureCd); // 현재 호출된 featureCd 저장
|
||||
// setActiveTab(featureCd)
|
||||
// fetchpbnstDetail(featureCd); // 상세정보 가져오기
|
||||
// }
|
||||
// }
|
||||
// }, [featureData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pbnstSttn.pbnstDetail.pbnstRuseFcltPk) setShowDetailView(true);
|
||||
if (pbnstSttn.pbnstDetail) {
|
||||
setPbnDetaileData({
|
||||
isModal: true,
|
||||
data: pbnstSttn.pbnstDetail,
|
||||
});
|
||||
}
|
||||
}, [pbnstSttn.pbnstDetail]);
|
||||
|
||||
|
||||
// 빗물재이용시설 cellClick 이벤트 (상세정보)
|
||||
const rainCellClick = (index: number | undefined, headId: string) => {
|
||||
if (
|
||||
@@ -183,7 +247,8 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
const hasWaterTreatmentLayer = checkedLayersData.some(layer =>
|
||||
layer.data?.lyrNm?.includes('하수처리') || layer.data?.lyrNm?.includes('재이용시설')
|
||||
);
|
||||
|
||||
const actTabData = tabData.find((tab) => tab.id === activeTab);
|
||||
console.log('tavbData',actTabData)
|
||||
// swcho : true, false 값 변경하여 시설 변경하여 테스트하였음
|
||||
// 재이용시설
|
||||
const isPbnst = true;
|
||||
@@ -193,182 +258,181 @@ const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
|
||||
const isRswt = false;
|
||||
// 온배수시설
|
||||
const isHtdfl = false;
|
||||
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
{/* 체크된 레이어가 없는 경우 (재이용시설 목록) */}
|
||||
{isPbnst && (
|
||||
if (isPbnst) {
|
||||
if (showDetailView) {
|
||||
// 재이용시설 상세
|
||||
return (
|
||||
<>
|
||||
{showDetailView ? (
|
||||
// 재이용시설 상세
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title='하수재이용시설 상세정보' />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<UITab
|
||||
defaultValue={TABS[0]?.val}
|
||||
tabList={TABS.map((tab) => ({
|
||||
value: tab.val,
|
||||
label: tab.text,
|
||||
onClick: () => setActiveTab(tab.val),
|
||||
}))}
|
||||
children={undefined}
|
||||
/>
|
||||
</div>
|
||||
<PbnstSttnModal
|
||||
data={pbnDetaileData.data || ({} as customPbnst)}
|
||||
titleVisible={false}
|
||||
/>
|
||||
<PbnstPrfmncSttnPopup isVisible={false} />
|
||||
</>
|
||||
) : (
|
||||
// 재이용시설 목록
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title='하수재이용시설 현황' />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={bdstTable || []}
|
||||
children={
|
||||
<HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} />
|
||||
}
|
||||
headlist={pbnstSttn.pbnstSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
pbnstCellClick(index);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title="하수재이용시설 상세정보" />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<UITab
|
||||
defaultValue={activeTab}
|
||||
tabList={tabData.map((tab) => ({
|
||||
value: tab.id,
|
||||
label: tab.name,
|
||||
onClick: () => setActiveTab(tab.id),
|
||||
}))}
|
||||
children={undefined}
|
||||
/>
|
||||
</div>
|
||||
<PbnstSttnModal
|
||||
data={tabData.find((tab) => tab.id === activeTab)?.data[0] || ({} as customPbnst)}
|
||||
titleVisible={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* 빗물이용시설 */}
|
||||
{isRain && (
|
||||
);
|
||||
} else {
|
||||
// 재이용시설 목록
|
||||
return (
|
||||
<>
|
||||
{showDetailView ? (
|
||||
// 빗물이용시설 상세
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title='빗물이용시설 상세정보' />
|
||||
</div>
|
||||
<RainPrfmncSttnPopup rainForm={rainDetailModal.data as customRainBdst} isModal={false} />
|
||||
{/* TODO: 지도 점검 결과 현황 추가 할 것 */}
|
||||
</>
|
||||
) : (
|
||||
// 빗물이용시설 목록
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title='빗물이용시설 현황' />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={rainTable || []}
|
||||
children={
|
||||
<HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} />
|
||||
}
|
||||
headlist={rainSttn.rainSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
rainCellClick(index ?? undefined, headId)
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title="하수재이용시설 현황" />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>
|
||||
엑셀다운로드
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={bdstTable || []}
|
||||
children={<HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} />}
|
||||
headlist={pbnstSttn.pbnstSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
pbnstCellClick(index);
|
||||
}}
|
||||
onPageChange={onPageChangeHandler}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* 중수도시설 */}
|
||||
{isRswt && (
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isRain) {
|
||||
if (showDetailView) {
|
||||
// 빗물이용시설 상세
|
||||
return (
|
||||
<>
|
||||
{showDetailView ? (
|
||||
// 재이용시설 상세
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title='중수도시설 상세정보' />
|
||||
</div>
|
||||
<RswtrPrfmncsSttnPopup rswtForm={rswtrDetailModal.data as customRswtBdst} isModal={false} />
|
||||
{/* TODO: 지도 점검 결과 현황 추가 할 것 */}
|
||||
</>
|
||||
) : (
|
||||
// 재이용시설 목록
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title='중수도시설 현황' />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={rswtrTable || []}
|
||||
children={
|
||||
<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />
|
||||
}
|
||||
headlist={rswtSttn.rswtSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
rswtCellClick(index, headId);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title="빗물이용시설 상세정보" />
|
||||
</div>
|
||||
<RainPrfmncSttnPopup rainForm={rainDetailModal.data as customRainBdst} isModal={false} />
|
||||
</>
|
||||
)}
|
||||
{/* 온배수시설 */}
|
||||
{isHtdfl && (
|
||||
);
|
||||
} else {
|
||||
// 빗물이용시설 목록
|
||||
return (
|
||||
<>
|
||||
{showDetailView ? (
|
||||
// 재이용시설 상세
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title='온배수시설 상세정보' />
|
||||
</div>
|
||||
{/* <HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} /> */}
|
||||
<HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} />
|
||||
</>
|
||||
) : (
|
||||
// 재이용시설 목록
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title='온배수시설 현황' />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={bdstTable || []}
|
||||
children={
|
||||
<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />
|
||||
}
|
||||
headlist={htdflSttn.htdflSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
htdflCellClick(index ?? undefined, headId);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title="빗물이용시설 현황" />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>
|
||||
엑셀다운로드
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={rainTable || []}
|
||||
children={<HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} />}
|
||||
headlist={rainSttn.rainSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
rainCellClick(index ?? undefined, headId);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isRswt) {
|
||||
if (showDetailView) {
|
||||
// 중수도시설 상세
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title="중수도시설 상세정보" />
|
||||
</div>
|
||||
<RswtrPrfmncsSttnPopup rswtForm={rswtrDetailModal.data as customRswtBdst} isModal={false} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
// 중수도시설 목록
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title="중수도시설 현황" />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>
|
||||
엑셀다운로드
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={rswtrTable || []}
|
||||
children={<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />}
|
||||
headlist={rswtSttn.rswtSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
rswtCellClick(index, headId);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHtdfl) {
|
||||
if (showDetailView) {
|
||||
// 온배수시설 상세
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title title="온배수시설 상세정보" />
|
||||
</div>
|
||||
<HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
// 온배수시설 목록
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title title="온배수시설 현황" />
|
||||
<div className="flex gap-2 mt-8">
|
||||
<Button className={btnClass} color="primary" onClick={excelClick}>
|
||||
엑셀다운로드
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable
|
||||
headName={[]}
|
||||
tableData={htdflTable || []}
|
||||
children={<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />}
|
||||
headlist={htdflSttn.htdflSttHeadList}
|
||||
pageSize={20}
|
||||
total={100000}
|
||||
cellClick={(index: number | undefined, headId: string) => {
|
||||
htdflCellClick(index ?? undefined, headId);
|
||||
}}
|
||||
onPageChange={setPageNation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null; // 조건에 맞는 콘텐츠가 없을 경우
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -88,12 +88,12 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
|
||||
return (
|
||||
<>
|
||||
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
|
||||
<div className="!mt-3 text-[2.5rem] font-bold">
|
||||
<div className="!mt-3 text-[2.5rem] font-bold text-center">
|
||||
{chartTitle}
|
||||
</div>): null}
|
||||
<div className="flex justify-center">
|
||||
<ChartContainer config={chartConfig ?? {}} className={chartClassName + " !w-[90%]"}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<div className="flex justify-center h-full w-full">
|
||||
<ChartContainer config={chartConfig ?? {}} className={chartClassName + " !w-[88%]"}>
|
||||
<BarChart accessibilityLayer data={chartData} margin={{ top: 5, right: 20, left: 30, bottom: 5 }}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="label"
|
||||
@@ -110,8 +110,7 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
|
||||
axisLine={true}
|
||||
fontSize={12}
|
||||
fontWeight={500}
|
||||
domain={[0, Math.ceil(barMaxValue * 1)]}
|
||||
label={yFieldText ? { value: yFieldText, className:"text-[13px] font-bold", position: 'insideLeft', angle: -90, offset: 20 } : undefined}
|
||||
label={yFieldText ? { value: yFieldText, className:"text-[13px] font-bold", position: 'insideLeft', angle: -90, offset: -24, orientation: "left" } : undefined}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />}/>
|
||||
{chartData && chartData.length > 0 && (
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "./lib/chart";
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
/*
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
@@ -17,6 +18,22 @@ const customStyles = {
|
||||
marginTop: "10px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
*/
|
||||
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
fontSize: "13px",
|
||||
fontWeight: "normal",
|
||||
marginTop: "16px",
|
||||
marginBottom: "8px",
|
||||
minHeight: "32px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
|
||||
type BarChartProps = {
|
||||
chartConfig: ChartConfig | undefined;
|
||||
@@ -60,7 +77,7 @@ const CustomTooltip = ({
|
||||
};
|
||||
|
||||
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
|
||||
const CustomLegend = (props: any) => {
|
||||
/*const CustomLegend = (props: any) => {
|
||||
const { payload } = props;
|
||||
return (
|
||||
<div style={customStyles.legendContainer}>
|
||||
@@ -72,6 +89,38 @@ const CustomLegend = (props: any) => {
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};*/
|
||||
const CustomLegend = (props: any) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<div style={customStyles.legendContainer}>
|
||||
{payload.map((entry: any, index: number) => (
|
||||
<div
|
||||
key={`legend-${index}`}
|
||||
className="flex items-center"
|
||||
style={{
|
||||
minWidth: "90px",
|
||||
margin: "2px 0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "14px",
|
||||
height: "14px",
|
||||
backgroundColor: entry.color,
|
||||
borderRadius: "3px",
|
||||
marginRight: "8px",
|
||||
minWidth: "14px",
|
||||
minHeight: "14px",
|
||||
}}
|
||||
></div>
|
||||
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DoubleBarChart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) {
|
||||
@@ -83,7 +132,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha
|
||||
.map(key => item[key])
|
||||
).flat());
|
||||
};
|
||||
console.log(chartData);
|
||||
|
||||
const barMaxValue = getMaxValue(chartData, 'barVal');
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -48,6 +48,7 @@ type LegendProps = {
|
||||
};
|
||||
|
||||
/** 차트 기본 스타일 */
|
||||
/*
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
@@ -58,6 +59,23 @@ const customStyles = {
|
||||
marginTop: "10px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
*/
|
||||
|
||||
/** 차트 기본 스타일 (라인차트와 동일하게) */
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap", // 여러 줄로 자동 줄바꿈
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "12px", // 아이템 간 간격
|
||||
fontSize: "13px",
|
||||
fontWeight: "normal",
|
||||
marginTop: "16px",
|
||||
marginBottom: "8px",
|
||||
minHeight: "32px", // 범례 영역 높이 통일
|
||||
} as CSSProperties,
|
||||
};
|
||||
|
||||
// 커스텀 툴팁
|
||||
const CustomTooltip = ({ active, payload, label }: {
|
||||
@@ -82,6 +100,7 @@ const CustomTooltip = ({ active, payload, label }: {
|
||||
};
|
||||
|
||||
// 커스텀 범례
|
||||
/*
|
||||
const CustomLegend = (props: LegendProps) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
@@ -97,12 +116,52 @@ const CustomLegend = (props: LegendProps) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
*/
|
||||
|
||||
const CustomLegend = (props: LegendProps) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<div style={customStyles.legendContainer}>
|
||||
{payload.map((entry: LegendEntry, index: number) => (
|
||||
<div
|
||||
key={`legend-${index}`}
|
||||
className="flex items-center"
|
||||
style={{
|
||||
minWidth: "90px",
|
||||
margin: "2px 0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "14px",
|
||||
height: "14px",
|
||||
backgroundColor: entry.fill,
|
||||
borderRadius: "3px",
|
||||
marginRight: "8px",
|
||||
minWidth: "14px",
|
||||
minHeight: "14px",
|
||||
}}
|
||||
></div>
|
||||
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>
|
||||
{entry.payload?.label || entry.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DoughnutChart(props: {
|
||||
chartConfig: any;
|
||||
chartData: ChartDataItem[] | undefined;
|
||||
chartTitle: string
|
||||
}) {
|
||||
const legendData = props.chartData?.map(item => ({
|
||||
value: item.label,
|
||||
fill: props.chartConfig?.[item.label]?.fill || 'blue',
|
||||
})) ?? [];
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardContent className="flex-1 pb-0">
|
||||
@@ -130,9 +189,9 @@ export default function DoughnutChart(props: {
|
||||
nameKey="label"
|
||||
innerRadius={60}
|
||||
/>
|
||||
<CustomLegend />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
<CustomLegend payload={legendData} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -18,6 +18,7 @@ type LineChartProps = {
|
||||
}
|
||||
|
||||
/** 차트 기본 스타일 */
|
||||
/*
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
@@ -28,6 +29,23 @@ const customStyles = {
|
||||
marginTop: "10px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
*/
|
||||
|
||||
/** 차트 기본 스타일 */
|
||||
const customStyles = {
|
||||
legendContainer: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
fontSize: "13px",
|
||||
fontWeight: "normal",
|
||||
marginTop: "16px",
|
||||
marginBottom: "8px",
|
||||
minHeight: "32px",
|
||||
} as CSSProperties,
|
||||
};
|
||||
|
||||
type LegendEntry = {
|
||||
value: string;
|
||||
@@ -60,7 +78,7 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?:
|
||||
};
|
||||
|
||||
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
|
||||
const CustomLegend = (props: LegendProps) => {
|
||||
/*const CustomLegend = (props: LegendProps) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
@@ -74,6 +92,40 @@ const CustomLegend = (props: LegendProps) => {
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};*/
|
||||
|
||||
// 커스텀 범례 (도넛차트와 동일하게)
|
||||
const CustomLegend = (props: LegendProps) => {
|
||||
const { payload } = props;
|
||||
if (!payload) return null;
|
||||
|
||||
return (
|
||||
<div style={customStyles.legendContainer}>
|
||||
{payload.map((entry: LegendEntry, index: number) => (
|
||||
<div
|
||||
key={`legend-${index}`}
|
||||
className="flex items-center"
|
||||
style={{
|
||||
minWidth: "90px",
|
||||
margin: "2px 0",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "14px",
|
||||
height: "14px",
|
||||
backgroundColor: entry.color,
|
||||
borderRadius: "3px",
|
||||
marginRight: "8px",
|
||||
minWidth: "14px",
|
||||
minHeight: "14px",
|
||||
}}
|
||||
></div>
|
||||
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type DataType = {label: string; val: string};
|
||||
|
||||
@@ -17,6 +17,7 @@ type MonthPickerProps = {
|
||||
field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열
|
||||
selectClass?: string;
|
||||
readOnly?: boolean;
|
||||
startYear?: number; // 시작 연도 props 추가
|
||||
};
|
||||
|
||||
// yyyyMM → Date 객체
|
||||
@@ -37,6 +38,7 @@ export const DateMonthPicker = ({
|
||||
field,
|
||||
selectClass,
|
||||
readOnly = false,
|
||||
startYear,
|
||||
}: MonthPickerProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -75,6 +77,7 @@ export const DateMonthPicker = ({
|
||||
<MonthPicker
|
||||
currentMonth={parsedDate}
|
||||
onMonthChange={handleSelect}
|
||||
startYear={startYear}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -28,35 +28,41 @@ export type MonthPickerProps = {
|
||||
newMonth: Date | undefined,
|
||||
shouldClosePopover?: boolean
|
||||
) => void;
|
||||
startYear?: number;
|
||||
};
|
||||
|
||||
export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) {
|
||||
export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPickerProps) {
|
||||
|
||||
const today = startOfToday();
|
||||
const currentYear = today.getFullYear();
|
||||
const currentYear = today.getFullYear();
|
||||
const yearFromProp = format(currentMonth, "yyyy");
|
||||
const selectedYear = parseInt(yearFromProp);
|
||||
const selectedYear = parseInt(yearFromProp);
|
||||
|
||||
// 현재 월의 시작 날짜
|
||||
const fullYearStart = parse(yearFromProp, "yyyy", new Date());
|
||||
|
||||
const yearEnd =
|
||||
selectedYear === currentYear ? today : endOfYear(fullYearStart);
|
||||
|
||||
|
||||
|
||||
|
||||
const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
|
||||
|
||||
|
||||
// startYear가 없으면 기존과 동일하게 (현재년도 - 20년부터 현재년도까지)
|
||||
// startYear가 있으면 해당 년도부터 현재년도까지, 내림차순으로 정렬
|
||||
const yearRange = startYear
|
||||
? Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i)
|
||||
: Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
|
||||
|
||||
const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd });
|
||||
|
||||
const getMonthButtonClass = (month: Date) =>
|
||||
cn(
|
||||
"inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800",
|
||||
isEqual(month, currentMonth) &&
|
||||
"bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
|
||||
"bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
|
||||
!isEqual(month, currentMonth) &&
|
||||
isEqual(month, getStartOfCurrentMonth()) &&
|
||||
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
|
||||
isEqual(month, getStartOfCurrentMonth()) &&
|
||||
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -77,7 +83,7 @@ const selectedYear = parseInt(yearFromProp);
|
||||
onMonthChange(newDate, false);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[120px] h-7">
|
||||
<SelectTrigger className="w-[140px] h-9 text-base">
|
||||
<SelectValue placeholder="년도" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -112,7 +118,7 @@ const selectedYear = parseInt(yearFromProp);
|
||||
onClick={() => onMonthChange(month, true)}
|
||||
>
|
||||
<time dateTime={format(month, "yyyy-MM-dd")}>
|
||||
{format(month, 'MMM', { locale: ko })}
|
||||
{format(month, 'MMM', { locale: ko })}
|
||||
</time>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from "@common/components";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
type ModalType = {
|
||||
type ModalType = {
|
||||
bodyClass: string;
|
||||
body: React.ReactNode;
|
||||
open?: boolean;
|
||||
@@ -10,43 +10,45 @@ type ModalType = {
|
||||
noFooterYn?: 'N' | 'Y';
|
||||
headerTitle?: string;
|
||||
footerContent?: React.ReactNode;
|
||||
useDirectRender?: boolean;
|
||||
}
|
||||
|
||||
export function UiModal({
|
||||
bodyClass,
|
||||
body,
|
||||
open,
|
||||
export function UiModal({
|
||||
bodyClass,
|
||||
body,
|
||||
open,
|
||||
onOpenChange,
|
||||
noHeaderYn = 'N',
|
||||
noFooterYn = 'N',
|
||||
headerTitle,
|
||||
footerContent
|
||||
footerContent,
|
||||
useDirectRender = false
|
||||
}: ModalType) {
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={bodyClass}>
|
||||
{noHeaderYn === 'N' && (
|
||||
<DialogHeader>
|
||||
<DialogDescription>
|
||||
{headerTitle}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
)}
|
||||
{(open && body) ?
|
||||
<div> {body}</div>:<></>
|
||||
|
||||
|
||||
|
||||
}
|
||||
{noFooterYn === 'N' && (
|
||||
<DialogFooter className="sm:justify-start">
|
||||
{footerContent}
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{noHeaderYn === 'N' && (
|
||||
<DialogHeader>
|
||||
<DialogDescription>
|
||||
{headerTitle}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
)}
|
||||
{(open && body) ?
|
||||
useDirectRender
|
||||
? body
|
||||
: <div>{body}</div>
|
||||
: <></>
|
||||
}
|
||||
{noFooterYn === 'N' && (
|
||||
<DialogFooter className="sm:justify-start">
|
||||
{footerContent}
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { LoadingWithAnimation } from '@common/components/ui/loading';
|
||||
|
||||
interface LoadingOverlayProps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading }) => {
|
||||
if (!isLoading) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
|
||||
<div className="relative w-16 h-16">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingOverlay;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Search } from 'lucide-react';
|
||||
import OlMap from 'ol/Map';
|
||||
import Overlay from 'ol/Overlay';
|
||||
import View from 'ol/View';
|
||||
import { defaults as defaultControls, OverviewMap } from 'ol/control';
|
||||
import { defaults } from 'ol/interaction';
|
||||
@@ -7,6 +8,7 @@ import * as olProj from 'ol/proj';
|
||||
import { register } from 'ol/proj/proj4';
|
||||
import proj4 from 'proj4';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import GisLonLat from './GisLonLat';
|
||||
import GisMapToggle from './GisMapToggle';
|
||||
import GisSelect from './GisSelect';
|
||||
@@ -14,11 +16,27 @@ import GisToolBar from './GisToolBar';
|
||||
import GisTree from './GisTree';
|
||||
import './lib/css/gis.common.css';
|
||||
import GisApp from './lib/gis.core';
|
||||
// import FacilityInfoPopup from './FacilityInfoPopup';
|
||||
|
||||
export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null });
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
|
||||
function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = false, isMiniMap = false, setIsOpen, setFeatureData, featureData, searchView: SearchView, detailView: DetailView, onFeatureSelect, setIsSearchModalOpen }) {
|
||||
function Gis({
|
||||
isOpen,
|
||||
gisMaps,
|
||||
isSearchModalOpen,
|
||||
treeData = [],
|
||||
isSearch = false,
|
||||
isMiniMap = false,
|
||||
setIsOpen,
|
||||
setFeatureData,
|
||||
featureData,
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
@@ -28,13 +46,21 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
const mapInstanceRef = useRef(null);
|
||||
const checkedLayerIds = useRef([]);
|
||||
|
||||
// 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가
|
||||
// const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]);
|
||||
// const facilityOverlaysRef = useRef([]);
|
||||
// 체크된 레이어 상태 관리
|
||||
const [checkedLayers, setCheckedLayers] = useState([]);
|
||||
const [checkedLayersData, setCheckedLayersData] = useState([]);
|
||||
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null)
|
||||
};
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
// Feature Click Event
|
||||
const handleFeatureSelect = useCallback((data) => {
|
||||
gisMap = mapInstanceRef.current;
|
||||
|
||||
@@ -89,8 +115,10 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
setFeatureData(data);
|
||||
setIsOpen(true);
|
||||
setIsSearchModalOpen(false);
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
}, [mapInstanceRef]);
|
||||
|
||||
// 지도 생성 및 초기화
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
|
||||
@@ -104,7 +132,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
]);
|
||||
register(proj4);
|
||||
|
||||
//맵 생성
|
||||
// OpenLayers 지도 객체 생성
|
||||
const map = new OlMap({
|
||||
layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']],
|
||||
renderer: 'webgl',
|
||||
@@ -132,7 +160,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
|
||||
gisMaps.current = map
|
||||
|
||||
//미니맵
|
||||
// 미니맵 추가
|
||||
if (isMiniMap) {
|
||||
const overviewMap = new OverviewMap({
|
||||
layers: [GisApp.Module.core.baseMiniMapLayers['base']],
|
||||
@@ -147,6 +175,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
map.addControl(overviewMap);
|
||||
}
|
||||
|
||||
// 스케일 컨트롤 및 베이스 레이어 설정
|
||||
gisCore.__proto__.map = map;
|
||||
gisCore.scaleControl();
|
||||
gisCore.changeBaseLayer('base');
|
||||
@@ -154,16 +183,20 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
mapInstanceRef.current = map; // 생성 완료 플래그 설정
|
||||
}, []);
|
||||
|
||||
// treeData 변경 시 레이어 코드 생성
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
// 기존 오버레이 및 레이어 모두 삭제
|
||||
gisCore.deleteAllOverlay();
|
||||
gisCore.deleteAllLayer();
|
||||
|
||||
// 트리 데이터를 레이어 코드 객체로 변환
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
|
||||
GisApp.layerCode = layerCode;
|
||||
GisApp.layerCode = layerCode; // 전역 레이어 코드 설정
|
||||
|
||||
//setTreeData(data);
|
||||
// 편집 가능한 레이어 목록 생성
|
||||
const layerItems = getLayerItems(layerCode);
|
||||
//setEditLayerItems(prev => [...layerItems])
|
||||
}, [treeData]);
|
||||
@@ -193,31 +226,161 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal
|
||||
}
|
||||
}
|
||||
|
||||
// // 데이터 로드 및 팝업 생성 로직
|
||||
// useEffect(() => {
|
||||
// if (!mapInstanceRef.current) return; // 맵이 생성되지 않았으면 실행 안 함
|
||||
|
||||
// // 1. 팝업 데이터 가져오기 (feature.js의 getData와 유사)
|
||||
// const fetchPopupData = async () => {
|
||||
// // 실제 API 호출 로직으로 대체해야 합니다.
|
||||
// // 예시: const response = await yourApiService.getFcltyWtCnt();
|
||||
// // setFacilityInfoPopupData(response.data);
|
||||
|
||||
// // 임시 목업 데이터
|
||||
// const mockData = [
|
||||
// { ftrIdn: 1, ftrCde: 'SA206', bplcNm: '처리장A', fcltyUseYn: 'Y', x: 127.005, y: 37.282, aCnt: 10, cCnt: 5, eCnt: 3, mCnt: 8, iCnt: 2 },
|
||||
// { ftrIdn: 2, ftrCde: 'SA112', bplcNm: '처리장B', fcltyUseYn: 'N', x: 127.010, y: 37.285, aCnt: 8, cCnt: 4, eCnt: 2, mCnt: 6, iCnt: 1 }
|
||||
// ];
|
||||
// setFacilityInfoPopupData(mockData);
|
||||
// };
|
||||
|
||||
// fetchPopupData();
|
||||
|
||||
// }, [mapInstanceRef.current]); // 맵 인스턴스가 생성된 후 한 번만 실행
|
||||
|
||||
// // 데이터가 변경되면 오버레이를 생성/업데이트하는 로직
|
||||
// useEffect(() => {
|
||||
// const map = mapInstanceRef.current;
|
||||
// if (!map || facilityInfoPopupData.length === 0) return;
|
||||
|
||||
// // 기존 오버레이 정리
|
||||
// facilityOverlaysRef.current.forEach(overlay => map.removeOverlay(overlay));
|
||||
// facilityOverlaysRef.current = [];
|
||||
|
||||
// facilityInfoPopupData.forEach(data => {
|
||||
// const popupId = `${data.ftrIdn}_${data.ftrCde}`;
|
||||
|
||||
// // 팝업을 담을 div 엘리먼트 생성
|
||||
// const popupElement = document.createElement('div');
|
||||
// popupElement.id = popupId;
|
||||
|
||||
// // OpenLayers Overlay 생성
|
||||
// const position = olProj.transform([Number(data.x), Number(data.y)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
|
||||
// const overlay = new Overlay({
|
||||
// element: popupElement,
|
||||
// position: position,
|
||||
// offset: [-75, -210], // 팝업 너비의 절반, 높이만큼 오프셋 조정
|
||||
// positioning: 'bottom-center',
|
||||
// className: 'facility-popup-overlay' // 필요시 공통 클래스 추가
|
||||
// });
|
||||
|
||||
// map.addOverlay(overlay);
|
||||
// facilityOverlaysRef.current.push(overlay);
|
||||
|
||||
// // React 컴포넌트를 popupElement에 렌더링
|
||||
// const root = createRoot(popupElement);
|
||||
// root.render(<FacilityInfoPopup data={data} />);
|
||||
// });
|
||||
|
||||
// // 초기 가시성 업데이트
|
||||
// updatePopupVisibility();
|
||||
|
||||
// }, [facilityInfoPopupData]); // 팝업 데이터가 변경될 때마다 실행
|
||||
|
||||
// // 레이어 가시성에 따라 팝업 가시성을 제어하는 함수
|
||||
// const updatePopupVisibility = useCallback(() => {
|
||||
// const map = mapInstanceRef.current;
|
||||
// if (!map) return;
|
||||
|
||||
// const visibleLayers = new Set();
|
||||
// map.getLayers().forEach(layer => {
|
||||
// if (layer.getVisible()) {
|
||||
// // feature.js의 로직처럼 typeName이나 ftrCde를 사용해 필터링
|
||||
// const typeName = layer.get('typeName');
|
||||
// if (typeName === 'VW_WTL_FCLTY_AS') { // 예시 레이어 이름
|
||||
// visibleLayers.add(layer.get('ftrCde'));
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// facilityOverlaysRef.current.forEach(overlay => {
|
||||
// const element = overlay.getElement();
|
||||
// const ftrCde = element.querySelector('table')?.classList[0]; // FacilityPopup의 클래스에서 ftrCde 가져오기
|
||||
|
||||
// if (visibleLayers.has(ftrCde)) {
|
||||
// element.style.display = '';
|
||||
// } else {
|
||||
// element.style.display = 'none';
|
||||
// }
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
// GisTree에서 체크 상태가 변경될 때 이 함수를 호출하도록 연결해야 합니다.
|
||||
// 예시: GisTree 컴포넌트에 onCheck 콜백을 추가하고, 그 콜백에서 이 함수를 호출
|
||||
// useEffect(() => {
|
||||
// // 레이어 가시성이 변경될 때마다 팝업 가시성 업데이트
|
||||
// const map = mapInstanceRef.current;
|
||||
// if (!map) return;
|
||||
|
||||
// const handleLayerVisibilityChange = () => {
|
||||
// updatePopupVisibility();
|
||||
// };
|
||||
|
||||
// map.getLayers().on('change:length', handleLayerVisibilityChange);
|
||||
// map.getLayers().forEach(layer => {
|
||||
// layer.on('change:visible', handleLayerVisibilityChange);
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// // 클린업
|
||||
// map.getLayers().un('change:length', handleLayerVisibilityChange);
|
||||
// map.getLayers().forEach(layer => {
|
||||
// layer.un('change:visible', handleLayerVisibilityChange);
|
||||
// });
|
||||
// };
|
||||
// }, [updatePopupVisibility]);
|
||||
|
||||
// 체크된 레이어 정보를 처리하는 함수
|
||||
const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => {
|
||||
setTimeout(() => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
}, 0);
|
||||
}, [onCheckedLayersChange]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
<GisTree visible={tabVisible} setVisible={setTabVisible} />
|
||||
<GisTree
|
||||
visible={tabVisible}
|
||||
setVisible={setTabVisible}
|
||||
onCheckedChange={handleCheckedLayersChange}
|
||||
/>
|
||||
|
||||
{/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */}
|
||||
|
||||
{/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{isOpen && featureData && !isSearchModalOpen && (
|
||||
<DetailView
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<SearchView
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
/>
|
||||
{/* SearchView가 존재할 때만 렌더링 */}
|
||||
{SearchView && (
|
||||
<SearchView
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
<GisLonLat />
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={onFeatureSelect} />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
</div>
|
||||
|
||||
{isSearch && (
|
||||
|
||||
@@ -187,7 +187,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
image.src = canvas.toDataURL('image/png');
|
||||
return image;
|
||||
};
|
||||
|
||||
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
return (
|
||||
@@ -220,6 +220,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" />
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
|
||||
{depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{}} alt={node.text}></img></span>}
|
||||
</div>
|
||||
)}
|
||||
sort={(a, b) => {
|
||||
|
||||
@@ -301,7 +301,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel
|
||||
resetAllValues();
|
||||
// 리셋 후 트리거를 false로 설정
|
||||
if (setRtnPartValue) {
|
||||
setRtnPartValue('0');
|
||||
setRtnPartValue('A');
|
||||
}
|
||||
}
|
||||
}, [resetTrigger]);
|
||||
|
||||
@@ -110,9 +110,7 @@ export function UiTable2({
|
||||
? 'text-right'
|
||||
: head.align === 'center'
|
||||
? 'text-center'
|
||||
: typeof el[head.id] === 'number'
|
||||
? 'text-right'
|
||||
: 'text-center'
|
||||
: 'text-center'
|
||||
}
|
||||
${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''}
|
||||
|
||||
@@ -82,7 +82,7 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr
|
||||
boldArray.includes(index)
|
||||
?
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${
|
||||
cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
} !font-bold`}
|
||||
@@ -97,7 +97,7 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr
|
||||
</TableCell>
|
||||
:
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${
|
||||
cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
|
||||
@@ -40,7 +40,8 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
href="#"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (startPage > 1) handlePageChange(startPage - 1);
|
||||
}}
|
||||
/>
|
||||
@@ -53,7 +54,7 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
|
||||
<PaginationLink
|
||||
href="#"
|
||||
isActive={pageNum === pageProps.cur}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
onClick={(e) => {e.preventDefault(); handlePageChange(pageNum)}}
|
||||
>
|
||||
{pageNum}
|
||||
</PaginationLink>
|
||||
@@ -65,7 +66,8 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
href="#"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (endPage < totalPages) handlePageChange(endPage + 1);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user