Files
yogibook_aury_app/lib/services/teacher_api.dart
T

411 lines
12 KiB
Dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../config/api_config.dart';
/// Un partecipante a una classe (vista insegnante).
class TeacherParticipant {
final int bookingId;
final String fullName;
final bool isLost;
const TeacherParticipant({
required this.bookingId,
required this.fullName,
required this.isLost,
});
factory TeacherParticipant.fromMap(Map<String, dynamic> m) =>
TeacherParticipant(
bookingId: (m['booking_id'] as num?)?.toInt() ?? 0,
fullName: (m['full_name'] ?? '').toString().trim(),
isLost: m['is_lost'] == true,
);
}
/// Una classe programmata con i suoi partecipanti.
class TeacherClass {
final int scheduleId;
final int serviceId;
final String className;
final String? colorClass;
final String dateTime; // "YYYY-MM-DD HH:MM:SS"
final int maxCapacity;
final int bookedCount;
final bool isFull;
final List<TeacherParticipant> participants;
const TeacherClass({
required this.scheduleId,
required this.serviceId,
required this.className,
required this.colorClass,
required this.dateTime,
required this.maxCapacity,
required this.bookedCount,
required this.isFull,
required this.participants,
});
factory TeacherClass.fromMap(Map<String, dynamic> m) => TeacherClass(
scheduleId: (m['schedule_id'] as num?)?.toInt() ?? 0,
serviceId: (m['service_id'] as num?)?.toInt() ?? 0,
className: (m['class_name'] ?? '').toString(),
colorClass: (m['color_class'])?.toString(),
dateTime: (m['date_time'] ?? '').toString(),
maxCapacity: (m['max_capacity'] as num?)?.toInt() ?? 0,
bookedCount: (m['booked_count'] as num?)?.toInt() ?? 0,
isFull: m['is_full'] == true,
participants: ((m['participants'] as List?) ?? [])
.cast<Map<String, dynamic>>()
.map(TeacherParticipant.fromMap)
.toList(),
);
}
/// Utente trovato nella ricerca (per aggiungi partecipante).
class TeacherUser {
final int id;
final String firstName;
final String lastName;
final String email;
const TeacherUser({
required this.id,
required this.firstName,
required this.lastName,
required this.email,
});
String get fullName => '$firstName $lastName'.trim();
factory TeacherUser.fromMap(Map<String, dynamic> m) => TeacherUser(
id: (m['id'] as num?)?.toInt() ?? 0,
firstName: (m['first_name'] ?? '').toString(),
lastName: (m['last_name'] ?? '').toString(),
email: (m['email'] ?? '').toString(),
);
}
/// Ordine/pacchetto di un utente con residui.
class TeacherOrder {
final int idOrderBook;
final String serviceName;
final int tickets;
final int used;
final int remaining;
final String? expireOn;
const TeacherOrder({
required this.idOrderBook,
required this.serviceName,
required this.tickets,
required this.used,
required this.remaining,
required this.expireOn,
});
factory TeacherOrder.fromMap(Map<String, dynamic> m) => TeacherOrder(
idOrderBook: (m['idorderbook'] as num?)?.toInt() ?? 0,
serviceName: (m['service_name'] ?? '').toString(),
tickets: (m['tickets'] as num?)?.toInt() ?? 0,
used: (m['used'] as num?)?.toInt() ?? 0,
remaining: (m['remaining'] as num?)?.toInt() ?? 0,
expireOn: (m['expireon'])?.toString(),
);
}
class TeacherApi {
static Map<String, String> _headers(String token) => {
'Accept': 'application/json',
'Authorization': 'Bearer $token',
};
/// Classi di un mese (YYYY-MM) con partecipanti. Solo staff lato server.
static Future<List<TeacherClass>> fetchClasses({
required String token,
required String month,
}) async {
final query = month.isNotEmpty ? '?month=$month' : '';
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_classes.php$query',
);
final res = await http.get(uri, headers: _headers(token));
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore classi');
}
return ((data['classes'] as List?) ?? [])
.cast<Map<String, dynamic>>()
.map(TeacherClass.fromMap)
.toList();
}
/// Segna una prenotazione come persa (lost=true) o la ripristina (false).
static Future<void> setLost({
required String token,
required int bookingId,
required bool lost,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_mark_lost.php',
);
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
},
body: 'booking_id=$bookingId&lost=${lost ? 'Y' : 'N'}',
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(
data['error'] ?? data['message'] ?? 'Errore aggiornamento',
);
}
}
/// Elimina una prenotazione (libera il ticket all'allievo).
static Future<void> deleteBooking({
required String token,
required int bookingId,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_delete_booking.php',
);
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
},
body: 'booking_id=$bookingId',
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(
data['error'] ?? data['message'] ?? 'Errore eliminazione',
);
}
}
/// Classi destinazione per riprogrammare una prenotazione
/// (future, non piene, escluse quelle dove è già iscritto).
static Future<List<TeacherClass>> availableClasses({
required String token,
required int bookingId,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_available_classes.php?booking_id=$bookingId',
);
final res = await http.get(
uri,
headers: {'Accept': 'application/json', 'Authorization': 'Bearer $token'},
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore classi');
}
return ((data['classes'] as List?) ?? [])
.cast<Map<String, dynamic>>()
.map(TeacherClass.fromMap)
.toList();
}
/// Esegue la riprogrammazione su una nuova classe.
static Future<void> reprogram({
required String token,
required int bookingId,
required int scheduleId,
required bool countAsReprogram,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_reprogram.php',
);
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
},
body:
'booking_id=$bookingId&schedule_id=$scheduleId'
'&is_reprogrammed=${countAsReprogram ? 'Y' : 'N'}',
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(
data['error'] ?? data['message'] ?? 'Errore riprogrammazione',
);
}
}
/// Ricerca utenti per nome/cognome (min 2 caratteri in uno dei due).
static Future<List<TeacherUser>> searchUsers({
required String token,
required String first,
required String last,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_search_users.php'
'?first=${Uri.encodeQueryComponent(first)}'
'&last=${Uri.encodeQueryComponent(last)}',
);
final res = await http.get(uri, headers: _headers(token));
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore ricerca');
}
return ((data['results'] as List?) ?? [])
.cast<Map<String, dynamic>>()
.map(TeacherUser.fromMap)
.toList();
}
/// Ordini con residui di un utente.
static Future<List<TeacherOrder>> userOrders({
required String token,
required int userId,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_user_orders.php?user_id=$userId',
);
final res = await http.get(uri, headers: _headers(token));
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore ordini');
}
return ((data['orders'] as List?) ?? [])
.cast<Map<String, dynamic>>()
.map(TeacherOrder.fromMap)
.toList();
}
/// Aggiunge un partecipante a una classe.
/// [mode] = 'scala' | 'omaggio' | 'nuovo'.
/// Per utente esistente passare [userId] > 0; per nuovo utente passare
/// [userId] = 0 con name/surname (email opzionale).
static Future<void> addBooking({
required String token,
required int scheduleId,
required int serviceId,
required String bookingStart,
required String mode,
int userId = 0,
String name = '',
String surname = '',
String email = '',
int? orderId,
String? newExpiry,
}) async {
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/api_teacher_add_booking.php',
);
final body = <String, String>{
'idserviceschedule': '$scheduleId',
'idservice': '$serviceId',
'bookingstart': bookingStart,
'bkmode': mode,
'user_id': '$userId',
'name': name,
'surname': surname,
'email': email,
if (orderId != null) 'idorder': '$orderId',
if (newExpiry != null) 'new_expiry': newExpiry,
};
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
},
body: body.entries
.map(
(e) =>
'${Uri.encodeQueryComponent(e.key)}='
'${Uri.encodeQueryComponent(e.value)}',
)
.join('&'),
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('RAW(${res.statusCode}): ${res.body}');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore inserimento');
}
}
}