1+ export class CellEditor {
2+ constructor ( controller ) {
3+ this . controller = controller
4+ this . autosaveTimeout = null
5+ }
6+
7+ handleCellClick ( event ) {
8+ const cell = event . currentTarget
9+ const cellId = cell . dataset . cellId
10+ const isLocked = cell . dataset . locked === 'true'
11+ const lockedBy = cell . dataset . lockedBy
12+ const field = cell . dataset . field
13+ const fieldType = cell . dataset . fieldType
14+
15+ // Prevent editing if locked by another user
16+ if ( isLocked && lockedBy !== this . controller . currentUser ) {
17+ this . controller . messageDisplayManager . showLockedMessage ( cell )
18+ return
19+ }
20+
21+ // Request lock
22+ this . controller . actionCableManager . perform ( 'lock_cell' , { cell_id : cellId } )
23+
24+ // Handle select fields differently
25+ if ( fieldType === 'select' ) {
26+ this . controller . cellRenderer . showSelectDropdown ( cell )
27+ } else {
28+ // For status field and other fields with special formatting,
29+ // replace content with plain text for editing
30+ if ( field === 'status' ) {
31+ const currentValue = cell . dataset . originalValue || cell . textContent . trim ( )
32+ cell . textContent = currentValue
33+ }
34+
35+ // Make cell editable
36+ cell . contentEditable = true
37+ cell . focus ( )
38+
39+ // Select all text
40+ const range = document . createRange ( )
41+ range . selectNodeContents ( cell )
42+ const sel = window . getSelection ( )
43+ sel . removeAllRanges ( )
44+ sel . addRange ( range )
45+ }
46+
47+ // Set edit timeout (30 seconds)
48+ this . controller . editTimeoutManager . setEditTimeout ( cellId )
49+ }
50+
51+ handleCellBlur ( event ) {
52+ const cell = event . currentTarget
53+ const cellId = cell . dataset . cellId
54+ const field = cell . dataset . field
55+
56+ // Clear any pending autosave to prevent duplicate saves
57+ if ( this . autosaveTimeout ) {
58+ clearTimeout ( this . autosaveTimeout )
59+ this . autosaveTimeout = null
60+ }
61+
62+ // Check if we should skip saving (e.g., Escape was pressed)
63+ if ( cell . dataset . skipSave !== 'true' ) {
64+ // Save changes
65+ this . saveCell ( cell )
66+ // Note: saveCell will handle unlocking
67+ } else {
68+ // Just unlock without saving
69+ this . controller . actionCableManager . perform ( 'unlock_cell' , { cell_id : cellId } )
70+ delete cell . dataset . skipSave
71+ }
72+
73+ // Make cell non-editable
74+ cell . contentEditable = false
75+
76+ // For status field, restore the formatted display with current value
77+ if ( field === 'status' ) {
78+ this . controller . cellRenderer . formatStatusCell ( cell )
79+ }
80+
81+ // Clear edit timeout
82+ this . controller . editTimeoutManager . clearEditTimeout ( cellId )
83+ }
84+
85+ handleCellInput ( event ) {
86+ const cell = event . currentTarget
87+ const cellId = cell . dataset . cellId
88+
89+ // Reset edit timeout on input
90+ this . controller . editTimeoutManager . clearEditTimeout ( cellId )
91+ this . controller . editTimeoutManager . setEditTimeout ( cellId )
92+
93+ // Debounced autosave
94+ clearTimeout ( this . autosaveTimeout )
95+ this . autosaveTimeout = setTimeout ( ( ) => {
96+ this . saveCell ( cell )
97+ } , 1000 )
98+ }
99+
100+ handleCellKeydown ( event ) {
101+ const cell = event . currentTarget
102+
103+ if ( event . key === 'Enter' && ! event . shiftKey ) {
104+ event . preventDefault ( )
105+ cell . blur ( )
106+ } else if ( event . key === 'Escape' ) {
107+ event . preventDefault ( )
108+ // Restore original value
109+ const originalValue = cell . dataset . originalValue || ''
110+ cell . textContent = originalValue
111+ // Don't save changes
112+ cell . dataset . skipSave = 'true'
113+ cell . blur ( )
114+ } else if ( event . key === 'Tab' ) {
115+ event . preventDefault ( )
116+ // Save current cell
117+ cell . blur ( )
118+
119+ // Find next/previous editable cell
120+ const allCells = this . controller . cellTargets
121+ const currentIndex = allCells . indexOf ( cell )
122+ let nextIndex
123+
124+ if ( event . shiftKey ) {
125+ // Shift+Tab: go to previous cell
126+ nextIndex = currentIndex - 1
127+ if ( nextIndex < 0 ) nextIndex = allCells . length - 1
128+ } else {
129+ // Tab: go to next cell
130+ nextIndex = currentIndex + 1
131+ if ( nextIndex >= allCells . length ) nextIndex = 0
132+ }
133+
134+ // Click on the next cell to edit it
135+ if ( allCells [ nextIndex ] ) {
136+ allCells [ nextIndex ] . click ( )
137+ }
138+ }
139+ }
140+
141+ saveCell ( cell ) {
142+ const cellId = cell . dataset . cellId
143+ const streamId = cell . dataset . streamId
144+ const field = cell . dataset . field
145+ const value = cell . textContent . trim ( )
146+ const originalValue = cell . dataset . originalValue || ''
147+
148+ console . log ( 'saveCell called for field:' , field , 'with value:' , value )
149+
150+ // Validate required data
151+ if ( ! cellId || ! streamId || ! field ) {
152+ console . error ( 'Missing required data for cell save:' , { cellId, streamId, field } )
153+ return
154+ }
155+
156+ // Validate cell is still in correct position
157+ const td = cell . closest ( 'td' )
158+ const tr = td ? td . closest ( 'tr' ) : null
159+ if ( ! td || ! tr ) {
160+ console . error ( 'Cell is not in a valid table structure!' , { cellId } )
161+ return
162+ }
163+
164+ // Only save if value changed
165+ if ( value !== originalValue ) {
166+ console . log ( 'Saving cell:' , { cellId, streamId, field, value, originalValue } )
167+
168+ this . controller . actionCableManager . perform ( 'update_cell' , {
169+ cell_id : cellId ,
170+ stream_id : streamId ,
171+ field : field ,
172+ value : value
173+ } )
174+
175+ // Update original value
176+ cell . dataset . originalValue = value
177+ } else {
178+ console . log ( 'No change detected, not saving:' , { value, originalValue } )
179+ }
180+
181+ // Unlock cell
182+ this . controller . actionCableManager . perform ( 'unlock_cell' , { cell_id : cellId } )
183+ }
184+ }
0 commit comments