Files
penyaluran_app/lib/app/modules/petugas_desa/views/dashboard_view.dart

1249 lines
44 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:penyaluran_app/app/routes/app_pages.dart';
import 'package:penyaluran_app/app/utils/format_helper.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/components/greeting_header.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/components/schedule_card.dart';
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart';
import 'package:penyaluran_app/app/services/supabase_service.dart';
import 'package:penyaluran_app/app/theme/app_theme.dart';
import 'package:penyaluran_app/app/widgets/cards/statistic_card.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
class DashboardView extends GetView<PetugasDesaDashboardController> {
const DashboardView({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
body: RefreshIndicator(
onRefresh: () => controller.refreshData(),
child: Obx(() => AnimationLimiter(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: controller.isLoading.value
? const Center(child: CircularProgressIndicator())
: AnimationConfiguration.staggeredList(
position: 0,
delay: const Duration(milliseconds: 100),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header dengan greeting yang lebih menarik
FadeInAnimation(
child: GreetingHeader(
name: controller.namaLengkap,
role: 'Petugas Desa',
desa: controller.desa,
nip: controller.nip,
profileImageUrl: controller.profileImageUrl,
),
),
const SizedBox(height: 24),
// Rangkuman Statistik
FadeInAnimation(
delay: const Duration(milliseconds: 250),
child: _buildStatistikRangkuman(),
),
const SizedBox(height: 24),
// Progress penyaluran
FadeInAnimation(
delay: const Duration(milliseconds: 300),
child: _buildProgressPenyaluran(),
),
const SizedBox(height: 24),
// Jadwal penyaluran hari ini
FadeInAnimation(
delay: const Duration(milliseconds: 350),
child: _buildJadwalHariIni(),
),
const SizedBox(height: 24),
// Grafik Penyaluran Bantuan
FadeInAnimation(
delay: const Duration(milliseconds: 400),
child: _buildGrafikPenyaluran(),
),
const SizedBox(height: 24),
// Statistik performa desa
FadeInAnimation(
delay: const Duration(milliseconds: 450),
child: _buildStatistikPerforma(),
),
const SizedBox(height: 24),
// Daftar penerima terbaru
FadeInAnimation(
delay: const Duration(milliseconds: 500),
child: _buildRecipientsList(textTheme),
),
const SizedBox(height: 16),
],
),
),
),
),
)),
),
);
}
Widget _buildActionButton(
String title, IconData icon, Color color, VoidCallback onTap) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Container(
width: 75,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.3), width: 1),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 8),
Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: color,
),
),
],
),
),
);
}
Widget _buildStatistikRangkuman() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ringkasan Kegiatan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildStatItem(
'Total Penerima',
controller.totalPenerima.value.toString(),
Icons.people,
Colors.blue,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatItem(
'Penyaluran',
controller.totalPenyaluran.value.toString(),
Icons.calendar_month,
Colors.green,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatItem(
'Pengaduan',
controller.jumlahDiproses.value.toString(),
Icons.warning_amber,
Colors.orange,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildStatItem(
'Penitipan',
controller.totalPenitipanTerverifikasi.value.toString(),
Icons.inventory,
Colors.purple,
),
),
],
),
],
),
);
}
Widget _buildStatItem(
String title, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
}
Widget _buildJadwalHariIni() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.event_note,
color: AppTheme.primaryColor,
size: 20,
),
),
const SizedBox(width: 10),
Text(
'Jadwal Penyaluran Hari Ini',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
],
),
],
),
const SizedBox(height: 16),
FutureBuilder<List<Map<String, dynamic>>?>(
future: SupabaseService.to.getJadwalAktif(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: SizedBox(
height: 100,
child: Center(child: CircularProgressIndicator()),
),
);
}
if (snapshot.hasError) {
return Container(
height: 100,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 28),
SizedBox(height: 8),
Text(
'Gagal memuat jadwal',
style: TextStyle(color: Colors.red),
),
],
),
),
);
}
final jadwalList = snapshot.data;
if (jadwalList == null || jadwalList.isEmpty) {
return Container(
height: 100,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.blue.withOpacity(0.1),
width: 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.event_busy,
color: Colors.blue.shade300,
size: 32,
),
const SizedBox(height: 8),
const Text(
'Tidak ada jadwal penyaluran hari ini',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: jadwalList.length,
itemBuilder: (context, index) {
final jadwal = jadwalList[index];
final DateTime tanggal =
DateTime.parse(jadwal['tanggal_penyaluran']);
final String formattedDate =
FormatHelper.formatDateTime(tanggal);
final kategoriBantuan =
jadwal['kategori_bantuan'] as Map<String, dynamic>;
final lokasiPenyaluran =
jadwal['lokasi_penyaluran'] as Map<String, dynamic>;
return Column(
children: [
if (index > 0) const SizedBox(height: 10),
ScheduleCard(
title: kategoriBantuan['nama'] ?? 'Jadwal Penyaluran',
location:
lokasiPenyaluran['nama'] ?? 'Lokasi tidak tersedia',
dateTime: formattedDate,
isToday: true,
onTap: () => Get.toNamed(Routes.detailPenyaluran,
parameters: {'id': jadwal['id']}),
),
],
);
},
);
},
),
],
),
);
}
Widget _buildProgressPenyaluran() {
// Menghitung nilai untuk progress berdasarkan status
final terlaksana = controller.penyaluranTerlaksana.value;
final batal = controller.penyaluranBatal.value;
final dijadwalkan = controller.penyaluranDijadwalkan.value;
final aktif = controller.penyaluranAktif.value;
final total = terlaksana + batal + dijadwalkan + aktif;
final progressValue = total > 0 ? (terlaksana + batal) / total : 0.0;
final belumTerlaksana = dijadwalkan + aktif;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo.shade700, Colors.indigo.shade500],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.indigo.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Progress Penyaluran',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Total: $total Penyaluran',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
),
const SizedBox(height: 20),
Row(
children: [
CircularPercentIndicator(
radius: 60.0,
lineWidth: 10.0,
percent: progressValue > 1.0 ? 1.0 : progressValue,
center: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${(progressValue * 100).toInt()}%',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Text(
'Selesai',
style: TextStyle(
fontSize: 12,
color: Colors.white70,
),
),
],
),
progressColor: Colors.white,
backgroundColor: Colors.white.withOpacity(0.2),
circularStrokeCap: CircularStrokeCap.round,
animation: true,
animationDuration: 1200,
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildProgressDetailItemNew(
Icons.check_circle,
'Telah Terlaksana',
'$terlaksana',
Colors.green.shade300,
),
const SizedBox(height: 12),
_buildProgressDetailItemNew(
Icons.pending_actions,
'Belum Terlaksana',
'$belumTerlaksana',
Colors.amber.shade300,
),
const SizedBox(height: 12),
_buildProgressDetailItemNew(
Icons.cancel,
'Dibatalkan',
'$batal',
Colors.red.shade300,
),
],
),
),
],
),
],
),
);
}
Widget _buildProgressDetailItemNew(
IconData icon, String label, String value, Color iconColor) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(icon, color: iconColor, size: 16),
),
const SizedBox(width: 10),
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
),
Text(
value,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
);
}
Widget _buildGrafikPenyaluran() {
final terlaksana = controller.penyaluranTerlaksana.value;
final batal = controller.penyaluranBatal.value;
final dijadwalkan = controller.penyaluranDijadwalkan.value;
final aktif = controller.penyaluranAktif.value;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.insert_chart,
color: AppTheme.primaryColor,
size: 20,
),
),
const SizedBox(width: 10),
Text(
'Status Penyaluran',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
],
),
TextButton(
onPressed: () => Get.toNamed(Routes.riwayatPenyaluran),
child: Text(
'Lihat Riwayat',
style: TextStyle(
fontSize: 12,
color: AppTheme.primaryColor,
),
),
),
],
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: _buildStatusItem(
'Terlaksana',
terlaksana.toString(),
Icons.check_circle,
Colors.green,
),
),
Expanded(
child: _buildStatusItem(
'Dijadwalkan',
dijadwalkan.toString(),
Icons.event,
Colors.blue,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildStatusItem(
'Aktif',
aktif.toString(),
Icons.play_circle,
Colors.orange,
),
),
Expanded(
child: _buildStatusItem(
'Dibatalkan',
batal.toString(),
Icons.cancel,
Colors.red,
),
),
],
),
const SizedBox(height: 20),
Container(
width: double.infinity,
height: 10,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(5),
),
child: Row(
children: [
if (terlaksana > 0)
Flexible(
flex: terlaksana,
child: Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(5),
),
),
),
if (aktif > 0)
Flexible(
flex: aktif,
child: Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: aktif > 0 && terlaksana == 0
? BorderRadius.circular(5)
: const BorderRadius.only(
topRight: Radius.circular(5),
bottomRight: Radius.circular(5),
),
),
),
),
if (dijadwalkan > 0)
Flexible(
flex: dijadwalkan,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius:
dijadwalkan > 0 && terlaksana == 0 && aktif == 0
? BorderRadius.circular(5)
: const BorderRadius.only(
topRight: Radius.circular(5),
bottomRight: Radius.circular(5),
),
),
),
),
if (batal > 0)
Flexible(
flex: batal,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(5),
bottomRight: Radius.circular(5),
),
),
),
),
],
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildLegendItem('Terlaksana', Colors.green),
const SizedBox(width: 12),
_buildLegendItem('Aktif', Colors.orange),
const SizedBox(width: 12),
_buildLegendItem('Dijadwalkan', Colors.blue),
const SizedBox(width: 12),
_buildLegendItem('Batal', Colors.red),
],
),
],
),
);
}
Widget _buildStatusItem(
String label, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.3), width: 1),
),
child: Column(
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
);
}
Widget _buildLegendItem(String label, Color color) {
return Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
),
],
);
}
Widget _buildStatistikPerforma() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.analytics,
color: AppTheme.primaryColor,
size: 20,
),
),
const SizedBox(width: 10),
Text(
'Statistik Performa',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: StatisticCard(
title: 'Penitipan',
count: controller.jumlahMenunggu.value.toString(),
subtitle: 'Perlu Konfirmasi',
height: 120,
icon: Icons.inbox,
gradient: LinearGradient(
colors: [Colors.orange.shade600, Colors.orange.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
const SizedBox(width: 12),
Expanded(
child: StatisticCard(
title: 'Penerima',
count: controller.totalPenerima.value.toString(),
subtitle: 'Terdaftar',
height: 120,
gradient: LinearGradient(
colors: [Colors.blue.shade600, Colors.blue.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
icon: Icons.people,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: StatisticCard(
title: 'Penyaluran',
count: controller.penyaluranTerlaksana.value.toString(),
subtitle: 'Terlaksana',
height: 120,
gradient: LinearGradient(
colors: [Colors.green.shade600, Colors.green.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
icon: Icons.assignment_turned_in,
),
),
const SizedBox(width: 12),
Expanded(
child: StatisticCard(
title: 'Pengaduan',
count: controller.jumlahDiproses.value.toString(),
subtitle: 'Memerlukan Tindakan',
height: 120,
gradient: LinearGradient(
colors: [Colors.red.shade600, Colors.red.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
icon: Icons.warning_amber,
),
),
],
),
],
),
);
}
Widget _buildRecipientsList(TextTheme textTheme) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.people,
color: AppTheme.primaryColor,
size: 20,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
'Daftar Penerima',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
InkWell(
onTap: () => Get.toNamed(Routes.daftarPenerima),
borderRadius: BorderRadius.circular(50),
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(50),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Lihat Semua',
style: TextStyle(
fontSize: 12,
color: AppTheme.primaryColor,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 2),
Icon(
Icons.arrow_forward,
size: 12,
color: AppTheme.primaryColor,
),
],
),
),
),
],
),
const SizedBox(height: 16),
FutureBuilder<List<Map<String, dynamic>>?>(
future: SupabaseService.to.getPenerimaTerbaru(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 100,
child: Center(child: CircularProgressIndicator()),
);
}
if (snapshot.hasError) {
return Container(
height: 100,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 28),
SizedBox(height: 8),
Text(
'Gagal memuat data penerima',
style: TextStyle(color: Colors.red),
),
],
),
),
);
}
final penerimaList = snapshot.data;
if (penerimaList == null || penerimaList.isEmpty) {
return Container(
height: 100,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.person_off, color: Colors.grey, size: 28),
SizedBox(height: 8),
Text(
'Belum ada data penerima',
style: TextStyle(color: Colors.grey),
),
],
),
),
);
}
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: penerimaList.length > 3 ? 3 : penerimaList.length,
itemBuilder: (context, index) {
final penerima = penerimaList[index];
final name =
penerima['nama_lengkap'] ?? 'Nama tidak tersedia';
final nik = penerima['nik'] ?? 'NIK tidak tersedia';
final status = penerima['status'] ?? 'AKTIF';
final id = penerima['id'] ?? 'ID tidak tersedia';
final fotoProfil = penerima['foto_profil'] ?? null;
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade700, Colors.blue.shade500],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.2),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Get.toNamed(Routes.detailPenerima, arguments: id);
},
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
Stack(
children: [
CircleAvatar(
radius: 25,
backgroundColor:
Colors.white.withOpacity(0.2),
backgroundImage: fotoProfil != null &&
fotoProfil.toString().isNotEmpty
? NetworkImage(fotoProfil)
: null,
child: (fotoProfil == null ||
fotoProfil.toString().isEmpty)
? Text(
name
.toString()
.substring(0, 1)
.toUpperCase(),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 24,
),
)
: null,
),
Positioned(
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: status == 'AKTIF'
? Colors.green
: Colors.red,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 1.5,
),
),
width: 12,
height: 12,
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 16,
),
),
const SizedBox(height: 4),
Row(
children: [
const Icon(
Icons.credit_card,
color: Colors.white70,
size: 14,
),
const SizedBox(width: 4),
Expanded(
child: Text(
'NIK: $nik',
style: const TextStyle(
color: Colors.white70,
fontSize: 12,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
status == 'AKTIF'
? Icons.check_circle
: Icons.cancel,
color: status == 'AKTIF'
? Colors.green.shade300
: Colors.red.shade300,
size: 12,
),
const SizedBox(width: 4),
Text(
status,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
),
),
);
},
);
},
),
],
),
);
}
}