├── scriptPython
├── .gitignore
├── .DS_Store
├── README.md
└── main.py
├── .DS_Store
├── myWallet
├── assets
│ ├── images
│ │ ├── icon.png
│ │ ├── arrowUp.png
│ │ ├── favicon.png
│ │ ├── netflix.png
│ │ ├── arrowDown.png
│ │ ├── react-logo.png
│ │ ├── adaptive-icon.png
│ │ ├── react-logo@2x.png
│ │ ├── react-logo@3x.png
│ │ ├── splash-icon.png
│ │ ├── homeLogo-selected.png
│ │ ├── partial-react-logo.png
│ │ ├── homeLogo-unselected.png
│ │ ├── calendrierLogo-selected.png
│ │ ├── reglagesLogo-selected.png
│ │ ├── reglagesLogo-unselected.png
│ │ └── calendrierLogo-unselected.png
│ └── fonts
│ │ ├── HelveticaNeueBold.otf
│ │ ├── HelveticaNeueRoman.otf
│ │ └── SpaceMono-Regular.ttf
├── app
│ ├── +not-found.tsx
│ ├── (tabs)
│ │ ├── _layout.tsx
│ │ ├── previewScreen.tsx
│ │ ├── index.tsx
│ │ └── reglagesScreen.tsx
│ └── _layout.tsx
├── constants
│ ├── categories.js
│ ├── Couleurs.ts
│ ├── CONST_TEMPOREL.js
│ └── configFirebase.js
├── tsconfig.json
├── app.json
├── components
│ ├── reglagesComponents
│ │ └── label.jsx
│ ├── previewComponents
│ │ ├── abonnement.jsx
│ │ ├── abonnements.jsx
│ │ └── dashboardCalendar.jsx
│ ├── customTabButton.jsx
│ ├── modals
│ │ ├── modalAjoutLabel.jsx
│ │ ├── modalSuppressionLabel.jsx
│ │ ├── modalDeleteAbonnement.jsx
│ │ ├── modalSortieArgent.jsx
│ │ ├── modalNouvelAbonnement.jsx
│ │ └── modalEntreeArgent.jsx
│ └── indexComponents
│ │ ├── transaction.jsx
│ │ ├── dashboardChiffres.jsx
│ │ └── transactions.jsx
├── .gitignore
├── package.json
├── scripts
│ ├── reset-project.js
│ └── budgetContext.js
└── README.md
└── README.md
/scriptPython/.gitignore:
--------------------------------------------------------------------------------
1 | serviceAccountKey.json
2 | configFirebase.py
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/.DS_Store
--------------------------------------------------------------------------------
/scriptPython/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/scriptPython/.DS_Store
--------------------------------------------------------------------------------
/myWallet/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/icon.png
--------------------------------------------------------------------------------
/myWallet/assets/images/arrowUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/arrowUp.png
--------------------------------------------------------------------------------
/myWallet/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/favicon.png
--------------------------------------------------------------------------------
/myWallet/assets/images/netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/netflix.png
--------------------------------------------------------------------------------
/myWallet/assets/images/arrowDown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/arrowDown.png
--------------------------------------------------------------------------------
/myWallet/assets/images/react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/react-logo.png
--------------------------------------------------------------------------------
/myWallet/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/myWallet/assets/images/react-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/react-logo@2x.png
--------------------------------------------------------------------------------
/myWallet/assets/images/react-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/react-logo@3x.png
--------------------------------------------------------------------------------
/myWallet/assets/images/splash-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/splash-icon.png
--------------------------------------------------------------------------------
/myWallet/assets/fonts/HelveticaNeueBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/fonts/HelveticaNeueBold.otf
--------------------------------------------------------------------------------
/myWallet/assets/fonts/HelveticaNeueRoman.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/fonts/HelveticaNeueRoman.otf
--------------------------------------------------------------------------------
/myWallet/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/myWallet/assets/images/homeLogo-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/homeLogo-selected.png
--------------------------------------------------------------------------------
/myWallet/assets/images/partial-react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/partial-react-logo.png
--------------------------------------------------------------------------------
/myWallet/assets/images/homeLogo-unselected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/homeLogo-unselected.png
--------------------------------------------------------------------------------
/myWallet/assets/images/calendrierLogo-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/calendrierLogo-selected.png
--------------------------------------------------------------------------------
/myWallet/assets/images/reglagesLogo-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/reglagesLogo-selected.png
--------------------------------------------------------------------------------
/myWallet/assets/images/reglagesLogo-unselected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/reglagesLogo-unselected.png
--------------------------------------------------------------------------------
/myWallet/assets/images/calendrierLogo-unselected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/solene-drnx/myWallet-Public/HEAD/myWallet/assets/images/calendrierLogo-unselected.png
--------------------------------------------------------------------------------
/myWallet/app/+not-found.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from 'react-native';
2 |
3 | export default function NotFoundScreen() {
4 | return (
5 |
6 | Page non trouvée
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/myWallet/constants/categories.js:
--------------------------------------------------------------------------------
1 | // Catégories temporaire pour faire mes tests
2 | const categoriesType = [
3 | { label: "🥪", value: "snack" },
4 | { label: "👩💻", value: "travail" },
5 | { label: "🛋️", value: "maison" },
6 | { label: "🛒", value: "course" }
7 | ];
8 |
9 | export {categoriesType};
--------------------------------------------------------------------------------
/myWallet/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "@/*": [
7 | "./*"
8 | ]
9 | }
10 | },
11 | "include": [
12 | "**/*.ts",
13 | "**/*.tsx",
14 | ".expo/types/**/*.ts",
15 | "expo-env.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/myWallet/constants/Couleurs.ts:
--------------------------------------------------------------------------------
1 | // Les petites couleurs de mon app
2 | export const couleurs = {
3 | darkGreen: "#002A00",
4 | lightGreen: "#71D561",
5 | white: "#FFFFFF",
6 | grey: "#F1F1F3",
7 | black: "#1A1A1A",
8 | lightRed: '#ED7437',
9 | darkRed: '#7E2C03'
10 | } as const;
11 |
12 | export type Couleurs = typeof couleurs;
13 |
--------------------------------------------------------------------------------
/myWallet/app/(tabs)/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Tabs } from 'expo-router';
2 | import React from 'react';
3 | import { StyleSheet, View } from 'react-native';
4 | import { CustomTabButton } from '@/components/customTabButton';
5 |
6 | export default function TabLayout() {
7 | return (
8 | // Gestion du menu en bas
9 |
10 | }
15 | />
16 |
17 | );
18 | }
19 |
20 | const styles = StyleSheet.create({
21 | containerGeneral: {
22 | flex: 1,
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/myWallet/constants/CONST_TEMPOREL.js:
--------------------------------------------------------------------------------
1 | // Dictionnaires avec les mois et les jours
2 | const MOIS_DICTIONNAIRE = [
3 | { label: "janvier", value: "0" },
4 | { label: "fevrier", value: "1" },
5 | { label: "mars", value: "2" },
6 | { label: "avril", value: "3" },
7 | { label: "mai", value: "4" },
8 | { label: "juin", value: "5" },
9 | { label: "juillet", value: "6" },
10 | { label: "aout", value: "7" },
11 | { label: "septembre", value: "8" },
12 | { label: "octobre", value: "9" },
13 | { label: "novembre", value: "10" },
14 | { label: "decembre", value: "11" },
15 | ];
16 |
17 | const JOURS_DICTIONNAIRE = [
18 | { label: "lundi", value: "0" },
19 | { label: "mardi", value: "1" },
20 | { label: "mercredi", value: "2" },
21 | { label: "jeudi", value: "3" },
22 | { label: "vendredi", value: "4" },
23 | { label: "samedi", value: "5" },
24 | { label: "dimanche", value: "6" }
25 | ]
26 |
27 | export {MOIS_DICTIONNAIRE, JOURS_DICTIONNAIRE};
--------------------------------------------------------------------------------
/myWallet/constants/configFirebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | import { getDatabase } from "firebase/database";
4 | import { getAnalytics } from "firebase/analytics";
5 | import { getStorage } from "firebase/storage";
6 | // TODO: Add SDKs for Firebase products that you want to use
7 | // https://firebase.google.com/docs/web/setup#available-libraries
8 |
9 | // Your web app's Firebase configuration
10 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional
11 | const firebaseConfig = {
12 | apiKey: "",
13 | authDomain: "",
14 | databaseURL:"",
15 | projectId:"",
16 | storageBucket: "",
17 | messagingSenderId: "",
18 | appId: "",
19 | measurementId: ""
20 | };
21 |
22 | // Initialize Firebase
23 | const app = initializeApp(firebaseConfig);
24 | //const analytics = getAnalytics(app);
25 | const database = getDatabase(app);
26 | const storage = getStorage(app);
27 |
28 | export {app, database, storage}
--------------------------------------------------------------------------------
/myWallet/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "myWallet",
4 | "slug": "myWallet",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "myapp",
9 | "userInterfaceStyle": "automatic",
10 | "newArchEnabled": true,
11 | "ios": {
12 | "supportsTablet": true
13 | },
14 | "android": {
15 | "adaptiveIcon": {
16 | "foregroundImage": "./assets/images/adaptive-icon.png",
17 | "backgroundColor": "#002A00"
18 | }
19 | },
20 | "web": {
21 | "bundler": "metro",
22 | "output": "static",
23 | "favicon": "./assets/images/favicon.png"
24 | },
25 | "plugins": [
26 | "expo-router",
27 | [
28 | "expo-splash-screen",
29 | {
30 | "image": "./assets/images/splash-icon.png",
31 | "imageWidth": 200,
32 | "resizeMode": "contain",
33 | "backgroundColor": "#002A00"
34 | }
35 | ]
36 | ],
37 | "experiments": {
38 | "typedRoutes": true
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/myWallet/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { useFonts } from "expo-font";
2 | import { Stack } from "expo-router";
3 | import * as SplashScreen from "expo-splash-screen";
4 | import { useEffect } from "react";
5 | import { BudgetProvider } from "@/scripts/budgetContext";
6 | import "react-native-reanimated";
7 |
8 | // Prevent the splash screen from auto-hiding before asset loading is complete.
9 | SplashScreen.preventAutoHideAsync();
10 |
11 | export default function RootLayout() {
12 | const [loaded] = useFonts({
13 | SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
14 | HelveticaBold: require("../assets/fonts/HelveticaNeueBold.otf"),
15 | HelveticaRegular: require("../assets/fonts/HelveticaNeueRoman.otf"),
16 | });
17 |
18 | useEffect(() => {
19 | if (loaded) {
20 | SplashScreen.hideAsync();
21 | }
22 | }, [loaded]);
23 |
24 | if (!loaded) {
25 | return null;
26 | }
27 |
28 | return (
29 | // Enveloppez l'ensemble de l'application avec BudgetProvider
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/myWallet/components/reglagesComponents/label.jsx:
--------------------------------------------------------------------------------
1 | import { Text, StyleSheet, View } from 'react-native';
2 | import { couleurs } from "@/constants/Couleurs";
3 |
4 | export function Label({smiley, label}) {
5 |
6 | return (
7 |
8 |
9 | {smiley}
10 |
11 | {label}
12 |
13 | )
14 | }
15 |
16 | const styles = StyleSheet.create({
17 | containerLabel: {
18 | display: "flex",
19 | flexDirection: "row",
20 | alignItems: "center",
21 | justifyContent: "flex-start",
22 | marginBottom: 5,
23 | },
24 | containerCercleGris: {
25 | width: 50,
26 | height: 50,
27 | backgroundColor: couleurs.grey,
28 | borderRadius: 100,
29 | display: "flex",
30 | justifyContent: "center",
31 | alignItems: "center"
32 | },
33 | smileyText: {
34 | fontSize: 25
35 | },
36 | textLabel: {
37 | fontFamily: "HelveticaBold",
38 | fontSize: 18,
39 | marginLeft: 15
40 | }
41 | });
--------------------------------------------------------------------------------
/myWallet/.gitignore:
--------------------------------------------------------------------------------
1 | expo-env.d.ts
2 | # @end expo-cli
3 |
4 | # Node.js dependencies
5 | node_modules/
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Expo and React Native generated files
11 | .expo/
12 | .expo-shared/
13 | dist/
14 | web-build/
15 | ios/
16 | android/
17 |
18 | # Logs and temporary files
19 | *.log
20 | *.tlog
21 | *.tmp
22 | *.temp
23 | *.cache
24 | *.swp
25 | *.swo
26 | *.log.*
27 |
28 | # IDE and Editor files
29 | .vscode/
30 | .idea/
31 | *.sublime-workspace
32 | *.sublime-project
33 | .DS_Store
34 | Thumbs.db
35 |
36 | # Build artifacts
37 | *.apk
38 | *.aab
39 | *.app
40 | *.ipa
41 | *.xcworkspace
42 | Pods/
43 | *.xcuserdatad/
44 | *.xcuserdata/
45 | *.lock
46 | *.orig
47 |
48 | # Miscellaneous
49 | # Ignore environment files (you can choose to version `.env.example`)
50 | .env
51 | .env.local
52 | .env.production
53 | .env.development
54 | .env.test
55 |
56 | # Ignore debugging and dependency lock files
57 | package-lock.json
58 | yarn.lock
59 |
60 | # Metro bundler cache and haste map
61 | metro-cache/
62 | haste-map-react-native-packager-*
63 |
64 | # React Native-specific files
65 | *.keystore
66 | *.jks
67 | *.map
68 | __tests__/output/
69 |
70 | # Expo
71 | *.expo/*
72 | *.expo-shared/*
73 | expo-settings.json
74 |
75 | # Firebase
76 | constants/serviceAccountKey.json
77 | scripts/serviceAccountKey.json
78 | scripts/configFirebase.py
79 | scripts/__pycache__/*
80 |
--------------------------------------------------------------------------------
/myWallet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mywallet",
3 | "main": "expo-router/entry",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "start": "expo start",
7 | "reset-project": "node ./scripts/reset-project.js",
8 | "android": "expo start --android",
9 | "ios": "expo start --ios",
10 | "web": "expo start --web",
11 | "test": "jest --watchAll",
12 | "lint": "expo lint"
13 | },
14 | "jest": {
15 | "preset": "jest-expo"
16 | },
17 | "dependencies": {
18 | "@expo/vector-icons": "^14.0.2",
19 | "@react-native-async-storage/async-storage": "^1.24.0",
20 | "@react-native-community/datetimepicker": "^8.2.0",
21 | "@react-native-firebase/app": "^21.6.2",
22 | "@react-native-firebase/database": "^21.6.2",
23 | "@react-navigation/bottom-tabs": "^7.2.0",
24 | "@react-navigation/native": "^7.0.14",
25 | "expo": "~52.0.25",
26 | "expo-blur": "~14.0.2",
27 | "expo-constants": "~17.0.4",
28 | "expo-file-system": "~18.0.7",
29 | "expo-font": "~13.0.3",
30 | "expo-haptics": "~14.0.1",
31 | "expo-image-picker": "^16.0.4",
32 | "expo-linking": "~7.0.4",
33 | "expo-router": "~4.0.16",
34 | "expo-splash-screen": "~0.29.20",
35 | "expo-status-bar": "~2.0.1",
36 | "expo-symbols": "~0.2.1",
37 | "expo-system-ui": "~4.0.7",
38 | "expo-web-browser": "~14.0.2",
39 | "install": "^0.13.0",
40 | "npx": "^10.2.2",
41 | "react": "18.3.1",
42 | "react-dom": "18.3.1",
43 | "react-native": "0.76.6",
44 | "react-native-element-dropdown": "^2.12.4",
45 | "react-native-gesture-handler": "~2.20.2",
46 | "react-native-reanimated": "~3.16.1",
47 | "react-native-safe-area-context": "4.12.0",
48 | "react-native-screens": "~4.4.0",
49 | "react-native-web": "~0.19.13",
50 | "react-native-webview": "13.12.5",
51 | "uuid": "^11.0.5"
52 | },
53 | "devDependencies": {
54 | "@babel/core": "^7.25.2",
55 | "@types/jest": "^29.5.12",
56 | "@types/react": "~18.3.12",
57 | "@types/react-native": "^0.72.8",
58 | "@types/react-test-renderer": "^18.3.0",
59 | "jest": "^29.2.1",
60 | "jest-expo": "~52.0.3",
61 | "react-test-renderer": "18.3.1",
62 | "typescript": "^5.3.3"
63 | },
64 | "private": true
65 | }
66 |
--------------------------------------------------------------------------------
/myWallet/app/(tabs)/previewScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Text, StyleSheet } from 'react-native';
2 | import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
3 | import { couleurs } from "@/constants/Couleurs";
4 | import {DashboardCalendar} from "@/components/previewComponents/dashboardCalendar";
5 | import {Abonnements} from "@/components/previewComponents/abonnements";
6 | import { useState } from 'react';
7 | import { ModalNouvelAbonnement } from "@/components/modals/modalNouvelAbonnement";
8 | import { ModalDeleteAbonnement } from "@/components/modals/modalDeleteAbonnement";
9 |
10 | // ÉCRAN ABONNEMENTS
11 | export default function PreviewScreen() {
12 | const [visbiliteModalNouvelAbonnement, setVisibiliteModalNouvelAbonnement] = useState(false);
13 | const [visibiliteModalDeleteAbonnement, setVisibiliteModalDeleteAbonnement] = useState(false);
14 | return (
15 |
16 |
17 | {/*Formulaire pour ajouter un abonnement*/}
18 |
22 | {/*Formulaire supprimer un abonnement*/}
23 |
27 | {/*Zone en haut de la page avec le calendrier de preview des abonnements*/}
28 |
34 | {/*Liste des abonnements sur le mois*/}
35 |
39 |
40 |
41 | )
42 | };
43 |
44 | const styles = StyleSheet.create({
45 | containerGeneral: {
46 | backgroundColor: couleurs.grey,
47 | flex: 1,
48 | }
49 | });
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # README - myWallet Project
2 |
3 | ## 😵 Description
4 | Ce repo GitHub contient deux composants principaux :
5 | 1. **MyWallet App** : Une application React Native pour la gestion des finances personnelles.
6 | 2. **Python Script** : Un script Python qui extrait les données Firebase et les exporte dans un fichier Excel pour une analyse plus poussée.
7 |
8 | ---
9 |
10 | ## 👾 Structure du Dépôt
11 |
12 | ```plaintext
13 | mywallet/
14 | ├── .gitignore
15 | ├── (Fichiers de l'application React Native)
16 | scriptPython/
17 | ├── .gitignore
18 | ├── export_transactions.py
19 | ├── serviceAccountKey.json (non inclus dans le repo)
20 | ```
21 |
22 | ---
23 |
24 | ## 💶 MyWallet App
25 |
26 | ### Description
27 | L'application myWallet est une solution mobile de gestion des finances personnelles, développée avec React Native. Elle permet de :
28 | - Suivre ton budgets mensuels et total.
29 | - Gérer tes transactions.
30 | - Organiser tes abonnements.
31 | - Personnaliser ton objectif financier.
32 |
33 | ### Installation
34 |
35 | #### Prérequis
36 | - Node.js et npm/yarn.
37 | - Expo CLI installé.
38 |
39 | #### Étapes
40 | 1. Naviguez dans le dossier `mywallet` :
41 | ```bash
42 | cd mywallet
43 | ```
44 | 2. Installez les dépendances :
45 | ```bash
46 | npm install
47 | ```
48 | 3. Lancez l'application :
49 | ```bash
50 | expo start
51 | ```
52 |
53 | ### Fonctionnalités
54 | - Gestion des budgets.
55 | - Suivi des transactions.
56 | - Organisation des abonnements.
57 |
58 | ---
59 |
60 | ## 🧲 Script Python
61 |
62 | ### Description
63 | Le script Python dans le dossier `scriptPython` extrait les transactions depuis Firebase et les exporte dans un fichier Excel formaté. Il génère des graphiques et des tableaux pour une analyse claire et visuelle des finances.
64 |
65 | ### Installation
66 |
67 | #### Prérequis
68 | - Python 3.x installé.
69 | - Clé de service Firebase (`serviceAccountKey.json`, non incluse dans le dépôt).
70 | - Modules Python requis :
71 | ```bash
72 | pip install firebase-admin xlsxwriter
73 | ```
74 |
75 | #### Configuration
76 | 1. Placez le fichier `serviceAccountKey.json` dans le dossier `scriptPython`.
77 | 2. Configurez l'URL Firebase dans `configFirebase.py`.
78 |
79 | #### Exécution
80 | 1. Naviguez dans le dossier `scriptPython` :
81 | ```bash
82 | cd scriptPython
83 | ```
84 | 2. Exécutez le script :
85 | ```bash
86 | python export_transactions.py
87 | ```
88 |
89 | ### Résultat
90 | Un fichier `transactions_2025.xlsx` est généré, contenant :
91 | - Une feuille par mois avec les transactions et les graphiques associés.
92 | - Une feuille "BILAN" résumant les finances annuelles.
93 |
94 | Des bisous
95 | Solène
--------------------------------------------------------------------------------
/myWallet/components/previewComponents/abonnement.jsx:
--------------------------------------------------------------------------------
1 | import {View, StyleSheet, Text, Image} from "react-native";
2 | import { couleurs } from "@/constants/Couleurs";
3 | import { MOIS_DICTIONNAIRE } from "@/constants/CONST_TEMPOREL";
4 | import {categoriesType} from "@/constants/categories";
5 |
6 | // COMPOSANT ABONNEMENT
7 | export function Abonnement({label, prix, date, image}){
8 | // Fonction qui formate la date correctement pour l'affichage (chaine de caractère)
9 | function formatDate(dateString) {
10 | const date = new Date(dateString);
11 | const moisIndex = date.getMonth();
12 | const mois = MOIS_DICTIONNAIRE.find(item => Number(item.value) === moisIndex)?.label || "Mois inconnu";
13 | const dateFormatee = date.getDate() + " " + mois + " " + date.getFullYear();
14 | return dateFormatee;
15 | }
16 |
17 | return(
18 |
19 | {/*Logo de l'abonnement */}
20 |
21 | {/*Informaiton sur l'abonnement*/}
22 |
23 |
24 | {label}
25 | {prix}€
26 |
27 | {formatDate(date)}
28 |
29 |
30 | );
31 | }
32 |
33 | const styles = StyleSheet.create({
34 | // container
35 | containerGlobal: {
36 | display: "flex",
37 | flexDirection: "row",
38 | paddingTop: 10
39 | //justifyContent: "flex-start"
40 | },
41 | imageAbonnement: {
42 | width: 50,
43 | height: 50,
44 | borderRadius: 100,
45 | display: "flex",
46 | justifyContent: "center",
47 | alignItems: "center"
48 | },
49 | containerInfosAbonnement: {
50 | display: "flex",
51 | flexDirection: "column",
52 | flex: 1,
53 | justifyContent: "center",
54 | paddingLeft: 10
55 | },
56 | containerPrixLabel: {
57 | display: "flex",
58 | flexDirection: "row",
59 | justifyContent: "space-between"
60 | },
61 | // Titres
62 | smileyText: {
63 | fontSize: 25
64 | },
65 | textLabel: {
66 | fontFamily: "HelveticaBold",
67 | fontSize: 18,
68 | color: couleurs.black
69 | },
70 | textPrix: {
71 | fontFamily: "HelveticaBold",
72 | fontSize: 18,
73 | color: couleurs.black
74 | },
75 | textDate: {
76 | fontFamily: "HelveticaRegular",
77 | fontSize: 14,
78 | color: couleurs.black
79 | }
80 | });
--------------------------------------------------------------------------------
/myWallet/scripts/reset-project.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * This script is used to reset the project to a blank state.
5 | * It moves the /app, /components, /hooks, /scripts, and /constants directories to /app-example and creates a new /app directory with an index.tsx and _layout.tsx file.
6 | * You can remove the `reset-project` script from package.json and safely delete this file after running it.
7 | */
8 |
9 | const fs = require("fs");
10 | const path = require("path");
11 |
12 | const root = process.cwd();
13 | const oldDirs = ["app", "components", "hooks", "constants", "scripts"];
14 | const newDir = "app-example";
15 | const newAppDir = "app";
16 | const newDirPath = path.join(root, newDir);
17 |
18 | const indexContent = `import { Text, View } from "react-native";
19 |
20 | export default function Index() {
21 | return (
22 |
29 | Edit app/index.tsx to edit this screen.
30 |
31 | );
32 | }
33 | `;
34 |
35 | const layoutContent = `import { Stack } from "expo-router";
36 |
37 | export default function RootLayout() {
38 | return ;
39 | }
40 | `;
41 |
42 | const moveDirectories = async () => {
43 | try {
44 | // Create the app-example directory
45 | await fs.promises.mkdir(newDirPath, { recursive: true });
46 | console.log(`📁 /${newDir} directory created.`);
47 |
48 | // Move old directories to new app-example directory
49 | for (const dir of oldDirs) {
50 | const oldDirPath = path.join(root, dir);
51 | const newDirPath = path.join(root, newDir, dir);
52 | if (fs.existsSync(oldDirPath)) {
53 | await fs.promises.rename(oldDirPath, newDirPath);
54 | console.log(`➡️ /${dir} moved to /${newDir}/${dir}.`);
55 | } else {
56 | console.log(`➡️ /${dir} does not exist, skipping.`);
57 | }
58 | }
59 |
60 | // Create new /app directory
61 | const newAppDirPath = path.join(root, newAppDir);
62 | await fs.promises.mkdir(newAppDirPath, { recursive: true });
63 | console.log("\n📁 New /app directory created.");
64 |
65 | // Create index.tsx
66 | const indexPath = path.join(newAppDirPath, "index.tsx");
67 | await fs.promises.writeFile(indexPath, indexContent);
68 | console.log("📄 app/index.tsx created.");
69 |
70 | // Create _layout.tsx
71 | const layoutPath = path.join(newAppDirPath, "_layout.tsx");
72 | await fs.promises.writeFile(layoutPath, layoutContent);
73 | console.log("📄 app/_layout.tsx created.");
74 |
75 | console.log("\n✅ Project reset complete. Next steps:");
76 | console.log(
77 | "1. Run `npx expo start` to start a development server.\n2. Edit app/index.tsx to edit the main screen.\n3. Delete the /app-example directory when you're done referencing it."
78 | );
79 | } catch (error) {
80 | console.error(`Error during script execution: ${error}`);
81 | }
82 | };
83 |
84 | moveDirectories();
85 |
--------------------------------------------------------------------------------
/myWallet/components/customTabButton.jsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, TouchableOpacity, View, Image } from "react-native";
2 | import homeLogoSelected from "@/assets/images/homeLogo-selected.png";
3 | import reglagesLogoSelected from "@/assets/images/reglagesLogo-selected.png";
4 | import calendrierLogoSelected from "@/assets/images/calendrierLogo-selected.png";
5 | import homeLogoUnselected from "@/assets/images/homeLogo-unselected.png";
6 | import reglagesLogoUnselected from "@/assets/images/reglagesLogo-unselected.png";
7 | import calendrierLogoUnselected from "@/assets/images/calendrierLogo-unselected.png";
8 | import { couleurs } from '@/constants/Couleurs.ts';
9 |
10 | // COMPOSANT POUR LE MENU GÉNÉRAL
11 | export function CustomTabButton({ state, descriptors, navigation }) {
12 | return (
13 |
14 | {state.routes.map((route, index) => {
15 | const { options } = descriptors[route.key];
16 | const label = options.title || route.name;
17 | const estSelectionne = state.index === index;
18 |
19 | const onPress = () => {
20 | const event = navigation.emit({
21 | type: 'tabPress',
22 | target: route.key,
23 | canPreventDefault: true,
24 | });
25 |
26 | if (!estSelectionne && !event.defaultPrevented) {
27 | navigation.navigate(route.name);
28 | }
29 | };
30 |
31 | return (
32 |
37 | {label === "index" && (
38 |
39 | )}
40 | {label === "previewScreen" && (
41 |
42 | )}
43 | {label === "reglagesScreen" && (
44 |
45 | )}
46 |
47 | );
48 | })}
49 |
50 | );
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | tabBar: {
55 | flexDirection: 'row',
56 | width: 250,
57 | height: 80,
58 | backgroundColor: couleurs.darkGreen,
59 | justifyContent: "space-around",
60 | alignItems: "center",
61 | borderRadius: 80,
62 | marginBottom: 40,
63 | marginLeft: "auto",
64 | marginRight: "auto",
65 | // Ombre pour iOS
66 | shadowColor: couleurs.black,
67 | shadowOffset: { width: 0, height: 5 },
68 | shadowOpacity: 0.25,
69 | shadowRadius: 10,
70 | // Ombre pour Android
71 | elevation: 10,
72 | },
73 | tabButton: {
74 | height: 70,
75 | width: 70,
76 | justifyContent: 'center',
77 | alignItems: 'center',
78 | backgroundColor: couleurs.darkGreen,
79 | borderRadius: 100
80 | },
81 | selectedTab: {
82 | backgroundColor: couleurs.lightGreen,
83 | },
84 | iconButtonNav: {
85 | width: 50,
86 | height: 50,
87 | }
88 | });
89 |
--------------------------------------------------------------------------------
/myWallet/README.md:
--------------------------------------------------------------------------------
1 | # README - myWallet
2 |
3 | ## 🗒️ Description
4 | Le but de cette application est de mieux gérer son budget personnel. Y'a une partie transactions pour ajouter des entrées et sorties d'argent. Y'a aussi un écran pour visualiser ses abonnements et les prochaines transactions
5 |
6 | ---
7 |
8 | ## 🧱 Fonctionnalités Principales
9 |
10 | ### 1. **Gestion des Budgets**
11 | - Suivi du budget mensuel et total.
12 | - Définition d'un objectif financier mensuel.
13 |
14 | ### 2. **Transactions**
15 | - Consultation des transactions mensuelles.
16 | - Interface pour suivre les dépenses et les revenus.
17 | - Les composants `Transaction` et `Transactions` permettent d'afficher et de gérer les données liées aux transactions.
18 |
19 | ### 3. **Abonnements**
20 | - Ajout et suppression d'abonnements mensuels.
21 | - Aperçu des abonnements grâce à un calendrier intégré.
22 |
23 | ### 4. **Réglages**
24 | - Modification des objectifs financiers.
25 | - Gestion des étiquettes (ajout/suppression de labels).
26 |
27 | ### 5. **Dashboard**
28 | - Visualisation des chiffres clés avec `DashboardChiffres`.
29 | - Calendrier avec `DashboardCalendar` pour afficher les abonnements à venir.
30 |
31 | ### 6. **Navigation**
32 | - Menu de navigation personnalisé en bas de l'application.
33 | - Gestion des écrans à l'aide d'une stack et de tabs.
34 |
35 | ---
36 |
37 | ## 🚧 Structure du Projet
38 |
39 | ```plaintext
40 | -> app
41 | -> tabs
42 | -> _layout.tsx
43 | -> index.tsx
44 | -> previewScreen.tsx
45 | -> reglagesScreen.tsx
46 | -> _layout.tsx
47 | -> +not-found.tsx
48 | -> assets
49 | -> fonts
50 | -> images
51 | -> components
52 | -> indexComponents
53 | -> dashboardChiffres.tsx
54 | -> transaction.jsx
55 | -> transactions.jsx
56 | -> modals
57 | -> modalAjoutLabel.tsx
58 | -> modalDeleteAbonnement.tsx
59 | -> modalEntreeArgent.tsx
60 | -> modalNouvelAbonnement.tsx
61 | -> modalSortieArgent.tsx
62 | -> modalSuppressionLabel.tsx
63 | -> previewComponents
64 | -> abonnement.jsx
65 | -> abonnements.jsx
66 | -> dashboardCalendar.jsx
67 | -> reglagesComponents
68 | -> label.jsx
69 | -> customTabButton.jsx
70 | -> constants
71 | -> CONST_TEMPORELS.js
72 | -> Couleurs.ts
73 | -> scripts
74 | -> budgetContext.js
75 | ```
76 |
77 | ---
78 |
79 | ## 🌸 Installation
80 |
81 | ### Prérequis
82 | - Node.js et npm/yarn.
83 | - Expo CLI installé.
84 |
85 | ### Étapes
86 | 1. Cloner le projet :
87 | ```bash
88 | git clone https://github.com/solene-drnx/myWallet-Public.git
89 | ```
90 | 2. Naviguer dans le dossier du projet :
91 | ```bash
92 | cd mywallet
93 | ```
94 | 3. Installer les dépendances :
95 | ```bash
96 | npm install
97 | ```
98 | 4. Configuration de Firebase
99 | Placez le fichier `serviceAccountKey.json` dans le dossier `constants`.
100 | Configurez les données manquantes Firebase dans `configFirebase.js`.
101 | 5. Lancer l'application :
102 | ```bash
103 | expo start
104 | ```
105 |
106 | ---
107 |
108 | ## 🕺 Utilisation
109 |
110 | 1. Démarrer l'application sur un émulateur ou un appareil physique.
111 | 2. Naviguer entre les écrans via le menu en bas de l'application.
112 | 3. Ajouter, modifier ou supprimer des budgets et abonnements selon vos besoins.
113 | 4. Consulter les rapports pour suivre vos finances.
114 |
115 | ---
116 |
117 | ## 🥸 Technologies Utilisées
118 | - **React Native** pour le développement mobile.
119 | - **Expo** pour simplifier la gestion des dépendances et l'exécution.
120 | - **Firebase** pour la gestion des données en arrière-plan.
121 | - **Context API** pour le partage de données globales.
122 |
--------------------------------------------------------------------------------
/scriptPython/README.md:
--------------------------------------------------------------------------------
1 | # README - Script d'Exportation de Transactions Firebase vers Excel
2 |
3 | ## 👩💻 Description
4 | Ce script Python extrait les transactions financières stockées dans Firebase et les exporte dans un fichier Excel formaté. Chaque mois de l'année est représenté sur une feuille distincte avec des transactions organisées par catégorie et des graphiques illustrant les données.
5 |
6 | ---
7 |
8 | ## 📦 Fonctionnalités Principales
9 |
10 | ### 1. **Extraction de Données Firebase**
11 | - Récupère les transactions depuis Firebase en utilisant les clés des transactions.
12 | - Filtre les données par mois et année en cours.
13 |
14 | ### 2. **Organisation Mensuelle**
15 | - Classe les transactions par mois avec traduction des noms des mois en français.
16 | - Regroupe les transactions par catégorie pour un traitement simplifié.
17 |
18 | ### 3. **Génération de Feuilles Excel**
19 | - Crée une feuille Excel pour chaque mois avec :
20 | - Les transactions listées ligne par ligne.
21 | - Les totaux par catégorie.
22 | - Une section pour les totaux mensuels : sorties, entrées et balance.
23 |
24 | ### 4. **Visualisation Graphique**
25 | - Génère des graphiques en colonnes et des diagrammes circulaires pour illustrer les données financières.
26 | - Intègre un onglet "BILAN" avec un résumé des données annuelles.
27 |
28 | ---
29 |
30 | ## 🚧 Structure du Script
31 |
32 | ### **Imports Principaux**
33 | - `xlsxwriter` : Création et gestion des fichiers Excel.
34 | - `firebase_admin` : Connexion et extraction des données depuis Firebase.
35 | - `datetime` : Gestion des dates et des formats temporels.
36 | - `collections.defaultdict` : Structure de données pour organiser les transactions par mois et catégorie.
37 |
38 | ### **Étapes du Script**
39 |
40 | 1. **Initialisation Firebase**
41 | - Connexion à Firebase via le fichier de clé de service `serviceAccountKey.json`.
42 | - Accès aux données via une référence de base.
43 |
44 | 2. **Traitement des Transactions**
45 | - Récupère toutes les transactions.
46 | - Filtre par année en cours et regroupe par mois.
47 | - Gestion des erreurs de conversion et de données manquantes.
48 |
49 | 3. **Création des Fichiers Excel**
50 | - Formatage des cellules pour une meilleure lisibilité (entêtes, contenu, formats monétaires).
51 | - Ajout des transactions par mois.
52 | - Génération de tableaux pour les totaux par catégorie.
53 |
54 | 4. **Visualisation Graphique**
55 | - Diagrammes circulaires pour la répartition des catégories.
56 | - Graphiques en colonnes pour les entrées, sorties et balances annuelles.
57 |
58 | 5. **Feuille "BILAN"**
59 | - Résumé des données mensuelles : sorties, entrées et balance.
60 | - Inclusion de graphiques pour une vue globale des finances annuelles.
61 |
62 | ---
63 |
64 | ## 🙇♀️ Installation
65 |
66 | ### Prérequis
67 | - Python 3.x installé.
68 | - Clé de service Firebase : `serviceAccountKey.json`.
69 | - Module `xlsxwriter` installé :
70 | ```bash
71 | pip install xlsxwriter
72 | ```
73 | - Module Firebase Admin SDK installé :
74 | ```bash
75 | pip install firebase-admin
76 | ```
77 |
78 | ### Configuration
79 | 1. Configurez Firebase :
80 | - Créez un projet Firebase.
81 | - Téléchargez la clé de service et placez-la dans le même répertoire que le script.
82 | - Mettez à jour l'URL de la base de données dans `configFirebase.py`.
83 |
84 | 2. Exécutez le script :
85 | ```bash
86 | python main.py
87 | ```
88 |
89 | ---
90 |
91 | ## 🚀 Utilisation
92 |
93 | 1. **Exécuter le Script** :
94 | - Le script crée un fichier `transactions_2025.xlsx` dans le répertoire courant.
95 | 2. **Explorer le Fichier Excel** :
96 | - Chaque mois a sa propre feuille contenant les transactions et graphiques.
97 | - L'onglet "BILAN" résume les données annuelles avec des graphiques détaillés.
98 |
99 | ---
100 |
101 | ## 🥐 Structure des Données Firebase
102 |
103 | ### Exemple de Transaction :
104 | Transactions générées depuis l'application mywallet
105 |
106 | ```json
107 | {
108 | "label": "Achat Supermarché",
109 | "categorie": "Alimentation",
110 | "prix": "-50.75",
111 | "date": "2025-01-15T10:30:00Z",
112 | "id": "12345"
113 | }
114 | ```
115 |
116 |
--------------------------------------------------------------------------------
/myWallet/components/modals/modalAjoutLabel.jsx:
--------------------------------------------------------------------------------
1 | import { Text, StyleSheet, View, TouchableOpacity, Image, Modal, TextInput, ScrollView } from 'react-native';
2 | import { BlurView } from "expo-blur";
3 | import React from "react";
4 | import { couleurs } from "@/constants/Couleurs";
5 |
6 | // FORMULAIRE POUR AJOUTER UN LABEL
7 | export function ModalAjoutLabel({visibiliteModalAjoutLabel, setVisibiliteModalAjoutLabel, pushLabel, nomLabel, setNomLabel, icon, setIcon}) {
8 | // Fonction qui gère l'ajout de l'abel (maj firebase et maj variables locales)
9 | function handleAjoutLabel() {
10 | if (nomLabel !== "" && icon !== "") {
11 | pushLabel();
12 | setNomLabel("");
13 | setIcon("");
14 | setVisibiliteModalAjoutLabel(false);
15 | }
16 | }
17 |
18 | return (
19 | setVisibiliteModalAjoutLabel(false)}
24 | >
25 |
26 |
27 | Ajouter un label
28 |
29 | {/*Input nom du label*/}
30 |
36 | {/*Input de l'icon du label*/}
37 |
43 |
44 | {/*Bouton annuler*/}
45 | setVisibiliteModalAjoutLabel(false)}>
46 | Fermer
47 |
48 | {/*Formulaire valider*/}
49 | handleAjoutLabel()}
52 | >
53 | Valider
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | const styles = StyleSheet.create({
62 | modalOverlay: {
63 | position: "absolute",
64 | top: 0,
65 | left: 0,
66 | right: 0,
67 | bottom: 0,
68 | backgroundColor: "rgba(0, 0, 0, 0.5)",
69 | justifyContent: "center",
70 | alignItems: "center",
71 | },
72 | containerModal: {
73 | width: "80%",
74 | padding: 20,
75 | backgroundColor: couleurs.white,
76 | borderRadius: 10,
77 | alignItems: "flex-start",
78 | elevation: 5,
79 | },
80 | modalTitle: {
81 | fontSize: 28,
82 | fontFamily: "HelveticaBold",
83 | marginBottom: 20,
84 | },
85 | input: {
86 | width: "100%",
87 | borderWidth: 1,
88 | borderColor: couleurs.grey,
89 | color: couleurs.black,
90 | borderRadius: 5,
91 | padding: 10,
92 | marginBottom: 15,
93 | },
94 | button: {
95 | marginTop: 5,
96 | padding: 10,
97 | borderRadius: 5,
98 | width: "100%",
99 | alignItems: "center",
100 | },
101 | buttonText: {
102 | color: "white",
103 | fontFamily: "HelveticaBold"
104 | },
105 | containerInputs: {
106 | display: "flex",
107 | flexDirection: "row",
108 | justifyContent: "space-between",
109 | width: "100%"
110 | }
111 | });
--------------------------------------------------------------------------------
/myWallet/components/indexComponents/transaction.jsx:
--------------------------------------------------------------------------------
1 | import {View, StyleSheet, Text} from "react-native";
2 | import { couleurs } from "@/constants/Couleurs";
3 | import { MOIS_DICTIONNAIRE } from "@/constants/CONST_TEMPOREL";
4 | import { useState, useEffect } from "react";
5 | import { get, ref } from "firebase/database";
6 | import { database } from "@/constants/configFirebase";
7 |
8 | // COMPOSANT TRANSACTION
9 | export function Transaction({icon, label, prix, date}){
10 | const [categoriesType, setCategoriesType] = useState([])
11 |
12 | // Fonction qui récupères les labels de la base de données
13 | async function getLabelsFromFirebase() {
14 | try {
15 | const reference = ref(database, "categories");
16 | const snapshot = await get(reference);
17 | if (snapshot.exists()) {
18 | return Object.entries(snapshot.val()).map(([key, value]) => ({
19 | label: value.icon,
20 | value: value.nomLabel
21 | }));
22 | } else {
23 | console.log("Aucune donnée disponible pour ce chemin.");
24 | return [];
25 | }
26 | } catch (error) {
27 | console.error("Erreur lors de la récupération des données :", error);
28 | return [];
29 | }
30 | }
31 |
32 | // Garde à jour les labels
33 | useEffect(() => {
34 | let isMounted = true;
35 |
36 | async function fetchData() {
37 | const data = await getLabelsFromFirebase();
38 | if (isMounted) {
39 | setCategoriesType(data);
40 | }
41 | }
42 |
43 | fetchData();
44 |
45 | return () => {
46 | isMounted = false;
47 | };
48 | }, []);
49 |
50 | // Formate la date en string pour l'afficher correctement
51 | function formatDate(dateString) {
52 | const date = new Date(dateString);
53 | const moisIndex = date.getMonth();
54 | const mois = MOIS_DICTIONNAIRE.find(item => Number(item.value) === moisIndex)?.label || "Mois inconnu";
55 | const dateFormatee = date.getDate() + " " + mois + " " + date.getFullYear();
56 | return dateFormatee;
57 | }
58 |
59 | // Formate la categorie avec son icon
60 | function formatCategorie(categorie) {
61 | let label;
62 | label = categorie
63 | ? categoriesType.find(item => categorie === item.value)?.label || "❓"
64 | : "rien";
65 | return label;
66 | }
67 |
68 |
69 | return(
70 |
71 | {/*Container smiley (cercle plus smiley)*/}
72 |
73 | {formatCategorie(icon)}
74 |
75 | {/*Informations sur la transaction*/}
76 |
77 |
78 | {label}
79 | {prix}€
80 |
81 | {formatDate(date)}
82 |
83 |
84 | );
85 | }
86 |
87 | const styles = StyleSheet.create({
88 | // container
89 | containerGlobal: {
90 | display: "flex",
91 | flexDirection: "row",
92 | paddingTop: 10
93 | //justifyContent: "flex-start"
94 | },
95 | containerSmileyCercleBlanc: {
96 | width: 50,
97 | height: 50,
98 | backgroundColor: couleurs.white,
99 | borderRadius: 100,
100 | display: "flex",
101 | justifyContent: "center",
102 | alignItems: "center"
103 | },
104 | containerInfoTransaction: {
105 | display: "flex",
106 | flexDirection: "column",
107 | flex: 1,
108 | justifyContent: "center",
109 | paddingLeft: 10
110 | },
111 | containerPrixLabel: {
112 | display: "flex",
113 | flexDirection: "row",
114 | justifyContent: "space-between"
115 | },
116 | // Titres
117 | smileyText: {
118 | fontSize: 25
119 | },
120 | textLabel: {
121 | fontFamily: "HelveticaBold",
122 | fontSize: 18,
123 | color: couleurs.black
124 | },
125 | textPrix: {
126 | fontFamily: "HelveticaBold",
127 | fontSize: 18,
128 | color: couleurs.black
129 | },
130 | textDate: {
131 | fontFamily: "HelveticaRegular",
132 | fontSize: 14,
133 | color: couleurs.black
134 | }
135 | });
--------------------------------------------------------------------------------
/myWallet/app/(tabs)/index.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
3 | import { couleurs } from "@/constants/Couleurs";
4 | import { DashboardChiffres } from "@/components/indexComponents/dashboardChiffres";
5 | import { Transactions } from "@/components/indexComponents/transactions";
6 | import { useState, useEffect } from "react";
7 | import { ModalEntreeArgent } from "@/components/modals/modalEntreeArgent";
8 | import { ModalSortieArgent } from "@/components/modals/modalSortieArgent";
9 | import { BudgetContext } from "@/scripts/budgetContext";
10 | import React, { useContext } from "react";
11 |
12 | // ÉCRAN TRANSACTIONS
13 | export default function HomeScreen() {
14 | const [visibiliteModalEntreeArgent, setVisibiliteModalEntreeArgent] = useState(false);
15 | const [visibiliteModalSortieArgent, setVisibiliteModalSortieArgent] = useState(false);
16 |
17 | // Import du context
18 | const {
19 | budgetTotal,
20 | budgetMensuel,
21 | objectifMensuel,
22 | nomLabel,
23 | icon,
24 | labels,
25 | getBudgetTotal,
26 | getBudgetMensuel,
27 | getObjectifMensuel,
28 | getLabels,
29 | setLabels,
30 | pushBudgeTotal,
31 | pushBudgetMensuel,
32 | pushLabel,
33 | setBudgetTotal,
34 | setBudgetMensuel,
35 | setObjectifMensuel,
36 | setNomLabel,
37 | setIcon,
38 | pushObjectifMensuel,
39 | calculerBudgetMensuel,
40 | calculerBudgetTotal
41 | } = useContext(BudgetContext);
42 |
43 | // Gestion des budgets (total, mensuel et objectif) pour qu'ils soient à jour quand on charge l'écran
44 | useEffect(() => {
45 | async function fetchBudget() {
46 | const budgetRecup = await calculerBudgetTotal();
47 | const budgetMensuelRecup = await calculerBudgetMensuel();
48 | const objectifMensuelRecup = await getObjectifMensuel();
49 | setBudgetMensuel(budgetMensuelRecup);
50 | setBudgetTotal(budgetRecup);
51 | setObjectifMensuel(objectifMensuelRecup);
52 | }
53 |
54 | fetchBudget();
55 | }, []);
56 |
57 | // recupere les labels pour que ce soit bien à jour
58 | useEffect(() => {
59 | async function fetchLabels() {
60 | try {
61 | const labelsRecup = await getLabels();
62 | const labelsArray = Object.values(labelsRecup);
63 | setLabels(labelsArray);
64 | } catch (error) {
65 | console.error("Erreur lors de la récupération des labels :", error);
66 | }
67 | }
68 | fetchLabels();
69 | }, []);
70 |
71 | useEffect(() => {
72 | console.log("labels dans le rendu : ", labels);
73 | }, [labels]);
74 |
75 |
76 | return (
77 |
78 |
79 | {/*Formulaire entrée argent*/}
80 |
92 | {/*Formulaire sortie d'argent*/}
93 |
105 | {/*Écran blanc avec les chiffres et les boutons en haut*/}
106 |
112 | {/*Ensemble de transactions effectuées sur le mois*/}
113 |
117 |
118 |
119 | );
120 | }
121 | const styles = StyleSheet.create({
122 | containerGeneral: {
123 | backgroundColor: couleurs.grey,
124 | flex: 1,
125 | }
126 | });
127 |
--------------------------------------------------------------------------------
/myWallet/components/indexComponents/dashboardChiffres.jsx:
--------------------------------------------------------------------------------
1 | import { Text, View, TouchableOpacity, Image, StyleSheet } from "react-native";
2 | import { couleurs } from '@/constants/Couleurs';
3 | import arrowUp from "@/assets/images/arrowUp.png";
4 | import arrowDown from "@/assets/images/arrowDown.png";
5 |
6 | // DASHBOARD AVEC LES CHIFFRES IMPORTANT DE L'APPLICATION
7 | export function DashboardChiffres({setVisibiliteModalEntreeArgent, setVisibiliteModalSortieArgent, budgetMensuel, bugdetTotal}){
8 | // Fonction qui ajoute des espaces entre les milliers
9 | function refactoringBudget(chiffre) {
10 | let budgetRefactore;
11 | if(chiffre >= 1000){
12 | let reste = (chiffre%1000).toFixed(2);
13 | let milliers = Math.floor(chiffre/1000);
14 | budgetRefactore = milliers + " " + reste + "€";
15 | } else {
16 | chiffre = chiffre.toFixed(2);
17 | budgetRefactore = chiffre + "€";
18 | }
19 | return budgetRefactore;
20 | }
21 |
22 | // Récupère le jour et retourne le pourcentage d'avancement dans le mois
23 | function timeToPourcentage() {
24 | const date = new Date();
25 | const jour = date.getUTCDate();
26 | const mois = date.getMonth();
27 | const annee = date.getFullYear();
28 |
29 | const estBissextile = (annee % 4 === 0 && annee % 100 !== 0) || (annee % 400 === 0);
30 | const joursFevrier = estBissextile ? 29 : 28;
31 |
32 | const mois31 = [0, 2, 4, 6, 7, 9, 11]; // Mois ayant 31 jours
33 | const mois30 = [3, 5, 8, 10]; // Mois ayant 30 jours
34 |
35 | let pourcentage;
36 |
37 | if (mois31.includes(mois)) {
38 | pourcentage = ((jour / 31) * 100).toFixed(0);
39 | } else if (mois30.includes(mois)) {
40 | pourcentage = ((jour / 30) * 100).toFixed(0);
41 | } else {
42 | pourcentage = ((jour / joursFevrier) * 100).toFixed(0);
43 | }
44 |
45 | pourcentage = Number(pourcentage);
46 | if (pourcentage === 0) {
47 | pourcentage++;
48 | }
49 | pourcentage += "%";
50 |
51 | return pourcentage;
52 | }
53 |
54 |
55 | return(
56 |
57 |
58 | {/*Chiffres (budget mensuel et budget général*/}
59 |
60 | {refactoringBudget(budgetMensuel)}
61 | {refactoringBudget(bugdetTotal)}
62 |
63 | {timeToPourcentage()}
64 |
65 | {/*Boutons vers les formulaires*/}
66 |
67 | setVisibiliteModalEntreeArgent(true)}>
68 |
69 | Entrée argent
70 |
71 | setVisibiliteModalSortieArgent(true)}>
72 |
73 | Sortie argent
74 |
75 |
76 |
77 | );
78 | }
79 |
80 | const styles = StyleSheet.create({
81 | // Containers
82 | containerGeneral : {
83 | backgroundColor: couleurs.white,
84 | margin: 20,
85 | padding: 10,
86 | borderRadius: 10,
87 | // Ombre pour iOS
88 | shadowColor: couleurs.black,
89 | shadowOffset: { width: 0, height: 5 },
90 | shadowOpacity: 0.25,
91 | shadowRadius: 10,
92 | // Ombre pour Android
93 | elevation: 10,
94 | },
95 | containerChiffres : {
96 | display: "flex",
97 | flexDirection: "row",
98 | justifyContent: "space-between",
99 | alignItems: "flex-start",
100 | },
101 | bouton : {
102 | display: "flex",
103 | flexDirection: "row",
104 | alignItems: "center",
105 | borderRadius: 5,
106 | width: 140,
107 | height: 30,
108 | marginRight: 5,
109 | },
110 | containerBoutons : {
111 | display: "flex",
112 | flexDirection: "row",
113 | marginTop: 50
114 | },
115 | // textes
116 | textChiffres : {
117 | fontFamily: 'HelveticaBold',
118 | fontSize: 50,
119 | marginTop: -10,
120 | color: couleurs.black
121 | },
122 | textChiffresDessous : {
123 | color: couleurs.grey,
124 | marginTop: -15,
125 | },
126 | textPourcentage: {
127 | fontFamily: 'HelveticaRegular',
128 | fontSize: 14,
129 | },
130 | textBouton : {
131 | fontFamily: 'HelveticaRegular',
132 | fontSize: 14,
133 | marginLeft: -3,
134 | marginTop: 3
135 | },
136 | // autres
137 | image : {
138 | width: 25,
139 | height: 25
140 | }
141 | });
--------------------------------------------------------------------------------
/myWallet/components/modals/modalSuppressionLabel.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | TouchableOpacity,
4 | Modal,
5 | View,
6 | Text
7 | } from "react-native";
8 | import { couleurs } from "@/constants/Couleurs";
9 | import { BlurView } from "expo-blur";
10 | import { Dropdown } from "react-native-element-dropdown";
11 | import { useState, useEffect } from "react";
12 | import { ref, set, get } from "firebase/database";
13 | import { database } from "@/constants/configFirebase";
14 |
15 | // FORMULAIRE DE SUPPRESSION D'UN LABEL
16 | export function ModalSuppressionLabel({ visibiliteModalSuppressionLabel, setVisibiliteModalSuppressionLabel }) {
17 | const [abonnements, setAbonnements] = useState([]);
18 | const [abonnementSelectionne, setAbonnementSelectionne] = useState(null);
19 | const [erreur, setErreur] = useState("");
20 |
21 | // Fonction qui supprime un label grâce à sa key (dans firebase et dans les variables locales)
22 | async function supprimerLabel(key) {
23 | try {
24 | if (!key) {
25 | setErreur("Veuillez sélectionner un abonnement.");
26 | return;
27 | }
28 | const abonnementRef = ref(database, `categories/${key}`);
29 | await set(abonnementRef, null);
30 | setAbonnements(prev => prev.filter(ab => ab.key !== key));
31 | setVisibiliteModalSuppressionLabel(false);
32 | } catch (error) {
33 | console.error("Erreur lors de la suppression de l'abonnement :", error);
34 | setErreur("Une erreur s'est produite lors de la suppression.");
35 | }
36 | }
37 |
38 | // Fonction qui récupère les labels depuis firebase
39 | async function getLabelsFromFirebase() {
40 | try {
41 | const reference = ref(database, "categories");
42 | const snapshot = await get(reference);
43 | if (snapshot.exists()) {
44 | return Object.entries(snapshot.val()).map(([key, value]) => ({
45 | key,
46 | label: value.nomLabel,
47 | value: key
48 | }));
49 | } else {
50 | console.log("Aucune donnée disponible pour ce chemin.");
51 | return [];
52 | }
53 | } catch (error) {
54 | console.error("Erreur lors de la récupération des données :", error);
55 | return [];
56 | }
57 | }
58 |
59 | // Garantie que les labels sont toujours à jour au lancement du composant
60 | useEffect(() => {
61 | let isMounted = true;
62 |
63 | async function fetchData() {
64 | const data = await getLabelsFromFirebase();
65 | if (isMounted) {
66 | setAbonnements(data);
67 | }
68 | }
69 |
70 | fetchData();
71 |
72 | return () => {
73 | isMounted = false;
74 | };
75 | }, [visibiliteModalSuppressionLabel]);
76 |
77 | return (
78 | setVisibiliteModalSuppressionLabel(false)}
83 | >
84 |
85 |
86 | Supprimer une catégorie
87 | {/*Menu déroulant avec les labels*/}
88 | setAbonnementSelectionne(item.value)}
95 | labelField="label"
96 | valueField="value"
97 | placeholder="Choisissez une catégorie"
98 | renderItem={item => (
99 |
100 | {item.label}
101 |
102 | )}
103 | containerStyle={styles.dropdownContainer}
104 | />
105 | {erreur ? {erreur} : null}
106 | {/*Bouton annuler*/}
107 | setVisibiliteModalSuppressionLabel(false)}>
108 | Fermer
109 |
110 | {/*Bouton valider*/}
111 | supprimerLabel(abonnementSelectionne)}
114 | >
115 | Valider
116 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | modalOverlay: {
125 | position: "absolute",
126 | top: 0,
127 | left: 0,
128 | right: 0,
129 | bottom: 0,
130 | backgroundColor: "rgba(0, 0, 0, 0.5)",
131 | justifyContent: "center",
132 | alignItems: "center",
133 | },
134 | containerModal: {
135 | width: "80%",
136 | padding: 20,
137 | backgroundColor: couleurs.white,
138 | borderRadius: 10,
139 | alignItems: "flex-start",
140 | elevation: 5,
141 | },
142 | modalTitle: {
143 | fontSize: 28,
144 | fontFamily: "HelveticaBold",
145 | marginBottom: 20,
146 | },
147 | input: {
148 | width: "100%",
149 | borderWidth: 1,
150 | borderColor: couleurs.grey,
151 | color: couleurs.black,
152 | borderRadius: 5,
153 | padding: 10,
154 | marginBottom: 15,
155 | },
156 | button: {
157 | marginTop: 5,
158 | padding: 10,
159 | borderRadius: 5,
160 | width: "100%",
161 | alignItems: "center",
162 | },
163 | buttonText: {
164 | color: "white",
165 | fontFamily: "HelveticaBold"
166 | },
167 | errorText: {
168 | color: "red",
169 | fontSize: 12,
170 | marginTop: 5,
171 | },
172 | placeholderStyle: {
173 | fontSize: 14,
174 | color: "#CBCBCC",
175 | },
176 | selectedTextStyle: {
177 | fontSize: 14,
178 | color: couleurs.black,
179 | },
180 | item: {
181 | padding: 10,
182 | },
183 | itemText: {
184 | fontSize: 14,
185 | fontFamily: 'HelveticaRegular',
186 | color: couleurs.black,
187 | }
188 | });
189 |
--------------------------------------------------------------------------------
/myWallet/components/modals/modalDeleteAbonnement.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | TouchableOpacity,
4 | Modal,
5 | View,
6 | Text
7 | } from "react-native";
8 | import { couleurs } from "@/constants/Couleurs";
9 | import { BlurView } from "expo-blur";
10 | import { Dropdown } from "react-native-element-dropdown";
11 | import { useState, useEffect } from "react";
12 | import { ref, set, get } from "firebase/database";
13 | import { database } from "@/constants/configFirebase";
14 |
15 | // FORMULAIRE SUPPRIMER ABONNEMENT
16 | export function ModalDeleteAbonnement({ visibiliteModalDeleteAbonnement, setVisibiliteModalDeleteAbonnement }) {
17 | const [abonnements, setAbonnements] = useState([]);
18 | const [abonnementSelectionne, setAbonnementSelectionne] = useState(null);
19 | const [erreur, setErreur] = useState("");
20 |
21 | // Fonction qui supprimer un abonnement à partir de sa key (dans firebase et dans les variables locales)
22 | async function supprimerAbonnement(key) {
23 | try {
24 | if (!key) {
25 | setErreur("Veuillez sélectionner un abonnement.");
26 | return;
27 | }
28 | const abonnementRef = ref(database, `abonnement/${key}`);
29 | await set(abonnementRef, null);
30 | setAbonnements(prev => prev.filter(ab => ab.key !== key));
31 | setVisibiliteModalDeleteAbonnement(false);
32 | } catch (error) {
33 | console.error("Erreur lors de la suppression de l'abonnement :", error);
34 | setErreur("Une erreur s'est produite lors de la suppression.");
35 | }
36 | }
37 |
38 | // Récupère les abonnements depuis firebase
39 | async function getAbonnementsFromFirebase() {
40 | try {
41 | const reference = ref(database, "abonnement");
42 | const snapshot = await get(reference);
43 | if (snapshot.exists()) {
44 | return Object.entries(snapshot.val()).map(([key, value]) => ({
45 | key,
46 | label: value.label,
47 | value: key
48 | }));
49 | } else {
50 | console.log("Aucune donnée disponible pour ce chemin.");
51 | return [];
52 | }
53 | } catch (error) {
54 | console.error("Erreur lors de la récupération des données :", error);
55 | return [];
56 | }
57 | }
58 |
59 | // Garantie que les abonnements sont bien à jour au chargement du composant
60 | useEffect(() => {
61 | let isMounted = true;
62 |
63 | async function fetchData() {
64 | const data = await getAbonnementsFromFirebase();
65 | if (isMounted) {
66 | setAbonnements(data);
67 | }
68 | }
69 |
70 | fetchData();
71 |
72 | return () => {
73 | isMounted = false;
74 | };
75 | }, [visibiliteModalDeleteAbonnement]);
76 |
77 | return (
78 | setVisibiliteModalDeleteAbonnement(false)}
83 | >
84 |
85 |
86 | Supprimer un abonnement
87 | {/*Dropdown avec la liste de tous les abonnements*/}
88 | setAbonnementSelectionne(item.value)}
95 | labelField="label"
96 | valueField="value"
97 | placeholder="Choisissez un abonnement"
98 | renderItem={item => (
99 |
100 | {item.label}
101 |
102 | )}
103 | containerStyle={styles.dropdownContainer}
104 | />
105 | {erreur ? {erreur} : null}
106 | {/*Bouton annuler*/}
107 | setVisibiliteModalDeleteAbonnement(false)}>
108 | Fermer
109 |
110 | {/*Formulaire valider*/}
111 | supprimerAbonnement(abonnementSelectionne)}
114 | >
115 | Valider
116 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | modalOverlay: {
125 | position: "absolute",
126 | top: 0,
127 | left: 0,
128 | right: 0,
129 | bottom: 0,
130 | backgroundColor: "rgba(0, 0, 0, 0.5)",
131 | justifyContent: "center",
132 | alignItems: "center",
133 | },
134 | containerModal: {
135 | width: "80%",
136 | padding: 20,
137 | backgroundColor: couleurs.white,
138 | borderRadius: 10,
139 | alignItems: "flex-start",
140 | elevation: 5,
141 | },
142 | modalTitle: {
143 | fontSize: 28,
144 | fontFamily: "HelveticaBold",
145 | marginBottom: 20,
146 | },
147 | input: {
148 | width: "100%",
149 | borderWidth: 1,
150 | borderColor: couleurs.grey,
151 | color: couleurs.black,
152 | borderRadius: 5,
153 | padding: 10,
154 | marginBottom: 15,
155 | },
156 | button: {
157 | marginTop: 5,
158 | padding: 10,
159 | borderRadius: 5,
160 | width: "100%",
161 | alignItems: "center",
162 | },
163 | buttonText: {
164 | color: "white",
165 | fontFamily: "HelveticaBold"
166 | },
167 | errorText: {
168 | color: "red",
169 | fontSize: 12,
170 | marginTop: 5,
171 | },
172 | placeholderStyle: {
173 | fontSize: 14,
174 | color: "#CBCBCC",
175 | },
176 | selectedTextStyle: {
177 | fontSize: 14,
178 | color: couleurs.black,
179 | },
180 | item: {
181 | padding: 10,
182 | },
183 | itemText: {
184 | fontSize: 14,
185 | fontFamily: 'HelveticaRegular',
186 | color: couleurs.black,
187 | }
188 | });
189 |
--------------------------------------------------------------------------------
/myWallet/components/indexComponents/transactions.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { couleurs } from "@/constants/Couleurs";
3 | import { Text, StyleSheet, View, ScrollView } from "react-native";
4 | import { Dropdown } from "react-native-element-dropdown";
5 | import { Transaction } from "@/components/indexComponents/transaction";
6 | import { get, ref } from "firebase/database";
7 | import { database } from "@/constants/configFirebase";
8 |
9 | // COMPOSANT QUI AFFICHE L'ENSEMBLE DES TRANSACTIONS DU MOIS
10 | export function Transactions({visibiliteModalEntreeArgent, visibiliteModalSortieArgent}) {
11 | const [transactions, setTransactions] = useState([]);
12 | const [moisSelectionne, setMoisSelectionne] = useState(moisEnCoursChiffre());
13 |
14 | const mois = [
15 | { label: "janvier", value: "0" },
16 | { label: "fevrier", value: "1" },
17 | { label: "mars", value: "2" },
18 | { label: "avril", value: "3" },
19 | { label: "mai", value: "4" },
20 | { label: "juin", value: "5" },
21 | { label: "juillet", value: "6" },
22 | { label: "aout", value: "7" },
23 | { label: "septembre", value: "8" },
24 | { label: "octobre", value: "9" },
25 | { label: "novembre", value: "10" },
26 | { label: "decembre", value: "11" },
27 | ];
28 |
29 | // Filtre le tableau pour le garder que les transactions du mois sélectionné
30 | function filtrerTransactionsParMois(transactions) {
31 | const nouveauTableauTransactions = [];
32 | for (const transaction of transactions) {
33 | const date = new Date(transaction.date);
34 | if (date.getMonth() === Number(moisSelectionne)) {
35 | nouveauTableauTransactions.push(transaction);
36 | }
37 | }
38 | return nouveauTableauTransactions;
39 | }
40 |
41 | // Fonction pour récupérer les données de Firebase
42 | async function getTransactionsFromFirebase() {
43 | const reference = ref(database, "transactions");
44 | try {
45 | const snapshot = await get(reference);
46 | if (snapshot.exists()) {
47 | return snapshot.val();
48 | } else {
49 | console.log("Aucune donnée disponible pour ce chemin.");
50 | return [];
51 | }
52 | } catch (error) {
53 | console.error("Erreur lors de la récupération des données :", error);
54 | return [];
55 | }
56 | }
57 |
58 | // Charger les transactions au montage du composant
59 | useEffect(() => {
60 | async function fetchData() {
61 | const data = await getTransactionsFromFirebase();
62 | setTransactions(Object.values(data));
63 | }
64 |
65 | fetchData();
66 | }, [visibiliteModalSortieArgent, visibiliteModalEntreeArgent]);
67 |
68 | // Fonction qui récupère le mois actuel en chaine de caractère
69 | function moisEnCours() {
70 | const date = new Date();
71 | const moisIndex = date.getMonth();
72 | const moisEnCours = mois.find((item) => Number(item.value) === moisIndex);
73 | return moisEnCours ? moisEnCours.label : null;
74 | }
75 |
76 | // Fonction qui récupère le mois actuel en chiffre
77 | function moisEnCoursChiffre() {
78 | const date = new Date();
79 | const moisIndex = date.getMonth();
80 | return moisIndex;
81 | }
82 |
83 | // Génère les composants visuels pour le dropdown
84 | const renderItem = (item) => {
85 | return (
86 |
87 | {item.label}
88 |
89 | );
90 | };
91 |
92 | const transactionsAffichees = filtrerTransactionsParMois(transactions);
93 |
94 | return (
95 |
96 |
97 | Transactions
98 | {/*Dropdown pour sélectionner un mois à afficher*/}
99 | setMoisSelectionne(item.value)}
112 | />
113 |
114 | {/*ScrollView avec toutes les transactions du mois*/}
115 |
116 | {transactions.length > 0 ? (
117 | transactionsAffichees.map((transaction, index) => (
118 |
125 | ))
126 | ) : (
127 | Aucune transaction disponible
128 | )}
129 |
130 |
131 | );
132 | }
133 |
134 | const styles = StyleSheet.create({
135 | containerGeneral: {
136 | marginLeft: 20,
137 | marginRight: 20,
138 | },
139 | dropdown: {
140 | height: 30,
141 | width: 120,
142 | borderColor: couleurs.black,
143 | borderWidth: 2,
144 | borderRadius: 20,
145 | paddingHorizontal: 10,
146 | backgroundColor: couleurs.grey,
147 | },
148 | icon: {
149 | marginRight: 5,
150 | },
151 | label: {
152 | position: "absolute",
153 | backgroundColor: couleurs.grey,
154 | fontFamily: "HelveticaBold",
155 | left: 22,
156 | top: 8,
157 | zIndex: 999,
158 | paddingHorizontal: 10,
159 | fontSize: 14,
160 | borderRadius: 100,
161 | },
162 | placeholderStyle: {
163 | fontSize: 14,
164 | },
165 | selectedTextStyle: {
166 | fontSize: 14,
167 | },
168 | iconStyle: {
169 | width: 20,
170 | height: 20,
171 | },
172 | item: {
173 | padding: 10,
174 | borderBottomWidth: 1,
175 | borderBottomColor: couleurs.black,
176 | },
177 | itemText: {
178 | fontSize: 14,
179 | fontFamily: "HelveticaRegular",
180 | color: couleurs.black,
181 | },
182 | dropdownContainer: {
183 | backgroundColor: couleurs.grey,
184 | borderWidth: 2,
185 | borderRadius: 5,
186 | borderColor: couleurs.black,
187 | },
188 | containerTitre: {
189 | display: "flex",
190 | flexDirection: "row",
191 | alignItems: "center",
192 | justifyContent: "space-between",
193 | },
194 | textTitreTransactions: {
195 | fontFamily: "HelveticaBold",
196 | fontSize: 30,
197 | },
198 | containerScrollView: {
199 | height: 350
200 | }
201 | });
202 |
--------------------------------------------------------------------------------
/myWallet/scripts/budgetContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from "react";
2 | import { database } from "@/constants/configFirebase";
3 | import { ref, get, update, set } from "firebase/database";
4 |
5 | export const BudgetContext = createContext();
6 |
7 | export function BudgetProvider({ children }) {
8 | const [budgetTotal, setBudgetTotal] = useState(0);
9 | const [budgetMensuel, setBudgetMensuel] = useState(0);
10 | const [objectifMensuel, setObjectifMensuel] = useState(0);
11 | const [nomLabel, setNomLabel] = useState("");
12 | const [icon, setIcon] = useState("");
13 | const [labels, setLabels] = useState([]);
14 |
15 | async function fetchData() {
16 | const data = await getLabels();
17 | setTransactions(Object.values(data)); // Convertir l'objet en tableau
18 | }
19 |
20 | async function getBudgetTotal() {
21 | const reference = ref(database, "general/budgetTotal");
22 | try {
23 | const snapshot = await get(reference);
24 | if (snapshot.exists()) {
25 | return snapshot.val();
26 | } else {
27 | console.log("Aucune donnée disponible pour ce chemin.");
28 | return 0;
29 | }
30 | } catch (error) {
31 | console.error("Erreur lors de la récupération des données :", error);
32 | return 0;
33 | }
34 | }
35 |
36 | async function getBudgetMensuel() {
37 | const reference = ref(database, "general/budgetMensuel");
38 | try {
39 | const snapshot = await get(reference);
40 | if (snapshot.exists()) {
41 | return snapshot.val();
42 | } else {
43 | console.log("Aucune donnée disponible pour ce chemin.");
44 | return 0;
45 | }
46 | } catch (error) {
47 | console.error("Erreur lors de la récupération des données :", error);
48 | return 0;
49 | }
50 | }
51 |
52 | async function getObjectifMensuel() {
53 | const reference = ref(database, "general/objectifMensuel");
54 | try {
55 | const snapshot = await get(reference);
56 | if (snapshot.exists()) {
57 | return snapshot.val();
58 | } else {
59 | console.log("Aucune donnée disponible pour ce chemin.");
60 | return 0;
61 | }
62 | } catch (error) {
63 | console.error("Erreur lors de la récupération des données :", error);
64 | return 0;
65 | }
66 | }
67 |
68 | async function getLabels() {
69 | const reference = ref(database, "categories");
70 | try {
71 | const snapshot = await get(reference);
72 | if (snapshot.exists()) {
73 | console.log(snapshot.val());
74 | console.log(Object.values(snapshot.val()));
75 | return Object.values(snapshot.val());
76 | } else {
77 | console.log("Aucune donnée disponible pour ce chemin.");
78 | return [];
79 | }
80 | } catch (error) {
81 | console.error("Erreur lors de la récupération des données :", error);
82 | return [];
83 | }
84 | }
85 |
86 | async function calculerBudgetMensuel() {
87 | const reference = ref(database, "transactions");
88 | let transactionsCalculBudgetMensuel = [];
89 | try {
90 | const snapshot = await get(reference);
91 | if (snapshot.exists()) {
92 | transactionsCalculBudgetMensuel = Object.values(snapshot.val());
93 | } else {
94 | console.log("Aucune donnée disponible pour ce chemin.");
95 | transactionsCalculBudgetMensuel = [];
96 | }
97 | } catch (error) {
98 | console.error("Erreur lors de la récupération des données :", error);
99 | transactionsCalculBudgetMensuel = [];
100 | }
101 |
102 | let budgetMensuelTemporaire = 0;
103 | const moisActuel = new Date().getMonth();
104 | for (let transaction of transactionsCalculBudgetMensuel) {
105 | const dateTransaction = new Date(transaction.date);
106 | if (dateTransaction.getMonth() === moisActuel) {
107 | budgetMensuelTemporaire += Number(transaction.prix);
108 | }
109 | }
110 | return budgetMensuelTemporaire;
111 | }
112 |
113 | async function calculerBudgetTotal() {
114 | const reference = ref(database, "transactions");
115 | let transactionsCalculBudgetMensuel = [];
116 | try {
117 | const snapshot = await get(reference);
118 | if (snapshot.exists()) {
119 | transactionsCalculBudgetMensuel = Object.values(snapshot.val());
120 | } else {
121 | console.log("Aucune donnée disponible pour ce chemin.");
122 | transactionsCalculBudgetMensuel = [];
123 | }
124 | } catch (error) {
125 | console.error("Erreur lors de la récupération des données :", error);
126 | transactionsCalculBudgetMensuel = [];
127 | }
128 |
129 | let bugetTotalTemporaire = 0;
130 | for (let transaction of transactionsCalculBudgetMensuel) {
131 | bugetTotalTemporaire += Number(transaction.prix);
132 | }
133 | console.log(bugetTotalTemporaire);
134 | return bugetTotalTemporaire;
135 | }
136 |
137 | function pushBudgeTotal(budgetApush) {
138 | const userRef = ref(database, "general");
139 | update(userRef, {
140 | budgetTotal: budgetApush,
141 | })
142 | .then(() => console.log("Push Firebase budget total réussi"))
143 | .catch((error) => console.error("Erreur lors de l'écriture des données :", error));
144 | }
145 |
146 | function pushBudgetMensuel(budgetApush) {
147 | const userRef = ref(database, "general");
148 | update(userRef, {
149 | budgetMensuel: budgetApush,
150 | })
151 | .then(() => console.log("Push Firebase budget mensuel réussi"))
152 | .catch((error) => console.error("Erreur lors de l'écriture des données :", error));
153 | }
154 |
155 | function pushObjectifMensuel() {
156 | const userRef = ref(database, "general");
157 | update(userRef, {
158 | objectifMensuel: objectifMensuel,
159 | })
160 | .then(() => console.log("Push Firebase objectif mensuel réussi"))
161 | .catch((error) => console.error("Erreur lors de l'écriture des données :", error));
162 | }
163 |
164 | function pushLabel() {
165 | const nouvelID = Date.now();
166 | const transactionsRef = ref(database, "categories/" + nouvelID);
167 | set(transactionsRef, {
168 | nomLabel: nomLabel,
169 | icon: icon,
170 | }).then(() => console.log("Entrée d'argent dans firebase")).catch((error) => console.error("Error écriture entree argent firebase:", error));
171 | }
172 |
173 | return (
174 |
200 | {children}
201 |
202 | );
203 | }
204 |
--------------------------------------------------------------------------------
/myWallet/components/previewComponents/abonnements.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { couleurs } from "@/constants/Couleurs";
3 | import { Text, StyleSheet, View, ScrollView } from "react-native";
4 | import { Dropdown } from "react-native-element-dropdown";
5 | import { get, ref } from "firebase/database";
6 | import { database } from "@/constants/configFirebase";
7 | import { MOIS_DICTIONNAIRE } from "@/constants/CONST_TEMPOREL";
8 | import { Abonnement } from "@/components/previewComponents/abonnement";
9 |
10 | // COMPOSANTS QUI CONTIENT L'ENSEMBLE DES ABONNEMENTS DU MOIS SÉLECTIONNÉ
11 | export function Abonnements({ visbiliteModalNouvelAbonnement, visibiliteModalDeleteAbonnement }) {
12 | const [abonnements, setAbonnements] = useState([]);
13 | const [moisSelectionne, setMoisSelectionne] = useState(moisEnCoursChiffre() || 0);
14 | const [abonnementsAffiches, setAbonnementsAffiches] = useState([]);
15 |
16 | // Fonction qui retourne le mois en cours en format string
17 | function moisEnCours() {
18 | const date = new Date();
19 | const moisIndex = date.getMonth();
20 | const moisEnCours = MOIS_DICTIONNAIRE.find((item) => Number(item.value) === moisIndex);
21 | return moisEnCours ? moisEnCours.label : null;
22 | }
23 |
24 | // Fonction qui retourne le mois en cours (chiffre)
25 | function moisEnCoursChiffre() {
26 | const date = new Date();
27 | const moisIndex = date.getMonth();
28 | return moisIndex;
29 | }
30 |
31 | // Génère les composants pour le menu déroulant
32 | const renderItem = (item) => {
33 | return (
34 |
35 | {item.label}
36 |
37 | );
38 | };
39 |
40 | // Fonction qui calcule la date de la prochaine transaction d'un abonnement
41 | function getProchaineTransactionAbonnement(date, recurrence) {
42 | if (!date) {
43 | console.error("Date invalide :", date);
44 | return "Date invalide";
45 | }
46 |
47 | date = new Date(date);
48 | if (isNaN(date)) {
49 | console.error("Date non valide après conversion :", date);
50 | return "Date non valide";
51 | }
52 |
53 | const dateAjd = new Date();
54 |
55 | if (date < dateAjd) {
56 | if (recurrence === "journalier") {
57 | while (date < dateAjd) {
58 | date.setDate(date.getDate() + 1);
59 | }
60 | } else if (recurrence === "mensuel") {
61 | while (date < dateAjd) {
62 | date.setMonth(date.getMonth() + 1);
63 | }
64 | } else if (recurrence === "annuel") {
65 | while (date < dateAjd) {
66 | date.setFullYear(date.getFullYear() + 1);
67 | }
68 | } else {
69 | return "Récurrence non prise en charge";
70 | }
71 | }
72 |
73 | console.log(
74 | "Date actuelle : " + dateAjd.toISOString() +
75 | " | Prochaine date : " + date.toISOString()
76 | );
77 |
78 | return date;
79 | }
80 |
81 | // Fonction qui filtre les abonnements à afficher en fonction de la date de prochaine transaction
82 | function filterAbonnementsCalendar() {
83 | const anciensAbonnements = abonnements;
84 | const updatedAbonnements = [];
85 | for (const abonnement of anciensAbonnements) {
86 | const abonnementDate = new Date(abonnement.date);
87 | const moisAbonnement = abonnementDate.getMonth();
88 | const finMois = new Date(new Date().getFullYear(), Number(moisSelectionne) + 1, 0);
89 | if (abonnementDate <= finMois) {
90 | if (abonnement.recurrence === "mensuel") {
91 | updatedAbonnements.push(abonnement);
92 | } else if (abonnement.recurrence === "annuel") {
93 | if (moisAbonnement === Number(moisSelectionne)) {
94 | updatedAbonnements.push(abonnement);
95 | }
96 | }
97 | }
98 | }
99 | return updatedAbonnements;
100 | }
101 |
102 | // Fonction qui récupère les abonnements depuis firebase
103 | async function getAbonnementsFromFirebase() {
104 | const reference = ref(database, "abonnement");
105 | try {
106 | const snapshot = await get(reference);
107 | if (snapshot.exists()) {
108 | return snapshot.val();
109 | } else {
110 | console.log("Aucune donnée disponible pour ce chemin.");
111 | return [];
112 | }
113 | } catch (error) {
114 | console.error("Erreur lors de la récupération des données :", error);
115 | return [];
116 | }
117 | }
118 |
119 | // Fonction qui garantie que les abonnements sont toujoursà jours
120 | useEffect(() => {
121 | async function fetchData() {
122 | const data = await getAbonnementsFromFirebase();
123 | setAbonnements(Object.values(data));
124 | }
125 |
126 | fetchData();
127 | }, [visbiliteModalNouvelAbonnement, visibiliteModalDeleteAbonnement]);
128 |
129 | useEffect(() => {
130 | const filteredAbonnements = [...filterAbonnementsCalendar()].reverse();
131 | setAbonnementsAffiches(filteredAbonnements);
132 | }, [moisSelectionne, abonnements]);
133 |
134 |
135 | return (
136 |
137 |
138 | Abonnements
139 | {/*Menu pour sélectionner un mois*/}
140 | setMoisSelectionne(item.value)}
153 | />
154 |
155 | {/*Scrollview avec la liste des abonnements du mois choisi */}
156 |
157 | {abonnementsAffiches.length > 0 ? (
158 | abonnementsAffiches.map((abonnement, index) => (
159 |
165 | ))
166 | ) : (
167 | Aucune transaction disponible
168 | )}
169 |
170 |
171 | );
172 | }
173 |
174 |
175 | const styles = StyleSheet.create({
176 | containerGeneral: {
177 | marginLeft: 20,
178 | marginRight: 20,
179 | },
180 | dropdown: {
181 | height: 30,
182 | width: 95,
183 | borderColor: couleurs.black,
184 | borderWidth: 2,
185 | borderRadius: 20,
186 | paddingHorizontal: 10,
187 | backgroundColor: couleurs.grey,
188 | },
189 | icon: {
190 | marginRight: 5,
191 | },
192 | label: {
193 | position: "absolute",
194 | backgroundColor: couleurs.grey,
195 | fontFamily: "HelveticaBold",
196 | left: 22,
197 | top: 8,
198 | zIndex: 999,
199 | paddingHorizontal: 10,
200 | fontSize: 14,
201 | borderRadius: 100,
202 | },
203 | placeholderStyle: {
204 | fontSize: 14,
205 | },
206 | selectedTextStyle: {
207 | fontSize: 14,
208 | },
209 | iconStyle: {
210 | width: 20,
211 | height: 20,
212 | },
213 | item: {
214 | padding: 10,
215 | borderBottomWidth: 1,
216 | borderBottomColor: couleurs.black,
217 | },
218 | itemText: {
219 | fontSize: 14,
220 | fontFamily: "HelveticaRegular",
221 | color: couleurs.black,
222 | },
223 | dropdownContainer: {
224 | backgroundColor: couleurs.grey,
225 | borderWidth: 2,
226 | borderRadius: 5,
227 | borderColor: couleurs.black,
228 | },
229 | containerTitre: {
230 | display: "flex",
231 | flexDirection: "row",
232 | alignItems: "center",
233 | justifyContent: "space-between",
234 | },
235 | textTitreTransactions: {
236 | fontFamily: "HelveticaBold",
237 | fontSize: 30,
238 | },
239 | containerScrollView: {
240 | height: 230
241 | }
242 | });
243 |
--------------------------------------------------------------------------------
/myWallet/components/previewComponents/dashboardCalendar.jsx:
--------------------------------------------------------------------------------
1 | import { Text, View, TouchableOpacity, Image, StyleSheet, ImageBackground } from "react-native";
2 | import { couleurs } from '@/constants/Couleurs';
3 | import arrowUp from "@/assets/images/arrowUp.png";
4 | import arrowDown from "@/assets/images/arrowDown.png";
5 | import { database } from "@/constants/configFirebase";
6 | import { ref, get } from "firebase/database";
7 | import { useState, useEffect } from "react";
8 | import { MOIS_DICTIONNAIRE, JOURS_DICTIONNAIRE } from "@/constants/CONST_TEMPOREL";
9 |
10 | // CALENDRIER POUR VISUALISER LES ABONNEMENTS À VENIR
11 | export function DashboardCalendar({ setVisibiliteModalNouvelAbonnement, setVisibiliteModalDeleteAbonnement, visbiliteModalNouvelAbonnement, visibiliteModalDeleteAbonnement }) {
12 | const [abonnements, setAbonnements] = useState([]);
13 |
14 | // Fonction qui génère le titre du dashboard (mois + année)
15 | function getTitreDashboard() {
16 | const date = new Date();
17 | const moisIndex = date.getMonth();
18 | const annee = date.getFullYear();
19 | const mois = MOIS_DICTIONNAIRE.find(item => Number(item.value) === moisIndex)?.label || "Mois inconnu";
20 | const chaine = mois.charAt(0).toUpperCase() + mois.slice(1).toLowerCase() + " " + annee;
21 | return chaine
22 | }
23 |
24 | // Fonction qui filtre les logo des abonnements à afficher en fonction du mois
25 | function filterAbonnementsCalendar() {
26 | const anciensAbonnements = abonnements;
27 | const updatedAbonnements = [];
28 | for (const abonnement of anciensAbonnements) {
29 | const abonnementDate = new Date(abonnement.date);
30 | const moisAbonnement = abonnementDate.getMonth();
31 | const moisActuel = new Date().getMonth();
32 | const finMois = new Date(new Date().getFullYear(), Number(moisActuel) + 1, 0);
33 | if (abonnementDate <= finMois) {
34 | if (abonnement.recurrence === "mensuel") {
35 | const tempDico = {
36 | jour: abonnementDate.getDate(),
37 | photoUri: abonnement.imageUrl
38 | };
39 | updatedAbonnements.push(tempDico);
40 | } else if (abonnement.recurrence === "annuel") {
41 | if (moisAbonnement === Number(moisActuel)) {
42 | const tempDico = {
43 | jour: abonnementDate.getDate(),
44 | photoUri: abonnement.imageUrl
45 | };
46 | updatedAbonnements.push(tempDico);
47 | }
48 | }
49 | }
50 | }
51 | return updatedAbonnements;
52 | }
53 |
54 | // Fonction qui récupère les abonnements depuis firebase
55 | async function getAbonnementsFromFirebase() {
56 | const reference = ref(database, "abonnement");
57 | try {
58 | const snapshot = await get(reference);
59 | if (snapshot.exists()) {
60 | return snapshot.val();
61 | } else {
62 | console.log("Aucune donnée disponible pour ce chemin.");
63 | return [];
64 | }
65 | } catch (error) {
66 | console.error("Erreur lors de la récupération des données :", error);
67 | return [];
68 | }
69 | }
70 |
71 | // Garantie que les abonnements sont tjrs à jour en local
72 | useEffect(() => {
73 | async function fetchData() {
74 | const data = await getAbonnementsFromFirebase();
75 | setAbonnements(Object.values(data));
76 | }
77 |
78 | fetchData();
79 | }, [visbiliteModalNouvelAbonnement, visibiliteModalDeleteAbonnement]);
80 |
81 | // Fonction qui retourne un tableau formaté pour correspondre les chiffres avec les jours de la semaine
82 | // ajoute des cases vides avant et apres les chiffres pour garantir le décalage
83 | function calculerDecalage() {
84 | const date = new Date();
85 | const annee = date.getFullYear();
86 | const mois = date.getMonth();
87 |
88 | // Premier jour du mois
89 | const premierDuMois = new Date(annee, mois, 1);
90 | const difference = (7 + premierDuMois.getDay()) % 7;
91 |
92 | // Calcul du nombre de jours dans le mois
93 | const estBissextile = (annee % 4 === 0 && annee % 100 !== 0) || (annee % 400 === 0);
94 | const joursFevrier = estBissextile ? 29 : 28;
95 | const mois31 = [0, 2, 4, 6, 7, 9, 11];
96 | const mois30 = [3, 5, 8, 10];
97 |
98 | let nbJoursMois = 0;
99 | if (mois31.includes(mois)) {
100 | nbJoursMois = 31;
101 | } else if (mois30.includes(mois)) {
102 | nbJoursMois = 30;
103 | } else if (mois === 1) {
104 | nbJoursMois = joursFevrier;
105 | }
106 |
107 | const tab = [];
108 | let ligne = [];
109 |
110 | // Ajouter les jours du mois au tableau
111 | for (let i = 0; i < difference; i++) {
112 | ligne.push("");
113 | }
114 |
115 | for (let jour = 1; jour <= nbJoursMois; jour++) {
116 | ligne.push(jour);
117 | if (ligne.length === 7) {
118 | tab.push(ligne);
119 | ligne = [];
120 | }
121 | }
122 |
123 | // Compléter la dernière ligne si nécessaire (case vide à la fin du mois)
124 | if (ligne.length > 0) {
125 | while (ligne.length < 7) {
126 | ligne.push("");
127 | }
128 | tab.push(ligne);
129 | }
130 |
131 | console.log(tab);
132 | return tab;
133 | }
134 |
135 | const abonnementsFiltres = filterAbonnementsCalendar();
136 |
137 | return (
138 |
139 |
140 | {/*Titre du dashboard*/}
141 | {getTitreDashboard()}
142 |
143 | {/*Ligne avec les jours (lun mar mer jeu ven sam dim*/}
144 |
145 | {
146 | JOURS_DICTIONNAIRE.map((jour, index) => (
147 |
148 | {jour.label.slice(0, 3)}
149 |
150 | ))
151 | }
152 |
153 | {/*Calendrier avec les chiffres alignés aux jours*/}
154 | {
155 | calculerDecalage().map((ligne, index) => (
156 |
157 | {ligne.map((jour, idx) =>
158 | // Ajoute le logo de l'abonnement en background si c'est le jour du prélèvement
159 | abonnementsFiltres.some(abonnement => abonnement.jour === jour) ? (
160 | abonnement.jour === jour)?.photoUri }}
163 | style={[styles.buttonImgBackground, { overflow: "hidden" }]}
164 | imageStyle={{ borderRadius: 100 }}
165 | resizeMode="cover"
166 | >
167 | {jour}
168 |
169 | ) : (
170 |
171 | {jour}
172 |
173 | )
174 | )}
175 |
176 | ))
177 | }
178 |
179 |
180 |
181 | {/*Bouton supprimer abonnement*/}
182 | setVisibiliteModalDeleteAbonnement(true)}>
183 |
184 | Supprimer
185 |
186 | {/*Bouton ajouter abonnement*/}
187 | setVisibiliteModalNouvelAbonnement(true)}>
188 |
189 | Ajouter
190 |
191 |
192 |
193 | );
194 | }
195 |
196 | const styles = StyleSheet.create({
197 | // Containers
198 | containerGeneral: {
199 | backgroundColor: couleurs.white,
200 | margin: 20,
201 | padding: 10,
202 | borderRadius: 10,
203 | // Ombre pour iOS
204 | shadowColor: couleurs.black,
205 | shadowOffset: { width: 0, height: 5 },
206 | shadowOpacity: 0.25,
207 | shadowRadius: 10,
208 | // Ombre pour Android
209 | elevation: 10,
210 | },
211 | bouton: {
212 | display: "flex",
213 | flexDirection: "row",
214 | alignItems: "center",
215 | borderRadius: 5,
216 | width: 140,
217 | height: 30,
218 | marginRight: 5,
219 | },
220 | containerBoutons: {
221 | display: "flex",
222 | flexDirection: "row",
223 | marginTop: 40
224 | },
225 | // textes
226 | textChiffres: {
227 | fontFamily: 'HelveticaBold',
228 | fontSize: 50,
229 | marginTop: -10,
230 | color: couleurs.black
231 | },
232 | textChiffresDessous: {
233 | color: couleurs.grey,
234 | marginTop: -15,
235 | },
236 | textPourcentage: {
237 | fontFamily: 'HelveticaRegular',
238 | fontSize: 14,
239 | },
240 | textBouton: {
241 | fontFamily: 'HelveticaRegular',
242 | fontSize: 14,
243 | marginLeft: -3,
244 | marginTop: 3
245 | },
246 | textJours: {
247 | fontSize: 14,
248 | fontFamily: "HelveticaBold",
249 | color: couleurs.black,
250 | },
251 | textChiffre: {
252 | fontSize: 14,
253 | fontFamily: "HelveticaRegular",
254 | color: couleurs.black,
255 | },
256 | // autres
257 | image: {
258 | width: 25,
259 | height: 25
260 | },
261 | textTitreTransactions: {
262 | fontFamily: "HelveticaBold",
263 | fontSize: 30,
264 | },
265 | containerLigneTitre: {
266 | display: "flex",
267 | flexDirection: "row",
268 | width: 280,
269 | justifyContent: "space-between"
270 | },
271 | calendarContainer: {
272 | width: "100%",
273 | justifyContent: "center",
274 | alignItems: "center",
275 | marginTop: 30
276 | },
277 | boxCalendar: {
278 | display: "flex",
279 | justifyContent: "center",
280 | alignItems: "center",
281 | width: 30,
282 | height: 30
283 | },
284 | buttonImgBackground: {
285 | display: "flex",
286 | justifyContent: "center",
287 | alignItems: "center",
288 | width: 30,
289 | height: 30
290 | }
291 | });
--------------------------------------------------------------------------------
/myWallet/app/(tabs)/reglagesScreen.tsx:
--------------------------------------------------------------------------------
1 | import { Text, StyleSheet, View, TouchableOpacity, Image, Modal, TextInput, ScrollView } from 'react-native';
2 | import { BlurView } from "expo-blur";
3 | import React, { useContext, useState, useEffect } from "react";
4 | import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
5 | import arrowUp from "@/assets/images/arrowUp.png";
6 | import arrowDown from "@/assets/images/arrowDown.png";
7 | import { couleurs } from "@/constants/Couleurs";
8 | import { BudgetContext } from "@/scripts/budgetContext";
9 | import { Label } from "@/components/reglagesComponents/label";
10 | import { ModalAjoutLabel } from "@/components/modals/modalAjoutLabel";
11 | import { ModalSuppressionLabel } from "@/components/modals/modalSuppressionLabel"
12 |
13 | // ÉCRAN DE RÉGLAGES
14 | export default function ReglagesScreen() {
15 | const [visibiliteModalObjectif, setVisibiliteModalObjectif] = useState(false);
16 | const [visibiliteModalAjoutLabel, setVisibiliteModalAjoutLabel] = useState(false);
17 | const [visibiliteModalSuppressionLabel, setVisibiliteModalSuppressionLabel] = useState(false);
18 |
19 | // Import du contexte
20 | const {
21 | budgetTotal,
22 | budgetMensuel,
23 | objectifMensuel,
24 | nomLabel,
25 | icon,
26 | labels,
27 | getBudgetTotal,
28 | getBudgetMensuel,
29 | getObjectifMensuel,
30 | getLabels,
31 | setLabels,
32 | pushBudgeTotal,
33 | pushBudgetMensuel,
34 | pushLabel,
35 | setBudgetTotal,
36 | setBudgetMensuel,
37 | setObjectifMensuel,
38 | setNomLabel,
39 | setIcon,
40 | pushObjectifMensuel,
41 | calculerBudgetMensuel,
42 | calculerBudgetTotal
43 | } = useContext(BudgetContext);
44 |
45 | // Gestion du changement d'objectif financier (-> maj firebase, et modification de la variable dans l'app)
46 | function handleChangementObjectif() {
47 | if (Number(objectifMensuel) !== 0) {
48 | pushObjectifMensuel();
49 | setBudgetMensuel(0);
50 | setVisibiliteModalObjectif(false);
51 | }
52 | }
53 |
54 | // Récupère les différents budgets et les mets à jour en local pour toujours avoir le bon
55 | useEffect(() => {
56 | async function fetchBudget() {
57 | const budgetRecup = await calculerBudgetTotal();
58 | const budgetMensuelRecup = await calculerBudgetMensuel();
59 | const objectifMensuelRecup = await getObjectifMensuel();
60 | setBudgetMensuel(budgetMensuelRecup);
61 | setBudgetTotal(budgetRecup);
62 | setObjectifMensuel(objectifMensuelRecup);
63 | }
64 |
65 | fetchBudget();
66 | }, [visibiliteModalObjectif, visibiliteModalAjoutLabel, visibiliteModalSuppressionLabel]);
67 |
68 | // Idem pour les labels
69 | useEffect(() => {
70 | async function fetchLabels() {
71 | try {
72 | const labelsRecup = await getLabels();
73 | const labelsArray = Object.values(labelsRecup);
74 | setLabels(labelsArray);
75 | } catch (error) {
76 | console.error("Erreur lors de la récupération des labels :", error);
77 | }
78 | }
79 | fetchLabels();
80 | }, [visibiliteModalObjectif, visibiliteModalAjoutLabel, visibiliteModalSuppressionLabel]);
81 |
82 |
83 | // Vérifier dans le rendu si label est vide
84 | useEffect(() => {
85 | console.log("labels dans le rendu : ", labels);
86 | }, [labels]);
87 |
88 | return (
89 |
90 |
91 | {/*Formulaire objectif financier*/}
92 | setVisibiliteModalObjectif(false)}
97 | >
98 |
99 |
100 | Modifier l'objectif mensuel
101 |
108 | setVisibiliteModalObjectif(false)}>
109 | Fermer
110 |
111 | handleChangementObjectif()}
114 | >
115 | Valider
116 |
117 |
118 |
119 |
120 | {/*Formulaire suppression label*/}
121 |
125 | {/*Formulaire ajout label*/}
126 |
135 |
136 |
137 | Réglages
138 |
139 |
140 | {/*Container objectif mensuel (chiffre et bouton de modification)*/}
141 |
142 |
143 | objectif mensuel
144 | {objectifMensuel}€
145 |
146 | setVisibiliteModalObjectif(true)}>
147 |
148 | Modifier l'objectif
149 |
150 |
151 | {/*Container label (liste et boutons de modification)*/}
152 |
153 |
154 | setVisibiliteModalSuppressionLabel(true)}>
155 |
156 | Supprimer label
157 |
158 | setVisibiliteModalAjoutLabel(true)}>
159 |
160 | Ajouter label
161 |
162 |
163 |
164 |
165 |
166 | {Array.isArray(labels) && labels.map((label, index) => (
167 |
172 | ))}
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | )
181 | };
182 |
183 | const styles = StyleSheet.create({
184 | containerGeneral: {
185 | backgroundColor: couleurs.white,
186 | padding: 10,
187 | borderRadius: 10,
188 | width: 208,
189 | marginTop: 5,
190 | // Ombre pour iOS
191 | shadowColor: couleurs.black,
192 | shadowOffset: { width: 0, height: 5 },
193 | shadowOpacity: 0.1,
194 | shadowRadius: 10,
195 | // Ombre pour Android
196 | elevation: 10,
197 | },
198 | containerLabels: {
199 | display: "flex",
200 | width: "100%",
201 | flexDirection: "row",
202 | justifyContent: "space-between",
203 | alignItems: "flex-start",
204 | },
205 | modalOverlay: {
206 | position: "absolute",
207 | top: 0,
208 | left: 0,
209 | right: 0,
210 | bottom: 0,
211 | backgroundColor: "rgba(0, 0, 0, 0.5)",
212 | justifyContent: "center",
213 | alignItems: "center",
214 | },
215 | containerModal: {
216 | width: "80%",
217 | padding: 20,
218 | backgroundColor: couleurs.white,
219 | borderRadius: 10,
220 | alignItems: "flex-start",
221 | elevation: 5,
222 | },
223 | modalTitle: {
224 | fontSize: 28,
225 | fontFamily: "HelveticaBold",
226 | marginBottom: 20,
227 | },
228 | button: {
229 | marginTop: 5,
230 | padding: 10,
231 | borderRadius: 5,
232 | width: "100%",
233 | alignItems: "center",
234 | },
235 | buttonText: {
236 | color: "white",
237 | fontFamily: "HelveticaBold"
238 | },
239 | containerSafeAreaView: {
240 | display: "flex",
241 | justifyContent: "center",
242 | alignItems: "center"
243 | },
244 | containerGlobalReglages: {
245 | width: "80%",
246 | },
247 | textTitreTransactions: {
248 | fontFamily: "HelveticaBold",
249 | fontSize: 40,
250 | },
251 | containerTitre: {
252 | marginTop: 40
253 | },
254 | bouton: {
255 | display: "flex",
256 | flexDirection: "column",
257 | alignItems: "flex-start",
258 | borderRadius: 5,
259 | width: 85,
260 | height: 82,
261 | },
262 | input: {
263 | width: "100%",
264 | borderWidth: 1,
265 | borderColor: couleurs.grey,
266 | color: couleurs.black,
267 | borderRadius: 5,
268 | padding: 10,
269 | marginBottom: 15,
270 | },
271 | textBouton: {
272 | fontFamily: 'HelveticaRegular',
273 | fontSize: 14,
274 | marginLeft: 20,
275 | marginTop: -15
276 | },
277 | image: {
278 | marginLeft: -10,
279 | marginTop: -12.5,
280 | width: 75,
281 | height: 75
282 | },
283 | textObjectif: {
284 | fontFamily: "HelveticaRegular",
285 | fontSize: 14,
286 | color: couleurs.darkGreen
287 | },
288 | bigTextObjectif: {
289 | fontFamily: "HelveticaBold",
290 | fontSize: 50,
291 | marginTop: -10,
292 | marginBottom: -10,
293 | color: couleurs.darkGreen
294 | },
295 | containerObjectif: {
296 | backgroundColor: couleurs.lightGreen,
297 | borderRadius: 5,
298 | padding: 15,
299 | width: 210,
300 | },
301 | containerObjectifGlobal: {
302 | display: "flex",
303 | width: "80%",
304 | flexDirection: "row",
305 | justifyContent: "space-between",
306 | paddingTop: 40
307 | },
308 | containerButtonLabel: {
309 | display: "flex",
310 | flexDirection: "column",
311 | },
312 | containerLabelsGeneral: {
313 | display: "flex",
314 | flexDirection: "row",
315 | width: "80%",
316 | justifyContent: "space-between"
317 | },
318 | containerScrollView: {
319 | height: 160,
320 | marginTop: -5,
321 | marginBottom: -5
322 | }
323 | });
--------------------------------------------------------------------------------
/scriptPython/main.py:
--------------------------------------------------------------------------------
1 | import xlsxwriter
2 | from firebase_admin import credentials, db
3 | import firebase_admin
4 | from datetime import datetime
5 | from collections import defaultdict
6 | from configFirebase import DATABASE_URL
7 |
8 | # Initialiser Firebase
9 | cred = credentials.Certificate("serviceAccountKey.json")
10 | firebase_admin.initialize_app(cred, {
11 | 'databaseURL': DATABASE_URL
12 | })
13 |
14 | # Couleurs pour générer les graphiques
15 | colors = ['#002A00', '#1C5518', '#388030', '#55AA49', '#71D561', "#0F3B0E", "#2E6E24", "#4F8E3A", "#6BB053", "#89D16A", "#A2E284", "#BFF69B"]
16 |
17 | # Constantes
18 | ref = db.reference('transactions')
19 | users = ref.get()
20 | transactions_2025 = defaultdict(list)
21 | mois_traduction = {
22 | "january": "janvier",
23 | "february": "fevrier",
24 | "march": "mars",
25 | "april": "avril",
26 | "may": "mai",
27 | "june": "juin",
28 | "july": "juillet",
29 | "august": "aout",
30 | "september": "septembre",
31 | "october": "octobre",
32 | "november": "novembre",
33 | "december": "decembre",
34 | }
35 | annee_actuelle = datetime.now().year
36 |
37 | # Récupérer les données depuis firebase
38 | for key, transaction in users.items():
39 | try:
40 | prix = float(transaction["prix"])
41 | date_str = transaction["date"]
42 | if date_str.endswith("Z"):
43 | date_str = date_str[:-1]
44 | date_obj = datetime.fromisoformat(date_str)
45 | mois_nom_en = date_obj.strftime("%B").lower()
46 | mois_nom_fr = mois_traduction.get(mois_nom_en, None)
47 |
48 | if mois_nom_fr and date_obj.year == annee_actuelle:
49 | transactions_2025[mois_nom_fr].append([
50 | transaction['label'],
51 | transaction['categorie'],
52 | prix,
53 | transaction['date'],
54 | key
55 | ])
56 | except KeyError as e:
57 | print(f"Champ manquant dans la transaction {key} : {e}")
58 | except ValueError as e:
59 | print(f"Erreur de conversion dans la transaction {key} : {e}")
60 |
61 |
62 | # Création du fichier excel
63 | workbook = xlsxwriter.Workbook('transactions_2025.xlsx')
64 |
65 | # Formatage des cases excel (couleur background et police d'écriture)
66 | header_format = workbook.add_format({
67 | 'bold': True,
68 | 'align': 'center',
69 | 'valign': 'vcenter',
70 | 'bg_color': '#71D561',
71 | 'font_name': 'Helvetica',
72 | 'color': '#002A00',
73 | })
74 | normal_format = workbook.add_format({
75 | 'align': 'left',
76 | 'valign': 'top',
77 | 'bg_color': '#F1F1F3',
78 | 'font_name': 'Helvetica',
79 | 'color': '#1A1A1A',
80 | })
81 | currency_format = workbook.add_format({
82 | 'num_format': '#,##0.00',
83 | 'align': 'center',
84 | })
85 |
86 | # Ajouter une feuille par mois
87 | for mois, transactions in transactions_2025.items():
88 | worksheet_name = mois.capitalize()[:31]
89 | worksheet = workbook.add_worksheet(name=worksheet_name)
90 |
91 | # Ajouter les en-têtes
92 | headers = ["Label", "Categorie", "Prix", "Date", "Identifiant"]
93 | worksheet.write_row(0, 0, headers, header_format)
94 |
95 | # Ajouter les transactions
96 | for row_idx, transaction in enumerate(transactions, start=1):
97 | worksheet.write_row(row_idx, 0, transaction, normal_format)
98 | worksheet.set_column(0, len(headers) - 1, 25)
99 |
100 | # Regrouper les transactions par catégorie
101 | categorie_totaux = defaultdict(float)
102 | for transaction in transactions:
103 | categorie = transaction[1]
104 | montant = transaction[2]
105 | categorie_totaux[categorie] += montant
106 |
107 | # Ajouter les données pour le graphique
108 | categories = list(categorie_totaux.keys())
109 | valeurs = [float(v) for v in categorie_totaux.values() if isinstance(v, (int, float))]
110 |
111 | if not categories or not valeurs:
112 | continue
113 |
114 | # Ajout d'un tableau avec les catégories de dépenses et d'un tableau avec le bilan du mois (entrées d'argent, sortie d'argent)
115 | start_row = 18
116 | worksheet.write(17, 6, "Categories", header_format)
117 | worksheet.write(17, 7, "Valeurs", header_format)
118 | worksheet.set_column(17, 6, 12)
119 | worksheet.set_column(17, 7, 8)
120 | worksheet.write_column(start_row, 6, categories, normal_format)
121 | worksheet.write_column(start_row, 7, valeurs, normal_format)
122 | worksheet.write(17, 9, "Total sorties argent", header_format)
123 | worksheet.write(17, 10, "Total entrées d'argent", header_format)
124 | worksheet.write(17, 11, "Balance", header_format)
125 | total_depenses = sum(t[2] for t in transactions if t[2] < 0)
126 | total_entrees = sum(t[2] for t in transactions if t[2] >= 0)
127 | balance = total_entrees + total_depenses
128 | worksheet.write(18, 9, total_depenses, normal_format)
129 | worksheet.write(18, 10, total_entrees, normal_format)
130 | worksheet.write(18, 11, balance, normal_format)
131 | worksheet.set_column(17, 9, 20)
132 | worksheet.set_column(17, 10, 20)
133 | worksheet.set_column(17, 11, 20)
134 |
135 | # Diagramme circulaire
136 | doughnut_chart = workbook.add_chart({'type': 'doughnut'})
137 | doughnut_chart.add_series({
138 | 'name': f"Catégories - {mois.capitalize()}",
139 | 'categories': [worksheet_name, start_row, 6, start_row + len(categories) - 1, 6],
140 | 'values': [worksheet_name, start_row, 7, start_row + len(categories) - 1, 7],
141 | 'data_labels': {
142 | 'percentage': True,
143 | 'font': {'name': 'Helvetica', 'size': 10, 'color': '#1A1A1A'},
144 | },
145 | 'points': [{'fill': {'color': color}} for color in colors[:len(categories)]], # Couleurs des seg
146 | })
147 | doughnut_chart.set_title({
148 | 'name': f"Catégories - {mois.capitalize()}",
149 | 'name_font': {'bold': True, 'size': 25, 'color': '#1A1A1A', 'name': 'Helvetica'}, # Police du titre
150 | })
151 | doughnut_chart.set_chartarea({
152 | 'border': {'color': '#1A1A1A', 'width': 3},
153 | 'fill': {'color': '#F1F1F3'},
154 | })
155 | doughnut_chart.set_plotarea({
156 | 'border': {'color': '#F1F1F3'},
157 | 'fill': {'color': '#F1F1F3'},
158 | 'layout': {
159 | 'x': 0,
160 | 'y': 0.16,
161 | 'width': 1,
162 | 'height': 0.8,
163 | }
164 | })
165 | worksheet.insert_chart('G2', doughnut_chart)
166 |
167 |
168 | # Feuille bilan
169 | bilan_sheet = workbook.add_worksheet("BILAN")
170 |
171 | # Données par mois
172 | months = [mois.capitalize() for mois in transactions_2025.keys()]
173 | bilan_sheet.write_row(0, 1, months, header_format)
174 | bilan_sheet.write(1, 0, "Sorties d'argent", header_format)
175 | bilan_sheet.write(2, 0, "Entrées d'argent", header_format)
176 | bilan_sheet.write(3, 0, "Balance", header_format)
177 | bilan_sheet.set_column(0, 0, 20)
178 | bilan_sheet.set_column(1, len(months) + 1, 10)
179 |
180 | sorties = []
181 | entrees = []
182 | balances = []
183 |
184 | for mois, transactions in transactions_2025.items():
185 | total_depenses = sum(t[2] for t in transactions if t[2] < 0)
186 | total_entrees = sum(t[2] for t in transactions if t[2] >= 0)
187 | sorties.append(total_depenses)
188 | entrees.append(total_entrees)
189 | balances.append(total_entrees + total_depenses)
190 |
191 | bilan_sheet.write_row(1, 1, sorties, normal_format)
192 | bilan_sheet.write_row(2, 1, entrees, normal_format)
193 | bilan_sheet.write_row(3, 1, balances, normal_format)
194 |
195 | # Graphique des sorties d'argent
196 | sorties_chart = workbook.add_chart({'type': 'column'})
197 | sorties_chart.add_series({
198 | 'name': "Sorties d'argent",
199 | 'categories': ['BILAN', 0, 1, 0, len(months)],
200 | 'values': ['BILAN', 1, 1, 1, len(months)],
201 | 'data_labels': {
202 | 'value': True,
203 | 'font': {'bold': True, 'color': '#71D561', 'name' : 'Helvetica'},
204 | },
205 | 'fill': {'color': '#71D561'},
206 | 'border': {'color': '#71D561'}
207 | })
208 | sorties_chart.set_title({
209 | 'name': "Sorties d'argent",
210 | 'name_font': {'bold': True, 'size': 25, 'color': '#71D561', 'name': 'Helvetica'},
211 | })
212 | sorties_chart.set_x_axis({
213 | 'name': 'Mois',
214 | 'name_font': {'bold': True, 'size': 12, 'color': "#71D561", 'name' : 'Helvetica'},
215 | 'num_font': {'color': "#71D561", 'name' : 'Helvetica'},
216 | 'major_gridlines': {
217 | 'visible': True,
218 | 'line': {'color': '#D3D3D3', 'width': 1, 'dash_type': 'dash'},
219 | },
220 | 'line': {'color': '#F1F1F3', 'width': 1.5},
221 | 'label_position': 'high',
222 | })
223 | sorties_chart.set_y_axis({
224 | 'name': 'Montant (€)',
225 | 'name_font': {'bold': True, 'size': 12, 'color': "#71D561", 'name' : 'Helvetica'},
226 | 'num_font': {'color': "#71D561", 'name' : 'Helvetica'},
227 | 'major_gridlines': {
228 | 'visible': False,
229 | },
230 | 'line': {'color': '#F1F1F3', 'width': 1.5},
231 | })
232 | sorties_chart.set_chartarea({
233 | 'border': {'color': '#71D561', 'width': 3},
234 | 'fill': {'color': '#002A00'},
235 | })
236 | sorties_chart.set_plotarea({
237 | 'border': {'color': '#002A00'},
238 | 'fill': {'color': '#002A00'},
239 | 'layout': {
240 | 'x': 0.17,
241 | 'y': 0.27,
242 | 'width': 0.8,
243 | 'height': 0.6,
244 | }
245 | })
246 | sorties_chart.set_legend({'none': True})
247 | bilan_sheet.insert_chart('B36', sorties_chart)
248 |
249 | # Graphique des entrées d'argent
250 | entrees_chart = workbook.add_chart({'type': 'column'})
251 | entrees_chart.add_series({
252 | 'name': "Entrées d'argent",
253 | 'categories': ['BILAN', 0, 1, 0, len(months)],
254 | 'values': ['BILAN', 2, 1, 2, len(months)],
255 | 'data_labels': {
256 | 'value': True,
257 | 'font': {'bold': True, 'color': '#002A00', 'name': 'Helvetica'},
258 | },
259 | 'fill': {'color': '#002A00'},
260 | 'border': {'color': '#002A00'}
261 | })
262 | entrees_chart.set_title({
263 | 'name': "Entrées d'argent",
264 | 'name_font': {'bold': True, 'size': 25, 'color': '#002A00', 'name': 'Helvetica'},
265 | })
266 | entrees_chart.set_x_axis({
267 | 'name': 'Mois',
268 | 'name_font': {'bold': True, 'size': 12, 'color': "#002A00", 'name': 'Helvetica'},
269 | 'num_font': {'color': "#002A00", 'name': 'Helvetica'},
270 | 'major_gridlines': {
271 | 'visible': True,
272 | 'line': {'color': '#D3D3D3', 'width': 1, 'dash_type': 'dash'},
273 | },
274 | 'line': {'color': '#F1F1F3', 'width': 1.5},
275 | 'label_position': 'high',
276 | })
277 | entrees_chart.set_y_axis({
278 | 'name': 'Montant (€)',
279 | 'name_font': {'bold': True, 'size': 12, 'color': "#002A00", 'name': 'Helvetica'},
280 | 'num_font': {'color': "#002A00", 'name': 'Helvetica'},
281 | 'major_gridlines': {
282 | 'visible': False,
283 | },
284 | 'line': {'color': '#F1F1F3', 'width': 1.5},
285 | })
286 | entrees_chart.set_chartarea({
287 | 'border': {'color': '#002A00', 'width': 3},
288 | 'fill': {'color': '#71D561'},
289 | })
290 |
291 | entrees_chart.set_plotarea({
292 | 'border': {'color': '#71D561'},
293 | 'fill': {'color': '#71D561'},
294 | 'layout': {
295 | 'x': 0.17,
296 | 'y': 0.27,
297 | 'width': 0.8,
298 | 'height': 0.6,
299 | }
300 | })
301 | entrees_chart.set_legend({'none': True})
302 | bilan_sheet.insert_chart('B21', entrees_chart)
303 |
304 | # Graphique de la balance (BarChart)
305 | balance_chart = workbook.add_chart({'type': 'column'})
306 | balance_chart.add_series({
307 | 'name': "Balance Mensuelle",
308 | 'categories': ['BILAN', 0, 1, 0, len(months)],
309 | 'values': ['BILAN', 3, 1, 3, len(months)],
310 | 'data_labels': {
311 | 'value': True,
312 | 'font': {'bold': True, 'color': '#71D561', 'name': 'Helvetica'},
313 | },
314 | 'fill': {'color': '#71D561'},
315 | 'border': {'color': '#71D561'}
316 | })
317 | balance_chart.set_title({
318 | 'name': "Balance Mensuelle",
319 | 'name_font': {'bold': True, 'size': 25, 'color': '#1A1A1A', 'name': 'Helvetica'},
320 | })
321 | balance_chart.set_x_axis({
322 | 'name': 'Mois',
323 | 'name_font': {'bold': True, 'size': 12, 'color': "#1A1A1A", 'name': 'Helvetica'},
324 | 'num_font': {'color': "#1A1A1A", 'name': 'Helvetica'},
325 | 'major_gridlines': {
326 | 'visible': False,
327 | },
328 | 'line': {'color': '#1A1A1A', 'width': 1.5},
329 | })
330 | balance_chart.set_y_axis({
331 | 'name': 'Montant (€)',
332 | 'name_font': {'bold': True, 'size': 12, 'color': "#1A1A1A", 'name': 'Helvetica'},
333 | 'num_font': {'color': "#1A1A1A", 'name': 'Helvetica'},
334 | 'major_gridlines': {
335 | 'visible': True,
336 | 'line': {'color': '#D3D3D3', 'width': 1, 'dash_type': 'dash'},
337 | },
338 | 'line': {'color': '#1A1A1A', 'width': 1.5},
339 | })
340 | balance_chart.set_chartarea({
341 | 'border': {'color': '#1A1A1A', 'width': 3},
342 | 'fill': {'color': '#F1F1F3'},
343 | })
344 | balance_chart.set_plotarea({
345 | 'border': {'color': '#F1F1F3'},
346 | 'fill': {'color': '#F1F1F3'},
347 | 'layout': {
348 | 'x': 0.15,
349 | 'y': 0.25,
350 | 'width': 0.8,
351 | 'height': 0.6,
352 | }
353 | })
354 | balance_chart.set_legend({'none': True})
355 | bilan_sheet.insert_chart('B6', balance_chart)
356 |
357 | workbook.close()
358 |
--------------------------------------------------------------------------------
/myWallet/components/modals/modalSortieArgent.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | TouchableOpacity,
4 | Modal,
5 | TextInput,
6 | View,
7 | Text
8 | } from "react-native";
9 | import { couleurs } from "@/constants/Couleurs";
10 | import { BlurView } from "expo-blur";
11 | import { Dropdown } from 'react-native-element-dropdown';
12 | import { database } from "@/constants/configFirebase";
13 | import { useState, useEffect } from "react";
14 | import { ref, set, get } from "firebase/database";
15 |
16 | // FORMULAIRE POUR AJOUTER UNE SORTIE D'ARGENT
17 | export function ModalSortieArgent({ visibiliteModalSortieArgent, setVisibiliteModalSortieArgent, getBudgetTotal, setBudgetTotal, pushBudgeTotal, getBudgetMensuel, setBudgetMensuel, pushBudgetMensuel, calculerBudgetMensuel, calculerBudgetTotal }) {
18 | const [labelEntreArgent, setLabelEntreArgent] = useState("");
19 | const [prixTransaction, setPrixTransaction] = useState("");
20 | const [categorie, setCategorie] = useState("");
21 | const [erreur, setErreur] = useState("blablablaest hgdfoqgf");
22 | // Date
23 | const [jourDate, setJourDate] = useState("");
24 | const [moisDate, setMoisDate] = useState(null);
25 | const [anneeDate, setAnneeDate] = useState(null);
26 |
27 | const [categorieTab, setCategorieTab] = useState([]);
28 |
29 | const mois = [
30 | { label: 'janvier', value: '0' },
31 | { label: 'fevrier', value: '1' },
32 | { label: 'mars', value: '2' },
33 | { label: 'avril', value: '3' },
34 | { label: 'mai', value: '4' },
35 | { label: 'juin', value: '5' },
36 | { label: 'juillet', value: '6' },
37 | { label: 'aout', value: '7' },
38 | { label: 'septembre', value: '8' },
39 | { label: 'octobre', value: '9' },
40 | { label: 'novembre', value: '10' },
41 | { label: 'decembre', value: '11' },
42 | ];
43 |
44 | // Fonction qui génère un tableau avec les années depuis 2010
45 | function genererTableauAnnee() {
46 | let tableau = [];
47 | for (let i = 2025; i > 2010; i--) {
48 | tableau.push({ label: i.toString(), value: i });
49 | }
50 | return tableau;
51 | }
52 |
53 | // fonction qui récupère les labels depuis firebase
54 | async function getLabelsFromFirebase() {
55 | try {
56 | const reference = ref(database, "categories");
57 | const snapshot = await get(reference);
58 | if (snapshot.exists()) {
59 | console.log("\n\ntest labels : " + Object.entries(snapshot.val()).map(([key, value]) => ({
60 | label: value.icon,
61 | value: value.nomLabel
62 | })));
63 | return Object.entries(snapshot.val()).map(([key, value]) => ({
64 | label: value.icon,
65 | value: value.nomLabel
66 | }));
67 | } else {
68 | console.log("Aucune donnée disponible pour ce chemin.");
69 | return [];
70 | }
71 | } catch (error) {
72 | console.error("Erreur lors de la récupération des données :", error);
73 | return [];
74 | }
75 | }
76 |
77 | // Garantie que les labels sont bien à jour au chargement du composant
78 | useEffect(() => {
79 | let isMounted = true;
80 |
81 | async function fetchData() {
82 | const data = await getLabelsFromFirebase();
83 | if (isMounted) {
84 | setCategorieTab(data);
85 | }
86 | }
87 |
88 | fetchData();
89 |
90 | return () => {
91 | isMounted = false;
92 | };
93 | }, [visibiliteModalSortieArgent]);
94 |
95 | // Reset la valeurs des variables locales
96 | function resetLabels() {
97 | setLabelEntreArgent("");
98 | setPrixTransaction("");
99 | setCategorie("");
100 | setJourDate("");
101 | setMoisDate(null);
102 | setAnneeDate(null);
103 | }
104 |
105 | // Fonction qui vérifie qu'une date sélectionnées par l'utilisateur est bien valide
106 | function checkDateExiste(jour, mois, annee) {
107 | if (mois < 0 || mois > 11) return false;
108 | console.log("jour : " + jour + "mois : " + mois + "année : " + annee);
109 | let estUneDate = true;
110 | const estBissextile = (annee % 4 === 0 && annee % 100 !== 0) || (annee % 400 === 0);
111 | const joursFevrier = estBissextile ? 29 : 28;
112 | const mois31 = [0, 2, 4, 6, 7, 9, 11];
113 | const mois30 = [3, 5, 8, 10];
114 | mois = Number(mois);
115 | if (mois31.includes(mois)) {
116 | console.log("mois 31");
117 | if (jour < 1 || jour > 31) estUneDate = false;
118 | } else if (mois30.includes(mois)) {
119 | console.log("mois 30");
120 | if (jour < 1 || jour > 30) estUneDate = false;
121 | } else if (mois === 1) {
122 | console.log("mois fevrier");
123 | if (jour < 1 || jour > joursFevrier) estUneDate = false;
124 | } else {
125 | estUneDate = false;
126 | }
127 | console.log("date ? " + estUneDate);
128 | return estUneDate;
129 | }
130 |
131 | // Fonction qui gère l'ajout d'une sortie d'argent (vérification des inputs et maj de firebase et des varibles locales)
132 | async function validationEntreeArgent() {
133 | const budgetTotalTemporaire = (await calculerBudgetTotal()) || 0;
134 | const budgetMensuelTemp = (await calculerBudgetMensuel()) || 0;
135 | // Vérifie que les inputs de l'utilisateur sont valides
136 | if (labelEntreArgent === "" || prixTransaction === "" || categorie === "" || jourDate === "" || moisDate === null || anneeDate === null || checkDateExiste(jourDate, moisDate, anneeDate) === false) {
137 | if (checkDateExiste(jourDate, moisDate, anneeDate) === false) {
138 | setErreur("Oups, ta date semble un peu bizarre. Vérifie-la pour moi !");
139 | } else {
140 | setErreur("Hey, tu as oublié de remplir tous les champs ! Allez, un petit effort");
141 | }
142 | resetLabels();
143 | setVisibiliteModalSortieArgent(true);
144 | return;
145 | } else {
146 | // Mise à jour de firebase
147 | const nouvelID = Date.now();
148 | const transactionsRef = ref(database, "transactions/" + nouvelID);
149 | const dateTransaction = new Date(anneeDate, moisDate, Number(jourDate));
150 | let prix = prixTransaction;
151 | prix = - prix;
152 | console.log(dateTransaction);
153 | console.log(dateTransaction.toDateString());
154 | set(transactionsRef, {
155 | label: labelEntreArgent,
156 | date: dateTransaction.toISOString(),
157 | prix: prix,
158 | categorie: categorie
159 | }).then(() => console.log("Entrée d'argent dans firebase")).catch((error) => console.error("Error écriture entree argent firebase:", error));
160 | // Mise à jour du budge total
161 | setBudgetTotal(budgetTotalTemporaire - Number(prixTransaction));
162 | pushBudgeTotal(budgetTotalTemporaire - Number(prixTransaction));
163 | const moisEnCours = new Date().getMonth();
164 | const anneeEnCours = new Date().getFullYear();
165 | // Mise à jour du budget mensuel
166 | if (Number(moisEnCours) === Number(moisDate) && Number(anneeDate) === Number(anneeEnCours)) {
167 | setBudgetMensuel(budgetMensuelTemp - Number(prixTransaction));
168 | pushBudgetMensuel(budgetMensuelTemp - Number(prixTransaction));
169 | }
170 | resetLabels();
171 | setVisibiliteModalSortieArgent(false);
172 | }
173 | }
174 |
175 | // Génère les composants pour le menu déroulant
176 | const renderItem = (item) => {
177 | return (
178 |
179 | {item.label}
180 |
181 | );
182 | };
183 |
184 | return (
185 | setVisibiliteModalSortieArgent(false)}
190 | >
191 | {/*
192 | setVisibiliteNotification(false)}
197 | >
198 |
199 |
200 |
201 |
202 | Erreur :
203 |
204 | setVisibiliteNotification(false)}>
205 |
206 |
207 |
208 | {erreur}
209 |
210 |
211 |
212 | */}
213 |
214 |
215 | Ajouter une sortie d'argent
216 | {/*Input nom de la transaction*/}
217 |
223 |
224 | {/*Input montant de la transaction*/}
225 |
232 | {/*Menu déroualnt pour sélectionner une catégorie de transaction*/}
233 | setCategorie(item.value)}
244 | renderItem={renderItem}
245 | containerStyle={styles.dropdownContainer}
246 | renderRightIcon={() => null}
247 | />
248 |
249 | {/*Container des inputs pour la date de la transaction*/}
250 |
251 | {/*Input du jour de la date*/}
252 |
259 | {/*Menu déroulant pour sélectionner un mois*/}
260 | setMoisDate(item.value)}
271 | renderItem={renderItem}
272 | containerStyle={styles.dropdownContainer}
273 | renderRightIcon={() => null}
274 | />
275 | {/*Menu déroulant pour sélectionner une année*/}
276 | setAnneeDate(item.value)}
287 | renderItem={renderItem}
288 | containerStyle={[styles.dropdownContainer, { width: 80 }]}
289 | renderRightIcon={() => null}
290 | />
291 |
292 | {/*Bouton annuler*/}
293 | {
294 | setVisibiliteModalSortieArgent(false);
295 | resetLabels();
296 | }}>
297 | Fermer
298 |
299 | {/*Bouton valider*/}
300 | validationEntreeArgent()}>
301 | Valider
302 |
303 |
304 |
305 |
306 | );
307 | }
308 |
309 | const styles = StyleSheet.create({
310 | modalOverlay: {
311 | position: "absolute",
312 | top: 0,
313 | left: 0,
314 | right: 0,
315 | bottom: 0,
316 | backgroundColor: "rgba(0, 0, 0, 0.5)", // Couche semi-transparente
317 | justifyContent: "center",
318 | alignItems: "center",
319 | },
320 | containerModal: {
321 | width: "80%",
322 | padding: 20,
323 | backgroundColor: couleurs.white,
324 | borderRadius: 10,
325 | alignItems: "center",
326 | elevation: 5,
327 | },
328 | modalTitle: {
329 | fontSize: 28,
330 | fontFamily: "HelveticaBold",
331 | marginBottom: 20,
332 | },
333 | input: {
334 | width: "100%",
335 | borderWidth: 1,
336 | borderColor: couleurs.grey,
337 | color: couleurs.black,
338 | borderRadius: 5,
339 | padding: 10,
340 | marginBottom: 15,
341 | },
342 | inputJour: {
343 | width: 70,
344 | borderWidth: 1,
345 | borderColor: couleurs.grey,
346 | borderRadius: 5,
347 | padding: 10,
348 | marginBottom: 15,
349 | },
350 | button: {
351 | marginTop: 5,
352 | padding: 10,
353 | borderRadius: 5,
354 | width: "100%",
355 | alignItems: "center",
356 | },
357 | buttonText: {
358 | color: "white",
359 | fontFamily: "HelveticaBold"
360 | },
361 | // Dropdown
362 | dropdown: { // dropdown replié
363 | height: 30,
364 | width: 120,
365 | paddingHorizontal: 10,
366 | borderWidth: 1,
367 | borderColor: couleurs.grey,
368 | borderRadius: 5,
369 | padding: 10,
370 | marginBottom: 15,
371 | },
372 | label: {
373 | position: 'absolute',
374 | backgroundColor: couleurs.grey,
375 | fontFamily: "HelveticaBold",
376 | left: 22,
377 | top: 8,
378 | zIndex: 999,
379 | paddingHorizontal: 10,
380 | fontSize: 14,
381 | borderRadius: 100
382 | },
383 | placeholderStyle: {
384 | fontSize: 14,
385 | color: "#CBCBCC",
386 | },
387 | selectedTextStyle: {
388 | fontSize: 14,
389 | color: couleurs.black,
390 | },
391 | iconStyle: {
392 | width: 20,
393 | height: 20,
394 | },
395 | item: {
396 | padding: 10,
397 | },
398 | itemText: {
399 | fontSize: 14,
400 | fontFamily: 'HelveticaRegular',
401 | color: couleurs.black,
402 | },
403 | dropdownContainer: {
404 | width: 100,
405 | borderRadius: 5
406 | },
407 | dropdownContainerAnnee: {
408 | width: 80,
409 | borderRadius: 5
410 | },
411 | dropdownContainerCategorie: {
412 | width: 100,
413 | borderRadius: 5
414 | },
415 | // Containers
416 | containerInputDate: {
417 | display: "flex",
418 | flexDirection: "row",
419 | width: "100%",
420 | justifyContent: "space-between"
421 | },
422 | notificationContainer: {
423 | width: "100%",
424 | height: 130,
425 | display: "flex",
426 | justifyContent: "flex-end",
427 | alignItems: "center"
428 | },
429 | notification: {
430 | width: "80%",
431 | height: 70,
432 | backgroundColor: couleurs.darkRed,
433 | borderRadius: 5,
434 | display: "flex",
435 | justifyContent: "center",
436 | padding: 10
437 | },
438 | containerCroisTexte: {
439 | display: "flex",
440 | flexDirection: "row",
441 | justifyContent: "space-between",
442 | alignItems: "center"
443 | },
444 | // Texte
445 | notificationTitre: {
446 | fontFamily: "HelveticaBold",
447 | fontSize: 20,
448 | color: couleurs.lightRed
449 | },
450 | notificationTexte: {
451 | fontFamily: "HelveticaRegular",
452 | fontSize: 14,
453 | color: couleurs.lightRed
454 | }
455 | });
456 |
--------------------------------------------------------------------------------
/myWallet/components/modals/modalNouvelAbonnement.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | TouchableOpacity,
4 | Modal,
5 | TextInput,
6 | View,
7 | Text,
8 | ImageBackground
9 | } from "react-native";
10 | import { couleurs } from "@/constants/Couleurs";
11 | import { BlurView } from "expo-blur";
12 | import { Dropdown } from 'react-native-element-dropdown';
13 | import { database } from "@/constants/configFirebase";
14 | import { useState } from "react";
15 | import { ref, set } from "firebase/database";
16 | import { getStorage, ref as storageRef, uploadBytes, getDownloadURL } from "firebase/storage";
17 | import { MOIS_DICTIONNAIRE } from "@/constants/CONST_TEMPOREL";
18 | import * as ImagePicker from 'expo-image-picker';
19 |
20 | // FORMULAIRE POUR AJOUTER UN ABONNEMENT
21 | export function ModalNouvelAbonnement({ visibiliteModalNouvelAbonnement, setVisibiliteModalNouvelAbonnement }) {
22 | const [nomAbonnement, setNomAbonnement] = useState("");
23 | const [prixAbonnement, setPrixAbonnement] = useState("");
24 | const [recurrence, setRecurrence] = useState("");
25 | const [erreur, setErreur] = useState("blablablaest hgdfoqgf");
26 | const [image, setImage] = useState("");
27 | // Date
28 | const [jourDate, setJourDate] = useState("");
29 | const [moisDate, setMoisDate] = useState(null);
30 | const [anneeDate, setAnneeDate] = useState(null);
31 |
32 | const recurrenceLabel = [
33 | { label: "mensuel", value: "mensuel" },
34 | { label: "annuel", value: "annuel" }
35 | ];
36 |
37 | // Fonction qui génère une liste avec les années depuis 2010
38 | function genererTableauAnnee() {
39 | let tableau = [];
40 | for (let i = 2025; i > 2010; i--) {
41 | tableau.push({ label: i.toString(), value: i });
42 | }
43 | return tableau;
44 | }
45 |
46 | // Foncrion qui reset les valeurs des labels dans les variables locales
47 | function resetLabels() {
48 | setNomAbonnement("");
49 | setPrixAbonnement("");
50 | setRecurrence("");
51 | setJourDate("");
52 | setMoisDate(null);
53 | setAnneeDate(null);
54 | setImage("");
55 | }
56 |
57 | // Fonction qui vérifie si une date entrée est une date valide
58 | function checkDateExiste(jour, mois, annee) {
59 | if (mois < 0 || mois > 11) return false;
60 | console.log("jour : " + jour + "mois : " + mois + "année : " + annee);
61 | let estUneDate = true;
62 | const estBissextile = (annee % 4 === 0 && annee % 100 !== 0) || (annee % 400 === 0);
63 | const joursFevrier = estBissextile ? 29 : 28;
64 | const mois31 = [0, 2, 4, 6, 7, 9, 11];
65 | const mois30 = [3, 5, 8, 10];
66 | mois = Number(mois);
67 | if (mois31.includes(mois)) {
68 | console.log("mois 31");
69 | if (jour < 1 || jour > 31) estUneDate = false;
70 | } else if (mois30.includes(mois)) {
71 | console.log("mois 30");
72 | if (jour < 1 || jour > 30) estUneDate = false;
73 | } else if (mois === 1) {
74 | console.log("mois fevrier");
75 | if (jour < 1 || jour > joursFevrier) estUneDate = false;
76 | } else {
77 | estUneDate = false;
78 | }
79 | console.log("date ? " + estUneDate);
80 | return estUneDate;
81 | }
82 |
83 | // Fonction qui upload le logo de l'abonnement sélectionnée dans la base de données firebase
84 | const uploadImageAsync = async (uri) => {
85 | const filename = uri.substring(uri.lastIndexOf('/') + 1);
86 | const blob = await (await fetch(uri)).blob();
87 | const storage = getStorage();
88 | const imageRef = storageRef(storage, `images/${filename}`);
89 | await uploadBytes(imageRef, blob);
90 | const downloadURL = await getDownloadURL(imageRef);
91 | return downloadURL;
92 | };
93 |
94 | // Fonction qui vérifie que les entrées de l'utilisateurs sont valides puis qui vient ajouter l'abonnement (firebase et maj des variables locales)
95 | async function validationEntreeArgent() {
96 | // Vérification des entrées de l'utilisateur
97 | if (image === "" || nomAbonnement === "" || prixAbonnement === "" || recurrence === "" || jourDate === "" || moisDate === null || anneeDate === null || checkDateExiste(jourDate, moisDate, anneeDate) === false) {
98 | if (checkDateExiste(jourDate, moisDate, anneeDate) === false) {
99 | setErreur("Oups, ta date semble un peu bizarre. Vérifie-la pour moi !");
100 | } else {
101 | setErreur("Hey, tu as oublié de remplir tous les champs ! Allez, un petit effort");
102 | }
103 | resetLabels();
104 | setVisibiliteModalNouvelAbonnement(true);
105 | return;
106 | } else {
107 | try {
108 | const nouvelID = Date.now();
109 | const transactionsRef = ref(database, "abonnement/" + nouvelID);
110 | const dateTransaction = new Date(anneeDate, moisDate, Number(jourDate));
111 |
112 | // Attendre l'upload de l'image et obtenir son URL
113 | let downloadURL = null;
114 | if (image) {
115 | console.log("Uploading image...");
116 | downloadURL = await uploadImageAsync(image);
117 | console.log("Image uploaded successfully. URL:", downloadURL);
118 | }
119 |
120 | // Ajouter l'entrée dans Firebase avec l'URL de l'image
121 | await set(transactionsRef, {
122 | label: nomAbonnement,
123 | date: dateTransaction.toISOString(),
124 | prix: prixAbonnement,
125 | recurrence: recurrence,
126 | imageUrl: downloadURL || null
127 | });
128 | console.log("Abonnement ajouté avec succès dans Firebase !");
129 |
130 | // Réinitialisation des champs
131 | resetLabels();
132 | setVisibiliteModalNouvelAbonnement(false);
133 | } catch (error) {
134 | console.error("Erreur lors de l'ajout de l'abonnement :", error);
135 | setErreur("Une erreur s'est produite. Merci de réessayer !");
136 | }
137 | }
138 | }
139 |
140 | // Génération des composants pour le dropdown
141 | const renderItem = (item) => {
142 | return (
143 |
144 | {item.label}
145 |
146 | );
147 | };
148 |
149 | // Fonction pour sélectionner une image dans notre gallerie
150 | const pickImage = async () => {
151 | let result = await ImagePicker.launchImageLibraryAsync({
152 | mediaTypes: ImagePicker.MediaTypeOptions.Images,
153 | allowsEditing: true,
154 | aspect: [1, 1],
155 | quality: 1,
156 | });
157 |
158 | console.log(result);
159 |
160 | if (!result.canceled && result.assets && result.assets.length > 0) {
161 | const uri = result.assets[0].uri;
162 | setImage(uri);
163 | console.log("Image URI sélectionnée :", uri);
164 | }
165 | };
166 |
167 | return (
168 | setVisibiliteModalNouvelAbonnement(false)}
173 | >
174 | {/*
175 | setVisibiliteNotification(false)}
180 | >
181 |
182 |
183 |
184 |
185 | Erreur :
186 |
187 | setVisibiliteNotification(false)}>
188 |
189 |
190 |
191 | {erreur}
192 |
193 |
194 |
195 | */}
196 |
197 |
198 | Ajouter un abonnement
199 | {/*Input pour le nom de l'abonnement*/}
200 |
206 |
207 | {/*Input pour le prix de l'abonnement*/}
208 |
215 | {/*Menu déroulant pour choisir la récurrence de l'abonnement*/}
216 | setRecurrence(item.value)}
227 | renderItem={renderItem}
228 | containerStyle={[styles.dropdownContainer, { width: 120 }]}
229 | renderRightIcon={() => null}
230 | />
231 |
232 | {/*Container des inputs pour choisir la date de l'abonnement*/}
233 |
234 | {/*Input pour choisir le jour de l'abonnement*/}
235 |
242 | {/*Menu déroulant pour choisir le mois de l'abonnement*/}
243 | setMoisDate(item.value)}
254 | renderItem={renderItem}
255 | containerStyle={styles.dropdownContainer}
256 | renderRightIcon={() => null}
257 | />
258 | {/*Menu déroulant pour choisir l'année de l'abonnement*/}
259 | setAnneeDate(item.value)}
270 | renderItem={renderItem}
271 | containerStyle={[styles.dropdownContainer, { width: 80 }]}
272 | renderRightIcon={() => null}
273 | />
274 |
275 | {/*Container pour choisir le logo de l'abonnement et affiche l'image sélectionnée en background si elle existe*/}
276 | {image ? (
277 | <>
278 |
279 | Logo ajouté
280 |
281 | >
282 | ) : (
283 |
284 | Ajouter un logo
285 |
286 | )}
287 | {/*Bouton annuler*/}
288 | {
289 | setVisibiliteModalNouvelAbonnement(false);
290 | resetLabels();
291 | }}>
292 | Fermer
293 |
294 | {/*Bouton valider*/}
295 | validationEntreeArgent()}>
296 | Valider
297 |
298 |
299 |
300 |
301 | );
302 | }
303 |
304 | const styles = StyleSheet.create({
305 | modalOverlay: {
306 | position: "absolute",
307 | top: 0,
308 | left: 0,
309 | right: 0,
310 | bottom: 0,
311 | backgroundColor: "rgba(0, 0, 0, 0.5)", // Couche semi-transparente
312 | justifyContent: "center",
313 | alignItems: "center",
314 | },
315 | containerModal: {
316 | width: "80%",
317 | padding: 20,
318 | backgroundColor: couleurs.white,
319 | borderRadius: 10,
320 | alignItems: "flex-start",
321 | elevation: 5,
322 | },
323 | modalTitle: {
324 | fontSize: 28,
325 | fontFamily: "HelveticaBold",
326 | marginBottom: 20,
327 | },
328 | input: {
329 | width: "100%",
330 | borderWidth: 1,
331 | borderColor: couleurs.grey,
332 | color: couleurs.black,
333 | borderRadius: 5,
334 | padding: 10,
335 | marginBottom: 15,
336 | },
337 | inputJour: {
338 | width: 70,
339 | borderWidth: 1,
340 | borderColor: couleurs.grey,
341 | borderRadius: 5,
342 | padding: 10,
343 | marginBottom: 15,
344 | },
345 | button: {
346 | marginTop: 5,
347 | padding: 10,
348 | borderRadius: 5,
349 | width: "100%",
350 | alignItems: "center",
351 | },
352 | buttonImgBackground: {
353 | width: "100%",
354 | paddingVertical: 10,
355 | alignItems: "center",
356 | borderRadius: 5,
357 | marginTop: 5,
358 | marginBottom: 30
359 | },
360 | buttonText: {
361 | color: "white",
362 | fontFamily: "HelveticaBold"
363 | },
364 | // Dropdown
365 | dropdown: { // dropdown replié
366 | height: 30,
367 | width: 120,
368 | paddingHorizontal: 10,
369 | borderWidth: 1,
370 | borderColor: couleurs.grey,
371 | borderRadius: 5,
372 | padding: 10,
373 | marginBottom: 15,
374 | },
375 | label: {
376 | position: 'absolute',
377 | backgroundColor: couleurs.grey,
378 | fontFamily: "HelveticaBold",
379 | left: 22,
380 | top: 8,
381 | zIndex: 999,
382 | paddingHorizontal: 10,
383 | fontSize: 14,
384 | borderRadius: 100
385 | },
386 | placeholderStyle: {
387 | fontSize: 14,
388 | color: "#CBCBCC",
389 | },
390 | selectedTextStyle: {
391 | fontSize: 14,
392 | color: couleurs.black,
393 | },
394 | iconStyle: {
395 | width: 20,
396 | height: 20,
397 | },
398 | item: {
399 | padding: 10,
400 | },
401 | itemText: {
402 | fontSize: 14,
403 | fontFamily: 'HelveticaRegular',
404 | color: couleurs.black,
405 | },
406 | dropdownContainer: {
407 | width: 100,
408 | borderRadius: 5
409 | },
410 | dropdownContainerAnnee: {
411 | width: 80,
412 | borderRadius: 5
413 | },
414 | dropdownContainerRecurrence: {
415 | width: 120,
416 | borderRadius: 5
417 | },
418 | // Containers
419 | containerInputDate: {
420 | display: "flex",
421 | flexDirection: "row",
422 | width: "100%",
423 | justifyContent: "space-between"
424 | },
425 | notificationContainer: {
426 | width: "100%",
427 | height: 130,
428 | display: "flex",
429 | justifyContent: "flex-end",
430 | alignItems: "center"
431 | },
432 | notification: {
433 | width: "80%",
434 | height: 70,
435 | backgroundColor: couleurs.darkRed,
436 | borderRadius: 5,
437 | display: "flex",
438 | justifyContent: "center",
439 | padding: 10
440 | },
441 | containerCroisTexte: {
442 | display: "flex",
443 | flexDirection: "row",
444 | justifyContent: "space-between",
445 | alignItems: "center"
446 | },
447 | // Texte
448 | notificationTitre: {
449 | fontFamily: "HelveticaBold",
450 | fontSize: 20,
451 | color: couleurs.lightRed
452 | },
453 | notificationTexte: {
454 | fontFamily: "HelveticaRegular",
455 | fontSize: 14,
456 | color: couleurs.lightRed
457 | },
458 | image: {
459 | width: 100,
460 | height: 100
461 | }
462 | });
463 |
--------------------------------------------------------------------------------
/myWallet/components/modals/modalEntreeArgent.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | TouchableOpacity,
4 | Modal,
5 | TextInput,
6 | View,
7 | Text
8 | } from "react-native";
9 | import { couleurs } from "@/constants/Couleurs";
10 | import { BlurView } from "expo-blur";
11 | import { Dropdown } from 'react-native-element-dropdown';
12 | import { database } from "@/constants/configFirebase";
13 | import { useState, useEffect } from "react";
14 | import { ref, set, get } from "firebase/database";
15 |
16 | // FORMULAIRE ENTRÉE D'ARGENT
17 | export function ModalEntreeArgent({ visibiliteModalEntreeArgent, setVisibiliteModalEntreeArgent, getBudgetTotal, setBudgetTotal, pushBudgeTotal, getBudgetMensuel, setBudgetMensuel, pushBudgetMensuel, calculerBudgetMensuel, calculerBudgetTotal }) {
18 | const [labelEntreArgent, setLabelEntreArgent] = useState("");
19 | const [prixTransaction, setPrixTransaction] = useState("");
20 | const [categorie, setCategorie] = useState("");
21 | const [erreur, setErreur] = useState("blablablaest hgdfoqgf");
22 | // Date
23 | const [jourDate, setJourDate] = useState("");
24 | const [moisDate, setMoisDate] = useState(null);
25 | const [anneeDate, setAnneeDate] = useState(null);
26 |
27 | const [categorieTab, setCategorieTab] = useState([]);
28 |
29 | const mois = [
30 | { label: 'janvier', value: '0' },
31 | { label: 'fevrier', value: '1' },
32 | { label: 'mars', value: '2' },
33 | { label: 'avril', value: '3' },
34 | { label: 'mai', value: '4' },
35 | { label: 'juin', value: '5' },
36 | { label: 'juillet', value: '6' },
37 | { label: 'aout', value: '7' },
38 | { label: 'septembre', value: '8' },
39 | { label: 'octobre', value: '9' },
40 | { label: 'novembre', value: '10' },
41 | { label: 'decembre', value: '11' },
42 | ];
43 |
44 | // Focntion qui genere la liste des années depuis 2010
45 | function genererTableauAnnee() {
46 | let tableau = [];
47 | for (let i = 2025; i > 2010; i--) {
48 | tableau.push({ label: i.toString(), value: i });
49 | }
50 | return tableau;
51 | }
52 |
53 | // Fonction qui récupère les labels depuis firebase
54 | async function getLabelsFromFirebase() {
55 | try {
56 | const reference = ref(database, "categories");
57 | const snapshot = await get(reference);
58 | if (snapshot.exists()) {
59 | console.log("\n\ntest labels : " + Object.entries(snapshot.val()).map(([key, value]) => ({
60 | label: value.icon,
61 | value: value.nomLabel
62 | })));
63 | return Object.entries(snapshot.val()).map(([key, value]) => ({
64 | label: value.icon,
65 | value: value.nomLabel
66 | }));
67 | } else {
68 | console.log("Aucune donnée disponible pour ce chemin.");
69 | return [];
70 | }
71 | } catch (error) {
72 | console.error("Erreur lors de la récupération des données :", error);
73 | return [];
74 | }
75 | }
76 |
77 | // Garantie que les labels sont bien à jour au chargement du composant
78 | useEffect(() => {
79 | let isMounted = true;
80 |
81 | async function fetchData() {
82 | const data = await getLabelsFromFirebase();
83 | if (isMounted) {
84 | setCategorieTab(data);
85 | }
86 | }
87 |
88 | fetchData();
89 |
90 | return () => {
91 | isMounted = false;
92 | };
93 | }, [visibiliteModalEntreeArgent]);
94 |
95 | // Reset les variables locales
96 | function resetLabels() {
97 | setLabelEntreArgent("");
98 | setPrixTransaction("");
99 | setCategorie("");
100 | setJourDate("");
101 | setMoisDate(null);
102 | setAnneeDate(null);
103 | }
104 |
105 | // Fonction qui vérifie qu'une date est bien une date valide
106 | function checkDateExiste(jour, mois, annee) {
107 | if (mois < 0 || mois > 11) return false;
108 | console.log("jour : " + jour + "mois : " + mois + "année : " + annee);
109 | let estUneDate = true;
110 | const estBissextile = (annee % 4 === 0 && annee % 100 !== 0) || (annee % 400 === 0);
111 | const joursFevrier = estBissextile ? 29 : 28;
112 | const mois31 = [0, 2, 4, 6, 7, 9, 11];
113 | const mois30 = [3, 5, 8, 10];
114 | mois = Number(mois);
115 | if (mois31.includes(mois)) {
116 | console.log("mois 31");
117 | if (jour < 1 || jour > 31) estUneDate = false;
118 | } else if (mois30.includes(mois)) {
119 | console.log("mois 30");
120 | if (jour < 1 || jour > 30) estUneDate = false;
121 | } else if (mois === 1) {
122 | console.log("mois fevrier");
123 | if (jour < 1 || jour > joursFevrier) estUneDate = false;
124 | } else {
125 | estUneDate = false;
126 | }
127 | console.log("date ? " + estUneDate);
128 | return estUneDate;
129 | }
130 |
131 | // Fonction qui vérifie que toutes les valeurs entrées par l'utilisateurs sont valide puis qui met à jour l'entrée d'argent dans firebase
132 | // puis les variables locales. La fonction met aussi à jour le budget total et le budget mensuel
133 | async function validationEntreeArgent() {
134 | const budgetTotalTemporaire = (await calculerBudgetTotal()) || 0;
135 | const budgetMensuelTemp = (await calculerBudgetMensuel()) || 0;
136 | // Vérifications des entrées de l'utilisateur
137 | if (labelEntreArgent === "" || prixTransaction === "" || categorie === "" || jourDate === "" || moisDate === null || anneeDate === null || checkDateExiste(jourDate, moisDate, anneeDate) === false) {
138 | if (checkDateExiste(jourDate, moisDate, anneeDate) === false) {
139 | setErreur("Oups, ta date semble un peu bizarre. Vérifie-la pour moi !");
140 | } else {
141 | setErreur("Hey, tu as oublié de remplir tous les champs ! Allez, un petit effort");
142 | }
143 | resetLabels();
144 | setVisibiliteModalEntreeArgent(true);
145 | return;
146 | } else {
147 | // Mise à jour de firebase
148 | const nouvelID = Date.now();
149 | const transactionsRef = ref(database, "transactions/" + nouvelID);
150 | const dateTransaction = new Date(anneeDate, moisDate, Number(jourDate));
151 | console.log(dateTransaction);
152 | console.log(dateTransaction.toDateString());
153 | set(transactionsRef, {
154 | label: labelEntreArgent,
155 | date: dateTransaction.toISOString(),
156 | prix: prixTransaction,
157 | categorie: categorie
158 | }).then(() => console.log("Entrée d'argent dans firebase")).catch((error) => console.error("Error écriture entree argent firebase:", error));
159 | // Mise à jour du budget total
160 | setBudgetTotal(budgetTotalTemporaire + Number(prixTransaction));
161 | pushBudgeTotal(budgetTotalTemporaire + Number(prixTransaction));
162 | // Mise à jour du buget mensuel
163 | const moisEnCours = new Date().getMonth();
164 | const anneeEnCours = new Date().getFullYear();
165 | if (Number(moisEnCours) === Number(moisDate) && Number(anneeDate) === Number(anneeEnCours)) {
166 | setBudgetMensuel(budgetMensuelTemp + Number(prixTransaction));
167 | pushBudgetMensuel(budgetMensuelTemp + Number(prixTransaction));
168 | }
169 | resetLabels();
170 | setVisibiliteModalEntreeArgent(false);
171 | }
172 | }
173 |
174 | // Composants pour le dropdown
175 | const renderItem = (item) => {
176 | return (
177 |
178 | {item.label}
179 |
180 | );
181 | };
182 |
183 | return (
184 | setVisibiliteModalEntreeArgent(false)}
189 | >
190 | {/*
191 | setVisibiliteNotification(false)}
196 | >
197 |
198 |
199 |
200 |
201 | Erreur :
202 |
203 | setVisibiliteNotification(false)}>
204 |
205 |
206 |
207 | {erreur}
208 |
209 |
210 |
211 | */}
212 |
213 |
214 | Ajouter une entrée d'argent
215 | {/*Input pour le nom de l'entrée d'argent*/}
216 |
222 |
223 | {/*Input pour le montant de la transaction*/}
224 |
231 | {/*
232 | setCategorie(item.value)}
243 | renderItem={renderItem}
244 | containerStyle={styles.dropdownContainer}
245 | renderRightIcon={() => null}
246 | />
247 | */}
248 | {/*Menu déroulant pour choisir une catégorie associée à la transaction*/}
249 | setCategorie(item.value)}
260 | renderItem={renderItem}
261 | containerStyle={styles.dropdownContainer}
262 | renderRightIcon={() => null}
263 | />
264 |
265 | {/* Container avec tous les input pour générer la date*/}
266 |
267 | {/*Input du jour de la date*/}
268 |
275 | {/*Menu déroulant pour choisir le mois de la date de la transaction*/}
276 | setMoisDate(item.value)}
287 | renderItem={renderItem}
288 | containerStyle={styles.dropdownContainer}
289 | renderRightIcon={() => null}
290 | />
291 | {/*Menu déroulant pour choisir l'année de la date de la transaction*/}
292 | setAnneeDate(item.value)}
303 | renderItem={renderItem}
304 | containerStyle={[styles.dropdownContainer, { width: 80 }]}
305 | renderRightIcon={() => null}
306 | />
307 |
308 | {/*Bouton pour annuler*/}
309 | {
310 | setVisibiliteModalEntreeArgent(false);
311 | resetLabels();
312 | }}>
313 | Fermer
314 |
315 | {/*Bouton pour valider l'entrée d'argent*/}
316 | validationEntreeArgent()}>
317 | Valider
318 |
319 |
320 |
321 |
322 | );
323 | }
324 |
325 | const styles = StyleSheet.create({
326 | modalOverlay: {
327 | position: "absolute",
328 | top: 0,
329 | left: 0,
330 | right: 0,
331 | bottom: 0,
332 | backgroundColor: "rgba(0, 0, 0, 0.5)", // Couche semi-transparente
333 | justifyContent: "center",
334 | alignItems: "center",
335 | },
336 | containerModal: {
337 | width: "80%",
338 | padding: 20,
339 | backgroundColor: couleurs.white,
340 | borderRadius: 10,
341 | alignItems: "center",
342 | elevation: 5,
343 | },
344 | modalTitle: {
345 | fontSize: 28,
346 | fontFamily: "HelveticaBold",
347 | marginBottom: 20,
348 | },
349 | input: {
350 | width: "100%",
351 | borderWidth: 1,
352 | borderColor: couleurs.grey,
353 | color: couleurs.black,
354 | borderRadius: 5,
355 | padding: 10,
356 | marginBottom: 15,
357 | },
358 | inputJour: {
359 | width: 70,
360 | borderWidth: 1,
361 | borderColor: couleurs.grey,
362 | borderRadius: 5,
363 | padding: 10,
364 | marginBottom: 15,
365 | },
366 | button: {
367 | marginTop: 5,
368 | padding: 10,
369 | borderRadius: 5,
370 | width: "100%",
371 | alignItems: "center",
372 | },
373 | buttonText: {
374 | color: "white",
375 | fontFamily: "HelveticaBold"
376 | },
377 | // Dropdown
378 | dropdown: { // dropdown replié
379 | height: 30,
380 | width: 120,
381 | paddingHorizontal: 10,
382 | borderWidth: 1,
383 | borderColor: couleurs.grey,
384 | borderRadius: 5,
385 | padding: 10,
386 | marginBottom: 15,
387 | },
388 | label: {
389 | position: 'absolute',
390 | backgroundColor: couleurs.grey,
391 | fontFamily: "HelveticaBold",
392 | left: 22,
393 | top: 8,
394 | zIndex: 999,
395 | paddingHorizontal: 10,
396 | fontSize: 14,
397 | borderRadius: 100
398 | },
399 | placeholderStyle: {
400 | fontSize: 14,
401 | color: "#CBCBCC",
402 | },
403 | selectedTextStyle: {
404 | fontSize: 14,
405 | color: couleurs.black,
406 | },
407 | iconStyle: {
408 | width: 20,
409 | height: 20,
410 | },
411 | item: {
412 | padding: 10,
413 | },
414 | itemText: {
415 | fontSize: 14,
416 | fontFamily: 'HelveticaRegular',
417 | color: couleurs.black,
418 | },
419 | dropdownContainer: {
420 | width: 100,
421 | borderRadius: 5
422 | },
423 | dropdownContainerAnnee: {
424 | width: 80,
425 | borderRadius: 5
426 | },
427 | dropdownContainerCategorie: {
428 | width: 100,
429 | borderRadius: 5
430 | },
431 | // Containers
432 | containerInputDate: {
433 | display: "flex",
434 | flexDirection: "row",
435 | width: "100%",
436 | justifyContent: "space-between"
437 | },
438 | notificationContainer: {
439 | width: "100%",
440 | height: 130,
441 | display: "flex",
442 | justifyContent: "flex-end",
443 | alignItems: "center"
444 | },
445 | notification: {
446 | width: "80%",
447 | height: 70,
448 | backgroundColor: couleurs.darkRed,
449 | borderRadius: 5,
450 | display: "flex",
451 | justifyContent: "center",
452 | padding: 10
453 | },
454 | containerCroisTexte: {
455 | display: "flex",
456 | flexDirection: "row",
457 | justifyContent: "space-between",
458 | alignItems: "center"
459 | },
460 | // Texte
461 | notificationTitre: {
462 | fontFamily: "HelveticaBold",
463 | fontSize: 20,
464 | color: couleurs.lightRed
465 | },
466 | notificationTexte: {
467 | fontFamily: "HelveticaRegular",
468 | fontSize: 14,
469 | color: couleurs.lightRed
470 | }
471 | });
--------------------------------------------------------------------------------