DefaultTable.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. <template>
  2. <q-table
  3. v-model:fullscreen="fullscreen"
  4. flat
  5. :class="
  6. props.table
  7. ? 'softpar-table bg-background table-bottom'
  8. : 'softpar-table bg-background '
  9. "
  10. :pagination="{ rowsPerPage }"
  11. :pagination-label="getPaginationLabel"
  12. row-key="id"
  13. :rows="rows"
  14. :rows-per-page-label="props.rowsPerPageLabel"
  15. :columns="props.columns"
  16. :visible-columns="visibleColumns"
  17. :filter="filter"
  18. @row-click="onRowClick"
  19. >
  20. <template #top>
  21. <q-input
  22. v-if="mostrarCampoPesquisa"
  23. v-model="filter"
  24. outlined
  25. dense
  26. debounce="500"
  27. placeholder="Buscar"
  28. style="min-width: 400px"
  29. clearable
  30. autofocus
  31. >
  32. <template #append>
  33. <q-icon name="mdi-magnify" />
  34. </template>
  35. </q-input>
  36. <q-checkbox
  37. v-if="props.mostrarToggleInativos"
  38. v-model="showInativos"
  39. class="q-ml-sm"
  40. :label="props.labelInativo"
  41. dense
  42. color="secondary"
  43. />
  44. <q-select
  45. v-if="mostrarSelecaoDeColunas"
  46. v-model="visibleColumns"
  47. class="q-ml-md"
  48. multiple
  49. dense
  50. outlined
  51. options-outlined
  52. :display-value="$q.lang.table.columns"
  53. emit-value
  54. map-options
  55. :options="mapColuns"
  56. option-value="name"
  57. style="min-width: 150px"
  58. options-selected-class="text-bold"
  59. />
  60. <q-btn
  61. v-if="mostrarBotaoFullscreen"
  62. flat
  63. class="q-ml-md"
  64. @click="fullscreen = !fullscreen"
  65. >
  66. <q-icon name="mdi-fullscreen" />
  67. </q-btn>
  68. <q-space />
  69. <q-btn-dropdown
  70. v-if="props.dropDown"
  71. class="q-mr-md"
  72. color="primary"
  73. label="opcoes"
  74. />
  75. <q-btn
  76. v-if="props.addItem"
  77. class="button-secondary"
  78. color="primary"
  79. padding="12px 16px"
  80. :outline="props.outlineAdd"
  81. :label="props.labelAdd"
  82. @click="onAddItem"
  83. >
  84. </q-btn>
  85. </template>
  86. <template #body-cell-status="{ value, row }">
  87. <q-td style="width: 8%">
  88. <q-item-section>
  89. <span class="text-center">
  90. <div v-if="row.status && value" class="ativo body2 text-positive">
  91. {{ $t("active") }}
  92. </div>
  93. <div v-if="!row.status" class="inativo body2 text-accent">
  94. {{ $t("inactive") }}
  95. </div>
  96. </span>
  97. </q-item-section>
  98. </q-td>
  99. </template>
  100. <template #body-cell-ativo="{ value, row }">
  101. <q-td style="width: 8%">
  102. <q-item-section>
  103. <span class="text-center">
  104. <div v-if="row.ativo && value" class="ativo body2 text-positive">
  105. {{ $t("active") }}
  106. </div>
  107. <div v-if="row.ativo && !value" class="ativo body2 text-positive">
  108. {{ $t("active") }}
  109. </div>
  110. <div v-if="!row.ativo" class="inativo body2 text-accent">
  111. {{ $t("active") }}
  112. </div>
  113. </span>
  114. </q-item-section>
  115. </q-td>
  116. </template>
  117. <template #body-cell-principal="{ value, row }">
  118. <q-td style="width: 1%">
  119. <q-item-section>
  120. <span class="text-center">
  121. <q-icon
  122. v-if="row.principal && value"
  123. name="mdi-star"
  124. size="1.5rem"
  125. style="color: #385873"
  126. onmouseover="this.style.color='#688FAF';"
  127. onmouseout="this.style.color='#385873';"
  128. @click.stop="togglePrincipal(row)"
  129. />
  130. <q-icon
  131. v-if="!row.principal"
  132. name="mdi-star-outline"
  133. size="1.5rem"
  134. style="color: #385873"
  135. onmouseover="this.style.color='#688FAF';"
  136. onmouseout="this.style.color='#385873';"
  137. @click.stop="togglePrincipal(row)"
  138. />
  139. </span>
  140. </q-item-section>
  141. </q-td>
  142. </template>
  143. <template v-if="!props.hideNoDataLabel" #no-data>
  144. <div class="q-my-md row justify-center full-width">
  145. <q-spinner v-if="loading" color="primary" size="30px" />
  146. <div v-else class="q-pa-md body2">{{ $t("no_records_found") }}</div>
  147. </div>
  148. </template>
  149. <template v-for="(index, name) in $slots" #[name]="data">
  150. <slot :name="name" v-bind="data"></slot>
  151. </template>
  152. </q-table>
  153. </template>
  154. <script setup>
  155. import { ref, onMounted, toRaw, watch } from "vue";
  156. import { useRouter } from "vue-router";
  157. import { api } from "boot/axios";
  158. const emit = defineEmits([
  159. "onRowClick",
  160. "onAddItem",
  161. "noRows",
  162. "togglePrincipal",
  163. ]);
  164. const props = defineProps({
  165. // colunas de configuração da tabela
  166. columns: {
  167. type: Array,
  168. required: true,
  169. },
  170. // rota da api, ex: /clientes
  171. apiRoute: {
  172. type: String,
  173. required: true,
  174. },
  175. labelAdd: {
  176. type: String,
  177. default: "Adicionar",
  178. },
  179. // botao de adicionar com aparencia de outline
  180. outlineAdd: {
  181. type: Boolean,
  182. default: false,
  183. },
  184. // ir para sub pagina on row click
  185. openItem: {
  186. type: Boolean,
  187. default: false,
  188. },
  189. // rota da sub page
  190. openItemRoute: {
  191. type: String,
  192. default: "",
  193. },
  194. // botao de adicionar
  195. addItem: {
  196. type: Boolean,
  197. default: true,
  198. },
  199. // botao de opcoes
  200. dropDown: {
  201. type: Boolean,
  202. default: false,
  203. },
  204. // botao de adicionar route
  205. addItemRoute: {
  206. type: String,
  207. default: "",
  208. },
  209. // quantidade de items por pagina
  210. rowsPerPage: {
  211. type: Number,
  212. default: 10,
  213. },
  214. comecarDesativado: {
  215. type: Boolean,
  216. default: false,
  217. },
  218. mostrarSelecaoDeColunas: {
  219. type: Boolean,
  220. default: false,
  221. },
  222. mostrarBotaoFullscreen: {
  223. type: Boolean,
  224. default: false,
  225. },
  226. mostrarToggleInativos: {
  227. type: Boolean,
  228. default: false,
  229. },
  230. mostrarCampoPesquisa: {
  231. type: Boolean,
  232. default: true,
  233. },
  234. noApiRoute: {
  235. type: Boolean,
  236. default: false,
  237. },
  238. table: {
  239. type: Boolean,
  240. default: false,
  241. },
  242. rowsPerPageLabel: {
  243. type: String,
  244. default: "Linhas por página",
  245. },
  246. hideNoDataLabel: {
  247. type: Boolean,
  248. default: false,
  249. },
  250. labelInativo: {
  251. type: String,
  252. default: "Exibir inativos",
  253. },
  254. });
  255. const router = useRouter();
  256. const rows = ref([]);
  257. const filter = ref("");
  258. const loading = ref(true);
  259. const fullscreen = ref(false);
  260. const showInativos = ref(false);
  261. const inativos = ref([]);
  262. const getPaginationLabel = (from, to, last) => {
  263. return `${from}-${to} de ${last}`;
  264. };
  265. watch(showInativos, () => {
  266. if (showInativos.value) {
  267. rows.value = rows.value.concat(inativos.value);
  268. } else {
  269. inativos.value = rows.value.filter(
  270. (row) => row.status === false || row.ativo === false,
  271. );
  272. rows.value = rows.value.filter((row) => row.ativo);
  273. }
  274. });
  275. watch(
  276. () => props.apiRoute,
  277. () => {
  278. onRequest();
  279. },
  280. );
  281. // remove as colunas obrigatórias do filtro de colunas
  282. const mapColuns = props.columns.reduce((accm, column) => {
  283. if (!column.required) {
  284. column.label = column.label.toUpperCase();
  285. accm.push(column);
  286. }
  287. return accm;
  288. }, []);
  289. // as colunas que serão carregadas
  290. const visibleColumns = ref(mapColuns.map((column) => column.name));
  291. const onRowClick = (evt, row, index) => {
  292. const item = toRaw(row);
  293. if (props.openItem) {
  294. if (props.openItemRoute) {
  295. router.push({ name: props.openItemRoute, params: { id: item.id } });
  296. } else {
  297. emit("onRowClick", { evt, row, index });
  298. }
  299. }
  300. };
  301. const onAddItem = () => {
  302. if (props.addItem) {
  303. if (props.addItemRoute) {
  304. router.push({ name: props.addItemRoute });
  305. } else {
  306. emit("onAddItem");
  307. }
  308. }
  309. };
  310. // busca os dados do banco com filtros e pagination
  311. const onRequest = async () => {
  312. // const filter = params.filter;
  313. if (props.noApiRoute) {
  314. loading.value = false;
  315. return;
  316. }
  317. // inicia o loading
  318. loading.value = true;
  319. // pega os dados do servidor
  320. const response = await api.get(`${props.apiRoute}`);
  321. // limpa os dados atuais e adiciona os novos
  322. rows.value.splice(0, rows.value.length, ...response.data.result);
  323. if (props.mostrarToggleInativos && !showInativos.value) {
  324. inativos.value = rows.value.filter(
  325. (row) => row.status === false || row.ativo === false,
  326. );
  327. rows.value = rows.value.filter((row) => row.ativo || row.status === true);
  328. }
  329. if (rows.value.length == 0) {
  330. emit("noRows");
  331. }
  332. // finaliza o loading
  333. loading.value = false;
  334. };
  335. // funcao exclusiva para contatos table, para alterar o contato principal
  336. const togglePrincipal = async (row) => {
  337. emit("togglePrincipal", row);
  338. };
  339. onMounted(() => {
  340. // faz a primeira requisição
  341. onRequest({
  342. filter: undefined,
  343. });
  344. if (props.comecarDesativado) {
  345. visibleColumns.value = mapColuns
  346. .map((column) => column.required)
  347. .map((column) => column.name);
  348. }
  349. });
  350. </script>
  351. <style lang="scss">
  352. @import "src/css/table.scss";
  353. .ativo {
  354. justify-content: center;
  355. align-items: center;
  356. padding: 5px 12px;
  357. gap: 10px;
  358. background: #cfdab7;
  359. border-radius: 24px;
  360. }
  361. .chip {
  362. justify-content: center;
  363. align-items: center;
  364. padding: 5px 12px;
  365. gap: 10px;
  366. border-radius: 24px;
  367. }
  368. .inativo {
  369. justify-content: center;
  370. align-items: flex-start;
  371. padding: 5px 12px;
  372. gap: 10px;
  373. background: #f7cfbb;
  374. border-radius: 24px;
  375. }
  376. </style>