11'use client' ;
22
3- import { useState , useEffect } from 'react' ;
3+ import { useState , useEffect , useRef } from 'react' ;
44import {
55 ChevronDown ,
66 ChevronUp ,
@@ -17,30 +17,32 @@ import {
1717 toggleItemStatus ,
1818 PlanItem ,
1919 addPlanItem ,
20- deletePlan ,
20+ deletePlanItem ,
2121} from '@/lib/planManageService' ;
2222
2323interface PlanSectionProps {
2424 userId : string ;
2525 planId : string ;
2626 title : string ;
2727 description ?: string ;
28- onDelete : ( planId : string , title : string ) => void ;
28+ onDeletePlan : ( planId : string , title : string ) => void ;
2929}
3030
3131export default function PlanSection ( {
3232 userId,
3333 planId,
3434 title,
3535 description,
36- onDelete ,
36+ onDeletePlan ,
3737} : PlanSectionProps ) {
3838 const [ isOpen , setIsOpen ] = useState ( true ) ;
3939 const [ tasks , setTasks ] = useState < PlanItem [ ] > ( [ ] ) ;
4040 const [ isTasksLoading , setIsTasksLoading ] = useState ( true ) ;
4141 const [ isAddingTask , setIsAddingTask ] = useState ( false ) ;
4242
43- const [ showMenu , setShowMenu ] = useState ( false ) ;
43+ const [ showPlanMenu , setShowPlanMenu ] = useState ( false ) ;
44+
45+ const menuRef = useRef < HTMLDivElement > ( null ) ;
4446
4547 // 1. 초기 데이터 로드
4648 useEffect ( ( ) => {
@@ -59,10 +61,29 @@ export default function PlanSection({
5961 setIsTasksLoading ( false ) ;
6062 }
6163 } ;
62-
6364 loadInitialTasks ( ) ;
6465 } , [ userId , planId ] ) ; // userId나 planId가 바뀔 때만 실행됨
6566
67+ // 1-2. 마우스 클릭 감지
68+ useEffect ( ( ) => {
69+ const handleClickOutside = ( event : MouseEvent ) => {
70+ // 메뉴가 열려있고, 클릭된 요소가 menuRef 내부가 아니라면 닫기
71+ if (
72+ showPlanMenu &&
73+ menuRef . current &&
74+ ! menuRef . current . contains ( event . target as Node )
75+ ) {
76+ setShowPlanMenu ( false ) ;
77+ }
78+ } ;
79+ document . addEventListener ( 'mousedown' , handleClickOutside ) ;
80+
81+ // 언마운트 시 제거
82+ return ( ) => {
83+ document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
84+ } ;
85+ } , [ showPlanMenu ] ) ;
86+
6687 // 2. 하위 항목 상태(완료/미완료) 토글 핸들러
6788 const handleToggleTask = async ( itemId : string , currentStatus : boolean ) => {
6889 try {
@@ -80,7 +101,7 @@ export default function PlanSection({
80101 const updatedTasks = await fetchPlanItems ( userId , planId ) ;
81102 setTasks ( updatedTasks ) ;
82103 } catch ( error ) {
83- console . error ( '상태 변경 실패:' , error ) ;
104+ console . error ( '상태 변경 실패: ' , error ) ;
84105 // 에러 시 원래대로 돌리거나 다시 불러오기
85106 const rolledBackTasks = await fetchPlanItems ( userId , planId ) ;
86107 setTasks ( rolledBackTasks ) ;
@@ -99,17 +120,32 @@ export default function PlanSection({
99120
100121 setIsAddingTask ( false ) ; // 입력 폼 닫기
101122 } catch ( error ) {
102- console . error ( '하위 항목 추가 실패:' , error ) ;
123+ console . error ( '하위 항목 추가 실패: ' , error ) ;
103124 }
104125 } ;
105126
106- //
107- const handleDeleteClick = ( e : React . MouseEvent ) => {
127+ // 4. 플랜 삭제 핸들러
128+ const handleDeletePlan = ( e : React . MouseEvent ) => {
108129 e . stopPropagation ( ) ; // 카드 열림/닫힘 방지
109130
110- onDelete ( planId , title ) ;
131+ onDeletePlan ( planId , title ) ;
132+
133+ setShowPlanMenu ( false ) ;
134+ } ;
111135
112- setShowMenu ( false ) ;
136+ // 5. 하위 항목 삭제 핸들러
137+ const handleDeletePlanItem = async ( itemId : string , title : string ) => {
138+ if ( confirm ( `'${ title } ' 하위 항목을 정말 삭제하시겠습니까?` ) ) {
139+ try {
140+ await deletePlanItem ( userId , itemId ) ;
141+
142+ // 목록 새로고침
143+ const fetchedPlanItems = await fetchPlanItems ( userId , planId ) ;
144+ setTasks ( fetchedPlanItems ) ;
145+ } catch ( err ) {
146+ console . error ( err ) ;
147+ }
148+ }
113149 } ;
114150
115151 // 완료된 할 일 개수 계산
@@ -119,7 +155,7 @@ export default function PlanSection({
119155 // isOpen 상태 false 시 포커스 초기화
120156 useEffect ( ( ) => {
121157 setIsAddingTask ( false ) ; //
122- setShowMenu ( false ) ; // 드롭다운 메뉴
158+ setShowPlanMenu ( false ) ; // 드롭다운 메뉴
123159 } , [ isOpen ] ) ;
124160
125161 return (
@@ -141,32 +177,32 @@ export default function PlanSection({
141177 { completedCount } /{ totalCount }
142178 </ span >
143179 </ div >
144- < div className = "relative z-10" >
180+ < div className = "relative z-10" ref = { menuRef } >
145181 < button
146182 onClick = { ( e ) => {
147183 e . stopPropagation ( ) ; // 부모 클릭(아코디언 토글) 방지
148- setShowMenu ( ! showMenu ) ;
184+ setShowPlanMenu ( ! showPlanMenu ) ;
149185 } }
150186 className = "rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100"
151187 >
152188 < MoreVertical size = { 20 } />
153189 </ button >
154190
155191 { /* 드롭다운 메뉴 (절대 위치) */ }
156- { showMenu && (
192+ { showPlanMenu && (
157193 < div className = "absolute top-8 right-0 w-32 overflow-hidden rounded-lg border border-gray-100 bg-white py-1 shadow-lg" >
158194 < button
159195 className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50"
160196 onClick = { ( e ) => {
161197 e . stopPropagation ( ) ;
162- alert ( '수정 기능' ) ;
198+ alert ( '플랜 수정 기능' ) ;
163199 } }
164200 >
165201 < Edit2 size = { 14 } /> 수정
166202 </ button >
167203 < button
168204 className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50"
169- onClick = { handleDeleteClick }
205+ onClick = { handleDeletePlan }
170206 >
171207 < Trash2 size = { 14 } /> 삭제
172208 </ button >
@@ -201,6 +237,7 @@ export default function PlanSection({
201237 deadline = { task . deadline }
202238 isCompleted = { task . isChecked }
203239 onToggle = { handleToggleTask }
240+ onDelete = { handleDeletePlanItem }
204241 />
205242 ) )
206243 ) }
@@ -215,7 +252,7 @@ export default function PlanSection({
215252 ) : (
216253 < button
217254 onClick = { ( ) => setIsAddingTask ( true ) }
218- className = "mt-2 flex w-full items-center justify-center gap-1 rounded-xl border-2 border-dashed border-[#556BD6]/30 py-3 text-sm font-medium text-[#556BD6] transition-colors hover:bg-[#556BD6]/5"
255+ className = "mt-2 flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-[#556BD6]/30 py-3 text-sm font-medium text-[#556BD6] transition-colors hover:bg-[#556BD6]/5"
219256 >
220257 < Plus size = { 16 } /> 새 하위항목 추가
221258 </ button >
0 commit comments