Equipe sur event details OK
Modif evenement OK
This commit is contained in:
@ -13,7 +13,7 @@ import 'views/user_management_page.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'providers/local_user_provider.dart';
|
import 'providers/local_user_provider.dart';
|
||||||
import 'services/user_service.dart';
|
import 'services/user_service.dart';
|
||||||
import 'pages/auth/reset_password_page.dart';
|
import 'views/reset_password_page.dart';
|
||||||
import 'config/env.dart';
|
import 'config/env.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
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
|
// ignore: avoid_web_libraries_in_flutter
|
||||||
import 'dart:html' as html;
|
import 'dart:html' as html;
|
||||||
|
|
||||||
class EventAddPage extends StatefulWidget {
|
class EventAddEditPage extends StatefulWidget {
|
||||||
const EventAddPage({super.key});
|
final EventModel? event;
|
||||||
|
const EventAddEditPage({super.key, this.event});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EventAddPage> createState() => _EventAddPageState();
|
State<EventAddEditPage> createState() => _EventAddEditPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EventAddPageState extends State<EventAddPage> {
|
class _EventAddEditPageState extends State<EventAddEditPage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final TextEditingController _nameController = TextEditingController();
|
final TextEditingController _nameController = TextEditingController();
|
||||||
final TextEditingController _descriptionController = TextEditingController();
|
final TextEditingController _descriptionController = TextEditingController();
|
||||||
@ -61,6 +62,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
bool _formChanged = false;
|
bool _formChanged = false;
|
||||||
EventStatus _selectedStatus = EventStatus.waitingForApproval;
|
EventStatus _selectedStatus = EventStatus.waitingForApproval;
|
||||||
|
|
||||||
|
bool get isEditMode => widget.event != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -73,8 +76,25 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
_addressController.addListener(_onAnyFieldChanged);
|
_addressController.addListener(_onAnyFieldChanged);
|
||||||
_descriptionController.addListener(_onAnyFieldChanged);
|
_descriptionController.addListener(_onAnyFieldChanged);
|
||||||
_addBeforeUnloadListener();
|
_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;
|
_selectedStatus = EventStatus.waitingForApproval;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _handleDescriptionChange() {
|
void _handleDescriptionChange() {
|
||||||
final lines = '\n'.allMatches(_descriptionController.text).length + 1;
|
final lines = '\n'.allMatches(_descriptionController.text).length + 1;
|
||||||
@ -284,6 +304,47 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
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(
|
final newEvent = EventModel(
|
||||||
id: '',
|
id: '',
|
||||||
name: _nameController.text.trim(),
|
name: _nameController.text.trim(),
|
||||||
@ -297,7 +358,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
customerId: '',
|
customerId: '',
|
||||||
address: _addressController.text.trim(),
|
address: _addressController.text.trim(),
|
||||||
workforce: _selectedUserIds
|
workforce: _selectedUserIds
|
||||||
.map((id) => FirebaseFirestore.instance.collection('users').doc(id))
|
.map((id) =>
|
||||||
|
FirebaseFirestore.instance.collection('users').doc(id))
|
||||||
.toList(),
|
.toList(),
|
||||||
latitude: 0.0,
|
latitude: 0.0,
|
||||||
longitude: 0.0,
|
longitude: 0.0,
|
||||||
@ -353,9 +415,10 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
_success = "Événement créé avec succès !";
|
_success = "Événement créé avec succès !";
|
||||||
});
|
});
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = "Erreur lors de la création : $e";
|
_error = "Erreur lors de la sauvegarde : $e";
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -385,7 +448,8 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
onWillPop: _onWillPop,
|
onWillPop: _onWillPop,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Créer un événement'),
|
title:
|
||||||
|
Text(isEditMode ? 'Modifier un événement' : 'Créer un événement'),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -393,10 +457,7 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16, vertical: 12),
|
horizontal: 16, vertical: 12),
|
||||||
child: Container(
|
|
||||||
// Pas de Card sur mobile, juste un conteneur
|
|
||||||
child: _buildFormContent(isMobile),
|
child: _buildFormContent(isMobile),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: Card(
|
: Card(
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
@ -764,11 +825,11 @@ class _EventAddPageState extends State<EventAddPage> {
|
|||||||
height: 20,
|
height: 20,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Text('Créer'),
|
: Text(isEditMode ? 'Enregistrer' : 'Créer'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
if (!isEditMode)
|
||||||
Center(
|
Center(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.check_circle, color: Colors.white),
|
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:url_launcher/url_launcher.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
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 {
|
class EventDetails extends StatelessWidget {
|
||||||
final EventModel event;
|
final EventModel event;
|
||||||
@ -93,6 +97,20 @@ class EventDetails extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_buildStatusIcon(event.status),
|
_buildStatusIcon(event.status),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
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)
|
if (Provider.of<LocalUserProvider>(context, listen: false)
|
||||||
@ -337,6 +355,9 @@ class EventDetails extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
// --- EQUIPE SECTION ---
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
EquipeSection(workforce: event.workforce),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -773,3 +794,80 @@ 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('Erreur lors du chargement de l\'équipe',
|
||||||
|
style: 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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
UserChipsList(
|
||||||
spacing: 12,
|
users: selectedUsers,
|
||||||
runSpacing: 12,
|
selectedUserIds: widget.selectedUserIds,
|
||||||
children: selectedUsers
|
showRemove: true,
|
||||||
.map((user) => Chip(
|
onRemove: (uid) {
|
||||||
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: () {
|
|
||||||
final newList = List<String>.from(widget.selectedUserIds)
|
final newList = List<String>.from(widget.selectedUserIds)
|
||||||
..remove(user.uid);
|
..remove(uid);
|
||||||
widget.onChanged(newList);
|
widget.onChanged(newList);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.grey[200],
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
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