diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx index ebfcac1ca..777c3dcf2 100644 --- a/apps/wr/app/map/newWindowPopup.tsx +++ b/apps/wr/app/map/newWindowPopup.tsx @@ -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 = ({ checkedLayersData = [], onClose }) => { - const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "0"); + const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]); + const [activeTab, setActiveTab] = useState('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 = ({ } = useWrStore((state) => state); const [bdstTable, setBdstTable] = useState(); const [pageNation, setPageNation] = useState(1); + const [pbnstRuseFcltPk, setPbnstRuseFcltPk] = useState(''); const [pbnDetaileData, setPbnDetaileData] = useState<{ isModal: boolean; data: customPbnst; @@ -107,10 +102,6 @@ const NewWindowPopup: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ const isRswt = false; // 온배수시설 const isHtdfl = false; - - - return ( -
- {/* 체크된 레이어가 없는 경우 (재이용시설 목록) */} - {isPbnst && ( + if (isPbnst) { + if (showDetailView) { + // 재이용시설 상세 + return ( <> - {showDetailView ? ( - // 재이용시설 상세 - <> -
- - </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 ( diff --git a/apps/wr/public/images/map/layericon/Layer_Select_BTN.png b/apps/wr/public/images/map/layericon/Layer_Select_BTN.png new file mode 100644 index 000000000..1e810feaf Binary files /dev/null and b/apps/wr/public/images/map/layericon/Layer_Select_BTN.png differ diff --git a/packages/common/business_components/src/ui/chart/BarChart.tsx b/packages/common/business_components/src/ui/chart/BarChart.tsx index 0802a72e4..a7282830d 100644 --- a/packages/common/business_components/src/ui/chart/BarChart.tsx +++ b/packages/common/business_components/src/ui/chart/BarChart.tsx @@ -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 && ( diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx index 9627485aa..b201b3bf9 100644 --- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx +++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx @@ -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 ( <> diff --git a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx index 39dee3d9e..99395267a 100644 --- a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx +++ b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index 4014702b6..28b4ceabf 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -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}; diff --git a/packages/common/business_components/src/ui/date_month_picker/index.tsx b/packages/common/business_components/src/ui/date_month_picker/index.tsx index d318b561f..1b97d9ce1 100644 --- a/packages/common/business_components/src/ui/date_month_picker/index.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/index.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx index acf17fba4..2b64ab439 100644 --- a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx index bbb27d327..df7fffd3a 100644 --- a/packages/common/business_components/src/ui/dialog_ui/index.tsx +++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx @@ -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> </> ); } diff --git a/packages/common/business_components/src/ui/loadingBar/index.tsx b/packages/common/business_components/src/ui/loadingBar/index.tsx new file mode 100644 index 000000000..37991d1d2 --- /dev/null +++ b/packages/common/business_components/src/ui/loadingBar/index.tsx @@ -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; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 6d46e77aa..977f21b2b 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -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 && ( diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 3c2cb2819..200e9d988 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -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) => { diff --git a/packages/common/business_components/src/ui/search_form_left/index.tsx b/packages/common/business_components/src/ui/search_form_left/index.tsx index e7858df2a..d0da2cd1a 100644 --- a/packages/common/business_components/src/ui/search_form_left/index.tsx +++ b/packages/common/business_components/src/ui/search_form_left/index.tsx @@ -301,7 +301,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel resetAllValues(); // 리셋 후 트리거를 false로 설정 if (setRtnPartValue) { - setRtnPartValue('0'); + setRtnPartValue('A'); } } }, [resetTrigger]); diff --git a/packages/common/business_components/src/ui/table/index-2.tsx b/packages/common/business_components/src/ui/table/index-2.tsx index b6a9750b0..5961ea396 100644 --- a/packages/common/business_components/src/ui/table/index-2.tsx +++ b/packages/common/business_components/src/ui/table/index-2.tsx @@ -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' : ''} diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index cd25bc8db..bb90153c2 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -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' : '' }`} diff --git a/packages/common/business_components/src/ui/table/uiPageNations.tsx b/packages/common/business_components/src/ui/table/uiPageNations.tsx index 891fc4c81..a4ee3b4fc 100644 --- a/packages/common/business_components/src/ui/table/uiPageNations.tsx +++ b/packages/common/business_components/src/ui/table/uiPageNations.tsx @@ -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); }} />