TreasuryPage.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. </div>
  62. <DefaultTable
  63. v-model:rows="launches"
  64. no-api-call
  65. :add-item="canAdd"
  66. title="Movimentações"
  67. description="movimentações"
  68. :female="true"
  69. :columns="launchColumns"
  70. @on-add-item="openLaunchCreate"
  71. >
  72. <template #body-cell-transaction_type="{ row }">
  73. <q-td class="text-left">
  74. <q-chip
  75. :color="row.transaction_type === 'entrada' ? 'positive' : 'negative'"
  76. text-color="white"
  77. dense
  78. square
  79. :label="row.transaction_type === 'entrada' ? 'Entrada' : 'Saída'"
  80. />
  81. </q-td>
  82. </template>
  83. <template #body-cell-amount="{ row }">
  84. <q-td
  85. class="text-left text-weight-medium"
  86. :class="row.transaction_type === 'entrada' ? 'text-positive' : 'text-negative'"
  87. >
  88. {{ row.transaction_type === "entrada" ? "+" : "-" }}
  89. {{ formatCurrency(row.amount) }}
  90. </q-td>
  91. </template>
  92. <template #body-cell-actions="{ row }">
  93. <q-td>
  94. <div class="row no-wrap q-gutter-xs">
  95. <q-btn v-if="canEdit" outline icon="mdi-pencil-outline" style="width: 36px" @click.stop="openLaunchEdit(row)" />
  96. <q-btn v-if="canDelete" outline color="negative" icon="mdi-trash-can-outline" style="width: 36px" @click.stop="deleteLaunch(row)" />
  97. </div>
  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. deleteTreasuryLaunchMe,
  111. } from "src/api/treasury";
  112. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  113. import DefaultTable from "src/components/defaults/DefaultTable.vue";
  114. import { permissionStore } from "src/stores/permission";
  115. const AddEditTreasuryAccountDialog = defineAsyncComponent(
  116. () => import("src/pages/financial/components/AddEditTreasuryAccountDialog.vue"),
  117. );
  118. const AddTreasuryLaunchDialog = defineAsyncComponent(
  119. () => import("src/pages/financial/components/AddTreasuryLaunchDialog.vue"),
  120. );
  121. const accounts = ref([]);
  122. const launches = ref([]);
  123. const selectedId = ref(null);
  124. const $q = useQuasar();
  125. const permissions = permissionStore();
  126. const canAdd = computed(() =>
  127. permissions.getAccess("franchisee_financial", "add"),
  128. );
  129. const canEdit = computed(() =>
  130. permissions.getAccess("franchisee_financial", "edit"),
  131. );
  132. const canDelete = computed(() =>
  133. permissions.getAccess("franchisee_financial", "delete"),
  134. );
  135. const launchColumns = [
  136. { name: "launch_date", label: "Data", field: "launch_date", align: "left" },
  137. { name: "description", label: "Descrição", field: "description", align: "left" },
  138. { name: "transaction_type", label: "Tipo", field: "transaction_type", align: "left" },
  139. { name: "amount", label: "Valor", field: "amount", align: "left" },
  140. { name: "actions", label: "Ações", field: null, align: "center" },
  141. ];
  142. const selectedAccount = computed(() =>
  143. accounts.value.find((a) => a.id === selectedId.value),
  144. );
  145. function formatCurrency(value) {
  146. return new Intl.NumberFormat("pt-BR", {
  147. style: "currency",
  148. currency: "BRL",
  149. }).format(Number(value ?? 0));
  150. }
  151. async function loadAccounts() {
  152. try {
  153. accounts.value = await getTreasuryAccountsMe();
  154. if (!selectedId.value && accounts.value.length) {
  155. selectAccount(accounts.value[0]);
  156. }
  157. } catch (e) {
  158. console.error("Erro ao carregar contas:", e);
  159. }
  160. }
  161. async function loadLaunches() {
  162. if (!selectedId.value) return;
  163. try {
  164. launches.value = await getTreasuryLaunchesMe(selectedId.value);
  165. } catch (e) {
  166. console.error("Erro ao carregar extrato:", e);
  167. }
  168. }
  169. function selectAccount(acc) {
  170. selectedId.value = acc.id;
  171. loadLaunches();
  172. }
  173. function openAccountCreate() {
  174. $q.dialog({
  175. component: AddEditTreasuryAccountDialog,
  176. }).onOk(loadAccounts);
  177. }
  178. function openAccountEdit(acc) {
  179. $q.dialog({
  180. component: AddEditTreasuryAccountDialog,
  181. componentProps: {
  182. account: acc,
  183. },
  184. }).onOk(loadAccounts);
  185. }
  186. function openLaunchCreate() {
  187. $q.dialog({
  188. component: AddTreasuryLaunchDialog,
  189. componentProps: {
  190. accountId: selectedId.value,
  191. },
  192. }).onOk(async () => {
  193. await loadAccounts();
  194. await loadLaunches();
  195. });
  196. }
  197. function openLaunchEdit(launch) {
  198. $q.dialog({
  199. component: AddTreasuryLaunchDialog,
  200. componentProps: { accountId: selectedId.value, launch },
  201. }).onOk(async () => {
  202. await Promise.all([loadAccounts(), loadLaunches()]);
  203. });
  204. }
  205. function deleteLaunch(launch) {
  206. $q.dialog({
  207. title: "Excluir movimentação",
  208. message: `Deseja excluir a movimentação "${launch.description}"?`,
  209. ok: { color: "negative", label: "Excluir" },
  210. cancel: { color: "secondary", outline: true, label: "Cancelar" },
  211. }).onOk(async () => {
  212. await deleteTreasuryLaunchMe(launch.id);
  213. await Promise.all([loadAccounts(), loadLaunches()]);
  214. });
  215. }
  216. onMounted(loadAccounts);
  217. </script>
  218. <style scoped>
  219. .selected-card {
  220. border-color: var(--q-primary);
  221. border-width: 2px;
  222. }
  223. </style>