1- import React from 'react' ;
1+ import React , { useState } from 'react' ;
22import { Nav , NavItem , OverlayTrigger , Tooltip } from "react-bootstrap" ;
33import { IndexLinkContainer } from "react-router-bootstrap" ;
44import Glyphicon from "react-bootstrap/lib/Glyphicon" ;
55import { MENU_INDEX , WIDGET , WIDGET_TITLE } from "../constants" ;
66import { useDispatch , useSelector } from "react-redux" ;
7- import { setSelectedWidget } from "../redux/actions/widgetActions" ;
7+ import { setSelectedWidget , saveWidgetData } from "../redux/actions/widgetActions" ;
8+ import { withRouter } from "react-router-dom" ;
9+ import UnsavedChangesModal from "../components/modals/UnsavedChangesModal" ;
810
9- const Menu = ( { urlQuery, onMenuItemClick = ( ) => { } } ) => {
11+ const Menu = ( { urlQuery, onMenuItemClick = ( ) => { } , history } ) => {
1012 const dispatch = useDispatch ( ) ;
13+ const selectedWidget = useSelector ( ( state ) => state . widget . selectedWidget ) ;
14+ const [ showUnsavedModal , setShowUnsavedModal ] = useState ( false ) ;
15+ const [ pendingNavigation , setPendingNavigation ] = useState ( null ) ;
1116
1217 // Utility function to check if a widget has changes
1318 const hasWidgetChanges = ( widgetState ) => {
@@ -92,6 +97,89 @@ const Menu = ({urlQuery, onMenuItemClick = () => {}}) => {
9297 return hasChanges ;
9398 } ;
9499
100+ // Get widget state name from menu index
101+ const getWidgetStateName = ( menuIndex ) => {
102+ const widgetMap = {
103+ [ MENU_INDEX [ WIDGET . OVERVIEW ] ] : 'overview' ,
104+ [ MENU_INDEX [ WIDGET . GENETICS ] ] : 'genetics' ,
105+ [ MENU_INDEX [ WIDGET . REAGENT ] ] : 'reagent' ,
106+ [ MENU_INDEX [ WIDGET . EXPRESSION ] ] : 'expression' ,
107+ [ MENU_INDEX [ WIDGET . INTERACTIONS ] ] : 'interactions' ,
108+ [ MENU_INDEX [ WIDGET . PHENOTYPES ] ] : 'phenotypes' ,
109+ [ MENU_INDEX [ WIDGET . DISEASE ] ] : 'disease' ,
110+ [ MENU_INDEX [ WIDGET . COMMENTS ] ] : 'comments'
111+ } ;
112+ return widgetMap [ menuIndex ] || null ;
113+ } ;
114+
115+ // Get widget title from menu index
116+ const getWidgetTitle = ( menuIndex ) => {
117+ const reverseMap = Object . keys ( MENU_INDEX ) . find ( key => MENU_INDEX [ key ] === menuIndex ) ;
118+ return reverseMap ? WIDGET_TITLE [ reverseMap ] : '' ;
119+ } ;
120+
121+ // Get current widget state
122+ const currentWidgetState = useSelector ( ( state ) => {
123+ const widgetName = getWidgetStateName ( selectedWidget ) ;
124+ return widgetName ? state [ widgetName ] : null ;
125+ } ) ;
126+
127+ // Handle navigation attempt
128+ const handleNavigationAttempt = ( targetMenuIndex , targetPath ) => {
129+ const hasChanges = hasWidgetChanges ( currentWidgetState ) ;
130+
131+ // If no unsaved changes, navigate directly
132+ if ( ! hasChanges ) {
133+ dispatch ( setSelectedWidget ( targetMenuIndex ) ) ;
134+ history . push ( targetPath + urlQuery ) ;
135+ onMenuItemClick ( ) ;
136+ window . scrollTo ( 0 , 0 ) ;
137+ return ;
138+ }
139+
140+ // Show modal for unsaved changes
141+ setPendingNavigation ( {
142+ menuIndex : targetMenuIndex ,
143+ path : targetPath
144+ } ) ;
145+ setShowUnsavedModal ( true ) ;
146+ } ;
147+
148+ // Handle save and continue
149+ const handleSaveAndContinue = ( ) => {
150+ const widgetName = getWidgetStateName ( selectedWidget ) ;
151+ if ( widgetName && pendingNavigation ) {
152+ dispatch ( saveWidgetData ( widgetName ) ) ;
153+ // Wait a moment for save to process, then navigate
154+ setTimeout ( ( ) => {
155+ dispatch ( setSelectedWidget ( pendingNavigation . menuIndex ) ) ;
156+ history . push ( pendingNavigation . path + urlQuery ) ;
157+ onMenuItemClick ( ) ;
158+ window . scrollTo ( 0 , 0 ) ;
159+ setShowUnsavedModal ( false ) ;
160+ setPendingNavigation ( null ) ;
161+ } , 500 ) ;
162+ }
163+ } ;
164+
165+ // Handle continue without saving
166+ const handleContinueWithoutSaving = ( ) => {
167+ if ( pendingNavigation ) {
168+ dispatch ( setSelectedWidget ( pendingNavigation . menuIndex ) ) ;
169+ history . push ( pendingNavigation . path + urlQuery ) ;
170+ onMenuItemClick ( ) ;
171+ window . scrollTo ( 0 , 0 ) ;
172+ setShowUnsavedModal ( false ) ;
173+ setPendingNavigation ( null ) ;
174+ }
175+ } ;
176+
177+ // Handle cancel navigation
178+ const handleCancelNavigation = ( ) => {
179+ setShowUnsavedModal ( false ) ;
180+ setPendingNavigation ( null ) ;
181+ } ;
182+
95183 // Reusable menu item component
96184 const MenuItemWithIcon = ( { widget, title, children } ) => {
97185 const widgetState = useSelector ( ( state ) => state [ widget ] ) ;
@@ -118,57 +206,89 @@ const Menu = ({urlQuery, onMenuItemClick = () => {}}) => {
118206
119207 return (
120208 < div >
209+ < UnsavedChangesModal
210+ show = { showUnsavedModal }
211+ onHide = { handleCancelNavigation }
212+ onSaveAndContinue = { handleSaveAndContinue }
213+ onContinueWithoutSaving = { handleContinueWithoutSaving }
214+ currentWidget = { getWidgetTitle ( selectedWidget ) }
215+ targetWidget = { pendingNavigation ? getWidgetTitle ( pendingNavigation . menuIndex ) : '' }
216+ />
121217 < div className = "panel panel-default" style = { { marginBottom : '10px' } } >
122218 < div className = "panel-body" >
123- < Nav bsStyle = "pills" stacked onSelect = { ( sel ) => dispatch ( setSelectedWidget ( sel ) ) } >
124- < IndexLinkContainer to = { WIDGET . OVERVIEW + urlQuery }
125- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . OVERVIEW ] } >
126- < NavItem eventKey = { MENU_INDEX [ WIDGET . OVERVIEW ] } onClick = { onMenuItemClick } >
127- < MenuItemWithIcon widget = "overview" title = { WIDGET_TITLE [ WIDGET . OVERVIEW ] } />
128- </ NavItem >
129- </ IndexLinkContainer >
130- < IndexLinkContainer to = { WIDGET . GENETICS + urlQuery }
131- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . GENETICS ] } >
132- < NavItem eventKey = { MENU_INDEX [ WIDGET . GENETICS ] } onClick = { onMenuItemClick } >
133- < MenuItemWithIcon widget = "genetics" title = { WIDGET_TITLE [ WIDGET . GENETICS ] } />
134- </ NavItem >
135- </ IndexLinkContainer >
136- < IndexLinkContainer to = { WIDGET . REAGENT + urlQuery }
137- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . REAGENT ] } >
138- < NavItem eventKey = { MENU_INDEX [ WIDGET . REAGENT ] } onClick = { onMenuItemClick } >
139- < MenuItemWithIcon widget = "reagent" title = { WIDGET_TITLE [ WIDGET . REAGENT ] } />
140- </ NavItem >
141- </ IndexLinkContainer >
142- < IndexLinkContainer to = { WIDGET . EXPRESSION + urlQuery }
143- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . EXPRESSION ] } >
144- < NavItem eventKey = { MENU_INDEX [ WIDGET . EXPRESSION ] } onClick = { onMenuItemClick } >
145- < MenuItemWithIcon widget = "expression" title = { WIDGET_TITLE [ WIDGET . EXPRESSION ] } />
146- </ NavItem >
147- </ IndexLinkContainer >
148- < IndexLinkContainer to = { WIDGET . INTERACTIONS + urlQuery }
149- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . INTERACTIONS ] } >
150- < NavItem eventKey = { MENU_INDEX [ WIDGET . INTERACTIONS ] } onClick = { onMenuItemClick } >
151- < MenuItemWithIcon widget = "interactions" title = { WIDGET_TITLE [ WIDGET . INTERACTIONS ] } />
152- </ NavItem >
153- </ IndexLinkContainer >
154- < IndexLinkContainer to = { WIDGET . PHENOTYPES + urlQuery }
155- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . PHENOTYPES ] } >
156- < NavItem eventKey = { MENU_INDEX [ WIDGET . PHENOTYPES ] } onClick = { onMenuItemClick } >
157- < MenuItemWithIcon widget = "phenotypes" title = { WIDGET_TITLE [ WIDGET . PHENOTYPES ] } />
158- </ NavItem >
159- </ IndexLinkContainer >
160- < IndexLinkContainer to = { WIDGET . DISEASE + urlQuery }
161- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . DISEASE ] } >
162- < NavItem eventKey = { MENU_INDEX [ WIDGET . DISEASE ] } onClick = { onMenuItemClick } >
163- < MenuItemWithIcon widget = "disease" title = { WIDGET_TITLE [ WIDGET . DISEASE ] } />
164- </ NavItem >
165- </ IndexLinkContainer >
166- < IndexLinkContainer to = { WIDGET . COMMENTS + urlQuery }
167- active = { useSelector ( ( state ) => state . widget . selectedWidget ) === MENU_INDEX [ WIDGET . COMMENTS ] } >
168- < NavItem eventKey = { MENU_INDEX [ WIDGET . COMMENTS ] } onClick = { onMenuItemClick } >
169- < MenuItemWithIcon widget = "comments" title = { WIDGET_TITLE [ WIDGET . COMMENTS ] } />
170- </ NavItem >
171- </ IndexLinkContainer >
219+ < Nav bsStyle = "pills" stacked >
220+ < NavItem
221+ active = { selectedWidget === MENU_INDEX [ WIDGET . OVERVIEW ] }
222+ onClick = { ( e ) => {
223+ e . preventDefault ( ) ;
224+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . OVERVIEW ] , WIDGET . OVERVIEW ) ;
225+ } }
226+ >
227+ < MenuItemWithIcon widget = "overview" title = { WIDGET_TITLE [ WIDGET . OVERVIEW ] } />
228+ </ NavItem >
229+ < NavItem
230+ active = { selectedWidget === MENU_INDEX [ WIDGET . GENETICS ] }
231+ onClick = { ( e ) => {
232+ e . preventDefault ( ) ;
233+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . GENETICS ] , WIDGET . GENETICS ) ;
234+ } }
235+ >
236+ < MenuItemWithIcon widget = "genetics" title = { WIDGET_TITLE [ WIDGET . GENETICS ] } />
237+ </ NavItem >
238+ < NavItem
239+ active = { selectedWidget === MENU_INDEX [ WIDGET . REAGENT ] }
240+ onClick = { ( e ) => {
241+ e . preventDefault ( ) ;
242+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . REAGENT ] , WIDGET . REAGENT ) ;
243+ } }
244+ >
245+ < MenuItemWithIcon widget = "reagent" title = { WIDGET_TITLE [ WIDGET . REAGENT ] } />
246+ </ NavItem >
247+ < NavItem
248+ active = { selectedWidget === MENU_INDEX [ WIDGET . EXPRESSION ] }
249+ onClick = { ( e ) => {
250+ e . preventDefault ( ) ;
251+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . EXPRESSION ] , WIDGET . EXPRESSION ) ;
252+ } }
253+ >
254+ < MenuItemWithIcon widget = "expression" title = { WIDGET_TITLE [ WIDGET . EXPRESSION ] } />
255+ </ NavItem >
256+ < NavItem
257+ active = { selectedWidget === MENU_INDEX [ WIDGET . INTERACTIONS ] }
258+ onClick = { ( e ) => {
259+ e . preventDefault ( ) ;
260+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . INTERACTIONS ] , WIDGET . INTERACTIONS ) ;
261+ } }
262+ >
263+ < MenuItemWithIcon widget = "interactions" title = { WIDGET_TITLE [ WIDGET . INTERACTIONS ] } />
264+ </ NavItem >
265+ < NavItem
266+ active = { selectedWidget === MENU_INDEX [ WIDGET . PHENOTYPES ] }
267+ onClick = { ( e ) => {
268+ e . preventDefault ( ) ;
269+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . PHENOTYPES ] , WIDGET . PHENOTYPES ) ;
270+ } }
271+ >
272+ < MenuItemWithIcon widget = "phenotypes" title = { WIDGET_TITLE [ WIDGET . PHENOTYPES ] } />
273+ </ NavItem >
274+ < NavItem
275+ active = { selectedWidget === MENU_INDEX [ WIDGET . DISEASE ] }
276+ onClick = { ( e ) => {
277+ e . preventDefault ( ) ;
278+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . DISEASE ] , WIDGET . DISEASE ) ;
279+ } }
280+ >
281+ < MenuItemWithIcon widget = "disease" title = { WIDGET_TITLE [ WIDGET . DISEASE ] } />
282+ </ NavItem >
283+ < NavItem
284+ active = { selectedWidget === MENU_INDEX [ WIDGET . COMMENTS ] }
285+ onClick = { ( e ) => {
286+ e . preventDefault ( ) ;
287+ handleNavigationAttempt ( MENU_INDEX [ WIDGET . COMMENTS ] , WIDGET . COMMENTS ) ;
288+ } }
289+ >
290+ < MenuItemWithIcon widget = "comments" title = { WIDGET_TITLE [ WIDGET . COMMENTS ] } />
291+ </ NavItem >
172292 </ Nav >
173293 </ div >
174294 </ div >
@@ -204,4 +324,4 @@ const Menu = ({urlQuery, onMenuItemClick = () => {}}) => {
204324 ) ;
205325}
206326
207- export default Menu ;
327+ export default withRouter ( Menu ) ;
0 commit comments