|
@@ -0,0 +1,272 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <DefaultHeaderPage>
|
|
|
|
|
+ <template #after>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ class="btn-export"
|
|
|
|
|
+ :label="$t('relatorio.exportar_excel')"
|
|
|
|
|
+ icon="mdi-download"
|
|
|
|
|
+ padding="8px 16px"
|
|
|
|
|
+ :loading="exportLoading"
|
|
|
|
|
+ @click="onExport"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </DefaultHeaderPage>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="relatorio-tabs-row q-mb-md">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="tab in tabs"
|
|
|
|
|
+ :key="tab.name"
|
|
|
|
|
+ class="relatorio-tab-card"
|
|
|
|
|
+ :class="{ active: activeTab === tab.name }"
|
|
|
|
|
+ @click="selectTab(tab.name)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-icon :name="tab.icon" size="22px" color="violet-normal" />
|
|
|
|
|
+ <div class="tab-body">
|
|
|
|
|
+ <span class="tab-count">
|
|
|
|
|
+ {{ counters[tab.counterKey] !== undefined ? counters[tab.counterKey] : '—' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="tab-label">{{ tab.label }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="activeTab === 'novos-associados'">
|
|
|
|
|
+ <DefaultTableServerSide
|
|
|
|
|
+ :key="tableKey"
|
|
|
|
|
+ :columns="columnsNovosAssociados"
|
|
|
|
|
+ :api-call="getNovoAssociadosPaginated"
|
|
|
|
|
+ :add-item="false"
|
|
|
|
|
+ :show-search-field="true"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else-if="activeTab === 'contatos-associados'">
|
|
|
|
|
+ <DefaultTableServerSide
|
|
|
|
|
+ :key="tableKey"
|
|
|
|
|
+ :columns="columnsContatos"
|
|
|
|
|
+ :api-call="getContatosAssociadosPaginated"
|
|
|
|
|
+ :add-item="false"
|
|
|
|
|
+ :show-search-field="true"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else-if="activeTab === 'exclusoes-mes'">
|
|
|
|
|
+ <DefaultTableServerSide
|
|
|
|
|
+ :key="tableKey"
|
|
|
|
|
+ :columns="columnsExclusoes"
|
|
|
|
|
+ :api-call="getExclusoesMesPaginated"
|
|
|
|
|
+ :add-item="false"
|
|
|
|
|
+ :show-search-field="true"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, onMounted } from "vue";
|
|
|
|
|
+import { useQuasar } from "quasar";
|
|
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
|
|
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
|
|
+import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
|
|
|
|
|
+import {
|
|
|
|
|
+ getRelatorioCounters,
|
|
|
|
|
+ getNovoAssociadosPaginated,
|
|
|
|
|
+ getContatosAssociadosPaginated,
|
|
|
|
|
+ getExclusoesMesPaginated,
|
|
|
|
|
+ exportRelatorio,
|
|
|
|
|
+} from "src/api/relatorio.js";
|
|
|
|
|
+
|
|
|
|
|
+const $q = useQuasar();
|
|
|
|
|
+const { t } = useI18n();
|
|
|
|
|
+
|
|
|
|
|
+const activeTab = ref("novos-associados");
|
|
|
|
|
+const tableKey = ref(0);
|
|
|
|
|
+const exportLoading = ref(false);
|
|
|
|
|
+const counters = ref({
|
|
|
|
|
+ novos_associados: undefined,
|
|
|
|
|
+ contatos: undefined,
|
|
|
|
|
+ exclusoes_mes: undefined,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const tabs = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "novos-associados",
|
|
|
|
|
+ label: t("relatorio.novos_associados"),
|
|
|
|
|
+ icon: "mdi-account-multiple-outline",
|
|
|
|
|
+ counterKey: "novos_associados",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "contatos-associados",
|
|
|
|
|
+ label: t("relatorio.contatos"),
|
|
|
|
|
+ icon: "mdi-phone-outline",
|
|
|
|
|
+ counterKey: "contatos",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "exclusoes-mes",
|
|
|
|
|
+ label: t("relatorio.exclusoes_mes"),
|
|
|
|
|
+ icon: "mdi-account-remove-outline",
|
|
|
|
|
+ counterKey: "exclusoes_mes",
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const columnsNovosAssociados = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "name",
|
|
|
|
|
+ label: t("common.terms.name"),
|
|
|
|
|
+ field: "name",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "cpf",
|
|
|
|
|
+ label: t("associado.cpf"),
|
|
|
|
|
+ field: "cpf",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "created_at",
|
|
|
|
|
+ label: t("relatorio.col.cadastro"),
|
|
|
|
|
+ field: "created_at",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const columnsContatos = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "name",
|
|
|
|
|
+ label: t("common.terms.name"),
|
|
|
|
|
+ field: "name",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "phone",
|
|
|
|
|
+ label: t("associado.phone"),
|
|
|
|
|
+ field: "phone",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "email",
|
|
|
|
|
+ label: t("associado.email"),
|
|
|
|
|
+ field: "email",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const columnsExclusoes = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "name",
|
|
|
|
|
+ label: t("common.terms.name"),
|
|
|
|
|
+ field: "name",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "cpf",
|
|
|
|
|
+ label: t("associado.cpf"),
|
|
|
|
|
+ field: "cpf",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "excluded_at",
|
|
|
|
|
+ label: t("relatorio.col.exclusao"),
|
|
|
|
|
+ field: "excluded_at",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const selectTab = (name) => {
|
|
|
|
|
+ activeTab.value = name;
|
|
|
|
|
+ tableKey.value++;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onExport = async () => {
|
|
|
|
|
+ exportLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await exportRelatorio(activeTab.value);
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ $q.notify({ type: "negative", message: t("http.errors.failed") });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ exportLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await getRelatorioCounters();
|
|
|
|
|
+ counters.value = data;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // counters permanecem como undefined, exibindo '—'
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+@use "src/css/quasar.variables.scss" as vars;
|
|
|
|
|
+
|
|
|
|
|
+.relatorio-tabs-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.relatorio-tab-card {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 14px 20px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ border: 1.5px solid vars.$color-border;
|
|
|
|
|
+ background: vars.$surface;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: border-color 0.15s, background 0.15s;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover:not(.active) {
|
|
|
|
|
+ border-color: vars.$violet-light-active;
|
|
|
|
|
+ background: vars.$violet-light;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ border-color: vars.$violet-normal;
|
|
|
|
|
+ background: vars.$violet-light;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-body {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-count {
|
|
|
|
|
+ font-size: 1.375rem;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: vars.$violet-normal;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-label {
|
|
|
|
|
+ font-size: 0.875rem;
|
|
|
|
|
+ color: vars.$color-text-2;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.btn-export {
|
|
|
|
|
+ background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
|
|
|
|
|
+ color: white !important;
|
|
|
|
|
+ border-radius: 8px !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.btn-export :deep(.q-icon) {
|
|
|
|
|
+ color: white !important;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|