4747 :options =" responseViews "
4848 :groupLabel =" t (' forms' , ' View mode' )"
4949 class="response-actions__toggle"
50- @update :active =" loadFormResults " />
50+ @update :active =" onChangeResponseView " />
5151
5252 <!-- Action menu for cloud export and deletion -->
5353 <NcActions
@@ -328,6 +328,7 @@ const responseViews = [
328328 id: ' responses' ,
329329 },
330330]
331+ const responseViewIds = new Set (responseViews .map ((view ) => view .id ))
331332
332333export default {
333334 // eslint-disable-next-line vue/multi-word-component-names
@@ -379,7 +380,7 @@ export default {
379380
380381 data () {
381382 return {
382- activeResponseView: responseViews[ 0 ] ,
383+ activeResponseView: {} ,
383384
384385 questions: [],
385386 submissions: [],
@@ -498,11 +499,16 @@ export default {
498499 // Reload results when form changes
499500 async hash () {
500501 await this .fetchFullForm (this .form .id )
501- this .loadActiveResponseViewFromLocalStorage ()
502- this .loadFormResults ()
502+ await this .syncActiveResponseViewFromRoute ()
503503 SetWindowTitle (this .formTitle )
504504 },
505505
506+ ' $route.query' : {
507+ handler () {
508+ this .syncActiveResponseViewFromRoute ()
509+ },
510+ },
511+
506512 limit () {
507513 this .loadFormResults ()
508514 },
@@ -525,40 +531,115 @@ export default {
525531
526532 // Persist active response view to localStorage when it changes
527533 activeResponseView (newView ) {
528- this .saveActiveResponseViewToLocalStorage (newView .id )
534+ if (newView? .id ) {
535+ this .saveActiveResponseViewToLocalStorage (newView .id )
536+ }
529537 },
530538 },
531539
532540 async beforeMount () {
533541 await this .fetchFullForm (this .form .id )
534- this .loadActiveResponseViewFromLocalStorage ()
535- this .loadFormResults ()
542+ await this .syncActiveResponseViewFromRoute ()
536543 SetWindowTitle (this .formTitle )
537544 },
538545
539546 methods: {
540547 /**
541- * Load the active response view preference from localStorage for the current form.
542- * Applies stored value if available and otherwise resets to default (summary)
548+ * Resolve a response view object by its ID.
549+ *
550+ * @param {string} viewId The requested response view ID
551+ * @return {object}
552+ */
553+ getResponseViewById (viewId ) {
554+ return (
555+ responseViews .find ((view ) => view .id === viewId) ?? responseViews[0 ]
556+ )
557+ },
558+
559+ /**
560+ * Read the explicit response view from the current route query.
561+ *
562+ * @return {string|null}
563+ */
564+ getRouteResponseViewId () {
565+ const matchingView = responseViews .find ((view ) => {
566+ return Object .hasOwn (this .$route .query , view .id )
567+ })
568+
569+ return matchingView? .id ?? null
570+ },
571+
572+ /**
573+ * Load the stored response view preference from localStorage for the current form.
574+ *
575+ * @return {string}
543576 */
544- loadActiveResponseViewFromLocalStorage () {
577+ loadStoredActiveResponseViewId () {
545578 try {
546- const storedViewId = localStorage .getItem (
547- ` nextcloud_forms_${ this .form .hash } _activeResponseView` ,
548- )
549- if (storedViewId) {
550- const view = responseViews .find ((v ) => v .id === storedViewId)
551- if (view) {
552- this .activeResponseView = view
553- }
554- } else {
555- this .activeResponseView = responseViews[0 ]
579+ const storageKey = this .getActiveResponseViewStorageKey ()
580+ if (! storageKey) {
581+ return responseViews[0 ].id
582+ }
583+
584+ const storedViewId = localStorage .getItem (storageKey)
585+ if (storedViewId && responseViewIds .has (storedViewId)) {
586+ return storedViewId
556587 }
588+
589+ return responseViews[0 ].id
557590 } catch (err) {
558591 logger .debug (' Error loading activeResponseView from localStorage' , {
559592 error: err,
560593 })
594+ return responseViews[0 ].id
595+ }
596+ },
597+
598+ /**
599+ * Resolve the effective response view using route state first and localStorage second.
600+ *
601+ * @return {string}
602+ */
603+ resolveActiveResponseViewId () {
604+ return (
605+ this .getRouteResponseViewId ()
606+ ?? this .loadStoredActiveResponseViewId ()
607+ )
608+ },
609+
610+ /**
611+ * Apply the effective route/localStorage view and refresh results when needed.
612+ */
613+ async syncActiveResponseViewFromRoute () {
614+ const routeViewId = this .getRouteResponseViewId ()
615+ const nextView = this .getResponseViewById (
616+ routeViewId ?? this .loadStoredActiveResponseViewId (),
617+ )
618+ const currentViewId = this .activeResponseView ? .id
619+
620+ if (currentViewId !== nextView .id ) {
621+ this .activeResponseView = nextView
622+ }
623+
624+ if (! routeViewId) {
625+ try {
626+ await this .$router .replace ({
627+ name: ' results' ,
628+ params: {
629+ hash: this .form .hash ,
630+ },
631+ query: {
632+ ... this .$route .query ,
633+ [nextView .id ]: null ,
634+ },
635+ })
636+ return
637+ } catch (error) {
638+ logger .debug (' Navigation cancelled' , { error })
639+ }
561640 }
641+
642+ this .loadFormResults ()
562643 },
563644
564645 /**
@@ -568,17 +649,62 @@ export default {
568649 */
569650 saveActiveResponseViewToLocalStorage (viewId ) {
570651 try {
571- localStorage .setItem (
572- ` nextcloud_forms_${ this .form .hash } _activeResponseView` ,
573- viewId,
574- )
652+ const storageKey = this .getActiveResponseViewStorageKey ()
653+ if (! storageKey) {
654+ return
655+ }
656+
657+ localStorage .setItem (storageKey, viewId)
575658 } catch (err) {
576659 logger .debug (' Error saving activeResponseView to localStorage' , {
577660 error: err,
578661 })
579662 }
580663 },
581664
665+ /**
666+ * Build the localStorage key for the active response view.
667+ *
668+ * @return {string|null}
669+ */
670+ getActiveResponseViewStorageKey () {
671+ const formHash = this .form ? .hash
672+ if (! formHash) {
673+ return null
674+ }
675+
676+ return ` nextcloud_forms_${ formHash} _activeResponseView`
677+ },
678+
679+ /**
680+ * Navigate to an explicit route query for the selected response view.
681+ *
682+ * @param {object} view The selected response view object
683+ */
684+ async onChangeResponseView (view ) {
685+ if (! view? .id ) {
686+ return
687+ }
688+ if (this .getRouteResponseViewId () === view .id ) {
689+ this .loadFormResults ()
690+ return
691+ }
692+
693+ try {
694+ await this .$router .push ({
695+ name: ' results' ,
696+ params: {
697+ hash: this .form .hash ,
698+ },
699+ query: {
700+ [view .id ]: null ,
701+ },
702+ })
703+ } catch (error) {
704+ logger .debug (' Navigation cancelled' , { error })
705+ }
706+ },
707+
582708 async onUnlinkFile () {
583709 await axios .patch (
584710 generateOcsUrl (' apps/forms/api/v3/forms/{formId}' , {
0 commit comments