Browse Source

feat: :sparkles: adicionar aprovacao de usuario

joykepler 1 tuần trước cách đây
mục cha
commit
7d5ed44628

+ 5 - 0
src/api/user.js

@@ -81,6 +81,11 @@ export const setUserOnLeave = async (id) => {
   return data.payload;
 };
 
+export const approveUser = async (id) => {
+  const { data } = await api.put(`/user/${id}/approve`);
+  return data.payload;
+};
+
 export const updateMyProfile = async (payload) => {
   const { data } = await api.put("/user/my/profile", payload);
   return data.payload;

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

@@ -69,7 +69,7 @@
 
     <template #body-cell-actions="{ row }">
       <q-td auto-width>
-        <q-item-section class="no-wrap" style="flex-direction: row">
+        <q-item-section :top="alignActionsLeft" class="no-wrap" style="flex-direction: row">
           <slot name="body-cell-actions" :row="row" />
           <q-btn
             v-if="deleteFunction"
@@ -158,6 +158,7 @@ const {
   deleteFunction,
   semTableTop,
   extraFilters,
+  alignActionsLeft,
 } = defineProps({
   columns: {
     type: Array,
@@ -223,6 +224,10 @@ const {
     type: Object,
     default: () => ({}),
   },
+  alignActionsLeft: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const { t } = useI18n();

+ 5 - 0
src/i18n/locales/en.json

@@ -481,10 +481,15 @@
     "set_on_leave_title": "Set member on leave",
     "set_on_leave_message": "Confirm putting {name} on leave?",
     "set_on_leave_confirm": "Confirm leave",
+    "approve_title": "Approve registration",
+    "approve_message": "Confirm approval of {name}'s registration?",
+    "approve_confirm": "Confirm approval",
     "import_associados": "Associates",
     "import_sync_result": "{created} created · {updated} updated · {inactivated} inactivated",
     "import_processing": "Import is still processing. Please refresh the page in a moment.",
     "search_placeholder": "Search Members",
+    "filter_by_position": "Filter by position",
+    "filter_by_sector": "Filter by sector",
     "no_dependents": "No dependents registered",
     "remove_photo": "Do you want to remove your profile photo?",
     "kinship": "Kinship",

+ 5 - 0
src/i18n/locales/es.json

@@ -482,10 +482,15 @@
     "set_on_leave_title": "Dar de baja al asociado",
     "set_on_leave_message": "¿Confirma la baja de {name}?",
     "set_on_leave_confirm": "Confirmar baja",
+    "approve_title": "Aprobar registro",
+    "approve_message": "¿Confirma la aprobación del registro de {name}?",
+    "approve_confirm": "Confirmar aprobación",
     "import_associados": "Asociados",
     "import_sync_result": "{created} creados · {updated} actualizados · {inactivated} inactivados",
     "import_processing": "La importación está en progreso. Actualice la página en unos instantes.",
     "search_placeholder": "Buscar por asociados",
+    "filter_by_position": "Filtrar por cargo",
+    "filter_by_sector": "Filtrar por sector",
     "no_dependents": "Ningún dependiente registrado",
     "remove_photo": "¿Deseas eliminar tu foto de perfil?",
     "kinship": "Parentesco",

+ 5 - 0
src/i18n/locales/pt.json

@@ -482,10 +482,15 @@
     "set_on_leave_title": "Afastar associado",
     "set_on_leave_message": "Confirma o afastamento de {name}?",
     "set_on_leave_confirm": "Confirmar afastamento",
+    "approve_title": "Aprovar cadastro",
+    "approve_message": "Confirma a aprovação do cadastro de {name}?",
+    "approve_confirm": "Confirmar aprovação",
     "import_associados": "Associados",
     "import_sync_result": "{created} criados · {updated} atualizados · {inactivated} inativados",
     "import_processing": "Importação em andamento. Atualize a página em alguns instantes.",
     "search_placeholder": "Buscar por associados",
+    "filter_by_position": "Filtrar por cargo",
+    "filter_by_sector": "Filtrar por setor",
     "no_dependents": "Nenhum dependente cadastrado",
     "remove_photo": "Deseja remover sua foto de perfil?",
     "kinship": "Parentesco",

+ 38 - 3
src/pages/gestao-associados/GestaoAssociadosPage.vue

@@ -53,6 +53,7 @@
       :api-call="getAssociadosPaginated"
       :add-item="false"
       :extra-filters="extraFilters"
+      align-actions-left
       open-item
       @on-row-click="onRowClick"
     >
@@ -65,6 +66,7 @@
           color="violet-normal"
           class="col-xs-12 col-sm-6 col-md-auto"
           style="min-width: 180px"
+          :placeholder="$t('associado.filter_by_position')"
         />
         <SectorSelect
           v-model="selectedSector"
@@ -74,11 +76,12 @@
           color="violet-normal"
           class="col-xs-12 col-sm-6 col-md-auto"
           style="min-width: 180px"
+          :placeholder="$t('associado.filter_by_sector')"
         />
       </template>
 
       <template #body-cell-actions="{ row }">
-        <div class="row no-wrap items-center" style="gap: 4px">
+        <div class="row no-wrap justify-start items-center" style="gap: 4px">
           <q-btn flat round dense color="violet-normal" icon="mdi-pencil" @click.stop="onRowClick({ row })" />
           <q-btn
             flat round dense
@@ -89,6 +92,16 @@
           />
           <q-btn flat round dense color="violet-normal" icon="mdi-calendar" @click.stop />
           <q-btn flat round dense color="violet-normal" icon="mdi-sofa" @click.stop />
+          <q-btn
+            v-if="row.status === 'pending' || row.status?.value === 'pending'"
+            flat 
+            round 
+            dense 
+            color="violet-normal"
+            icon="mdi-check-circle-outline"
+            :title="$t('associado.approve_confirm')"
+            @click.stop="onApprove(row)"
+          />
         </div>
       </template>
 
@@ -114,7 +127,7 @@ import { ref, computed, defineAsyncComponent, useTemplateRef } from "vue";
 import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
 import { permissionStore } from "src/stores/permission";
-import { getAssociadosPaginated, importAssociados, setUserOnLeave } from "src/api/user";
+import { getAssociadosPaginated, importAssociados, setUserOnLeave, approveUser } from "src/api/user";
 import { getStatusColor, getStatusI18nKey } from "src/helpers/utils";
 import { useImportPoller } from "src/composables/useImportPoller";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
@@ -188,7 +201,7 @@ const columns = computed(() => [
   {
     name: "actions",
     label: t("common.terms.actions"),
-    align: "right",
+    align: "center",
     required: true,
     width: "15%",
   },
@@ -250,6 +263,28 @@ const onSetOnLeave = (row) => {
   });
 };
 
+const onApprove = (row) => {
+  if (!permission_store.getAccess("associado", "edit")) {
+    $q.notify({ type: "negative", message: t("validation.permissions.edit") });
+    return;
+  }
+
+  $q.dialog({
+    title: t("associado.approve_title"),
+    message: t("associado.approve_message", { name: row.name }),
+    cancel: { flat: true, label: t("common.actions.cancel"), color: "grey-7" },
+    ok: { unelevated: true, label: t("associado.approve_confirm"), color: "violet-normal" },
+    persistent: true,
+  }).onOk(async () => {
+    try {
+      await approveUser(row.id);
+      tableRef.value?.refresh();
+    } catch {
+      // silent
+    }
+  });
+};
+
 const onOpenImportHistory = () => {
   $q.dialog({ component: ImportHistoryDialog, componentProps: { type: 'associado' } });
 };