Compare commits
No commits in common. "master" and "feature/FRONT-07" have entirely different histories.
master
...
feature/FR
6
frontend/.gitignore
vendored
6
frontend/.gitignore
vendored
@ -43,9 +43,3 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
|
||||||
# Fichiers générés automatiquement par Flutter pour l'enregistrement des plugins
|
|
||||||
**/GeneratedPluginRegistrant.java
|
|
||||||
**/generated_plugin_registrant.*
|
|
||||||
**/generated_plugins.cmake
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package io.flutter.plugins;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import io.flutter.Log;
|
||||||
|
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated file. Do not edit.
|
||||||
|
* This file is generated by the Flutter tool based on the
|
||||||
|
* plugins that support the Android platform.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public final class GeneratedPluginRegistrant {
|
||||||
|
private static final String TAG = "GeneratedPluginRegistrant";
|
||||||
|
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin flutter_plugin_android_lifecycle, io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new io.flutter.plugins.imagepicker.ImagePickerPlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin image_picker_android, io.flutter.plugins.imagepicker.ImagePickerPlugin", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
flutterEngine.getPlugins().add(new io.flutter.plugins.urllauncher.UrlLauncherPlugin());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error registering plugin url_launcher_android, io.flutter.plugins.urllauncher.UrlLauncherPlugin", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/widgets/admin/DashboardSidebarAdmin.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/Statistique_manage_widget.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/admin_manage_widget.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart';
|
import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart';
|
||||||
import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart';
|
import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart';
|
||||||
import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart';
|
import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart';
|
||||||
@ -27,58 +24,41 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(60.0),
|
preferredSize: const Size.fromHeight(60.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(color: Colors.grey.shade300),
|
bottom: BorderSide(color: Colors.grey.shade300),
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DashboardAppBarAdmin(
|
|
||||||
selectedIndex: selectedIndex,
|
|
||||||
onTabChange: onTabChange,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: DashboardAppBarAdmin(
|
||||||
|
selectedIndex: selectedIndex,
|
||||||
|
onTabChange: onTabChange,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: Column(
|
),
|
||||||
children: [
|
body: Column(
|
||||||
Expanded(
|
children: [
|
||||||
child: Row(
|
Expanded(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: _getBody(),
|
||||||
children: [
|
),
|
||||||
SizedBox(
|
const AppFooter(),
|
||||||
width: 250,
|
],
|
||||||
child: DashboardSidebarAdmin(
|
),
|
||||||
selectedIndex: selectedIndex,
|
);
|
||||||
onTabChange: onTabChange,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: _getBody(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const AppFooter(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _getBody() {
|
Widget _getBody() {
|
||||||
switch (selectedIndex) {
|
switch (selectedIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
return const GestionnaireManagementWidget();
|
return const GestionnaireManagementWidget();
|
||||||
case 1:
|
case 1:
|
||||||
return const ParentManagementWidget();
|
return const ParentManagementWidget();
|
||||||
case 2:
|
case 2:
|
||||||
return const AssistanteMaternelleManagementWidget();
|
return const AssistanteMaternelleManagementWidget();
|
||||||
case 3:
|
case 3:
|
||||||
return const AdministrateurManagementWidget();
|
return const Center(child: Text("👨💼 Administrateurs"));
|
||||||
case 4:
|
|
||||||
return const StatistiqueManageWidget();
|
|
||||||
default:
|
default:
|
||||||
return const Center(child: Text("Page non trouvée"));
|
return const Center(child: Text("Page non trouvée"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,8 +140,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
body: LayoutBuilder(
|
body: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// Version desktop (web)
|
// Version desktop (web)
|
||||||
|
if (kIsWeb) {
|
||||||
// if (kIsWeb) {
|
|
||||||
final w = constraints.maxWidth;
|
final w = constraints.maxWidth;
|
||||||
final h = constraints.maxHeight;
|
final h = constraints.maxHeight;
|
||||||
|
|
||||||
@ -347,12 +346,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Version mobile (à implémenter)
|
// Version mobile (à implémenter)
|
||||||
// return const Center(
|
return const Center(
|
||||||
// child: Text('Version mobile à implémenter'),
|
child: Text('Version mobile à implémenter'),
|
||||||
// );
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class TokenService {
|
class TokenService {
|
||||||
// static const _storage = FlutterSecureStorage();
|
static const _storage = FlutterSecureStorage();
|
||||||
static const _tokenKey = 'access_token';
|
static const _tokenKey = 'access_token';
|
||||||
static const String _refreshTokenKey = 'refresh_token';
|
static const String _refreshTokenKey = 'refresh_token';
|
||||||
static const _roleKey = 'user_role';
|
static const _roleKey = 'user_role';
|
||||||
|
|||||||
@ -112,51 +112,40 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getUserId() async {
|
/*static const String _usersKey = 'users';
|
||||||
final token = await TokenService.getToken();
|
static const String _parentsKey = 'parents';
|
||||||
if (token == null) return '';
|
static const String _childrenKey = 'children';
|
||||||
|
|
||||||
try {
|
// Méthode pour se connecter (mode démonstration)
|
||||||
final parts = token.split('.');
|
static Future<AppUser> login(String email, String password) async {
|
||||||
if (parts.length != 3) return '';
|
await Future.delayed(const Duration(seconds: 1)); // Simule un délai de traitement
|
||||||
|
throw Exception('Mode démonstration - Connexion désactivée');
|
||||||
final payload = parts[1];
|
|
||||||
final normalizedPayload = base64Url.normalize(payload);
|
|
||||||
final decoded = utf8.decode(base64Url.decode(normalizedPayload));
|
|
||||||
final Map<String, dynamic> payloadMap = jsonDecode(decoded);
|
|
||||||
|
|
||||||
return payloadMap['sub'] ?? '';
|
|
||||||
} catch (e) {
|
|
||||||
print('Error extracting user id from token: $e');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getUserNameById() async {
|
// Méthode pour s'inscrire (mode démonstration)
|
||||||
final userid = await getUserId();
|
static Future<AppUser> register({
|
||||||
final token = await TokenService.getToken();
|
required String email,
|
||||||
|
required String password,
|
||||||
if (token == null || userid.isEmpty) return null;
|
required String firstName,
|
||||||
try {
|
required String lastName,
|
||||||
final response = await http.get(
|
required String role,
|
||||||
Uri.parse('$baseUrl${ApiConfig.users}/$userid'),
|
}) async {
|
||||||
headers: {
|
await Future.delayed(const Duration(seconds: 1)); // Simule un délai de traitement
|
||||||
'Authorization': 'Bearer $token',
|
throw Exception('Mode démonstration - Inscription désactivée');
|
||||||
'accept': '*/*',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final data = jsonDecode(response.body);
|
|
||||||
final firstName = data['prenom'];
|
|
||||||
// final lastName = data['nom'];
|
|
||||||
return '$firstName';
|
|
||||||
} else {
|
|
||||||
print('Erreur Api: ${response.statusCode} - ${response.body}');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error fetching user name: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Méthode pour se déconnecter (mode démonstration)
|
||||||
|
static Future<void> logout() async {
|
||||||
|
// Ne fait rien en mode démonstration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode pour vérifier si l'utilisateur est connecté (mode démonstration)
|
||||||
|
static Future<bool> isLoggedIn() async {
|
||||||
|
return false; // Toujours non connecté en mode démonstration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode pour récupérer l'utilisateur connecté (mode démonstration)
|
||||||
|
static Future<AppUser?> getCurrentUser() async {
|
||||||
|
return null; // Aucun utilisateur en mode démonstration
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/services/api/api_config.dart';
|
|
||||||
import 'package:p_tits_pas/services/api/tokenService.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
|
|
||||||
class UserService {
|
|
||||||
final String baseUrl = ApiConfig.baseUrl;
|
|
||||||
|
|
||||||
//Recuperer tous les utilisateurs
|
|
||||||
Future<List<Map<String, dynamic>>> getAllUsers() async {
|
|
||||||
try {
|
|
||||||
final token = await TokenService.getToken();
|
|
||||||
if (token == null) {
|
|
||||||
throw Exception('Token non disponible');
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse('$baseUrl${ApiConfig.users}'),
|
|
||||||
headers: ApiConfig.authHeaders(token),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final List<dynamic> data = jsonDecode(response.body);
|
|
||||||
return data.cast<Map<String, dynamic>>();
|
|
||||||
} else {
|
|
||||||
throw Exception('Erreur lors de la récupération des utilisateurs: ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur de connexion: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Récuperer les utilisateurs en fonction du role
|
|
||||||
Future<List<Map<String, dynamic>>> getUsersByRole(String role) async {
|
|
||||||
try {
|
|
||||||
final allUsers = await getAllUsers();
|
|
||||||
return allUsers.where((user) =>
|
|
||||||
user['role']?.toString().toLowerCase() == role.toLowerCase()
|
|
||||||
).toList();
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération des utilisateurs par rôle: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtrer les utilisateurs par statut
|
|
||||||
Future<List<Map<String, dynamic>>> filterUsersByStatus(String? status) async {
|
|
||||||
try {
|
|
||||||
final allUsers = await getAllUsers();
|
|
||||||
if (status == null || status.isEmpty) return allUsers;
|
|
||||||
|
|
||||||
return allUsers
|
|
||||||
.where((user) =>
|
|
||||||
user['status']?.toString().toLowerCase() == status.toLowerCase())
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors du filtrage: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Supprimer un utilisateur
|
|
||||||
Future<bool> deleteUser(String userId) async {
|
|
||||||
try {
|
|
||||||
final token = await TokenService.getToken();
|
|
||||||
if (token == null) {
|
|
||||||
throw Exception('Token non disponible');
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await http.delete(
|
|
||||||
Uri.parse('$baseUrl${ApiConfig.users}/$userId'),
|
|
||||||
headers: ApiConfig.authHeaders(token),
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.statusCode == 200 || response.statusCode == 204;
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la suppression: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Récupérer les détails d'un utilisateur
|
|
||||||
Future<Map<String, dynamic>?> getUserById(String userId) async {
|
|
||||||
try {
|
|
||||||
final token = await TokenService.getToken();
|
|
||||||
if (token == null) {
|
|
||||||
throw Exception('Token non disponible');
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse('$baseUrl${ApiConfig.users}/$userId'),
|
|
||||||
headers: ApiConfig.authHeaders(token),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return jsonDecode(response.body);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Erreur lors de la récupération de l\'utilisateur: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class DashboardSidebarAdmin extends StatelessWidget {
|
|
||||||
final int selectedIndex;
|
|
||||||
final ValueChanged<int> onTabChange;
|
|
||||||
|
|
||||||
const DashboardSidebarAdmin({
|
|
||||||
Key? key,
|
|
||||||
required this.selectedIndex,
|
|
||||||
required this.onTabChange,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final items = [
|
|
||||||
{'title': 'Gestionnaires', 'icon': Icons.admin_panel_settings},
|
|
||||||
{'title': 'Parents', 'icon': Icons.family_restroom},
|
|
||||||
{'title': 'Assistantes maternelles', 'icon': Icons.woman},
|
|
||||||
{'title': 'Administrateurs', 'icon': Icons.supervisor_account},
|
|
||||||
{'title': 'Statistiques', 'icon': Icons.bar_chart},
|
|
||||||
];
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: 250,
|
|
||||||
color: const Color(0xFFF7F7F7),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Avatar en haut
|
|
||||||
Center(
|
|
||||||
child: Column(
|
|
||||||
children: const [
|
|
||||||
CircleAvatar(radius: 32, child: Icon(Icons.person, size: 32)),
|
|
||||||
SizedBox(height: 8),
|
|
||||||
Text("Admin", style: TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
const Text("Navigation", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Navigation
|
|
||||||
...List.generate(items.length, (index) {
|
|
||||||
final item = items[index];
|
|
||||||
final isActive = index == selectedIndex;
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: ListTile(
|
|
||||||
tileColor: isActive ? const Color(0xFF9CC5C0) : null,
|
|
||||||
leading: Icon(item['icon'] as IconData, color: isActive ? Color(0xFF9CC5C0) : Colors.black54),
|
|
||||||
title: Text(
|
|
||||||
item['title'] as String,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isActive ? Color(0xFF9CC5C0) : Colors.black,
|
|
||||||
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
||||||
onTap: () => onTabChange(index),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class StatistiqueManageWidget extends StatelessWidget {
|
|
||||||
const StatistiqueManageWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
'Statistiques',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/base_user_management.dart';
|
|
||||||
|
|
||||||
class AdministrateurManagementWidget extends StatelessWidget {
|
|
||||||
const AdministrateurManagementWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BaseUserManagementWidget(
|
|
||||||
config: UserDisplayConfig(
|
|
||||||
title: 'Administrateur',
|
|
||||||
role: 'administrateur',
|
|
||||||
defaultIcon: Icons.admin_panel_settings,
|
|
||||||
filterFields: [
|
|
||||||
FilterField(
|
|
||||||
label: 'Rechercher',
|
|
||||||
hint: 'Nom ou email',
|
|
||||||
type: FilterType.text,
|
|
||||||
filter: (user, query) {
|
|
||||||
final fullName =
|
|
||||||
'${user['firstName'] ?? ''} ${user['lastName'] ?? ''}'
|
|
||||||
.toLowerCase();
|
|
||||||
final email = (user['email'] ?? '').toLowerCase();
|
|
||||||
return fullName.contains(query.toLowerCase()) ||
|
|
||||||
email.contains(query.toLowerCase());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Statut',
|
|
||||||
hint: 'Tous',
|
|
||||||
type: FilterType.dropdown,
|
|
||||||
options: ['actif', 'en_attente', 'inactif', 'supprimé'],
|
|
||||||
filter: (user, status) {
|
|
||||||
if (status.isEmpty) return true;
|
|
||||||
return user['statut']?.toString().toLowerCase() ==
|
|
||||||
status.toLowerCase();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.edit,
|
|
||||||
color: Colors.orange,
|
|
||||||
tooltip: 'Modifier',
|
|
||||||
onPressed: _editAdministrateur,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.security,
|
|
||||||
color: Colors.blue,
|
|
||||||
tooltip: 'Gérer droits',
|
|
||||||
onPressed: _manageRights,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.lock_reset,
|
|
||||||
color: Colors.purple,
|
|
||||||
tooltip: 'Réinitialiser MDP',
|
|
||||||
onPressed: _resetPassword,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.toggle_on,
|
|
||||||
color: Colors.green,
|
|
||||||
tooltip: 'Activer/Désactiver',
|
|
||||||
onPressed: _toggleStatus,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
tooltip: 'Supprimer',
|
|
||||||
onPressed: _deleteAdministrateur,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
getDisplayName: (user) => '${user['prenom'] ?? ''} ${user['nom'] ?? ''}',
|
|
||||||
getSubtitle: (user) {
|
|
||||||
final email = user['email'] ?? '';
|
|
||||||
final profession = user['profession'] ?? 'Non spécifiée';
|
|
||||||
final ville = user['ville'] ?? '';
|
|
||||||
// final statut = AdministrateurService.getStatutDisplay(user['statut']);
|
|
||||||
final statut = user['statut'] ?? 'inactif';
|
|
||||||
final changementMdp =
|
|
||||||
user['changement_mdp_obligatoire'] == true ? 'MDP à changer' : '';
|
|
||||||
|
|
||||||
return '$email\n$profession${ville.isNotEmpty ? ' • $ville' : ''}\nStatut: $statut ${changementMdp.isNotEmpty ? '• $changementMdp' : ''}';
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _editAdministrateur(
|
|
||||||
BuildContext context, Map<String, dynamic> user) async {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Modifier administrateur: ${user['prenom']} ${user['nom']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _manageRights(
|
|
||||||
BuildContext context, Map<String, dynamic> user) async {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Gérer droits pour: ${user['prenom']} ${user['nom']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _resetPassword(
|
|
||||||
BuildContext context, Map<String, dynamic> user) async {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Réinitialiser mot de passe pour: ${user['prenom']} ${user['nom']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _toggleStatus(
|
|
||||||
BuildContext context, Map<String, dynamic> user) async {
|
|
||||||
final currentStatus = user['statut'] ?? 'inactif';
|
|
||||||
final newStatus = currentStatus == 'actif' ? 'inactif' : 'actif';
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'${newStatus == 'actif' ? 'Activer' : 'Désactiver'} administrateur: ${user['prenom']} ${user['nom']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _deleteAdministrateur(
|
|
||||||
BuildContext context, Map<String, dynamic> user) async {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Supprimer administrateur: ${user['prenom']} ${user['nom']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,278 +1,106 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/base_user_management.dart';
|
|
||||||
|
|
||||||
class AssistanteMaternelleManagementWidget extends StatelessWidget {
|
class AssistanteMaternelleManagementWidget extends StatelessWidget {
|
||||||
const AssistanteMaternelleManagementWidget({super.key});
|
const AssistanteMaternelleManagementWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseUserManagementWidget(
|
final assistantes = [
|
||||||
config: UserDisplayConfig(
|
{
|
||||||
title: 'Assistantes Maternelles',
|
"nom": "Marie Dupont",
|
||||||
role: 'assistante_maternelle',
|
"numeroAgrement": "AG123456",
|
||||||
defaultIcon: Icons.face,
|
"zone": "Paris 14",
|
||||||
filterFields: [
|
"capacite": 3,
|
||||||
FilterField(
|
},
|
||||||
label: 'Rechercher',
|
{
|
||||||
hint: 'Nom ou email',
|
"nom": "Claire Martin",
|
||||||
type: FilterType.text,
|
"numeroAgrement": "AG654321",
|
||||||
filter: (user, query) {
|
"zone": "Lyon 7",
|
||||||
final fullName = '${user['prenom'] ?? ''} ${user['nom'] ?? ''}'.toLowerCase();
|
"capacite": 2,
|
||||||
final email = (user['email'] ?? '').toLowerCase();
|
},
|
||||||
return fullName.contains(query.toLowerCase()) ||
|
];
|
||||||
email.contains(query.toLowerCase());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Zone géographique',
|
|
||||||
hint: 'Ville ou département',
|
|
||||||
type: FilterType.text,
|
|
||||||
filter: (user, query) {
|
|
||||||
final zone = (user['zone'] ?? user['ville'] ?? user['code_postal'] ?? '').toLowerCase();
|
|
||||||
return zone.contains(query.toLowerCase());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Capacité minimum',
|
|
||||||
hint: 'Nombre d\'enfants',
|
|
||||||
type: FilterType.number,
|
|
||||||
filter: (user, query) {
|
|
||||||
final capacite = int.tryParse(user['capacite']?.toString() ?? '0') ?? 0;
|
|
||||||
final minCapacite = int.tryParse(query) ?? 0;
|
|
||||||
return capacite >= minCapacite;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Statut',
|
|
||||||
hint: 'Tous',
|
|
||||||
type: FilterType.dropdown,
|
|
||||||
options: ['actif', 'en_attente', 'inactif'],
|
|
||||||
filter: (user, status) {
|
|
||||||
if (status.isEmpty) return true;
|
|
||||||
return user['statut']?.toString().toLowerCase() == status.toLowerCase();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.edit,
|
|
||||||
color: Colors.orange,
|
|
||||||
tooltip: 'Modifier',
|
|
||||||
onPressed: _editAssistante,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
tooltip: 'Supprimer',
|
|
||||||
onPressed: _deleteAssistante,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.location_on,
|
|
||||||
color: Colors.green,
|
|
||||||
tooltip: 'Voir zone',
|
|
||||||
onPressed: _showZone,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
getSubtitle: (user) {
|
|
||||||
final email = user['email'] ?? '';
|
|
||||||
final numeroAgrement = user['numeroAgrement'] ?? user['agrement'] ?? 'N/A';
|
|
||||||
final zone = user['code_postal'] ?? user['ville'] ?? 'Non spécifiée';
|
|
||||||
final capacite = user['capacite'] ?? user['capaciteAccueil'] ?? 'N/A';
|
|
||||||
return '$email\nN° Agrément: $numeroAgrement\nZone: $zone | Capacité: $capacite';
|
|
||||||
},
|
|
||||||
getDisplayName: (user) => '${user['prenom'] ?? ''} ${user['nom'] ?? ''}',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _editAssistante(BuildContext context, Map<String, dynamic> assistante) async {
|
return Padding(
|
||||||
// TODO: Implémenter l'édition
|
padding: const EdgeInsets.all(16),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
child: Column(
|
||||||
const SnackBar(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
content: Text('Fonctionnalité de modification à implémenter'),
|
children: [
|
||||||
backgroundColor: Colors.orange,
|
// 🔎 Zone de filtre
|
||||||
),
|
_buildFilterSection(),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _deleteAssistante(BuildContext context, Map<String, dynamic> assistante) async {
|
const SizedBox(height: 16),
|
||||||
final confirmed = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Confirmer la suppression'),
|
|
||||||
content: Text(
|
|
||||||
'Êtes-vous sûr de vouloir supprimer le compte de ${assistante['firstName']} ${assistante['lastName']} ?\n\n'
|
|
||||||
'Cette action supprimera également tous les contrats et données associés.'
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: const Text('Annuler'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
|
||||||
child: const Text('Supprimer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirmed == true) {
|
// 📋 Liste des assistantes
|
||||||
try {
|
ListView.builder(
|
||||||
final userService = UserService();
|
shrinkWrap: true,
|
||||||
final success = await userService.deleteUser(assistante['id']);
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: assistantes.length,
|
||||||
if (success && context.mounted) {
|
itemBuilder: (context, index) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
final assistante = assistantes[index];
|
||||||
SnackBar(
|
return Card(
|
||||||
content: Text('${assistante['firstName']} ${assistante['lastName']} supprimé avec succès'),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
backgroundColor: Colors.green,
|
child: ListTile(
|
||||||
),
|
leading: const Icon(Icons.face),
|
||||||
);
|
title: Text(assistante['nom'].toString()),
|
||||||
// Le widget se rechargera automatiquement via le système de state
|
subtitle: Text(
|
||||||
} else {
|
"N° Agrément : ${assistante['numeroAgrement']}\nZone : ${assistante['zone']} | Capacité : ${assistante['capacite']}"),
|
||||||
throw Exception('Erreur lors de la suppression');
|
trailing: Row(
|
||||||
}
|
mainAxisSize: MainAxisSize.min,
|
||||||
} catch (e) {
|
children: [
|
||||||
if (context.mounted) {
|
IconButton(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
icon: const Icon(Icons.edit),
|
||||||
SnackBar(
|
onPressed: () {
|
||||||
content: Text('Erreur: ${e.toString()}'),
|
// TODO: Ajouter modification
|
||||||
backgroundColor: Colors.red,
|
},
|
||||||
),
|
),
|
||||||
);
|
IconButton(
|
||||||
}
|
icon: const Icon(Icons.delete),
|
||||||
}
|
onPressed: () {
|
||||||
}
|
// TODO: Ajouter suppression
|
||||||
}
|
},
|
||||||
|
),
|
||||||
static Future<void> _showZone(BuildContext context, Map<String, dynamic> assistante) async {
|
],
|
||||||
final zone = assistante['zone'] ?? assistante['ville'] ?? 'Non spécifiée';
|
|
||||||
final adresse = assistante['adresse'] ?? assistante['address'] ?? '';
|
|
||||||
final codePostal = assistante['codePostal'] ?? assistante['zipCode'] ?? '';
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text('Zone d\'intervention - ${assistante['firstName']} ${assistante['lastName']}'),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (zone.isNotEmpty) Text('Zone: $zone', style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (adresse.isNotEmpty) Text('Adresse: $adresse'),
|
|
||||||
if (codePostal.isNotEmpty) Text('Code postal: $codePostal'),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('Capacité d\'accueil: ${assistante['capacite'] ?? assistante['capaciteAccueil'] ?? 'N/A'} enfants'),
|
|
||||||
Text('N° Agrément: ${assistante['numeroAgrement'] ?? assistante['agrement'] ?? 'N/A'}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: const Text('Fermer'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Ouvrir dans Maps
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Intégration Maps à implémenter'),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFilterSection() {
|
||||||
|
return Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Zone géographique",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
// TODO: Ajouter logique de filtrage par zone
|
||||||
},
|
},
|
||||||
child: const Text('Voir sur la carte'),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Capacité minimum",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: (value) {
|
||||||
|
// TODO: Ajouter logique de filtrage par capacité
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return Padding(
|
|
||||||
// padding: const EdgeInsets.all(16),
|
|
||||||
// child: Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// children: [
|
|
||||||
// // 🔎 Zone de filtre
|
|
||||||
// _buildFilterSection(),
|
|
||||||
|
|
||||||
// const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// // 📋 Liste des assistantes
|
|
||||||
// ListView.builder(
|
|
||||||
// shrinkWrap: true,
|
|
||||||
// physics: const NeverScrollableScrollPhysics(),
|
|
||||||
// itemCount: assistantes.length,
|
|
||||||
// itemBuilder: (context, index) {
|
|
||||||
// final assistante = assistantes[index];
|
|
||||||
// return Card(
|
|
||||||
// margin: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
// child: ListTile(
|
|
||||||
// leading: const Icon(Icons.face),
|
|
||||||
// title: Text(assistante['nom'].toString()),
|
|
||||||
// subtitle: Text(
|
|
||||||
// "N° Agrément : ${assistante['numeroAgrement']}\nZone : ${assistante['zone']} | Capacité : ${assistante['capacite']}"),
|
|
||||||
// trailing: Row(
|
|
||||||
// mainAxisSize: MainAxisSize.min,
|
|
||||||
// children: [
|
|
||||||
// IconButton(
|
|
||||||
// icon: const Icon(Icons.edit),
|
|
||||||
// onPressed: () {
|
|
||||||
// // TODO: Ajouter modification
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// IconButton(
|
|
||||||
// icon: const Icon(Icons.delete),
|
|
||||||
// onPressed: () {
|
|
||||||
// // TODO: Ajouter suppression
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Widget _buildFilterSection() {
|
|
||||||
// return Wrap(
|
|
||||||
// spacing: 16,
|
|
||||||
// runSpacing: 8,
|
|
||||||
// children: [
|
|
||||||
// SizedBox(
|
|
||||||
// width: 200,
|
|
||||||
// child: TextField(
|
|
||||||
// decoration: const InputDecoration(
|
|
||||||
// labelText: "Zone géographique",
|
|
||||||
// border: OutlineInputBorder(),
|
|
||||||
// ),
|
|
||||||
// onChanged: (value) {
|
|
||||||
// // TODO: Ajouter logique de filtrage par zone
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 200,
|
|
||||||
// child: TextField(
|
|
||||||
// decoration: const InputDecoration(
|
|
||||||
// labelText: "Capacité minimum",
|
|
||||||
// border: OutlineInputBorder(),
|
|
||||||
// ),
|
|
||||||
// keyboardType: TextInputType.number,
|
|
||||||
// onChanged: (value) {
|
|
||||||
// // TODO: Ajouter logique de filtrage par capacité
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@ -1,424 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
|
||||||
|
|
||||||
/// Configuration pour personnaliser l'affichage des utilisateurs
|
|
||||||
class UserDisplayConfig {
|
|
||||||
final String title;
|
|
||||||
final String role;
|
|
||||||
final IconData defaultIcon;
|
|
||||||
final List<FilterField> filterFields;
|
|
||||||
final List<UserAction> actions;
|
|
||||||
final String Function(Map<String, dynamic>) getSubtitle;
|
|
||||||
final String Function(Map<String, dynamic>) getDisplayName;
|
|
||||||
|
|
||||||
const UserDisplayConfig({
|
|
||||||
required this.title,
|
|
||||||
required this.role,
|
|
||||||
required this.defaultIcon,
|
|
||||||
required this.filterFields,
|
|
||||||
required this.actions,
|
|
||||||
required this.getSubtitle,
|
|
||||||
required this.getDisplayName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration d'un champ de filtre
|
|
||||||
class FilterField {
|
|
||||||
final String label;
|
|
||||||
final String hint;
|
|
||||||
final FilterType type;
|
|
||||||
final List<String>? options;
|
|
||||||
final bool Function(Map<String, dynamic>, String) filter;
|
|
||||||
|
|
||||||
const FilterField({
|
|
||||||
required this.label,
|
|
||||||
required this.hint,
|
|
||||||
required this.type,
|
|
||||||
required this.filter,
|
|
||||||
this.options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FilterType { text, dropdown, number }
|
|
||||||
|
|
||||||
/// Configuration d'une action sur un utilisateur
|
|
||||||
class UserAction {
|
|
||||||
final IconData icon;
|
|
||||||
final Color color;
|
|
||||||
final String tooltip;
|
|
||||||
final Future<void> Function(BuildContext, Map<String, dynamic>) onPressed;
|
|
||||||
|
|
||||||
const UserAction({
|
|
||||||
required this.icon,
|
|
||||||
required this.color,
|
|
||||||
required this.tooltip,
|
|
||||||
required this.onPressed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Widget de gestion d'utilisateurs réutilisable
|
|
||||||
class BaseUserManagementWidget extends StatefulWidget {
|
|
||||||
final UserDisplayConfig config;
|
|
||||||
|
|
||||||
const BaseUserManagementWidget({
|
|
||||||
super.key,
|
|
||||||
required this.config,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<BaseUserManagementWidget> createState() =>
|
|
||||||
_BaseUserManagementWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BaseUserManagementWidgetState extends State<BaseUserManagementWidget> {
|
|
||||||
final UserService _userService = UserService();
|
|
||||||
final Map<String, TextEditingController> _filterControllers = {};
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> _allUsers = [];
|
|
||||||
List<Map<String, dynamic>> _filteredUsers = [];
|
|
||||||
bool _isLoading = true;
|
|
||||||
String? _error;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_initializeFilters();
|
|
||||||
_loadUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initializeFilters() {
|
|
||||||
for (final field in widget.config.filterFields) {
|
|
||||||
_filterControllers[field.label] = TextEditingController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (final controller in _filterControllers.values) {
|
|
||||||
controller.dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadUsers() async {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_error = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final users = await _userService.getUsersByRole(widget.config.role);
|
|
||||||
setState(() {
|
|
||||||
_allUsers = users;
|
|
||||||
_filteredUsers = users;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
setState(() {
|
|
||||||
_error = e.toString();
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _applyFilters() {
|
|
||||||
setState(() {
|
|
||||||
_filteredUsers = _allUsers.where((user) {
|
|
||||||
return widget.config.filterFields.every((field) {
|
|
||||||
final controller = _filterControllers[field.label];
|
|
||||||
if (controller == null || controller.text.isEmpty) return true;
|
|
||||||
return field.filter(user, controller.text);
|
|
||||||
});
|
|
||||||
}).toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getStatusDisplay(Map<String, dynamic> user) {
|
|
||||||
final status = user['statut'];
|
|
||||||
if (status == null) return 'Non défini';
|
|
||||||
|
|
||||||
switch (status.toString().toLowerCase()) {
|
|
||||||
case 'actif':
|
|
||||||
return 'Actif';
|
|
||||||
case 'en_attente':
|
|
||||||
return 'En attente';
|
|
||||||
case 'inactif':
|
|
||||||
return 'Inactif';
|
|
||||||
case 'deleted':
|
|
||||||
return 'Supprimé';
|
|
||||||
default:
|
|
||||||
return status.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getStatusColor(Map<String, dynamic> user) {
|
|
||||||
final status = user['statut']?.toString().toLowerCase();
|
|
||||||
switch (status) {
|
|
||||||
case 'actif':
|
|
||||||
return Colors.green;
|
|
||||||
case 'en_attente':
|
|
||||||
return Colors.orange;
|
|
||||||
case 'inactif':
|
|
||||||
return Colors.grey;
|
|
||||||
case 'supprimé':
|
|
||||||
return Colors.red;
|
|
||||||
default:
|
|
||||||
return Colors.grey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showUserDetails(Map<String, dynamic> user) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text(widget.config.getDisplayName(user)),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('Email: ${user['email']}'),
|
|
||||||
Text('Rôle: ${user['role']}'),
|
|
||||||
Text('Statut: ${_getStatusDisplay(user)}'),
|
|
||||||
Text('ID: ${user['id']}'),
|
|
||||||
if (user['createdAt'] != null)
|
|
||||||
Text(
|
|
||||||
'Créé le: ${DateTime.parse(user['createdAt']).toLocal().toString().split(' ')[0]}'),
|
|
||||||
// Affichage des champs spécifiques selon le type d'utilisateur
|
|
||||||
...user.entries
|
|
||||||
.where((e) => ![
|
|
||||||
'id',
|
|
||||||
'email',
|
|
||||||
'role',
|
|
||||||
'status',
|
|
||||||
'createdAt',
|
|
||||||
'updatedAt',
|
|
||||||
'firstName',
|
|
||||||
'lastName'
|
|
||||||
].contains(e.key))
|
|
||||||
.map((e) => Text('${e.key}: ${e.value}'))
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: const Text('Fermer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${widget.config.title} (${_filteredUsers.length})',
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.refresh),
|
|
||||||
onPressed: _loadUsers,
|
|
||||||
tooltip: 'Actualiser',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildFilterSection(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Expanded(
|
|
||||||
child: _buildUsersList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFilterSection() {
|
|
||||||
return Wrap(
|
|
||||||
spacing: 16,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: widget.config.filterFields.map((field) {
|
|
||||||
final controller = _filterControllers[field.label]!;
|
|
||||||
|
|
||||||
switch (field.type) {
|
|
||||||
case FilterType.text:
|
|
||||||
case FilterType.number:
|
|
||||||
return SizedBox(
|
|
||||||
width: 250,
|
|
||||||
child: TextField(
|
|
||||||
controller: controller,
|
|
||||||
keyboardType: field.type == FilterType.number
|
|
||||||
? TextInputType.number
|
|
||||||
: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: field.label,
|
|
||||||
hintText: field.hint,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
prefixIcon: const Icon(Icons.search),
|
|
||||||
),
|
|
||||||
onChanged: (value) => _applyFilters(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
case FilterType.dropdown:
|
|
||||||
return SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: DropdownButtonFormField<String>(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: field.label,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
const DropdownMenuItem(value: '', child: Text("Tous")),
|
|
||||||
...?field.options?.map((option) =>
|
|
||||||
DropdownMenuItem(value: option, child: Text(option))),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
controller.text = value ?? '';
|
|
||||||
_applyFilters();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildUsersList() {
|
|
||||||
if (_isLoading) {
|
|
||||||
return const Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
CircularProgressIndicator(),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Text('Chargement...'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_error != null) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.error_outline, size: 48, color: Colors.red[300]),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Erreur de chargement',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
_error!,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: _loadUsers,
|
|
||||||
child: const Text('Réessayer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_filteredUsers.isEmpty) {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.people_outline, size: 48, color: Colors.grey[400]),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
_allUsers.isEmpty ? 'Aucun utilisateur trouvé' : 'Aucun résultat',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
if (_allUsers.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const Text('Essayez de modifier vos critères de recherche'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: _filteredUsers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final user = _filteredUsers[index];
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: _getStatusColor(user).withOpacity(0.2),
|
|
||||||
child: Icon(
|
|
||||||
widget.config.defaultIcon,
|
|
||||||
color: _getStatusColor(user),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(widget.config.getDisplayName(user)),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(widget.config.getSubtitle(user)),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _getStatusColor(user).withOpacity(0.2),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: _getStatusColor(user)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_getStatusDisplay(user),
|
|
||||||
style: TextStyle(
|
|
||||||
color: _getStatusColor(user),
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
isThreeLine: true,
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.visibility, color: Colors.blue),
|
|
||||||
tooltip: "Voir détails",
|
|
||||||
onPressed: () => _showUserDetails(user),
|
|
||||||
),
|
|
||||||
...widget.config.actions
|
|
||||||
.map(
|
|
||||||
(action) => IconButton(
|
|
||||||
icon: Icon(action.icon, color: action.color),
|
|
||||||
tooltip: action.tooltip,
|
|
||||||
onPressed: () => action.onPressed(context, user),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/services/auth_service.dart';
|
|
||||||
|
|
||||||
class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidget {
|
class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final int selectedIndex;
|
final int selectedIndex;
|
||||||
@ -7,29 +6,6 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
|
|||||||
|
|
||||||
const DashboardAppBarAdmin({Key? key, required this.selectedIndex, required this.onTabChange}) : super(key: key);
|
const DashboardAppBarAdmin({Key? key, required this.selectedIndex, required this.onTabChange}) : super(key: key);
|
||||||
|
|
||||||
void _Logout(BuildContext context) async {
|
|
||||||
try {
|
|
||||||
final authS = AuthService();
|
|
||||||
await authS.logout();
|
|
||||||
Navigator.of(context).pushReplacementNamed('/login');
|
|
||||||
} catch (e) {
|
|
||||||
print('Erreur lors de la déconnexion: $e');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Erreur lors de la déconnexion'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> _getUserName() async {
|
|
||||||
final authS = AuthService();
|
|
||||||
final userName = await authS.getUserNameById();
|
|
||||||
return userName ?? 'Admin';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
|
||||||
|
|
||||||
@ -53,36 +29,33 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
|
|||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
SizedBox(width: MediaQuery.of(context).size.width * 0.1),
|
||||||
|
|
||||||
// Navigation principale
|
// Navigation principale
|
||||||
// _buildNavItem(context, 'Gestionnaires', 0),
|
_buildNavItem(context, 'Gestionnaires', 0),
|
||||||
// const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
// _buildNavItem(context, 'Parents', 1),
|
_buildNavItem(context, 'Parents', 1),
|
||||||
// const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
// _buildNavItem(context, 'Assistantes maternelles', 2),
|
_buildNavItem(context, 'Assistantes maternelles', 2),
|
||||||
// const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
// _buildNavItem(context, 'Administrateurs', 4),
|
_buildNavItem(context, 'Administrateurs', 3),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: isMobile
|
actions: isMobile
|
||||||
? [_buildMobileMenu(context)]
|
? [_buildMobileMenu(context)]
|
||||||
: [
|
: [
|
||||||
// Nom de l'utilisateur
|
// Nom de l'utilisateur
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FutureBuilder<String>(
|
child: Text(
|
||||||
future: _getUserName(),
|
'Admin',
|
||||||
builder: (context, snapshot) {
|
style: TextStyle(
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
color: Colors.black,
|
||||||
return const Text("Chargement...");
|
fontSize: 16,
|
||||||
} else if (snapshot.hasError) {
|
fontWeight: FontWeight.w500,
|
||||||
return const Text("Erreur");
|
),
|
||||||
} else {
|
|
||||||
return Text(snapshot.data ?? "Admin");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Bouton déconnexion
|
// Bouton déconnexion
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 16),
|
padding: const EdgeInsets.only(right: 16),
|
||||||
@ -104,28 +77,28 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget _buildNavItem(BuildContext context, String title, int index) {
|
Widget _buildNavItem(BuildContext context, String title, int index) {
|
||||||
// final bool isActive = index == selectedIndex;
|
final bool isActive = index == selectedIndex;
|
||||||
// return InkWell(
|
return InkWell(
|
||||||
// onTap: () => onTabChange(index),
|
onTap: () => onTabChange(index),
|
||||||
// child: Container(
|
child: Container(
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
// decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
|
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
|
||||||
// borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
// border: isActive ? null : Border.all(color: Colors.black26),
|
border: isActive ? null : Border.all(color: Colors.black26),
|
||||||
// ),
|
),
|
||||||
// child: Text(
|
child: Text(
|
||||||
// title,
|
title,
|
||||||
// style: TextStyle(
|
style: TextStyle(
|
||||||
// color: isActive ? Colors.white : Colors.black,
|
color: isActive ? Colors.white : Colors.black,
|
||||||
// fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
||||||
// fontSize: 14,
|
fontSize: 14,
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _buildMobileMenu(BuildContext context) {
|
Widget _buildMobileMenu(BuildContext context) {
|
||||||
@ -160,8 +133,8 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
|
|||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_Logout(context);
|
// TODO: Implémenter la logique de déconnexion
|
||||||
},
|
},
|
||||||
child: const Text('Déconnecter'),
|
child: const Text('Déconnecter'),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,195 +1,121 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/admin/base_user_management.dart';
|
|
||||||
|
|
||||||
class ParentManagementWidget extends StatelessWidget {
|
class ParentManagementWidget extends StatelessWidget {
|
||||||
const ParentManagementWidget({super.key});
|
const ParentManagementWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseUserManagementWidget(
|
// 🔁 Simulation de données parents
|
||||||
config: UserDisplayConfig(
|
final parents = [
|
||||||
title: 'Gestion des Parents',
|
{
|
||||||
role: 'parent',
|
"nom": "Jean Dupuis",
|
||||||
defaultIcon: Icons.person_outline,
|
"email": "jean.dupuis@email.com",
|
||||||
filterFields: [
|
"statut": "Actif",
|
||||||
FilterField(
|
"enfants": 2,
|
||||||
label: 'Rechercher',
|
},
|
||||||
hint: 'Nom ou email',
|
{
|
||||||
type: FilterType.text,
|
"nom": "Lucie Morel",
|
||||||
filter: (user, query) {
|
"email": "lucie.morel@email.com",
|
||||||
final fullName = '${user['prenom'] ?? ''} ${user['nom'] ?? ''}'.toLowerCase();
|
"statut": "En attente",
|
||||||
final email = (user['email'] ?? '').toLowerCase();
|
"enfants": 1,
|
||||||
return fullName.contains(query.toLowerCase()) ||
|
},
|
||||||
email.contains(query.toLowerCase());
|
];
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Statut',
|
|
||||||
hint: 'Tous',
|
|
||||||
type: FilterType.dropdown,
|
|
||||||
options: ['actif', 'en_attente', 'inactif', 'supprimé'],
|
|
||||||
filter: (user, status) {
|
|
||||||
if (status.isEmpty) return true;
|
|
||||||
return user['statut']?.toString().toLowerCase() == status.toLowerCase();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
FilterField(
|
|
||||||
label: 'Nombre d\'enfants',
|
|
||||||
hint: 'Minimum',
|
|
||||||
type: FilterType.number,
|
|
||||||
filter: (user, query) {
|
|
||||||
final nombreEnfants = int.tryParse(user['nombreEnfants']?.toString() ?? '0') ?? 0;
|
|
||||||
final minEnfants = int.tryParse(query) ?? 0;
|
|
||||||
return nombreEnfants >= minEnfants;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.edit,
|
|
||||||
color: Colors.orange,
|
|
||||||
tooltip: 'Modifier',
|
|
||||||
onPressed: _editParent,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
tooltip: 'Supprimer',
|
|
||||||
onPressed: _deleteParent,
|
|
||||||
),
|
|
||||||
const UserAction(
|
|
||||||
icon: Icons.child_care,
|
|
||||||
color: Colors.purple,
|
|
||||||
tooltip: 'Voir enfants',
|
|
||||||
onPressed: _showChildren,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
getDisplayName: (user) => '${user['prenom'] ?? ''} ${user['nom'] ?? ''}',
|
|
||||||
getSubtitle: (user) {
|
|
||||||
final email = user['email'] ?? '';
|
|
||||||
final nombreEnfants = user['nombreEnfants'] ?? user['children']?.length ?? 0;
|
|
||||||
return '$email\nNombre d\'enfants: $nombreEnfants';
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _editParent(BuildContext context, Map<String, dynamic> parent) async {
|
return Padding(
|
||||||
// TODO: Implémenter l'édition
|
padding: const EdgeInsets.all(16),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
child: Column(
|
||||||
const SnackBar(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
content: Text('Fonctionnalité de modification à implémenter'),
|
children: [
|
||||||
backgroundColor: Colors.orange,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _deleteParent(BuildContext context, Map<String, dynamic> parent) async {
|
_buildSearchSection(),
|
||||||
final confirmed = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Confirmer la suppression'),
|
|
||||||
content: Text(
|
|
||||||
'Êtes-vous sûr de vouloir supprimer le compte de ${parent['prenom']} ${parent['nom']} ?\n\n'
|
|
||||||
'Cette action supprimera également tous les contrats et données associés.'
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: const Text('Annuler'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
|
||||||
child: const Text('Supprimer'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirmed == true) {
|
const SizedBox(height: 16),
|
||||||
try {
|
|
||||||
final userService = UserService();
|
|
||||||
final success = await userService.deleteUser(parent['id']);
|
|
||||||
|
|
||||||
if (success && context.mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('${parent['prenom']} ${parent['nom']} supprimé avec succès'),
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw Exception('Erreur lors de la suppression');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (context.mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Erreur: ${e.toString()}'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _showChildren(BuildContext context, Map<String, dynamic> parent) async {
|
ListView.builder(
|
||||||
final children = parent['children'] as List<dynamic>? ?? [];
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
showDialog(
|
itemCount: parents.length,
|
||||||
context: context,
|
itemBuilder: (context, index) {
|
||||||
builder: (context) => AlertDialog(
|
final parent = parents[index];
|
||||||
title: Text('Enfants de ${parent['prenom']} ${parent['nom']}'),
|
return Card(
|
||||||
content: Container(
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
width: double.maxFinite,
|
child: ListTile(
|
||||||
constraints: const BoxConstraints(maxHeight: 400),
|
leading: const Icon(Icons.person_outline),
|
||||||
child: children.isEmpty
|
title: Text(parent['nom'].toString()),
|
||||||
? const Text('Aucun enfant enregistré')
|
subtitle: Text(
|
||||||
: SingleChildScrollView(
|
"${parent['email']}\nStatut : ${parent['statut']} | Enfants : ${parent['enfants']}",
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: children.map((child) => Card(
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.child_care),
|
|
||||||
title: Text(child['prenom'] ?? child['firstName'] ?? 'Nom non défini'),
|
|
||||||
subtitle: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (child['dateNaissance'] != null || child['birthDate'] != null)
|
|
||||||
Text('Né(e) le: ${child['dateNaissance'] ?? child['birthDate']}'),
|
|
||||||
if (child['age'] != null)
|
|
||||||
Text('Âge: ${child['age']} ans'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
isThreeLine: true,
|
|
||||||
),
|
|
||||||
)).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
isThreeLine: true,
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.visibility),
|
||||||
|
tooltip: "Voir dossier",
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Voir le statut du dossier
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: "Modifier",
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Modifier parent
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
tooltip: "Supprimer",
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Supprimer compte
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
],
|
||||||
TextButton(
|
)
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: const Text('Fermer'),
|
|
||||||
),
|
|
||||||
if (children.isNotEmpty)
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
// TODO: Naviguer vers la gestion des enfants
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Navigation vers la gestion des enfants à implémenter'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Gérer les enfants'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Widget _buildSearchSection() {
|
||||||
|
return Wrap(
|
||||||
|
spacing: 16,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 220,
|
||||||
|
child: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Nom du parent",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
// TODO: Ajouter logique de recherche
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 220,
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Statut",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(value: "Actif", child: Text("Actif")),
|
||||||
|
DropdownMenuItem(value: "En attente", child: Text("En attente")),
|
||||||
|
DropdownMenuItem(value: "Supprimé", child: Text("Supprimé")),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
// TODO: Ajouter logique de filtrage
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/services/auth_service.dart';
|
|
||||||
|
|
||||||
class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final int selectedIndex;
|
final int selectedIndex;
|
||||||
@ -10,29 +9,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
|
||||||
|
|
||||||
Future <String> _getUserName() async {
|
|
||||||
final authS = AuthService();
|
|
||||||
final userName = await authS.getUserNameById();
|
|
||||||
return userName ?? 'Bienvenue';
|
|
||||||
}
|
|
||||||
|
|
||||||
void _logout (BuildContext context) {
|
|
||||||
try {
|
|
||||||
final authS = AuthService();
|
|
||||||
authS.logout();
|
|
||||||
Navigator.of(context).pushReplacementNamed('/login');
|
|
||||||
} catch (e) {
|
|
||||||
print('Erreur lors de la déconnexion: $e');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Erreur lors de la déconnexion'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isMobile = MediaQuery.of(context).size.width < 768;
|
final isMobile = MediaQuery.of(context).size.width < 768;
|
||||||
@ -41,6 +17,20 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
|
// Logo de la ville
|
||||||
|
// Container(
|
||||||
|
// height: 32,
|
||||||
|
// width: 32,
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: Colors.white,
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// ),
|
||||||
|
// child: const Icon(
|
||||||
|
// Icons.location_city,
|
||||||
|
// color: Color(0xFF9CC5C0),
|
||||||
|
// size: 20,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
SizedBox(width: MediaQuery.of(context).size.width * 0.19),
|
SizedBox(width: MediaQuery.of(context).size.width * 0.19),
|
||||||
const Text(
|
const Text(
|
||||||
"P'tit Pas",
|
"P'tit Pas",
|
||||||
@ -64,20 +54,16 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
? [_buildMobileMenu(context)]
|
? [_buildMobileMenu(context)]
|
||||||
: [
|
: [
|
||||||
// Nom de l'utilisateur
|
// Nom de l'utilisateur
|
||||||
Padding(
|
const Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FutureBuilder<String>(
|
child: Text(
|
||||||
future: _getUserName(),
|
'Jean Dupont',
|
||||||
builder: (context, snapshot) {
|
style: TextStyle(
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
color: Colors.black,
|
||||||
return const Text("Chargement...");
|
fontSize: 16,
|
||||||
} else if (snapshot.hasError) {
|
fontWeight: FontWeight.w500,
|
||||||
return const Text("Erreur");
|
),
|
||||||
} else {
|
|
||||||
return Text(snapshot.data ?? "Admin");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -159,7 +145,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_logout(context);
|
|
||||||
// TODO: Implémenter la logique de déconnexion
|
// TODO: Implémenter la logique de déconnexion
|
||||||
},
|
},
|
||||||
child: const Text('Déconnecter'),
|
child: const Text('Déconnecter'),
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/ChildrenSidebarwidget.dart';
|
import 'package:p_tits_pas/widgets/dashbord_parent/ChildrenSidebarwidget.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart';
|
||||||
import 'package:p_tits_pas/widgets/dashbord_parent/wid_mainContentArea.dart';
|
import 'package:p_tits_pas/widgets/dashbord_parent/wid_mainContentArea.dart';
|
||||||
|
import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
|
||||||
|
|
||||||
Widget Dashbord_body() {
|
Widget Dashbord_body() {
|
||||||
return Row(
|
return Row(
|
||||||
|
|||||||
@ -139,6 +139,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.28"
|
version: "2.0.28"
|
||||||
|
flutter_secure_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage
|
||||||
|
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.2.4"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.3"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -626,6 +674,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.13.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -18,7 +18,8 @@ dependencies:
|
|||||||
image_picker: ^1.0.7
|
image_picker: ^1.0.7
|
||||||
js: ^0.6.7
|
js: ^0.6.7
|
||||||
url_launcher: ^6.2.4
|
url_launcher: ^6.2.4
|
||||||
http: ^1.2.2
|
http: ^1.5.0
|
||||||
|
flutter_secure_storage: ^9.2.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
20
frontend/windows/flutter/generated_plugin_registrant.cc
Normal file
20
frontend/windows/flutter/generated_plugin_registrant.cc
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
|
}
|
||||||
15
frontend/windows/flutter/generated_plugin_registrant.h
Normal file
15
frontend/windows/flutter/generated_plugin_registrant.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter/plugin_registry.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||||
26
frontend/windows/flutter/generated_plugins.cmake
Normal file
26
frontend/windows/flutter/generated_plugins.cmake
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
file_selector_windows
|
||||||
|
flutter_secure_storage_windows
|
||||||
|
url_launcher_windows
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
|
endforeach(plugin)
|
||||||
|
|
||||||
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
|
endforeach(ffi_plugin)
|
||||||
Loading…
x
Reference in New Issue
Block a user