fixed pages
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user