@@ -12,7 +12,6 @@ import {
1212 RotateCcw ,
1313 CheckCircle2 ,
1414 XCircle ,
15- // Mail,
1615 MailPlus ,
1716 MailX ,
1817} from 'lucide-react' ;
@@ -35,12 +34,8 @@ export interface StatementItemProps {
3534 part : 'subject' | 'verb' | 'object' ,
3635 statementId : string
3736 ) => void ;
38- onPartUpdate : (
39- statementId : string ,
40- part : 'subject' | 'verb' | 'object' ,
41- value : string
42- ) => void ;
43- onSave : ( statementId : string ) => void ;
37+ // When the green save icon is clicked, the updated entry (draft) is passed back
38+ onLocalSave : ( updatedEntry : Entry ) => void ;
4439 onDelete : ( statementId : string ) => void ;
4540 onTogglePublic : ( statementId : string ) => void ;
4641 onEditClick : ( statementId : string ) => void ;
@@ -49,6 +44,7 @@ export interface StatementItemProps {
4944 actionId : string ,
5045 updated : { text : string ; dueDate ?: string }
5146 ) => void ;
47+ onCancel ?: ( statementId : string ) => void ;
5248 onDeleteAction ?: ( statementId : string , actionId : string ) => void ;
5349 onAddAction ?: (
5450 statementId : string ,
@@ -64,11 +60,11 @@ const StatementItem: React.FC<StatementItemProps> = ({
6460 isEditing,
6561 editingPart,
6662 onPartClick,
67- onPartUpdate,
68- onSave,
63+ onLocalSave,
6964 onDelete,
7065 onTogglePublic,
7166 onEditClick,
67+ onCancel,
7268 onEditAction = ( ) => { } ,
7369 onDeleteAction = ( ) => { } ,
7470 onAddAction = ( ) => { } ,
@@ -79,12 +75,31 @@ const StatementItem: React.FC<StatementItemProps> = ({
7975 const [ isActionsExpanded , setIsActionsExpanded ] = React . useState ( false ) ;
8076 const objectInputRef = useRef < HTMLInputElement > ( null ) ;
8177
78+ // Local "draft" state to hold unsaved modifications.
79+ const [ draft , setDraft ] = React . useState < Entry > ( statement ) ;
80+
81+ // Whenever the statement prop changes (or when not editing), re-sync the draft.
82+ useEffect ( ( ) => {
83+ setDraft ( statement ) ;
84+ } , [ statement ] ) ;
85+
8286 useEffect ( ( ) => {
8387 if ( editingPart === 'object' && objectInputRef . current ) {
8488 objectInputRef . current . focus ( ) ;
8589 }
8690 } , [ editingPart ] ) ;
8791
92+ // Local function to update a specific part in the draft.
93+ const updatePart = ( part : 'subject' | 'verb' | 'object' , value : string ) => {
94+ setDraft ( ( prev ) => ( {
95+ ...prev ,
96+ atoms : {
97+ ...prev . atoms ,
98+ [ part ] : value ,
99+ } ,
100+ } ) ) ;
101+ } ;
102+
88103 if ( isEditing ) {
89104 return (
90105 < div className = 'flex items-center space-x-2 bg-gray-100 p-2 rounded' >
@@ -93,88 +108,101 @@ const StatementItem: React.FC<StatementItemProps> = ({
93108 variant = 'ghost'
94109 size = 'sm'
95110 onClick = { ( ) => {
96- console . log ( 'Privacy toggle clicked for statement:' , statement . id ) ;
97- onTogglePublic ( statement . id ) ;
111+ console . log ( 'Privacy toggle clicked for statement:' , draft . id ) ;
112+ onTogglePublic ( draft . id ) ;
98113 } }
99114 className = { `rounded-md px-3 py-2 transition-colors ${
100- statement . isPublic
115+ draft . isPublic
101116 ? 'bg-green-100 text-green-700 hover:bg-green-200'
102117 : 'bg-red-100 text-red-700 hover:bg-red-200'
103118 } `}
104119 >
105- { statement . isPublic ? < MailPlus size = { 16 } /> : < MailX size = { 16 } /> }
120+ { draft . isPublic ? < MailPlus size = { 16 } /> : < MailX size = { 16 } /> }
106121 </ Button >
107122
108123 < div className = 'flex flex-1 items-center space-x-2' >
109124 { /* Subject */ }
110125 < div
111- onClick = { ( ) => onPartClick ( 'subject' , statement . id ) }
126+ onClick = { ( ) => onPartClick ( 'subject' , draft . id ) }
112127 className = 'cursor-pointer px-2 py-1 rounded bg-subjectSelector hover:bg-subjectSelectorHover'
113128 >
114129 { editingPart === 'subject' ? (
115130 < SubjectSelector
116- value = { statement . atoms . subject }
117- onChange = { ( value ) =>
118- onPartUpdate ( statement . id , 'subject' , value )
119- }
131+ value = { draft . atoms . subject }
132+ onChange = { ( value ) => updatePart ( 'subject' , value ) }
120133 onAddDescriptor = { ( ) => { } }
121134 username = {
122- statement . atoms . subject . split ( "'s" ) [ 0 ] ||
123- statement . atoms . subject
135+ draft . atoms . subject . split ( "'s" ) [ 0 ] || draft . atoms . subject
124136 }
125137 />
126138 ) : (
127- statement . atoms . subject
139+ draft . atoms . subject
128140 ) }
129141 </ div >
130142 { /* Verb */ }
131143 < div
132144 className = 'cursor-pointer px-2 py-1 rounded bg-verbSelector hover:bg-verbSelectorHover'
133- onClick = { ( ) => onPartClick ( 'verb' , statement . id ) }
145+ onClick = { ( ) => onPartClick ( 'verb' , draft . id ) }
134146 >
135147 { editingPart === 'verb' ? (
136148 < VerbSelector
137- onVerbSelect = { ( verb ) =>
138- onPartUpdate ( statement . id , 'verb' , verb . id )
139- }
149+ onVerbSelect = { ( verb ) => updatePart ( 'verb' , verb . id ) }
140150 onClose = { ( ) => onPartClick ( 'verb' , '' ) }
141151 />
142152 ) : (
143- < span > { getVerbName ( statement . atoms . verb ) } </ span >
153+ < span > { getVerbName ( draft . atoms . verb ) } </ span >
144154 ) }
145155 </ div >
146156 { /* Object */ }
147157 < div
148- onClick = { ( ) => onPartClick ( 'object' , statement . id ) }
158+ onClick = { ( ) => onPartClick ( 'object' , draft . id ) }
149159 className = 'cursor-pointer px-2 py-1 rounded bg-objectInput hover:bg-objectInputHover'
150160 >
151161 { editingPart === 'object' ? (
152162 < Input
153163 ref = { objectInputRef }
154- value = { statement . atoms . object }
155- onChange = { ( e ) =>
156- onPartUpdate ( statement . id , 'object' , e . target . value )
157- }
164+ value = { draft . atoms . object }
165+ onChange = { ( e ) => updatePart ( 'object' , e . target . value ) }
158166 className = 'w-full'
159167 />
160168 ) : (
161- statement . atoms . object
169+ draft . atoms . object
162170 ) }
163171 </ div >
164172 </ div >
165173 < div className = 'flex items-center space-x-2 ml-auto' >
174+ { /* Final Save button (green icon):
175+ This commits the local draft to the database via onLocalSave */ }
166176 < Button
167177 variant = 'ghost'
168178 size = 'sm'
169- onClick = { ( ) => onSave ( statement . id ) }
179+ onClick = { ( ) => {
180+ onLocalSave ( draft ) ;
181+ } }
170182 className = 'text-green-500 hover:text-green-700'
171183 >
172184 < Save size = { 16 } />
173185 </ Button >
186+ { /* Cancel button: resets draft and exits edit mode */ }
187+ < Button
188+ variant = 'ghost'
189+ size = 'sm'
190+ onClick = { ( ) => {
191+ // Reset local draft to original statement passed from parent.
192+ setDraft ( statement ) ;
193+ // Call the onCancel prop if provided.
194+ if ( onCancel ) {
195+ onCancel ( statement . id ) ;
196+ }
197+ } }
198+ className = 'text-gray-500 hover:text-gray-700'
199+ >
200+ Cancel
201+ </ Button >
174202 < Button
175203 variant = 'ghost'
176204 size = 'sm'
177- onClick = { ( ) => onDelete ( statement . id ) }
205+ onClick = { ( ) => onDelete ( draft . id ) }
178206 className = 'text-red-500 hover:text-red-700'
179207 >
180208 < Trash2 size = { 16 } />
@@ -184,7 +212,7 @@ const StatementItem: React.FC<StatementItemProps> = ({
184212 ) ;
185213 }
186214
187- // Static view when not in editing mode with grouped Edit and Delete
215+ // Static view when not in editing mode.
188216 return (
189217 < div
190218 className = { `bg-white border rounded-md p-3 space-y-2 shadow-sm ${
@@ -202,7 +230,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
202230 statement . isPublic ? 'text-green-500' : 'text-red-500'
203231 } `}
204232 >
205- { /* {statement.isPublic ? <Eye size={16} /> : <EyeOff size={16} /> } */ }
206233 { statement . isPublic ? (
207234 < MailPlus size = { 16 } />
208235 ) : (
@@ -216,15 +243,13 @@ const StatementItem: React.FC<StatementItemProps> = ({
216243 : 'This statement is private' }
217244 </ TooltipContent >
218245 </ Tooltip >
219- { /* Construct full sentence from atoms */ }
220246 < span > { `${ statement . atoms . subject } ${ getVerbName (
221247 statement . atoms . verb
222248 ) } ${ statement . atoms . object } `} </ span >
223249 </ div >
224250
225251 { /* Right side: resolved icon, actions counter + dropdown */ }
226252 < div className = 'flex items-center space-x-4' >
227- { /* Resolved icon */ }
228253 { statement . isResolved && (
229254 < Tooltip >
230255 < TooltipTrigger asChild >
@@ -237,7 +262,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
237262 </ TooltipContent >
238263 </ Tooltip >
239264 ) }
240- { /* Actions counter */ }
241265 < div
242266 onClick = { ( ) => setIsActionsExpanded ( ( prev ) => ! prev ) }
243267 className = 'cursor-pointer'
@@ -265,7 +289,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
265289 < Trash2 className = 'mr-2 h-4 w-4' />
266290 Delete
267291 </ DropdownMenuItem >
268- { /* Toggle Resolved */ }
269292 < DropdownMenuItem onClick = { ( ) => onToggleResolved ( statement . id ) } >
270293 { statement . isResolved ? (
271294 < >
@@ -279,7 +302,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
279302 </ >
280303 ) }
281304 </ DropdownMenuItem >
282- { /* Reset option if provided */ }
283305 { onReset && (
284306 < DropdownMenuItem onClick = { ( ) => onReset ( statement . id ) } >
285307 < RotateCcw className = 'mr-2 h-4 w-4' />
@@ -291,7 +313,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
291313 </ div >
292314 </ div >
293315
294- { /* Inline actions preview if expanded */ }
295316 { isActionsExpanded && (
296317 < div className = 'mt-2' >
297318 < ActionLine
0 commit comments