Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs-sn-map
iipbs_sn / build (push) Has been cancelled
iipbs_sn / build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose, DialogOverlay } from '@common/components/ui'
|
||||
import React from 'react'
|
||||
|
||||
interface ModalProps {
|
||||
isModalOpen: boolean;
|
||||
setIsModalOpen: (open: boolean) => void;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal }) => {
|
||||
return (
|
||||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||
<DialogOverlay className='z-[200]'/>
|
||||
<DialogContent className="w-auto max-w-[1200px] h-auto max-h-[450px] overflow-auto z-[1000]">
|
||||
{title ? <DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader> : null}
|
||||
{children}
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closeModal}>닫기</button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
||||
@@ -0,0 +1,796 @@
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { UiModal } from '@common/business_components/ui/dialog_ui';
|
||||
import { Button } from '@common/components';
|
||||
import { UiTable2 } from '@common/business_components/ui/table/index-2';
|
||||
import { CommonTableColumn } from "./table";
|
||||
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
const TABS = [
|
||||
{ key: 'basic', label: '기본정보' },
|
||||
{ key: 'operation', label: '운영정보' },
|
||||
{ key: 'report', label: '하수도정비기본계획보고서' },
|
||||
];
|
||||
|
||||
const WORK_TYPES = [
|
||||
{ key: 'maintenance', label: '보수 및 갱신' },
|
||||
{ key: 'cleaning', label: '청소 및 준설' },
|
||||
{ key: 'inspection', label: '점검 및 진단' },
|
||||
];
|
||||
|
||||
const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`);
|
||||
|
||||
// 하수처리구역 테이블 헤더 리스트
|
||||
const swageZoneTableHeadlist = [
|
||||
// { id: 'ctpvCd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
// { id: 'sggCd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설번호' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
]
|
||||
// 하수처리시설 테이블 헤더 리스트
|
||||
const swageFcltTableHeadlist = [
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설코드' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
{ id: 'fcltCpct', title: '시설용량' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
{ id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
{ id: 'clsgYmd', title: '폐쇄일자' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
{ id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'daddr', title: '상세주소' },
|
||||
{ id: 'rdnmAddr', title: '도로명주소' },
|
||||
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
{ id: 'pllnArea', title: '부지면적' },
|
||||
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
|
||||
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
|
||||
{ id: 'geom', title: 'geometry' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
{ id: 'rprsMsrcCn', title: '대표공법내용' },
|
||||
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
|
||||
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
|
||||
{ id: 'sttyCrtrSs', title: '법정기준SS' },
|
||||
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
|
||||
{ id: 'sttyCrtrTph', title: '법정기준총인' },
|
||||
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
|
||||
{ id: 'bsnNm', title: '유역명' },
|
||||
];
|
||||
|
||||
// '구분' 선택 목록
|
||||
const BASIC_TYPES = [
|
||||
{ key: 'SEWER_PIPE', label: '하수관로' },
|
||||
{ key: 'tb_il_swage_prcs_fclt', label: '하수처리시설' },
|
||||
{ key: 'tb_ms_swage_prcs_zone', label: '하수처리구역' },
|
||||
{ key: 'MANHOLE', label: '하수맨홀' },
|
||||
{ key: 'WASTEWATER_PUMP', label: '오수펌프' },
|
||||
{ key: 'RAINWATER_PUMP', label: '빗물펌프' },
|
||||
{ key: 'OUTFALL', label: '우수토실' },
|
||||
{ key: 'RETENTION_FACILITY', label: '하수저류시설' },
|
||||
{ key: 'PRIORITY_OUTFALL', label: '중점우수토실' },
|
||||
];
|
||||
|
||||
const sidoList = [
|
||||
{ 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: "통영시" },
|
||||
// ...경상남도의 시군구 추가
|
||||
],
|
||||
// ...나머지 시도별 시군구 추가
|
||||
};
|
||||
|
||||
const 처리구역List = ['선택', 'A', 'B'];
|
||||
const 설치년도List = ['선택', ...YEARS];
|
||||
|
||||
// swageZoneTableHeadlist, tableData 타입 예시 (실제 타입에 맞게 수정)
|
||||
type TableHead = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
type TableRow = Record<string, any>;
|
||||
|
||||
interface SearchModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
// filterParams 타입 정의 추가
|
||||
type FilterParams = {
|
||||
swtrmt_zone_nm?: string;
|
||||
ctpv_cd?: number;
|
||||
sgg_cd?: number;
|
||||
swtrmt_fclt_nm?: string;
|
||||
// 추가 필터 속성들...
|
||||
};
|
||||
|
||||
// 해당 년/월의 마지막 일 구하는 함수
|
||||
function getLastDayOfMonth(year: string, month: string) {
|
||||
if (!year || !month) return 31;
|
||||
const y = parseInt(year, 10);
|
||||
const m = parseInt(month, 10);
|
||||
if (isNaN(y) || isNaN(m)) return 31;
|
||||
return new Date(y, m, 0).getDate();
|
||||
}
|
||||
// 날짜 조합 함수
|
||||
const getDateString = (year: string, month: string, day: string, defaultDate: string) => {
|
||||
if (!year && !month && !day) return defaultDate;
|
||||
const y = year || defaultDate.slice(0, 4);
|
||||
const m = month || defaultDate.slice(4, 6);
|
||||
const d = day || defaultDate.slice(6, 8);
|
||||
return `${y}${m}${d}`;
|
||||
};
|
||||
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
const [activeTab, setActiveTab] = React.useState('basic');
|
||||
const [workType, setWorkType] = React.useState(WORK_TYPES[0]?.key ?? '');
|
||||
const [startYear, setStartYear] = React.useState('');
|
||||
const [endYear, setEndYear] = React.useState('');
|
||||
const [showTable, setShowTable] = React.useState(false);
|
||||
const [curPage, setCurPage] = React.useState(1);
|
||||
const [basicType, setBasicType] = React.useState(BASIC_TYPES[0]?.key ?? '');
|
||||
const [하수처리구역명, set하수처리구역명] = React.useState('');
|
||||
const [하수처리시설명, set하수처리시설명] = React.useState('');
|
||||
const [시도, set시도] = React.useState(0);
|
||||
const [시군구, set시군구] = React.useState(0);
|
||||
const [처리구역, set처리구역] = React.useState('');
|
||||
const [설치년도1, set설치년도1] = React.useState('');
|
||||
const [설치년도2, set설치년도2] = React.useState('');
|
||||
const [tableData, setTableData] = React.useState<any[]>([]);
|
||||
const [시설용량최소, set시설용량최소] = React.useState('');
|
||||
const [시설용량최대, set시설용량최대] = React.useState('');
|
||||
// 날짜 상태 선언
|
||||
const [가동개시_시작_년, set가동개시_시작_년] = React.useState('');
|
||||
const [가동개시_시작_월, set가동개시_시작_월] = React.useState('');
|
||||
const [가동개시_시작_일, set가동개시_시작_일] = React.useState('');
|
||||
const [가동개시_종료_년, set가동개시_종료_년] = React.useState('');
|
||||
const [가동개시_종료_월, set가동개시_종료_월] = React.useState('');
|
||||
const [가동개시_종료_일, set가동개시_종료_일] = React.useState('');
|
||||
|
||||
// 오늘 날짜 구하기
|
||||
const today = new Date();
|
||||
const todayYear = today.getFullYear().toString();
|
||||
const todayMonth = (today.getMonth() + 1).toString().padStart(2, '0');
|
||||
const todayDate = today.getDate().toString().padStart(2, '0');
|
||||
|
||||
// UiTable2에 넘길 headlist를 BASIC_TYPES 값에 따라 분기 처리
|
||||
let dynamicHeadlist = swageZoneTableHeadlist;
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') {
|
||||
dynamicHeadlist = swageZoneTableHeadlist; // 하수처리구역
|
||||
} else if (basicType === 'tb_il_swage_prcs_fclt') {
|
||||
dynamicHeadlist = swageFcltTableHeadlist; // 하수처리시설
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 선택된 시도에 따라 시군구 리스트 가져오기
|
||||
const selectedSigunguList = sigunguMap[시도] || [{ id: 0, name: '선택' }];
|
||||
|
||||
// 조회 버튼 클릭 시 1페이지로 이동하도록 수정
|
||||
const handleSearch = () => {
|
||||
setCurPage(1); // 페이지를 1로 초기화
|
||||
fetchFilteredData();
|
||||
setShowTable(true);
|
||||
};
|
||||
|
||||
// 선택된 필터 값을 저장할 상태 추가
|
||||
const [filterParams, setFilterParams] = React.useState<FilterParams>({});
|
||||
|
||||
// cellClick 함수 타입 지정
|
||||
const handleCellClick = (row: TableRow, col: TableHead) => {
|
||||
// 원하는 동작 구현
|
||||
console.log(row, col);
|
||||
};
|
||||
|
||||
const modalMinWidth = 'min-w-[900px]';
|
||||
const modalMaxWidth = 'max-w-6xl';
|
||||
|
||||
// 모달 높이 동적 계산
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [dynamicHeight, setDynamicHeight] = React.useState<number | undefined>(undefined);
|
||||
|
||||
// 구분(basicType)이 바뀔 때마다 queryParams, filterParams 초기화
|
||||
React.useEffect(() => {
|
||||
// filterParams 초기화
|
||||
setFilterParams({});
|
||||
set시설용량최소('');
|
||||
set시설용량최대('');
|
||||
set가동개시_시작_년('');
|
||||
set가동개시_시작_월('');
|
||||
set가동개시_시작_일('');
|
||||
set가동개시_종료_년('');
|
||||
set가동개시_종료_월('');
|
||||
set가동개시_종료_일('');
|
||||
set하수처리구역명('');
|
||||
set하수처리시설명('');
|
||||
set시도(0);
|
||||
set시군구(0);
|
||||
set처리구역('');
|
||||
set설치년도1('');
|
||||
set설치년도2('');
|
||||
// queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음
|
||||
}, [basicType]);
|
||||
|
||||
// 필터 값이 변경될 때 쿼리 파라미터 업데이트
|
||||
React.useEffect(() => {
|
||||
const params: FilterParams = {};
|
||||
|
||||
if (하수처리구역명) {
|
||||
params.swtrmt_zone_nm = 하수처리구역명;
|
||||
}
|
||||
|
||||
// 시도 선택 시 (0이 아닌 경우)
|
||||
if (시도 !== 0) {
|
||||
const selectedSido = sidoList.find(item => item.id === 시도);
|
||||
if (selectedSido) {
|
||||
params.ctpv_cd = selectedSido.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 시군구 선택 시 (0이 아닌 경우)
|
||||
if (시군구 !== 0) {
|
||||
const selectedSigungu = (sigunguMap[시도] || []).find((item: { id: number; name: string }) => item.id === 시군구);
|
||||
if (selectedSigungu) {
|
||||
params.sgg_cd = selectedSigungu.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 하수처리시설명이 있으면 추가
|
||||
if (하수처리시설명) {
|
||||
params.swtrmt_fclt_nm = 하수처리시설명;
|
||||
}
|
||||
|
||||
setFilterParams(params);
|
||||
}, [하수처리구역명, 시도, 시군구, 하수처리시설명]);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setDynamicHeight(contentRef.current.scrollHeight + 60); // 여유 padding
|
||||
}
|
||||
}, [activeTab, showTable, curPage, tableData.length]);
|
||||
|
||||
|
||||
// 검색 API 호출 함수
|
||||
const fetchFilteredData = () => {
|
||||
const queryParams = new URLSearchParams({
|
||||
lyrTypeNm: basicType
|
||||
});
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
|
||||
// 구분별 분기 처리
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') {
|
||||
// 하수처리구역
|
||||
// 시도
|
||||
if (filterParams.ctpv_cd) {
|
||||
cqlFilters.push(`ctpv_cd='${filterParams.ctpv_cd}'`);
|
||||
}
|
||||
// 시군구
|
||||
if (filterParams.sgg_cd) {
|
||||
cqlFilters.push(`sgg_cd='${filterParams.sgg_cd}'`);
|
||||
}
|
||||
// 하수처리구역명
|
||||
if (filterParams.swtrmt_zone_nm) {
|
||||
cqlFilters.push(`swtrmt_zone_nm ILIKE '%${filterParams.swtrmt_zone_nm}%'`);
|
||||
}
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else if (basicType === 'tb_il_swage_prcs_fclt') {
|
||||
// 하수처리시설
|
||||
// 가동개시일자 범위 필터 추가
|
||||
// 기본값: 19000101 ~ 오늘
|
||||
const startDate = getDateString(가동개시_시작_년, 가동개시_시작_월, 가동개시_시작_일, '19000101');
|
||||
const endDate = getDateString(가동개시_종료_년, 가동개시_종료_월, 가동개시_종료_일, `${todayYear}${todayMonth}${todayDate}`);
|
||||
cqlFilters.push(`oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`);
|
||||
// 시설용량 범위(fclt_cpct) 필터 추가
|
||||
if (시설용량최소 && 시설용량최대) cqlFilters.push(`fclt_cpct BETWEEN ${시설용량최소} AND ${시설용량최대}`);
|
||||
else if (시설용량최소) cqlFilters.push(`fclt_cpct >= ${시설용량최소}`);
|
||||
else if (시설용량최대) cqlFilters.push(`fclt_cpct <= ${시설용량최대}`);
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else {
|
||||
// 기타 구분(예: 하수관로, 맨홀 등) - 필요시 추가
|
||||
// 예시: 이름으로 검색
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
}
|
||||
|
||||
// 파라미터 이름 오타 주의! (Swagger와 동일하게)
|
||||
if (cqlFilters.length > 0) {
|
||||
queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
}
|
||||
|
||||
console.log('API URL:', `${API_URL}/v1/map/features?${queryParams.toString()}`);
|
||||
axios.get(`${API_URL}/v1/map/features?${queryParams.toString()}`)
|
||||
.then(res => {
|
||||
// 결과값을 표에 사용할 수 있도록 상태에 저장
|
||||
console.log('필터링된 API 결과:', res.data.length);
|
||||
console.log('필터링된 API 결과:', res.data);
|
||||
setTableData(res.data || []); // res.data 구조에 따라 조정 필요
|
||||
setShowTable(true);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('API ERROR:', err);
|
||||
setTableData([]); // 에러 시 빈 배열로 초기화
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const renderBody = () => (
|
||||
<>
|
||||
{/* 탭 영역 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
className={`px-4 py-2 rounded-t border border-b-0 text-base font-semibold
|
||||
${activeTab === tab.key
|
||||
? 'bg-[#bfcfe6] text-black'
|
||||
: 'bg-[#e6e6e6] text-gray-700'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: '#888',
|
||||
borderBottom: activeTab === tab.key ? '2px solid white' : undefined,
|
||||
minWidth: 140,
|
||||
fontSize: '1.1rem',
|
||||
height: '2.4rem', // 글자 크기에서 약간의 여백만 있도록 높이 조정
|
||||
}}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 기본정보 탭 */}
|
||||
{activeTab === 'basic' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 구분 */}
|
||||
<div className="flex justify-between items-center mb-3 gap-4">
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">구분</label>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1"
|
||||
value={basicType}
|
||||
onChange={e => setBasicType(e.target.value)}
|
||||
>
|
||||
{BASIC_TYPES.map(opt => (
|
||||
<option key={opt.key} value={opt.key}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* 분기처리: 하수처리구역 */}
|
||||
{basicType === 'tb_ms_swage_prcs_zone' && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시도</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시도} onChange={e => set시도(parseInt(e.target.value))}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold text-xl mr-2">시군구</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(parseInt(e.target.value))}>
|
||||
{selectedSigunguList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리구역명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리구역명} onChange={e => set하수처리구역명(e.target.value)} />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 분기처리: 하수처리시설 */}
|
||||
{basicType === 'tb_il_swage_prcs_fclt' && (
|
||||
<div className="mb-3">
|
||||
{/* 가동개시일자 */}
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">가동개시일자</label>
|
||||
{/* 시작일자 */}
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_시작_년}
|
||||
onChange={e => set가동개시_시작_년(e.target.value)}
|
||||
>
|
||||
<option value="">년</option>
|
||||
{Array.from({ length: today.getFullYear() - 1900 + 1 }, (_, i) => {
|
||||
const y = (today.getFullYear() - i).toString();
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_시작_월}
|
||||
onChange={e => set가동개시_시작_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_시작_일}
|
||||
onChange={e => set가동개시_시작_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
<span className="mx-1">~</span>
|
||||
{/* 종료일자 */}
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_종료_년}
|
||||
onChange={e => set가동개시_종료_년(e.target.value)}
|
||||
>
|
||||
<option value="">년</option>
|
||||
{Array.from({ length: today.getFullYear() - 1900 + 1 }, (_, i) => {
|
||||
const y = (today.getFullYear() - i).toString();
|
||||
// 시작년이 선택된 경우, 종료년은 시작년 이상만 표시
|
||||
if (가동개시_시작_년 && parseInt(y) < parseInt(가동개시_시작_년)) return null;
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_종료_월}
|
||||
onChange={e => set가동개시_종료_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월이 선택된 경우, 종료년이 시작년과 같으면 시작월 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
parseInt(m) < parseInt(가동개시_시작_월)
|
||||
) return null;
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_종료_일}
|
||||
onChange={e => set가동개시_종료_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월/일이 선택된 경우, 종료년/월이 시작년/월과 같으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월이 선택된 경우, 종료년/월이 시작년/월과 같고 시작일이 선택되었으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월만 선택된 경우, 종료년/월이 시작년/월과 같으면 모든 일 표시(위 조건에 포함)
|
||||
// 시작년만 선택된 경우, 종료년이 시작년과 같으면 모든 월/일 표시(위 조건에 포함)
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
</div>
|
||||
{/* 시설용량 */}
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시설용량</label>
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded text-xl px-2 py-1 mr-2 w-40 placeholder:text-center"
|
||||
placeholder="최소"
|
||||
value={시설용량최소}
|
||||
onChange={e => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||||
set시설용량최소(val);
|
||||
}}
|
||||
/>
|
||||
<span className="text-xl mr-2">~</span>
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded text-xl px-2 py-1 mr-4 w-40 placeholder:text-center"
|
||||
placeholder="최대"
|
||||
value={시설용량최대}
|
||||
onChange={e => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||||
set시설용량최대(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* 하수처리시설명 */}
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 운영정보 탭 */}
|
||||
{activeTab === 'operation' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
<div className="flex items-center gap-6 mb-6">
|
||||
<label className="w-24 font-semibold text-base">업무구분</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={workType}
|
||||
onChange={e => setWorkType(e.target.value)}
|
||||
style={{ minWidth: 140 }}
|
||||
>
|
||||
{WORK_TYPES.map(w => (
|
||||
<option key={w.key} value={w.key}>{w.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="w-16 text-center font-semibold text-base">년도</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={startYear}
|
||||
onChange={e => setStartYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-2 text-lg">~</span>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={endYear}
|
||||
onChange={e => setEndYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={() => setShowTable(true)}>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 하수도정비기본계획보고서서 탭 */}
|
||||
{activeTab === 'report' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 시도, 시군구, 하수처리구역 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.name}>{opt.id}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{/* {sigunguMap.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))} */}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<input className="border rounded px-2 py-1 w-24" placeholder="0000" />
|
||||
</div>
|
||||
{/* 승인년도 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">승인년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
<div className="border-t pt-6 space-y-2" style={{ maxWidth: '100vw', maxHeight: '60vh', overflowX: 'auto'}}>
|
||||
{/* 선택된 탭의 label */}
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
{TABS.find(tab => tab.key === activeTab)?.label}
|
||||
</div>
|
||||
{/* "▶" + 선택된 탭 label + " 조회 결과" */}
|
||||
<div className="text-lg font-semibold mb-6">
|
||||
▶ {TABS.find(tab => tab.key === activeTab)?.label} 조회 결과
|
||||
</div>
|
||||
<UiTable2 headName='' tableData={tableData} headlist={dynamicHeadlist}
|
||||
pageSize={tableData.length < 10 ? tableData.length : 10}
|
||||
total={tableData.length} children={undefined}
|
||||
cellClick={() => { }} onPageChange={setCurPage}
|
||||
resetPageNo={false}
|
||||
cur={curPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
{/* </DialogContent>
|
||||
</Dialog>
|
||||
); */}
|
||||
|
||||
return (
|
||||
<UiModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
bodyClass={`
|
||||
${modalMaxWidth} ${modalMinWidth}
|
||||
transition-all duration-300
|
||||
w-full overflow-hidden
|
||||
`}
|
||||
body={renderBody()}
|
||||
headerTitle="검색"
|
||||
// 필요시 footerContent 등 추가
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(SearchModal);
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@common/components/ui";
|
||||
import React from "react";
|
||||
|
||||
interface InputSectionProps {
|
||||
title: string;
|
||||
data: { year: string; type: string }[];
|
||||
sectionKey: string;
|
||||
onInputChange: (section: string, index: number, field: string, value: string) => void;
|
||||
}
|
||||
|
||||
const InputSection: React.FC<InputSectionProps> = ({ title, data, sectionKey, onInputChange }) => {
|
||||
return (
|
||||
<div>
|
||||
{/* 섹션 제목 */}
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">{title}</h3>
|
||||
</div>
|
||||
{/* 테이블 */}
|
||||
<div className="card !m-[1.2rem] !p-0r">
|
||||
<Table variant="secondary" className="w-full">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">년도</TableHead>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">구분</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={row.year}
|
||||
onChange={(e) => onInputChange(sectionKey, index, "year", e.target.value)}
|
||||
className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm text-center leading-normal">
|
||||
{row.type}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputSection;
|
||||
@@ -0,0 +1,46 @@
|
||||
import smh from "../popupComponent/smh";
|
||||
import Ssc from "../popupComponent/Ssc";
|
||||
import SwtrmtFclt from "../popupComponent/SwtrmtFclt";
|
||||
|
||||
// 레이어 이름과 컴포넌트를 매핑
|
||||
const layerContentMap: { [key: string]: { component?: React.FC<any> ; props?: any; } } = {
|
||||
// TODO: gis데이터 추가되면 추가 작업 필요
|
||||
"하수처리시설": {
|
||||
component: SwtrmtFclt,
|
||||
},
|
||||
"하수처리구역": { },
|
||||
"중점우수토실":{component: Ssc,},
|
||||
"하수관로":{component: SwtrmtFclt,},
|
||||
"하수맨홀":{component: smh,},
|
||||
};
|
||||
|
||||
// 동적 컴포넌트 반환 함수
|
||||
export const getLayerContentComponent = (layerNm: string): React.FC => {
|
||||
const component = layerContentMap[layerNm];
|
||||
if (!component || !component.component) {
|
||||
// component가 없으면 null 반환
|
||||
return () => null;
|
||||
}
|
||||
return component.component;
|
||||
};
|
||||
// 사용자 입력 필드 (보수 및 갱신, 청소 및 준설, 점검 및 진단)
|
||||
export const handleInputChange = (section: string,
|
||||
index: number,
|
||||
field: string,
|
||||
value: string,
|
||||
dataMap: {
|
||||
[key: string]: {
|
||||
data: { year: string; type: string }[];
|
||||
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string }[]>>;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const target = dataMap[section];
|
||||
if (!target) return;
|
||||
|
||||
const updated = [...target.data];
|
||||
if (updated[index]) {
|
||||
updated[index][field as keyof typeof updated[number]] = value;
|
||||
}
|
||||
target.setData(updated);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
// 하수처리시설
|
||||
export const SwtrmtFcltCol = [
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
{ id: 'fcltCpct', title: '시설용량' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
{ id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
{ id: 'clsgYmd', title: '폐쇄일자' },
|
||||
{ id: 'prcsMsrcCd', title: '처리공법코드' },
|
||||
{ id: 'laeCd', title: '지자체코드' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'sareaCd', title: '권역코드' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
{ id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'daddr', title: '상세주소' },
|
||||
{ id: 'rdnmAddr', title: '도로명주소' },
|
||||
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
{ id: 'pllnArea', title: '부지면적' },
|
||||
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
|
||||
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
{ id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprsMsrcCn', title: '대표공법내용' },
|
||||
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
|
||||
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
|
||||
{ id: 'sttyCrtrSs', title: '법정기준SS' },
|
||||
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
|
||||
{ id: 'sttyCrtrTph', title: '법정기준총인' },
|
||||
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
|
||||
{ id: 'bsnNm', title: '유역명' },
|
||||
{ id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
|
||||
// 하수처리구역
|
||||
export const SwtrmtPrcsZoneCol = [
|
||||
{ id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설코드' },
|
||||
{ id: 'ctpvCd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggCd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
// 우수토실
|
||||
import Modal from '@/components/ui/modal';
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
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: "mainSscYn", title: "중점우수토실 대상여부", type: "select", options: ['Y', 'N'] },
|
||||
];
|
||||
const headlist = [
|
||||
{ id: 'startrain', title: '강우시작일시' }, { id: 'endrain', title: '강우종료일시' }, { id: '선행건기', title: '선행건기(h)' }, { id: '강우정보', title: '강우정보' }, { id: '누적강우량', title: '누적강우량(mm)' }, { id: '지속시간', title: '지속시간(h)' }, { id: '강우강도', title: '강우강도(mm/h)' },
|
||||
]
|
||||
const headlist3 = [{ id: 'nm', title: '회차' }, { id: 'date', title: '측정일자' }, { id: 'measure', title: '수량(m)' }, { id: 'bod', title: 'BOD(mg/L)' }, { id: 'toc', title: 'TOC(mg/L)' }, { id: '오염부하량', title: '오염부하량(kg)' }]
|
||||
const Ssc = () => {
|
||||
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
return (
|
||||
<div><VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={0} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}} />
|
||||
<div>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">중점우수토실 운영정보</h3>
|
||||
</div>
|
||||
<Button className='mx-auto block w-64 mt-3 py-2 text-white rounded-sm text-sm font-medium hover:bg-blue-700 bg-blue-600' onClick={openModal}>상세보기</Button>
|
||||
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
|
||||
<div className='grid grid-col-3'><UiTable headName='강우정보' tableData={[]} headlist={headlist} pageSize={0} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}></UiTable>
|
||||
<UiTable headName='측정정보' tableData={[]} headlist={[]} pageSize={0} total={0} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}> {/* 커스텀 헤더 */}
|
||||
<TableRow>
|
||||
<TableHead colSpan={5} className="text-center bg-blue-100 font-bold">
|
||||
수량
|
||||
</TableHead>
|
||||
<TableHead colSpan={3} className="text-center bg-blue-100 font-bold">
|
||||
수질
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead className="text-center">측정시작일시</TableHead>
|
||||
<TableHead className="text-center">측정종료일시</TableHead>
|
||||
<TableHead className="text-center">측정간격(분)</TableHead>
|
||||
<TableHead className="text-center">측정기기설치위치</TableHead>
|
||||
<TableHead className="text-center">유량계 종류 및 모델명</TableHead>
|
||||
<TableHead className="text-center">채수시작일시</TableHead>
|
||||
<TableHead className="text-center">채수종료일시</TableHead>
|
||||
<TableHead className="text-center">채수횟수</TableHead>
|
||||
</TableRow></UiTable>
|
||||
<UiTable headName='수량/수질측정 정보' tableData={[]} headlist={headlist3} pageSize={0} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}></UiTable></div>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Ssc
|
||||
@@ -0,0 +1,45 @@
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import React, { useState } from 'react'
|
||||
import InputSection from '../InputSection';
|
||||
import { handleInputChange } from '../common';
|
||||
// 하수처리시설
|
||||
// TODO: 컬럼에 보고서 엉 ? 이거 버튼누르면 팝업으로 상세화면 띄워주는 건지 확인 필요
|
||||
const inputColumn = [
|
||||
{ id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] },
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "basu", title: "배수구역", type: "input" },
|
||||
{ id: "bungu", title: "배수분구", type: "input" },
|
||||
{ id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] },
|
||||
{ id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] },
|
||||
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
|
||||
];
|
||||
const SwtrmtFclt = () => {
|
||||
const [repairData, setRepairData] = useState([
|
||||
{ year: "2025", type: "개대체" },
|
||||
{ year: "2025", type: "보수" },
|
||||
]);
|
||||
const [cleaningData, setCleaningData] = useState([
|
||||
{ year: "2025", type: "청소" },
|
||||
{ year: "2025", type: "준설" },
|
||||
]);
|
||||
const [inspectionData, setInspectionData] = useState([
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
]);
|
||||
const dataMap = {
|
||||
repair: { data: repairData, setData: setRepairData },
|
||||
cleaning: { data: cleaningData, setData: setCleaningData },
|
||||
inspection: { data: inspectionData, setData: setInspectionData },
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
<InputSection title="보수 및 갱신" data={repairData} sectionKey="repair" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
|
||||
<InputSection title="청소 및 준설" data={cleaningData} sectionKey="cleaning" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
|
||||
<InputSection title="점검 및 진단" data={inspectionData} sectionKey="inspection" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} /></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwtrmtFclt
|
||||
@@ -0,0 +1,21 @@
|
||||
// 하수맨홀
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import React from 'react'
|
||||
const inputColumn = [
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "basu", title: "배수구역", type: "input" },
|
||||
{ id: "bungu", title: "배수분구", type: "input" },
|
||||
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
|
||||
{id:'spec', title:'규격', type:'input'},
|
||||
];
|
||||
const Smh = () => {
|
||||
return (
|
||||
<div>
|
||||
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Smh;
|
||||
@@ -1,24 +1,19 @@
|
||||
'use client'
|
||||
import { VerticalTable } from "@/components/verticalTable";
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components/ui";
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { SwtrmtFcltCol } from "./table";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SwtrmtFcltCol, SwtrmtPrcsZoneCol } from "./components/common/table";
|
||||
import { useGisContext } from "@common/business_components/ui/map/Gis";
|
||||
import { getData } from "@/components/lib/api";
|
||||
const inputColumn = [
|
||||
{ id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] },
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "basu", title: "배수구역", type: "input" },
|
||||
{ id: "bungu", title: "배수분구", type: "input" },
|
||||
{ id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] },
|
||||
{ id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] },
|
||||
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
|
||||
];
|
||||
import { getLayerContentComponent } from "./components/common";
|
||||
// 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; };
|
||||
const featureId = featureData.getId(); // 피처의 속성 가져오기
|
||||
const featureId = featureData.getId(); // 피처의 id 가져오기
|
||||
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
|
||||
const [tableData, setTableData] = useState<any>(null); // 피처 데이터 상태
|
||||
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
|
||||
let [buttonChk,setButtonChk] = useState(true)
|
||||
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
|
||||
const getSelectedLayers = () => {
|
||||
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
|
||||
@@ -31,88 +26,45 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
});
|
||||
return featureLayer.get('lyrNm'); // 레이어 이름 출력
|
||||
};
|
||||
const [repairData, setRepairData] = useState([
|
||||
{ year: "2025", type: "개대체" },
|
||||
{ year: "2025", type: "보수" },
|
||||
]);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (featureId) {
|
||||
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
|
||||
setLayerNm(selectedLayerName); // 상태 업데이트
|
||||
|
||||
const [cleaningData, setCleaningData] = useState([
|
||||
{ year: "2025", type: "청소" },
|
||||
{ year: "2025", type: "준설" },
|
||||
]);
|
||||
|
||||
const [inspectionData, setInspectionData] = useState([
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
]);
|
||||
const handleInputChange = (section: string, index: number, field: string, value: string) => {
|
||||
const updateData = (data: { year: string; type: string }[], setData: React.Dispatch<React.SetStateAction<{ year: string; type: string }[]>>) => {
|
||||
const updated = [...data];
|
||||
if (updated[index]) {
|
||||
updated[index][field as keyof typeof updated[number]] = value;
|
||||
}
|
||||
setData(updated);
|
||||
};
|
||||
|
||||
if (section === "repair") {
|
||||
updateData(repairData, setRepairData);
|
||||
} else if (section === "cleaning") {
|
||||
updateData(cleaningData, setCleaningData);
|
||||
} else if (section === "inspection") {
|
||||
updateData(inspectionData, setInspectionData);
|
||||
const data = await getData(`v1/map/features/${featureId}`);
|
||||
switch (selectedLayerName) {
|
||||
case '하수처리시설':
|
||||
setHeadlist(SwtrmtFcltCol);
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '하수처리구역':
|
||||
setHeadlist(SwtrmtPrcsZoneCol)
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '중점우수토실':
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '우수토실':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수관로':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수맨홀':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setTableData(data);
|
||||
};
|
||||
}
|
||||
};
|
||||
async function transformFeatureDataToArray(featureId: any, keys: string[]) {
|
||||
const data = await getData(`v1/map/features/${featureId}`);
|
||||
// 필요한 키만 추출하여 새로운 객체 생성
|
||||
console.log("data", data);
|
||||
const transformedData = keys.reduce((acc, key) => {
|
||||
acc[key] = data[key] !== undefined ? data[key] : "N/A"; // 값이 없으면 "N/A"로 처리
|
||||
return acc;
|
||||
}, {} as Record<string, any>);
|
||||
|
||||
// 배열로 감싸서 반환
|
||||
return [transformedData];
|
||||
}
|
||||
const keys = SwtrmtFcltCol.map((column) => column.id);
|
||||
const transformedFeatureData = transformFeatureDataToArray(featureId, keys);
|
||||
useEffect(() => { setLayerNm(getSelectedLayers()) }, [featureId]);
|
||||
const renderSection = (title: string, data: { year: string; type: string }[], sectionKey: string) => (
|
||||
<div>
|
||||
{/* 섹션 제목 */}
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">{title}</h3>
|
||||
</div>
|
||||
{/* 테이블 */}
|
||||
<div className="card !m-[1.2rem] !p-0r">
|
||||
<Table variant="secondary" className="w-full">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">년도</TableHead>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">구분</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={row.year}
|
||||
onChange={(e) => handleInputChange(sectionKey, index, "year", e.target.value)}
|
||||
className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm text-center leading-normal">{row.type}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
fetchData();
|
||||
}, [featureId]);
|
||||
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) : null;
|
||||
return (
|
||||
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[1000]">
|
||||
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[200]">
|
||||
<div className="card-header bg-primary flex items-center justify-between p-4">
|
||||
<div className="flex mx-3 flex-col">
|
||||
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
|
||||
@@ -127,20 +79,15 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
<VerticalTable headName='LX 연계정보' tableData={[]} headlist={SwtrmtFcltCol} pageSize={20} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
<VerticalTable headName='LX 연계정보' tableData={[tableData]} headlist={headlist} pageSize={20} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
{renderSection("보수 및 갱신", repairData, "repair")}
|
||||
{renderSection("청소 및 준설", cleaningData, "cleaning")}
|
||||
{renderSection("점검 및 진단", inspectionData, "inspection")}
|
||||
</div>
|
||||
<div className="w-full mx-5 flex items-center justify-end space-x-2">
|
||||
<Button className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
|
||||
<div className="w-full mx-5 flex items-center justify-end space-x-2">
|
||||
{buttonChk ? <Button className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
|
||||
저장
|
||||
</Button>
|
||||
</Button> : null}
|
||||
</div>
|
||||
{ContentComponent && <ContentComponent />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
export const SwtrmtFcltCol = [
|
||||
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
|
||||
{ id: 'fclt_cpct', title: '시설용량' },
|
||||
{ id: 'rgn_expln', title: '지역설명' },
|
||||
{ id: 'cmcn_ymd', title: '준공일자' },
|
||||
{ id: 'oprtng_strt_ymd', title: '가동개시일자' },
|
||||
{ id: 'clsg_ymd', title: '폐쇄일자' },
|
||||
{ id: 'prcs_msrc_cd', title: '처리공법코드' },
|
||||
{ id: 'lae_cd', title: '지자체코드' },
|
||||
{ id: 'lae_nm', title: '지자체명' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'sarea_cd', title: '권역코드' },
|
||||
{ id: 'sarea_nm', title: '권역명' },
|
||||
{ id: 'lrge_sarea_nm', title: '대권역명' },
|
||||
{ id: 'rvr_nm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'daddr', title: '상세주소' },
|
||||
{ id: 'rdnm_addr', title: '도로명주소' },
|
||||
{ id: 'dtl_rdnm_addr', title: '상세도로명주소' },
|
||||
{ id: 'dschg_wtst_nm', title: '방류수계명' },
|
||||
{ id: 'plln_area', title: '부지면적' },
|
||||
{ id: 'mng_rprtv_bzenty_nm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmt_ppltn_cnt', title: '하수처리인구수' },
|
||||
{ id: 'prcs_zone_area_sum', title: '처리구역면적합계' },
|
||||
{ id: 'geom', title: 'geometry' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
{ id: 'oprtng_stts_cd', title: '가동상태코드' },
|
||||
{ id: 'rprs_msrc_cn', title: '대표공법내용' },
|
||||
{ id: 'stty_crtr_bod', title: '법정기준생물화학적산소요구량' },
|
||||
{ id: 'stty_crtr_toc', title: '법정기준TOC' },
|
||||
{ id: 'stty_crtr_ss', title: '법정기준SS' },
|
||||
{ id: 'stty_crtr_tn', title: '법정기준총질소' },
|
||||
{ id: 'stty_crtr_tph', title: '법정기준총인' },
|
||||
{ id: 'stty_crtr_clbcl_cnt', title: '법정기준대장균수' },
|
||||
{ id: 'bsn_nm', title: '유역명' },
|
||||
{ id: 'obsrvn_spt_cd', title: '관측지점코드' },
|
||||
];
|
||||
@@ -10,31 +10,44 @@ import proj4 from 'proj4';
|
||||
import { register } from 'ol/proj/proj4';
|
||||
import GisApp from './lib/gis.core';
|
||||
import GisContent from './GisContent';
|
||||
import GisLonLat from './GisLonLat';
|
||||
import GisToolBar from './GisToolBar';
|
||||
import GisTab from './GisTab';
|
||||
import MapToggleBtn from './MapToggleBtn';
|
||||
import FeatureClickEventPopup from '@/map/featureClickEventPopup';
|
||||
|
||||
export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null});
|
||||
export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null});
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
|
||||
function Gis({ treeData = [], isSearch = false }) {
|
||||
const mapRef = useRef();
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
const [gisMap, setGisMap] = useState(null);
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [featureData,setFeatureData] = useState(null) // 선택된 피쳐 데이터
|
||||
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
//const [treeData, setTreeData] = useState([]);
|
||||
const checkedLayerIds = useRef([]);
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null)
|
||||
};
|
||||
};
|
||||
const handleFeatureSelect = (data) => {
|
||||
console.log('check',data)
|
||||
setFeatureData(data); // 선택된 피처 데이터 저장
|
||||
setIsOpen(true); // 팝업 열기
|
||||
};
|
||||
setFeatureData(data); // 선택된 피처 데이터 저장
|
||||
setIsOpen(true); // 팝업 열기
|
||||
};
|
||||
|
||||
// SearchModal이 열릴 때 featureClickEventPopup 닫기
|
||||
useEffect(() => {
|
||||
if (isSearchModalOpen && isOpen) {
|
||||
setIsOpen(false);
|
||||
setFeatureData(null);
|
||||
}
|
||||
}, [isSearchModalOpen]);
|
||||
|
||||
//맵 생성 및 컨트롤 추가
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
@@ -46,8 +59,8 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
],
|
||||
]);
|
||||
register(proj4);
|
||||
|
||||
//맵 생성
|
||||
|
||||
//맵 생성성
|
||||
const map = new OlMap({
|
||||
layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']],
|
||||
renderer: 'webgl',
|
||||
@@ -72,8 +85,8 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
maxZoom: gisCore.defaults.maxZoom,
|
||||
}),
|
||||
});
|
||||
|
||||
//미니맵 생성
|
||||
|
||||
//미니맵
|
||||
const overviewMap = new OverviewMap({
|
||||
layers: [GisApp.Module.core.baseMiniMapLayers['base']],
|
||||
view: new View({
|
||||
@@ -84,33 +97,33 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
collapseLabel: '\u00BB',
|
||||
label: '\u00AB',
|
||||
});
|
||||
|
||||
map.addControl(overviewMap);
|
||||
gisCore.__proto__.map = map;
|
||||
//스케일 생성
|
||||
|
||||
gisCore.scaleControl();
|
||||
//배경지도 설정
|
||||
gisCore.changeBaseLayer('base');
|
||||
setGisMap(map);
|
||||
setMapObject({ map: map });
|
||||
}, []);
|
||||
|
||||
// 레이어 정보 설정
|
||||
useEffect(() => {
|
||||
//if (!locgovCd) return null;
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
gisCore.deleteAllOverlay();
|
||||
gisCore.deleteAllLayer();
|
||||
|
||||
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
|
||||
GisApp.layerCode = layerCode;
|
||||
|
||||
|
||||
//setTreeData(data);
|
||||
const layerItems = getLayerItems(layerCode);
|
||||
//setEditLayerItems(prev => [...layerItems])
|
||||
}, [treeData]);
|
||||
|
||||
//레이어 정렬
|
||||
const getLayerItems= (layerCode) => {
|
||||
const layerItems = Object.entries(layerCode)
|
||||
.filter(([key, obj], idx) => obj?.updtPsblYn === 'Y')
|
||||
.map(([key, obj]) => ({
|
||||
...obj,
|
||||
name: obj.lyrNm,
|
||||
@@ -125,28 +138,33 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
return layerItems;
|
||||
};
|
||||
|
||||
//피쳐 클릭 이벤트 핸들러
|
||||
const handleFeatureClick = useCallback((feature) => {
|
||||
console.log('Gis.js: 피처 클릭됨:', feature.getId());
|
||||
console.log('Gis.js: 피처 클릭됨:', feature.getProperties());
|
||||
//우측 피쳐 상세 팝업 호출
|
||||
}, []);
|
||||
if (mapObject.map) {
|
||||
try {
|
||||
gisMap = mapObject.map;
|
||||
} catch (e) {
|
||||
console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
{isOpen && featureData && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
<GisContent onFeatureSelect={handleFeatureSelect} />
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} />
|
||||
</div>
|
||||
</GisContext.Provider>
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} />
|
||||
<GisContent onFeatureSelect={handleFeatureSelect}/>
|
||||
{isOpen && featureData && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
{/* <GisLonLat/>
|
||||
<GisToolBar/>
|
||||
<MapToggleBtn/> */}
|
||||
</div>
|
||||
</GisContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs';
|
||||
import GisTree from './GisTree';
|
||||
import GisSearch from './GisSearch';
|
||||
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
|
||||
import SearchModal from './SearchModal';
|
||||
import SearchModal from '@/map/SearchModal';
|
||||
|
||||
|
||||
function GisTab({ visible, setVisible, isSearch }) {
|
||||
function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen }) {
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogClose,
|
||||
} from '@common/components/ui/dialog';
|
||||
import { Button } from '@common/components';
|
||||
import { UiTable } from '../../ui';
|
||||
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
const TABS = [
|
||||
{ key: 'basic', label: '기본정보' },
|
||||
{ key: 'operation', label: '운영정보' },
|
||||
{ key: 'report', label: '하수도정비기본계획보고서' },
|
||||
];
|
||||
|
||||
const WORK_TYPES = [
|
||||
{ key: 'maintenance', label: '보수 및 갱신' },
|
||||
{ key: 'cleaning', label: '청소 및 준설' },
|
||||
{ key: 'inspection', label: '점검 및 진단' },
|
||||
];
|
||||
|
||||
const YEARS = Array.from({ length: 150 }, (_, i) => `${2025 - i}`);
|
||||
|
||||
const tableHeadlist = [
|
||||
{ id: 'no', title: 'NO' },
|
||||
{ id: 'fileNm', title: '파일명' },
|
||||
{ id: 'manageYear', title: '년도' },
|
||||
{ id: 'bungi', title: '분기' },
|
||||
{ id: 'registDt', title: '등록 일자' },
|
||||
{ id: 'registUsr', title: '등록자' }
|
||||
]
|
||||
|
||||
const tableData = [
|
||||
{ 'no': '1', 'fileNm': '파일명', 'manageYear': '2023', 'registDt': '2025-04-16', 'registUsr': 'me', 'bungi': '1분기' },
|
||||
{ 'no': '2', 'fileNm': '파일명', 'manageYear': '2024', 'registDt': '2025-04-16', 'registUsr': 'me2', 'bungi': '1분기' },
|
||||
{ 'no': '3', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '4', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '5', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '6', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '7', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '8', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '9', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '10', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' }
|
||||
]
|
||||
|
||||
const BASIC_TYPES = [
|
||||
{ key: 'SEWER_PIPE', label: '하수관로' },
|
||||
{ key: 'TREATMENT_FACILITY', label: '하수처리시설' },
|
||||
{ key: 'TREATMENT_AREA', label: '하수처리구역' },
|
||||
{ key: 'MANHOLE', label: '하수맨홀' },
|
||||
{ key: 'WASTEWATER_PUMP', label: '오수펌프' },
|
||||
{ key: 'RAINWATER_PUMP', label: '빗물펌프' },
|
||||
{ key: 'OUTFALL', label: '우수토실' },
|
||||
{ key: 'RETENTION_FACILITY', label: '하수저류시설' },
|
||||
{ key: 'PRIORITY_OUTFALL', label: '중점우수토실' },
|
||||
];
|
||||
|
||||
const sidoList = ['선택', '서울', '경기', '부산'];
|
||||
const sigunguList = ['선택', '강남구', '서초구', '수원시'];
|
||||
const 처리구역List = ['선택', 'A', 'B'];
|
||||
const 하수관용도List = ['선택', '우수', '오수'];
|
||||
const 관재질List = ['선택', 'PVC', 'PE'];
|
||||
const 설치년도List = ['선택', ...YEARS];
|
||||
const 경과년도List = ['선택', '1', '2', '3'];
|
||||
|
||||
// tableHeadlist, tableData 타입 예시 (실제 타입에 맞게 수정)
|
||||
type TableHead = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
type TableRow = Record<string, any>;
|
||||
|
||||
interface SearchModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
const [activeTab, setActiveTab] = React.useState('operation');
|
||||
const [workType, setWorkType] = React.useState(WORK_TYPES[0]?.key ?? '');
|
||||
const [startYear, setStartYear] = React.useState('');
|
||||
const [endYear, setEndYear] = React.useState('');
|
||||
const [showTable, setShowTable] = React.useState(false);
|
||||
const [curPage, setCurPage] = React.useState(1);
|
||||
const [basicType, setBasicType] = React.useState(BASIC_TYPES[0]?.key ?? '');
|
||||
const [관리번호, set관리번호] = React.useState('');
|
||||
const [시도, set시도] = React.useState('');
|
||||
const [시군구, set시군구] = React.useState('');
|
||||
const [처리구역, set처리구역] = React.useState('');
|
||||
const [하수관용도, set하수관용도] = React.useState('');
|
||||
const [관재질, set관재질] = React.useState('');
|
||||
const [설치년도1, set설치년도1] = React.useState('');
|
||||
const [설치년도2, set설치년도2] = React.useState('');
|
||||
const [경과년도, set경과년도] = React.useState('');
|
||||
const [연장1, set연장1] = React.useState('');
|
||||
const [연장2, set연장2] = React.useState('');
|
||||
|
||||
// cellClick 함수 타입 지정
|
||||
const handleCellClick = (row: TableRow, col: TableHead) => {
|
||||
// 원하는 동작 구현
|
||||
console.log(row, col);
|
||||
};
|
||||
|
||||
const modalMinWidth = 'min-w-[900px]';
|
||||
const modalMaxWidth = 'max-w-6xl';
|
||||
|
||||
// 모달 높이 동적 계산
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [dynamicHeight, setDynamicHeight] = React.useState<number | undefined>(undefined);
|
||||
|
||||
// API 호출 테스트 (axios 사용)
|
||||
React.useEffect(() => {
|
||||
// API_URL root 경로 확인 테스트
|
||||
console.log('API_URL:', API_URL);
|
||||
|
||||
// lyrTypeNm과 cql_filter에 따른 API 호출 테스트
|
||||
axios.get(`${API_URL}/v1/map/features?lyrTypeNm=tb_ms_swage_prcs_zone&cql_filter=ctpv_nm='전라북도'`)
|
||||
.then(res => {
|
||||
console.log('API DATA:', res.data); // 실제 JSON 데이터 출력
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('API ERROR:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setDynamicHeight(contentRef.current.scrollHeight + 60); // 여유 padding
|
||||
}
|
||||
}, [activeTab, showTable, curPage, tableData.length]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={`
|
||||
${modalMaxWidth} ${modalMinWidth}
|
||||
transition-all duration-300
|
||||
w-full overflow-hidden
|
||||
`}
|
||||
style={{ minHeight: showTable ? 600 : 220 }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검색</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 탭 영역 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
className={`px-4 py-2 rounded-t border border-b-0 text-base font-semibold
|
||||
${activeTab === tab.key
|
||||
? 'bg-[#bfcfe6] text-black'
|
||||
: 'bg-[#e6e6e6] text-gray-700'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: '#888',
|
||||
borderBottom: activeTab === tab.key ? '2px solid white' : undefined,
|
||||
minWidth: 140,
|
||||
fontSize: '1.1rem'
|
||||
}}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 기본정보 탭 */}
|
||||
{activeTab === 'basic' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 구분 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">구분</label>
|
||||
<select className="border rounded px-2 py-1" value={basicType} onChange={e => setBasicType(e.target.value)}>
|
||||
{BASIC_TYPES.map(opt => (
|
||||
<option key={opt.key} value={opt.key}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 관리번호, 시도, 시군구 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">관리번호</label>
|
||||
<input className="border rounded px-2 py-1 mr-4" value={관리번호} onChange={e => set관리번호(e.target.value)} />
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{sigunguList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 처리구역, 하수관용도, 관재질 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수관용도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={하수관용도} onChange={e => set하수관용도(e.target.value)}>
|
||||
{하수관용도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">관재질</label>
|
||||
<select className="border rounded px-2 py-1" value={관재질} onChange={e => set관재질(e.target.value)}>
|
||||
{관재질List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 설치년도, 경과년도 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">설치년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1 mr-6" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">경과년도</label>
|
||||
<select className="border rounded px-2 py-1" value={경과년도} onChange={e => set경과년도(e.target.value)}>
|
||||
{경과년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 연장 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">연장(m)</label>
|
||||
<input className="border rounded px-2 py-1 mr-2" value={연장1} onChange={e => set연장1(e.target.value)} />
|
||||
<span className="mx-1">~</span>
|
||||
<input className="border rounded px-2 py-1" value={연장2} onChange={e => set연장2(e.target.value)} />
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 운영정보 탭 */}
|
||||
{activeTab === 'operation' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
<div className="flex items-center gap-6 mb-6">
|
||||
<label className="w-24 font-semibold text-base">업무구분</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={workType}
|
||||
onChange={e => setWorkType(e.target.value)}
|
||||
style={{ minWidth: 140 }}
|
||||
>
|
||||
{WORK_TYPES.map(w => (
|
||||
<option key={w.key} value={w.key}>{w.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="w-16 text-center font-semibold text-base">년도</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={startYear}
|
||||
onChange={e => setStartYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-2 text-lg">~</span>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={endYear}
|
||||
onChange={e => setEndYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={() => setShowTable(true)}>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 하수도정비기본계획보고서서 탭 */}
|
||||
{activeTab === 'report' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 시도, 시군구, 하수처리구역 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{sigunguList.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<input className="border rounded px-2 py-1 w-24" placeholder="0000" />
|
||||
</div>
|
||||
{/* 승인년도 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">승인년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
<div className="border-t pt-6 space-y-2">
|
||||
{/* 선택된 탭의 label */}
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
{TABS.find(tab => tab.key === activeTab)?.label}
|
||||
</div>
|
||||
{/* "▶" + 선택된 탭 label + " 조회 결과" */}
|
||||
<div className="text-lg font-semibold mb-6">
|
||||
▶ {TABS.find(tab => tab.key === activeTab)?.label} 조회 결과
|
||||
</div>
|
||||
<UiTable headName='' tableData={tableData} headlist={tableHeadlist}
|
||||
pageSize={10} total={20} children={undefined}
|
||||
cellClick={() => { }} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.')
|
||||
}}>
|
||||
</UiTable>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(SearchModal);
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import UiPageNations from "./uiPageNations";
|
||||
|
||||
type UiTableProps = {
|
||||
headName: React.ReactNode | undefined;
|
||||
tableData: { [key: string]: string | number | boolean | object }[];
|
||||
headlist: UiHeadProps[];
|
||||
pageSize: number;
|
||||
total: number;
|
||||
children: React.ReactNode | undefined;
|
||||
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
|
||||
onPageChange: (page: number) => void;
|
||||
className?: string;
|
||||
resetPageNo?: boolean;
|
||||
cur?: number;
|
||||
}
|
||||
|
||||
type UiHeadProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
align?: string;
|
||||
}
|
||||
|
||||
export function UiTable2({ tableData, headlist, pageSize, total, children, headName, cellClick, onPageChange, className = '', resetPageNo = false, cur }: UiTableProps) {
|
||||
const [idx, setIdx] = useState<number>(-1);
|
||||
|
||||
const [curState, setCurState] = useState<number>(cur ?? 1);
|
||||
useEffect(() => {
|
||||
setCurState(cur ?? 1);
|
||||
}, [cur]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetPageNo) {
|
||||
setCurState(1);
|
||||
onPageChange(1);
|
||||
}
|
||||
}, [onPageChange, resetPageNo]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurState(page);
|
||||
onPageChange(page);
|
||||
}
|
||||
|
||||
const handleCellClick = (index: number, headId: string) => {
|
||||
if (cellClick) {
|
||||
cellClick(index, headId);
|
||||
setIdx(index);
|
||||
}
|
||||
};
|
||||
|
||||
const startIdx = (curState - 1) * pageSize;
|
||||
const endIdx = startIdx + pageSize;
|
||||
const pageData = tableData.slice(startIdx, endIdx);
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">{headName}</h3>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`card !m-[1.2rem] !p-0r w-full relative ${className}`}>
|
||||
<div>
|
||||
<Table variant="secondary" className="w-full">
|
||||
{/* 스크롤 관련 div 제거 */}
|
||||
<TableHeader className="bg-white">
|
||||
{children ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<TableRow>
|
||||
{headlist?.map((head) => (
|
||||
<TableHead key={head.id}
|
||||
className="min-w-[120px] max-w-[180px] w-[150px]
|
||||
text-center whitespace-nowrap overflow-hidden
|
||||
text-ellipsis bg-white">
|
||||
{head.title}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableHeader>
|
||||
<TableBody className='text-center'>
|
||||
{pageData.map((el: { [key: string]: string | number | boolean | object }, index) => (
|
||||
<TableRow key={index + "row"}>
|
||||
{headlist?.map((head) => (
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={() => handleCellClick(startIdx + index, head.id)}
|
||||
key={head.id + index}
|
||||
>
|
||||
{typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{/* 페이지네이션을 sticky 하단에 고정 */}
|
||||
{total > 0 && (
|
||||
<div className="sticky left-0 right-0 bottom-0 bg-white z-20 border-t border-gray-200">
|
||||
<UiPageNations total={total} cur={curState} pageSize={pageSize} onPageChange={handlePageChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -30,5 +30,5 @@
|
||||
"**/*.mjs",
|
||||
"**/*.jsx",
|
||||
"**/*.json"
|
||||
]
|
||||
, "../../../apps/sn/app/map/SearchModal.tsx" ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user