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: null ,
383384
384385 questions: [],
385386 submissions: [],
@@ -498,10 +499,16 @@ export default {
498499 // Reload results when form changes
499500 async hash () {
500501 await this .fetchFullForm (this .form .id )
501- this .loadFormResults ()
502+ await this .syncActiveResponseViewFromRoute ()
502503 SetWindowTitle (this .formTitle )
503504 },
504505
506+ ' $route.query' : {
507+ handler () {
508+ this .syncActiveResponseViewFromRoute ()
509+ },
510+ },
511+
505512 limit () {
506513 this .loadFormResults ()
507514 },
@@ -521,15 +528,179 @@ export default {
521528 })
522529 this .loadFormResults ()
523530 }, INPUT_DEBOUNCE_MS ),
531+
532+ // Persist active response view to localStorage when it changes
533+ activeResponseView (newView ) {
534+ if (newView? .id ) {
535+ this .saveActiveResponseViewToLocalStorage (newView .id )
536+ }
537+ },
524538 },
525539
526540 async beforeMount () {
527541 await this .fetchFullForm (this .form .id )
528- this .loadFormResults ()
542+ await this .syncActiveResponseViewFromRoute ()
529543 SetWindowTitle (this .formTitle )
530544 },
531545
532546 methods: {
547+ /**
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+ return this .$route .query .view ?? null
566+ },
567+
568+ /**
569+ * Load the stored response view preference from localStorage for the current form.
570+ *
571+ * @return {string}
572+ */
573+ loadStoredActiveResponseViewId () {
574+ try {
575+ const storageKey = this .getActiveResponseViewStorageKey ()
576+ if (! storageKey) {
577+ return responseViews[0 ].id
578+ }
579+
580+ const storedViewId = localStorage .getItem (storageKey)
581+ if (storedViewId && responseViewIds .has (storedViewId)) {
582+ return storedViewId
583+ }
584+
585+ return responseViews[0 ].id
586+ } catch (err) {
587+ logger .debug (' Error loading activeResponseView from localStorage' , {
588+ error: err,
589+ })
590+ return responseViews[0 ].id
591+ }
592+ },
593+
594+ /**
595+ * Resolve the effective response view using route state first and localStorage second.
596+ *
597+ * @return {string}
598+ */
599+ resolveActiveResponseViewId () {
600+ return (
601+ this .getRouteResponseViewId ()
602+ ?? this .loadStoredActiveResponseViewId ()
603+ )
604+ },
605+
606+ /**
607+ * Apply the effective route/localStorage view and refresh results when needed.
608+ */
609+ async syncActiveResponseViewFromRoute () {
610+ const routeViewId = this .getRouteResponseViewId ()
611+ const nextView = this .getResponseViewById (
612+ routeViewId ?? this .loadStoredActiveResponseViewId (),
613+ )
614+ const currentViewId = this .activeResponseView ? .id
615+
616+ if (currentViewId !== nextView .id ) {
617+ this .activeResponseView = nextView
618+ }
619+
620+ if (! routeViewId) {
621+ try {
622+ await this .$router .replace ({
623+ name: ' results' ,
624+ params: {
625+ hash: this .form .hash ,
626+ },
627+ query: {
628+ ... this .$route .query ,
629+ view: nextView .id ,
630+ },
631+ })
632+ return
633+ } catch (error) {
634+ logger .debug (' Navigation cancelled' , { error })
635+ }
636+ }
637+
638+ this .loadFormResults ()
639+ },
640+
641+ /**
642+ * Save the active response view preference to localStorage for the current form.
643+ *
644+ * @param {string} viewId - The ID of the view ('summary' or 'responses')
645+ */
646+ saveActiveResponseViewToLocalStorage (viewId ) {
647+ try {
648+ const storageKey = this .getActiveResponseViewStorageKey ()
649+ if (! storageKey) {
650+ return
651+ }
652+
653+ localStorage .setItem (storageKey, viewId)
654+ } catch (err) {
655+ logger .debug (' Error saving activeResponseView to localStorage' , {
656+ error: err,
657+ })
658+ }
659+ },
660+
661+ /**
662+ * Build the localStorage key for the active response view.
663+ *
664+ * @return {string|null}
665+ */
666+ getActiveResponseViewStorageKey () {
667+ const formHash = this .form ? .hash
668+ if (! formHash) {
669+ return null
670+ }
671+
672+ return ` nextcloud_forms_${ formHash} _activeResponseView`
673+ },
674+
675+ /**
676+ * Navigate to an explicit route query for the selected response view.
677+ *
678+ * @param {object} view The selected response view object
679+ */
680+ async onChangeResponseView (view ) {
681+ if (! view? .id ) {
682+ return
683+ }
684+ if (this .getRouteResponseViewId () === view .id ) {
685+ this .loadFormResults ()
686+ return
687+ }
688+
689+ try {
690+ await this .$router .push ({
691+ name: ' results' ,
692+ params: {
693+ hash: this .form .hash ,
694+ },
695+ query: {
696+ view: view .id ,
697+ },
698+ })
699+ } catch (error) {
700+ logger .debug (' Navigation cancelled' , { error })
701+ }
702+ },
703+
533704 async onUnlinkFile () {
534705 await axios .patch (
535706 generateOcsUrl (' apps/forms/api/v3/forms/{formId}' , {
0 commit comments