Compare commits
2 Commits
acab16e101
...
080fb7d077
Author | SHA1 | Date | |
---|---|---|---|
080fb7d077 | |||
57c59c911a |
@ -13,7 +13,7 @@ import 'views/user_management_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'providers/local_user_provider.dart';
|
||||
import 'services/user_service.dart';
|
||||
import 'pages/auth/reset_password_page.dart';
|
||||
import 'views/reset_password_page.dart';
|
||||
import 'config/env.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
|
@ -20,22 +20,24 @@ class EventProvider with ChangeNotifier {
|
||||
print(
|
||||
'Loading events for user: $userId (canViewAllEvents: $canViewAllEvents)');
|
||||
QuerySnapshot eventsSnapshot;
|
||||
if (canViewAllEvents) {
|
||||
// On charge tous les events pour les users non-admins aussi
|
||||
eventsSnapshot = await _firestore.collection('events').get();
|
||||
} else {
|
||||
eventsSnapshot = await _firestore
|
||||
.collection('events')
|
||||
.where('workforce',
|
||||
arrayContains: _firestore.collection('users').doc(userId))
|
||||
.get();
|
||||
}
|
||||
|
||||
print('Found ${eventsSnapshot.docs.length} events for user');
|
||||
|
||||
_events = eventsSnapshot.docs.map((doc) {
|
||||
// On filtre côté client si l'utilisateur n'est pas admin
|
||||
final allEvents = eventsSnapshot.docs.map((doc) {
|
||||
print('Event data: ${doc.data()}');
|
||||
return EventModel.fromMap(doc.data() as Map<String, dynamic>, doc.id);
|
||||
}).toList();
|
||||
if (canViewAllEvents) {
|
||||
_events = allEvents;
|
||||
} else {
|
||||
final userRef = _firestore.collection('users').doc(userId);
|
||||
_events = allEvents
|
||||
.where((e) => e.workforce.any((ref) => ref.id == userRef.id))
|
||||
.toList();
|
||||
}
|
||||
|
||||
print('Parsed ${_events.length} events');
|
||||
|
||||
|
@ -145,7 +145,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const EventAddPage(),
|
||||
builder: (context) => const EventAddEditPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -23,14 +23,15 @@ import 'package:em2rp/views/widgets/inputs/option_selector_widget.dart';
|
||||
// ignore: avoid_web_libraries_in_flutter
|
||||
import 'dart:html' as html;
|
||||
|
||||
class EventAddPage extends StatefulWidget {
|
||||
const EventAddPage({super.key});
|
||||
class EventAddEditPage extends StatefulWidget {
|
||||
final EventModel? event;
|
||||
const EventAddEditPage({super.key, this.event});
|
||||
|
||||
@override
|
||||
State<EventAddPage> createState() => _EventAddPageState();
|
||||
State<EventAddEditPage> createState() => _EventAddEditPageState();
|
||||
}
|
||||
|
||||
class _EventAddPageState extends State<EventAddPage> {
|
||||
class _EventAddEditPageState extends State<EventAddEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
@ -61,6 +62,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
bool _formChanged = false;
|
||||
EventStatus _selectedStatus = EventStatus.waitingForApproval;
|
||||
|
||||
bool get isEditMode => widget.event != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -73,8 +76,25 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
_addressController.addListener(_onAnyFieldChanged);
|
||||
_descriptionController.addListener(_onAnyFieldChanged);
|
||||
_addBeforeUnloadListener();
|
||||
if (isEditMode) {
|
||||
final e = widget.event!;
|
||||
_nameController.text = e.name;
|
||||
_descriptionController.text = e.description;
|
||||
_basePriceController.text = e.basePrice.toStringAsFixed(2);
|
||||
_installationController.text = e.installationTime.toString();
|
||||
_disassemblyController.text = e.disassemblyTime.toString();
|
||||
_addressController.text = e.address;
|
||||
_startDateTime = e.startDateTime;
|
||||
_endDateTime = e.endDateTime;
|
||||
_selectedEventType = e.eventTypeId.isNotEmpty ? e.eventTypeId : null;
|
||||
_selectedUserIds = e.workforce.map((ref) => ref.id).toList();
|
||||
_uploadedFiles = List<Map<String, String>>.from(e.documents);
|
||||
_selectedOptions = List<Map<String, dynamic>>.from(e.options);
|
||||
_selectedStatus = e.status;
|
||||
} else {
|
||||
_selectedStatus = EventStatus.waitingForApproval;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDescriptionChange() {
|
||||
final lines = '\n'.allMatches(_descriptionController.text).length + 1;
|
||||
@ -284,6 +304,47 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
});
|
||||
try {
|
||||
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
||||
if (isEditMode) {
|
||||
// Edition : on met à jour l'événement existant
|
||||
final updatedEvent = EventModel(
|
||||
id: widget.event!.id,
|
||||
name: _nameController.text.trim(),
|
||||
description: _descriptionController.text.trim(),
|
||||
startDateTime: _startDateTime!,
|
||||
endDateTime: _endDateTime!,
|
||||
basePrice: double.tryParse(_basePriceController.text) ?? 0.0,
|
||||
installationTime: int.tryParse(_installationController.text) ?? 0,
|
||||
disassemblyTime: int.tryParse(_disassemblyController.text) ?? 0,
|
||||
eventTypeId: _selectedEventType!,
|
||||
customerId: '',
|
||||
address: _addressController.text.trim(),
|
||||
workforce: _selectedUserIds
|
||||
.map((id) =>
|
||||
FirebaseFirestore.instance.collection('users').doc(id))
|
||||
.toList(),
|
||||
latitude: 0.0,
|
||||
longitude: 0.0,
|
||||
documents: _uploadedFiles,
|
||||
options: _selectedOptions
|
||||
.map((opt) => {
|
||||
'name': opt['name'],
|
||||
'price': opt['price'],
|
||||
})
|
||||
.toList(),
|
||||
status: _selectedStatus,
|
||||
);
|
||||
final docRef = FirebaseFirestore.instance
|
||||
.collection('events')
|
||||
.doc(widget.event!.id);
|
||||
await docRef.update(updatedEvent.toMap());
|
||||
// Gestion des fichiers (si besoin, à adapter selon ta logique)
|
||||
// ...
|
||||
setState(() {
|
||||
_success = "Événement modifié avec succès !";
|
||||
});
|
||||
if (context.mounted) Navigator.of(context).pop();
|
||||
} else {
|
||||
// Création : logique existante
|
||||
final newEvent = EventModel(
|
||||
id: '',
|
||||
name: _nameController.text.trim(),
|
||||
@ -297,7 +358,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
customerId: '',
|
||||
address: _addressController.text.trim(),
|
||||
workforce: _selectedUserIds
|
||||
.map((id) => FirebaseFirestore.instance.collection('users').doc(id))
|
||||
.map((id) =>
|
||||
FirebaseFirestore.instance.collection('users').doc(id))
|
||||
.toList(),
|
||||
latitude: 0.0,
|
||||
longitude: 0.0,
|
||||
@ -353,9 +415,10 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
_success = "Événement créé avec succès !";
|
||||
});
|
||||
if (context.mounted) Navigator.of(context).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = "Erreur lors de la création : $e";
|
||||
_error = "Erreur lors de la sauvegarde : $e";
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
@ -385,7 +448,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Créer un événement'),
|
||||
title:
|
||||
Text(isEditMode ? 'Modifier un événement' : 'Créer un événement'),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
@ -393,10 +457,7 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
child: Container(
|
||||
// Pas de Card sur mobile, juste un conteneur
|
||||
child: _buildFormContent(isMobile),
|
||||
),
|
||||
)
|
||||
: Card(
|
||||
elevation: 6,
|
||||
@ -764,11 +825,11 @@ class _EventAddPageState extends State<EventAddPage> {
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Créer'),
|
||||
: Text(isEditMode ? 'Enregistrer' : 'Créer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (!isEditMode)
|
||||
Center(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.check_circle, color: Colors.white),
|
||||
|
@ -9,6 +9,10 @@ import 'package:latlong2/latlong.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:em2rp/views/widgets/user_management/user_card.dart';
|
||||
import 'package:em2rp/models/user_model.dart';
|
||||
import 'package:em2rp/views/widgets/user_management/user_multi_select_widget.dart';
|
||||
import 'package:em2rp/views/event_add_page.dart';
|
||||
|
||||
class EventDetails extends StatelessWidget {
|
||||
final EventModel event;
|
||||
@ -93,6 +97,21 @@ class EventDetails extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildStatusIcon(event.status),
|
||||
const SizedBox(width: 8),
|
||||
Spacer(),
|
||||
if (Provider.of<LocalUserProvider>(context, listen: false)
|
||||
.hasPermission('edit_event'))
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit, color: AppColors.rouge),
|
||||
tooltip: 'Modifier',
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EventAddEditPage(event: event),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Provider.of<LocalUserProvider>(context, listen: false)
|
||||
@ -337,6 +356,9 @@ class EventDetails extends StatelessWidget {
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
// --- EQUIPE SECTION ---
|
||||
const SizedBox(height: 16),
|
||||
EquipeSection(workforce: event.workforce),
|
||||
],
|
||||
],
|
||||
),
|
||||
@ -773,3 +795,84 @@ class _FirestoreStatusButtonState extends State<_FirestoreStatusButton> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EquipeSection extends StatelessWidget {
|
||||
final List workforce;
|
||||
const EquipeSection({super.key, required this.workforce});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (workforce.isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Equipe',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
const SizedBox(height: 8),
|
||||
Text('Aucun membre assigné.',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
);
|
||||
}
|
||||
return FutureBuilder<List<UserModel>>(
|
||||
future: _fetchUsers(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(
|
||||
snapshot.error.toString().contains('permission-denied')
|
||||
? "Vous n'avez pas la permission de voir tous les membres de l'équipe."
|
||||
: "Erreur lors du chargement de l'équipe : ${snapshot.error}",
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
final users = snapshot.data ?? [];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Equipe',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
const SizedBox(height: 8),
|
||||
if (users.isEmpty)
|
||||
Text('Aucun membre assigné.',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
if (users.isNotEmpty)
|
||||
UserChipsList(
|
||||
users: users,
|
||||
showRemove: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<UserModel>> _fetchUsers() async {
|
||||
final firestore = FirebaseFirestore.instance;
|
||||
List<UserModel> users = [];
|
||||
for (final ref in workforce) {
|
||||
try {
|
||||
final doc = await firestore.doc(ref.path).get();
|
||||
if (doc.exists) {
|
||||
users.add(
|
||||
UserModel.fromMap(doc.data() as Map<String, dynamic>, doc.id));
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return users;
|
||||
}
|
||||
}
|
||||
|
@ -70,26 +70,15 @@ class _UserMultiSelectState extends State<_UserMultiSelect> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: selectedUsers
|
||||
.map((user) => Chip(
|
||||
avatar: ProfilePictureWidget(userId: user.uid, radius: 28),
|
||||
label: Text('${user.firstName} ${user.lastName}',
|
||||
style: const TextStyle(fontSize: 16)),
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
deleteIcon: const Icon(Icons.close, size: 20),
|
||||
onDeleted: () {
|
||||
UserChipsList(
|
||||
users: selectedUsers,
|
||||
selectedUserIds: widget.selectedUserIds,
|
||||
showRemove: true,
|
||||
onRemove: (uid) {
|
||||
final newList = List<String>.from(widget.selectedUserIds)
|
||||
..remove(user.uid);
|
||||
..remove(uid);
|
||||
widget.onChanged(newList);
|
||||
},
|
||||
backgroundColor: Colors.grey[200],
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
@ -188,3 +177,43 @@ class _UserPickerDialogState extends State<_UserPickerDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserChipsList extends StatelessWidget {
|
||||
final List<UserModel> users;
|
||||
final List<String> selectedUserIds;
|
||||
final ValueChanged<String>? onRemove;
|
||||
final bool showRemove;
|
||||
final double avatarRadius;
|
||||
const UserChipsList({
|
||||
super.key,
|
||||
required this.users,
|
||||
this.selectedUserIds = const [],
|
||||
this.onRemove,
|
||||
this.showRemove = false,
|
||||
this.avatarRadius = 28,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: users
|
||||
.map((user) => Chip(
|
||||
avatar: ProfilePictureWidget(
|
||||
userId: user.uid, radius: avatarRadius),
|
||||
label: Text('${user.firstName} ${user.lastName}',
|
||||
style: const TextStyle(fontSize: 16)),
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
deleteIcon:
|
||||
showRemove ? const Icon(Icons.close, size: 20) : null,
|
||||
onDeleted: showRemove && onRemove != null
|
||||
? () => onRemove!(user.uid)
|
||||
: null,
|
||||
backgroundColor: Colors.grey[200],
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user