TreasuryPage.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <div>
  3. <DefaultHeaderPage title="Tesouraria" :show-filter-icon="false" />
  4. <div class="row items-center justify-between q-px-md q-mb-sm">
  5. <div class="text-subtitle1 text-weight-medium">Bancos / Caixa</div>
  6. <q-btn
  7. v-if="canAdd"
  8. color="primary"
  9. label="Nova conta"
  10. icon="mdi-plus"
  11. unelevated
  12. no-caps
  13. @click="openAccountCreate"
  14. />
  15. </div>
  16. <div class="row q-px-md q-col-gutter-md">
  17. <div
  18. v-for="acc in accounts"
  19. :key="acc.id"
  20. class="col-12 col-sm-6 col-md-4"
  21. >
  22. <q-card
  23. flat
  24. bordered
  25. class="q-pa-md cursor-pointer"
  26. :class="{ 'selected-card': acc.id === selectedId }"
  27. @click="selectAccount(acc)"
  28. >
  29. <div class="row items-center no-wrap">
  30. <q-icon name="mdi-bank-outline" size="sm" color="primary" class="q-mr-sm" />
  31. <div class="text-subtitle2 ellipsis">{{ acc.name }}</div>
  32. <q-space />
  33. <q-btn
  34. v-if="canEdit"
  35. flat
  36. round
  37. dense
  38. icon="mdi-pencil-outline"
  39. size="sm"
  40. @click.stop="openAccountEdit(acc)"
  41. />
  42. </div>
  43. <div class="text-h6 q-mt-sm">{{ formatCurrency(acc.balance) }}</div>
  44. <div class="text-caption text-grey-7">
  45. {{ acc.bank_name || "Conta" }}
  46. <span v-if="acc.bank_agency"> · Ag. {{ acc.bank_agency }}</span>
  47. <span v-if="acc.bank_account"> · CC {{ acc.bank_account }}</span>
  48. </div>
  49. </q-card>
  50. </div>
  51. <div v-if="!accounts.length" class="col-12 text-grey-7 q-pa-md">
  52. Nenhuma conta cadastrada. Clique em "Nova conta" para começar.
  53. </div>
  54. </div>
  55. <!-- Extrato -->
  56. <div v-if="selectedId" class="q-px-md q-mt-lg">
  57. <div class="row items-center justify-between q-mb-sm">
  58. <div class="text-subtitle1 text-weight-medium">
  59. Extrato — {{ selectedAccount?.name }}
  60. </div>
  61. <q-btn
  62. v-if="canAdd"
  63. color="primary-2"
  64. label="Nova movimentação"
  65. icon="mdi-swap-vertical"
  66. unelevated
  67. no-caps
  68. @click="openLaunchCreate"
  69. />
  70. </div>
  71. <DefaultTable
  72. v-model:rows="launches"
  73. no-api-call
  74. :add-item="false"
  75. title="Movimentações"
  76. descricao="movimentações"
  77. :feminino="true"
  78. :columns="launchColumns"
  79. >
  80. <template #body-cell-transaction_type="{ row }">
  81. <q-td class="text-left">
  82. <q-chip
  83. :color="row.transaction_type === 'entrada' ? 'positive' : 'negative'"
  84. text-color="white"
  85. dense
  86. square
  87. :label="row.transaction_type === 'entrada' ? 'Entrada' : 'Saída'"
  88. />
  89. </q-td>
  90. </template>
  91. <template #body-cell-amount="{ row }">
  92. <q-td
  93. class="text-left text-weight-medium"
  94. :class="row.transaction_type === 'entrada' ? 'text-positive' : 'text-negative'"
  95. >
  96. {{ row.transaction_type === "entrada" ? "+" : "-" }}
  97. {{ formatCurrency(row.amount) }}
  98. </q-td>
  99. </template>
  100. </DefaultTable>
  101. </div>
  102. </div>
  103. </template>
  104. <script setup>
  105. import { computed, defineAsyncComponent, onMounted, ref } from "vue";
  106. import { useQuasar } from "quasar";
  107. import {
  108. getTreasuryAccountsMe,
  109. getTreasuryLaunchesMe,
  110. } from "src/api/treasury";
  111. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  112. import DefaultTable from "src/components/defaults/DefaultTable.vue";
  113. import { permissionStore } from "src/stores/permission";
  114. const AddEditTreasuryAccountDialog = defineAsyncComponent(
  115. () => import("src/pages/financial/components/AddEditTreasuryAccountDialog.vue"),
  116. );
  117. const AddTreasuryLaunchDialog = defineAsyncComponent(
  118. () => import("src/pages/financial/components/AddTreasuryLaunchDialog.vue"),
  119. );
  120. const accounts = ref([]);
  121. const launches = ref([]);
  122. const selectedId = ref(null);
  123. const $q = useQuasar();
  124. const permissions = permissionStore();
  125. const canAdd = computed(() =>
  126. permissions.getAccess("franchisee_financial", "add"),
  127. );
  128. const canEdit = computed(() =>
  129. permissions.getAccess("franchisee_financial", "edit"),
  130. );
  131. const launchColumns = [
  132. { name: "launch_date", label: "Data", field: "launch_date", align: "left" },
  133. { name: "description", label: "Descrição", field: "description", align: "left" },
  134. { name: "transaction_type", label: "Tipo", field: "transaction_type", align: "left" },
  135. { name: "amount", label: "Valor", field: "amount", align: "left" },
  136. ];
  137. const selectedAccount = computed(() =>
  138. accounts.value.find((a) => a.id === selectedId.value),
  139. );
  140. function formatCurrency(value) {
  141. return new Intl.NumberFormat("pt-BR", {
  142. style: "currency",
  143. currency: "BRL",
  144. }).format(Number(value ?? 0));
  145. }
  146. async function loadAccounts() {
  147. try {
  148. accounts.value = await getTreasuryAccountsMe();
  149. if (!selectedId.value && accounts.value.length) {
  150. selectAccount(accounts.value[0]);
  151. }
  152. } catch (e) {
  153. console.error("Erro ao carregar contas:", e);
  154. }
  155. }
  156. async function loadLaunches() {
  157. if (!selectedId.value) return;
  158. try {
  159. launches.value = await getTreasuryLaunchesMe(selectedId.value);
  160. } catch (e) {
  161. console.error("Erro ao carregar extrato:", e);
  162. }
  163. }
  164. function selectAccount(acc) {
  165. selectedId.value = acc.id;
  166. loadLaunches();
  167. }
  168. function openAccountCreate() {
  169. $q.dialog({
  170. component: AddEditTreasuryAccountDialog,
  171. }).onOk(loadAccounts);
  172. }
  173. function openAccountEdit(acc) {
  174. $q.dialog({
  175. component: AddEditTreasuryAccountDialog,
  176. componentProps: {
  177. account: acc,
  178. },
  179. }).onOk(loadAccounts);
  180. }
  181. function openLaunchCreate() {
  182. $q.dialog({
  183. component: AddTreasuryLaunchDialog,
  184. componentProps: {
  185. accountId: selectedId.value,
  186. },
  187. }).onOk(async () => {
  188. await loadAccounts();
  189. await loadLaunches();
  190. });
  191. }
  192. onMounted(loadAccounts);
  193. </script>
  194. <style scoped>
  195. .selected-card {
  196. border-color: var(--q-primary);
  197. border-width: 2px;
  198. }
  199. </style>