265 lines
8.5 KiB
Dart
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')));
|
|
}
|
|
}
|
|
}
|