Feat: done post data for add_passenger_screen
This commit is contained in:
parent
430cce5f10
commit
e447c93584
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
#Wed Mar 12 19:14:57 WIB 2025
|
#Thu Mar 13 10:11:24 WIB 2025
|
||||||
base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex
|
base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex
|
||||||
base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex
|
base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex
|
||||||
base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex
|
base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex
|
||||||
|
|
|
@ -4,12 +4,14 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
class DropdownComponent extends StatefulWidget {
|
class DropdownComponent extends StatefulWidget {
|
||||||
|
final List<String> items;
|
||||||
final String? value;
|
final String? value;
|
||||||
final Function(String?) onChanged;
|
final Function(String?) onChanged;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
|
||||||
const DropdownComponent({
|
const DropdownComponent({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
required this.items,
|
||||||
this.value,
|
this.value,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
|
@ -20,9 +22,7 @@ class DropdownComponent extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DropdownComponentState extends State<DropdownComponent> {
|
class _DropdownComponentState extends State<DropdownComponent> {
|
||||||
final List<String> items = ["KTP", "Paspor"];
|
|
||||||
String? selectedValue;
|
String? selectedValue;
|
||||||
|
|
||||||
bool _isMenuOpen = false;
|
bool _isMenuOpen = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -39,7 +39,7 @@ class _DropdownComponentState extends State<DropdownComponent> {
|
||||||
child: DropdownButton2<String>(
|
child: DropdownButton2<String>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: selectedValue,
|
value: selectedValue,
|
||||||
items: items.map((String item) {
|
items: widget.items.map((String item) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: item,
|
value: item,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -77,7 +77,9 @@ class _DropdownComponentState extends State<DropdownComponent> {
|
||||||
padding: EdgeInsets.only(left: 10.w, right: 16.w, top: 4.h, bottom: 4.h),
|
padding: EdgeInsets.only(left: 10.w, right: 16.w, top: 4.h, bottom: 4.h),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10.r),
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
border: Border.all(width: 1.w, color: _isMenuOpen ? PrimaryColors.primary800 : GrayColors.gray200),
|
border: Border.all(
|
||||||
|
width: 1.w,
|
||||||
|
color: _isMenuOpen ? PrimaryColors.primary800 : GrayColors.gray200),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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 '../../../constants/colors.dart';
|
import '../../../constants/colors.dart';
|
||||||
|
@ -6,15 +7,27 @@ import '../../../constants/colors.dart';
|
||||||
class TextFieldComponent extends StatelessWidget {
|
class TextFieldComponent extends StatelessWidget {
|
||||||
final TextEditingController? controller;
|
final TextEditingController? controller;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
final String? Function(String?)? validators;
|
||||||
|
final TextInputType? textInputType;
|
||||||
|
final List<TextInputFormatter>? inputFormatters;
|
||||||
|
|
||||||
const TextFieldComponent({this.controller, required this.hintText});
|
const TextFieldComponent({
|
||||||
|
this.controller,
|
||||||
|
required this.hintText,
|
||||||
|
this.validators,
|
||||||
|
this.textInputType,
|
||||||
|
this.inputFormatters
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.r)),
|
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.r)),
|
||||||
child: TextField(
|
child: TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
validator: validators,
|
||||||
|
keyboardType: textInputType,
|
||||||
|
inputFormatters: inputFormatters,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefix: Padding(padding: EdgeInsets.symmetric(horizontal: 10.w)),
|
prefix: Padding(padding: EdgeInsets.symmetric(horizontal: 10.w)),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
|
|
@ -27,11 +27,21 @@ class GrayColors {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RedColors {
|
class RedColors {
|
||||||
|
static const red100 = Color(0xFFFDE8E8);
|
||||||
static const red200 = Color(0xFFFBD5D5);
|
static const red200 = Color(0xFFFBD5D5);
|
||||||
|
static const red300 = Color(0xFFF8B4B4);
|
||||||
|
static const red400 = Color(0xFFF98080);
|
||||||
|
static const red500 = Color(0xFFF05252);
|
||||||
static const red600 = Color(0xFFE02424);
|
static const red600 = Color(0xFFE02424);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GreenColors {
|
class GreenColors {
|
||||||
|
static const green50 = Color(0xFFE8F6F0);
|
||||||
static const green100 = Color(0xFFDEF7EC);
|
static const green100 = Color(0xFFDEF7EC);
|
||||||
|
static const green200 = Color(0xFFBCF0DA);
|
||||||
|
static const green300 = Color(0xFF84E1BC);
|
||||||
|
static const green400 = Color(0xFF31C48D);
|
||||||
static const green500 = Color(0xFF0E9F6E);
|
static const green500 = Color(0xFF0E9F6E);
|
||||||
|
static const green600 = Color(0xFF057A55);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
|
Future<bool> isNoIdUnique(String userId, String noId) async {
|
||||||
|
final querySnapshot = await FirebaseFirestore.instance
|
||||||
|
.collection('users')
|
||||||
|
.doc(userId)
|
||||||
|
.collection('passenger')
|
||||||
|
.where('noId', isEqualTo: noId)
|
||||||
|
.get();
|
||||||
|
return querySnapshot.docs.isEmpty;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
// Formatter untuk mengubah input menjadi uppercase
|
||||||
|
class UpperCaseTextFormatter extends TextInputFormatter {
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
|
||||||
|
return newValue.copyWith(
|
||||||
|
text: newValue.text.toUpperCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../constants/colors.dart';
|
||||||
|
|
||||||
|
class SnackbarHelper {
|
||||||
|
static void showSuccess(String title, String message) {
|
||||||
|
Get.snackbar(
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
backgroundColor: GreenColors.green500,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showError(String title, String message) {
|
||||||
|
Get.snackbar(
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showInfo(String title, String message) {
|
||||||
|
Get.snackbar(
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,4 +12,22 @@ class Validators {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String? validatorName(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama Lengkap tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? validatorNoID(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'No ID tidak boleh kosong';
|
||||||
|
} else if (value.length > 16) {
|
||||||
|
return 'No ID tidak boleh lebih dari 16 digit';
|
||||||
|
} else if (value.length < 16) {
|
||||||
|
return 'No ID kurang dari 16 digit';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,9 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||||
if (docSnapshot.exists) {
|
if (docSnapshot.exists) {
|
||||||
final data = docSnapshot.data();
|
final data = docSnapshot.data();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
return UserData.fromMap(data);
|
final userData = UserData.fromMap(data);
|
||||||
|
final updatedUserData = userData.copyWith(uid: docSnapshot.id);
|
||||||
|
return updatedUserData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:e_porter/domain/repositories/profil_repository.dart';
|
||||||
|
import '../../_core/service/logger_service.dart';
|
||||||
|
import '../../domain/models/user_entity.dart';
|
||||||
|
|
||||||
|
class ProfilRepositoryImpl implements ProfilRepository {
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createPassenger({
|
||||||
|
required String userId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
DocumentReference docRef = await _firestore
|
||||||
|
.collection('users')
|
||||||
|
.doc(userId)
|
||||||
|
.collection('passenger')
|
||||||
|
.add(passenger.toMap());
|
||||||
|
logger.d("Passenger doc id: ${docRef.id}");
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../data/repositories/profil_repository_impl.dart';
|
||||||
|
import '../../presentation/controllers/profil_controller.dart';
|
||||||
|
import '../repositories/profil_repository.dart';
|
||||||
|
import '../usecases/profil_usecase.dart';
|
||||||
|
|
||||||
|
class ProfilBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
// Injeksi repository
|
||||||
|
Get.lazyPut<ProfilRepository>(() => ProfilRepositoryImpl());
|
||||||
|
|
||||||
|
// Injeksi use case, menggunakan repository yang telah di-inject
|
||||||
|
Get.lazyPut(() => CreatePassengerUseCase(Get.find()));
|
||||||
|
|
||||||
|
// Injeksi controller, menggunakan use case yang sudah tersedia
|
||||||
|
Get.lazyPut(() => ProfilController(createPassengerUseCase: Get.find()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ class UserEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserData {
|
class UserData {
|
||||||
|
final String uid;
|
||||||
final String? tipeId;
|
final String? tipeId;
|
||||||
final String? noId;
|
final String? noId;
|
||||||
final String? name;
|
final String? name;
|
||||||
|
@ -24,6 +25,7 @@ class UserData {
|
||||||
final String? role;
|
final String? role;
|
||||||
|
|
||||||
UserData({
|
UserData({
|
||||||
|
required this.uid,
|
||||||
required this.tipeId,
|
required this.tipeId,
|
||||||
required this.noId,
|
required this.noId,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -50,6 +52,7 @@ class UserData {
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserData(
|
return UserData(
|
||||||
|
uid: map['uid'] ?? '',
|
||||||
tipeId: map['tipeId'] ?? '',
|
tipeId: map['tipeId'] ?? '',
|
||||||
noId: map['noId'] ?? '',
|
noId: map['noId'] ?? '',
|
||||||
name: map['name'] as String?,
|
name: map['name'] as String?,
|
||||||
|
@ -66,6 +69,7 @@ class UserData {
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
|
'uid': uid,
|
||||||
'tipeId': tipeId,
|
'tipeId': tipeId,
|
||||||
'noId': noId,
|
'noId': noId,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
@ -79,4 +83,66 @@ class UserData {
|
||||||
'role': role,
|
'role': role,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserData copyWith({
|
||||||
|
String? uid,
|
||||||
|
String? tipeId,
|
||||||
|
String? noId,
|
||||||
|
String? name,
|
||||||
|
String? email,
|
||||||
|
String? phone,
|
||||||
|
String? birthDate,
|
||||||
|
String? gender,
|
||||||
|
String? work,
|
||||||
|
String? city,
|
||||||
|
String? address,
|
||||||
|
String? role,
|
||||||
|
}) {
|
||||||
|
return UserData(
|
||||||
|
uid: uid ?? this.uid,
|
||||||
|
tipeId: tipeId ?? this.tipeId,
|
||||||
|
noId: noId ?? this.noId,
|
||||||
|
name: name ?? this.name,
|
||||||
|
email: email ?? this.email,
|
||||||
|
phone: phone ?? this.phone,
|
||||||
|
birthDate: birthDate ?? this.birthDate,
|
||||||
|
gender: gender ?? this.gender,
|
||||||
|
work: work ?? this.work,
|
||||||
|
city: city ?? this.city,
|
||||||
|
address: address ?? this.address,
|
||||||
|
role: role ?? this.role,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PassengerModel {
|
||||||
|
final String typeId;
|
||||||
|
final String noId;
|
||||||
|
final String name;
|
||||||
|
final String gender;
|
||||||
|
|
||||||
|
PassengerModel({
|
||||||
|
required this.typeId,
|
||||||
|
required this.noId,
|
||||||
|
required this.name,
|
||||||
|
required this.gender,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'typeId': typeId,
|
||||||
|
'noId': noId,
|
||||||
|
'name': name,
|
||||||
|
'gender': gender,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory PassengerModel.fromMap(Map<String, dynamic> map) {
|
||||||
|
return PassengerModel(
|
||||||
|
typeId: map['typeId'] ?? '',
|
||||||
|
noId: map['noId'] ?? '',
|
||||||
|
name: map['name'] ?? '',
|
||||||
|
gender: map['gender'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import '../models/user_entity.dart';
|
||||||
|
|
||||||
|
abstract class ProfilRepository {
|
||||||
|
Future<void> createPassenger({
|
||||||
|
required String userId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import '../models/user_entity.dart';
|
||||||
|
import '../repositories/profil_repository.dart';
|
||||||
|
|
||||||
|
class CreatePassengerUseCase {
|
||||||
|
final ProfilRepository profilRepository;
|
||||||
|
|
||||||
|
CreatePassengerUseCase(this.profilRepository);
|
||||||
|
|
||||||
|
Future<void> call({
|
||||||
|
required String userId,
|
||||||
|
required PassengerModel passenger,
|
||||||
|
}) async {
|
||||||
|
// Lakukan validasi atau logika bisnis lain jika diperlukan
|
||||||
|
await profilRepository.createPassenger(
|
||||||
|
userId: userId,
|
||||||
|
passenger: passenger,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ class AuthController extends GetxController {
|
||||||
|
|
||||||
final uid = userEntity.uid;
|
final uid = userEntity.uid;
|
||||||
final roleFromDB = await getUserRoleUseCase(uid);
|
final roleFromDB = await getUserRoleUseCase(uid);
|
||||||
logger.d("roleFromDB: $roleFromDB, roleFromOnboarding: $roleFromOnboarding");
|
logger.d("roleFromDB: $roleFromDB, roleFromOnboarding: $roleFromOnboarding, UID: $uid");
|
||||||
|
|
||||||
if (roleFromDB != null && roleFromOnboarding != null && roleFromDB != roleFromOnboarding) {
|
if (roleFromDB != null && roleFromOnboarding != null && roleFromDB != roleFromOnboarding) {
|
||||||
_showErrorSnackbar(
|
_showErrorSnackbar(
|
||||||
|
@ -58,14 +58,13 @@ class AuthController extends GetxController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userData.role!.toLowerCase() != effectiveRole.toLowerCase()) {
|
if (userData.role!.toLowerCase() != effectiveRole.toLowerCase()) {
|
||||||
_showErrorSnackbar("Role Tidak Sesuai",
|
_showErrorSnackbar(
|
||||||
"Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
|
"Role Tidak Sesuai", "Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await PreferencesService.saveUserData(userData);
|
await PreferencesService.saveUserData(userData);
|
||||||
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
|
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
|
||||||
|
|
||||||
} on AuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
_showErrorSnackbar("Login Gagal", e.message);
|
_showErrorSnackbar("Login Gagal", e.message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../domain/models/user_entity.dart';
|
||||||
|
import '../../domain/usecases/profil_usecase.dart';
|
||||||
|
|
||||||
|
class ProfilController extends GetxController {
|
||||||
|
final CreatePassengerUseCase createPassengerUseCase;
|
||||||
|
|
||||||
|
ProfilController({required this.createPassengerUseCase});
|
||||||
|
|
||||||
|
Future<void> addPassenger({
|
||||||
|
required String userId,
|
||||||
|
required String typeId,
|
||||||
|
required String noId,
|
||||||
|
required String name,
|
||||||
|
required String gender,
|
||||||
|
}) async {
|
||||||
|
final passenger = PassengerModel(
|
||||||
|
typeId: typeId,
|
||||||
|
noId: noId,
|
||||||
|
name: name,
|
||||||
|
gender: gender,
|
||||||
|
);
|
||||||
|
await createPassengerUseCase(
|
||||||
|
userId: userId,
|
||||||
|
passenger: passenger,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,19 @@ import 'package:e_porter/_core/component/text_field/dropdown/dropdown_component.
|
||||||
import 'package:e_porter/_core/component/text_field/text_input/text_field_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/utils/snackbar/snackbar_helper.dart';
|
||||||
|
import 'package:e_porter/_core/validators/validators.dart';
|
||||||
|
import 'package:e_porter/presentation/controllers/profil_controller.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:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../../../_core/service/logger_service.dart';
|
||||||
|
import '../../../../_core/service/preferences_service.dart';
|
||||||
|
import '../../../../_core/utils/firestore/unique_helper.dart';
|
||||||
|
import '../../../../_core/utils/formatter/uppercase_helper.dart';
|
||||||
|
|
||||||
class AddPassengerScreen extends StatefulWidget {
|
class AddPassengerScreen extends StatefulWidget {
|
||||||
const AddPassengerScreen({super.key});
|
const AddPassengerScreen({super.key});
|
||||||
|
|
||||||
|
@ -17,7 +26,13 @@ class AddPassengerScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddPassengerScreenState extends State<AddPassengerScreen> {
|
class _AddPassengerScreenState extends State<AddPassengerScreen> {
|
||||||
final ValueNotifier<String> selectedGender = ValueNotifier<String>('');
|
final ValueNotifier<String> selectedGender = ValueNotifier<String>('Laki-laki');
|
||||||
|
final TextEditingController _nameController = TextEditingController();
|
||||||
|
final TextEditingController _noIdController = TextEditingController();
|
||||||
|
final ProfilController profilController = Get.find<ProfilController>();
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
String selectedTypeId = 'KTP';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -34,59 +49,85 @@ class _AddPassengerScreenState extends State<AddPassengerScreen> {
|
||||||
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: SingleChildScrollView(
|
child: Form(
|
||||||
child: Column(
|
key: _formKey,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: SingleChildScrollView(
|
||||||
children: [
|
child: Column(
|
||||||
TypographyStyles.body(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
"Silahkan isi informasi data diri calon penumpang baru",
|
children: [
|
||||||
color: GrayColors.gray600,
|
TypographyStyles.body(
|
||||||
fontWeight: FontWeight.w400,
|
"Silahkan isi informasi data diri calon penumpang baru",
|
||||||
maxlines: 2,
|
color: GrayColors.gray600,
|
||||||
),
|
fontWeight: FontWeight.w400,
|
||||||
SizedBox(height: 32.h),
|
maxlines: 2,
|
||||||
TypographyStyles.body('Nama Lengkap', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
),
|
||||||
SizedBox(height: 16.w),
|
SizedBox(height: 32.h),
|
||||||
TextFieldComponent(hintText: 'Masukkan nama lengkap'),
|
TypographyStyles.body('Nama Lengkap', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 16.w),
|
||||||
Row(
|
TextFieldComponent(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
controller: _nameController,
|
||||||
children: [
|
hintText: 'Masukkan nama lengkap',
|
||||||
Column(
|
validators: Validators.validatorName,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
inputFormatters: [
|
||||||
children: [
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
|
||||||
TypographyStyles.body('Tipe ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
UpperCaseTextFormatter(),
|
||||||
SizedBox(height: 16.h),
|
],
|
||||||
DropdownComponent(
|
textInputType: TextInputType.text,
|
||||||
hintText: "Pilih jenis dokument",
|
),
|
||||||
value: "KTP",
|
SizedBox(height: 20.h),
|
||||||
onChanged: (value) {},
|
Row(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
],
|
children: [
|
||||||
),
|
Column(
|
||||||
SizedBox(width: 16.w),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.body('No ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
TypographyStyles.body('Tipe ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
TextFieldComponent(hintText: 'Masukkan ID')
|
DropdownComponent(
|
||||||
|
hintText: "Pilih jenis dokument",
|
||||||
|
items: ['KTP', 'Pasport'],
|
||||||
|
value: selectedTypeId,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedTypeId = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
SizedBox(width: 16.w),
|
||||||
],
|
Expanded(
|
||||||
),
|
child: Column(
|
||||||
SizedBox(height: 20.w),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
TypographyStyles.body('Jenis Kelamin', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
children: [
|
||||||
Row(
|
TypographyStyles.body('No ID', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
children: [
|
SizedBox(height: 16.h),
|
||||||
_buildRadioButton(context, label: 'Laki-laki', value: 'Laki-laki'),
|
TextFieldComponent(
|
||||||
SizedBox(width: 40.h),
|
controller: _noIdController,
|
||||||
_buildRadioButton(context, label: 'Perempuan', value: 'Perempuan')
|
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')
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -97,7 +138,11 @@ class _AddPassengerScreenState extends State<AddPassengerScreen> {
|
||||||
child: ButtonFill(
|
child: ButtonFill(
|
||||||
text: 'Simpan',
|
text: 'Simpan',
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
onTap: () {},
|
onTap: () async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_onSavePassenger();
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -128,4 +173,47 @@ class _AddPassengerScreenState extends State<AddPassengerScreen> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onSavePassenger() async {
|
||||||
|
final name = _nameController.text.trim();
|
||||||
|
final noId = _noIdController.text.trim();
|
||||||
|
final gender = selectedGender.value;
|
||||||
|
final typeId = selectedTypeId;
|
||||||
|
|
||||||
|
if (name.isEmpty || noId.isEmpty) {
|
||||||
|
Get.snackbar('Error', 'Nama dan No ID tidak boleh kosong');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final userData = await PreferencesService.getUserData();
|
||||||
|
if (userData == null || userData.uid.isEmpty) {
|
||||||
|
SnackbarHelper.showInfo('Error', 'User ID tidak ditemukan, silakan login kembali');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final userId = userData.uid;
|
||||||
|
|
||||||
|
final bool isUnique = await isNoIdUnique(userId, noId);
|
||||||
|
if (!isUnique) {
|
||||||
|
SnackbarHelper.showError('Error', 'No ID sudah digunakan. Harap gunakan No ID yang berbeda');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await profilController.addPassenger(
|
||||||
|
userId: userId,
|
||||||
|
typeId: typeId,
|
||||||
|
noId: noId,
|
||||||
|
name: name,
|
||||||
|
gender: gender,
|
||||||
|
);
|
||||||
|
_nameController.clear();
|
||||||
|
_noIdController.clear();
|
||||||
|
|
||||||
|
SnackbarHelper.showSuccess('Sukses', 'Berhasil menambahkan penumpang baru');
|
||||||
|
logger.d("Berhasil menambah penumpang: {name: $name, typeId: $typeId, noId: $noId, gender: $gender}");
|
||||||
|
} catch (e) {
|
||||||
|
SnackbarHelper.showError('Error', 'Gagal menambahkan penumpang baru: $e');
|
||||||
|
logger.e("Gagal menambah penumpang: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:e_porter/domain/bindings/auth_binding.dart';
|
import 'package:e_porter/domain/bindings/auth_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/navigation_binding.dart';
|
import 'package:e_porter/domain/bindings/navigation_binding.dart';
|
||||||
|
import 'package:e_porter/domain/bindings/profil_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/search_flight_binding.dart';
|
import 'package:e_porter/domain/bindings/search_flight_binding.dart';
|
||||||
import 'package:e_porter/domain/bindings/ticket_binding.dart';
|
import 'package:e_porter/domain/bindings/ticket_binding.dart';
|
||||||
import 'package:e_porter/presentation/screens/auth/pages/forget_password_screen.dart';
|
import 'package:e_porter/presentation/screens/auth/pages/forget_password_screen.dart';
|
||||||
|
@ -116,6 +117,7 @@ class AppRoutes {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: Routes.ADDPASSENGER,
|
name: Routes.ADDPASSENGER,
|
||||||
page: () => AddPassengerScreen(),
|
page: () => AddPassengerScreen(),
|
||||||
|
binding: ProfilBinding(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue