import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../models/lesson.dart'; import '../models/school.dart'; import '../services/lessons_api.dart'; import 'select_school_page.dart'; import 'home_page.dart'; import 'meditation_page.dart'; import '../widgets/app_drawer.dart'; import '../widgets/app_bottom_nav.dart'; import '../widgets/yogibook_background.dart'; import 'reschedule_page.dart'; import 'orders_page.dart'; class LessonsPage extends StatefulWidget { final String token; final School school; final String? userFirstName; const LessonsPage({ super.key, required this.token, required this.school, this.userFirstName, }); @override State createState() => _LessonsPageState(); } class _LessonsPageState extends State { bool loading = true; String error = ''; String currentMonth = DateFormat('yyyy-MM').format(DateTime.now()); LessonsSummary? summary; List lessons = []; int bottomIndex = 1; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { loading = true; error = ''; }); try { final data = await LessonsApi.fetchMyLessons( token: widget.token, month: currentMonth, ); setState(() { summary = data.summary; lessons = data.lessons; }); } catch (e) { setState(() => error = 'Errore: $e'); } finally { setState(() => loading = false); } } // Navigazione mesi calcolata lato client (l'API non restituisce prev/next). String _shiftMonth(String yyyyMm, int delta) { final dt = DateFormat('yyyy-MM').parse(yyyyMm); final shifted = DateTime(dt.year, dt.month + delta, 1); return DateFormat('yyyy-MM').format(shifted); } void _goPrevMonth() { setState(() => currentMonth = _shiftMonth(currentMonth, -1)); _load(); } void _goNextMonth() { setState(() => currentMonth = _shiftMonth(currentMonth, 1)); _load(); } String _monthLabel(String yyyyMm) { final dt = DateFormat('yyyy-MM').parse(yyyyMm); return DateFormat('MMMM yyyy', 'it_IT').format(dt); } String _weekdayLabel(String ymd) { final dt = DateFormat('yyyy-MM-dd').parse(ymd); final s = DateFormat('EEEE', 'it_IT').format(dt); return s[0].toUpperCase() + s.substring(1); } String _dayNum(String ymd) { final dt = DateFormat('yyyy-MM-dd').parse(ymd); return DateFormat('dd').format(dt); } String get _name => (widget.userFirstName ?? '').trim(); String get _avatarLetter => _name.isNotEmpty ? _name[0].toUpperCase() : 'U'; void _handleBottomNav(int i) { if (i == bottomIndex) return; setState(() => bottomIndex = i); if (i == 0) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => HomePage( token: widget.token, school: widget.school, userFirstName: widget.userFirstName, ), ), ); return; } if (i == 1) return; // sei giĆ  su Lezioni if (i == 2) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('TODO: vai ad Account'))); return; } if (i == 3) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => MeditationPage( token: widget.token, school: widget.school, userFirstName: widget.userFirstName, ), ), ); return; } } @override Widget build(BuildContext context) { return Scaffold( drawer: AppDrawer( token: widget.token, school: widget.school, userFirstName: widget.userFirstName, ), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, title: Column( children: [ const Text( 'Le mie lezioni', style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18), ), const SizedBox(height: 2), Text( widget.school.name, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black54, ), ), ], ), actions: [ Padding( padding: const EdgeInsets.only(right: 12), child: CircleAvatar( radius: 16, backgroundColor: const Color(0xFF10B981), child: Text( _avatarLetter, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w900, fontSize: 12, ), ), ), ), ], ), bottomNavigationBar: AppBottomNav( currentIndex: bottomIndex, onTap: _handleBottomNav, ), body: YogibookBackground( child: SafeArea( top: false, child: Column( children: [ // Riepilogo conteggi (le card pastello della webapp) if (summary != null) Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 4), child: _SummaryStrip(summary: summary!), ), // Selettore mese Padding( padding: const EdgeInsets.fromLTRB(16, 6, 16, 4), child: _MonthPillCompact( label: _monthLabel(currentMonth), onPrev: _goPrevMonth, onNext: _goNextMonth, ), ), // Bottone "Programma lezioni" (solo se ci sono ticket residui) if (summary != null && summary!.toSchedule > 0) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: SizedBox( width: double.infinity, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF10B981), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), icon: const Icon(Icons.add, color: Colors.white), label: Text( 'Programma lezioni (${summary!.toSchedule} disponibili)', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w800, ), ), onPressed: () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => OrdersPage(token: widget.token), ), ); _load(); }, ), ), ), Expanded( child: loading ? const Center(child: CircularProgressIndicator()) : error.isNotEmpty ? _ErrorBox(message: error, onRetry: _load) : lessons.isEmpty ? _EmptyState( onBrowse: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('TODO: vedi corsi')), ); }, ) : ListView.builder( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), itemCount: lessons.length + 1, itemBuilder: (_, i) { if (i == lessons.length) { return Padding( padding: const EdgeInsets.only(top: 8, bottom: 6), child: Center( child: Image.asset( 'assets/images/yogibook_logo.png', height: 56, ), ), ); } final l = lessons[i]; return _LessonGreenCardCompact( lesson: l, weekday: _weekdayLabel(l.date), dayNum: _dayNum(l.date), onReschedule: l.canReschedule ? () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => ReschedulePage( token: widget.token, bookingId: l.bookingId, serviceId: l.serviceId, className: l.className, ), ), ); _load(); } : null, onCancel: l.canDelete ? () async { final conferma = await showDialog( context: context, builder: (dialogCtx) => AlertDialog( title: const Text( 'Cancellare la lezione?', ), content: Text( 'Vuoi cancellare "${l.className}" del ${l.date}? ' 'Ricordati di riprogrammarla entro la scadenza.', ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogCtx, false), child: const Text('Annulla'), ), ElevatedButton( onPressed: () => Navigator.pop(dialogCtx, true), child: const Text('Cancella'), ), ], ), ); if (conferma != true) return; try { await LessonsApi.cancelLesson( token: widget.token, bookingId: l.bookingId, ); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar( const SnackBar( content: Text('Lezione cancellata'), ), ); _load(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar( SnackBar(content: Text('$e')), ); } } : null, ); }, ), ), ], ), ), ), ); } } class _SummaryStrip extends StatelessWidget { final LessonsSummary summary; const _SummaryStrip({required this.summary}); @override Widget build(BuildContext context) { final items = <_SummaryItem>[ _SummaryItem('Acquistate', summary.purchased, const Color(0xFFE2C4FB)), _SummaryItem('Praticate', summary.practiced, const Color(0xFFC4E1FB)), _SummaryItem('Prenotate', summary.booked, const Color(0xFFCDFBC4)), _SummaryItem('Da confermare', summary.pending, const Color(0xFFFBFAC4)), _SummaryItem( 'Da programmare', summary.toSchedule, const Color(0xFFFBE4C4), ), _SummaryItem('Perse', summary.lost, const Color(0xFFFBC7C4)), ]; return SizedBox( height: 76, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: items.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (_, i) { final it = items[i]; return Container( width: 96, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: it.color, borderRadius: BorderRadius.circular(12), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '${it.value}', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w900, ), ), const SizedBox(height: 2), Text( it.label, textAlign: TextAlign.center, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w700, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ); }, ), ); } } class _SummaryItem { final String label; final int value; final Color color; _SummaryItem(this.label, this.value, this.color); } class _MonthPillCompact extends StatelessWidget { final String label; final VoidCallback? onPrev; final VoidCallback? onNext; const _MonthPillCompact({ required this.label, required this.onPrev, required this.onNext, }); @override Widget build(BuildContext context) { const green = Color(0xFF10B981); return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow( blurRadius: 14, color: Color(0x11000000), offset: Offset(0, 8), ), ], ), child: Row( children: [ IconButton( onPressed: onPrev, icon: const Icon(Icons.chevron_left), iconSize: 22, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 40, minHeight: 40), color: onPrev == null ? Colors.black26 : green, ), Expanded( child: Text( label, textAlign: TextAlign.center, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w900), ), ), IconButton( onPressed: onNext, icon: const Icon(Icons.chevron_right), iconSize: 22, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 40, minHeight: 40), color: onNext == null ? Colors.black26 : green, ), ], ), ); } } class _LessonGreenCardCompact extends StatelessWidget { final Lesson lesson; final String weekday; final String dayNum; final VoidCallback? onReschedule; final VoidCallback? onCancel; const _LessonGreenCardCompact({ required this.lesson, required this.weekday, required this.dayNum, required this.onReschedule, required this.onCancel, }); @override Widget build(BuildContext context) { const green = Color(0xFF10B981); const darkGreen = Color(0xFF065F46); final time = _shortTime(lesson.time); final canModify = lesson.canReschedule || lesson.canDelete; return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFFE7F8F1), borderRadius: BorderRadius.circular(18), boxShadow: const [ BoxShadow( blurRadius: 18, color: Color(0x12000000), offset: Offset(0, 10), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(18), child: Row( children: [ // left panel Container( width: 92, color: const Color(0xFFBFF3DE), padding: const EdgeInsets.fromLTRB(10, 12, 10, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( dayNum, style: const TextStyle( fontSize: 28, height: 1, fontWeight: FontWeight.w900, color: darkGreen, ), ), const SizedBox(height: 4), Text( weekday, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: darkGreen, ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 6, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.6), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.schedule, size: 14, color: Colors.black54, ), const SizedBox(width: 6), FittedBox( fit: BoxFit.scaleDown, child: Text( time, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w800, ), ), ), ], ), ), ], ), ), // right body Expanded( child: Container( padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), color: const Color(0xFFE7F8F1), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( lesson.className, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w900, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), Row( children: [ const Icon( Icons.place_outlined, size: 16, color: Colors.black54, ), const SizedBox(width: 6), Expanded( child: Text( lesson.location.trim().isEmpty ? 'Sala da definire' : lesson.location, style: const TextStyle( fontSize: 12, color: Color(0xFF404040), fontWeight: FontWeight.w600, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 8), Row( children: [ Expanded( child: SizedBox( height: 30, child: ElevatedButton( onPressed: onReschedule, style: ElevatedButton.styleFrom( backgroundColor: green, padding: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'Riprogramma', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w800, ), ), ), ), ), const SizedBox(width: 8), Expanded( child: SizedBox( height: 30, child: OutlinedButton( onPressed: onCancel, style: OutlinedButton.styleFrom( foregroundColor: green, side: const BorderSide(color: green, width: 2), padding: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text( 'Cancella', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w800, ), ), ), ), ), ], ), if (!canModify) ...[ const SizedBox(height: 6), const Text( 'Non modificabile', style: TextStyle(fontSize: 11, color: Colors.black54), ), ], ], ), ), ), ], ), ), ); } static String _shortTime(String t) => t.length >= 5 ? t.substring(0, 5) : t; } class _EmptyState extends StatelessWidget { final VoidCallback onBrowse; const _EmptyState({required this.onBrowse}); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(18), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.event_busy, size: 64, color: Colors.black26), const SizedBox(height: 14), const Text( 'Nessuna lezione prenotata', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: Colors.black54, ), ), const SizedBox(height: 6), const Text( 'Le tue lezioni appariranno qui', style: TextStyle(color: Colors.black45), ), const SizedBox(height: 14), ElevatedButton( onPressed: onBrowse, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF10B981), padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), child: const Text('Vedi i corsi'), ), ], ), ), ); } } class _ErrorBox extends StatelessWidget { final String message; final VoidCallback onRetry; const _ErrorBox({required this.message, required this.onRetry}); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(18), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 58, color: Colors.redAccent), const SizedBox(height: 10), Text(message, textAlign: TextAlign.center), const SizedBox(height: 12), ElevatedButton(onPressed: onRetry, child: const Text('Riprova')), ], ), ), ); } }