DefaultTable.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <template>
  2. <q-table
  3. v-model:fullscreen="fullscreen"
  4. flat
  5. row-key="id"
  6. :pagination="{ rowsPerPage }"
  7. :pagination-label="getPaginationLabel"
  8. :rows-per-page-label="$t('common.ui.table.rows_per_page')"
  9. :grid="$q.screen.lt.sm"
  10. :visible-columns
  11. :filter
  12. :columns
  13. :loading
  14. :rows
  15. class="softpar-table q-pa-sm q-mt-md"
  16. @row-click="onRowClick"
  17. >
  18. <template #top>
  19. <div
  20. class="flex full-width align-center q-mb-md q-pl-sm"
  21. style="gap: 1rem"
  22. >
  23. <div v-if="title" class="column text-h6">
  24. <span>{{ title }}</span>
  25. <span class="text-body2">{{
  26. `${rows.length} ${description} ${female ? "cadastradas" : "cadastrados"}`
  27. }}</span>
  28. </div>
  29. <slot name="top" :rows="rows" />
  30. <q-space />
  31. <q-btn
  32. v-if="addItem"
  33. color="primary"
  34. style="width: 40px; height: 40px"
  35. icon="mdi-plus"
  36. :outline="outlineAdd"
  37. @click="onAddItem"
  38. >
  39. </q-btn>
  40. </div>
  41. <div class="flex full-width align-center q-mb-md" style="gap: 1rem">
  42. <DefaultInput
  43. v-if="showSearchField"
  44. v-model="filter"
  45. debounce="250"
  46. label="Busque por Unidade, status ou Responsavel"
  47. label-color="dark"
  48. :placeholder="$t('common.actions.search')"
  49. clearable
  50. class="q-mt-sm full-width search-input"
  51. color="dark"
  52. bg-color="transparent"
  53. dense
  54. input-class="q-pl-xs"
  55. >
  56. <template #prepend>
  57. <q-icon name="mdi-magnify" color="grey-6" />
  58. </template>
  59. </DefaultInput>
  60. <DefaultSelect
  61. v-if="showColumnsSelect"
  62. v-model="visibleColumns"
  63. multiple
  64. options-outlined
  65. :display-value="$q.lang.table.columns"
  66. emit-value
  67. map-options
  68. :options="mapColumns"
  69. style="width: 150px"
  70. options-selected-class="text-bold"
  71. />
  72. </div>
  73. </template>
  74. <template #body-cell-actions="{ row }">
  75. <q-td auto-width>
  76. <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
  77. <slot name="body-cell-actions" :row="row" />
  78. <q-btn
  79. v-if="openItemRoute"
  80. outline
  81. icon="mdi-pencil-outline"
  82. style="width: 36px"
  83. @click.prevent.stop="
  84. router.push({ name: openItemRoute, params: { id: row.id } })
  85. "
  86. />
  87. <q-btn
  88. v-if="deleteFunction"
  89. outline
  90. icon="mdi-trash-can-outline"
  91. style="width: 36px"
  92. color="negative"
  93. @click.prevent.stop="onDelete(row.id)"
  94. />
  95. </q-item-section>
  96. </q-td>
  97. </template>
  98. <template #item="{ cols, row, index }">
  99. <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3">
  100. <q-card
  101. bordered
  102. flat
  103. class="q-pa-sm"
  104. @click="onRowClick($event, row, index)"
  105. >
  106. <q-list dense>
  107. <q-item
  108. v-for="col in cols.filter((col) => col.name !== 'desc')"
  109. :key="col.name"
  110. >
  111. <template v-if="col.name !== 'actions'">
  112. <q-item-section>
  113. <q-item-label caption>{{ col.label }}</q-item-label>
  114. <q-item-label>{{ col.value }}</q-item-label>
  115. </q-item-section>
  116. </template>
  117. <template v-else>
  118. <slot name="body-cell-actions" :row="row" />
  119. <q-item-section
  120. v-if="openItemRoute || deleteFunction"
  121. style="flex-direction: row; gap: 4px"
  122. >
  123. <q-btn
  124. v-if="openItemRoute"
  125. outline
  126. icon="mdi-pencil-outline"
  127. style="width: 36px"
  128. @click.prevent.stop="
  129. router.push({
  130. name: openItemRoute,
  131. params: { id: row.id },
  132. })
  133. "
  134. />
  135. <q-btn
  136. v-if="deleteFunction"
  137. outline
  138. icon="mdi-trash-can-outline"
  139. style="width: 36px"
  140. color="negative"
  141. @click.prevent.stop="onDelete(row.id)"
  142. />
  143. </q-item-section>
  144. </template>
  145. </q-item>
  146. </q-list>
  147. </q-card>
  148. </div>
  149. </template>
  150. <template #loading>
  151. <q-inner-loading showing color="primary" />
  152. </template>
  153. <template v-if="!hideNoDataLabel" #no-data>
  154. <div v-if="!loading" class="q-my-md row justify-center full-width">
  155. <div class="q-pa-md body2">
  156. {{ $t("http.errors.no_records_found") }}
  157. </div>
  158. </div>
  159. </template>
  160. <template v-for="slotName in usableSlots($slots)" #[slotName]="data">
  161. <slot :name="slotName" v-bind="data" />
  162. </template>
  163. </q-table>
  164. </template>
  165. <script setup>
  166. import { ref, onMounted, toRaw, watch } from "vue";
  167. import { useI18n } from "vue-i18n";
  168. import { useRouter } from "vue-router";
  169. import { useQuasar } from "quasar";
  170. import DefaultInput from "./DefaultInput.vue";
  171. import DefaultSelect from "./DefaultSelect.vue";
  172. const emit = defineEmits(["onRowClick", "onAddItem", "noRows"]);
  173. const {
  174. columns,
  175. apiCall,
  176. outlineAdd,
  177. openItem,
  178. openItemRoute,
  179. addItem,
  180. addItemRoute,
  181. rowsPerPage,
  182. showSearchField,
  183. noApiCall,
  184. hideNoDataLabel,
  185. deleteFunction,
  186. } = defineProps({
  187. title: {
  188. type: String,
  189. default: null,
  190. },
  191. columns: {
  192. type: Array,
  193. required: true,
  194. },
  195. apiCall: {
  196. type: Function,
  197. default: null,
  198. },
  199. description: {
  200. type: String,
  201. default: "linhas",
  202. },
  203. female: {
  204. type: Boolean,
  205. default: true,
  206. },
  207. outlineAdd: {
  208. type: Boolean,
  209. default: false,
  210. },
  211. openItem: {
  212. type: Boolean,
  213. default: false,
  214. },
  215. openItemRoute: {
  216. type: String,
  217. default: "",
  218. },
  219. addItem: {
  220. type: Boolean,
  221. default: false,
  222. },
  223. addItemRoute: {
  224. type: String,
  225. default: "",
  226. },
  227. rowsPerPage: {
  228. type: Number,
  229. default: 10,
  230. },
  231. showSearchField: {
  232. type: Boolean,
  233. default: true,
  234. },
  235. showColumnsSelect: {
  236. type: Boolean,
  237. default: false,
  238. },
  239. noApiCall: {
  240. type: Boolean,
  241. default: false,
  242. },
  243. hideNoDataLabel: {
  244. type: Boolean,
  245. default: false,
  246. },
  247. deleteFunction: {
  248. type: Function,
  249. default: null,
  250. },
  251. });
  252. const router = useRouter();
  253. const { t } = useI18n();
  254. const $q = useQuasar();
  255. const rows = defineModel("rows", { type: Array, default: [] });
  256. const filter = ref("");
  257. const loading = ref(true);
  258. const fullscreen = ref(false);
  259. const mapColumns = columns.reduce((accm, column) => {
  260. if (!column.required) {
  261. accm.push({
  262. label: column.label,
  263. value: column.name,
  264. });
  265. }
  266. return accm;
  267. }, []);
  268. const visibleColumns = ref(mapColumns.map((column) => column.value));
  269. const getPaginationLabel = (from, to, last) => {
  270. return `${from}-${to} ${t("common.ui.table.of")} ${last}`;
  271. };
  272. const onRowClick = (evt, row, index) => {
  273. const item = toRaw(row);
  274. if (openItem) {
  275. if (openItemRoute) {
  276. router.push({ name: openItemRoute, params: { id: item.id } });
  277. } else {
  278. emit("onRowClick", { evt, row, index });
  279. }
  280. }
  281. };
  282. const onAddItem = () => {
  283. if (addItem) {
  284. if (addItemRoute) {
  285. router.push({ name: addItemRoute });
  286. } else {
  287. emit("onAddItem");
  288. }
  289. }
  290. };
  291. const onDelete = async (id) => {
  292. if (deleteFunction) {
  293. $q.dialog({
  294. title: t("common.ui.messages.confirm_action"),
  295. message: t("common.ui.messages.are_you_sure_delete"),
  296. ok: {
  297. color: "negative",
  298. label: t("common.actions.delete"),
  299. },
  300. cancel: {
  301. color: "primary",
  302. outline: true,
  303. label: t("common.actions.cancel"),
  304. },
  305. }).onOk(async () => {
  306. loading.value = true;
  307. try {
  308. await deleteFunction(id);
  309. await onRequest();
  310. } catch (error) {
  311. console.error(error);
  312. } finally {
  313. loading.value = false;
  314. }
  315. });
  316. }
  317. };
  318. const onRequest = async () => {
  319. if (noApiCall) {
  320. loading.value = false;
  321. return;
  322. }
  323. loading.value = true;
  324. const response = await apiCall();
  325. rows.value = response;
  326. if (rows.value.length == 0) {
  327. emit("noRows");
  328. }
  329. loading.value = false;
  330. };
  331. const usableSlots = (slots) => {
  332. const availableSlots = Object.keys(slots);
  333. const usableSlots = availableSlots.filter(
  334. (slot) =>
  335. !["body-cell-actions", "top", "loading", "no-data"].includes(slot),
  336. );
  337. return usableSlots;
  338. };
  339. watch(
  340. () => apiCall,
  341. async () => {
  342. await onRequest();
  343. },
  344. );
  345. onMounted(async () => {
  346. await onRequest({
  347. filter: undefined,
  348. });
  349. });
  350. defineExpose({
  351. refresh: onRequest,
  352. });
  353. </script>
  354. <style lang="scss">
  355. @import "src/css/table.scss";
  356. </style>