Tambahkan fitur daftar penerima dan navigasi detail penerima
- Buat kontroler dan tampilan untuk daftar penerima - Tambahkan rute baru untuk daftar penerima dan detail penerima - Perbarui dashboard dengan navigasi ke daftar penerima - Tambahkan kemampuan untuk membuka detail penerima dari dashboard - Integrasikan fitur baru ke dalam drawer navigasi Petugas Desa
This commit is contained in:
12
lib/app/modules/petugas_desa/bindings/penerima_binding.dart
Normal file
12
lib/app/modules/petugas_desa/bindings/penerima_binding.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
|
||||
class PenerimaBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<PenerimaController>(
|
||||
() => PenerimaController(),
|
||||
fenix: true,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
|
||||
class PetugasDesaBinding extends Bindings {
|
||||
@override
|
||||
@ -8,5 +9,8 @@ class PetugasDesaBinding extends Bindings {
|
||||
() => PetugasDesaController(),
|
||||
fenix: true,
|
||||
);
|
||||
Get.lazyPut<PenerimaController>(
|
||||
() => PenerimaController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class PenerimaController extends GetxController {
|
||||
final RxList<Map<String, dynamic>> daftarPenerima =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
final RxBool isLoading = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchDaftarPenerima();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// Pastikan data dimuat saat controller siap
|
||||
if (daftarPenerima.isEmpty) {
|
||||
fetchDaftarPenerima();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchDaftarPenerima() {
|
||||
isLoading.value = true;
|
||||
|
||||
// Simulasi data penerima
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
daftarPenerima.value = [
|
||||
{
|
||||
'id': '1',
|
||||
'nama': 'Bagus Jayadi',
|
||||
'nik': '3201020107030010',
|
||||
'noKK': '3201020107030383',
|
||||
'noHandphone': '089891256532',
|
||||
'email': 'bgjayadi@gmail.com',
|
||||
'jenisKelamin': 'Pria',
|
||||
'agama': 'Islam',
|
||||
'tempatTanggalLahir': 'Bogor, 2 Juni 1990',
|
||||
'alamatLengkap':
|
||||
'Jl. Leada Natsir No. 22 RT 001/003 Kec. Gunung Putri Kab. Bogor',
|
||||
'pekerjaan': 'Petani',
|
||||
'pendidikanTerakhir': 'Sekolah Dasar (SD)',
|
||||
'status': 'Belum disalurkan',
|
||||
'foto': 'assets/images/profile.jpg',
|
||||
'terverifikasi': true,
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'nama': 'Siti Rahayu',
|
||||
'nik': '3201020107030011',
|
||||
'noKK': '3201020107030384',
|
||||
'noHandphone': '089891256533',
|
||||
'email': 'sitirahayu@gmail.com',
|
||||
'jenisKelamin': 'Wanita',
|
||||
'agama': 'Islam',
|
||||
'tempatTanggalLahir': 'Bogor, 15 Agustus 1985',
|
||||
'alamatLengkap':
|
||||
'Jl. Raya Bogor No. 45 RT 002/004 Kec. Gunung Putri Kab. Bogor',
|
||||
'pekerjaan': 'Ibu Rumah Tangga',
|
||||
'pendidikanTerakhir': 'SMP',
|
||||
'status': 'Selesai',
|
||||
'foto': 'assets/images/profile.jpg',
|
||||
'terverifikasi': true,
|
||||
},
|
||||
{
|
||||
'id': '3',
|
||||
'nama': 'Budi Santoso',
|
||||
'nik': '3201020107030012',
|
||||
'noKK': '3201020107030385',
|
||||
'noHandphone': '089891256534',
|
||||
'email': 'budisantoso@gmail.com',
|
||||
'jenisKelamin': 'Pria',
|
||||
'agama': 'Islam',
|
||||
'tempatTanggalLahir': 'Jakarta, 10 Januari 1980',
|
||||
'alamatLengkap':
|
||||
'Jl. Merdeka No. 12 RT 003/005 Kec. Gunung Putri Kab. Bogor',
|
||||
'pekerjaan': 'Buruh',
|
||||
'pendidikanTerakhir': 'SMA',
|
||||
'status': 'Selesai',
|
||||
'foto': 'assets/images/profile.jpg',
|
||||
'terverifikasi': true,
|
||||
},
|
||||
{
|
||||
'id': '4',
|
||||
'nama': 'Dewi Lestari',
|
||||
'nik': '3201020107030013',
|
||||
'noKK': '3201020107030386',
|
||||
'noHandphone': '089891256535',
|
||||
'email': 'dewilestari@gmail.com',
|
||||
'jenisKelamin': 'Wanita',
|
||||
'agama': 'Islam',
|
||||
'tempatTanggalLahir': 'Bandung, 5 Mei 1992',
|
||||
'alamatLengkap':
|
||||
'Jl. Pahlawan No. 8 RT 004/006 Kec. Gunung Putri Kab. Bogor',
|
||||
'pekerjaan': 'Guru',
|
||||
'pendidikanTerakhir': 'S1',
|
||||
'status': 'Selesai',
|
||||
'foto': 'assets/images/profile.jpg',
|
||||
'terverifikasi': true,
|
||||
},
|
||||
{
|
||||
'id': '5',
|
||||
'nama': 'Ahmad Fauzi',
|
||||
'nik': '3201020107030014',
|
||||
'noKK': '3201020107030387',
|
||||
'noHandphone': '089891256536',
|
||||
'email': 'ahmadfauzi@gmail.com',
|
||||
'jenisKelamin': 'Pria',
|
||||
'agama': 'Islam',
|
||||
'tempatTanggalLahir': 'Surabaya, 20 Desember 1988',
|
||||
'alamatLengkap':
|
||||
'Jl. Sudirman No. 15 RT 005/007 Kec. Gunung Putri Kab. Bogor',
|
||||
'pekerjaan': 'Wiraswasta',
|
||||
'pendidikanTerakhir': 'SMA',
|
||||
'status': 'Terjadwal',
|
||||
'foto': 'assets/images/profile.jpg',
|
||||
'terverifikasi': true,
|
||||
},
|
||||
];
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic>? getPenerimaById(String id) {
|
||||
try {
|
||||
if (daftarPenerima.isEmpty) {
|
||||
// Jika data belum dimuat, muat data terlebih dahulu
|
||||
fetchDaftarPenerima();
|
||||
// Kembalikan data dummy sementara
|
||||
return {
|
||||
'id': id,
|
||||
'nama': 'Memuat data...',
|
||||
'nik': 'Memuat...',
|
||||
'noKK': 'Memuat...',
|
||||
'noHandphone': 'Memuat...',
|
||||
'email': 'Memuat...',
|
||||
'jenisKelamin': 'Memuat...',
|
||||
'agama': 'Memuat...',
|
||||
'tempatTanggalLahir': 'Memuat...',
|
||||
'alamatLengkap': 'Memuat...',
|
||||
'pekerjaan': 'Memuat...',
|
||||
'pendidikanTerakhir': 'Memuat...',
|
||||
'status': 'Memuat...',
|
||||
'terverifikasi': false,
|
||||
};
|
||||
}
|
||||
return daftarPenerima.firstWhere((penerima) => penerima['id'] == id);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
316
lib/app/modules/petugas_desa/views/daftar_penerima_view.dart
Normal file
316
lib/app/modules/petugas_desa/views/daftar_penerima_view.dart
Normal file
@ -0,0 +1,316 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class DaftarPenerimaView extends GetView<PenerimaController> {
|
||||
const DaftarPenerimaView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Daftar Penerima'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
// Implementasi pencarian
|
||||
showSearch(
|
||||
context: context,
|
||||
delegate: PenerimaSearchDelegate(controller.daftarPenerima),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.daftarPenerima.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Tidak ada data penerima'),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: controller.daftarPenerima.length,
|
||||
itemBuilder: (context, index) {
|
||||
final penerima = controller.daftarPenerima[index];
|
||||
return _buildPenerimaCard(context, penerima);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPenerimaCard(
|
||||
BuildContext context, Map<String, dynamic> penerima) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// Navigasi ke halaman detail penerima
|
||||
Get.toNamed('/daftar-penerima/detail', arguments: penerima['id']);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
// Foto profil
|
||||
CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundColor: AppTheme.primaryColor.withOpacity(0.1),
|
||||
child: penerima['foto'] != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
child: Image.asset(
|
||||
penerima['foto'],
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Icon(
|
||||
Icons.person,
|
||||
size: 30,
|
||||
color: AppTheme.primaryColor,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.person,
|
||||
size: 30,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Informasi penerima
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
penerima['nama'] ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (penerima['terverifikasi'] == true)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.verified,
|
||||
size: 14,
|
||||
color: Colors.green,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Text(
|
||||
'Terverifikasi',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'NIK: ${penerima['nik'] ?? ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 8),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text(
|
||||
// penerima['alamatLengkap'] ?? '',
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// color: Colors.grey[600],
|
||||
// ),
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// _buildStatusBadge(penerima['status']),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusBadge(String? status) {
|
||||
Color backgroundColor;
|
||||
Color textColor;
|
||||
|
||||
switch (status) {
|
||||
case 'Selesai':
|
||||
backgroundColor = Colors.green.withOpacity(0.1);
|
||||
textColor = Colors.green;
|
||||
break;
|
||||
case 'Terjadwal':
|
||||
backgroundColor = Colors.blue.withOpacity(0.1);
|
||||
textColor = Colors.blue;
|
||||
break;
|
||||
case 'Belum disalurkan':
|
||||
backgroundColor = Colors.orange.withOpacity(0.1);
|
||||
textColor = Colors.orange;
|
||||
break;
|
||||
default:
|
||||
backgroundColor = Colors.grey.withOpacity(0.1);
|
||||
textColor = Colors.grey;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
status ?? 'Tidak diketahui',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PenerimaSearchDelegate extends SearchDelegate {
|
||||
final List<Map<String, dynamic>> daftarPenerima;
|
||||
|
||||
PenerimaSearchDelegate(this.daftarPenerima);
|
||||
|
||||
@override
|
||||
List<Widget> buildActions(BuildContext context) {
|
||||
return [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
query = '';
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
close(context, null);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
return _buildSearchResults();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return _buildSearchResults();
|
||||
}
|
||||
|
||||
Widget _buildSearchResults() {
|
||||
final filteredList = daftarPenerima.where((penerima) {
|
||||
final nama = penerima['nama']?.toString().toLowerCase() ?? '';
|
||||
final nik = penerima['nik']?.toString().toLowerCase() ?? '';
|
||||
final alamat = penerima['alamatLengkap']?.toString().toLowerCase() ?? '';
|
||||
final searchLower = query.toLowerCase();
|
||||
|
||||
return nama.contains(searchLower) ||
|
||||
nik.contains(searchLower) ||
|
||||
alamat.contains(searchLower);
|
||||
}).toList();
|
||||
|
||||
if (filteredList.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Tidak ada hasil yang ditemukan'),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: filteredList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final penerima = filteredList[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
close(context, null);
|
||||
Get.toNamed('/daftar-penerima/detail', arguments: penerima['id']);
|
||||
},
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: AppTheme.primaryColor.withOpacity(0.1),
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
penerima['nama'] ?? '',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text('NIK: ${penerima['nik'] ?? ''}'),
|
||||
trailing: penerima['terverifikasi'] == true
|
||||
? const Icon(
|
||||
Icons.verified,
|
||||
color: Colors.green,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:penyaluran_app/app/modules/petugas_desa/components/greeting_head
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/progress_section.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/schedule_card.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
import 'package:penyaluran_app/app/widgets/statistic_card.dart';
|
||||
|
||||
@ -114,7 +115,9 @@ class DashboardView extends GetView<PetugasDesaController> {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Get.toNamed('/daftar-penerima');
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
@ -135,11 +138,11 @@ class DashboardView extends GetView<PetugasDesaController> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildRecipientItem(
|
||||
'Siti Rahayu', '3201020107030010', 'Selesai', textTheme),
|
||||
'Siti Rahayu', '3201020107030011', 'Selesai', textTheme),
|
||||
_buildRecipientItem(
|
||||
'Budi Santoso', '3201020107030011', 'Selesai', textTheme),
|
||||
'Budi Santoso', '3201020107030012', 'Selesai', textTheme),
|
||||
_buildRecipientItem(
|
||||
'Dewi Lestari', '3201020107030012', 'Selesai', textTheme),
|
||||
'Dewi Lestari', '3201020107030013', 'Selesai', textTheme),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -153,31 +156,45 @@ class DashboardView extends GetView<PetugasDesaController> {
|
||||
gradient: AppTheme.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
name,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'NIK: $nik',
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// Navigasi ke detail penerima dengan ID statis
|
||||
// Kita gunakan ID 1 untuk Siti Rahayu, 2 untuk Budi Santoso, 3 untuk Dewi Lestari
|
||||
String id = "1"; // Default
|
||||
if (nik == "3201020107030011") {
|
||||
id = "2";
|
||||
} else if (nik == "3201020107030012") {
|
||||
id = "3";
|
||||
}
|
||||
Get.toNamed('/daftar-penerima/detail', arguments: id);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
name,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'NIK: $nik',
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
434
lib/app/modules/petugas_desa/views/detail_penerima_view.dart
Normal file
434
lib/app/modules/petugas_desa/views/detail_penerima_view.dart
Normal file
@ -0,0 +1,434 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class DetailPenerimaView extends GetView<PenerimaController> {
|
||||
const DetailPenerimaView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String id = Get.arguments as String;
|
||||
|
||||
return Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Penerima'),
|
||||
),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final penerima = controller.getPenerimaById(id);
|
||||
|
||||
if (penerima == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Penerima'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Data penerima tidak ditemukan'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Penerima'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
// Implementasi edit penerima
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header dengan foto dan nama
|
||||
_buildHeader(penerima),
|
||||
|
||||
// Detail informasi penerima
|
||||
_buildDetailInfo(penerima),
|
||||
|
||||
// Status penyaluran
|
||||
_buildStatusSection(penerima),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: _buildBottomButtons(penerima),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildHeader(Map<String, dynamic> penerima) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppTheme.primaryGradient,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Foto profil
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.white,
|
||||
child: penerima['foto'] != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset(
|
||||
penerima['foto'],
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.primaryColor,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Nama penerima
|
||||
Text(
|
||||
penerima['nama'] ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// NIK
|
||||
Text(
|
||||
penerima['nik'] ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Badge terverifikasi
|
||||
if (penerima['terverifikasi'] == true)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.verified,
|
||||
size: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Terverifikasi',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailInfo(Map<String, dynamic> penerima) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Detail Penerima',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Informasi detail dalam bentuk card
|
||||
Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoRow('NIK', penerima['nik'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('No KK', penerima['noKK'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('No Handphone', penerima['noHandphone'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('Email', penerima['email'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow(
|
||||
'Jenis Kelamin', penerima['jenisKelamin'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('Agama', penerima['agama'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('Tempat, Tanggal Lahir',
|
||||
penerima['tempatTanggalLahir'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow(
|
||||
'Alamat Lengkap', penerima['alamatLengkap'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('Pekerjaan', penerima['pekerjaan'] ?? '-'),
|
||||
const Divider(),
|
||||
_buildInfoRow('Pendidikan Terakhir',
|
||||
penerima['pendidikanTerakhir'] ?? '-'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusSection(Map<String, dynamic> penerima) {
|
||||
Color statusColor;
|
||||
IconData statusIcon;
|
||||
|
||||
switch (penerima['status']) {
|
||||
case 'Selesai':
|
||||
statusColor = Colors.green;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'Terjadwal':
|
||||
statusColor = Colors.blue;
|
||||
statusIcon = Icons.event;
|
||||
break;
|
||||
case 'Belum disalurkan':
|
||||
statusColor = Colors.orange;
|
||||
statusIcon = Icons.pending;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.grey;
|
||||
statusIcon = Icons.help;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Status Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
statusIcon,
|
||||
color: statusColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
penerima['status'] ?? 'Tidak diketahui',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
if (penerima['status'] == 'Belum disalurkan')
|
||||
const Text(
|
||||
'Penerima ini belum dijadwalkan penyaluran bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
if (penerima['status'] == 'Terjadwal')
|
||||
const Text(
|
||||
'Penerima ini sudah dijadwalkan penyaluran bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
if (penerima['status'] == 'Selesai')
|
||||
const Text(
|
||||
'Penerima ini sudah menerima bantuan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomButtons(Map<String, dynamic> penerima) {
|
||||
// Jika status sudah selesai, tidak perlu menampilkan tombol
|
||||
if (penerima['status'] == 'Selesai') {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi lihat riwayat penyaluran
|
||||
},
|
||||
icon: const Icon(Icons.history),
|
||||
label: const Text('Lihat Riwayat Penyaluran'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Jika status belum disalurkan, tampilkan tombol jadwalkan
|
||||
if (penerima['status'] == 'Belum disalurkan') {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi jadwalkan penyaluran
|
||||
},
|
||||
icon: const Icon(Icons.event),
|
||||
label: const Text('Jadwalkan Penyaluran'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Jika status terjadwal, tampilkan tombol konfirmasi penyaluran
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi konfirmasi penyaluran
|
||||
},
|
||||
icon: const Icon(Icons.check_circle),
|
||||
label: const Text('Konfirmasi Penyaluran'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_penerima_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/penyaluran_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart';
|
||||
@ -334,6 +336,14 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
},
|
||||
)),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.people_outline),
|
||||
title: const Text('Daftar Penerima'),
|
||||
onTap: () {
|
||||
Navigator.pop(context); // Tutup drawer terlebih dahulu
|
||||
Get.toNamed('/daftar-penerima');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
|
@ -10,6 +10,10 @@ import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/petugas_desa_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/petugas_desa_binding.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_penerima_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_penerima_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart';
|
||||
|
||||
part 'app_routes.dart';
|
||||
|
||||
@ -54,5 +58,15 @@ class AppPages {
|
||||
page: () => const PermintaanPenjadwalanView(),
|
||||
binding: PetugasDesaBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: _Paths.daftarPenerima,
|
||||
page: () => const DaftarPenerimaView(),
|
||||
binding: PenerimaBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: _Paths.detailPenerima,
|
||||
page: () => const DetailPenerimaView(),
|
||||
binding: PenerimaBinding(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ abstract class Routes {
|
||||
static const donaturDashboard = _Paths.donaturDashboard;
|
||||
static const splash = _Paths.splash;
|
||||
static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan;
|
||||
static const daftarPenerima = _Paths.daftarPenerima;
|
||||
static const detailPenerima = _Paths.detailPenerima;
|
||||
}
|
||||
|
||||
abstract class _Paths {
|
||||
@ -24,4 +26,6 @@ abstract class _Paths {
|
||||
static const donaturDashboard = '/donatur-dashboard';
|
||||
static const splash = '/splash';
|
||||
static const permintaanPenjadwalan = '/permintaan-penjadwalan';
|
||||
static const daftarPenerima = '/daftar-penerima';
|
||||
static const detailPenerima = '/daftar-penerima/detail';
|
||||
}
|
||||
|
Reference in New Issue
Block a user