This commit introduces a complete overhaul of how equipment is assigned to events, focusing on an enhanced user experience, advanced selection capabilities, and robust conflict detection.
**Key Features & Enhancements:**
- **Advanced Equipment Selection UI (`EquipmentSelectionDialog`):**
- New full-screen dialog to select equipment and containers ("boîtes") for an event.
- Hierarchical view showing containers and a flat list of all individual equipment.
- Real-time search and filtering by equipment category.
- Side panel summarizing the current selection and providing recommendations for containers based on selected equipment.
- Supports quantity selection for consumables and cables.
- **Conflict Detection & Management (`EventAvailabilityService`):**
- A new service (`EventAvailabilityService`) checks for equipment availability against other events based on the selected date range.
- The selection dialog visually highlights equipment and containers with scheduling conflicts (e.g., already used, partially unavailable).
- A dedicated conflict resolution dialog (`EquipmentConflictDialog`) appears if conflicting items are selected, allowing the user to either remove them or force the assignment.
- **Integrated Event Form (`EventAssignedEquipmentSection`):**
- The event creation/editing form now includes a new section for managing assigned equipment.
- It clearly displays assigned containers and standalone equipment, showing the composition of each container.
- Integrates the new selection dialog, ensuring all assignments are checked for conflicts before being saved.
- **Event Preparation & Return Workflow (`EventPreparationPage`):**
- New page (`EventPreparationPage`) for managing the check-out (preparation) and check-in (return) of equipment for an event.
- Provides a checklist of all assigned equipment.
- Users can validate each item, with options to "validate all" or finalize with missing items.
- Includes a dialog (`MissingEquipmentDialog`) to handle discrepancies.
- Supports tracking returned quantities for consumables.
**Data Model and Other Changes:**
- The `EventModel` now includes `assignedContainers` to explicitly link containers to an event.
- `EquipmentAssociatedEventsSection` on the equipment detail page is now functional, displaying current, upcoming, and past events for that item.
- Added deployment and versioning scripts (`scripts/deploy.js`, `scripts/increment_version.js`, `scripts/toggle_env.js`) to automate the release process.
- Introduced an application version display in the main drawer (`AppVersion`).
216 lines
7.2 KiB
Dart
216 lines
7.2 KiB
Dart
import 'package:em2rp/providers/users_provider.dart';
|
|
import 'package:em2rp/providers/event_provider.dart';
|
|
import 'package:em2rp/providers/equipment_provider.dart';
|
|
import 'package:em2rp/providers/container_provider.dart';
|
|
import 'package:em2rp/providers/maintenance_provider.dart';
|
|
import 'package:em2rp/providers/alert_provider.dart';
|
|
import 'package:em2rp/utils/auth_guard_widget.dart';
|
|
import 'package:em2rp/views/calendar_page.dart';
|
|
import 'package:em2rp/views/login_page.dart';
|
|
import 'package:em2rp/views/equipment_management_page.dart';
|
|
import 'package:em2rp/views/container_management_page.dart';
|
|
import 'package:em2rp/views/container_form_page.dart';
|
|
import 'package:em2rp/views/container_detail_page.dart';
|
|
import 'package:em2rp/views/event_preparation_page.dart';
|
|
import 'package:em2rp/models/container_model.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_core/firebase_core.dart';
|
|
import 'firebase_options.dart';
|
|
import 'utils/colors.dart';
|
|
import 'views/my_account_page.dart';
|
|
import 'views/user_management_page.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'providers/local_user_provider.dart';
|
|
import 'services/user_service.dart';
|
|
import 'views/reset_password_page.dart';
|
|
import 'config/env.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await Firebase.initializeApp(
|
|
options: DefaultFirebaseOptions.currentPlatform,
|
|
);
|
|
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
|
|
|
|
runApp(
|
|
MultiProvider(
|
|
providers: [
|
|
// Injection du service UserService
|
|
Provider<UserService>(create: (_) => UserService()),
|
|
|
|
// LocalUserProvider pour la gestion de l'authentification
|
|
ChangeNotifierProvider<LocalUserProvider>(
|
|
create: (context) => LocalUserProvider()),
|
|
|
|
// Injection des Providers en utilisant UserService
|
|
ChangeNotifierProvider<UsersProvider>(
|
|
create: (context) => UsersProvider(context.read<UserService>()),
|
|
),
|
|
|
|
// EventProvider pour la gestion des événements
|
|
ChangeNotifierProvider<EventProvider>(
|
|
create: (context) => EventProvider(),
|
|
),
|
|
|
|
// Providers pour la gestion du matériel
|
|
ChangeNotifierProvider<EquipmentProvider>(
|
|
create: (context) => EquipmentProvider(),
|
|
),
|
|
ChangeNotifierProvider<ContainerProvider>(
|
|
create: (context) => ContainerProvider(),
|
|
),
|
|
ChangeNotifierProvider<MaintenanceProvider>(
|
|
create: (context) => MaintenanceProvider(),
|
|
),
|
|
ChangeNotifierProvider<AlertProvider>(
|
|
create: (context) => AlertProvider(),
|
|
),
|
|
],
|
|
child: const MyApp(),
|
|
),
|
|
);
|
|
}
|
|
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'EM2 ERP',
|
|
theme: ThemeData(
|
|
primarySwatch: Colors.red,
|
|
primaryColor: AppColors.noir,
|
|
colorScheme:
|
|
ColorScheme.fromSwatch().copyWith(secondary: AppColors.rouge),
|
|
textTheme: const TextTheme(
|
|
bodyMedium: TextStyle(color: AppColors.noir),
|
|
),
|
|
inputDecorationTheme: const InputDecorationTheme(
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(color: AppColors.noir),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(color: AppColors.gris),
|
|
),
|
|
labelStyle: TextStyle(color: AppColors.noir),
|
|
hintStyle: TextStyle(color: AppColors.gris),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
foregroundColor: AppColors.blanc,
|
|
backgroundColor: AppColors.noir,
|
|
),
|
|
),
|
|
),
|
|
locale: const Locale('fr', 'FR'),
|
|
supportedLocales: const [
|
|
Locale('fr', 'FR'),
|
|
],
|
|
localizationsDelegates: const [
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
home: const AutoLoginWrapper(),
|
|
routes: {
|
|
'/login': (context) => const LoginPage(),
|
|
'/calendar': (context) => const AuthGuard(child: CalendarPage()),
|
|
'/my_account': (context) => const AuthGuard(child: MyAccountPage()),
|
|
'/user_management': (context) => const AuthGuard(
|
|
requiredPermission: "view_all_users", child: UserManagementPage()),
|
|
'/reset_password': (context) {
|
|
final args = ModalRoute.of(context)!.settings.arguments
|
|
as Map<String, dynamic>;
|
|
return ResetPasswordPage(
|
|
email: args['email'] as String,
|
|
actionCode: args['actionCode'] as String,
|
|
);
|
|
},
|
|
'/equipment_management': (context) => const AuthGuard(
|
|
requiredPermission: "view_equipment",
|
|
child: EquipmentManagementPage()),
|
|
'/container_management': (context) => const AuthGuard(
|
|
requiredPermission: "view_equipment",
|
|
child: ContainerManagementPage()),
|
|
'/container_form': (context) {
|
|
final args = ModalRoute.of(context)?.settings.arguments;
|
|
return AuthGuard(
|
|
requiredPermission: "manage_equipment",
|
|
child: ContainerFormPage(
|
|
container: args as ContainerModel?,
|
|
),
|
|
);
|
|
},
|
|
'/container_detail': (context) {
|
|
final container = ModalRoute.of(context)!.settings.arguments as ContainerModel;
|
|
return AuthGuard(
|
|
requiredPermission: "view_equipment",
|
|
child: ContainerDetailPage(container: container),
|
|
);
|
|
},
|
|
'/event_preparation': (context) {
|
|
final eventId = ModalRoute.of(context)!.settings.arguments as String;
|
|
return AuthGuard(
|
|
child: EventPreparationPage(eventId: eventId),
|
|
);
|
|
},
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class AutoLoginWrapper extends StatefulWidget {
|
|
const AutoLoginWrapper({super.key});
|
|
|
|
@override
|
|
State<AutoLoginWrapper> createState() => _AutoLoginWrapperState();
|
|
}
|
|
|
|
class _AutoLoginWrapperState extends State<AutoLoginWrapper> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_autoLogin();
|
|
}
|
|
|
|
Future<void> _autoLogin() async {
|
|
try {
|
|
final localAuthProvider =
|
|
Provider.of<LocalUserProvider>(context, listen: false);
|
|
|
|
// Vérifier si l'utilisateur est déjà connecté
|
|
if (FirebaseAuth.instance.currentUser == null && Env.isDevelopment) {
|
|
// Connexion automatique en mode développement
|
|
await localAuthProvider.signInWithEmailAndPassword(
|
|
Env.devAdminEmail,
|
|
Env.devAdminPassword,
|
|
);
|
|
}
|
|
|
|
// Charger les données utilisateur
|
|
await localAuthProvider.loadUserData();
|
|
|
|
if (mounted) {
|
|
Navigator.of(context).pushReplacementNamed('/calendar');
|
|
}
|
|
} catch (e) {
|
|
print('Auto login failed: $e');
|
|
if (mounted) {
|
|
Navigator.of(context).pushReplacementNamed('/login');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
}
|