Files
yogibook_aury_app/lib/screens/reschedule_page.dart
T
2026-08-11 07:54:22 +02:00

265 lines
8.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/lesson.dart';
import '../services/lessons_api.dart';
import '../widgets/yogibook_background.dart';
/// Schermata per scegliere uno slot.
/// Due modalità:
/// - RIPROGRAMMA: passa bookingId (+ serviceId della lezione)
/// - PRENOTA: passa orderId (+ serviceId dell'ordine)
class ReschedulePage extends StatefulWidget {
final String token;
final int serviceId;
final String className;
// Modalità riprogramma
final int? bookingId;
// Modalità prenota da ticket
final int? orderId;
const ReschedulePage({
super.key,
required this.token,
required this.serviceId,
required this.className,
this.bookingId,
this.orderId,
});
bool get isBooking => orderId != null;
@override
State<ReschedulePage> createState() => _ReschedulePageState();
}
class _ReschedulePageState extends State<ReschedulePage> {
bool loading = true;
String error = '';
String currentMonth = DateFormat('yyyy-MM').format(DateTime.now());
List<AvailableSlot> slots = [];
@override
void initState() {
super.initState();
_load();
}
Future<void> _load() async {
setState(() {
loading = true;
error = '';
});
try {
final data = await LessonsApi.fetchAvailableSlots(
token: widget.token,
serviceId: widget.serviceId,
month: currentMonth,
);
setState(() => slots = data);
} catch (e) {
setState(() => error = 'Errore: $e');
} finally {
setState(() => loading = false);
}
}
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);
}
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);
}
@override
Widget build(BuildContext context) {
const green = Color(0xFF10B981);
final titolo = widget.isBooking ? 'Prenota lezione' : 'Riprogramma';
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Column(
children: [
Text(
titolo,
style: const TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
Text(
widget.className,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.black54,
),
),
],
),
centerTitle: true,
),
body: YogibookBackground(
child: SafeArea(
top: false,
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 10),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.chevron_left),
color: green,
onPressed: () {
setState(
() => currentMonth = _shiftMonth(currentMonth, -1),
);
_load();
},
),
Expanded(
child: Text(
_monthLabel(currentMonth),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w900,
),
),
),
IconButton(
icon: const Icon(Icons.chevron_right),
color: green,
onPressed: () {
setState(
() => currentMonth = _shiftMonth(currentMonth, 1),
);
_load();
},
),
],
),
),
),
Expanded(
child: loading
? const Center(child: CircularProgressIndicator())
: error.isNotEmpty
? Center(child: Text(error))
: slots.isEmpty
? const Center(
child: Text('Nessuno slot disponibile questo mese'),
)
: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
itemCount: slots.length,
itemBuilder: (_, i) {
final s = slots[i];
return Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
title: Text(
s.className,
style: const TextStyle(
fontWeight: FontWeight.w800,
),
),
subtitle: Text(
'${_weekdayLabel(s.date)} ${s.date}${s.time}\n'
'Posti liberi: ${s.freePlaces}/${s.maxCapacity}'
'${s.alreadyBooked ? " • Sei già prenotato" : ""}',
),
isThreeLine: true,
trailing: s.bookable
? ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: green,
),
onPressed: () => _confirm(s),
child: const Text('Scegli'),
)
: const Text(
'Non disp.',
style: TextStyle(
color: Colors.black38,
fontWeight: FontWeight.w700,
),
),
),
);
},
),
),
],
),
),
),
);
}
Future<void> _confirm(AvailableSlot s) async {
final azione = widget.isBooking ? 'prenotare' : 'spostare';
final ok = await showDialog<bool>(
context: context,
builder: (dialogCtx) => AlertDialog(
title: Text('Confermi?'),
content: Text(
'Vuoi $azione "${s.className}" del ${s.date} alle ${s.time}?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogCtx, false),
child: const Text('Annulla'),
),
ElevatedButton(
onPressed: () => Navigator.pop(dialogCtx, true),
child: const Text('Conferma'),
),
],
),
);
if (ok != true) return;
try {
final String msg;
if (widget.isBooking) {
msg = await LessonsApi.bookFromTicket(
token: widget.token,
orderId: widget.orderId!,
newScheduleId: s.scheduleId,
);
} else {
msg = await LessonsApi.reschedule(
token: widget.token,
bookingId: widget.bookingId!,
newScheduleId: s.scheduleId,
);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
Navigator.pop(context);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
}
}
}