Przeglądaj źródła

feat(financial): tela de plano de contas (CRUD com tipo receita/despesa)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee 1 miesiąc temu
rodzic
commit
9c399da504

+ 212 - 0
src/pages/financial/ChartOfAccountsPage.vue

@@ -0,0 +1,212 @@
+<template>
+  <div>
+    <DefaultHeaderPage title="Plano de Contas" :show-filter-icon="false" />
+
+    <div class="row justify-end items-center q-px-md q-mb-sm">
+      <q-btn
+        color="primary"
+        label="Nova conta"
+        icon="mdi-plus"
+        unelevated
+        no-caps
+        @click="openCreate"
+      />
+    </div>
+
+    <div class="q-px-md">
+      <DefaultTable
+        v-model:rows="rows"
+        no-api-call
+        :add-item="false"
+        title="Plano de Contas"
+        descricao="contas"
+        :feminino="true"
+        :columns="columns"
+        :delete-function="onDelete"
+      >
+        <template #body-cell-chart_type="{ row }">
+          <q-td class="text-left">
+            <q-chip
+              :color="row.chart_type === 'receita' ? 'positive' : 'blue-grey-5'"
+              text-color="white"
+              dense
+              square
+              :label="chartTypeLabel(row.chart_type)"
+            />
+          </q-td>
+        </template>
+
+        <template #body-cell-actions="{ row }">
+          <q-td auto-width>
+            <q-btn
+              color="primary-2"
+              label="Editar"
+              dense
+              no-caps
+              outline
+              class="q-px-sm"
+              @click="openEdit(row)"
+            />
+          </q-td>
+        </template>
+      </DefaultTable>
+    </div>
+
+    <q-dialog v-model="dialog">
+      <q-card style="min-width: 360px; max-width: 460px">
+        <DefaultDialogHeader
+          :title="editing ? 'Editar conta' : 'Nova conta'"
+          @close="dialog = false"
+        />
+
+        <q-form ref="formRef">
+          <q-card-section class="q-gutter-sm">
+            <div class="row q-col-gutter-sm">
+              <DefaultInput
+                v-model="form.code"
+                label="Código (ex.: 1.1.01)"
+                class="col-5"
+                outlined
+                :rules="[(v) => !!v || 'Informe o código']"
+              />
+              <DefaultSelect
+                v-model="form.chart_type"
+                label="Tipo"
+                :options="chartTypeOptions"
+                class="col-7"
+                outlined
+                emit-value
+                map-options
+                :rules="[(v) => !!v || 'Selecione o tipo']"
+              />
+            </div>
+            <DefaultInput
+              v-model="form.description"
+              label="Descrição"
+              outlined
+              :rules="[(v) => !!v || 'Informe a descrição']"
+            />
+          </q-card-section>
+
+          <q-card-actions align="right" class="q-pa-md">
+            <q-btn flat label="Cancelar" color="grey-7" @click="dialog = false" />
+            <q-btn
+              color="primary"
+              label="Salvar"
+              :loading="saving"
+              @click="onSave"
+            />
+          </q-card-actions>
+        </q-form>
+      </q-card>
+    </q-dialog>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { useQuasar } from "quasar";
+import {
+  getPlanAccountsMe,
+  createPlanAccountMe,
+  updatePlanAccountMe,
+  deletePlanAccountMe,
+} from "src/api/financial_plan_account";
+
+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 DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+
+const $q = useQuasar();
+
+const rows = ref([]);
+const dialog = ref(false);
+const saving = ref(false);
+const editing = ref(false);
+const editingId = ref(null);
+const formRef = ref(null);
+
+const defaultForm = () => ({ code: "", description: "", chart_type: "despesa" });
+const form = ref(defaultForm());
+
+const chartTypeOptions = [
+  { label: "Receita", value: "receita" },
+  { label: "Despesa", value: "despesa" },
+];
+
+function chartTypeLabel(type) {
+  return chartTypeOptions.find((o) => o.value === type)?.label ?? type;
+}
+
+const columns = [
+  { name: "code", label: "Código", field: "code", align: "left", sortable: true },
+  { name: "description", label: "Descrição", field: "description", align: "left" },
+  { name: "chart_type", label: "Tipo", field: "chart_type", align: "left" },
+  { name: "actions", label: "Ações", field: "actions", align: "right" },
+];
+
+async function loadData() {
+  try {
+    rows.value = await getPlanAccountsMe();
+  } catch (e) {
+    console.error("Erro ao carregar plano de contas:", e);
+  }
+}
+
+function openCreate() {
+  editing.value = false;
+  editingId.value = null;
+  form.value = defaultForm();
+  dialog.value = true;
+}
+
+function openEdit(row) {
+  editing.value = true;
+  editingId.value = row.id;
+  form.value = {
+    code: row.code,
+    description: row.description,
+    chart_type: row.chart_type,
+  };
+  dialog.value = true;
+}
+
+async function onSave() {
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+  saving.value = true;
+  try {
+    if (editing.value) {
+      await updatePlanAccountMe(editingId.value, form.value);
+    } else {
+      await createPlanAccountMe(form.value);
+    }
+    dialog.value = false;
+    await loadData();
+  } catch (e) {
+    console.error(e);
+  } finally {
+    saving.value = false;
+  }
+}
+
+function onDelete(id) {
+  $q.dialog({
+    title: "Excluir conta",
+    message: "Tem certeza que deseja excluir esta conta do plano?",
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    try {
+      await deletePlanAccountMe(id);
+      await loadData();
+    } catch (e) {
+      console.error(e);
+    }
+  });
+}
+
+onMounted(loadData);
+</script>

+ 14 - 0
src/router/routes/financial.route.js

@@ -40,4 +40,18 @@ export default [
       ],
     },
   },
+  {
+    path: "/financial/chart-of-accounts",
+    name: "ChartOfAccountsPage",
+    component: () => import("pages/financial/ChartOfAccountsPage.vue"),
+    meta: {
+      title: { value: "Plano de Contas", translate: false },
+      requireAuth: true,
+      breadcrumbs: [
+        { name: "DashboardPage", title: "Dashboard" },
+        { name: "FinancialPage", title: "Financeiro" },
+        { name: "ChartOfAccountsPage", title: "Plano de Contas" },
+      ],
+    },
+  },
 ];