|
|
@@ -0,0 +1,367 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <DefaultHeaderPage title="Tesouraria" :show-filter-icon="false" />
|
|
|
+
|
|
|
+ <div class="row items-center justify-between q-px-md q-mb-sm">
|
|
|
+ <div class="text-subtitle1 text-weight-medium">Bancos / Caixa</div>
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ label="Nova conta"
|
|
|
+ icon="mdi-plus"
|
|
|
+ unelevated
|
|
|
+ no-caps
|
|
|
+ @click="openAccountCreate"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="row q-px-md q-col-gutter-md">
|
|
|
+ <div
|
|
|
+ v-for="acc in accounts"
|
|
|
+ :key="acc.id"
|
|
|
+ class="col-12 col-sm-6 col-md-4"
|
|
|
+ >
|
|
|
+ <q-card
|
|
|
+ flat
|
|
|
+ bordered
|
|
|
+ class="q-pa-md cursor-pointer"
|
|
|
+ :class="{ 'selected-card': acc.id === selectedId }"
|
|
|
+ @click="selectAccount(acc)"
|
|
|
+ >
|
|
|
+ <div class="row items-center no-wrap">
|
|
|
+ <q-icon name="mdi-bank-outline" size="sm" color="primary" class="q-mr-sm" />
|
|
|
+ <div class="text-subtitle2 ellipsis">{{ acc.name }}</div>
|
|
|
+ <q-space />
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ round
|
|
|
+ dense
|
|
|
+ icon="mdi-pencil-outline"
|
|
|
+ size="sm"
|
|
|
+ @click.stop="openAccountEdit(acc)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="text-h6 q-mt-sm">{{ formatCurrency(acc.balance) }}</div>
|
|
|
+ <div class="text-caption text-grey-7">
|
|
|
+ {{ acc.bank_name || "Conta" }}
|
|
|
+ <span v-if="acc.bank_agency"> · Ag. {{ acc.bank_agency }}</span>
|
|
|
+ <span v-if="acc.bank_account"> · CC {{ acc.bank_account }}</span>
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="!accounts.length" class="col-12 text-grey-7 q-pa-md">
|
|
|
+ Nenhuma conta cadastrada. Clique em "Nova conta" para começar.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Extrato -->
|
|
|
+ <div v-if="selectedId" class="q-px-md q-mt-lg">
|
|
|
+ <div class="row items-center justify-between q-mb-sm">
|
|
|
+ <div class="text-subtitle1 text-weight-medium">
|
|
|
+ Extrato — {{ selectedAccount?.name }}
|
|
|
+ </div>
|
|
|
+ <q-btn
|
|
|
+ color="primary-2"
|
|
|
+ label="Nova movimentação"
|
|
|
+ icon="mdi-swap-vertical"
|
|
|
+ unelevated
|
|
|
+ no-caps
|
|
|
+ @click="openLaunchCreate"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <DefaultTable
|
|
|
+ v-model:rows="launches"
|
|
|
+ no-api-call
|
|
|
+ :add-item="false"
|
|
|
+ title="Movimentações"
|
|
|
+ descricao="movimentações"
|
|
|
+ :feminino="true"
|
|
|
+ :columns="launchColumns"
|
|
|
+ >
|
|
|
+ <template #body-cell-transaction_type="{ row }">
|
|
|
+ <q-td class="text-left">
|
|
|
+ <q-chip
|
|
|
+ :color="row.transaction_type === 'entrada' ? 'positive' : 'negative'"
|
|
|
+ text-color="white"
|
|
|
+ dense
|
|
|
+ square
|
|
|
+ :label="row.transaction_type === 'entrada' ? 'Entrada' : 'Saída'"
|
|
|
+ />
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+ <template #body-cell-amount="{ row }">
|
|
|
+ <q-td
|
|
|
+ class="text-left text-weight-medium"
|
|
|
+ :class="row.transaction_type === 'entrada' ? 'text-positive' : 'text-negative'"
|
|
|
+ >
|
|
|
+ {{ row.transaction_type === "entrada" ? "+" : "-" }}
|
|
|
+ {{ formatCurrency(row.amount) }}
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+ </DefaultTable>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Diálogo conta -->
|
|
|
+ <q-dialog v-model="accountDialog">
|
|
|
+ <q-card style="min-width: 360px; max-width: 480px">
|
|
|
+ <DefaultDialogHeader
|
|
|
+ :title="editingAccount ? 'Editar conta' : 'Nova conta'"
|
|
|
+ @close="accountDialog = false"
|
|
|
+ />
|
|
|
+ <q-form ref="accountFormRef">
|
|
|
+ <q-card-section class="q-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="accountForm.name"
|
|
|
+ label="Nome (ex.: Banco do Brasil, Caixa Loja)"
|
|
|
+ outlined
|
|
|
+ :rules="[(v) => !!v || 'Informe o nome']"
|
|
|
+ />
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultInput v-model="accountForm.bank_name" label="Banco" class="col-6" outlined />
|
|
|
+ <DefaultInput v-model="accountForm.bank_agency" label="Agência" class="col-6" outlined />
|
|
|
+ <DefaultInput v-model="accountForm.bank_account" label="Conta" class="col-6" outlined />
|
|
|
+ <DefaultSelect
|
|
|
+ v-model="accountForm.bank_type_account"
|
|
|
+ label="Tipo"
|
|
|
+ :options="accountTypeOptions"
|
|
|
+ class="col-6"
|
|
|
+ outlined
|
|
|
+ emit-value
|
|
|
+ map-options
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-card-section>
|
|
|
+ <q-card-actions align="right" class="q-pa-md">
|
|
|
+ <q-btn flat label="Cancelar" color="grey-7" @click="accountDialog = false" />
|
|
|
+ <q-btn color="primary" label="Salvar" :loading="savingAccount" @click="saveAccount" />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-form>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+
|
|
|
+ <!-- Diálogo movimentação -->
|
|
|
+ <q-dialog v-model="launchDialog">
|
|
|
+ <q-card style="min-width: 360px; max-width: 460px">
|
|
|
+ <DefaultDialogHeader title="Nova movimentação" @close="launchDialog = false" />
|
|
|
+ <q-form ref="launchFormRef">
|
|
|
+ <q-card-section class="q-gutter-sm">
|
|
|
+ <DefaultSelect
|
|
|
+ v-model="launchForm.transaction_type"
|
|
|
+ label="Tipo"
|
|
|
+ :options="transactionTypeOptions"
|
|
|
+ outlined
|
|
|
+ emit-value
|
|
|
+ map-options
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="launchForm.description"
|
|
|
+ label="Descrição"
|
|
|
+ outlined
|
|
|
+ :rules="[(v) => !!v || 'Informe a descrição']"
|
|
|
+ />
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultCurrencyInput
|
|
|
+ v-model="launchForm.amount"
|
|
|
+ label="Valor"
|
|
|
+ class="col-6"
|
|
|
+ outlined
|
|
|
+ />
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model="launchForm.launch_date"
|
|
|
+ label="Data"
|
|
|
+ class="col-6"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-card-section>
|
|
|
+ <q-card-actions align="right" class="q-pa-md">
|
|
|
+ <q-btn flat label="Cancelar" color="grey-7" @click="launchDialog = false" />
|
|
|
+ <q-btn color="primary-2" label="Salvar" :loading="savingLaunch" @click="saveLaunch" />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-form>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
+import {
|
|
|
+ getTreasuryAccountsMe,
|
|
|
+ createTreasuryAccountMe,
|
|
|
+ updateTreasuryAccountMe,
|
|
|
+ getTreasuryLaunchesMe,
|
|
|
+ createTreasuryLaunchMe,
|
|
|
+} from "src/api/treasury";
|
|
|
+
|
|
|
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
|
|
|
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
|
|
|
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
|
|
|
+import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
|
|
|
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
|
|
|
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
+
|
|
|
+const accounts = ref([]);
|
|
|
+const launches = ref([]);
|
|
|
+const selectedId = ref(null);
|
|
|
+
|
|
|
+const accountDialog = ref(false);
|
|
|
+const savingAccount = ref(false);
|
|
|
+const editingAccount = ref(false);
|
|
|
+const editingAccountId = ref(null);
|
|
|
+const accountFormRef = ref(null);
|
|
|
+
|
|
|
+const launchDialog = ref(false);
|
|
|
+const savingLaunch = ref(false);
|
|
|
+const launchFormRef = ref(null);
|
|
|
+
|
|
|
+const accountForm = ref(defaultAccountForm());
|
|
|
+const launchForm = ref(defaultLaunchForm());
|
|
|
+
|
|
|
+function defaultAccountForm() {
|
|
|
+ return {
|
|
|
+ name: "",
|
|
|
+ treasury_type: "bank",
|
|
|
+ bank_name: "",
|
|
|
+ bank_agency: "",
|
|
|
+ bank_account: "",
|
|
|
+ bank_type_account: null,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function defaultLaunchForm() {
|
|
|
+ return {
|
|
|
+ transaction_type: "entrada",
|
|
|
+ description: "",
|
|
|
+ amount: 0,
|
|
|
+ launch_date: new Date().toISOString().slice(0, 10),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+const accountTypeOptions = [
|
|
|
+ { label: "Corrente", value: "corrente" },
|
|
|
+ { label: "Poupança", value: "poupanca" },
|
|
|
+];
|
|
|
+
|
|
|
+const transactionTypeOptions = [
|
|
|
+ { label: "Entrada", value: "entrada" },
|
|
|
+ { label: "Saída", value: "saida" },
|
|
|
+];
|
|
|
+
|
|
|
+const launchColumns = [
|
|
|
+ { name: "launch_date", label: "Data", field: "launch_date", align: "left" },
|
|
|
+ { name: "description", label: "Descrição", field: "description", align: "left" },
|
|
|
+ { name: "transaction_type", label: "Tipo", field: "transaction_type", align: "left" },
|
|
|
+ { name: "amount", label: "Valor", field: "amount", align: "left" },
|
|
|
+];
|
|
|
+
|
|
|
+const selectedAccount = computed(() =>
|
|
|
+ accounts.value.find((a) => a.id === selectedId.value),
|
|
|
+);
|
|
|
+
|
|
|
+function formatCurrency(value) {
|
|
|
+ return new Intl.NumberFormat("pt-BR", {
|
|
|
+ style: "currency",
|
|
|
+ currency: "BRL",
|
|
|
+ }).format(Number(value ?? 0));
|
|
|
+}
|
|
|
+
|
|
|
+async function loadAccounts() {
|
|
|
+ try {
|
|
|
+ accounts.value = await getTreasuryAccountsMe();
|
|
|
+ if (!selectedId.value && accounts.value.length) {
|
|
|
+ selectAccount(accounts.value[0]);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Erro ao carregar contas:", e);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadLaunches() {
|
|
|
+ if (!selectedId.value) return;
|
|
|
+ try {
|
|
|
+ launches.value = await getTreasuryLaunchesMe(selectedId.value);
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Erro ao carregar extrato:", e);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function selectAccount(acc) {
|
|
|
+ selectedId.value = acc.id;
|
|
|
+ loadLaunches();
|
|
|
+}
|
|
|
+
|
|
|
+function openAccountCreate() {
|
|
|
+ editingAccount.value = false;
|
|
|
+ editingAccountId.value = null;
|
|
|
+ accountForm.value = defaultAccountForm();
|
|
|
+ accountDialog.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function openAccountEdit(acc) {
|
|
|
+ editingAccount.value = true;
|
|
|
+ editingAccountId.value = acc.id;
|
|
|
+ accountForm.value = {
|
|
|
+ name: acc.name,
|
|
|
+ treasury_type: "bank",
|
|
|
+ bank_name: acc.bank_name || "",
|
|
|
+ bank_agency: acc.bank_agency || "",
|
|
|
+ bank_account: acc.bank_account || "",
|
|
|
+ bank_type_account: acc.bank_type_account || null,
|
|
|
+ };
|
|
|
+ accountDialog.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+async function saveAccount() {
|
|
|
+ const valid = await accountFormRef.value?.validate();
|
|
|
+ if (!valid) return;
|
|
|
+ savingAccount.value = true;
|
|
|
+ try {
|
|
|
+ if (editingAccount.value) {
|
|
|
+ await updateTreasuryAccountMe(editingAccountId.value, accountForm.value);
|
|
|
+ } else {
|
|
|
+ await createTreasuryAccountMe(accountForm.value);
|
|
|
+ }
|
|
|
+ accountDialog.value = false;
|
|
|
+ await loadAccounts();
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ savingAccount.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function openLaunchCreate() {
|
|
|
+ launchForm.value = defaultLaunchForm();
|
|
|
+ launchDialog.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+async function saveLaunch() {
|
|
|
+ const valid = await launchFormRef.value?.validate();
|
|
|
+ if (!valid) return;
|
|
|
+ savingLaunch.value = true;
|
|
|
+ try {
|
|
|
+ await createTreasuryLaunchMe({
|
|
|
+ account_id: selectedId.value,
|
|
|
+ ...launchForm.value,
|
|
|
+ });
|
|
|
+ launchDialog.value = false;
|
|
|
+ await loadAccounts();
|
|
|
+ await loadLaunches();
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ savingLaunch.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(loadAccounts);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.selected-card {
|
|
|
+ border-color: var(--q-primary);
|
|
|
+ border-width: 2px;
|
|
|
+}
|
|
|
+</style>
|