├── 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 | 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 | }); --------------------------------------------------------------------------------