From c48e5786a87959d8a2bad5226b96536dfdf62e41 Mon Sep 17 00:00:00 2001
From: Hangzhi Yu <hangzhi@protonmail.com>
Date: Thu, 6 Mar 2025 01:16:58 +0100
Subject: [PATCH] Restructure components

---
 .../components/person/PersonActions.vue       | 192 +++++++-----------
 .../frontend/components/person/PersonForm.vue |  59 +++++-
 .../frontend/components/person/PersonList.vue |  12 +-
 .../components/person/PersonListWrapper.vue   |  22 ++
 .../components/person/PersonOverview.vue      |   1 -
 .../person/PersonOverviewWrapper.vue          |  33 +++
 aleksis/core/frontend/routes.js               |   4 +-
 7 files changed, 189 insertions(+), 134 deletions(-)
 create mode 100644 aleksis/core/frontend/components/person/PersonListWrapper.vue
 create mode 100644 aleksis/core/frontend/components/person/PersonOverviewWrapper.vue

diff --git a/aleksis/core/frontend/components/person/PersonActions.vue b/aleksis/core/frontend/components/person/PersonActions.vue
index 5bad55e08..0929fde34 100644
--- a/aleksis/core/frontend/components/person/PersonActions.vue
+++ b/aleksis/core/frontend/components/person/PersonActions.vue
@@ -4,107 +4,89 @@ import PersonForm from "./PersonForm.vue";
 
 <template>
   <div>
-    <person-form
-      v-if="$route.query.action === 'edit'"
-      :i18n-key="i18nKey"
-      :gql-query="gqlQuery"
-      :gql-delete-mutation="gqlDeleteMutation"
-      :gql-patch-mutation="gqlPatchMutation"
-      :gql-create-mutation="gqlCreateMutation"
-      :edit-item="editItem"
-      :is-create="false"
-      :fallback-url="{ name: 'core.personById', params: { id: person.id } }"
-      :success-redirect-url="{
+    <v-btn
+      v-if="person.canEdit"
+      color="primary"
+      :to="{
         name: 'core.personById',
         params: { id: person.id },
+        query: { _ui_action: 'edit' },
       }"
-      v-on="$listeners"
-    />
-    <template v-else-if="person && person.id">
-      <v-btn
-        v-if="person.canEdit"
-        color="primary"
+    >
+      <v-icon left>$edit</v-icon>
+      {{ $t("actions.edit") }}
+    </v-btn>
+    <v-btn
+      v-if="person.canChangePersonPreferences"
+      color="secondary"
+      outlined
+      text
+      :to="{
+        name: 'core.preferencesPersonByPk',
+        params: { pk: person.id },
+      }"
+    >
+      <v-icon left>$preferences</v-icon>
+      {{ $t("preferences.person.change_preferences") }}
+    </v-btn>
+
+    <button-menu
+      v-if="
+        person.canImpersonatePerson ||
+        person.canInvitePerson ||
+        person.canDelete
+      "
+    >
+      <v-list-item
+        v-if="person.canImpersonatePerson"
         :to="{
-          name: 'core.personById',
-          params: { id: person.id },
-          query: { action: 'edit' },
+          name: 'impersonate.impersonateByUserPk',
+          params: { uid: person.userid },
+          query: { next: $route.path },
         }"
       >
-        <v-icon left>$edit</v-icon>
-        {{ $t("actions.edit") }}
-      </v-btn>
-      <v-btn
-        v-if="person.canChangePersonPreferences"
-        color="secondary"
-        outlined
-        text
+        <v-list-item-icon>
+          <v-icon>mdi-account-box-outline</v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            {{ $t("person.impersonation.impersonate") }}
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+
+      <v-list-item
+        v-if="person.canInvitePerson"
         :to="{
-          name: 'core.preferencesPersonByPk',
-          params: { pk: person.id },
+          name: 'core.invitePerson',
+          params: { id: person.id },
         }"
       >
-        <v-icon left>$preferences</v-icon>
-        {{ $t("preferences.person.change_preferences") }}
-      </v-btn>
+        <v-list-item-icon>
+          <v-icon>mdi-account-plus-outline</v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            {{ $t("person.invite") }}
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
 
-      <button-menu
-        v-if="
-          person.canImpersonatePerson ||
-          person.canInvitePerson ||
-          person.canDelete
-        "
+      <v-list-item
+        v-if="person.canDelete"
+        @click="showDeleteConfirm = true"
+        class="error--text"
       >
-        <v-list-item
-          v-if="person.canImpersonatePerson"
-          :to="{
-            name: 'impersonate.impersonateByUserPk',
-            params: { uid: person.userid },
-            query: { next: $route.path },
-          }"
-        >
-          <v-list-item-icon>
-            <v-icon>mdi-account-box-outline</v-icon>
-          </v-list-item-icon>
-          <v-list-item-content>
-            <v-list-item-title>
-              {{ $t("person.impersonation.impersonate") }}
-            </v-list-item-title>
-          </v-list-item-content>
-        </v-list-item>
-
-        <v-list-item
-          v-if="person.canInvitePerson"
-          :to="{
-            name: 'core.invitePerson',
-            params: { id: person.id },
-          }"
-        >
-          <v-list-item-icon>
-            <v-icon>mdi-account-plus-outline</v-icon>
-          </v-list-item-icon>
-          <v-list-item-content>
-            <v-list-item-title>
-              {{ $t("person.invite") }}
-            </v-list-item-title>
-          </v-list-item-content>
-        </v-list-item>
-
-        <v-list-item
-          v-if="person.canDelete"
-          @click="showDeleteConfirm = true"
-          class="error--text"
-        >
-          <v-list-item-icon>
-            <v-icon color="error">$deleteContent</v-icon>
-          </v-list-item-icon>
-          <v-list-item-content>
-            <v-list-item-title>
-              {{ $t("person.delete") }}
-            </v-list-item-title>
-          </v-list-item-content>
-        </v-list-item>
-      </button-menu>
-    </template>
+        <v-list-item-icon>
+          <v-icon color="error">$deleteContent</v-icon>
+        </v-list-item-icon>
+        <v-list-item-content>
+          <v-list-item-title>
+            {{ $t("person.delete") }}
+          </v-list-item-title>
+        </v-list-item-content>
+      </v-list-item>
+    </button-menu>
     <delete-dialog
       v-model="showDeleteConfirm"
       :gql-delete-mutation="deleteMutation"
@@ -127,12 +109,9 @@ import PersonForm from "./PersonForm.vue";
 import { deletePersons } from "./personList.graphql";
 import DeleteDialog from "../generic/dialogs/DeleteDialog.vue";
 
-import personCRUDMixin from "../../mixins/personCRUDMixin.js";
-
 export default {
   name: "PersonActions",
   components: { DeleteDialog },
-  mixins: [personCRUDMixin],
   props: {
     person: {
       type: Object,
@@ -145,33 +124,6 @@ export default {
       deleteMutation: deletePersons,
     };
   },
-  computed: {
-    editItem() {
-      return {
-        id: this.person.id,
-        firstName: this.person.firstName,
-        additionalName: this.person.additionalName,
-        lastName: this.person.lastName,
-        shortName: this.person.shortName,
-        user: this.person.userid,
-        description: this.person.description,
-        sex: this.person.sex,
-        street: this.person.street,
-        housenumber: this.person.housenumber,
-        postalCode: this.person.postalCode,
-        place: this.person.place,
-        phoneNumber: this.person.phoneNumber,
-        mobileNumber: this.person.mobileNumber,
-        email: this.person.email,
-        dateOfBirth: this.person.dateOfBirth,
-        placeOfBirth: this.person.placeOfBirth,
-        photo: this.person.photo,
-        avatar: this.person.avatar,
-        guardians: this.person.guardians.map((g) => g.id),
-        primaryGroup: this.person?.primaryGroup?.id,
-      };
-    },
-  },
 };
 </script>
 
diff --git a/aleksis/core/frontend/components/person/PersonForm.vue b/aleksis/core/frontend/components/person/PersonForm.vue
index cae913842..fef7aff50 100644
--- a/aleksis/core/frontend/components/person/PersonForm.vue
+++ b/aleksis/core/frontend/components/person/PersonForm.vue
@@ -12,11 +12,17 @@ import AdditionalImage from "./AdditionalImage.vue";
   <fullscreen-dialog-object-form
     v-bind="$attrs"
     v-on="$listeners"
+    :i18n-key="i18nKey"
+    :gql-query="gqlQuery"
+    :gql-delete-mutation="gqlDeleteMutation"
+    :gql-patch-mutation="gqlPatchMutation"
+    :gql-create-mutation="gqlCreateMutation"
     full-width
     :fields="filteredFields"
     :create-item-i18n-key="createItemI18nKey"
     :edit-item-i18n-key="editItemI18nKey"
     :get-patch-data="getPatchData"
+    :edit-item="editItem"
   >
     <!-- eslint-disable-next-line vue/valid-v-slot -->
     <template #firstName.field="{ attrs, on }">
@@ -132,12 +138,15 @@ import AdditionalImage from "./AdditionalImage.vue";
 <script>
 import formRulesMixin from "../../mixins/formRulesMixin.js";
 import permissionsMixin from "../../mixins/permissions.js";
+import personCRUDMixin from "../../mixins/personCRUDMixin.js";
 
 import { gqlEditableFieldsPreference, gqlUsers } from "./personForm.graphql";
 
+import gqlPersonOverview from "./personOverview.graphql";
+
 export default {
   name: "PersonForm",
-  mixins: [formRulesMixin, permissionsMixin],
+  mixins: [formRulesMixin, permissionsMixin, personCRUDMixin],
   data() {
     return {
       fields: [
@@ -256,13 +265,33 @@ export default {
       ],
       createItemI18nKey: "person.form.create",
       editItemI18nKey: "person.form.edit",
+      person: null,
     };
   },
+  props: {
+    id: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
   apollo: {
     users: gqlUsers,
     systemProperties: {
       query: gqlEditableFieldsPreference,
     },
+    person: {
+      query: gqlPersonOverview,
+      variables() {
+        return {
+          id: this.id,
+        };
+      },
+      skip() {
+        return !this.id;
+      },
+      update: (data) => data.object,
+    }
   },
   computed: {
     editPersonID() {
@@ -299,6 +328,34 @@ export default {
       }
       return this.fields;
     },
+    editItem() {
+      if (this.person) {
+        return {
+          id: this.person.id,
+          firstName: this.person.firstName,
+          additionalName: this.person.additionalName,
+          lastName: this.person.lastName,
+          shortName: this.person.shortName,
+          user: this.person.userid,
+          description: this.person.description,
+          sex: this.person.sex,
+          street: this.person.street,
+          housenumber: this.person.housenumber,
+          postalCode: this.person.postalCode,
+          place: this.person.place,
+          phoneNumber: this.person.phoneNumber,
+          mobileNumber: this.person.mobileNumber,
+          email: this.person.email,
+          dateOfBirth: this.person.dateOfBirth,
+          placeOfBirth: this.person.placeOfBirth,
+          photo: this.person.photo,
+          avatar: this.person.avatar,
+          guardians: this.person.guardians.map((g) => g.id),
+          primaryGroup: this.person?.primaryGroup?.id,
+        };
+      }
+      return null;
+    },
   },
   methods: {
     handleNameInput(input, itemModel, setter) {
diff --git a/aleksis/core/frontend/components/person/PersonList.vue b/aleksis/core/frontend/components/person/PersonList.vue
index 048df5b9f..e7fa45bd6 100644
--- a/aleksis/core/frontend/components/person/PersonList.vue
+++ b/aleksis/core/frontend/components/person/PersonList.vue
@@ -65,17 +65,9 @@ export default {
         color="secondary"
         :to="{
           name: 'core.persons',
-          query: { action: 'create' },
+          query: { _ui_action: 'create' },
         }"
-        :disabled="$route.query.action === 'create'"
-      />
-
-      <person-form
-        v-if="$route.query.action === 'create'"
-        v-bind="attrs"
-        v-on="on"
-        :fallback-url="{ name: 'core.persons' }"
-        :success-redirect-url="{ name: 'core.persons' }"
+        :disabled="$route.query._ui_action === 'create'"
       />
     </template>
 
diff --git a/aleksis/core/frontend/components/person/PersonListWrapper.vue b/aleksis/core/frontend/components/person/PersonListWrapper.vue
new file mode 100644
index 000000000..0913ba77f
--- /dev/null
+++ b/aleksis/core/frontend/components/person/PersonListWrapper.vue
@@ -0,0 +1,22 @@
+<template>
+  <component :is="currentComponent" v-bind="componentProps"/>
+</template>
+
+<script>
+import PersonForm from "./PersonForm.vue";
+import PersonList from "./PersonList.vue";
+
+export default {
+  computed: {
+    currentComponent() {
+      return this.$route.query._ui_action === "create" ? PersonForm : PersonList;
+    },
+    componentProps() {
+      return this.$route.query._ui_action === "create" ? {
+        fallbackUrl: { name: 'core.persons' },
+        successRedirectUrl: { name: 'core.persons' },
+      } : {};
+    },
+  }
+};
+</script>
diff --git a/aleksis/core/frontend/components/person/PersonOverview.vue b/aleksis/core/frontend/components/person/PersonOverview.vue
index b041afd02..209671ae3 100644
--- a/aleksis/core/frontend/components/person/PersonOverview.vue
+++ b/aleksis/core/frontend/components/person/PersonOverview.vue
@@ -43,7 +43,6 @@
           <person-actions
             :class="classes"
             :person="person"
-            @save="$refs.overview.$apollo.queries.object.refresh()"
           />
         </template>
 
diff --git a/aleksis/core/frontend/components/person/PersonOverviewWrapper.vue b/aleksis/core/frontend/components/person/PersonOverviewWrapper.vue
new file mode 100644
index 000000000..5be70d514
--- /dev/null
+++ b/aleksis/core/frontend/components/person/PersonOverviewWrapper.vue
@@ -0,0 +1,33 @@
+<template>
+  <component :is="currentComponent" v-bind="componentProps"/>
+</template>
+
+<script>
+import PersonForm from "./PersonForm.vue";
+import PersonOverview from "./PersonOverview.vue";
+
+export default {
+  computed: {
+    currentComponent() {
+      return this.$route.query._ui_action === "edit" ? PersonForm : PersonOverview;
+    },
+    componentProps() {
+      return this.$route.query._ui_action === "edit" ? {
+        fallbackUrl: { name: "core.personById", params: { id: this.id } },
+        successRedirectUrl: { name: "core.personById", params: { id: this.id } },
+        isCreate: false,
+        id: this.id,
+      } : {
+        id: this.id,
+      };
+    },
+  },
+  props: {
+    id: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+};
+</script>
diff --git a/aleksis/core/frontend/routes.js b/aleksis/core/frontend/routes.js
index 048272f56..f604de3b5 100644
--- a/aleksis/core/frontend/routes.js
+++ b/aleksis/core/frontend/routes.js
@@ -114,7 +114,7 @@ const routes = [
     children: [
       {
         path: "/persons/",
-        component: () => import("./components/person/PersonList.vue"),
+        component: () => import("./components/person/PersonListWrapper.vue"),
         name: "core.persons",
         meta: {
           inMenu: true,
@@ -126,7 +126,7 @@ const routes = [
       },
       {
         path: "/persons/:id(\\d+)",
-        component: () => import("./components/person/PersonOverview.vue"),
+        component: () => import("./components/person/PersonOverviewWrapper.vue"),
         name: "core.personById",
         props: true,
         meta: {
-- 
GitLab