diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7fa42f4..fd3404b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:icon="@mipmap/launcher_icon"> CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Bumrent App + BumRent CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - bumrent_app + BumRent CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/app/bindings/petugas_bumdes_binding.dart b/lib/app/bindings/petugas_bumdes_binding.dart index c1fd3db..974fa8f 100644 --- a/lib/app/bindings/petugas_bumdes_binding.dart +++ b/lib/app/bindings/petugas_bumdes_binding.dart @@ -19,12 +19,12 @@ class PetugasBumdesBinding extends Bindings { print('Error removing controller: $e'); } - // Gunakan put untuk memastikan controller selalu tersedia dan permanent - Get.put( - PetugasBumdesDashboardController(), - permanent: true, + // Gunakan lazyPut untuk memastikan controller hanya diinisialisasi saat dibutuhkan + Get.lazyPut( + () => PetugasBumdesDashboardController(), + fenix: true, // Akan dibuat ulang jika dihapus ); - print('✅ PetugasBumdesDashboardController registered successfully'); + print('✅ PetugasBumdesDashboardController initialized successfully'); } } diff --git a/lib/app/bindings/warga_binding.dart b/lib/app/bindings/warga_binding.dart index 4cbd4d9..da51012 100644 --- a/lib/app/bindings/warga_binding.dart +++ b/lib/app/bindings/warga_binding.dart @@ -5,7 +5,15 @@ import '../modules/warga/controllers/warga_dashboard_controller.dart'; class WargaBinding extends Bindings { @override void dependencies() { - Get.lazyPut(() => AuthProvider()); - Get.lazyPut(() => WargaDashboardController()); + // Pastikan AuthProvider teregistrasi + if (!Get.isRegistered()) { + Get.put(AuthProvider()); + } + + // Gunakan lazyPut untuk memastikan controller hanya diinisialisasi saat dibutuhkan + Get.lazyPut( + () => WargaDashboardController(), + fenix: true, // Akan dibuat ulang jika dihapus + ); } } diff --git a/lib/app/data/providers/auth_provider.dart b/lib/app/data/providers/auth_provider.dart index 1d8fd81..ff1587e 100644 --- a/lib/app/data/providers/auth_provider.dart +++ b/lib/app/data/providers/auth_provider.dart @@ -80,8 +80,18 @@ class AuthProvider extends GetxService { // Clear any cached user data or state // This method is called during logout to ensure all user-related data is cleared debugPrint('Clearing AuthProvider cached data'); - // Currently, signOut() handles most of the cleanup, but this method can be extended - // if additional cleanup is needed in the future + + // Explicitly clear any cached data that might be stored in the provider + // This is important to ensure no user data remains after logout or registration + try { + // Force refresh of the auth state + client.auth.refreshSession(); + + // Log the cleanup action + debugPrint('AuthProvider cached data cleared successfully'); + } catch (e) { + debugPrint('Error clearing AuthProvider cached data: $e'); + } } User? get currentUser => client.auth.currentUser; @@ -669,6 +679,25 @@ class AuthProvider extends GetxService { item, ); + // Ensure updated_at is not null, use created_at as fallback + if (processedItem['updated_at'] == null && + processedItem['created_at'] != null) { + debugPrint('updated_at is null, using created_at as fallback'); + processedItem['updated_at'] = processedItem['created_at']; + } else if (processedItem['updated_at'] == null && + processedItem['created_at'] == null) { + // If both are null, use current timestamp as last resort + debugPrint( + 'Both updated_at and created_at are null, using current timestamp', + ); + processedItem['updated_at'] = DateTime.now().toIso8601String(); + } + + // Debug the updated_at field + debugPrint( + 'updated_at after processing: ${processedItem['updated_at']}', + ); + // If aset_id is null and paket_id is not null, fetch package data if (item['aset_id'] == null && item['paket_id'] != null) { final String paketId = item['paket_id']; diff --git a/lib/app/modules/auth/controllers/auth_controller.dart b/lib/app/modules/auth/controllers/auth_controller.dart index 45b243d..d1a8b48 100644 --- a/lib/app/modules/auth/controllers/auth_controller.dart +++ b/lib/app/modules/auth/controllers/auth_controller.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import '../../../data/providers/auth_provider.dart'; import '../../../routes/app_routes.dart'; import 'dart:math'; +import '../../../modules/warga/controllers/warga_dashboard_controller.dart'; class AuthController extends GetxController { final AuthProvider _authProvider = Get.find(); @@ -185,7 +186,9 @@ class AuthController extends GetxController { } void _navigateToWargaDashboard() { - Get.offAllNamed(Routes.WARGA_DASHBOARD); + // Navigate to warga dashboard with parameter to indicate it's coming from login + // This will trigger an immediate refresh of the data + Get.offAllNamed(Routes.WARGA_DASHBOARD, arguments: {'from_login': true}); } void forgotPassword() async { @@ -215,7 +218,7 @@ class AuthController extends GetxController { Get.snackbar( 'Berhasil', 'Link reset password telah dikirim ke email Anda', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green[100], colorText: Colors.green[800], icon: const Icon(Icons.check_circle, color: Colors.green), @@ -312,7 +315,18 @@ class AuthController extends GetxController { 'register_id': registerId, // Add register_id to the warga_desa table }); - // Registration successful + // Reset registration fields BEFORE navigation to ensure clean state + resetRegistrationFields(); + + // Bersihkan data auth provider untuk memastikan tidak ada data user yang tersimpan + _authProvider.clearAuthData(); + + // Print debug message + print( + 'Registration successful: Fields and controllers have been cleared', + ); + + // Registration successful - navigate to success page Get.offNamed( Routes.REGISTRATION_SUCCESS, arguments: {'register_id': registerId}, @@ -478,4 +492,99 @@ class AuthController extends GetxController { return true; } + + // Check registration status by register_id and email + Future?> checkRegistrationStatus( + String registerId, + String email, + ) async { + try { + isLoading.value = true; + + print('Checking registration status - ID: $registerId, Email: $email'); + + // Validasi input + if (registerId.isEmpty || email.isEmpty) { + print('Invalid input: registerId or email is empty'); + return null; + } + + // Query warga_desa table where register_id and email match + final response = + await _authProvider.client + .from('warga_desa') + .select( + '*', + ) // Ensure we select all columns including 'keterangan' + .eq('register_id', registerId) + .eq('email', email) + .maybeSingle(); + + // Log response for debugging + print('Registration status query response: $response'); + + // Validasi hasil query + if (response == null) { + print('No matching registration found'); + return null; + } + + if (response is Map && response.isEmpty) { + print('Empty response received'); + return null; + } + + // Jika berhasil, kembalikan data + print('Registration found with status: ${response['status']}'); + return response; + } catch (e) { + print('Error checking registration status: ${e.toString()}'); + return null; + } finally { + isLoading.value = false; + } + } + + // Reset all registration fields + void resetRegistrationFields() { + // Reset text controllers + emailController.clear(); + passwordController.clear(); + nameController.clear(); + confirmPasswordController.clear(); + + // Reset form fields + email.value = ''; + password.value = ''; + nik.value = ''; + phoneNumber.value = ''; + selectedRole.value = 'WARGA'; // Reset to default role + alamatLengkap.value = ''; + tanggalLahir.value = null; + rtRw.value = ''; + kelurahan.value = ''; + kecamatan.value = ''; + + // Reset form status + isPasswordVisible.value = false; + isConfirmPasswordVisible.value = false; + errorMessage.value = ''; + + // Reset form key if needed + if (formKey.currentState != null) { + formKey.currentState!.reset(); + } + + // Bersihkan WargaDashboardController jika terdaftar + try { + if (Get.isRegistered()) { + print( + 'Removing WargaDashboardController to ensure clean state after registration', + ); + Get.delete(force: true); + } + } catch (e) { + print('Error removing WargaDashboardController: $e'); + } + } } diff --git a/lib/app/modules/auth/views/login_view.dart b/lib/app/modules/auth/views/login_view.dart index b85ab2f..0328271 100644 --- a/lib/app/modules/auth/views/login_view.dart +++ b/lib/app/modules/auth/views/login_view.dart @@ -9,7 +9,7 @@ class LoginView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: true, + resizeToAvoidBottomInset: false, body: Stack( children: [ // Background gradient @@ -77,9 +77,6 @@ class LoginView extends GetView { SafeArea( child: SingleChildScrollView( physics: const ClampingScrollPhysics(), - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( diff --git a/lib/app/modules/auth/views/registration_success_view.dart b/lib/app/modules/auth/views/registration_success_view.dart index f4b43cb..425debb 100644 --- a/lib/app/modules/auth/views/registration_success_view.dart +++ b/lib/app/modules/auth/views/registration_success_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../../../theme/app_colors.dart'; import 'package:flutter/rendering.dart'; +import '../../../routes/app_routes.dart'; class RegistrationSuccessView extends StatefulWidget { const RegistrationSuccessView({Key? key}) : super(key: key); @@ -284,7 +285,7 @@ class _RegistrationSuccessViewState extends State Get.snackbar( 'Berhasil Disalin', 'Kode registrasi telah disalin ke clipboard', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: AppColors.successLight, colorText: AppColors.success, margin: const EdgeInsets.all(16), @@ -329,7 +330,7 @@ class _RegistrationSuccessViewState extends State child: ElevatedButton( onPressed: () { // Navigate back to login page - Get.offAllNamed('/login'); + Get.offNamed(Routes.LOGIN); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, diff --git a/lib/app/modules/auth/views/registration_view.dart b/lib/app/modules/auth/views/registration_view.dart index 30f0652..b09e474 100644 --- a/lib/app/modules/auth/views/registration_view.dart +++ b/lib/app/modules/auth/views/registration_view.dart @@ -2,100 +2,108 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../controllers/auth_controller.dart'; import '../../../theme/app_colors.dart'; +import 'package:intl/intl.dart'; class RegistrationView extends GetView { const RegistrationView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - // Background gradient - same as login page - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - AppColors.primaryLight.withOpacity(0.1), - AppColors.background, - AppColors.accentLight.withOpacity(0.1), - ], - ), - ), - ), - - // Pattern overlay - same as login page - Opacity( - opacity: 0.03, - child: Container( + return WillPopScope( + onWillPop: () async { + // Reset all registration fields when leaving the page + controller.resetRegistrationFields(); + return true; + }, + child: Scaffold( + body: Stack( + children: [ + // Background gradient - same as login page + Container( decoration: BoxDecoration( - color: Colors.blue[50], // Temporary solid color - ), - ), - ), - - // Accent circles - same as login page - Positioned( - top: -40, - right: -20, - child: Container( - width: 150, - height: 150, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( + gradient: LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, colors: [ - AppColors.primary.withOpacity(0.2), - Colors.transparent, + AppColors.primaryLight.withOpacity(0.1), + AppColors.background, + AppColors.accentLight.withOpacity(0.1), ], ), ), ), - ), - Positioned( - bottom: -50, - left: -30, - child: Container( - width: 180, - height: 180, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [ - AppColors.accent.withOpacity(0.2), - Colors.transparent, - ], - ), - ), - ), - ), - // Main content - SafeArea( - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 20), - _buildBackButton(), - const SizedBox(height: 20), - _buildHeader(), - const SizedBox(height: 30), - _buildRegistrationCard(), - _buildLoginLink(), - _buildCheckStatusLink(), - const SizedBox(height: 30), - ], + // Pattern overlay - same as login page + Opacity( + opacity: 0.03, + child: Container( + decoration: BoxDecoration( + color: Colors.blue[50], // Temporary solid color ), ), ), - ), - ], + + // Accent circles - same as login page + Positioned( + top: -40, + right: -20, + child: Container( + width: 150, + height: 150, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + AppColors.primary.withOpacity(0.2), + Colors.transparent, + ], + ), + ), + ), + ), + Positioned( + bottom: -50, + left: -30, + child: Container( + width: 180, + height: 180, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + AppColors.accent.withOpacity(0.2), + Colors.transparent, + ], + ), + ), + ), + ), + + // Main content + SafeArea( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + _buildBackButton(), + const SizedBox(height: 20), + _buildHeader(), + const SizedBox(height: 30), + _buildRegistrationCard(), + _buildLoginLink(), + _buildCheckStatusLink(), + const SizedBox(height: 30), + ], + ), + ), + ), + ), + ], + ), ), ); } @@ -104,7 +112,11 @@ class RegistrationView extends GetView { return Align( alignment: Alignment.topLeft, child: InkWell( - onTap: () => Get.back(), + onTap: () { + // Reset all registration fields when going back + controller.resetRegistrationFields(); + Get.back(); + }, borderRadius: BorderRadius.circular(50), child: Container( padding: const EdgeInsets.all(10), @@ -644,7 +656,11 @@ class RegistrationView extends GetView { style: TextStyle(color: AppColors.textSecondary), ), TextButton( - onPressed: () => Get.back(), + onPressed: () { + // Reset all registration fields when going back to login + controller.resetRegistrationFields(); + Get.back(); + }, style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), @@ -694,7 +710,8 @@ class RegistrationView extends GetView { // Show dialog to check registration status void _showCheckStatusDialog(BuildContext context) { final TextEditingController codeController = TextEditingController(); - final TextEditingController identifierController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final authController = Get.find(); showDialog( context: context, @@ -788,9 +805,9 @@ class RegistrationView extends GetView { ), const SizedBox(height: 16), - // Email/NIK/Phone field + // Email field Text( - 'Email/NIK/No HP', + 'Email', style: TextStyle( fontWeight: FontWeight.w600, color: AppColors.textPrimary, @@ -799,12 +816,13 @@ class RegistrationView extends GetView { ), const SizedBox(height: 8), TextField( - controller: identifierController, + controller: emailController, + keyboardType: TextInputType.emailAddress, decoration: InputDecoration( - hintText: 'Masukkan email, NIK, atau no HP', + hintText: 'Masukkan email yang terdaftar', hintStyle: TextStyle(color: AppColors.textLight), prefixIcon: Icon( - Icons.person_outline, + Icons.email_outlined, color: AppColors.iconGrey, size: 22, ), @@ -835,20 +853,99 @@ class RegistrationView extends GetView { width: double.infinity, height: 48, child: ElevatedButton( - onPressed: () { - // TODO: Implement check status functionality + onPressed: () async { + // Validasi input + if (codeController.text.isEmpty || + emailController.text.isEmpty) { + Get.snackbar( + 'Error', + 'Kode registrasi dan Email harus diisi', + snackPosition: SnackPosition.TOP, + backgroundColor: AppColors.errorLight, + colorText: AppColors.error, + icon: Icon( + Icons.error_outline, + color: AppColors.error, + ), + duration: const Duration(seconds: 3), + ); + return; + } + + // Simpan nilai input untuk digunakan nanti + final String registerId = codeController.text.trim(); + final String email = emailController.text.trim(); + + print( + 'Checking registration status for ID: $registerId, Email: $email', + ); + + // Tutup dialog input Navigator.pop(context); - // Show a mock response for now - Get.snackbar( - 'Status Pendaftaran', - 'Status pendaftaran sedang diproses. Silakan tunggu konfirmasi lebih lanjut.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: AppColors.infoLight, - colorText: AppColors.info, - icon: Icon(Icons.info_outline, color: AppColors.info), - duration: const Duration(seconds: 4), + // Tampilkan loading indicator + Get.dialog( + const Center(child: CircularProgressIndicator()), + barrierDismissible: false, ); + + try { + // Cek status pendaftaran + final result = await authController + .checkRegistrationStatus(registerId, email); + + // Debug log + print('Registration status check result: $result'); + + // Tutup dialog loading + Get.back(); + + // Periksa hasil query + if (result != null && result.isNotEmpty) { + print('Valid result found, showing status dialog'); + + // Tambahkan delay kecil untuk memastikan UI diperbarui dengan benar + // Ini membantu memastikan dialog status muncul setelah dialog loading ditutup + Future.delayed(const Duration(milliseconds: 100), () { + _showRegistrationStatusDialog(result); + }); + } else { + print('No result found or empty result'); + + // Tampilkan pesan error + Get.snackbar( + 'Tidak Ditemukan', + 'Data pendaftaran tidak ditemukan. Pastikan kode registrasi dan email yang dimasukkan benar.', + snackPosition: SnackPosition.TOP, + backgroundColor: AppColors.errorLight, + colorText: AppColors.error, + icon: Icon( + Icons.error_outline, + color: AppColors.error, + ), + duration: const Duration(seconds: 4), + ); + } + } catch (e) { + // Tutup dialog loading jika terjadi error + Get.back(); + + print('Error checking registration status: $e'); + + // Tampilkan pesan error + Get.snackbar( + 'Error', + 'Terjadi kesalahan saat memeriksa status. Silakan coba lagi nanti.', + snackPosition: SnackPosition.TOP, + backgroundColor: AppColors.errorLight, + colorText: AppColors.error, + icon: Icon( + Icons.error_outline, + color: AppColors.error, + ), + duration: const Duration(seconds: 4), + ); + } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.accent, @@ -875,6 +972,219 @@ class RegistrationView extends GetView { ); } + // Show dialog with registration status details + void _showRegistrationStatusDialog(Map data) { + // Log data yang diterima untuk debugging + print('Showing registration status dialog with data: $data'); + + // Validasi data + if (data.isEmpty) { + print('Error: Empty data received in _showRegistrationStatusDialog'); + Get.snackbar( + 'Error', + 'Data status pendaftaran tidak valid', + snackPosition: SnackPosition.TOP, + backgroundColor: AppColors.errorLight, + colorText: AppColors.error, + duration: const Duration(seconds: 3), + ); + return; + } + + final String status = data['status'] ?? 'unknown'; + final String namaLengkap = data['nama_lengkap'] ?? ''; + final String keterangan = data['keterangan'] ?? ''; + + print('Processing status: $status, nama: $namaLengkap'); + + // Define status color and message based on status + Color statusColor; + String statusMessage; + IconData statusIcon; + + switch (status) { + case 'pending': + statusColor = AppColors.warning; + statusMessage = + keterangan.isNotEmpty + ? keterangan + : 'Pendaftaran Anda sedang dalam proses verifikasi oleh petugas. Mohon tunggu konfirmasi lebih lanjut.'; + statusIcon = Icons.hourglass_top; + break; + case 'active': + statusColor = AppColors.success; + statusMessage = + keterangan.isNotEmpty + ? keterangan + : 'Pendaftaran Anda telah disetujui. Anda dapat login menggunakan email dan password yang telah didaftarkan.'; + statusIcon = Icons.check_circle; + break; + case 'dibatalkan': + statusColor = AppColors.error; + statusMessage = + keterangan.isNotEmpty + ? keterangan + : 'Maaf, pendaftaran Anda ditolak. Silakan hubungi admin untuk informasi lebih lanjut.'; + statusIcon = Icons.cancel; + break; + case 'suspended': + statusColor = AppColors.error; + statusMessage = + keterangan.isNotEmpty + ? keterangan + : 'Akun Anda saat ini dinonaktifkan. Silakan hubungi admin untuk informasi lebih lanjut.'; + statusIcon = Icons.block; + break; + default: + statusColor = AppColors.textSecondary; + statusMessage = + keterangan.isNotEmpty + ? keterangan + : 'Status pendaftaran tidak diketahui. Silakan hubungi admin untuk informasi lebih lanjut.'; + statusIcon = Icons.help; + } + + print('Preparing to show dialog with status: $status, color: $statusColor'); + + // Gunakan Get.dialog sebagai pengganti showDialog untuk menghindari masalah context + Get.dialog( + Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon(statusIcon, color: statusColor, size: 40), + ), + const SizedBox(height: 24), + Text( + 'Status Pendaftaran', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + namaLengkap, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColors.textSecondary, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(30), + ), + child: Text( + status.toUpperCase(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: statusColor, + ), + ), + ), + const SizedBox(height: 24), + Text( + statusMessage, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + color: AppColors.textSecondary, + height: 1.5, + ), + ), + + // Tampilkan informasi tambahan jika ada + if (data.containsKey('tanggal_update') && + data['tanggal_update'] != null) ...[ + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.infoLight.withOpacity(0.3), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: AppColors.info, + ), + const SizedBox(width: 8), + Text( + 'Informasi Tambahan', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: AppColors.info, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'Terakhir diperbarui: ${_formatDate(data['tanggal_update'])}', + style: TextStyle( + fontSize: 13, + color: AppColors.textSecondary, + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Get.back(), + style: ElevatedButton.styleFrom( + backgroundColor: statusColor, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: const Text( + 'Tutup', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + barrierDismissible: true, + ); + + print('Dialog setup completed'); + } + // New method to build date picker field Widget _buildDateField(BuildContext context) { return GestureDetector( @@ -944,4 +1254,24 @@ class RegistrationView extends GetView { }), ); } + + String _formatDate(dynamic date) { + if (date is String) { + try { + final DateTime parsedDate = DateTime.parse(date); + return DateFormat('dd MMM yyyy, HH:mm').format(parsedDate); + } catch (e) { + return date; + } + } else if (date is num) { + final DateTime parsedDate = DateTime.fromMillisecondsSinceEpoch( + date.toInt() * 1000, + ); + return DateFormat('dd MMM yyyy, HH:mm').format(parsedDate); + } else if (date is DateTime) { + return DateFormat('dd MMM yyyy, HH:mm').format(date); + } else { + return "Format tanggal tidak diketahui"; + } + } } diff --git a/lib/app/modules/petugas_bumdes/controllers/list_petugas_mitra_controller.dart b/lib/app/modules/petugas_bumdes/controllers/list_petugas_mitra_controller.dart index cfcca82..33f92c3 100644 --- a/lib/app/modules/petugas_bumdes/controllers/list_petugas_mitra_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/list_petugas_mitra_controller.dart @@ -49,7 +49,7 @@ class ListPetugasMitraController extends GetxController { Get.snackbar( 'Sukses', 'Petugas mitra berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -62,7 +62,7 @@ class ListPetugasMitraController extends GetxController { Get.snackbar( 'Sukses', 'Data petugas mitra berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -73,7 +73,7 @@ class ListPetugasMitraController extends GetxController { Get.snackbar( 'Sukses', 'Petugas mitra berhasil dihapus', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -86,7 +86,7 @@ class ListPetugasMitraController extends GetxController { Get.snackbar( 'Status Diperbarui', 'Status petugas mitra diubah menjadi ${!currentStatus ? 'Aktif' : 'Nonaktif'}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_aset_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_aset_controller.dart index d557e53..e2dfb81 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_aset_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_aset_controller.dart @@ -122,7 +122,7 @@ class PetugasAsetController extends GetxController { Get.snackbar( 'Error Memuat Data', 'Gagal mengambil data aset dari server. Silakan coba lagi nanti.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -258,7 +258,7 @@ class PetugasAsetController extends GetxController { Get.snackbar( 'Gagal', 'Terjadi kesalahan saat menghapus aset', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -274,7 +274,7 @@ class PetugasAsetController extends GetxController { Get.snackbar( 'Error', 'Gagal menghapus aset: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_cbp_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_cbp_controller.dart index 0be092c..9a5dcec 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_cbp_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_cbp_controller.dart @@ -66,7 +66,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Error', 'Gagal memuat data. Silakan coba lagi.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isLoading.value = false; @@ -92,7 +92,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Rekening Utama', 'Rekening ${account['bank_name']} telah dijadikan rekening utama', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -109,7 +109,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Rekening Ditambahkan', 'Rekening bank baru telah berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -125,7 +125,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Rekening Diperbarui', 'Informasi rekening bank telah berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -138,7 +138,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Tidak Dapat Menghapus', 'Rekening utama tidak dapat dihapus. Silakan atur rekening lain sebagai utama terlebih dahulu.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); return; } @@ -148,7 +148,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Rekening Dihapus', 'Rekening bank telah berhasil dihapus', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -164,7 +164,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Status Diperbarui', 'Status mitra telah diubah menjadi ${partner['is_active'] ? 'Aktif' : 'Tidak Aktif'}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -181,7 +181,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Mitra Ditambahkan', 'Mitra baru telah berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -197,7 +197,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Mitra Diperbarui', 'Informasi mitra telah berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -210,7 +210,7 @@ class PetugasBumdesCbpController extends GetxController { Get.snackbar( 'Mitra Dihapus', 'Mitra telah berhasil dihapus', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_dashboard_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_dashboard_controller.dart index e8a2737..4420f1b 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_dashboard_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_bumdes_dashboard_controller.dart @@ -1,12 +1,14 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../data/providers/auth_provider.dart'; -import '../../../routes/app_routes.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:bumrent_app/app/data/providers/auth_provider.dart'; +import 'package:bumrent_app/app/data/providers/aset_provider.dart'; +import 'package:bumrent_app/app/data/providers/pesanan_provider.dart'; +import 'package:bumrent_app/app/routes/app_routes.dart'; import '../../../services/sewa_service.dart'; import '../../../services/service_manager.dart'; import '../../../data/models/pembayaran_model.dart'; import '../../../services/pembayaran_service.dart'; -import '../../../data/providers/aset_provider.dart'; -import '../../../data/providers/pesanan_provider.dart'; class PetugasBumdesDashboardController extends GetxController { AuthProvider? _authProvider; @@ -297,37 +299,56 @@ class PetugasBumdesDashboardController extends GetxController { void logout() async { try { - // Clear providers data + // Store login route for navigation + final loginRoute = Routes.LOGIN; + + // Sign out from Supabase if (_authProvider != null) { - // Sign out from Supabase await _authProvider!.signOut(); - - // Clear auth provider data - _authProvider!.clearAuthData(); - - // Clear aset provider data - try { - final asetProvider = Get.find(); - asetProvider.clearCache(); - } catch (e) { - print('Error clearing AsetProvider: $e'); - } - - // Clear pesanan provider data - try { - final pesananProvider = Get.find(); - pesananProvider.clearCache(); - } catch (e) { - print('Error clearing PesananProvider: $e'); - } } - // Navigate to login screen - Get.offAllNamed(Routes.LOGIN); + // Navigate to login screen while context is still valid + Get.offAllNamed(loginRoute); + + // Clear auth provider data if available + if (_authProvider != null) { + _authProvider!.clearAuthData(); + } + + // Clear provider caches + _clearProviderCaches(); + + // Clean up GetX controllers but keep navigation intact + Get.deleteAll(force: false); } catch (e) { print('Error during logout: $e'); // Still try to navigate to login even if sign out fails Get.offAllNamed(Routes.LOGIN); } } + + // Helper method to clear provider caches that need explicit clearing + void _clearProviderCaches() { + try { + // Clear AsetProvider cache + if (Get.isRegistered()) { + final asetProvider = Get.find(); + asetProvider.clearCache(); + } + } catch (e) { + print('Error clearing AsetProvider: $e'); + } + + try { + // Clear PesananProvider cache + if (Get.isRegistered()) { + final pesananProvider = Get.find(); + pesananProvider.clearCache(); + } + } catch (e) { + print('Error clearing PesananProvider: $e'); + } + + // Add other providers here that need explicit cache clearing + } } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_detail_penyewa_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_detail_penyewa_controller.dart index db6e6d2..9856ac1 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_detail_penyewa_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_detail_penyewa_controller.dart @@ -21,7 +21,7 @@ class PetugasDetailPenyewaController extends GetxController { Get.snackbar( 'Error', 'Data penyewa tidak ditemukan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -46,7 +46,7 @@ class PetugasDetailPenyewaController extends GetxController { Get.snackbar( 'Error', 'Gagal memuat data penyewa', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isLoading.value = false; @@ -203,14 +203,14 @@ class PetugasDetailPenyewaController extends GetxController { Get.snackbar( 'Berhasil', 'Status penyewa berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } catch (e) { print('Error updating penyewa status: $e'); Get.snackbar( 'Error', 'Gagal memperbarui status penyewa', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isLoading.value = false; diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_laporan_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_laporan_controller.dart index 3e02c0a..691448d 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_laporan_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_laporan_controller.dart @@ -184,7 +184,7 @@ class PetugasLaporanController extends GetxController { 'Gagal membuat laporan: $e', backgroundColor: Colors.red[100], colorText: Colors.red[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isLoading.value = false; @@ -1641,7 +1641,7 @@ class PetugasLaporanController extends GetxController { 'Harap generate laporan terlebih dahulu', backgroundColor: Colors.amber[100], colorText: Colors.amber[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -1654,7 +1654,7 @@ class PetugasLaporanController extends GetxController { 'Harap generate laporan terlebih dahulu', backgroundColor: Colors.amber[100], colorText: Colors.amber[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); return; } @@ -1687,7 +1687,7 @@ class PetugasLaporanController extends GetxController { 'Laporan disimpan di ${file.path}', backgroundColor: Colors.green[100], colorText: Colors.green[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, duration: const Duration(seconds: 5), ); @@ -1701,7 +1701,7 @@ class PetugasLaporanController extends GetxController { 'Gagal menyimpan laporan: $e', backgroundColor: Colors.red[100], colorText: Colors.red[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } @@ -1714,7 +1714,7 @@ class PetugasLaporanController extends GetxController { 'Harap generate laporan terlebih dahulu', backgroundColor: Colors.amber[100], colorText: Colors.amber[900], - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); return; } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_manajemen_bumdes_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_manajemen_bumdes_controller.dart index 33655ab..664b846 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_manajemen_bumdes_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_manajemen_bumdes_controller.dart @@ -79,7 +79,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Rekening utama berhasil diubah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -93,7 +93,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Status mitra berhasil diubah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -110,7 +110,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Rekening bank berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -124,7 +124,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Rekening bank berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -145,7 +145,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Rekening bank berhasil dihapus', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -155,7 +155,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Mitra berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -166,7 +166,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Mitra berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -177,7 +177,7 @@ class PetugasManajemenBumdesController extends GetxController { Get.snackbar( 'Sukses', 'Mitra berhasil dihapus', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_paket_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_paket_controller.dart index 147cc2b..8754f00 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_paket_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_paket_controller.dart @@ -87,7 +87,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal memuat data paket. Silakan coba lagi.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -320,7 +320,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Sukses', 'Paket baru berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -334,7 +334,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal menambahkan paket. Silakan coba lagi.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -373,7 +373,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Sukses', 'Paket berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -388,7 +388,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal memperbarui paket. Silakan coba lagi.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -440,7 +440,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Gagal', 'Terjadi kesalahan saat menghapus paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, duration: const Duration(seconds: 3), @@ -462,7 +462,7 @@ class PetugasPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal menghapus paket: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, duration: const Duration(seconds: 3), diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_penyewa_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_penyewa_controller.dart index ad0e8bb..22756f1 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_penyewa_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_penyewa_controller.dart @@ -6,6 +6,7 @@ class PetugasPenyewaController extends GetxController { // Reactive variables final isLoading = true.obs; + final isRefreshing = false.obs; final penyewaList = >[].obs; final filteredPenyewaList = >[].obs; final filterStatus = 'all'.obs; @@ -30,6 +31,39 @@ class PetugasPenyewaController extends GetxController { fetchPenyewaList(); } + // Method khusus untuk pull-to-refresh yang tidak menampilkan loading spinner penuh layar + Future refreshPenyewaList() async { + try { + isRefreshing.value = true; + + // Get all penyewa data without filtering + final data = + await _authProvider.client + .from('warga_desa') + .select( + 'user_id, nama_lengkap, email, nik, no_hp, avatar, status, keterangan', + ) + as List; + + // Filter out rows where user_id is null + final filteredData = data.where((row) => row['user_id'] != null).toList(); + + // Get total sewa count for each user + final enrichedData = await _enrichWithSewaCount(filteredData); + + penyewaList.value = enrichedData; + + // Apply filters to update filteredPenyewaList + applyFilters(); + + // Tampilkan pesan sukses + } catch (e) { + print('Error refreshing penyewa list: $e'); + } finally { + isRefreshing.value = false; + } + } + void changeTab(int index) { currentTabIndex.value = index; applyFilters(); @@ -180,14 +214,14 @@ class PetugasPenyewaController extends GetxController { Get.snackbar( 'Berhasil', 'Status penyewa berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } catch (e) { print('Error updating penyewa status: $e'); Get.snackbar( 'Gagal', 'Terjadi kesalahan saat memperbarui status penyewa', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isLoading.value = false; diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_aset_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_aset_controller.dart index bc5aa9a..2c9d3fa 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_aset_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_aset_controller.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:bumrent_app/app/data/models/aset_model.dart'; import 'package:bumrent_app/app/data/providers/aset_provider.dart'; +import 'package:bumrent_app/app/routes/app_routes.dart'; class PetugasTambahAsetController extends GetxController { // Flag to check if in edit mode @@ -13,34 +14,38 @@ class PetugasTambahAsetController extends GetxController { @override Future onInit() async { super.onInit(); - + try { // Handle edit mode and load data if needed final args = Get.arguments; - debugPrint('[DEBUG] PetugasTambahAsetController initialized with args: $args'); - + debugPrint( + '[DEBUG] PetugasTambahAsetController initialized with args: $args', + ); + if (args != null && args is Map) { isEditing.value = args['isEditing'] ?? false; debugPrint('[DEBUG] isEditing set to: ${isEditing.value}'); - + if (isEditing.value) { // Get asset ID from arguments final assetId = args['assetId']?.toString() ?? ''; debugPrint('[DEBUG] Edit mode: Loading asset with ID: $assetId'); - + if (assetId.isNotEmpty) { // Store the asset ID and load asset data this.assetId = assetId; debugPrint('[DEBUG] Asset ID set to: $assetId'); - + // Load asset data and await completion await _loadAssetData(assetId); } else { - debugPrint('[ERROR] Edit mode but no assetId provided in arguments'); + debugPrint( + '[ERROR] Edit mode but no assetId provided in arguments', + ); Get.snackbar( 'Error', 'ID Aset tidak ditemukan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); // Optionally navigate back if in edit mode without an ID Future.delayed(Duration.zero, () => Get.back()); @@ -53,7 +58,9 @@ class PetugasTambahAsetController extends GetxController { } } else { // Default values for new asset when no arguments are passed - debugPrint('[DEBUG] No arguments passed, defaulting to add new asset mode'); + debugPrint( + '[DEBUG] No arguments passed, defaulting to add new asset mode', + ); quantityController.text = '1'; unitOfMeasureController.text = 'Unit'; } @@ -62,11 +69,11 @@ class PetugasTambahAsetController extends GetxController { debugPrint('Stack trace: $stackTrace'); // Ensure loading is set to false even if there's an error isLoading.value = false; - + Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat data', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } @@ -85,21 +92,21 @@ class PetugasTambahAsetController extends GetxController { try { isLoading.value = true; debugPrint('[DEBUG] Fetching asset data for ID: $assetId'); - + // Fetch asset data from Supabase final aset = await _asetProvider.getAsetById(assetId); - + if (aset == null) { throw Exception('Aset tidak ditemukan'); } - + debugPrint('[DEBUG] Successfully fetched asset data: ${aset.toJson()}'); - + // Populate form fields with the fetched data nameController.text = aset.nama ?? ''; descriptionController.text = aset.deskripsi ?? ''; quantityController.text = (aset.kuantitas ?? 1).toString(); - + // Ensure the status matches one of the available options exactly final status = aset.status?.toLowerCase() ?? 'tersedia'; if (status == 'tersedia') { @@ -110,18 +117,19 @@ class PetugasTambahAsetController extends GetxController { // Default to 'Tersedia' if status is not recognized selectedStatus.value = 'Tersedia'; } - + // Handle time options and pricing if (aset.satuanWaktuSewa != null && aset.satuanWaktuSewa!.isNotEmpty) { // Reset time options timeOptions.forEach((key, value) => value.value = false); - + // Process each satuan waktu sewa for (var sws in aset.satuanWaktuSewa) { - final satuan = sws['nama_satuan_waktu']?.toString().toLowerCase() ?? ''; + final satuan = + sws['nama_satuan_waktu']?.toString().toLowerCase() ?? ''; final harga = sws['harga'] as int? ?? 0; final maksimalWaktu = sws['maksimal_waktu'] as int? ?? 24; - + if (satuan.contains('jam')) { timeOptions['Per Jam']?.value = true; pricePerHourController.text = harga.toString(); @@ -133,19 +141,21 @@ class PetugasTambahAsetController extends GetxController { } } } - + // Clear existing images selectedImages.clear(); networkImageUrls.clear(); - + // Get all image URLs from the model final allImageUrls = aset.imageUrls.toList(); - + // If no imageUrls but has imageUrl, use that as fallback (backward compatibility) - if (allImageUrls.isEmpty && aset.imageUrl != null && aset.imageUrl!.isNotEmpty) { + if (allImageUrls.isEmpty && + aset.imageUrl != null && + aset.imageUrl!.isNotEmpty) { allImageUrls.add(aset.imageUrl!); } - + // Add all images to the lists for (final imageUrl in allImageUrls) { if (imageUrl != null && imageUrl.isNotEmpty) { @@ -161,27 +171,30 @@ class PetugasTambahAsetController extends GetxController { } } } - - debugPrint('Total ${networkImageUrls.length} images loaded for asset $assetId'); + + debugPrint( + 'Total ${networkImageUrls.length} images loaded for asset $assetId', + ); debugPrint('[DEBUG] Successfully loaded asset data for ID: $assetId'); } catch (e, stackTrace) { debugPrint('[ERROR] Failed to load asset data: $e'); debugPrint('Stack trace: $stackTrace'); - + Get.snackbar( 'Error', 'Gagal memuat data aset: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); - + // Optionally navigate back if there's an error Future.delayed(const Duration(seconds: 2), () => Get.back()); } finally { isLoading.value = false; } } + // Form controllers final nameController = TextEditingController(); final descriptionController = TextEditingController(); @@ -213,8 +226,6 @@ class PetugasTambahAsetController extends GetxController { final isFormValid = false.obs; final isSubmitting = false.obs; - - @override void onClose() { // Dispose controllers @@ -255,11 +266,10 @@ class PetugasTambahAsetController extends GetxController { if (!anySelected) { timeOptions[option]?.value = true; } - + validateForm(); } - // Create a new asset in Supabase Future _createAsset( Map assetData, @@ -268,7 +278,7 @@ class PetugasTambahAsetController extends GetxController { try { // Create the asset in the 'aset' table final response = await _asetProvider.createAset(assetData); - + if (response == null || response['id'] == null) { debugPrint('❌ Failed to create asset: No response or ID from server'); return null; @@ -285,7 +295,7 @@ class PetugasTambahAsetController extends GetxController { harga: sws['harga'], maksimalWaktu: sws['maksimal_waktu'], ); - + if (!success) { debugPrint('❌ Failed to add satuan waktu sewa: $sws'); } @@ -307,12 +317,12 @@ class PetugasTambahAsetController extends GetxController { ) async { try { debugPrint('\n🔄 Starting update for asset ID: $assetId'); - + // 1. Extract and remove foto_aset from assetData as it's not in the aset table final fotoAsetUrl = assetData['foto_aset']; assetData.remove('foto_aset'); debugPrint('📝 Asset data prepared for update (without foto_aset)'); - + // 2. Update the main asset data (without foto_aset) debugPrint('🔄 Updating main asset data...'); final success = await _asetProvider.updateAset(assetId, assetData); @@ -326,7 +336,7 @@ class PetugasTambahAsetController extends GetxController { debugPrint('\n🔄 Updating rental time units...'); // First, delete existing satuan waktu sewa await _asetProvider.deleteSatuanWaktuSewaByAsetId(assetId); - + // Then add the new ones for (var sws in satuanWaktuSewa) { debugPrint(' - Adding: ${sws['satuan_waktu']} (${sws['harga']} IDR)'); @@ -346,21 +356,25 @@ class PetugasTambahAsetController extends GetxController { ...networkImageUrls, ...selectedImages.map((file) => file.path), ]; - + debugPrint('\n🖼️ Processing photos for asset $assetId'); debugPrint(' - Network URLs: ${networkImageUrls.length}'); debugPrint(' - Local files: ${selectedImages.length}'); - debugPrint(' - Total unique photos: ${allImageUrls.toSet().length} (before deduplication)'); - + debugPrint( + ' - Total unique photos: ${allImageUrls.toSet().length} (before deduplication)', + ); + try { // Use updateFotoAset which handles both uploading new photos and updating the database final photoSuccess = await _asetProvider.updateFotoAset( asetId: assetId, fotoUrls: allImageUrls, ); - + if (!photoSuccess) { - debugPrint('⚠️ Some photos might not have been updated for asset $assetId'); + debugPrint( + '⚠️ Some photos might not have been updated for asset $assetId', + ); // We don't fail the whole update if photo update fails // as the main asset data has been saved successfully } else { @@ -377,7 +391,6 @@ class PetugasTambahAsetController extends GetxController { debugPrint('\n✅ Asset update completed successfully for ID: $assetId'); return true; - } catch (e, stackTrace) { debugPrint('❌ Error updating asset: $e'); debugPrint('Stack trace: $stackTrace'); @@ -445,30 +458,30 @@ class PetugasTambahAsetController extends GetxController { // Handle time options and pricing final List> satuanWaktuSewa = []; - + if (timeOptions['Per Jam']?.value == true) { final hargaPerJam = int.tryParse(pricePerHourController.text) ?? 0; final maxJam = int.tryParse(maxHourController.text) ?? 24; - + if (hargaPerJam <= 0) { throw Exception('Harga per jam harus lebih dari 0'); } - + satuanWaktuSewa.add({ 'satuan_waktu': 'jam', 'harga': hargaPerJam, 'maksimal_waktu': maxJam, }); } - + if (timeOptions['Per Hari']?.value == true) { final hargaPerHari = int.tryParse(pricePerDayController.text) ?? 0; final maxHari = int.tryParse(maxDayController.text) ?? 30; - + if (hargaPerHari <= 0) { throw Exception('Harga per hari harus lebih dari 0'); } - + satuanWaktuSewa.add({ 'satuan_waktu': 'hari', 'harga': hargaPerHari, @@ -483,7 +496,7 @@ class PetugasTambahAsetController extends GetxController { // Handle image uploads List imageUrls = []; - + if (networkImageUrls.isNotEmpty) { // Use existing network URLs imageUrls = List.from(networkImageUrls); @@ -505,12 +518,12 @@ class PetugasTambahAsetController extends GetxController { // Create or update the asset bool success; String? createdAssetId; - + if (isEditing.value && (assetId?.isNotEmpty ?? false)) { // Update existing asset debugPrint('🔄 Updating asset with ID: $assetId'); success = await _updateAsset(assetId!, assetData, satuanWaktuSewa); - + // Update all photos if we have any if (success && imageUrls.isNotEmpty) { await _asetProvider.updateFotoAset( @@ -523,7 +536,7 @@ class PetugasTambahAsetController extends GetxController { debugPrint('🔄 Creating new asset'); createdAssetId = await _createAsset(assetData, satuanWaktuSewa); success = createdAssetId != null; - + // Add all photos for new asset if (success && createdAssetId != null && imageUrls.isNotEmpty) { await _asetProvider.updateFotoAset( @@ -537,16 +550,18 @@ class PetugasTambahAsetController extends GetxController { // Show success message Get.snackbar( 'Sukses', - isEditing.value ? 'Aset berhasil diperbarui' : 'Aset berhasil ditambahkan', - snackPosition: SnackPosition.BOTTOM, + isEditing.value + ? 'Aset berhasil diperbarui' + : 'Aset berhasil ditambahkan', + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, - duration: const Duration(seconds: 3), + duration: const Duration(seconds: 2), ); - // Navigate back with success after a short delay - await Future.delayed(const Duration(seconds: 1)); - Get.back(result: true); + // Navigate back with success result instead of using offNamed + await Future.delayed(const Duration(milliseconds: 500)); + Get.offNamed(Routes.PETUGAS_ASET); } else { throw Exception('Gagal menyimpan aset'); } @@ -557,14 +572,13 @@ class PetugasTambahAsetController extends GetxController { 'Terjadi kesalahan: ${e.toString()}', backgroundColor: Colors.red, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isSubmitting.value = false; } } - // Example method to upload images (to be implemented with your backend) // Future> _uploadImages(List images) async { // List urls = []; @@ -593,7 +607,7 @@ class PetugasTambahAsetController extends GetxController { Get.snackbar( 'Error', 'Gagal mengambil gambar dari kamera: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -615,7 +629,7 @@ class PetugasTambahAsetController extends GetxController { Get.snackbar( 'Error', 'Gagal memilih gambar dari galeri: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -625,7 +639,9 @@ class PetugasTambahAsetController extends GetxController { // For demonstration purposes: add sample image void addSampleImage() { // In a real app, this would open the image picker - selectedImages.add(XFile('assets/images/sample_asset_${selectedImages.length + 1}.jpg')); + selectedImages.add( + XFile('assets/images/sample_asset_${selectedImages.length + 1}.jpg'), + ); validateForm(); } } diff --git a/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_paket_controller.dart b/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_paket_controller.dart index 37a9bb5..a7db25f 100644 --- a/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_paket_controller.dart +++ b/lib/app/modules/petugas_bumdes/controllers/petugas_tambah_paket_controller.dart @@ -5,6 +5,7 @@ import 'package:bumrent_app/app/data/models/paket_model.dart'; import 'package:image_picker/image_picker.dart'; import 'package:bumrent_app/app/data/providers/aset_provider.dart'; import 'dart:io'; +import 'package:bumrent_app/app/routes/app_routes.dart'; import 'package:uuid/uuid.dart'; class PetugasTambahPaketController extends GetxController { @@ -47,6 +48,9 @@ class PetugasTambahPaketController extends GetxController { // New RxBool for editing final isEditing = false.obs; + // New RxBool for viewing (read-only mode) + final isViewing = false.obs; + final timeOptions = {'Per Jam': true.obs, 'Per Hari': false.obs}; final pricePerHourController = TextEditingController(); final maxHourController = TextEditingController(); @@ -64,11 +68,13 @@ class PetugasTambahPaketController extends GetxController { void onInit() { super.onInit(); - // Ambil flag isEditing dari arguments + // Ambil flag isEditing dan isViewing dari arguments isEditing.value = Get.arguments != null && Get.arguments['isEditing'] == true; + isViewing.value = + Get.arguments != null && Get.arguments['isViewing'] == true; - if (isEditing.value) { + if (isEditing.value || isViewing.value) { final paketArg = Get.arguments['paket']; String? paketId; if (paketArg != null) { @@ -230,7 +236,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Pilih aset dan masukkan jumlah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -251,7 +257,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Jumlah harus lebih dari 0', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -264,7 +270,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Jumlah melebihi stok yang tersedia', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -286,7 +292,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Sukses', 'Item berhasil ditambahkan ke paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -300,7 +306,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Pilih aset dan masukkan jumlah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -321,7 +327,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Jumlah harus lebih dari 0', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -349,7 +355,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Jumlah melebihi stok yang tersedia', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -362,7 +368,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Jumlah melebihi stok yang tersedia', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -385,7 +391,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Sukses', 'Item berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -402,7 +408,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Dihapus', 'Item berhasil dihapus dari paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.orange, colorText: Colors.white, ); @@ -538,13 +544,13 @@ class PetugasTambahPaketController extends GetxController { } // Sukses - Get.back(); + Get.offNamed(Routes.PETUGAS_PAKET); Get.snackbar( 'Berhasil', 'Paket berhasil diperbarui', backgroundColor: Colors.green, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } else { // --- ADD LOGIC --- @@ -611,13 +617,13 @@ class PetugasTambahPaketController extends GetxController { } } // Sukses - Get.back(); + Get.back(result: true); Get.snackbar( 'Berhasil', 'Paket berhasil ditambahkan', backgroundColor: Colors.green, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } catch (e) { @@ -627,7 +633,7 @@ class PetugasTambahPaketController extends GetxController { 'Terjadi kesalahan: \\${e.toString()}', backgroundColor: Colors.red, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } finally { isSubmitting.value = false; @@ -810,7 +816,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal mengambil gambar dari kamera: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -833,7 +839,7 @@ class PetugasTambahPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal memilih gambar dari galeri: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); diff --git a/lib/app/modules/petugas_bumdes/views/list_petugas_mitra_view.dart b/lib/app/modules/petugas_bumdes/views/list_petugas_mitra_view.dart index 38a982d..5245899 100644 --- a/lib/app/modules/petugas_bumdes/views/list_petugas_mitra_view.dart +++ b/lib/app/modules/petugas_bumdes/views/list_petugas_mitra_view.dart @@ -476,7 +476,7 @@ class ListPetugasMitraView extends GetView { Get.snackbar( 'Error', 'Harap isi semua data', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -600,7 +600,7 @@ class ListPetugasMitraView extends GetView { Get.snackbar( 'Error', 'Harap isi semua data', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); diff --git a/lib/app/modules/petugas_bumdes/views/list_tagihan_periode_view.dart b/lib/app/modules/petugas_bumdes/views/list_tagihan_periode_view.dart index 7e06a49..4e35916 100644 --- a/lib/app/modules/petugas_bumdes/views/list_tagihan_periode_view.dart +++ b/lib/app/modules/petugas_bumdes/views/list_tagihan_periode_view.dart @@ -203,7 +203,7 @@ class ListTagihanPeriodeView extends GetView { backgroundColor: Colors.orange.withOpacity(0.1), colorText: Colors.orange.shade800, duration: const Duration(seconds: 3), - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, margin: const EdgeInsets.all(8), ); }, diff --git a/lib/app/modules/petugas_bumdes/views/petugas_aset_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_aset_view.dart index cc70fad..e45cf0b 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_aset_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_aset_view.dart @@ -81,11 +81,15 @@ class _PetugasAsetViewState extends State ), floatingActionButton: FloatingActionButton.extended( onPressed: () { - // Navigate to PetugasTambahAsetView in add mode + // Navigate to PetugasTambahAsetView in add mode and refresh data when returning Get.toNamed( Routes.PETUGAS_TAMBAH_ASET, arguments: {'isEditing': false, 'assetData': null}, - ); + )?.then((_) { + // Refresh data when returning from tambah_aset page + debugPrint('Returning from tambah aset page, refreshing data...'); + controller.loadAsetData(); + }); }, backgroundColor: AppColorsPetugas.babyBlueBright, icon: Icon(Icons.add, color: AppColorsPetugas.blueGrotto), @@ -456,7 +460,7 @@ class _PetugasAsetViewState extends State Get.snackbar( 'Error', 'ID Aset tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); return; } @@ -467,7 +471,13 @@ class _PetugasAsetViewState extends State 'isEditing': true, 'assetId': assetId, }, - ); + )?.then((_) { + // Refresh data when returning from edit page + debugPrint( + 'Returning from edit aset page, refreshing data...', + ); + controller.loadAsetData(); + }); }, child: Container( padding: const EdgeInsets.all(5), @@ -670,235 +680,16 @@ class _PetugasAsetViewState extends State } } - void _showAddEditAssetDialog( - BuildContext context, { - Map? aset, - }) { - final isEditing = aset != null; - - // In a real app, this would have proper form handling with controllers - showDialog( - context: context, - builder: (context) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: Container( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppColorsPetugas.babyBlue, - shape: BoxShape.circle, - ), - child: Icon( - isEditing ? Icons.edit : Icons.add, - color: AppColorsPetugas.navyBlue, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - isEditing ? 'Edit Aset' : 'Tambah Aset Baru', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColorsPetugas.navyBlue, - ), - ), - const SizedBox(height: 4), - Text( - 'Silakan lengkapi form di bawah ini', - style: TextStyle( - fontSize: 14, - color: AppColorsPetugas.textSecondary, - ), - ), - ], - ), - ), - ], - ), - - const SizedBox(height: 24), - - // Mock form - In a real app this would have actual form fields - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColorsPetugas.babyBlueBright, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColorsPetugas.babyBlue), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Form pengelolaan aset akan ditampilkan di sini dengan field untuk:', - style: TextStyle( - fontSize: 14, - color: AppColorsPetugas.textPrimary, - ), - ), - const SizedBox(height: 16), - _buildMockFormField('Nama Aset', 'Contoh: Meja Rapat'), - _buildMockFormField('Kategori', 'Pilih kategori aset'), - _buildMockFormField( - 'Harga', - 'Masukkan harga per unit/periode', - ), - _buildMockFormField( - 'Satuan', - 'Contoh: per hari, per bulan', - ), - _buildMockFormField('Stok', 'Jumlah unit tersedia'), - _buildMockFormField( - 'Deskripsi', - 'Keterangan lengkap aset', - ), - _buildMockToggle( - 'Status Ketersediaan', - isEditing && aset?['tersedia'] == true, - ), - ], - ), - ), - - const SizedBox(height: 24), - - // Action buttons - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Batal', - style: TextStyle( - color: AppColorsPetugas.textSecondary, - fontWeight: FontWeight.w600, - ), - ), - ), - const SizedBox(width: 16), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - // In a real app, we would save the form data - Get.snackbar( - isEditing ? 'Aset Diperbarui' : 'Aset Ditambahkan', - isEditing - ? 'Aset berhasil diperbarui' - : 'Aset baru berhasil ditambahkan', - backgroundColor: AppColorsPetugas.success, - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, - margin: const EdgeInsets.all(16), - borderRadius: 10, - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: AppColorsPetugas.blueGrotto, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text(isEditing ? 'Simpan' : 'Tambah'), - ), - ], - ), - ], - ), - ), - ); - }, - ); - } - - Widget _buildMockFormField(String label, String hint) { - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: AppColorsPetugas.textPrimary, - ), - ), - const SizedBox(height: 6), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColorsPetugas.babyBlue), - ), - child: Row( - children: [ - Expanded( - child: Text( - hint, - style: TextStyle( - fontSize: 14, - color: AppColorsPetugas.textLight, - ), - ), - ), - Icon( - Icons.keyboard_arrow_down, - color: AppColorsPetugas.textSecondary, - size: 20, - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildMockToggle(String label, bool value) { - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: AppColorsPetugas.textPrimary, - ), - ), - Switch( - value: value, - onChanged: (_) {}, - activeColor: AppColorsPetugas.blueGrotto, - ), - ], - ), - ); + void _showAddEditAssetDialog(BuildContext context) { + // Navigate to PetugasTambahAsetView in add mode and refresh data when returning + Get.toNamed( + Routes.PETUGAS_TAMBAH_ASET, + arguments: {'isEditing': false, 'assetData': null}, + )?.then((_) { + // Refresh data when returning from tambah_aset page + debugPrint('Returning from tambah aset page, refreshing data...'); + controller.loadAsetData(); + }); } void _showDeleteConfirmation( diff --git a/lib/app/modules/petugas_bumdes/views/petugas_detail_sewa_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_detail_sewa_view.dart index 76c5482..9ef9447 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_detail_sewa_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_detail_sewa_view.dart @@ -1023,7 +1023,7 @@ class _PetugasDetailSewaViewState extends State { 'Status sewa telah diubah menjadi SELESAI', backgroundColor: Colors.green, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -1054,7 +1054,7 @@ class _PetugasDetailSewaViewState extends State { 'Status sewa telah diubah menjadi DIKEMBALIKAN', backgroundColor: Colors.teal, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -1127,7 +1127,7 @@ class _PetugasDetailSewaViewState extends State { 'Status sewa telah diubah menjadi DIBATALKAN', backgroundColor: Colors.red, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -1381,7 +1381,7 @@ class _PetugasDetailSewaViewState extends State { 'Status pengajuan diubah menjadi Periksa Pembayaran', backgroundColor: Colors.amber.shade700, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); break; @@ -1394,7 +1394,7 @@ class _PetugasDetailSewaViewState extends State { 'Pengajuan sewa aset telah disetujui', backgroundColor: Colors.green.shade600, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); break; @@ -1407,7 +1407,7 @@ class _PetugasDetailSewaViewState extends State { 'Permintaan pembayaran denda telah dikirim', backgroundColor: Colors.deepOrange, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); break; @@ -1420,7 +1420,7 @@ class _PetugasDetailSewaViewState extends State { 'Status pengajuan diubah menjadi Periksa Denda', backgroundColor: Colors.red.shade600, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); break; @@ -1433,7 +1433,7 @@ class _PetugasDetailSewaViewState extends State { 'Aset telah dikembalikan dan sewa telah selesai', backgroundColor: Colors.green.shade600, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); break; @@ -1476,7 +1476,7 @@ class _PetugasDetailSewaViewState extends State { 'Status sewa telah diubah menjadi DIBATALKAN', backgroundColor: Colors.red, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -1595,7 +1595,7 @@ class _PetugasDetailSewaViewState extends State { 'Status sewa diubah menjadi Dikembalikan', backgroundColor: Colors.teal, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -1628,7 +1628,7 @@ class _PetugasDetailSewaViewState extends State { 'Permintaan pembayaran denda telah dikirim', backgroundColor: Colors.deepOrange, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -1661,7 +1661,7 @@ class _PetugasDetailSewaViewState extends State { 'Sewa aset telah selesai', backgroundColor: Colors.purple, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }; } else if (status == 'Dikembalikan') { @@ -1709,7 +1709,7 @@ class _PetugasDetailSewaViewState extends State { 'Sewa aset telah selesai', backgroundColor: Colors.purple, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, icon: const Icon(Icons.task_alt_outlined), @@ -1817,7 +1817,7 @@ class _PetugasDetailSewaViewState extends State { 'Bukti sewa dalam format PDF sedang diunduh', backgroundColor: AppColorsPetugas.blueGrotto, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -1837,7 +1837,7 @@ class _PetugasDetailSewaViewState extends State { 'Bukti sewa dalam format JPG sedang diunduh', backgroundColor: AppColorsPetugas.blueGrotto, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -1857,7 +1857,7 @@ class _PetugasDetailSewaViewState extends State { 'Data sewa dalam format XLSX sedang diunduh', backgroundColor: AppColorsPetugas.blueGrotto, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -1963,7 +1963,8 @@ class _PetugasDetailSewaViewState extends State { width: double.infinity, child: OutlinedButton.icon( onPressed: () { - nominalController.text = sewa.totalTagihan.toString(); + nominalController.text = + sewa.totalTagihan.toInt().toString(); }, icon: Icon( Icons.payments_outlined, @@ -2456,7 +2457,7 @@ class _PetugasDetailSewaViewState extends State { 'Nominal denda harus diisi', backgroundColor: Colors.red, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); return; } @@ -2501,7 +2502,7 @@ class _PetugasDetailSewaViewState extends State { 'Permintaan pembayaran denda telah dikirim', backgroundColor: Colors.deepOrange, colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, icon: const Icon(Icons.check_circle), diff --git a/lib/app/modules/petugas_bumdes/views/petugas_manajemen_bumdes_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_manajemen_bumdes_view.dart index 61403fb..48a4683 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_manajemen_bumdes_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_manajemen_bumdes_view.dart @@ -567,7 +567,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur tambah rekening bank sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -639,7 +639,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur edit rekening bank sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -689,7 +689,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur hapus rekening bank sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -765,7 +765,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur tambah mitra sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -845,7 +845,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur edit mitra sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( @@ -895,7 +895,7 @@ class PetugasManajemenBumdesView Get.snackbar( 'Info', 'Fitur hapus mitra sedang dalam pengembangan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, style: ElevatedButton.styleFrom( diff --git a/lib/app/modules/petugas_bumdes/views/petugas_paket_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_paket_view.dart index 066d621..5be7117 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_paket_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_paket_view.dart @@ -55,11 +55,17 @@ class PetugasPaketView extends GetView { ), ), floatingActionButton: FloatingActionButton.extended( - onPressed: - () => Get.toNamed( - Routes.PETUGAS_TAMBAH_PAKET, - arguments: {'isEditing': false}, - ), + onPressed: () async { + final result = await Get.toNamed( + Routes.PETUGAS_TAMBAH_PAKET, + arguments: {'isEditing': false}, + ); + + // Refresh the package list if a package was added + if (result == true) { + controller.loadPaketData(); + } + }, label: Text( 'Tambah Paket', style: TextStyle( @@ -142,11 +148,17 @@ class PetugasPaketView extends GetView { ), const SizedBox(height: 16), ElevatedButton.icon( - onPressed: - () => Get.toNamed( - Routes.PETUGAS_TAMBAH_PAKET, - arguments: {'isEditing': false}, - ), + onPressed: () async { + final result = await Get.toNamed( + Routes.PETUGAS_TAMBAH_PAKET, + arguments: {'isEditing': false}, + ); + + // Refresh the package list if a package was added + if (result == true) { + controller.loadPaketData(); + } + }, icon: const Icon(Icons.add), label: const Text('Tambah Paket'), style: ElevatedButton.styleFrom( @@ -376,6 +388,21 @@ class PetugasPaketView extends GetView { child: Material( color: Colors.transparent, child: InkWell( + onTap: () async { + final result = await Get.toNamed( + Routes.PETUGAS_TAMBAH_PAKET, + arguments: { + 'isEditing': false, + 'isViewing': true, + 'paket': paket, + }, + ); + + // Refresh the package list if data was modified + if (result == true) { + controller.loadPaketData(); + } + }, child: Row( children: [ // Paket image or icon diff --git a/lib/app/modules/petugas_bumdes/views/petugas_penyewa_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_penyewa_view.dart index e4b85e5..5c2e57b 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_penyewa_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_penyewa_view.dart @@ -111,6 +111,27 @@ class _PetugasPenyewaViewState extends State ), ), ), + actions: [ + // Tambahkan indikator refresh + Obx( + () => + controller.isRefreshing.value + ? Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ), + ), + ) + : SizedBox.shrink(), + ), + ], ), drawer: PetugasSideNavbar(controller: dashboardController), drawerEdgeDragWidth: 60, @@ -148,7 +169,13 @@ class _PetugasPenyewaViewState extends State } if (controller.filteredPenyewaList.isEmpty) { - return _buildEmptyState(); + return RefreshIndicator( + onRefresh: () async { + await controller.refreshPenyewaList(); + }, + color: AppColorsPetugas.blueGrotto, + child: _buildEmptyStateWithScrollView(), + ); } return _buildPenyewaList(); @@ -282,208 +309,234 @@ class _PetugasPenyewaViewState extends State ); } + // Widget untuk menampilkan empty state dengan ScrollView untuk mendukung RefreshIndicator + Widget _buildEmptyStateWithScrollView() { + return ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ + SizedBox( + height: MediaQuery.of(Get.context!).size.height * 0.7, + child: _buildEmptyState(), + ), + ], + ); + } + Widget _buildPenyewaList() { - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: controller.filteredPenyewaList.length, - itemBuilder: (context, index) { - final penyewa = controller.filteredPenyewaList[index]; - return Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(16), - child: InkWell( + return RefreshIndicator( + onRefresh: () async { + await controller.refreshPenyewaList(); + }, + color: AppColorsPetugas.blueGrotto, + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: controller.filteredPenyewaList.length, + itemBuilder: (context, index) { + final penyewa = controller.filteredPenyewaList[index]; + return Container( + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(16), - child: Padding( - padding: const EdgeInsets.all(0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header with avatar and badge - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColorsPetugas.babyBlueLight.withOpacity(0.2), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + child: Padding( + padding: const EdgeInsets.all(0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header with avatar and badge + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColorsPetugas.babyBlueLight.withOpacity( + 0.2, + ), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + // Avatar with border + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: CircleAvatar( + radius: 24, + backgroundColor: AppColorsPetugas.babyBlueLight, + backgroundImage: + penyewa['avatar'] != null && + penyewa['avatar'] + .toString() + .isNotEmpty + ? NetworkImage(penyewa['avatar']) + : null, + child: + penyewa['avatar'] == null || + penyewa['avatar'].toString().isEmpty + ? const Icon( + Icons.person, + color: Colors.white, + ) + : null, + ), + ), + const SizedBox(width: 16), + // Name and email + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + penyewa['nama_lengkap'] ?? + 'Nama tidak tersedia', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColorsPetugas.navyBlue, + ), + ), + const SizedBox(height: 4), + Row( + children: [ + Icon( + Icons.email_outlined, + size: 14, + color: Colors.grey[600], + ), + const SizedBox(width: 4), + Expanded( + child: Text( + penyewa['email'] ?? + 'Email tidak tersedia', + style: TextStyle( + fontSize: 13, + color: Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), + _buildStatusBadge(penyewa['status']), + ], ), ), - child: Row( - children: [ - // Avatar with border - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: CircleAvatar( - radius: 24, - backgroundColor: AppColorsPetugas.babyBlueLight, - backgroundImage: - penyewa['avatar'] != null && - penyewa['avatar'] - .toString() - .isNotEmpty - ? NetworkImage(penyewa['avatar']) - : null, - child: - penyewa['avatar'] == null || - penyewa['avatar'].toString().isEmpty - ? const Icon( - Icons.person, - color: Colors.white, - ) - : null, - ), - ), - const SizedBox(width: 16), - // Name and email - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - penyewa['nama_lengkap'] ?? - 'Nama tidak tersedia', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: AppColorsPetugas.navyBlue, - ), - ), - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.email_outlined, - size: 14, - color: Colors.grey[600], - ), - const SizedBox(width: 4), - Expanded( - child: Text( - penyewa['email'] ?? - 'Email tidak tersedia', - style: TextStyle( - fontSize: 13, - color: Colors.grey[600], - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ], - ), - ), - _buildStatusBadge(penyewa['status']), - ], - ), - ), - // Content section - Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Show additional info only for Aktif and Ditangguhkan tabs - if (controller.currentTabIndex.value != 0) ...[ - Row( - children: [ - _buildInfoChip( - Icons.credit_card_outlined, - 'NIK: ${penyewa['nik'] ?? 'Tidak tersedia'}', - ), - const SizedBox(width: 8), - _buildInfoChip( - Icons.phone_outlined, - penyewa['no_hp'] ?? 'No. HP tidak tersedia', - ), - ], - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildInfoTile( - 'Total Sewa', - penyewa['total_sewa']?.toString() ?? '0', - Icons.shopping_bag_outlined, - AppColorsPetugas.blueGrotto, - ), - if (controller.currentTabIndex.value == 1 || - controller.currentTabIndex.value == 2) - _buildActionChip( - label: 'Lihat Detail', - color: AppColorsPetugas.blueGrotto, - icon: Icons.visibility, - onTap: () { - Get.toNamed( - Routes.PETUGAS_DETAIL_PENYEWA, - arguments: { - 'userId': penyewa['user_id'], - }, - ); - }, + // Content section + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Show additional info only for Aktif and Ditangguhkan tabs + if (controller.currentTabIndex.value != 0) ...[ + Row( + children: [ + _buildInfoChip( + Icons.credit_card_outlined, + 'NIK: ${penyewa['nik'] ?? 'Tidak tersedia'}', ), - ], - ), - ], + const SizedBox(width: 8), + _buildInfoChip( + Icons.phone_outlined, + penyewa['no_hp'] ?? 'No. HP tidak tersedia', + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + _buildInfoTile( + 'Total Sewa', + penyewa['total_sewa']?.toString() ?? '0', + Icons.shopping_bag_outlined, + AppColorsPetugas.blueGrotto, + ), + if (controller.currentTabIndex.value == 1 || + controller.currentTabIndex.value == 2) + _buildActionChip( + label: 'Lihat Detail', + color: AppColorsPetugas.blueGrotto, + icon: Icons.visibility, + onTap: () { + Get.toNamed( + Routes.PETUGAS_DETAIL_PENYEWA, + arguments: { + 'userId': penyewa['user_id'], + }, + ); + }, + ), + ], + ), + ], - // Add "Detail" button for Verifikasi tab - if (controller.currentTabIndex.value == 0) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: _buildActionChip( - label: 'Lihat Detail & Verifikasi', - color: AppColorsPetugas.blueGrotto, - icon: Icons.visibility, - onTap: () { - Get.toNamed( - Routes.PETUGAS_DETAIL_PENYEWA, - arguments: { - 'userId': penyewa['user_id'], - }, - ); - }, + // Add "Detail" button for Verifikasi tab + if (controller.currentTabIndex.value == 0) ...[ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: _buildActionChip( + label: 'Lihat Detail & Verifikasi', + color: AppColorsPetugas.blueGrotto, + icon: Icons.visibility, + onTap: () { + Get.toNamed( + Routes.PETUGAS_DETAIL_PENYEWA, + arguments: { + 'userId': penyewa['user_id'], + }, + ); + }, + ), ), - ), - ], - ), + ], + ), + ], ], - ], + ), ), - ), - ], + ], + ), ), ), ), - ), - ); - }, + ); + }, + ), ); } diff --git a/lib/app/modules/petugas_bumdes/views/petugas_tambah_paket_view.dart b/lib/app/modules/petugas_bumdes/views/petugas_tambah_paket_view.dart index 0075481..2a734b5 100644 --- a/lib/app/modules/petugas_bumdes/views/petugas_tambah_paket_view.dart +++ b/lib/app/modules/petugas_bumdes/views/petugas_tambah_paket_view.dart @@ -15,7 +15,9 @@ class PetugasTambahPaketView extends GetView { appBar: AppBar( title: Obx( () => Text( - controller.isEditing.value ? 'Edit Paket' : 'Tambah Paket', + controller.isViewing.value + ? 'Detail Paket' + : (controller.isEditing.value ? 'Edit Paket' : 'Tambah Paket'), style: const TextStyle(fontWeight: FontWeight.w600), ), ), @@ -168,15 +170,16 @@ class PetugasTambahPaketView extends GetView { 'Item Paket', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), - ElevatedButton.icon( - onPressed: () => _showAddItemDialog(), - icon: const Icon(Icons.add), - label: const Text('Tambah Item'), - style: ElevatedButton.styleFrom( - backgroundColor: AppColorsPetugas.babyBlueLight, - foregroundColor: AppColorsPetugas.blueGrotto, + if (!controller.isViewing.value) + ElevatedButton.icon( + onPressed: () => _showAddItemDialog(), + icon: const Icon(Icons.add), + label: const Text('Tambah Item'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColorsPetugas.babyBlueLight, + foregroundColor: AppColorsPetugas.blueGrotto, + ), ), - ), ], ), const SizedBox(height: 16), @@ -208,26 +211,33 @@ class PetugasTambahPaketView extends GetView { Text('Stok tersedia: ${item['stok']}'), ], ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon( - Icons.edit, - color: Colors.blue, - ), - onPressed: () => _showEditItemDialog(index), - ), - IconButton( - icon: const Icon( - Icons.delete, - color: Colors.red, - ), - onPressed: - () => controller.removeItem(index), - ), - ], - ), + trailing: + controller.isViewing.value + ? null + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.edit, + color: Colors.blue, + ), + onPressed: + () => + _showEditItemDialog(index), + ), + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.red, + ), + onPressed: + () => controller.removeItem( + index, + ), + ), + ], + ), ), ); }, @@ -526,6 +536,9 @@ class PetugasTambahPaketView extends GetView { String? prefixText, IconData? prefixIcon, }) { + final petugasController = + this.controller; // Reference to PetugasTambahPaketController + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -539,7 +552,7 @@ class PetugasTambahPaketView extends GetView { color: AppColorsPetugas.textPrimary, ), ), - if (isRequired) ...[ + if (isRequired && !petugasController.isViewing.value) ...[ const SizedBox(width: 4), Text( '*', @@ -553,43 +566,55 @@ class PetugasTambahPaketView extends GetView { ], ), const SizedBox(height: 8), - TextFormField( - controller: controller, - maxLines: maxLines, - keyboardType: keyboardType, - inputFormatters: inputFormatters, - decoration: InputDecoration( - hintText: hint, - hintStyle: TextStyle(color: AppColorsPetugas.textLight), - filled: true, - fillColor: AppColorsPetugas.babyBlueBright, - prefixText: prefixText, - prefixIcon: - prefixIcon != null - ? Icon( - prefixIcon, - size: 20, - color: AppColorsPetugas.textSecondary, - ) - : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, + Obx( + () => TextFormField( + controller: controller, + maxLines: maxLines, + keyboardType: keyboardType, + inputFormatters: inputFormatters, + readOnly: petugasController.isViewing.value, + style: TextStyle( + color: + petugasController.isViewing.value + ? AppColorsPetugas.textSecondary + : AppColorsPetugas.textPrimary, ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide( - color: AppColorsPetugas.blueGrotto, - width: 1.5, + decoration: InputDecoration( + hintText: hint, + hintStyle: TextStyle(color: AppColorsPetugas.textLight), + filled: true, + fillColor: + petugasController.isViewing.value + ? AppColorsPetugas.babyBlueLight + : AppColorsPetugas.babyBlueBright, + prefixText: prefixText, + prefixIcon: + prefixIcon != null + ? Icon( + prefixIcon, + size: 20, + color: AppColorsPetugas.textSecondary, + ) + : null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide( + color: AppColorsPetugas.blueGrotto, + width: 1.5, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12, ), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, ), ), ), @@ -646,7 +671,10 @@ class PetugasTambahPaketView extends GetView { vertical: 12, ), filled: true, - fillColor: AppColorsPetugas.babyBlueBright, + fillColor: + controller.isViewing.value + ? AppColorsPetugas.babyBlueLight + : AppColorsPetugas.babyBlueBright, ), items: options.map((option) { @@ -661,9 +689,12 @@ class PetugasTambahPaketView extends GetView { ), ); }).toList(), - onChanged: (value) { - if (value != null) onChanged(value); - }, + onChanged: + controller.isViewing.value + ? null // Disable in view-only mode + : (value) { + if (value != null) onChanged(value); + }, icon: Icon( Icons.keyboard_arrow_down_rounded, color: AppColorsPetugas.blueGrotto, @@ -717,41 +748,42 @@ class PetugasTambahPaketView extends GetView { runSpacing: 12, children: [ // Add button - GestureDetector( - onTap: _showImageSourceOptions, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: AppColorsPetugas.babyBlueBright, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: AppColorsPetugas.babyBlue, - width: 1, - style: BorderStyle.solid, + if (!controller.isViewing.value) + GestureDetector( + onTap: _showImageSourceOptions, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: AppColorsPetugas.babyBlueBright, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: AppColorsPetugas.babyBlue, + width: 1, + style: BorderStyle.solid, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_photo_alternate_outlined, + color: AppColorsPetugas.blueGrotto, + size: 32, + ), + const SizedBox(height: 8), + Text( + 'Tambah Foto', + style: TextStyle( + fontSize: 12, + color: AppColorsPetugas.blueGrotto, + fontWeight: FontWeight.w500, + ), + ), + ], ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.add_photo_alternate_outlined, - color: AppColorsPetugas.blueGrotto, - size: 32, - ), - const SizedBox(height: 8), - Text( - 'Tambah Foto', - style: TextStyle( - fontSize: 12, - color: AppColorsPetugas.blueGrotto, - fontWeight: FontWeight.w500, - ), - ), - ], - ), ), - ), // Image previews ...List.generate(controller.selectedImages.length, ( index, @@ -811,21 +843,24 @@ class PetugasTambahPaketView extends GetView { Positioned( top: 4, right: 4, - child: InkWell( - onTap: () => controller.removeImage(index), - child: Container( - padding: const EdgeInsets.all(4), - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.close, - size: 18, - color: Colors.red, - ), - ), - ), + child: + !controller.isViewing.value + ? InkWell( + onTap: () => controller.removeImage(index), + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + size: 18, + color: Colors.red, + ), + ), + ) + : const SizedBox.shrink(), ), ], ); @@ -949,67 +984,97 @@ class PetugasTambahPaketView extends GetView { ), ], ), - child: Row( - children: [ - OutlinedButton.icon( - onPressed: () => Get.back(), - icon: const Icon(Icons.arrow_back), - label: const Text('Batal'), - style: OutlinedButton.styleFrom( - foregroundColor: AppColorsPetugas.textSecondary, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - side: BorderSide(color: AppColorsPetugas.divider), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + child: Obx(() { + // In view-only mode, just show a back button + if (controller.isViewing.value) { + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => Get.back(), + icon: const Icon(Icons.arrow_back), + label: const Text('Kembali'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColorsPetugas.blueGrotto, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), ), - ), - const SizedBox(width: 16), - Expanded( - child: Obx(() { - final isValid = controller.isFormValid.value; - final isSubmitting = controller.isSubmitting.value; - return ElevatedButton.icon( - onPressed: - controller.isFormChanged.value && !isSubmitting - ? controller.savePaket - : null, - icon: + ); + } + + // For edit/add mode, show cancel and save buttons + return Row( + children: [ + OutlinedButton.icon( + onPressed: () => Get.back(), + icon: const Icon(Icons.arrow_back), + label: const Text('Batal'), + style: OutlinedButton.styleFrom( + foregroundColor: AppColorsPetugas.textSecondary, + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + side: BorderSide(color: AppColorsPetugas.divider), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Obx(() { + final isValid = controller.isFormValid.value; + final isSubmitting = controller.isSubmitting.value; + return ElevatedButton.icon( + onPressed: + controller.isFormChanged.value && !isSubmitting + ? controller.savePaket + : null, + icon: + isSubmitting + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.save), + label: Text( isSubmitting - ? const SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.save), - label: Text( - isSubmitting - ? 'Menyimpan...' - : (controller.isEditing.value - ? 'Simpan Paket' - : 'Tambah Paket'), - ), - style: ElevatedButton.styleFrom( - backgroundColor: AppColorsPetugas.blueGrotto, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - textStyle: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + ? 'Menyimpan...' + : (controller.isEditing.value + ? 'Simpan Paket' + : 'Tambah Paket'), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + style: ElevatedButton.styleFrom( + backgroundColor: AppColorsPetugas.blueGrotto, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + disabledBackgroundColor: AppColorsPetugas.textLight, ), - disabledBackgroundColor: AppColorsPetugas.textLight, - ), - ); - }), - ), - ], - ), + ); + }), + ), + ], + ); + }), ); } @@ -1035,7 +1100,10 @@ class PetugasTambahPaketView extends GetView { () => Material( color: Colors.transparent, child: InkWell( - onTap: () => controller.toggleTimeOption(option), + onTap: + controller.isViewing.value + ? null + : () => controller.toggleTimeOption(option), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric( @@ -1173,12 +1241,22 @@ class PetugasTambahPaketView extends GetView { controller: priceController, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], + readOnly: controller.isViewing.value, + style: TextStyle( + color: + controller.isViewing.value + ? AppColorsPetugas.textSecondary + : AppColorsPetugas.textPrimary, + ), decoration: InputDecoration( hintText: 'Masukkan harga', hintStyle: TextStyle(color: AppColorsPetugas.textLight), prefixText: 'Rp ', filled: true, - fillColor: AppColorsPetugas.babyBlueBright, + fillColor: + controller.isViewing.value + ? AppColorsPetugas.babyBlueLight + : AppColorsPetugas.babyBlueBright, contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, @@ -1210,11 +1288,21 @@ class PetugasTambahPaketView extends GetView { controller: maxController, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], + readOnly: controller.isViewing.value, + style: TextStyle( + color: + controller.isViewing.value + ? AppColorsPetugas.textSecondary + : AppColorsPetugas.textPrimary, + ), decoration: InputDecoration( hintText: 'Opsional', hintStyle: TextStyle(color: AppColorsPetugas.textLight), filled: true, - fillColor: AppColorsPetugas.babyBlueBright, + fillColor: + controller.isViewing.value + ? AppColorsPetugas.babyBlueLight + : AppColorsPetugas.babyBlueBright, contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, diff --git a/lib/app/modules/petugas_mitra/controllers/petugas_mitra_dashboard_controller.dart b/lib/app/modules/petugas_mitra/controllers/petugas_mitra_dashboard_controller.dart index 1eefd10..378fdbc 100644 --- a/lib/app/modules/petugas_mitra/controllers/petugas_mitra_dashboard_controller.dart +++ b/lib/app/modules/petugas_mitra/controllers/petugas_mitra_dashboard_controller.dart @@ -41,7 +41,7 @@ class PetugasMitraDashboardController extends GetxController { Get.snackbar( 'Error', 'Gagal keluar dari aplikasi', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } } diff --git a/lib/app/modules/warga/controllers/order_sewa_aset_controller.dart b/lib/app/modules/warga/controllers/order_sewa_aset_controller.dart index a04ab3c..a5718cb 100644 --- a/lib/app/modules/warga/controllers/order_sewa_aset_controller.dart +++ b/lib/app/modules/warga/controllers/order_sewa_aset_controller.dart @@ -185,7 +185,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Error', errorMessage.value, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }); } @@ -211,7 +211,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Info', 'Tidak dapat menampilkan data - data tidak tersedia', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.amber, colorText: Colors.black, duration: const Duration(seconds: 3), @@ -231,6 +231,9 @@ class OrderSewaAsetController extends GetxController { debugPrint('💾 Saved asetId to GetStorage: ${asetId.value}'); } + // Bersihkan data controller + clearData(); + super.onClose(); } @@ -445,6 +448,9 @@ class OrderSewaAsetController extends GetxController { void onBackPressed() { debugPrint('🔙 Back button pressed in OrderSewaAsetView'); + // Bersihkan data controller sebelum kembali + clearData(); + try { // Try to use the navigation service navigationService.backFromOrderSewaAset(); @@ -2287,7 +2293,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Error', message, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -2299,7 +2305,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Sukses', message, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -2767,7 +2773,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Error', 'Gagal membuat pesanan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -2777,7 +2783,7 @@ class OrderSewaAsetController extends GetxController { Get.snackbar( 'Error', 'Terjadi kesalahan: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -3537,4 +3543,43 @@ class OrderSewaAsetController extends GetxController { isLoadingBookings(false); } } + + // Clear controller data + void clearData() { + aset.value = null; + isLoading.value = true; + hasError.value = false; + errorMessage.value = ''; + asetId.value = ''; + isAset.value = true; + isPaket.value = false; + assetPhotos.clear(); + currentPhotoIndex.value = 0; + isPhotosLoading.value = false; + selectedSatuanWaktu.value = null; + duration.value = 1; + totalPrice.value = 0; + jumlahUnit.value = 1; + maxUnit.value = 1; + selectedDate.value = ''; + startHour.value = -1; + endHour.value = -1; + formattedTimeRange.value = ''; + startDate.value = null; + endDate.value = null; + formattedDateRange.value = ''; + bookedDates.clear(); + isLoadingBookedDates.value = false; + maxDayLimit.value = 0; + availableHours.clear(); + bookedHours.clear(); + selectedHours.clear(); + bookedHoursList.clear(); + isLoadingBookings.value = false; + hourlyInventory.clear(); + unavailableDatesForHourly.clear(); + paketItems.clear(); + isPaketItemsLoaded.value = false; + paketId.value = ''; + } } diff --git a/lib/app/modules/warga/controllers/order_sewa_paket_controller.dart b/lib/app/modules/warga/controllers/order_sewa_paket_controller.dart index 67bfb63..7b3ea5f 100644 --- a/lib/app/modules/warga/controllers/order_sewa_paket_controller.dart +++ b/lib/app/modules/warga/controllers/order_sewa_paket_controller.dart @@ -141,7 +141,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal memuat data paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -152,7 +152,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat data paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -166,10 +166,10 @@ class OrderSewaPaketController extends GetxController { try { debugPrint('🔄 Processing paket data for ID: $id'); debugPrint('📦 Raw paket data type: ${rawPaket.runtimeType}'); - + // Initialize loadedPaket with a default value late final PaketModel loadedPaket; - + try { // Handle Map directly - pakets from getPakets() are always maps loadedPaket = PaketModel.fromMap(rawPaket); @@ -183,10 +183,16 @@ class OrderSewaPaketController extends GetxController { deskripsi: getPaketDeskripsi(rawPaket), harga: getPaketHarga(rawPaket), kuantitas: getPaketKuantitas(rawPaket), - foto: const [], // Initialize with empty list, will be populated later + foto: + const < + String + >[], // Initialize with empty list, will be populated later satuanWaktuSewa: getPaketSatuanWaktuSewa(rawPaket), foto_paket: getPaketMainPhoto(rawPaket), - images: const [], // Initialize with empty list, will be populated later + images: + const < + String + >[], // Initialize with empty list, will be populated later createdAt: DateTime.now(), updatedAt: DateTime.now(), ); @@ -352,7 +358,7 @@ class OrderSewaPaketController extends GetxController { if (items.isNotEmpty) { paketItems.value = items; debugPrint('✅ Loaded ${paketItems.length} package items'); - } + } } catch (e) { debugPrint('❌ Error loading paket items: $e'); paketItems.value = []; @@ -1030,7 +1036,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Waktu Tidak Tersedia', 'Jam ini tidak tersedia karena ada aset dalam paket yang sudah dipesan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.orange, colorText: Colors.white, ); @@ -1114,7 +1120,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Data paket tidak lengkap', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1131,7 +1137,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Silakan pilih waktu sewa', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1163,7 +1169,7 @@ class OrderSewaPaketController extends GetxController { durasi: orderData['durasi'] ?? 1, // Default to 1 if not provided totalHarga: orderData['total_harga'].toInt(), ); - + // Create a mock result for navigation final resultData = { 'id': 'order_${DateTime.now().millisecondsSinceEpoch}', @@ -1174,21 +1180,23 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Sukses', 'Pesanan berhasil dibuat', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); // Navigate to payment page if (result && resultData != null && resultData['id'] != null) { - navigationService.navigateToPembayaranSewa(resultData['id'].toString()); + navigationService.navigateToPembayaranSewa( + resultData['id'].toString(), + ); } else if (result) { // If result is true but we don't have an ID, navigate back to sewa aset navigationService.navigateToSewaAset(); Get.snackbar( 'Error', 'Gagal mendapatkan ID pesanan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1196,7 +1204,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal membuat pesanan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1205,7 +1213,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Gagal membuat pesanan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1215,7 +1223,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Error', 'Terjadi kesalahan: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1392,7 +1400,7 @@ class OrderSewaPaketController extends GetxController { Get.snackbar( 'Peringatan', 'Pilih tanggal terlebih dahulu', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.orange, colorText: Colors.white, ); diff --git a/lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak b/lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak index 48831d8..4ef15e5 100644 --- a/lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak +++ b/lib/app/modules/warga/controllers/pembayaran_sewa_controller.bak @@ -378,7 +378,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Pesanan Dibatalkan', 'Batas waktu pembayaran telah berakhir', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, duration: Duration(seconds: 5), @@ -417,7 +417,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal mengambil foto: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -443,7 +443,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal memilih foto dari galeri: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -459,7 +459,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Mohon unggah bukti pembayaran terlebih dahulu', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -541,7 +541,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Sukses', 'Bukti pembayaran berhasil diunggah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -550,7 +550,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal mengunggah bukti pembayaran: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -654,7 +654,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Sukses', 'Pembayaran tunai berhasil disubmit', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -1090,7 +1090,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Berhasil', 'Data berhasil diperbarui', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, duration: const Duration(seconds: 2), @@ -1104,7 +1104,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal memperbarui data', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); diff --git a/lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart b/lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart index 028f5d1..f6b9af2 100644 --- a/lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart +++ b/lib/app/modules/warga/controllers/pembayaran_sewa_controller.dart @@ -305,6 +305,9 @@ class PembayaranSewaController extends GetxController : 0, 'status': rentalData['status'] ?? 'MENUNGGU PEMBAYARAN', 'created_at': DateTime.now().toString(), + 'updated_at': + DateTime.now() + .toString(), // Explicitly set updated_at for countdown 'denda': 0, // Default value 'keterangan': '', // Default value 'image_url': rentalData['imageUrl'], @@ -359,13 +362,19 @@ class PembayaranSewaController extends GetxController 'price_per_unit': 10000, 'total_price': 50000, 'status': 'MENUNGGU PEMBAYARAN', - 'created_at': - DateTime.now().toString(), // Use this for countdown calculation + 'created_at': DateTime.now().toString(), + 'updated_at': + DateTime.now() + .toString(), // Explicitly set updated_at for countdown 'denda': 20000, // Dummy data for denda 'keterangan': 'Terjadi kerusakan pada bagian kaki', // Dummy keterangan for denda }; + debugPrint( + 'DEBUG: Set updated_at in orderDetails: ${orderDetails.value['updated_at']}', + ); + // Update the current step based on the status updateCurrentStepBasedOnStatus(); @@ -433,14 +442,27 @@ class PembayaranSewaController extends GetxController if (data['status'] != null && data['status'].toString().isNotEmpty) { val?['status'] = data['status']; - debugPrint( - '📊 Order status from sewa_aset: \\${data['status']}', - ); + debugPrint('📊 Order status from sewa_aset: ${data['status']}'); } - // Tambahkan mapping updated_at + + // Ensure updated_at is always set if (data['updated_at'] != null) { val?['updated_at'] = data['updated_at']; + debugPrint( + '📅 Using updated_at from database: ${data['updated_at']}', + ); + } else if (data['created_at'] != null) { + val?['updated_at'] = data['created_at']; + debugPrint( + '📅 Using created_at as fallback for updated_at: ${data['created_at']}', + ); + } else { + val?['updated_at'] = DateTime.now().toIso8601String(); + debugPrint( + '📅 Using current timestamp as fallback for updated_at', + ); } + // Format rental period if (data['waktu_mulai'] != null && data['waktu_selesai'] != null) { @@ -448,12 +470,12 @@ class PembayaranSewaController extends GetxController final startTime = DateTime.parse(data['waktu_mulai']); final endTime = DateTime.parse(data['waktu_selesai']); val?['rental_period'] = - '\\${startTime.day}/\\${startTime.month}/\\${startTime.year}, \\${startTime.hour}:\\${startTime.minute.toString().padLeft(2, '0')} - \\${endTime.hour}:\\${endTime.minute.toString().padLeft(2, '0')}'; + '${startTime.day}/${startTime.month}/${startTime.year}, ${startTime.hour}:${startTime.minute.toString().padLeft(2, '0')} - ${endTime.hour}:${endTime.minute.toString().padLeft(2, '0')}'; debugPrint( - '✅ Successfully formatted rental period: \\${val?['rental_period']}', + '✅ Successfully formatted rental period: ${val?['rental_period']}', ); } catch (e) { - debugPrint('❌ Error parsing date: \\${e}'); + debugPrint('❌ Error parsing date: ${e}'); } } else { debugPrint( @@ -577,7 +599,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Pesanan Dibatalkan', 'Batas waktu pembayaran telah berakhir', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, duration: Duration(seconds: 5), @@ -624,7 +646,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal mengambil foto: \\${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -653,7 +675,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal memilih foto dari galeri: \\${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -679,7 +701,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Mohon unggah bukti pembayaran terlebih dahulu', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -691,7 +713,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Info', 'Tidak ada perubahan yang perlu disimpan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.blue, colorText: Colors.white, ); @@ -843,7 +865,7 @@ class PembayaranSewaController extends GetxController val?['status'] == 'PEMBAYARAN DENDA') { val?['status'] = 'PERIKSA PEMBAYARAN DENDA'; } else { - val?['status'] = 'MEMERIKSA PEMBAYARAN'; + val?['status'] = 'PERIKSA PEMBAYARAN'; } }); @@ -894,7 +916,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Sukses', 'Bukti pembayaran berhasil diunggah', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -903,7 +925,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Error', 'Gagal mengunggah bukti pembayaran: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -930,19 +952,19 @@ class PembayaranSewaController extends GetxController newStatus = 'MENUNGGU PEMBAYARAN'; break; case 1: - newStatus = 'MEMERIKSA PEMBAYARAN'; + newStatus = 'PERIKSA PEMBAYARAN'; break; case 2: newStatus = 'DITERIMA'; break; case 3: - newStatus = 'PENGEMBALIAN'; + newStatus = 'DIKEMBALIKAN'; break; case 4: newStatus = 'PEMBAYARAN DENDA'; break; case 5: - newStatus = 'MEMERIKSA PEMBAYARAN DENDA'; + newStatus = 'PERIKSA PEMBAYARAN DENDA'; break; case 6: newStatus = 'SELESAI'; @@ -965,7 +987,7 @@ class PembayaranSewaController extends GetxController case 'MENUNGGU PEMBAYARAN': currentStep.value = 0; break; - case 'MEMERIKSA PEMBAYARAN': + case 'PERIKSA PEMBAYARAN': currentStep.value = 1; break; case 'DITERIMA': @@ -974,7 +996,7 @@ class PembayaranSewaController extends GetxController case 'AKTIF': currentStep.value = 3; break; - case 'PENGEMBALIAN': + case 'DIKEMBALIKAN': currentStep.value = 4; break; case 'PEMBAYARAN DENDA': @@ -1003,7 +1025,7 @@ class PembayaranSewaController extends GetxController void submitCashPayment() { // Update order status orderDetails.update((val) { - val?['status'] = 'MEMERIKSA PEMBAYARAN'; + val?['status'] = 'PERIKSA PEMBAYARAN'; }); // Cancel countdown timer as payment has been submitted @@ -1013,7 +1035,7 @@ class PembayaranSewaController extends GetxController Get.snackbar( 'Sukses', 'Pembayaran tunai berhasil disubmit', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -1266,7 +1288,20 @@ class PembayaranSewaController extends GetxController updateCurrentStepBasedOnStatus(); // Restart countdown timer if needed - if (orderDetails.value['status'] == 'MENUNGGU PEMBAYARAN') { + if ((orderDetails.value['status'] ?? '').toString().toUpperCase() == + 'MENUNGGU PEMBAYARAN') { + debugPrint('Status is MENUNGGU PEMBAYARAN, restarting countdown timer'); + + // Ensure updated_at is set to current time if refreshing with MENUNGGU PEMBAYARAN status + if (orderDetails.value['updated_at'] == null) { + orderDetails.update((val) { + val?['updated_at'] = DateTime.now().toIso8601String(); + }); + debugPrint( + 'Set updated_at to current time: ${orderDetails.value['updated_at']}', + ); + } + _countdownTimer?.cancel(); startCountdownTimer(); } diff --git a/lib/app/modules/warga/controllers/sewa_aset_controller.dart b/lib/app/modules/warga/controllers/sewa_aset_controller.dart index fd63c9e..9a21835 100644 --- a/lib/app/modules/warga/controllers/sewa_aset_controller.dart +++ b/lib/app/modules/warga/controllers/sewa_aset_controller.dart @@ -168,7 +168,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat data aset', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -279,7 +279,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Error', message, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -321,7 +321,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Sukses', 'Pesanan berhasil dibuat', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -383,7 +383,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Error', 'Terjadi kesalahan saat memuat data paket', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -429,7 +429,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Error', 'Gagal memuat data paket. Silakan coba lagi nanti.', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -470,7 +470,7 @@ class SewaAsetController extends GetxController Get.snackbar( 'Sukses', 'Pesanan paket berhasil dibuat', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); diff --git a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart index 83247b7..3e91919 100644 --- a/lib/app/modules/warga/controllers/warga_dashboard_controller.dart +++ b/lib/app/modules/warga/controllers/warga_dashboard_controller.dart @@ -46,30 +46,51 @@ class WargaDashboardController extends GetxController { final dendaAktifCount = 0.obs; @override - void onInit() { + void onInit() async { super.onInit(); // Set navigation index to Home (0) navigationService.setNavIndex(0); - // Load user data - fetchProfileFromWargaDesa(); - _loadUserData(); + // Check if navigation is coming from login + final args = Get.arguments; + final bool isFromLogin = args != null && args['from_login'] == true; - // Load sample data - _loadSampleData(); + if (isFromLogin) { + print('onInit: Navigation from login detected, prioritizing data fetch'); + } - // Load dummy data for bills and penalties - loadDummyData(); + // Verifikasi bahwa pengguna sudah login sebelum melakukan fetch data + if (_authProvider.currentUser != null) { + // Prioritize loading user profile data first + await fetchProfileFromWargaDesa(); - // Load unpaid rentals - loadUnpaidRentals(); + // If the profile data was not loaded successfully, try again after a short delay + if (userName.value == 'Pengguna Warga' || userNik.value.isEmpty) { + print('onInit: Profile data not loaded, retrying after delay'); + await Future.delayed(const Duration(milliseconds: 800)); + await fetchProfileFromWargaDesa(); + } - // Debug count sewa_aset by status - _debugCountSewaAset(); + // Load other user data + await _loadUserData(); - // Load sewa aktif - loadActiveRentals(); + // Load other data in parallel to speed up the dashboard initialization + Future.wait([ + _loadSampleData(), + loadDummyData(), + loadUnpaidRentals(), + _debugCountSewaAset(), + loadActiveRentals(), + ]).then((_) => print('onInit: All data loaded successfully')); + + // If coming from login, make sure UI is updated + if (isFromLogin) { + update(); + } + } else { + print('onInit: User not logged in, skipping data fetch'); + } } Future _loadUserData() async { @@ -116,7 +137,7 @@ class WargaDashboardController extends GetxController { } } - void _loadSampleData() { + Future _loadSampleData() async { // Clear any existing data activeRentals.clear(); @@ -147,10 +168,37 @@ class WargaDashboardController extends GetxController { navigationService.toSewaAset(); } - void refreshData() { - fetchProfileFromWargaDesa(); - _loadSampleData(); - loadDummyData(); + Future refreshData() async { + print('refreshData: Refreshing dashboard data'); + try { + // First fetch profile data + await fetchProfileFromWargaDesa(); + await _loadUserData(); + + // Then load all other data in parallel + await Future.wait([ + _loadSampleData(), + loadDummyData(), + loadUnpaidRentals(), + loadActiveRentals(), + _debugCountSewaAset(), + ]); + + // Update UI + update(); + print('refreshData: Dashboard data refreshed successfully'); + } catch (e) { + print('refreshData: Error refreshing data: $e'); + // Show error message to user + Get.snackbar( + 'Perhatian', + 'Terjadi kesalahan saat memuat data', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red.shade100, + colorText: Colors.red.shade900, + duration: const Duration(seconds: 3), + ); + } } void onNavItemTapped(int index) { @@ -176,12 +224,14 @@ class WargaDashboardController extends GetxController { Get.toNamed(Routes.WARGA_SEWA, arguments: {'tab': 3}); } - void logout() async { + Future logout() async { + print('logout: Logging out user'); await _authProvider.signOut(); navigationService.toLogin(); + print('logout: User logged out and redirected to login screen'); } - void loadDummyData() { + Future loadDummyData() async { // Dummy active bills activeBills.clear(); activeBills.add({ @@ -331,24 +381,77 @@ class WargaDashboardController extends GetxController { Future fetchProfileFromWargaDesa() async { try { final user = _authProvider.currentUser; - if (user == null) return; + if (user == null) { + print( + 'fetchProfileFromWargaDesa: No current user found, skipping fetch', + ); + return; // Exit early if no user is logged in + } + final userId = user.id; + print('fetchProfileFromWargaDesa: Fetching data for user: $userId'); + final data = await _authProvider.client .from('warga_desa') .select('nik, alamat, email, nama_lengkap, no_hp, avatar') .eq('user_id', userId) .maybeSingle(); + if (data != null) { + print('fetchProfileFromWargaDesa: Data retrieved successfully'); userNik.value = data['nik']?.toString() ?? ''; userAddress.value = data['alamat']?.toString() ?? ''; userEmail.value = data['email']?.toString() ?? ''; userName.value = data['nama_lengkap']?.toString() ?? ''; userPhone.value = data['no_hp']?.toString() ?? ''; userAvatar.value = data['avatar']?.toString() ?? ''; + + // Trigger UI refresh + update(); + print('fetchProfileFromWargaDesa: Profile data updated'); + } else { + print('fetchProfileFromWargaDesa: No data found for user: $userId'); } } catch (e) { print('Error fetching profile from warga_desa: $e'); + // If it fails, try again after a delay + await Future.delayed(const Duration(seconds: 1)); + try { + await _retryFetchProfile(); + } catch (retryError) { + print('Retry error fetching profile: $retryError'); + } + } + } + + // Helper method to retry fetching profile + Future _retryFetchProfile() async { + final user = _authProvider.currentUser; + if (user == null) { + print('_retryFetchProfile: No current user found, skipping retry'); + return; // Exit early if no user is logged in + } + + print('_retryFetchProfile: Retrying fetch for user: ${user.id}'); + + final data = + await _authProvider.client + .from('warga_desa') + .select('nik, alamat, email, nama_lengkap, no_hp, avatar') + .eq('user_id', user.id) + .maybeSingle(); + + if (data != null) { + print('_retryFetchProfile: Data retrieved successfully on retry'); + userNik.value = data['nik']?.toString() ?? ''; + userAddress.value = data['alamat']?.toString() ?? ''; + userEmail.value = data['email']?.toString() ?? ''; + userName.value = data['nama_lengkap']?.toString() ?? ''; + userPhone.value = data['no_hp']?.toString() ?? ''; + userAvatar.value = data['avatar']?.toString() ?? ''; + update(); + print('_retryFetchProfile: Profile data updated'); } } @@ -533,7 +636,7 @@ class WargaDashboardController extends GetxController { Get.snackbar( 'Gagal', 'Tidak dapat mengakses ${source == ImageSource.camera ? 'kamera' : 'galeri'}', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red.shade700, colorText: Colors.white, duration: const Duration(seconds: 3), diff --git a/lib/app/modules/warga/controllers/warga_sewa_controller.dart b/lib/app/modules/warga/controllers/warga_sewa_controller.dart index 9b8dbaf..d334e6c 100644 --- a/lib/app/modules/warga/controllers/warga_sewa_controller.dart +++ b/lib/app/modules/warga/controllers/warga_sewa_controller.dart @@ -254,10 +254,28 @@ class WargaSewaController extends GetxController debugPrint('Fetched ${sewaAsetList.length} sewa_aset records'); + // Debug the structure of the first record if available + if (sewaAsetList.isNotEmpty) { + debugPrint('Sample sewa_aset record: ${sewaAsetList.first}'); + debugPrint('updated_at field: ${sewaAsetList.first['updated_at']}'); + } + // Process each sewa_aset record for (var sewaAset in sewaAsetList) { final processedData = await _processRentalData(sewaAset); processedData['status'] = sewaAset['status'] ?? 'MENUNGGU PEMBAYARAN'; + + // Ensure updated_at is set correctly + if (sewaAset['updated_at'] == null && + processedData['status'] == 'MENUNGGU PEMBAYARAN') { + // If updated_at is null but status is MENUNGGU PEMBAYARAN, use created_at as fallback + processedData['updated_at'] = + sewaAset['created_at'] ?? DateTime.now().toIso8601String(); + debugPrint( + 'Using created_at as fallback for updated_at: ${processedData['updated_at']}', + ); + } + rentals.add(processedData); } @@ -321,7 +339,7 @@ class WargaSewaController extends GetxController Get.snackbar( 'Berhasil', 'Pesanan berhasil dibatalkan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -337,7 +355,7 @@ class WargaSewaController extends GetxController Get.snackbar( 'Gagal', 'Gagal membatalkan pesanan: $e', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -377,7 +395,7 @@ class WargaSewaController extends GetxController Get.snackbar( 'Info', 'Navigasi ke halaman pembayaran', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); } diff --git a/lib/app/modules/warga/views/order_sewa_aset_view.dart b/lib/app/modules/warga/views/order_sewa_aset_view.dart index af76f9c..a0a729e 100644 --- a/lib/app/modules/warga/views/order_sewa_aset_view.dart +++ b/lib/app/modules/warga/views/order_sewa_aset_view.dart @@ -285,9 +285,10 @@ class OrderSewaAsetView extends GetView { return WillPopScope( onWillPop: () async { - // Handle back button press here - handleBackButtonPress(); - return false; // We handle the navigation ourselves + // Bersihkan controller saat pengguna keluar dari halaman dengan tombol back perangkat + controller.clearData(); + debugPrint('🧹 Controller data cleared by WillPopScope'); + return true; }, child: Scaffold( backgroundColor: AppColors.background, @@ -474,108 +475,60 @@ class OrderSewaAsetView extends GetView { }, // Add onTap handler for the image onTap: () { - debugPrint("📸 Image tapped - opening fullscreen viewer"); - final imageUrl = controller.getCurrentPhotoUrl(); - if (imageUrl != null && imageUrl.isNotEmpty) { - debugPrint("📸 Current image URL: $imageUrl"); - debugPrint("📸 Total photos: ${controller.assetPhotos.length}"); + debugPrint( + "📸 Image tapped - attempting to open fullscreen viewer", + ); + try { + final imageUrl = controller.getCurrentPhotoUrl(); + if (imageUrl != null && imageUrl.isNotEmpty) { + debugPrint("📸 Current image URL: $imageUrl"); + debugPrint( + "📸 Total photos: ${controller.assetPhotos.length}", + ); - // Extract all image URLs from the assetPhotos collection - final List photoUrls = []; - for (var photo in controller.assetPhotos) { - final url = photo.fotoAset; - if (url.isNotEmpty) { - photoUrls.add(url); - debugPrint("📸 Added photo URL: $url"); + // Extract all image URLs from the assetPhotos collection + final List photoUrls = []; + for (var photo in controller.assetPhotos) { + final url = photo.fotoAset; + if (url.isNotEmpty) { + photoUrls.add(url); + debugPrint("📸 Added photo URL: $url"); + } } + + if (photoUrls.isEmpty) { + debugPrint("📸 No valid photo URLs found"); + return; + } + + debugPrint( + "📸 About to navigate to fullscreen viewer with ${photoUrls.length} photos", + ); + + // Navigate to full screen image viewer using GetX + Get.to( + () => FullScreenImageViewer( + imageUrls: photoUrls, + initialIndex: controller.currentPhotoIndex.value, + ), + fullscreenDialog: true, + transition: Transition.fade, + ); + + debugPrint("📸 Navigation initiated"); + } else { + debugPrint("📸 No valid current image URL"); } - - if (photoUrls.isEmpty) { - debugPrint("📸 No valid photo URLs found"); - return; - } - - showDialog( - context: Get.context!, - builder: - (context) => Dialog( - insetPadding: EdgeInsets.zero, - child: Container( - width: double.infinity, - height: double.infinity, - color: Colors.black, - child: Stack( - children: [ - // Image - Center( - child: InteractiveViewer( - panEnabled: true, - boundaryMargin: EdgeInsets.all(80), - minScale: 0.5, - maxScale: 4, - child: CachedNetworkImage( - imageUrl: - photoUrls[controller - .currentPhotoIndex - .value], - fit: BoxFit.contain, - placeholder: - (context, url) => Center( - child: CircularProgressIndicator( - color: Colors.white, - ), - ), - errorWidget: - (context, url, error) => Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.broken_image_rounded, - size: 64, - color: Colors.grey[400], - ), - SizedBox(height: 16), - Text( - 'Gagal memuat foto', - style: TextStyle( - color: Colors.grey[600], - fontSize: 16, - ), - ), - ], - ), - ), - ), - ), - ), - - // Close button - Positioned( - top: 40, - right: 20, - child: IconButton( - icon: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black54, - shape: BoxShape.circle, - ), - child: Icon( - Icons.close, - color: Colors.white, - ), - ), - onPressed: () => Navigator.of(context).pop(), - ), - ), - ], - ), - ), - ), + } catch (e) { + debugPrint("📸 ERROR in image tap handler: $e"); + Get.snackbar( + 'Error', + 'Failed to open image viewer', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.shade100, + colorText: Colors.red.shade900, + duration: const Duration(seconds: 2), ); - } else { - debugPrint("📸 No valid current image URL"); } }, child: ClipRRect( @@ -667,17 +620,19 @@ class OrderSewaAsetView extends GetView { // Gradient overlay Positioned.fill( - child: DecoratedBox( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.1), - Colors.transparent, - Colors.black.withOpacity(0.3), - ], - stops: [0.0, 0.6, 1.0], + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0.1), + Colors.transparent, + Colors.black.withOpacity(0.3), + ], + stops: [0.0, 0.6, 1.0], + ), ), ), ), @@ -687,13 +642,15 @@ class OrderSewaAsetView extends GetView { Positioned( bottom: 16, right: 16, - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), - borderRadius: BorderRadius.circular(12), + child: IgnorePointer( + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.zoom_in, color: Colors.white, size: 24), ), - child: Icon(Icons.zoom_in, color: Colors.white, size: 24), ), ), @@ -1058,88 +1015,176 @@ class OrderSewaAsetView extends GetView { asetId, ), builder: (context, snapshot) { - return Container( - width: 60, - height: 60, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular( - 8, - ), - color: - AppColors - .surfaceLight, - ), - child: - snapshot.connectionState == - ConnectionState - .waiting - ? Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: - 2, - color: - AppColors - .primary, - ), + return GestureDetector( + onTap: () async { + debugPrint( + "📸 Package item image tapped - attempting to open fullscreen viewer for asset ID: $asetId", + ); + try { + // Get all photos for this asset + final photos = + await controller + .asetProvider + .getAsetPhotos( + asetId, + ); + if (photos.isEmpty) { + debugPrint( + "📸 No photos found for asset ID: $asetId", + ); + return; + } + + // Extract valid photo URLs + final List + photoUrls = []; + for (var photo + in photos) { + final url = + photo.fotoAset; + if (url.isNotEmpty) { + photoUrls.add(url); + debugPrint( + "📸 Added photo URL: $url", + ); + } + } + + if (photoUrls.isEmpty) { + debugPrint( + "📸 No valid photo URLs found", + ); + return; + } + + debugPrint( + "📸 About to navigate to fullscreen viewer with ${photoUrls.length} photos", + ); + + // Navigate to full screen image viewer using GetX + Get.to( + () => + FullScreenImageViewer( + imageUrls: + photoUrls, + initialIndex: 0, ), - ) - : snapshot.data != - null - ? ClipRRect( - borderRadius: - BorderRadius.circular( - 8, + fullscreenDialog: + true, + transition: + Transition.fade, + ); + + debugPrint( + "📸 Navigation initiated", + ); + } catch (e) { + debugPrint( + "📸 ERROR in package item image tap handler: $e", + ); + Get.snackbar( + 'Error', + 'Failed to open image viewer', + snackPosition: + SnackPosition + .BOTTOM, + backgroundColor: + Colors + .red + .shade100, + colorText: + Colors + .red + .shade900, + duration: + const Duration( + seconds: 2, + ), + ); + } + }, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 8, + ), + color: + AppColors + .surfaceLight, + ), + child: + snapshot.connectionState == + ConnectionState + .waiting + ? Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: + 2, + color: + AppColors + .primary, ), - child: CachedNetworkImage( - imageUrl: - snapshot - .data!, - fit: - BoxFit - .cover, - placeholder: - ( - context, - url, - ) => Center( - child: SizedBox( - width: - 20, - height: - 20, - child: CircularProgressIndicator( - strokeWidth: - 2, - color: - AppColors.primary, + ), + ) + : snapshot.data != + null + ? ClipRRect( + borderRadius: + BorderRadius.circular( + 8, + ), + child: CachedNetworkImage( + imageUrl: + snapshot + .data!, + fit: + BoxFit + .cover, + placeholder: + ( + context, + url, + ) => Center( + child: SizedBox( + width: + 20, + height: + 20, + child: CircularProgressIndicator( + strokeWidth: + 2, + color: + AppColors.primary, + ), ), ), - ), - errorWidget: - ( - context, - url, - error, - ) => Icon( - Icons - .image_not_supported_outlined, - color: - AppColors - .textLight, - ), + errorWidget: + ( + context, + url, + error, + ) => Icon( + Icons + .image_not_supported_outlined, + color: + AppColors.textLight, + ), + ), + ) + : Icon( + Icons + .image_not_supported_outlined, + color: + AppColors + .textLight, ), - ) - : Icon( - Icons - .image_not_supported_outlined, - color: - AppColors - .textLight, - ), + ), ); }, ), @@ -2458,98 +2503,173 @@ class _FullScreenImageViewerState extends State { for (int i = 0; i < widget.imageUrls.length && i < 3; i++) { debugPrint("📸 Image URL $i: ${widget.imageUrls[i]}"); } + + // Hide status bar for immersive view + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [], // Hide all system UI + ).catchError((error) { + debugPrint("📸 Error setting system UI mode: $error"); + }); } @override void dispose() { + // Restore status bar when exiting + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: SystemUiOverlay.values, // Restore all system UI + ).catchError((error) { + debugPrint("📸 Error restoring system UI mode: $error"); + }); + pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Stack( - children: [ - // Photo Gallery - PhotoViewGallery.builder( - scrollPhysics: const BouncingScrollPhysics(), - builder: (BuildContext context, int index) { - final imageUrl = widget.imageUrls[index]; - debugPrint("📸 Building image at index $index: $imageUrl"); + return WillPopScope( + onWillPop: () async { + // Ensure UI is restored on back button press + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: SystemUiOverlay.values, + ); + return true; + }, + child: Scaffold( + backgroundColor: Colors.black, + body: SafeArea( + child: Stack( + children: [ + // Photo Gallery + GestureDetector( + onTap: () { + // Add tap functionality to close when tapping on image + Navigator.of(context).pop(); + }, + child: PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: (BuildContext context, int index) { + final imageUrl = widget.imageUrls[index]; + debugPrint("📸 Building image at index $index: $imageUrl"); - return PhotoViewGalleryPageOptions( - imageProvider: CachedNetworkImageProvider(imageUrl), - initialScale: PhotoViewComputedScale.contained, - minScale: PhotoViewComputedScale.contained * 0.8, - maxScale: PhotoViewComputedScale.covered * 2.0, - heroAttributes: PhotoViewHeroAttributes(tag: "photo_$index"), - ); - }, - itemCount: widget.imageUrls.length, - loadingBuilder: - (context, event) => Center( - child: SizedBox( - width: 30.0, - height: 30.0, - child: CircularProgressIndicator( - value: - event == null - ? 0 - : event.cumulativeBytesLoaded / - event.expectedTotalBytes!, - valueColor: AlwaysStoppedAnimation(Colors.white), + return PhotoViewGalleryPageOptions( + imageProvider: CachedNetworkImageProvider(imageUrl), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 2.0, + heroAttributes: PhotoViewHeroAttributes( + tag: "photo_$index", + ), + errorBuilder: (context, error, stackTrace) { + debugPrint("📸 Error loading image: $error"); + return Container( + color: Colors.black, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.broken_image_rounded, + color: Colors.white70, + size: 50, + ), + const SizedBox(height: 16), + const Text( + 'Gagal memuat gambar', + style: TextStyle( + color: Colors.white70, + fontSize: 16, + ), + ), + ], + ), + ), + ); + }, + ); + }, + itemCount: widget.imageUrls.length, + loadingBuilder: + (context, event) => Center( + child: Container( + width: 40.0, + height: 40.0, + child: CircularProgressIndicator( + value: + event == null + ? 0 + : event.cumulativeBytesLoaded / + (event.expectedTotalBytes ?? 1), + valueColor: const AlwaysStoppedAnimation( + Colors.white, + ), + ), + ), + ), + backgroundDecoration: const BoxDecoration( + color: Colors.black, + ), + pageController: pageController, + onPageChanged: (index) { + setState(() { + currentIndex = index; + debugPrint("📸 Page changed to index: $index"); + }); + }, + ), + ), + + // Close button + Positioned( + top: 20, + left: 20, + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.black54, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + color: Colors.white, + size: 24, ), ), ), - backgroundDecoration: BoxDecoration(color: Colors.black), - pageController: pageController, - onPageChanged: (index) { - setState(() { - currentIndex = index; - debugPrint("📸 Page changed to index: $index"); - }); - }, - ), - - // Close button - Positioned( - top: 40, - left: 20, - child: IconButton( - icon: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black54, - shape: BoxShape.circle, - ), - child: Icon(Icons.close, color: Colors.white), ), - onPressed: () => Navigator.of(context).pop(), - ), - ), - // Image counter - Positioned( - top: 40, - right: 20, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(16), - ), - child: Text( - '${currentIndex + 1}/${widget.imageUrls.length}', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, + // Image counter + Positioned( + top: 20, + right: 20, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + '${currentIndex + 1}/${widget.imageUrls.length}', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), ), ), - ), + ], ), - ], + ), ), ); } diff --git a/lib/app/modules/warga/views/order_sewa_paket_view.dart.remaining b/lib/app/modules/warga/views/order_sewa_paket_view.dart.remaining index 325f320..25c7868 100644 --- a/lib/app/modules/warga/views/order_sewa_paket_view.dart.remaining +++ b/lib/app/modules/warga/views/order_sewa_paket_view.dart.remaining @@ -318,7 +318,7 @@ Get.snackbar( 'Perhatian', 'Pilih jam mulai terlebih dahulu', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: AppColors.warning, colorText: Colors.white, ); diff --git a/lib/app/modules/warga/views/pembayaran_sewa_view.dart b/lib/app/modules/warga/views/pembayaran_sewa_view.dart index 9fb0726..e864140 100644 --- a/lib/app/modules/warga/views/pembayaran_sewa_view.dart +++ b/lib/app/modules/warga/views/pembayaran_sewa_view.dart @@ -102,25 +102,25 @@ class PembayaranSewaView extends GetView { .toUpperCase(); final updatedAtStr = controller.orderDetails.value['updated_at']; - print('DEBUG status: ' + status); - print( - 'DEBUG updated_at (raw): ' + - (updatedAtStr?.toString() ?? 'NULL'), + debugPrint('DEBUG status (batas waktu): $status'); + debugPrint( + 'DEBUG updated_at batas waktu (raw): ${updatedAtStr?.toString() ?? 'NULL'}', ); if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) { try { final updatedAt = DateTime.parse(updatedAtStr); - print( - 'DEBUG updated_at (parsed): ' + - updatedAt.toIso8601String(), + debugPrint( + 'DEBUG updated_at batas waktu (parsed): ${updatedAt.toIso8601String()}', ); return CountdownTimerWidget(updatedAt: updatedAt); } catch (e) { - print('ERROR parsing updated_at: ' + e.toString()); - return Text( - 'Format tanggal salah', - style: TextStyle(color: Colors.red), + debugPrint('ERROR parsing updated_at batas waktu: $e'); + // Fallback to current time if parsing fails + final now = DateTime.now(); + debugPrint( + 'Using current time as fallback for batas waktu: ${now.toIso8601String()}', ); + return CountdownTimerWidget(updatedAt: now); } } return SizedBox.shrink(); @@ -322,25 +322,25 @@ class PembayaranSewaView extends GetView { .toUpperCase(); final updatedAtStr = controller.orderDetails.value['updated_at']; - print('DEBUG status: ' + status); - print( - 'DEBUG updated_at (raw): ' + - (updatedAtStr?.toString() ?? 'NULL'), + debugPrint('DEBUG status (batas waktu): $status'); + debugPrint( + 'DEBUG updated_at batas waktu (raw): ${updatedAtStr?.toString() ?? 'NULL'}', ); if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) { try { final updatedAt = DateTime.parse(updatedAtStr); - print( - 'DEBUG updated_at (parsed): ' + - updatedAt.toIso8601String(), + debugPrint( + 'DEBUG updated_at batas waktu (parsed): ${updatedAt.toIso8601String()}', ); return CountdownTimerWidget(updatedAt: updatedAt); } catch (e) { - print('ERROR parsing updated_at: ' + e.toString()); - return Text( - 'Format tanggal salah', - style: TextStyle(color: Colors.red), + debugPrint('ERROR parsing updated_at batas waktu: $e'); + // Fallback to current time if parsing fails + final now = DateTime.now(); + debugPrint( + 'Using current time as fallback for batas waktu: ${now.toIso8601String()}', ); + return CountdownTimerWidget(updatedAt: now); } } return SizedBox.shrink(); @@ -363,7 +363,7 @@ class PembayaranSewaView extends GetView { 'step': 0, }, { - 'title': 'Memeriksa Pembayaran', + 'title': 'Periksa Pembayaran', 'description': 'Pembayaran sedang diverifikasi oleh petugas', 'icon': Icons.receipt_long, 'step': 1, @@ -381,8 +381,8 @@ class PembayaranSewaView extends GetView { 'step': 3, }, { - 'title': 'Pengembalian', - 'description': 'Proses pengembalian aset sewa', + 'title': 'Dikembalikan', + 'description': 'Aset sudah dikembalikan', 'icon': Icons.assignment_return, 'step': 4, }, @@ -2585,9 +2585,8 @@ class _CountdownTimerWidgetState extends State { @override void initState() { super.initState(); - print( - 'DEBUG [CountdownTimerWidget] updatedAt: ' + - widget.updatedAt.toIso8601String(), + debugPrint( + 'DEBUG [CountdownTimerWidget] updatedAt: ${widget.updatedAt.toIso8601String()}', ); updateRemaining(); timer = Timer.periodic( @@ -2599,18 +2598,29 @@ class _CountdownTimerWidgetState extends State { void updateRemaining() { final now = DateTime.now(); final end = widget.updatedAt.add(const Duration(hours: 1)); + + debugPrint('Current time: $now'); + debugPrint('Deadline: $end'); + setState(() { remaining = end.difference(now); + debugPrint('Remaining time: ${remaining.inSeconds} seconds'); + if (remaining.isNegative) { + debugPrint('Countdown expired, setting to zero'); remaining = Duration.zero; timer?.cancel(); - widget.onTimeout?.call(); + if (widget.onTimeout != null) { + debugPrint('Calling onTimeout callback'); + widget.onTimeout?.call(); + } } }); } @override void dispose() { + debugPrint('CountdownTimerWidget disposed'); timer?.cancel(); super.dispose(); } @@ -2620,9 +2630,15 @@ class _CountdownTimerWidgetState extends State { if (remaining.inSeconds <= 0) { return Text('Waktu habis', style: TextStyle(color: Colors.red)); } + final h = remaining.inHours; final m = remaining.inMinutes % 60; final s = remaining.inSeconds % 60; + + final timeString = + '${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + debugPrint('Rendering countdown: $timeString'); + return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( @@ -2636,7 +2652,7 @@ class _CountdownTimerWidgetState extends State { Icon(Icons.timer_outlined, size: 14, color: Colors.red), const SizedBox(width: 4), Text( - 'Bayar dalam ${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}', + 'Bayar dalam $timeString', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, diff --git a/lib/app/modules/warga/views/sewa_aset_view.dart b/lib/app/modules/warga/views/sewa_aset_view.dart index 7395605..397e9fe 100644 --- a/lib/app/modules/warga/views/sewa_aset_view.dart +++ b/lib/app/modules/warga/views/sewa_aset_view.dart @@ -1524,7 +1524,7 @@ class SewaAsetView extends GetView { Get.snackbar( 'Error', 'ID aset tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1754,7 +1754,7 @@ class SewaAsetView extends GetView { Get.snackbar( 'Error', 'ID aset tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -1801,7 +1801,7 @@ class SewaAsetView extends GetView { Get.snackbar( 'Error', 'ID aset tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); diff --git a/lib/app/modules/warga/views/warga_dashboard_view.dart b/lib/app/modules/warga/views/warga_dashboard_view.dart index 4f17d58..dc4d263 100644 --- a/lib/app/modules/warga/views/warga_dashboard_view.dart +++ b/lib/app/modules/warga/views/warga_dashboard_view.dart @@ -14,6 +14,19 @@ class WargaDashboardView extends GetView { Widget build(BuildContext context) { final size = MediaQuery.of(context).size; + // Check if coming from login and trigger refresh + WidgetsBinding.instance.addPostFrameCallback((_) { + final args = Get.arguments; + final bool isFromLogin = args != null && args['from_login'] == true; + if (isFromLogin) { + // Trigger refresh after UI is built + controller.refreshData(); + print( + 'WargaDashboardView: Auto-refreshed data due to login navigation', + ); + } + }); + return WillPopScope( onWillPop: () async => false, // Prevent back navigation child: WargaLayout( diff --git a/lib/app/modules/warga/views/warga_profile_view.dart b/lib/app/modules/warga/views/warga_profile_view.dart index 321e786..07f7666 100644 --- a/lib/app/modules/warga/views/warga_profile_view.dart +++ b/lib/app/modules/warga/views/warga_profile_view.dart @@ -80,7 +80,7 @@ class WargaProfileView extends GetView { Get.snackbar( 'Gagal', 'Terjadi kesalahan saat menyimpan foto profil', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -99,7 +99,7 @@ class WargaProfileView extends GetView { Get.snackbar( 'Gagal', 'Terjadi kesalahan saat menghapus foto profil', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -124,7 +124,7 @@ class WargaProfileView extends GetView { Get.snackbar( 'Sukses', 'Perubahan berhasil disimpan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.green, colorText: Colors.white, ); @@ -132,7 +132,7 @@ class WargaProfileView extends GetView { Get.snackbar( 'Gagal', 'Terjadi kesalahan saat menyimpan perubahan', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -156,6 +156,9 @@ class WargaProfileView extends GetView { : IconButton( onPressed: () { isEditing.value = true; + // Update controllers with current values when entering edit mode + nameController.text = controller.userName.value; + phoneController.text = controller.userPhone.value; }, icon: const Icon(Icons.edit_outlined), tooltip: 'Edit Profil', @@ -365,14 +368,6 @@ class WargaProfileView extends GetView { } else { // Set avatar deleted flag isAvatarDeleted.value = true; - - Get.snackbar( - 'Info', - 'Foto profil akan dihapus setelah menekan Simpan', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade700, - colorText: Colors.white, - ); } }, icon: const Icon(Icons.delete_outline, size: 16), @@ -468,163 +463,242 @@ class WargaProfileView extends GetView { ) { return Column( children: [ - // Section 1: Data Diri + // Section 1: Data Diri - Dengan desain yang lebih modern Card( - elevation: 4, + elevation: 2, + shadowColor: AppColors.primary.withOpacity(0.3), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(20), ), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.person_rounded, - color: AppColors.primary, - size: 22, - ), - const SizedBox(width: 10), - Text( - 'Data Diri', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), - ), - if (isEditing) ...[ - const Spacer(), - Text( - 'Mode Edit', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.orange.shade700, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, AppColors.primary.withOpacity(0.05)], + ), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.person_rounded, + color: AppColors.primary, + size: 22, ), ), - const SizedBox(width: 6), - Icon( - Icons.edit_note, - color: Colors.orange.shade700, - size: 18, + const SizedBox(width: 14), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Data Diri', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + Text( + 'Informasi personal Anda', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], ), + if (isEditing) ...[ + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + Icon( + Icons.edit_note, + color: Colors.orange.shade700, + size: 16, + ), + const SizedBox(width: 4), + Text( + 'Mode Edit', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.orange.shade700, + ), + ), + ], + ), + ), + ], ], - ], - ), - const SizedBox(height: 20), - // Email - always read-only - _buildInfoItemModern( - context, - icon: Icons.email_rounded, - title: 'Email', - value: controller.userEmail.value, - ), - const SizedBox(height: 16), - // Nama Lengkap - editable - _buildEditableInfoItem( - context, - icon: Icons.person_rounded, - title: 'Nama Lengkap', - value: controller.userName.value, - isEditing: isEditing, - controller: nameController, - ), - const SizedBox(height: 16), - // Nomor Telepon - editable - _buildEditableInfoItem( - context, - icon: Icons.phone_rounded, - title: 'Nomor Telepon', - value: controller.userPhone.value, - isEditing: isEditing, - controller: phoneController, - keyboardType: TextInputType.phone, - ), - ], + ), + const SizedBox(height: 24), + // Email - always read-only + _buildInfoItemModern( + context, + icon: Icons.email_rounded, + title: 'Email', + value: controller.userEmail.value, + isVerified: true, + ), + const SizedBox(height: 16), + // Nama Lengkap - editable + _buildEditableInfoItem( + context, + icon: Icons.person_rounded, + title: 'Nama Lengkap', + value: controller.userName.value, + isEditing: isEditing, + controller: nameController, + ), + const SizedBox(height: 16), + // Nomor Telepon - editable + _buildEditableInfoItem( + context, + icon: Icons.phone_rounded, + title: 'Nomor Telepon', + value: controller.userPhone.value, + isEditing: isEditing, + controller: phoneController, + keyboardType: TextInputType.phone, + ), + ], + ), ), ), ), const SizedBox(height: 16), - // Section 2: Informasi Warga + // Section 2: Informasi Warga - Dengan desain yang lebih modern Card( - elevation: 4, + elevation: 2, + shadowColor: AppColors.accent.withOpacity(0.3), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(20), ), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.badge_rounded, - color: AppColors.primary, - size: 22, - ), - const SizedBox(width: 10), - Text( - 'Informasi Warga', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.primary, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, AppColors.accent.withOpacity(0.05)], + ), + ), + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.accent.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.badge_rounded, + color: AppColors.accent, + size: 22, + ), ), + const SizedBox(width: 14), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Informasi Warga', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.accent, + ), + ), + Text( + 'Detail kependudukan', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 24), + _buildInfoItemModern( + context, + icon: Icons.credit_card_rounded, + title: 'NIK', + value: controller.userNik.value, + isImportant: true, + ), + const SizedBox(height: 16), + _buildInfoItemModern( + context, + icon: Icons.calendar_today_rounded, + title: 'Tanggal Lahir', + value: formatTanggalLahir( + controller.userTanggalLahir.value, ), - ], - ), - const SizedBox(height: 20), - _buildInfoItemModern( - context, - icon: Icons.credit_card_rounded, - title: 'NIK', - value: controller.userNik.value, - ), - const SizedBox(height: 16), - _buildInfoItemModern( - context, - icon: Icons.calendar_today_rounded, - title: 'Tanggal Lahir', - value: controller.userTanggalLahir.value, - ), - const SizedBox(height: 16), - _buildInfoItemModern( - context, - icon: Icons.home_rounded, - title: 'Alamat', - value: controller.userAddress.value, - isMultiLine: true, - ), - const SizedBox(height: 16), - _buildInfoItemModern( - context, - icon: Icons.location_on_rounded, - title: 'RT/RW', - value: controller.userRtRw.value, - ), - const SizedBox(height: 16), - _buildInfoItemModern( - context, - icon: Icons.location_city_rounded, - title: 'Kelurahan/Desa', - value: controller.userKelurahanDesa.value, - ), - const SizedBox(height: 16), - _buildInfoItemModern( - context, - icon: Icons.map_rounded, - title: 'Kecamatan', - value: controller.userKecamatan.value, - ), - ], + ), + const SizedBox(height: 16), + _buildInfoItemModern( + context, + icon: Icons.home_rounded, + title: 'Alamat', + value: controller.userAddress.value, + isMultiLine: true, + ), + const SizedBox(height: 16), + // Tampilkan RT/RW dan Kelurahan/Desa dalam dua baris terpisah + _buildInfoItemModern( + context, + icon: Icons.location_on_rounded, + title: 'RT/RW', + value: controller.userRtRw.value, + ), + const SizedBox(height: 16), + _buildInfoItemModern( + context, + icon: Icons.location_city_rounded, + title: 'Kelurahan/Desa', + value: controller.userKelurahanDesa.value, + ), + const SizedBox(height: 16), + _buildInfoItemModern( + context, + icon: Icons.map_rounded, + title: 'Kecamatan', + value: controller.userKecamatan.value, + ), + ], + ), ), ), ), @@ -638,46 +712,85 @@ class WargaProfileView extends GetView { required String title, required String value, bool isMultiLine = false, + bool isImportant = false, + bool isVerified = false, + bool isCompact = false, }) { return Container( decoration: BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade200), + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: + isImportant + ? AppColors.primary.withOpacity(0.3) + : Colors.grey.shade200, + width: isImportant ? 1.5 : 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.05), + blurRadius: 5, + offset: const Offset(0, 2), + ), + ], + ), + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: isCompact ? 12 : 16, ), - padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: isMultiLine ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ Container( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.all(isCompact ? 6 : 8), decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.1), + color: + isImportant + ? AppColors.primary.withOpacity(0.15) + : AppColors.accent.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), - child: Icon(icon, color: AppColors.primary, size: 20), + child: Icon( + icon, + color: isImportant ? AppColors.primary : AppColors.accent, + size: isCompact ? 16 : 20, + ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.grey.shade600, - ), + Row( + children: [ + Text( + title, + style: TextStyle( + fontSize: isCompact ? 12 : 13, + fontWeight: FontWeight.w500, + color: Colors.grey.shade600, + ), + ), + if (isVerified) ...[ + const SizedBox(width: 4), + Icon(Icons.verified, size: 14, color: Colors.green), + ], + ], ), const SizedBox(height: 4), Text( value.isEmpty ? 'Tidak tersedia' : value, style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: Colors.grey.shade800, + fontSize: isCompact ? 14 : 15, + fontWeight: isImportant ? FontWeight.w700 : FontWeight.w600, + color: + isImportant + ? AppColors.primary + : value.isEmpty + ? Colors.grey.shade400 + : Colors.grey.shade800, ), maxLines: isMultiLine ? 3 : 1, overflow: TextOverflow.ellipsis, @@ -685,6 +798,12 @@ class WargaProfileView extends GetView { ], ), ), + if (isImportant) + Icon( + Icons.priority_high, + size: 16, + color: AppColors.primary.withOpacity(0.7), + ), ], ), ); @@ -821,7 +940,7 @@ class WargaProfileView extends GetView { Get.snackbar( 'Info', 'Fitur Ubah Password akan segera tersedia', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, ); }, ), @@ -933,4 +1052,54 @@ class WargaProfileView extends GetView { ), ); } + + // Fungsi untuk memformat tanggal lahir ke format Indonesia + String formatTanggalLahir(String tanggal) { + if (tanggal.isEmpty) return ''; + + try { + // Coba parse tanggal dari berbagai format yang mungkin + DateTime? dateTime; + + // Coba format yyyy-MM-dd + if (tanggal.contains('-')) { + final parts = tanggal.split('-'); + if (parts.length == 3) { + dateTime = DateTime.tryParse(tanggal); + } + } + // Coba format dd/MM/yyyy + else if (tanggal.contains('/')) { + final parts = tanggal.split('/'); + if (parts.length == 3) { + dateTime = DateTime.tryParse('${parts[2]}-${parts[1]}-${parts[0]}'); + } + } + + // Jika tidak bisa parse, kembalikan nilai asli + if (dateTime == null) return tanggal; + + // Daftar nama bulan dalam bahasa Indonesia + final List namaBulan = [ + 'Januari', + 'Februari', + 'Maret', + 'April', + 'Mei', + 'Juni', + 'Juli', + 'Agustus', + 'September', + 'Oktober', + 'November', + 'Desember', + ]; + + // Format tanggal ke format Indonesia: dd Bulan yyyy + return '${dateTime.day} ${namaBulan[dateTime.month - 1]} ${dateTime.year}'; + } catch (e) { + // Jika ada error, kembalikan nilai asli + return tanggal; + } + } } diff --git a/lib/app/modules/warga/views/warga_sewa_view.dart b/lib/app/modules/warga/views/warga_sewa_view.dart index cf7e374..a8d206f 100644 --- a/lib/app/modules/warga/views/warga_sewa_view.dart +++ b/lib/app/modules/warga/views/warga_sewa_view.dart @@ -570,8 +570,34 @@ class WargaSewaView extends GetView { if ((rental['status'] ?? '').toString().toUpperCase() == 'MENUNGGU PEMBAYARAN' && rental['updated_at'] != null) - CountdownTimerWidget( - updatedAt: DateTime.parse(rental['updated_at']), + Builder( + builder: (context) { + final updatedAtStr = rental['updated_at']; + debugPrint('DEBUG status: ${rental['status']}'); + debugPrint('DEBUG updated_at: $updatedAtStr'); + + if (updatedAtStr != null) { + try { + final updatedAt = DateTime.parse(updatedAtStr); + debugPrint( + 'DEBUG parsed updated_at: ${updatedAt.toIso8601String()}', + ); + return CountdownTimerWidget( + updatedAt: updatedAt, + ); + } catch (e) { + debugPrint('ERROR parsing updated_at: $e'); + return Text( + 'Format tanggal salah', + style: TextStyle( + color: Colors.red, + fontSize: 12, + ), + ); + } + } + return SizedBox.shrink(); + }, ), ], ), @@ -1856,32 +1882,46 @@ class CountdownTimerWidget extends StatefulWidget { class _CountdownTimerWidgetState extends State { late Duration remaining; Timer? timer; + @override void initState() { super.initState(); + debugPrint( + 'CountdownTimerWidget initialized with updatedAt: ${widget.updatedAt}', + ); updateRemaining(); timer = Timer.periodic( const Duration(seconds: 1), (_) => updateRemaining(), ); - print('DEBUG updated_at: ${widget.updatedAt}'); } void updateRemaining() { final now = DateTime.now(); final deadline = widget.updatedAt.add(const Duration(hours: 1)); + + debugPrint('Current time: $now'); + debugPrint('Deadline: $deadline'); + setState(() { remaining = deadline.difference(now); + debugPrint('Remaining time: ${remaining.inSeconds} seconds'); + if (remaining.isNegative) { + debugPrint('Countdown expired, setting to zero'); remaining = Duration.zero; timer?.cancel(); - widget.onTimeout?.call(); + if (widget.onTimeout != null) { + debugPrint('Calling onTimeout callback'); + widget.onTimeout?.call(); + } } }); } @override void dispose() { + debugPrint('CountdownTimerWidget disposed'); timer?.cancel(); super.dispose(); } @@ -1891,9 +1931,15 @@ class _CountdownTimerWidgetState extends State { if (remaining.inSeconds <= 0) { return Text('Waktu habis', style: TextStyle(color: Colors.red)); } + final h = remaining.inHours; final m = remaining.inMinutes % 60; final s = remaining.inSeconds % 60; + + final timeString = + '${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; + debugPrint('Rendering countdown: $timeString'); + return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( @@ -1907,7 +1953,7 @@ class _CountdownTimerWidgetState extends State { Icon(Icons.timer_outlined, size: 14, color: Colors.red), const SizedBox(width: 4), Text( - 'Bayar dalam ${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}', + 'Bayar dalam $timeString', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, diff --git a/lib/app/services/navigation_service.dart b/lib/app/services/navigation_service.dart index e1113e6..d4d9185 100644 --- a/lib/app/services/navigation_service.dart +++ b/lib/app/services/navigation_service.dart @@ -41,7 +41,7 @@ class NavigationService extends GetxService { Get.snackbar( 'Error', 'ID aset tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -63,7 +63,7 @@ class NavigationService extends GetxService { Get.snackbar( 'Error', 'ID sewa tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -87,7 +87,7 @@ class NavigationService extends GetxService { Get.snackbar( 'Error', 'ID paket tidak valid', - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, backgroundColor: Colors.red, colorText: Colors.white, ); @@ -133,7 +133,7 @@ class NavigationService extends GetxService { void toProfile() { debugPrint('🧭 Navigating to Profile'); setNavIndex(2); // Profile tab - Get.offNamed(Routes.PROFILE); + Get.offAllNamed(Routes.PROFILE); } /// Navigasi ke dashboard sesuai role diff --git a/lib/main.dart b/lib/main.dart index a68e259..24c081c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -48,10 +48,6 @@ void main() async { debugPrint('❌ Error initializing auth provider: $e'); } - // Pre-register the dashboard controller to fix dependency issues - Get.put(PetugasBumdesDashboardController(), permanent: true); - debugPrint('✅ PetugasBumdesDashboardController initialized globally'); - // Register services yang akan digunakan secara global ServiceManager.registerServices(); diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f..96d3fee 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" + "info": { + "version": 1, + "author": "xcode" }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} + "images": [ + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_16.png", + "scale": "1x" + }, + { + "size": "16x16", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "2x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_32.png", + "scale": "1x" + }, + { + "size": "32x32", + "idiom": "mac", + "filename": "app_icon_64.png", + "scale": "2x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_128.png", + "scale": "1x" + }, + { + "size": "128x128", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "2x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_256.png", + "scale": "1x" + }, + { + "size": "256x256", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "2x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_512.png", + "scale": "1x" + }, + { + "size": "512x512", + "idiom": "mac", + "filename": "app_icon_1024.png", + "scale": "2x" + } + ] +} \ No newline at end of file diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 82b6f9d..cec18ab 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 13b35eb..bb79e70 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 0a3f5fa..fbfce02 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bdb5722..dbddff7 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index f083318..4312f99 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 326c0e7..a7b7093 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 2f1632c..dea77f3 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/pubspec.lock b/pubspec.lock index 0270988..9fb26b9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -97,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -270,6 +294,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.5" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -509,6 +541,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" jwt_decode: dependency: transitive description: @@ -1146,6 +1186,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" yet_another_json_isolate: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8f5d835..539b3a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,28 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^5.0.0 + # App icon generation tool + flutter_launcher_icons: ^0.13.1 + +# App icon configuration +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/images/logo_app.png" + min_sdk_android: 21 # android min sdk min:16, default 21 + web: + generate: true + image_path: "assets/images/logo_app.png" + background_color: "#ffffff" + theme_color: "#ffffff" + windows: + generate: true + image_path: "assets/images/logo_app.png" + icon_size: 48 # min:48, max:256, default: 48 + macos: + generate: true + image_path: "assets/images/logo_app.png" + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..fbfce02 100644 Binary files a/web/favicon.png and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..9611409 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..a7b7093 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d7..9611409 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c566..a7b7093 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ diff --git a/web/manifest.json b/web/manifest.json index 478b4ef..d0ed8e4 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -3,8 +3,8 @@ "short_name": "bumrent_app", "start_url": ".", "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", + "background_color": "#ffffff", + "theme_color": "#ffffff", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, @@ -32,4 +32,4 @@ "purpose": "maskable" } ] -} +} \ No newline at end of file diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..e90a7c3 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ