Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs-sn-map
iipbs_sn / build (push) Waiting to run

This commit is contained in:
yschoi
2025-05-20 15:08:17 +09:00
10 changed files with 678 additions and 655 deletions
@@ -0,0 +1,159 @@
import React, { useMemo } from "react";
import { SelectBox2 } from "@common/business_components/ui/select/index-2";
interface Option {
id: number;
name: string;
}
interface SidoSigunguSelectBoxProps {
sido: number;
sigungu: number;
onSidoChange: (id: number) => void;
onSigunguChange: (id: number) => void;
className?: string;
}
const sidoList: { id: number, name: string }[] = [
{ id: 0, name: '선택' },
{ id: 26, name: '부산광역시' },
{ id: 28, name: '인천광역시' },
{ id: 30, name: '대전광역시' },
{ id: 41, name: '경기도' },
{ id: 42, name: '강원도' },
{ id: 44, name: '충청남도' },
{ id: 45, name: '전라북도' },
{ id: 46, name: '전라남도' },
{ id: 47, name: '경상북도' },
{ id: 48, name: '경상남도' },
];
const sigunguMap: Record<number, { id: number; name: string }[]> = {
26: [
{ id: 0, name: '선택' },
// ...부산산광역시의 시군구 추가
],
28: [
{ id: 0, name: '선택' },
// ...인천광역시의 시군구 추가
],
30: [
{ id: 0, name: '선택' },
// ...대전광역시의 시군구 추가
],
41: [
{ id: 0, name: '선택' },
{ id: 41820, name: '가평군' },
{ id: 41550, name: '안성시' },
{ id: 41830, name: '양평군' },
{ id: 41670, name: '여주시' },
{ id: 41500, name: '이천시' },
{ id: 41430, name: '의왕시' },
{ id: 41150, name: '의정부시' },
{ id: 41480, name: '파주시' },
{ id: 41650, name: '포천시' },
// ...경기도의 시군구 추가
],
42: [
{ id: 0, name: '선택' },
{ id: 42170, name: '동해시' },
{ id: 42110, name: '춘천시' },
{ id: 42720, name: '홍천군' },
// ...강원도의 시군구 추가
],
44: [
{ id: 0, name: '선택' },
{ id: 44770, name: '서천군' },
// ...충청남도의 시군구 추가
],
45: [
{ id: 0, name: '선택' },
{ id: 45130, name: '군산시' },
{ id: 45190, name: '남원시' },
{ id: 45730, name: '무주군' },
{ id: 45800, name: '부안군' },
{ id: 45770, name: '순창군' },
{ id: 45140, name: '익산시' },
{ id: 45750, name: '임실군' },
{ id: 45710, name: '완주군' },
{ id: 45110, name: '전주시' },
// ...전라북도의 시군구 추가
],
46: [
{ id: 0, name: '선택' },
{ id: 46810, name: "강진군" },
{ id: 46770, name: "고흥군" },
{ id: 46720, name: "곡성군" },
{ id: 46730, name: "구례군" },
{ id: 46230, name: "광양시" },
{ id: 46170, name: "나주시" },
{ id: 46110, name: "목포시" },
{ id: 46840, name: "무안군" },
{ id: 46150, name: "순천시" },
{ id: 46910, name: "신안군" },
{ id: 46130, name: "여수시" },
{ id: 46890, name: "완도군" },
// ...전라남도의 시군구 추가
],
47: [
{ id: 0, name: '선택' },
{ id: 47130, name: "경주시" },
{ id: 47830, name: "고령군" },
{ id: 47190, name: "구미시" },
{ id: 47720, name: "군위군" },
{ id: 47150, name: "김천시" },
{ id: 47280, name: "문경시" },
{ id: 47920, name: "봉화군" },
{ id: 47250, name: "상주시" },
{ id: 47770, name: "영덕군" },
{ id: 47760, name: "영양군" },
{ id: 47210, name: "영주시" },
{ id: 47900, name: "예천군" },
{ id: 47930, name: "울진군" },
{ id: 47730, name: "의성군" },
{ id: 47750, name: "청송군" },
{ id: 47850, name: "칠곡군" },
{ id: 47110, name: "포항시" },
// ...경상북도의 시군구 추가
],
48: [
{ id: 0, name: '선택' },
{ id: 48310, name: "거제시" },
{ id: 48250, name: "김해시" },
{ id: 48270, name: "밀양시" },
{ id: 48220, name: "통영시" },
// ...경상남도의 시군구 추가
],
// ...나머지 시도별 시군구 추가
};
export function SidoSigunguSelectBox({
sido,
sigungu,
onSidoChange,
onSigunguChange,
className = "",
}: SidoSigunguSelectBoxProps) {
// 선택된 시도에 따라 시군구 리스트를 가져옴
const sigunguList = useMemo(() => sigunguMap[sido] || [{ id: 0, name: '선택' }], [sido]);
return (
<div className={`flex items-center ${className}`}>
<SelectBox2
className="w-50 mr-8"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={sidoList.map(opt => ({ val: opt.id, text: opt.name }))}
value={sido}
onSelectValue={val => onSidoChange(Number(val))}
/>
<SelectBox2
className="w-50 mr-8"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={sidoList.map(opt => ({ val: opt.id, text: opt.name }))}
value={sigungu}
onSelectValue={val => onSigunguChange(Number(val))}
/>
</div>
);
}
@@ -0,0 +1,111 @@
import React from "react";
import { SelectBox2 } from "@common/business_components/ui/select/index-2";
interface YearRangeSelectBoxProps {
start: string;
end: string;
onChange: (range: { start: string; end: string }) => void;
startLabel?: string;
endLabel?: string;
minYear?: number;
maxYear?: number;
className?: string;
selectClass?: string;
}
// 시작년도 selectArray 생성 함수
export function getStartYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = "년",
endYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
endYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !endYear || parseInt(y) <= parseInt(endYear));
return [
{ val: "0", text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
// 종료년도 selectArray 생성 함수
export function getEndYearArray({
minYear = 1900,
maxYear = new Date().getFullYear(),
placeholder = "년",
startYear,
}: {
minYear?: number;
maxYear?: number;
placeholder?: string;
startYear?: string;
} = {}) {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !startYear || parseInt(y) >= parseInt(startYear));
return [
{ val: "9999", text: placeholder },
...years.map(y => ({ val: y, text: y })),
];
}
export function YearRangeSelectBox({
start,
end,
onChange,
startLabel = "시작년도",
endLabel = "",
minYear = 1900,
maxYear = new Date().getFullYear(),
className = "",
selectClass = "rounded-lg bg-white"
}: YearRangeSelectBoxProps) {
// 시작년도 배열
const getStartYearArray = () => {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !end || parseInt(y) <= parseInt(end));
return [
{ val: "0", text: "년" },
...years.map(y => ({ val: y, text: y })),
];
};
// 종료년도 배열
const getEndYearArray = () => {
const years = Array.from({ length: maxYear - minYear + 1 }, (_, i) =>
(maxYear - i).toString()
).filter(y => !start || parseInt(y) >= parseInt(start));
return [
{ val: "9999", text: "년" },
...years.map(y => ({ val: y, text: y })),
];
};
return (
<div className={`flex items-center ${className}`}>
<SelectBox2
className="w-50 mr-3"
selectClass={selectClass}
label={startLabel}
selectArray={getStartYearArray()}
onSelectValue={val => onChange({ start: String(val), end })}
value={start}
/>
<span className="font-semibold text-xl mx-2">~</span>
<SelectBox2
className="w-50 mr-8"
selectClass={selectClass}
label={endLabel}
selectArray={getEndYearArray()}
onSelectValue={val => onChange({ start, end: String(val) })}
value={end}
/>
</div>
);
}
+7 -3
View File
@@ -7,20 +7,24 @@ interface ModalProps {
title?: string;
children: React.ReactNode;
closeModal: () => void;
saveModal?: () => void
}
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal }) => {
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal,saveModal }) => {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogOverlay className='z-[200]'/>
<DialogContent className="w-auto max-w-[1200px] min-w-[400px] min-h-[300px] h-auto max-h-[450px] overflow-auto z-[1000]">
<DialogOverlay className='z-[200]' />
<DialogContent className="w-auto max-w-[1200px] min-w-[400px] min-h-[150px] h-auto max-h-[450px] overflow-auto z-[1000]">
{title ? <DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader> : null}
{children}
<DialogFooter>
<DialogClose>
<div className='flex justify-end gap-2'>
{saveModal ? <button className="px-4 py-2 bg-primary text-white rounded" onClick={saveModal}>저장</button> : null}
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closeModal}>닫기</button>
</div>
</DialogClose>
</DialogFooter>
</DialogContent>
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -17,9 +17,10 @@ type LayerContentMap = {
const layerContentMap: LayerContentMap = {
// TODO: gis데이터 추가되면 추가 작업 필요
"하수처리시설": {
component: StormWaterPump,
component: SwtrmtFclt,
},
"하수처리구역": { },
"우수토실":{component: Ssc,},
"중점우수토실":{component: Ssc,},
"하수관로":{component: MsSwagePpl,},
"하수맨홀":{component: smh,},
@@ -5,7 +5,7 @@ import { UiTable } from '@common/business_components/ui';
import { Button, TableHead, TableRow } from '@common/components/ui';
import React from 'react'
const inputColumn = [
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
{ id: "mainSscYn", title: "중점우수토실 대상여부", type: "select", options: ['Y', 'N'] },
];
const headlist = [
@@ -1,8 +1,8 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "bungu", title: "배수분구", type: "input" },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "freq", title: "설계빈도", type: "input" },
]
// 오수펌프
+37 -19
View File
@@ -6,6 +6,7 @@ import { SwtrmtFcltCol, SwtrmtPrcsZoneCol } from "./components/common/table";
import { useGisContext } from "@common/business_components/ui/map/Gis";
import { getData, putData } from "@/components/lib/api";
import { getLayerContentComponent } from "./components/common";
import Modal from "@/components/ui/modal";
// TODO:
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
@@ -19,6 +20,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const [buttonChk, setButtonChk] = useState(true)
const [fetchData, setfetchData] = useState<any>(); // api 호출여부
const contentRef = useRef<HTMLDivElement>(null);
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
const getSelectedLayers = () => {
let result = null;
@@ -31,7 +33,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
featureLayer = layer; // 피처가 속한 레이어 찾기
}
});
// SearchMoal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
if (featureLayer.get('lyrNm') === null) {
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
@@ -46,8 +48,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
if (featureId) {
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
setLayerNm(selectedLayerName); // 상태 업데이트
const data = await getData(`v1/fclts/${featureId}`);
const layerId = featureId.split('.')[0]
const data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`);
setfetchData(data)
switch (selectedLayerName) {
case '하수처리시설':
@@ -73,6 +75,9 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
case '빗물펌프':
setButtonChk(true)
break;
case '하수맨홀':
setButtonChk(true)
break;
default:
break;
}
@@ -81,34 +86,46 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
}
fetchData();
}, [featureId]);
// TODO: api 호출작업 필요(사용자 입력부분)
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
};
const handleModalSave = () => {
setIsModalOpen(false); // 모달 닫기
btnClickEvent(); // 저장 이벤트 실행
};
const handleModalClose = () => {
setIsModalOpen(false); // 모달 닫기
};
const btnClickEvent = async() => {
const bodyData: Record<string, any> = {}
if (buttonChk && contentRef.current) {
const inputElements = contentRef.current.querySelectorAll('input');
const selectElements = contentRef.current.querySelectorAll('select');
// 카멜케이스 변환 함수
const toCamelCase = (str: string) =>
str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
// select 태그의 값 가져오기
selectElements.forEach((select) => {
bodyData[select.id] = select.value;
const camelCaseId = toCamelCase(select.id);
bodyData[camelCaseId] = select.value;
});
// input 태그의 값 가져오기
inputElements.forEach((input) => {
bodyData[input.id] = input.value;
const camelCaseId = toCamelCase(input.id);
bodyData[camelCaseId] = input.value;
});
if (layerNm === '하수관로' || layerNm === '하수처리시설') {
bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
} else if (layerNm === '우수토실') {
bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
} else if (layerNm === '오수펌프') {
bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
}
else if (layerNm === '빗물펌프') {
bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
}
if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if(layerNm === '하수맨홀')bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl'
bodyData['fid'] = featureId
const data = await putData(`v1/fclts/${featureId}`, bodyData);
if(data){
console.log('저장완료', data);
if(data.status==200){
alert('저장되었습니다')
setTableData(data)
setfetchData(data)
}
}
}
@@ -133,10 +150,11 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
throw new Error("Function not implemented.");
}}></VerticalTable>
<div className="w-full mx-5 flex items-center justify-end space-x-2">
{buttonChk ? <Button onClick={btnClickEvent} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
{buttonChk ? <Button onClick={handleSaveClick} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
저장
</Button> : null}
</div>
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
{ContentComponent && <ContentComponent featureData={fetchData} />}
</div>
</div>
@@ -0,0 +1,45 @@
/**
* @remarks
* 이 버전은 SearchModal에서 사용하기 위해 `SelectContent`의 z-index를 `z-[101]`로 수정하였습니다.
*/
import { Label } from "@common/components";
import React from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./lib/selectBox";
/** 차트 기본 스타일 */
interface SelectBox1 {
text:string,
val:string|number
}
const SelectBox2 = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
label:string,
selectClass:string|undefined,
className:string,
selectArray:SelectBox1[],
onSelectValue:(val:string|number)=>void,
value?: string|number
}
>(({ label, className, selectClass, selectArray, onSelectValue, value }, ref)=>{
const placeHolder = selectArray?.[0]?.text ?? "";
return (
<div className={className+" flex items-center flex-nowrap"} ref={ref}>
{label && <Label className="inline-block w-auto whitespace-nowrap text-xl text-gray-600 mr-2">{label}</Label>}
<Select onValueChange={onSelectValue} value={value !== undefined ? String(value) : undefined}>
<SelectTrigger className={selectClass?selectClass:"flex-1 p-2 border rounded"}>
<SelectValue placeholder={placeHolder} />
</SelectTrigger>
<SelectContent className="z-[101]">
{selectArray.map((selectArray) => (
<SelectItem key={selectArray.val} value={String(selectArray.val)}>{selectArray.text}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
})
export { SelectBox2 };
@@ -51,7 +51,6 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
const startIdx = (curState - 1) * pageSize;
const endIdx = startIdx + pageSize;
const pageData = tableData.slice(startIdx, endIdx);
return (
<>
@@ -81,7 +80,7 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
)}
</TableHeader>
<TableBody className='text-center'>
{pageData.map((el: { [key: string]: string | number | boolean | object }, index) => (
{tableData.map((el: { [key: string]: string | number | boolean | object }, index) => (
<TableRow key={index + "row"}>
{headlist?.map((head) => (
<TableCell