Add utilities, constants, sample data and quick start guide
- Add helper functions (formatCurrency, formatDate, validation, etc.) - Add constants (colors, spacing, error messages, etc.) - Add sample data generator for testing - Add QUICKSTART.md for quick setup - Update app.json with proper configuration
This commit is contained in:
149
QUICKSTART.md
Normal file
149
QUICKSTART.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# 🚀 Démarrage rapide - WalletTracker
|
||||||
|
|
||||||
|
Guide pour lancer l'application en 5 minutes.
|
||||||
|
|
||||||
|
## Étape 1 : Vérifier les prérequis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier Node.js (v16+)
|
||||||
|
node --version
|
||||||
|
|
||||||
|
# Vérifier npm
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Étape 2 : Installer les dépendances
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd WalletTracker
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Étape 3 : Configurer Firebase
|
||||||
|
|
||||||
|
### Option A : Configuration rapide (pour tester)
|
||||||
|
|
||||||
|
1. Allez sur https://console.firebase.google.com/
|
||||||
|
2. Créez un nouveau projet "WalletTracker"
|
||||||
|
3. Ajoutez une application Web
|
||||||
|
4. Copiez les identifiants dans `src/config/firebase.ts`
|
||||||
|
|
||||||
|
### Option B : Configuration complète
|
||||||
|
|
||||||
|
Suivez le guide détaillé dans `FIREBASE_SETUP.md`
|
||||||
|
|
||||||
|
## Étape 4 : Lancer l'application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous verrez un QR code s'afficher dans le terminal.
|
||||||
|
|
||||||
|
## Étape 5 : Tester sur votre téléphone
|
||||||
|
|
||||||
|
### Sur iOS ou Android :
|
||||||
|
|
||||||
|
1. Téléchargez **Expo Go** depuis l'App Store ou Google Play
|
||||||
|
2. Ouvrez Expo Go
|
||||||
|
3. Scannez le QR code affiché dans le terminal
|
||||||
|
4. L'application se chargera automatiquement
|
||||||
|
|
||||||
|
### Sur émulateur :
|
||||||
|
|
||||||
|
**iOS (Mac uniquement)** :
|
||||||
|
```bash
|
||||||
|
npm run ios
|
||||||
|
```
|
||||||
|
|
||||||
|
**Android** :
|
||||||
|
```bash
|
||||||
|
npm run android
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎉 C'est prêt !
|
||||||
|
|
||||||
|
Vous devriez voir l'écran de connexion de WalletTracker.
|
||||||
|
|
||||||
|
### Première utilisation :
|
||||||
|
|
||||||
|
1. Cliquez sur **"Créer un compte"**
|
||||||
|
2. Remplissez le formulaire :
|
||||||
|
- Nom : Votre nom
|
||||||
|
- Email : votre@email.com
|
||||||
|
- Mot de passe : minimum 6 caractères
|
||||||
|
3. Cliquez sur **"Créer mon compte"**
|
||||||
|
4. Vous êtes redirigé vers le Dashboard !
|
||||||
|
|
||||||
|
### Ajouter votre première transaction :
|
||||||
|
|
||||||
|
1. Cliquez sur le bouton **"Dépense"** ou **"Revenu"**
|
||||||
|
2. Entrez le montant
|
||||||
|
3. Sélectionnez une catégorie
|
||||||
|
4. Ajoutez une note (optionnel)
|
||||||
|
5. Cliquez sur **"Ajouter la transaction"**
|
||||||
|
|
||||||
|
## 📱 Commandes utiles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lancer l'application
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# Lancer sur iOS
|
||||||
|
npm run ios
|
||||||
|
|
||||||
|
# Lancer sur Android
|
||||||
|
npm run android
|
||||||
|
|
||||||
|
# Lancer sur le web
|
||||||
|
npm run web
|
||||||
|
|
||||||
|
# Nettoyer le cache
|
||||||
|
npm start -- --clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Problèmes courants
|
||||||
|
|
||||||
|
### L'application ne se lance pas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nettoyer et réinstaller
|
||||||
|
rm -rf node_modules
|
||||||
|
npm install
|
||||||
|
npm start -- --clear
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreur Firebase
|
||||||
|
|
||||||
|
Vérifiez que vous avez bien :
|
||||||
|
- Copié les identifiants Firebase dans `src/config/firebase.ts`
|
||||||
|
- Activé Authentication (Email/Password) dans Firebase Console
|
||||||
|
- Créé la base de données Firestore
|
||||||
|
|
||||||
|
### QR code ne fonctionne pas
|
||||||
|
|
||||||
|
- Assurez-vous que votre téléphone et ordinateur sont sur le même réseau Wi-Fi
|
||||||
|
- Essayez de scanner avec l'appareil photo puis ouvrir avec Expo Go
|
||||||
|
- Utilisez le mode tunnel : `npm start -- --tunnel`
|
||||||
|
|
||||||
|
## 📚 Prochaines étapes
|
||||||
|
|
||||||
|
- Lisez le `README.md` pour comprendre l'architecture
|
||||||
|
- Consultez `FIREBASE_SETUP.md` pour la configuration complète
|
||||||
|
- Utilisez `TESTING.md` pour tester toutes les fonctionnalités
|
||||||
|
|
||||||
|
## 💡 Conseils
|
||||||
|
|
||||||
|
- **Développement** : Utilisez `npm start` et Expo Go pour un rechargement rapide
|
||||||
|
- **Production** : Utilisez EAS Build pour créer des binaires iOS/Android
|
||||||
|
- **Débogage** : Secouez votre téléphone pour ouvrir le menu de développement
|
||||||
|
|
||||||
|
## 🆘 Besoin d'aide ?
|
||||||
|
|
||||||
|
- Documentation Expo : https://docs.expo.dev/
|
||||||
|
- Documentation Firebase : https://firebase.google.com/docs
|
||||||
|
- React Native : https://reactnative.dev/docs/getting-started
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bon développement ! 💪**
|
||||||
161
src/utils/constants.ts
Normal file
161
src/utils/constants.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* Constantes utilisées dans l'application
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Couleurs principales
|
||||||
|
export const COLORS = {
|
||||||
|
primary: '#4A90E2',
|
||||||
|
secondary: '#6C757D',
|
||||||
|
success: '#52C41A',
|
||||||
|
danger: '#FF6B6B',
|
||||||
|
warning: '#FFA07A',
|
||||||
|
info: '#13C2C2',
|
||||||
|
light: '#F8F9FA',
|
||||||
|
dark: '#333',
|
||||||
|
white: '#FFF',
|
||||||
|
gray: '#999',
|
||||||
|
border: '#E0E0E0'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Couleurs des catégories par défaut
|
||||||
|
export const CATEGORY_COLORS = {
|
||||||
|
// Dépenses
|
||||||
|
courses: '#FF6B6B',
|
||||||
|
logement: '#4ECDC4',
|
||||||
|
transport: '#45B7D1',
|
||||||
|
loisirs: '#FFA07A',
|
||||||
|
restaurant: '#98D8C8',
|
||||||
|
sante: '#F7DC6F',
|
||||||
|
vetements: '#BB8FCE',
|
||||||
|
education: '#85C1E2',
|
||||||
|
abonnements: '#F8B739',
|
||||||
|
autre: '#95A5A6',
|
||||||
|
|
||||||
|
// Revenus
|
||||||
|
salaire: '#52C41A',
|
||||||
|
freelance: '#13C2C2',
|
||||||
|
investissement: '#1890FF',
|
||||||
|
cadeau: '#EB2F96',
|
||||||
|
autreRevenu: '#52C41A'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tailles de police
|
||||||
|
export const FONT_SIZES = {
|
||||||
|
xs: 11,
|
||||||
|
sm: 12,
|
||||||
|
md: 14,
|
||||||
|
lg: 16,
|
||||||
|
xl: 18,
|
||||||
|
xxl: 24,
|
||||||
|
xxxl: 32
|
||||||
|
};
|
||||||
|
|
||||||
|
// Espacements
|
||||||
|
export const SPACING = {
|
||||||
|
xs: 4,
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
lg: 16,
|
||||||
|
xl: 24,
|
||||||
|
xxl: 32
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rayons de bordure
|
||||||
|
export const BORDER_RADIUS = {
|
||||||
|
sm: 8,
|
||||||
|
md: 12,
|
||||||
|
lg: 16,
|
||||||
|
xl: 20,
|
||||||
|
round: 999
|
||||||
|
};
|
||||||
|
|
||||||
|
// Formats de date
|
||||||
|
export const DATE_FORMATS = {
|
||||||
|
short: 'DD/MM/YYYY',
|
||||||
|
medium: 'DD MMM YYYY',
|
||||||
|
long: 'DD MMMM YYYY',
|
||||||
|
full: 'dddd DD MMMM YYYY'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Messages d'erreur
|
||||||
|
export const ERROR_MESSAGES = {
|
||||||
|
network: 'Erreur de connexion. Vérifiez votre connexion Internet.',
|
||||||
|
auth: {
|
||||||
|
invalidEmail: 'Adresse email invalide',
|
||||||
|
weakPassword: 'Le mot de passe doit contenir au moins 6 caractères',
|
||||||
|
emailInUse: 'Cette adresse email est déjà utilisée',
|
||||||
|
userNotFound: 'Aucun compte trouvé avec cette adresse email',
|
||||||
|
wrongPassword: 'Mot de passe incorrect',
|
||||||
|
tooManyRequests: 'Trop de tentatives. Veuillez réessayer plus tard.'
|
||||||
|
},
|
||||||
|
transaction: {
|
||||||
|
invalidAmount: 'Montant invalide',
|
||||||
|
missingCategory: 'Veuillez sélectionner une catégorie',
|
||||||
|
addFailed: 'Impossible d\'ajouter la transaction',
|
||||||
|
updateFailed: 'Impossible de mettre à jour la transaction',
|
||||||
|
deleteFailed: 'Impossible de supprimer la transaction'
|
||||||
|
},
|
||||||
|
subscription: {
|
||||||
|
invalidName: 'Nom invalide',
|
||||||
|
invalidAmount: 'Montant invalide',
|
||||||
|
invalidDate: 'Date invalide',
|
||||||
|
addFailed: 'Impossible d\'ajouter l\'abonnement',
|
||||||
|
updateFailed: 'Impossible de mettre à jour l\'abonnement',
|
||||||
|
deleteFailed: 'Impossible de supprimer l\'abonnement'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Messages de succès
|
||||||
|
export const SUCCESS_MESSAGES = {
|
||||||
|
auth: {
|
||||||
|
signupSuccess: 'Compte créé avec succès',
|
||||||
|
loginSuccess: 'Connexion réussie',
|
||||||
|
logoutSuccess: 'Déconnexion réussie'
|
||||||
|
},
|
||||||
|
transaction: {
|
||||||
|
addSuccess: 'Transaction ajoutée avec succès',
|
||||||
|
updateSuccess: 'Transaction mise à jour',
|
||||||
|
deleteSuccess: 'Transaction supprimée'
|
||||||
|
},
|
||||||
|
subscription: {
|
||||||
|
addSuccess: 'Abonnement ajouté avec succès',
|
||||||
|
updateSuccess: 'Abonnement mis à jour',
|
||||||
|
deleteSuccess: 'Abonnement supprimé'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Limites
|
||||||
|
export const LIMITS = {
|
||||||
|
maxTransactionsPerPage: 50,
|
||||||
|
maxCategoriesPerUser: 50,
|
||||||
|
maxSubscriptionsPerUser: 100,
|
||||||
|
maxNoteLength: 500,
|
||||||
|
minPasswordLength: 6,
|
||||||
|
maxImageSize: 5 * 1024 * 1024 // 5 MB
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fréquences d'abonnement
|
||||||
|
export const SUBSCRIPTION_FREQUENCIES = [
|
||||||
|
{ value: 'daily', label: 'Quotidien' },
|
||||||
|
{ value: 'weekly', label: 'Hebdomadaire' },
|
||||||
|
{ value: 'monthly', label: 'Mensuel' },
|
||||||
|
{ value: 'yearly', label: 'Annuel' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Jours de rappel par défaut
|
||||||
|
export const DEFAULT_REMINDER_DAYS = 3;
|
||||||
|
|
||||||
|
// Clés de stockage AsyncStorage
|
||||||
|
export const STORAGE_KEYS = {
|
||||||
|
user: '@wallettracker_user',
|
||||||
|
theme: '@wallettracker_theme',
|
||||||
|
language: '@wallettracker_language'
|
||||||
|
};
|
||||||
|
|
||||||
|
// URLs utiles
|
||||||
|
export const URLS = {
|
||||||
|
privacyPolicy: 'https://example.com/privacy',
|
||||||
|
termsOfService: 'https://example.com/terms',
|
||||||
|
support: 'https://example.com/support',
|
||||||
|
github: 'https://github.com/yourusername/wallettracker'
|
||||||
|
};
|
||||||
251
src/utils/helpers.ts
Normal file
251
src/utils/helpers.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/**
|
||||||
|
* Fonctions utilitaires pour l'application
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate un montant en euros
|
||||||
|
*/
|
||||||
|
export const formatCurrency = (amount: number): string => {
|
||||||
|
return new Intl.NumberFormat('fr-FR', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'EUR'
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate une date
|
||||||
|
*/
|
||||||
|
export const formatDate = (
|
||||||
|
date: Date,
|
||||||
|
format: 'short' | 'medium' | 'long' = 'medium'
|
||||||
|
): string => {
|
||||||
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
|
short: { day: '2-digit', month: '2-digit', year: 'numeric' },
|
||||||
|
medium: { day: '2-digit', month: 'short', year: 'numeric' },
|
||||||
|
long: { day: '2-digit', month: 'long', year: 'numeric' }
|
||||||
|
}[format];
|
||||||
|
|
||||||
|
return new Intl.DateTimeFormat('fr-FR', options).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formate une date relative (il y a X jours)
|
||||||
|
*/
|
||||||
|
export const formatRelativeDate = (date: Date): string => {
|
||||||
|
const now = new Date();
|
||||||
|
const diffInMs = now.getTime() - date.getTime();
|
||||||
|
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffInDays === 0) return "Aujourd'hui";
|
||||||
|
if (diffInDays === 1) return 'Hier';
|
||||||
|
if (diffInDays < 7) return `Il y a ${diffInDays} jours`;
|
||||||
|
if (diffInDays < 30) {
|
||||||
|
const weeks = Math.floor(diffInDays / 7);
|
||||||
|
return `Il y a ${weeks} semaine${weeks > 1 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
if (diffInDays < 365) {
|
||||||
|
const months = Math.floor(diffInDays / 30);
|
||||||
|
return `Il y a ${months} mois`;
|
||||||
|
}
|
||||||
|
const years = Math.floor(diffInDays / 365);
|
||||||
|
return `Il y a ${years} an${years > 1 ? 's' : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient le nom du mois en français
|
||||||
|
*/
|
||||||
|
export const getMonthName = (date: Date, format: 'long' | 'short' = 'long'): string => {
|
||||||
|
return new Intl.DateTimeFormat('fr-FR', {
|
||||||
|
month: format,
|
||||||
|
year: 'numeric'
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le nombre de jours entre deux dates
|
||||||
|
*/
|
||||||
|
export const daysBetween = (date1: Date, date2: Date): number => {
|
||||||
|
const diffInMs = Math.abs(date2.getTime() - date1.getTime());
|
||||||
|
return Math.ceil(diffInMs / (1000 * 60 * 60 * 24));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si une date est dans le mois en cours
|
||||||
|
*/
|
||||||
|
export const isCurrentMonth = (date: Date): boolean => {
|
||||||
|
const now = new Date();
|
||||||
|
return (
|
||||||
|
date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient le premier jour du mois
|
||||||
|
*/
|
||||||
|
export const getFirstDayOfMonth = (date: Date): Date => {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtient le dernier jour du mois
|
||||||
|
*/
|
||||||
|
export const getLastDayOfMonth = (date: Date): Date => {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valide une adresse email
|
||||||
|
*/
|
||||||
|
export const isValidEmail = (email: string): boolean => {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valide un montant
|
||||||
|
*/
|
||||||
|
export const isValidAmount = (amount: string | number): boolean => {
|
||||||
|
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
|
||||||
|
return !isNaN(numAmount) && numAmount > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tronque un texte
|
||||||
|
*/
|
||||||
|
export const truncate = (text: string, maxLength: number): string => {
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
return text.substring(0, maxLength) + '...';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalise la première lettre
|
||||||
|
*/
|
||||||
|
export const capitalize = (text: string): string => {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère une couleur aléatoire
|
||||||
|
*/
|
||||||
|
export const generateRandomColor = (): string => {
|
||||||
|
const colors = [
|
||||||
|
'#FF6B6B',
|
||||||
|
'#4ECDC4',
|
||||||
|
'#45B7D1',
|
||||||
|
'#FFA07A',
|
||||||
|
'#98D8C8',
|
||||||
|
'#F7DC6F',
|
||||||
|
'#BB8FCE',
|
||||||
|
'#85C1E2',
|
||||||
|
'#F8B739',
|
||||||
|
'#52C41A',
|
||||||
|
'#13C2C2',
|
||||||
|
'#1890FF',
|
||||||
|
'#EB2F96'
|
||||||
|
];
|
||||||
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le pourcentage
|
||||||
|
*/
|
||||||
|
export const calculatePercentage = (value: number, total: number): number => {
|
||||||
|
if (total === 0) return 0;
|
||||||
|
return (value / total) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arrondit un nombre à N décimales
|
||||||
|
*/
|
||||||
|
export const roundTo = (num: number, decimals: number = 2): number => {
|
||||||
|
return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groupe les transactions par date
|
||||||
|
*/
|
||||||
|
export const groupByDate = <T extends { date: Date }>(items: T[]): Map<string, T[]> => {
|
||||||
|
const grouped = new Map<string, T[]>();
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const dateKey = formatDate(item.date, 'short');
|
||||||
|
const existing = grouped.get(dateKey) || [];
|
||||||
|
grouped.set(dateKey, [...existing, item]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groupe les transactions par mois
|
||||||
|
*/
|
||||||
|
export const groupByMonth = <T extends { date: Date }>(items: T[]): Map<string, T[]> => {
|
||||||
|
const grouped = new Map<string, T[]>();
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const monthKey = getMonthName(item.date);
|
||||||
|
const existing = grouped.get(monthKey) || [];
|
||||||
|
grouped.set(monthKey, [...existing, item]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trie les transactions par date (plus récentes en premier)
|
||||||
|
*/
|
||||||
|
export const sortByDateDesc = <T extends { date: Date }>(items: T[]): T[] => {
|
||||||
|
return [...items].sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filtre les transactions par période
|
||||||
|
*/
|
||||||
|
export const filterByDateRange = <T extends { date: Date }>(
|
||||||
|
items: T[],
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date
|
||||||
|
): T[] => {
|
||||||
|
return items.filter((item) => item.date >= startDate && item.date <= endDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la somme des montants
|
||||||
|
*/
|
||||||
|
export const sumAmounts = <T extends { amount: number }>(items: T[]): number => {
|
||||||
|
return items.reduce((sum, item) => sum + item.amount, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attend X millisecondes (pour les animations)
|
||||||
|
*/
|
||||||
|
export const wait = (ms: number): Promise<void> => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce une fonction
|
||||||
|
*/
|
||||||
|
export const debounce = <T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
delay: number
|
||||||
|
): ((...args: Parameters<T>) => void) => {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => func(...args), delay);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si l'objet est vide
|
||||||
|
*/
|
||||||
|
export const isEmpty = (obj: any): boolean => {
|
||||||
|
if (obj === null || obj === undefined) return true;
|
||||||
|
if (typeof obj === 'string') return obj.trim().length === 0;
|
||||||
|
if (Array.isArray(obj)) return obj.length === 0;
|
||||||
|
if (typeof obj === 'object') return Object.keys(obj).length === 0;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
237
src/utils/sampleData.ts
Normal file
237
src/utils/sampleData.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
/**
|
||||||
|
* Données d'exemple pour faciliter les tests et le développement
|
||||||
|
* Ces fonctions peuvent être utilisées pour peupler la base de données avec des données de test
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { transactionService } from '../services/transactionService';
|
||||||
|
import { subscriptionService } from '../services/subscriptionService';
|
||||||
|
import { TransactionType, SubscriptionFrequency } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère des transactions d'exemple pour un utilisateur
|
||||||
|
*/
|
||||||
|
export const generateSampleTransactions = async (userId: string) => {
|
||||||
|
const transactions = [
|
||||||
|
// Revenus
|
||||||
|
{
|
||||||
|
type: 'income' as TransactionType,
|
||||||
|
amount: 2500,
|
||||||
|
category: 'Salaire',
|
||||||
|
date: new Date(2025, 9, 1),
|
||||||
|
note: 'Salaire mensuel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'income' as TransactionType,
|
||||||
|
amount: 500,
|
||||||
|
category: 'Freelance',
|
||||||
|
date: new Date(2025, 9, 15),
|
||||||
|
note: 'Projet web client XYZ'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Courses
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 85.50,
|
||||||
|
category: 'Courses',
|
||||||
|
date: new Date(2025, 9, 5),
|
||||||
|
note: 'Supermarché Carrefour'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 42.30,
|
||||||
|
category: 'Courses',
|
||||||
|
date: new Date(2025, 9, 12),
|
||||||
|
note: 'Marché local'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 67.80,
|
||||||
|
category: 'Courses',
|
||||||
|
date: new Date(2025, 9, 19),
|
||||||
|
note: 'Supermarché Leclerc'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Logement
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 850,
|
||||||
|
category: 'Logement',
|
||||||
|
date: new Date(2025, 9, 1),
|
||||||
|
note: 'Loyer mensuel'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 120,
|
||||||
|
category: 'Logement',
|
||||||
|
date: new Date(2025, 9, 10),
|
||||||
|
note: 'Électricité et gaz'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Transport
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 60,
|
||||||
|
category: 'Transport',
|
||||||
|
date: new Date(2025, 9, 3),
|
||||||
|
note: 'Essence'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 75,
|
||||||
|
category: 'Transport',
|
||||||
|
date: new Date(2025, 9, 8),
|
||||||
|
note: 'Pass Navigo'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Restaurant
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 45,
|
||||||
|
category: 'Restaurant',
|
||||||
|
date: new Date(2025, 9, 6),
|
||||||
|
note: 'Dîner au restaurant italien'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 28,
|
||||||
|
category: 'Restaurant',
|
||||||
|
date: new Date(2025, 9, 13),
|
||||||
|
note: 'Déjeuner avec collègues'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 15,
|
||||||
|
category: 'Restaurant',
|
||||||
|
date: new Date(2025, 9, 20),
|
||||||
|
note: 'Fast food'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Loisirs
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 60,
|
||||||
|
category: 'Loisirs',
|
||||||
|
date: new Date(2025, 9, 7),
|
||||||
|
note: 'Cinéma et pop-corn'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 35,
|
||||||
|
category: 'Loisirs',
|
||||||
|
date: new Date(2025, 9, 14),
|
||||||
|
note: 'Jeu vidéo Steam'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Santé
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 25,
|
||||||
|
category: 'Santé',
|
||||||
|
date: new Date(2025, 9, 9),
|
||||||
|
note: 'Pharmacie - médicaments'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dépenses - Vêtements
|
||||||
|
{
|
||||||
|
type: 'expense' as TransactionType,
|
||||||
|
amount: 89,
|
||||||
|
category: 'Vêtements',
|
||||||
|
date: new Date(2025, 9, 16),
|
||||||
|
note: 'Nouvelle paire de chaussures'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const transaction of transactions) {
|
||||||
|
await transactionService.addTransaction(
|
||||||
|
userId,
|
||||||
|
transaction.type,
|
||||||
|
transaction.amount,
|
||||||
|
transaction.category,
|
||||||
|
transaction.date,
|
||||||
|
transaction.note
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log('✅ Transactions d\'exemple créées avec succès');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur lors de la création des transactions d\'exemple:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère des abonnements d'exemple pour un utilisateur
|
||||||
|
*/
|
||||||
|
export const generateSampleSubscriptions = async (userId: string) => {
|
||||||
|
const subscriptions = [
|
||||||
|
{
|
||||||
|
name: 'Netflix',
|
||||||
|
amount: 15.99,
|
||||||
|
category: 'Abonnements',
|
||||||
|
frequency: 'monthly' as SubscriptionFrequency,
|
||||||
|
dayOfMonth: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spotify',
|
||||||
|
amount: 9.99,
|
||||||
|
category: 'Abonnements',
|
||||||
|
frequency: 'monthly' as SubscriptionFrequency,
|
||||||
|
dayOfMonth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Amazon Prime',
|
||||||
|
amount: 6.99,
|
||||||
|
category: 'Abonnements',
|
||||||
|
frequency: 'monthly' as SubscriptionFrequency,
|
||||||
|
dayOfMonth: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Salle de sport',
|
||||||
|
amount: 35,
|
||||||
|
category: 'Santé',
|
||||||
|
frequency: 'monthly' as SubscriptionFrequency,
|
||||||
|
dayOfMonth: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Assurance téléphone',
|
||||||
|
amount: 120,
|
||||||
|
category: 'Autre',
|
||||||
|
frequency: 'yearly' as SubscriptionFrequency,
|
||||||
|
dayOfMonth: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const subscription of subscriptions) {
|
||||||
|
const now = new Date();
|
||||||
|
const nextPaymentDate = new Date(now.getFullYear(), now.getMonth(), subscription.dayOfMonth);
|
||||||
|
|
||||||
|
// Si la date est déjà passée ce mois-ci, passer au mois suivant
|
||||||
|
if (nextPaymentDate < now) {
|
||||||
|
nextPaymentDate.setMonth(nextPaymentDate.getMonth() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await subscriptionService.addSubscription(
|
||||||
|
userId,
|
||||||
|
subscription.name,
|
||||||
|
subscription.amount,
|
||||||
|
subscription.category,
|
||||||
|
subscription.frequency,
|
||||||
|
nextPaymentDate,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log('✅ Abonnements d\'exemple créés avec succès');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur lors de la création des abonnements d\'exemple:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère toutes les données d'exemple
|
||||||
|
*/
|
||||||
|
export const generateAllSampleData = async (userId: string) => {
|
||||||
|
console.log('🔄 Génération des données d\'exemple...');
|
||||||
|
await generateSampleTransactions(userId);
|
||||||
|
await generateSampleSubscriptions(userId);
|
||||||
|
console.log('✅ Toutes les données d\'exemple ont été générées !');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user