Feat: done for features details passenger
This commit is contained in:
parent
986ff5445e
commit
9c330514a7
|
@ -680,7 +680,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-05-15T05:07:55.383919Z",
|
"generated": "2025-05-16T07:28:56.076101Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// ignore_for_file: deprecated_member_use
|
// ignore_for_file: deprecated_member_use
|
||||||
|
import 'dart:developer';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:e_porter/domain/repositories/profil_repository.dart';
|
import 'package:e_porter/domain/repositories/profil_repository.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import '../../_core/service/logger_service.dart';
|
|
||||||
import '../../domain/models/user_entity.dart';
|
import '../../domain/models/user_entity.dart';
|
||||||
|
|
||||||
class ProfilRepositoryImpl implements ProfilRepository {
|
class ProfilRepositoryImpl implements ProfilRepository {
|
||||||
|
@ -17,7 +16,7 @@ class ProfilRepositoryImpl implements ProfilRepository {
|
||||||
try {
|
try {
|
||||||
DocumentReference docRef =
|
DocumentReference docRef =
|
||||||
await _firestore.collection('users').doc(userId).collection('passenger').add(passenger.toMap());
|
await _firestore.collection('users').doc(userId).collection('passenger').add(passenger.toMap());
|
||||||
logger.d("Passenger doc id: ${docRef.id}");
|
log("Passenger doc id: ${docRef.id}");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -27,12 +26,30 @@ class ProfilRepositoryImpl implements ProfilRepository {
|
||||||
Future<List<PassengerModel>> getPassengerById(String userId) async {
|
Future<List<PassengerModel>> getPassengerById(String userId) async {
|
||||||
try {
|
try {
|
||||||
QuerySnapshot querySnapshot = await _firestore.collection('users').doc(userId).collection('passenger').get();
|
QuerySnapshot querySnapshot = await _firestore.collection('users').doc(userId).collection('passenger').get();
|
||||||
return querySnapshot.docs.map((doc) => PassengerModel.fromMap(doc.data() as Map<String, dynamic>)).toList();
|
return querySnapshot.docs
|
||||||
|
.map((doc) => PassengerModel.fromMap({
|
||||||
|
'id': doc.id,
|
||||||
|
...doc.data() as Map<String, dynamic>
|
||||||
|
}))
|
||||||
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deletePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _firestore.collection('users').doc(userId).collection('passenger').doc(passengerId).delete();
|
||||||
|
log("Passenger deleted with ID: $passengerId");
|
||||||
|
} catch (e) {
|
||||||
|
log("Error deleting passenger: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
Future<UserData> getUserById(String userId) async {
|
Future<UserData> getUserById(String userId) async {
|
||||||
try {
|
try {
|
||||||
|
@ -46,6 +63,26 @@ class ProfilRepositoryImpl implements ProfilRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updatePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _firestore
|
||||||
|
.collection('users')
|
||||||
|
.doc(userId)
|
||||||
|
.collection('passenger')
|
||||||
|
.doc(passengerId)
|
||||||
|
.update(passenger.toMap());
|
||||||
|
log("Passenger updated with ID: $passengerId");
|
||||||
|
} catch (e) {
|
||||||
|
log("Error updating passenger: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> changePassword({
|
Future<void> changePassword({
|
||||||
required String oldPassword,
|
required String oldPassword,
|
||||||
|
|
|
@ -15,6 +15,8 @@ class ProfilBinding extends Bindings {
|
||||||
Get.lazyPut(() => GetUserByIdUseCase(Get.find()));
|
Get.lazyPut(() => GetUserByIdUseCase(Get.find()));
|
||||||
Get.lazyPut(() => ChangePasswordUseCase(Get.find()));
|
Get.lazyPut(() => ChangePasswordUseCase(Get.find()));
|
||||||
Get.lazyPut(() => ChangePhoneUseCase(Get.find()));
|
Get.lazyPut(() => ChangePhoneUseCase(Get.find()));
|
||||||
|
Get.lazyPut(() => DeletePassengerUseCase(Get.find()));
|
||||||
|
Get.lazyPut(() => UpdatePassengerUseCase(Get.find()));
|
||||||
// Get.lazyPut(() => ChangeEmailUseCase(Get.find()));
|
// Get.lazyPut(() => ChangeEmailUseCase(Get.find()));
|
||||||
|
|
||||||
Get.lazyPut(
|
Get.lazyPut(
|
||||||
|
@ -24,7 +26,8 @@ class ProfilBinding extends Bindings {
|
||||||
getUserByIdUseCase: Get.find(),
|
getUserByIdUseCase: Get.find(),
|
||||||
changePasswordUseCase: Get.find(),
|
changePasswordUseCase: Get.find(),
|
||||||
changePhoneUseCase: Get.find(),
|
changePhoneUseCase: Get.find(),
|
||||||
// changeEmailUseCase: Get.find(),
|
deletePassengerUseCase: Get.find(),
|
||||||
|
updatePassengerUseCase: Get.find(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,12 +116,14 @@ class UserData {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PassengerModel {
|
class PassengerModel {
|
||||||
|
final String? id; // Added document ID
|
||||||
final String typeId;
|
final String typeId;
|
||||||
final String noId;
|
final String noId;
|
||||||
final String name;
|
final String name;
|
||||||
final String gender;
|
final String gender;
|
||||||
|
|
||||||
PassengerModel({
|
PassengerModel({
|
||||||
|
this.id,
|
||||||
required this.typeId,
|
required this.typeId,
|
||||||
required this.noId,
|
required this.noId,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -139,6 +141,7 @@ class PassengerModel {
|
||||||
|
|
||||||
factory PassengerModel.fromMap(Map<String, dynamic> map) {
|
factory PassengerModel.fromMap(Map<String, dynamic> map) {
|
||||||
return PassengerModel(
|
return PassengerModel(
|
||||||
|
id: map['id'],
|
||||||
typeId: map['typeId'] ?? '',
|
typeId: map['typeId'] ?? '',
|
||||||
noId: map['noId'] ?? '',
|
noId: map['noId'] ?? '',
|
||||||
name: map['name'] ?? '',
|
name: map['name'] ?? '',
|
||||||
|
|
|
@ -10,6 +10,17 @@ abstract class ProfilRepository {
|
||||||
|
|
||||||
Future<UserData> getUserById(String userId);
|
Future<UserData> getUserById(String userId);
|
||||||
|
|
||||||
|
Future<void> deletePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> updatePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
});
|
||||||
|
|
||||||
Future<void> changePassword({
|
Future<void> changePassword({
|
||||||
required String oldPassword,
|
required String oldPassword,
|
||||||
required String newPassword,
|
required String newPassword,
|
||||||
|
|
|
@ -64,6 +64,38 @@ class ChangePhoneUseCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DeletePassengerUseCase {
|
||||||
|
final ProfilRepository profilRepository;
|
||||||
|
DeletePassengerUseCase(this.profilRepository);
|
||||||
|
|
||||||
|
Future<void> call({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
}) {
|
||||||
|
return profilRepository.deletePassenger(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passengerId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdatePassengerUseCase {
|
||||||
|
final ProfilRepository profilRepository;
|
||||||
|
UpdatePassengerUseCase(this.profilRepository);
|
||||||
|
|
||||||
|
Future<void> call({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
}) {
|
||||||
|
return profilRepository.updatePassenger(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passengerId,
|
||||||
|
passenger: passenger,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// class ChangeEmailUseCase {
|
// class ChangeEmailUseCase {
|
||||||
// final ProfilRepository profilRepository;
|
// final ProfilRepository profilRepository;
|
||||||
// ChangeEmailUseCase(this.profilRepository);
|
// ChangeEmailUseCase(this.profilRepository);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:e_porter/_core/service/logger_service.dart';
|
|
||||||
import 'package:e_porter/_core/service/preferences_service.dart';
|
import 'package:e_porter/_core/service/preferences_service.dart';
|
||||||
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
|
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
@ -14,6 +13,8 @@ class ProfilController extends GetxController {
|
||||||
final GetUserByIdUseCase getUserByIdUseCase;
|
final GetUserByIdUseCase getUserByIdUseCase;
|
||||||
final ChangePasswordUseCase changePasswordUseCase;
|
final ChangePasswordUseCase changePasswordUseCase;
|
||||||
final ChangePhoneUseCase changePhoneUseCase;
|
final ChangePhoneUseCase changePhoneUseCase;
|
||||||
|
final DeletePassengerUseCase deletePassengerUseCase;
|
||||||
|
final UpdatePassengerUseCase updatePassengerUseCase;
|
||||||
// final ChangeEmailUseCase changeEmailUseCase;
|
// final ChangeEmailUseCase changeEmailUseCase;
|
||||||
|
|
||||||
var passengerList = <PassengerModel>[].obs;
|
var passengerList = <PassengerModel>[].obs;
|
||||||
|
@ -21,6 +22,8 @@ class ProfilController extends GetxController {
|
||||||
var isLoading = false.obs;
|
var isLoading = false.obs;
|
||||||
var isChangingPassword = false.obs;
|
var isChangingPassword = false.obs;
|
||||||
var isChangingPhone = false.obs;
|
var isChangingPhone = false.obs;
|
||||||
|
var isDeletingPassenger = false.obs;
|
||||||
|
var isUpdatingPassenger = false.obs;
|
||||||
// var isChangingEmail = false.obs;
|
// var isChangingEmail = false.obs;
|
||||||
|
|
||||||
ProfilController({
|
ProfilController({
|
||||||
|
@ -29,6 +32,8 @@ class ProfilController extends GetxController {
|
||||||
required this.getUserByIdUseCase,
|
required this.getUserByIdUseCase,
|
||||||
required this.changePasswordUseCase,
|
required this.changePasswordUseCase,
|
||||||
required this.changePhoneUseCase,
|
required this.changePhoneUseCase,
|
||||||
|
required this.deletePassengerUseCase,
|
||||||
|
required this.updatePassengerUseCase,
|
||||||
// required this.changeEmailUseCase,
|
// required this.changeEmailUseCase,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,7 +102,50 @@ class ProfilController extends GetxController {
|
||||||
List<PassengerModel> list = await getPassengerByIdUseCase(userId);
|
List<PassengerModel> list = await getPassengerByIdUseCase(userId);
|
||||||
passengerList.assignAll(list);
|
passengerList.assignAll(list);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.e("Error fetching passengers: $e");
|
log("Error fetching passengers: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deletePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
}) async {
|
||||||
|
isDeletingPassenger.value = true;
|
||||||
|
try {
|
||||||
|
await deletePassengerUseCase(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passengerId,
|
||||||
|
);
|
||||||
|
SnackbarHelper.showSuccess("Berhasil", "Data penumpang berhasil dihapus.");
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
log("Error deleting passenger: $e");
|
||||||
|
SnackbarHelper.showError("Gagal", "Terjadi kesalahan saat menghapus data penumpang.");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isDeletingPassenger.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updatePassenger({
|
||||||
|
required String userId,
|
||||||
|
required String passengerId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
}) async {
|
||||||
|
isUpdatingPassenger.value = true;
|
||||||
|
try {
|
||||||
|
await updatePassengerUseCase(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passengerId,
|
||||||
|
passenger: passenger,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
log("Error updating passenger: $e");
|
||||||
|
SnackbarHelper.showError("Gagal", "Terjadi kesalahan saat mengupdate data penumpang.");
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isUpdatingPassenger.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,21 @@
|
||||||
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
|
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
|
||||||
import 'package:e_porter/_core/component/button/button_fill.dart';
|
import 'package:e_porter/_core/component/button/button_fill.dart';
|
||||||
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
|
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
|
||||||
|
import 'package:e_porter/_core/component/text_field/dropdown/dropdown_component.dart';
|
||||||
|
import 'package:e_porter/_core/component/text_field/text_input/text_field_component.dart';
|
||||||
import 'package:e_porter/_core/constants/colors.dart';
|
import 'package:e_porter/_core/constants/colors.dart';
|
||||||
import 'package:e_porter/_core/constants/typography.dart';
|
import 'package:e_porter/_core/constants/typography.dart';
|
||||||
|
import 'package:e_porter/_core/service/preferences_service.dart';
|
||||||
|
import 'package:e_porter/_core/utils/formatter/uppercase_helper.dart';
|
||||||
|
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
|
||||||
|
import 'package:e_porter/_core/validators/validators.dart';
|
||||||
|
import 'package:e_porter/domain/models/user_entity.dart';
|
||||||
|
import 'package:e_porter/presentation/controllers/profil_controller.dart';
|
||||||
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
|
||||||
|
|
||||||
class PassengerListScreen extends StatefulWidget {
|
class PassengerListScreen extends StatefulWidget {
|
||||||
const PassengerListScreen({super.key});
|
const PassengerListScreen({super.key});
|
||||||
|
@ -19,6 +26,104 @@ class PassengerListScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PassengerListScreenState extends State<PassengerListScreen> {
|
class _PassengerListScreenState extends State<PassengerListScreen> {
|
||||||
|
final ValueNotifier<String> selectedGender = ValueNotifier<String>('Laki-laki');
|
||||||
|
final _profileController = Get.find<ProfilController>();
|
||||||
|
|
||||||
|
// Controllers for edit form
|
||||||
|
final TextEditingController _nameController = TextEditingController();
|
||||||
|
final TextEditingController _noIdController = TextEditingController();
|
||||||
|
String selectedTypeId = 'KTP';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
_noIdController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadPassengers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadPassengers() async {
|
||||||
|
final userData = await PreferencesService.getUserData();
|
||||||
|
if (userData == null || userData.uid.isEmpty) {
|
||||||
|
SnackbarHelper.showError('Error', 'User ID tidak ditemukan, silakan login kembali');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final userId = userData.uid;
|
||||||
|
await _profileController.fetchPassangerById(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePassenger(PassengerModel passenger) async {
|
||||||
|
if (passenger.id == null) {
|
||||||
|
SnackbarHelper.showError('Error', 'ID Penumpang tidak valid');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final userData = await PreferencesService.getUserData();
|
||||||
|
if (userData == null || userData.uid.isEmpty) {
|
||||||
|
SnackbarHelper.showError('Error', 'User ID tidak ditemukan, silakan login kembali');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final userId = userData.uid;
|
||||||
|
final success = await _profileController.deletePassenger(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passenger.id!,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await _loadPassengers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updatePassenger(PassengerModel passenger) async {
|
||||||
|
if (passenger.id == null) {
|
||||||
|
SnackbarHelper.showError('Error', 'ID Penumpang tidak valid');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_nameController.text.isEmpty) {
|
||||||
|
SnackbarHelper.showError('Error', 'Nama tidak boleh kosong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_noIdController.text.isEmpty) {
|
||||||
|
SnackbarHelper.showError('Error', 'Nomor ID tidak boleh kosong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final userData = await PreferencesService.getUserData();
|
||||||
|
if (userData == null || userData.uid.isEmpty) {
|
||||||
|
SnackbarHelper.showError('Error', 'User ID tidak ditemukan, silakan login kembali');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final userId = userData.uid;
|
||||||
|
|
||||||
|
final updatedPassenger = PassengerModel(
|
||||||
|
id: passenger.id,
|
||||||
|
name: _nameController.text,
|
||||||
|
typeId: selectedTypeId,
|
||||||
|
noId: _noIdController.text,
|
||||||
|
gender: selectedGender.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
final success = await _profileController.updatePassenger(
|
||||||
|
userId: userId,
|
||||||
|
passengerId: passenger.id!,
|
||||||
|
passenger: updatedPassenger,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
SnackbarHelper.showSuccess('Berhasil', 'Data penumpang berhasil diperbarui');
|
||||||
|
await _loadPassengers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -33,65 +138,370 @@ class _PassengerListScreenState extends State<PassengerListScreen> {
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
child: ListView.builder(
|
child: Obx(
|
||||||
itemCount: 2,
|
() {
|
||||||
itemBuilder: (context, index) {
|
if (_profileController.passengerList.isEmpty) {
|
||||||
return Padding(
|
return Center(
|
||||||
padding: EdgeInsets.only(bottom: 16.h),
|
child: TypographyStyles.body(
|
||||||
child: _buildCardItem(
|
'Bulum ada penumpang',
|
||||||
value: 'Name',
|
color: GrayColors.gray400,
|
||||||
onTap: () {
|
fontWeight: FontWeight.w500,
|
||||||
// Get.toNamed(Routes.passengerDetail);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
)),
|
return ListView.builder(
|
||||||
|
itemCount: _profileController.passengerList.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final passenger = _profileController.passengerList[index];
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 16.h),
|
||||||
|
child: _buildPassengerCard(passenger),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: CustomeShadowCotainner(
|
bottomNavigationBar: CustomeShadowCotainner(
|
||||||
child: ButtonFill(
|
child: ButtonFill(
|
||||||
text: 'Tambah Penumpang',
|
text: 'Tambah Penumpang',
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Get.toNamed(Routes.ADDPASSENGER);
|
final result = await Get.toNamed(Routes.ADDPASSENGER);
|
||||||
|
if (result == true) {
|
||||||
|
await _loadPassengers();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCardItem({required String value, required VoidCallback onTap}) {
|
Widget _buildPassengerCard(PassengerModel passenger) {
|
||||||
return ZoomTapAnimation(
|
return Container(
|
||||||
child: GestureDetector(
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
|
||||||
onTap: onTap,
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
color: Colors.white,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
decoration: BoxDecoration(
|
border: Border.all(color: GrayColors.gray200),
|
||||||
color: Colors.white,
|
),
|
||||||
borderRadius: BorderRadius.circular(10.r),
|
child: Row(
|
||||||
border: Border.all(color: GrayColors.gray200),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.caption(
|
||||||
|
'Dewasa (${passenger.gender})',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: GrayColors.gray500,
|
||||||
|
),
|
||||||
|
TypographyStyles.body('${passenger.name}', color: GrayColors.gray800),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
icon: Icon(Icons.more_vert_outlined, color: GrayColors.gray500, size: 24.w),
|
||||||
|
color: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
|
),
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'delete') {
|
||||||
|
_showDeleteConfirmation(passenger);
|
||||||
|
} else if (value == 'details') {
|
||||||
|
Get.bottomSheet(
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10.r),
|
||||||
|
topRight: Radius.circular(10.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildDetailContent(passenger),
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
} else if (value == 'edit') {
|
||||||
|
Get.bottomSheet(
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10.r),
|
||||||
|
topRight: Radius.circular(10.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildEditContent(passenger),
|
||||||
|
),
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'details',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.person, size: 18, color: GrayColors.gray600),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
TypographyStyles.caption('Lihat Detail', color: GrayColors.gray600),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'edit',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.edit, size: 18, color: GrayColors.gray600),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
TypographyStyles.caption('Edit', color: GrayColors.gray600),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.delete, size: 18, color: Colors.red),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
TypographyStyles.caption('Hapus', color: Colors.red),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailContent(PassengerModel passenger) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.height * 0.070,
|
||||||
|
height: 6.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: GrayColors.gray200,
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 20.h, top: 16.h),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.h6('Detail Penumpang', color: GrayColors.gray800),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TypographyStyles.caption('Nama Penumpang', color: GrayColors.gray400, fontWeight: FontWeight.w400),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6.h),
|
||||||
|
child: TypographyStyles.body('${passenger.name}', color: GrayColors.gray800),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
TypographyStyles.caption('Identitas', color: GrayColors.gray400, fontWeight: FontWeight.w400),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6.h),
|
||||||
|
child: TypographyStyles.body('${passenger.typeId} - ${passenger.noId}', color: GrayColors.gray800),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
TypographyStyles.caption('Jenis Kelamin', color: GrayColors.gray400, fontWeight: FontWeight.w400),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6.h),
|
||||||
|
child: TypographyStyles.body('${passenger.gender}', color: GrayColors.gray800),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDeleteConfirmation(PassengerModel passenger) {
|
||||||
|
Get.dialog(
|
||||||
|
AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
title: TypographyStyles.h6('Konfirmasi', color: GrayColors.gray800),
|
||||||
|
content: TypographyStyles.body(
|
||||||
|
'Apakah Anda yakin ingin menghapus data penumpang ${passenger.name}?',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: GrayColors.gray500,
|
||||||
|
maxlines: 3,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: TypographyStyles.body('Batal', color: GrayColors.gray600),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Get.back();
|
||||||
|
await _deletePassenger(passenger);
|
||||||
|
},
|
||||||
|
child: TypographyStyles.body('Hapus', color: RedColors.red500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEditContent(PassengerModel passenger) {
|
||||||
|
_nameController.text = passenger.name;
|
||||||
|
_noIdController.text = passenger.noId;
|
||||||
|
selectedTypeId = passenger.typeId;
|
||||||
|
selectedGender.value = passenger.gender;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.height * 0.070,
|
||||||
|
height: 6.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: GrayColors.gray200,
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 20.h, top: 16.h),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.h6('Edit Penumpang', color: GrayColors.gray800),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TypographyStyles.body('Nama Lengkap', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
SizedBox(height: 16.w),
|
||||||
|
TextFieldComponent(
|
||||||
|
controller: _nameController,
|
||||||
|
hintText: 'Masukkan nama lengkap',
|
||||||
|
validators: Validators.validatorName,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
|
||||||
|
UpperCaseTextFormatter(),
|
||||||
|
],
|
||||||
|
textInputType: TextInputType.text,
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.caption('Dewasa', fontWeight: FontWeight.w400, color: GrayColors.gray500),
|
TypographyStyles.body('Tipe ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
TypographyStyles.body(value, color: GrayColors.gray800)
|
SizedBox(height: 16.h),
|
||||||
|
DropdownComponent(
|
||||||
|
hintText: "Pilih jenis dokument",
|
||||||
|
items: ['KTP', 'Pasport'],
|
||||||
|
value: selectedTypeId,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedTypeId = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SvgPicture.asset(
|
SizedBox(width: 16.w),
|
||||||
'assets/icons/ic_more_than.svg',
|
Expanded(
|
||||||
color: GrayColors.gray500,
|
child: Column(
|
||||||
width: 20.w,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
height: 20.h,
|
children: [
|
||||||
),
|
TypographyStyles.body('No ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
SizedBox(height: 16.h),
|
||||||
|
TextFieldComponent(
|
||||||
|
controller: _noIdController,
|
||||||
|
hintText: 'Masukkan ID',
|
||||||
|
validators: Validators.validatorNoID,
|
||||||
|
textInputType: TextInputType.number,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
LengthLimitingTextInputFormatter(16),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 20.w),
|
||||||
|
TypographyStyles.body('Jenis Kelamin', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildRadioButton(context, label: 'Laki-laki', value: 'Laki-laki'),
|
||||||
|
SizedBox(width: 40.h),
|
||||||
|
_buildRadioButton(context, label: 'Perempuan', value: 'Perempuan')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
ButtonFill(
|
||||||
|
text: 'Simpan',
|
||||||
|
textColor: Colors.white,
|
||||||
|
onTap: () async {
|
||||||
|
bool isDataChanged = passenger.name != _nameController.text ||
|
||||||
|
passenger.typeId != selectedTypeId ||
|
||||||
|
passenger.noId != _noIdController.text ||
|
||||||
|
passenger.gender != selectedGender.value;
|
||||||
|
|
||||||
|
if (!isDataChanged) {
|
||||||
|
SnackbarHelper.showInfo('Info', 'Tidak ada perubahan data yang dilakukan');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Get.isBottomSheetOpen ?? false) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
await _updatePassenger(passenger);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRadioButton(
|
||||||
|
BuildContext context, {
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return ValueListenableBuilder<String>(
|
||||||
|
valueListenable: selectedGender,
|
||||||
|
builder: (context, selected, child) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Radio<String>(
|
||||||
|
value: value,
|
||||||
|
groupValue: selected,
|
||||||
|
activeColor: PrimaryColors.primary800,
|
||||||
|
onChanged: (val) {
|
||||||
|
selectedGender.value = val!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
TypographyStyles.body(label, color: GrayColors.gray800, fontWeight: FontWeight.w500)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@ class AppRoutes {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.PASSENGERLIST,
|
name: Routes.PASSENGERLIST,
|
||||||
page: () => PassengerListScreen(),
|
page: () => PassengerListScreen(),
|
||||||
|
binding: ProfilBinding(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue