Browse Source

Merge branch 'development'

Gustavo Mantovani 2 weeks ago
parent
commit
26ac7ae136

+ 18 - 0
src/api/supportRequest.js

@@ -0,0 +1,18 @@
+import api from "src/api";
+
+export const getSupportRequests = async ({ page = 1, perPage = 10, filter, status } = {}) => {
+  const response = await api.get("/support-requests", {
+    params: { page, per_page: perPage, filter: filter || undefined, status: status || undefined },
+  });
+  return { data: { result: response.data.payload } };
+};
+
+export const getSupportRequest = async (id) => {
+  const { data } = await api.get(`/support-request/${id}`);
+  return data.payload;
+};
+
+export const updateSupportRequestStatus = async (id, status) => {
+  const { data } = await api.put(`/support-request/${id}`, { status });
+  return data.payload;
+};

+ 1 - 1
src/components/defaults/DefaultTableServerSide.vue

@@ -235,7 +235,7 @@ const router = useRouter();
 const rows = ref([]);
 const loading = ref(true);
 const fullscreen = ref(false);
-const rowsPerPageOptions = [5];
+const rowsPerPageOptions = [5, 10];
 
 const pagination = ref({
   filter: undefined,

+ 43 - 4
src/i18n/locales/en.json

@@ -325,8 +325,9 @@
       "birth_date": "Birth Date",
       "selfie_verified": "Selfie Verified",
       "document_verified": "Document Verified",
-      "document_front": "Document Front",
-      "document_back": "Document Back",
+      "document_front": "Document (Front)",
+      "document_back": "Document (Back)",
+      "no_documents": "No documents uploaded",
       "is_approved": "Approved",
       "average_rating": "Average Rating",
       "total_services": "Total Services",
@@ -716,7 +717,8 @@
       "service_type": "Service Type",
       "speciality": "Speciality",
       "reviews": "Reviews",
-      "payments": "Payments"
+      "payments": "Payments",
+      "support_requests": "Support requests"
     }
   },
   "charts": {
@@ -812,5 +814,42 @@
         }
       }
     }
+  },
+  "support_request": {
+    "singular": "Support request",
+    "plural": "Support requests",
+    "fields": {
+      "title": "Subject",
+      "category": "Category",
+      "name": "Name",
+      "email": "Email",
+      "phone": "Phone",
+      "user_type": "Type",
+      "status": "Status",
+      "message": "Message",
+      "handled_by": "Handled by",
+      "resolved_at": "Resolved at"
+    },
+    "categories": {
+      "payment": "Payment",
+      "schedule": "Schedule",
+      "account": "Account",
+      "other": "Other"
+    },
+    "status": {
+      "pending": "Pending",
+      "in_progress": "In progress",
+      "resolved": "Resolved"
+    },
+    "actions": {
+      "mark_in_progress": "Take request",
+      "mark_resolved": "Mark as resolved"
+    },
+    "user_types": {
+      "client": "Client",
+      "provider": "Provider",
+      "admin": "Administrator",
+      "user": "Operator"
+    }
   }
-}
+}

+ 43 - 4
src/i18n/locales/es.json

@@ -325,8 +325,9 @@
       "birth_date": "Fecha de Nacimiento",
       "selfie_verified": "Selfie Verificada",
       "document_verified": "Documento Verificado",
-      "document_front": "Frente del Documento",
-      "document_back": "Verso del Documento",
+      "document_front": "Documento (Frente)",
+      "document_back": "Documento (Reverso)",
+      "no_documents": "Ningún documento enviado",
       "is_approved": "Aprobado",
       "average_rating": "Calificación Promedio",
       "total_services": "Total de Servicios",
@@ -716,7 +717,8 @@
       "service_type": "Tipo de Servicio",
       "speciality": "Especialidad",
       "reviews": "Evaluaciones",
-      "payments": "Pagos"
+      "payments": "Pagos",
+      "support_requests": "Atenciones"
     }
   },
   "charts": {
@@ -812,5 +814,42 @@
         }
       }
     }
+  },
+  "support_request": {
+    "singular": "Atención",
+    "plural": "Atenciones",
+    "fields": {
+      "title": "Asunto",
+      "category": "Categoría",
+      "name": "Nombre",
+      "email": "Correo",
+      "phone": "Teléfono",
+      "user_type": "Tipo",
+      "status": "Estado",
+      "message": "Mensaje",
+      "handled_by": "Atendido por",
+      "resolved_at": "Resuelto el"
+    },
+    "categories": {
+      "payment": "Pago",
+      "schedule": "Agenda",
+      "account": "Cuenta",
+      "other": "Otro"
+    },
+    "status": {
+      "pending": "Pendiente",
+      "in_progress": "En atención",
+      "resolved": "Resuelto"
+    },
+    "actions": {
+      "mark_in_progress": "Tomar atención",
+      "mark_resolved": "Marcar como resuelto"
+    },
+    "user_types": {
+      "client": "Cliente",
+      "provider": "Prestador",
+      "admin": "Administrador",
+      "user": "Operador"
+    }
   }
-}
+}

+ 43 - 4
src/i18n/locales/pt.json

@@ -325,8 +325,9 @@
       "birth_date": "Data de Nascimento",
       "selfie_verified": "Selfie Verificada",
       "document_verified": "Documento Verificado",
-      "document_front": "Frente do Documento",
-      "document_back": "Verso do Documento",
+      "document_front": "Documento (Frente)",
+      "document_back": "Documento (Verso)",
+      "no_documents": "Nenhum documento enviado",
       "is_approved": "Aprovado",
       "average_rating": "Avaliação Média",
       "total_services": "Total de Serviços",
@@ -716,7 +717,8 @@
       "service_type": "Tipo de Serviço",
       "speciality": "Especialidade",
       "reviews": "Avaliações",
-      "payments": "Pagamentos"
+      "payments": "Pagamentos",
+      "support_requests": "Atendimentos"
     }
   },
   "charts": {
@@ -812,5 +814,42 @@
         }
       }
     }
+  },
+  "support_request": {
+    "singular": "Atendimento",
+    "plural": "Atendimentos",
+    "fields": {
+      "title": "Assunto",
+      "category": "Categoria",
+      "name": "Nome",
+      "email": "E-mail",
+      "phone": "Telefone",
+      "user_type": "Tipo",
+      "status": "Status",
+      "message": "Mensagem",
+      "handled_by": "Atendido por",
+      "resolved_at": "Resolvido em"
+    },
+    "categories": {
+      "payment": "Pagamento",
+      "schedule": "Agendamento",
+      "account": "Conta",
+      "other": "Outro"
+    },
+    "status": {
+      "pending": "Pendente",
+      "in_progress": "Em atendimento",
+      "resolved": "Resolvido"
+    },
+    "actions": {
+      "mark_in_progress": "Assumir atendimento",
+      "mark_resolved": "Marcar como resolvido"
+    },
+    "user_types": {
+      "client": "Cliente",
+      "provider": "Prestador",
+      "admin": "Administrador",
+      "user": "Operador"
+    }
   }
-}
+}

+ 1 - 1
src/pages/client/components/AddEditClientDialog.vue

@@ -28,7 +28,7 @@
             <q-card-section class="row q-col-gutter-sm">
               <div v-if="client" class="col-12 flex flex-center column q-pb-sm">
                 <q-avatar size="80px" color="grey-3">
-                  <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" />
+                  <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" @error="avatarPreview = null" />
                   <q-icon v-else name="mdi-account" size="50px" color="grey-6" />
                 </q-avatar>
                 <input

+ 70 - 35
src/pages/provider/components/AddEditProviderDialog.vue

@@ -97,18 +97,19 @@
                 />
 
                 <div class="col-12">
-                  <q-select
+                  <div class="text-subtitle2 text-grey-7 q-mb-xs">
+                    {{ $t('provider.fields.approval_status') }}
+                  </div>
+                  <q-option-group
                     v-model="form.approval_status"
                     :options="approvalStatusOptions"
-                    :label="$t('provider.fields.approval_status')"
-                    emit-value
-                    map-options
-                    outlined
-                    dense
-                    :error="!!serverErrors?.approval_status"
-                    :error-message="serverErrors?.approval_status"
-                    class="col-12"
+                    type="radio"
+                    color="primary"
+                    inline
                   />
+                  <div v-if="serverErrors?.approval_status" class="text-negative text-caption">
+                    {{ serverErrors.approval_status }}
+                  </div>
                 </div>
 
                 <ProviderSpecialitiesPanel v-if="provider" :provider-id="provider.id" />
@@ -119,7 +120,7 @@
                   <div class="row q-col-gutter-md">
                     <div class="col-auto flex flex-center column">
                       <q-avatar size="80px" color="grey-3">
-                        <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" />
+                        <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" @error="avatarPreview = null" />
                         <q-icon v-else name="mdi-account" size="50px" color="grey-6" />
                       </q-avatar>
                       <input
@@ -163,52 +164,67 @@
                       </div>
 
                       <div class="col-md-6 col-12">
-
                         <div class="text-subtitle2 text-grey-7 q-mb-sm">
                           {{ $t('provider.fields.selfie_verified') }}
                         </div>
-
                         <q-checkbox
                           v-model="form.selfie_verified"
-                          label="Verificar foto"
+                          :label="$t('provider.fields.selfie_verified')"
                           color="primary"
                         />
-
                       </div>
 
                       <div class="col-12">
-
                         <div class="text-subtitle2 text-grey-7 q-mb-sm">
                           {{ $t('provider.fields.document_verified') }}
                         </div>
 
-                        <div v-if="documentFrontUrl || documentBackUrl" class="row q-col-gutter-sm q-mb-sm">
-                          <div v-if="documentFrontUrl" class="col">
+                        <div class="row q-col-gutter-sm q-mb-sm">
+                          <div class="col-auto">
                             <div class="text-caption text-grey-6 q-mb-xs">{{ $t('provider.fields.document_front') }}</div>
-                            <q-img
-                              :src="documentFrontUrl"
-                              fit="cover"
-                              style="height: 110px; width: 300px; object-fit: contain;border-radius: 6px; cursor: pointer;"
-                              @click="docLightbox = documentFrontUrl"
-                            />
+                            <div
+                              class="doc-frame"
+                              :class="{ 'doc-frame--clickable': documentFrontUrl }"
+                              @click="documentFrontUrl && (docLightbox = documentFrontUrl)"
+                            >
+                              <q-img
+                                v-if="documentFrontUrl"
+                                :src="documentFrontUrl"
+                                fit="contain"
+                                style="height: 100%; width: 100%;"
+                              />
+                              <div v-else class="column flex-center text-grey-5">
+                                <q-icon name="mdi-image-off-outline" size="34px" />
+                                <div class="text-caption q-mt-xs">{{ $t('provider.fields.no_documents') }}</div>
+                              </div>
+                            </div>
                           </div>
-                          <div v-if="documentBackUrl" class="col">
+                          <div class="col-auto">
                             <div class="text-caption text-grey-6 q-mb-xs">{{ $t('provider.fields.document_back') }}</div>
-                            <q-img
-                              :src="documentBackUrl"
-                              fit="cover"
-                              style="height: 110px; width: 300px; object-fit: contain;border-radius: 6px; cursor: pointer;"
-                              @click="docLightbox = documentBackUrl"
-                            />
+                            <div
+                              class="doc-frame"
+                              :class="{ 'doc-frame--clickable': documentBackUrl }"
+                              @click="documentBackUrl && (docLightbox = documentBackUrl)"
+                            >
+                              <q-img
+                                v-if="documentBackUrl"
+                                :src="documentBackUrl"
+                                fit="contain"
+                                style="height: 100%; width: 100%;"
+                              />
+                              <div v-else class="column flex-center text-grey-5">
+                                <q-icon name="mdi-image-off-outline" size="34px" />
+                                <div class="text-caption q-mt-xs">{{ $t('provider.fields.no_documents') }}</div>
+                              </div>
+                            </div>
                           </div>
                         </div>
 
                         <q-checkbox
                           v-model="form.document_verified"
-                          label="Validar documentos"
+                          :label="$t('provider.fields.document_verified')"
                           color="primary"
                         />
-
                       </div>
                     </div>
                   </div>
@@ -339,8 +355,8 @@ const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   document: provider ? provider?.document : "",
   rg: provider ? provider?.rg : "",
   birth_date: provider ? provider?.birth_date : null,
- selfie_verified: provider? provider?.selfie_verified:false,
- document_verified: provider? provider?.document_verified:false,
+  selfie_verified: provider ? provider?.selfie_verified : false,
+  document_verified: provider ? provider?.document_verified : false,
   approval_status: provider ? provider?.approval_status : "pending",
   daily_price_8h: provider ? Number(provider?.daily_price_8h) : null,
   daily_price_6h: provider ? Number(provider?.daily_price_6h) : null,
@@ -382,7 +398,7 @@ const onOKClick = async () => {
       const fields = getUpdatedFields.value;
       Object.entries(fields).forEach(([key, value]) => {
         if (value !== null && value !== undefined) {
-          formData.append(key, String(value));
+          formData.append(key, typeof value === 'boolean' ? (value ? '1' : '0') : String(value));
         }
       });
       formData.append('avatar', avatarFile.value);
@@ -419,8 +435,27 @@ onMounted(async () => {
     avatarPreview.value = provider.profile_media?.url ?? null;
 
     const full = await getProvider(provider.id);
+    avatarPreview.value = full.profile_media?.url ?? avatarPreview.value;
     documentFrontUrl.value = full.document_front_media?.url ?? null;
     documentBackUrl.value = full.document_back_media?.url ?? null;
   }
 });
 </script>
+
+<style scoped>
+.doc-frame {
+  width: 300px;
+  height: 170px;
+  border: 1px solid #e0e0e0;
+  border-radius: 6px;
+  background: #fafafa;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.doc-frame--clickable {
+  cursor: pointer;
+}
+</style>

+ 129 - 0
src/pages/supportRequest/SupportRequestsPage.vue

@@ -0,0 +1,129 @@
+<template>
+  <div>
+    <DefaultHeaderPage />
+    <div>
+      <div class="row q-px-sm q-mb-sm">
+        <q-select
+          v-model="statusFilter"
+          :options="statusOptions"
+          :label="$t('support_request.fields.status')"
+          emit-value
+          map-options
+          dense
+          outlined
+          clearable
+          style="min-width: 220px"
+        />
+      </div>
+      <DefaultTableServerSide
+        :key="statusFilter ?? 'all'"
+        ref="tableRef"
+        :columns="columns"
+        :api-call="apiCall"
+        :add-item="false"
+        :rows-per-page="10"
+        open-item
+        @on-row-click="onRowClick"
+      >
+        <template #body-cell-status="slotProps">
+          <q-td :props="slotProps">
+            <q-badge :color="statusColor(slotProps.row.status)" :label="statusLabel(slotProps.row.status)" />
+          </q-td>
+        </template>
+      </DefaultTableServerSide>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineAsyncComponent, useTemplateRef, computed, ref } from "vue";
+import { useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { getSupportRequests } from "src/api/supportRequest";
+
+import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+
+const SupportRequestDetailDialog = defineAsyncComponent(() =>
+  import("src/pages/supportRequest/components/SupportRequestDetailDialog.vue")
+);
+
+const $q = useQuasar();
+const tableRef = useTemplateRef("tableRef");
+const { t } = useI18n();
+
+const statusFilter = ref(null);
+
+const apiCall = (params) => getSupportRequests({ ...params, status: statusFilter.value });
+
+const statusOptions = computed(() => [
+  { label: t("support_request.status.pending"), value: "pending" },
+  { label: t("support_request.status.in_progress"), value: "in_progress" },
+  { label: t("support_request.status.resolved"), value: "resolved" },
+]);
+
+const categoryLabel = (category) => t(`support_request.categories.${category}`, category);
+const statusLabel = (status) => t(`support_request.status.${status}`, status);
+const userTypeLabel = (type) =>
+  type ? t(`support_request.user_types.${type.toLowerCase()}`, type) : "—";
+const statusColor = (status) =>
+  ({ pending: "orange", in_progress: "blue", resolved: "green" }[status] || "grey");
+
+const columns = computed(() => [
+  {
+    name: "title",
+    label: t("support_request.fields.title"),
+    field: "title",
+    align: "left",
+    sortable: false,
+  },
+  {
+    name: "category",
+    label: t("support_request.fields.category"),
+    field: (row) => categoryLabel(row.category),
+    align: "left",
+    sortable: false,
+  },
+  {
+    name: "name",
+    label: t("support_request.fields.name"),
+    field: "name",
+    align: "left",
+    sortable: false,
+  },
+  {
+    name: "user_type",
+    label: t("support_request.fields.user_type"),
+    field: (row) => userTypeLabel(row.user_type),
+    align: "left",
+    sortable: false,
+  },
+  {
+    name: "status",
+    label: t("support_request.fields.status"),
+    field: (row) => statusLabel(row.status),
+    align: "left",
+    sortable: false,
+  },
+  {
+    name: "created_at",
+    label: t("common.terms.created_at"),
+    field: "created_at",
+    align: "left",
+    sortable: false,
+  },
+]);
+
+const onRowClick = ({ row }) => {
+  $q.dialog({
+    component: SupportRequestDetailDialog,
+    componentProps: {
+      supportRequest: row,
+    },
+  }).onOk(async (updated) => {
+    if (updated) {
+      tableRef.value.refresh();
+    }
+  });
+};
+</script>

+ 165 - 0
src/pages/supportRequest/components/SupportRequestDetailDialog.vue

@@ -0,0 +1,165 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin overflow-hidden" style="width: 720px; max-width: 92vw">
+      <DefaultDialogHeader :title="title" @close="handleClose" />
+
+      <q-card-section class="q-gutter-y-sm">
+        <div class="row items-center justify-between">
+          <div class="text-h6">{{ form.title }}</div>
+          <q-badge :color="statusColor(form.status)" :label="statusLabel(form.status)" />
+        </div>
+
+        <q-list dense>
+          <q-item>
+            <q-item-section>
+              <q-item-label caption>{{ $t("support_request.fields.category") }}</q-item-label>
+              <q-item-label>{{ categoryLabel(form.category) }}</q-item-label>
+            </q-item-section>
+            <q-item-section>
+              <q-item-label caption>{{ $t("support_request.fields.user_type") }}</q-item-label>
+              <q-item-label>{{ userTypeLabel(form.user_type) }}</q-item-label>
+            </q-item-section>
+          </q-item>
+
+          <q-item>
+            <q-item-section>
+              <q-item-label caption>{{ $t("support_request.fields.name") }}</q-item-label>
+              <q-item-label>{{ form.name || emptyLabel }}</q-item-label>
+            </q-item-section>
+            <q-item-section>
+              <q-item-label caption>{{ $t("support_request.fields.email") }}</q-item-label>
+              <q-item-label>
+                <a v-if="form.email" :href="`mailto:${form.email}`">{{ form.email }}</a>
+                <span v-else>{{ emptyLabel }}</span>
+              </q-item-label>
+            </q-item-section>
+            <q-item-section>
+              <q-item-label caption>{{ $t("support_request.fields.phone") }}</q-item-label>
+              <q-item-label>
+                <a v-if="form.phone" :href="`tel:${form.phone}`">{{ form.phone }}</a>
+                <span v-else>{{ emptyLabel }}</span>
+              </q-item-label>
+            </q-item-section>
+          </q-item>
+
+          <q-item>
+            <q-item-section>
+              <q-item-label caption>{{ $t("common.terms.created_at") }}</q-item-label>
+              <q-item-label>{{ form.created_at }}</q-item-label>
+            </q-item-section>
+            <q-item-section v-if="form.handler">
+              <q-item-label caption>{{ $t("support_request.fields.handled_by") }}</q-item-label>
+              <q-item-label>{{ form.handler }}</q-item-label>
+            </q-item-section>
+            <q-item-section v-if="form.resolved_at">
+              <q-item-label caption>{{ $t("support_request.fields.resolved_at") }}</q-item-label>
+              <q-item-label>{{ form.resolved_at }}</q-item-label>
+            </q-item-section>
+          </q-item>
+        </q-list>
+
+        <div>
+          <div class="text-caption text-grey-7 q-mb-xs">
+            {{ $t("support_request.fields.message") }}
+          </div>
+          <div class="support-message">{{ form.message }}</div>
+        </div>
+      </q-card-section>
+
+      <q-card-actions align="right" class="q-px-md q-pb-md q-gutter-x-sm">
+        <q-btn :label="$t('common.actions.close')" flat color="primary" @click="handleClose" />
+        <q-btn
+          v-if="canEdit && form.status !== 'in_progress' && form.status !== 'resolved'"
+          color="blue"
+          unelevated
+          :label="$t('support_request.actions.mark_in_progress')"
+          :loading="loading"
+          @click="changeStatus('in_progress')"
+        />
+        <q-btn
+          v-if="canEdit && form.status !== 'resolved'"
+          color="green"
+          unelevated
+          :label="$t('support_request.actions.mark_resolved')"
+          :loading="loading"
+          @click="changeStatus('resolved')"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { permissionStore } from "src/stores/permission";
+import { updateSupportRequestStatus } from "src/api/supportRequest";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { supportRequest } = defineProps({
+  supportRequest: {
+    type: Object,
+    required: true,
+  },
+});
+
+const { t } = useI18n();
+const $q = useQuasar();
+const permission_store = permissionStore();
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+
+const form = ref({ ...supportRequest });
+const loading = ref(false);
+const updated = ref(false);
+
+const title = () => t("ui.navigation.support_requests");
+const canEdit = computed(() => permission_store.getAccess("support.request", "edit") !== false);
+
+const emptyLabel = "—";
+const categoryLabel = (category) => t(`support_request.categories.${category}`, category);
+const statusLabel = (status) => t(`support_request.status.${status}`, status);
+const userTypeLabel = (type) =>
+  type ? t(`support_request.user_types.${type.toLowerCase()}`, type) : emptyLabel;
+const statusColor = (status) =>
+  ({ pending: "orange", in_progress: "blue", resolved: "green" }[status] || "grey");
+
+const changeStatus = async (status) => {
+  loading.value = true;
+  try {
+    const result = await updateSupportRequestStatus(form.value.id, status);
+    form.value = { ...form.value, ...result };
+    updated.value = true;
+    $q.notify({ type: "positive", message: t("common.ui.messages.updated") });
+  } catch (error) {
+    console.error(error);
+    $q.notify({ type: "negative", message: t("http.errors.failed") });
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleClose = () => {
+  if (updated.value) {
+    onDialogOK(true);
+  } else {
+    onDialogCancel();
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.support-message {
+  white-space: pre-wrap;
+  word-break: break-word;
+  background: #f5f5f7;
+  border-radius: 12px;
+  padding: 14px;
+  line-height: 1.5;
+  max-height: 320px;
+  overflow-y: auto;
+}
+</style>

+ 22 - 0
src/router/routes/supportRequest.route.js

@@ -0,0 +1,22 @@
+export default [
+  {
+    path: "/support-requests",
+    name: "SupportRequestsPage",
+    component: () => import("pages/supportRequest/SupportRequestsPage.vue"),
+    meta: {
+      title: "ui.navigation.support_requests",
+      requireAuth: true,
+      requiredPermission: "support.request",
+      breadcrumbs: [
+        {
+          name: "DashboardPage",
+          title: "ui.navigation.dashboard",
+        },
+        {
+          name: "SupportRequestsPage",
+          title: "ui.navigation.support_requests",
+        },
+      ],
+    },
+  },
+];

+ 9 - 0
src/stores/navigation.js

@@ -49,6 +49,15 @@ export const navigationStore = defineStore("navigation", () => {
       permission: false,
       permissionScope: ["payment", "config.payment"],
     },
+    {
+      type: "single",
+      title: "ui.navigation.support_requests",
+      name: "SupportRequestsPage",
+      icon: "mdi-face-agent",
+      disable: false,
+      permission: false,
+      permissionScope: "support.request",
+    },
     {
       type: "expansive",
       title: "ui.navigation.registration",