first commit
This commit is contained in:
75
lib/app/modules/warga/bindings/order_sewa_aset_binding.dart
Normal file
75
lib/app/modules/warga/bindings/order_sewa_aset_binding.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
import '../../../data/providers/auth_provider.dart';
|
||||
import '../controllers/order_sewa_aset_controller.dart';
|
||||
|
||||
class OrderSewaAsetBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
debugPrint('⚡ OrderSewaAsetBinding: dependencies called');
|
||||
final box = GetStorage();
|
||||
|
||||
// Ensure providers are registered
|
||||
if (!Get.isRegistered<AsetProvider>()) {
|
||||
debugPrint('⚡ Registering AsetProvider');
|
||||
Get.put(AsetProvider(), permanent: true);
|
||||
}
|
||||
|
||||
if (!Get.isRegistered<AuthProvider>()) {
|
||||
debugPrint('⚡ Registering AuthProvider');
|
||||
Get.put(AuthProvider(), permanent: true);
|
||||
}
|
||||
|
||||
// Check if we have the asetId in arguments
|
||||
final args = Get.arguments;
|
||||
debugPrint('⚡ Arguments received in binding: $args');
|
||||
String? asetId;
|
||||
|
||||
if (args != null && args.containsKey('asetId') && args['asetId'] != null) {
|
||||
asetId = args['asetId'].toString();
|
||||
if (asetId.isNotEmpty) {
|
||||
debugPrint('✅ Valid asetId found in arguments: $asetId');
|
||||
// Simpan ID di storage untuk digunakan saat hot reload
|
||||
box.write('current_aset_id', asetId);
|
||||
debugPrint('💾 Saved asetId to GetStorage in binding: $asetId');
|
||||
} else {
|
||||
debugPrint('⚠️ Warning: Empty asetId found in arguments');
|
||||
}
|
||||
} else {
|
||||
debugPrint(
|
||||
'⚠️ Warning: No valid asetId found in arguments, checking storage',
|
||||
);
|
||||
// Cek apakah ada ID tersimpan di storage
|
||||
if (box.hasData('current_aset_id')) {
|
||||
asetId = box.read<String>('current_aset_id');
|
||||
debugPrint('📦 Found asetId in GetStorage: $asetId');
|
||||
}
|
||||
}
|
||||
|
||||
// Only delete the existing controller if we're not in a hot reload situation
|
||||
if (Get.isRegistered<OrderSewaAsetController>()) {
|
||||
// Check if we're going through a hot reload by looking at the controller's state
|
||||
final existingController = Get.find<OrderSewaAsetController>();
|
||||
if (existingController.aset.value == null) {
|
||||
// Controller exists but doesn't have data, likely a fresh navigation or reload
|
||||
debugPrint('⚡ Removing old OrderSewaAsetController without data');
|
||||
Get.delete<OrderSewaAsetController>(force: true);
|
||||
|
||||
// Use put instead of lazyPut to ensure controller is created immediately
|
||||
debugPrint('⚡ Creating new OrderSewaAsetController');
|
||||
Get.put(OrderSewaAsetController());
|
||||
} else {
|
||||
// Controller exists and has data, leave it alone during hot reload
|
||||
debugPrint(
|
||||
'🔥 Hot reload detected, preserving existing controller with data',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No controller exists, create a new one
|
||||
debugPrint('⚡ Creating new OrderSewaAsetController (first time)');
|
||||
Get.put(OrderSewaAsetController());
|
||||
}
|
||||
}
|
||||
}
|
22
lib/app/modules/warga/bindings/order_sewa_paket_binding.dart
Normal file
22
lib/app/modules/warga/bindings/order_sewa_paket_binding.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/order_sewa_paket_controller.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
import '../../../data/providers/sewa_provider.dart';
|
||||
|
||||
class OrderSewaPaketBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Ensure providers are registered
|
||||
if (!Get.isRegistered<AsetProvider>()) {
|
||||
Get.put(AsetProvider());
|
||||
}
|
||||
|
||||
if (!Get.isRegistered<SewaProvider>()) {
|
||||
Get.put(SewaProvider());
|
||||
}
|
||||
|
||||
Get.lazyPut<OrderSewaPaketController>(
|
||||
() => OrderSewaPaketController(),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/pembayaran_sewa_controller.dart';
|
||||
|
||||
class PembayaranSewaBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<PembayaranSewaController>(() => PembayaranSewaController());
|
||||
}
|
||||
}
|
16
lib/app/modules/warga/bindings/sewa_aset_binding.dart
Normal file
16
lib/app/modules/warga/bindings/sewa_aset_binding.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/sewa_aset_controller.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
|
||||
class SewaAsetBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Register AsetProvider if not already registered
|
||||
if (!Get.isRegistered<AsetProvider>()) {
|
||||
Get.put(AsetProvider(), permanent: true);
|
||||
}
|
||||
|
||||
// Register SewaAsetController
|
||||
Get.lazyPut<SewaAsetController>(() => SewaAsetController());
|
||||
}
|
||||
}
|
34
lib/app/modules/warga/bindings/warga_sewa_binding.dart
Normal file
34
lib/app/modules/warga/bindings/warga_sewa_binding.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/warga_sewa_controller.dart';
|
||||
import '../controllers/warga_dashboard_controller.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
import '../../../data/providers/auth_provider.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
|
||||
class WargaSewaBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Ensure NavigationService is registered and set to Sewa tab
|
||||
if (Get.isRegistered<NavigationService>()) {
|
||||
final navService = Get.find<NavigationService>();
|
||||
navService.setNavIndex(1); // Set to Sewa tab
|
||||
}
|
||||
|
||||
// Ensure AuthProvider is registered
|
||||
if (!Get.isRegistered<AuthProvider>()) {
|
||||
Get.put(AuthProvider(), permanent: true);
|
||||
}
|
||||
|
||||
// Ensure AsetProvider is registered
|
||||
if (!Get.isRegistered<AsetProvider>()) {
|
||||
Get.put(AsetProvider(), permanent: true);
|
||||
}
|
||||
|
||||
// Register WargaDashboardController if not already registered
|
||||
if (!Get.isRegistered<WargaDashboardController>()) {
|
||||
Get.put(WargaDashboardController());
|
||||
}
|
||||
|
||||
Get.lazyPut<WargaSewaController>(() => WargaSewaController());
|
||||
}
|
||||
}
|
2364
lib/app/modules/warga/controllers/order_sewa_aset_controller.dart
Normal file
2364
lib/app/modules/warga/controllers/order_sewa_aset_controller.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,443 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_logs/flutter_logs.dart';
|
||||
import '../../../data/models/paket_model.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
import '../../../data/providers/sewa_provider.dart';
|
||||
import '../../../services/service_manager.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
|
||||
class OrderSewaPaketController extends GetxController {
|
||||
// Dependencies
|
||||
final AsetProvider asetProvider = Get.find<AsetProvider>();
|
||||
final SewaProvider sewaProvider = Get.find<SewaProvider>();
|
||||
final NavigationService navigationService = ServiceManager().navigationService;
|
||||
|
||||
// State variables
|
||||
final paket = Rx<PaketModel?>(null);
|
||||
final paketImages = RxList<String>([]);
|
||||
final isLoading = RxBool(true);
|
||||
final isPhotosLoading = RxBool(true);
|
||||
final selectedSatuanWaktu = Rx<Map<String, dynamic>?>(null);
|
||||
final selectedDate = RxString('');
|
||||
final selectedStartDate = Rx<DateTime?>(null);
|
||||
final selectedEndDate = Rx<DateTime?>(null);
|
||||
final selectedStartTime = RxInt(-1);
|
||||
final selectedEndTime = RxInt(-1);
|
||||
final formattedDateRange = RxString('');
|
||||
final formattedTimeRange = RxString('');
|
||||
final totalPrice = RxDouble(0.0);
|
||||
final kuantitas = RxInt(1);
|
||||
final isSubmitting = RxBool(false);
|
||||
|
||||
// Format currency
|
||||
final currencyFormat = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
FlutterLogs.logInfo("OrderSewaPaketController", "onInit", "Initializing OrderSewaPaketController");
|
||||
|
||||
// Get the paket ID from arguments
|
||||
final Map<String, dynamic> args = Get.arguments ?? {};
|
||||
final String? paketId = args['id'];
|
||||
|
||||
if (paketId != null) {
|
||||
loadPaketData(paketId);
|
||||
} else {
|
||||
debugPrint('❌ No paket ID provided in arguments');
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle hot reload - restore state if needed
|
||||
void handleHotReload() {
|
||||
if (paket.value == null) {
|
||||
final Map<String, dynamic> args = Get.arguments ?? {};
|
||||
final String? paketId = args['id'];
|
||||
|
||||
if (paketId != null) {
|
||||
// Try to get from cache first
|
||||
final cachedPaket = GetStorage().read('cached_paket_$paketId');
|
||||
if (cachedPaket != null) {
|
||||
debugPrint('🔄 Hot reload: Restoring paket from cache');
|
||||
paket.value = cachedPaket;
|
||||
loadPaketPhotos(paketId);
|
||||
initializePriceOptions();
|
||||
} else {
|
||||
loadPaketData(paketId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load paket data from API
|
||||
Future<void> loadPaketData(String id) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
debugPrint('🔍 Loading paket data for ID: $id');
|
||||
|
||||
// First check if we have it in cache
|
||||
final cachedPaket = GetStorage().read('cached_paket_$id');
|
||||
if (cachedPaket != null) {
|
||||
debugPrint('✅ Found cached paket data');
|
||||
paket.value = cachedPaket;
|
||||
await loadPaketPhotos(id);
|
||||
initializePriceOptions();
|
||||
} else {
|
||||
// Get all pakets and filter for the one we need
|
||||
final List<dynamic> allPakets = await asetProvider.getPakets();
|
||||
final rawPaket = allPakets.firstWhere(
|
||||
(paket) => paket['id'] == id,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
// Declare loadedPaket outside the if block for wider scope
|
||||
PaketModel? loadedPaket;
|
||||
|
||||
if (rawPaket != null) {
|
||||
// Convert to PaketModel
|
||||
try {
|
||||
// Handle Map directly - pakets from getPakets() are always maps
|
||||
loadedPaket = PaketModel.fromMap(rawPaket);
|
||||
debugPrint('✅ Successfully converted paket to PaketModel');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Error converting paket map to PaketModel: $e');
|
||||
// Fallback using our helper methods
|
||||
loadedPaket = PaketModel(
|
||||
id: getPaketId(rawPaket),
|
||||
nama: getPaketNama(rawPaket),
|
||||
deskripsi: getPaketDeskripsi(rawPaket),
|
||||
harga: getPaketHarga(rawPaket),
|
||||
kuantitas: getPaketKuantitas(rawPaket),
|
||||
foto_paket: getPaketMainPhoto(rawPaket),
|
||||
satuanWaktuSewa: getPaketSatuanWaktuSewa(rawPaket),
|
||||
);
|
||||
debugPrint('✅ Created PaketModel using helper methods');
|
||||
}
|
||||
|
||||
// Update the state with the loaded paket
|
||||
if (loadedPaket != null) {
|
||||
debugPrint('✅ Loaded paket: ${loadedPaket.nama}');
|
||||
paket.value = loadedPaket;
|
||||
|
||||
// Cache for future use
|
||||
GetStorage().write('cached_paket_$id', loadedPaket);
|
||||
|
||||
// Load photos for this paket
|
||||
await loadPaketPhotos(id);
|
||||
|
||||
// Set initial pricing option
|
||||
initializePriceOptions();
|
||||
|
||||
// Ensure we have at least one photo if available
|
||||
if (paketImages.isEmpty) {
|
||||
String? mainPhoto = getPaketMainPhoto(paket.value);
|
||||
if (mainPhoto != null && mainPhoto.isNotEmpty) {
|
||||
paketImages.add(mainPhoto);
|
||||
debugPrint('✅ Added main paket photo: $mainPhoto');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugPrint('❌ No paket found with id: $id');
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the total price if we have a paket loaded
|
||||
if (paket.value != null) {
|
||||
calculateTotalPrice();
|
||||
debugPrint('💰 Total price calculated: ${totalPrice.value}');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Error loading paket data: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods to safely access paket properties
|
||||
String? getPaketId(dynamic paket) {
|
||||
if (paket == null) return null;
|
||||
try {
|
||||
return paket.id ?? paket['id'];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? getPaketNama(dynamic paket) {
|
||||
if (paket == null) return null;
|
||||
try {
|
||||
return paket.nama ?? paket['nama'];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? getPaketDeskripsi(dynamic paket) {
|
||||
if (paket == null) return null;
|
||||
try {
|
||||
return paket.deskripsi ?? paket['deskripsi'];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
double getPaketHarga(dynamic paket) {
|
||||
if (paket == null) return 0.0;
|
||||
try {
|
||||
var harga = paket.harga ?? paket['harga'] ?? 0;
|
||||
return double.tryParse(harga.toString()) ?? 0.0;
|
||||
} catch (_) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
int getPaketKuantitas(dynamic paket) {
|
||||
if (paket == null) return 1;
|
||||
try {
|
||||
var qty = paket.kuantitas ?? paket['kuantitas'] ?? 1;
|
||||
return int.tryParse(qty.toString()) ?? 1;
|
||||
} catch (_) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
String? getPaketMainPhoto(dynamic paket) {
|
||||
if (paket == null) return null;
|
||||
try {
|
||||
return paket.foto_paket ?? paket['foto_paket'];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> getPaketSatuanWaktuSewa(dynamic paket) {
|
||||
if (paket == null) return [];
|
||||
try {
|
||||
return paket.satuanWaktuSewa ?? paket['satuanWaktuSewa'] ?? [];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Load photos for the paket
|
||||
Future<void> loadPaketPhotos(String paketId) async {
|
||||
try {
|
||||
isPhotosLoading.value = true;
|
||||
final photos = await asetProvider.getFotoPaket(paketId);
|
||||
if (photos != null && photos.isNotEmpty) {
|
||||
paketImages.clear();
|
||||
for (var photo in photos) {
|
||||
try {
|
||||
if (photo.fotoPaket != null && photo.fotoPaket.isNotEmpty) {
|
||||
paketImages.add(photo.fotoPaket);
|
||||
} else if (photo.fotoAset != null && photo.fotoAset.isNotEmpty) {
|
||||
paketImages.add(photo.fotoAset);
|
||||
}
|
||||
} catch (e) {
|
||||
var fotoUrl = photo['foto_paket'] ?? photo['foto_aset'];
|
||||
if (fotoUrl != null && fotoUrl.isNotEmpty) {
|
||||
paketImages.add(fotoUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isPhotosLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize price options
|
||||
void initializePriceOptions() {
|
||||
if (paket.value == null) return;
|
||||
|
||||
final satuanWaktuSewa = getPaketSatuanWaktuSewa(paket.value);
|
||||
if (satuanWaktuSewa.isNotEmpty) {
|
||||
// Default to the first option
|
||||
selectSatuanWaktu(satuanWaktuSewa.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Select satuan waktu
|
||||
void selectSatuanWaktu(Map<String, dynamic> satuanWaktu) {
|
||||
selectedSatuanWaktu.value = satuanWaktu;
|
||||
|
||||
// Reset date and time selections
|
||||
selectedStartDate.value = null;
|
||||
selectedEndDate.value = null;
|
||||
selectedStartTime.value = -1;
|
||||
selectedEndTime.value = -1;
|
||||
selectedDate.value = '';
|
||||
formattedDateRange.value = '';
|
||||
formattedTimeRange.value = '';
|
||||
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
// Check if the rental is daily
|
||||
bool isDailyRental() {
|
||||
final namaSatuan = selectedSatuanWaktu.value?['nama_satuan_waktu'] ?? '';
|
||||
return namaSatuan.toString().toLowerCase().contains('hari');
|
||||
}
|
||||
|
||||
// Select date range for daily rental
|
||||
void selectDateRange(DateTime start, DateTime end) {
|
||||
selectedStartDate.value = start;
|
||||
selectedEndDate.value = end;
|
||||
|
||||
// Format the date range
|
||||
final formatter = DateFormat('d MMM yyyy', 'id');
|
||||
if (start.year == end.year && start.month == end.month && start.day == end.day) {
|
||||
formattedDateRange.value = formatter.format(start);
|
||||
} else {
|
||||
formattedDateRange.value = '${formatter.format(start)} - ${formatter.format(end)}';
|
||||
}
|
||||
|
||||
selectedDate.value = formatter.format(start);
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
// Select date for hourly rental
|
||||
void selectDate(DateTime date) {
|
||||
selectedStartDate.value = date;
|
||||
selectedDate.value = DateFormat('d MMM yyyy', 'id').format(date);
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
// Select time range for hourly rental
|
||||
void selectTimeRange(int start, int end) {
|
||||
selectedStartTime.value = start;
|
||||
selectedEndTime.value = end;
|
||||
|
||||
// Format the time range
|
||||
final startTime = '$start:00';
|
||||
final endTime = '$end:00';
|
||||
formattedTimeRange.value = '$startTime - $endTime';
|
||||
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
// Calculate total price
|
||||
void calculateTotalPrice() {
|
||||
if (selectedSatuanWaktu.value == null) {
|
||||
totalPrice.value = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
final basePrice = double.tryParse(selectedSatuanWaktu.value!['harga'].toString()) ?? 0.0;
|
||||
|
||||
if (isDailyRental()) {
|
||||
if (selectedStartDate.value != null && selectedEndDate.value != null) {
|
||||
final days = selectedEndDate.value!.difference(selectedStartDate.value!).inDays + 1;
|
||||
totalPrice.value = basePrice * days;
|
||||
} else {
|
||||
totalPrice.value = basePrice;
|
||||
}
|
||||
} else {
|
||||
if (selectedStartTime.value >= 0 && selectedEndTime.value >= 0) {
|
||||
final hours = selectedEndTime.value - selectedStartTime.value;
|
||||
totalPrice.value = basePrice * hours;
|
||||
} else {
|
||||
totalPrice.value = basePrice;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply by quantity
|
||||
totalPrice.value *= kuantitas.value;
|
||||
}
|
||||
|
||||
// Format price as currency
|
||||
String formatPrice(double price) {
|
||||
return currencyFormat.format(price);
|
||||
}
|
||||
|
||||
// Submit order
|
||||
Future<void> submitOrder() async {
|
||||
try {
|
||||
if (paket.value == null || selectedSatuanWaktu.value == null) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Data paket tidak lengkap',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((isDailyRental() && (selectedStartDate.value == null || selectedEndDate.value == null)) ||
|
||||
(!isDailyRental() && (selectedStartDate.value == null || selectedStartTime.value < 0 || selectedEndTime.value < 0))) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Silakan pilih waktu sewa',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
||||
// Prepare order data
|
||||
final Map<String, dynamic> orderData = {
|
||||
'id_paket': paket.value!.id,
|
||||
'id_satuan_waktu_sewa': selectedSatuanWaktu.value!['id'],
|
||||
'tanggal_mulai': selectedStartDate.value!.toIso8601String(),
|
||||
'tanggal_selesai': selectedEndDate.value?.toIso8601String() ?? selectedStartDate.value!.toIso8601String(),
|
||||
'jam_mulai': isDailyRental() ? null : selectedStartTime.value,
|
||||
'jam_selesai': isDailyRental() ? null : selectedEndTime.value,
|
||||
'total_harga': totalPrice.value,
|
||||
'kuantitas': kuantitas.value,
|
||||
};
|
||||
|
||||
// Submit the order
|
||||
final result = await sewaProvider.createPaketOrder(orderData);
|
||||
|
||||
if (result != null) {
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Pesanan berhasil dibuat',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
|
||||
// Navigate to payment page
|
||||
navigationService.navigateToPembayaranSewa(result['id']);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal membuat pesanan',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Error submitting order: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Terjadi kesalahan: $e',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle back button press
|
||||
void onBackPressed() {
|
||||
navigationService.navigateToSewaAset();
|
||||
}
|
||||
}
|
1115
lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak
Normal file
1115
lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak
Normal file
File diff suppressed because it is too large
Load Diff
1202
lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart
Normal file
1202
lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart
Normal file
File diff suppressed because it is too large
Load Diff
471
lib/app/modules/warga/controllers/sewa_aset_controller.dart
Normal file
471
lib/app/modules/warga/controllers/sewa_aset_controller.dart
Normal file
@ -0,0 +1,471 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
import '../../../data/models/aset_model.dart';
|
||||
import '../../../routes/app_routes.dart';
|
||||
import '../../../data/models/pesanan_model.dart';
|
||||
import '../../../data/models/satuan_waktu_model.dart';
|
||||
import '../../../data/models/satuan_waktu_sewa_model.dart';
|
||||
import '../../../data/providers/auth_provider.dart';
|
||||
import '../../../data/providers/pesanan_provider.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
import '../../../services/service_manager.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class SewaAsetController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
final AsetProvider _asetProvider = Get.find<AsetProvider>();
|
||||
final AuthProvider authProvider = Get.find<AuthProvider>();
|
||||
final PesananProvider pesananProvider = Get.put(PesananProvider());
|
||||
final NavigationService navigationService = Get.find<NavigationService>();
|
||||
final box = GetStorage();
|
||||
|
||||
// Tab controller
|
||||
late TabController tabController;
|
||||
// Reactive tab index
|
||||
final currentTabIndex = 0.obs;
|
||||
|
||||
// State variables
|
||||
final asets = <AsetModel>[].obs;
|
||||
final filteredAsets = <AsetModel>[].obs;
|
||||
|
||||
// Paket-related variables
|
||||
final pakets = RxList<dynamic>([]);
|
||||
final filteredPakets = RxList<dynamic>([]);
|
||||
final isLoadingPakets = false.obs;
|
||||
|
||||
final isLoading = true.obs;
|
||||
|
||||
// Search controller
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
// Reactive variables
|
||||
final isOrdering = false.obs;
|
||||
final selectedAset = Rx<AsetModel?>(null);
|
||||
final selectedSatuanWaktuSewa = Rx<SatuanWaktuSewaModel?>(null);
|
||||
final selectedDurasi = 1.obs;
|
||||
final totalHarga = 0.obs;
|
||||
final selectedDate = DateTime.now().obs;
|
||||
final selectedTime = '08:00'.obs;
|
||||
final satuanWaktuDropdownItems =
|
||||
<DropdownMenuItem<SatuanWaktuSewaModel>>[].obs;
|
||||
|
||||
// Flag untuk menangani hot reload
|
||||
final hasInitialized = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
debugPrint('🚀 SewaAsetController: onInit called');
|
||||
|
||||
// Initialize tab controller
|
||||
tabController = TabController(length: 2, vsync: this);
|
||||
// Listen for tab changes
|
||||
tabController.addListener(() {
|
||||
currentTabIndex.value = tabController.index;
|
||||
|
||||
// Load packages data when switching to package tab for the first time
|
||||
if (currentTabIndex.value == 1 && pakets.isEmpty) {
|
||||
loadPakets();
|
||||
}
|
||||
});
|
||||
|
||||
loadAsets();
|
||||
|
||||
searchController.addListener(() {
|
||||
if (currentTabIndex.value == 0) {
|
||||
filterAsets(searchController.text);
|
||||
} else {
|
||||
filterPakets(searchController.text);
|
||||
}
|
||||
});
|
||||
|
||||
hasInitialized.value = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
debugPrint('🚀 SewaAsetController: onReady called');
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
debugPrint('🧹 SewaAsetController: onClose called');
|
||||
searchController.dispose();
|
||||
tabController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// Method untuk menangani hot reload
|
||||
void handleHotReload() {
|
||||
debugPrint('🔥 Hot reload detected in SewaAsetController');
|
||||
if (!hasInitialized.value) {
|
||||
debugPrint('🔄 Reinitializing SewaAsetController after hot reload');
|
||||
loadAsets();
|
||||
if (currentTabIndex.value == 1) {
|
||||
loadPakets();
|
||||
}
|
||||
hasInitialized.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method untuk menangani tombol back
|
||||
void onBackPressed() {
|
||||
debugPrint('🔙 Back button pressed in SewaAsetView');
|
||||
navigationService.backFromSewaAset();
|
||||
}
|
||||
|
||||
Future<void> loadAsets() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final sewaAsets = await _asetProvider.getSewaAsets();
|
||||
|
||||
// Debug data satuan waktu sewa yang diterima
|
||||
debugPrint('===== DEBUG ASET & SATUAN WAKTU SEWA =====');
|
||||
for (var aset in sewaAsets) {
|
||||
debugPrint('Aset: ${aset.nama} (ID: ${aset.id})');
|
||||
|
||||
if (aset.satuanWaktuSewa.isEmpty) {
|
||||
debugPrint(' - Tidak ada satuan waktu sewa yang terkait');
|
||||
} else {
|
||||
debugPrint(
|
||||
' - Memiliki ${aset.satuanWaktuSewa.length} satuan waktu sewa:',
|
||||
);
|
||||
for (var sws in aset.satuanWaktuSewa) {
|
||||
debugPrint(' * ID: ${sws['id']}');
|
||||
debugPrint(' Aset ID: ${sws['aset_id']}');
|
||||
debugPrint(' Satuan Waktu ID: ${sws['satuan_waktu_id']}');
|
||||
debugPrint(' Harga: ${sws['harga']}');
|
||||
debugPrint(' Nama Satuan Waktu: ${sws['nama_satuan_waktu']}');
|
||||
debugPrint(' -----');
|
||||
}
|
||||
}
|
||||
debugPrint('=====================================');
|
||||
}
|
||||
|
||||
asets.assignAll(sewaAsets);
|
||||
filteredAsets.assignAll(sewaAsets);
|
||||
|
||||
// Tambahkan log info tentang jumlah aset yang berhasil dimuat
|
||||
debugPrint('Loaded ${sewaAsets.length} aset sewa successfully');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading asets: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Terjadi kesalahan saat memuat data aset',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void filterAsets(String query) {
|
||||
if (query.isEmpty) {
|
||||
filteredAsets.assignAll(asets);
|
||||
} else {
|
||||
filteredAsets.assignAll(
|
||||
asets
|
||||
.where(
|
||||
(aset) => aset.nama.toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void refreshAsets() {
|
||||
loadAsets();
|
||||
}
|
||||
|
||||
String formatPrice(dynamic price) {
|
||||
if (price == null) return 'Rp 0';
|
||||
|
||||
// Handle different types
|
||||
num numericPrice;
|
||||
if (price is int || price is double) {
|
||||
numericPrice = price;
|
||||
} else if (price is String) {
|
||||
numericPrice = double.tryParse(price) ?? 0;
|
||||
} else {
|
||||
return 'Rp 0';
|
||||
}
|
||||
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
return formatter.format(numericPrice);
|
||||
}
|
||||
|
||||
void selectAset(AsetModel aset) {
|
||||
selectedAset.value = aset;
|
||||
// Reset related values
|
||||
selectedSatuanWaktuSewa.value = null;
|
||||
selectedDurasi.value = 1;
|
||||
totalHarga.value = 0;
|
||||
|
||||
// Prepare dropdown items for satuan waktu sewa
|
||||
updateSatuanWaktuDropdown();
|
||||
}
|
||||
|
||||
void updateSatuanWaktuDropdown() {
|
||||
satuanWaktuDropdownItems.clear();
|
||||
|
||||
if (selectedAset.value != null &&
|
||||
selectedAset.value!.satuanWaktuSewa.isNotEmpty) {
|
||||
for (var item in selectedAset.value!.satuanWaktuSewa) {
|
||||
final satuanWaktuSewa = SatuanWaktuSewaModel.fromJson(item);
|
||||
satuanWaktuDropdownItems.add(
|
||||
DropdownMenuItem<SatuanWaktuSewaModel>(
|
||||
value: satuanWaktuSewa,
|
||||
child: Text(
|
||||
'${satuanWaktuSewa.namaSatuanWaktu ?? "Unknown"} - Rp${NumberFormat.decimalPattern('id').format(satuanWaktuSewa.harga)}',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void selectSatuanWaktu(SatuanWaktuSewaModel? satuanWaktuSewa) {
|
||||
selectedSatuanWaktuSewa.value = satuanWaktuSewa;
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
void updateDurasi(int durasi) {
|
||||
if (durasi < 1) durasi = 1;
|
||||
selectedDurasi.value = durasi;
|
||||
calculateTotalPrice();
|
||||
}
|
||||
|
||||
void calculateTotalPrice() {
|
||||
if (selectedSatuanWaktuSewa.value != null) {
|
||||
totalHarga.value =
|
||||
selectedSatuanWaktuSewa.value!.harga * selectedDurasi.value;
|
||||
} else {
|
||||
totalHarga.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void pickDate(DateTime date) {
|
||||
selectedDate.value = date;
|
||||
}
|
||||
|
||||
void pickTime(String time) {
|
||||
selectedTime.value = time;
|
||||
}
|
||||
|
||||
// Helper method to show error snackbar
|
||||
void _showError(String message) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
message,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
// Method untuk melakukan pemesanan
|
||||
Future<void> placeOrderAset() async {
|
||||
if (selectedAset.value == null) {
|
||||
_showError('Silakan pilih aset terlebih dahulu');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSatuanWaktuSewa.value == null) {
|
||||
_showError('Silakan pilih satuan waktu sewa');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDurasi.value <= 0) {
|
||||
_showError('Durasi sewa harus lebih dari 0');
|
||||
return;
|
||||
}
|
||||
|
||||
final userId = authProvider.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
_showError('Anda belum login, silakan login terlebih dahulu');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final result = await _asetProvider.orderAset(
|
||||
userId: userId,
|
||||
asetId: selectedAset.value!.id,
|
||||
satuanWaktuSewaId: selectedSatuanWaktuSewa.value!.id,
|
||||
durasi: selectedDurasi.value,
|
||||
totalHarga: totalHarga.value,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Pesanan berhasil dibuat',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
resetSelections();
|
||||
} else {
|
||||
_showError('Gagal membuat pesanan');
|
||||
}
|
||||
} catch (e) {
|
||||
_showError('Terjadi kesalahan: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Method untuk reset pilihan setelah pemesanan berhasil
|
||||
void resetSelections() {
|
||||
selectedAset.value = null;
|
||||
selectedSatuanWaktuSewa.value = null;
|
||||
selectedDurasi.value = 1;
|
||||
totalHarga.value = 0;
|
||||
}
|
||||
|
||||
// Load packages data from paket table
|
||||
Future<void> loadPakets() async {
|
||||
try {
|
||||
isLoadingPakets.value = true;
|
||||
|
||||
// Call the provider method to get paket data
|
||||
final paketData = await _asetProvider.getPakets();
|
||||
|
||||
// Debug paket data
|
||||
debugPrint('===== DEBUG PAKET & SATUAN WAKTU SEWA =====');
|
||||
for (var paket in paketData) {
|
||||
debugPrint('Paket: ${paket['nama']} (ID: ${paket['id']})');
|
||||
|
||||
if (paket['satuanWaktuSewa'] == null ||
|
||||
paket['satuanWaktuSewa'].isEmpty) {
|
||||
debugPrint(' - Tidak ada satuan waktu sewa yang terkait');
|
||||
} else {
|
||||
debugPrint(
|
||||
' - Memiliki ${paket['satuanWaktuSewa'].length} satuan waktu sewa:',
|
||||
);
|
||||
for (var sws in paket['satuanWaktuSewa']) {
|
||||
debugPrint(' * ID: ${sws['id']}');
|
||||
debugPrint(' Paket ID: ${sws['paket_id']}');
|
||||
debugPrint(' Satuan Waktu ID: ${sws['satuan_waktu_id']}');
|
||||
debugPrint(' Harga: ${sws['harga']}');
|
||||
debugPrint(' Nama Satuan Waktu: ${sws['nama_satuan_waktu']}');
|
||||
debugPrint(' -----');
|
||||
}
|
||||
}
|
||||
debugPrint('=====================================');
|
||||
}
|
||||
|
||||
pakets.assignAll(paketData);
|
||||
filteredPakets.assignAll(paketData);
|
||||
|
||||
debugPrint('Loaded ${paketData.length} paket successfully');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading pakets: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Terjadi kesalahan saat memuat data paket',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoadingPakets.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to filter pakets based on search query
|
||||
void filterPakets(String query) {
|
||||
if (query.isEmpty) {
|
||||
filteredPakets.assignAll(pakets);
|
||||
} else {
|
||||
filteredPakets.assignAll(
|
||||
pakets
|
||||
.where(
|
||||
(paket) => paket['nama'].toString().toLowerCase().contains(
|
||||
query.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void refreshPakets() {
|
||||
loadPakets();
|
||||
}
|
||||
|
||||
// Method to load paket data
|
||||
Future<void> loadPaketData() async {
|
||||
try {
|
||||
isLoadingPakets.value = true;
|
||||
final result = await _asetProvider.getPakets();
|
||||
if (result != null) {
|
||||
pakets.clear();
|
||||
filteredPakets.clear();
|
||||
pakets.addAll(result);
|
||||
filteredPakets.addAll(result);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error loading pakets: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal memuat data paket. Silakan coba lagi nanti.',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoadingPakets.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method for placing an order for a paket
|
||||
Future<void> placeOrderPaket({
|
||||
required String paketId,
|
||||
required String satuanWaktuSewaId,
|
||||
required int durasi,
|
||||
required int totalHarga,
|
||||
}) async {
|
||||
debugPrint('===== PLACE ORDER PAKET =====');
|
||||
debugPrint('paketId: $paketId');
|
||||
debugPrint('satuanWaktuSewaId: $satuanWaktuSewaId');
|
||||
debugPrint('durasi: $durasi');
|
||||
debugPrint('totalHarga: $totalHarga');
|
||||
|
||||
final userId = authProvider.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
_showError('Anda belum login, silakan login terlebih dahulu');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final result = await _asetProvider.orderPaket(
|
||||
userId: userId,
|
||||
paketId: paketId,
|
||||
satuanWaktuSewaId: satuanWaktuSewaId,
|
||||
durasi: durasi,
|
||||
totalHarga: totalHarga,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Pesanan paket berhasil dibuat',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} else {
|
||||
_showError('Gagal membuat pesanan paket');
|
||||
}
|
||||
} catch (e) {
|
||||
_showError('Terjadi kesalahan: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../../data/providers/auth_provider.dart';
|
||||
import '../../../routes/app_routes.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
|
||||
class WargaDashboardController extends GetxController {
|
||||
// Dependency injection
|
||||
final AuthProvider _authProvider = Get.find<AuthProvider>();
|
||||
final NavigationService navigationService = Get.find<NavigationService>();
|
||||
|
||||
// User data
|
||||
final userName = 'Pengguna Warga'.obs;
|
||||
final userRole = 'Warga'.obs;
|
||||
final userAvatar = Rx<String?>(null);
|
||||
final userEmail = ''.obs;
|
||||
final userNik = ''.obs;
|
||||
final userPhone = ''.obs;
|
||||
final userAddress = ''.obs;
|
||||
|
||||
// Navigation state is now managed by NavigationService
|
||||
|
||||
// Sample data (would be loaded from API)
|
||||
final activeRentals = <Map<String, dynamic>>[].obs;
|
||||
|
||||
// Active bills
|
||||
final activeBills = <Map<String, dynamic>>[].obs;
|
||||
|
||||
// Active penalties
|
||||
final activePenalties = <Map<String, dynamic>>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Set navigation index to Home (0)
|
||||
navigationService.setNavIndex(0);
|
||||
|
||||
// Load user data
|
||||
_loadUserData();
|
||||
|
||||
// Load sample data
|
||||
_loadSampleData();
|
||||
|
||||
// Load dummy data for bills and penalties
|
||||
loadDummyData();
|
||||
|
||||
// Load unpaid rentals
|
||||
loadUnpaidRentals();
|
||||
}
|
||||
|
||||
Future<void> _loadUserData() async {
|
||||
try {
|
||||
// Get the full name from warga_desa table
|
||||
final fullName = await _authProvider.getUserFullName();
|
||||
if (fullName != null && fullName.isNotEmpty) {
|
||||
userName.value = fullName;
|
||||
}
|
||||
|
||||
// Get the avatar URL
|
||||
final avatar = await _authProvider.getUserAvatar();
|
||||
userAvatar.value = avatar;
|
||||
|
||||
// Get the role name
|
||||
final roleId = await _authProvider.getUserRoleId();
|
||||
if (roleId != null) {
|
||||
final roleName = await _authProvider.getRoleName(roleId);
|
||||
if (roleName != null) {
|
||||
userRole.value = roleName;
|
||||
}
|
||||
}
|
||||
|
||||
// Load additional user data
|
||||
// In a real app, these would come from the API/database
|
||||
userEmail.value = await _authProvider.getUserEmail() ?? '';
|
||||
userNik.value = await _authProvider.getUserNIK() ?? '';
|
||||
userPhone.value = await _authProvider.getUserPhone() ?? '';
|
||||
userAddress.value = await _authProvider.getUserAddress() ?? '';
|
||||
} catch (e) {
|
||||
print('Error loading user data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _loadSampleData() {
|
||||
// Clear any existing data
|
||||
activeRentals.clear();
|
||||
|
||||
// Load active rentals from API
|
||||
// For now, using sample data
|
||||
activeRentals.add({
|
||||
'id': '1',
|
||||
'name': 'Kursi',
|
||||
'time': '24 April 2023, 10:00 - 12:00',
|
||||
'duration': '2 jam',
|
||||
'price': 'Rp50.000',
|
||||
'can_extend': true,
|
||||
});
|
||||
}
|
||||
|
||||
void extendRental(String rentalId) {
|
||||
// Implementasi untuk memperpanjang sewa
|
||||
// Seharusnya melakukan API call ke backend
|
||||
}
|
||||
|
||||
void endRental(String rentalId) {
|
||||
// Implementasi untuk mengakhiri sewa
|
||||
// Seharusnya melakukan API call ke backend
|
||||
}
|
||||
|
||||
void navigateToRentals() {
|
||||
// Navigate to SewaAset using the navigation service
|
||||
navigationService.toSewaAset();
|
||||
}
|
||||
|
||||
void refreshData() {
|
||||
// Refresh data from repository
|
||||
_loadSampleData();
|
||||
loadDummyData();
|
||||
}
|
||||
|
||||
void onNavItemTapped(int index) {
|
||||
if (navigationService.currentNavIndex.value == index) {
|
||||
return; // Don't do anything if same tab
|
||||
}
|
||||
|
||||
navigationService.setNavIndex(index);
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
// Already on Home tab
|
||||
break;
|
||||
case 1:
|
||||
// Navigate to Sewa page
|
||||
navigationService.toWargaSewa();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void logout() async {
|
||||
await _authProvider.signOut();
|
||||
navigationService.toLogin();
|
||||
}
|
||||
|
||||
void loadDummyData() {
|
||||
// Dummy active bills
|
||||
activeBills.clear();
|
||||
activeBills.add({
|
||||
'id': '1',
|
||||
'title': 'Tagihan Air',
|
||||
'due_date': '30 Apr 2023',
|
||||
'amount': 'Rp 125.000',
|
||||
});
|
||||
activeBills.add({
|
||||
'id': '2',
|
||||
'title': 'Sewa Aula Desa',
|
||||
'due_date': '15 Apr 2023',
|
||||
'amount': 'Rp 350.000',
|
||||
});
|
||||
|
||||
// Dummy active penalties
|
||||
activePenalties.clear();
|
||||
activePenalties.add({
|
||||
'id': '1',
|
||||
'title': 'Keterlambatan Sewa Traktor',
|
||||
'days_late': '7',
|
||||
'amount': 'Rp 75.000',
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> loadUnpaidRentals() async {
|
||||
try {
|
||||
final results = await _authProvider.getSewaAsetByStatus([
|
||||
'MENUNGGU PEMBAYARAN',
|
||||
'PEMBAYARANAN DENDA',
|
||||
]);
|
||||
activeBills.value = results;
|
||||
} catch (e) {
|
||||
print('Error loading unpaid rentals: $e');
|
||||
}
|
||||
}
|
||||
}
|
710
lib/app/modules/warga/controllers/warga_sewa_controller.dart
Normal file
710
lib/app/modules/warga/controllers/warga_sewa_controller.dart
Normal file
@ -0,0 +1,710 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../routes/app_routes.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
import '../../../data/providers/auth_provider.dart';
|
||||
import '../../../data/providers/aset_provider.dart';
|
||||
|
||||
class WargaSewaController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
|
||||
// Get navigation service
|
||||
final NavigationService navigationService = Get.find<NavigationService>();
|
||||
|
||||
// Get auth provider for user data and sewa_aset queries
|
||||
final AuthProvider authProvider = Get.find<AuthProvider>();
|
||||
|
||||
// Get aset provider for asset data
|
||||
final AsetProvider asetProvider = Get.find<AsetProvider>();
|
||||
|
||||
// Observable lists for different rental statuses
|
||||
final rentals = <Map<String, dynamic>>[].obs;
|
||||
final pendingRentals = <Map<String, dynamic>>[].obs;
|
||||
final acceptedRentals = <Map<String, dynamic>>[].obs;
|
||||
final completedRentals = <Map<String, dynamic>>[].obs;
|
||||
final cancelledRentals = <Map<String, dynamic>>[].obs;
|
||||
|
||||
// Loading states
|
||||
final isLoading = false.obs;
|
||||
final isLoadingPending = false.obs;
|
||||
final isLoadingAccepted = false.obs;
|
||||
final isLoadingCompleted = false.obs;
|
||||
final isLoadingCancelled = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Ensure tab index is set to Sewa (1)
|
||||
navigationService.setNavIndex(1);
|
||||
|
||||
// Initialize tab controller with 6 tabs
|
||||
tabController = TabController(length: 6, vsync: this);
|
||||
|
||||
// Set initial tab and ensure tab view is updated
|
||||
tabController.index = 0;
|
||||
|
||||
// Load real rental data for all tabs
|
||||
loadRentalsData();
|
||||
loadPendingRentals();
|
||||
loadAcceptedRentals();
|
||||
loadCompletedRentals();
|
||||
loadCancelledRentals();
|
||||
|
||||
// Listen to tab changes to update state if needed
|
||||
tabController.addListener(() {
|
||||
// Update selected tab index when changed via swipe
|
||||
final int currentIndex = tabController.index;
|
||||
debugPrint('Tab changed to index: $currentIndex');
|
||||
|
||||
// Load data for the selected tab if not already loaded
|
||||
switch (currentIndex) {
|
||||
case 0: // Belum Bayar
|
||||
if (rentals.isEmpty && !isLoading.value) {
|
||||
loadRentalsData();
|
||||
}
|
||||
break;
|
||||
case 1: // Pending
|
||||
if (pendingRentals.isEmpty && !isLoadingPending.value) {
|
||||
loadPendingRentals();
|
||||
}
|
||||
break;
|
||||
case 2: // Diterima
|
||||
if (acceptedRentals.isEmpty && !isLoadingAccepted.value) {
|
||||
loadAcceptedRentals();
|
||||
}
|
||||
break;
|
||||
case 3: // Aktif
|
||||
// Add Aktif tab logic when needed
|
||||
break;
|
||||
case 4: // Selesai
|
||||
if (completedRentals.isEmpty && !isLoadingCompleted.value) {
|
||||
loadCompletedRentals();
|
||||
}
|
||||
break;
|
||||
case 5: // Dibatalkan
|
||||
if (cancelledRentals.isEmpty && !isLoadingCancelled.value) {
|
||||
loadCancelledRentals();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// Ensure nav index is set to Sewa (1) when the controller is ready
|
||||
// This helps maintain correct state during hot reload
|
||||
navigationService.setNavIndex(1);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// Load real data from sewa_aset table
|
||||
Future<void> loadRentalsData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// Clear existing data
|
||||
rentals.clear();
|
||||
|
||||
// Get sewa_aset data with status "MENUNGGU PEMBAYARAN" or "PEMBAYARAN DENDA"
|
||||
final sewaAsetList = await authProvider.getSewaAsetByStatus([
|
||||
'MENUNGGU PEMBAYARAN',
|
||||
'PEMBAYARAN DENDA'
|
||||
]);
|
||||
|
||||
debugPrint('Fetched ${sewaAsetList.length} sewa_aset records');
|
||||
|
||||
// Process each sewa_aset record
|
||||
for (var sewaAset in sewaAsetList) {
|
||||
// Get asset details if aset_id is available
|
||||
String assetName = 'Aset';
|
||||
String? imageUrl;
|
||||
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
||||
|
||||
if (sewaAset['aset_id'] != null) {
|
||||
final asetData = await asetProvider.getAsetById(sewaAset['aset_id']);
|
||||
if (asetData != null) {
|
||||
assetName = asetData.nama;
|
||||
imageUrl = asetData.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse waktu mulai and waktu selesai
|
||||
DateTime? waktuMulai;
|
||||
DateTime? waktuSelesai;
|
||||
String waktuSewa = '';
|
||||
String tanggalSewa = '';
|
||||
String jamMulai = '';
|
||||
String jamSelesai = '';
|
||||
String rentangWaktu = '';
|
||||
|
||||
if (sewaAset['waktu_mulai'] != null && sewaAset['waktu_selesai'] != null) {
|
||||
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
||||
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
||||
|
||||
// Format for display
|
||||
final formatTanggal = DateFormat('dd-MM-yyyy');
|
||||
final formatWaktu = DateFormat('HH:mm');
|
||||
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||
|
||||
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
||||
jamMulai = formatWaktu.format(waktuMulai);
|
||||
jamSelesai = formatWaktu.format(waktuSelesai);
|
||||
|
||||
// Format based on satuan waktu
|
||||
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
||||
// For hours, show time range on same day
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
||||
// For days, show date range
|
||||
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
||||
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
||||
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
||||
} else {
|
||||
// Default format
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
}
|
||||
|
||||
// Full time format for waktuSewa
|
||||
waktuSewa = '${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
||||
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
||||
}
|
||||
|
||||
// Format price
|
||||
String totalPrice = 'Rp 0';
|
||||
if (sewaAset['total'] != null) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
totalPrice = formatter.format(sewaAset['total']);
|
||||
}
|
||||
|
||||
// Add to rentals list
|
||||
rentals.add({
|
||||
'id': sewaAset['id'] ?? '',
|
||||
'name': assetName,
|
||||
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
||||
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
||||
'waktuSewa': waktuSewa,
|
||||
'duration': '${sewaAset['durasi'] ?? 0} ${namaSatuanWaktu}',
|
||||
'status': sewaAset['status'] ?? 'MENUNGGU PEMBAYARAN',
|
||||
'totalPrice': totalPrice,
|
||||
'countdown': '00:59:59', // Default countdown
|
||||
'tanggalSewa': tanggalSewa,
|
||||
'jamMulai': jamMulai,
|
||||
'jamSelesai': jamSelesai,
|
||||
'rentangWaktu': rentangWaktu,
|
||||
'namaSatuanWaktu': namaSatuanWaktu,
|
||||
'waktuMulai': sewaAset['waktu_mulai'],
|
||||
'waktuSelesai': sewaAset['waktu_selesai'],
|
||||
});
|
||||
}
|
||||
|
||||
debugPrint('Processed ${rentals.length} rental records');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading rentals data: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
void navigateToRentals() {
|
||||
navigationService.toSewaAset();
|
||||
}
|
||||
|
||||
void onNavItemTapped(int index) {
|
||||
if (navigationService.currentNavIndex.value == index) return;
|
||||
|
||||
navigationService.setNavIndex(index);
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
// Navigate to Home
|
||||
Get.offNamed(Routes.WARGA_DASHBOARD);
|
||||
break;
|
||||
case 1:
|
||||
// Already on Sewa tab
|
||||
break;
|
||||
case 2:
|
||||
// Navigate to Langganan
|
||||
Get.offNamed(Routes.LANGGANAN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
void cancelRental(String id) {
|
||||
Get.snackbar(
|
||||
'Info',
|
||||
'Pembatalan berhasil',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
|
||||
// Navigate to payment page with the selected rental data
|
||||
void viewRentalDetail(Map<String, dynamic> rental) {
|
||||
debugPrint('Navigating to payment page with rental ID: ${rental['id']}');
|
||||
|
||||
// Navigate to payment page with rental data
|
||||
Get.toNamed(
|
||||
Routes.PEMBAYARAN_SEWA,
|
||||
arguments: {
|
||||
'orderId': rental['id'],
|
||||
'rentalData': rental,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void payRental(String id) {
|
||||
Get.snackbar(
|
||||
'Info',
|
||||
'Navigasi ke halaman pembayaran',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
|
||||
// Load data for the Selesai tab (status: SELESAI)
|
||||
Future<void> loadCompletedRentals() async {
|
||||
try {
|
||||
isLoadingCompleted.value = true;
|
||||
|
||||
// Clear existing data
|
||||
completedRentals.clear();
|
||||
|
||||
// Get sewa_aset data with status "SELESAI"
|
||||
final sewaAsetList = await authProvider.getSewaAsetByStatus(['SELESAI']);
|
||||
|
||||
debugPrint('Fetched ${sewaAsetList.length} completed sewa_aset records');
|
||||
|
||||
// Process each sewa_aset record
|
||||
for (var sewaAset in sewaAsetList) {
|
||||
// Get asset details if aset_id is available
|
||||
String assetName = 'Aset';
|
||||
String? imageUrl;
|
||||
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
||||
|
||||
if (sewaAset['aset_id'] != null) {
|
||||
final asetData = await asetProvider.getAsetById(sewaAset['aset_id']);
|
||||
if (asetData != null) {
|
||||
assetName = asetData.nama;
|
||||
imageUrl = asetData.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse waktu mulai and waktu selesai
|
||||
DateTime? waktuMulai;
|
||||
DateTime? waktuSelesai;
|
||||
String waktuSewa = '';
|
||||
String tanggalSewa = '';
|
||||
String jamMulai = '';
|
||||
String jamSelesai = '';
|
||||
String rentangWaktu = '';
|
||||
|
||||
if (sewaAset['waktu_mulai'] != null && sewaAset['waktu_selesai'] != null) {
|
||||
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
||||
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
||||
|
||||
// Format for display
|
||||
final formatTanggal = DateFormat('dd-MM-yyyy');
|
||||
final formatWaktu = DateFormat('HH:mm');
|
||||
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||
|
||||
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
||||
jamMulai = formatWaktu.format(waktuMulai);
|
||||
jamSelesai = formatWaktu.format(waktuSelesai);
|
||||
|
||||
// Format based on satuan waktu
|
||||
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
||||
// For hours, show time range on same day
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
||||
// For days, show date range
|
||||
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
||||
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
||||
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
||||
} else {
|
||||
// Default format
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
}
|
||||
|
||||
// Full time format for waktuSewa
|
||||
waktuSewa = '${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
||||
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
||||
}
|
||||
|
||||
// Format price
|
||||
String totalPrice = 'Rp 0';
|
||||
if (sewaAset['total'] != null) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
totalPrice = formatter.format(sewaAset['total']);
|
||||
}
|
||||
|
||||
// Add to completed rentals list
|
||||
completedRentals.add({
|
||||
'id': sewaAset['id'] ?? '',
|
||||
'name': assetName,
|
||||
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
||||
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
||||
'waktuSewa': waktuSewa,
|
||||
'duration': '${sewaAset['durasi'] ?? 0} ${namaSatuanWaktu}',
|
||||
'status': sewaAset['status'] ?? 'SELESAI',
|
||||
'totalPrice': totalPrice,
|
||||
'tanggalSewa': tanggalSewa,
|
||||
'jamMulai': jamMulai,
|
||||
'jamSelesai': jamSelesai,
|
||||
'rentangWaktu': rentangWaktu,
|
||||
'namaSatuanWaktu': namaSatuanWaktu,
|
||||
'waktuMulai': sewaAset['waktu_mulai'],
|
||||
'waktuSelesai': sewaAset['waktu_selesai'],
|
||||
});
|
||||
}
|
||||
|
||||
debugPrint('Processed ${completedRentals.length} completed rental records');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading completed rentals data: $e');
|
||||
} finally {
|
||||
isLoadingCompleted.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load data for the Dibatalkan tab (status: DIBATALKAN)
|
||||
Future<void> loadCancelledRentals() async {
|
||||
try {
|
||||
isLoadingCancelled.value = true;
|
||||
|
||||
// Clear existing data
|
||||
cancelledRentals.clear();
|
||||
|
||||
// Get sewa_aset data with status "DIBATALKAN"
|
||||
final sewaAsetList = await authProvider.getSewaAsetByStatus(['DIBATALKAN']);
|
||||
|
||||
debugPrint('Fetched ${sewaAsetList.length} cancelled sewa_aset records');
|
||||
|
||||
// Process each sewa_aset record
|
||||
for (var sewaAset in sewaAsetList) {
|
||||
// Get asset details if aset_id is available
|
||||
String assetName = 'Aset';
|
||||
String? imageUrl;
|
||||
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
||||
|
||||
if (sewaAset['aset_id'] != null) {
|
||||
final asetData = await asetProvider.getAsetById(sewaAset['aset_id']);
|
||||
if (asetData != null) {
|
||||
assetName = asetData.nama;
|
||||
imageUrl = asetData.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse waktu mulai and waktu selesai
|
||||
DateTime? waktuMulai;
|
||||
DateTime? waktuSelesai;
|
||||
String waktuSewa = '';
|
||||
String tanggalSewa = '';
|
||||
String jamMulai = '';
|
||||
String jamSelesai = '';
|
||||
String rentangWaktu = '';
|
||||
|
||||
if (sewaAset['waktu_mulai'] != null && sewaAset['waktu_selesai'] != null) {
|
||||
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
||||
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
||||
|
||||
// Format for display
|
||||
final formatTanggal = DateFormat('dd-MM-yyyy');
|
||||
final formatWaktu = DateFormat('HH:mm');
|
||||
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||
|
||||
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
||||
jamMulai = formatWaktu.format(waktuMulai);
|
||||
jamSelesai = formatWaktu.format(waktuSelesai);
|
||||
|
||||
// Format based on satuan waktu
|
||||
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
||||
// For hours, show time range on same day
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
||||
// For days, show date range
|
||||
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
||||
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
||||
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
||||
} else {
|
||||
// Default format
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
}
|
||||
|
||||
// Full time format for waktuSewa
|
||||
waktuSewa = '${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
||||
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
||||
}
|
||||
|
||||
// Format price
|
||||
String totalPrice = 'Rp 0';
|
||||
if (sewaAset['total'] != null) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
totalPrice = formatter.format(sewaAset['total']);
|
||||
}
|
||||
|
||||
// Add to cancelled rentals list
|
||||
cancelledRentals.add({
|
||||
'id': sewaAset['id'] ?? '',
|
||||
'name': assetName,
|
||||
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
||||
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
||||
'waktuSewa': waktuSewa,
|
||||
'duration': '${sewaAset['durasi'] ?? 0} ${namaSatuanWaktu}',
|
||||
'status': sewaAset['status'] ?? 'DIBATALKAN',
|
||||
'totalPrice': totalPrice,
|
||||
'tanggalSewa': tanggalSewa,
|
||||
'jamMulai': jamMulai,
|
||||
'jamSelesai': jamSelesai,
|
||||
'rentangWaktu': rentangWaktu,
|
||||
'namaSatuanWaktu': namaSatuanWaktu,
|
||||
'waktuMulai': sewaAset['waktu_mulai'],
|
||||
'waktuSelesai': sewaAset['waktu_selesai'],
|
||||
'alasanPembatalan': sewaAset['alasan_pembatalan'] ?? '-',
|
||||
});
|
||||
}
|
||||
|
||||
debugPrint('Processed ${cancelledRentals.length} cancelled rental records');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading cancelled rentals data: $e');
|
||||
} finally {
|
||||
isLoadingCancelled.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load data for the Pending tab (status: PERIKSA PEMBAYARAN)
|
||||
Future<void> loadPendingRentals() async {
|
||||
try {
|
||||
isLoadingPending.value = true;
|
||||
|
||||
// Clear existing data
|
||||
pendingRentals.clear();
|
||||
|
||||
// Get sewa_aset data with status "PERIKSA PEMBAYARAN"
|
||||
final sewaAsetList = await authProvider.getSewaAsetByStatus(['PERIKSA PEMBAYARAN']);
|
||||
|
||||
debugPrint('Fetched ${sewaAsetList.length} pending sewa_aset records');
|
||||
|
||||
// Process each sewa_aset record
|
||||
for (var sewaAset in sewaAsetList) {
|
||||
// Get asset details if aset_id is available
|
||||
String assetName = 'Aset';
|
||||
String? imageUrl;
|
||||
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
||||
|
||||
if (sewaAset['aset_id'] != null) {
|
||||
final asetData = await asetProvider.getAsetById(sewaAset['aset_id']);
|
||||
if (asetData != null) {
|
||||
assetName = asetData.nama;
|
||||
imageUrl = asetData.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse waktu mulai and waktu selesai
|
||||
DateTime? waktuMulai;
|
||||
DateTime? waktuSelesai;
|
||||
String waktuSewa = '';
|
||||
String tanggalSewa = '';
|
||||
String jamMulai = '';
|
||||
String jamSelesai = '';
|
||||
String rentangWaktu = '';
|
||||
|
||||
if (sewaAset['waktu_mulai'] != null && sewaAset['waktu_selesai'] != null) {
|
||||
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
||||
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
||||
|
||||
// Format for display
|
||||
final formatTanggal = DateFormat('dd-MM-yyyy');
|
||||
final formatWaktu = DateFormat('HH:mm');
|
||||
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||
|
||||
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
||||
jamMulai = formatWaktu.format(waktuMulai);
|
||||
jamSelesai = formatWaktu.format(waktuSelesai);
|
||||
|
||||
// Format based on satuan waktu
|
||||
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
||||
// For hours, show time range on same day
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
||||
// For days, show date range
|
||||
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
||||
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
||||
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
||||
} else {
|
||||
// Default format
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
}
|
||||
|
||||
// Full time format for waktuSewa
|
||||
waktuSewa = '${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
||||
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
||||
}
|
||||
|
||||
// Format price
|
||||
String totalPrice = 'Rp 0';
|
||||
if (sewaAset['total'] != null) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
totalPrice = formatter.format(sewaAset['total']);
|
||||
}
|
||||
|
||||
// Add to pending rentals list
|
||||
pendingRentals.add({
|
||||
'id': sewaAset['id'] ?? '',
|
||||
'name': assetName,
|
||||
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
||||
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
||||
'waktuSewa': waktuSewa,
|
||||
'duration': '${sewaAset['durasi'] ?? 0} ${namaSatuanWaktu}',
|
||||
'status': sewaAset['status'] ?? 'PERIKSA PEMBAYARAN',
|
||||
'totalPrice': totalPrice,
|
||||
'tanggalSewa': tanggalSewa,
|
||||
'jamMulai': jamMulai,
|
||||
'jamSelesai': jamSelesai,
|
||||
'rentangWaktu': rentangWaktu,
|
||||
'namaSatuanWaktu': namaSatuanWaktu,
|
||||
'waktuMulai': sewaAset['waktu_mulai'],
|
||||
'waktuSelesai': sewaAset['waktu_selesai'],
|
||||
});
|
||||
}
|
||||
|
||||
debugPrint('Processed ${pendingRentals.length} pending rental records');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading pending rentals data: $e');
|
||||
} finally {
|
||||
isLoadingPending.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load data for the Diterima tab (status: DITERIMA)
|
||||
Future<void> loadAcceptedRentals() async {
|
||||
try {
|
||||
isLoadingAccepted.value = true;
|
||||
|
||||
// Clear existing data
|
||||
acceptedRentals.clear();
|
||||
|
||||
// Get sewa_aset data with status "DITERIMA"
|
||||
final sewaAsetList = await authProvider.getSewaAsetByStatus(['DITERIMA']);
|
||||
|
||||
debugPrint('Fetched ${sewaAsetList.length} accepted sewa_aset records');
|
||||
|
||||
// Process each sewa_aset record
|
||||
for (var sewaAset in sewaAsetList) {
|
||||
// Get asset details if aset_id is available
|
||||
String assetName = 'Aset';
|
||||
String? imageUrl;
|
||||
String namaSatuanWaktu = sewaAset['nama_satuan_waktu'] ?? 'jam';
|
||||
|
||||
if (sewaAset['aset_id'] != null) {
|
||||
final asetData = await asetProvider.getAsetById(sewaAset['aset_id']);
|
||||
if (asetData != null) {
|
||||
assetName = asetData.nama;
|
||||
imageUrl = asetData.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse waktu mulai and waktu selesai
|
||||
DateTime? waktuMulai;
|
||||
DateTime? waktuSelesai;
|
||||
String waktuSewa = '';
|
||||
String tanggalSewa = '';
|
||||
String jamMulai = '';
|
||||
String jamSelesai = '';
|
||||
String rentangWaktu = '';
|
||||
|
||||
if (sewaAset['waktu_mulai'] != null && sewaAset['waktu_selesai'] != null) {
|
||||
waktuMulai = DateTime.parse(sewaAset['waktu_mulai']);
|
||||
waktuSelesai = DateTime.parse(sewaAset['waktu_selesai']);
|
||||
|
||||
// Format for display
|
||||
final formatTanggal = DateFormat('dd-MM-yyyy');
|
||||
final formatWaktu = DateFormat('HH:mm');
|
||||
final formatTanggalLengkap = DateFormat('dd MMMM yyyy', 'id_ID');
|
||||
|
||||
tanggalSewa = formatTanggalLengkap.format(waktuMulai);
|
||||
jamMulai = formatWaktu.format(waktuMulai);
|
||||
jamSelesai = formatWaktu.format(waktuSelesai);
|
||||
|
||||
// Format based on satuan waktu
|
||||
if (namaSatuanWaktu.toLowerCase() == 'jam') {
|
||||
// For hours, show time range on same day
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
} else if (namaSatuanWaktu.toLowerCase() == 'hari') {
|
||||
// For days, show date range
|
||||
final tanggalMulai = formatTanggalLengkap.format(waktuMulai);
|
||||
final tanggalSelesai = formatTanggalLengkap.format(waktuSelesai);
|
||||
rentangWaktu = '$tanggalMulai - $tanggalSelesai';
|
||||
} else {
|
||||
// Default format
|
||||
rentangWaktu = '$jamMulai - $jamSelesai';
|
||||
}
|
||||
|
||||
// Full time format for waktuSewa
|
||||
waktuSewa = '${formatTanggal.format(waktuMulai)} | ${formatWaktu.format(waktuMulai)} - '
|
||||
'${formatTanggal.format(waktuSelesai)} | ${formatWaktu.format(waktuSelesai)}';
|
||||
}
|
||||
|
||||
// Format price
|
||||
String totalPrice = 'Rp 0';
|
||||
if (sewaAset['total'] != null) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'id',
|
||||
symbol: 'Rp ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
totalPrice = formatter.format(sewaAset['total']);
|
||||
}
|
||||
|
||||
// Add to accepted rentals list
|
||||
acceptedRentals.add({
|
||||
'id': sewaAset['id'] ?? '',
|
||||
'name': assetName,
|
||||
'imageUrl': imageUrl ?? 'assets/images/gambar_pendukung.jpg',
|
||||
'jumlahUnit': sewaAset['kuantitas'] ?? 0,
|
||||
'waktuSewa': waktuSewa,
|
||||
'duration': '${sewaAset['durasi'] ?? 0} ${namaSatuanWaktu}',
|
||||
'status': sewaAset['status'] ?? 'DITERIMA',
|
||||
'totalPrice': totalPrice,
|
||||
'tanggalSewa': tanggalSewa,
|
||||
'jamMulai': jamMulai,
|
||||
'jamSelesai': jamSelesai,
|
||||
'rentangWaktu': rentangWaktu,
|
||||
'namaSatuanWaktu': namaSatuanWaktu,
|
||||
'waktuMulai': sewaAset['waktu_mulai'],
|
||||
'waktuSelesai': sewaAset['waktu_selesai'],
|
||||
});
|
||||
}
|
||||
|
||||
debugPrint('Processed ${acceptedRentals.length} accepted rental records');
|
||||
} catch (e) {
|
||||
debugPrint('Error loading accepted rentals data: $e');
|
||||
} finally {
|
||||
isLoadingAccepted.value = false;
|
||||
}
|
||||
}
|
||||
}
|
2178
lib/app/modules/warga/views/order_sewa_aset_view.dart
Normal file
2178
lib/app/modules/warga/views/order_sewa_aset_view.dart
Normal file
File diff suppressed because it is too large
Load Diff
981
lib/app/modules/warga/views/order_sewa_paket_view.dart
Normal file
981
lib/app/modules/warga/views/order_sewa_paket_view.dart
Normal file
@ -0,0 +1,981 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import '../controllers/order_sewa_paket_controller.dart';
|
||||
import '../../../data/models/paket_model.dart';
|
||||
import '../../../routes/app_routes.dart';
|
||||
import '../../../services/navigation_service.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:flutter_logs/flutter_logs.dart';
|
||||
import '../../../theme/app_colors.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class OrderSewaPaketView extends GetView<OrderSewaPaketController> {
|
||||
const OrderSewaPaketView({super.key});
|
||||
|
||||
// Function to show confirmation dialog
|
||||
void showOrderConfirmationDialog() {
|
||||
final paket = controller.paket.value!;
|
||||
final PaketModel? paketModel = paket is PaketModel ? paket : null;
|
||||
final totalPrice = controller.totalPrice.value;
|
||||
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header with success icon
|
||||
Container(
|
||||
width: 72,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primarySoft,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
color: AppColors.primary,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
// Title
|
||||
Text(
|
||||
'Konfirmasi Pesanan',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
|
||||
// Subtitle
|
||||
Text(
|
||||
'Periksa detail pesanan Anda',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Order details
|
||||
Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Paket name
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Paket',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
paketModel?.nama ?? controller.getPaketNama(paket) ?? 'Paket tanpa nama',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(height: 24, color: AppColors.divider),
|
||||
|
||||
// Duration info
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Durasi',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Text(
|
||||
controller.isDailyRental()
|
||||
? controller.formattedDateRange.value
|
||||
: '${controller.selectedDate.value}, ${controller.formattedTimeRange.value}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(height: 24, color: AppColors.divider),
|
||||
|
||||
// Total price info
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Total',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Text(
|
||||
controller.formatPrice(controller.totalPrice.value),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Action buttons
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Get.back(),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
side: BorderSide(color: AppColors.primary),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Batal',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => ElevatedButton(
|
||||
onPressed: controller.isSubmitting.value
|
||||
? null
|
||||
: () {
|
||||
Get.back();
|
||||
controller.submitOrder();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
backgroundColor: AppColors.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: controller.isSubmitting.value
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'Pesan',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Handle hot reload by checking if controller needs to be reset
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// This will be called after the widget tree is built
|
||||
controller.handleHotReload();
|
||||
|
||||
// Ensure navigation service is registered for back button functionality
|
||||
if (!Get.isRegistered<NavigationService>()) {
|
||||
Get.put(NavigationService());
|
||||
debugPrint('✅ Created new NavigationService instance in view');
|
||||
}
|
||||
});
|
||||
|
||||
// Function to handle back button press
|
||||
void handleBackButtonPress() {
|
||||
debugPrint('🔙 Back button pressed - navigating to SewaAsetView');
|
||||
try {
|
||||
// First try to use the controller's method
|
||||
controller.onBackPressed();
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Error handling back via controller: $e');
|
||||
// Fallback to direct navigation
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: AppColors.textPrimary),
|
||||
onPressed: handleBackButtonPress,
|
||||
),
|
||||
title: Text(
|
||||
'Pesan Paket',
|
||||
style: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Obx(
|
||||
() => controller.isLoading.value
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: controller.paket.value == null
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline_rounded,
|
||||
size: 64,
|
||||
color: AppColors.error,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Paket tidak ditemukan',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Silakan kembali dan pilih paket lain',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: handleBackButtonPress,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text('Kembali'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTopSection(),
|
||||
_buildPaketDetails(),
|
||||
_buildPriceOptions(),
|
||||
_buildDateSelection(context),
|
||||
SizedBox(height: 100), // Space for bottom bar
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSheet: Obx(
|
||||
() => controller.isLoading.value || controller.paket.value == null
|
||||
? SizedBox.shrink()
|
||||
: _buildBottomBar(onTapPesan: showOrderConfirmationDialog),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build top section with paket images
|
||||
Widget _buildTopSection() {
|
||||
return Container(
|
||||
height: 280,
|
||||
width: double.infinity,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Photo gallery
|
||||
Obx(
|
||||
() => controller.isPhotosLoading.value
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: controller.paketImages.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
size: 64,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Tidak ada foto',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: PhotoViewGallery.builder(
|
||||
scrollPhysics: BouncingScrollPhysics(),
|
||||
builder: (BuildContext context, int index) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
imageProvider: CachedNetworkImageProvider(
|
||||
controller.paketImages[index],
|
||||
),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 2,
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: 'paket_image_$index',
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: controller.paketImages.length,
|
||||
loadingBuilder: (context, event) => Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
),
|
||||
pageController: PageController(),
|
||||
),
|
||||
),
|
||||
|
||||
// Gradient overlay at the top for back button
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.5),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build paket details section
|
||||
Widget _buildPaketDetails() {
|
||||
final paket = controller.paket.value!;
|
||||
final PaketModel? paketModel = paket is PaketModel ? paket : null;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Paket name and availability badge
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
paketModel?.nama ?? controller.getPaketNama(paket) ?? 'Paket tanpa nama',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.success.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Tersedia',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.success,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
'Deskripsi',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
paketModel?.deskripsi ?? controller.getPaketDeskripsi(paket) ?? 'Tidak ada deskripsi untuk paket ini.',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|