Modifications des permissions, ajout Presta OK, vue calendrier ok
This commit is contained in:
410
em2rp/lib/views/pages/event_add_page.dart
Normal file
410
em2rp/lib/views/pages/event_add_page.dart
Normal file
@ -0,0 +1,410 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:em2rp/providers/event_provider.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:em2rp/models/event_model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:em2rp/views/widgets/inputs/int_stepper_field.dart';
|
||||
|
||||
class EventAddPage extends StatefulWidget {
|
||||
const EventAddPage({super.key});
|
||||
|
||||
@override
|
||||
State<EventAddPage> createState() => _EventAddPageState();
|
||||
}
|
||||
|
||||
class _EventAddPageState extends State<EventAddPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _descriptionController = TextEditingController();
|
||||
final TextEditingController _priceController = TextEditingController();
|
||||
final TextEditingController _installationController = TextEditingController();
|
||||
final TextEditingController _disassemblyController = TextEditingController();
|
||||
final TextEditingController _latitudeController = TextEditingController();
|
||||
final TextEditingController _longitudeController = TextEditingController();
|
||||
DateTime? _startDateTime;
|
||||
DateTime? _endDateTime;
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
String? _success;
|
||||
String? _selectedEventType;
|
||||
final List<String> _eventTypes = ['Bal', 'Mariage', 'Anniversaire'];
|
||||
int _descriptionMaxLines = 3;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_descriptionController.addListener(_handleDescriptionChange);
|
||||
}
|
||||
|
||||
void _handleDescriptionChange() {
|
||||
final lines = '\n'.allMatches(_descriptionController.text).length + 1;
|
||||
setState(() {
|
||||
_descriptionMaxLines = lines.clamp(3, 6);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_descriptionController.dispose();
|
||||
_priceController.dispose();
|
||||
_installationController.dispose();
|
||||
_disassemblyController.dispose();
|
||||
_latitudeController.dispose();
|
||||
_longitudeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (!_formKey.currentState!.validate() ||
|
||||
_startDateTime == null ||
|
||||
_endDateTime == null ||
|
||||
_selectedEventType == null) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
_success = null;
|
||||
});
|
||||
try {
|
||||
final eventProvider = Provider.of<EventProvider>(context, listen: false);
|
||||
final newEvent = EventModel(
|
||||
id: '',
|
||||
name: _nameController.text.trim(),
|
||||
description: _descriptionController.text.trim(),
|
||||
startDateTime: _startDateTime!,
|
||||
endDateTime: _endDateTime!,
|
||||
price: double.tryParse(_priceController.text) ?? 0.0,
|
||||
installationTime: int.tryParse(_installationController.text) ?? 0,
|
||||
disassemblyTime: int.tryParse(_disassemblyController.text) ?? 0,
|
||||
eventTypeId: _selectedEventType!,
|
||||
customerId: '', // à adapter si tu veux gérer les clients
|
||||
address: LatLng(
|
||||
double.tryParse(_latitudeController.text) ?? 0.0,
|
||||
double.tryParse(_longitudeController.text) ?? 0.0,
|
||||
),
|
||||
workforce: [],
|
||||
);
|
||||
await eventProvider.addEvent(newEvent);
|
||||
setState(() {
|
||||
_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";
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildSectionTitle(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Créer un événement'),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Card(
|
||||
elevation: 6,
|
||||
margin: const EdgeInsets.all(24),
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 32),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 0.0, bottom: 4.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Informations principales',
|
||||
style: const TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Nom de l\'événement',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.event),
|
||||
),
|
||||
validator: (v) =>
|
||||
v == null || v.isEmpty ? 'Champ requis' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedEventType,
|
||||
items: _eventTypes
|
||||
.map((type) => DropdownMenuItem<String>(
|
||||
value: type,
|
||||
child: Text(type),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (val) =>
|
||||
setState(() => _selectedEventType = val),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Type d\'événement',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.category),
|
||||
),
|
||||
validator: (v) =>
|
||||
v == null ? 'Sélectionnez un type' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2099),
|
||||
);
|
||||
if (picked != null) {
|
||||
final time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (time != null) {
|
||||
setState(() {
|
||||
_startDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
time.hour,
|
||||
time.minute,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Début',
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.calendar_today),
|
||||
suffixIcon: const Icon(Icons.edit_calendar),
|
||||
),
|
||||
controller: TextEditingController(
|
||||
text: _startDateTime == null
|
||||
? ''
|
||||
: DateFormat('dd/MM/yyyy HH:mm')
|
||||
.format(_startDateTime!),
|
||||
),
|
||||
validator: (v) => _startDateTime == null
|
||||
? 'Champ requis'
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _startDateTime ?? DateTime.now(),
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2099),
|
||||
);
|
||||
if (picked != null) {
|
||||
final time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
if (time != null) {
|
||||
setState(() {
|
||||
_endDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
time.hour,
|
||||
time.minute,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
child: TextFormField(
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Fin',
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.calendar_today),
|
||||
suffixIcon: const Icon(Icons.edit_calendar),
|
||||
),
|
||||
controller: TextEditingController(
|
||||
text: _endDateTime == null
|
||||
? ''
|
||||
: DateFormat('dd/MM/yyyy HH:mm')
|
||||
.format(_endDateTime!),
|
||||
),
|
||||
validator: (v) => _endDateTime == null
|
||||
? 'Champ requis'
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _priceController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Prix (€)',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.euro),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
_buildSectionTitle('Détails'),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 48,
|
||||
maxHeight: 48.0 * 10,
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: _descriptionController,
|
||||
minLines: 1,
|
||||
maxLines: _descriptionMaxLines > 10
|
||||
? 10
|
||||
: _descriptionMaxLines,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Description',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.description),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: IntStepperField(
|
||||
label: 'Installation (h)',
|
||||
controller: _installationController,
|
||||
min: 0,
|
||||
max: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: IntStepperField(
|
||||
label: 'Démontage (h)',
|
||||
controller: _disassemblyController,
|
||||
min: 0,
|
||||
max: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildSectionTitle('Localisation'),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _latitudeController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Latitude',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.location_on),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _longitudeController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Longitude',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.location_on),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_error != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(_error!,
|
||||
style: const TextStyle(color: Colors.red)),
|
||||
),
|
||||
if (_success != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(_success!,
|
||||
style: const TextStyle(color: Colors.green)),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => Navigator.of(context).pop(),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.check),
|
||||
onPressed: _isLoading ? null : _submit,
|
||||
label: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Créer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user