411 lines
12 KiB
Dart
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');
|
|
}
|
|
}
|
|
}
|