added pages to project

This commit is contained in:
2026-08-11 07:54:22 +02:00
parent 9c623f96c0
commit ab36215b7a
14 changed files with 2049 additions and 195 deletions
+172 -3
View File
@@ -19,7 +19,67 @@ class LessonsApi {
String? month,
}) async {
final query = (month != null && month.isNotEmpty) ? '?month=$month' : '';
final uri = Uri.parse('${ApiConfig.phpApiBase}/my_lessons.php$query');
final uri = Uri.parse('${ApiConfig.laravelApiBase}/my_lessons.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 lezioni');
}
return LessonsResponse.fromJson(data);
}
/// Cancella una prenotazione dell'utente.
static Future<bool> cancelLesson({
required String token,
required int bookingId,
}) async {
final uri = Uri.parse('${ApiConfig.laravelApiBase}/cancel_lesson.php');
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({'booking_id': bookingId}),
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('Risposta non valida dal server (${res.statusCode})');
}
if (res.statusCode == 200 && data['success'] == true) {
return true;
}
throw Exception(data['error'] ?? data['message'] ?? 'Errore cancellazione');
}
/// Elenca gli slot disponibili per riprogrammare/prenotare.
static Future<List<AvailableSlot>> fetchAvailableSlots({
required String token,
required int serviceId,
String? month,
}) async {
final monthQuery = (month != null && month.isNotEmpty)
? '&month=$month'
: '';
final uri = Uri.parse(
'${ApiConfig.laravelApiBase}/available_slots.php?service_id=$serviceId$monthQuery',
);
final res = await http.get(uri, headers: _headers(token));
@@ -31,9 +91,118 @@ class LessonsApi {
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore lezioni');
throw Exception(data['error'] ?? data['message'] ?? 'Errore slot');
}
return LessonsResponse.fromJson(data);
final raw = (data['slots'] as List?) ?? const [];
return raw
.map((e) => AvailableSlot.fromJson((e as Map).cast<String, dynamic>()))
.toList();
}
/// Esegue la riprogrammazione di una lezione su un nuovo slot.
static Future<String> reschedule({
required String token,
required int bookingId,
required int newScheduleId,
}) async {
final uri = Uri.parse('${ApiConfig.laravelApiBase}/reschedule.php');
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'booking_id': bookingId,
'new_schedule_id': newScheduleId,
}),
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('Risposta non valida dal server (${res.statusCode})');
}
if (res.statusCode == 200 && data['success'] == true) {
return (data['message'] ?? 'Riprogrammazione richiesta').toString();
}
throw Exception(
data['error'] ?? data['message'] ?? 'Errore riprogrammazione',
);
}
/// Elenca gli ordini/pacchetti dell'utente con residui.
static Future<List<OrderPackage>> fetchOrders({required String token}) async {
final uri = Uri.parse('${ApiConfig.laravelApiBase}/orders.php');
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('Risposta non valida dal server (${res.statusCode})');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore ordini');
}
final raw = (data['orders'] as List?) ?? const [];
return raw
.map((e) => OrderPackage.fromJson((e as Map).cast<String, dynamic>()))
.toList();
}
/// Prenota una nuova lezione da un ticket residuo.
static Future<String> bookFromTicket({
required String token,
required int orderId,
required int newScheduleId,
}) async {
final uri = Uri.parse('${ApiConfig.laravelApiBase}/book_from_ticket.php');
final res = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({'order_id': orderId, 'new_schedule_id': newScheduleId}),
);
Map<String, dynamic> data;
try {
data = jsonDecode(res.body) as Map<String, dynamic>;
} catch (_) {
throw Exception('Risposta non valida dal server (${res.statusCode})');
}
if (res.statusCode == 200 && data['success'] == true) {
return (data['message'] ?? 'Prenotazione richiesta').toString();
}
throw Exception(data['error'] ?? data['message'] ?? 'Errore prenotazione');
}
/// Elenca gli ordini dettagliati (con conteggi e lezioni).
static Future<List<OrderDetail>> fetchOrdersDetail({
required String token,
}) async {
final uri = Uri.parse('${ApiConfig.laravelApiBase}/orders_detail.php');
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('Risposta non valida dal server (${res.statusCode})');
}
if (res.statusCode != 200 || data['success'] != true) {
throw Exception(data['error'] ?? data['message'] ?? 'Errore ordini');
}
final raw = (data['orders'] as List?) ?? const [];
return raw
.map((e) => OrderDetail.fromJson((e as Map).cast<String, dynamic>()))
.toList();
}
}
+106
View File
@@ -1,5 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import '../config/api_config.dart';
class VanguardApi {
@@ -123,4 +126,107 @@ class VanguardApi {
throw Exception('Logout failed (${res.statusCode}): ${res.body}');
}
// ---------- Profilo (Vanguard native) ----------
/// GET /me — dati profilo. Ritorna il contenuto di "data".
static Future<Map<String, dynamic>> getMe({required String token}) async {
final url = Uri.parse('${ApiConfig.laravelApiBase}/me');
final res = await http.get(url, headers: authHeaders(token));
if (res.statusCode != 200) {
throw Exception('Get profile failed (${res.statusCode}): ${res.body}');
}
final body = jsonDecode(res.body) as Map<String, dynamic>;
final data = body['data'];
if (data is! Map<String, dynamic>) {
throw Exception('Formato risposta /me non valido.');
}
return data;
}
/// PATCH /me/details — aggiorna nome e cognome.
static Future<Map<String, dynamic>> updateDetails({
required String token,
String? firstName,
String? lastName,
}) async {
final url = Uri.parse('${ApiConfig.laravelApiBase}/me/details');
final payload = <String, dynamic>{};
if (firstName != null) payload['first_name'] = firstName;
if (lastName != null) payload['last_name'] = lastName;
final res = await http.patch(
url,
headers: authHeaders(token),
body: jsonEncode(payload),
);
if (res.statusCode != 200) {
throw Exception('Update details failed (${res.statusCode}): ${res.body}');
}
final body = jsonDecode(res.body) as Map<String, dynamic>;
return (body['data'] as Map<String, dynamic>?) ?? {};
}
/// PATCH /me/details/auth — aggiorna la password (nuova + conferma).
static Future<void> updatePassword({
required String token,
required String password,
required String passwordConfirmation,
}) async {
final url = Uri.parse('${ApiConfig.laravelApiBase}/me/details/auth');
final res = await http.patch(
url,
headers: authHeaders(token),
body: jsonEncode({
'password': password,
'password_confirmation': passwordConfirmation,
}),
);
if (res.statusCode != 200) {
throw Exception(
'Update password failed (${res.statusCode}): ${res.body}',
);
}
}
/// POST /me/avatar — upload avatar (multipart). Ritorna l'URL avatar aggiornato.
static Future<String?> uploadAvatar({
required String token,
required String filePath,
}) async {
final url = Uri.parse('${ApiConfig.laravelApiBase}/me/avatar');
final mimeType = lookupMimeType(filePath) ?? 'image/jpeg';
final parts = mimeType.split('/');
final request = http.MultipartRequest('POST', url)
..headers['Accept'] = 'application/json'
..headers['Authorization'] = 'Bearer $token'
..files.add(
await http.MultipartFile.fromPath(
'file',
filePath,
contentType: MediaType(parts[0], parts[1]),
),
);
final streamed = await request.send();
final res = await http.Response.fromStream(streamed);
if (res.statusCode != 200) {
throw Exception('Upload avatar failed (${res.statusCode}): ${res.body}');
}
final body = jsonDecode(res.body) as Map<String, dynamic>;
final data = body['data'] as Map<String, dynamic>?;
return data?['avatar']?.toString();
}
}