하수관로 정보팝업 구현
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@common/components";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
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;
|
||||
isEditable?: boolean;
|
||||
};
|
||||
|
||||
type UiHeadProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
align?: string;
|
||||
type?: string;
|
||||
options?: string[];
|
||||
};
|
||||
|
||||
export function VerticalTable({
|
||||
tableData,
|
||||
headlist,
|
||||
pageSize,
|
||||
total,
|
||||
children,
|
||||
headName,
|
||||
cellClick,
|
||||
onPageChange,
|
||||
className = "",
|
||||
resetPageNo = false,
|
||||
isEditable = false,
|
||||
}: UiTableProps) {
|
||||
const [cur, setCur] = useState<number>(1);
|
||||
const [formData, setFormData] = useState<{ [key: string]: string | number | boolean }>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (resetPageNo) {
|
||||
setCur(1);
|
||||
onPageChange(1);
|
||||
}
|
||||
}, [onPageChange, resetPageNo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length > 0 && tableData[0]) {
|
||||
setFormData(tableData[0] as { [key: string]: string | number | boolean });
|
||||
}
|
||||
}, [tableData]);
|
||||
|
||||
const handleInputChange = (id: string, value: string | number | boolean) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[id]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCur(page);
|
||||
onPageChange(page);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">{headName}</h3>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
|
||||
<Table variant="secondary">
|
||||
<TableBody className="text-center">
|
||||
{headlist?.map((head) => (
|
||||
<TableRow key={head.id}>
|
||||
{/* 컬럼 제목 */}
|
||||
<TableCell className="font-bold bg-blue-100 text-xs">
|
||||
{head.title}
|
||||
</TableCell>
|
||||
{/* 데이터 값 또는 입력 필드 */}
|
||||
<TableCell className="text-xs">
|
||||
{isEditable ? (
|
||||
head.type === "select" ? (
|
||||
<select
|
||||
value={formData[head.id] ?? "-"}
|
||||
onChange={(e) => handleInputChange(head.id, e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
|
||||
>
|
||||
{head.options?.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : head.type === "input" ? (
|
||||
<input
|
||||
type="text"
|
||||
value={formData[head.id] ?? ""}
|
||||
onChange={(e) => handleInputChange(head.id, e.target.value)}
|
||||
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
|
||||
/>
|
||||
) : (
|
||||
<span>{formData[head.id] ?? "-"}</span>
|
||||
)
|
||||
) : (
|
||||
<span>{formData[head.id] ?? "-"}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{total > 0 && (
|
||||
<UiPageNations
|
||||
total={total}
|
||||
cur={cur}
|
||||
pageSize={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
'use client'
|
||||
import { VerticalTable } from "@/components/verticalTable";
|
||||
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components/ui";
|
||||
import { useState } from "react";
|
||||
const RxTableColumn = [
|
||||
{ id: 'FTR_CDE', title: '지형지물부호' },
|
||||
{ id: 'FTR_IDN', title: '관리번호' },
|
||||
{ id: 'HJD_CDE', title: '행정읍면동코드' },
|
||||
{ id: 'SHT_NUM', title: '도엽번호' },
|
||||
{ id: 'MNG_CDE', title: '관리기관코드' },
|
||||
{ id: 'IST_YMD', title: '설치일자' },
|
||||
{ id: 'SBA_CDE', title: '하수관용도' },
|
||||
{ id: 'MOP_CDE', title: '관재질' },
|
||||
{ id: 'LIT_CDE', title: '규모' },
|
||||
{ id: 'FOR_CDE', title: '시설물형태' },
|
||||
{ id: 'STD_DIP', title: '관경' },
|
||||
{ id: 'STD_HOL', title: '가로길이' },
|
||||
{ id: 'STD_VEL', title: '세로길이' },
|
||||
{ id: 'BYC_LEN', title: '연장' },
|
||||
{ id: 'BEG_DEP', title: '시점깊이' },
|
||||
{ id: 'END_DEP', title: '종점깊이' },
|
||||
{ id: 'SBK_HSL', title: '시점관저고' },
|
||||
{ id: 'SBL_HSL', title: '종점관저고' },
|
||||
{ id: 'PIP_SLP', title: '평균구배' },
|
||||
{ id: 'SPH_LIN', title: '차선통로수' },
|
||||
{ id: 'BST_ARA', title: '우수배수면적' },
|
||||
{ id: 'DRT_ARA', title: '오수배수면적' },
|
||||
{ id: 'SBQ_SPD', title: '우천시유속' },
|
||||
{ id: 'SBR_SPD', title: '청천시유속' },
|
||||
{ id: 'CNT_NUM', title: '공사번호' },
|
||||
{ id: 'BOM_CDE', title: '시점맨홀지형지물부호' },
|
||||
{ id: 'BOM_IDN', title: '시점맨홀관리번호' },
|
||||
{ id: 'EOM_CDE', title: '중점맨홀지형지물부호' },
|
||||
{ id: 'EOM_IDN', title: '중점맨홀관리번호' },
|
||||
{ id: 'SYS_CHK', title: '대장초기화여부' },
|
||||
{ id: 'PIP_LBL', title: '관라벨' },
|
||||
{ id: 'IQT_CDE', title: '탐사구분' },
|
||||
{ id: 'ORG_IDN', title: '기관관리번호' },
|
||||
{ id: 'SRV_CHK', title: '인입관 유무' }
|
||||
];
|
||||
const tableData = [
|
||||
{
|
||||
FTR_CDE: "001",
|
||||
FTR_IDN: "관리번호001",
|
||||
HJD_CDE: "행정코드001",
|
||||
SHT_NUM: "도엽번호001",
|
||||
MNG_CDE: "관리기관001",
|
||||
IST_YMD: "2025-04-16",
|
||||
SBA_CDE: "하수관용도001",
|
||||
MOP_CDE: "관재질001",
|
||||
LIT_CDE: "규모001",
|
||||
FOR_CDE: "시설물형태001",
|
||||
STD_DIP: "300mm",
|
||||
STD_HOL: "500mm",
|
||||
STD_VEL: "1.2m/s",
|
||||
BYC_LEN: "100m",
|
||||
BEG_DEP: "1.5m",
|
||||
END_DEP: "2.0m",
|
||||
SBK_HSL: "50m",
|
||||
SBL_HSL: "48m",
|
||||
PIP_SLP: "2%",
|
||||
SPH_LIN: "2개",
|
||||
BST_ARA: "200㎡",
|
||||
DRT_ARA: "150㎡",
|
||||
SBQ_SPD: "3.5m/s",
|
||||
SBR_SPD: "2.0m/s",
|
||||
CNT_NUM: "공사번호001",
|
||||
BOM_CDE: "맨홀지형지물부호001",
|
||||
BOM_IDN: "맨홀관리번호001",
|
||||
EOM_CDE: "중점맨홀지형지물부호001",
|
||||
EOM_IDN: "중점맨홀관리번호001",
|
||||
SYS_CHK: "초기화여부001",
|
||||
PIP_LBL: "관라벨001",
|
||||
IQT_CDE: "탐사구분001",
|
||||
ORG_IDN: "기관관리번호001",
|
||||
SRV_CHK: "인입관 유무001",
|
||||
},
|
||||
];
|
||||
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 FeatureClickEventPopup = ({ onClose }: { onClose: () => void }) => {
|
||||
// TODO: 런타임 에러 해결 필요
|
||||
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 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 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>
|
||||
);
|
||||
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 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">하수관로 정보</h3>
|
||||
<div className="font-medium text-white text-label">(00구 00리 하수관로)</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-white hover:text-gray-700 focus:outline-none"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
<VerticalTable headName='LX 연계정보' tableData={tableData} headlist={RxTableColumn} 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>
|
||||
{/* here */}
|
||||
{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">
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default FeatureClickEventPopup;
|
||||
@@ -14,6 +14,7 @@ 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, checkedLayerIds:null});
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
@@ -22,11 +23,14 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(true); // 팝업 열기/닫기 상태
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
//const [treeData, setTreeData] = useState([]);
|
||||
const checkedLayerIds = useRef([]);
|
||||
let gisMap = null;
|
||||
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
@@ -131,6 +135,11 @@ function Gis({ treeData = [], isSearch = false }) {
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} />
|
||||
{isOpen && (
|
||||
<FeatureClickEventPopup
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
<GisLonLat/>
|
||||
<GisToolBar/>
|
||||
|
||||
Reference in New Issue
Block a user