| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- <template>
- <div>
- <DefaultHeaderPage title="Plano de Contas" :show-filter-icon="false" />
- <div class="q-px-md">
- <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>
- </template>
- <script setup>
- import { ref, computed, onMounted } from "vue";
- import { useQuasar } from "quasar";
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- import DefaultInput from "src/components/defaults/DefaultInput.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 { permissionStore } from "src/stores/permission";
- const $q = useQuasar();
- const canAdd = permissionStore().getAccess("franchisor_financial", "add");
- const accounts = ref([]);
- 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;
- };
- 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 () => {
- loading.value = true;
- try {
- accounts.value = await getFinancialPlanAccounts();
- } catch {
- $q.notify({ type: "negative", message: "Erro ao carregar o plano de contas." });
- } finally {
- loading.value = false;
- }
- };
- const handleAddItem = () => {
- $q.dialog({ component: AddFinancialPlanAccountDialog }).onOk(() => {
- $q.notify({ type: "positive", message: "Conta cadastrada com sucesso." });
- fetchAccounts();
- });
- };
- onMounted(fetchAccounts);
- </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>
|