fixed pages

This commit is contained in:
2026-09-04 08:15:25 +02:00
parent c80535eb97
commit a1167a2d03
14 changed files with 363 additions and 174 deletions
+5 -18
View File
@@ -9,7 +9,6 @@ import 'meditation_page.dart';
import '../widgets/app_drawer.dart';
import '../widgets/app_bottom_nav.dart';
import 'package:url_launcher/url_launcher.dart';
import '../widgets/yogibook_background.dart';
import 'reschedule_page.dart';
import 'orders_page.dart';
@@ -116,22 +115,9 @@ class _LessonsPageState extends State<LessonsPage> {
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,
),
),
],
title: const Text(
'Le mie lezioni',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
actions: [
Padding(
@@ -158,7 +144,8 @@ class _LessonsPageState extends State<LessonsPage> {
userFirstName: widget.userFirstName,
),
body: YogibookBackground(
body: Container(
color: const Color(0xFFE2FDFF),
child: SafeArea(
top: false,
child: Column(
+1 -1
View File
@@ -273,7 +273,7 @@ class _MeditationPageState extends State<MeditationPage>
elevation: 0,
centerTitle: true,
title: const Text(
'Meditazione',
'Respira',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
actions: [
+19 -3
View File
@@ -81,6 +81,11 @@ class _ReschedulePageState extends State<ReschedulePage> {
return s[0].toUpperCase() + s.substring(1);
}
String _dateLabel(String ymd) {
final dt = DateFormat('yyyy-MM-dd').parse(ymd);
return DateFormat('dd/MM/yyyy').format(dt);
}
@override
Widget build(BuildContext context) {
const green = Color(0xFF10B981);
@@ -176,6 +181,7 @@ class _ReschedulePageState extends State<ReschedulePage> {
return _SlotCard(
slot: s,
weekday: _weekdayLabel(s.date),
dateLabel: _dateLabel(s.date),
onChoose: () => _confirm(s),
);
},
@@ -239,11 +245,13 @@ class _ReschedulePageState extends State<ReschedulePage> {
class _SlotCard extends StatelessWidget {
final AvailableSlot slot;
final String weekday;
final String dateLabel;
final VoidCallback onChoose;
const _SlotCard({
required this.slot,
required this.weekday,
required this.dateLabel,
required this.onChoose,
});
@@ -316,7 +324,7 @@ class _SlotCard extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
'$weekday ${slot.date} • ${slot.time}',
'$weekday $dateLabel • ${slot.time}',
style: const TextStyle(
fontSize: 12,
color: Colors.black54,
@@ -325,10 +333,18 @@ class _SlotCard extends StatelessWidget {
),
const SizedBox(height: 2),
Text(
'Posti liberi: ${slot.freePlaces}/${slot.maxCapacity}',
isBooked
? 'Già prenotato'
: isFull
? 'Non disponibile'
: 'Disponibile',
style: TextStyle(
fontSize: 12,
color: isFull ? orange : Colors.black45,
color: isFull
? const Color(0xFF9AA0A6)
: isBooked
? orange
: green,
fontWeight: FontWeight.w700,
),
),
+43 -37
View File
@@ -18,7 +18,7 @@ class _SplashPageState extends State<SplashPage> {
static const Color kRed = Color(0xFFB01E28);
Timer? _timer;
int _seconds = 5; // countdown iniziale
int _seconds = 8; // countdown iniziale
// Evita doppie navigazioni: countdown, tap e sblocco possono
// altrimenti innescare più push.
@@ -176,44 +176,50 @@ class _SplashPageState extends State<SplashPage> {
Positioned(
left: 0,
right: 0,
bottom: 30,
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.75),
borderRadius: BorderRadius.circular(16),
),
child: Image.asset(
'assets/images/yogibook_logo.png',
height: 40,
fit: BoxFit.contain,
),
),
const SizedBox(height: 14),
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.25),
border: Border.all(color: Colors.white, width: 1.5),
),
child: Text(
'$_seconds',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
bottom: 0,
child: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.75),
borderRadius: BorderRadius.circular(16),
),
child: Image.asset(
'assets/images/yogibook_logo.png',
height: 40,
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 14),
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.25),
border: Border.all(color: Colors.white, width: 1.5),
),
child: Text(
'$_seconds',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
],
),
],
),
),
),
],
@@ -109,6 +109,10 @@ class _TeacherAddParticipantPageState extends State<TeacherAddParticipantPage> {
_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 {
+199 -53
View File
@@ -27,8 +27,9 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
// copia locale modificabile dei partecipanti (per aggiornare la UI)
late List<TeacherParticipant> _participants;
int _busyBookingId =
0; // booking in aggiornamento (per disabilitare il tasto)
int _busyBookingId = 0; // booking in aggiornamento
bool _changed =
false; // true se ci sono state modifiche (per refresh lista)/ booking in aggiornamento (per disabilitare il tasto)
@override
void initState() {
@@ -100,6 +101,7 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
);
});
}
_changed = true;
_snack(markLost ? 'Segnata come persa.' : 'Ripristinata.');
} catch (e) {
_snack('$e');
@@ -143,6 +145,7 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
setState(() {
_participants.removeWhere((x) => x.bookingId == p.bookingId);
});
_changed = true;
_snack('Prenotazione eliminata.');
} catch (e) {
_snack('$e');
@@ -151,6 +154,129 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
}
}
Future<void> _confirmPending(TeacherParticipant p) async {
setState(() => _busyBookingId = p.bookingId);
try {
await TeacherApi.confirmPending(
token: widget.token,
bookingId: p.bookingId,
);
// da pending a confermato: aggiorna lo stato locale
final idx = _participants.indexWhere((x) => x.bookingId == p.bookingId);
if (idx != -1) {
setState(() {
_participants[idx] = TeacherParticipant(
bookingId: p.bookingId,
fullName: p.fullName,
isLost: false,
isPending: false,
);
});
}
_changed = true;
_snack('${p.fullName} confermato.');
} catch (e) {
_snack('$e');
} finally {
if (mounted) setState(() => _busyBookingId = 0);
}
}
Widget _pendingTile(TeacherParticipant p) {
final busy = _busyBookingId == p.bookingId;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFFFFBF0),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFF5E6B8)),
),
child: Row(
children: [
Container(
width: 42,
height: 42,
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Color(0xFFE0A800),
shape: BoxShape.circle,
),
child: Text(
_initials(p.fullName),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800,
fontSize: 15,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
p.fullName,
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 15,
),
),
const SizedBox(height: 2),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF4D6),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'In attesa',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w800,
color: Color(0xFF8A6D00),
),
),
),
],
),
),
if (busy)
const SizedBox(
width: 36,
height: 36,
child: Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(strokeWidth: 2),
),
)
else ...[
IconButton(
onPressed: () => _confirmPending(p),
tooltip: 'Conferma',
icon: const Icon(
Icons.check_circle_outline_rounded,
color: Color(0xFF1A7F52),
),
),
IconButton(
onPressed: () => _deleteBooking(p),
tooltip: 'Rimuovi',
icon: const Icon(
Icons.delete_outline_rounded,
color: Colors.black45,
),
),
],
],
),
);
}
Future<void> _reprogram(TeacherParticipant p) async {
final done = await Navigator.push<bool>(
context,
@@ -173,63 +299,85 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
@override
Widget build(BuildContext context) {
final active = _participants.where((p) => !p.isLost).toList();
final lost = _participants.where((p) => p.isLost).toList();
final pending = _participants.where((p) => p.isPending).toList();
final active = _participants
.where((p) => !p.isPending && !p.isLost)
.toList();
final lost = _participants.where((p) => !p.isPending && p.isLost).toList();
final cls = widget.cls;
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: const Text(
'Dettaglio classe',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
return PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) {
// quando si esce, se ci sono state modifiche la lista si aggiorna
},
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context, _changed),
),
title: const Text(
'Dettaglio classe',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18),
),
),
),
body: YogibookBackground(
child: SafeArea(
top: false,
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
children: [
_headerCard(),
const SizedBox(height: 18),
_sectionLabel('Iscritti (${active.length})'),
const SizedBox(height: 8),
if (active.isEmpty)
_emptyRow('Nessun iscritto attivo.')
else
...active.map((p) => _tile(p)),
if (lost.isNotEmpty) ...[
body: YogibookBackground(
child: SafeArea(
top: false,
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
children: [
_headerCard(),
const SizedBox(height: 18),
_sectionLabel('Lezioni perse (${lost.length})'),
if (pending.isNotEmpty) ...[
_sectionLabel('In attesa di conferma (${pending.length})'),
const SizedBox(height: 8),
...pending.map((p) => _pendingTile(p)),
const SizedBox(height: 18),
],
_sectionLabel('Iscritti (${active.length})'),
const SizedBox(height: 8),
...lost.map((p) => _tile(p)),
],
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kGreen,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
if (active.isEmpty)
_emptyRow('Nessun iscritto attivo.')
else
...active.map((p) => _tile(p)),
if (lost.isNotEmpty) ...[
const SizedBox(height: 18),
_sectionLabel('Lezioni perse (${lost.length})'),
const SizedBox(height: 8),
...lost.map((p) => _tile(p)),
],
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kGreen,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
),
icon: const Icon(Icons.person_add_alt_1, color: Colors.white),
label: const Text(
'Aggiungi partecipante',
style: TextStyle(
fontWeight: FontWeight.w900,
icon: const Icon(
Icons.person_add_alt_1,
color: Colors.white,
),
label: const Text(
'Aggiungi partecipante',
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
),
),
onPressed: _openAddParticipant,
),
onPressed: _openAddParticipant,
),
),
],
],
),
),
),
),
@@ -245,10 +393,8 @@ class _TeacherClassDetailPageState extends State<TeacherClassDetailPage> {
),
);
if (added == true && mounted) {
_snack(
'Partecipante aggiunto. Aggiorna il pannello per vedere la lista.',
);
// torna al pannello classi, che ricaricando mostrerà i dati aggiornati
_changed = true;
_snack('Partecipante aggiunto.');
Navigator.pop(context, true);
}
}
+21
View File
@@ -392,6 +392,27 @@ class _ClassCard extends StatelessWidget {
color: fillColor,
),
),
if (cls.participants.any((p) => p.isPending)) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF4D6),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${cls.participants.where((p) => p.isPending).length} in attesa',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w800,
color: Color(0xFF8A6D00),
),
),
),
],
],
),
],