Просмотр исходного кода

ajustes gestao associados:filtros

Gustavo Zanatta 2 недель назад
Родитель
Сommit
4490d24fe2

+ 2 - 0
src/api/user.js

@@ -64,6 +64,7 @@ export const getUsersPaginated = async ({
   status,
   position_id,
   sector_id,
+  first_access,
   withStatusOptions,
 } = {}) => {
   const params = { page, per_page: perPage };
@@ -72,6 +73,7 @@ export const getUsersPaginated = async ({
   if (filter)      params.search      = filter;
   if (position_id) params.position_id = position_id;
   if (sector_id)   params.sector_id   = sector_id;
+  if (first_access !== null && first_access !== undefined) params.first_access = first_access;
   if (withStatusOptions) params.with_status_options = true;
   const { data } = await api.get("/user/paginated", { params });
   return { data: { result: data.payload } };

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

@@ -30,7 +30,7 @@
           clearable
           autofocus
           color="violet-normal"
-          class="col-xs-12 col-md"
+          :class="stackFilters ? 'col-12' : 'col-xs-12 col-md'"
         >
           <template #prepend>
             <q-icon name="mdi-magnify" />
@@ -160,6 +160,7 @@ const {
   semTableTop,
   extraFilters,
   alignActionsLeft,
+  stackFilters,
 } = defineProps({
   columns: {
     type: Array,
@@ -233,6 +234,10 @@ const {
     type: Boolean,
     default: false,
   },
+  stackFilters: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const { t } = useI18n();

+ 16 - 0
src/composables/useUserStatusOptions.js

@@ -0,0 +1,16 @@
+import { computed } from "vue";
+import { useI18n } from "vue-i18n";
+
+export const useUserStatusOptions = () => {
+  const { t } = useI18n();
+
+  const userStatusOptions = computed(() => [
+    { label: t("common.status.active"),   value: "active" },
+    { label: t("common.status.inactive"), value: "inactive" },
+    { label: t("common.status.pending"),  value: "pending" },
+    { label: t("common.status.on_leave"), value: "on_leave" },
+    { label: t("common.status.refused"),  value: "refused" },
+  ]);
+
+  return { userStatusOptions };
+};

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

@@ -579,6 +579,8 @@
     "search_placeholder": "Search Members",
     "filter_by_position": "Filter by position",
     "filter_by_sector": "Filter by sector",
+    "filter_by_status": "Filter by status",
+    "filter_by_first_access": "Filter by app access",
     "no_dependents": "No dependents registered",
     "no_approved_dependents": "No approved dependents",
     "remove_photo": "Do you want to remove your profile photo?",

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

@@ -580,6 +580,8 @@
     "search_placeholder": "Buscar por asociados",
     "filter_by_position": "Filtrar por cargo",
     "filter_by_sector": "Filtrar por sector",
+    "filter_by_status": "Filtrar por estado",
+    "filter_by_first_access": "Filtrar por acceso a la app",
     "no_dependents": "Ningún dependiente registrado",
     "no_approved_dependents": "Ningún dependiente aprobado",
     "remove_photo": "¿Deseas eliminar tu foto de perfil?",

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

@@ -580,6 +580,8 @@
     "search_placeholder": "Buscar por associados",
     "filter_by_position": "Filtrar por cargo",
     "filter_by_sector": "Filtrar por setor",
+    "filter_by_status": "Filtrar por status",
+    "filter_by_first_access": "Filtrar por acesso ao app",
     "no_dependents": "Nenhum dependente cadastrado",
     "no_approved_dependents": "Nenhum dependente aprovado",
     "remove_photo": "Deseja remover sua foto de perfil?",

+ 70 - 7
src/pages/gestao-associados/GestaoAssociadosPage.vue

@@ -79,6 +79,7 @@
       :extra-filters="extraFilters"
       align-actions-left
       open-item
+      stack-filters
       @on-row-click="onRowClick"
     >
       <template #filters>
@@ -102,6 +103,42 @@
           style="min-width: 180px"
           :placeholder="$t('associado.filter_by_sector')"
         />
+        <DefaultSelect
+          v-model="selectedStatus"
+          :label="$t('common.terms.status')"
+          :options="filteredStatusOptions"
+          use-input
+          hide-selected
+          fill-input
+          clearable
+          dense
+          outlined
+          emit-value
+          map-options
+          color="violet-normal"
+          class="col-xs-12 col-sm-6 col-md-auto"
+          style="min-width: 180px"
+          :placeholder="$t('associado.filter_by_status')"
+          @filter="filterStatusOptions"
+        />
+        <DefaultSelect
+          v-model="selectedFirstAccess"
+          :label="$t('associado.primeiro_acesso')"
+          :options="filteredFirstAccessOptions"
+          use-input
+          hide-selected
+          fill-input
+          clearable
+          dense
+          outlined
+          emit-value
+          map-options
+          color="violet-normal"
+          class="col-xs-12 col-sm-6 col-md-auto"
+          style="min-width: 180px"
+          :placeholder="$t('associado.filter_by_first_access')"
+          @filter="filterFirstAccessOptions"
+        />
       </template>
 
       <template #body-cell-actions="{ row }">
@@ -163,10 +200,12 @@ import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
 import { permissionStore } from "src/stores/permission";
 import { getAssociadosPaginated, importAssociados, importAfastamentos, setUserOnLeave, approveUser } from "src/api/user";
-import { getStatusColor, getStatusI18nKey } from "src/helpers/utils";
+import { getStatusColor, getStatusI18nKey, normalizeString } from "src/helpers/utils";
 import { useImportPoller } from "src/composables/useImportPoller";
+import { useUserStatusOptions } from "src/composables/useUserStatusOptions";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import PositionSelect from "src/components/selects/PositionSelect.vue";
 import SectorSelect from "src/components/selects/SectorSelect.vue";
 import AccessLogsTable from "./components/AccessLogsTable.vue";
@@ -191,11 +230,41 @@ const { polling: importing, start: startPolling } = useImportPoller();
 const importAfastamentosFileInput = useTemplateRef("importAfastamentosFileInput");
 const { polling: importingAfastamentos, start: startAfastamentosPolling } = useImportPoller();
 
+const { userStatusOptions } = useUserStatusOptions();
+
+const firstAccessOptions = computed(() => [
+  { label: t("common.status.yes"), value: "true" },
+  { label: t("common.status.no"), value: "false" },
+]);
+
+const filteredStatusOptions = ref(userStatusOptions.value);
+const filteredFirstAccessOptions = ref(firstAccessOptions.value);
+
+const filterStatusOptions = (val, update) => {
+  const needle = normalizeString(val);
+  filteredStatusOptions.value = userStatusOptions.value.filter((o) =>
+    normalizeString(o.label).includes(needle),
+  );
+  update();
+};
+
+const filterFirstAccessOptions = (val, update) => {
+  const needle = normalizeString(val);
+  filteredFirstAccessOptions.value = firstAccessOptions.value.filter((o) =>
+    normalizeString(o.label).includes(needle),
+  );
+  update();
+};
+
 const selectedPosition = ref(null);
 const selectedSector = ref(null);
+const selectedStatus = ref(null);
+const selectedFirstAccess = ref(null);
 const extraFilters = computed(() => ({
   position_id: selectedPosition.value?.value,
   sector_id: selectedSector.value?.value,
+  status: selectedStatus.value,
+  first_access: selectedFirstAccess.value === null ? null : selectedFirstAccess.value === "true",
 }));
 
 const columns = computed(() => [
@@ -204,7 +273,6 @@ const columns = computed(() => [
     label: t("associado.cracha"),
     field: "registration",
     align: "left",
-    sortable: true,
     width: "10%",
   },
   {
@@ -212,7 +280,6 @@ const columns = computed(() => [
     label: t("common.terms.name"),
     field: "name",
     align: "left",
-    sortable: true,
     width: "25%",
   },
   {
@@ -220,7 +287,6 @@ const columns = computed(() => [
     label: t("associado.position"),
     field: (row) => row.position?.name ?? "—",
     align: "left",
-    sortable: true,
     width: "25%",
   },
   {
@@ -228,7 +294,6 @@ const columns = computed(() => [
     label: t("associado.admission"),
     field: (row) => (row.admission_date ? row.admission_date : "—"),
     align: "left",
-    sortable: true,
     width: "10%",
   },
   {
@@ -236,7 +301,6 @@ const columns = computed(() => [
     label: t("associado.validity"),
     field: (row) => (row.expiry_date ? row.expiry_date : "—"),
     align: "left",
-    sortable: true,
     width: "10%",
   },
   {
@@ -258,7 +322,6 @@ const columns = computed(() => [
     label: t("common.terms.status"),
     field: "status",
     align: "center",
-    sortable: true,
     width: "5%",
   },
 ]);

+ 4 - 10
src/pages/gestao-associados/components/AddEditAssociadoDialog.vue

@@ -16,7 +16,7 @@
           <DefaultSelect
             v-model="form.status"
             v-model:error="validationErrors.status"
-            :options="statusOptions"
+            :options="userStatusOptions"
             :label="$t('common.terms.status')"
             emit-value
             map-options
@@ -165,7 +165,7 @@
 </template>
 
 <script setup>
-import { ref, computed, useTemplateRef, watch, onMounted, defineAsyncComponent } from "vue";
+import { ref, useTemplateRef, watch, onMounted, defineAsyncComponent } from "vue";
 import { useInputRules } from "src/composables/useInputRules";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
@@ -174,6 +174,7 @@ import { getDependentsByUser, deleteDependent } from "src/api/profile";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { useDependentStatus } from "src/composables/useDependentStatus";
+import { useUserStatusOptions } from "src/composables/useUserStatusOptions";
 
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import DefaultInput from "src/components/defaults/DefaultInput.vue";
@@ -205,6 +206,7 @@ const $q = useQuasar();
 const { inputRules } = useInputRules();
 const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
 const { dependentStatusColor, dependentStatusLabel } = useDependentStatus();
+const { userStatusOptions } = useUserStatusOptions();
 const formRef = useTemplateRef("formRef");
 const confirmPassword = ref("");
 const dependents = ref([]);
@@ -257,14 +259,6 @@ const selectedSector = ref(
   associado?.sector ? { label: associado.sector.name, value: associado.sector.id } : null,
 );
 
-const statusOptions = computed(() => [
-  { label: t("common.status.active"),   value: "active" },
-  { label: t("common.status.inactive"), value: "inactive" },
-  { label: t("common.status.pending"),  value: "pending" },
-  { label: t("common.status.on_leave"), value: "on_leave" },
-  { label: t("common.status.refused"),  value: "refused" },
-]);
-
 const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   name:           associado?.name           ?? "",
   email:          associado?.email          ?? "",