kelola penyewa dan beberapa error fix
@ -1,8 +1,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application
|
<application
|
||||||
android:label="bumrent_app"
|
android:label="BumRent"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/launcher_icon">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
BIN
android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/logo_app.png
Normal file
After Width: | Height: | Size: 70 KiB |
@ -427,7 +427,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
@ -484,7 +484,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 610 B |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 988 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 11 KiB |
@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Bumrent App</string>
|
<string>BumRent</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>bumrent_app</string>
|
<string>BumRent</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
@ -19,12 +19,12 @@ class PetugasBumdesBinding extends Bindings {
|
|||||||
print('Error removing controller: $e');
|
print('Error removing controller: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gunakan put untuk memastikan controller selalu tersedia dan permanent
|
// Gunakan lazyPut untuk memastikan controller hanya diinisialisasi saat dibutuhkan
|
||||||
Get.put<PetugasBumdesDashboardController>(
|
Get.lazyPut<PetugasBumdesDashboardController>(
|
||||||
PetugasBumdesDashboardController(),
|
() => PetugasBumdesDashboardController(),
|
||||||
permanent: true,
|
fenix: true, // Akan dibuat ulang jika dihapus
|
||||||
);
|
);
|
||||||
|
|
||||||
print('✅ PetugasBumdesDashboardController registered successfully');
|
print('✅ PetugasBumdesDashboardController initialized successfully');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,15 @@ import '../modules/warga/controllers/warga_dashboard_controller.dart';
|
|||||||
class WargaBinding extends Bindings {
|
class WargaBinding extends Bindings {
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
Get.lazyPut<AuthProvider>(() => AuthProvider());
|
// Pastikan AuthProvider teregistrasi
|
||||||
Get.lazyPut<WargaDashboardController>(() => WargaDashboardController());
|
if (!Get.isRegistered<AuthProvider>()) {
|
||||||
|
Get.put(AuthProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gunakan lazyPut untuk memastikan controller hanya diinisialisasi saat dibutuhkan
|
||||||
|
Get.lazyPut<WargaDashboardController>(
|
||||||
|
() => WargaDashboardController(),
|
||||||
|
fenix: true, // Akan dibuat ulang jika dihapus
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,18 @@ class AuthProvider extends GetxService {
|
|||||||
// Clear any cached user data or state
|
// Clear any cached user data or state
|
||||||
// This method is called during logout to ensure all user-related data is cleared
|
// This method is called during logout to ensure all user-related data is cleared
|
||||||
debugPrint('Clearing AuthProvider cached data');
|
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;
|
User? get currentUser => client.auth.currentUser;
|
||||||
@ -669,6 +679,25 @@ class AuthProvider extends GetxService {
|
|||||||
item,
|
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 aset_id is null and paket_id is not null, fetch package data
|
||||||
if (item['aset_id'] == null && item['paket_id'] != null) {
|
if (item['aset_id'] == null && item['paket_id'] != null) {
|
||||||
final String paketId = item['paket_id'];
|
final String paketId = item['paket_id'];
|
||||||
|
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import '../../../data/providers/auth_provider.dart';
|
import '../../../data/providers/auth_provider.dart';
|
||||||
import '../../../routes/app_routes.dart';
|
import '../../../routes/app_routes.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import '../../../modules/warga/controllers/warga_dashboard_controller.dart';
|
||||||
|
|
||||||
class AuthController extends GetxController {
|
class AuthController extends GetxController {
|
||||||
final AuthProvider _authProvider = Get.find<AuthProvider>();
|
final AuthProvider _authProvider = Get.find<AuthProvider>();
|
||||||
@ -185,7 +186,9 @@ class AuthController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToWargaDashboard() {
|
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 {
|
void forgotPassword() async {
|
||||||
@ -215,7 +218,7 @@ class AuthController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Link reset password telah dikirim ke email Anda',
|
'Link reset password telah dikirim ke email Anda',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green[100],
|
backgroundColor: Colors.green[100],
|
||||||
colorText: Colors.green[800],
|
colorText: Colors.green[800],
|
||||||
icon: const Icon(Icons.check_circle, color: Colors.green),
|
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
|
'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(
|
Get.offNamed(
|
||||||
Routes.REGISTRATION_SUCCESS,
|
Routes.REGISTRATION_SUCCESS,
|
||||||
arguments: {'register_id': registerId},
|
arguments: {'register_id': registerId},
|
||||||
@ -478,4 +492,99 @@ class AuthController extends GetxController {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check registration status by register_id and email
|
||||||
|
Future<Map<String, dynamic>?> 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<String, dynamic> && 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<WargaDashboardController>()) {
|
||||||
|
print(
|
||||||
|
'Removing WargaDashboardController to ensure clean state after registration',
|
||||||
|
);
|
||||||
|
Get.delete<WargaDashboardController>(force: true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error removing WargaDashboardController: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ class LoginView extends GetView<AuthController> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: false,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// Background gradient
|
// Background gradient
|
||||||
@ -77,9 +77,6 @@ class LoginView extends GetView<AuthController> {
|
|||||||
SafeArea(
|
SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
||||||
),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../theme/app_colors.dart';
|
import '../../../theme/app_colors.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import '../../../routes/app_routes.dart';
|
||||||
|
|
||||||
class RegistrationSuccessView extends StatefulWidget {
|
class RegistrationSuccessView extends StatefulWidget {
|
||||||
const RegistrationSuccessView({Key? key}) : super(key: key);
|
const RegistrationSuccessView({Key? key}) : super(key: key);
|
||||||
@ -284,7 +285,7 @@ class _RegistrationSuccessViewState extends State<RegistrationSuccessView>
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil Disalin',
|
'Berhasil Disalin',
|
||||||
'Kode registrasi telah disalin ke clipboard',
|
'Kode registrasi telah disalin ke clipboard',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: AppColors.successLight,
|
backgroundColor: AppColors.successLight,
|
||||||
colorText: AppColors.success,
|
colorText: AppColors.success,
|
||||||
margin: const EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
@ -329,7 +330,7 @@ class _RegistrationSuccessViewState extends State<RegistrationSuccessView>
|
|||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Navigate back to login page
|
// Navigate back to login page
|
||||||
Get.offAllNamed('/login');
|
Get.offNamed(Routes.LOGIN);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primary,
|
backgroundColor: AppColors.primary,
|
||||||
|
@ -2,100 +2,108 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../controllers/auth_controller.dart';
|
import '../controllers/auth_controller.dart';
|
||||||
import '../../../theme/app_colors.dart';
|
import '../../../theme/app_colors.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class RegistrationView extends GetView<AuthController> {
|
class RegistrationView extends GetView<AuthController> {
|
||||||
const RegistrationView({Key? key}) : super(key: key);
|
const RegistrationView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return WillPopScope(
|
||||||
body: Stack(
|
onWillPop: () async {
|
||||||
children: [
|
// Reset all registration fields when leaving the page
|
||||||
// Background gradient - same as login page
|
controller.resetRegistrationFields();
|
||||||
Container(
|
return true;
|
||||||
decoration: BoxDecoration(
|
},
|
||||||
gradient: LinearGradient(
|
child: Scaffold(
|
||||||
begin: Alignment.topRight,
|
body: Stack(
|
||||||
end: Alignment.bottomLeft,
|
children: [
|
||||||
colors: [
|
// Background gradient - same as login page
|
||||||
AppColors.primaryLight.withOpacity(0.1),
|
Container(
|
||||||
AppColors.background,
|
|
||||||
AppColors.accentLight.withOpacity(0.1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Pattern overlay - same as login page
|
|
||||||
Opacity(
|
|
||||||
opacity: 0.03,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue[50], // Temporary solid color
|
gradient: LinearGradient(
|
||||||
),
|
begin: Alignment.topRight,
|
||||||
),
|
end: Alignment.bottomLeft,
|
||||||
),
|
|
||||||
|
|
||||||
// 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: [
|
colors: [
|
||||||
AppColors.primary.withOpacity(0.2),
|
AppColors.primaryLight.withOpacity(0.1),
|
||||||
Colors.transparent,
|
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
|
// Pattern overlay - same as login page
|
||||||
SafeArea(
|
Opacity(
|
||||||
child: SingleChildScrollView(
|
opacity: 0.03,
|
||||||
physics: const BouncingScrollPhysics(),
|
child: Container(
|
||||||
child: Padding(
|
decoration: BoxDecoration(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
color: Colors.blue[50], // Temporary solid color
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
// 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<AuthController> {
|
|||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => Get.back(),
|
onTap: () {
|
||||||
|
// Reset all registration fields when going back
|
||||||
|
controller.resetRegistrationFields();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
@ -644,7 +656,11 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
style: TextStyle(color: AppColors.textSecondary),
|
style: TextStyle(color: AppColors.textSecondary),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () {
|
||||||
|
// Reset all registration fields when going back to login
|
||||||
|
controller.resetRegistrationFields();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
),
|
),
|
||||||
@ -694,7 +710,8 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
// Show dialog to check registration status
|
// Show dialog to check registration status
|
||||||
void _showCheckStatusDialog(BuildContext context) {
|
void _showCheckStatusDialog(BuildContext context) {
|
||||||
final TextEditingController codeController = TextEditingController();
|
final TextEditingController codeController = TextEditingController();
|
||||||
final TextEditingController identifierController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
final authController = Get.find<AuthController>();
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -788,9 +805,9 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Email/NIK/Phone field
|
// Email field
|
||||||
Text(
|
Text(
|
||||||
'Email/NIK/No HP',
|
'Email',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColors.textPrimary,
|
color: AppColors.textPrimary,
|
||||||
@ -799,12 +816,13 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextField(
|
TextField(
|
||||||
controller: identifierController,
|
controller: emailController,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan email, NIK, atau no HP',
|
hintText: 'Masukkan email yang terdaftar',
|
||||||
hintStyle: TextStyle(color: AppColors.textLight),
|
hintStyle: TextStyle(color: AppColors.textLight),
|
||||||
prefixIcon: Icon(
|
prefixIcon: Icon(
|
||||||
Icons.person_outline,
|
Icons.email_outlined,
|
||||||
color: AppColors.iconGrey,
|
color: AppColors.iconGrey,
|
||||||
size: 22,
|
size: 22,
|
||||||
),
|
),
|
||||||
@ -835,20 +853,99 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 48,
|
height: 48,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO: Implement check status functionality
|
// 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);
|
Navigator.pop(context);
|
||||||
|
|
||||||
// Show a mock response for now
|
// Tampilkan loading indicator
|
||||||
Get.snackbar(
|
Get.dialog(
|
||||||
'Status Pendaftaran',
|
const Center(child: CircularProgressIndicator()),
|
||||||
'Status pendaftaran sedang diproses. Silakan tunggu konfirmasi lebih lanjut.',
|
barrierDismissible: false,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
backgroundColor: AppColors.infoLight,
|
|
||||||
colorText: AppColors.info,
|
|
||||||
icon: Icon(Icons.info_outline, color: AppColors.info),
|
|
||||||
duration: const Duration(seconds: 4),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.accent,
|
backgroundColor: AppColors.accent,
|
||||||
@ -875,6 +972,219 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show dialog with registration status details
|
||||||
|
void _showRegistrationStatusDialog(Map<String, dynamic> 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
|
// New method to build date picker field
|
||||||
Widget _buildDateField(BuildContext context) {
|
Widget _buildDateField(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@ -944,4 +1254,24 @@ class RegistrationView extends GetView<AuthController> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class ListPetugasMitraController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Petugas mitra berhasil ditambahkan',
|
'Petugas mitra berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class ListPetugasMitraController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Data petugas mitra berhasil diperbarui',
|
'Data petugas mitra berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ class ListPetugasMitraController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Petugas mitra berhasil dihapus',
|
'Petugas mitra berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ class ListPetugasMitraController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Status Diperbarui',
|
'Status Diperbarui',
|
||||||
'Status petugas mitra diubah menjadi ${!currentStatus ? 'Aktif' : 'Nonaktif'}',
|
'Status petugas mitra diubah menjadi ${!currentStatus ? 'Aktif' : 'Nonaktif'}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ class PetugasAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error Memuat Data',
|
'Error Memuat Data',
|
||||||
'Gagal mengambil data aset dari server. Silakan coba lagi nanti.',
|
'Gagal mengambil data aset dari server. Silakan coba lagi nanti.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -258,7 +258,7 @@ class PetugasAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat menghapus aset',
|
'Terjadi kesalahan saat menghapus aset',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -274,7 +274,7 @@ class PetugasAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menghapus aset: ${e.toString()}',
|
'Gagal menghapus aset: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -66,7 +66,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data. Silakan coba lagi.',
|
'Gagal memuat data. Silakan coba lagi.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
@ -92,7 +92,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Rekening Utama',
|
'Rekening Utama',
|
||||||
'Rekening ${account['bank_name']} telah dijadikan 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(
|
Get.snackbar(
|
||||||
'Rekening Ditambahkan',
|
'Rekening Ditambahkan',
|
||||||
'Rekening bank baru telah berhasil ditambahkan',
|
'Rekening bank baru telah berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Rekening Diperbarui',
|
'Rekening Diperbarui',
|
||||||
'Informasi rekening bank telah berhasil diperbarui',
|
'Informasi rekening bank telah berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +138,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Tidak Dapat Menghapus',
|
'Tidak Dapat Menghapus',
|
||||||
'Rekening utama tidak dapat dihapus. Silakan atur rekening lain sebagai utama terlebih dahulu.',
|
'Rekening utama tidak dapat dihapus. Silakan atur rekening lain sebagai utama terlebih dahulu.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -148,7 +148,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Rekening Dihapus',
|
'Rekening Dihapus',
|
||||||
'Rekening bank telah berhasil dihapus',
|
'Rekening bank telah berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +164,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Status Diperbarui',
|
'Status Diperbarui',
|
||||||
'Status mitra telah diubah menjadi ${partner['is_active'] ? 'Aktif' : 'Tidak Aktif'}',
|
'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(
|
Get.snackbar(
|
||||||
'Mitra Ditambahkan',
|
'Mitra Ditambahkan',
|
||||||
'Mitra baru telah berhasil ditambahkan',
|
'Mitra baru telah berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Mitra Diperbarui',
|
'Mitra Diperbarui',
|
||||||
'Informasi mitra telah berhasil diperbarui',
|
'Informasi mitra telah berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,7 +210,7 @@ class PetugasBumdesCbpController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Mitra Dihapus',
|
'Mitra Dihapus',
|
||||||
'Mitra telah berhasil dihapus',
|
'Mitra telah berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../data/providers/auth_provider.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import '../../../routes/app_routes.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/sewa_service.dart';
|
||||||
import '../../../services/service_manager.dart';
|
import '../../../services/service_manager.dart';
|
||||||
import '../../../data/models/pembayaran_model.dart';
|
import '../../../data/models/pembayaran_model.dart';
|
||||||
import '../../../services/pembayaran_service.dart';
|
import '../../../services/pembayaran_service.dart';
|
||||||
import '../../../data/providers/aset_provider.dart';
|
|
||||||
import '../../../data/providers/pesanan_provider.dart';
|
|
||||||
|
|
||||||
class PetugasBumdesDashboardController extends GetxController {
|
class PetugasBumdesDashboardController extends GetxController {
|
||||||
AuthProvider? _authProvider;
|
AuthProvider? _authProvider;
|
||||||
@ -297,37 +299,56 @@ class PetugasBumdesDashboardController extends GetxController {
|
|||||||
|
|
||||||
void logout() async {
|
void logout() async {
|
||||||
try {
|
try {
|
||||||
// Clear providers data
|
// Store login route for navigation
|
||||||
|
final loginRoute = Routes.LOGIN;
|
||||||
|
|
||||||
|
// Sign out from Supabase
|
||||||
if (_authProvider != null) {
|
if (_authProvider != null) {
|
||||||
// Sign out from Supabase
|
|
||||||
await _authProvider!.signOut();
|
await _authProvider!.signOut();
|
||||||
|
|
||||||
// Clear auth provider data
|
|
||||||
_authProvider!.clearAuthData();
|
|
||||||
|
|
||||||
// Clear aset provider data
|
|
||||||
try {
|
|
||||||
final asetProvider = Get.find<AsetProvider>();
|
|
||||||
asetProvider.clearCache();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error clearing AsetProvider: $e');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear pesanan provider data
|
|
||||||
try {
|
|
||||||
final pesananProvider = Get.find<PesananProvider>();
|
|
||||||
pesananProvider.clearCache();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error clearing PesananProvider: $e');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to login screen
|
// Navigate to login screen while context is still valid
|
||||||
Get.offAllNamed(Routes.LOGIN);
|
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) {
|
} catch (e) {
|
||||||
print('Error during logout: $e');
|
print('Error during logout: $e');
|
||||||
// Still try to navigate to login even if sign out fails
|
// Still try to navigate to login even if sign out fails
|
||||||
Get.offAllNamed(Routes.LOGIN);
|
Get.offAllNamed(Routes.LOGIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to clear provider caches that need explicit clearing
|
||||||
|
void _clearProviderCaches() {
|
||||||
|
try {
|
||||||
|
// Clear AsetProvider cache
|
||||||
|
if (Get.isRegistered<AsetProvider>()) {
|
||||||
|
final asetProvider = Get.find<AsetProvider>();
|
||||||
|
asetProvider.clearCache();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error clearing AsetProvider: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Clear PesananProvider cache
|
||||||
|
if (Get.isRegistered<PesananProvider>()) {
|
||||||
|
final pesananProvider = Get.find<PesananProvider>();
|
||||||
|
pesananProvider.clearCache();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error clearing PesananProvider: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other providers here that need explicit cache clearing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class PetugasDetailPenyewaController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Data penyewa tidak ditemukan',
|
'Data penyewa tidak ditemukan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ class PetugasDetailPenyewaController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data penyewa',
|
'Gagal memuat data penyewa',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
@ -203,14 +203,14 @@ class PetugasDetailPenyewaController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Status penyewa berhasil diperbarui',
|
'Status penyewa berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error updating penyewa status: $e');
|
print('Error updating penyewa status: $e');
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui status penyewa',
|
'Gagal memperbarui status penyewa',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
@ -184,7 +184,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Gagal membuat laporan: $e',
|
'Gagal membuat laporan: $e',
|
||||||
backgroundColor: Colors.red[100],
|
backgroundColor: Colors.red[100],
|
||||||
colorText: Colors.red[900],
|
colorText: Colors.red[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
@ -1641,7 +1641,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Harap generate laporan terlebih dahulu',
|
'Harap generate laporan terlebih dahulu',
|
||||||
backgroundColor: Colors.amber[100],
|
backgroundColor: Colors.amber[100],
|
||||||
colorText: Colors.amber[900],
|
colorText: Colors.amber[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1654,7 +1654,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Harap generate laporan terlebih dahulu',
|
'Harap generate laporan terlebih dahulu',
|
||||||
backgroundColor: Colors.amber[100],
|
backgroundColor: Colors.amber[100],
|
||||||
colorText: Colors.amber[900],
|
colorText: Colors.amber[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1687,7 +1687,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Laporan disimpan di ${file.path}',
|
'Laporan disimpan di ${file.path}',
|
||||||
backgroundColor: Colors.green[100],
|
backgroundColor: Colors.green[100],
|
||||||
colorText: Colors.green[900],
|
colorText: Colors.green[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 5),
|
duration: const Duration(seconds: 5),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1701,7 +1701,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Gagal menyimpan laporan: $e',
|
'Gagal menyimpan laporan: $e',
|
||||||
backgroundColor: Colors.red[100],
|
backgroundColor: Colors.red[100],
|
||||||
colorText: Colors.red[900],
|
colorText: Colors.red[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1714,7 +1714,7 @@ class PetugasLaporanController extends GetxController {
|
|||||||
'Harap generate laporan terlebih dahulu',
|
'Harap generate laporan terlebih dahulu',
|
||||||
backgroundColor: Colors.amber[100],
|
backgroundColor: Colors.amber[100],
|
||||||
colorText: Colors.amber[900],
|
colorText: Colors.amber[900],
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Rekening utama berhasil diubah',
|
'Rekening utama berhasil diubah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Status mitra berhasil diubah',
|
'Status mitra berhasil diubah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Rekening bank berhasil ditambahkan',
|
'Rekening bank berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Rekening bank berhasil diperbarui',
|
'Rekening bank berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Rekening bank berhasil dihapus',
|
'Rekening bank berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Mitra berhasil ditambahkan',
|
'Mitra berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Mitra berhasil diperbarui',
|
'Mitra berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ class PetugasManajemenBumdesController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Mitra berhasil dihapus',
|
'Mitra berhasil dihapus',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data paket. Silakan coba lagi.',
|
'Gagal memuat data paket. Silakan coba lagi.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -320,7 +320,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Paket baru berhasil ditambahkan',
|
'Paket baru berhasil ditambahkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -334,7 +334,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menambahkan paket. Silakan coba lagi.',
|
'Gagal menambahkan paket. Silakan coba lagi.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -373,7 +373,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Paket berhasil diperbarui',
|
'Paket berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -388,7 +388,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui paket. Silakan coba lagi.',
|
'Gagal memperbarui paket. Silakan coba lagi.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -440,7 +440,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat menghapus paket',
|
'Terjadi kesalahan saat menghapus paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
@ -462,7 +462,7 @@ class PetugasPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal menghapus paket: ${e.toString()}',
|
'Gagal menghapus paket: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
|
@ -6,6 +6,7 @@ class PetugasPenyewaController extends GetxController {
|
|||||||
|
|
||||||
// Reactive variables
|
// Reactive variables
|
||||||
final isLoading = true.obs;
|
final isLoading = true.obs;
|
||||||
|
final isRefreshing = false.obs;
|
||||||
final penyewaList = <Map<String, dynamic>>[].obs;
|
final penyewaList = <Map<String, dynamic>>[].obs;
|
||||||
final filteredPenyewaList = <Map<String, dynamic>>[].obs;
|
final filteredPenyewaList = <Map<String, dynamic>>[].obs;
|
||||||
final filterStatus = 'all'.obs;
|
final filterStatus = 'all'.obs;
|
||||||
@ -30,6 +31,39 @@ class PetugasPenyewaController extends GetxController {
|
|||||||
fetchPenyewaList();
|
fetchPenyewaList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method khusus untuk pull-to-refresh yang tidak menampilkan loading spinner penuh layar
|
||||||
|
Future<void> 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<dynamic>;
|
||||||
|
|
||||||
|
// 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) {
|
void changeTab(int index) {
|
||||||
currentTabIndex.value = index;
|
currentTabIndex.value = index;
|
||||||
applyFilters();
|
applyFilters();
|
||||||
@ -180,14 +214,14 @@ class PetugasPenyewaController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Status penyewa berhasil diperbarui',
|
'Status penyewa berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error updating penyewa status: $e');
|
print('Error updating penyewa status: $e');
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat memperbarui status penyewa',
|
'Terjadi kesalahan saat memperbarui status penyewa',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
@ -4,6 +4,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:bumrent_app/app/data/models/aset_model.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/data/providers/aset_provider.dart';
|
||||||
|
import 'package:bumrent_app/app/routes/app_routes.dart';
|
||||||
|
|
||||||
class PetugasTambahAsetController extends GetxController {
|
class PetugasTambahAsetController extends GetxController {
|
||||||
// Flag to check if in edit mode
|
// Flag to check if in edit mode
|
||||||
@ -13,34 +14,38 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
Future<void> onInit() async {
|
Future<void> onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle edit mode and load data if needed
|
// Handle edit mode and load data if needed
|
||||||
final args = Get.arguments;
|
final args = Get.arguments;
|
||||||
debugPrint('[DEBUG] PetugasTambahAsetController initialized with args: $args');
|
debugPrint(
|
||||||
|
'[DEBUG] PetugasTambahAsetController initialized with args: $args',
|
||||||
|
);
|
||||||
|
|
||||||
if (args != null && args is Map<String, dynamic>) {
|
if (args != null && args is Map<String, dynamic>) {
|
||||||
isEditing.value = args['isEditing'] ?? false;
|
isEditing.value = args['isEditing'] ?? false;
|
||||||
debugPrint('[DEBUG] isEditing set to: ${isEditing.value}');
|
debugPrint('[DEBUG] isEditing set to: ${isEditing.value}');
|
||||||
|
|
||||||
if (isEditing.value) {
|
if (isEditing.value) {
|
||||||
// Get asset ID from arguments
|
// Get asset ID from arguments
|
||||||
final assetId = args['assetId']?.toString() ?? '';
|
final assetId = args['assetId']?.toString() ?? '';
|
||||||
debugPrint('[DEBUG] Edit mode: Loading asset with ID: $assetId');
|
debugPrint('[DEBUG] Edit mode: Loading asset with ID: $assetId');
|
||||||
|
|
||||||
if (assetId.isNotEmpty) {
|
if (assetId.isNotEmpty) {
|
||||||
// Store the asset ID and load asset data
|
// Store the asset ID and load asset data
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
debugPrint('[DEBUG] Asset ID set to: $assetId');
|
debugPrint('[DEBUG] Asset ID set to: $assetId');
|
||||||
|
|
||||||
// Load asset data and await completion
|
// Load asset data and await completion
|
||||||
await _loadAssetData(assetId);
|
await _loadAssetData(assetId);
|
||||||
} else {
|
} else {
|
||||||
debugPrint('[ERROR] Edit mode but no assetId provided in arguments');
|
debugPrint(
|
||||||
|
'[ERROR] Edit mode but no assetId provided in arguments',
|
||||||
|
);
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID Aset tidak ditemukan',
|
'ID Aset tidak ditemukan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
// Optionally navigate back if in edit mode without an ID
|
// Optionally navigate back if in edit mode without an ID
|
||||||
Future.delayed(Duration.zero, () => Get.back());
|
Future.delayed(Duration.zero, () => Get.back());
|
||||||
@ -53,7 +58,9 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default values for new asset when no arguments are passed
|
// 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';
|
quantityController.text = '1';
|
||||||
unitOfMeasureController.text = 'Unit';
|
unitOfMeasureController.text = 'Unit';
|
||||||
}
|
}
|
||||||
@ -62,11 +69,11 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
debugPrint('Stack trace: $stackTrace');
|
debugPrint('Stack trace: $stackTrace');
|
||||||
// Ensure loading is set to false even if there's an error
|
// Ensure loading is set to false even if there's an error
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan saat memuat data',
|
'Terjadi kesalahan saat memuat data',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,21 +92,21 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
debugPrint('[DEBUG] Fetching asset data for ID: $assetId');
|
debugPrint('[DEBUG] Fetching asset data for ID: $assetId');
|
||||||
|
|
||||||
// Fetch asset data from Supabase
|
// Fetch asset data from Supabase
|
||||||
final aset = await _asetProvider.getAsetById(assetId);
|
final aset = await _asetProvider.getAsetById(assetId);
|
||||||
|
|
||||||
if (aset == null) {
|
if (aset == null) {
|
||||||
throw Exception('Aset tidak ditemukan');
|
throw Exception('Aset tidak ditemukan');
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint('[DEBUG] Successfully fetched asset data: ${aset.toJson()}');
|
debugPrint('[DEBUG] Successfully fetched asset data: ${aset.toJson()}');
|
||||||
|
|
||||||
// Populate form fields with the fetched data
|
// Populate form fields with the fetched data
|
||||||
nameController.text = aset.nama ?? '';
|
nameController.text = aset.nama ?? '';
|
||||||
descriptionController.text = aset.deskripsi ?? '';
|
descriptionController.text = aset.deskripsi ?? '';
|
||||||
quantityController.text = (aset.kuantitas ?? 1).toString();
|
quantityController.text = (aset.kuantitas ?? 1).toString();
|
||||||
|
|
||||||
// Ensure the status matches one of the available options exactly
|
// Ensure the status matches one of the available options exactly
|
||||||
final status = aset.status?.toLowerCase() ?? 'tersedia';
|
final status = aset.status?.toLowerCase() ?? 'tersedia';
|
||||||
if (status == 'tersedia') {
|
if (status == 'tersedia') {
|
||||||
@ -110,18 +117,19 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
// Default to 'Tersedia' if status is not recognized
|
// Default to 'Tersedia' if status is not recognized
|
||||||
selectedStatus.value = 'Tersedia';
|
selectedStatus.value = 'Tersedia';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle time options and pricing
|
// Handle time options and pricing
|
||||||
if (aset.satuanWaktuSewa != null && aset.satuanWaktuSewa!.isNotEmpty) {
|
if (aset.satuanWaktuSewa != null && aset.satuanWaktuSewa!.isNotEmpty) {
|
||||||
// Reset time options
|
// Reset time options
|
||||||
timeOptions.forEach((key, value) => value.value = false);
|
timeOptions.forEach((key, value) => value.value = false);
|
||||||
|
|
||||||
// Process each satuan waktu sewa
|
// Process each satuan waktu sewa
|
||||||
for (var sws in aset.satuanWaktuSewa) {
|
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 harga = sws['harga'] as int? ?? 0;
|
||||||
final maksimalWaktu = sws['maksimal_waktu'] as int? ?? 24;
|
final maksimalWaktu = sws['maksimal_waktu'] as int? ?? 24;
|
||||||
|
|
||||||
if (satuan.contains('jam')) {
|
if (satuan.contains('jam')) {
|
||||||
timeOptions['Per Jam']?.value = true;
|
timeOptions['Per Jam']?.value = true;
|
||||||
pricePerHourController.text = harga.toString();
|
pricePerHourController.text = harga.toString();
|
||||||
@ -133,19 +141,21 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing images
|
// Clear existing images
|
||||||
selectedImages.clear();
|
selectedImages.clear();
|
||||||
networkImageUrls.clear();
|
networkImageUrls.clear();
|
||||||
|
|
||||||
// Get all image URLs from the model
|
// Get all image URLs from the model
|
||||||
final allImageUrls = aset.imageUrls.toList();
|
final allImageUrls = aset.imageUrls.toList();
|
||||||
|
|
||||||
// If no imageUrls but has imageUrl, use that as fallback (backward compatibility)
|
// 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!);
|
allImageUrls.add(aset.imageUrl!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all images to the lists
|
// Add all images to the lists
|
||||||
for (final imageUrl in allImageUrls) {
|
for (final imageUrl in allImageUrls) {
|
||||||
if (imageUrl != null && imageUrl.isNotEmpty) {
|
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');
|
debugPrint('[DEBUG] Successfully loaded asset data for ID: $assetId');
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
debugPrint('[ERROR] Failed to load asset data: $e');
|
debugPrint('[ERROR] Failed to load asset data: $e');
|
||||||
debugPrint('Stack trace: $stackTrace');
|
debugPrint('Stack trace: $stackTrace');
|
||||||
|
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data aset: ${e.toString()}',
|
'Gagal memuat data aset: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Optionally navigate back if there's an error
|
// Optionally navigate back if there's an error
|
||||||
Future.delayed(const Duration(seconds: 2), () => Get.back());
|
Future.delayed(const Duration(seconds: 2), () => Get.back());
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form controllers
|
// Form controllers
|
||||||
final nameController = TextEditingController();
|
final nameController = TextEditingController();
|
||||||
final descriptionController = TextEditingController();
|
final descriptionController = TextEditingController();
|
||||||
@ -213,8 +226,6 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
final isFormValid = false.obs;
|
final isFormValid = false.obs;
|
||||||
final isSubmitting = false.obs;
|
final isSubmitting = false.obs;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
// Dispose controllers
|
// Dispose controllers
|
||||||
@ -255,11 +266,10 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
if (!anySelected) {
|
if (!anySelected) {
|
||||||
timeOptions[option]?.value = true;
|
timeOptions[option]?.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
validateForm();
|
validateForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create a new asset in Supabase
|
// Create a new asset in Supabase
|
||||||
Future<String?> _createAsset(
|
Future<String?> _createAsset(
|
||||||
Map<String, dynamic> assetData,
|
Map<String, dynamic> assetData,
|
||||||
@ -268,7 +278,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
// Create the asset in the 'aset' table
|
// Create the asset in the 'aset' table
|
||||||
final response = await _asetProvider.createAset(assetData);
|
final response = await _asetProvider.createAset(assetData);
|
||||||
|
|
||||||
if (response == null || response['id'] == null) {
|
if (response == null || response['id'] == null) {
|
||||||
debugPrint('❌ Failed to create asset: No response or ID from server');
|
debugPrint('❌ Failed to create asset: No response or ID from server');
|
||||||
return null;
|
return null;
|
||||||
@ -285,7 +295,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
harga: sws['harga'],
|
harga: sws['harga'],
|
||||||
maksimalWaktu: sws['maksimal_waktu'],
|
maksimalWaktu: sws['maksimal_waktu'],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
debugPrint('❌ Failed to add satuan waktu sewa: $sws');
|
debugPrint('❌ Failed to add satuan waktu sewa: $sws');
|
||||||
}
|
}
|
||||||
@ -307,12 +317,12 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
debugPrint('\n🔄 Starting update for asset ID: $assetId');
|
debugPrint('\n🔄 Starting update for asset ID: $assetId');
|
||||||
|
|
||||||
// 1. Extract and remove foto_aset from assetData as it's not in the aset table
|
// 1. Extract and remove foto_aset from assetData as it's not in the aset table
|
||||||
final fotoAsetUrl = assetData['foto_aset'];
|
final fotoAsetUrl = assetData['foto_aset'];
|
||||||
assetData.remove('foto_aset');
|
assetData.remove('foto_aset');
|
||||||
debugPrint('📝 Asset data prepared for update (without foto_aset)');
|
debugPrint('📝 Asset data prepared for update (without foto_aset)');
|
||||||
|
|
||||||
// 2. Update the main asset data (without foto_aset)
|
// 2. Update the main asset data (without foto_aset)
|
||||||
debugPrint('🔄 Updating main asset data...');
|
debugPrint('🔄 Updating main asset data...');
|
||||||
final success = await _asetProvider.updateAset(assetId, assetData);
|
final success = await _asetProvider.updateAset(assetId, assetData);
|
||||||
@ -326,7 +336,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
debugPrint('\n🔄 Updating rental time units...');
|
debugPrint('\n🔄 Updating rental time units...');
|
||||||
// First, delete existing satuan waktu sewa
|
// First, delete existing satuan waktu sewa
|
||||||
await _asetProvider.deleteSatuanWaktuSewaByAsetId(assetId);
|
await _asetProvider.deleteSatuanWaktuSewaByAsetId(assetId);
|
||||||
|
|
||||||
// Then add the new ones
|
// Then add the new ones
|
||||||
for (var sws in satuanWaktuSewa) {
|
for (var sws in satuanWaktuSewa) {
|
||||||
debugPrint(' - Adding: ${sws['satuan_waktu']} (${sws['harga']} IDR)');
|
debugPrint(' - Adding: ${sws['satuan_waktu']} (${sws['harga']} IDR)');
|
||||||
@ -346,21 +356,25 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
...networkImageUrls,
|
...networkImageUrls,
|
||||||
...selectedImages.map((file) => file.path),
|
...selectedImages.map((file) => file.path),
|
||||||
];
|
];
|
||||||
|
|
||||||
debugPrint('\n🖼️ Processing photos for asset $assetId');
|
debugPrint('\n🖼️ Processing photos for asset $assetId');
|
||||||
debugPrint(' - Network URLs: ${networkImageUrls.length}');
|
debugPrint(' - Network URLs: ${networkImageUrls.length}');
|
||||||
debugPrint(' - Local files: ${selectedImages.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 {
|
try {
|
||||||
// Use updateFotoAset which handles both uploading new photos and updating the database
|
// Use updateFotoAset which handles both uploading new photos and updating the database
|
||||||
final photoSuccess = await _asetProvider.updateFotoAset(
|
final photoSuccess = await _asetProvider.updateFotoAset(
|
||||||
asetId: assetId,
|
asetId: assetId,
|
||||||
fotoUrls: allImageUrls,
|
fotoUrls: allImageUrls,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!photoSuccess) {
|
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
|
// We don't fail the whole update if photo update fails
|
||||||
// as the main asset data has been saved successfully
|
// as the main asset data has been saved successfully
|
||||||
} else {
|
} else {
|
||||||
@ -377,7 +391,6 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
|
|
||||||
debugPrint('\n✅ Asset update completed successfully for ID: $assetId');
|
debugPrint('\n✅ Asset update completed successfully for ID: $assetId');
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
debugPrint('❌ Error updating asset: $e');
|
debugPrint('❌ Error updating asset: $e');
|
||||||
debugPrint('Stack trace: $stackTrace');
|
debugPrint('Stack trace: $stackTrace');
|
||||||
@ -445,30 +458,30 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
|
|
||||||
// Handle time options and pricing
|
// Handle time options and pricing
|
||||||
final List<Map<String, dynamic>> satuanWaktuSewa = [];
|
final List<Map<String, dynamic>> satuanWaktuSewa = [];
|
||||||
|
|
||||||
if (timeOptions['Per Jam']?.value == true) {
|
if (timeOptions['Per Jam']?.value == true) {
|
||||||
final hargaPerJam = int.tryParse(pricePerHourController.text) ?? 0;
|
final hargaPerJam = int.tryParse(pricePerHourController.text) ?? 0;
|
||||||
final maxJam = int.tryParse(maxHourController.text) ?? 24;
|
final maxJam = int.tryParse(maxHourController.text) ?? 24;
|
||||||
|
|
||||||
if (hargaPerJam <= 0) {
|
if (hargaPerJam <= 0) {
|
||||||
throw Exception('Harga per jam harus lebih dari 0');
|
throw Exception('Harga per jam harus lebih dari 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
satuanWaktuSewa.add({
|
satuanWaktuSewa.add({
|
||||||
'satuan_waktu': 'jam',
|
'satuan_waktu': 'jam',
|
||||||
'harga': hargaPerJam,
|
'harga': hargaPerJam,
|
||||||
'maksimal_waktu': maxJam,
|
'maksimal_waktu': maxJam,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeOptions['Per Hari']?.value == true) {
|
if (timeOptions['Per Hari']?.value == true) {
|
||||||
final hargaPerHari = int.tryParse(pricePerDayController.text) ?? 0;
|
final hargaPerHari = int.tryParse(pricePerDayController.text) ?? 0;
|
||||||
final maxHari = int.tryParse(maxDayController.text) ?? 30;
|
final maxHari = int.tryParse(maxDayController.text) ?? 30;
|
||||||
|
|
||||||
if (hargaPerHari <= 0) {
|
if (hargaPerHari <= 0) {
|
||||||
throw Exception('Harga per hari harus lebih dari 0');
|
throw Exception('Harga per hari harus lebih dari 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
satuanWaktuSewa.add({
|
satuanWaktuSewa.add({
|
||||||
'satuan_waktu': 'hari',
|
'satuan_waktu': 'hari',
|
||||||
'harga': hargaPerHari,
|
'harga': hargaPerHari,
|
||||||
@ -483,7 +496,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
|
|
||||||
// Handle image uploads
|
// Handle image uploads
|
||||||
List<String> imageUrls = [];
|
List<String> imageUrls = [];
|
||||||
|
|
||||||
if (networkImageUrls.isNotEmpty) {
|
if (networkImageUrls.isNotEmpty) {
|
||||||
// Use existing network URLs
|
// Use existing network URLs
|
||||||
imageUrls = List.from(networkImageUrls);
|
imageUrls = List.from(networkImageUrls);
|
||||||
@ -505,12 +518,12 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
// Create or update the asset
|
// Create or update the asset
|
||||||
bool success;
|
bool success;
|
||||||
String? createdAssetId;
|
String? createdAssetId;
|
||||||
|
|
||||||
if (isEditing.value && (assetId?.isNotEmpty ?? false)) {
|
if (isEditing.value && (assetId?.isNotEmpty ?? false)) {
|
||||||
// Update existing asset
|
// Update existing asset
|
||||||
debugPrint('🔄 Updating asset with ID: $assetId');
|
debugPrint('🔄 Updating asset with ID: $assetId');
|
||||||
success = await _updateAsset(assetId!, assetData, satuanWaktuSewa);
|
success = await _updateAsset(assetId!, assetData, satuanWaktuSewa);
|
||||||
|
|
||||||
// Update all photos if we have any
|
// Update all photos if we have any
|
||||||
if (success && imageUrls.isNotEmpty) {
|
if (success && imageUrls.isNotEmpty) {
|
||||||
await _asetProvider.updateFotoAset(
|
await _asetProvider.updateFotoAset(
|
||||||
@ -523,7 +536,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
debugPrint('🔄 Creating new asset');
|
debugPrint('🔄 Creating new asset');
|
||||||
createdAssetId = await _createAsset(assetData, satuanWaktuSewa);
|
createdAssetId = await _createAsset(assetData, satuanWaktuSewa);
|
||||||
success = createdAssetId != null;
|
success = createdAssetId != null;
|
||||||
|
|
||||||
// Add all photos for new asset
|
// Add all photos for new asset
|
||||||
if (success && createdAssetId != null && imageUrls.isNotEmpty) {
|
if (success && createdAssetId != null && imageUrls.isNotEmpty) {
|
||||||
await _asetProvider.updateFotoAset(
|
await _asetProvider.updateFotoAset(
|
||||||
@ -537,16 +550,18 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
// Show success message
|
// Show success message
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
isEditing.value ? 'Aset berhasil diperbarui' : 'Aset berhasil ditambahkan',
|
isEditing.value
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
? 'Aset berhasil diperbarui'
|
||||||
|
: 'Aset berhasil ditambahkan',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate back with success after a short delay
|
// Navigate back with success result instead of using offNamed
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
Get.back(result: true);
|
Get.offNamed(Routes.PETUGAS_ASET);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Gagal menyimpan aset');
|
throw Exception('Gagal menyimpan aset');
|
||||||
}
|
}
|
||||||
@ -557,14 +572,13 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
'Terjadi kesalahan: ${e.toString()}',
|
'Terjadi kesalahan: ${e.toString()}',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Example method to upload images (to be implemented with your backend)
|
// Example method to upload images (to be implemented with your backend)
|
||||||
// Future<List<String>> _uploadImages(List<XFile> images) async {
|
// Future<List<String>> _uploadImages(List<XFile> images) async {
|
||||||
// List<String> urls = [];
|
// List<String> urls = [];
|
||||||
@ -593,7 +607,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil gambar dari kamera: $e',
|
'Gagal mengambil gambar dari kamera: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -615,7 +629,7 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memilih gambar dari galeri: $e',
|
'Gagal memilih gambar dari galeri: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -625,7 +639,9 @@ class PetugasTambahAsetController extends GetxController {
|
|||||||
// For demonstration purposes: add sample image
|
// For demonstration purposes: add sample image
|
||||||
void addSampleImage() {
|
void addSampleImage() {
|
||||||
// In a real app, this would open the image picker
|
// 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();
|
validateForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'package:bumrent_app/app/data/models/paket_model.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:bumrent_app/app/data/providers/aset_provider.dart';
|
import 'package:bumrent_app/app/data/providers/aset_provider.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:bumrent_app/app/routes/app_routes.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class PetugasTambahPaketController extends GetxController {
|
class PetugasTambahPaketController extends GetxController {
|
||||||
@ -47,6 +48,9 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
// New RxBool for editing
|
// New RxBool for editing
|
||||||
final isEditing = false.obs;
|
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 timeOptions = {'Per Jam': true.obs, 'Per Hari': false.obs};
|
||||||
final pricePerHourController = TextEditingController();
|
final pricePerHourController = TextEditingController();
|
||||||
final maxHourController = TextEditingController();
|
final maxHourController = TextEditingController();
|
||||||
@ -64,11 +68,13 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
// Ambil flag isEditing dari arguments
|
// Ambil flag isEditing dan isViewing dari arguments
|
||||||
isEditing.value =
|
isEditing.value =
|
||||||
Get.arguments != null && Get.arguments['isEditing'] == true;
|
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'];
|
final paketArg = Get.arguments['paket'];
|
||||||
String? paketId;
|
String? paketId;
|
||||||
if (paketArg != null) {
|
if (paketArg != null) {
|
||||||
@ -230,7 +236,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Pilih aset dan masukkan jumlah',
|
'Pilih aset dan masukkan jumlah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -251,7 +257,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Jumlah harus lebih dari 0',
|
'Jumlah harus lebih dari 0',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -264,7 +270,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Jumlah melebihi stok yang tersedia',
|
'Jumlah melebihi stok yang tersedia',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -286,7 +292,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Item berhasil ditambahkan ke paket',
|
'Item berhasil ditambahkan ke paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -300,7 +306,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Pilih aset dan masukkan jumlah',
|
'Pilih aset dan masukkan jumlah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -321,7 +327,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Jumlah harus lebih dari 0',
|
'Jumlah harus lebih dari 0',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -349,7 +355,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Jumlah melebihi stok yang tersedia',
|
'Jumlah melebihi stok yang tersedia',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -362,7 +368,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Jumlah melebihi stok yang tersedia',
|
'Jumlah melebihi stok yang tersedia',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -385,7 +391,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Item berhasil diperbarui',
|
'Item berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -402,7 +408,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Dihapus',
|
'Dihapus',
|
||||||
'Item berhasil dihapus dari paket',
|
'Item berhasil dihapus dari paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -538,13 +544,13 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sukses
|
// Sukses
|
||||||
Get.back();
|
Get.offNamed(Routes.PETUGAS_PAKET);
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Paket berhasil diperbarui',
|
'Paket berhasil diperbarui',
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// --- ADD LOGIC ---
|
// --- ADD LOGIC ---
|
||||||
@ -611,13 +617,13 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sukses
|
// Sukses
|
||||||
Get.back();
|
Get.back(result: true);
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Paket berhasil ditambahkan',
|
'Paket berhasil ditambahkan',
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -627,7 +633,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
'Terjadi kesalahan: \\${e.toString()}',
|
'Terjadi kesalahan: \\${e.toString()}',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
@ -810,7 +816,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil gambar dari kamera: $e',
|
'Gagal mengambil gambar dari kamera: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -833,7 +839,7 @@ class PetugasTambahPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memilih gambar dari galeri: $e',
|
'Gagal memilih gambar dari galeri: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -476,7 +476,7 @@ class ListPetugasMitraView extends GetView<ListPetugasMitraController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Harap isi semua data',
|
'Harap isi semua data',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -600,7 +600,7 @@ class ListPetugasMitraView extends GetView<ListPetugasMitraController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Harap isi semua data',
|
'Harap isi semua data',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -203,7 +203,7 @@ class ListTagihanPeriodeView extends GetView<ListTagihanPeriodeController> {
|
|||||||
backgroundColor: Colors.orange.withOpacity(0.1),
|
backgroundColor: Colors.orange.withOpacity(0.1),
|
||||||
colorText: Colors.orange.shade800,
|
colorText: Colors.orange.shade800,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
margin: const EdgeInsets.all(8),
|
margin: const EdgeInsets.all(8),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -81,11 +81,15 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
|
|||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Navigate to PetugasTambahAsetView in add mode
|
// Navigate to PetugasTambahAsetView in add mode and refresh data when returning
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
Routes.PETUGAS_TAMBAH_ASET,
|
Routes.PETUGAS_TAMBAH_ASET,
|
||||||
arguments: {'isEditing': false, 'assetData': null},
|
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,
|
backgroundColor: AppColorsPetugas.babyBlueBright,
|
||||||
icon: Icon(Icons.add, color: AppColorsPetugas.blueGrotto),
|
icon: Icon(Icons.add, color: AppColorsPetugas.blueGrotto),
|
||||||
@ -456,7 +460,7 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID Aset tidak valid',
|
'ID Aset tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -467,7 +471,13 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
|
|||||||
'isEditing': true,
|
'isEditing': true,
|
||||||
'assetId': assetId,
|
'assetId': assetId,
|
||||||
},
|
},
|
||||||
);
|
)?.then((_) {
|
||||||
|
// Refresh data when returning from edit page
|
||||||
|
debugPrint(
|
||||||
|
'Returning from edit aset page, refreshing data...',
|
||||||
|
);
|
||||||
|
controller.loadAsetData();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
@ -670,235 +680,16 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showAddEditAssetDialog(
|
void _showAddEditAssetDialog(BuildContext context) {
|
||||||
BuildContext context, {
|
// Navigate to PetugasTambahAsetView in add mode and refresh data when returning
|
||||||
Map<String, dynamic>? aset,
|
Get.toNamed(
|
||||||
}) {
|
Routes.PETUGAS_TAMBAH_ASET,
|
||||||
final isEditing = aset != null;
|
arguments: {'isEditing': false, 'assetData': null},
|
||||||
|
)?.then((_) {
|
||||||
// In a real app, this would have proper form handling with controllers
|
// Refresh data when returning from tambah_aset page
|
||||||
showDialog(
|
debugPrint('Returning from tambah aset page, refreshing data...');
|
||||||
context: context,
|
controller.loadAsetData();
|
||||||
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 _showDeleteConfirmation(
|
void _showDeleteConfirmation(
|
||||||
|
@ -1023,7 +1023,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status sewa telah diubah menjadi SELESAI',
|
'Status sewa telah diubah menjadi SELESAI',
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -1054,7 +1054,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status sewa telah diubah menjadi DIKEMBALIKAN',
|
'Status sewa telah diubah menjadi DIKEMBALIKAN',
|
||||||
backgroundColor: Colors.teal,
|
backgroundColor: Colors.teal,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -1127,7 +1127,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status sewa telah diubah menjadi DIBATALKAN',
|
'Status sewa telah diubah menjadi DIBATALKAN',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -1381,7 +1381,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status pengajuan diubah menjadi Periksa Pembayaran',
|
'Status pengajuan diubah menjadi Periksa Pembayaran',
|
||||||
backgroundColor: Colors.amber.shade700,
|
backgroundColor: Colors.amber.shade700,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1394,7 +1394,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Pengajuan sewa aset telah disetujui',
|
'Pengajuan sewa aset telah disetujui',
|
||||||
backgroundColor: Colors.green.shade600,
|
backgroundColor: Colors.green.shade600,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1407,7 +1407,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Permintaan pembayaran denda telah dikirim',
|
'Permintaan pembayaran denda telah dikirim',
|
||||||
backgroundColor: Colors.deepOrange,
|
backgroundColor: Colors.deepOrange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1420,7 +1420,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status pengajuan diubah menjadi Periksa Denda',
|
'Status pengajuan diubah menjadi Periksa Denda',
|
||||||
backgroundColor: Colors.red.shade600,
|
backgroundColor: Colors.red.shade600,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1433,7 +1433,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Aset telah dikembalikan dan sewa telah selesai',
|
'Aset telah dikembalikan dan sewa telah selesai',
|
||||||
backgroundColor: Colors.green.shade600,
|
backgroundColor: Colors.green.shade600,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1476,7 +1476,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status sewa telah diubah menjadi DIBATALKAN',
|
'Status sewa telah diubah menjadi DIBATALKAN',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -1595,7 +1595,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Status sewa diubah menjadi Dikembalikan',
|
'Status sewa diubah menjadi Dikembalikan',
|
||||||
backgroundColor: Colors.teal,
|
backgroundColor: Colors.teal,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1628,7 +1628,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Permintaan pembayaran denda telah dikirim',
|
'Permintaan pembayaran denda telah dikirim',
|
||||||
backgroundColor: Colors.deepOrange,
|
backgroundColor: Colors.deepOrange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1661,7 +1661,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Sewa aset telah selesai',
|
'Sewa aset telah selesai',
|
||||||
backgroundColor: Colors.purple,
|
backgroundColor: Colors.purple,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
} else if (status == 'Dikembalikan') {
|
} else if (status == 'Dikembalikan') {
|
||||||
@ -1709,7 +1709,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Sewa aset telah selesai',
|
'Sewa aset telah selesai',
|
||||||
backgroundColor: Colors.purple,
|
backgroundColor: Colors.purple,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.task_alt_outlined),
|
icon: const Icon(Icons.task_alt_outlined),
|
||||||
@ -1817,7 +1817,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Bukti sewa dalam format PDF sedang diunduh',
|
'Bukti sewa dalam format PDF sedang diunduh',
|
||||||
backgroundColor: AppColorsPetugas.blueGrotto,
|
backgroundColor: AppColorsPetugas.blueGrotto,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1837,7 +1837,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Bukti sewa dalam format JPG sedang diunduh',
|
'Bukti sewa dalam format JPG sedang diunduh',
|
||||||
backgroundColor: AppColorsPetugas.blueGrotto,
|
backgroundColor: AppColorsPetugas.blueGrotto,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1857,7 +1857,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Data sewa dalam format XLSX sedang diunduh',
|
'Data sewa dalam format XLSX sedang diunduh',
|
||||||
backgroundColor: AppColorsPetugas.blueGrotto,
|
backgroundColor: AppColorsPetugas.blueGrotto,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1963,7 +1963,8 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
nominalController.text = sewa.totalTagihan.toString();
|
nominalController.text =
|
||||||
|
sewa.totalTagihan.toInt().toString();
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.payments_outlined,
|
Icons.payments_outlined,
|
||||||
@ -2456,7 +2457,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Nominal denda harus diisi',
|
'Nominal denda harus diisi',
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2501,7 +2502,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
|
|||||||
'Permintaan pembayaran denda telah dikirim',
|
'Permintaan pembayaran denda telah dikirim',
|
||||||
backgroundColor: Colors.deepOrange,
|
backgroundColor: Colors.deepOrange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.check_circle),
|
icon: const Icon(Icons.check_circle),
|
||||||
|
@ -567,7 +567,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur tambah rekening bank sedang dalam pengembangan',
|
'Fitur tambah rekening bank sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -639,7 +639,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur edit rekening bank sedang dalam pengembangan',
|
'Fitur edit rekening bank sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -689,7 +689,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur hapus rekening bank sedang dalam pengembangan',
|
'Fitur hapus rekening bank sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -765,7 +765,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur tambah mitra sedang dalam pengembangan',
|
'Fitur tambah mitra sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -845,7 +845,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur edit mitra sedang dalam pengembangan',
|
'Fitur edit mitra sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -895,7 +895,7 @@ class PetugasManajemenBumdesView
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur hapus mitra sedang dalam pengembangan',
|
'Fitur hapus mitra sedang dalam pengembangan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
@ -55,11 +55,17 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed:
|
onPressed: () async {
|
||||||
() => Get.toNamed(
|
final result = await Get.toNamed(
|
||||||
Routes.PETUGAS_TAMBAH_PAKET,
|
Routes.PETUGAS_TAMBAH_PAKET,
|
||||||
arguments: {'isEditing': false},
|
arguments: {'isEditing': false},
|
||||||
),
|
);
|
||||||
|
|
||||||
|
// Refresh the package list if a package was added
|
||||||
|
if (result == true) {
|
||||||
|
controller.loadPaketData();
|
||||||
|
}
|
||||||
|
},
|
||||||
label: Text(
|
label: Text(
|
||||||
'Tambah Paket',
|
'Tambah Paket',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -142,11 +148,17 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed:
|
onPressed: () async {
|
||||||
() => Get.toNamed(
|
final result = await Get.toNamed(
|
||||||
Routes.PETUGAS_TAMBAH_PAKET,
|
Routes.PETUGAS_TAMBAH_PAKET,
|
||||||
arguments: {'isEditing': false},
|
arguments: {'isEditing': false},
|
||||||
),
|
);
|
||||||
|
|
||||||
|
// Refresh the package list if a package was added
|
||||||
|
if (result == true) {
|
||||||
|
controller.loadPaketData();
|
||||||
|
}
|
||||||
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Tambah Paket'),
|
label: const Text('Tambah Paket'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@ -376,6 +388,21 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
|
|||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// Paket image or icon
|
// Paket image or icon
|
||||||
|
@ -111,6 +111,27 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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),
|
drawer: PetugasSideNavbar(controller: dashboardController),
|
||||||
drawerEdgeDragWidth: 60,
|
drawerEdgeDragWidth: 60,
|
||||||
@ -148,7 +169,13 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (controller.filteredPenyewaList.isEmpty) {
|
if (controller.filteredPenyewaList.isEmpty) {
|
||||||
return _buildEmptyState();
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await controller.refreshPenyewaList();
|
||||||
|
},
|
||||||
|
color: AppColorsPetugas.blueGrotto,
|
||||||
|
child: _buildEmptyStateWithScrollView(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _buildPenyewaList();
|
return _buildPenyewaList();
|
||||||
@ -282,208 +309,234 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
Widget _buildPenyewaList() {
|
||||||
return ListView.builder(
|
return RefreshIndicator(
|
||||||
padding: const EdgeInsets.all(16),
|
onRefresh: () async {
|
||||||
itemCount: controller.filteredPenyewaList.length,
|
await controller.refreshPenyewaList();
|
||||||
itemBuilder: (context, index) {
|
},
|
||||||
final penyewa = controller.filteredPenyewaList[index];
|
color: AppColorsPetugas.blueGrotto,
|
||||||
return Container(
|
child: ListView.builder(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
itemCount: controller.filteredPenyewaList.length,
|
||||||
color: Colors.white,
|
itemBuilder: (context, index) {
|
||||||
borderRadius: BorderRadius.circular(16),
|
final penyewa = controller.filteredPenyewaList[index];
|
||||||
boxShadow: [
|
return Container(
|
||||||
BoxShadow(
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
color: Colors.black.withOpacity(0.05),
|
decoration: BoxDecoration(
|
||||||
blurRadius: 10,
|
color: Colors.white,
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Padding(
|
boxShadow: [
|
||||||
padding: const EdgeInsets.all(0),
|
BoxShadow(
|
||||||
child: Column(
|
color: Colors.black.withOpacity(0.05),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
blurRadius: 10,
|
||||||
children: [
|
offset: const Offset(0, 4),
|
||||||
// Header with avatar and badge
|
),
|
||||||
Container(
|
],
|
||||||
padding: const EdgeInsets.all(16),
|
),
|
||||||
decoration: BoxDecoration(
|
child: Material(
|
||||||
color: AppColorsPetugas.babyBlueLight.withOpacity(0.2),
|
color: Colors.transparent,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: BorderRadius.circular(16),
|
||||||
topLeft: Radius.circular(16),
|
child: InkWell(
|
||||||
topRight: Radius.circular(16),
|
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
|
// Content section
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Show additional info only for Aktif and Ditangguhkan tabs
|
// Show additional info only for Aktif and Ditangguhkan tabs
|
||||||
if (controller.currentTabIndex.value != 0) ...[
|
if (controller.currentTabIndex.value != 0) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildInfoChip(
|
_buildInfoChip(
|
||||||
Icons.credit_card_outlined,
|
Icons.credit_card_outlined,
|
||||||
'NIK: ${penyewa['nik'] ?? 'Tidak tersedia'}',
|
'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'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
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
|
// Add "Detail" button for Verifikasi tab
|
||||||
if (controller.currentTabIndex.value == 0) ...[
|
if (controller.currentTabIndex.value == 0) ...[
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment:
|
||||||
children: [
|
MainAxisAlignment.spaceBetween,
|
||||||
Expanded(
|
children: [
|
||||||
child: _buildActionChip(
|
Expanded(
|
||||||
label: 'Lihat Detail & Verifikasi',
|
child: _buildActionChip(
|
||||||
color: AppColorsPetugas.blueGrotto,
|
label: 'Lihat Detail & Verifikasi',
|
||||||
icon: Icons.visibility,
|
color: AppColorsPetugas.blueGrotto,
|
||||||
onTap: () {
|
icon: Icons.visibility,
|
||||||
Get.toNamed(
|
onTap: () {
|
||||||
Routes.PETUGAS_DETAIL_PENYEWA,
|
Get.toNamed(
|
||||||
arguments: {
|
Routes.PETUGAS_DETAIL_PENYEWA,
|
||||||
'userId': penyewa['user_id'],
|
arguments: {
|
||||||
},
|
'userId': penyewa['user_id'],
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => 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),
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -168,15 +170,16 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
'Item Paket',
|
'Item Paket',
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
if (!controller.isViewing.value)
|
||||||
onPressed: () => _showAddItemDialog(),
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.add),
|
onPressed: () => _showAddItemDialog(),
|
||||||
label: const Text('Tambah Item'),
|
icon: const Icon(Icons.add),
|
||||||
style: ElevatedButton.styleFrom(
|
label: const Text('Tambah Item'),
|
||||||
backgroundColor: AppColorsPetugas.babyBlueLight,
|
style: ElevatedButton.styleFrom(
|
||||||
foregroundColor: AppColorsPetugas.blueGrotto,
|
backgroundColor: AppColorsPetugas.babyBlueLight,
|
||||||
|
foregroundColor: AppColorsPetugas.blueGrotto,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@ -208,26 +211,33 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
Text('Stok tersedia: ${item['stok']}'),
|
Text('Stok tersedia: ${item['stok']}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing:
|
||||||
mainAxisSize: MainAxisSize.min,
|
controller.isViewing.value
|
||||||
children: [
|
? null
|
||||||
IconButton(
|
: Row(
|
||||||
icon: const Icon(
|
mainAxisSize: MainAxisSize.min,
|
||||||
Icons.edit,
|
children: [
|
||||||
color: Colors.blue,
|
IconButton(
|
||||||
),
|
icon: const Icon(
|
||||||
onPressed: () => _showEditItemDialog(index),
|
Icons.edit,
|
||||||
),
|
color: Colors.blue,
|
||||||
IconButton(
|
),
|
||||||
icon: const Icon(
|
onPressed:
|
||||||
Icons.delete,
|
() =>
|
||||||
color: Colors.red,
|
_showEditItemDialog(index),
|
||||||
),
|
),
|
||||||
onPressed:
|
IconButton(
|
||||||
() => controller.removeItem(index),
|
icon: const Icon(
|
||||||
),
|
Icons.delete,
|
||||||
],
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
|
onPressed:
|
||||||
|
() => controller.removeItem(
|
||||||
|
index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -526,6 +536,9 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
String? prefixText,
|
String? prefixText,
|
||||||
IconData? prefixIcon,
|
IconData? prefixIcon,
|
||||||
}) {
|
}) {
|
||||||
|
final petugasController =
|
||||||
|
this.controller; // Reference to PetugasTambahPaketController
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -539,7 +552,7 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
color: AppColorsPetugas.textPrimary,
|
color: AppColorsPetugas.textPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isRequired) ...[
|
if (isRequired && !petugasController.isViewing.value) ...[
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'*',
|
'*',
|
||||||
@ -553,43 +566,55 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
Obx(
|
||||||
controller: controller,
|
() => TextFormField(
|
||||||
maxLines: maxLines,
|
controller: controller,
|
||||||
keyboardType: keyboardType,
|
maxLines: maxLines,
|
||||||
inputFormatters: inputFormatters,
|
keyboardType: keyboardType,
|
||||||
decoration: InputDecoration(
|
inputFormatters: inputFormatters,
|
||||||
hintText: hint,
|
readOnly: petugasController.isViewing.value,
|
||||||
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
style: TextStyle(
|
||||||
filled: true,
|
color:
|
||||||
fillColor: AppColorsPetugas.babyBlueBright,
|
petugasController.isViewing.value
|
||||||
prefixText: prefixText,
|
? AppColorsPetugas.textSecondary
|
||||||
prefixIcon:
|
: AppColorsPetugas.textPrimary,
|
||||||
prefixIcon != null
|
|
||||||
? Icon(
|
|
||||||
prefixIcon,
|
|
||||||
size: 20,
|
|
||||||
color: AppColorsPetugas.textSecondary,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
decoration: InputDecoration(
|
||||||
borderRadius: BorderRadius.circular(10),
|
hintText: hint,
|
||||||
borderSide: BorderSide.none,
|
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
||||||
),
|
filled: true,
|
||||||
focusedBorder: OutlineInputBorder(
|
fillColor:
|
||||||
borderRadius: BorderRadius.circular(10),
|
petugasController.isViewing.value
|
||||||
borderSide: BorderSide(
|
? AppColorsPetugas.babyBlueLight
|
||||||
color: AppColorsPetugas.blueGrotto,
|
: AppColorsPetugas.babyBlueBright,
|
||||||
width: 1.5,
|
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<PetugasTambahPaketController> {
|
|||||||
vertical: 12,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: AppColorsPetugas.babyBlueBright,
|
fillColor:
|
||||||
|
controller.isViewing.value
|
||||||
|
? AppColorsPetugas.babyBlueLight
|
||||||
|
: AppColorsPetugas.babyBlueBright,
|
||||||
),
|
),
|
||||||
items:
|
items:
|
||||||
options.map((option) {
|
options.map((option) {
|
||||||
@ -661,9 +689,12 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (value) {
|
onChanged:
|
||||||
if (value != null) onChanged(value);
|
controller.isViewing.value
|
||||||
},
|
? null // Disable in view-only mode
|
||||||
|
: (value) {
|
||||||
|
if (value != null) onChanged(value);
|
||||||
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.keyboard_arrow_down_rounded,
|
Icons.keyboard_arrow_down_rounded,
|
||||||
color: AppColorsPetugas.blueGrotto,
|
color: AppColorsPetugas.blueGrotto,
|
||||||
@ -717,41 +748,42 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
runSpacing: 12,
|
runSpacing: 12,
|
||||||
children: [
|
children: [
|
||||||
// Add button
|
// Add button
|
||||||
GestureDetector(
|
if (!controller.isViewing.value)
|
||||||
onTap: _showImageSourceOptions,
|
GestureDetector(
|
||||||
child: Container(
|
onTap: _showImageSourceOptions,
|
||||||
width: 100,
|
child: Container(
|
||||||
height: 100,
|
width: 100,
|
||||||
decoration: BoxDecoration(
|
height: 100,
|
||||||
color: AppColorsPetugas.babyBlueBright,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10),
|
color: AppColorsPetugas.babyBlueBright,
|
||||||
border: Border.all(
|
borderRadius: BorderRadius.circular(10),
|
||||||
color: AppColorsPetugas.babyBlue,
|
border: Border.all(
|
||||||
width: 1,
|
color: AppColorsPetugas.babyBlue,
|
||||||
style: BorderStyle.solid,
|
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
|
// Image previews
|
||||||
...List<Widget>.generate(controller.selectedImages.length, (
|
...List<Widget>.generate(controller.selectedImages.length, (
|
||||||
index,
|
index,
|
||||||
@ -811,21 +843,24 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
top: 4,
|
top: 4,
|
||||||
right: 4,
|
right: 4,
|
||||||
child: InkWell(
|
child:
|
||||||
onTap: () => controller.removeImage(index),
|
!controller.isViewing.value
|
||||||
child: Container(
|
? InkWell(
|
||||||
padding: const EdgeInsets.all(4),
|
onTap: () => controller.removeImage(index),
|
||||||
decoration: const BoxDecoration(
|
child: Container(
|
||||||
color: Colors.white,
|
padding: const EdgeInsets.all(4),
|
||||||
shape: BoxShape.circle,
|
decoration: const BoxDecoration(
|
||||||
),
|
color: Colors.white,
|
||||||
child: const Icon(
|
shape: BoxShape.circle,
|
||||||
Icons.close,
|
),
|
||||||
size: 18,
|
child: const Icon(
|
||||||
color: Colors.red,
|
Icons.close,
|
||||||
),
|
size: 18,
|
||||||
),
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -949,67 +984,97 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Obx(() {
|
||||||
children: [
|
// In view-only mode, just show a back button
|
||||||
OutlinedButton.icon(
|
if (controller.isViewing.value) {
|
||||||
onPressed: () => Get.back(),
|
return SizedBox(
|
||||||
icon: const Icon(Icons.arrow_back),
|
width: double.infinity,
|
||||||
label: const Text('Batal'),
|
child: ElevatedButton.icon(
|
||||||
style: OutlinedButton.styleFrom(
|
onPressed: () => Get.back(),
|
||||||
foregroundColor: AppColorsPetugas.textSecondary,
|
icon: const Icon(Icons.arrow_back),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
label: const Text('Kembali'),
|
||||||
side: BorderSide(color: AppColorsPetugas.divider),
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
backgroundColor: AppColorsPetugas.blueGrotto,
|
||||||
borderRadius: BorderRadius.circular(10),
|
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(() {
|
// For edit/add mode, show cancel and save buttons
|
||||||
final isValid = controller.isFormValid.value;
|
return Row(
|
||||||
final isSubmitting = controller.isSubmitting.value;
|
children: [
|
||||||
return ElevatedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed:
|
onPressed: () => Get.back(),
|
||||||
controller.isFormChanged.value && !isSubmitting
|
icon: const Icon(Icons.arrow_back),
|
||||||
? controller.savePaket
|
label: const Text('Batal'),
|
||||||
: null,
|
style: OutlinedButton.styleFrom(
|
||||||
icon:
|
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
|
isSubmitting
|
||||||
? const SizedBox(
|
? 'Menyimpan...'
|
||||||
width: 24,
|
: (controller.isEditing.value
|
||||||
height: 24,
|
? 'Simpan Paket'
|
||||||
child: CircularProgressIndicator(
|
: 'Tambah Paket'),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
shape: RoundedRectangleBorder(
|
style: ElevatedButton.styleFrom(
|
||||||
borderRadius: BorderRadius.circular(12),
|
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<PetugasTambahPaketController> {
|
|||||||
() => Material(
|
() => Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => controller.toggleTimeOption(option),
|
onTap:
|
||||||
|
controller.isViewing.value
|
||||||
|
? null
|
||||||
|
: () => controller.toggleTimeOption(option),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@ -1173,12 +1241,22 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
controller: priceController,
|
controller: priceController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
readOnly: controller.isViewing.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
controller.isViewing.value
|
||||||
|
? AppColorsPetugas.textSecondary
|
||||||
|
: AppColorsPetugas.textPrimary,
|
||||||
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan harga',
|
hintText: 'Masukkan harga',
|
||||||
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
||||||
prefixText: 'Rp ',
|
prefixText: 'Rp ',
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: AppColorsPetugas.babyBlueBright,
|
fillColor:
|
||||||
|
controller.isViewing.value
|
||||||
|
? AppColorsPetugas.babyBlueLight
|
||||||
|
: AppColorsPetugas.babyBlueBright,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
@ -1210,11 +1288,21 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
|
|||||||
controller: maxController,
|
controller: maxController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
readOnly: controller.isViewing.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
controller.isViewing.value
|
||||||
|
? AppColorsPetugas.textSecondary
|
||||||
|
: AppColorsPetugas.textPrimary,
|
||||||
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Opsional',
|
hintText: 'Opsional',
|
||||||
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: AppColorsPetugas.babyBlueBright,
|
fillColor:
|
||||||
|
controller.isViewing.value
|
||||||
|
? AppColorsPetugas.babyBlueLight
|
||||||
|
: AppColorsPetugas.babyBlueBright,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
|
@ -41,7 +41,7 @@ class PetugasMitraDashboardController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal keluar dari aplikasi',
|
'Gagal keluar dari aplikasi',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
errorMessage.value,
|
errorMessage.value,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -211,7 +211,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Tidak dapat menampilkan data - data tidak tersedia',
|
'Tidak dapat menampilkan data - data tidak tersedia',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.amber,
|
backgroundColor: Colors.amber,
|
||||||
colorText: Colors.black,
|
colorText: Colors.black,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
@ -231,6 +231,9 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
debugPrint('💾 Saved asetId to GetStorage: ${asetId.value}');
|
debugPrint('💾 Saved asetId to GetStorage: ${asetId.value}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bersihkan data controller
|
||||||
|
clearData();
|
||||||
|
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,6 +448,9 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
void onBackPressed() {
|
void onBackPressed() {
|
||||||
debugPrint('🔙 Back button pressed in OrderSewaAsetView');
|
debugPrint('🔙 Back button pressed in OrderSewaAsetView');
|
||||||
|
|
||||||
|
// Bersihkan data controller sebelum kembali
|
||||||
|
clearData();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to use the navigation service
|
// Try to use the navigation service
|
||||||
navigationService.backFromOrderSewaAset();
|
navigationService.backFromOrderSewaAset();
|
||||||
@ -2287,7 +2293,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
message,
|
message,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -2299,7 +2305,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
message,
|
message,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -2767,7 +2773,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal membuat pesanan',
|
'Gagal membuat pesanan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -2777,7 +2783,7 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan: $e',
|
'Terjadi kesalahan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -3537,4 +3543,43 @@ class OrderSewaAsetController extends GetxController {
|
|||||||
isLoadingBookings(false);
|
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 = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data paket',
|
'Gagal memuat data paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -152,7 +152,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan saat memuat data paket',
|
'Terjadi kesalahan saat memuat data paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -166,10 +166,10 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
debugPrint('🔄 Processing paket data for ID: $id');
|
debugPrint('🔄 Processing paket data for ID: $id');
|
||||||
debugPrint('📦 Raw paket data type: ${rawPaket.runtimeType}');
|
debugPrint('📦 Raw paket data type: ${rawPaket.runtimeType}');
|
||||||
|
|
||||||
// Initialize loadedPaket with a default value
|
// Initialize loadedPaket with a default value
|
||||||
late final PaketModel loadedPaket;
|
late final PaketModel loadedPaket;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle Map directly - pakets from getPakets() are always maps
|
// Handle Map directly - pakets from getPakets() are always maps
|
||||||
loadedPaket = PaketModel.fromMap(rawPaket);
|
loadedPaket = PaketModel.fromMap(rawPaket);
|
||||||
@ -183,10 +183,16 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
deskripsi: getPaketDeskripsi(rawPaket),
|
deskripsi: getPaketDeskripsi(rawPaket),
|
||||||
harga: getPaketHarga(rawPaket),
|
harga: getPaketHarga(rawPaket),
|
||||||
kuantitas: getPaketKuantitas(rawPaket),
|
kuantitas: getPaketKuantitas(rawPaket),
|
||||||
foto: const <String>[], // Initialize with empty list, will be populated later
|
foto:
|
||||||
|
const <
|
||||||
|
String
|
||||||
|
>[], // Initialize with empty list, will be populated later
|
||||||
satuanWaktuSewa: getPaketSatuanWaktuSewa(rawPaket),
|
satuanWaktuSewa: getPaketSatuanWaktuSewa(rawPaket),
|
||||||
foto_paket: getPaketMainPhoto(rawPaket),
|
foto_paket: getPaketMainPhoto(rawPaket),
|
||||||
images: const <String>[], // Initialize with empty list, will be populated later
|
images:
|
||||||
|
const <
|
||||||
|
String
|
||||||
|
>[], // Initialize with empty list, will be populated later
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
@ -352,7 +358,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
if (items.isNotEmpty) {
|
if (items.isNotEmpty) {
|
||||||
paketItems.value = items;
|
paketItems.value = items;
|
||||||
debugPrint('✅ Loaded ${paketItems.length} package items');
|
debugPrint('✅ Loaded ${paketItems.length} package items');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ Error loading paket items: $e');
|
debugPrint('❌ Error loading paket items: $e');
|
||||||
paketItems.value = [];
|
paketItems.value = [];
|
||||||
@ -1030,7 +1036,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Waktu Tidak Tersedia',
|
'Waktu Tidak Tersedia',
|
||||||
'Jam ini tidak tersedia karena ada aset dalam paket yang sudah dipesan',
|
'Jam ini tidak tersedia karena ada aset dalam paket yang sudah dipesan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1114,7 +1120,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Data paket tidak lengkap',
|
'Data paket tidak lengkap',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1131,7 +1137,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Silakan pilih waktu sewa',
|
'Silakan pilih waktu sewa',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1163,7 +1169,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
durasi: orderData['durasi'] ?? 1, // Default to 1 if not provided
|
durasi: orderData['durasi'] ?? 1, // Default to 1 if not provided
|
||||||
totalHarga: orderData['total_harga'].toInt(),
|
totalHarga: orderData['total_harga'].toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a mock result for navigation
|
// Create a mock result for navigation
|
||||||
final resultData = {
|
final resultData = {
|
||||||
'id': 'order_${DateTime.now().millisecondsSinceEpoch}',
|
'id': 'order_${DateTime.now().millisecondsSinceEpoch}',
|
||||||
@ -1174,21 +1180,23 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pesanan berhasil dibuat',
|
'Pesanan berhasil dibuat',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to payment page
|
// Navigate to payment page
|
||||||
if (result && resultData != null && resultData['id'] != null) {
|
if (result && resultData != null && resultData['id'] != null) {
|
||||||
navigationService.navigateToPembayaranSewa(resultData['id'].toString());
|
navigationService.navigateToPembayaranSewa(
|
||||||
|
resultData['id'].toString(),
|
||||||
|
);
|
||||||
} else if (result) {
|
} else if (result) {
|
||||||
// If result is true but we don't have an ID, navigate back to sewa aset
|
// If result is true but we don't have an ID, navigate back to sewa aset
|
||||||
navigationService.navigateToSewaAset();
|
navigationService.navigateToSewaAset();
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mendapatkan ID pesanan',
|
'Gagal mendapatkan ID pesanan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1196,7 +1204,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal membuat pesanan',
|
'Gagal membuat pesanan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1205,7 +1213,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal membuat pesanan',
|
'Gagal membuat pesanan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1215,7 +1223,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan: $e',
|
'Terjadi kesalahan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1392,7 +1400,7 @@ class OrderSewaPaketController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Peringatan',
|
'Peringatan',
|
||||||
'Pilih tanggal terlebih dahulu',
|
'Pilih tanggal terlebih dahulu',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -378,7 +378,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Pesanan Dibatalkan',
|
'Pesanan Dibatalkan',
|
||||||
'Batas waktu pembayaran telah berakhir',
|
'Batas waktu pembayaran telah berakhir',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: Duration(seconds: 5),
|
duration: Duration(seconds: 5),
|
||||||
@ -417,7 +417,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil foto: ${e.toString()}',
|
'Gagal mengambil foto: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -443,7 +443,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memilih foto dari galeri: ${e.toString()}',
|
'Gagal memilih foto dari galeri: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -459,7 +459,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Mohon unggah bukti pembayaran terlebih dahulu',
|
'Mohon unggah bukti pembayaran terlebih dahulu',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -541,7 +541,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Bukti pembayaran berhasil diunggah',
|
'Bukti pembayaran berhasil diunggah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -550,7 +550,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengunggah bukti pembayaran: ${e.toString()}',
|
'Gagal mengunggah bukti pembayaran: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -654,7 +654,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pembayaran tunai berhasil disubmit',
|
'Pembayaran tunai berhasil disubmit',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1090,7 +1090,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Data berhasil diperbarui',
|
'Data berhasil diperbarui',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
@ -1104,7 +1104,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memperbarui data',
|
'Gagal memperbarui data',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -305,6 +305,9 @@ class PembayaranSewaController extends GetxController
|
|||||||
: 0,
|
: 0,
|
||||||
'status': rentalData['status'] ?? 'MENUNGGU PEMBAYARAN',
|
'status': rentalData['status'] ?? 'MENUNGGU PEMBAYARAN',
|
||||||
'created_at': DateTime.now().toString(),
|
'created_at': DateTime.now().toString(),
|
||||||
|
'updated_at':
|
||||||
|
DateTime.now()
|
||||||
|
.toString(), // Explicitly set updated_at for countdown
|
||||||
'denda': 0, // Default value
|
'denda': 0, // Default value
|
||||||
'keterangan': '', // Default value
|
'keterangan': '', // Default value
|
||||||
'image_url': rentalData['imageUrl'],
|
'image_url': rentalData['imageUrl'],
|
||||||
@ -359,13 +362,19 @@ class PembayaranSewaController extends GetxController
|
|||||||
'price_per_unit': 10000,
|
'price_per_unit': 10000,
|
||||||
'total_price': 50000,
|
'total_price': 50000,
|
||||||
'status': 'MENUNGGU PEMBAYARAN',
|
'status': 'MENUNGGU PEMBAYARAN',
|
||||||
'created_at':
|
'created_at': DateTime.now().toString(),
|
||||||
DateTime.now().toString(), // Use this for countdown calculation
|
'updated_at':
|
||||||
|
DateTime.now()
|
||||||
|
.toString(), // Explicitly set updated_at for countdown
|
||||||
'denda': 20000, // Dummy data for denda
|
'denda': 20000, // Dummy data for denda
|
||||||
'keterangan':
|
'keterangan':
|
||||||
'Terjadi kerusakan pada bagian kaki', // Dummy keterangan for denda
|
'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
|
// Update the current step based on the status
|
||||||
updateCurrentStepBasedOnStatus();
|
updateCurrentStepBasedOnStatus();
|
||||||
|
|
||||||
@ -433,14 +442,27 @@ class PembayaranSewaController extends GetxController
|
|||||||
if (data['status'] != null &&
|
if (data['status'] != null &&
|
||||||
data['status'].toString().isNotEmpty) {
|
data['status'].toString().isNotEmpty) {
|
||||||
val?['status'] = data['status'];
|
val?['status'] = data['status'];
|
||||||
debugPrint(
|
debugPrint('📊 Order status from sewa_aset: ${data['status']}');
|
||||||
'📊 Order status from sewa_aset: \\${data['status']}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// Tambahkan mapping updated_at
|
|
||||||
|
// Ensure updated_at is always set
|
||||||
if (data['updated_at'] != null) {
|
if (data['updated_at'] != null) {
|
||||||
val?['updated_at'] = data['updated_at'];
|
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
|
// Format rental period
|
||||||
if (data['waktu_mulai'] != null &&
|
if (data['waktu_mulai'] != null &&
|
||||||
data['waktu_selesai'] != null) {
|
data['waktu_selesai'] != null) {
|
||||||
@ -448,12 +470,12 @@ class PembayaranSewaController extends GetxController
|
|||||||
final startTime = DateTime.parse(data['waktu_mulai']);
|
final startTime = DateTime.parse(data['waktu_mulai']);
|
||||||
final endTime = DateTime.parse(data['waktu_selesai']);
|
final endTime = DateTime.parse(data['waktu_selesai']);
|
||||||
val?['rental_period'] =
|
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(
|
debugPrint(
|
||||||
'✅ Successfully formatted rental period: \\${val?['rental_period']}',
|
'✅ Successfully formatted rental period: ${val?['rental_period']}',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('❌ Error parsing date: \\${e}');
|
debugPrint('❌ Error parsing date: ${e}');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@ -577,7 +599,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Pesanan Dibatalkan',
|
'Pesanan Dibatalkan',
|
||||||
'Batas waktu pembayaran telah berakhir',
|
'Batas waktu pembayaran telah berakhir',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: Duration(seconds: 5),
|
duration: Duration(seconds: 5),
|
||||||
@ -624,7 +646,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengambil foto: \\${e.toString()}',
|
'Gagal mengambil foto: \\${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -653,7 +675,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memilih foto dari galeri: \\${e.toString()}',
|
'Gagal memilih foto dari galeri: \\${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -679,7 +701,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Mohon unggah bukti pembayaran terlebih dahulu',
|
'Mohon unggah bukti pembayaran terlebih dahulu',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -691,7 +713,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Tidak ada perubahan yang perlu disimpan',
|
'Tidak ada perubahan yang perlu disimpan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -843,7 +865,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
val?['status'] == 'PEMBAYARAN DENDA') {
|
val?['status'] == 'PEMBAYARAN DENDA') {
|
||||||
val?['status'] = 'PERIKSA PEMBAYARAN DENDA';
|
val?['status'] = 'PERIKSA PEMBAYARAN DENDA';
|
||||||
} else {
|
} else {
|
||||||
val?['status'] = 'MEMERIKSA PEMBAYARAN';
|
val?['status'] = 'PERIKSA PEMBAYARAN';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -894,7 +916,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Bukti pembayaran berhasil diunggah',
|
'Bukti pembayaran berhasil diunggah',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -903,7 +925,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal mengunggah bukti pembayaran: ${e.toString()}',
|
'Gagal mengunggah bukti pembayaran: ${e.toString()}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -930,19 +952,19 @@ class PembayaranSewaController extends GetxController
|
|||||||
newStatus = 'MENUNGGU PEMBAYARAN';
|
newStatus = 'MENUNGGU PEMBAYARAN';
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
newStatus = 'MEMERIKSA PEMBAYARAN';
|
newStatus = 'PERIKSA PEMBAYARAN';
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
newStatus = 'DITERIMA';
|
newStatus = 'DITERIMA';
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
newStatus = 'PENGEMBALIAN';
|
newStatus = 'DIKEMBALIKAN';
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
newStatus = 'PEMBAYARAN DENDA';
|
newStatus = 'PEMBAYARAN DENDA';
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
newStatus = 'MEMERIKSA PEMBAYARAN DENDA';
|
newStatus = 'PERIKSA PEMBAYARAN DENDA';
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
newStatus = 'SELESAI';
|
newStatus = 'SELESAI';
|
||||||
@ -965,7 +987,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
case 'MENUNGGU PEMBAYARAN':
|
case 'MENUNGGU PEMBAYARAN':
|
||||||
currentStep.value = 0;
|
currentStep.value = 0;
|
||||||
break;
|
break;
|
||||||
case 'MEMERIKSA PEMBAYARAN':
|
case 'PERIKSA PEMBAYARAN':
|
||||||
currentStep.value = 1;
|
currentStep.value = 1;
|
||||||
break;
|
break;
|
||||||
case 'DITERIMA':
|
case 'DITERIMA':
|
||||||
@ -974,7 +996,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
case 'AKTIF':
|
case 'AKTIF':
|
||||||
currentStep.value = 3;
|
currentStep.value = 3;
|
||||||
break;
|
break;
|
||||||
case 'PENGEMBALIAN':
|
case 'DIKEMBALIKAN':
|
||||||
currentStep.value = 4;
|
currentStep.value = 4;
|
||||||
break;
|
break;
|
||||||
case 'PEMBAYARAN DENDA':
|
case 'PEMBAYARAN DENDA':
|
||||||
@ -1003,7 +1025,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
void submitCashPayment() {
|
void submitCashPayment() {
|
||||||
// Update order status
|
// Update order status
|
||||||
orderDetails.update((val) {
|
orderDetails.update((val) {
|
||||||
val?['status'] = 'MEMERIKSA PEMBAYARAN';
|
val?['status'] = 'PERIKSA PEMBAYARAN';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cancel countdown timer as payment has been submitted
|
// Cancel countdown timer as payment has been submitted
|
||||||
@ -1013,7 +1035,7 @@ class PembayaranSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pembayaran tunai berhasil disubmit',
|
'Pembayaran tunai berhasil disubmit',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1266,7 +1288,20 @@ class PembayaranSewaController extends GetxController
|
|||||||
updateCurrentStepBasedOnStatus();
|
updateCurrentStepBasedOnStatus();
|
||||||
|
|
||||||
// Restart countdown timer if needed
|
// 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();
|
_countdownTimer?.cancel();
|
||||||
startCountdownTimer();
|
startCountdownTimer();
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan saat memuat data aset',
|
'Terjadi kesalahan saat memuat data aset',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -279,7 +279,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
message,
|
message,
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -321,7 +321,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pesanan berhasil dibuat',
|
'Pesanan berhasil dibuat',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -383,7 +383,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Terjadi kesalahan saat memuat data paket',
|
'Terjadi kesalahan saat memuat data paket',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -429,7 +429,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Gagal memuat data paket. Silakan coba lagi nanti.',
|
'Gagal memuat data paket. Silakan coba lagi nanti.',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -470,7 +470,7 @@ class SewaAsetController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Pesanan paket berhasil dibuat',
|
'Pesanan paket berhasil dibuat',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -46,30 +46,51 @@ class WargaDashboardController extends GetxController {
|
|||||||
final dendaAktifCount = 0.obs;
|
final dendaAktifCount = 0.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
// Set navigation index to Home (0)
|
// Set navigation index to Home (0)
|
||||||
navigationService.setNavIndex(0);
|
navigationService.setNavIndex(0);
|
||||||
|
|
||||||
// Load user data
|
// Check if navigation is coming from login
|
||||||
fetchProfileFromWargaDesa();
|
final args = Get.arguments;
|
||||||
_loadUserData();
|
final bool isFromLogin = args != null && args['from_login'] == true;
|
||||||
|
|
||||||
// Load sample data
|
if (isFromLogin) {
|
||||||
_loadSampleData();
|
print('onInit: Navigation from login detected, prioritizing data fetch');
|
||||||
|
}
|
||||||
|
|
||||||
// Load dummy data for bills and penalties
|
// Verifikasi bahwa pengguna sudah login sebelum melakukan fetch data
|
||||||
loadDummyData();
|
if (_authProvider.currentUser != null) {
|
||||||
|
// Prioritize loading user profile data first
|
||||||
|
await fetchProfileFromWargaDesa();
|
||||||
|
|
||||||
// Load unpaid rentals
|
// If the profile data was not loaded successfully, try again after a short delay
|
||||||
loadUnpaidRentals();
|
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
|
// Load other user data
|
||||||
_debugCountSewaAset();
|
await _loadUserData();
|
||||||
|
|
||||||
// Load sewa aktif
|
// Load other data in parallel to speed up the dashboard initialization
|
||||||
loadActiveRentals();
|
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<void> _loadUserData() async {
|
Future<void> _loadUserData() async {
|
||||||
@ -116,7 +137,7 @@ class WargaDashboardController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadSampleData() {
|
Future<void> _loadSampleData() async {
|
||||||
// Clear any existing data
|
// Clear any existing data
|
||||||
activeRentals.clear();
|
activeRentals.clear();
|
||||||
|
|
||||||
@ -147,10 +168,37 @@ class WargaDashboardController extends GetxController {
|
|||||||
navigationService.toSewaAset();
|
navigationService.toSewaAset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshData() {
|
Future<void> refreshData() async {
|
||||||
fetchProfileFromWargaDesa();
|
print('refreshData: Refreshing dashboard data');
|
||||||
_loadSampleData();
|
try {
|
||||||
loadDummyData();
|
// 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) {
|
void onNavItemTapped(int index) {
|
||||||
@ -176,12 +224,14 @@ class WargaDashboardController extends GetxController {
|
|||||||
Get.toNamed(Routes.WARGA_SEWA, arguments: {'tab': 3});
|
Get.toNamed(Routes.WARGA_SEWA, arguments: {'tab': 3});
|
||||||
}
|
}
|
||||||
|
|
||||||
void logout() async {
|
Future<void> logout() async {
|
||||||
|
print('logout: Logging out user');
|
||||||
await _authProvider.signOut();
|
await _authProvider.signOut();
|
||||||
navigationService.toLogin();
|
navigationService.toLogin();
|
||||||
|
print('logout: User logged out and redirected to login screen');
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadDummyData() {
|
Future<void> loadDummyData() async {
|
||||||
// Dummy active bills
|
// Dummy active bills
|
||||||
activeBills.clear();
|
activeBills.clear();
|
||||||
activeBills.add({
|
activeBills.add({
|
||||||
@ -331,24 +381,77 @@ class WargaDashboardController extends GetxController {
|
|||||||
Future<void> fetchProfileFromWargaDesa() async {
|
Future<void> fetchProfileFromWargaDesa() async {
|
||||||
try {
|
try {
|
||||||
final user = _authProvider.currentUser;
|
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;
|
final userId = user.id;
|
||||||
|
print('fetchProfileFromWargaDesa: Fetching data for user: $userId');
|
||||||
|
|
||||||
final data =
|
final data =
|
||||||
await _authProvider.client
|
await _authProvider.client
|
||||||
.from('warga_desa')
|
.from('warga_desa')
|
||||||
.select('nik, alamat, email, nama_lengkap, no_hp, avatar')
|
.select('nik, alamat, email, nama_lengkap, no_hp, avatar')
|
||||||
.eq('user_id', userId)
|
.eq('user_id', userId)
|
||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
print('fetchProfileFromWargaDesa: Data retrieved successfully');
|
||||||
userNik.value = data['nik']?.toString() ?? '';
|
userNik.value = data['nik']?.toString() ?? '';
|
||||||
userAddress.value = data['alamat']?.toString() ?? '';
|
userAddress.value = data['alamat']?.toString() ?? '';
|
||||||
userEmail.value = data['email']?.toString() ?? '';
|
userEmail.value = data['email']?.toString() ?? '';
|
||||||
userName.value = data['nama_lengkap']?.toString() ?? '';
|
userName.value = data['nama_lengkap']?.toString() ?? '';
|
||||||
userPhone.value = data['no_hp']?.toString() ?? '';
|
userPhone.value = data['no_hp']?.toString() ?? '';
|
||||||
userAvatar.value = data['avatar']?.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) {
|
} catch (e) {
|
||||||
print('Error fetching profile from warga_desa: $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<void> _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(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Tidak dapat mengakses ${source == ImageSource.camera ? 'kamera' : 'galeri'}',
|
'Tidak dapat mengakses ${source == ImageSource.camera ? 'kamera' : 'galeri'}',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red.shade700,
|
backgroundColor: Colors.red.shade700,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
|
@ -254,10 +254,28 @@ class WargaSewaController extends GetxController
|
|||||||
|
|
||||||
debugPrint('Fetched ${sewaAsetList.length} sewa_aset records');
|
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
|
// Process each sewa_aset record
|
||||||
for (var sewaAset in sewaAsetList) {
|
for (var sewaAset in sewaAsetList) {
|
||||||
final processedData = await _processRentalData(sewaAset);
|
final processedData = await _processRentalData(sewaAset);
|
||||||
processedData['status'] = sewaAset['status'] ?? 'MENUNGGU PEMBAYARAN';
|
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);
|
rentals.add(processedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +339,7 @@ class WargaSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Berhasil',
|
'Berhasil',
|
||||||
'Pesanan berhasil dibatalkan',
|
'Pesanan berhasil dibatalkan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -337,7 +355,7 @@ class WargaSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Gagal membatalkan pesanan: $e',
|
'Gagal membatalkan pesanan: $e',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -377,7 +395,7 @@ class WargaSewaController extends GetxController
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Navigasi ke halaman pembayaran',
|
'Navigasi ke halaman pembayaran',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,9 +285,10 @@ class OrderSewaAsetView extends GetView<OrderSewaAsetController> {
|
|||||||
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
// Handle back button press here
|
// Bersihkan controller saat pengguna keluar dari halaman dengan tombol back perangkat
|
||||||
handleBackButtonPress();
|
controller.clearData();
|
||||||
return false; // We handle the navigation ourselves
|
debugPrint('🧹 Controller data cleared by WillPopScope');
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: AppColors.background,
|
||||||
@ -474,108 +475,60 @@ class OrderSewaAsetView extends GetView<OrderSewaAsetController> {
|
|||||||
},
|
},
|
||||||
// Add onTap handler for the image
|
// Add onTap handler for the image
|
||||||
onTap: () {
|
onTap: () {
|
||||||
debugPrint("📸 Image tapped - opening fullscreen viewer");
|
debugPrint(
|
||||||
final imageUrl = controller.getCurrentPhotoUrl();
|
"📸 Image tapped - attempting to open fullscreen viewer",
|
||||||
if (imageUrl != null && imageUrl.isNotEmpty) {
|
);
|
||||||
debugPrint("📸 Current image URL: $imageUrl");
|
try {
|
||||||
debugPrint("📸 Total photos: ${controller.assetPhotos.length}");
|
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
|
// Extract all image URLs from the assetPhotos collection
|
||||||
final List<String> photoUrls = [];
|
final List<String> photoUrls = [];
|
||||||
for (var photo in controller.assetPhotos) {
|
for (var photo in controller.assetPhotos) {
|
||||||
final url = photo.fotoAset;
|
final url = photo.fotoAset;
|
||||||
if (url.isNotEmpty) {
|
if (url.isNotEmpty) {
|
||||||
photoUrls.add(url);
|
photoUrls.add(url);
|
||||||
debugPrint("📸 Added photo URL: $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");
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
if (photoUrls.isEmpty) {
|
debugPrint("📸 ERROR in image tap handler: $e");
|
||||||
debugPrint("📸 No valid photo URLs found");
|
Get.snackbar(
|
||||||
return;
|
'Error',
|
||||||
}
|
'Failed to open image viewer',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
showDialog(
|
backgroundColor: Colors.red.shade100,
|
||||||
context: Get.context!,
|
colorText: Colors.red.shade900,
|
||||||
builder:
|
duration: const Duration(seconds: 2),
|
||||||
(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(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
debugPrint("📸 No valid current image URL");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@ -667,17 +620,19 @@ class OrderSewaAsetView extends GetView<OrderSewaAsetController> {
|
|||||||
|
|
||||||
// Gradient overlay
|
// Gradient overlay
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: DecoratedBox(
|
child: IgnorePointer(
|
||||||
decoration: BoxDecoration(
|
child: DecoratedBox(
|
||||||
gradient: LinearGradient(
|
decoration: BoxDecoration(
|
||||||
begin: Alignment.topCenter,
|
gradient: LinearGradient(
|
||||||
end: Alignment.bottomCenter,
|
begin: Alignment.topCenter,
|
||||||
colors: [
|
end: Alignment.bottomCenter,
|
||||||
Colors.black.withOpacity(0.1),
|
colors: [
|
||||||
Colors.transparent,
|
Colors.black.withOpacity(0.1),
|
||||||
Colors.black.withOpacity(0.3),
|
Colors.transparent,
|
||||||
],
|
Colors.black.withOpacity(0.3),
|
||||||
stops: [0.0, 0.6, 1.0],
|
],
|
||||||
|
stops: [0.0, 0.6, 1.0],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -687,13 +642,15 @@ class OrderSewaAsetView extends GetView<OrderSewaAsetController> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
child: Container(
|
child: IgnorePointer(
|
||||||
padding: EdgeInsets.all(8),
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.all(8),
|
||||||
color: Colors.black.withOpacity(0.5),
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
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<OrderSewaAsetController> {
|
|||||||
asetId,
|
asetId,
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Container(
|
return GestureDetector(
|
||||||
width: 60,
|
onTap: () async {
|
||||||
height: 60,
|
debugPrint(
|
||||||
decoration: BoxDecoration(
|
"📸 Package item image tapped - attempting to open fullscreen viewer for asset ID: $asetId",
|
||||||
borderRadius:
|
);
|
||||||
BorderRadius.circular(
|
try {
|
||||||
8,
|
// Get all photos for this asset
|
||||||
),
|
final photos =
|
||||||
color:
|
await controller
|
||||||
AppColors
|
.asetProvider
|
||||||
.surfaceLight,
|
.getAsetPhotos(
|
||||||
),
|
asetId,
|
||||||
child:
|
);
|
||||||
snapshot.connectionState ==
|
if (photos.isEmpty) {
|
||||||
ConnectionState
|
debugPrint(
|
||||||
.waiting
|
"📸 No photos found for asset ID: $asetId",
|
||||||
? Center(
|
);
|
||||||
child: SizedBox(
|
return;
|
||||||
width: 20,
|
}
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
// Extract valid photo URLs
|
||||||
strokeWidth:
|
final List<String>
|
||||||
2,
|
photoUrls = [];
|
||||||
color:
|
for (var photo
|
||||||
AppColors
|
in photos) {
|
||||||
.primary,
|
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,
|
||||||
),
|
),
|
||||||
)
|
fullscreenDialog:
|
||||||
: snapshot.data !=
|
true,
|
||||||
null
|
transition:
|
||||||
? ClipRRect(
|
Transition.fade,
|
||||||
borderRadius:
|
);
|
||||||
BorderRadius.circular(
|
|
||||||
8,
|
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
|
: snapshot.data !=
|
||||||
.data!,
|
null
|
||||||
fit:
|
? ClipRRect(
|
||||||
BoxFit
|
borderRadius:
|
||||||
.cover,
|
BorderRadius.circular(
|
||||||
placeholder:
|
8,
|
||||||
(
|
),
|
||||||
context,
|
child: CachedNetworkImage(
|
||||||
url,
|
imageUrl:
|
||||||
) => Center(
|
snapshot
|
||||||
child: SizedBox(
|
.data!,
|
||||||
width:
|
fit:
|
||||||
20,
|
BoxFit
|
||||||
height:
|
.cover,
|
||||||
20,
|
placeholder:
|
||||||
child: CircularProgressIndicator(
|
(
|
||||||
strokeWidth:
|
context,
|
||||||
2,
|
url,
|
||||||
color:
|
) => Center(
|
||||||
AppColors.primary,
|
child: SizedBox(
|
||||||
|
width:
|
||||||
|
20,
|
||||||
|
height:
|
||||||
|
20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth:
|
||||||
|
2,
|
||||||
|
color:
|
||||||
|
AppColors.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
errorWidget:
|
||||||
errorWidget:
|
(
|
||||||
(
|
context,
|
||||||
context,
|
url,
|
||||||
url,
|
error,
|
||||||
error,
|
) => Icon(
|
||||||
) => Icon(
|
Icons
|
||||||
Icons
|
.image_not_supported_outlined,
|
||||||
.image_not_supported_outlined,
|
color:
|
||||||
color:
|
AppColors.textLight,
|
||||||
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<FullScreenImageViewer> {
|
|||||||
for (int i = 0; i < widget.imageUrls.length && i < 3; i++) {
|
for (int i = 0; i < widget.imageUrls.length && i < 3; i++) {
|
||||||
debugPrint("📸 Image URL $i: ${widget.imageUrls[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
|
@override
|
||||||
void dispose() {
|
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();
|
pageController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return WillPopScope(
|
||||||
backgroundColor: Colors.black,
|
onWillPop: () async {
|
||||||
body: Stack(
|
// Ensure UI is restored on back button press
|
||||||
children: [
|
SystemChrome.setEnabledSystemUIMode(
|
||||||
// Photo Gallery
|
SystemUiMode.manual,
|
||||||
PhotoViewGallery.builder(
|
overlays: SystemUiOverlay.values,
|
||||||
scrollPhysics: const BouncingScrollPhysics(),
|
);
|
||||||
builder: (BuildContext context, int index) {
|
return true;
|
||||||
final imageUrl = widget.imageUrls[index];
|
},
|
||||||
debugPrint("📸 Building image at index $index: $imageUrl");
|
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(
|
return PhotoViewGalleryPageOptions(
|
||||||
imageProvider: CachedNetworkImageProvider(imageUrl),
|
imageProvider: CachedNetworkImageProvider(imageUrl),
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
minScale: PhotoViewComputedScale.contained * 0.8,
|
minScale: PhotoViewComputedScale.contained * 0.8,
|
||||||
maxScale: PhotoViewComputedScale.covered * 2.0,
|
maxScale: PhotoViewComputedScale.covered * 2.0,
|
||||||
heroAttributes: PhotoViewHeroAttributes(tag: "photo_$index"),
|
heroAttributes: PhotoViewHeroAttributes(
|
||||||
);
|
tag: "photo_$index",
|
||||||
},
|
),
|
||||||
itemCount: widget.imageUrls.length,
|
errorBuilder: (context, error, stackTrace) {
|
||||||
loadingBuilder:
|
debugPrint("📸 Error loading image: $error");
|
||||||
(context, event) => Center(
|
return Container(
|
||||||
child: SizedBox(
|
color: Colors.black,
|
||||||
width: 30.0,
|
child: Center(
|
||||||
height: 30.0,
|
child: Column(
|
||||||
child: CircularProgressIndicator(
|
mainAxisSize: MainAxisSize.min,
|
||||||
value:
|
children: [
|
||||||
event == null
|
Icon(
|
||||||
? 0
|
Icons.broken_image_rounded,
|
||||||
: event.cumulativeBytesLoaded /
|
color: Colors.white70,
|
||||||
event.expectedTotalBytes!,
|
size: 50,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
),
|
||||||
|
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<Color>(
|
||||||
|
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
|
// Image counter
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 40,
|
top: 20,
|
||||||
right: 20,
|
right: 20,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(
|
||||||
decoration: BoxDecoration(
|
horizontal: 12,
|
||||||
color: Colors.black54,
|
vertical: 8,
|
||||||
borderRadius: BorderRadius.circular(16),
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Text(
|
color: Colors.black54,
|
||||||
'${currentIndex + 1}/${widget.imageUrls.length}',
|
borderRadius: BorderRadius.circular(16),
|
||||||
style: TextStyle(
|
),
|
||||||
color: Colors.white,
|
child: Text(
|
||||||
fontWeight: FontWeight.bold,
|
'${currentIndex + 1}/${widget.imageUrls.length}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Perhatian',
|
'Perhatian',
|
||||||
'Pilih jam mulai terlebih dahulu',
|
'Pilih jam mulai terlebih dahulu',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: AppColors.warning,
|
backgroundColor: AppColors.warning,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -102,25 +102,25 @@ class PembayaranSewaView extends GetView<PembayaranSewaController> {
|
|||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
final updatedAtStr =
|
final updatedAtStr =
|
||||||
controller.orderDetails.value['updated_at'];
|
controller.orderDetails.value['updated_at'];
|
||||||
print('DEBUG status: ' + status);
|
debugPrint('DEBUG status (batas waktu): $status');
|
||||||
print(
|
debugPrint(
|
||||||
'DEBUG updated_at (raw): ' +
|
'DEBUG updated_at batas waktu (raw): ${updatedAtStr?.toString() ?? 'NULL'}',
|
||||||
(updatedAtStr?.toString() ?? 'NULL'),
|
|
||||||
);
|
);
|
||||||
if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) {
|
if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) {
|
||||||
try {
|
try {
|
||||||
final updatedAt = DateTime.parse(updatedAtStr);
|
final updatedAt = DateTime.parse(updatedAtStr);
|
||||||
print(
|
debugPrint(
|
||||||
'DEBUG updated_at (parsed): ' +
|
'DEBUG updated_at batas waktu (parsed): ${updatedAt.toIso8601String()}',
|
||||||
updatedAt.toIso8601String(),
|
|
||||||
);
|
);
|
||||||
return CountdownTimerWidget(updatedAt: updatedAt);
|
return CountdownTimerWidget(updatedAt: updatedAt);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ERROR parsing updated_at: ' + e.toString());
|
debugPrint('ERROR parsing updated_at batas waktu: $e');
|
||||||
return Text(
|
// Fallback to current time if parsing fails
|
||||||
'Format tanggal salah',
|
final now = DateTime.now();
|
||||||
style: TextStyle(color: Colors.red),
|
debugPrint(
|
||||||
|
'Using current time as fallback for batas waktu: ${now.toIso8601String()}',
|
||||||
);
|
);
|
||||||
|
return CountdownTimerWidget(updatedAt: now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
@ -322,25 +322,25 @@ class PembayaranSewaView extends GetView<PembayaranSewaController> {
|
|||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
final updatedAtStr =
|
final updatedAtStr =
|
||||||
controller.orderDetails.value['updated_at'];
|
controller.orderDetails.value['updated_at'];
|
||||||
print('DEBUG status: ' + status);
|
debugPrint('DEBUG status (batas waktu): $status');
|
||||||
print(
|
debugPrint(
|
||||||
'DEBUG updated_at (raw): ' +
|
'DEBUG updated_at batas waktu (raw): ${updatedAtStr?.toString() ?? 'NULL'}',
|
||||||
(updatedAtStr?.toString() ?? 'NULL'),
|
|
||||||
);
|
);
|
||||||
if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) {
|
if (status == 'MENUNGGU PEMBAYARAN' && updatedAtStr != null) {
|
||||||
try {
|
try {
|
||||||
final updatedAt = DateTime.parse(updatedAtStr);
|
final updatedAt = DateTime.parse(updatedAtStr);
|
||||||
print(
|
debugPrint(
|
||||||
'DEBUG updated_at (parsed): ' +
|
'DEBUG updated_at batas waktu (parsed): ${updatedAt.toIso8601String()}',
|
||||||
updatedAt.toIso8601String(),
|
|
||||||
);
|
);
|
||||||
return CountdownTimerWidget(updatedAt: updatedAt);
|
return CountdownTimerWidget(updatedAt: updatedAt);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ERROR parsing updated_at: ' + e.toString());
|
debugPrint('ERROR parsing updated_at batas waktu: $e');
|
||||||
return Text(
|
// Fallback to current time if parsing fails
|
||||||
'Format tanggal salah',
|
final now = DateTime.now();
|
||||||
style: TextStyle(color: Colors.red),
|
debugPrint(
|
||||||
|
'Using current time as fallback for batas waktu: ${now.toIso8601String()}',
|
||||||
);
|
);
|
||||||
|
return CountdownTimerWidget(updatedAt: now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
@ -363,7 +363,7 @@ class PembayaranSewaView extends GetView<PembayaranSewaController> {
|
|||||||
'step': 0,
|
'step': 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Memeriksa Pembayaran',
|
'title': 'Periksa Pembayaran',
|
||||||
'description': 'Pembayaran sedang diverifikasi oleh petugas',
|
'description': 'Pembayaran sedang diverifikasi oleh petugas',
|
||||||
'icon': Icons.receipt_long,
|
'icon': Icons.receipt_long,
|
||||||
'step': 1,
|
'step': 1,
|
||||||
@ -381,8 +381,8 @@ class PembayaranSewaView extends GetView<PembayaranSewaController> {
|
|||||||
'step': 3,
|
'step': 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Pengembalian',
|
'title': 'Dikembalikan',
|
||||||
'description': 'Proses pengembalian aset sewa',
|
'description': 'Aset sudah dikembalikan',
|
||||||
'icon': Icons.assignment_return,
|
'icon': Icons.assignment_return,
|
||||||
'step': 4,
|
'step': 4,
|
||||||
},
|
},
|
||||||
@ -2585,9 +2585,8 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
print(
|
debugPrint(
|
||||||
'DEBUG [CountdownTimerWidget] updatedAt: ' +
|
'DEBUG [CountdownTimerWidget] updatedAt: ${widget.updatedAt.toIso8601String()}',
|
||||||
widget.updatedAt.toIso8601String(),
|
|
||||||
);
|
);
|
||||||
updateRemaining();
|
updateRemaining();
|
||||||
timer = Timer.periodic(
|
timer = Timer.periodic(
|
||||||
@ -2599,18 +2598,29 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
void updateRemaining() {
|
void updateRemaining() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final end = widget.updatedAt.add(const Duration(hours: 1));
|
final end = widget.updatedAt.add(const Duration(hours: 1));
|
||||||
|
|
||||||
|
debugPrint('Current time: $now');
|
||||||
|
debugPrint('Deadline: $end');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
remaining = end.difference(now);
|
remaining = end.difference(now);
|
||||||
|
debugPrint('Remaining time: ${remaining.inSeconds} seconds');
|
||||||
|
|
||||||
if (remaining.isNegative) {
|
if (remaining.isNegative) {
|
||||||
|
debugPrint('Countdown expired, setting to zero');
|
||||||
remaining = Duration.zero;
|
remaining = Duration.zero;
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
widget.onTimeout?.call();
|
if (widget.onTimeout != null) {
|
||||||
|
debugPrint('Calling onTimeout callback');
|
||||||
|
widget.onTimeout?.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
debugPrint('CountdownTimerWidget disposed');
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -2620,9 +2630,15 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
if (remaining.inSeconds <= 0) {
|
if (remaining.inSeconds <= 0) {
|
||||||
return Text('Waktu habis', style: TextStyle(color: Colors.red));
|
return Text('Waktu habis', style: TextStyle(color: Colors.red));
|
||||||
}
|
}
|
||||||
|
|
||||||
final h = remaining.inHours;
|
final h = remaining.inHours;
|
||||||
final m = remaining.inMinutes % 60;
|
final m = remaining.inMinutes % 60;
|
||||||
final s = remaining.inSeconds % 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(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -2636,7 +2652,7 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
Icon(Icons.timer_outlined, size: 14, color: Colors.red),
|
Icon(Icons.timer_outlined, size: 14, color: Colors.red),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Bayar dalam ${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}',
|
'Bayar dalam $timeString',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
@ -1524,7 +1524,7 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID aset tidak valid',
|
'ID aset tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1754,7 +1754,7 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID aset tidak valid',
|
'ID aset tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -1801,7 +1801,7 @@ class SewaAsetView extends GetView<SewaAsetController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID aset tidak valid',
|
'ID aset tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,19 @@ class WargaDashboardView extends GetView<WargaDashboardController> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final size = MediaQuery.of(context).size;
|
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(
|
return WillPopScope(
|
||||||
onWillPop: () async => false, // Prevent back navigation
|
onWillPop: () async => false, // Prevent back navigation
|
||||||
child: WargaLayout(
|
child: WargaLayout(
|
||||||
|
@ -80,7 +80,7 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat menyimpan foto profil',
|
'Terjadi kesalahan saat menyimpan foto profil',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -99,7 +99,7 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat menghapus foto profil',
|
'Terjadi kesalahan saat menghapus foto profil',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -124,7 +124,7 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Perubahan berhasil disimpan',
|
'Perubahan berhasil disimpan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -132,7 +132,7 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Terjadi kesalahan saat menyimpan perubahan',
|
'Terjadi kesalahan saat menyimpan perubahan',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -156,6 +156,9 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
: IconButton(
|
: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
isEditing.value = true;
|
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),
|
icon: const Icon(Icons.edit_outlined),
|
||||||
tooltip: 'Edit Profil',
|
tooltip: 'Edit Profil',
|
||||||
@ -365,14 +368,6 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
} else {
|
} else {
|
||||||
// Set avatar deleted flag
|
// Set avatar deleted flag
|
||||||
isAvatarDeleted.value = true;
|
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),
|
icon: const Icon(Icons.delete_outline, size: 16),
|
||||||
@ -468,163 +463,242 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
) {
|
) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// Section 1: Data Diri
|
// Section 1: Data Diri - Dengan desain yang lebih modern
|
||||||
Card(
|
Card(
|
||||||
elevation: 4,
|
elevation: 2,
|
||||||
|
shadowColor: AppColors.primary.withOpacity(0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
child: Padding(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(20),
|
decoration: BoxDecoration(
|
||||||
child: Column(
|
borderRadius: BorderRadius.circular(20),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
gradient: LinearGradient(
|
||||||
children: [
|
begin: Alignment.topLeft,
|
||||||
Row(
|
end: Alignment.bottomRight,
|
||||||
children: [
|
colors: [Colors.white, AppColors.primary.withOpacity(0.05)],
|
||||||
Icon(
|
),
|
||||||
Icons.person_rounded,
|
),
|
||||||
color: AppColors.primary,
|
child: Padding(
|
||||||
size: 22,
|
padding: const EdgeInsets.all(20),
|
||||||
),
|
child: Column(
|
||||||
const SizedBox(width: 10),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'Data Diri',
|
Row(
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 18,
|
Container(
|
||||||
fontWeight: FontWeight.bold,
|
padding: const EdgeInsets.all(10),
|
||||||
color: AppColors.primary,
|
decoration: BoxDecoration(
|
||||||
),
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
),
|
borderRadius: BorderRadius.circular(12),
|
||||||
if (isEditing) ...[
|
),
|
||||||
const Spacer(),
|
child: Icon(
|
||||||
Text(
|
Icons.person_rounded,
|
||||||
'Mode Edit',
|
color: AppColors.primary,
|
||||||
style: TextStyle(
|
size: 22,
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.orange.shade700,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 14),
|
||||||
Icon(
|
Column(
|
||||||
Icons.edit_note,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
color: Colors.orange.shade700,
|
children: [
|
||||||
size: 18,
|
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: 24),
|
||||||
const SizedBox(height: 20),
|
// Email - always read-only
|
||||||
// Email - always read-only
|
_buildInfoItemModern(
|
||||||
_buildInfoItemModern(
|
context,
|
||||||
context,
|
icon: Icons.email_rounded,
|
||||||
icon: Icons.email_rounded,
|
title: 'Email',
|
||||||
title: 'Email',
|
value: controller.userEmail.value,
|
||||||
value: controller.userEmail.value,
|
isVerified: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Nama Lengkap - editable
|
// Nama Lengkap - editable
|
||||||
_buildEditableInfoItem(
|
_buildEditableInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.person_rounded,
|
icon: Icons.person_rounded,
|
||||||
title: 'Nama Lengkap',
|
title: 'Nama Lengkap',
|
||||||
value: controller.userName.value,
|
value: controller.userName.value,
|
||||||
isEditing: isEditing,
|
isEditing: isEditing,
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Nomor Telepon - editable
|
// Nomor Telepon - editable
|
||||||
_buildEditableInfoItem(
|
_buildEditableInfoItem(
|
||||||
context,
|
context,
|
||||||
icon: Icons.phone_rounded,
|
icon: Icons.phone_rounded,
|
||||||
title: 'Nomor Telepon',
|
title: 'Nomor Telepon',
|
||||||
value: controller.userPhone.value,
|
value: controller.userPhone.value,
|
||||||
isEditing: isEditing,
|
isEditing: isEditing,
|
||||||
controller: phoneController,
|
controller: phoneController,
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Section 2: Informasi Warga
|
// Section 2: Informasi Warga - Dengan desain yang lebih modern
|
||||||
Card(
|
Card(
|
||||||
elevation: 4,
|
elevation: 2,
|
||||||
|
shadowColor: AppColors.accent.withOpacity(0.3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
child: Padding(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(20),
|
decoration: BoxDecoration(
|
||||||
child: Column(
|
borderRadius: BorderRadius.circular(20),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
gradient: LinearGradient(
|
||||||
children: [
|
begin: Alignment.topLeft,
|
||||||
Row(
|
end: Alignment.bottomRight,
|
||||||
children: [
|
colors: [Colors.white, AppColors.accent.withOpacity(0.05)],
|
||||||
Icon(
|
),
|
||||||
Icons.badge_rounded,
|
),
|
||||||
color: AppColors.primary,
|
child: Padding(
|
||||||
size: 22,
|
padding: const EdgeInsets.all(20),
|
||||||
),
|
child: Column(
|
||||||
const SizedBox(width: 10),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'Informasi Warga',
|
Row(
|
||||||
style: TextStyle(
|
children: [
|
||||||
fontSize: 18,
|
Container(
|
||||||
fontWeight: FontWeight.bold,
|
padding: const EdgeInsets.all(10),
|
||||||
color: AppColors.primary,
|
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: 16),
|
||||||
const SizedBox(height: 20),
|
_buildInfoItemModern(
|
||||||
_buildInfoItemModern(
|
context,
|
||||||
context,
|
icon: Icons.home_rounded,
|
||||||
icon: Icons.credit_card_rounded,
|
title: 'Alamat',
|
||||||
title: 'NIK',
|
value: controller.userAddress.value,
|
||||||
value: controller.userNik.value,
|
isMultiLine: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildInfoItemModern(
|
// Tampilkan RT/RW dan Kelurahan/Desa dalam dua baris terpisah
|
||||||
context,
|
_buildInfoItemModern(
|
||||||
icon: Icons.calendar_today_rounded,
|
context,
|
||||||
title: 'Tanggal Lahir',
|
icon: Icons.location_on_rounded,
|
||||||
value: controller.userTanggalLahir.value,
|
title: 'RT/RW',
|
||||||
),
|
value: controller.userRtRw.value,
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
_buildInfoItemModern(
|
const SizedBox(height: 16),
|
||||||
context,
|
_buildInfoItemModern(
|
||||||
icon: Icons.home_rounded,
|
context,
|
||||||
title: 'Alamat',
|
icon: Icons.location_city_rounded,
|
||||||
value: controller.userAddress.value,
|
title: 'Kelurahan/Desa',
|
||||||
isMultiLine: true,
|
value: controller.userKelurahanDesa.value,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildInfoItemModern(
|
_buildInfoItemModern(
|
||||||
context,
|
context,
|
||||||
icon: Icons.location_on_rounded,
|
icon: Icons.map_rounded,
|
||||||
title: 'RT/RW',
|
title: 'Kecamatan',
|
||||||
value: controller.userRtRw.value,
|
value: controller.userKecamatan.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<WargaDashboardController> {
|
|||||||
required String title,
|
required String title,
|
||||||
required String value,
|
required String value,
|
||||||
bool isMultiLine = false,
|
bool isMultiLine = false,
|
||||||
|
bool isImportant = false,
|
||||||
|
bool isVerified = false,
|
||||||
|
bool isCompact = false,
|
||||||
}) {
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.shade50,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: Colors.grey.shade200),
|
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(
|
child: Row(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
isMultiLine ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
isMultiLine ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: EdgeInsets.all(isCompact ? 6 : 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primary.withOpacity(0.1),
|
color:
|
||||||
|
isImportant
|
||||||
|
? AppColors.primary.withOpacity(0.15)
|
||||||
|
: AppColors.accent.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(10),
|
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),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
title,
|
children: [
|
||||||
style: TextStyle(
|
Text(
|
||||||
fontSize: 13,
|
title,
|
||||||
fontWeight: FontWeight.w500,
|
style: TextStyle(
|
||||||
color: Colors.grey.shade600,
|
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),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
value.isEmpty ? 'Tidak tersedia' : value,
|
value.isEmpty ? 'Tidak tersedia' : value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15,
|
fontSize: isCompact ? 14 : 15,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: isImportant ? FontWeight.w700 : FontWeight.w600,
|
||||||
color: Colors.grey.shade800,
|
color:
|
||||||
|
isImportant
|
||||||
|
? AppColors.primary
|
||||||
|
: value.isEmpty
|
||||||
|
? Colors.grey.shade400
|
||||||
|
: Colors.grey.shade800,
|
||||||
),
|
),
|
||||||
maxLines: isMultiLine ? 3 : 1,
|
maxLines: isMultiLine ? 3 : 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@ -685,6 +798,12 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (isImportant)
|
||||||
|
Icon(
|
||||||
|
Icons.priority_high,
|
||||||
|
size: 16,
|
||||||
|
color: AppColors.primary.withOpacity(0.7),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -821,7 +940,7 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Info',
|
'Info',
|
||||||
'Fitur Ubah Password akan segera tersedia',
|
'Fitur Ubah Password akan segera tersedia',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -933,4 +1052,54 @@ class WargaProfileView extends GetView<WargaDashboardController> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,8 +570,34 @@ class WargaSewaView extends GetView<WargaSewaController> {
|
|||||||
if ((rental['status'] ?? '').toString().toUpperCase() ==
|
if ((rental['status'] ?? '').toString().toUpperCase() ==
|
||||||
'MENUNGGU PEMBAYARAN' &&
|
'MENUNGGU PEMBAYARAN' &&
|
||||||
rental['updated_at'] != null)
|
rental['updated_at'] != null)
|
||||||
CountdownTimerWidget(
|
Builder(
|
||||||
updatedAt: DateTime.parse(rental['updated_at']),
|
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<CountdownTimerWidget> {
|
class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
||||||
late Duration remaining;
|
late Duration remaining;
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
debugPrint(
|
||||||
|
'CountdownTimerWidget initialized with updatedAt: ${widget.updatedAt}',
|
||||||
|
);
|
||||||
updateRemaining();
|
updateRemaining();
|
||||||
timer = Timer.periodic(
|
timer = Timer.periodic(
|
||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
(_) => updateRemaining(),
|
(_) => updateRemaining(),
|
||||||
);
|
);
|
||||||
print('DEBUG updated_at: ${widget.updatedAt}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRemaining() {
|
void updateRemaining() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final deadline = widget.updatedAt.add(const Duration(hours: 1));
|
final deadline = widget.updatedAt.add(const Duration(hours: 1));
|
||||||
|
|
||||||
|
debugPrint('Current time: $now');
|
||||||
|
debugPrint('Deadline: $deadline');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
remaining = deadline.difference(now);
|
remaining = deadline.difference(now);
|
||||||
|
debugPrint('Remaining time: ${remaining.inSeconds} seconds');
|
||||||
|
|
||||||
if (remaining.isNegative) {
|
if (remaining.isNegative) {
|
||||||
|
debugPrint('Countdown expired, setting to zero');
|
||||||
remaining = Duration.zero;
|
remaining = Duration.zero;
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
widget.onTimeout?.call();
|
if (widget.onTimeout != null) {
|
||||||
|
debugPrint('Calling onTimeout callback');
|
||||||
|
widget.onTimeout?.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
debugPrint('CountdownTimerWidget disposed');
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -1891,9 +1931,15 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
if (remaining.inSeconds <= 0) {
|
if (remaining.inSeconds <= 0) {
|
||||||
return Text('Waktu habis', style: TextStyle(color: Colors.red));
|
return Text('Waktu habis', style: TextStyle(color: Colors.red));
|
||||||
}
|
}
|
||||||
|
|
||||||
final h = remaining.inHours;
|
final h = remaining.inHours;
|
||||||
final m = remaining.inMinutes % 60;
|
final m = remaining.inMinutes % 60;
|
||||||
final s = remaining.inSeconds % 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(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -1907,7 +1953,7 @@ class _CountdownTimerWidgetState extends State<CountdownTimerWidget> {
|
|||||||
Icon(Icons.timer_outlined, size: 14, color: Colors.red),
|
Icon(Icons.timer_outlined, size: 14, color: Colors.red),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Bayar dalam ${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}',
|
'Bayar dalam $timeString',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
@ -41,7 +41,7 @@ class NavigationService extends GetxService {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID aset tidak valid',
|
'ID aset tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -63,7 +63,7 @@ class NavigationService extends GetxService {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID sewa tidak valid',
|
'ID sewa tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -87,7 +87,7 @@ class NavigationService extends GetxService {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'ID paket tidak valid',
|
'ID paket tidak valid',
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
@ -133,7 +133,7 @@ class NavigationService extends GetxService {
|
|||||||
void toProfile() {
|
void toProfile() {
|
||||||
debugPrint('🧭 Navigating to Profile');
|
debugPrint('🧭 Navigating to Profile');
|
||||||
setNavIndex(2); // Profile tab
|
setNavIndex(2); // Profile tab
|
||||||
Get.offNamed(Routes.PROFILE);
|
Get.offAllNamed(Routes.PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigasi ke dashboard sesuai role
|
/// Navigasi ke dashboard sesuai role
|
||||||
|
@ -48,10 +48,6 @@ void main() async {
|
|||||||
debugPrint('❌ Error initializing auth provider: $e');
|
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
|
// Register services yang akan digunakan secara global
|
||||||
ServiceManager.registerServices();
|
ServiceManager.registerServices();
|
||||||
|
|
||||||
|
@ -1,68 +1,68 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"info": {
|
||||||
{
|
"version": 1,
|
||||||
"size" : "16x16",
|
"author": "xcode"
|
||||||
"idiom" : "mac",
|
|
||||||
"filename" : "app_icon_16.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
},
|
||||||
{
|
"images": [
|
||||||
"size" : "16x16",
|
{
|
||||||
"idiom" : "mac",
|
"size": "16x16",
|
||||||
"filename" : "app_icon_32.png",
|
"idiom": "mac",
|
||||||
"scale" : "2x"
|
"filename": "app_icon_16.png",
|
||||||
},
|
"scale": "1x"
|
||||||
{
|
},
|
||||||
"size" : "32x32",
|
{
|
||||||
"idiom" : "mac",
|
"size": "16x16",
|
||||||
"filename" : "app_icon_32.png",
|
"idiom": "mac",
|
||||||
"scale" : "1x"
|
"filename": "app_icon_32.png",
|
||||||
},
|
"scale": "2x"
|
||||||
{
|
},
|
||||||
"size" : "32x32",
|
{
|
||||||
"idiom" : "mac",
|
"size": "32x32",
|
||||||
"filename" : "app_icon_64.png",
|
"idiom": "mac",
|
||||||
"scale" : "2x"
|
"filename": "app_icon_32.png",
|
||||||
},
|
"scale": "1x"
|
||||||
{
|
},
|
||||||
"size" : "128x128",
|
{
|
||||||
"idiom" : "mac",
|
"size": "32x32",
|
||||||
"filename" : "app_icon_128.png",
|
"idiom": "mac",
|
||||||
"scale" : "1x"
|
"filename": "app_icon_64.png",
|
||||||
},
|
"scale": "2x"
|
||||||
{
|
},
|
||||||
"size" : "128x128",
|
{
|
||||||
"idiom" : "mac",
|
"size": "128x128",
|
||||||
"filename" : "app_icon_256.png",
|
"idiom": "mac",
|
||||||
"scale" : "2x"
|
"filename": "app_icon_128.png",
|
||||||
},
|
"scale": "1x"
|
||||||
{
|
},
|
||||||
"size" : "256x256",
|
{
|
||||||
"idiom" : "mac",
|
"size": "128x128",
|
||||||
"filename" : "app_icon_256.png",
|
"idiom": "mac",
|
||||||
"scale" : "1x"
|
"filename": "app_icon_256.png",
|
||||||
},
|
"scale": "2x"
|
||||||
{
|
},
|
||||||
"size" : "256x256",
|
{
|
||||||
"idiom" : "mac",
|
"size": "256x256",
|
||||||
"filename" : "app_icon_512.png",
|
"idiom": "mac",
|
||||||
"scale" : "2x"
|
"filename": "app_icon_256.png",
|
||||||
},
|
"scale": "1x"
|
||||||
{
|
},
|
||||||
"size" : "512x512",
|
{
|
||||||
"idiom" : "mac",
|
"size": "256x256",
|
||||||
"filename" : "app_icon_512.png",
|
"idiom": "mac",
|
||||||
"scale" : "1x"
|
"filename": "app_icon_512.png",
|
||||||
},
|
"scale": "2x"
|
||||||
{
|
},
|
||||||
"size" : "512x512",
|
{
|
||||||
"idiom" : "mac",
|
"size": "512x512",
|
||||||
"filename" : "app_icon_1024.png",
|
"idiom": "mac",
|
||||||
"scale" : "2x"
|
"filename": "app_icon_512.png",
|
||||||
}
|
"scale": "1x"
|
||||||
],
|
},
|
||||||
"info" : {
|
{
|
||||||
"version" : 1,
|
"size": "512x512",
|
||||||
"author" : "xcode"
|
"idiom": "mac",
|
||||||
}
|
"filename": "app_icon_1024.png",
|
||||||
}
|
"scale": "2x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.8 KiB |
48
pubspec.lock
@ -33,6 +33,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.7"
|
version: "4.0.7"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -97,6 +105,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -270,6 +294,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.5"
|
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:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -509,6 +541,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.7"
|
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:
|
jwt_decode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1146,6 +1186,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
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:
|
yet_another_json_isolate:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
22
pubspec.yaml
@ -72,6 +72,28 @@ dev_dependencies:
|
|||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^5.0.0
|
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
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
BIN
web/favicon.png
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 67 KiB |
@ -3,8 +3,8 @@
|
|||||||
"short_name": "bumrent_app",
|
"short_name": "bumrent_app",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#0175C2",
|
"background_color": "#ffffff",
|
||||||
"theme_color": "#0175C2",
|
"theme_color": "#ffffff",
|
||||||
"description": "A new Flutter project.",
|
"description": "A new Flutter project.",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
@ -32,4 +32,4 @@
|
|||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 1.9 KiB |