From 811dedd12e3b9f3db482d9157bdf978ad00a8da4 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Tue, 13 May 2025 21:45:50 +0900 Subject: [PATCH 01/12] =?UTF-8?q?refactor:=20props=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20useMapStore=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MapPage/BottomSheet.tsx | 30 +++++---- src/pages/MapPage/MapPage.tsx | 62 +------------------ src/pages/MapPage/components/InfoTable.tsx | 7 ++- .../MapPage/components/map/MapComponent.tsx | 30 ++++----- 4 files changed, 42 insertions(+), 87 deletions(-) diff --git a/src/pages/MapPage/BottomSheet.tsx b/src/pages/MapPage/BottomSheet.tsx index 984a08a..400351a 100644 --- a/src/pages/MapPage/BottomSheet.tsx +++ b/src/pages/MapPage/BottomSheet.tsx @@ -1,22 +1,28 @@ import { useRef, TouchEvent } from 'react'; import styled from 'styled-components'; import InfoTable from './components/InfoTable'; -import { BottomSheetProps } from './interface'; import ListContainer from './components/ListContainer'; import CommentList from './components/comment/CommentList'; import NoInfo from './components/NoInfo'; import SearchResult from './components/SearchResult'; import { Slope } from '../../apis/slopeMap'; +import { useMapStore } from './mapStore'; + +const BottomSheet = () => { + const { + slopeData, + selectedMarkerId, + chooseSelectItem, + bottomSheetHeight, + setBottomSheetHeight, + searchMod, + } = useMapStore(); + + const height = bottomSheetHeight; + const setHeight = setBottomSheetHeight; + const selectItem = + selectedMarkerId !== null ? slopeData[selectedMarkerId] : null; -const BottomSheet: React.FC = ({ - slopeData, - selectItem, - onItemClick, - height, - setHeight, - onCloseInfo, - searchMod, -}) => { const startY = useRef(0); const currentHeight = useRef(200); //현재 높이 const isDragging = useRef(false); //드래그 상태 @@ -128,7 +134,7 @@ const BottomSheet: React.FC = ({ if (selectItem !== null) { return (
- +
); @@ -162,7 +168,7 @@ const BottomSheet: React.FC = ({ key={index} item={item} onClick={() => { - onItemClick(item, index); + chooseSelectItem(item, index); }} > )) diff --git a/src/pages/MapPage/MapPage.tsx b/src/pages/MapPage/MapPage.tsx index 2519f4b..3ee721d 100644 --- a/src/pages/MapPage/MapPage.tsx +++ b/src/pages/MapPage/MapPage.tsx @@ -9,22 +9,7 @@ import SearchComponent from './components/map/Search'; import { useMapStore } from './mapStore'; import ButtonGroup from './components/ButtonGroup'; const MapPage = () => { - const { - selectedMarkerId, - allTextShow, - userLocation, - slopeData, - searchMod, - bottomSheetHeight, - mapInstance, - setBottomSheetHeight, - fetchSlopes, - handleSearch, - chooseSelectItem, - setUserLocation, - setMapInstance, - setSelectedMarkerId, - } = useMapStore(); + const { userLocation, searchMod, fetchSlopes, handleSearch } = useMapStore(); useEffect(() => { if (!searchMod) fetchSlopes(); @@ -33,50 +18,9 @@ const MapPage = () => { return ( <> - - { - setSelectedMarkerId(null); - }} - searchMod={searchMod} - /> - + + - {/* { - setAllTextShow(!allTextShow); - }} - > - {allTextShow ? '위성지도' : '일반지도'} - - { - setAllTextShow(!allTextShow); - }} - > - {allTextShow ? '전체표기' : '개별표기'} - - - - */} diff --git a/src/pages/MapPage/components/InfoTable.tsx b/src/pages/MapPage/components/InfoTable.tsx index 02ae269..a2b51b1 100644 --- a/src/pages/MapPage/components/InfoTable.tsx +++ b/src/pages/MapPage/components/InfoTable.tsx @@ -1,7 +1,12 @@ import styled from 'styled-components'; import { InfotableProps } from '../interface'; +import { useMapStore } from '../mapStore'; -const InfoTable: React.FC = ({ selectItem, onCloseInfo }) => { +const InfoTable: React.FC = ({ selectItem }) => { + const { setSelectedMarkerId } = useMapStore(); + const onCloseInfo = () => { + setSelectedMarkerId(null); + }; if (!selectItem) return null; const grade = selectItem.priority?.grade?.includes('A') ? 'A' diff --git a/src/pages/MapPage/components/map/MapComponent.tsx b/src/pages/MapPage/components/map/MapComponent.tsx index a42e06e..280ba8c 100644 --- a/src/pages/MapPage/components/map/MapComponent.tsx +++ b/src/pages/MapPage/components/map/MapComponent.tsx @@ -12,7 +12,6 @@ import CmarkerIcon from '../../../../assets/c.webp'; import DmarkerIcon from '../../../../assets/d.webp'; import FmarkerIcon from '../../../../assets/f.webp'; import UserPosIcon from '../../../../assets/current_position.png'; -import { MapComponentProps } from '../../interface'; import { MapTypeId, useMapStore } from '../../mapStore'; declare global { interface Window { @@ -22,17 +21,18 @@ declare global { } } -const MapComponent: React.FC = ({ - selectedMarkerId, - escarpmentData, - allTextShow, - userLocation, - setUserLocation, - //추가 - mapInstance, - setMapInstance, - onMarkerClick, -}) => { +const MapComponent = () => { + const { + selectedMarkerId, + slopeData, + allTextShow, + userLocation, + setUserLocation, + mapInstance, + setMapInstance, + chooseSelectItem, + } = useMapStore(); + const { mapTypeId, setIsMapReady } = useMapStore(); const navermaps = useNavermaps(); const [_errorMessage, setErrorMessage] = useState(null); @@ -188,8 +188,8 @@ const MapComponent: React.FC = ({ `, }} /> - {escarpmentData.length > 0 - ? escarpmentData.map((item, index) => { + {slopeData.length > 0 + ? slopeData.map((item, index) => { // console.log(item); const grade = item.priority?.grade.includes('A') ? 'A' @@ -247,7 +247,7 @@ const MapComponent: React.FC = ({ anchor: new navermaps.Point(16, 16), }} onClick={() => { - onMarkerClick(item, index); + chooseSelectItem(item, index); }} /> ); From f239d0e8679897f9e43df128f270c0e975873809 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Tue, 13 May 2025 21:48:35 +0900 Subject: [PATCH 02/12] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MapPage/components/ButtonGroup.tsx | 81 -------------------- 1 file changed, 81 deletions(-) diff --git a/src/pages/MapPage/components/ButtonGroup.tsx b/src/pages/MapPage/components/ButtonGroup.tsx index f061b9b..49dd4b6 100644 --- a/src/pages/MapPage/components/ButtonGroup.tsx +++ b/src/pages/MapPage/components/ButtonGroup.tsx @@ -1,84 +1,3 @@ -// import styled from 'styled-components' -// import { useMapStore } from '../mapStore'; -// import MyLocationIcon from '@mui/icons-material/MyLocationRounded'; - -// const ButtonGroup = () => { -// const {allTextShow,setAllTextShow,moveToMyLocation}=useMapStore(); -// return ( -// -// { -// setAllTextShow(!allTextShow); -// }} -// > -// {allTextShow ? '위성지도' : '일반지도'} -// -// { -// setAllTextShow(!allTextShow); -// }} -// > -// {allTextShow ? '전체표기' : '개별표기'} -// -// -// -// -// -// ); -// }; - -// export default ButtonGroup; - -// const Container=styled.div` -// position: absolute; -// top: 50px; -// right: 10px; -// ` -// const AllShowButton = styled.button<{ $isSelect: boolean }>` -// position: absolute; -// top: 50px; -// right: 10px; -// border: none; -// border-radius: 8px; -// height: 30px; -// padding: 5px 10px; -// box-shadow: ${({ theme }) => theme.shadows.sm}; -// font-weight: ${({ theme }) => theme.fonts.weights.bold}; -// font-size: ${({ theme }) => theme.fonts.sizes.ms}; -// background-color: ${({ $isSelect, theme }) => -// $isSelect ? theme.colors.primaryDark : '#fff'}; -// color: ${({ $isSelect, theme }) => -// !$isSelect ? theme.colors.primaryDark : '#fff'}; -// &:focus { -// outline: none; -// } -// transition: all 0.15s ease-in-out; - -// &:active { -// transform: scale(1.1); -// } -// `; - -// const MyPosition = styled.button` -// position: absolute; -// top: 90px; -// right: 10px; -// border: none; -// border-radius: 8px; -// padding: 5px 10px; -// box-shadow: ${({ theme }) => theme.shadows.sm}; -// font-weight: 550; -// background-color: #fff; -// transition: all 0.15s ease-in-out; -// &:hover { -// background-color: ${({ theme }) => theme.colors.grey[200]}; -// } -// &:active { -// transform: scale(1.1); -// } -// `; import styled from 'styled-components'; import { useMapStore, MapTypeId } from '../mapStore'; import MyLocationIcon from '@mui/icons-material/MyLocationRounded'; From 9bb882a1028406e8324f41343185a0176efc000d Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Tue, 13 May 2025 21:49:57 +0900 Subject: [PATCH 03/12] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MapPage/mapStore.ts | 132 ---------------------------------- 1 file changed, 132 deletions(-) diff --git a/src/pages/MapPage/mapStore.ts b/src/pages/MapPage/mapStore.ts index e19bd83..21a3637 100644 --- a/src/pages/MapPage/mapStore.ts +++ b/src/pages/MapPage/mapStore.ts @@ -1,135 +1,3 @@ -// import { create } from 'zustand'; -// import { Slope, slopeMapAPI } from '../../apis/slopeMap'; - -// export interface MapState { -// // 맵 상태 -// selectedMarkerId: number | null; -// allTextShow: boolean; -// userLocation: naver.maps.LatLng | null; -// slopeData: Slope[]; -// searchMod: boolean; -// bottomSheetHeight: number; -// mapInstance: naver.maps.Map | null; -// mapTypeId: naver.maps.MapTypeId; - -// // 액션 -// setSelectedMarkerId: (id: number | null) => void; -// setAllTextShow: (show: boolean) => void; -// setUserLocation: (location: naver.maps.LatLng | null) => void; -// setSlopeData: (data: Slope[]) => void; -// setSearchMod: (mod: boolean) => void; -// setBottomSheetHeight: (height: number) => void; -// setMapInstance: (map: naver.maps.Map | null) => void; -// setMapTypeId: (typeId: naver.maps.MapTypeId) => void; - -// // 비즈니스 로직 -// fetchSlopes: () => Promise; -// handleSearch: (searchValue: string) => void; -// chooseSelectItem: (item: Slope, index: number) => void; -// moveToMyLocation: () => void; -// closeInfo: () => void; -// } - -// export const useMapStore = create((set, get) => ({ -// // 초기 상태 -// selectedMarkerId: null, -// allTextShow: false, -// userLocation: null, -// slopeData: [], -// searchMod: false, -// bottomSheetHeight: 200, -// mapInstance: null, -// mapTypeId: naver.maps.MapTypeId.NORMAL, - -// // 액션 -// setSelectedMarkerId: (id) => set({ selectedMarkerId: id }), -// setAllTextShow: (show) => set({ allTextShow: show }), -// setUserLocation: (location) => set({ userLocation: location }), -// setSlopeData: (data) => set({ slopeData: data }), -// setSearchMod: (mod) => set({ searchMod: mod }), -// setBottomSheetHeight: (height) => set({ bottomSheetHeight: height }), -// setMapInstance: (map) => set({ mapInstance: map }), -// setMapTypeId: (typeId) => set({ mapTypeId: typeId }), - -// // 비즈니스 로직 -// fetchSlopes: async () => { -// const { userLocation } = get(); -// if (!userLocation?.lat() || !userLocation?.lng()) return; - -// try { -// const data = await slopeMapAPI.fetchNearbySlopes( -// userLocation.lat(), -// userLocation.lng() -// ); -// set({ slopeData: data || [] }); -// } catch (error) { -// console.error('Error fetching slopes:', error); -// set({ slopeData: [] }); -// } -// }, - -// handleSearch: async (searchValue) => { -// const { fetchSlopes, userLocation, mapInstance } = get(); - -// if (searchValue === '') { -// set({ searchMod: false, selectedMarkerId: null }); -// fetchSlopes(); -// return; -// } - -// set({ selectedMarkerId: null, searchMod: true }); - -// if (!userLocation?.lat() || !userLocation?.lng()) return; - -// try { -// const data = await slopeMapAPI.searchSlopes( -// searchValue, -// userLocation.lat(), -// userLocation.lng() -// ); -// set({ slopeData: data || [] }); - -// if (mapInstance && data && data.length > 0) { -// const coordinates = data[0].location.coordinates.start.coordinates; -// mapInstance.panTo( -// new naver.maps.LatLng(coordinates[1], coordinates[0]) -// ); -// } -// } catch (error) { -// console.error('Error search slopes:', error); -// set({ slopeData: [] }); -// } -// }, - -// chooseSelectItem: (item, index) => { -// const { mapInstance, selectedMarkerId } = get(); - -// if (mapInstance && item) { -// const coordinates = item.location.coordinates.start.coordinates; -// mapInstance.panTo(new naver.maps.LatLng(coordinates[1], coordinates[0])); - -// set({ selectedMarkerId: selectedMarkerId === index ? null : index }); -// } -// }, - -// moveToMyLocation: () => { -// const { mapInstance, userLocation, fetchSlopes } = get(); - -// if (!mapInstance || !userLocation) return; - -// mapInstance.setZoom(15); - -// setTimeout(() => { -// mapInstance.panTo(userLocation); -// }, 100); - -// fetchSlopes(); -// }, - -// closeInfo: () => { -// set({ selectedMarkerId: null }); -// }, -// })); import { create } from 'zustand'; import { Slope, slopeMapAPI } from '../../apis/slopeMap'; From 3a4be6119512eb002dcf74a44011c1fbb48ac274 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Tue, 13 May 2025 22:27:39 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor:=20props=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20type->interface.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/MapPage/BottomSheet.tsx | 2 +- src/pages/MapPage/MapPage.tsx | 4 +- src/pages/MapPage/components/ButtonGroup.tsx | 2 +- src/pages/MapPage/components/InfoTable.tsx | 4 +- .../MapPage/components/ListContainer.tsx | 3 +- src/pages/MapPage/components/NoInfo.tsx | 4 +- src/pages/MapPage/components/SearchResult.tsx | 12 +-- .../MapPage/components/map/LeftModal.tsx | 5 +- .../MapPage/components/map/MapComponent.tsx | 2 +- src/pages/MapPage/interface.ts | 97 ++++++++++++------- 10 files changed, 74 insertions(+), 61 deletions(-) diff --git a/src/pages/MapPage/BottomSheet.tsx b/src/pages/MapPage/BottomSheet.tsx index 400351a..c2a04ca 100644 --- a/src/pages/MapPage/BottomSheet.tsx +++ b/src/pages/MapPage/BottomSheet.tsx @@ -6,7 +6,7 @@ import CommentList from './components/comment/CommentList'; import NoInfo from './components/NoInfo'; import SearchResult from './components/SearchResult'; import { Slope } from '../../apis/slopeMap'; -import { useMapStore } from './mapStore'; +import { useMapStore } from '../../stores/mapStore'; const BottomSheet = () => { const { diff --git a/src/pages/MapPage/MapPage.tsx b/src/pages/MapPage/MapPage.tsx index 3ee721d..913c48c 100644 --- a/src/pages/MapPage/MapPage.tsx +++ b/src/pages/MapPage/MapPage.tsx @@ -1,12 +1,10 @@ import { useEffect } from 'react'; - +import { useMapStore } from '../../stores/mapStore'; import styled from 'styled-components'; - import BottomSheet from './BottomSheet'; import MapComponent from './components/map/MapComponent'; import SearchComponent from './components/map/Search'; -import { useMapStore } from './mapStore'; import ButtonGroup from './components/ButtonGroup'; const MapPage = () => { const { userLocation, searchMod, fetchSlopes, handleSearch } = useMapStore(); diff --git a/src/pages/MapPage/components/ButtonGroup.tsx b/src/pages/MapPage/components/ButtonGroup.tsx index 49dd4b6..c2e6f0e 100644 --- a/src/pages/MapPage/components/ButtonGroup.tsx +++ b/src/pages/MapPage/components/ButtonGroup.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { useMapStore, MapTypeId } from '../mapStore'; +import { useMapStore, MapTypeId } from '../../../stores/mapStore'; import MyLocationIcon from '@mui/icons-material/MyLocationRounded'; const ButtonGroup = () => { diff --git a/src/pages/MapPage/components/InfoTable.tsx b/src/pages/MapPage/components/InfoTable.tsx index a2b51b1..0abb75b 100644 --- a/src/pages/MapPage/components/InfoTable.tsx +++ b/src/pages/MapPage/components/InfoTable.tsx @@ -1,8 +1,8 @@ import styled from 'styled-components'; import { InfotableProps } from '../interface'; -import { useMapStore } from '../mapStore'; +import { useMapStore } from '../../../stores/mapStore'; -const InfoTable: React.FC = ({ selectItem }) => { +const InfoTable = ({ selectItem }: InfotableProps) => { const { setSelectedMarkerId } = useMapStore(); const onCloseInfo = () => { setSelectedMarkerId(null); diff --git a/src/pages/MapPage/components/ListContainer.tsx b/src/pages/MapPage/components/ListContainer.tsx index 12c5b7d..fecab9e 100644 --- a/src/pages/MapPage/components/ListContainer.tsx +++ b/src/pages/MapPage/components/ListContainer.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import styled from 'styled-components'; import { ListProps } from '../interface'; -const ListContainer: React.FC = ({ item, onClick }) => { +const ListContainer = ({ item, onClick }: ListProps) => { if (!item) return null; const grade = item.priority?.grade.includes('A') ? 'A' diff --git a/src/pages/MapPage/components/NoInfo.tsx b/src/pages/MapPage/components/NoInfo.tsx index 3275554..dc10cf8 100644 --- a/src/pages/MapPage/components/NoInfo.tsx +++ b/src/pages/MapPage/components/NoInfo.tsx @@ -1,9 +1,7 @@ import styled from 'styled-components'; import ErrorOutlineRoundedIcon from '@mui/icons-material/ErrorOutlineRounded'; +import { NoInfoProps } from '../interface'; -interface NoInfoProps { - text: string; -} const NoInfo = ({ text }: NoInfoProps) => ( diff --git a/src/pages/MapPage/components/SearchResult.tsx b/src/pages/MapPage/components/SearchResult.tsx index 85577fc..6cde673 100644 --- a/src/pages/MapPage/components/SearchResult.tsx +++ b/src/pages/MapPage/components/SearchResult.tsx @@ -1,15 +1,5 @@ import styled from 'styled-components'; - -interface SearchResultProps { - resultCount: number; - gradeCount: { - A: number; - B: number; - C: number; - D: number; - F: number; - }; -} +import { SearchResultProps } from '../interface'; const SearchResult = ({ resultCount, gradeCount }: SearchResultProps) => { return ( diff --git a/src/pages/MapPage/components/map/LeftModal.tsx b/src/pages/MapPage/components/map/LeftModal.tsx index ee93168..f359a16 100644 --- a/src/pages/MapPage/components/map/LeftModal.tsx +++ b/src/pages/MapPage/components/map/LeftModal.tsx @@ -6,10 +6,7 @@ import { userAPI } from '../../../../apis/User'; import TermsofUseModal from './TermsofUseModal'; import ArrowBackIcon from '@mui/icons-material/ArrowBackIosNewRounded'; import { useNotificationStore } from '../../../../hooks/notificationStore'; -interface LeftModalProps { - isOpen: boolean; - onClose: () => void; -} +import { LeftModalProps } from '../../interface'; const LeftModal = ({ isOpen, onClose }: LeftModalProps) => { const [animationOpen, setAnimationOpen] = useState(false); diff --git a/src/pages/MapPage/components/map/MapComponent.tsx b/src/pages/MapPage/components/map/MapComponent.tsx index 280ba8c..7b4eb9b 100644 --- a/src/pages/MapPage/components/map/MapComponent.tsx +++ b/src/pages/MapPage/components/map/MapComponent.tsx @@ -12,7 +12,7 @@ import CmarkerIcon from '../../../../assets/c.webp'; import DmarkerIcon from '../../../../assets/d.webp'; import FmarkerIcon from '../../../../assets/f.webp'; import UserPosIcon from '../../../../assets/current_position.png'; -import { MapTypeId, useMapStore } from '../../mapStore'; +import { MapTypeId, useMapStore } from '../../../../stores/mapStore'; declare global { interface Window { ReactNativeWebView?: { diff --git a/src/pages/MapPage/interface.ts b/src/pages/MapPage/interface.ts index eab8932..6651915 100644 --- a/src/pages/MapPage/interface.ts +++ b/src/pages/MapPage/interface.ts @@ -1,27 +1,25 @@ import { Slope } from '../../apis/slopeMap'; -export interface MapComponentProps { - selectedMarkerId: number | null; - escarpmentData: Slope[]; - allTextShow: boolean; - userLocation: naver.maps.LatLng | null; - setUserLocation: (location: naver.maps.LatLng | null) => void; - mapInstance: naver.maps.Map | null; - setMapInstance: (map: naver.maps.Map | null) => void; - onMarkerClick: (item: Slope, index: number) => void; -} -export interface BottomSheetProps { - slopeData: Slope[]; - selectItem: Slope | null; - onItemClick: (item: Slope, index: number) => void; - height: number; - setHeight: (height: number) => void; - onCloseInfo: () => void; - searchMod: boolean; +export interface CommentData { + _id: string; + slopeId: string; + userId: UserInfo; + content: string; + imageUrls: string[]; + createdAt: string; + updatedAt: string; + __v: number; } +interface UserInfo { + _id: string; + name: string; + organization: string; + isAdmin: boolean; +} + +//props관련 export interface InfotableProps { selectItem: Slope | null; - onCloseInfo: () => void; } export interface ListProps { item: Slope | null; @@ -31,24 +29,57 @@ export interface ListProps { export interface SearchComponentProps { onSearch: (value: string) => void; } +export interface NoInfoProps { + text: string; +} +export interface CommentContainerProps { + comment: CommentData; + fetchComment: () => Promise; +} +export interface SearchResultProps { + resultCount: number; + gradeCount: { + A: number; + B: number; + C: number; + D: number; + F: number; + }; +} +export interface DeleteModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: () => void; +} +export interface CommentListProps { + slopeId: string; +} -interface UserInfo { +//모달 관련 + +export interface DeleteConfirmModalProps { + isOpen: boolean; + onClose: () => void; +} + +export interface UserDelete { _id: string; + isAdmin: string; name: string; organization: string; - isAdmin: boolean; + phone: string; } -export interface CommentData { - _id: string; - slopeId: string; - userId: UserInfo; - content: string; - imageUrls: string[]; - createdAt: string; - updatedAt: string; - __v: number; + +export interface LeftModalProps { + isOpen: boolean; + onClose: () => void; } -export interface CommentContainerProps { - comment: CommentData; - fetchComment: () => Promise; + +export interface PrivacyPolicyModalProps { + isOpen: boolean; + onClose: () => void; +} +export interface TermsofUseModalProps { + isOpen: boolean; + onClose: () => void; } From a0539e1e4b728ace47f1efcd4944789d2bc6308c Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Tue, 13 May 2025 22:28:55 +0900 Subject: [PATCH 05/12] refactor: modal type->interface.ts --- src/pages/MapPage/components/map/DeleteIdModal.tsx | 14 ++------------ .../MapPage/components/map/PrivacyPolicyModal.tsx | 6 +----- .../MapPage/components/map/TermsofUseModal.tsx | 6 +----- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/pages/MapPage/components/map/DeleteIdModal.tsx b/src/pages/MapPage/components/map/DeleteIdModal.tsx index 7a3b468..6b9ba5d 100644 --- a/src/pages/MapPage/components/map/DeleteIdModal.tsx +++ b/src/pages/MapPage/components/map/DeleteIdModal.tsx @@ -2,18 +2,8 @@ import styled from 'styled-components'; import { useState, useEffect } from 'react'; import { userAPI } from '../../../../apis/User'; import { useNotificationStore } from '../../../../hooks/notificationStore'; -interface DeleteConfirmModalProps { - isOpen: boolean; - onClose: () => void; -} - -interface UserDelete { - _id: string; - isAdmin: string; - name: string; - organization: string; - phone: string; -} +import { DeleteConfirmModalProps, UserDelete } from '../../interface'; + const DeleteIdModal = ({ isOpen, onClose }: DeleteConfirmModalProps) => { const [inputValue, setInputValue] = useState(''); const [user, setUser] = useState(null); diff --git a/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx b/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx index c67c5ac..3b3103e 100644 --- a/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx +++ b/src/pages/MapPage/components/map/PrivacyPolicyModal.tsx @@ -1,10 +1,6 @@ import { useEffect, useState } from 'react'; import styled from 'styled-components'; - -interface PrivacyPolicyModalProps { - isOpen: boolean; - onClose: () => void; -} +import { PrivacyPolicyModalProps } from '../../interface'; const PrivacyPolicyModal = ({ isOpen, onClose }: PrivacyPolicyModalProps) => { const [animationOpen, setAnimationOpen] = useState(false); diff --git a/src/pages/MapPage/components/map/TermsofUseModal.tsx b/src/pages/MapPage/components/map/TermsofUseModal.tsx index 3b5ae11..9035447 100644 --- a/src/pages/MapPage/components/map/TermsofUseModal.tsx +++ b/src/pages/MapPage/components/map/TermsofUseModal.tsx @@ -1,11 +1,7 @@ import { useEffect, useState } from 'react'; +import { TermsofUseModalProps } from '../../interface'; import styled from 'styled-components'; -interface TermsofUseModalProps { - isOpen: boolean; - onClose: () => void; -} - const TermsofUseModal = ({ isOpen, onClose }: TermsofUseModalProps) => { const [animationOpen, setAnimationOpen] = useState(false); From a7255e5dea98d970db94758b99c9fdd0f377259f Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Thu, 15 May 2025 21:46:50 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20store=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/commentStore.ts | 25 +++++++++++++++++++++++ src/{pages/MapPage => stores}/mapStore.ts | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/stores/commentStore.ts rename src/{pages/MapPage => stores}/mapStore.ts (98%) diff --git a/src/stores/commentStore.ts b/src/stores/commentStore.ts new file mode 100644 index 0000000..f7e3780 --- /dev/null +++ b/src/stores/commentStore.ts @@ -0,0 +1,25 @@ +import { create } from 'zustand'; + +interface commentStore { + //초기상태 + isMoreOpen: boolean; + isDeleteOpen: boolean; + isModiOpen: boolean; + //액션 + setIsMore: (value: boolean) => void; + setIsDelete: (value: boolean) => void; + setIsModi: (value: boolean) => void; +} + +export const useCommentStore = create((set) => ({ + //초기상태 + isMoreOpen: false, + isDeleteOpen: false, + isModiOpen: false, + //액션 + setIsMore: (value) => set({ isMoreOpen: value }), + setIsDelete: (value) => set({ isDeleteOpen: value }), + setIsModi: (value) => set({ isModiOpen: value }), + + //비즈니스 로직 +})); diff --git a/src/pages/MapPage/mapStore.ts b/src/stores/mapStore.ts similarity index 98% rename from src/pages/MapPage/mapStore.ts rename to src/stores/mapStore.ts index 21a3637..7a19f5f 100644 --- a/src/pages/MapPage/mapStore.ts +++ b/src/stores/mapStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { Slope, slopeMapAPI } from '../../apis/slopeMap'; +import { Slope, slopeMapAPI } from '../apis/slopeMap'; // 문자열 상수로 맵 타입 정의 export enum MapTypeId { From 7ba83afbbc49b990106404562bd9cc22f169e50e Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Thu, 15 May 2025 21:47:16 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20comment=20store=20=EA=B5=AC?= =?UTF-8?q?=EC=B6=95=20=EB=B0=8F=20=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/comment/CommentContainer.tsx | 14 +++-------- .../components/comment/CommentCreateModal.tsx | 24 ++++++++----------- .../components/comment/CommentDeleteModal.tsx | 19 +++++++-------- .../components/comment/CommentList.tsx | 17 ++++--------- .../components/comment/CommentUpdateModal.tsx | 24 ++++++++----------- src/pages/MapPage/interface.ts | 12 +++++++--- 6 files changed, 45 insertions(+), 65 deletions(-) diff --git a/src/pages/MapPage/components/comment/CommentContainer.tsx b/src/pages/MapPage/components/comment/CommentContainer.tsx index 90aad9d..1cbca43 100644 --- a/src/pages/MapPage/components/comment/CommentContainer.tsx +++ b/src/pages/MapPage/components/comment/CommentContainer.tsx @@ -1,15 +1,13 @@ -import { useState } from 'react'; import styled from 'styled-components'; import MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded'; import { CommentContainerProps } from '../../interface'; import { slopeCommentAPI } from '../../../../apis/slopeMap'; import CommentDeleteModal from './CommentDeleteModal'; import CommentUpdateModal from './CommentUpdateModal'; +import { useCommentStore } from '../../../../stores/commentStore'; const CommentContainer = ({ comment, fetchComment }: CommentContainerProps) => { - const [isMore, setIsMore] = useState(false); - const [isDelete, setIsDelete] = useState(false); - const [isModi, setIsModi] = useState(false); + const { isMoreOpen, setIsMore, setIsDelete, setIsModi } = useCommentStore(); //수정 삭제에 접근 가능한지 const accessible = (): boolean => { @@ -42,10 +40,6 @@ const CommentContainer = ({ comment, fetchComment }: CommentContainerProps) => { <> {/* 삭제모달 */} { - setIsDelete(false); - }} onSubmit={() => { handleDelete(); setIsMore(false); @@ -54,8 +48,6 @@ const CommentContainer = ({ comment, fetchComment }: CommentContainerProps) => { /> {/*수정모달 */} setIsModi(false)} onSubmit={handleUpdate} defaultComment={comment.content} defaultImages={comment.imageUrls.map( @@ -87,7 +79,7 @@ const CommentContainer = ({ comment, fetchComment }: CommentContainerProps) => { sx={{ width: '20px', height: '20px', opacity: '0.6' }} /> - {isMore && ( + {isMoreOpen && ( <> { diff --git a/src/pages/MapPage/components/comment/CommentCreateModal.tsx b/src/pages/MapPage/components/comment/CommentCreateModal.tsx index 92825bb..5e14d10 100644 --- a/src/pages/MapPage/components/comment/CommentCreateModal.tsx +++ b/src/pages/MapPage/components/comment/CommentCreateModal.tsx @@ -1,12 +1,8 @@ import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { useNotificationStore } from '../../../../hooks/notificationStore'; - -interface CommentAddModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (comment: string, images: File[]) => void; -} +import { CommentAddModalProps } from '../../interface'; +import { useCommentStore } from '../../../../stores/commentStore'; interface ImageFile { file: File; @@ -23,11 +19,11 @@ interface MobileImageAsset { dataUrl?: string; } -const CommentAddModal = ({ - isOpen, - onClose, - onSubmit, -}: CommentAddModalProps) => { +const CommentAddModal = ({ onSubmit }: CommentAddModalProps) => { + const { isMoreOpen, setIsMore } = useCommentStore(); + const onClose = () => { + setIsMore(false); + }; const [comment, setComment] = useState(''); const [images, setImages] = useState([]); //전역 알람 @@ -38,11 +34,11 @@ const CommentAddModal = ({ typeof window !== 'undefined' && window.ReactNativeWebView != null; //모달이 열릴 때 초기상태로 복원 useEffect(() => { - if (isOpen) { + if (isMoreOpen) { setComment(''); setImages([]); } - }, [isOpen]); + }, [isMoreOpen]); //사진 권한 요청 useEffect(() => { @@ -243,7 +239,7 @@ const CommentAddModal = ({ }; return ( - +

코멘트 작성

diff --git a/src/pages/MapPage/components/comment/CommentDeleteModal.tsx b/src/pages/MapPage/components/comment/CommentDeleteModal.tsx index 05c5d42..bf3c0e8 100644 --- a/src/pages/MapPage/components/comment/CommentDeleteModal.tsx +++ b/src/pages/MapPage/components/comment/CommentDeleteModal.tsx @@ -1,17 +1,14 @@ import styled from 'styled-components'; +import { DeleteModalProps } from '../../interface'; +import { useCommentStore } from '../../../../stores/commentStore'; -interface DeleteModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: () => void; -} -const CommentDeleteModal = ({ - isOpen, - onClose, - onSubmit, -}: DeleteModalProps) => { +const CommentDeleteModal = ({ onSubmit }: DeleteModalProps) => { + const { isDeleteOpen, setIsDelete } = useCommentStore(); + const onClose = () => { + setIsDelete(false); + }; return ( - +

삭제 확인

diff --git a/src/pages/MapPage/components/comment/CommentList.tsx b/src/pages/MapPage/components/comment/CommentList.tsx index 3c1d9e8..5183075 100644 --- a/src/pages/MapPage/components/comment/CommentList.tsx +++ b/src/pages/MapPage/components/comment/CommentList.tsx @@ -3,16 +3,13 @@ import styled from 'styled-components'; import { useEffect, useState } from 'react'; import CommentAddModal from './CommentCreateModal'; import { slopeCommentAPI } from '../../../../apis/slopeMap'; -import { CommentData } from '../../interface'; +import { CommentData, CommentListProps } from '../../interface'; import NoInfo from '../NoInfo'; - -interface CommentListProps { - slopeId: string; -} +import { useCommentStore } from '../../../../stores/commentStore'; const CommentList = ({ slopeId }: CommentListProps) => { const [commentData, setCommentData] = useState([]); - const [isCreate, setIsCreate] = useState(false); + const { setIsMore } = useCommentStore(); const handleCreate = async (content: string, images: File[]) => { try { const formData = new FormData(); @@ -29,7 +26,7 @@ const CommentList = ({ slopeId }: CommentListProps) => { // 생성 후 코멘트 목록 다시 조회 const newData = await slopeCommentAPI.getComment(slopeId); setCommentData(newData); - setIsCreate(false); + setIsMore(false); } catch (error) { console.log('코멘트 생성 실패', error); } @@ -50,15 +47,11 @@ const CommentList = ({ slopeId }: CommentListProps) => { return ( { - setIsCreate(false); - }} onSubmit={(content, images) => handleCreate(content, images)} /> { - setIsCreate(true); + setIsMore(true); }} > 글 등록/사진 등록 diff --git a/src/pages/MapPage/components/comment/CommentUpdateModal.tsx b/src/pages/MapPage/components/comment/CommentUpdateModal.tsx index 314074c..7b58444 100644 --- a/src/pages/MapPage/components/comment/CommentUpdateModal.tsx +++ b/src/pages/MapPage/components/comment/CommentUpdateModal.tsx @@ -1,15 +1,8 @@ import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { useNotificationStore } from '../../../../hooks/notificationStore'; - -interface CommentUpdateModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (formData: FormData) => void; - defaultComment: string; - defaultImages: string[]; - commentId: string; -} +import { useCommentStore } from '../../../../stores/commentStore'; +import { CommentUpdateModalProps } from '../../interface'; interface ImageFile { file: File | null; @@ -28,13 +21,15 @@ interface MobileImageAsset { dataUrl?: string; } const CommentUpdateModal = ({ - isOpen, - onClose, onSubmit, defaultComment, defaultImages, commentId, }: CommentUpdateModalProps) => { + const { isModiOpen, setIsModi } = useCommentStore(); + const onClose = () => { + setIsModi(false); + }; const [comment, setComment] = useState(defaultComment); const [images, setImages] = useState(() => defaultImages.map((url) => ({ @@ -45,6 +40,7 @@ const CommentUpdateModal = ({ url: url, })) ); + const isReactNativeWebView = typeof window !== 'undefined' && window.ReactNativeWebView != null; const showNotification = useNotificationStore( @@ -52,7 +48,7 @@ const CommentUpdateModal = ({ ); // 모달이 열릴 때마다 초기 상태로 재설정 useEffect(() => { - if (isOpen) { + if (isModiOpen) { setComment(defaultComment); setImages( defaultImages.map((url) => ({ @@ -64,7 +60,7 @@ const CommentUpdateModal = ({ })) ); } - }, [isOpen]); + }, [isModiOpen]); // 디버깅용 로그 useEffect(() => { @@ -314,7 +310,7 @@ const CommentUpdateModal = ({ } }; return ( - +

코멘트 수정

diff --git a/src/pages/MapPage/interface.ts b/src/pages/MapPage/interface.ts index 6651915..76cef0d 100644 --- a/src/pages/MapPage/interface.ts +++ b/src/pages/MapPage/interface.ts @@ -46,15 +46,21 @@ export interface SearchResultProps { F: number; }; } +export interface CommentAddModalProps { + onSubmit: (comment: string, images: File[]) => void; +} export interface DeleteModalProps { - isOpen: boolean; - onClose: () => void; onSubmit: () => void; } export interface CommentListProps { slopeId: string; } - +export interface CommentUpdateModalProps { + onSubmit: (formData: FormData) => void; + defaultComment: string; + defaultImages: string[]; + commentId: string; +} //모달 관련 export interface DeleteConfirmModalProps { From 17ba2a3904f9ad1b98048ce0f1a08da080ba17d1 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 17 May 2025 21:00:17 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=EC=97=AC=EB=9F=AC=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StepSlope/components/DeleteModal.tsx | 35 ++++++++- .../StepSlope/components/table/DataTable.tsx | 48 +++++++++--- .../components/table/TableAction.tsx | 22 +++++- .../components/table/TableCheckbox.tsx | 53 ++++++++++++++ .../components/table/TableModals.tsx | 3 + .../StepSlope/components/table/coloums.ts | 31 +++++++- .../components/table/store/tableStore.ts | 37 ++++++++-- .../StepSlope/pages/SteepSlopeLookUp.tsx | 73 +++++++++++++++++-- 8 files changed, 272 insertions(+), 30 deletions(-) create mode 100644 src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx diff --git a/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx b/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx index 3977664..fe809db 100644 --- a/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx +++ b/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx @@ -1,11 +1,13 @@ import styled from 'styled-components'; import { Slope } from '../../../../apis/slopeMap'; +import { useEffect, useState } from 'react'; interface DeleteConfirmModalProps { isOpen: boolean; onClose: () => void; onConfirm: () => void; selectedRow: Slope | null; + selectedRows: Slope[]; } const DeleteConfirmModal = ({ @@ -13,13 +15,27 @@ const DeleteConfirmModal = ({ onClose, onConfirm, selectedRow, + selectedRows = [], }: DeleteConfirmModalProps) => { + const [count, setCount] = useState(0); + const [isMulti, setIsMulti] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onConfirm(); onClose(); }; - + useEffect(() => { + if (isOpen) { + const selectedCount = selectedRows.length; + setCount(selectedCount); + setIsMulti(selectedCount > 1); + console.log('Modal opened with:', { + selectedCount, + isMulti: selectedCount > 1, + selectedRows, + }); + } + }, [isOpen, selectedRows]); return ( @@ -29,8 +45,20 @@ const DeleteConfirmModal = ({
- {selectedRow?.name}를 삭제하시려면 입력창에 - 삭제라고 입력해 주세요. + {isMulti ? ( + // 다중 선택 시 메시지 + <> + 선택한 {count}개의 항목을 삭제하시려면 + 입력창에 + 삭제라고 입력해 주세요. + + ) : ( + // 단일 선택 시 메시지 + <> + {selectedRow?.name}를 삭제하시려면 입력창에 + 삭제라고 입력해 주세요. + + )} ); }; - const ModalOverlay = styled.div<{ $isOpen: boolean }>` display: ${(props) => (props.$isOpen ? 'flex' : 'none')}; position: fixed; diff --git a/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx b/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx index b509695..5e26230 100644 --- a/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx @@ -32,6 +32,22 @@ const DataTable: React.FC = ({ rowVirtualizer.getTotalSize() - (paddingTop + rowVirtualizer.getVirtualItems().length * 40); + // 셀 렌더링 함수 + const renderCell = (cell: any) => { + // 체크박스 열인 경우 별도 처리 + if (cell.column.id === 'select') { + // 컬럼 정의에서 cell 함수를 호출 + const cellContent = (cell.column.columnDef as any).cell; + if (typeof cellContent === 'function') { + return cellContent({ row: cell.row, table, column: cell.column }); + } + return cellContent; + } + + // 일반 셀 + return cell.getValue() as string; + }; + return ( @@ -39,7 +55,14 @@ const DataTable: React.FC = ({ {table.getHeaderGroups()[0].headers.map((header) => ( - {header.column.columnDef.header as string} + {header.column.id === 'select' + ? typeof (header.column.columnDef as any).header === + 'function' + ? ((header.column.columnDef as any).header as any)({ + table, + }) + : (header.column.columnDef as any).header + : (header.column.columnDef.header as string)} = ({ return ( { - setSelectedRow( - selectedRow?.managementNo === row.original.managementNo - ? null - : row.original - ); + onClick={(e) => { + // 체크박스 열이 아닌 부분을 클릭했을 때만 기존 선택 동작 수행 + if ( + !(e.target as HTMLElement).closest('input[type="checkbox"]') + ) { + setSelectedRow( + selectedRow?.managementNo === row.original.managementNo + ? null + : row.original + ); + } }} $selected={ - selectedRow?.managementNo === row.original.managementNo + selectedRow?.managementNo === row.original.managementNo || + row.getIsSelected?.() || + false } > {row.getVisibleCells().map((cell) => ( - {cell.getValue() as string} + {renderCell(cell)} ))} diff --git a/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx b/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx index 6d848b9..92ff5f7 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx @@ -2,27 +2,41 @@ import styled from 'styled-components'; import React from 'react'; import { Slope } from '../../../../../apis/slopeMap'; import LoadingMessage from '../../../components/LoadingMessage'; + interface TableActionProps { isLoading: boolean; selectedRow: Slope | null; + // 선택된 행들 배열 추가 + selectedRows: Slope[]; openEditModal: () => void; openDeleteModal: () => void; } + const TableAction: React.FC = ({ isLoading, selectedRow, + selectedRows, openEditModal, openDeleteModal, }) => { + // 선택된 행 수 + const selectedCount = selectedRows?.length || 0; + return ( <> {isLoading && } - {/* 하단 버튼 컨테이너 */} - {selectedRow && ( + + {/* 하단 버튼 컨테이너 - 단일 선택 또는 다중 선택 시 표시 */} + {(selectedRow || selectedCount > 0) && ( - 수정 + {/* 단일 선택일 때만 수정 버튼 표시 */} + {selectedCount <= 1 && selectedRow && ( + 수정 + )} + + {/* 삭제 버튼은 항상 표시, 다중 선택 시 개수 표시 */} - 삭제 + {selectedCount > 1 ? `${selectedCount}개 항목 삭제` : '삭제'} )} diff --git a/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx b/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx new file mode 100644 index 0000000..e546ff9 --- /dev/null +++ b/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx @@ -0,0 +1,53 @@ +import React, { useRef, useEffect } from 'react'; +import styled from 'styled-components'; + +interface TableCheckboxProps { + indeterminate?: boolean; + checked?: boolean; + disabled?: boolean; + onChange: (e: React.ChangeEvent) => void; +} + +const TableCheckbox: React.FC = ({ + indeterminate = false, + checked = false, + disabled = false, + onChange, + ...rest +}) => { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + ref.current.indeterminate = indeterminate; + } + }, [indeterminate]); + + return ( + + + + ); +}; + +export default TableCheckbox; + +const CheckboxContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 100%; +`; + +const StyledCheckbox = styled.input` + cursor: pointer; + width: 16px; + height: 16px; +`; diff --git a/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx b/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx index 1a4e4e1..478d249 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx @@ -17,6 +17,7 @@ interface TableModalProps { closeDeleteModal: () => void; handleDelete: () => void; selectedRow: Slope | null; + selectedRows: Slope[]; isEditModalOpen: boolean; closeEditModal: () => void; handleEdit: (updatedSlope: Slope) => void; @@ -33,6 +34,7 @@ const TableModals: React.FC = ({ closeDeleteModal, handleDelete, selectedRow, + selectedRows, isEditModalOpen, closeEditModal, handleEdit, @@ -49,6 +51,7 @@ const TableModals: React.FC = ({ isOpen={isDeleteModalOpen} onClose={closeDeleteModal} onConfirm={handleDelete} + selectedRows={selectedRows} selectedRow={selectedRow} /> (); export const getSlopeColumns = () => [ + columnHelper.display({ + id: 'select', + header: ({ table }) => { + // 이벤트 핸들러를 올바르게 정의 + const handleToggleAll = (e: React.ChangeEvent) => { + table.toggleAllRowsSelected?.(e.target.checked); + }; + + return React.createElement(TableCheckbox, { + checked: table.getIsAllRowsSelected?.() || false, + indeterminate: table.getIsSomeRowsSelected?.() || false, + onChange: handleToggleAll, // 함수 자체를 전달, 호출 결과 아님 + }); + }, + cell: ({ row }) => { + // 이벤트 핸들러를 올바르게 정의 + const handleToggleRow = (e: React.ChangeEvent) => { + row.toggleSelected?.(e.target.checked); + }; + + return React.createElement(TableCheckbox, { + checked: row.getIsSelected?.() || false, + disabled: !row.getCanSelect?.() || false, + onChange: handleToggleRow, // 함수 자체를 전달, 호출 결과 아님 + }); + }, + size: 50, + }), columnHelper.accessor( (_row, index) => { return index + 1; diff --git a/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts b/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts index b874e04..f71131e 100644 --- a/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts +++ b/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts @@ -3,6 +3,7 @@ import { VisibilityState, ColumnSizingState, OnChangeFn, + RowSelectionState, } from '@tanstack/react-table'; import { getDefaultColumnVisibility } from '..//coloums'; @@ -17,14 +18,18 @@ export interface TableState { selectedRegion: { city: string; county: string } | null; grade: string; + // 행 선택 상태 추가 + rowSelection: RowSelectionState; + // 모달 상태 isModalOpen: boolean; isRegionModalOpen: boolean; isDeleteModalOpen: boolean; isEditModalOpen: boolean; - // 선택된 행 - selectedRow: T | null; + // 선택된 행 - 단일 선택에서 다중 선택으로 변경 + selectedRow: T | null; // 기존 호환성을 위해 유지 + selectedRows: T[]; // 다중 선택을 위한 배열 추가 // 액션 - 타입 수정됨 setColumnVisibility: OnChangeFn; @@ -34,6 +39,11 @@ export interface TableState { setInputValue: (value: string) => void; setSelectedRegion: (region: { city: string; county: string } | null) => void; setGrade: (value: string) => void; + + // 행 선택 상태 설정 함수 추가 + setRowSelection: OnChangeFn; + setSelectedRows: (rows: T[]) => void; + openModal: () => void; closeModal: () => void; openRegionModal: () => void; @@ -58,12 +68,17 @@ export function createTableStore(initialState: Partial> = {}) { inputValue: '', selectedRegion: null, grade: '선택안함', + + // 행 선택 상태 초기화 + rowSelection: {}, + isModalOpen: false, isRegionModalOpen: false, isDeleteModalOpen: false, isEditModalOpen: false, selectedRow: null, + selectedRows: [], // 다중 선택 배열 초기화 // 액션 메서드들 - TanStack Table 호환 타입으로 수정 setColumnVisibility: (updaterOrValue) => { @@ -88,6 +103,19 @@ export function createTableStore(initialState: Partial> = {}) { } }, + // 행 선택 상태 설정 함수 추가 + setRowSelection: (updaterOrValue) => { + if (typeof updaterOrValue === 'function') { + set((state) => ({ + rowSelection: updaterOrValue(state.rowSelection), + })); + } else { + set({ rowSelection: updaterOrValue }); + } + }, + + setSelectedRows: (rows) => set({ selectedRows: rows }), + setTotalCount: (count) => set({ totalCount: count }), setSearchQuery: (query) => set({ searchQuery: query }), setInputValue: (value) => set({ inputValue: value }), @@ -111,12 +139,11 @@ export function createTableStore(initialState: Partial> = {}) { inputValue: '', selectedRegion: null, columnVisibility: getDefaultColumnVisibility(), + rowSelection: {}, // 선택 상태도 초기화 + selectedRows: [], // 선택된 행 배열도 초기화 }), // 초기 상태 오버라이드 ...initialState, })); } - -// 특정 타입의 테이블 스토어 생성 예시 -// export const useSlopeOutlierStore = createTableStore(); diff --git a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx index 9d4fdab..c69c548 100644 --- a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx +++ b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx @@ -1,9 +1,10 @@ -import React, { useRef, useCallback, useEffect } from 'react'; +import React, { useRef, useCallback, useEffect, useState } from 'react'; import { useReactTable, getCoreRowModel, ColumnResizeMode, FilterFn, + RowSelectionState, } from '@tanstack/react-table'; import { useVirtualizer } from '@tanstack/react-virtual'; import { rankItem } from '@tanstack/match-sorter-utils'; @@ -66,6 +67,8 @@ const SteepSlopeLookUp = () => { selectedRow, setSelectedRow, resetFilters, + setSelectedRows, + selectedRows, } = useSteepSlopeStore(); // 테이블 컨테이너 ref는 훅 내에서 직접 생성 @@ -128,6 +131,8 @@ const SteepSlopeLookUp = () => { addMeta({ itemRank }); return itemRank.passed; }; + // 행 선택 상태 추가 + const [rowSelection, setRowSelection] = useState({}); //테이블 선언 const table = useReactTable({ @@ -136,11 +141,14 @@ const SteepSlopeLookUp = () => { state: { columnVisibility, columnSizing, + rowSelection, }, onColumnVisibilityChange: setColumnVisibility, onColumnSizingChange: setColumnSizing, columnResizeMode: 'onChange' as ColumnResizeMode, enableColumnResizing: true, + onRowSelectionChange: setRowSelection, + enableRowSelection: true, getCoreRowModel: getCoreRowModel(), defaultColumn: { minSize: 80, @@ -152,6 +160,17 @@ const SteepSlopeLookUp = () => { globalFilterFn: 'fuzzy', }); + useEffect(() => { + // rowSelection 상태에서 선택된 행 추출 + const selectedRowsArray = Object.keys(rowSelection) + .filter((key) => rowSelection[key]) + .map((key) => flatData[parseInt(key)]); + + // 선택된 행 정보 업데이트 + setSelectedRows(selectedRowsArray); + setSelectedRow(selectedRowsArray.length > 0 ? selectedRowsArray[0] : null); + }, [rowSelection, flatData]); + // 스크롤 이벤트 핸들러(무한스크롤 기능) const handleScroll = useCallback(() => { if (!tableContainerRef.current || !hasNextPage || isFetching) return; @@ -181,20 +200,58 @@ const SteepSlopeLookUp = () => { setSelectedRegion({ city, county }); }; + // const handleDelete = async () => { + // try { + // if (selectedRow) { + // await slopeManageAPI.deleteSlope([selectedRow._id]); + // // 삭제 성공 후 데이터 갱신 + // await queryClient.invalidateQueries({ queryKey: ['slopes'] }); + // setSelectedRow(null); + // closeDeleteModal(); + // } + // } catch (error) { + // console.error('삭제 실패:', error); + // } + // }; const handleDelete = async () => { try { - if (selectedRow) { - await slopeManageAPI.deleteSlope([selectedRow._id]); - // 삭제 성공 후 데이터 갱신 - await queryClient.invalidateQueries({ queryKey: ['slopes'] }); + // 선택된 행들이 있는지 확인 (단일 선택 또는 다중 선택) + const rowsToDelete = + selectedRows.length > 0 + ? selectedRows + : selectedRow + ? [selectedRow] + : []; + + if (rowsToDelete.length > 0) { + const idsToDelete = rowsToDelete.map((row) => row._id); // 선택된 모든 항목의 ID 추출 + await slopeManageAPI.deleteSlope(idsToDelete); // API 호출로 삭제 처리 + await queryClient.invalidateQueries({ queryKey: ['slopes'] }); // 삭제 성공 후 데이터 갱신 + + // rowSelection 상태를 사용하는 경우 + if (setRowSelection) setRowSelection({}); + // 선택된 행 상태 초기화 + if (setSelectedRows) setSelectedRows([]); setSelectedRow(null); - closeDeleteModal(); + + closeDeleteModal(); // 모달 닫기 + + if (showNotification) { + showNotification(`${idsToDelete.length}개 항목이 삭제되었습니다.`, { + severity: 'success', + }); + } } } catch (error) { console.error('삭제 실패:', error); + // 실패 알림 (알림 기능이 있는 경우) + if (showNotification) { + showNotification('삭제 중 오류가 발생했습니다.', { + severity: 'error', + }); + } } }; - const handleEdit = async (updatedSlope: Slope) => { try { await slopeManageAPI.updateSlope(updatedSlope); @@ -246,6 +303,7 @@ const SteepSlopeLookUp = () => { closeDeleteModal={closeDeleteModal} handleDelete={handleDelete} selectedRow={selectedRow} + selectedRows={selectedRows} isEditModalOpen={isEditModalOpen} closeEditModal={closeEditModal} handleEdit={handleEdit} @@ -281,6 +339,7 @@ const SteepSlopeLookUp = () => { From 390d087dc221d0ea05a54af02d4d99e50fe479c2 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 17 May 2025 21:14:34 +0900 Subject: [PATCH 09/12] =?UTF-8?q?refactor:=20store=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManagePage/StepSlope/components/table/TableToolbar.tsx | 2 +- src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx | 2 +- .../components/table/store => stores}/steepSlopeStore.ts | 2 +- .../StepSlope/components/table/store => stores}/tableStore.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/{pages/ManagePage/StepSlope/components/table/store => stores}/steepSlopeStore.ts (74%) rename src/{pages/ManagePage/StepSlope/components/table/store => stores}/tableStore.ts (97%) diff --git a/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx b/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx index ece622e..691b347 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx @@ -6,7 +6,7 @@ import CachedRoundedIcon from '@mui/icons-material/CachedRounded'; import TravelExploreRoundedIcon from '@mui/icons-material/TravelExploreRounded'; import DownloadRoundedIcon from '@mui/icons-material/DownloadRounded'; import SearchRoundedIcon from '@mui/icons-material/SearchRounded'; -import { useSteepSlopeStore } from './store/steepSlopeStore'; +import { useSteepSlopeStore } from '../../../../../stores/steepSlopeStore'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; interface Region { diff --git a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx index c69c548..f5849c1 100644 --- a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx +++ b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx @@ -19,7 +19,7 @@ import styled from 'styled-components'; import { slopeManageAPI } from '../../../../apis/slopeManage'; import { useNotificationStore } from '../../../../hooks/notificationStore'; import { getSlopeColumns } from '../components/table/coloums'; -import { useSteepSlopeStore } from '../components/table/store/steepSlopeStore'; +import { useSteepSlopeStore } from '../../../../stores/steepSlopeStore'; import TableToolbar from '../components/table/TableToolbar'; import DataTable from '../components/table/DataTable'; diff --git a/src/pages/ManagePage/StepSlope/components/table/store/steepSlopeStore.ts b/src/stores/steepSlopeStore.ts similarity index 74% rename from src/pages/ManagePage/StepSlope/components/table/store/steepSlopeStore.ts rename to src/stores/steepSlopeStore.ts index 254876e..394d18c 100644 --- a/src/pages/ManagePage/StepSlope/components/table/store/steepSlopeStore.ts +++ b/src/stores/steepSlopeStore.ts @@ -1,5 +1,5 @@ import { createTableStore } from './tableStore'; -import { Slope } from '../../../../../../apis/slopeMap'; +import { Slope } from '../apis/slopeMap'; // 급경사지 스토어 생성 - 팩토리 함수 사용 export const useSteepSlopeStore = createTableStore(); diff --git a/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts b/src/stores/tableStore.ts similarity index 97% rename from src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts rename to src/stores/tableStore.ts index f71131e..10e7e83 100644 --- a/src/pages/ManagePage/StepSlope/components/table/store/tableStore.ts +++ b/src/stores/tableStore.ts @@ -5,7 +5,7 @@ import { OnChangeFn, RowSelectionState, } from '@tanstack/react-table'; -import { getDefaultColumnVisibility } from '..//coloums'; +import { getDefaultColumnVisibility } from '../pages/ManagePage/StepSlope/components/table/coloums'; // 제네릭 타입 T는 각 테이블에서 사용할 데이터 타입입니다 (Slope, SlopeOutlier 등) export interface TableState { From 239b6dc175caadcde094f383f17126e9f12df979 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 17 May 2025 21:55:38 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20props=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AddSlopeContainer.tsx | 6 +- .../components/ColumnFilterModal.tsx | 9 +- .../StepSlope/components/DeleteModal.tsx | 10 +- .../StepSlope/components/EditModal.tsx | 8 +- .../components/RegionFilterModal.tsx | 15 ++- .../StepSlope/components/SlopeForm.tsx | 10 +- .../StepSlope/components/table/DataTable.tsx | 18 +-- .../components/table/TableAction.tsx | 11 +- .../components/table/TableCheckbox.tsx | 8 +- .../components/table/TableModals.tsx | 20 +--- .../components/table/TableToolbar.tsx | 17 +-- .../StepSlope/pages/SteepSlopeAdd.tsx | 6 +- src/pages/ManagePage/interface.ts | 113 ++++++++++++++++++ 13 files changed, 131 insertions(+), 120 deletions(-) diff --git a/src/pages/ManagePage/StepSlope/components/AddSlopeContainer.tsx b/src/pages/ManagePage/StepSlope/components/AddSlopeContainer.tsx index 84941e0..1c37c70 100644 --- a/src/pages/ManagePage/StepSlope/components/AddSlopeContainer.tsx +++ b/src/pages/ManagePage/StepSlope/components/AddSlopeContainer.tsx @@ -1,11 +1,7 @@ import { Slope } from '../../../../apis/slopeMap'; import SlopeForm from './SlopeForm'; import { slopeManageAPI } from '../../../../apis/slopeManage'; - -interface AddSlopeProps { - isOpen: boolean; - onClose: () => void; -} +import { AddSlopeProps } from '../../interface'; const AddSlope = ({ isOpen, onClose }: AddSlopeProps) => { const onSubmit = async (newSlopeData: Slope) => { diff --git a/src/pages/ManagePage/StepSlope/components/ColumnFilterModal.tsx b/src/pages/ManagePage/StepSlope/components/ColumnFilterModal.tsx index dc3057f..f2f50b6 100644 --- a/src/pages/ManagePage/StepSlope/components/ColumnFilterModal.tsx +++ b/src/pages/ManagePage/StepSlope/components/ColumnFilterModal.tsx @@ -1,12 +1,5 @@ import styled from 'styled-components'; -import { Table } from '@tanstack/react-table'; -import { Slope } from '../../../../apis/slopeMap'; - -interface FilterModalProps { - isOpen: boolean; - onClose: () => void; - table: Table; -} +import { FilterModalProps } from '../../interface'; const FilterModal = ({ isOpen, onClose, table }: FilterModalProps) => { return ( diff --git a/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx b/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx index fe809db..da4aff3 100644 --- a/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx +++ b/src/pages/ManagePage/StepSlope/components/DeleteModal.tsx @@ -1,14 +1,6 @@ import styled from 'styled-components'; -import { Slope } from '../../../../apis/slopeMap'; import { useEffect, useState } from 'react'; - -interface DeleteConfirmModalProps { - isOpen: boolean; - onClose: () => void; - onConfirm: () => void; - selectedRow: Slope | null; - selectedRows: Slope[]; -} +import { DeleteConfirmModalProps } from '../../interface'; const DeleteConfirmModal = ({ isOpen, diff --git a/src/pages/ManagePage/StepSlope/components/EditModal.tsx b/src/pages/ManagePage/StepSlope/components/EditModal.tsx index 468e518..db2d01a 100644 --- a/src/pages/ManagePage/StepSlope/components/EditModal.tsx +++ b/src/pages/ManagePage/StepSlope/components/EditModal.tsx @@ -1,13 +1,7 @@ import { Slope } from '../../../../apis/slopeMap'; import { useState, useEffect } from 'react'; import SlopeForm from './SlopeForm'; - -interface EditModalProps { - isOpen: boolean; - onClose: () => void; - onSubmit: (updatedSlope: Slope) => void; - selectedRow: Slope | null; -} +import { EditModalProps } from '../../interface'; const EditModal = ({ isOpen, diff --git a/src/pages/ManagePage/StepSlope/components/RegionFilterModal.tsx b/src/pages/ManagePage/StepSlope/components/RegionFilterModal.tsx index 1041453..d5e9578 100644 --- a/src/pages/ManagePage/StepSlope/components/RegionFilterModal.tsx +++ b/src/pages/ManagePage/StepSlope/components/RegionFilterModal.tsx @@ -1,14 +1,13 @@ import styled from 'styled-components'; import { useState } from 'react'; import { CityOptions, CountyOptions } from './regionData'; +import { RegionFilterModalProps } from '../../interface'; -interface FilterModalProps { - isOpen: boolean; - onClose: () => void; - onRegionSelect: (city: string, county: string) => void; -} - -const FilterModal = ({ isOpen, onClose, onRegionSelect }: FilterModalProps) => { +const RegionFilterModal = ({ + isOpen, + onClose, + onRegionSelect, +}: RegionFilterModalProps) => { const [selectedCity, setSelectedCity] = useState('지역'); const [selectedCounty, setSelectedCounty] = useState('모두'); @@ -60,7 +59,7 @@ const FilterModal = ({ isOpen, onClose, onRegionSelect }: FilterModalProps) => { ); }; -export default FilterModal; +export default RegionFilterModal; const ModalOverlay = styled.div<{ $isOpen: boolean }>` display: ${(props) => (props.$isOpen ? 'flex' : 'none')}; diff --git a/src/pages/ManagePage/StepSlope/components/SlopeForm.tsx b/src/pages/ManagePage/StepSlope/components/SlopeForm.tsx index 8d9bb17..ff9408d 100644 --- a/src/pages/ManagePage/StepSlope/components/SlopeForm.tsx +++ b/src/pages/ManagePage/StepSlope/components/SlopeForm.tsx @@ -1,15 +1,7 @@ import styled from 'styled-components'; import { Slope } from '../../../../apis/slopeMap'; import { useState, useEffect } from 'react'; - -interface SlopeFormProps { - titleText: string; - initialData: Slope; - isOpen: boolean; - onClose: () => void; - onSubmit: (data: Slope) => void; - submitButtonText: string; -} +import { SlopeFormProps } from '../../interface'; const SlopeForm = ({ titleText, diff --git a/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx b/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx index 5e26230..b61baa7 100644 --- a/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/DataTable.tsx @@ -1,22 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { - Table as TableInstance, - Row as RowInstance, -} from '@tanstack/react-table'; - -import { Virtualizer } from '@tanstack/react-virtual'; -import { Slope } from '../../../../../apis/slopeMap'; - -interface DataTableProps { - tableContainerRef: React.RefObject; - handleScroll: () => void; - table: TableInstance; - rows: RowInstance[]; - rowVirtualizer: Virtualizer; - selectedRow: Slope | null; - setSelectedRow: (row: Slope | null) => void; -} +import { DataTableProps } from '../../../interface'; const DataTable: React.FC = ({ tableContainerRef, diff --git a/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx b/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx index 92ff5f7..26bfe22 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableAction.tsx @@ -1,16 +1,7 @@ import styled from 'styled-components'; import React from 'react'; -import { Slope } from '../../../../../apis/slopeMap'; import LoadingMessage from '../../../components/LoadingMessage'; - -interface TableActionProps { - isLoading: boolean; - selectedRow: Slope | null; - // 선택된 행들 배열 추가 - selectedRows: Slope[]; - openEditModal: () => void; - openDeleteModal: () => void; -} +import { TableActionProps } from '../../../interface'; const TableAction: React.FC = ({ isLoading, diff --git a/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx b/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx index e546ff9..827d5bb 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableCheckbox.tsx @@ -1,12 +1,6 @@ import React, { useRef, useEffect } from 'react'; import styled from 'styled-components'; - -interface TableCheckboxProps { - indeterminate?: boolean; - checked?: boolean; - disabled?: boolean; - onChange: (e: React.ChangeEvent) => void; -} +import { TableCheckboxProps } from '../../../interface'; const TableCheckbox: React.FC = ({ indeterminate = false, diff --git a/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx b/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx index 478d249..ba37f3c 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableModals.tsx @@ -1,27 +1,9 @@ import React from 'react'; -import { Slope } from '../../../../../apis/slopeMap'; import FilterModal from '../ColumnFilterModal'; import DeleteConfirmModal from '../DeleteModal'; import EditModal from '../EditModal'; import RegionFilterModal from '../RegionFilterModal'; -import { Table as TableInstance } from '@tanstack/react-table'; - -interface TableModalProps { - isModalOpen: boolean; - closeModal: () => void; - table: TableInstance; - isRegionModalOpen: boolean; - closeRegionModal: () => void; - handleRegionSelect: (city: string, county: string) => void; - isDeleteModalOpen: boolean; - closeDeleteModal: () => void; - handleDelete: () => void; - selectedRow: Slope | null; - selectedRows: Slope[]; - isEditModalOpen: boolean; - closeEditModal: () => void; - handleEdit: (updatedSlope: Slope) => void; -} +import { TableModalProps } from '../../../interface'; const TableModals: React.FC = ({ isModalOpen, diff --git a/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx b/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx index 691b347..8808436 100644 --- a/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx +++ b/src/pages/ManagePage/StepSlope/components/table/TableToolbar.tsx @@ -9,22 +9,7 @@ import SearchRoundedIcon from '@mui/icons-material/SearchRounded'; import { useSteepSlopeStore } from '../../../../../stores/steepSlopeStore'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; -interface Region { - city: string; - county: string; -} - -interface TableToolbarProps { - title: string; - setSearchQuery: (query: string) => void; - inputValue: string; - setInputValue: (value: string) => void; - selectedRegion: Region | null; - resetFilters: () => void; - downloadExcel: () => void; - isDownloading: boolean; - totalCount: number; -} +import { TableToolbarProps } from '../../../interface'; const TableToolbar: React.FC = ({ title, diff --git a/src/pages/ManagePage/StepSlope/pages/SteepSlopeAdd.tsx b/src/pages/ManagePage/StepSlope/pages/SteepSlopeAdd.tsx index 70313c5..b499146 100644 --- a/src/pages/ManagePage/StepSlope/pages/SteepSlopeAdd.tsx +++ b/src/pages/ManagePage/StepSlope/pages/SteepSlopeAdd.tsx @@ -5,11 +5,7 @@ import { slopeManageAPI } from '../../../../apis/slopeManage'; import CloudUploadRoundedIcon from '@mui/icons-material/CloudUploadRounded'; import { useNotificationStore } from '../../../../hooks/notificationStore'; import AddSlope from '../components/AddSlopeContainer'; -interface FileInputContainerProps { - $isDragActive?: boolean; - $hasFile?: boolean; - theme?: any; -} +import { FileInputContainerProps } from '../../interface'; const SteepSlopeAdd: React.FC = () => { const [isDragActive, setIsDragActive] = useState(false); diff --git a/src/pages/ManagePage/interface.ts b/src/pages/ManagePage/interface.ts index 7860da5..d356838 100644 --- a/src/pages/ManagePage/interface.ts +++ b/src/pages/ManagePage/interface.ts @@ -35,3 +35,116 @@ export interface PaginationProps { onLastPage: () => void; onPageSizeChange: (size: number) => void; } + +//급경사지 테이블 관련 props타입 +import { + Table as TableInstance, + Row as RowInstance, +} from '@tanstack/react-table'; + +import { Virtualizer } from '@tanstack/react-virtual'; +import { Slope } from '../../apis/slopeMap'; + +export interface DataTableProps { + tableContainerRef: React.RefObject; + handleScroll: () => void; + table: TableInstance; + rows: RowInstance[]; + rowVirtualizer: Virtualizer; + selectedRow: Slope | null; + setSelectedRow: (row: Slope | null) => void; +} + +export interface TableActionProps { + isLoading: boolean; + selectedRow: Slope | null; + // 선택된 행들 배열 추가 + selectedRows: Slope[]; + openEditModal: () => void; + openDeleteModal: () => void; +} + +export interface TableCheckboxProps { + indeterminate?: boolean; + checked?: boolean; + disabled?: boolean; + onChange: (e: React.ChangeEvent) => void; +} + +export interface TableModalProps { + isModalOpen: boolean; + closeModal: () => void; + table: TableInstance; + isRegionModalOpen: boolean; + closeRegionModal: () => void; + handleRegionSelect: (city: string, county: string) => void; + isDeleteModalOpen: boolean; + closeDeleteModal: () => void; + handleDelete: () => void; + selectedRow: Slope | null; + selectedRows: Slope[]; + isEditModalOpen: boolean; + closeEditModal: () => void; + handleEdit: (updatedSlope: Slope) => void; +} + +export interface TableToolbarProps { + title: string; + setSearchQuery: (query: string) => void; + inputValue: string; + setInputValue: (value: string) => void; + selectedRegion: Region | null; + resetFilters: () => void; + downloadExcel: () => void; + isDownloading: boolean; + totalCount: number; +} + +interface Region { + city: string; + county: string; +} + +export interface AddSlopeProps { + isOpen: boolean; + onClose: () => void; +} + +export interface FilterModalProps { + isOpen: boolean; + onClose: () => void; + table: TableInstance; +} +export interface RegionFilterModalProps { + isOpen: boolean; + onClose: () => void; + onRegionSelect: (city: string, county: string) => void; +} +export interface DeleteConfirmModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + selectedRow: Slope | null; + selectedRows: Slope[]; +} + +export interface EditModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (updatedSlope: Slope) => void; + selectedRow: Slope | null; +} + +export interface SlopeFormProps { + titleText: string; + initialData: Slope; + isOpen: boolean; + onClose: () => void; + onSubmit: (data: Slope) => void; + submitButtonText: string; +} +export interface FileInputContainerProps { + $isDragActive?: boolean; + $hasFile?: boolean; + theme?: any; +} From d5634bfc3defb30ce45dbbe6376130d778feccb5 Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 17 May 2025 21:55:52 +0900 Subject: [PATCH 11/12] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx index f5849c1..3c50e76 100644 --- a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx +++ b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx @@ -200,19 +200,6 @@ const SteepSlopeLookUp = () => { setSelectedRegion({ city, county }); }; - // const handleDelete = async () => { - // try { - // if (selectedRow) { - // await slopeManageAPI.deleteSlope([selectedRow._id]); - // // 삭제 성공 후 데이터 갱신 - // await queryClient.invalidateQueries({ queryKey: ['slopes'] }); - // setSelectedRow(null); - // closeDeleteModal(); - // } - // } catch (error) { - // console.error('삭제 실패:', error); - // } - // }; const handleDelete = async () => { try { // 선택된 행들이 있는지 확인 (단일 선택 또는 다중 선택) From c6253baaa64d23e75a239315dc1380cd5337decb Mon Sep 17 00:00:00 2001 From: KimDoHyun Date: Sat, 17 May 2025 21:58:28 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20useEffect=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx index 3c50e76..dc29da9 100644 --- a/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx +++ b/src/pages/ManagePage/StepSlope/pages/SteepSlopeLookUp.tsx @@ -169,7 +169,7 @@ const SteepSlopeLookUp = () => { // 선택된 행 정보 업데이트 setSelectedRows(selectedRowsArray); setSelectedRow(selectedRowsArray.length > 0 ? selectedRowsArray[0] : null); - }, [rowSelection, flatData]); + }, [rowSelection, flatData, setSelectedRow, setSelectedRows]); // 스크롤 이벤트 핸들러(무한스크롤 기능) const handleScroll = useCallback(() => {