Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
355
lib/core/validation/validators.dart
Normal file
355
lib/core/validation/validators.dart
Normal file
@@ -0,0 +1,355 @@
|
||||
/// Core validation utilities for form fields
|
||||
library validators;
|
||||
|
||||
/// Validator function type
|
||||
typedef FieldValidator = String? Function(String?)?;
|
||||
|
||||
/// Compose multiple validators
|
||||
FieldValidator composeValidators(List<FieldValidator> validators) {
|
||||
return (String? value) {
|
||||
for (final validator in validators) {
|
||||
final result = validator?.call(value);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Common validators
|
||||
class Validators {
|
||||
Validators._(); // Prevent instantiation
|
||||
|
||||
/// Required field validator
|
||||
static FieldValidator required({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return message ?? 'Ce champ est requis';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Minimum length validator
|
||||
static FieldValidator minLength(int length, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value != null && value.trim().length < length) {
|
||||
return message ?? 'Minimum $length caractères requis';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Maximum length validator
|
||||
static FieldValidator maxLength(int length, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value != null && value.length > length) {
|
||||
return message ?? 'Maximum $length caractères autorisés';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Email validator
|
||||
static FieldValidator email({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null; // Use required() separately if needed
|
||||
}
|
||||
|
||||
final emailRegex = RegExp(
|
||||
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
|
||||
);
|
||||
|
||||
if (!emailRegex.hasMatch(value.trim())) {
|
||||
return message ?? 'Adresse email invalide';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Numeric validator
|
||||
static FieldValidator numeric({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null; // Use required() separately if needed
|
||||
}
|
||||
|
||||
if (double.tryParse(value.trim()) == null) {
|
||||
return message ?? 'Veuillez entrer un nombre valide';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Minimum value validator (for numeric fields)
|
||||
static FieldValidator minValue(double min, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final numValue = double.tryParse(value.trim());
|
||||
if (numValue == null) {
|
||||
return 'Nombre invalide';
|
||||
}
|
||||
|
||||
if (numValue < min) {
|
||||
return message ?? 'La valeur doit être au moins $min';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Maximum value validator (for numeric fields)
|
||||
static FieldValidator maxValue(double max, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final numValue = double.tryParse(value.trim());
|
||||
if (numValue == null) {
|
||||
return 'Nombre invalide';
|
||||
}
|
||||
|
||||
if (numValue > max) {
|
||||
return message ?? 'La valeur doit être au maximum $max';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Range validator (for numeric fields)
|
||||
static FieldValidator range(double min, double max, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final numValue = double.tryParse(value.trim());
|
||||
if (numValue == null) {
|
||||
return 'Nombre invalide';
|
||||
}
|
||||
|
||||
if (numValue < min || numValue > max) {
|
||||
return message ?? 'La valeur doit être entre $min et $max';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Phone number validator (simple version)
|
||||
static FieldValidator phone({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Allow digits, spaces, +, -, ()
|
||||
final phoneRegex = RegExp(r'^[\d\s\+\-\(\)]+$');
|
||||
if (!phoneRegex.hasMatch(value.trim())) {
|
||||
return message ?? 'Numéro de téléphone invalide';
|
||||
}
|
||||
|
||||
// Check minimum length (at least 8 digits)
|
||||
final digitsOnly = value.replaceAll(RegExp(r'[^\d]'), '');
|
||||
if (digitsOnly.length < 8) {
|
||||
return message ?? 'Numéro de téléphone trop court';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// URL validator
|
||||
static FieldValidator url({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(value.trim());
|
||||
if (!uri.hasScheme || !uri.hasAuthority) {
|
||||
return message ?? 'URL invalide';
|
||||
}
|
||||
} catch (e) {
|
||||
return message ?? 'URL invalide';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Pattern/Regex validator
|
||||
static FieldValidator pattern(RegExp regex, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!regex.hasMatch(value.trim())) {
|
||||
return message ?? 'Format invalide';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Match validator (confirm password, etc.)
|
||||
static FieldValidator match(String otherValue, {String? message}) {
|
||||
return (String? value) {
|
||||
if (value != otherValue) {
|
||||
return message ?? 'Les valeurs ne correspondent pas';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Custom validator
|
||||
static FieldValidator custom(bool Function(String?) test, {String? message}) {
|
||||
return (String? value) {
|
||||
if (!test(value)) {
|
||||
return message ?? 'Valeur invalide';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Alphanumeric validator
|
||||
static FieldValidator alphanumeric({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final alphanumericRegex = RegExp(r'^[a-zA-Z0-9]+$');
|
||||
if (!alphanumericRegex.hasMatch(value.trim())) {
|
||||
return message ?? 'Seuls les caractères alphanumériques sont autorisés';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// No whitespace validator
|
||||
static FieldValidator noWhitespace({String? message}) {
|
||||
return (String? value) {
|
||||
if (value == null) return null;
|
||||
|
||||
if (value.contains(' ')) {
|
||||
return message ?? 'Les espaces ne sont pas autorisés';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Finance-specific validators
|
||||
class FinanceValidators {
|
||||
FinanceValidators._();
|
||||
|
||||
/// Amount validator (positive number with max 2 decimals)
|
||||
static FieldValidator amount({
|
||||
double? min,
|
||||
double? max,
|
||||
String? message,
|
||||
}) {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if numeric
|
||||
final numValue = double.tryParse(value.trim());
|
||||
if (numValue == null) {
|
||||
return 'Montant invalide';
|
||||
}
|
||||
|
||||
// Check if positive
|
||||
if (numValue <= 0) {
|
||||
return 'Le montant doit être positif';
|
||||
}
|
||||
|
||||
// Check min/max
|
||||
if (min != null && numValue < min) {
|
||||
return message ?? 'Le montant minimum est $min';
|
||||
}
|
||||
if (max != null && numValue > max) {
|
||||
return message ?? 'Le montant maximum est $max';
|
||||
}
|
||||
|
||||
// Check max 2 decimals
|
||||
final parts = value.trim().split('.');
|
||||
if (parts.length > 1 && parts[1].length > 2) {
|
||||
return 'Maximum 2 décimales autorisées';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// Budget line name validator
|
||||
static FieldValidator budgetLineName() {
|
||||
return composeValidators([
|
||||
Validators.required(message: 'Le nom de la ligne budgétaire est requis'),
|
||||
Validators.minLength(3, message: 'Minimum 3 caractères'),
|
||||
Validators.maxLength(100, message: 'Maximum 100 caractères'),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Budget description validator
|
||||
static FieldValidator budgetDescription({bool required = false}) {
|
||||
return composeValidators([
|
||||
if (required)
|
||||
Validators.required(message: 'La description est requise'),
|
||||
Validators.maxLength(500, message: 'Maximum 500 caractères'),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Rejection reason validator
|
||||
static FieldValidator rejectionReason() {
|
||||
return composeValidators([
|
||||
Validators.required(message: 'La raison du rejet est requise'),
|
||||
Validators.minLength(10, message: 'Veuillez fournir une raison plus détaillée (min 10 caractères)'),
|
||||
Validators.maxLength(500, message: 'Maximum 500 caractères'),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Approval comment validator (optional but with constraints if provided)
|
||||
static FieldValidator approvalComment() {
|
||||
return composeValidators([
|
||||
Validators.maxLength(500, message: 'Maximum 500 caractères'),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Budget name validator
|
||||
static FieldValidator budgetName() {
|
||||
return composeValidators([
|
||||
Validators.required(message: 'Le nom du budget est requis'),
|
||||
Validators.minLength(3, message: 'Minimum 3 caractères'),
|
||||
Validators.maxLength(200, message: 'Maximum 200 caractères'),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Fiscal year validator
|
||||
static FieldValidator fiscalYear() {
|
||||
return (String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'L\'année est requise';
|
||||
}
|
||||
|
||||
final year = int.tryParse(value.trim());
|
||||
if (year == null) {
|
||||
return 'Année invalide';
|
||||
}
|
||||
|
||||
final currentYear = DateTime.now().year;
|
||||
if (year < currentYear - 5 || year > currentYear + 10) {
|
||||
return 'L\'année doit être entre ${currentYear - 5} et ${currentYear + 10}';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user