770 lines
22 KiB
Dart
770 lines
22 KiB
Dart
import 'package:http/http.dart' as http;
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
class MLService {
|
|
// API URL - Change based on environment
|
|
// Untuk emulator Android: 10.0.2.2
|
|
// Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local
|
|
static const String baseUrl = 'http://192.168.1.2:5000';
|
|
|
|
static const int timeoutSeconds = 30;
|
|
|
|
/// Login using account data from MySQL login table.
|
|
static Future<Map<String, dynamic>> login({
|
|
required String usernameOrEmail,
|
|
required String password,
|
|
}) async {
|
|
try {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/api/login'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode({
|
|
'email': usernameOrEmail,
|
|
'username': usernameOrEmail,
|
|
'password': password,
|
|
}),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
if (response.statusCode == 200) {
|
|
return body;
|
|
}
|
|
|
|
return {'status': 'error', 'message': body['message'] ?? 'Login gagal'};
|
|
} on TimeoutException {
|
|
return {
|
|
'status': 'error',
|
|
'message':
|
|
'Server tidak merespons. Pastikan Flask aktif dan IP/port API sudah benar.',
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Tidak dapat terhubung ke server: $e',
|
|
};
|
|
}
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> sendForgotPasswordOtp({
|
|
required String usernameOrEmail,
|
|
}) async {
|
|
return _postJson('/api/forgot-password/send-otp', {
|
|
'email': usernameOrEmail,
|
|
'username': usernameOrEmail,
|
|
}, fallbackMessage: 'Gagal membuat OTP');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> verifyForgotPasswordOtp({
|
|
required String usernameOrEmail,
|
|
required String otp,
|
|
}) async {
|
|
return _postJson('/api/forgot-password/verify-otp', {
|
|
'email': usernameOrEmail,
|
|
'username': usernameOrEmail,
|
|
'otp': otp,
|
|
}, fallbackMessage: 'Gagal verifikasi OTP');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> resetPasswordWithOtp({
|
|
required String usernameOrEmail,
|
|
required String otp,
|
|
required String newPassword,
|
|
}) async {
|
|
return _postJson('/api/forgot-password/reset', {
|
|
'email': usernameOrEmail,
|
|
'username': usernameOrEmail,
|
|
'otp': otp,
|
|
'new_password': newPassword,
|
|
}, fallbackMessage: 'Gagal reset password');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> changePassword({
|
|
required String usernameOrEmail,
|
|
required String currentPassword,
|
|
required String newPassword,
|
|
}) async {
|
|
return _postJson('/api/change-password', {
|
|
'email': usernameOrEmail,
|
|
'username': usernameOrEmail,
|
|
'current_password': currentPassword,
|
|
'new_password': newPassword,
|
|
}, fallbackMessage: 'Gagal mengubah password');
|
|
}
|
|
|
|
static Future<Map<String, dynamic>> _postJson(
|
|
String path,
|
|
Map<String, dynamic> payload, {
|
|
required String fallbackMessage,
|
|
}) async {
|
|
try {
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl$path'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(payload),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
return body;
|
|
}
|
|
|
|
return {'status': 'error', 'message': body['message'] ?? fallbackMessage};
|
|
} on TimeoutException {
|
|
return {
|
|
'status': 'error',
|
|
'message':
|
|
'Server tidak merespons. Pastikan Flask aktif dan IP/port API sudah benar.',
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Tidak dapat terhubung ke server: $e',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Convert grams to kilograms with rounding rules:
|
|
/// - <= 500g -> 0.5kg
|
|
/// - 500g < g <= 1000g -> 1kg
|
|
/// - > 1000g -> round up to nearest 0.5kg
|
|
static double gramsToKgRounded(num grams) {
|
|
if (grams <= 0) {
|
|
throw ArgumentError('grams must be greater than 0');
|
|
}
|
|
if (grams <= 500) return 0.5;
|
|
if (grams <= 1000) return 1.0;
|
|
return (grams / 500).ceilToDouble() * 0.5;
|
|
}
|
|
|
|
/// Health Check - Test if API is running
|
|
static Future<bool> healthCheck() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/health'))
|
|
.timeout(Duration(seconds: 5));
|
|
return response.statusCode == 200;
|
|
} catch (e) {
|
|
print('Health check error: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Get Metadata - Retrieve model information
|
|
static Future<Map<String, dynamic>> getMetadata() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/metadata'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
return {'status': 'error', 'message': 'Failed to get metadata'};
|
|
}
|
|
} catch (e) {
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Simplified Prediction - For UI use
|
|
static Future<Map<String, dynamic>> simplePrediksi({
|
|
required String productName,
|
|
required String category,
|
|
required int unitPrice,
|
|
DateTime? predictionDate,
|
|
}) async {
|
|
try {
|
|
final date = predictionDate ?? DateTime.now();
|
|
|
|
// Map product names to encoded values (adjust based on your encoding)
|
|
final productMap = {
|
|
'Tepung Terigu 1kg': 1,
|
|
'Telur 1kg': 2,
|
|
'Gula Pasir 1kg': 3,
|
|
'Susu Bubuk': 4,
|
|
'Cokelat Bubuk 250gr': 5,
|
|
'Mentega 500gr': 6,
|
|
'Keju Parut 250gr': 7,
|
|
'Baking Powder': 8,
|
|
};
|
|
|
|
final categoryMap = {
|
|
'Tepung': 1,
|
|
'Telur': 2,
|
|
'Gula': 3,
|
|
'Susu': 4,
|
|
'Cokelat': 5,
|
|
'Mentega': 6,
|
|
'Keju': 7,
|
|
'Bahan Tambahan': 8,
|
|
};
|
|
|
|
final produkEncoded = productMap[productName] ?? 1;
|
|
final kategoriEncoded = categoryMap[category] ?? 1;
|
|
|
|
return await prediksiStok(
|
|
tahun: date.year,
|
|
bulan: date.month,
|
|
hari: date.day,
|
|
hariDalamMinggu: date.weekday,
|
|
hariMinggu: date.weekday,
|
|
hargaSatuanUpdate: unitPrice,
|
|
totalHargaUpdate: unitPrice, // Simplified
|
|
produkEncoded: produkEncoded,
|
|
namaProdukEncoded: produkEncoded,
|
|
kategoriProdukEncoded: kategoriEncoded,
|
|
);
|
|
} catch (e) {
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Single Prediction - Predict stock demand
|
|
static Future<Map<String, dynamic>> prediksiStok({
|
|
required int tahun,
|
|
required int bulan,
|
|
required int hari,
|
|
required int hariDalamMinggu,
|
|
required int hariMinggu,
|
|
required int hargaSatuanUpdate,
|
|
required int totalHargaUpdate,
|
|
required int produkEncoded,
|
|
required int namaProdukEncoded,
|
|
required int kategoriProdukEncoded,
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'tahun': tahun,
|
|
'bulan': bulan,
|
|
'hari': hari,
|
|
'hari_dalam_minggu': hariDalamMinggu,
|
|
'hari_minggu': hariMinggu,
|
|
'harga_satuan_update': hargaSatuanUpdate,
|
|
'total_harga_update': totalHargaUpdate,
|
|
'produk_encoded': produkEncoded,
|
|
'nama_produk_encoded': namaProdukEncoded,
|
|
'kategori_produk_encoded': kategoriProdukEncoded,
|
|
};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/prediksi'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Batch Prediction - Predict multiple items
|
|
static Future<Map<String, dynamic>> batchPrediksi(
|
|
List<Map<String, dynamic>> items,
|
|
) async {
|
|
try {
|
|
final data = {'items': items};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/batch-prediksi'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Get API Info
|
|
static Future<Map<String, dynamic>> getInfo() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/info'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
} else {
|
|
return {'status': 'error', 'message': 'Failed to get info'};
|
|
}
|
|
} catch (e) {
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// DASHBOARD ENDPOINTS
|
|
// ========================================================================
|
|
|
|
/// Ringkasan dashboard: penggunaan bahan
|
|
static Future<Map<String, dynamic>> getDashboardSummary() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/api/dashboard/summary'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
}
|
|
|
|
return {
|
|
'status': false,
|
|
'message': 'Server error: ${response.statusCode}',
|
|
'penggunaan_bahan': [],
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
'status': false,
|
|
'message': 'Connection error: $e',
|
|
'penggunaan_bahan': [],
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Total bahan keluar yang digunakan hari ini.
|
|
static Future<Map<String, dynamic>> getBahanDigunakanHariIni() async {
|
|
const fallback = {
|
|
'total': 0,
|
|
'satuan': 'kg',
|
|
'keterangan': 'total penggunaan hari ini',
|
|
};
|
|
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/api/dashboard/bahan-digunakan-hari-ini'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return data is Map<String, dynamic> ? data : fallback;
|
|
}
|
|
|
|
return {
|
|
...fallback,
|
|
'status': false,
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
} catch (e) {
|
|
return {...fallback, 'status': false, 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// REPORT ENDPOINTS - LAPORAN
|
|
// ========================================================================
|
|
|
|
/// Helper GET untuk endpoint laporan (response: {status, message, data})
|
|
static Future<Map<String, dynamic>> _getReport(String path) async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl$path'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
return jsonDecode(response.body);
|
|
}
|
|
|
|
print('Report request failed [$path]: ${response.statusCode}');
|
|
return {
|
|
'status': false,
|
|
'message': 'Server error: ${response.statusCode}',
|
|
'data': [],
|
|
};
|
|
} catch (e) {
|
|
print('Report request error [$path]: $e');
|
|
return {'status': false, 'message': 'Connection error: $e', 'data': []};
|
|
}
|
|
}
|
|
|
|
/// Laporan stok bahan
|
|
static Future<Map<String, dynamic>> getReportStock() async {
|
|
return _getReport('/laporan/stok');
|
|
}
|
|
|
|
/// Riwayat stok masuk
|
|
static Future<Map<String, dynamic>> getReportStockIn() async {
|
|
return _getReport('/laporan/stok-masuk');
|
|
}
|
|
|
|
/// Laporan prediksi permintaan
|
|
static Future<Map<String, dynamic>> getReportPredictions() async {
|
|
return _getReport('/laporan/prediksi');
|
|
}
|
|
|
|
/// Laporan bahan kritis
|
|
static Future<Map<String, dynamic>> getReportCritical() async {
|
|
return _getReport('/laporan/bahan-kritis');
|
|
}
|
|
|
|
// ========================================================================
|
|
// DATABASE ENDPOINTS - PRODUCTS & TRANSACTIONS
|
|
// ========================================================================
|
|
|
|
/// Consume stock (batch) after production
|
|
static Future<Map<String, dynamic>> consumeStock({
|
|
required List<Map<String, dynamic>> items,
|
|
String? recipeName,
|
|
int? productionQuantity,
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'items': items,
|
|
if (recipeName != null) 'recipe_name': recipeName,
|
|
if (productionQuantity != null)
|
|
'production_quantity': productionQuantity,
|
|
};
|
|
|
|
print('[consumeStock] Request payload: ${jsonEncode(data)}');
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/stock/consume'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
print('[consumeStock] Response status: ${response.statusCode}');
|
|
print('[consumeStock] Response body: ${response.body}');
|
|
|
|
final parsedBody =
|
|
response.body.isNotEmpty ? jsonDecode(response.body) : null;
|
|
|
|
if (response.statusCode == 200) {
|
|
return parsedBody is Map<String, dynamic>
|
|
? parsedBody
|
|
: {'status': 'success'};
|
|
}
|
|
|
|
if (parsedBody is Map<String, dynamic>) {
|
|
return parsedBody;
|
|
}
|
|
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
} catch (e) {
|
|
print('Consume stock error: $e');
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Get all products from database
|
|
static Future<List<Map<String, dynamic>>> getProducts() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/products'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['status'] == 'success') {
|
|
return List<Map<String, dynamic>>.from(data['products']);
|
|
}
|
|
}
|
|
return [];
|
|
} catch (e) {
|
|
print('Get products error: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Create new product in database
|
|
static Future<Map<String, dynamic>> createProduct({
|
|
required String name,
|
|
required String category,
|
|
required int price,
|
|
required int currentStock,
|
|
double? minStock,
|
|
String? unit,
|
|
String? productType,
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'name': name,
|
|
'category': category,
|
|
'price': price,
|
|
'current_stock': currentStock,
|
|
if (minStock != null) 'min_stock': minStock,
|
|
if (unit != null) 'unit': unit,
|
|
if (productType != null) 'product_type': productType,
|
|
};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/products'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 201) {
|
|
return jsonDecode(response.body);
|
|
} else if (response.statusCode == 409) {
|
|
final result = jsonDecode(response.body);
|
|
return result;
|
|
} else if (response.statusCode == 400) {
|
|
final result = jsonDecode(response.body);
|
|
return {
|
|
'status': 'error',
|
|
'message': result['message'] ?? 'Bad request',
|
|
};
|
|
} else {
|
|
print('Create product error - Status: ${response.statusCode}');
|
|
print('Response body: ${response.body}');
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Create product exception: $e');
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Get specific product by ID
|
|
static Future<Map<String, dynamic>?> getProduct(int productId) async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/products/$productId'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['status'] == 'success') {
|
|
return data['product'];
|
|
}
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
print('Get product error: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Save transaction and update stock automatically
|
|
static Future<Map<String, dynamic>> addTransactionWithStockUpdate({
|
|
required int productId,
|
|
required int quantity,
|
|
required int unitPrice,
|
|
required int totalPrice,
|
|
required String transactionDate, // Format: 'YYYY-MM-DD'
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'produk_id': productId,
|
|
'jumlah': quantity,
|
|
'unit_price': unitPrice,
|
|
'total_price': totalPrice,
|
|
'transaction_date': transactionDate,
|
|
};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/transaksi'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 201) {
|
|
final result = jsonDecode(response.body);
|
|
return result;
|
|
} else {
|
|
return {
|
|
'status': 'error',
|
|
'message': 'Server error: ${response.statusCode}',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
print('Add transaction with stock update error: $e');
|
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
|
|
/// Save transaction to database (legacy - without stock update)
|
|
static Future<bool> saveTransaction({
|
|
required String productName,
|
|
required String category,
|
|
required int quantity,
|
|
required int unitPrice,
|
|
required int totalPrice,
|
|
required String transactionDate, // Format: 'YYYY-MM-DD'
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'product_name': productName,
|
|
'category': category,
|
|
'quantity': quantity,
|
|
'unit_price': unitPrice,
|
|
'total_price': totalPrice,
|
|
'transaction_date': transactionDate,
|
|
};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/transactions'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 201) {
|
|
final result = jsonDecode(response.body);
|
|
return result['status'] == 'success';
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
print('Save transaction error: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Get transaction history from database
|
|
static Future<List<Map<String, dynamic>>> getTransactions({
|
|
int limit = 100,
|
|
int offset = 0,
|
|
String? productName,
|
|
}) async {
|
|
try {
|
|
var url = '$baseUrl/transactions?limit=$limit&offset=$offset';
|
|
if (productName != null && productName.isNotEmpty) {
|
|
url += '&product_name=$productName';
|
|
}
|
|
|
|
final response = await http
|
|
.get(Uri.parse(url))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['status'] == 'success') {
|
|
return List<Map<String, dynamic>>.from(data['transactions']);
|
|
}
|
|
}
|
|
return [];
|
|
} catch (e) {
|
|
print('Get transactions error: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Save prediction result to database
|
|
static Future<bool> savePrediction({
|
|
required String productName,
|
|
required String category,
|
|
required int unitPrice,
|
|
required String predictionDate, // Format: 'YYYY-MM-DD'
|
|
required int predictedQuantity,
|
|
double? rawValue,
|
|
int? estimatedTotalPrice,
|
|
String? estimatedNeeds,
|
|
double? accuracyR2,
|
|
double? errorMae,
|
|
}) async {
|
|
try {
|
|
final data = {
|
|
'product_name': productName,
|
|
'category': category,
|
|
'unit_price': unitPrice,
|
|
'prediction_date': predictionDate,
|
|
'predicted_quantity': predictedQuantity,
|
|
if (rawValue != null) 'raw_value': rawValue,
|
|
if (estimatedTotalPrice != null)
|
|
'estimated_total_price': estimatedTotalPrice,
|
|
if (estimatedNeeds != null) 'estimated_needs': estimatedNeeds,
|
|
if (accuracyR2 != null) 'accuracy_r2': accuracyR2,
|
|
if (errorMae != null) 'error_mae': errorMae,
|
|
};
|
|
|
|
final response = await http
|
|
.post(
|
|
Uri.parse('$baseUrl/predictions'),
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(data),
|
|
)
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 201) {
|
|
final result = jsonDecode(response.body);
|
|
return result['status'] == 'success';
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
print('Save prediction error: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// RECIPES ENDPOINTS
|
|
// ========================================================================
|
|
|
|
/// Get all recipes with ingredients
|
|
static Future<List<Map<String, dynamic>>> getRecipes() async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/recipes'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['status'] == 'success') {
|
|
return List<Map<String, dynamic>>.from(data['recipes']);
|
|
}
|
|
}
|
|
return [];
|
|
} catch (e) {
|
|
print('Get recipes error: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Get specific recipe by ID with ingredients
|
|
static Future<Map<String, dynamic>?> getRecipe(int recipeId) async {
|
|
try {
|
|
final response = await http
|
|
.get(Uri.parse('$baseUrl/recipes/$recipeId'))
|
|
.timeout(Duration(seconds: timeoutSeconds));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
if (data['status'] == 'success') {
|
|
return data['recipe'];
|
|
}
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
print('Get recipe error: $e');
|
|
return null;
|
|
}
|
|
}
|
|
}
|