DashboardPayoutTable.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <template>
  2. <div class="table-wrap">
  3. <table class="payout-table">
  4. <thead>
  5. <tr>
  6. <th
  7. v-for="(column, index) in columns"
  8. :key="column.key"
  9. >
  10. <div class="header-wrapper">
  11. <div class="header-content">
  12. <q-icon
  13. class="cursor-pointer sort-icon q-my-md"
  14. :name="
  15. column.direction === 'desc'
  16. ? 'south'
  17. : 'north'
  18. "
  19. size="15px"
  20. :color="
  21. column.direction
  22. ? 'primary'
  23. : '#b0b0b0'
  24. "
  25. @click="toggleSort(column)"
  26. />
  27. <q-icon
  28. class="cursor-pointer move-icon"
  29. color="#888"
  30. name="chevron_left"
  31. size="18px"
  32. @click="moveLeft(index)"
  33. />
  34. <span class="header-label">
  35. {{ column.label }}
  36. </span>
  37. <q-icon
  38. class="cursor-pointer move-icon"
  39. color="#888"
  40. name="chevron_right"
  41. size="18px"
  42. @click="moveRight(index)"
  43. />
  44. </div>
  45. </div>
  46. </th>
  47. </tr>
  48. </thead>
  49. <tbody>
  50. <tr
  51. v-for="item in sortedRows"
  52. :key="item.property_id"
  53. >
  54. <td
  55. v-for="column in columns"
  56. :key="column.key"
  57. >
  58. <template
  59. v-if="
  60. column.key ===
  61. 'final_payout_amount'
  62. "
  63. >
  64. {{
  65. formatCurrency(
  66. item[column.key]
  67. )
  68. }}
  69. </template>
  70. <template
  71. v-else-if="
  72. column.key ===
  73. 'reservations_count'
  74. "
  75. >
  76. {{
  77. formatInteger(
  78. resolveReservationsCount(item)
  79. )
  80. }}
  81. </template>
  82. <template v-else>
  83. {{ item[column.key] }}
  84. </template>
  85. </td>
  86. </tr>
  87. <tr class="payout-total-row">
  88. <td>Total</td>
  89. <td>
  90. {{
  91. formatInteger(
  92. totalReservations
  93. )
  94. }}
  95. </td>
  96. <td>
  97. {{
  98. formatCurrency(totalValue)
  99. }}
  100. </td>
  101. </tr>
  102. </tbody>
  103. </table>
  104. </div>
  105. </template>
  106. <script setup>
  107. import {
  108. computed,
  109. ref,
  110. } from "vue";
  111. const props = defineProps({
  112. rows: {
  113. type: Array,
  114. default: () => [],
  115. },
  116. totalReservations: {
  117. type: Number,
  118. default: 0,
  119. },
  120. totalValue: {
  121. type: Number,
  122. default: 0,
  123. },
  124. });
  125. const columns = ref([
  126. {
  127. key: "property_label",
  128. label: "Unidade",
  129. direction: null,
  130. },
  131. {
  132. key: "reservations_count",
  133. label: "Reservas",
  134. direction: null,
  135. },
  136. {
  137. key: "final_payout_amount",
  138. label: "Valor",
  139. direction: null,
  140. },
  141. ]);
  142. const toggleSort = (
  143. column
  144. ) => {
  145. column.direction =
  146. column.direction === "asc"
  147. ? "desc"
  148. : "asc";
  149. };
  150. const moveLeft = (index) => {
  151. const lastIndex =
  152. columns.value.length - 1;
  153. const targetIndex =
  154. index === 0
  155. ? lastIndex
  156. : index - 1;
  157. [
  158. columns.value[targetIndex],
  159. columns.value[index],
  160. ] = [
  161. columns.value[index],
  162. columns.value[targetIndex],
  163. ];
  164. };
  165. const moveRight = (index) => {
  166. const lastIndex =
  167. columns.value.length - 1;
  168. const targetIndex =
  169. index === lastIndex
  170. ? 0
  171. : index + 1;
  172. [
  173. columns.value[targetIndex],
  174. columns.value[index],
  175. ] = [
  176. columns.value[index],
  177. columns.value[targetIndex],
  178. ];
  179. };
  180. const sortedRows = computed(() => {
  181. const activeSorts =
  182. columns.value.filter(
  183. (column) => column.direction
  184. );
  185. if (!activeSorts.length) {
  186. return [...props.rows];
  187. }
  188. return [...props.rows].sort(
  189. (a, b) => {
  190. for (const sort of activeSorts) {
  191. const direction =
  192. sort.direction === "asc"
  193. ? 1
  194. : -1;
  195. const valueA =
  196. sort.key === "reservations_count"
  197. ? resolveReservationsCount(a)
  198. : a[sort.key];
  199. const valueB =
  200. sort.key === "reservations_count"
  201. ? resolveReservationsCount(b)
  202. : b[sort.key];
  203. if (valueA > valueB) {
  204. return direction;
  205. }
  206. if (valueA < valueB) {
  207. return -direction;
  208. }
  209. }
  210. return 0;
  211. }
  212. );
  213. });
  214. const resolveReservationsCount = (item) =>
  215. Number(
  216. item.checkout_reservations_count ??
  217. item.distinct_reservations_count ??
  218. item.reservations_count ??
  219. 0
  220. );
  221. const formatCurrency = (
  222. value
  223. ) => {
  224. return new Intl.NumberFormat(
  225. "pt-BR",
  226. {
  227. style: "currency",
  228. currency: "BRL",
  229. minimumFractionDigits: 2,
  230. }
  231. ).format(Number(value ?? 0));
  232. };
  233. const formatInteger = (
  234. value
  235. ) => {
  236. return Number(
  237. value ?? 0
  238. ).toLocaleString("pt-BR");
  239. };
  240. </script>
  241. <style scoped lang="scss">
  242. .table-wrap {
  243. overflow-x: auto;
  244. }
  245. .payout-table {
  246. width: 100%;
  247. border-collapse: collapse;
  248. }
  249. .payout-table th,
  250. .payout-table td {
  251. padding: 10px 16px;
  252. border-bottom: 1px solid #aaaaaa;
  253. color: #273136;
  254. vertical-align: middle;
  255. text-align: center;
  256. }
  257. .payout-table th {
  258. font-size: 17px !important;
  259. font-weight: 700;
  260. color: #232323;
  261. }
  262. .payout-total-row td {
  263. font-size: 17px !important;
  264. font-weight: 700;
  265. border-bottom: 0;
  266. color: #4d4e4e;
  267. text-align: center;
  268. }
  269. .header-wrapper {
  270. display: flex;
  271. flex-direction: column;
  272. align-items: center;
  273. gap: 2px;
  274. }
  275. .header-content {
  276. display: flex;
  277. align-items: center;
  278. gap: 4px;
  279. }
  280. .header-label {
  281. line-height: 1;
  282. }
  283. .sort-icon {
  284. transition: 0.2s ease;
  285. }
  286. .move-icon {
  287. opacity: 0.7;
  288. transition: 0.2s ease;
  289. }
  290. .move-icon:hover,
  291. .sort-icon:hover {
  292. opacity: 1;
  293. transform: scale(1.08);
  294. }
  295. .cursor-pointer {
  296. cursor: pointer;
  297. }
  298. @media (max-width: 640px) {
  299. .payout-table {
  300. min-width: 0;
  301. }
  302. .payout-table th,
  303. .payout-table td {
  304. padding: 8px 12px;
  305. font-size: 13px;
  306. }
  307. .payout-table th,
  308. .payout-total-row td {
  309. font-size: 14px !important;
  310. }
  311. .header-content {
  312. gap: 2px;
  313. }
  314. }
  315. </style>