11'use client' ;
22
3- import { Check , Calendar , Edit2 , Trash2 , MoreVertical } from 'lucide-react' ;
3+ import { Check , Calendar , Edit2 , Trash2 , MoreVertical , X } from 'lucide-react' ;
44import { useEffect , useRef , useState } from 'react' ;
5+ import DatePicker from './DatePicker' ;
56
67interface TaskItemProps {
78 id : string ; // id (토글 식별용)
89 text : string ;
910 isCompleted : boolean ;
10- deadline ?: string | Date ; // Date 타입 받을 수 있게 유연하게
11+ deadline ?: Date ; // Date 타입 받을 수 있게 유연하게
1112 onToggle : ( id : string , currentStatus : boolean ) => void ; // 토글 핸들러
1213 onDelete : ( itemId : string , text : string ) => void ;
14+ onUpdate : (
15+ itemId : string ,
16+ newText : string ,
17+ newDeadline ?: Date | null
18+ ) => void ;
1319}
1420
1521export default function TaskItem ( {
@@ -19,9 +25,15 @@ export default function TaskItem({
1925 deadline,
2026 onToggle,
2127 onDelete,
28+ onUpdate,
2229} : TaskItemProps ) {
2330 const [ showPlanItemMenu , setShowPlanItemMenu ] = useState ( false ) ;
2431
32+ const [ isEditing , setIsEditing ] = useState ( false ) ;
33+ const [ editText , setEditText ] = useState ( text ) ;
34+ // deadline이 string으로 올 수도 있으니 안전하게 변환 (보통은 Date로 옴)
35+ const [ editDeadline , setEditDeadline ] = useState < Date | undefined > ( undefined ) ;
36+
2537 const menuRef = useRef < HTMLDivElement > ( null ) ;
2638
2739 // 날짜 포맷팅 (Date 객체나 문자열 모두 처리)
@@ -63,77 +75,147 @@ export default function TaskItem({
6375 setShowPlanItemMenu ( false ) ;
6476 } ;
6577
66- return (
67- < div
68- className = { `mb-2 flex items-center justify-between rounded-xl p-4 transition-colors ${
69- isCompleted ? 'bg-gray-50' : 'bg-gray-100'
70- } `}
71- >
72- < div className = "flex w-full items-center gap-3" >
73- { /* 체크박스 (클릭 가능) */ }
74- < div
75- onClick = { ( ) => onToggle ( id , isCompleted ) }
76- className = { `flex h-6 w-6 flex-shrink-0 cursor-pointer items-center justify-center rounded-md border transition-colors ${
77- isCompleted
78- ? 'border-green-500 bg-green-500 text-white'
79- : 'border-gray-300 bg-white hover:border-purple-400'
80- } `}
81- >
82- { isCompleted && < Check size = { 16 } strokeWidth = { 3 } /> }
78+ // 하위항목 수정 모드 시작
79+ const handleStartEdit = ( e : React . MouseEvent ) => {
80+ e . stopPropagation ( ) ;
81+
82+ setEditText ( text ) ;
83+ setEditDeadline ( deadline ) ;
84+
85+ setIsEditing ( true ) ;
86+ setShowPlanItemMenu ( false ) ;
87+ } ;
88+
89+ // 하위 항목 수정 저장
90+ const handleSaveEdit = ( ) => {
91+ if ( ! editText . trim ( ) ) return ;
92+ // 변경된 내용 부모에게 전달 (날짜가 없으면 null)
93+ onUpdate ( id , editText , editDeadline || null ) ;
94+ setIsEditing ( false ) ;
95+ } ;
96+
97+ // 하위 항목 수정 취소
98+ const handleCancelEdit = ( ) => {
99+ setIsEditing ( false ) ;
100+ } ;
101+
102+ if ( isEditing ) {
103+ return (
104+ < div className = "animate-fadeIn mb-2 flex items-center gap-3 rounded-xl border-2 border-[#556BD6]/30 bg-white p-2 pl-4 shadow-sm" >
105+ { /* 아이콘 (수정 모드임을 표시) */ }
106+ < div className = "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-md border border-gray-200 bg-gray-50" >
107+ < Edit2 size = { 14 } className = "text-[#556BD6]" />
83108 </ div >
84109
85- { /* 텍스트 내용 */ }
86- < div className = "flex flex-col" >
87- < span
88- className = { `text-sm font-medium ${
89- isCompleted ? 'text-gray-400 line-through' : 'text-gray-700'
90- } `}
110+ < div className = "flex flex-1 flex-col gap-2" >
111+ { /* 텍스트 입력 */ }
112+ < input
113+ autoFocus
114+ type = "text"
115+ value = { editText }
116+ onChange = { ( e ) => setEditText ( e . target . value ) }
117+ onKeyDown = { ( e ) => {
118+ if ( e . key === 'Enter' && ! e . nativeEvent . isComposing )
119+ handleSaveEdit ( ) ;
120+ if ( e . key === 'Escape' ) handleCancelEdit ( ) ;
121+ } }
122+ className = "w-full bg-transparent text-sm font-medium text-gray-900 placeholder:text-gray-400 focus:outline-none"
123+ />
124+
125+ { /* 날짜 선택 (DatePicker 재사용) */ }
126+ < div className = "flex items-center" >
127+ < DatePicker date = { editDeadline } setDate = { setEditDeadline } />
128+ </ div >
129+ </ div >
130+
131+ { /* 저장/취소 버튼 */ }
132+ < div className = "flex items-center gap-1 pr-2" >
133+ < button
134+ onClick = { handleSaveEdit }
135+ className = "rounded-lg p-2 text-[#556BD6] transition-colors hover:bg-[#556BD6]/10"
91136 >
92- { text }
93- </ span >
94- { deadline && (
95- < span className = "mt-0.5 flex items-center gap-1 text-xs text-gray-400" >
96- { /* 작은 달력 아이콘 추가 */ }
97- < Calendar size = { 10 } />
98- 마감: { formatDate ( deadline ) }
99- </ span >
100- ) }
137+ < span className = "text-xs font-bold" > 저장</ span >
138+ </ button >
139+ < button
140+ onClick = { handleCancelEdit }
141+ className = "rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100"
142+ >
143+ < X size = { 18 } />
144+ </ button >
101145 </ div >
102146 </ div >
147+ ) ;
148+ } else {
149+ return (
103150 < div
104- ref = { menuRef }
105- className = { `relative ${ showPlanItemMenu ? 'z-50' : 'z-10' } ` }
151+ className = { `mb-2 flex items-center justify-between rounded-xl p-4 transition-colors ${
152+ isCompleted ? 'bg-gray-50' : 'bg-gray-100'
153+ } `}
106154 >
107- < button
108- onClick = { ( e ) => {
109- e . stopPropagation ( ) ; // 부모 클릭(아코디언 토글) 방지
110- setShowPlanItemMenu ( ! showPlanItemMenu ) ;
111- } }
112- className = "rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100"
113- >
114- < MoreVertical size = { 20 } />
115- </ button >
116-
117- { showPlanItemMenu && (
118- < div className = "absolute top-8 right-0 w-32 overflow-hidden rounded-lg border border-gray-100 bg-white py-1 shadow-lg" >
119- < button
120- className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50"
121- onClick = { ( e ) => {
122- e . stopPropagation ( ) ;
123- alert ( '수정 기능' ) ;
124- } }
125- >
126- < Edit2 size = { 14 } /> 수정
127- </ button >
128- < button
129- className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50"
130- onClick = { handleDeletePlanItem }
155+ < div className = "flex w-full items-center gap-3" >
156+ { /* 체크박스 */ }
157+ < div
158+ onClick = { ( ) => onToggle ( id , isCompleted ) }
159+ className = { `flex h-6 w-6 flex-shrink-0 cursor-pointer items-center justify-center rounded-md border transition-colors ${
160+ isCompleted
161+ ? 'border-green-500 bg-green-500 text-white'
162+ : 'border-gray-300 bg-white hover:border-purple-400'
163+ } `}
164+ >
165+ { isCompleted && < Check size = { 16 } strokeWidth = { 3 } /> }
166+ </ div >
167+
168+ { /* 텍스트 및 날짜 표시 */ }
169+ < div className = "flex flex-col" >
170+ < span
171+ className = { `text-sm font-medium ${
172+ isCompleted ? 'text-gray-400 line-through' : 'text-gray-700'
173+ } `}
131174 >
132- < Trash2 size = { 14 } /> 삭제
133- </ button >
175+ { text }
176+ </ span >
177+ { deadline && (
178+ < span className = "mt-0.5 flex items-center gap-1 text-xs text-gray-400" >
179+ < Calendar size = { 10 } />
180+ 마감: { formatDate ( deadline ) }
181+ </ span >
182+ ) }
134183 </ div >
135- ) }
184+ </ div >
185+
186+ { /* 메뉴 버튼 */ }
187+ < div
188+ ref = { menuRef }
189+ className = { `relative ${ showPlanItemMenu ? 'z-50' : 'z-10' } ` }
190+ >
191+ < button
192+ onClick = { ( e ) => {
193+ e . stopPropagation ( ) ;
194+ setShowPlanItemMenu ( ! showPlanItemMenu ) ;
195+ } }
196+ className = "rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100"
197+ >
198+ < MoreVertical size = { 20 } />
199+ </ button >
200+
201+ { showPlanItemMenu && (
202+ < div className = "absolute top-8 right-0 w-32 overflow-hidden rounded-lg border border-gray-100 bg-white py-1 shadow-lg" >
203+ < button
204+ className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50"
205+ onClick = { handleStartEdit } // ✅ 수정 시작
206+ >
207+ < Edit2 size = { 14 } /> 수정
208+ </ button >
209+ < button
210+ className = "flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50"
211+ onClick = { handleDeletePlanItem }
212+ >
213+ < Trash2 size = { 14 } /> 삭제
214+ </ button >
215+ </ div >
216+ ) }
217+ </ div >
136218 </ div >
137- </ div >
138- ) ;
219+ ) ;
220+ }
139221}
0 commit comments