From ca73f11bb5b44884a1e8c3b3c56e1da4e49d4bd6 Mon Sep 17 00:00:00 2001 From: swcho Date: Wed, 21 May 2025 11:17:13 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20SearchModal=20=EC=97=90=EC=84=9C?= =?UTF-8?q?=EC=9D=98=20UiTable=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20Gis=20?= =?UTF-8?q?=EC=A7=80=EB=8F=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20?= =?UTF-8?q?zoom,=20=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EB=8A=94=20Click=20Event=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business_components/src/ui/map/Gis.js | 82 +++++++++++++++++-- .../src/ui/map/GisSelect.js | 51 ++++++------ 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index f45ffce3f..9d99923a8 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -36,12 +36,76 @@ function Gis({ treeData = [], isSearch = false }) { }; // SearchModal, GisContent 등에서 호출 - const handleFeatureSelect = (data) => { - console.log('check',data) - setFeatureData(data); // 선택된 피처 데이터 저장 - setIsOpen(true); // 팝업 열기 - setIsSearchModalOpen(false); // SearchModal 닫기 - }; + const handleFeatureSelect = useCallback((data) => { + gisMap = mapInstanceRef.current; + + if (!gisMap) { + console.warn('지도가 초기화되지 않았습니다.'); + return; + } + + // 항상 같은 select 객체 사용 + const select = GisApp.Module.select; + if (!select) return; + + // 1. 기존 선택 초기화 + select.resetSelectedFeature(); + + // 2. data가 OpenLayers Feature 인스턴스인지 확인 + let foundFeature = null; + if (data && typeof data.getGeometry === 'function' && typeof data.setStyle === 'function') { + // 이미 Feature 인스턴스라면 그대로 사용 + foundFeature = data; + } else { + // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 + const featureId = data.fid || data.ftrIdn || data.id; + gisMap.getLayers().getArray().forEach(layer => { + const source = layer.getSource?.(); + if (source?.getFeatureById) { + const feature = source.getFeatureById(String(featureId).trim()); + if (feature) foundFeature = feature; + } + }); + } + + // 3. 피처 선택 + if (foundFeature) { + let geometry = foundFeature.getGeometry(); + if (geometry) { + let extent = geometry.getExtent(); + + // // EPSG:4326 좌표계로 추정되는 경우(값이 위도/경도 범위) + // console.log('before extent', extent); + // const is4326 = + // extent[0] >= -90 && extent[0] <= 90 && + // extent[1] >= -180 && extent[1] <= 180 && + // extent[2] >= -90 && extent[2] <= 90 && + // extent[3] >= -180 && extent[3] <= 180; + // if (is4326) { + // // extent: [위도, 경도, 위도, 경도] → [경도, 위도, 경도, 위도]로 순서 변경 + // const reorderedExtent = [extent[1], extent[0], extent[3], extent[2]]; + // // EPSG:4326 extent를 EPSG:3857로 변환 + // extent = olProj.transformExtent(reorderedExtent, 'EPSG:4326', 'EPSG:3857'); + // console.log('after extent', extent); + // } + + gisMap.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 16, + constrainResolution: true, + nearest: true, + }); + } + select.selectFeature(foundFeature, true); + } else { + console.warn('OpenLayers Feature를 찾을 수 없습니다:', data); + } + + setFeatureData(data); + setIsOpen(true); + setIsSearchModalOpen(false); + }, [mapInstanceRef]); useEffect(() => { if (!mapRef.current) return; @@ -144,10 +208,10 @@ function Gis({ treeData = [], isSearch = false }) { return ( <> - +
- - + + {/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} {isOpen && featureData && !isSearchModalOpen && ( { - // if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함 - - // const select = new GisOlSelect(gisMap); - // GisApp.Module.select = select; - - // select.getSelectedInfo(function (features) { - // if (features?.[0]) { - // // onFeatureClick?.(features[0]); // props로 넘긴 함수 실행 - // onFeatureSelect(layer.getProperties()); //피쳐 속성데이터 상위컴포넌트로 전달 - // } - // }); - // }, [gisMap, onFeatureSelect]); - const select = new GisOlSelect(gisMap); - GisApp.Module.select = select; - select.getSelectedInfo(function (features) { - //clickFeature(features[0]); - if(features.length > 0) { - const layer = features[0]; - onFeatureSelect(layer); //피쳐 속성데이터 상위컴포넌트로 전달 + useEffect(() => { + if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함 + + // 이미 select가 있으면 재생성하지 않음 + if (!GisApp.Module.select) { + const select = new GisOlSelect(gisMap); + GisApp.Module.select = select; + + // 피처 선택 시 콜백 등록 + select.getSelectedInfo(function (features) { + if (features?.[0]) { + console.log('GisSelect features', features); + onFeatureSelect(features[0]); + } + }); } - }); + }, [gisMap, onFeatureSelect]); + + // const select = new GisOlSelect(gisMap); + // GisApp.Module.select = select; + + // select.getSelectedInfo(function (features) { + // //clickFeature(features[0]); + // if(features.length > 0) { + // const layer = features[0]; + // // console.log('GisSelect layer', layer); + // onFeatureSelect(layer); //피쳐 속성데이터 상위컴포넌트로 전달 + // } + // }); return ( <> From f1601a7f99ebf56cd1758e64fe4287119e51752b Mon Sep 17 00:00:00 2001 From: yschoi Date: Thu, 22 May 2025 09:44:37 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EB=8F=84=EC=8B=9C=EC=B9=A8=EC=88=98=20?= =?UTF-8?q?=EC=98=88=EB=B0=A9=EC=82=AC=EC=97=85=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/attachment/attachment-interface.tsx | 74 +++++++++++++ .../lib/api/attachment/attachment-service.tsx | 42 ++++++++ .../components/lib/utils/fileUploadUtil.tsx | 9 +- apps/sn/app/components/ui/fileUploadForm.tsx | 100 ++++++++++++------ apps/sn/app/oper/floodPre/page.tsx | 2 +- apps/sn/app/styles/components.css | 4 + apps/sn/middleware.ts | 54 ++++++++++ apps/sn/next.config.ts | 26 ++--- .../src/ui/main_content_div/index.tsx | 2 +- .../src/ui/map/lib/gis.core.js | 6 +- 10 files changed, 265 insertions(+), 54 deletions(-) create mode 100644 apps/sn/app/components/lib/api/attachment/attachment-interface.tsx create mode 100644 apps/sn/app/components/lib/api/attachment/attachment-service.tsx create mode 100644 apps/sn/middleware.ts diff --git a/apps/sn/app/components/lib/api/attachment/attachment-interface.tsx b/apps/sn/app/components/lib/api/attachment/attachment-interface.tsx new file mode 100644 index 000000000..351e76fcb --- /dev/null +++ b/apps/sn/app/components/lib/api/attachment/attachment-interface.tsx @@ -0,0 +1,74 @@ +import type { AxiosResponse } from "axios"; + +interface SortConfig { + empty: boolean; + sorted: boolean; + unsorted: boolean; +} + +interface PageableConfig { + sort: SortConfig; + offset: number; + pageNumber: number; + pageSize: number; + paged: boolean; + unpaged: boolean; +} + +export interface PageableList { + content: T[]; + pageable: PageableConfig; + sort: SortConfig; + totalPages: number; + totalElements: number; + number: number; + numberOfElements: number; + size: number; + first: boolean; + last: boolean; + empty: boolean; +} + +export interface PageableParams { + page?: number; + size?: number; + sort?: string[]; +} + +export interface AttachmentParams extends PageableParams { + keywordType?: string; + keyword?: string; + atflCd?: string; + atflSn?: number; + atflId?: string; + orgnlFileNm?: string; + atflFlieSz?: number; + atflType?: string; + dwnldNocs?: number; + delYn?: string; + lnkgDmnId?: string; + lnkgDmnNm?: string; +} + +export interface Attachment { + atflCd: string; + atflSn: number; + atflId: string; + physFileNm: string; + orgnlFileNm: string; + atflFlieSz: number; + atflType: string; + dwnldNocs: number; + delYn: string; + lnkgDmnId: string; + lnkgDmnNm: string; + regDt: string; +} + +export type AttachmentList = PageableList; + +export interface AttachmentService { + getAttachmentsList: (params: AttachmentParams) => Promise; + getAttachmentsInfo: (atflCd: string) => Promise; + deleteAttachmentByAtflCd: (atflCd: string) => Promise; +} diff --git a/apps/sn/app/components/lib/api/attachment/attachment-service.tsx b/apps/sn/app/components/lib/api/attachment/attachment-service.tsx new file mode 100644 index 000000000..36390c0fb --- /dev/null +++ b/apps/sn/app/components/lib/api/attachment/attachment-service.tsx @@ -0,0 +1,42 @@ +import type { AxiosResponse } from "axios"; +import type { + Attachment, + AttachmentList, + AttachmentParams, + AttachmentService, +} from "../attachment/attachment-interface"; +import { getData, deleteData } from "../index"; + +export const ENDPOINT = { + ATTACH_SERVICE: { + TUS_UPLOAD: "/attach-service/api/v1/tus/upload", + EDITOR_UPLOAD: "/attach-service/api/v1/upload/editor", + IMAGES: "/attach-service/api/v1/images", + MOVIE_THUMBNAIL: "/attach-service/api/v1/movie/thumbnail", + ATTACHMENTS: "/attach-service/api/v1/atfl/attachments", + DOWNLOAD: "/attach-service/api/v1/atflcd/download", + STREAM_VIDEO: "/attach-service/api/v1/stream/video", + }, +}; + +export class AttachmentApiService implements AttachmentService { + public async getAttachmentsList( + params: AttachmentParams, + ): Promise { + const response = await getData(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}`); + return response.data; + } + + public async getAttachmentsInfo(atflCd: string): Promise { + const response = await getData( + `${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflCd}`, + ); + return response.data; + } + + public async deleteAttachmentByAtflCd(atflCd: string): Promise { + await deleteData(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/atflCd/${atflCd}`); + } +} + +export const AttachmentApi = new AttachmentApiService(); diff --git a/apps/sn/app/components/lib/utils/fileUploadUtil.tsx b/apps/sn/app/components/lib/utils/fileUploadUtil.tsx index 4685dd336..c2d380b54 100644 --- a/apps/sn/app/components/lib/utils/fileUploadUtil.tsx +++ b/apps/sn/app/components/lib/utils/fileUploadUtil.tsx @@ -1,10 +1,9 @@ import { Upload, type HttpResponse } from "tus-js-client"; import { getCookie, setCookie } from "@utils"; +import { ENDPOINT } from "../api/attachment/attachment-service"; -const AUTH_USER_ID = process.env.AUTH_USER_ID || ''; -const CLAIM_NAME = process.env.CLAIM_NAME || ''; -const BASE_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || ''; -const END_URL = `${BASE_URL}/attach-service/api/v1/tus/upload`; +const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; +const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; type UploadOptions = { handleUploadStart?: (upload: Upload) => void; @@ -24,7 +23,7 @@ export const handleUpload = (files: FileList, options?: UploadOptions) => { }; const upload = new Upload(file, { - endpoint: END_URL, + endpoint: ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD, chunkSize: 10 * 1024 * 1024, headers: { [CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "", diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx index 3c57bb812..d05b73eaf 100644 --- a/apps/sn/app/components/ui/fileUploadForm.tsx +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -1,13 +1,26 @@ "use client" import { FileUpload, SelectBox } from '@common/business_components/ui'; - import { Button, Input } from '@common/components/ui'; import React, { useState } from 'react' +import type { HttpResponse, Upload } from "tus-js-client"; +import { FileUploader } from "@common/components"; +import { handleUpload } from "@utils"; const FileUploadForm = ({ title }: { title: string }) => { const [files, setFiles] = useState(null); const [selectedYear, setSelectedYear] = useState(''); const [selectedQuarter, setSelectedQuarter] = useState(''); + const [upload, setUpload] = useState(null); + + const handleUploadResponse = (file: File) => async (res: HttpResponse) => { + const fileInfo = res.getHeader("X-File-Info"); + if (!fileInfo) return; + + const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; + const orgnlFileNm = file.name; + + console.log(atflCd, atflId, orgnlFileNm); + }; // 현재 년도부터 10년 전까지의 년도 옵션 생성 const yearOptions = Array.from({ length: 10 }, (_, i) => { @@ -23,38 +36,63 @@ const FileUploadForm = ({ title }: { title: string }) => {
- setSelectedYear(String(value))} - /> setSelectedQuarter(String(value))} - /> -
-
-
- -
- + customPreview={<>} + /> +
+ +
diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index 62ecd15b8..99b17d1bd 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -15,7 +15,7 @@ const Page = () => { const handleDownload = async () => { try { - const response = await fetch('/sn/attach-service/api/v1/download/6066b08c-2a55-438c-b8ff-2aed9739a0c6', { + const response = await fetch('/attach-service/api/v1/download/0dda8ecd-63dd-4e90-bbe1-08d6fcd08c9a', { method: 'GET', }); diff --git a/apps/sn/app/styles/components.css b/apps/sn/app/styles/components.css index b67efb8f0..4dc30bafd 100644 --- a/apps/sn/app/styles/components.css +++ b/apps/sn/app/styles/components.css @@ -22,4 +22,8 @@ @apply border-b-[#3f4042]; } } + + input[type="file"] { + @apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer; + } } diff --git a/apps/sn/middleware.ts b/apps/sn/middleware.ts new file mode 100644 index 000000000..be1d093ae --- /dev/null +++ b/apps/sn/middleware.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from "next/server"; + +const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; +const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +const serverApiUrl = + process.env.NEXT_PUBLIC_SERVER_API_URL || "http://localhost:3002"; +const geoServerUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; + +export function middleware(req: NextRequest) { + console.log("Middleware 진입:", req.nextUrl.pathname); + const pathname = req.nextUrl.pathname; + const searchParams = req.nextUrl.searchParams; + + if (pathname.startsWith("/server")) { + const queryParams = searchParams.toString(); + const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/geoserver")) { + const queryParams = searchParams.toString(); + const newUrl = `${geoServerUrl}${pathname.replace("/geoserver", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/attach-service")) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + const newUrl = `${serverApiUrl}${pathname}`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + console.log( + `[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`, + ); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/server/:path*","/geoserver/:path*", "/attach-service/:path*"], +}; diff --git a/apps/sn/next.config.ts b/apps/sn/next.config.ts index 12162c0a0..f149ae9b8 100644 --- a/apps/sn/next.config.ts +++ b/apps/sn/next.config.ts @@ -6,7 +6,7 @@ const cnServerUrl = process.env.NEXT_PUBLIC_SERVER_API_URL?.replace(/\/$/, '') ? const nextConfig: NextConfig = { - basePath: "/sn", + // basePath: "/sn", typescript: { ignoreBuildErrors: true, // 이 줄 추가 }, @@ -44,18 +44,18 @@ const nextConfig: NextConfig = { return config; }, - async rewrites() { - return [ - { - source: '/geoserver/:hasudo*', - destination: `${geoServerUrl}/:hasudo*`, - }, - { - source: '/attach-service/:path*', - destination: `${cnServerUrl}/attach-service/:path*`, - }, - ]; - }, + // async rewrites() { + // return [ + // { + // source: '/geoserver/:hasudo*', + // destination: `${geoServerUrl}/:hasudo*`, + // }, + // { + // source: '/attach-service/:path*', + // destination: `${cnServerUrl}/attach-service/:path*`, + // }, + // ]; + // }, }; export default nextConfig; diff --git a/packages/common/business_components/src/ui/main_content_div/index.tsx b/packages/common/business_components/src/ui/main_content_div/index.tsx index 1432f472e..ea95cd2ab 100644 --- a/packages/common/business_components/src/ui/main_content_div/index.tsx +++ b/packages/common/business_components/src/ui/main_content_div/index.tsx @@ -2,7 +2,7 @@ export default function MainDiv(props: {children: React.ReactNode}) { return ( -
+
{props.children}
) diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js index faebb90d3..4085e2907 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.core.js +++ b/packages/common/business_components/src/ui/map/lib/gis.core.js @@ -28,8 +28,8 @@ GisApp.layerInfos = []; //기본정보 //GisApp.GEOSERVER_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/ows'; //GisApp.GEOSERVER_WMS_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/wms'; -GisApp.GEOSERVER_URL = '/sn/geoserver/hasudo/ows'; -GisApp.GEOSERVER_WMS_URL = '/sn/geoserver/hasudo/wms'; +GisApp.GEOSERVER_URL = '/geoserver/hasudo/ows'; +GisApp.GEOSERVER_WMS_URL = '/geoserver/hasudo/wms'; //맵 정보 GisApp.EMAP_URL = '/emap/'; @@ -47,7 +47,7 @@ GisApp.BASE_MAP_TYPES = []; GisApp.FROM_PROJ = 'EPSG:4326'; GisApp.TO_PROJ = 'EPSG:3857'; GisApp.TO_DATA_PROJ = 'EPSG:4326'; -GisApp.CONTEXT_LAYER_PATH = '/sn/images/map/layericon/'; +GisApp.CONTEXT_LAYER_PATH = '/images/map/layericon/'; GisApp.layerCode = {}; GisApp.hlLayerId = 'LightFeatureList'; GisApp.hLayerId = 'highLightFeature'; From 808e44c8258d4dd0ea93c670deeb192c76bbccc1 Mon Sep 17 00:00:00 2001 From: swcho Date: Thu, 22 May 2025 09:49:37 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC(SearchModal)=20=EC=9C=84=EC=B9=98=20Gis=20=EC=A2=8C?= =?UTF-8?q?=EC=B8=A1=20=EC=83=81=EB=8B=A8=20=EB=B2=84=ED=8A=BC=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=20=EB=B3=80=EA=B2=BD,=20GisTab.js=EB=A5=BC=20Gis?= =?UTF-8?q?=20Tree.js=20=EB=82=B4=EB=B6=80=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business_components/src/ui/map/Gis.js | 24 +-- .../src/ui/map/GisContent.js | 45 ++++-- .../business_components/src/ui/map/GisTab.js | 150 +++++++++--------- .../business_components/src/ui/map/GisTree.js | 92 +++++++---- 4 files changed, 179 insertions(+), 132 deletions(-) diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 9d99923a8..1973a78f3 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,6 +1,5 @@ import './lib/css/gis.common.css'; import React, { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react'; -import { Tabs, TabsList, TabsTrigger, TabsContent } from '@common/components/ui/tabs'; import OlMap from 'ol/Map'; import View from 'ol/View'; import { defaults as defaultControls, OverviewMap } from 'ol/control'; @@ -10,13 +9,11 @@ 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'; +import GisTree from './GisTree'; +import SearchModal from '@/map/SearchModal'; -export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null}); +export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); export const useGisContext = () => useContext(GisContext); function Gis({ treeData = [], isSearch = false }) { @@ -24,7 +21,7 @@ function Gis({ treeData = [], isSearch = false }) { const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 - const [featureData,setFeatureData] = useState(null) // 선택된 피쳐 데이터 + const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); const mapInstanceRef = useRef(null); // 맵 생성 여부를 추적할 ref @@ -181,7 +178,7 @@ function Gis({ treeData = [], isSearch = false }) { //setEditLayerItems(prev => [...layerItems]) }, [treeData]); - const getLayerItems= (layerCode) => { + const getLayerItems = (layerCode) => { const layerItems = Object.entries(layerCode) .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') .map(([key, obj]) => ({ @@ -210,8 +207,8 @@ function Gis({ treeData = [], isSearch = false }) { <>
- - + + {/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} {isOpen && featureData && !isSearchModalOpen && ( )} +
{/* @@ -229,4 +231,4 @@ function Gis({ treeData = [], isSearch = false }) { ); } -export default React.memo(Gis); +export default React.memo(Gis); \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js index 93edcd12c..ad8a53605 100644 --- a/packages/common/business_components/src/ui/map/GisContent.js +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import GisApp from './lib/gis.core'; import { useGisContext } from './Gis'; import GisLonLat from './GisLonLat'; @@ -6,9 +6,9 @@ import GisSelect from './GisSelect'; import GisToolBar from './GisToolBar'; import './lib/css/gis.edit.css'; import MapToggleBtn from './MapToggleBtn'; -import GisEdit from './GisEdit'; +import { Search } from 'lucide-react'; -export default function GisContent({ edit = false, onFeatureSelect }) { +export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) { const { gisMap, gisCore } = useGisContext(); const [visibleComponents, setVisibleComponents] = useState({}); const addressResultLayer = useRef(null); @@ -16,12 +16,12 @@ export default function GisContent({ edit = false, onFeatureSelect }) { //셀렉트 버튼 이벤트 // const clickFeature = useCallback(feature => { - // if (feature) { - // const layer = feature.getLayer(); - // setVisibleComponents(prevState => ({ - // ...prevState, - // })); - // } + // if (feature) { + // const layer = feature.getLayer(); + // setVisibleComponents(prevState => ({ + // ...prevState, + // })); + // } // }, []); const selectRslt = useCallback(feature => { @@ -45,16 +45,35 @@ export default function GisContent({ edit = false, onFeatureSelect }) { })); } }, [gisMap]); - + if (!gisMap) return null; - + return ( <> {/**/} - + + + {/* 검색 모달 */} + {isSearch && ( +
+ +
+ )} ); -} +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/GisTab.js b/packages/common/business_components/src/ui/map/GisTab.js index 1522cc1b5..27dd36e2b 100644 --- a/packages/common/business_components/src/ui/map/GisTab.js +++ b/packages/common/business_components/src/ui/map/GisTab.js @@ -1,85 +1,85 @@ -import React, { useState } from 'react'; -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 '@/map/SearchModal'; +// import React, { useState } from 'react'; +// 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 '@/map/SearchModal'; -function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) { - const [activeTab, setActiveTab] = useState('layers'); +// function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen, onFeatureSelect }) { +// const [activeTab, setActiveTab] = useState('layers'); - return ( - <> -
- - - 레이어 - {isSearch && ( - - // - // - // - )} - +// return ( +// <> +//
+// +// +// 레이어 +// {/* {isSearch && ( +// +// // +// // +// // +// )} */} +// - {/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */} -
- -
+// {/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */} +//
+// +//
- {/* GisSearch도 동일하게 처리 */} - {/* {isSearch && ( -
- -
- )} */} +// {/* GisSearch도 동일하게 처리 */} +// {/* {isSearch && ( +//
+// +//
+// )} */} - {/* 🔍 검색 모달 */} - -
+// {/* 🔍 검색 모달 */} +// {/* */} +// - {/* 접기 버튼 */} - -
+// {/* 접기 버튼 */} +// +//
- {/* 열기 버튼 */} - {!visible && ( - - )} - - ); -} +// {/* 열기 버튼 */} +// {!visible && ( +// +// )} +// +// ); +// } -export default React.memo(GisTab); +// export default React.memo(GisTab); diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index d01786187..6a2e8face 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -1,5 +1,4 @@ import GisApp from './lib/gis.core'; -import { util } from './lib/util'; import React, { useEffect, useState } from 'react'; import { useGisContext } from './Gis'; import { DndProvider } from "react-dnd"; @@ -8,14 +7,14 @@ import { MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components/ui/tabs"; +import { ChevronLeft, ChevronRight } from 'lucide-react'; -export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => {} }) { +export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible }) { const { gisCore, treeData } = useGisContext(); const [checked, setChecked] = useState([]); const [expanded, setExpanded] = useState([]); const [nodes, setNodes] = useState([]); - const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [activeTab, setActiveTab] = useState('layers'); useEffect(() => { if (treeData && treeData.length > 0) { @@ -115,7 +114,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes }; const makeLegendSrc = layerinfo => { - if(!layerinfo.gobjTy) return null; + if (!layerinfo.gobjTy) return null; let src; if (layerinfo.iconNm) { @@ -176,34 +175,61 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes return ( <> - - ( -
- {node.droppable && ( - - - )} - handleCheck(node.id)} className="cursor-pointer"/> - {node.icon && {node.icon}} - {node.text} -
- )} - sort={(a, b) => { - const aOrder = parseInt(a.data?.sortOrdr ?? '0'); - const bOrder = parseInt(b.data?.sortOrdr ?? '0'); - return aOrder - bOrder; - }} - /> -
+
+
+

레이어

+
+ + ( +
+ {node.droppable && ( + + + )} + handleCheck(node.id)} className="cursor-pointer" /> + {node.icon && {node.icon}} + {node.text} +
+ )} + sort={(a, b) => { + const aOrder = parseInt(a.data?.sortOrdr ?? '0'); + const bOrder = parseInt(b.data?.sortOrdr ?? '0'); + return aOrder - bOrder; + }} + /> +
+
+ {/* 접기 버튼 */} + +
+ {/* 열기 버튼 */} + {!visible && ( + + )} ); }); From 9ef0f43dfe15f71ea86dea1c10ca03766a70a4bf Mon Sep 17 00:00:00 2001 From: yschoi Date: Thu, 22 May 2025 11:13:28 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=EB=B3=91=ED=95=A9=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sn/app/oper/floodPre/page.tsx | 138 ++++++++++++++--------------- 1 file changed, 65 insertions(+), 73 deletions(-) diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx index fdeec56f4..84ccfa743 100644 --- a/apps/sn/app/oper/floodPre/page.tsx +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -18,82 +18,74 @@ const Page = () => { const [manageYear, setManageYear] = useState('') const [mngQu, setMngQu] = useState('') const [usr, setUsr] = useState('') - const [pjNm, setPjNm] = useState('') - - - const handleDownload = async () => { - try { - const response = await fetch('/attach-service/api/v1/download/6066b08c-2a55-438c-b8ff-2aed9739a0c6', { - method: 'GET', - }); - - if (!response.ok) { - throw new Error('파일 다운로드 실패'); - } - - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - - // 다운로드를 트리거할 가짜 태그 생성 - const link = document.createElement('a'); - link.href = url; - link.download = 'test.jpg'; // 원하는 파일명 지정 - document.body.appendChild(link); - link.click(); - - // 메모리 해제 - link.remove(); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error('다운로드 중 오류 발생:', error); + const [pjNm, setpjNm] = useState('') + const [fileNm, setFileNm] = useState('') + // TODO: 초기값 세팅필요 + const isChckFile = () => { + if (fileNm === '' || fileNm === undefined) { + alert('파일을 선택해주세요') } - }; - - - return ( - - - -
-
- - { if (date) setSelectedDate(date) }} /> - ~ - { if (date) setSelectedDate(date) }} /> -
-
- - setCompany(e.target.value)} - /> -
-
- - setPjNm(e.target.value)} - /> -
+ // TODO: 추후 파일코드 동적으로 세팅 + handleDownload('/sn/attach-service/api/v1/download/6066b08c-2a55-438c-b8ff-2aed9739a0c6', fileNm) + } + //TODO: 파일 업로드 처리 +return ( + + + +
+
+ + { if (date) setFromDate(date) }} /> + ~ + { if (date) setToDate(date) }} />
- - - - - - -
-

사업정보 등록현황

-
- - +
+ + setManageYear(e.target.value)} + /> + ~ + setMngQu(e.target.value)} + />
+
+ + setUsr(e.target.value)} + /> +
+
+ + setpjNm(e.target.value)} + /> +
+
+ + + + + + +
+

사업정보 등록현황

+
+ +
{ if (index != null) { const fileName = tableData[index]?.fileNm ?? ""; setFileNm(fileName) } }} onPageChange={function (page: number): void {