RelatoriosPage.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <template>
  2. <div>
  3. <DefaultHeaderPage>
  4. <template #after>
  5. <q-btn
  6. unelevated
  7. dense
  8. class="btn-export"
  9. icon="mdi-download"
  10. padding="6px 10px"
  11. :loading="exportLoading"
  12. :title="$t('relatorio.exportar_excel')"
  13. @click="onExport"
  14. />
  15. </template>
  16. </DefaultHeaderPage>
  17. <div class="relatorio-tabs-row q-mb-md">
  18. <div
  19. v-for="tab in tabs"
  20. :key="tab.name"
  21. class="relatorio-tab-card"
  22. :class="{ active: activeTab === tab.name }"
  23. @click="selectTab(tab.name)"
  24. >
  25. <q-icon :name="tab.icon" size="22px" color="violet-normal" />
  26. <div class="tab-body">
  27. <span class="tab-count">
  28. {{ counters[tab.counterKey] !== undefined ? counters[tab.counterKey] : '—' }}
  29. </span>
  30. <span class="tab-label">{{ tab.label }}</span>
  31. </div>
  32. </div>
  33. </div>
  34. <div v-if="activeTab === 'novos-associados'">
  35. <DefaultTableServerSide
  36. ref="tableRef"
  37. :key="tableKey"
  38. :columns="columnsNovosAssociados"
  39. :api-call="getNovoAssociadosPaginated"
  40. :add-item="false"
  41. :show-search-field="true"
  42. />
  43. </div>
  44. <div v-else-if="activeTab === 'contatos-associados'">
  45. <DefaultTableServerSide
  46. ref="tableRef"
  47. :key="tableKey"
  48. :columns="columnsContatos"
  49. :api-call="getContatosAssociadosPaginated"
  50. :add-item="false"
  51. :show-search-field="true"
  52. />
  53. </div>
  54. <div v-else-if="activeTab === 'exclusoes-mes'">
  55. <DefaultTableServerSide
  56. ref="tableRef"
  57. :key="tableKey"
  58. :columns="columnsExclusoes"
  59. :api-call="getExclusoesMesPaginated"
  60. :add-item="false"
  61. :show-search-field="true"
  62. />
  63. </div>
  64. </div>
  65. </template>
  66. <script setup>
  67. import { ref, computed, onMounted, useTemplateRef } from "vue";
  68. import { useQuasar } from "quasar";
  69. import { useI18n } from "vue-i18n";
  70. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  71. import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
  72. import {
  73. getRelatorioCounters,
  74. getNovoAssociadosPaginated,
  75. getContatosAssociadosPaginated,
  76. getExclusoesMesPaginated,
  77. exportRelatorio,
  78. } from "src/api/relatorio.js";
  79. const $q = useQuasar();
  80. const { t } = useI18n();
  81. const activeTab = ref("novos-associados");
  82. const tableKey = ref(0);
  83. const exportLoading = ref(false);
  84. const tableRef = useTemplateRef("tableRef");
  85. const counters = ref({
  86. novos_associados: undefined,
  87. contatos: undefined,
  88. exclusoes_mes: undefined,
  89. });
  90. const tabs = computed(() => [
  91. {
  92. name: "novos-associados",
  93. label: t("relatorio.novos_associados"),
  94. icon: "mdi-account-multiple-outline",
  95. counterKey: "novos_associados",
  96. },
  97. {
  98. name: "contatos-associados",
  99. label: t("relatorio.contatos"),
  100. icon: "mdi-phone-outline",
  101. counterKey: "contatos",
  102. },
  103. {
  104. name: "exclusoes-mes",
  105. label: t("relatorio.exclusoes_mes"),
  106. icon: "mdi-account-remove-outline",
  107. counterKey: "exclusoes_mes",
  108. },
  109. ]);
  110. const columnsNovosAssociados = computed(() => [
  111. {
  112. name: "name",
  113. label: t("common.terms.name"),
  114. field: "name",
  115. align: "left",
  116. sortable: true,
  117. },
  118. {
  119. name: "cpf",
  120. label: t("associado.cpf"),
  121. field: "cpf",
  122. align: "left",
  123. sortable: false,
  124. },
  125. {
  126. name: "created_at",
  127. label: t("relatorio.col.cadastro"),
  128. field: "created_at",
  129. align: "left",
  130. sortable: false,
  131. },
  132. ]);
  133. const columnsContatos = computed(() => [
  134. {
  135. name: "name",
  136. label: t("common.terms.name"),
  137. field: "name",
  138. align: "left",
  139. sortable: true,
  140. },
  141. {
  142. name: "phone",
  143. label: t("associado.phone"),
  144. field: "phone",
  145. align: "left",
  146. sortable: false,
  147. },
  148. {
  149. name: "email",
  150. label: t("associado.email"),
  151. field: "email",
  152. align: "left",
  153. sortable: false,
  154. },
  155. ]);
  156. const columnsExclusoes = computed(() => [
  157. {
  158. name: "name",
  159. label: t("common.terms.name"),
  160. field: "name",
  161. align: "left",
  162. sortable: true,
  163. },
  164. {
  165. name: "cpf",
  166. label: t("associado.cpf"),
  167. field: "cpf",
  168. align: "left",
  169. sortable: false,
  170. },
  171. {
  172. name: "excluded_at",
  173. label: t("relatorio.col.exclusao"),
  174. field: "excluded_at",
  175. align: "left",
  176. sortable: false,
  177. },
  178. ]);
  179. const selectTab = (name) => {
  180. activeTab.value = name;
  181. tableKey.value++;
  182. };
  183. const onExport = async () => {
  184. exportLoading.value = true;
  185. try {
  186. const search = tableRef.value?.getFilter() || undefined;
  187. await exportRelatorio(activeTab.value, search);
  188. } catch {
  189. $q.notify({ type: "negative", message: t("http.errors.failed") });
  190. } finally {
  191. exportLoading.value = false;
  192. }
  193. };
  194. onMounted(async () => {
  195. try {
  196. const data = await getRelatorioCounters();
  197. counters.value = data;
  198. } catch {
  199. // counters permanecem como undefined, exibindo '—'
  200. }
  201. });
  202. </script>
  203. <style lang="scss" scoped>
  204. @use "src/css/quasar.variables.scss" as vars;
  205. .relatorio-tabs-row {
  206. display: flex;
  207. gap: 12px;
  208. }
  209. .relatorio-tab-card {
  210. flex: 1;
  211. display: flex;
  212. align-items: center;
  213. gap: 12px;
  214. padding: 14px 20px;
  215. border-radius: 8px;
  216. border: 1.5px solid vars.$color-border;
  217. background: vars.$surface;
  218. cursor: pointer;
  219. transition: border-color 0.15s, background 0.15s;
  220. user-select: none;
  221. &:hover:not(.active) {
  222. border-color: vars.$violet-light-active;
  223. background: vars.$violet-light;
  224. }
  225. &.active {
  226. border-color: vars.$violet-normal;
  227. background: vars.$violet-light;
  228. }
  229. }
  230. .tab-body {
  231. display: flex;
  232. align-items: baseline;
  233. gap: 8px;
  234. }
  235. .tab-count {
  236. font-size: 1.375rem;
  237. font-weight: 700;
  238. color: vars.$violet-normal;
  239. line-height: 1;
  240. }
  241. .tab-label {
  242. font-size: 0.875rem;
  243. color: vars.$color-text-2;
  244. font-weight: 400;
  245. }
  246. .btn-export {
  247. background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
  248. color: white !important;
  249. border-radius: 8px !important;
  250. }
  251. .btn-export :deep(.q-icon) {
  252. color: white !important;
  253. }
  254. </style>