import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../services/teacher_api.dart'; import '../widgets/yogibook_background.dart'; /// Aggiungi partecipante a una classe (vista insegnante). /// Flusso: cerca/crea utente -> scegli modalità -> conferma. /// Ritorna `true` (Navigator.pop) se l'inserimento è avvenuto. class TeacherAddParticipantPage extends StatefulWidget { final String token; final TeacherClass cls; const TeacherAddParticipantPage({ super.key, required this.token, required this.cls, }); @override State createState() => _TeacherAddParticipantPageState(); } class _TeacherAddParticipantPageState extends State { static const Color kGreen = Color(0xFF10B981); final _firstCtrl = TextEditingController(); final _lastCtrl = TextEditingController(); final _emailCtrl = TextEditingController(); // ricerca List _results = []; bool _searching = false; // utente selezionato (null = nuovo utente da creare) TeacherUser? _selectedUser; bool _isNewUser = false; // ordini dell'utente selezionato List _orders = []; bool _loadingOrders = false; // modalità: 'scala' | 'omaggio' | 'nuovo' String _mode = 'omaggio'; int? _selectedOrderId; DateTime _newExpiry = DateTime.now().add(const Duration(days: 90)); bool _submitting = false; @override void dispose() { _firstCtrl.dispose(); _lastCtrl.dispose(); _emailCtrl.dispose(); super.dispose(); } void _snack(String msg) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } Future _search() async { final f = _firstCtrl.text.trim(); final l = _lastCtrl.text.trim(); if (f.length < 2 && l.length < 2) { setState(() => _results = []); return; } setState(() => _searching = true); try { final r = await TeacherApi.searchUsers( token: widget.token, first: f, last: l, ); if (!mounted) return; setState(() => _results = r); } catch (e) { _snack('$e'); } finally { if (mounted) setState(() => _searching = false); } } Future _selectUser(TeacherUser u) async { setState(() { _selectedUser = u; _isNewUser = false; _results = []; _firstCtrl.text = u.firstName; _lastCtrl.text = u.lastName; _emailCtrl.text = u.email; _loadingOrders = true; _orders = []; }); try { final o = await TeacherApi.userOrders(token: widget.token, userId: u.id); if (!mounted) return; // default modalità: scala se ha pacchetti con residui, altrimenti omaggio final usable = o.where((x) => x.remaining > 0).toList(); setState(() { _orders = o; if (usable.isNotEmpty) { _mode = 'scala'; _selectedOrderId = usable.first.idOrderBook; } else { _mode = 'omaggio'; } }); // DEBUG temporaneo — rimuovere dopo diagnosi _snack('DEBUG ordini: ${o.length} totali, ${usable.length} con residui'); // DEBUG temporaneo _snack('DEBUG: ${o.length} ordini totali, ${usable.length} con residui'); } catch (e) { _snack('$e'); } finally { if (mounted) setState(() => _loadingOrders = false); } } // segna che vogliamo creare un nuovo utente coi dati digitati void _useAsNewUser() { final f = _firstCtrl.text.trim(); final l = _lastCtrl.text.trim(); if (f.isEmpty || l.isEmpty) { _snack('Inserisci nome e cognome del nuovo utente.'); return; } setState(() { _selectedUser = null; _isNewUser = true; _results = []; _orders = []; _mode = 'omaggio'; // nuovo utente non ha pacchetti }); } void _clearSelection() { setState(() { _selectedUser = null; _isNewUser = false; _orders = []; _selectedOrderId = null; _mode = 'omaggio'; }); } Future _confirm() async { // deve esserci un utente selezionato o un nuovo utente valido if (_selectedUser == null && !_isNewUser) { _snack('Seleziona un utente o creane uno nuovo.'); return; } if (_mode == 'scala' && _selectedOrderId == null) { _snack('Seleziona un pacchetto da cui scalare.'); return; } setState(() => _submitting = true); try { await TeacherApi.addBooking( token: widget.token, scheduleId: widget.cls.scheduleId, serviceId: widget.cls.serviceId, bookingStart: widget.cls.dateTime, mode: _mode, userId: _selectedUser?.id ?? 0, name: _isNewUser ? _firstCtrl.text.trim() : '', surname: _isNewUser ? _lastCtrl.text.trim() : '', email: _emailCtrl.text.trim(), orderId: _mode == 'scala' ? _selectedOrderId : null, newExpiry: _mode == 'nuovo' ? DateFormat('yyyy-MM-dd').format(_newExpiry) : null, ); if (!mounted) return; Navigator.pop(context, true); } catch (e) { _snack('$e'); setState(() => _submitting = false); } } @override Widget build(BuildContext context) { final hasSelection = _selectedUser != null || _isNewUser; return Scaffold( appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, title: const Text( 'Aggiungi partecipante', style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18), ), ), body: YogibookBackground( child: SafeArea( top: false, child: ListView( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), children: [ _classBanner(), const SizedBox(height: 14), _searchFields(), if (!hasSelection) ...[const SizedBox(height: 8), _resultsBox()], if (hasSelection) ...[ const SizedBox(height: 14), _selectionCard(), const SizedBox(height: 14), _modeSection(), const SizedBox(height: 20), _confirmButton(), ], ], ), ), ), ); } Widget _classBanner() { final dt = DateTime.tryParse(widget.cls.dateTime); final when = dt == null ? widget.cls.dateTime : DateFormat("EEE d MMM, HH:mm", 'it_IT').format(dt); return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), boxShadow: const [ BoxShadow( blurRadius: 12, color: Color(0x0F000000), offset: Offset(0, 6), ), ], ), child: Row( children: [ const Icon(Icons.class_, color: kGreen), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.cls.className, style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 15, ), ), Text( '$when · ${widget.cls.bookedCount}/${widget.cls.maxCapacity} posti', style: const TextStyle(fontSize: 12, color: Colors.black54), ), ], ), ), ], ), ); } Widget _searchFields() { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), boxShadow: const [ BoxShadow( blurRadius: 12, color: Color(0x0F000000), offset: Offset(0, 6), ), ], ), child: Column( children: [ Row( children: [ Expanded( child: TextField( controller: _firstCtrl, onChanged: (_) => _search(), decoration: const InputDecoration( labelText: 'Nome', isDense: true, border: OutlineInputBorder(), ), ), ), const SizedBox(width: 10), Expanded( child: TextField( controller: _lastCtrl, onChanged: (_) => _search(), decoration: const InputDecoration( labelText: 'Cognome', isDense: true, border: OutlineInputBorder(), ), ), ), ], ), const SizedBox(height: 10), TextField( controller: _emailCtrl, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email (per nuovo utente)', isDense: true, border: OutlineInputBorder(), ), ), ], ), ); } Widget _resultsBox() { if (_searching) { return const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()), ); } final f = _firstCtrl.text.trim(); final l = _lastCtrl.text.trim(); if (f.length < 2 && l.length < 2) { return const Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Text( 'Digita almeno 2 lettere per cercare.', style: TextStyle(color: Colors.black45), ), ); } return Column( children: [ ..._results.map( (u) => Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: ListTile( leading: CircleAvatar( backgroundColor: kGreen, child: Text( ((u.firstName.isNotEmpty ? u.firstName[0] : '') + (u.lastName.isNotEmpty ? u.lastName[0] : '')) .toUpperCase(), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13, ), ), ), title: Text( u.fullName, style: const TextStyle(fontWeight: FontWeight.w700), ), subtitle: Text(u.email), onTap: () => _selectUser(u), ), ), ), // opzione "crea nuovo utente" con i dati digitati Container( margin: const EdgeInsets.only(top: 4), decoration: BoxDecoration( color: const Color(0xFFF3F0FF), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFD9CFFB)), ), child: ListTile( leading: const Icon(Icons.person_add, color: Color(0xFF4F46E5)), title: const Text( 'Crea nuovo utente', style: TextStyle(fontWeight: FontWeight.w800), ), subtitle: const Text('Usa nome, cognome ed email digitati sopra'), onTap: _useAsNewUser, ), ), ], ); } Widget _selectionCard() { final title = _isNewUser ? '${_firstCtrl.text.trim()} ${_lastCtrl.text.trim()} (nuovo)' : _selectedUser!.fullName; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFE7F8F1), borderRadius: BorderRadius.circular(14), border: Border.all(color: kGreen), ), child: Row( children: [ const Icon(Icons.check_circle, color: kGreen), const SizedBox(width: 10), Expanded( child: Text( title, style: const TextStyle(fontWeight: FontWeight.w800), ), ), TextButton(onPressed: _clearSelection, child: const Text('Cambia')), ], ), ); } Widget _modeSection() { final usable = _orders.where((o) => o.remaining > 0).toList(); return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), boxShadow: const [ BoxShadow( blurRadius: 12, color: Color(0x0F000000), offset: Offset(0, 6), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Modalità', style: TextStyle(fontWeight: FontWeight.w900, fontSize: 14), ), const SizedBox(height: 6), if (_loadingOrders) const Padding( padding: EdgeInsets.all(12), child: Center(child: CircularProgressIndicator()), ) else ...[ // scala (solo se ci sono pacchetti usabili) RadioListTile( contentPadding: EdgeInsets.zero, activeColor: kGreen, value: 'scala', groupValue: _mode, onChanged: usable.isEmpty ? null : (v) => setState(() => _mode = v!), title: Text( usable.isEmpty ? 'Scala da pacchetto (nessuno disponibile)' : 'Scala da un pacchetto', style: TextStyle( fontWeight: FontWeight.w700, color: usable.isEmpty ? Colors.black38 : Colors.black87, ), ), ), if (_mode == 'scala' && usable.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 12, bottom: 8), child: Column( children: usable .map( (o) => RadioListTile( contentPadding: EdgeInsets.zero, activeColor: kGreen, value: o.idOrderBook, groupValue: _selectedOrderId, onChanged: (v) => setState(() => _selectedOrderId = v), title: Text( o.serviceName, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, ), ), subtitle: Text( '${o.remaining}/${o.tickets} residui' '${o.expireOn != null ? ' · scade ${o.expireOn}' : ''}', style: const TextStyle(fontSize: 12), ), ), ) .toList(), ), ), // omaggio RadioListTile( contentPadding: EdgeInsets.zero, activeColor: kGreen, value: 'omaggio', groupValue: _mode, onChanged: (v) => setState(() => _mode = v!), title: const Text( 'Aggiungi come omaggio', style: TextStyle(fontWeight: FontWeight.w700), ), subtitle: const Text( 'Non consuma pacchetti, non spostabile', style: TextStyle(fontSize: 12), ), ), // nuovo ordine RadioListTile( contentPadding: EdgeInsets.zero, activeColor: kGreen, value: 'nuovo', groupValue: _mode, onChanged: (v) => setState(() => _mode = v!), title: const Text( 'Crea nuovo ordine (1 lezione)', style: TextStyle(fontWeight: FontWeight.w700), ), ), if (_mode == 'nuovo') Padding( padding: const EdgeInsets.only(left: 12, top: 4), child: Row( children: [ const Icon(Icons.event, size: 18, color: Colors.black45), const SizedBox(width: 8), Text( 'Scadenza: ${DateFormat('dd/MM/yyyy').format(_newExpiry)}', style: const TextStyle(fontWeight: FontWeight.w600), ), const Spacer(), TextButton( onPressed: () async { final picked = await showDatePicker( context: context, initialDate: _newExpiry, firstDate: DateTime.now(), lastDate: DateTime.now().add( const Duration(days: 730), ), ); if (picked != null) { setState(() => _newExpiry = picked); } }, child: const Text('Cambia'), ), ], ), ), ], ], ), ); } Widget _confirmButton() { return SizedBox( width: double.infinity, height: 50, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: kGreen, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), onPressed: _submitting ? null : _confirm, child: _submitting ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text( 'Aggiungi alla classe', style: TextStyle( fontWeight: FontWeight.w900, color: Colors.white, ), ), ), ); } }