|
@@ -3,19 +3,57 @@
|
|
|
<DefaultHeaderPage title="Plano de Contas" :show-filter-icon="false" />
|
|
<DefaultHeaderPage title="Plano de Contas" :show-filter-icon="false" />
|
|
|
|
|
|
|
|
<div class="q-px-md">
|
|
<div class="q-px-md">
|
|
|
- <DefaultTable
|
|
|
|
|
- v-model:rows="rows"
|
|
|
|
|
- no-api-call
|
|
|
|
|
- :add-item="canAdd"
|
|
|
|
|
- add-item-label="Nova Conta"
|
|
|
|
|
- dense-add
|
|
|
|
|
- :loading="loading"
|
|
|
|
|
- title="Plano de Contas"
|
|
|
|
|
- description="contas"
|
|
|
|
|
- :female="true"
|
|
|
|
|
- :columns="columns"
|
|
|
|
|
- @on-add-item="handleAddItem"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <q-card flat bordered class="q-pa-md q-mt-md">
|
|
|
|
|
+ <div class="row items-center q-gutter-md q-mb-md">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="text-h6">Plano de Contas</div>
|
|
|
|
|
+ <div class="text-body2">{{ accounts.length }} contas cadastradas</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <q-space />
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="canAdd"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ label="Nova Conta"
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ @click="handleAddItem"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="search"
|
|
|
|
|
+ dense
|
|
|
|
|
+ clearable
|
|
|
|
|
+ debounce="250"
|
|
|
|
|
+ label="Buscar por código ou nome da conta"
|
|
|
|
|
+ class="q-mb-md"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prepend>
|
|
|
|
|
+ <q-icon name="mdi-magnify" color="grey-6" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </DefaultInput>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="account-tree-header">
|
|
|
|
|
+ <div>Código</div>
|
|
|
|
|
+ <div>Nome da Conta</div>
|
|
|
|
|
+ <div>Tipo</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-list>
|
|
|
|
|
+ <FinancialPlanAccountTreeNode
|
|
|
|
|
+ v-for="node in filteredTree"
|
|
|
|
|
+ :key="node.id"
|
|
|
|
|
+ :node="node"
|
|
|
|
|
+ :force-expanded="!!normalizedSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </q-list>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="!loading && filteredTree.length === 0" class="text-center q-pa-lg">
|
|
|
|
|
+ Nenhuma conta encontrada.
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-inner-loading :showing="loading" color="primary" />
|
|
|
|
|
+ </q-card>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -24,8 +62,9 @@
|
|
|
import { ref, computed, onMounted } from "vue";
|
|
import { ref, computed, onMounted } from "vue";
|
|
|
import { useQuasar } from "quasar";
|
|
import { useQuasar } from "quasar";
|
|
|
import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
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 AddFinancialPlanAccountDialog from "src/components/financial/AddFinancialPlanAccountDialog.vue";
|
|
import AddFinancialPlanAccountDialog from "src/components/financial/AddFinancialPlanAccountDialog.vue";
|
|
|
|
|
+import FinancialPlanAccountTreeNode from "src/components/financial/FinancialPlanAccountTreeNode.vue";
|
|
|
import { getFinancialPlanAccounts } from "src/api/financial_plan_account";
|
|
import { getFinancialPlanAccounts } from "src/api/financial_plan_account";
|
|
|
import { permissionStore } from "src/stores/permission";
|
|
import { permissionStore } from "src/stores/permission";
|
|
|
|
|
|
|
@@ -34,25 +73,49 @@ const canAdd = permissionStore().getAccess("franchisor_financial", "add");
|
|
|
|
|
|
|
|
const accounts = ref([]);
|
|
const accounts = ref([]);
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
|
|
+const search = ref("");
|
|
|
|
|
+
|
|
|
|
|
+const accountTree = computed(() => {
|
|
|
|
|
+ const nodes = new Map(
|
|
|
|
|
+ accounts.value.map((account) => [account.id, { ...account, children: [] }]),
|
|
|
|
|
+ );
|
|
|
|
|
+ const roots = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (const node of nodes.values()) {
|
|
|
|
|
+ const parent = node.parent_id ? nodes.get(node.parent_id) : null;
|
|
|
|
|
+ if (parent) parent.children.push(node);
|
|
|
|
|
+ else roots.push(node);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const sortNodes = (items) => {
|
|
|
|
|
+ items.sort((a, b) =>
|
|
|
|
|
+ String(a.code).localeCompare(String(b.code), "pt-BR", { numeric: true }),
|
|
|
|
|
+ );
|
|
|
|
|
+ items.forEach((item) => sortNodes(item.children));
|
|
|
|
|
+ return items;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
-const rows = computed(() =>
|
|
|
|
|
- accounts.value.map((account) => ({
|
|
|
|
|
- id: account.id,
|
|
|
|
|
- code: account.code,
|
|
|
|
|
- name: account.description,
|
|
|
|
|
- type: account.chart_type,
|
|
|
|
|
- parent: account.parent
|
|
|
|
|
- ? `${account.parent.code} — ${account.parent.description}`
|
|
|
|
|
- : "—",
|
|
|
|
|
- })),
|
|
|
|
|
-);
|
|
|
|
|
-
|
|
|
|
|
-const columns = [
|
|
|
|
|
- { name: "code", label: "Código", field: "code", align: "left" },
|
|
|
|
|
- { name: "name", label: "Nome da Conta", field: "name", align: "left" },
|
|
|
|
|
- { name: "parent", label: "Conta Pai", field: "parent", align: "left" },
|
|
|
|
|
- { name: "type", label: "Tipo", field: "type", align: "left" },
|
|
|
|
|
-];
|
|
|
|
|
|
|
+ return sortNodes(roots);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const normalizedSearch = computed(() => search.value?.trim().toLocaleLowerCase("pt-BR") ?? "");
|
|
|
|
|
+
|
|
|
|
|
+const filteredTree = computed(() => {
|
|
|
|
|
+ if (!normalizedSearch.value) return accountTree.value;
|
|
|
|
|
+
|
|
|
|
|
+ const filterNodes = (nodes) =>
|
|
|
|
|
+ nodes.reduce((result, node) => {
|
|
|
|
|
+ const children = filterNodes(node.children);
|
|
|
|
|
+ const matches = `${node.code} ${node.description}`
|
|
|
|
|
+ .toLocaleLowerCase("pt-BR")
|
|
|
|
|
+ .includes(normalizedSearch.value);
|
|
|
|
|
+
|
|
|
|
|
+ if (matches || children.length) result.push({ ...node, children });
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ return filterNodes(accountTree.value);
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
const fetchAccounts = async () => {
|
|
const fetchAccounts = async () => {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
@@ -74,3 +137,21 @@ const handleAddItem = () => {
|
|
|
|
|
|
|
|
onMounted(fetchAccounts);
|
|
onMounted(fetchAccounts);
|
|
|
</script>
|
|
</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.account-tree-header {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 18% 1fr 18%;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ padding: 12px 56px 12px 16px;
|
|
|
|
|
+ border-bottom: 1px solid #c7c7c7;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 599px) {
|
|
|
|
|
+ .account-tree-header {
|
|
|
|
|
+ grid-template-columns: 25% 1fr 25%;
|
|
|
|
|
+ padding-left: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|