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:
2025-10-23 14:39:15 +02:00
parent 8bde3d4f21
commit 5eb5c7a2f8
4 changed files with 798 additions and 0 deletions

161
src/utils/constants.ts Normal file
View 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
View 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
View 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 !');
};