Perbarui dependensi dengan menambahkan paket percent_indicator versi 4.2.4. Modifikasi file pubspec.yaml dan pubspec.lock untuk mencerminkan perubahan ini. Selain itu, perbarui status penerimaan di PelaksanaanPenyaluranController dari 'SUDAHMENERIMA' menjadi 'DITERIMA' untuk konsistensi. Tambahkan fungsionalitas baru di PetugasDesaDashboardController untuk memuat jadwal hari ini dan total penitipan terverifikasi. Perbarui tampilan di beberapa view untuk meningkatkan pengalaman pengguna dan konsistensi data.
This commit is contained in:
@ -46,6 +46,9 @@ class ProfileController extends GetxController {
|
||||
Future<void> loadUserData() async {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
// Hapus cache data user sebelum mengambil data baru
|
||||
_supabaseService.clearUserProfileCache();
|
||||
|
||||
// Mendapatkan data user dari service
|
||||
final userData = await _supabaseService.getUserProfile();
|
||||
if (userData != null) {
|
||||
@ -67,9 +70,15 @@ class ProfileController extends GetxController {
|
||||
if (roleData.value?['foto_profil'] != null) {
|
||||
fotoProfil.value = roleData.value?['foto_profil'] ?? '';
|
||||
print(fotoProfil.value);
|
||||
} else {
|
||||
// Reset foto profil jika tidak ada data
|
||||
fotoProfil.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Muat ulang data user di AuthController untuk memastikan konsistensi
|
||||
await _authController.refreshUserData();
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
@ -140,6 +149,95 @@ class ProfileController extends GetxController {
|
||||
// Metode untuk menghapus foto profil
|
||||
void clearFotoProfil() {
|
||||
fotoProfilPath.value = '';
|
||||
if (isEditing.value) {
|
||||
// Cek jika user adalah warga
|
||||
if (user.value?.role?.toLowerCase() == 'warga') {
|
||||
Get.snackbar(
|
||||
'Tidak Diizinkan',
|
||||
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.amber,
|
||||
colorText: Colors.black,
|
||||
duration: const Duration(seconds: 5),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tandai bahwa foto profil akan dihapus saat menyimpan perubahan
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Konfirmasi'),
|
||||
content: const Text('Apakah Anda yakin ingin menghapus foto profil?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
try {
|
||||
final userData = user.value;
|
||||
if (userData == null) return;
|
||||
|
||||
// Update data profil dengan foto kosong
|
||||
switch (userData.role?.toLowerCase() ?? 'unknown') {
|
||||
case 'donatur':
|
||||
await _supabaseService.updateDonaturProfile(
|
||||
userId: userData.id,
|
||||
nama: nameController.text,
|
||||
noHp: phoneController.text,
|
||||
email: emailController.text,
|
||||
fotoProfil:
|
||||
'', // Kosongkan foto profil dengan string kosong
|
||||
);
|
||||
break;
|
||||
case 'petugas_desa':
|
||||
await _supabaseService.updatePetugasDesaProfile(
|
||||
userId: userData.id,
|
||||
nama: nameController.text,
|
||||
noHp: phoneController.text,
|
||||
email: emailController.text,
|
||||
fotoProfil:
|
||||
'', // Kosongkan foto profil dengan string kosong
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Hapus cache dan refresh data
|
||||
_supabaseService.clearUserProfileCache();
|
||||
fotoProfil.value = '';
|
||||
await _authController.refreshUserData();
|
||||
await loadUserData();
|
||||
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Foto profil berhasil dihapus',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal menghapus foto profil: ${e.toString()}',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text('Hapus'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk mengupload foto profil
|
||||
@ -179,11 +277,34 @@ class ProfileController extends GetxController {
|
||||
return;
|
||||
}
|
||||
|
||||
final userData = user.value;
|
||||
if (userData == null) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Data user tidak ditemukan',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cek jika user adalah warga, maka tidak diperbolehkan mengubah profil
|
||||
if (userData.role?.toLowerCase() == 'warga') {
|
||||
Get.snackbar(
|
||||
'Tidak Diizinkan',
|
||||
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
backgroundColor: Colors.amber,
|
||||
colorText: Colors.black,
|
||||
duration: const Duration(seconds: 5),
|
||||
);
|
||||
isEditing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
final userData = user.value;
|
||||
if (userData == null) throw 'Data user tidak ditemukan';
|
||||
|
||||
// Upload foto profil jika ada
|
||||
String? fotoProfilUrl;
|
||||
if (fotoProfilPath.isNotEmpty) {
|
||||
@ -195,15 +316,6 @@ class ProfileController extends GetxController {
|
||||
|
||||
// Update data sesuai role
|
||||
switch (userData.role?.toLowerCase() ?? 'unknown') {
|
||||
case 'warga':
|
||||
await _supabaseService.updateWargaProfile(
|
||||
userId: userData.id,
|
||||
namaLengkap: nameController.text,
|
||||
noHp: phoneController.text,
|
||||
email: emailController.text,
|
||||
fotoProfil: fotoProfilUrl,
|
||||
);
|
||||
break;
|
||||
case 'donatur':
|
||||
await _supabaseService.updateDonaturProfile(
|
||||
userId: userData.id,
|
||||
@ -226,14 +338,17 @@ class ProfileController extends GetxController {
|
||||
throw 'Role tidak valid';
|
||||
}
|
||||
|
||||
// Refresh data lokal
|
||||
await loadUserData();
|
||||
// Hapus cache data profil sebelum refresh
|
||||
_supabaseService.clearUserProfileCache();
|
||||
|
||||
// Reset path foto setelah update
|
||||
fotoProfilPath.value = '';
|
||||
|
||||
// Refresh data di AuthController untuk menyebarkan perubahan ke seluruh aplikasi
|
||||
await _authController.refreshUserData();
|
||||
|
||||
// Reset path foto setelah update
|
||||
fotoProfilPath.value = '';
|
||||
// Refresh data lokal
|
||||
await loadUserData();
|
||||
|
||||
// Keluar dari mode edit
|
||||
isEditing.value = false;
|
||||
|
@ -58,17 +58,22 @@ class ProfileView extends GetView<ProfileController> {
|
||||
title: const Text('Profil'),
|
||||
actions: [
|
||||
Obx(() {
|
||||
if (controller.isEditing.value) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: controller.updateProfile,
|
||||
);
|
||||
} else {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: controller.toggleEditMode,
|
||||
);
|
||||
// Hanya tampilkan tombol edit jika user bukan warga
|
||||
if (controller.user.value?.role?.toLowerCase() != 'warga') {
|
||||
if (controller.isEditing.value) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: controller.updateProfile,
|
||||
);
|
||||
} else {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: controller.toggleEditMode,
|
||||
);
|
||||
}
|
||||
}
|
||||
return const SizedBox
|
||||
.shrink(); // Jangan tampilkan apapun untuk warga
|
||||
}),
|
||||
],
|
||||
),
|
||||
@ -122,7 +127,8 @@ class ProfileView extends GetView<ProfileController> {
|
||||
|
||||
// Tombol edit foto (hanya muncul dalam mode edit)
|
||||
Obx(() {
|
||||
if (controller.isEditing.value) {
|
||||
if (controller.isEditing.value &&
|
||||
controller.user.value?.role?.toLowerCase() != 'warga') {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
@ -164,13 +170,36 @@ class ProfileView extends GetView<ProfileController> {
|
||||
}),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Obx(() => Text(
|
||||
controller.user.value?.name ?? 'Pengguna',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)),
|
||||
// Menggunakan Obx untuk reaktif memperbarui nama
|
||||
Obx(() {
|
||||
// Mengambil nama dari roleData jika ada, atau dari user
|
||||
String displayName = '';
|
||||
|
||||
final roleDataValue = controller.roleData.value;
|
||||
final userValue = controller.user.value;
|
||||
|
||||
if (roleDataValue != null) {
|
||||
// Prioritaskan data dari roleData karena lebih spesifik
|
||||
if (roleDataValue['nama_lengkap'] != null) {
|
||||
displayName = roleDataValue['nama_lengkap'];
|
||||
} else if (roleDataValue['nama'] != null) {
|
||||
displayName = roleDataValue['nama'];
|
||||
}
|
||||
}
|
||||
|
||||
// Gunakan data dari user jika tidak ada di roleData
|
||||
if (displayName.isEmpty && userValue != null) {
|
||||
displayName = userValue.name ?? 'Pengguna';
|
||||
}
|
||||
|
||||
return Text(
|
||||
displayName,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 4),
|
||||
Obx(() {
|
||||
final role = controller.user.value?.role;
|
||||
@ -235,8 +264,10 @@ class ProfileView extends GetView<ProfileController> {
|
||||
fit: BoxFit.cover,
|
||||
width: 120,
|
||||
height: 120,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
_buildDefaultProfileImage(),
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
print('Error loading local image: $error');
|
||||
return _buildDefaultProfileImage();
|
||||
},
|
||||
)
|
||||
: Image.network(
|
||||
imagePath,
|
||||
@ -254,8 +285,11 @@ class ProfileView extends GetView<ProfileController> {
|
||||
),
|
||||
);
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
_buildDefaultProfileImage(),
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
print('Error loading network image: $error');
|
||||
print('Failed image URL: $imagePath');
|
||||
return _buildDefaultProfileImage();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -321,157 +355,273 @@ class ProfileView extends GetView<ProfileController> {
|
||||
return Obx(() {
|
||||
final isEditing = controller.isEditing.value;
|
||||
final user = controller.user.value;
|
||||
// Form tidak bisa diedit jika usernya warga
|
||||
final bool canEdit = isEditing && user?.role?.toLowerCase() != 'warga';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Informasi Pribadi',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.person_outline, color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Informasi Pribadi',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Nama
|
||||
TextField(
|
||||
controller: controller.nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nama Lengkap',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.person),
|
||||
enabled: isEditing,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Menampilkan notifikasi khusus untuk warga
|
||||
if (user?.role?.toLowerCase() == 'warga') ...[
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.amber.withOpacity(0.5)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline,
|
||||
color: Colors.amber[800], size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Data warga hanya dapat diubah melalui aplikasi verifikasi data warga. Silakan hubungi petugas desa untuk perubahan data.',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.amber[900],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// Email
|
||||
TextField(
|
||||
controller: controller.emailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.email),
|
||||
enabled: false, // Email tidak bisa diubah
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Nomor Telepon
|
||||
TextField(
|
||||
controller: controller.phoneController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nomor Telepon',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.phone),
|
||||
enabled: isEditing,
|
||||
// Nama
|
||||
TextField(
|
||||
controller: controller.nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nama Lengkap',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: Icon(Icons.person),
|
||||
enabled: canEdit,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email
|
||||
TextField(
|
||||
controller: controller.emailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: Icon(Icons.email),
|
||||
enabled: false, // Email tidak bisa diubah
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Nomor Telepon
|
||||
TextField(
|
||||
controller: controller.phoneController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nomor Telepon',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: Icon(Icons.phone),
|
||||
enabled: canEdit,
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
],
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Informasi tambahan sesuai role
|
||||
if (user != null) ...[
|
||||
if (user.role?.toLowerCase() == 'warga') ...[
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Informasi Warga',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.infoColor.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border:
|
||||
Border.all(color: AppTheme.infoColor.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(
|
||||
Icons.perm_identity, 'NIK', roleData?['nik'] ?? '-'),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.wc, 'Jenis Kelamin',
|
||||
roleData?['jenis_kelamin'] ?? '-'),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(
|
||||
Icons.home, 'Alamat', roleData?['alamat'] ?? '-'),
|
||||
if (user.desa != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(
|
||||
Icons.location_city, 'Desa', user.desa!.nama),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||
user.desa!.kecamatan ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||
user.desa!.kabupaten ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||
user.desa!.provinsi ?? ''),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.person_pin_circle,
|
||||
color: AppTheme.infoColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Informasi Lainnya',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(Icons.perm_identity, 'NIK',
|
||||
roleData?['nik'] ?? '-'),
|
||||
_buildInfoRow(Icons.wc, 'Jenis Kelamin',
|
||||
roleData?['jenis_kelamin'] ?? '-'),
|
||||
_buildInfoRow(
|
||||
Icons.home, 'Alamat', roleData?['alamat'] ?? '-'),
|
||||
if (user.desa != null) ...[
|
||||
_buildInfoRow(
|
||||
Icons.location_city, 'Desa', user.desa!.nama),
|
||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||
user.desa!.kecamatan ?? ''),
|
||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||
user.desa!.kabupaten ?? ''),
|
||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||
user.desa!.provinsi ?? ''),
|
||||
],
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (user.role?.toLowerCase() == 'donatur') ...[
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Informasi Donatur',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.successColor.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border:
|
||||
Border.all(color: AppTheme.successColor.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(Icons.business, 'Instansi',
|
||||
roleData?['instansi'] ?? '-'),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(
|
||||
Icons.work, 'Jabatan', roleData?['jabatan'] ?? '-'),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.volunteer_activism,
|
||||
color: AppTheme.successColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Informasi Donatur',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(Icons.business, 'Instansi',
|
||||
roleData?['instansi'] ?? '-'),
|
||||
_buildInfoRow(Icons.work, 'Jabatan',
|
||||
roleData?['jabatan'] ?? '-'),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (user.role?.toLowerCase() == 'petugas_desa') ...[
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Informasi Petugas Desa',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border:
|
||||
Border.all(color: AppTheme.primaryColor.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(Icons.badge, 'NIP', roleData?['nip'] ?? '-'),
|
||||
if (user.desa != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(
|
||||
Icons.location_city, 'Desa', user.desa!.nama),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||
user.desa!.kecamatan ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||
user.desa!.kabupaten ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||
user.desa!.provinsi ?? ''),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.badge, color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Informasi Petugas Desa',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(() {
|
||||
final roleData = controller.roleData.value;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildInfoRow(
|
||||
Icons.badge, 'NIP', roleData?['nip'] ?? '-'),
|
||||
if (user.desa != null) ...[
|
||||
_buildInfoRow(
|
||||
Icons.location_city, 'Desa', user.desa!.nama),
|
||||
_buildInfoRow(Icons.location_on, 'Kecamatan',
|
||||
user.desa!.kecamatan ?? ''),
|
||||
_buildInfoRow(Icons.location_on, 'Kabupaten',
|
||||
user.desa!.kabupaten ?? ''),
|
||||
_buildInfoRow(Icons.location_on, 'Provinsi',
|
||||
user.desa!.provinsi ?? ''),
|
||||
],
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -480,28 +630,54 @@ class ProfileView extends GetView<ProfileController> {
|
||||
}
|
||||
|
||||
Widget _buildPasswordSection(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Keamanan',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _showChangePasswordDialog(context),
|
||||
icon: const Icon(Icons.lock),
|
||||
label: const Text('Ubah Password'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
],
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.security, color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Keamanan',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _showChangePasswordDialog(context),
|
||||
icon: const Icon(Icons.lock),
|
||||
label: const Text('Ubah Password'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -512,34 +688,52 @@ class ProfileView extends GetView<ProfileController> {
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Ubah Password'),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.lock_reset, color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
const Text('Ubah Password'),
|
||||
],
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: currentPasswordController,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password Saat Ini',
|
||||
border: OutlineInputBorder(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: const Icon(Icons.key),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: newPasswordController,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password Baru',
|
||||
border: OutlineInputBorder(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: confirmPasswordController,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Konfirmasi Password Baru',
|
||||
border: OutlineInputBorder(),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
prefixIcon: const Icon(Icons.lock_clock),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
@ -550,6 +744,9 @@ class ProfileView extends GetView<ProfileController> {
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@ -561,6 +758,11 @@ class ProfileView extends GetView<ProfileController> {
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: const Text('Simpan'),
|
||||
),
|
||||
@ -570,21 +772,65 @@ class ProfileView extends GetView<ProfileController> {
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(IconData icon, String label, String value) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, size: 18, color: Colors.grey[700]),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'$label: $value',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 22,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value.isEmpty ? '-' : value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user