diff --git a/README.md b/README.md index 30d23de..c387215 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Study project - Yet another personal finance app ## Overview - Keep track of your personal expenses and income -- Draw nice charts +- [Draw nice charts](https://vue-data-ui.graphieros.com/) +- [Use nice UI components](https://vuetifyjs.com/) - Import and export data - [_Don't take this code too serious_](./docs/THINGS_TO_FIX.md) - diff --git a/service-node-koa/app/controllers/dashboard.mjs b/service-node-koa/app/controllers/dashboard.mjs new file mode 100644 index 0000000..e69f5f8 --- /dev/null +++ b/service-node-koa/app/controllers/dashboard.mjs @@ -0,0 +1,8 @@ +import { getDashboard } from '../services/index.mjs' + +export const getDashboardRequest = async ctx => { + const { usuario_id } = ctx.request.params + const { inicio, fim } = ctx.request.query + // more validations + ctx.body = await getDashboard({usuario_id, inicio, fim}) +} diff --git a/service-node-koa/app/controllers/index.mjs b/service-node-koa/app/controllers/index.mjs index 051df1a..f5fe66f 100644 --- a/service-node-koa/app/controllers/index.mjs +++ b/service-node-koa/app/controllers/index.mjs @@ -1,7 +1,8 @@ // reexportação dos módulos de rota -export * from "./categoria.mjs" -export * from "./conta.mjs" -export * from "./movimentacao.mjs" -export * from "./planejamento.mjs" -export * from "./recorrencia.mjs" -export * from "./user.mjs" +export * from './categoria.mjs' +export * from './conta.mjs' +export * from './dashboard.mjs' +export * from './movimentacao.mjs' +export * from './planejamento.mjs' +export * from './recorrencia.mjs' +export * from './user.mjs' diff --git a/service-node-koa/app/main.mjs b/service-node-koa/app/main.mjs index faeb61f..7d0cda0 100644 --- a/service-node-koa/app/main.mjs +++ b/service-node-koa/app/main.mjs @@ -12,7 +12,7 @@ import { findCategoriaRequest, findContaRequest, findMovimentacaoRequest, - findRecorrenciaRequest, geraLancamentosRequest, + findRecorrenciaRequest, geraLancamentosRequest, getDashboardRequest, insertCategoriaRequest, insertContaRequest, insertMovimentacaoRequest, @@ -109,6 +109,8 @@ new ApiBuilder({router}).path(b => { b.get("/lancamentos", geraLancamentosRequest) }) }); + + b.get("/dashboard", getDashboardRequest) }); }).build(); diff --git a/service-node-koa/app/services/dashboard.mjs b/service-node-koa/app/services/dashboard.mjs new file mode 100644 index 0000000..f3167c3 --- /dev/null +++ b/service-node-koa/app/services/dashboard.mjs @@ -0,0 +1,179 @@ +import { knex } from '../config/db/index.mjs' + +/** + * + * @param usuario_id + * @param inicio + * @param fim + */ +export const getDashboard = async ({ usuario_id, inicio, fim }) => { + inicio = new Date(inicio).toISOString() + fim = new Date(fim).toISOString() + return { + receitaDespesaTotalPeriodo: await receitaDespesaTotalPeriodo({ usuario_id, inicio, fim }), + receitaDespesaEfetivadaPeriodo: await receitaDespesaEfetivadaPeriodo({ usuario_id, inicio, fim }), + despesaConta: await despesaConta({ usuario_id, inicio, fim }), + despesaCategoria: await despesaCategoria({ usuario_id, inicio, fim }), + receitaConta: await receitaConta({ usuario_id, inicio, fim }), + receitaCategoria: await receitaCategoria({ usuario_id, inicio, fim }), + composicaoDespesas: await composicaoDespesas({ usuario_id, inicio, fim }), + composicaoReceitas: await composicaoReceitas({ usuario_id, inicio, fim }), + saldos: { + // Saldos relativos ao período + anteriorGeral: 0, + anterior1Ano: -10, + anterior6Meses: 0, + anterior1Mes: 0, + periodo: 0, + projetado1Mes: 0, + projetado6Meses: 10, + projetado1Ano: 0 + }, + vencimentos: { + quitadas: 7, + aVencer: 3, + emAtraso: 0 + }, + limites: [], + planejamentos: [] + } +} + +async function receitaDespesaTotalPeriodo({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select * + from movimentacao + where conta_id in (select id from conta where usuario_id = :usuario_id) + and vencimento between :inicio and :fim) + select 'Receita total' as label, sum(valor) as value, 'lightgreen' as color + from data_frame + where tipo_movimentacao_id = 1 + union + select 'Despesa total' as label, sum(valor) as value, 'red' as color + from data_frame + where tipo_movimentacao_id = 2 + `, { usuario_id, inicio, fim }) +} + +async function receitaDespesaEfetivadaPeriodo({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select * + from movimentacao + where conta_id in (select id from conta where usuario_id = :usuario_id) + and vencimento between :inicio and :fim + and efetivada is not null) + select 'Receita efetivada' as label, sum(valor) as value, 'lightgreen' as color + from data_frame + where tipo_movimentacao_id = 1 + union + select 'Despesa efetivada' as label, sum(valor) as value, 'red' as color + from data_frame + where tipo_movimentacao_id = 2 + `, { usuario_id, inicio, fim }) + +} + +async function despesaConta({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select * + from conta + join movimentacao on conta.id = movimentacao.conta_id + where usuario_id = :usuario_id + and tipo_movimentacao_id = 2 + and vencimento between :inicio and :fim) + select descricao as label, + cor as color, + sum(valor) as value + from data_frame + group by descricao, cor + `, { usuario_id, inicio, fim }) +} + +async function despesaCategoria({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select categoria.*, movimentacao.* + from movimentacao + left join categoria on categoria.id = movimentacao.categoria_id + where usuario_id = :usuario_id + and tipo_movimentacao_id = 2 + and vencimento between :inicio and :fim) + select descricao as label, + cor as color, + sum(valor) as value + from data_frame + group by descricao, cor + `, { usuario_id, inicio, fim }) +} + + +async function receitaConta({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select * + from conta + join movimentacao on conta.id = movimentacao.conta_id + where usuario_id = :usuario_id + and tipo_movimentacao_id = 1 + and vencimento between :inicio and :fim) + select descricao as label, + cor as color, + sum(valor) as value + from data_frame + group by descricao, cor + `, { usuario_id, inicio, fim }) +} + +async function receitaCategoria({ usuario_id, inicio, fim }) { + return knex.raw(` + with data_frame as (select categoria.*, movimentacao.* + from movimentacao + left join categoria on categoria.id = movimentacao.categoria_id + where usuario_id = :usuario_id + and tipo_movimentacao_id = 1 + and vencimento between :inicio and :fim) + select descricao as label, + cor as color, + sum(valor) as value + from data_frame + group by descricao, cor + `, { usuario_id, inicio, fim }) +} + +async function composicaoDespesas({ usuario_id, inicio, fim }){ + const contas = await knex("conta").where({usuario_id}) + for await (const conta of contas) { + const conta_id = conta.id + conta.color = conta.cor + conta.data = await knex.raw(` + with data_frame as (select categoria.*, movimentacao.* + from movimentacao + left join categoria on categoria.id = movimentacao.categoria_id + where conta_id = :conta_id + and vencimento between :inicio and :fim + and tipo_movimentacao_id = 2) + select descricao as label, cor as color, sum(valor) as value + from data_frame + group by descricao, cor + `,{conta_id, inicio, fim}) + } + return contas.filter(c => c.data.length) +} + +async function composicaoReceitas({ usuario_id, inicio, fim }){ + const contas = await knex("conta").where({usuario_id}) + for await (const conta of contas) { + const conta_id = conta.id + conta.color = conta.cor + conta.data = await knex.raw(` + with data_frame as (select categoria.*, movimentacao.* + from movimentacao + left join categoria on categoria.id = movimentacao.categoria_id + where conta_id = :conta_id + and vencimento between :inicio and :fim + and tipo_movimentacao_id = 1) + select descricao as label, cor as color, sum(valor) as value + from data_frame + group by descricao, cor + `,{conta_id, inicio, fim}) + } + return contas.filter(c => c.data.length) +} diff --git a/service-node-koa/app/services/index.mjs b/service-node-koa/app/services/index.mjs index 582e78e..8d7344c 100644 --- a/service-node-koa/app/services/index.mjs +++ b/service-node-koa/app/services/index.mjs @@ -1,12 +1,13 @@ // reexportação dos módulos de serviço -export * from "./categoria.mjs"; -export * from "./conta.mjs"; -export * from "./modelocategoria.mjs"; -export * from "./movimentacao.mjs"; -export * from "./planejamento.mjs"; -export * from "./recorrencia.mjs"; -export * from "./tipo_conta.mjs"; -export * from "./tipo_movimentacao.mjs"; -export * from "./tipo_recorrencia.mjs"; -export * from "./usuario.mjs"; -export * from "./invite.mjs"; +export * from './categoria.mjs' +export * from './conta.mjs' +export * from './dashboard.mjs' +export * from './modelocategoria.mjs' +export * from './movimentacao.mjs' +export * from './planejamento.mjs' +export * from './recorrencia.mjs' +export * from './tipo_conta.mjs' +export * from './tipo_movimentacao.mjs' +export * from './tipo_recorrencia.mjs' +export * from './usuario.mjs' +export * from './invite.mjs' diff --git a/web-app-vue/package-lock.json b/web-app-vue/package-lock.json index f9a6fa1..afc6ec5 100644 --- a/web-app-vue/package-lock.json +++ b/web-app-vue/package-lock.json @@ -11,7 +11,7 @@ "date-fns": "^2.30.0", "jwt-decode": "^3.1.2", "pinia": "^2.1.7", - "vue": "^3.4.25", + "vue": "^3.4.27", "vue-data-ui": "^2.1.43", "vue-router": "^4.3.0", "vuetify": "^3.5.17" @@ -1774,36 +1774,36 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.25.tgz", - "integrity": "sha512-Y2pLLopaElgWnMNolgG8w3C5nNUVev80L7hdQ5iIKPtMJvhVpG0zhnBG/g3UajJmZdvW0fktyZTotEHD1Srhbg==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dependencies": { "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.25", + "@vue/shared": "3.4.27", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.25.tgz", - "integrity": "sha512-Ugz5DusW57+HjllAugLci19NsDK+VyjGvmbB2TXaTcSlQxwL++2PETHx/+Qv6qFwNLzSt7HKepPe4DcTE3pBWg==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dependencies": { - "@vue/compiler-core": "3.4.25", - "@vue/shared": "3.4.25" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.25.tgz", - "integrity": "sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dependencies": { "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.25", - "@vue/compiler-dom": "3.4.25", - "@vue/compiler-ssr": "3.4.25", - "@vue/shared": "3.4.25", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -1811,12 +1811,12 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.25.tgz", - "integrity": "sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dependencies": { - "@vue/compiler-dom": "3.4.25", - "@vue/shared": "3.4.25" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/devtools-api": { @@ -1839,48 +1839,48 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.25.tgz", - "integrity": "sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", "dependencies": { - "@vue/shared": "3.4.25" + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.25.tgz", - "integrity": "sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", "dependencies": { - "@vue/reactivity": "3.4.25", - "@vue/shared": "3.4.25" + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.25.tgz", - "integrity": "sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", "dependencies": { - "@vue/runtime-core": "3.4.25", - "@vue/shared": "3.4.25", + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.25.tgz", - "integrity": "sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", "dependencies": { - "@vue/compiler-ssr": "3.4.25", - "@vue/shared": "3.4.25" + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { - "vue": "3.4.25" + "vue": "3.4.27" } }, "node_modules/@vue/shared": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.25.tgz", - "integrity": "sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==" + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" }, "node_modules/@vue/test-utils": { "version": "2.4.2", @@ -6245,15 +6245,15 @@ } }, "node_modules/vue": { - "version": "3.4.25", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.25.tgz", - "integrity": "sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==", - "dependencies": { - "@vue/compiler-dom": "3.4.25", - "@vue/compiler-sfc": "3.4.25", - "@vue/runtime-dom": "3.4.25", - "@vue/server-renderer": "3.4.25", - "@vue/shared": "3.4.25" + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "dependencies": { + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { "typescript": "*" diff --git a/web-app-vue/package.json b/web-app-vue/package.json index 86712aa..f831817 100644 --- a/web-app-vue/package.json +++ b/web-app-vue/package.json @@ -14,7 +14,7 @@ "date-fns": "^2.30.0", "jwt-decode": "^3.1.2", "pinia": "^2.1.7", - "vue": "^3.4.25", + "vue": "^3.4.27", "vue-data-ui": "^2.1.43", "vue-router": "^4.3.0", "vuetify": "^3.5.17" diff --git a/web-app-vue/src/App.vue b/web-app-vue/src/App.vue index cfc049f..c8bf934 100644 --- a/web-app-vue/src/App.vue +++ b/web-app-vue/src/App.vue @@ -24,7 +24,7 @@ const show = ref(false) - + diff --git a/web-app-vue/src/components/dashboard/controles-dashboard.vue b/web-app-vue/src/components/dashboard/controles-dashboard.vue index cca90b8..85db4d3 100644 --- a/web-app-vue/src/components/dashboard/controles-dashboard.vue +++ b/web-app-vue/src/components/dashboard/controles-dashboard.vue @@ -12,11 +12,19 @@ Receitas x Despesas - + +
- +
+
@@ -25,12 +33,20 @@ Detalhes Despesas

Despesas por conta

- + +
+

Despesas por categoria

- +
@@ -39,12 +55,20 @@ Detalhes Receitas

Receitas por conta

- + +
+

Receitas por categoria

- +
@@ -52,27 +76,26 @@ Composição - - - -

Composição de despesas

-
-

{{ conta.label }}

- +
+

{{ conta.descricao }}

+
+
- - - - - +

Composição de receitas

-
-

{{ conta.label }}

- +
+

{{ conta.descricao }}

+
@@ -146,7 +169,7 @@ Limites - + @@ -165,49 +188,57 @@ import { computed, onMounted, ref } from 'vue' import { VueDataUi } from 'vue-data-ui' import ChipPeriodo from '@/shared/chip-periodo.vue' import { useDashboardStore } from '@/stores/dashboardStore' -import StackBarChart from '@/shared/charts/stack-bar-chart.vue' import ChipSaldo from '@/shared/chip-saldo.vue' -// TODO compute configs? -import { donutConfig, lineChartConfig, sparkBarConfig, sparkStackBarConfig } from '@/services/chart-config' -import { prepareMoney } from '@/services/formaters' +import { + donutConfig, + lineChartConfig, + sparkBarConfig, + sparkStackBarConfig +} from '@/services/chart-config' const inicio = ref(startOfMonth(new Date())) const fim = ref(endOfMonth(new Date())) -const folha = ref('rxd') +const folha = ref('') const dashboardState = useDashboardStore() -const receitaDespesaBarConfig = computed(() => sparkBarConfig) +const receitaDespesaBarConfig = computed(() => { + const total = dashboardState.store.dashboard.receitaDespesaTotalPeriodo?.reduce((acc, e) => { + acc += e.value + return acc + }, 0) || 0 + return { + style: { + ...sparkBarConfig.style, + layout: { + ...sparkBarConfig.style.layout, + independant: true, + percentage: false, + target: total + } + } + } +}) const receitaDespesaTotalPeriodo = computed(() => { - const total = dashboardState.store.dashboard.receitaDespesaTotalPeriodo - .reduce((acc, e) => { - acc += e.value - return acc - }, 0) - return dashboardState.store.dashboard.receitaDespesaTotalPeriodo.map(r => ({ + return dashboardState.store.dashboard.receitaDespesaTotalPeriodo?.map((r) => ({ ...r, - name: `${r.label} ${prepareMoney(r.value)}`, - value: 100 * r.value / total + name: r.label, + prefix: 'R$ ' })) }) const receitaDespesaEfetivadaPeriodo = computed(() => { - const total = dashboardState.store.dashboard.receitaDespesaTotalPeriodo - .reduce((acc, e) => { - acc += e.value - return acc - }, 0) - return dashboardState.store.dashboard.receitaDespesaEfetivadaPeriodo.map(r => ({ + return dashboardState.store.dashboard.receitaDespesaEfetivadaPeriodo?.map((r) => ({ ...r, - name: `${r.label} ${prepareMoney(r.value)}`, - value: 100 * r.value / total + name: r.label, + prefix: 'R$ ' })) }) const despesaConta = computed(() => { - return dashboardState.store.dashboard.despesaConta.map(r => ({ + return dashboardState.store.dashboard.despesaConta.map((r) => ({ ...r, name: `${r.label}`, values: [r.value] @@ -215,7 +246,7 @@ const despesaConta = computed(() => { }) const despesaCategoria = computed(() => { - return dashboardState.store.dashboard.despesaCategoria.map(r => ({ + return dashboardState.store.dashboard.despesaCategoria.map((r) => ({ ...r, name: `${r.label}`, values: [r.value] @@ -223,7 +254,7 @@ const despesaCategoria = computed(() => { }) const receitaConta = computed(() => { - return dashboardState.store.dashboard.receitaConta.map(r => ({ + return dashboardState.store.dashboard.receitaConta.map((r) => ({ ...r, name: `${r.label}`, values: [r.value] @@ -231,7 +262,7 @@ const receitaConta = computed(() => { }) const receitaCategoria = computed(() => { - return dashboardState.store.dashboard.receitaCategoria.map(r => ({ + return dashboardState.store.dashboard.receitaCategoria.map((r) => ({ ...r, name: `${r.label}`, values: [r.value] @@ -239,10 +270,10 @@ const receitaCategoria = computed(() => { }) const composicaoDespesas = computed(() => { - return dashboardState.store.dashboard.composicaoDespesas.map(g => { + return dashboardState.store.dashboard.composicaoDespesas.map((g) => { return { ...g, - data: g.data.map(r => ({ + data: g.data.map((r) => ({ ...r, name: r.label })) @@ -251,10 +282,10 @@ const composicaoDespesas = computed(() => { }) const composicaoReceitas = computed(() => { - return dashboardState.store.dashboard.composicaoReceitas.map(g => { + return dashboardState.store.dashboard.composicaoReceitas.map((g) => { return { ...g, - data: g.data.map(r => ({ + data: g.data.map((r) => ({ ...r, name: r.label })) @@ -272,42 +303,30 @@ const planejamentos = computed(() => { const dataset = ref([ { - 'name': 'name', - 'series': [ - 1, - 2, - 3, - 4, - 4 - ], - 'color': '#6376DD', - 'type': 'line', - 'shape': 'circle', - 'useArea': true, - 'useProgression': true, - 'dataLabels': true, - 'smooth': true, - 'dashed': false, - 'useTag': 'none' + name: 'name', + series: [1, 2, 3, 4, 4], + color: '#6376DD', + type: 'line', + shape: 'circle', + useArea: true, + useProgression: true, + dataLabels: true, + smooth: true, + dashed: false, + useTag: 'none' }, { - 'name': 'name', - 'series': [ - 3, - 3, - 3, - 3, - 3 - ], - 'color': '#d24141', - 'type': 'line', - 'shape': 'circle', - 'useArea': false, - 'useProgression': false, - 'dataLabels': true, - 'smooth': true, - 'dashed': false, - 'useTag': 'none' + name: 'name', + series: [3, 3, 3, 3, 3], + color: '#d24141', + type: 'line', + shape: 'circle', + useArea: false, + useProgression: false, + dataLabels: true, + smooth: true, + dashed: false, + useTag: 'none' } ]) diff --git a/web-app-vue/src/components/planejamento/detalhe-planejamento.vue b/web-app-vue/src/components/planejamento/detalhe-planejamento.vue index 3d05b57..8f24b43 100644 --- a/web-app-vue/src/components/planejamento/detalhe-planejamento.vue +++ b/web-app-vue/src/components/planejamento/detalhe-planejamento.vue @@ -3,17 +3,18 @@ v-if="!edit" rounded variant="outlined" + class="ma-2" + size="x-large" :color="categoria.cor || 'green-accent-2'" :prepend-icon=" - planejamento?.tipo_movimentacao_id == 1 + planejamento?.tipo_movimentacao_id === 1 ? 'mdi-cash-plus' - : planejamento?.tipo_movimentacao_id == 2 + : planejamento?.tipo_movimentacao_id === 2 ? 'mdi-cash-minus' : 'mdi-cash' " :append-icon="planejamento?.id ? 'mdi-playlist-edit' : 'mdi-playlist-plus'" - class="ma-2" - size="x-large" + :title="props.planejamento?.descricao" @click="edit = true" > {{ planejamento?.id ? descricao : 'Novo planejamento' }} @@ -86,7 +87,6 @@ - + + diff --git a/web-app-vue/src/components/planejamento/lista-planejamentos.vue b/web-app-vue/src/components/planejamento/lista-planejamentos.vue index 99d87bf..e76bf51 100644 --- a/web-app-vue/src/components/planejamento/lista-planejamentos.vue +++ b/web-app-vue/src/components/planejamento/lista-planejamentos.vue @@ -14,31 +14,42 @@

Não há planejamentos para exibir

- + + + + + + + - + diff --git a/web-app-vue/src/components/recorrencia/list-recorrencia.vue b/web-app-vue/src/components/recorrencia/list-recorrencia.vue index d2326ee..9793c04 100644 --- a/web-app-vue/src/components/recorrencia/list-recorrencia.vue +++ b/web-app-vue/src/components/recorrencia/list-recorrencia.vue @@ -21,9 +21,11 @@ import { useRecorrenciaStore } from '@/stores/recorrenciaStore' import DetalheRecorrencia from '@/components/recorrencia/detalhe-recorrencia.vue' import { computed, onMounted } from 'vue' import { useCategoriaStore } from '@/stores/categoriaStore' +import { useContaStore } from '@/stores/contaStore' const recorrenciaStore = useRecorrenciaStore() const categoriaStore = useCategoriaStore() +const contaStore = useContaStore() const recorrencias = computed(() => { return recorrenciaStore.store.recorrencias || [] @@ -45,7 +47,8 @@ const doDel = async (rec) => { onMounted(async () => await Promise.all([ recorrenciaStore.sincronizarRecorrencia(), - categoriaStore.sincronizarCategorias() + categoriaStore.sincronizarCategorias(), + contaStore.sincronizarContas() ]) ) diff --git a/web-app-vue/src/services/api.js b/web-app-vue/src/services/api.js index 26c0403..89b2d5c 100644 --- a/web-app-vue/src/services/api.js +++ b/web-app-vue/src/services/api.js @@ -185,3 +185,6 @@ export const delRecorrencia = async ({ id, recorrencia_id }) => export const geraLancamentosRecorrencia = async ({ id, recorrencia_id }) => await get({ uri: `/${id}/recorrencia/${recorrencia_id}/lancamentos` }) + +export const getDashboard = async ({ id, inicio, fim }) => + await get({ uri: uriParams({ uri: `/${id}/dashboard`, params: { inicio, fim } }) }) diff --git a/web-app-vue/src/services/chart-config.js b/web-app-vue/src/services/chart-config.js index 61c220b..64a831d 100644 --- a/web-app-vue/src/services/chart-config.js +++ b/web-app-vue/src/services/chart-config.js @@ -5,8 +5,8 @@ export const sparkBarConfig = { 'backgroundColor': '#212121', 'fontFamily': 'inherit', 'layout': { - 'percentage': true, - 'target': 0 + 'percentage': false, + 'target': 0 // must compute proper value }, 'gutter': { 'backgroundColor': '#000000', @@ -33,7 +33,7 @@ export const sparkBarConfig = { 'bold': true }, 'value': { - 'show': false, + 'show': true, 'bold': true } } diff --git a/web-app-vue/src/services/router.js b/web-app-vue/src/services/router.js index dd4cddc..aaf8e5d 100644 --- a/web-app-vue/src/services/router.js +++ b/web-app-vue/src/services/router.js @@ -15,7 +15,7 @@ export const routes = [ { component: AuthPage, path: '/auth' }, { path: '/', redirect: '/dashboard' }, { component: DashboardPage, path: '/dashboard', label: 'Dashboard', icon: 'mdi-chart-bar' }, - { component: NovaMovimentacaoPage, path: '/nova-movimentacao', label: 'Nova movimentação', icon: 'mdi-currency-usd' }, + { component: NovaMovimentacaoPage, path: '/nova-movimentacao', label: 'Novo lançamento', icon: 'mdi-currency-usd' }, { component: ContasPage, path: '/contas', label: 'Contas', icon: 'mdi-card-account-details' }, { component: CategoriasPage, path: '/categorias', label: 'Categorias', icon: 'mdi-playlist-check' }, { component: HistoricoPage, path: '/historico', label: 'Histórico', icon: 'mdi-clipboard-text-search-outline' }, diff --git a/web-app-vue/src/shared/charts/stack-bar-chart.vue b/web-app-vue/src/shared/charts/stack-bar-chart.vue deleted file mode 100644 index 1df0bca..0000000 --- a/web-app-vue/src/shared/charts/stack-bar-chart.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - diff --git a/web-app-vue/src/stores/dashboardStore.js b/web-app-vue/src/stores/dashboardStore.js index 946486e..986a926 100644 --- a/web-app-vue/src/stores/dashboardStore.js +++ b/web-app-vue/src/stores/dashboardStore.js @@ -2,99 +2,22 @@ import { defineStore } from 'pinia' import { useUserStore } from '@/stores/userStore' import { reactive } from 'vue' import { getRedLine } from '@/services/redLine' +import { getDashboard } from '@/services/api' export const useDashboardStore = defineStore('dashboard-store', () => { const uState = useUserStore() const store = reactive({ dashboard: getRedLine()?.dashboard || { - receitaDespesaTotalPeriodo: [ - // Receita x Despesa - simple-bar - { label: 'Receita Total', value: 18000, color: 'lightgreen' }, - { label: 'Despesa Total', value: 14500, color: 'red' } - ], - receitaDespesaEfetivadaPeriodo: [ - // Receita x Despesa - simple-bar - { label: 'Receita Efetivada', value: 18000, color: 'lightgreen' }, - { label: 'Despesa Efetivada', value: 1500, color: 'red' } - ], - despesaConta: [ - // Despesas por conta - pie - { label: 'Banco', value: 5800.55, color: 'lightyellow' }, - { label: 'Cartão', value: 2600.02, color: 'orange' }, - { label: 'Carteira', value: 1500, color: 'darkgreen' } - ], - despesaCategoria: [ - // Despesas por categoria - pie - { label: 'Moradia', value: 4000, color: 'gray' }, - { label: 'Alimentação', value: 5000, color: 'red' }, - { label: 'Internet', value: 600, color: 'green' }, - { label: 'Empréstimos', value: 3000, color: 'brown' }, - { label: 'Transporte', value: 1500, color: 'blue' } - ], - receitaConta: [ - // Receitas por conta - { label: 'Banco', value: 18000, color: 'lightyellow' } - ], - receitaCategoria: [ - // Receitas categoria - { label: 'Salário', value: 18000, color: 'lightgreen' } - ], - composicaoDespesas: [ - { - label: 'Banco', - color: 'red', - data: [ - { label: 'Moradia', value: 4000, color: 'gray' }, - { label: 'Internet', value: 600, color: 'green' } - ] - }, - { - label: 'Cartão', - color: 'cyan', - data: [{ label: 'Empréstimos', value: 3000, color: 'brown' }] - }, - { - label: 'Carteira', - color: 'violet', - data: [ - { label: 'Transporte', value: 1500, color: 'blue' }, - { label: 'Alimentação', value: 5000, color: 'red' }, - { label: 'Outros', value: 700, color: 'blue' } - ] - } - ], - composicaoReceitas: [ - { - label: 'Conta 1', - color: 'lightblue', - data: [{ label: 'Salário', value: 18000, color: 'lightgreen' }] - } - ], - saldos: { - // Saldos relativos ao período - anteriorGeral: 0, - anterior1Ano: -10, - anterior6Meses: 0, - anterior1Mes: 0, - periodo: 0, - projetado1Mes: 0, - projetado6Meses: 10, - projetado1Ano: 0 - }, - vencimentos: { - quitadas: 7, - aVencer: 3, - emAtraso: 0 - }, - limites: [], - planejamentos: [] + receitaDespesaTotalPeriodo: [], + receitaDespesaEfetivadaPeriodo: [] } }) const sincronizarDashboard = async (inicio, fim) => { const { id } = uState.userData - console.log(id, inicio, fim) + const result = await getDashboard({ id, inicio, fim }) + store.dashboard = result } return { store, sincronizarDashboard } diff --git a/web-app-vue/src/stores/planejamentoStore.js b/web-app-vue/src/stores/planejamentoStore.js index a7a56d8..df32fd3 100644 --- a/web-app-vue/src/stores/planejamentoStore.js +++ b/web-app-vue/src/stores/planejamentoStore.js @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { useUserStore } from '@/stores/userStore' import { reactive } from 'vue' -import { getRedLine } from '@/services/redLine' +import { getRedLine, setRedLine } from '@/services/redLine' import { delPlanejamento, insertPlanejamento, listPlanejamentos, updatePlanejamento } from "@/services/api"; export const usePlanejamentoStore = defineStore('planejamento-store', () => { @@ -19,7 +19,11 @@ export const usePlanejamentoStore = defineStore('planejamento-store', () => { const sincronizarPlanejamentos = async () => { const { id } = uState.userData const { q, limit, offset } = store.filtroPlanejamentos + store.planejamentos = [] store.planejamentos = await listPlanejamentos({ id, q, limit, offset }) + const redline = getRedLine() + redline.planejamentos = [...store.planejamentos] + setRedLine(redline) } const salvaPlanejamento = async planejamento => { diff --git a/web-app-vue/vite.config.js b/web-app-vue/vite.config.js index 9dd9e8a..da421e4 100644 --- a/web-app-vue/vite.config.js +++ b/web-app-vue/vite.config.js @@ -7,6 +7,9 @@ import vuetify from 'vite-plugin-vuetify' // https://vitejs.dev/config/ export default defineConfig({ + define: { + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false + }, plugins: [vue(), /*VueDevtools(),*/ vuetify()], resolve: { alias: {