1- import React , { useRef , useEffect } from 'react' ;
2- import { Input } from '../ui/input' ;
1+ import React , { useEffect } from 'react' ;
32import { Button } from '../ui/button' ;
4- import SubjectSelector from '../statementWizard/selectors/SubjectSelector' ;
5- import VerbSelector from '../statementWizard/selectors/VerbSelector' ;
63import { getVerbName } from '../../../utils/verbUtils' ;
74import {
85 Trash2 ,
@@ -58,7 +55,6 @@ export interface StatementItemProps {
5855const StatementItem : React . FC < StatementItemProps > = ( {
5956 statement,
6057 isEditing,
61- editingPart,
6258 onPartClick,
6359 onLocalSave,
6460 onDelete,
@@ -72,41 +68,76 @@ const StatementItem: React.FC<StatementItemProps> = ({
7268 onToggleActionResolved = ( ) => { } ,
7369} ) => {
7470 const [ isActionsExpanded , setIsActionsExpanded ] = React . useState ( false ) ;
75- const objectInputRef = useRef < HTMLInputElement > ( null ) ;
7671
7772 // Local "draft" state to hold unsaved modifications.
7873 const [ draft , setDraft ] = React . useState < Entry > ( statement ) ;
7974
80- // Local state to track if we are currently saving the draft. Will control the save button.
75+ // initialDraft freezes the original values when editing begins.
76+ const [ initialDraft , setInitialDraft ] = React . useState < Entry > ( statement ) ;
77+
78+ // Local state to track if we are currently saving the draft.
8179 const [ isSaving , setIsSaving ] = React . useState ( false ) ;
82- // Compute if there are any changes compared to the original statement prop.
83- const hasChanged =
84- draft . atoms . subject !== statement . atoms . subject ||
85- draft . atoms . verb !== statement . atoms . verb ||
86- draft . atoms . object !== statement . atoms . object ||
87- draft . isPublic !== statement . isPublic ;
8880
89- // Whenever the statement prop changes (or when not editing), re-sync the draft.
81+ // First useEffect: Capture changes in edit mode status
9082 useEffect ( ( ) => {
91- setDraft ( statement ) ;
92- } , [ statement ] ) ;
83+ // Create a deep copy of the statement to avoid reference issues
84+ const statementCopy = JSON . parse ( JSON . stringify ( statement ) ) ;
85+
86+ if ( isEditing ) {
87+ // Only capture initial state when entering edit mode
88+ // This will be our reference point for comparison
89+ setInitialDraft ( statementCopy ) ;
90+ } else {
91+ // Reset both states when exiting edit mode
92+ setInitialDraft ( statementCopy ) ;
93+ setDraft ( statementCopy ) ;
94+ }
95+ // eslint-disable-next-line react-hooks/exhaustive-deps
96+ } , [ isEditing ] ) ; // Intentionally excluding statement to avoid resetting initialDraft
9397
98+ // Second useEffect: Always keep draft updated with latest statement to reflect modal changes
9499 useEffect ( ( ) => {
95- if ( editingPart === 'object' && objectInputRef . current ) {
96- objectInputRef . current . focus ( ) ;
100+ if ( isEditing ) {
101+ // When statement changes while in edit mode, update the draft
102+ // This happens when the modal updates the statement
103+ setDraft ( JSON . parse ( JSON . stringify ( statement ) ) ) ;
97104 }
98- } , [ editingPart ] ) ;
105+ } , [
106+ statement ,
107+ // We're specifically tracking these properties to ensure we detect changes
108+ // from the modal even if the statement reference doesn't change
109+ statement . atoms . subject ,
110+ statement . atoms . verb ,
111+ statement . atoms . object ,
112+ statement . isPublic ,
113+ isEditing ,
114+ ] ) ;
99115
100- // Local function to update a specific part in the draft.
116+ // Uncomment and use this if you need to update parts directly instead of using the modal
117+ /*
101118 const updatePart = (part: 'subject' | 'verb' | 'object', value: string) => {
102- setDraft ( ( prev ) => ( {
103- ...prev ,
104- atoms : {
105- ...prev . atoms ,
106- [ part ] : value ,
107- } ,
108- } ) ) ;
119+ // Create a new draft object to ensure React detects the change
120+ setDraft((prevDraft) => {
121+ const newDraft = JSON.parse(JSON.stringify(prevDraft));
122+ newDraft.atoms[part] = value;
123+ return newDraft;
124+ });
109125 };
126+ */
127+
128+ // Compute if draft has changed from the initial state
129+ const hasSubjectChanged = draft . atoms . subject !== initialDraft . atoms . subject ;
130+ const hasVerbChanged = draft . atoms . verb !== initialDraft . atoms . verb ;
131+ const hasObjectChanged = draft . atoms . object !== initialDraft . atoms . object ;
132+ const hasPrivacyChanged = draft . isPublic !== initialDraft . isPublic ;
133+
134+ const hasChanged =
135+ hasSubjectChanged ||
136+ hasVerbChanged ||
137+ hasObjectChanged ||
138+ hasPrivacyChanged ;
139+
140+ // Enable save button when any part of the statement has been changed
110141
111142 if ( isEditing ) {
112143 return (
@@ -116,11 +147,12 @@ const StatementItem: React.FC<StatementItemProps> = ({
116147 variant = 'ghost'
117148 size = 'sm'
118149 onClick = { ( ) => {
119- console . log ( 'Privacy toggle clicked for statement:' , draft . id ) ;
120- setDraft ( ( prev ) => ( {
121- ...prev ,
122- isPublic : ! prev . isPublic ,
123- } ) ) ;
150+ // Create a new draft object to ensure React detects the change
151+ setDraft ( ( prevDraft ) => {
152+ const newDraft = JSON . parse ( JSON . stringify ( prevDraft ) ) ;
153+ newDraft . isPublic = ! prevDraft . isPublic ;
154+ return newDraft ;
155+ } ) ;
124156 } }
125157 className = { `rounded-md px-3 py-2 transition-colors ${
126158 draft . isPublic
@@ -134,55 +166,37 @@ const StatementItem: React.FC<StatementItemProps> = ({
134166 < div className = 'flex flex-1 items-center space-x-2' >
135167 { /* Subject */ }
136168 < div
137- onClick = { ( ) => onPartClick ( 'subject' , draft . id ) }
169+ onClick = { ( ) => {
170+ // Just call onPartClick to open the modal, and don't try to edit inline
171+ onPartClick ( 'subject' , draft . id ) ;
172+ } }
138173 className = 'cursor-pointer px-2 py-1 rounded bg-subjectSelector hover:bg-subjectSelectorHover'
139174 >
140- { editingPart === 'subject' ? (
141- < SubjectSelector
142- value = { draft . atoms . subject }
143- onChange = { ( value ) => updatePart ( 'subject' , value ) }
144- onAddDescriptor = { ( ) => { } }
145- username = {
146- draft . atoms . subject . split ( "'s" ) [ 0 ] || draft . atoms . subject
147- }
148- />
149- ) : (
150- draft . atoms . subject
151- ) }
175+ { draft . atoms . subject }
152176 </ div >
153177 { /* Verb */ }
154178 < div
179+ onClick = { ( ) => {
180+ // Just call onPartClick to open the modal, and don't try to edit inline
181+ onPartClick ( 'verb' , draft . id ) ;
182+ } }
155183 className = 'cursor-pointer px-2 py-1 rounded bg-verbSelector hover:bg-verbSelectorHover'
156- onClick = { ( ) => onPartClick ( 'verb' , draft . id ) }
157184 >
158- { editingPart === 'verb' ? (
159- < VerbSelector
160- onVerbSelect = { ( verb ) => updatePart ( 'verb' , verb . id ) }
161- onClose = { ( ) => onPartClick ( 'verb' , '' ) }
162- />
163- ) : (
164- < span > { getVerbName ( draft . atoms . verb ) } </ span >
165- ) }
185+ < span > { getVerbName ( draft . atoms . verb ) } </ span >
166186 </ div >
167187 { /* Object */ }
168188 < div
169- onClick = { ( ) => onPartClick ( 'object' , draft . id ) }
189+ onClick = { ( ) => {
190+ // Just call onPartClick to open the modal, and don't try to edit inline
191+ onPartClick ( 'object' , draft . id ) ;
192+ } }
170193 className = 'cursor-pointer px-2 py-1 rounded bg-objectInput hover:bg-objectInputHover'
171194 >
172- { editingPart === 'object' ? (
173- < Input
174- ref = { objectInputRef }
175- value = { draft . atoms . object }
176- onChange = { ( e ) => updatePart ( 'object' , e . target . value ) }
177- className = 'w-full'
178- />
179- ) : (
180- draft . atoms . object
181- ) }
195+ { draft . atoms . object }
182196 </ div >
183197 </ div >
184198 < div className = 'flex items-center space-x-2 ml-auto' >
185- { /* This button needs a span wrapper to always show the tooltip */ }
199+ { /* Save button with tooltip */ }
186200 < Tooltip >
187201 < TooltipTrigger asChild >
188202 < span className = 'inline-block' >
@@ -195,7 +209,7 @@ const StatementItem: React.FC<StatementItemProps> = ({
195209 setIsSaving ( false ) ;
196210 } }
197211 disabled = { ! hasChanged || isSaving }
198- className = 'text-green-500 hover:text-green-700 px-2'
212+ className = 'text-green-500 hover:text-green-700 px-4 py- 2'
199213 >
200214 < Save size = { 16 } />
201215 </ Button >
@@ -206,35 +220,42 @@ const StatementItem: React.FC<StatementItemProps> = ({
206220 </ TooltipContent >
207221 </ Tooltip >
208222
223+ { /* Cancel button with PenOff icon and tooltip */ }
209224 < Tooltip >
210225 < TooltipTrigger asChild >
211- < Button
212- variant = 'ghost'
213- size = 'sm'
214- onClick = { ( ) => {
215- setDraft ( statement ) ;
216- if ( onCancel ) onCancel ( statement . id ) ;
217- } }
218- className = 'text-red-500 hover:text-red-700 px-2'
219- >
220- < PenOff size = { 16 } />
221- </ Button >
226+ < span className = 'inline-block' >
227+ < Button
228+ variant = 'ghost'
229+ size = 'sm'
230+ onClick = { ( ) => {
231+ // Deep clone to avoid reference issues
232+ setDraft ( JSON . parse ( JSON . stringify ( initialDraft ) ) ) ;
233+ if ( onCancel ) onCancel ( statement . id ) ;
234+ } }
235+ className = 'text-red-500 hover:text-red-700 px-4 py-2'
236+ >
237+ < PenOff size = { 16 } />
238+ </ Button >
239+ </ span >
222240 </ TooltipTrigger >
223241 < TooltipContent className = 'p-2 bg-black text-white rounded' >
224242 Cancel editing
225243 </ TooltipContent >
226244 </ Tooltip >
227245
246+ { /* Delete button with tooltip */ }
228247 < Tooltip >
229248 < TooltipTrigger asChild >
230- < Button
231- variant = 'ghost'
232- size = 'sm'
233- onClick = { ( ) => onDelete ( draft . id ) }
234- className = 'text-red-500 hover:text-red-700 px-2'
235- >
236- < Trash2 size = { 16 } />
237- </ Button >
249+ < span className = 'inline-block' >
250+ < Button
251+ variant = 'ghost'
252+ size = 'sm'
253+ onClick = { ( ) => onDelete ( draft . id ) }
254+ className = 'text-red-500 hover:text-red-700 px-4 py-2'
255+ >
256+ < Trash2 size = { 16 } />
257+ </ Button >
258+ </ span >
238259 </ TooltipTrigger >
239260 < TooltipContent className = 'p-2 bg-black text-white rounded' >
240261 Delete statement
@@ -252,9 +273,7 @@ const StatementItem: React.FC<StatementItemProps> = ({
252273 statement . isResolved ? 'border-green-500' : 'border-gray-200'
253274 } `}
254275 >
255- { /* Top row: statement, actions counter, etc. */ }
256276 < div className = 'flex items-center justify-between' >
257- { /* Left side: privacy icon + full statement text */ }
258277 < div className = 'flex items-center space-x-2' >
259278 < Tooltip >
260279 < TooltipTrigger asChild >
@@ -280,8 +299,6 @@ const StatementItem: React.FC<StatementItemProps> = ({
280299 statement . atoms . verb
281300 ) } ${ statement . atoms . object } `} </ span >
282301 </ div >
283-
284- { /* Right side: resolved icon, actions counter + dropdown */ }
285302 < div className = 'flex items-center space-x-4' >
286303 { statement . isResolved && (
287304 < Tooltip >
0 commit comments