├── client
├── .env
├── src
│ ├── assets
│ │ ├── soho-dark
│ │ │ └── fonts
│ │ │ │ ├── lato-v17-latin-ext_latin-300.woff
│ │ │ │ ├── lato-v17-latin-ext_latin-700.woff
│ │ │ │ ├── lato-v17-latin-ext_latin-300.woff2
│ │ │ │ ├── lato-v17-latin-ext_latin-700.woff2
│ │ │ │ ├── lato-v17-latin-ext_latin-regular.woff
│ │ │ │ └── lato-v17-latin-ext_latin-regular.woff2
│ │ ├── soho-light
│ │ │ └── fonts
│ │ │ │ ├── lato-v17-latin-ext_latin-300.woff
│ │ │ │ ├── lato-v17-latin-ext_latin-300.woff2
│ │ │ │ ├── lato-v17-latin-ext_latin-700.woff
│ │ │ │ ├── lato-v17-latin-ext_latin-700.woff2
│ │ │ │ ├── lato-v17-latin-ext_latin-regular.woff
│ │ │ │ └── lato-v17-latin-ext_latin-regular.woff2
│ │ ├── favicon.svg
│ │ └── logo.svg
│ ├── main.jsx
│ ├── i18n
│ │ ├── i18n.js
│ │ ├── translationEN.json
│ │ ├── translationES.json
│ │ ├── translationIT.json
│ │ ├── translationDE.json
│ │ └── translationFR.json
│ ├── Footer.jsx
│ ├── finances
│ │ ├── components
│ │ │ └── ResumeAccounts.jsx
│ │ ├── buckets
│ │ │ ├── ListBucket.jsx
│ │ │ ├── EditBucket.jsx
│ │ │ ├── CreateBucket.jsx
│ │ │ └── Buckets.jsx
│ │ ├── accounts
│ │ │ ├── Accounts.jsx
│ │ │ ├── AddAccounts.jsx
│ │ │ └── EditAccount.jsx
│ │ ├── transactions
│ │ │ ├── ListTransaction.jsx
│ │ │ └── AddTransactions.jsx
│ │ ├── vaults
│ │ │ ├── ListVault.jsx
│ │ │ ├── ListVaultTransactions.jsx
│ │ │ ├── Vaults.jsx
│ │ │ ├── CreateVault.jsx
│ │ │ └── EditVault.jsx
│ │ ├── expense
│ │ │ ├── ListExpenses.jsx
│ │ │ └── AddExpense.jsx
│ │ └── Listing.jsx
│ ├── App.jsx
│ ├── index.css
│ └── Layout.jsx
├── vite.config.js
├── Dockerfile
├── .gitignore
├── README.md
├── .eslintrc.cjs
├── index.html
├── package.json
├── nginx.conf
└── public
│ └── vite.svg
├── server
├── Dockerfile
├── controllers
│ ├── category
│ │ ├── addCategory.controller.js
│ │ └── allCategory.controller.js
│ ├── notifications
│ │ └── notifications.controller.js
│ ├── vaults
│ │ ├── vaultList.controller.js
│ │ ├── transactionVaultList.controller.js
│ │ ├── createVault.controller.js
│ │ ├── editVault.controller.js
│ │ └── vault.controller.js
│ ├── buckets
│ │ ├── listBucket.controller.js
│ │ ├── editBucket.controller.js
│ │ └── buckets.controller.js
│ ├── expense
│ │ ├── expensesList.controller.js
│ │ └── addExpense.controller.js
│ ├── transactions
│ │ ├── transactionsList.controller.js
│ │ ├── transactions.controller.js
│ │ └── transactionVault.controller.js
│ ├── edit
│ │ ├── deleteBucket.controller.js
│ │ ├── deleteVault.controller.js
│ │ ├── deleteAccount.controller.js
│ │ ├── deleteExpense.controller.js
│ │ ├── deleteTransaction.controller.js
│ │ ├── deleteTransactionVault.controller.js
│ │ ├── vaultCell.controller.js
│ │ └── bucketCell.controller.js
│ └── accounts
│ │ ├── accounts.controller.js
│ │ └── editAccount.controller.js
├── package.json
├── models
│ ├── monthlyVaults.js
│ ├── accounts.js
│ ├── vaults.js
│ ├── monthlyBuckets.js
│ ├── expenses.js
│ ├── transactionsVaults.js
│ ├── buckets.js
│ └── transactions.js
├── server.js
└── routes
│ ├── edit.js
│ └── finance.js
├── docker-compose.yml
├── .gitignore
└── README.md
/client/.env:
--------------------------------------------------------------------------------
1 | VITE_BACKEND_ADRESS=http://localhost
2 | VITE_BACKEND_PORT=3000
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18
2 | WORKDIR /app
3 | COPY package*.json ./
4 | RUN npm install
5 | COPY . .
6 | EXPOSE 3000
7 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-300.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-700.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-300.woff2
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-700.woff2
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-300.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-300.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-300.woff2
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-700.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-700.woff2
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-regular.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-dark/fonts/lato-v17-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-regular.woff
--------------------------------------------------------------------------------
/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KayatoSan/Budget5S/HEAD/client/src/assets/soho-light/fonts/lato-v17-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/server/controllers/category/addCategory.controller.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const get = async (req, res, next) => {
4 |
5 | };
6 |
7 | const post = async (req, res, next) => {
8 |
9 | };
10 |
11 | module.exports = {
12 | get,
13 | post,
14 | };
15 |
--------------------------------------------------------------------------------
/server/controllers/category/allCategory.controller.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const get = async (req, res, next) => {
4 |
5 | };
6 |
7 | const post = async (req, res, next) => {
8 |
9 | };
10 |
11 | module.exports = {
12 | get,
13 | post,
14 | };
15 |
--------------------------------------------------------------------------------
/server/controllers/notifications/notifications.controller.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const get = async (req, res, next) => {
4 |
5 | };
6 |
7 | const post = async (req, res, next) => {
8 |
9 |
10 | };
11 |
12 | module.exports = {
13 | get,
14 | post,
15 | };
16 |
--------------------------------------------------------------------------------
/client/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18 as build
2 | WORKDIR /app
3 | COPY package*.json ./
4 | RUN npm install
5 | COPY . .
6 | RUN npm run build
7 |
8 | FROM nginx:alpine
9 | COPY nginx.conf /etc/nginx/conf.d/default.conf
10 | COPY --from=build /app/dist /usr/share/nginx/html
11 | EXPOSE 80
12 | CMD ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/server/controllers/vaults/vaultList.controller.js:
--------------------------------------------------------------------------------
1 | const dbVaults = require("../../models/vaults");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbVaults.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 |
12 | };
13 |
14 | module.exports = {
15 | get,
16 | post,
17 | };
18 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/server/controllers/buckets/listBucket.controller.js:
--------------------------------------------------------------------------------
1 | const dbBuckets = require("../../models/buckets");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbBuckets.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 |
12 | };
13 |
14 | module.exports = {
15 | get,
16 | post,
17 | };
18 |
--------------------------------------------------------------------------------
/server/controllers/expense/expensesList.controller.js:
--------------------------------------------------------------------------------
1 | const dbExpenses = require("../../models/expenses");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbExpenses.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 |
12 | };
13 |
14 | module.exports = {
15 | get,
16 | post,
17 | };
18 |
--------------------------------------------------------------------------------
/server/controllers/transactions/transactionsList.controller.js:
--------------------------------------------------------------------------------
1 | const dbTransactions = require("../../models/transactions");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbTransactions.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 |
12 | };
13 |
14 | module.exports = {
15 | get,
16 | post,
17 | };
18 |
--------------------------------------------------------------------------------
/server/controllers/vaults/transactionVaultList.controller.js:
--------------------------------------------------------------------------------
1 | const dbTransactionsVault = require("../../models/transactionsVaults");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbTransactionsVault.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 |
12 | };
13 |
14 | module.exports = {
15 | get,
16 | post,
17 | };
18 |
--------------------------------------------------------------------------------
/server/controllers/edit/deleteBucket.controller.js:
--------------------------------------------------------------------------------
1 | const dbBuckets = require('../../models/buckets')
2 |
3 | const post = async (req, res, next) => {
4 | console.log(req.params.id, "was deleted")
5 | try {
6 | await dbBuckets.findByIdAndDelete(req.params.id)
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
17 |
--------------------------------------------------------------------------------
/server/controllers/edit/deleteVault.controller.js:
--------------------------------------------------------------------------------
1 | const dbVaults = require('../../models/vaults')
2 |
3 | const post = async (req, res, next) => {
4 | console.log(req.params.id, "was deleted")
5 | try {
6 | await dbVaults.findByIdAndDelete(req.params.id)
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
17 |
--------------------------------------------------------------------------------
/server/controllers/edit/deleteAccount.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require('../../models/accounts')
2 |
3 | const post = async (req, res, next) => {
4 | console.log(req.params.id, "was deleted")
5 | try {
6 | await dbAccounts.findByIdAndDelete(req.params.id)
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
17 |
--------------------------------------------------------------------------------
/server/controllers/edit/deleteExpense.controller.js:
--------------------------------------------------------------------------------
1 | const dbExpenses = require('../../models/expenses')
2 |
3 | const post = async (req, res, next) => {
4 | try {
5 | await dbExpenses.findByIdAndDelete(req.params.id)
6 | console.log(req.params.id, "was deleted")
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | client:
4 | build:
5 | context: ./client
6 | dockerfile: Dockerfile
7 | ports:
8 | - "8000:80"
9 | depends_on:
10 | - server
11 | server:
12 | build:
13 | context: ./server
14 | dockerfile: Dockerfile
15 | ports:
16 | - "3000:3000"
17 | depends_on:
18 | - mongo
19 | mongo:
20 | image: mongo
21 | ports:
22 | - "27017:27017"
--------------------------------------------------------------------------------
/server/controllers/edit/deleteTransaction.controller.js:
--------------------------------------------------------------------------------
1 | const dbTransactions = require('../../models/transactions')
2 |
3 | const post = async (req, res, next) => {
4 | try {
5 | await dbTransactions.findByIdAndDelete(req.params.id)
6 | console.log(req.params.id, "was deleted")
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
17 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.1",
4 | "description": "Praise the golden sun",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "body-parser": "^1.20.2",
13 | "cors": "^2.8.5",
14 | "express": "^4.18.3",
15 | "http": "^0.0.1-security",
16 | "mongoose": "^8.2.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/server/controllers/edit/deleteTransactionVault.controller.js:
--------------------------------------------------------------------------------
1 | const dbTransactionsVaults = require('../../models/transactionsVaults')
2 |
3 | const post = async (req, res, next) => {
4 | try {
5 | await dbTransactionsVaults.findByIdAndDelete(req.params.id)
6 | console.log(req.params.id, "was deleted")
7 | next()
8 | } catch (err) {
9 | console.error(err)
10 | next()
11 | }
12 | };
13 |
14 | module.exports = {
15 | post,
16 | };
17 |
--------------------------------------------------------------------------------
/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import './i18n/i18n';
5 | import "./index.css";
6 |
7 | // PRIME REACT
8 | import { PrimeReactProvider } from "primereact/api";
9 | import "primeflex/primeflex.css";
10 | import "primereact/resources/primereact.css";
11 | import "primeicons/primeicons.css";
12 |
13 | ReactDOM.createRoot(document.getElementById("root")).render(
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Budget5S
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/server/controllers/accounts/accounts.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 |
3 | const get = async (req, res, next) => {
4 | const data = await dbAccounts.find()
5 | res.send({
6 | data
7 | })
8 | };
9 |
10 | const post = async (req, res, next) => {
11 | try {
12 | await dbAccounts.create({
13 | label: req.body.data.label,
14 | balance: req.body.data.balance,
15 | creationDate: Date.now(),
16 | assignable: req.body.assignable,
17 | });
18 | res.redirect('/accounts/')
19 | } catch (err) {
20 | console.error(err);
21 | }
22 |
23 | };
24 |
25 | module.exports = {
26 | get,
27 | post,
28 | };
29 |
--------------------------------------------------------------------------------
/server/controllers/edit/vaultCell.controller.js:
--------------------------------------------------------------------------------
1 | const dbMonthlyVaults = require("../../models/monthlyVaults");
2 |
3 | const post = async (req, res, next) => {
4 |
5 | switch (req.body.field) {
6 | case "monthly.assigned":
7 | await dbMonthlyVaults.findByIdAndUpdate(
8 | req.body.id,
9 | { assigned: req.body.value },
10 | { new: true }
11 | );
12 | break;
13 | case "monthly.target":
14 | await dbMonthlyVaults.findByIdAndUpdate(
15 | req.body.id,
16 | {target: req.body.value},
17 | {new: true}
18 | );
19 | break;
20 |
21 | default:
22 | break;
23 | }
24 | };
25 |
26 | module.exports = {
27 | post,
28 | };
29 |
--------------------------------------------------------------------------------
/server/controllers/edit/bucketCell.controller.js:
--------------------------------------------------------------------------------
1 | const dbMonthlyBuckets = require("../../models/monthlyBuckets");
2 |
3 | const post = async (req, res, next) => {
4 |
5 | switch (req.body.field) {
6 | case "monthly.assigned":
7 | await dbMonthlyBuckets.findByIdAndUpdate(
8 | req.body.id,
9 | { assigned: req.body.value },
10 | { new: true }
11 | );
12 | break;
13 | case "monthly.target":
14 | await dbMonthlyBuckets.findByIdAndUpdate(
15 | req.body.id,
16 | {target: req.body.value},
17 | {new: true}
18 | );
19 | break;
20 |
21 | default:
22 | break;
23 | }
24 | };
25 |
26 | module.exports = {
27 | post,
28 | };
29 |
--------------------------------------------------------------------------------
/server/models/monthlyVaults.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const monthlyVaults = new mongoose.Schema({
4 | vaultRef: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "vaults",
7 | required: true,
8 | },
9 | assigned: { type: Number, required: true },
10 | month: { type: String, required: false },
11 | year: { type: String, required: false },
12 | date: { type: Date, required: true },
13 | });
14 |
15 | monthlyVaults.pre("save", function (next) {
16 | if (this.isNew) {
17 | const date = this.date;
18 | this.month = date.getMonth() + 1;
19 | this.year = date.getFullYear();
20 | }
21 | next();
22 | });
23 |
24 | module.exports = mongoose.model("monthlyvaults", monthlyVaults);
25 |
--------------------------------------------------------------------------------
/server/controllers/accounts/editAccount.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 |
3 | const get = async (req, res, next) => {
4 | try {
5 | const data = await dbAccounts.findById(req.params.id);
6 | res.send({
7 | data,
8 | });
9 | } catch (err) {
10 | console.error(err);
11 | }
12 | };
13 |
14 | const post = async (req, res, next) => {
15 | const body = req.body;
16 | try {
17 | await dbAccounts.findOneAndUpdate(
18 | { _id: req.params.id },
19 | {
20 | label: body.data.label,
21 | balance: body.data.balance,
22 | assignable: body.assignable,
23 | }
24 | );
25 | } catch (err) {
26 | console.error(err);
27 | }
28 | };
29 |
30 | module.exports = {
31 | get,
32 | post,
33 | };
34 |
--------------------------------------------------------------------------------
/server/models/accounts.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const accounts = new mongoose.Schema({
4 | label: { type: String, required: true },
5 | balance: { type: Number, required: true },
6 | clearBalance: { type: Number, required: false },
7 | unClearBalance: { type: Number, required: false },
8 | currency: { type: String, required: false },
9 | creationDate: { type: Date, required: true },
10 | transactionsLinked: [
11 | {
12 | type: mongoose.Schema.Types.ObjectId,
13 | ref: "transactions",
14 | },
15 | ],
16 | color: { type: String, required: false }, // FOR THE STYLE
17 | icon: { type: String, required: false }, // FOR THE STYLE
18 | assignable: {type: Boolean, required: false, default: true}
19 | });
20 |
21 | module.exports = mongoose.model("accounts", accounts);
22 |
--------------------------------------------------------------------------------
/server/models/vaults.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const vaults = new mongoose.Schema({
4 | label: { type: String, required: true },
5 | accountLinked: { type: mongoose.Schema.Types.ObjectId, ref: "accounts" },
6 | monthlyType: { type: Boolean, required: true },
7 | dateLimit: { type: Date, required: false },
8 | UNIXLimit: {
9 | type: Number,
10 | required: false,
11 | default: 253636783200000 /* = 999/99/99 */,
12 | },
13 | dateDue: { type: Date, required: false },
14 | dayDue: { type: Number, required: false },
15 | amount: { type: Number, required: false },
16 | balance: { type: Number, required: true },
17 | target: { type: Number, required: false },
18 | archived: {type: Boolean, required: true},
19 | });
20 |
21 |
22 | module.exports = mongoose.model("vaults", vaults);
23 |
--------------------------------------------------------------------------------
/server/models/monthlyBuckets.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const monthlyBuckets = new mongoose.Schema({
4 | bucketRef: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "buckets",
7 | required: true,
8 | },
9 | assigned: { type: Number, required: true },
10 | month: { type: String, required: false },
11 | year: { type: String, required: false },
12 | date: { type: Date, required: true },
13 | UNIXDate: {type: Number, required: true},
14 | target: {type: Number, required: false},
15 | });
16 |
17 | monthlyBuckets.pre("save", function (next) {
18 | if (this.isNew) {
19 | const date = this.date;
20 | this.month = date.getMonth() + 1;
21 | this.year = date.getFullYear();
22 | }
23 | next();
24 | });
25 |
26 | module.exports = mongoose.model("monthlybuckets", monthlyBuckets);
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | // IMPORT
2 | const mongoose = require("mongoose").mongoose.connect(
3 | "mongodb://mongo:27017/budget5s"
4 | );
5 | const cors = require("cors");
6 | const express = require("express");
7 | const app = express();
8 | const http = require("http").createServer(app);
9 | const bodyParser = require("body-parser");
10 |
11 | // ROUTES
12 | const financeRoutes = require("./routes/finance");
13 | const editDataRoutes = require("./routes/edit");
14 |
15 | // CRUD
16 | app.use(bodyParser.json());
17 | app.use(
18 | cors({
19 | origin: "*",
20 | methods: ["GET", "POST"],
21 | allowedHeaders: ["Content-Type", "Authorization"],
22 | maxAge: 600,
23 | })
24 | );
25 |
26 | // ROUTES CALL
27 | app.use(financeRoutes, editDataRoutes);
28 |
29 | // 🚀 LAUNCH
30 | http.listen(3000, () => {
31 | console.log("🚀 Started on 3000");
32 | });
33 |
--------------------------------------------------------------------------------
/server/models/expenses.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const expenses = new mongoose.Schema({
4 | label: { type: String, required: false },
5 | date: { type: Date, required: true },
6 | month: { type: String, required: false },
7 | year: { type: String, required: false },
8 | amount: { type: Number, required: true },
9 | bucketRef: { type: mongoose.Schema.Types.ObjectId, ref: "buckets" },
10 | accountRef: { type: mongoose.Schema.Types.ObjectId, ref: "accounts" },
11 | UNIXDate: { type: Number, required: false },
12 | });
13 |
14 | expenses.pre("save", function (next) {
15 | if (this.isNew) {
16 | const date = this.date;
17 | this.month = date.getMonth() + 1;
18 | this.year = date.getFullYear();
19 | this.UNIXDate = new Date(date.getFullYear(), date.getMonth(), "1");
20 | }
21 | next();
22 | });
23 |
24 | module.exports = mongoose.model("expenses", expenses);
25 |
--------------------------------------------------------------------------------
/client/src/i18n/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 |
4 | import translationEN from './translationEN.json'
5 | import translationFR from './translationFR.json'
6 | import translationES from './translationES.json'
7 | import translationIT from './translationIT.json'
8 | import translationDE from './translationDE.json'
9 |
10 | const resources = {
11 | en: {
12 | translation: translationEN
13 | },
14 | fr: {
15 | translation: translationFR
16 | },
17 | es: {
18 | translation: translationES
19 | },
20 | it: {
21 | translation: translationIT
22 | },
23 | de: {
24 | translation: translationDE
25 | }
26 | };
27 |
28 | i18n
29 | .use(initReactI18next)
30 | .init({
31 | resources,
32 | lng: "en",
33 | fallbackLng: "en",
34 |
35 | interpolation: {
36 | escapeValue: false
37 | }
38 | });
39 |
40 | export default i18n;
--------------------------------------------------------------------------------
/server/controllers/vaults/createVault.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 | const dbVaults = require("../../models/vaults");
3 |
4 | const get = async (req, res, next) => {
5 | const dataAccounts = await dbAccounts.find();
6 | res.send({
7 | dataAccounts,
8 | });
9 | };
10 |
11 | const post = async (req, res, next) => {
12 | try {
13 | await dbVaults.create({
14 | label: req.body.label,
15 | balance: 0,
16 | UNIXLimit: 253636783200000,
17 | dateDue: req.body.dateDue,
18 | creationDate: Date.now(),
19 | archived: false,
20 | accountLinked: req.body.accountLinked,
21 | monthlyType: req.body.monthlyType,
22 | dateDue: req.body.dateDue,
23 | target: req.body.target,
24 | });
25 | } catch (err) {
26 | console.error(err);
27 | }
28 | };
29 |
30 | module.exports = {
31 | get,
32 | post,
33 | };
34 |
--------------------------------------------------------------------------------
/server/models/transactionsVaults.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const transactionsVaults = new mongoose.Schema({
4 | label: { type: String, required: false },
5 | date: { type: Date, required: true },
6 | month: { type: String, required: false },
7 | year: { type: String, required: false },
8 | UNIXDate: { type: Number, required: false },
9 | amount: { type: Number, required: true },
10 | vaultRef: { type: mongoose.Schema.Types.ObjectId, ref: "buckets" },
11 | accountRef: { type: mongoose.Schema.Types.ObjectId, ref: "accounts" },
12 | });
13 |
14 | transactionsVaults.pre("save", function (next) {
15 | if (this.isNew) {
16 | const date = this.date;
17 | this.month = date.getMonth() + 1;
18 | this.year = date.getFullYear();
19 | this.UNIXDate = new Date(this.year, this.month, "1");
20 | }
21 | next();
22 | });
23 |
24 | module.exports = mongoose.model("transactionsvaults", transactionsVaults);
25 |
--------------------------------------------------------------------------------
/server/models/buckets.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const buckets = new mongoose.Schema({
4 | label: { type: String, required: true },
5 | amount: { type: Number, default: 0, required: true },
6 | balance: { type: Number, required: false },
7 | defaultTarget: { type: Number, default: 0, required: true },
8 | creationDate: { type: Date, required: true },
9 | targeted: { type: Boolean, default: 0, required: false },
10 | dayDue: { type: String, required: false },
11 | type: { type: String, required: false }, //
12 | category: { type: String, required: false }, //
13 | dateLimit: { type: Date, required: false },
14 | UNIXLimit: {
15 | type: Number,
16 | required: true,
17 | default: 253636783200000 /* = 999/99/99 */,
18 | },
19 | note: { type: String, required: false }, //
20 | color: { type: String, required: false }, //
21 | icon: { type: String, required: false }, //
22 | });
23 |
24 |
25 | module.exports = mongoose.model("buckets", buckets);
26 |
--------------------------------------------------------------------------------
/server/controllers/buckets/editBucket.controller.js:
--------------------------------------------------------------------------------
1 | const dbBuckets = require("../../models/buckets");
2 |
3 | const get = async (req, res, next) => {
4 | try {
5 | const findBucket = await dbBuckets.findById(req.params.id).lean();
6 | res.send({
7 | data: findBucket,
8 | });
9 | } catch (err) {
10 | console.error(err);
11 | }
12 | }
13 |
14 | const post = async (req, res, next) => {
15 | const body = req.body
16 | const date = new Date(req.body.date)
17 | const unixDate = date.getTime()
18 | await dbBuckets.findOneAndUpdate({_id: body.data._id}, {
19 | label: body.data.label,
20 | type: body.data.type,
21 | category: body.data.category,
22 | color: body.data.color,
23 | monthly: body.data.monthly,
24 | amount: body.data.amount,
25 | balance: body.data.balance,
26 | dateLimit: body.date,
27 | UNIXLimit: unixDate,
28 | targeted: body.data.targeted
29 |
30 | })
31 | }
32 |
33 |
34 | module.exports = {
35 | get,
36 | post,
37 | };
38 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite --host",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@tabler/icons-react": "^3.1.0",
14 | "i18next": "^23.13.0",
15 | "primeflex": "^3.3.1",
16 | "primeicons": "^6.0.1",
17 | "primereact": "^10.5.1",
18 | "react": "^18.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-i18next": "^15.0.1",
21 | "react-router-dom": "^6.22.3"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.2.64",
25 | "@types/react-dom": "^18.2.21",
26 | "@vitejs/plugin-react": "^4.2.1",
27 | "eslint": "^8.57.0",
28 | "eslint-plugin-react": "^7.34.0",
29 | "eslint-plugin-react-hooks": "^4.6.0",
30 | "eslint-plugin-react-refresh": "^0.4.5",
31 | "vite": "^5.1.6"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/models/transactions.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const transactions = new mongoose.Schema({
4 | accountRef: { type: mongoose.Schema.Types.ObjectId, ref: "accounts" },
5 | label: { type: String, required: false },
6 | amount: { type: Number, required: true },
7 | currency: { type: String, required: false },
8 | date: { type: Date, required: true },
9 | month: { type: String, required: false },
10 | year: { type: String, required: false },
11 | UNIXDate: { type: Number, required: false },
12 | color: { type: String, required: false }, // FOR THE STYLE
13 | icon: { type: String, required: false }, // FOR THE STYLE
14 | });
15 |
16 | transactions.pre("save", function (next) {
17 | if (this.isNew) {
18 | const date = this.date;
19 | this.month = date.getMonth() + 1;
20 | this.year = date.getFullYear();
21 | this.UNIXDate = new Date(this.year, this.month, "1");
22 | }
23 | next();
24 | });
25 |
26 | module.exports = mongoose.model("transactions", transactions);
27 |
--------------------------------------------------------------------------------
/server/controllers/vaults/editVault.controller.js:
--------------------------------------------------------------------------------
1 | const dbVaults = require("../../models/vaults");
2 | const dbAccounts = require("../../models/accounts");
3 |
4 | const get = async (req, res, next) => {
5 | try {
6 | const dataVaults = await dbVaults.findById(req.params.id);
7 | const dataAccounts = await dbAccounts.findById(dataVaults.accountLinked);
8 | const listAccounts = await dbAccounts.find()
9 | res.send({
10 | dataVaults,
11 | dataAccounts,
12 | listAccounts,
13 | });
14 | } catch (err) {
15 | console.error(err);
16 | }
17 | }
18 |
19 | const post = async (req, res, next) => {
20 | const body = req.body.data
21 | const date = new Date(body.date)
22 | const unixDate = date.getTime()
23 | await dbVaults.findOneAndUpdate({_id: req.params.id}, {
24 | label: body.label,
25 | accountLinked: body.accountLinked,
26 | monthlyType: body.monthly,
27 | dateDue: date,
28 | UNIXLimit: 253636783200000,
29 | })
30 | }
31 |
32 |
33 | module.exports = {
34 | get,
35 | post,
36 | };
--------------------------------------------------------------------------------
/client/src/Footer.jsx:
--------------------------------------------------------------------------------
1 | import { useTranslation } from 'react-i18next';
2 | import { Dropdown } from 'primereact/dropdown';
3 |
4 | function Footer() {
5 | const {t, i18n} = useTranslation()
6 | const languages = [
7 | { name: 'English', code: 'en' },
8 | { name: 'Français', code: 'fr' },
9 | { name: 'Italiano', code: 'it' },
10 | { name: 'Español', code: 'es' },
11 | { name: 'German', code: 'de' }
12 | ];
13 |
14 | const changeLanguage = (e) => {
15 | i18n.changeLanguage(e.value.code);
16 | };
17 | return (
18 | <>
19 |
20 |
21 |
22 | {t('Made to help anybody')}
23 | lang.code === i18n.language)}
25 | options={languages}
26 | onChange={changeLanguage}
27 | optionLabel="name"
28 | placeholder="Select a Language"
29 | className="p-mb-3 max-h-3rem flex align-items-center ml-6 justify-content-center"
30 | />
31 |
32 |
33 |
34 |
35 | >
36 | );
37 | }
38 |
39 | export default Footer;
40 |
--------------------------------------------------------------------------------
/client/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 | server_name localhost;
5 |
6 | #access_log /var/log/nginx/host.access.log main;
7 |
8 | location / {
9 | root /usr/share/nginx/html;
10 | try_files $uri $uri/ /index.html;
11 | index index.html index.htm;
12 | }
13 |
14 | #error_page 404 /404.html;
15 |
16 | # redirect server error pages to the static page /50x.html
17 | #
18 | error_page 500 502 503 504 /50x.html;
19 | location = /50x.html {
20 | root /usr/share/nginx/html;
21 | }
22 |
23 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80
24 | #
25 | #location ~ \.php$ {
26 | # proxy_pass http://127.0.0.1;
27 | #}
28 |
29 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
30 | #
31 | #location ~ \.php$ {
32 | # root html;
33 | # fastcgi_pass 127.0.0.1:9000;
34 | # fastcgi_index index.php;
35 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
36 | # include fastcgi_params;
37 | #}
38 |
39 | # deny access to .htaccess files, if Apache's document root
40 | # concurs with nginx's one
41 | #
42 | #location ~ /\.ht {
43 | # deny all;
44 | #}
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/server/controllers/expense/addExpense.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 | const dbBuckets = require("../../models/buckets");
3 | const dbTransactions = require("../../models/transactions");
4 | const dbExpenses = require("../../models/expenses");
5 |
6 | const get = async (req, res, next) => {
7 | const dataAccounts = await dbAccounts.find();
8 | const dataBuckets = await dbBuckets.find();
9 | res.send({
10 | dataAccounts,
11 | dataBuckets,
12 | });
13 | };
14 |
15 | const post = async (req, res, next) => {
16 | try {
17 | const account = await dbAccounts.findById(req.body.account);
18 | await dbAccounts.findByIdAndUpdate(
19 | req.body.account,
20 | { balance: account.balance - req.body.amount },
21 | { new: true }
22 | );
23 | await dbExpenses.create({
24 | label: req.body.label,
25 | amount: req.body.amount,
26 | date: req.body.date,
27 | bucketRef: req.body.bucket,
28 | accountRef: req.body.account
29 | });
30 | const bucket = await dbBuckets.findById(req.body.bucket)
31 | await dbBuckets.findByIdAndUpdate(req.body.bucket,
32 | {balance : bucket.balance - req.body.amount},
33 | {new : true}
34 | )
35 | } catch (err) {
36 | console.error(err);
37 | }
38 | };
39 |
40 | module.exports = {
41 | get,
42 | post,
43 | };
44 |
--------------------------------------------------------------------------------
/server/controllers/transactions/transactions.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 | const dbBuckets = require("../../models/buckets");
3 | const dbTransactions = require("../../models/transactions")
4 |
5 | const get = async (req, res, next) => {
6 | const dataAccounts = await dbAccounts.find();
7 | res.send({
8 | dataAccounts,
9 | });
10 | };
11 |
12 | const post = async (req, res, next) => {
13 | if (req.body.options === true) {
14 | const newBalance = req.body.oldBalance + req.body.amount;
15 | await dbAccounts.findByIdAndUpdate(
16 | req.body.account,
17 | { balance: newBalance },
18 | { new: true }
19 | );
20 | await dbTransactions.create({
21 | label: req.body.label,
22 | amount: req.body.amount,
23 | date: req.body.date,
24 | accountRef: req.body.account,
25 | });
26 | } else {
27 | const newBalance = req.body.oldBalance - req.body.amount;
28 | await dbAccounts.findByIdAndUpdate(
29 | req.body.account,
30 | { balance: newBalance },
31 | { new: true }
32 | );
33 | await dbTransactions.create({
34 | label: req.body.label,
35 | amount: -req.body.amount,
36 | date: req.body.date,
37 | accountRef: req.body.account,
38 | });
39 | }
40 | };
41 |
42 | module.exports = {
43 | get,
44 | post,
45 | };
46 |
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/controllers/transactions/transactionVault.controller.js:
--------------------------------------------------------------------------------
1 | const dbAccounts = require("../../models/accounts");
2 | const dbVaults = require("../../models/vaults");
3 | const dbTransactions = require("../../models/transactions");
4 | const dbTransactionsVault = require("../../models/transactionsVaults");
5 |
6 | const get = async (req, res, next) => {
7 | const dataAccounts = await dbAccounts.find();
8 | const dataVaults = await dbVaults.find();
9 | res.send({
10 | dataAccounts,
11 | dataVaults,
12 | });
13 | };
14 |
15 | const post = async (req, res, next) => {
16 | try {
17 | // Update the source account
18 | await dbAccounts.findByIdAndUpdate(
19 | req.body.sourceAccount,
20 | { $inc: { balance: -req.body.amount } },
21 | { new: true }
22 | );
23 | // Update the linked account to the vault
24 | await dbAccounts.findByIdAndUpdate(
25 | req.body.accountLinked,
26 | { $inc: { balance: req.body.amount } },
27 | { new: true }
28 | );
29 | // Update the balance of the vault for non-monhtly type
30 | await dbVaults.findByIdAndUpdate(req.body.vault, {
31 | $inc: { balance: req.body.amount },
32 | });
33 | await dbTransactionsVault.create({
34 | label: req.body.label,
35 | amount: req.body.amount,
36 | date: req.body.date,
37 | vaultRef: req.body.vault,
38 | accountRef: req.body.accountLinked,
39 | });
40 | } catch (err) {
41 | console.error(err);
42 | }
43 | };
44 |
45 | module.exports = {
46 | get,
47 | post,
48 | };
49 |
--------------------------------------------------------------------------------
/server/routes/edit.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const editBucket = require("../controllers/buckets/editBucket.controller");
5 | const editVault = require("../controllers/vaults/editVault.controller");
6 | const editAccount = require("../controllers/accounts/editAccount.controller")
7 |
8 |
9 | const bucketCell = require("../controllers/edit/bucketCell.controller")
10 | const vaultCell = require("../controllers/edit/vaultCell.controller")
11 |
12 | const deleteBucket = require("../controllers/edit/deleteBucket.controller")
13 | const deleteVault = require("../controllers/edit/deleteVault.controller")
14 | const deleteAccount = require("../controllers/edit/deleteAccount.controller")
15 | const deleteTransaction = require("../controllers/edit/deleteTransaction.controller")
16 | const deleteTransactionVault = require("../controllers/edit/deleteTransactionVault.controller")
17 | const deleteExpense = require("../controllers/edit/deleteExpense.controller")
18 |
19 | router.post("/edit/bucketcell", bucketCell.post);
20 |
21 | router.post("/edit/vaultcell", vaultCell.post);
22 |
23 | router.get("/edit/bucket/:id/", editBucket.get);
24 | router.post("/edit/bucket/:id/", editBucket.post);
25 |
26 |
27 | router.get("/edit/vault/:id/", editVault.get);
28 | router.post("/edit/vault/:id/", editVault.post);
29 |
30 | router.get("/edit/account/:id/", editAccount.get);
31 | router.post("/edit/account/:id/", editAccount.post);
32 |
33 |
34 | // DELETE
35 |
36 | router.post("/edit/bucket/delete/:id/", deleteBucket.post)
37 | router.post("/edit/vault/delete/:id/", deleteVault.post)
38 | router.post("/edit/account/delete/:id/", deleteAccount.post)
39 | router.post("/edit/transaction/delete/:id/", deleteTransaction.post)
40 | router.post("/edit/transaction/vault/delete/:id/", deleteTransactionVault.post)
41 | router.post("/edit/expense/delete/:id/", deleteExpense.post)
42 |
43 | module.exports = router;
44 |
--------------------------------------------------------------------------------
/client/src/finances/components/ResumeAccounts.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | import { useTranslation } from 'react-i18next';
4 |
5 | const ResumeAccounts = () => {
6 | const {t, i18n} = useTranslation()
7 |
8 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
9 | const [sumBalance, setSumBalance] = useState(0)
10 | const [balanceAssignable, setSumBalanceAssignable] = useState(0)
11 | const [balanceUnassignable, setSumBalanceUnassignable] = useState(0)
12 | const [data, setData] = useState([]);
13 |
14 |
15 |
16 | const fetchData = async () => {
17 | try {
18 | const fetchGet = await fetch(urlAPI),
19 | fetchResponse = await fetchGet.json();
20 | return fetchResponse.data;
21 | } catch (error) {
22 | console.error("An error occurred while executing fetchData() : ", error);
23 | }
24 | };
25 |
26 | const showAccounts = data.map((account) => (
27 |
31 |
32 |
{account.label}
33 |
{account.balance} €
34 |
35 |
36 | ));
37 |
38 | useEffect(() => {
39 |
40 | fetchData().then((data) => {
41 | setData(data);
42 | setSumBalance(() => data.reduce((acc, curr) => acc + curr.balance, 0))
43 | setSumBalanceAssignable(() => data.filter(object => object.assignable).reduce((acc, curr) => acc + curr.balance, 0))
44 | setSumBalanceUnassignable(() => data.filter(object => !object.assignable).reduce((acc, curr) => acc + curr.balance, 0))
45 |
46 | });
47 |
48 | console.log(balanceAssignable)
49 | }, []);
50 |
51 | return (
52 | <>
53 | {showAccounts}
54 |
55 |
56 |
57 |
{t('Total on your account')} : {sumBalance} €
58 |
{t('Total unassignable')} : -{balanceUnassignable} €
59 |
{t('Total assignable')} : {balanceAssignable} €
60 |
61 |
62 |
63 | >
64 | );
65 | };
66 |
67 | export default ResumeAccounts;
68 |
--------------------------------------------------------------------------------
/server/controllers/vaults/vault.controller.js:
--------------------------------------------------------------------------------
1 | const dbVault = require("../../models/vaults");
2 | const dbMonthlyVaults = require("../../models/monthlyVaults");
3 | const dbTransactionsVaults = require("../../models/transactionsVaults");
4 |
5 | const get = async (req, res, next) => {
6 | try {
7 | const month = req.params.month,
8 | formatedMonth = month < 10 ? `0${month}` : `${month}`;
9 | const dateFilter = new Date(`${req.params.year}-${formatedMonth}`);
10 |
11 | const findAllVault = await dbVault
12 | .find({ UNIXLimit: { $gt: (dateFilter.getTime()) }, archived: false})
13 | .lean();
14 |
15 | async function monthly(idVault, date) {
16 | const checkData = await dbMonthlyVaults
17 | .findOne({ vaultRef: idVault, date: date })
18 | .lean();
19 |
20 | if (checkData !== null) {
21 | return checkData;
22 | } else {
23 | await dbMonthlyVaults.create({
24 | vaultRef: idVault,
25 | assigned: 0,
26 | date: dateFilter,
27 | month: req.params.month,
28 | year: req.params.year,
29 | });
30 | return await dbMonthlyVaults
31 | .findOne({ vaultRef: idVault, date: date })
32 | .lean();
33 | }
34 | }
35 |
36 | async function transactionsMonthly(idVault, month, year) {
37 | const findTransactionsVault = await dbTransactionsVaults.find({
38 | vaultRef: idVault,
39 | month: month,
40 | year: year,
41 | });
42 | return findTransactionsVault;
43 | }
44 |
45 |
46 | async function transactionsVault(idVault) {
47 | const findTransactionsVault = await dbTransactionsVaults.find({
48 | vaultRef: idVault,
49 | });
50 | return findTransactionsVault;
51 | }
52 |
53 | async function prevTransactions(idVault) {
54 | const findPrevTrans = await dbTransactionsVaults.find({
55 | vaultRef: idVault,
56 | UNIXDate: { $lt: dateFilter.getTime() },
57 | });
58 | return findPrevTrans;
59 | }
60 |
61 | const mergedVault = await Promise.all(
62 | findAllVault.map(async (prevData) => ({
63 | ...prevData,
64 | monthly: await monthly(prevData._id, dateFilter),
65 | transactions: (await transactionsMonthly(prevData._id, req.params.month, req.params.year)
66 | ).reduce((total, item) => total + item.amount, 0),
67 | prevTransactions: (
68 | await prevTransactions(prevData._id)
69 | ).reduce((total, item) => total + item.amount, 0),
70 | }))
71 | );
72 |
73 |
74 | res.send({
75 | data: mergedVault,
76 | });
77 | } catch (err) {
78 | console.error(err);
79 | }
80 | };
81 |
82 | const post = async (req, res, next) => {};
83 |
84 | module.exports = {
85 | get,
86 | post,
87 | };
88 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /public/logo.svg
2 | .DS_Store
3 |
4 | /client/soho-dark.css
5 | /client/soho-light.css
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 | .pnpm-debug.log*
15 |
16 | # Diagnostic reports (https://nodejs.org/api/report.html)
17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 | *.lcov
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (https://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # Snowpack dependency directory (https://snowpack.dev/)
52 | web_modules/
53 |
54 | # TypeScript cache
55 | *.tsbuildinfo
56 |
57 | # Optional npm cache directory
58 | .npm
59 |
60 | # Optional eslint cache
61 | .eslintcache
62 |
63 | # Optional stylelint cache
64 | .stylelintcache
65 |
66 | # Microbundle cache
67 | .rpt2_cache/
68 | .rts2_cache_cjs/
69 | .rts2_cache_es/
70 | .rts2_cache_umd/
71 |
72 | # Optional REPL history
73 | .node_repl_history
74 |
75 | # Output of 'npm pack'
76 | *.tgz
77 |
78 | # Yarn Integrity file
79 | .yarn-integrity
80 |
81 | # dotenv environment variable files
82 | # .env
83 | # .env.development.local
84 | # .env.test.local
85 | # .env.production.local
86 | # .env.local
87 |
88 | # parcel-bundler cache (https://parceljs.org/)
89 | .cache
90 | .parcel-cache
91 |
92 | # Next.js build output
93 | .next
94 | out
95 |
96 | # Nuxt.js build / generate output
97 | .nuxt
98 | dist
99 |
100 | # Gatsby files
101 | .cache/
102 | # Comment in the public line in if your project uses Gatsby and not Next.js
103 | # https://nextjs.org/blog/next-9-1#public-directory-support
104 | # public
105 |
106 | # vuepress build output
107 | .vuepress/dist
108 |
109 | # vuepress v2.x temp and cache directory
110 | .temp
111 | .cache
112 |
113 | # Docusaurus cache and generated files
114 | .docusaurus
115 |
116 | # Serverless directories
117 | .serverless/
118 |
119 | # FuseBox cache
120 | .fusebox/
121 |
122 | # DynamoDB Local files
123 | .dynamodb/
124 |
125 | # TernJS port file
126 | .tern-port
127 |
128 | # Stores VSCode versions used for testing VSCode extensions
129 | .vscode-test
130 |
131 | # yarn v2
132 | .yarn/cache
133 | .yarn/unplugged
134 | .yarn/build-state.yml
135 | .yarn/install-state.gz
136 | .pnp.*
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes, BrowserRouter } from "react-router-dom";
2 |
3 | // ACCOUNTS
4 | import Accounts from "./finances/accounts/Accounts";
5 | import AddAccounts from "./finances/accounts/AddAccounts";
6 | import EditAccount from "./finances/accounts/EditAccount";
7 | // BUCKETS
8 | import Buckets from "./finances/buckets/Buckets";
9 | import CreateBucket from "./finances/buckets/CreateBucket";
10 | import ListBuckets from "./finances/buckets/ListBucket";
11 | import EditBucket from "./finances/buckets/EditBucket";
12 | // VAULTS
13 | import Vaults from "./finances/vaults/Vaults";
14 | import CreateVault from "./finances/vaults/CreateVault";
15 | import ListVaults from "./finances/vaults/ListVault";
16 | import EditVault from "./finances/vaults/EditVault";
17 | // TRANSACTIONS
18 | import AddTransactions from "./finances/transactions/AddTransactions";
19 | import TransactionsVault from "./finances/transactions/TransactionsVault";
20 | import TransactionsVaultList from "./finances/vaults/ListVaultTransactions";
21 | import ListTransactions from "./finances/transactions/ListTransaction";
22 | // EXPENSES
23 | import AddExpense from "./finances/expense/AddExpense";
24 | import ListExpenses from "./finances/expense/ListExpenses";
25 | // MISC
26 | import Listing from "./finances/Listing";
27 | import Layout from "./Layout";
28 | import Footer from "./Footer";
29 |
30 | function App() {
31 | return (
32 | <>
33 |
34 |
35 | }>
36 | } />
37 | } />
38 | } />
39 |
40 | } />
41 | } />
42 |
43 | } />
44 | } />
45 | } />
46 | } />
47 | } />
48 | } />
49 | } />
50 | } />
51 | } />
52 |
53 | } />
54 | } />
55 | } />
56 | }
59 | />
60 |
61 |
62 |
63 |
64 | >
65 | );
66 | }
67 |
68 | export default App;
69 |
--------------------------------------------------------------------------------
/server/routes/finance.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | // ACCOUNTS
5 | const accounts = require("../controllers/accounts/accounts.controller");
6 | // BUCKETS
7 | const buckets = require("../controllers/buckets/buckets.controller");
8 | const listBucket = require("../controllers/buckets/listBucket.controller");
9 |
10 | // VAULTS
11 | const vault = require("../controllers/vaults/vault.controller");
12 | const vaultList = require("../controllers/vaults/vaultList.controller");
13 | const createVault = require("../controllers/vaults/createVault.controller");
14 | const transactionVaultList = require("../controllers/vaults/transactionVaultList.controller");
15 | const transactionVault = require("../controllers/transactions/transactionVault.controller");
16 | // TRANSACTIONS
17 | const transactions = require("../controllers/transactions/transactions.controller");
18 | const transactionsList = require("../controllers/transactions/transactionsList.controller");
19 | // EXPENSES
20 | const addExpense = require("../controllers/expense/addExpense.controller");
21 | const expensesList = require("../controllers/expense/expensesList.controller");
22 | // USEFUL
23 | const notifications = require("../controllers/notifications/notifications.controller");
24 | // MISC
25 | const allCategory = require("../controllers/category/allCategory.controller");
26 | const addCategory = require("../controllers/category/addCategory.controller");
27 |
28 | // ROUTER
29 |
30 | // ACCOUNTS
31 | router.get("/all/accounts", accounts.get); // GET the list of all accounts
32 | router.post("/accounts/add", accounts.post); // POST for add an account
33 | // BUCKETS
34 | router.get("/buckets/list", listBucket.get);
35 |
36 | router.get("/buckets/:year/:month/", buckets.get); // GET The bucket monthly
37 | router.post("/buckets/create", buckets.post); // POST for creating an new bucket
38 | router.get("/buckets/:year/:month/", buckets.get); // GET The bucket monthly
39 |
40 | // VAULTS
41 | router.get("/transactions/vaults/list", transactionVaultList.get);
42 |
43 | router.get("/vaults/list", vaultList.get);
44 |
45 | router.get("/transactions/vaults/", transactionVault.get);
46 | router.post("/transactions/vaults/", transactionVault.post);
47 | router.get("/vaults/:year/:month/", vault.get);
48 | router.get("/vaults/create", createVault.get);
49 | router.post("/vaults/create", createVault.post);
50 | // TRANSACTIONS
51 | router.get("/transactions/list/", transactionsList.get);
52 |
53 | router.get("/transactions/add", transactions.get);
54 | router.post("/transactions/add", transactions.post);
55 | // EXPENSE
56 | router.get("/expenses/list/", expensesList.get);
57 |
58 | router.get("/expenses/add/", addExpense.get);
59 | router.post("/expenses/add/", addExpense.post);
60 | // MISC
61 | router.get("/category/all", allCategory.get);
62 | router.post("/category/add", addCategory.post);
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | router.get("/", notifications.get);
75 | router.post("/", notifications.post);
76 |
77 | module.exports = router;
78 |
--------------------------------------------------------------------------------
/client/src/finances/buckets/ListBucket.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { Card } from "primereact/card";
4 | import { Divider } from "primereact/divider";
5 | import { DataTable } from "primereact/datatable";
6 | import { Column } from "primereact/column";
7 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
8 |
9 | import { IconX } from "@tabler/icons-react";
10 |
11 | import { useTranslation } from 'react-i18next';
12 |
13 | const listBucket = () => {
14 | const {t, i18n} = useTranslation()
15 |
16 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/buckets/list`;
17 | const [data, setData] = useState();
18 |
19 | const fetchData = async () => {
20 | try {
21 | const fetchGet = await fetch(urlAPI),
22 | fetchResponse = await fetchGet.json();
23 | return fetchResponse.data;
24 | } catch (error) {
25 | console.error("An error occurred while executing fetchData() : ", error);
26 | }
27 | };
28 |
29 | const confirm = (id) => {
30 | confirmDialog({
31 | message: `${t('Delete Bucket Confirmation')}`,
32 | header: "Delete Confirmation",
33 | icon: "pi pi-info-circle",
34 | defaultFocus: "reject",
35 | acceptClassName: "p-button-danger",
36 | accept: async () => {
37 | try {
38 | await fetch(`${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/bucket/delete/${id}/`, {
39 | method: "POST",
40 | headers: {
41 | "Content-Type": "application/json",
42 | },
43 | });
44 | window.location.reload();
45 | } catch (err) {
46 | console.error("An error occurred : ", err);
47 | }
48 | },
49 | reject: () => {},
50 | });
51 | };
52 |
53 | const target = (object) => {
54 | if (object.targeted === false) {
55 | return ;
56 | }
57 | return {object.defaultTarget}
;
58 | };
59 |
60 | const editLink = (object) => {
61 | return (
62 | <>
63 |
64 |
65 |
66 | {
68 | confirm(object._id);
69 | }}
70 | >
71 |
72 |
73 | >
74 | );
75 | };
76 |
77 | useEffect(() => {
78 | fetchData().then((data) => {
79 | setData(data);
80 | });
81 | }, []);
82 |
83 | return (
84 | <>
85 |
86 |
87 |
92 |
93 |
94 | {t('Listing')}
95 |
96 |
97 |
98 |
99 |
100 |
105 |
106 |
107 |
108 |
109 |
110 | >
111 | );
112 | };
113 |
114 | export default listBucket;
115 |
--------------------------------------------------------------------------------
/client/src/finances/accounts/Accounts.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { DataTable } from "primereact/datatable";
4 | import { Column } from "primereact/column";
5 | import { Card } from "primereact/card";
6 | import { Divider } from "primereact/divider";
7 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
8 |
9 | import { useTranslation } from 'react-i18next';
10 |
11 | const Accounts = () => {
12 | const {t, i18n} = useTranslation()
13 |
14 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
15 | const [data, setData] = useState([]);
16 |
17 | const fetchData = async () => {
18 | try {
19 | const fetchGet = await fetch(urlAPI),
20 | fetchResponse = await fetchGet.json();
21 | return fetchResponse.data;
22 | } catch (error) {
23 | console.error("An error occurred while executing fetchData() : ", error);
24 | }
25 | };
26 |
27 | const confirm = (id) => {
28 | confirmDialog({
29 | message: `${t('Delete Account Confirmation')}`,
30 | header: "Delete Confirmation",
31 | icon: "pi pi-info-circle",
32 | defaultFocus: "reject",
33 | acceptClassName: "p-button-danger",
34 | accept: async () => {
35 | try {
36 | await fetch(`${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/account/delete/${id}/`, {
37 | method: "POST",
38 | headers: {
39 | "Content-Type": "application/json",
40 | },
41 | });
42 | window.location.reload();
43 | } catch (err) {
44 | console.error("An error occurred : ", err);
45 | }
46 | },
47 | reject: () => {},
48 | });
49 | };
50 |
51 | const editLink = (object) => {
52 | return (
53 | <>
54 |
55 |
56 |
57 | {
59 | confirm(object._id);
60 | }}
61 | >
62 |
63 |
64 | >
65 | );
66 | };
67 |
68 | const formatCurrency = (value) => {
69 | return value.toLocaleString("fr-FR", {
70 | style: "currency",
71 | currency: "EUR",
72 | });
73 | };
74 |
75 | const priceBody = (data) => {
76 | return formatCurrency(data.balance);
77 | };
78 |
79 | useEffect(() => {
80 | fetchData().then((data) => {
81 | setData(data);
82 | });
83 | }, []);
84 | return (
85 | <>
86 |
87 |
88 |
93 |
94 |
95 | {t('Listing')}
96 |
97 |
98 |
99 |
100 |
101 |
106 |
107 |
108 |
109 |
110 |
111 | >
112 | );
113 | };
114 |
115 | export default Accounts;
116 |
--------------------------------------------------------------------------------
/client/src/assets/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | height: 100vh;
3 | padding: 0;
4 | }
5 | /*
6 | FORCE ET HONNEUR !
7 | */
8 | @media (prefers-color-scheme: dark) {
9 | .footer {
10 | background-color:#282936;
11 | position: absolute;
12 | /* bottom: 0; */
13 | margin-top:5rem;
14 | width:100%;
15 | height:5rem;
16 | border-top: 1px solid #00000015;
17 | }
18 | .navbar {
19 | background-color: #28293673;
20 | -webkit-backdrop-filter: blur(5px);
21 | backdrop-filter: blur(5px);
22 | position: fixed;
23 | z-index: 999;
24 | width:100%;
25 | border-bottom: 1px solid #00000015;
26 |
27 | }
28 | .nav-clear {
29 | width:100%;
30 | height:5.4rem;
31 | background-color:#282936;
32 | margin-bottom:4rem;
33 | }
34 |
35 | .p-megamenu-panel {
36 | margin-top:1rem;
37 | background-color: #282936;
38 | border-radius: 5px;
39 | font-size: 1.125rem;
40 | -webkit-backdrop-filter: blur(5px);
41 | backdrop-filter: blur(5px);
42 | border: 1px solid #20212c;
43 | }
44 |
45 | .p-megamenu {
46 | border:0;
47 | border-radius: 0;
48 | background-color: #00000000;
49 | height: 4rem;
50 |
51 | }
52 | .edit-icon {
53 | font-size: 1.2rem;
54 | color: #ffffffef;
55 | }
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | .footer {
60 | background-color:rgb(255, 255, 255);
61 | margin-top:5rem;
62 | position: absolute;
63 | /* bottom: 0; */
64 | width:100%;
65 | height:5rem;
66 | border-top: 1px solid #00000015;
67 | }
68 | .navbar {
69 | background-color: rgba(255, 255, 255, 0.397);
70 | -webkit-backdrop-filter: blur(5px);
71 | backdrop-filter: blur(5px);
72 | position: fixed;
73 | z-index: 999;
74 | width:100%;
75 | border-bottom: 1px solid #00000015;
76 |
77 | }
78 | .nav-clear {
79 | width:100%;
80 | height:5.4rem;
81 | background-color:rgb(255, 255, 255);
82 | margin-bottom:4rem;
83 | }
84 |
85 | .p-megamenu-panel {
86 | margin-top:1rem;
87 | background-color: rgba(255, 255, 255, 0.849);
88 | border-radius: 5px;
89 | font-size: 1.125rem;
90 | -webkit-backdrop-filter: blur(5px);
91 | backdrop-filter: blur(5px);
92 | border: 1px solid #dfdfdf;
93 | }
94 |
95 | .p-megamenu {
96 | border:0;
97 | border-radius: 0;
98 | background-color: #00000000;
99 | height: 4rem;
100 |
101 | }
102 |
103 | .edit-icon {
104 | font-size: 1.2rem;
105 | color: #20212c
106 | }
107 | }
108 |
109 | html {
110 | font-size: 14px;
111 | }
112 |
113 | body {
114 | font-family: var(--font-family);
115 | font-weight: normal;
116 | background: var(--surface-ground);
117 | color: var(--text-color);
118 | margin:0;
119 | -webkit-font-smoothing: antialiased;
120 | -moz-osx-font-smoothing: grayscale;
121 | }
122 |
123 | .p-card-content {
124 | padding: 0
125 | }
126 |
127 | .card {
128 | background: var(--surface-card);
129 | padding: 1rem;
130 | border-radius: 10px;
131 | /* margin-bottom: 1rem; */
132 | border: 1px solid rgba(92, 92, 92, 0.15) !important;
133 | box-shadow: 0px 0px 5px 1px #00000005;
134 | }
135 |
136 |
137 |
138 | #hex-large {
139 | height:100%;
140 | width:3rem
141 | }
142 |
143 | .p-progressbar-value-animate {
144 | max-width:100%
145 | }
146 | .p-progressbar-value {
147 | background-color: #29c76f;
148 | }
149 | /* "Parvum verbum ad"
150 | Une personne délicieusement gentille
151 | */
152 | .p-progressbar-perfect .p-progressbar-value {
153 | background-color:royalblue;
154 | }
155 | .p-progressbar-over .p-progressbar-value {
156 | background-color:red;
157 | }
--------------------------------------------------------------------------------
/client/src/finances/accounts/AddAccounts.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import { Card } from "primereact/card";
5 | import { InputText } from "primereact/inputtext";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Divider } from "primereact/divider";
9 | import { Checkbox } from "primereact/checkbox";
10 |
11 | import { useTranslation } from 'react-i18next';
12 |
13 | const AddAccounts = () => {
14 | const {t, i18n} = useTranslation()
15 |
16 | const navigate = useNavigate();
17 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${
18 | import.meta.env.VITE_BACKEND_PORT
19 | }/accounts/add`;
20 | const [assignable, setAssignable] = useState(false);
21 | const [formData, setFormData] = useState({});
22 | const handleChange = (e) => {
23 | const { id, value } = e.target;
24 | setFormData((prevData) => ({
25 | ...prevData,
26 | [id]: value,
27 | }));
28 | };
29 |
30 | const postForm = async (e) => {
31 | e.preventDefault();
32 | try {
33 | await fetch(urlAPI, {
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify({ data: formData, assignable }),
39 | });
40 | navigate("/");
41 | } catch (err) {
42 | console.error("An error occurred : ", err);
43 | }
44 | };
45 |
46 | useEffect(() => {}, []);
47 | return (
48 | <>
49 |
50 |
55 |
56 |
57 | {t('Options')}
58 |
59 |
60 |
61 | setAssignable(e.checked)}
64 | checked={assignable}
65 | >
66 |
67 | {t('Assignable')}
68 |
69 |
70 |
71 |
72 | {t('Form')}
73 |
74 |
75 |
76 |
77 |
78 |
79 | {t('Label of your account')}
80 |
81 |
82 |
88 |
89 |
90 |
91 |
92 | {t('Initial balance of your account')}
93 |
94 |
95 | {
99 | setFormData((prevData) => ({
100 | ...prevData,
101 | balance: e.value,
102 | }));
103 | }}
104 | mode="currency"
105 | currency="EUR"
106 | locale="fr-Fr"
107 | />
108 |
109 |
110 |
115 |
116 |
117 |
118 | >
119 | );
120 | };
121 |
122 | export default AddAccounts;
123 |
--------------------------------------------------------------------------------
/server/controllers/buckets/buckets.controller.js:
--------------------------------------------------------------------------------
1 | const dbBuckets = require("../../models/buckets");
2 | const dbMonthlyBuckets = require("../../models/monthlyBuckets");
3 | const dbExpenses = require("../../models/expenses");
4 |
5 | const get = async (req, res, next) => {
6 | try {
7 | const month = req.params.month,
8 | formatedMonth = month < 10 ? `0${month}` : `${month}`;
9 | const dateFilter = new Date(req.params.year, formatedMonth - 1, 1);
10 |
11 | const findAllBucket = await dbBuckets
12 | .find({ $or: [
13 | { timeLimited: true, UNIXLimit: { $gt: dateFilter.getTime() } },
14 | { timeLimited: false },
15 | { timeLimited: null }
16 | ]})
17 | .lean();
18 |
19 | async function monthly(idBucket, date, defaultTarget) {
20 | const checkData = await dbMonthlyBuckets
21 | .findOne({ bucketRef: idBucket, date: date })
22 | .lean();
23 | if (checkData !== null) {
24 | return checkData;
25 | } else {
26 | await dbMonthlyBuckets.create({
27 | bucketRef: idBucket,
28 | assigned: 0,
29 | target: defaultTarget,
30 | date: dateFilter,
31 | UNIXDate: dateFilter.getTime(),
32 | month: req.params.month,
33 | year: req.params.year,
34 | });
35 | return await dbMonthlyBuckets
36 | .findOne({ bucketRef: idBucket, date: date })
37 | .lean();
38 | }
39 | }
40 |
41 | async function expense(idBucket) {
42 | const findExpense = await dbExpenses.find({
43 | bucketRef: idBucket,
44 | UNIXDate: dateFilter.getTime(),
45 | });
46 | return findExpense;
47 | }
48 |
49 | async function prevExpense(idBucket) {
50 | const findPrevExpense = await dbExpenses.find({
51 | bucketRef: idBucket,
52 | UNIXDate: { $lt: dateFilter.getTime() },
53 | });
54 | return findPrevExpense;
55 | }
56 |
57 | async function prevAssigned(idBucket) {
58 | const findPrevAssigned = await dbMonthlyBuckets.find({
59 | bucketRef: idBucket,
60 | UNIXDate: { $lt: dateFilter.getTime() },
61 | });
62 | return findPrevAssigned;
63 | }
64 |
65 | const mergedBucket = await Promise.all(
66 | findAllBucket.map(async (prevData) => ({
67 | ...prevData,
68 | monthly: await monthly(
69 | prevData._id,
70 | dateFilter,
71 | prevData.defaultTarget
72 | ),
73 | prevAssigned: (
74 | await prevAssigned(prevData._id)
75 | ).reduce((total, item) => total + item.assigned, 0),
76 | prevExpense: (
77 | await prevExpense(prevData._id)
78 | ).reduce((total, item) => total + item.amount, 0),
79 | expense: (
80 | await expense(prevData._id)
81 | ).reduce((total, item) => total + item.amount, 0),
82 | }))
83 | );
84 | res.send({
85 | data: mergedBucket,
86 | });
87 | } catch (err) {
88 | console.error(err);
89 | }
90 | };
91 |
92 | const post = async (req, res, next) => {
93 | if (req.body.timelimited === true) {
94 | try {
95 | await dbBuckets.create({
96 | label: req.body.label,
97 | balance: 0,
98 | UNIXLimit: new Date(req.body.date).getTime(),
99 | dateLimit: req.body.date,
100 | creationDate: Date.now(),
101 | archived: false,
102 | targeted: req.body.targeted,
103 | defaultTarget: req.body.target,
104 | });
105 | } catch (err) {
106 | console.error(err);
107 | }
108 | } else if (req.body.timelimited === false) {
109 | try {
110 | await dbBuckets.create({
111 | label: req.body.label,
112 | balance: 0,
113 | UNIXLimit: 253636783200000,
114 | creationDate: Date.now(),
115 | archived: false,
116 | targeted: req.body.targeted,
117 | defaultTarget: req.body.target,
118 | });
119 | } catch (err) {
120 | console.error(err);
121 | }
122 | }
123 | };
124 |
125 | module.exports = {
126 | get,
127 | post,
128 | };
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## Badges
4 |
5 | [](http://www.gnu.org/licenses/agpl-3.0)
6 | [](#)
7 | [](#)
8 | [](#)
9 | [](#)
10 |
11 | # Budget5S
12 |
13 | Budget5S aims to offer a free alternative to budget managers. This is an alpha version, with new features to come, changes to be made, and, ESPECIALLY, bugs to be fixed, so it's for informed users. You've been warned!
14 |
15 | There are 3 components/mechanics:
16 |
17 | - Account: Your bank account or piggy bank with an initial balance.
18 |
19 | - Bucket: A bucket, as its name suggests, is for defining a budget. For example, a “Restaurant” bucket can be defined with a monthly amount to be reached, or on the contrary, without an amount depending on the importance of the bucket to keep flexibility. A “Restaurant” bucket can afford to be without an amount, while a “Rent” bucket will have to have a fixed monthly amount (or at your own risk 😉).
20 |
21 | - Vault: To build up your financial security or for other specific needs, you can either define it as a monthly requirement without worrying about the total amount (e.g., I want to save €100 every month) or with a predefined total amount (I want to save €10,000 by the end of the year). The amount required to reach this goal will be calculated and displayed for each month. Vaults are linked to an account, which is important for transfers, as you will see below.
22 |
23 | To manage your accounts, buckets, and vaults, you can:
24 |
25 | - Make a transaction on your account, to remove or add money with a label (for example: Salary - €2,500) or even to notify an unexpected money transfer, for example, if you're giving money to a relative and there's no bucket or vault for it.
26 |
27 | - Transaction vaults: If you want to transfer money to your vault, the “transaction vaults” will ask you from which source account to withdraw the money, for which vault. The operation will be recorded in the vault, and the money from the source account will be transferred to the account linked to the vault!
28 |
29 | - And finally, you have the expenses. The goal is simple: you indicate the account that you have used, the bucket in which the expense belongs (Restaurant, rent, shopping, etc.).
30 |
31 | 
32 |
33 |
34 | Easy to use !
35 |
36 |
37 |
38 | ## Installation
39 |
40 | You can install it via Docker (or podman...):
41 |
42 | Clone the git:
43 | ```bash
44 | git clone https://github.com/KayatoSan/Budget5S
45 | cd Budget5S
46 | ```
47 |
48 | Now build the app and launch it with the docker-compose.yml:
49 | ```bash
50 | docker-compose build
51 | docker-compose up -d
52 | ```
53 |
54 | The client (ReactJS + ViteJS) is in the `/client` folder, and the server (NodeJS) is in the `/server` folder.
55 |
56 | ## Environment Variables
57 |
58 | Environment variables are stored in the `.env` file:
59 |
60 | `VITE_BACKEND_ADRESS` The address of your backend written in the ReactJS client, default: `http://localhost`
61 |
62 | `VITE_BACKEND_PORT` The port of your backend written in the ReactJS client, default: `3000`
63 |
64 | If you change the port used in docker-compose.yml, you need to change the environment variables in the `.env` file.
65 |
66 | ## Roadmap
67 |
68 | - Switch for dark mode. Dark mode is available but is currently tied to your system settings.
69 |
70 | - Showcase website
71 |
72 | - Internationalization
73 |
74 | - Change currency display & format
75 |
76 | - Bug fixes are THE priority!
77 |
78 | - A real documentation (with Docusaurus)
79 |
80 | - Guide for first use
81 |
82 | - Allow the upload of photos for expenses, for example, to keep a photo of the restaurant receipt with the expense!
83 |
84 | And more, in the long term!
85 |
--------------------------------------------------------------------------------
/client/src/finances/transactions/ListTransaction.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { Card } from "primereact/card";
4 | import { Divider } from "primereact/divider";
5 | import { DataTable } from "primereact/datatable";
6 | import { Column } from "primereact/column";
7 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
8 |
9 | import { useTranslation } from 'react-i18next';
10 |
11 | const listTransaction = () => {
12 | const {t, i18n} = useTranslation()
13 |
14 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/transactions/list`;
15 | const urlAPIAccounts = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
16 |
17 | const [data, setData] = useState();
18 | const [accounts, setAccounts] = useState();
19 |
20 | const fetchData = async () => {
21 | try {
22 | const fetchGet = await fetch(urlAPI),
23 | fetchResponse = await fetchGet.json();
24 | setData(fetchResponse.data);
25 | } catch (error) {
26 | console.error("An error occurred while executing fetchData() : ", error);
27 | }
28 | };
29 |
30 | const fetchAccounts = async () => {
31 | try {
32 | const fetchGet = await fetch(urlAPIAccounts),
33 | fetchResponse = await fetchGet.json();
34 | setAccounts(fetchResponse.data);
35 | } catch (err) {
36 | console.error(
37 | "An error occurred while executing fetchAccounts() : ",
38 | error
39 | );
40 | }
41 | };
42 |
43 | const confirm = (id) => {
44 | confirmDialog({
45 | message: `${t('Delete Transaction Confirmation')}`,
46 | header: "Delete Confirmation",
47 | icon: "pi pi-info-circle",
48 | defaultFocus: "reject",
49 | acceptClassName: "p-button-danger",
50 | accept: async () => {
51 | try {
52 | await fetch(`${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/transaction/delete/${id}/`, {
53 | method: "POST",
54 | headers: {
55 | "Content-Type": "application/json",
56 | },
57 | });
58 | window.location.reload();
59 | } catch (err) {
60 | console.error("An error occurred : ", err);
61 | }
62 | },
63 | reject: () => {},
64 | });
65 | };
66 |
67 | const editLink = (object) => {
68 | return (
69 | <>
70 | {
72 | confirm(object._id);
73 | }}
74 | >
75 |
76 |
77 | >
78 | );
79 | };
80 |
81 | const accountRef = (object) => {
82 | const account = object.accountRef;
83 | const result = accounts.find((data) => data._id === account);
84 | if (result === undefined) {
85 | return {t('No longer exist')}
;
86 | }
87 | return {result && result.label}
;
88 | };
89 |
90 | useEffect(() => {
91 | fetchAccounts().then(() => {
92 | fetchData();
93 | });
94 | }, []);
95 |
96 | return (
97 | <>
98 |
99 |
100 |
105 |
106 |
107 | {t('Listing')}
108 |
109 |
110 |
111 |
112 |
113 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | >
125 | );
126 | };
127 |
128 | export default listTransaction;
129 |
--------------------------------------------------------------------------------
/client/src/finances/accounts/EditAccount.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate, useParams } from "react-router-dom";
3 |
4 | import { Card } from "primereact/card";
5 | import { InputText } from "primereact/inputtext";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Divider } from "primereact/divider";
9 | import { Checkbox } from "primereact/checkbox";
10 |
11 | import { useTranslation } from 'react-i18next';
12 |
13 | const editAccount = () => {
14 | const {t, i18n} = useTranslation()
15 |
16 | let { id } = useParams();
17 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${
18 | import.meta.env.VITE_BACKEND_PORT
19 | }/edit/account/${id}`;
20 | const navigate = useNavigate();
21 |
22 | const [assignable, setAssignable] = useState();
23 | const [data, setData] = useState(false);
24 |
25 | const fetchData = async () => {
26 | try {
27 | const fetchGet = await fetch(urlAPI),
28 | fetchResponse = await fetchGet.json();
29 | return fetchResponse.data;
30 | } catch (error) {
31 | console.error("An error occurred while executing fetchData() : ", error);
32 | }
33 | };
34 |
35 | const handleChange = (e) => {
36 | fetchData().then((data) => {
37 | setData(data);
38 | });
39 | };
40 |
41 | const postForm = async (e) => {
42 | e.preventDefault();
43 | try {
44 | navigate("/");
45 | await fetch(urlAPI, {
46 | method: "POST",
47 | headers: {
48 | "Content-Type": "application/json",
49 | },
50 | body: JSON.stringify({
51 | data,
52 | assignable,
53 | }),
54 | });
55 | } catch (err) {
56 | console.error("An error occurred : ", err);
57 | }
58 | };
59 |
60 | useEffect(() => {
61 | fetchData().then((data) => {
62 | setData(data);
63 | setAssignable(data.assignable);
64 | });
65 | }, []);
66 | return (
67 | <>
68 |
69 |
74 |
75 |
76 | {t('Options')}
77 |
78 |
79 |
80 | setAssignable(e.checked)}
83 | checked={assignable}
84 | >
85 |
86 | {t('Assignable')}
87 |
88 |
89 |
90 |
91 | {t('Form')}
92 |
93 |
94 |
95 |
96 |
97 | {t('Label of your account')}
98 |
99 |
100 | setData({ ...data, label: e.target.value })}
106 | />
107 |
108 |
109 |
110 |
111 | {t('Balance of your account')}
112 |
113 |
114 | setData({ ...data, balance: e.value })}
119 | mode="currency"
120 | currency="EUR"
121 | locale="fr-Fr"
122 | // disabled={!targeted}
123 | />
124 |
125 |
126 |
127 |
128 |
133 |
134 |
135 |
136 | >
137 | );
138 | };
139 |
140 | export default editAccount;
141 |
--------------------------------------------------------------------------------
/client/src/finances/vaults/ListVault.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { Card } from "primereact/card";
4 | import { Divider } from "primereact/divider";
5 | import { DataTable } from "primereact/datatable";
6 | import { Column } from "primereact/column";
7 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
8 |
9 | import { IconCheck, IconX } from "@tabler/icons-react";
10 |
11 | import { useTranslation } from 'react-i18next';
12 |
13 | const listVault = () => {
14 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/vaults/list`;
15 | const urlAPIAccounts = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
16 |
17 | const {t, i18n} = useTranslation()
18 |
19 | const [data, setData] = useState();
20 | const [accounts, setAccounts] = useState();
21 |
22 | const fetchData = async () => {
23 | try {
24 | const fetchGet = await fetch(urlAPI),
25 | fetchResponse = await fetchGet.json();
26 | setData(fetchResponse.data);
27 | } catch (err) {
28 | console.error("An error occurred while executing fetchData() : ", error);
29 | }
30 | };
31 | const fetchAccounts = async () => {
32 | try {
33 | const fetchGet = await fetch(urlAPIAccounts),
34 | fetchResponse = await fetchGet.json();
35 | setAccounts(fetchResponse.data);
36 | } catch (err) {
37 | console.error("An error occurred while executing fetchData() : ", error);
38 | }
39 | };
40 | const targetState = (object) => {
41 | if (object.monthlyType == true) {
42 | return ;
43 | } else if (object.monthlyType === false) {
44 | return ;
45 | }
46 | };
47 |
48 | const confirm = (id) => {
49 | confirmDialog({
50 | message: `${t('Delete vault confirmation')}`,
51 | header: "Delete Confirmation",
52 | icon: "pi pi-info-circle",
53 | defaultFocus: "reject",
54 | acceptClassName: "p-button-danger",
55 | accept: async () => {
56 | try {
57 | await fetch(`${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/vault/delete/${id}/`, {
58 | method: "POST",
59 | headers: {
60 | "Content-Type": "application/json",
61 | },
62 | });
63 | window.location.reload();
64 | } catch (err) {
65 | console.error("An error occurred : ", err);
66 | }
67 | },
68 | reject: () => {},
69 | });
70 | };
71 |
72 | const editLink = (object) => {
73 | return (
74 | <>
75 |
76 |
77 |
78 | {
80 | confirm(object._id);
81 | }}
82 | >
83 |
84 |
85 | >
86 | );
87 | };
88 |
89 | const accountLinked = (object) => {
90 | const account = object.accountLinked;
91 | const result = accounts.find((data) => data._id === object.accountLinked);
92 | if (result === undefined) {
93 | return {t('No longer exist')}
;
94 | }
95 | return {result && result.label}
;
96 | };
97 |
98 | useEffect(() => {
99 | fetchAccounts().then(() => {
100 | fetchData();
101 | });
102 | }, []);
103 |
104 | return (
105 | <>
106 |
107 |
108 |
113 |
114 |
115 | {t('Listing')}
116 |
117 |
118 |
119 |
120 |
121 |
126 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | >
139 | );
140 | };
141 |
142 | export default listVault;
143 |
--------------------------------------------------------------------------------
/client/src/i18n/translationEN.json:
--------------------------------------------------------------------------------
1 | {
2 | "Account REF": "Account REF",
3 | "Account Subtitle": "List all your accounts, if you want to modify the balance of yours accounts, let's add an transaction",
4 | "Accounts": "Accounts",
5 | "Add Account Subtitle": "Whether it's a bank account or a piggy bank",
6 | "Add an account": "Add an account",
7 | "Add an expense": "Add an expense",
8 | "Add or remove money from one of your accounts": "Add or remove money from one of your accounts",
9 | "Amount": "Amount",
10 | "Assignable": "Assignable",
11 | "Balance": "Balance",
12 | "Buckets": "Buckets",
13 | "Create a bucket": "Create a bucket",
14 | "Create a vault": "Create a vault",
15 | "Create bucket subtitle": "Organize your money",
16 | "Create bucket title": "Create a bucket",
17 | "Create vault subtitle": "Organize your empire",
18 | "Create vault title": "Create a vault",
19 | "Date": "Date",
20 | "Date Limit": "Date Limit",
21 | "DateDue tooltip": "Set an date due for your goal !",
22 | "Delete Account Confirmation": "Are you sure do you want to delete this account ?",
23 | "Delete Bucket Confirmation": "Are you sure do you want to delete this bucket ?",
24 | "Delete Transaction Confirmation": "Are you sure do you want to delete this transaction ?",
25 | "Delete expense confirmation": "Are you sure do you want to delete this expense ?",
26 | "Delete vault confirmation": "Are you sure do you want to delete this vault ?",
27 | "Due date": "Due date",
28 | "Edit": "Edit",
29 | "Edit bucket subtitle": "Organize your money",
30 | "Edit bucket title": "Edit your bucket",
31 | "Edit vault subtitle": "Organize your empire",
32 | "Edit vault title": "Edit your vault",
33 | "Every month": "Every month",
34 | "Expense": "Expense",
35 | "Expense list subtitle": "List all your expenses",
36 | "Expense list title": "Expenses",
37 | "Expenses": "Expenses",
38 | "Form": "Form",
39 | "Help ?": "Help ?",
40 | "Initial balance of your account": "Initial balance of your account",
41 | "Label": "Label",
42 | "Label (only to recognize)": "Label (only to recognize)",
43 | "Label of your account": "Label of your account",
44 | "Label of your vault": "Label of your vault",
45 | "Linked account": "Linked account",
46 | "List all your transactions, income or withdraw": "Transactions list subtitle",
47 | "List all your vault transactions": "List vault transaction subtitle",
48 | "List all your vaults and edit them": "List vault subtitle",
49 | "List buckets subtitle": "List all your buckets and edit them",
50 | "List bucket subtitle": "Don't be shy, spend it !",
51 | "List bucket title": "🥛 Buckets",
52 | "List vault subtitle": "For emperors, build your empire",
53 | "List vault title": "🏦 Vaults",
54 | "Listing": "Listing",
55 | "Listing / Edit": "Listing / Edit",
56 | "Make a transaction": "Make a transaction",
57 | "Made to help anybody": "Made to help anybody",
58 | "Management": "Management",
59 | "Monthly": "Monthly",
60 | "Monthly tooltip": "Save an amount every month",
61 | "New source account": "New source account",
62 | "No longer exist": "No longer exist",
63 | "OR": "OR",
64 | "Option": "Option",
65 | "Options": "Options",
66 | "Resume": "Resume",
67 | "Save an amount every month": "Save an amount every month",
68 | "Set an date due for your goal !": "Set an date due for your goal !",
69 | "Solde": "Balance",
70 | "Source account": "Source account",
71 | "Stamp an expense": "Stamp an expense",
72 | "Submit": "Submit",
73 | "Switch to": "Switch to",
74 | "Target": "Target",
75 | "Target global": "Target global",
76 | "Target to reach every month": "Target to reach every month",
77 | "Targeted": "Targeted",
78 | "The due date has passed, you have transferred a total of": "The due date has passed, you have transferred a total of",
79 | "The old balance switch to": "The old balance switch to",
80 | "Time Limited": "Time Limited",
81 | "Total assignable": "Total assignable",
82 | "Total on your account": "Total on your account",
83 | "Total transfered": "Total transfered",
84 | "Total unassignable": "Total unassignable",
85 | "Transactions": "Transactions",
86 | "Transactions list subtitle": "List all your transactions, income or withdraw",
87 | "Transactions listing": "Transactions listing",
88 | "Transactions vault": "Transactions vault",
89 | "Transactions vault subtitle": "Saving money",
90 | "Transfered": "Transfered",
91 | "Vault": "Vault",
92 | "Vault REF": "Vault REF",
93 | "Vaults": "Vaults",
94 | "You must assign an amount": "You must assign an amount",
95 | "You need to transfer": "You need to transfer",
96 | "assigned": "assigned",
97 | "assigned this month": "assigned this month",
98 | "more is needed for reach target": "more is needed for reach target",
99 | "needed": "needed",
100 | "required this month": "required this month",
101 | "switch to": "switch to",
102 | "target isn't required": "target isn't required",
103 | "to reach the target this month": "to reach the target this month",
104 | "transfered on": "transfered on"
105 | }
--------------------------------------------------------------------------------
/client/src/i18n/translationES.json:
--------------------------------------------------------------------------------
1 | {
2 | "Account REF": "REF de la cuenta",
3 | "Account Subtitle": "Lista todas tus cuentas, si deseas modificar el saldo de tus cuentas, agrega una transacción",
4 | "Accounts": "Cuentas",
5 | "Add Account Subtitle": "Ya sea una cuenta bancaria o una hucha",
6 | "Add an account": "Añadir una cuenta",
7 | "Add an expense": "Añadir un gasto",
8 | "Add or remove money from one of your accounts": "Añadir o retirar dinero de una de tus cuentas",
9 | "Amount": "Monto",
10 | "Assignable": "Asignable",
11 | "Balance": "Saldo",
12 | "Buckets": "Cubos",
13 | "Create a bucket": "Crear un cubo",
14 | "Create a vault": "Crear una bóveda",
15 | "Create bucket subtitle": "Organiza tu dinero",
16 | "Create bucket title": "Crear un cubo",
17 | "Create vault subtitle": "Organiza tu imperio",
18 | "Create vault title": "Crear una bóveda",
19 | "Date": "Fecha",
20 | "Date Limit": "Fecha límite",
21 | "DateDue tooltip": "¡Establece una fecha límite para tu objetivo!",
22 | "Delete Account Confirmation": "¿Estás seguro de que deseas eliminar esta cuenta?",
23 | "Delete Bucket Confirmation": "¿Estás seguro de que deseas eliminar este cubo?",
24 | "Delete Transaction Confirmation": "¿Estás seguro de que deseas eliminar esta transacción?",
25 | "Delete expense confirmation": "¿Estás seguro de que deseas eliminar este gasto?",
26 | "Delete vault confirmation": "¿Estás seguro de que deseas eliminar esta bóveda?",
27 | "Due date": "Fecha de vencimiento",
28 | "Edit": "Editar",
29 | "Edit bucket subtitle": "Organiza tu dinero",
30 | "Edit bucket title": "Editar tu cubo",
31 | "Edit vault subtitle": "Organiza tu imperio",
32 | "Edit vault title": "Editar tu bóveda",
33 | "Every month": "Cada mes",
34 | "Expense": "Gasto",
35 | "Expense list subtitle": "Lista todos tus gastos",
36 | "Expense list title": "Gastos",
37 | "Expenses": "Gastos",
38 | "Form": "Formulario",
39 | "Help ?": "¿Necesitas ayuda?",
40 | "Initial balance of your account": "Saldo inicial de tu cuenta",
41 | "Label": "Etiqueta",
42 | "Label (only to recognize)": "Etiqueta (solo para reconocer)",
43 | "Label of your account": "Etiqueta de tu cuenta",
44 | "Label of your vault": "Etiqueta de tu bóveda",
45 | "Linked account": "Cuenta vinculada",
46 | "List all your transactions, income or withdraw": "Lista todas tus transacciones, ingresos o retiros",
47 | "List all your vault transactions": "Lista todas tus transacciones de bóveda",
48 | "List all your vaults and edit them": "Lista todas tus bóvedas y edítalas",
49 | "List buckets subtitle": "Lista todos tus cubos y edítalos",
50 | "List bucket subtitle": "¡No seas tímido, gástalo!",
51 | "List bucket title": "🥛 Cubos",
52 | "List vault subtitle": "Para emperadores, construye tu imperio",
53 | "List vault title": "🏦 Bóvedas",
54 | "Listing": "Lista",
55 | "Listing / Edit": "Lista / Editar",
56 | "Make a transaction": "Realizar una transacción",
57 | "Made to help anybody": "Hecho para ayudar a cualquiera",
58 | "Management": "Gestión",
59 | "Monthly": "Mensual",
60 | "Monthly tooltip": "Ahorra una cantidad cada mes",
61 | "New source account": "Nueva cuenta fuente",
62 | "No longer exist": "Ya no existe",
63 | "OR": "O",
64 | "Option": "Opción",
65 | "Options": "Opciones",
66 | "Resume": "Resumen",
67 | "Save an amount every month": "Ahorra una cantidad cada mes",
68 | "Set an date due for your goal !": "¡Establece una fecha límite para tu objetivo!",
69 | "Source account": "Cuenta fuente",
70 | "Stamp an expense": "Registrar un gasto",
71 | "Submit": "Enviar",
72 | "Switch to": "Cambiar a",
73 | "Target": "Objetivo",
74 | "Target global": "Objetivo global",
75 | "Target to reach every month": "Objetivo a alcanzar cada mes",
76 | "Targeted": "Enfocado",
77 | "The due date has passed, you have transferred a total of": "La fecha de vencimiento ha pasado, has transferido un total de",
78 | "The old balance switch to": "El antiguo saldo cambió a",
79 | "Time Limited": "Tiempo limitado",
80 | "Total assignable": "Total asignable",
81 | "Total on your account": "Total en tu cuenta",
82 | "Total transfered": "Total transferido",
83 | "Total unassignable": "Total no asignable",
84 | "Transactions": "Transacciones",
85 | "Transactions list subtitle": "Lista todas tus transacciones, ingresos o retiros",
86 | "Transactions listing": "Lista de transacciones",
87 | "Transactions vault": "Transacciones de bóveda",
88 | "Transactions vault subtitle": "Ahorrando dinero",
89 | "Transfered": "Transferido",
90 | "Vault": "Bóveda",
91 | "Vault REF": "REF de la bóveda",
92 | "Vaults": "Bóvedas",
93 | "You must assign an amount": "Debes asignar una cantidad",
94 | "You need to transfer": "Necesitas transferir",
95 | "assigned": "asignado",
96 | "assigned this month": "asignado este mes",
97 | "more is needed for reach target": "se necesita más para alcanzar el objetivo",
98 | "needed": "necesario",
99 | "required this month": "requerido este mes",
100 | "switch to": "cambiar a",
101 | "target isn't required": "el objetivo no es necesario",
102 | "to reach the target this month": "para alcanzar el objetivo este mes",
103 | "transfered on": "transferido el",
104 | "spended / assigned this month":"% gastado / asignado este mes"
105 | }
--------------------------------------------------------------------------------
/client/src/finances/vaults/ListVaultTransactions.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { Card } from "primereact/card";
4 | import { Divider } from "primereact/divider";
5 | import { DataTable } from "primereact/datatable";
6 | import { Column } from "primereact/column";
7 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
8 |
9 | import { useTranslation } from 'react-i18next';
10 |
11 | const listTransaction = () => {
12 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/transactions/vaults/list`;
13 | const urlAPIAccounts = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
14 | const urlAPIVaults = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/vaults/list`;
15 |
16 | const {t, i18n} = useTranslation()
17 |
18 | const [data, setData] = useState();
19 | const [accounts, setAccounts] = useState();
20 | const [vaults, setVaults] = useState();
21 |
22 | const fetchData = async () => {
23 | try {
24 | const fetchGet = await fetch(urlAPI),
25 | fetchResponse = await fetchGet.json();
26 | setData(fetchResponse.data);
27 | } catch (error) {
28 | console.error("An error occurred while executing fetchData() : ", error);
29 | }
30 | };
31 |
32 | const fetchAccounts = async () => {
33 | try {
34 | const fetchGet = await fetch(urlAPIAccounts),
35 | fetchResponse = await fetchGet.json();
36 | setAccounts(fetchResponse.data);
37 | } catch (err) {
38 | console.error("An error occurred while executing fetchData() : ", error);
39 | }
40 | };
41 |
42 | const fetchVaults = async () => {
43 | try {
44 | const fetchGet = await fetch(urlAPIVaults),
45 | fetchResponse = await fetchGet.json();
46 | setVaults(fetchResponse.data);
47 | } catch (err) {
48 | console.error("An error occurred while executing fetchData() : ", error);
49 | }
50 | };
51 |
52 | const confirm = (id) => {
53 | confirmDialog({
54 | message: "Are you sure do you want to delete this transaction ?",
55 | header: "Delete Confirmation",
56 | icon: "pi pi-info-circle",
57 | defaultFocus: "reject",
58 | acceptClassName: "p-button-danger",
59 | accept: async () => {
60 | try {
61 | await fetch(
62 | `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/transaction/vault/delete/${id}/`,
63 | {
64 | method: "POST",
65 | headers: {
66 | "Content-Type": "application/json",
67 | },
68 | }
69 | );
70 | window.location.reload();
71 | } catch (err) {
72 | console.error("An error occurred : ", err);
73 | }
74 | },
75 | reject: () => {},
76 | });
77 | };
78 |
79 | const editLink = (object) => {
80 | return (
81 | <>
82 |
83 |
84 |
85 | {
87 | confirm(object._id);
88 | }}
89 | >
90 |
91 |
92 | >
93 | );
94 | };
95 |
96 | const accountRef = (object) => {
97 | const account = object.accountRef;
98 | const result = accounts.find((data) => data._id === account);
99 | if (result === undefined) {
100 | return {t('No longer exist')}
;
101 | }
102 | return {result && result.label}
;
103 | };
104 |
105 | const vaultRef = (object) => {
106 | const vault = object.vaultRef;
107 | const result = vaults.find((data) => data._id === vault);
108 | if (result === undefined) {
109 | return {t('No longer exist')}
;
110 | }
111 | return {result && result.label}
;
112 | };
113 |
114 | useEffect(() => {
115 | fetchAccounts();
116 | fetchVaults().then(() => {
117 | fetchData();
118 | });
119 | }, []);
120 |
121 | return (
122 | <>
123 |
124 |
125 |
130 |
131 |
132 | {t('Listing')}
133 |
134 |
135 |
136 |
137 |
138 |
139 |
144 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | >
156 | );
157 | };
158 |
159 | export default listTransaction;
160 |
--------------------------------------------------------------------------------
/client/src/finances/expense/ListExpenses.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import { Card } from "primereact/card";
4 | import { Divider } from "primereact/divider";
5 | import { DataTable } from "primereact/datatable";
6 | import { Column } from "primereact/column";
7 |
8 | import { ConfirmDialog, confirmDialog } from "primereact/confirmdialog";
9 |
10 | import { useTranslation } from 'react-i18next';
11 |
12 | const listExpenses = () => {
13 | const {t, i18n} = useTranslation()
14 |
15 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/expenses/list`;
16 | const urlAPIAccounts = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/all/accounts`;
17 | const urlAPIBucket = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/buckets/list`;
18 |
19 | const [data, setData] = useState();
20 | const [accounts, setAccounts] = useState();
21 | const [buckets, setBuckets] = useState();
22 |
23 | const fetchData = async () => {
24 | try {
25 | const fetchGet = await fetch(urlAPI),
26 | fetchResponse = await fetchGet.json();
27 | setData(fetchResponse.data);
28 | } catch (error) {
29 | console.error("An error occurred while executing fetchData() : ", error);
30 | }
31 | };
32 |
33 | const fetchAccounts = async () => {
34 | try {
35 | const fetchGet = await fetch(urlAPIAccounts),
36 | fetchResponse = await fetchGet.json();
37 | setAccounts(fetchResponse.data);
38 | } catch (err) {
39 | console.error("An error occurred while executing fetchData() : ", error);
40 | }
41 | };
42 |
43 | const fetchBucket = async () => {
44 | try {
45 | const fetchGet = await fetch(urlAPIBucket),
46 | fetchResponse = await fetchGet.json();
47 | setBuckets(fetchResponse.data);
48 | } catch (err) {
49 | console.error("An error occurred while executing fetchData() : ", error);
50 | }
51 | };
52 |
53 | const confirm = (id) => {
54 | confirmDialog({
55 | message: `${t('Delete expense confirmation')}`,
56 | header: "Delete Confirmation",
57 | icon: "pi pi-info-circle",
58 | defaultFocus: "reject",
59 | acceptClassName: "p-button-danger",
60 | accept: async () => {
61 | try {
62 | await fetch(`${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/expense/delete/${id}/`, {
63 | method: "POST",
64 | headers: {
65 | "Content-Type": "application/json",
66 | },
67 | });
68 | window.location.reload();
69 | } catch (err) {
70 | console.error("An error occurred : ", err);
71 | }
72 | },
73 | reject: () => {},
74 | });
75 | };
76 |
77 | const editLink = (object) => {
78 | return (
79 | <>
80 | {
82 | confirm(object._id);
83 | }}
84 | >
85 |
86 |
87 | >
88 | );
89 | };
90 |
91 | const dateList = (object) => {
92 | const date = new Date(object.date);
93 | const dateFormatted = date.toLocaleString("gb-GB");
94 | return {dateFormatted}
;
95 | };
96 |
97 | const accountRef = (object) => {
98 | const account = object.accountRef;
99 | const result = accounts.find((data) => data._id === account);
100 | if (result === undefined) {
101 | return {t('No longer exist')}
;
102 | }
103 | return {result && result.label}
;
104 | };
105 |
106 | const bucketRef = (object) => {
107 | const bucket = object.bucketRef;
108 | const result = buckets.find((data) => data._id === bucket);
109 | if (result === undefined) {
110 | return {t('No longer exist')}
;
111 | }
112 | return {result && result.label}
;
113 | };
114 |
115 | useEffect(() => {
116 | fetchAccounts();
117 | fetchBucket().then(() => {
118 | fetchData();
119 | });
120 | }, []);
121 |
122 | return (
123 | <>
124 |
125 |
126 |
131 |
132 |
133 | {t('Listing')}
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
146 |
151 |
152 |
153 |
154 |
155 |
156 | >
157 | );
158 | };
159 |
160 | export default listExpenses;
161 |
--------------------------------------------------------------------------------
/client/src/i18n/translationIT.json:
--------------------------------------------------------------------------------
1 | {
2 | "Account REF": "RIF Conto",
3 | "Account Subtitle": "Elenca tutti i tuoi conti, se vuoi modificare il saldo dei tuoi conti, aggiungi una transazione",
4 | "Accounts": "Conti",
5 | "Add Account Subtitle": "Che sia un conto bancario o un salvadanaio",
6 | "Add an account": "Aggiungi un conto",
7 | "Add an expense": "Aggiungi una spesa",
8 | "Add or remove money from one of your accounts": "Aggiungi o rimuovi denaro da uno dei tuoi conti",
9 | "Amount": "Importo",
10 | "Assignable": "Assegnabile",
11 | "Balance": "Saldo",
12 | "Buckets": "Categorie",
13 | "Create a bucket": "Crea una categoria",
14 | "Create a vault": "Crea una cassaforte",
15 | "Create bucket subtitle": "Organizza il tuo denaro",
16 | "Create bucket title": "Crea una categoria",
17 | "Create vault subtitle": "Organizza il tuo impero",
18 | "Create vault title": "Crea una cassaforte",
19 | "Date": "Data",
20 | "Date Limit": "Data Limite",
21 | "DateDue tooltip": "Imposta una data di scadenza per il tuo obiettivo!",
22 | "Delete Account Confirmation": "Sei sicuro di voler eliminare questo conto?",
23 | "Delete Bucket Confirmation": "Sei sicuro di voler eliminare questa categoria?",
24 | "Delete Transaction Confirmation": "Sei sicuro di voler eliminare questa transazione?",
25 | "Delete expense confirmation": "Sei sicuro di voler eliminare questa spesa?",
26 | "Delete vault confirmation": "Sei sicuro di voler eliminare questa cassaforte?",
27 | "Due date": "Data di scadenza",
28 | "Edit": "Modifica",
29 | "Edit bucket subtitle": "Organizza il tuo denaro",
30 | "Edit bucket title": "Modifica la tua categoria",
31 | "Edit vault subtitle": "Organizza il tuo impero",
32 | "Edit vault title": "Modifica la tua cassaforte",
33 | "Every month": "Ogni mese",
34 | "Expense": "Spesa",
35 | "Expense list subtitle": "Elenca tutte le tue spese",
36 | "Expense list title": "Spese",
37 | "Expenses": "Spese",
38 | "Form": "Modulo",
39 | "Help ?": "Aiuto?",
40 | "Initial balance of your account": "Saldo iniziale del tuo conto",
41 | "Label": "Etichetta",
42 | "Label (only to recognize)": "Etichetta (solo per riconoscere)",
43 | "Label of your account": "Etichetta del tuo conto",
44 | "Label of your vault": "Etichetta della tua cassaforte",
45 | "Linked account": "Conto collegato",
46 | "List all your transactions, income or withdraw": "Elenca tutte le tue transazioni, entrate o prelievi",
47 | "List all your vault transactions": "Elenca tutte le transazioni della tua cassaforte",
48 | "List all your vaults and edit them": "Elenca tutte le tue casseforti e modificale",
49 | "List buckets subtitle": "Elenca tutte le tue categorie e modificale",
50 | "List bucket subtitle": "Non essere timido, spendi!",
51 | "List bucket title": "🥛 Categorie",
52 | "List vault subtitle": "Per imperatori, costruisci il tuo impero",
53 | "List vault title": "🏦 Casseforti",
54 | "Listing": "Elenco",
55 | "Listing / Edit": "Elenco / Modifica",
56 | "Make a transaction": "Effettua una transazione",
57 | "Made to help anybody": "Fatto per aiutare chiunque",
58 | "Management": "Gestione",
59 | "Monthly": "Mensile",
60 | "Monthly tooltip": "Risparmia una somma ogni mese",
61 | "New source account": "Nuovo conto di origine",
62 | "No longer exist": "Non esiste più",
63 | "OR": "O",
64 | "Option": "Opzione",
65 | "Options": "Opzioni",
66 | "Resume": "Riprendi",
67 | "Save an amount every month": "Risparmia una somma ogni mese",
68 | "Set an date due for your goal !": "Imposta una data di scadenza per il tuo obiettivo!",
69 | "Source account": "Conto di origine",
70 | "Stamp an expense": "Timbra una spesa",
71 | "Submit": "Invia",
72 | "Switch to": "Passa a",
73 | "Target": "Obiettivo",
74 | "Target global": "Obiettivo globale",
75 | "Target to reach every month": "Obiettivo da raggiungere ogni mese",
76 | "Targeted": "Mirato",
77 | "The due date has passed, you have transferred a total of": "La data di scadenza è passata, hai trasferito un totale di",
78 | "The old balance switch to": "Il vecchio saldo è passato a",
79 | "Time Limited": "Tempo Limitato",
80 | "Total assignable": "Totale assegnabile",
81 | "Total on your account": "Totale sul tuo conto",
82 | "Total transfered": "Totale trasferito",
83 | "Total unassignable": "Totale non assegnabile",
84 | "Transactions": "Transazioni",
85 | "Transactions list subtitle": "Elenca tutte le tue transazioni, entrate o prelievi",
86 | "Transactions listing": "Elenco transazioni",
87 | "Transactions vault": "Transazioni della cassaforte",
88 | "Transactions vault subtitle": "Risparmio denaro",
89 | "Transfered": "Trasferito",
90 | "Vault": "Cassaforte",
91 | "Vault REF": "RIF Cassaforte",
92 | "Vaults": "Casseforti",
93 | "You must assign an amount": "Devi assegnare un importo",
94 | "You need to transfer": "Devi trasferire",
95 | "assigned": "assegnato",
96 | "assigned this month": "assegnato questo mese",
97 | "more is needed for reach target": "serve di più per raggiungere l'obiettivo",
98 | "needed": "necessario",
99 | "required this month": "richiesto questo mese",
100 | "switch to": "passa a",
101 | "target isn't required": "l'obiettivo non è richiesto",
102 | "to reach the target this month": "per raggiungere l'obiettivo questo mese",
103 | "transfered on": "trasferito il",
104 | "spended / assigned this month":"% speso / assegnato questo mese"
105 | }
--------------------------------------------------------------------------------
/client/src/i18n/translationDE.json:
--------------------------------------------------------------------------------
1 | {
2 | "Account REF": "Konto-REF",
3 | "Account Subtitle": "Liste alle deine Konten auf, wenn du das Guthaben deiner Konten ändern möchtest, füge eine Transaktion hinzu",
4 | "Accounts": "Konten",
5 | "Add Account Subtitle": "Egal ob Bankkonto oder Sparschwein",
6 | "Add an account": "Konto hinzufügen",
7 | "Add an expense": "Ausgabe hinzufügen",
8 | "Add or remove money from one of your accounts": "Geld von einem deiner Konten hinzufügen oder entfernen",
9 | "Amount": "Betrag",
10 | "Assignable": "Zuweisbar",
11 | "Balance": "Guthaben",
12 | "Buckets": "Kategorien",
13 | "Create a bucket": "Kategorie erstellen",
14 | "Create a vault": "Tresor erstellen",
15 | "Create bucket subtitle": "Organisiere dein Geld",
16 | "Create bucket title": "Kategorie erstellen",
17 | "Create vault subtitle": "Organisiere dein Imperium",
18 | "Create vault title": "Tresor erstellen",
19 | "Date": "Datum",
20 | "Date Limit": "Fristdatum",
21 | "DateDue tooltip": "Setze ein Fälligkeitsdatum für dein Ziel!",
22 | "Delete Account Confirmation": "Bist du sicher, dass du dieses Konto löschen möchtest?",
23 | "Delete Bucket Confirmation": "Bist du sicher, dass du diese Kategorie löschen möchtest?",
24 | "Delete Transaction Confirmation": "Bist du sicher, dass du diese Transaktion löschen möchtest?",
25 | "Delete expense confirmation": "Bist du sicher, dass du diese Ausgabe löschen möchtest?",
26 | "Delete vault confirmation": "Bist du sicher, dass du diesen Tresor löschen möchtest?",
27 | "Due date": "Fälligkeitsdatum",
28 | "Edit": "Bearbeiten",
29 | "Edit bucket subtitle": "Organisiere dein Geld",
30 | "Edit bucket title": "Kategorie bearbeiten",
31 | "Edit vault subtitle": "Organisiere dein Imperium",
32 | "Edit vault title": "Tresor bearbeiten",
33 | "Every month": "Jeden Monat",
34 | "Expense": "Ausgabe",
35 | "Expense list subtitle": "Liste alle deine Ausgaben",
36 | "Expense list title": "Ausgaben",
37 | "Expenses": "Ausgaben",
38 | "Form": "Formular",
39 | "Help ?": "Hilfe?",
40 | "Initial balance of your account": "Anfangsguthaben deines Kontos",
41 | "Label": "Bezeichnung",
42 | "Label (only to recognize)": "Bezeichnung (nur zur Erkennung)",
43 | "Label of your account": "Bezeichnung deines Kontos",
44 | "Label of your vault": "Bezeichnung deines Tresors",
45 | "Linked account": "Verknüpftes Konto",
46 | "List all your transactions, income or withdraw": "Liste alle deine Transaktionen, Einnahmen oder Auszahlungen",
47 | "List all your vault transactions": "Liste alle deine Tresor-Transaktionen",
48 | "List all your vaults and edit them": "Liste alle deine Tresore und bearbeite sie",
49 | "List buckets subtitle": "Liste alle deine Kategorien und bearbeite sie",
50 | "List bucket subtitle": "Sei nicht schüchtern, gib es aus!",
51 | "List bucket title": "🥛 Kategorien",
52 | "List vault subtitle": "Für Kaiser, baue dein Imperium",
53 | "List vault title": "🏦 Tresore",
54 | "Listing": "Auflistung",
55 | "Listing / Edit": "Auflistung / Bearbeiten",
56 | "Make a transaction": "Transaktion durchführen",
57 | "Made to help anybody": "Gemacht, um jedem zu helfen",
58 | "Management": "Verwaltung",
59 | "Monthly": "Monatlich",
60 | "Monthly tooltip": "Spare jeden Monat einen Betrag",
61 | "New source account": "Neues Quellkonto",
62 | "No longer exist": "Existiert nicht mehr",
63 | "OR": "ODER",
64 | "Option": "Option",
65 | "Options": "Optionen",
66 | "Resume": "Zusammenfassung",
67 | "Save an amount every month": "Spare jeden Monat einen Betrag",
68 | "Set an date due for your goal !": "Setze ein Fälligkeitsdatum für dein Ziel!",
69 | "Source account": "Quellkonto",
70 | "Stamp an expense": "Ausgabe stempeln",
71 | "Submit": "Einreichen",
72 | "Switch to": "Wechseln zu",
73 | "Target": "Ziel",
74 | "Target global": "Globales Ziel",
75 | "Target to reach every month": "Ziel, das jeden Monat erreicht werden soll",
76 | "Targeted": "Gezielt",
77 | "The due date has passed, you have transferred a total of": "Das Fälligkeitsdatum ist verstrichen, du hast insgesamt überwiesen",
78 | "The old balance switch to": "Das alte Guthaben wechselte zu",
79 | "Time Limited": "Zeitlich begrenzt",
80 | "Total assignable": "Insgesamt zuweisbar",
81 | "Total on your account": "Gesamt auf deinem Konto",
82 | "Total transfered": "Insgesamt überwiesen",
83 | "Total unassignable": "Insgesamt nicht zuweisbar",
84 | "Transactions": "Transaktionen",
85 | "Transactions list subtitle": "Liste alle deine Transaktionen, Einnahmen oder Auszahlungen",
86 | "Transactions listing": "Transaktionsliste",
87 | "Transactions vault": "Tresor-Transaktionen",
88 | "Transactions vault subtitle": "Geld sparen",
89 | "Transfered": "Überwiesen",
90 | "Vault": "Tresor",
91 | "Vault REF": "Tresor-REF",
92 | "Vaults": "Tresore",
93 | "You must assign an amount": "Du musst einen Betrag zuweisen",
94 | "You need to transfer": "Du musst übertragen",
95 | "assigned": "zugewiesen",
96 | "assigned this month": "diesen Monat zugewiesen",
97 | "more is needed for reach target": "mehr wird benötigt, um das Ziel zu erreichen",
98 | "needed": "benötigt",
99 | "required this month": "diesen Monat erforderlich",
100 | "switch to": "wechseln zu",
101 | "target isn't required": "Ziel ist nicht erforderlich",
102 | "to reach the target this month": "um das Ziel diesen Monat zu erreichen",
103 | "transfered on": "überwiesen am",
104 | "spended / assigned this month":"% ausgegeben / zugewiesen diesen Monat"
105 | }
--------------------------------------------------------------------------------
/client/src/i18n/translationFR.json:
--------------------------------------------------------------------------------
1 | {
2 | "Account REF": "Référence du compte",
3 | "Account Subtitle": "Listez tous vos comptes, si vous voulez modifier le solde de vos comptes, ajoutez une transaction",
4 | "Accounts": "Comptes",
5 | "Add Account Subtitle": "Que ce soit un compte bancaire ou une tirelire",
6 | "Add an account": "Ajouter un compte",
7 | "Add an expense": "Ajouter une dépense",
8 | "Add or remove money from one of your accounts": "Ajouter ou retirer de l'argent d'un de vos comptes",
9 | "Amount": "Montant",
10 | "Assignable": "Affectable",
11 | "Balance": "Solde",
12 | "Buckets": "Cagnottes",
13 | "Create a bucket": "Créer une cagnotte",
14 | "Create a vault": "Créer un coffre-fort",
15 | "Create bucket subtitle": "Organisez votre argent",
16 | "Create bucket title": "Créer une cagnotte",
17 | "Create vault subtitle": "Organisez votre empire",
18 | "Create vault title": "Créer un coffre-fort",
19 | "Date": "Date",
20 | "Date Limit": "Date limite",
21 | "DateDue tooltip": "Définissez une date d'échéance pour votre objectif !",
22 | "Delete Account Confirmation": "Êtes-vous sûr de vouloir supprimer ce compte ?",
23 | "Delete Bucket Confirmation": "Êtes-vous sûr de vouloir supprimer cette cagnotte ?",
24 | "Delete Transaction Confirmation": "Êtes-vous sûr de vouloir supprimer cette transaction ?",
25 | "Delete expense confirmation": "Êtes-vous sûr de vouloir supprimer cette dépense ?",
26 | "Delete vault confirmation": "Êtes-vous sûr de vouloir supprimer ce coffre-fort ?",
27 | "Due date": "Date d'échéance",
28 | "Edit": "Modifier",
29 | "Edit bucket subtitle": "Organisez votre argent",
30 | "Edit bucket title": "Modifier votre cagnotte",
31 | "Edit vault subtitle": "Organisez votre empire",
32 | "Edit vault title": "Modifier votre coffre-fort",
33 | "Every month": "Chaque mois",
34 | "Expense": "Dépense",
35 | "Expense list subtitle": "Listez toutes vos dépenses",
36 | "Expense list title": "Dépenses",
37 | "Expenses": "Dépenses",
38 | "Form": "Formulaire",
39 | "Help ?": "Besoin d'aide ?",
40 | "Initial balance of your account": "Solde initial de votre compte",
41 | "Label": "Libellé",
42 | "Label (only to recognize)": "Libellé (uniquement pour reconnaître)",
43 | "Label of your account": "Libellé de votre compte",
44 | "Label of your vault": "Libellé de votre coffre-fort",
45 | "Linked account": "Compte lié",
46 | "List all your transactions, income or withdraw": "Listez toutes vos transactions, revenus ou retraits",
47 | "List all your vault transactions": "Listez toutes vos transactions de coffre-fort",
48 | "List all your vaults and edit them": "Listez tous vos coffres-forts et modifiez-les",
49 | "List buckets subtitle": "Listez toutes vos cagnottes et modifiez-les",
50 | "List bucket subtitle": "Ne soyez pas timide, dépensez-le !",
51 | "List bucket title": "🥛 Cagnottes",
52 | "List vault subtitle": "Pour les empereurs, construisez votre empire",
53 | "List vault title": "🏦 Coffres-forts",
54 | "Listing": "Liste",
55 | "Listing / Edit": "Liste / Modifier",
56 | "Make a transaction": "Faire une transaction",
57 | "Made to help anybody": "Conçu pour aider quiconque",
58 | "Management": "Gestion",
59 | "Monthly": "Mensuel",
60 | "Monthly tooltip": "Économisez un montant chaque mois",
61 | "New source account": "Nouveau compte source",
62 | "No longer exist": "N'existe plus",
63 | "OR": "OU",
64 | "Option": "Option",
65 | "Options": "Options",
66 | "Resume": "Résumé",
67 | "Save an amount every month": "Économisez un montant chaque mois",
68 | "Set an date due for your goal !": "Définissez une date d'échéance pour votre objectif !",
69 | "Solde": "Solde",
70 | "Source account": "Compte source",
71 | "Stamp an expense": "Tamponner une dépense",
72 | "Submit": "Soumettre",
73 | "Switch to": "Passer à",
74 | "Target": "Objectif",
75 | "Target global": "Objectif global",
76 | "Target to reach every month": "Objectif à atteindre chaque mois",
77 | "Targeted": "Ciblé",
78 | "The due date has passed, you have transferred a total of": "La date d'échéance est passée, vous avez transféré un total de",
79 | "The old balance switch to": "Le solde précédent est passé à",
80 | "Time Limited": "Limité dans le temps",
81 | "Total assignable": "Total affectable",
82 | "Total on your account": "Total sur votre compte",
83 | "Total transfered": "Total transféré",
84 | "Total unassignable": "Total non affectable",
85 | "Transactions": "Transactions",
86 | "Transactions list subtitle": "Listez toutes vos transactions, revenus ou retraits",
87 | "Transactions listing": "Liste des transactions",
88 | "Transactions vault": "Transactions de coffre-fort",
89 | "Transactions vault subtitle": "Épargne",
90 | "Transfered": "Transféré",
91 | "Vault": "Coffre-fort",
92 | "Vault REF": "Référence du coffre-fort",
93 | "Vaults": "Coffres-forts",
94 | "You must assign an amount": "Vous devez affecter un montant",
95 | "You need to transfer": "Vous devez transférer",
96 | "assigned": "affecté",
97 | "assigned this month": "affecté ce mois-ci",
98 | "more is needed for reach target": "plus est nécessaire pour atteindre l'objectif",
99 | "needed": "nécessaire",
100 | "required this month": "nécessaire ce mois-ci",
101 | "switch to": "passer à",
102 | "target isn't required": "l'objectif n'est pas requis",
103 | "to reach the target this month": "pour atteindre l'objectif ce mois-ci",
104 | "transfered on": "transféré le",
105 | "spended / assigned this month":"% dépensé / assigné ce mois-ci"
106 | }
--------------------------------------------------------------------------------
/client/src/Layout.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet, Link, useNavigate } from "react-router-dom";
2 | import { MegaMenu } from "primereact/megamenu";
3 | import logo from "./assets/logo.svg";
4 | import { useTranslation } from 'react-i18next';
5 |
6 | const Layout = () => {
7 | const navigate = useNavigate();
8 | const dark = window.matchMedia("(prefers-color-scheme: dark)");
9 | const {t, i18n} = useTranslation()
10 |
11 | const items = [
12 | {
13 | label: `${t('Accounts')}`,
14 | items: [
15 | [
16 | {
17 | label: `${t('Management')}`,
18 | items: [
19 | {
20 | label: `${t('Listing / Edit')}`,
21 | command: () => {
22 | navigate("/accounts/all");
23 | },
24 | },
25 | {
26 | label: `${t('Add an account')}`,
27 | command: () => {
28 | navigate("/accounts/add");
29 | },
30 | },
31 | {
32 | label: `${t('Transactions listing')}`,
33 | command: () => {
34 | navigate("/transactions/list");
35 | },
36 | },
37 | ],
38 | },
39 | {
40 | label: `${t('Useful')}`,
41 | items: [
42 | {
43 | label: `${t('Make a transaction')}`,
44 | command: () => {
45 | navigate("/transactions/add");
46 | },
47 | },
48 | ],
49 | },
50 | ],
51 | ],
52 | },
53 | {
54 | label: `${t('Buckets')}`,
55 | items: [
56 | [
57 | {
58 | label: `${t('Management')}`,
59 | items: [
60 | {
61 | label: `${t('Listing / Edit')}`,
62 | command: () => {
63 | navigate("/buckets/list");
64 | },
65 | },
66 | {
67 | label: `${t('Create a bucket')}`,
68 | command: () => {
69 | navigate("/bucket/create");
70 | },
71 | },
72 | ],
73 | },
74 | ],
75 | ],
76 | },
77 | {
78 | label: `${t('Vaults')}`,
79 | items: [
80 | [
81 | {
82 | label: `${t('Management')}`,
83 | items: [
84 | {
85 | label: `${t('Listing / Edit')}`,
86 | command: () => {
87 | navigate("/vaults/list");
88 | },
89 | },
90 | {
91 | label: `${t('Create a vault')}`,
92 | command: () => {
93 | navigate("/vault/create");
94 | },
95 | },
96 | {
97 | label: `${t('Transactions vaults')}`,
98 | command: () => {
99 | navigate("/transactions/vault");
100 | },
101 | },
102 | {
103 | label: `${t('Transactions listing')}`,
104 | command: () => {
105 | navigate("/transactions/vault/list");
106 | },
107 | },
108 | ],
109 | },
110 | ],
111 | ],
112 | },
113 | {
114 | label: `${t('Add an expense')}`,
115 | icon: "pi pi-calculator",
116 | command: () => {
117 | navigate("/expense/add");
118 | },
119 | },
120 | {
121 | label: `${t('Expenses')}`,
122 | items: [
123 | [
124 | {
125 | label: `${t('Management')}`,
126 | items: [
127 | {
128 | label: `${t('Listing / Edit')}`,
129 | command: () => {
130 | navigate("/expenses/list");
131 | },
132 | },
133 | ],
134 | },
135 | ],
136 | ],
137 | },
138 | {
139 | label: `${t('Help ?')}`,
140 | icon: "pi pi-question-circle",
141 | command: () => {
142 | navigate("/help");
143 | },
144 | },
145 | ];
146 |
147 | return (
148 | <>
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | {" "}
158 |
159 |
160 |
161 |
162 |
163 |
164 | >
165 | );
166 | };
167 |
168 | export default Layout;
169 |
170 | {
171 | /**
172 |
173 | Home
174 |
175 |
176 | Accounts
177 |
178 |
179 | Add Account
180 |
181 |
182 | Add Expense
183 |
184 |
185 | Add Transactions
186 |
187 |
188 | Create Vault
189 |
190 |
191 | Create Bucket
192 |
193 |
194 | Transaction Vault
195 |
196 | */
197 | }
198 |
--------------------------------------------------------------------------------
/client/src/finances/buckets/EditBucket.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate, useParams } from "react-router-dom";
3 |
4 | import { Card } from "primereact/card";
5 | import { InputText } from "primereact/inputtext";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Divider } from "primereact/divider";
9 | import { Calendar } from "primereact/calendar";
10 | import { Checkbox } from "primereact/checkbox";
11 |
12 | import { useTranslation } from 'react-i18next';
13 |
14 | const editBucket = () => {
15 | const {t, i18n} = useTranslation()
16 |
17 | let { id } = useParams();
18 |
19 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/bucket/${id}`;
20 | const navigate = useNavigate();
21 |
22 | const [date, setDate] = useState(false);
23 | const [data, setData] = useState(false);
24 |
25 | const [timelimited, setTimeLimited] = useState();
26 | const [targeted, setTargeted] = useState();
27 |
28 | const fetchData = async () => {
29 | try {
30 | const fetchGet = await fetch(urlAPI),
31 | fetchResponse = await fetchGet.json();
32 | return fetchResponse.data;
33 | } catch (error) {
34 | console.error("An error occurred while executing fetchData() : ", error);
35 | }
36 | };
37 |
38 | const handleChange = (e) => {
39 | fetchData().then((data) => {
40 | setData(data);
41 | });
42 | };
43 |
44 | const postForm = async (e) => {
45 | e.preventDefault();
46 | try {
47 | navigate("/");
48 | await fetch(urlAPI, {
49 | method: "POST",
50 | headers: {
51 | "Content-Type": "application/json",
52 | },
53 | body: JSON.stringify({
54 | data,
55 | targeted,
56 | timelimited,
57 | date,
58 | }),
59 | });
60 | } catch (err) {
61 | console.error("An error occurred : ", err);
62 | }
63 | };
64 |
65 | useEffect(() => {
66 | fetchData().then((data) => {
67 | setData(data);
68 | const fetchedDate = new Date(data.dateLimit);
69 | setDate(fetchedDate);
70 | setTargeted(data.target);
71 | setTimeLimited(data.timeLimited);
72 | });
73 | }, []);
74 | return (
75 | <>
76 |
77 |
82 |
83 |
84 | {t('Options')}
85 |
86 |
87 |
88 |
89 | setTargeted(e.checked)}
92 | checked={targeted}
93 | >
94 |
95 | {t('Targeted')}
96 |
97 |
98 |
99 |
100 | setTimeLimited(e.checked)}
103 | checked={timelimited}
104 | >
105 |
106 | {t('Time limited')}
107 |
108 |
109 |
110 |
111 |
112 |
113 | {t('Form')}
114 |
115 |
116 |
117 |
118 |
119 |
120 | {t('Label of your vault')}
121 |
122 |
123 | setData({ ...data, label: e.target.value })}
129 | />
130 |
131 |
132 |
133 |
134 | {t('Target to reach every month')}
135 |
136 |
137 |
142 | setData({ ...data, defaultTarget: e.target.value })
143 | }
144 | mode="currency"
145 | currency="EUR"
146 | locale="fr-Fr"
147 | disabled={!targeted}
148 | />
149 |
150 |
151 |
152 |
153 | {t('Date limit')}
154 |
155 |
160 | setDate(e.target.value)
161 | }
162 | hourFormat="24"
163 | disabled={!timelimited}
164 | />
165 |
166 |
167 |
168 |
169 |
174 |
175 |
176 |
177 | >
178 | );
179 | };
180 |
181 | export default editBucket;
--------------------------------------------------------------------------------
/client/src/finances/buckets/CreateBucket.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import { Card } from "primereact/card";
5 | import { InputText } from "primereact/inputtext";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Divider } from "primereact/divider";
9 | import { Calendar } from "primereact/calendar";
10 | import { Checkbox } from "primereact/checkbox";
11 |
12 | import { useTranslation } from 'react-i18next';
13 |
14 | const createBucket = () => {
15 | const {t, i18n} = useTranslation()
16 |
17 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/buckets/create`;
18 | const navigate = useNavigate();
19 |
20 | const [date, setDate] = useState(new Date());
21 | const [formData, setFormData] = useState({});
22 |
23 | const [timelimited, setTimeLimited] = useState(false);
24 | const [targeted, setTargeted] = useState(false);
25 |
26 | const handleChange = (e) => {
27 | const { id, value } = e.target;
28 | setFormData((prevData) => ({
29 | ...prevData,
30 | [id]: value,
31 | }));
32 | };
33 |
34 | const postForm = async (e) => {
35 | e.preventDefault();
36 | try {
37 | navigate("/");
38 | await fetch(urlAPI, {
39 | method: "POST",
40 | headers: {
41 | "Content-Type": "application/json",
42 | },
43 | body: JSON.stringify({
44 | label: formData.label,
45 | timelimited,
46 | date,
47 | targeted,
48 | target: formData.target,
49 | }),
50 | });
51 | } catch (err) {
52 | console.error("An error occurred : ", err);
53 | }
54 | };
55 |
56 | useEffect(() => {}, []);
57 | return (
58 | <>
59 |
60 |
65 |
66 |
67 | {t('Options')}
68 |
69 |
70 |
71 |
72 |
73 | setTargeted(e.checked)}
76 | checked={targeted}
77 | >
78 |
79 | {t('Targeted')}
80 |
81 |
82 |
83 |
84 | setTimeLimited(e.checked)}
87 | checked={timelimited}
88 | >
89 |
90 | {t('Time Limited')}
91 |
92 |
93 |
94 |
95 |
96 |
97 | {t('Form')}
98 |
99 |
100 |
101 |
102 |
103 |
104 | {t('Label')}
105 |
106 |
107 |
113 |
114 |
115 |
116 |
117 | {t('Target to reach every month')}
118 |
119 |
120 | {
124 | setFormData((prevData) => ({
125 | ...prevData,
126 | target: e.value,
127 | }));
128 | }}
129 | mode="currency"
130 | currency="EUR"
131 | locale="fr-Fr"
132 | disabled={!targeted}
133 | />
134 |
135 |
136 |
137 |
138 | {t('Date Limit')}
139 |
140 | setDate(e.value)}
145 | hourFormat="24"
146 | disabled={!timelimited}
147 | />
148 |
149 |
150 | {/**
151 |
152 | Initial balance of your account
153 |
154 |
155 | {
159 | setFormData((prevData) => ({
160 | ...prevData,
161 | balance: e.value,
162 | }));
163 | }}
164 | mode="currency"
165 | currency="EUR"
166 | locale="fr-Fr"
167 | />
168 |
*/}
169 |
170 |
171 |
172 |
177 |
178 |
179 |
180 | >
181 | );
182 | };
183 |
184 | export default createBucket;
185 |
--------------------------------------------------------------------------------
/client/src/finances/vaults/Vaults.jsx:
--------------------------------------------------------------------------------
1 | import { DataTable } from "primereact/datatable";
2 | import { Column } from "primereact/column";
3 | import { InputNumber } from "primereact/inputnumber";
4 | import { Tag } from "primereact/tag";
5 | import { Message } from "primereact/message";
6 |
7 | import { useTranslation } from 'react-i18next';
8 |
9 | const Vaults = (props) => {
10 | const {t, i18n} = useTranslation()
11 | const dueDate = (rowData) => {
12 | const date = new Date(rowData.dateDue);
13 | const day = date.getDate();
14 | const month = date.getMonth() + 1;
15 | const year = date.getFullYear();
16 | if (rowData.monthlyType === true) {
17 | return ;
18 | }
19 | return ;
20 | };
21 | const needed = (rowData) => {
22 | const calculate = rowData.target - rowData.monthly.assigned;
23 | if (rowData.monthly.assigned < rowData.target) {
24 | return ;
25 | }
26 | };
27 |
28 | const assigned = (rowData) => {
29 | const target = rowData.target;
30 | const assigned = rowData.monthly.assigned;
31 | const monthlyType = rowData.monthlyType;
32 | const date = new Date(rowData.monthly.date);
33 | const dateDue = new Date(rowData.dateDue);
34 | const countMonth =
35 | (dateDue.getFullYear() - date.getFullYear()) * 12 +
36 | (dateDue.getMonth() - date.getMonth()) +
37 | 1;
38 |
39 | if (!monthlyType) {
40 | const targetMonthly = (target - rowData.prevTransactions) / countMonth;
41 | if (countMonth <= 0) {
42 | return (
43 | <>
44 |
51 | >
52 | );
53 | } else if (assigned < targetMonthly) {
54 | return (
55 | <>
56 |
63 | >
64 | );
65 | } else if (assigned >= targetMonthly) {
66 | return assigned;
67 | }
68 | }
69 | if (assigned < target) {
70 | return (
71 | <>
72 |
77 | >
78 | );
79 | } else if (assigned >= target) {
80 | return assigned;
81 | }
82 | };
83 |
84 | const neededThisMonth = (rowData) => {
85 | const target = rowData.target;
86 | const assigned = rowData.monthly.assigned;
87 | const monthlyType = rowData.monthlyType;
88 | const date = new Date(rowData.monthly.date);
89 | const dateDue = new Date(rowData.dateDue);
90 | if (monthlyType === true) {
91 | if (assigned >= target) {
92 | return <>0>;
93 | } else if (assigned < target) {
94 | return <>{target - assigned}>;
95 | }
96 | } else if (monthlyType === false) {
97 | const countMonth =
98 | (dateDue.getFullYear() - date.getFullYear()) * 12 +
99 | (dateDue.getMonth() - date.getMonth()) +
100 | 1;
101 | const monthlyDue =
102 | (target - (rowData.transactions + rowData.prevTransactions)) /
103 | countMonth;
104 | const targetMonthly = (target - rowData.prevTransactions) / countMonth;
105 | if (rowData.transactions >= targetMonthly) {
106 | return (
107 | <>
108 |
113 | >
114 | );
115 | }
116 | return (
117 | <>
118 |
125 | >
126 | );
127 | }
128 | };
129 | const totalTransactions = (rowData) => {
130 | const calculate = rowData.transactions + rowData.prevTransactions;
131 | return calculate;
132 | };
133 | const cellEditor = (options) => {
134 | return (
135 | options.editorCallback(e.value)}
138 | mode="currency"
139 | currency="EUR"
140 | locale="fr-FR"
141 | onKeyDown={(e) => e.stopPropagation()}
142 | />
143 | );
144 | };
145 |
146 | const onCellEditComplete = (e) => {
147 | let { rowData, newValue, field, originalEvent: event } = e;
148 | try {
149 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${
150 | import.meta.env.VITE_BACKEND_PORT
151 | }/edit/vaultcell`;
152 | fetch(urlAPI, {
153 | method: "POST",
154 | headers: {
155 | "Content-Type": "application/json",
156 | },
157 | body: JSON.stringify({
158 | id: rowData.monthly._id,
159 | idBucket: rowData.monthly.bucketRef,
160 | field: field,
161 | value: newValue,
162 | }),
163 | });
164 | } catch (err) {
165 | console.error("An error occurred : ", err);
166 | }
167 | };
168 | return (
169 | <>
170 |
176 |
177 |
178 |
179 | cellEditor(options)}
183 | onCellEditComplete={onCellEditComplete}
184 | header={t('assigned')}
185 | >
186 |
187 |
188 |
189 |
190 |
191 | {props.totalAssigned} €
192 | {t('assigned')}
193 |
194 | >
195 | );
196 | };
197 |
198 | export default Vaults;
199 |
--------------------------------------------------------------------------------
/client/src/finances/buckets/Buckets.jsx:
--------------------------------------------------------------------------------
1 | import { DataTable } from "primereact/datatable";
2 | import { ColumnGroup } from 'primereact/columngroup';
3 | import { Row } from 'primereact/row';
4 | import { Column } from "primereact/column";
5 | import { ProgressBar } from "primereact/progressbar";
6 | import { Tag } from "primereact/tag";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Message } from "primereact/message";
9 |
10 | import { useTranslation } from 'react-i18next';
11 |
12 |
13 | const Buckets = (props) => {
14 | const {t, i18n} = useTranslation()
15 |
16 | const available = (rowData) => {
17 | const calculate =
18 | rowData.monthly.assigned +
19 | rowData.prevAssigned +
20 | (-rowData.expense + -rowData.prevExpense);
21 | if (calculate < 0) {
22 | return ;
23 | }
24 | return ;
25 | };
26 |
27 | const assigned = (rowData) => {
28 | const target = rowData.monthly.target;
29 | const assigned = rowData.monthly.assigned;
30 | const needed = target - assigned;
31 | if (rowData.target === false) {
32 | return assigned;
33 | } else if (assigned < target) {
34 | return (
35 | <>
36 |
41 | >
42 | );
43 | } else if (assigned >= target) {
44 | return assigned;
45 | }
46 | };
47 | const target = (rowData) => {
48 | const target = rowData.targeted;
49 | if (target === true) {
50 | return rowData.monthly.target;
51 | }
52 | return (
53 | <>
54 |
59 | >
60 | );
61 | };
62 |
63 | const progressBar = (rowData) => {
64 | const calculate = (rowData.expense / rowData.monthly.assigned) * 100;
65 | if (rowData.monthly.assigned === 0) {
66 | return ;
67 | } else if (isNaN(calculate)) {
68 | return ;
69 | } else if (calculate > 100) {
70 | return (
71 |
75 | );
76 | } else if (calculate === 100) {
77 | return (
78 |
82 | );
83 | }
84 | return ;
85 | };
86 |
87 | const cellEditor = (options) => {
88 | return (
89 | options.editorCallback(e.value)}
93 | mode="currency"
94 | currency="EUR"
95 | locale="fr-FR"
96 | onKeyDown={(e) => e.stopPropagation()}
97 | />
98 | );
99 | };
100 |
101 | const onCellEditComplete = (e) => {
102 | let { rowData, newValue, field, originalEvent: event } = e;
103 | try {
104 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/bucketcell`;
105 | fetch(urlAPI, {
106 | method: "POST",
107 | headers: {
108 | "Content-Type": "application/json",
109 | },
110 | body: JSON.stringify({
111 | id: rowData.monthly._id,
112 | idBucket: rowData.monthly.bucketRef,
113 | field: field,
114 | value: newValue,
115 | }),
116 | });
117 | } catch (err) {
118 | console.error("An error occured : ", err);
119 | }
120 | };
121 |
122 | const footerTarget = () => {
123 | const data = props.fetch
124 | const calculate = data.reduce(
125 | (total, objet) => total + objet.monthly.target,
126 | 0
127 | );
128 | return calculate
129 | }
130 |
131 | const footerAvailable = () => {
132 | const data = props.fetch
133 | const assigned = data.reduce(
134 | (total, objet) => total + objet.monthly.assigned,
135 | 0
136 | );
137 | const prevAssigned = data.reduce(
138 | (total, objet) => total + objet.prevAssigned,
139 | 0
140 | );
141 | const expense = data.reduce(
142 | (total, objet) => total + objet.expense,
143 | 0
144 | );
145 | const prevExpense = data.reduce(
146 | (total, objet) => total + objet.prevExpense,
147 | 0
148 | );
149 | const calculate =
150 | assigned +
151 | prevAssigned +
152 | (-expense + -prevExpense);
153 |
154 | return calculate
155 | }
156 |
157 | const footerAssigned = () => {
158 | const data = props.fetch
159 | const calculate = data.reduce(
160 | (total, objet) => total + objet.monthly.assigned,
161 | 0
162 | );
163 | return calculate
164 | }
165 |
166 | const footerExpense = () => {
167 | const data = props.fetch
168 | const calculate = data.reduce(
169 | (total, objet) => total + objet.expense,
170 | 0
171 | );
172 | return calculate
173 | }
174 |
175 | const footerTable = (
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | );
187 |
188 | return (
189 | <>
190 |
197 |
198 |
203 | cellEditor(options)}
208 | onCellEditComplete={onCellEditComplete}
209 | header={t('Target')}
210 | >
211 | cellEditor(options)}
216 | onCellEditComplete={onCellEditComplete}
217 | header={t('assigned')}
218 | >
219 |
224 |
229 |
230 | >
231 | );
232 | };
233 |
234 | export default Buckets;
235 |
--------------------------------------------------------------------------------
/client/src/finances/expense/AddExpense.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import { Dropdown } from "primereact/dropdown";
5 | import { Button } from "primereact/button";
6 | import { InputNumber } from "primereact/inputnumber";
7 | import { InputText } from "primereact/inputtext";
8 | import { Card } from "primereact/card";
9 | import { Skeleton } from "primereact/skeleton";
10 | import { Divider } from "primereact/divider";
11 | import { Calendar } from "primereact/calendar";
12 | import { Chip } from "primereact/chip";
13 |
14 | import { IconWallet } from "@tabler/icons-react";
15 |
16 | import { useTranslation } from 'react-i18next';
17 |
18 | const Expense = () => {
19 | const {t, i18n} = useTranslation()
20 |
21 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/expenses/add`;
22 | const navigate = useNavigate();
23 | const [formData, setFormData] = useState({});
24 |
25 | const [accountsData, setAccountsData] = useState({});
26 | const [selectedAccount, setSelectedAccount] = useState(null);
27 |
28 | const [bucketsData, setBucketsData] = useState({});
29 | const [selectedBucket, setSelectedBucket] = useState(null);
30 |
31 | const [date, setDate] = useState(new Date());
32 |
33 | const fetchData = async () => {
34 | try {
35 | const fetchGet = await fetch(urlAPI),
36 | fetchResponse = await fetchGet.json();
37 | return fetchResponse;
38 | } catch (error) {
39 | console.error("An error occurred while executing fetchData() : ", error);
40 | }
41 | };
42 |
43 | const postData = async (e) => {
44 | navigate("/");
45 | e.preventDefault();
46 | try {
47 | await fetch(urlAPI, {
48 | method: "POST",
49 | headers: {
50 | "Content-Type": "application/json",
51 | },
52 | body: JSON.stringify({
53 | account: selectedAccount._id,
54 | bucket: selectedBucket._id,
55 | label: formData.label,
56 | amount: formData.amount,
57 | date,
58 | }),
59 | });
60 | } catch (err) {
61 | console.error("An error occurred : ", err);
62 | }
63 | };
64 |
65 | const addBalanceToAccount = (data) => {
66 | const transformedData = data.dataAccounts.map((item) => ({
67 | _id: item._id,
68 | type: "ACCOUNTS",
69 | label: `${item.label} | ${item.balance} €`,
70 | balance: item.balance,
71 | }));
72 | setAccountsData(transformedData);
73 | };
74 |
75 | useEffect(() => {
76 | fetchData().then((data) => {
77 | addBalanceToAccount(data);
78 | setBucketsData(data.dataBuckets);
79 | });
80 | }, []);
81 |
82 | return (
83 | <>
84 |
85 |
90 |
91 |
92 | {t('Resume')}
93 |
94 |
95 |
96 | {selectedBucket && formData.amount ? (
97 |
101 |
102 |
103 |
104 |
105 | {selectedAccount.label}, {t('The old balance switch to')}{" "}
106 | {selectedAccount.balance - formData.amount} €
107 |
108 | >
109 | }
110 | />
111 | ) : (
112 |
113 | )}
114 |
115 |
116 |
117 | {t('Form')}
118 |
119 |
120 |
121 |
122 |
123 | {t('Date')}
124 |
125 | setDate(e.value)}
130 | showTime
131 | hourFormat="24"
132 | />
133 |
134 |
135 |
136 | {t('Label (only to recognize')}
137 |
138 | {
143 | setFormData((prevData) => ({
144 | ...prevData,
145 | label: e.target.value,
146 | }));
147 | }}
148 | />
149 |
150 |
151 |
152 | {t('Source account')}
153 |
154 | setSelectedAccount(e.value)}
157 | options={accountsData}
158 | optionLabel="label"
159 | placeholder={t('Account')}
160 | className="w-full md:w-auto"
161 | />
162 |
163 |
164 |
165 | {t('Bucket')}
166 |
167 | setSelectedBucket(e.value)}
170 | options={bucketsData}
171 | optionLabel="label"
172 | placeholder={t('Bucket')}
173 | className="w-full md:w-auto"
174 | />
175 |
176 |
177 |
178 |
179 | {t('Amount')}
180 |
181 | {
185 | setFormData((prevData) => ({
186 | ...prevData,
187 | amount: e.value,
188 | }));
189 | }}
190 | mode="currency"
191 | currency="EUR"
192 | locale="fr-FR"
193 | />
194 |
195 |
200 |
201 |
202 |
203 | >
204 | );
205 | };
206 |
207 | export default Expense;
208 |
--------------------------------------------------------------------------------
/client/src/finances/Listing.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import Buckets from "./buckets/Buckets";
4 | import Vaults from "./vaults/Vaults";
5 | import { Card } from "primereact/card";
6 | import ResumeAccounts from "./components/ResumeAccounts";
7 | import { Divider } from "primereact/divider";
8 |
9 | import { IconChevronRight, IconChevronLeft } from "@tabler/icons-react";
10 |
11 | import { useTranslation } from 'react-i18next';
12 |
13 | const Listing = (props) => {
14 | const {t, i18n} = useTranslation()
15 | const [monthSelected, setMonthSelected] = useState(new Date().getMonth() + 1);
16 | const [yearSelected, setYearSelected] = useState(new Date().getFullYear());
17 | const [beforeMonth, setBeforeMonth] = useState([]);
18 | const [afterMonth, setAfterMonth] = useState([]);
19 |
20 | const listMonth = () => {
21 | const arrayMonth = [];
22 | for (let i = -6; i <= 6; i++) {
23 | const date = new Date(yearSelected, monthSelected + i, 0);
24 | arrayMonth.push({
25 | month: date.toLocaleString("default", { month: "long" }),
26 | year: date.getFullYear(),
27 | monthNumbered: date.getMonth() + 1,
28 | });
29 | }
30 | setBeforeMonth(
31 | arrayMonth.filter((item, index) => {
32 | return index >= 0 && index <= 5;
33 | })
34 | );
35 | setAfterMonth(
36 | arrayMonth.filter((item, index) => {
37 | return index >= 7 && index <= 13;
38 | })
39 | );
40 | };
41 |
42 | const urlAPIBuckets = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/buckets/${yearSelected}/${monthSelected}`;
43 | const urlAPIVault = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/vaults/${yearSelected}/${monthSelected}`;
44 |
45 | const [monthName, setMonthName] = useState();
46 |
47 | const [buckets, setBuckets] = useState([]);
48 | const [bucketsTotalAssigned, setBucketsTotalAssigned] = useState();
49 | const [vaultsTotalAssigned, setVaultsTotalAssigned] = useState();
50 | const [vaults, setVaults] = useState([]);
51 |
52 | const fetchBuckets = async () => {
53 | try {
54 | const fetchGet = await fetch(urlAPIBuckets),
55 | fetchResponse = await fetchGet.json();
56 | return fetchResponse.data;
57 | } catch (error) {
58 | console.error("An error occurred while executing fetchData() : ", error);
59 | }
60 | };
61 | const fetchVaults = async () => {
62 | try {
63 | const fetchGet = await fetch(urlAPIVault),
64 | fetchResponse = await fetchGet.json();
65 | return fetchResponse.data;
66 | } catch (error) {
67 | console.error("An error occurred while executing fetchData() : ", error);
68 | }
69 | };
70 |
71 | useEffect(() => {
72 | if (monthSelected === 13) {
73 | setMonthSelected(1);
74 | setYearSelected(yearSelected + 1);
75 | } else if (monthSelected === 0) {
76 | setMonthSelected(12);
77 | setYearSelected(yearSelected - 1);
78 | } else if (monthSelected > 0 && monthSelected < 13) {
79 | fetchBuckets().then((data) => {
80 | setBuckets(data);
81 | const calculate = data.reduce(
82 | (total, objet) => total + objet.monthly.assigned,
83 | 0
84 | );
85 | setBucketsTotalAssigned(calculate);
86 | });
87 | fetchVaults().then((data) => {
88 | setVaults(data);
89 | const calculate = data.reduce(
90 | (total, objet) => total + objet.monthly.assigned,
91 | 0
92 | );
93 | setVaultsTotalAssigned(calculate);
94 | });
95 | listMonth();
96 | const monthNamed = new Date(yearSelected, monthSelected, 0);
97 | setMonthName(monthNamed.toLocaleString("default", { month: "long" }));
98 | }
99 | }, [monthSelected]);
100 |
101 | return (
102 | <>
103 | {/* TODO Revoir le responsive design sur toutes les cards (bug mobile) */}
104 |
105 |
106 | {beforeMonth.map((item, index) => (
107 |
108 | {
111 | setMonthSelected(item.monthNumbered);
112 | setYearSelected(item.year);
113 | }}
114 | >
115 | {item.month} {item.year}
116 |
117 |
118 | ))}
119 |
120 | setMonthSelected((prevMonth) => prevMonth - 1)}
122 | size={28}
123 | className="pt-2 cursor-pointer hover:text-900 text-600"
124 | stroke={2}
125 | />
126 |
127 |
128 | {monthName} {yearSelected}
129 |
130 |
131 | setMonthSelected((prevMonth) => prevMonth + 1)}
133 | size={28}
134 | className="pt-2 cursor-pointer hover:text-900 text-600"
135 | stroke={2}
136 | />
137 |
138 | {afterMonth.map((item, index) => (
139 |
140 | {
143 | setMonthSelected(item.monthNumbered);
144 | setYearSelected(item.year);
145 | }}
146 | >
147 | {item.month} {item.year}
148 |
149 |
150 | ))}
151 |
152 |
153 |
154 |
155 |
156 |
{" "}
157 |
158 |
159 | {vaultsTotalAssigned + bucketsTotalAssigned} € {t('assigned this month')}
160 |
161 |
162 |
163 |
164 |
165 |
170 |
171 |
176 |
177 |
178 |
179 |
180 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | >
192 | );
193 | };
194 |
195 | export default Listing;
196 |
--------------------------------------------------------------------------------
/client/src/finances/vaults/CreateVault.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import { Card } from "primereact/card";
5 | import { InputText } from "primereact/inputtext";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { Divider } from "primereact/divider";
9 | import { Calendar } from "primereact/calendar";
10 | import { Dropdown } from "primereact/dropdown";
11 | import { Checkbox } from "primereact/checkbox";
12 |
13 | import { useTranslation } from 'react-i18next';
14 |
15 | const createVault = () => {
16 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/vaults/create`;
17 | const navigate = useNavigate();
18 |
19 | const {t, i18n} = useTranslation()
20 |
21 | const [date, setDate] = useState(new Date());
22 | const [formData, setFormData] = useState({});
23 |
24 | const [accountsData, setAccountsData] = useState({});
25 | const [selectedAccount, setSelectedAccount] = useState(null);
26 |
27 | const [dateDue, setDateDue] = useState(false);
28 | const [targeted, setTargeted] = useState(false);
29 | const [monthly, setMonthly] = useState(true);
30 |
31 | const fetchData = async () => {
32 | try {
33 | const fetchGet = await fetch(urlAPI),
34 | fetchResponse = await fetchGet.json();
35 | return fetchResponse;
36 | } catch (error) {
37 | console.error("An error occurred while executing fetchData() : ", error);
38 | }
39 | };
40 |
41 | const addBalanceToAccount = (data) => {
42 | const transformedData = data.dataAccounts.map((item) => ({
43 | _id: item._id,
44 | type: "ACCOUNTS",
45 | label: `${item.label} | ${item.balance} €`,
46 | balance: item.balance,
47 | }));
48 | setAccountsData(transformedData);
49 | };
50 |
51 | const handleChange = (e) => {
52 | const { id, value } = e.target;
53 | setFormData((prevData) => ({
54 | ...prevData,
55 | [id]: value,
56 | }));
57 | };
58 |
59 | const postForm = async (e) => {
60 | e.preventDefault();
61 | try {
62 | navigate("/");
63 | await fetch(urlAPI, {
64 | method: "POST",
65 | headers: {
66 | "Content-Type": "application/json",
67 | },
68 | body: JSON.stringify({
69 | label: formData.label,
70 | balance: formData.balance,
71 | dateDue: date,
72 | target: formData.target,
73 | monthlyType: monthly,
74 | accountLinked: selectedAccount._id,
75 | }),
76 | });
77 | } catch (err) {
78 | console.error("An error occurred : ", err);
79 | }
80 | };
81 |
82 | useEffect(() => {
83 | fetchData().then((data) => {
84 | addBalanceToAccount(data);
85 | });
86 | }, []);
87 | return (
88 | <>
89 |
90 |
95 |
96 |
97 | {t('Options')}
98 |
99 |
100 |
101 |
102 |
103 | {
106 | setMonthly(e.checked);
107 | setDateDue(!e.checked);
108 | }}
109 | checked={monthly}
110 | tooltip={t('Monthly tooltip')}
111 | >
112 |
113 | {t('Monthly')}
114 |
115 |
116 |
117 | {t('OR')}
118 |
119 |
120 | {
123 | setMonthly(!e.checked);
124 | setDateDue(e.checked);
125 | }}
126 | checked={dateDue}
127 | tooltip={t('DateDue tooltip')}
128 | >
129 |
130 | {t('Date due')}
131 |
132 |
133 |
134 |
135 |
136 |
137 | Form
138 |
139 |
140 |
141 |
142 |
143 |
144 | {t('Label of your vault')}
145 |
146 |
147 |
153 |
154 |
155 |
156 |
157 | {t('Source account')}
158 |
159 | setSelectedAccount(e.value)}
162 | options={accountsData}
163 | optionLabel="label"
164 | placeholder={t('Account')}
165 | className="w-full md:w-auto"
166 | />
167 |
168 |
169 |
170 |
171 | {monthly ? (
172 | {t('Target to reach every month')}
173 | ) : (
174 | {t('Target global')}
175 | )}
176 |
177 |
178 |
{
182 | setFormData((prevData) => ({
183 | ...prevData,
184 | target: e.value,
185 | }));
186 | }}
187 | mode="currency"
188 | currency="EUR"
189 | locale="fr-Fr"
190 | />
191 |
192 |
193 |
194 |
195 | {t('Date due')}
196 |
197 | setDate(e.value)}
202 | hourFormat="24"
203 | disabled={!dateDue}
204 | />
205 |
206 |
207 |
208 |
209 |
214 |
215 |
216 |
217 | >
218 | );
219 | };
220 |
221 | export default createVault;
222 |
--------------------------------------------------------------------------------
/client/src/finances/transactions/AddTransactions.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import { Dropdown } from "primereact/dropdown";
5 | import { SelectButton } from "primereact/selectbutton";
6 | import { Button } from "primereact/button";
7 | import { InputNumber } from "primereact/inputnumber";
8 | import { InputText } from "primereact/inputtext";
9 | import { Card } from "primereact/card";
10 | import { Skeleton } from "primereact/skeleton";
11 | import { Divider } from "primereact/divider";
12 | import { Calendar } from "primereact/calendar";
13 | import { Chip } from "primereact/chip";
14 | import { Message } from "primereact/message";
15 |
16 | import { IconWallet } from "@tabler/icons-react";
17 |
18 | import { useTranslation } from 'react-i18next';
19 |
20 | const Transactions = () => {
21 | const {t, i18n} = useTranslation()
22 |
23 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/transactions/add`;
24 | const navigate = useNavigate();
25 | const [formData, setFormData] = useState({});
26 |
27 | const [accountsData, setAccountsData] = useState({});
28 | const [selectedAccount, setSelectedAccount] = useState(null);
29 |
30 | const [date, setDate] = useState(new Date());
31 |
32 | const options = ["+ Income", "- Withdrawal"]; // TODO NEED TO FIX, WITH I18N !
33 | const [valueOptions, setValueOptions] = useState(options[0]);
34 |
35 | const fetchData = async () => {
36 | try {
37 | const fetchGet = await fetch(urlAPI),
38 | fetchResponse = await fetchGet.json();
39 | return fetchResponse;
40 | } catch (error) {
41 | console.error("An error occurred while executing fetchData() : ", error);
42 | }
43 | };
44 |
45 | const postData = async (e) => {
46 | e.preventDefault();
47 | navigate("/");
48 | const increment = valueOptions === "+ Income";
49 | try {
50 | await fetch(urlAPI, {
51 | method: "POST",
52 | headers: {
53 | "Content-Type": "application/json",
54 | },
55 | body: JSON.stringify({
56 | account: selectedAccount._id,
57 | label: formData.label,
58 | amount: formData.amount,
59 | date,
60 | options: increment,
61 | oldBalance: selectedAccount.balance,
62 | }),
63 | });
64 | } catch (err) {
65 | console.error("An error occurred : ", err);
66 | }
67 | };
68 |
69 | const addBalanceToAccount = (data) => {
70 | const transformedData = data.dataAccounts.map((item) => ({
71 | _id: item._id,
72 | type: "ACCOUNTS",
73 | labelBalance: `${item.label} | ${item.balance} €`,
74 | label: item.label,
75 | balance: item.balance,
76 | }));
77 | setAccountsData(transformedData);
78 | };
79 |
80 | useEffect(() => {
81 | fetchData().then((data) => {
82 | addBalanceToAccount(data);
83 | });
84 | }, []);
85 |
86 | return (
87 | <>
88 |
89 | {/* TODO Revoir le responsive design sur toutes les cards (bug mobile) */}
90 |
95 |
96 |
97 | {t('Resume')}
98 |
99 |
100 |
101 |
102 | {selectedAccount && formData.amount ? (
103 |
107 |
108 |
109 |
110 |
111 | {selectedAccount.label} : {t('the old balance of')}
112 | {selectedAccount.balance} € {t('switch to')}
113 | {valueOptions === "+ Income"
114 | ? selectedAccount.balance + formData.amount
115 | : selectedAccount.balance - formData.amount}
116 | €
117 |
118 | >
119 | }
120 | />
121 | ) : (
122 |
123 | )}
124 |
125 |
126 |
127 |
128 | {t('Form')}
129 |
130 |
131 |
132 |
133 |
134 |
135 | {t('Date')}
136 |
137 | setDate(e.value)}
142 | showTime
143 | hourFormat="24"
144 | />
145 |
146 |
147 |
148 |
149 | {t('Label (only to recognize)')}
150 |
151 | {
156 | setFormData((prevData) => ({
157 | ...prevData,
158 | label: e.target.value,
159 | }));
160 | }}
161 | />
162 |
163 |
164 |
165 |
166 | {t('Account')}
167 |
168 | setSelectedAccount(e.value)}
172 | options={accountsData}
173 | optionLabel="labelBalance"
174 | placeholder={t('Account')}
175 | />
176 |
177 |
178 |
179 |
180 | {t('Amount')}
181 |
182 | {
186 | setFormData((prevData) => ({
187 | ...prevData,
188 | amount: e.value,
189 | }));
190 | }}
191 | mode="currency"
192 | currency="EUR"
193 | locale="fr-Fr"
194 | />
195 |
196 |
197 |
198 | setValueOptions(e.value)}
201 | options={options}
202 | />
203 |
204 |
205 |
210 |
211 | {valueOptions === "- Withdrawal" ? (
212 |
217 | ) : null}
218 |
219 |
220 | >
221 | );
222 | };
223 |
224 | export default Transactions;
225 |
--------------------------------------------------------------------------------
/client/src/finances/vaults/EditVault.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useNavigate, useParams } from "react-router-dom";
3 |
4 | import { Dropdown } from "primereact/dropdown";
5 | import { Button } from "primereact/button";
6 | import { InputNumber } from "primereact/inputnumber";
7 | import { InputText } from "primereact/inputtext";
8 | import { Card } from "primereact/card";
9 | import { Divider } from "primereact/divider";
10 | import { Calendar } from "primereact/calendar";
11 | import { Checkbox } from "primereact/checkbox";
12 |
13 | import { useTranslation } from 'react-i18next';
14 |
15 | const editVault = () => {
16 | let { id } = useParams();
17 |
18 | const urlAPI = `${import.meta.env.VITE_BACKEND_ADRESS}:${import.meta.env.VITE_BACKEND_PORT}/edit/vault/${id}`;
19 | const navigate = useNavigate();
20 |
21 | const {t, i18n} = useTranslation()
22 |
23 | const [formData, setFormData] = useState({});
24 | const [selectedAccount, setSelectedAccount] = useState();
25 |
26 | const [selectedVault, setSelectedVault] = useState({});
27 | const [accountLinked, setAccountLinked] = useState(null);
28 | const [listAccounts, setListAccounts] = useState({});
29 |
30 | const [dateDue, setDateDue] = useState();
31 | const [targeted, setTargeted] = useState();
32 | const [monthly, setMonthly] = useState();
33 | const [date, setDate] = useState(new Date());
34 |
35 | const fetchData = async () => {
36 | try {
37 | const fetchGet = await fetch(urlAPI),
38 | fetchResponse = await fetchGet.json();
39 | return fetchResponse;
40 | } catch (error) {
41 | console.error("An error occurred while executing fetchData() : ", error);
42 | }
43 | };
44 |
45 | const handleChange = (e) => {
46 | const { id, value } = e.target;
47 | setFormData((prevData) => ({
48 | ...prevData,
49 | [id]: value,
50 | }));
51 | };
52 |
53 | const postData = async (e) => {
54 | e.preventDefault();
55 |
56 | try {
57 | console.log(formData);
58 | await fetch(urlAPI, {
59 | method: "POST",
60 | headers: {
61 | "Content-Type": "application/json",
62 | },
63 | body: JSON.stringify({
64 | data: {
65 | vault: selectedVault._id,
66 | accountLinked: accountLinked._id,
67 | label: selectedVault.label,
68 | target: selectedVault.target,
69 | monthly,
70 | date,
71 | },
72 | }),
73 | });
74 | } catch (err) {
75 | console.error("An error occurred : ", err);
76 | }
77 | };
78 |
79 | const addBalanceToAccount = (data) => {
80 | data = { ...data, label: `${data.label} | ${data.balance} €` };
81 | setAccountLinked(data);
82 | };
83 |
84 | const addBalanceToListAccounts = (data) => {
85 | const transformedData = data.map((item) => ({
86 | _id: item._id,
87 | type: "ACCOUNTS",
88 | label: `${item.label} | ${item.balance} €`,
89 | balance: item.balance,
90 | }));
91 | setListAccounts(transformedData);
92 | };
93 |
94 | useEffect(() => {
95 | fetchData().then((data) => {
96 | setMonthly(data.dataVaults.monthlyType);
97 | setDateDue(!data.dataVaults.monthlyType);
98 | setSelectedVault(data.dataVaults);
99 | addBalanceToAccount(data.dataAccounts);
100 | addBalanceToListAccounts(data.listAccounts);
101 | const fetchedDate = new Date(data.dataVaults.dateDue);
102 | setDate(fetchedDate);
103 | });
104 | }, []);
105 |
106 | return (
107 | <>
108 |
109 |
114 |
115 |
116 | {t('Options')}
117 |
118 |
119 |
120 |
121 |
122 | {
125 | setMonthly(e.checked);
126 | setDateDue(!e.checked);
127 | }}
128 | checked={monthly}
129 | tooltip={t('Save an amount every month')}
130 | >
131 |
132 | {t('Monthly')}
133 |
134 |
135 |
136 | {t('OR')}
137 |
138 |
139 | {
142 | setMonthly(!e.checked);
143 | setDateDue(e.checked);
144 | }}
145 | checked={dateDue}
146 | tooltip={t('Set an date due for your goal !')}
147 | >
148 |
149 | {t('Date due')}
150 |
151 |
152 |
153 |
154 |
155 |
156 | {t('Form')}
157 |
158 |
159 |
160 |
161 |
162 |
163 | {t('Label of your vault')}
164 |
165 |
166 | {
172 | setSelectedVault((prevData) => ({
173 | ...prevData,
174 | label: e.target.value,
175 | }));
176 | }}
177 | />
178 |
179 |
180 |
181 |
182 | {t('New source account')}
183 |
184 | setAccountLinked(e.value)}
187 | options={listAccounts}
188 | optionLabel="label"
189 | placeholder={accountLinked && accountLinked.label}
190 | className="w-full md:w-auto"
191 | />
192 |
193 |
194 |
195 |
196 | {monthly ? (
197 | {t('Target to reach every month')}
198 | ) : (
199 | {t('Target global')}
200 | )}
201 |
202 |
203 |
{
208 | setSelectedVault((prevData) => ({
209 | ...prevData,
210 | target: e.value,
211 | }));
212 | }}
213 | mode="currency"
214 | currency="EUR"
215 | locale="fr-Fr"
216 | />
217 |
218 |
219 |
220 |
221 | {t('Date due')}
222 |
223 | setDate(e.value)}
228 | showTime
229 | hourFormat="24"
230 | disabled={!dateDue}
231 | />
232 |
233 |
234 |
235 |
236 |
241 |
242 |
243 |
244 | >
245 | );
246 | };
247 |
248 | export default editVault;
249 |
--------------------------------------------------------------------------------
/client/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
20 |
21 |
22 |
23 |
29 |
34 |
39 |
48 |
53 |
57 |
65 |
75 |
76 |
77 |
78 |
80 |
82 |
84 |
86 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
103 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------