1606 lines
55 KiB
Dart
1606 lines
55 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:penyaluran_app/app/data/models/penerima_penyaluran_model.dart';
|
|
import 'package:penyaluran_app/app/modules/penyaluran/detail_penyaluran_controller.dart';
|
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:penyaluran_app/app/modules/penyaluran/konfirmasi_penerima_page.dart';
|
|
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
|
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
|
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
|
|
|
class DetailPenyaluranPage extends StatelessWidget {
|
|
final controller = Get.put(DetailPenyaluranController());
|
|
final ImagePicker _picker = ImagePicker();
|
|
final searchController = TextEditingController();
|
|
final RxString searchQuery = ''.obs;
|
|
final RxString statusFilter = 'SEMUA'.obs;
|
|
final RxBool showAllItems = false.obs;
|
|
final int initialItemCount = 4;
|
|
final ScrollController scrollController = ScrollController();
|
|
final RxBool showScrollToTop = false.obs;
|
|
|
|
DetailPenyaluranPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Tambahkan listener untuk scroll controller
|
|
scrollController.addListener(() {
|
|
if (scrollController.offset > 300) {
|
|
showScrollToTop.value = true;
|
|
} else {
|
|
showScrollToTop.value = false;
|
|
}
|
|
});
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Detail Penyaluran'),
|
|
centerTitle: true,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => Get.back(),
|
|
),
|
|
),
|
|
body: Obx(() {
|
|
if (controller.isLoading.value) {
|
|
return _buildLoadingState();
|
|
}
|
|
|
|
if (controller.penyaluran.value == null) {
|
|
return const Center(
|
|
child: Text('Data penyaluran tidak ditemukan'),
|
|
);
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await controller.refreshData();
|
|
},
|
|
child: SingleChildScrollView(
|
|
controller: scrollController,
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildInfoCard(context),
|
|
const SizedBox(height: 16),
|
|
_buildPenerimaPenyaluranSection(context),
|
|
const SizedBox(height: 24),
|
|
// Menampilkan section alasan pembatalan jika status BATALTERLAKSANA
|
|
if (controller.penyaluran.value?.status?.toUpperCase() ==
|
|
'BATALTERLAKSANA' &&
|
|
controller.penyaluran.value?.alasanPembatalan != null &&
|
|
controller.penyaluran.value!.alasanPembatalan!.isNotEmpty)
|
|
_buildPembatalanSection(context),
|
|
const SizedBox(height: 24),
|
|
// Tombol aksi akan ditampilkan di bottomNavigationBar
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
floatingActionButton: Obx(() => showScrollToTop.value
|
|
? FloatingActionButton(
|
|
mini: true,
|
|
backgroundColor: AppTheme.primaryColor,
|
|
child: const Icon(Icons.arrow_upward),
|
|
onPressed: () {
|
|
scrollController.animateTo(
|
|
0,
|
|
duration: const Duration(milliseconds: 500),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
},
|
|
)
|
|
: const SizedBox.shrink()),
|
|
bottomNavigationBar: Obx(() {
|
|
final status = controller.penyaluran.value?.status?.toUpperCase() ?? '';
|
|
if (status == 'AKTIF' ||
|
|
status == 'DISETUJUI' ||
|
|
status == 'DIJADWALKAN') {
|
|
return _buildActionButtons(context);
|
|
}
|
|
return const SizedBox.shrink();
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingState() {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const CircularProgressIndicator(),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Memuat data penyaluran...',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoCard(BuildContext context) {
|
|
final penyaluran = controller.penyaluran.value!;
|
|
final skema = controller.skemaBantuan.value;
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header dengan status
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text(
|
|
'Informasi Penyaluran',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
_buildStatusBadge(penyaluran.status ?? '-'),
|
|
],
|
|
),
|
|
const Divider(height: 24),
|
|
|
|
// Informasi penyaluran
|
|
_buildInfoRow('Nama', penyaluran.nama ?? '-'),
|
|
_buildInfoRow(
|
|
'Tanggal',
|
|
penyaluran.tanggalPenyaluran != null
|
|
? DateFormatter.formatDateTime(
|
|
penyaluran.tanggalPenyaluran!)
|
|
: 'Belum dijadwalkan'),
|
|
// Tampilkan tanggal selesai jika status TERLAKSANA atau BATALTERLAKSANA
|
|
if (penyaluran.status == 'TERLAKSANA' ||
|
|
penyaluran.status == 'BATALTERLAKSANA')
|
|
_buildInfoRow(
|
|
'Tanggal Selesai',
|
|
penyaluran.tanggalSelesai != null
|
|
? DateFormatter.formatDateTime(penyaluran.tanggalSelesai!)
|
|
: '-'),
|
|
_buildInfoRow(
|
|
'Jumlah Penerima', '${penyaluran.jumlahPenerima ?? 0} orang'),
|
|
|
|
// Informasi skema bantuan
|
|
if (skema != null) ...[
|
|
const Divider(height: 24),
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.category,
|
|
size: 16, color: AppTheme.secondaryColor),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Skema: ${skema.nama ?? '-'}',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.secondaryColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
skema.deskripsi ?? 'Tidak ada deskripsi',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
|
|
// Alasan penolakan jika ada
|
|
if (penyaluran.alasanPembatalan != null &&
|
|
penyaluran.alasanPembatalan!.isNotEmpty) ...[
|
|
const Divider(height: 24),
|
|
if (penyaluran.status?.toUpperCase() == 'BATALTERLAKSANA') ...[
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.errorColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: AppTheme.errorColor.withOpacity(0.5),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.cancel_outlined,
|
|
color: AppTheme.errorColor,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Alasan Pembatalan:',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.errorColor,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
penyaluran.alasanPembatalan!,
|
|
style: TextStyle(
|
|
color: Colors.red[700],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
] else ...[
|
|
Text(
|
|
'Alasan Pembatalan:',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.red[700],
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
penyaluran.alasanPembatalan!,
|
|
style: TextStyle(
|
|
color: Colors.red[700],
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPenerimaPenyaluranSection(BuildContext context) {
|
|
return Card(
|
|
elevation: 3,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header dengan judul dan jumlah penerima
|
|
Container(
|
|
padding: const EdgeInsets.all(16.0),
|
|
decoration: const BoxDecoration(
|
|
color: AppTheme.primaryColor,
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(16),
|
|
topRight: Radius.circular(16),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Row(
|
|
children: [
|
|
Icon(
|
|
Icons.people_alt_rounded,
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'Daftar Penerima',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Obx(() => Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
'${_getFilteredPenerima().length} Orang',
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Statistik penerima
|
|
Obx(() => _buildStatistikPenerima(context)),
|
|
|
|
// Search field dengan filter status
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
|
child: Column(
|
|
children: [
|
|
// Search field dengan icon dan tombol hapus
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
spreadRadius: 1,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: TextField(
|
|
controller: searchController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Cari nama, NIK, atau alamat...',
|
|
prefixIcon: const Icon(Icons.search,
|
|
color: AppTheme.primaryColor),
|
|
suffixIcon: Obx(() => searchQuery.value.isNotEmpty
|
|
? IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () {
|
|
searchController.clear();
|
|
searchQuery.value = '';
|
|
},
|
|
)
|
|
: const SizedBox.shrink()),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: const BorderSide(
|
|
color: AppTheme.primaryColor, width: 1),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
vertical: 12, horizontal: 16),
|
|
),
|
|
onChanged: (value) {
|
|
searchQuery.value = value.toLowerCase();
|
|
},
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Filter status dengan label
|
|
SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: [
|
|
_buildFilterChip('Semua', true),
|
|
const SizedBox(width: 8),
|
|
_buildFilterChip('Sudah Menerima', false),
|
|
const SizedBox(width: 8),
|
|
_buildFilterChip('Belum Menerima', false),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Daftar penerima
|
|
Obx(() {
|
|
final filteredList = _getFilteredPenerima();
|
|
|
|
if (filteredList.isEmpty) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(32.0),
|
|
child: Column(
|
|
children: [
|
|
Icon(
|
|
Icons.person_off_outlined,
|
|
size: 80,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Tidak ada data penerima',
|
|
style:
|
|
Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Coba ubah filter pencarian',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Colors.grey.shade500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Tentukan jumlah item yang akan ditampilkan
|
|
final itemsToShow =
|
|
showAllItems.value || filteredList.length <= initialItemCount
|
|
? filteredList
|
|
: filteredList.sublist(0, initialItemCount);
|
|
|
|
// Tentukan jumlah kolom berdasarkan lebar layar
|
|
final screenWidth = MediaQuery.of(context).size.width;
|
|
final crossAxisCount = screenWidth > 600 ? 2 : 1;
|
|
final childAspectRatio = screenWidth > 600 ? 2.5 : 2.2;
|
|
|
|
return Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: AnimationLimiter(
|
|
child: GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: crossAxisCount,
|
|
childAspectRatio: childAspectRatio,
|
|
crossAxisSpacing: 12,
|
|
mainAxisSpacing: 12,
|
|
),
|
|
itemCount: itemsToShow.length,
|
|
itemBuilder: (context, index) {
|
|
return AnimationConfiguration.staggeredGrid(
|
|
position: index,
|
|
duration: const Duration(milliseconds: 375),
|
|
columnCount: crossAxisCount,
|
|
child: ScaleAnimation(
|
|
child: FadeInAnimation(
|
|
child: _buildPenerimaItem(
|
|
context, itemsToShow[index]),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
// Tombol untuk menampilkan lebih banyak atau lebih sedikit item
|
|
if (filteredList.length > initialItemCount)
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: TextButton.icon(
|
|
onPressed: () {
|
|
showAllItems.value = !showAllItems.value;
|
|
},
|
|
icon: Icon(
|
|
showAllItems.value
|
|
? Icons.expand_less
|
|
: Icons.expand_more,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
label: Text(
|
|
showAllItems.value
|
|
? 'Tampilkan Lebih Sedikit'
|
|
: 'Lihat Semua (${filteredList.length})',
|
|
style: const TextStyle(
|
|
color: AppTheme.primaryColor,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatistikPenerima(BuildContext context) {
|
|
// Hitung jumlah yang sudah dan belum menerima
|
|
int totalPenerima = controller.penerimaPenyaluran.length;
|
|
int sudahMenerima = controller.penerimaPenyaluran
|
|
.where((item) => item.statusPenerimaan?.toUpperCase() == 'DITERIMA')
|
|
.length;
|
|
int belumMenerima = totalPenerima - sudahMenerima;
|
|
|
|
// Hitung persentase
|
|
double persentaseSudah =
|
|
totalPenerima > 0 ? (sudahMenerima / totalPenerima) * 100 : 0;
|
|
|
|
// Tentukan warna berdasarkan persentase
|
|
Color progressColor = persentaseSudah > 75
|
|
? AppTheme.successColor
|
|
: persentaseSudah > 50
|
|
? AppTheme.warningColor
|
|
: AppTheme.errorColor;
|
|
|
|
// Tentukan lebar layar untuk responsivitas
|
|
final screenWidth = MediaQuery.of(context).size.width;
|
|
final isWideScreen = screenWidth > 600;
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
border: Border(
|
|
bottom: BorderSide(color: Colors.grey.shade200, width: 1),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.insert_chart_outlined,
|
|
size: 18,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Progres Penyaluran',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: progressColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: progressColor.withOpacity(0.3)),
|
|
),
|
|
child: Text(
|
|
'${persentaseSudah.toStringAsFixed(1)}%',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: progressColor,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Progress bar dengan label
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Sudah Menerima',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
Text(
|
|
'$sudahMenerima dari $totalPenerima',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 6),
|
|
Stack(
|
|
children: [
|
|
// Background progress bar
|
|
Container(
|
|
height: 12,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade200,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
),
|
|
// Foreground progress bar
|
|
FractionallySizedBox(
|
|
widthFactor: persentaseSudah / 100,
|
|
child: Container(
|
|
height: 12,
|
|
decoration: BoxDecoration(
|
|
color: progressColor,
|
|
borderRadius: BorderRadius.circular(6),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: progressColor.withOpacity(0.3),
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Statistik detail
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Expanded(
|
|
child: _buildStatistikItem(
|
|
'Sudah Menerima',
|
|
sudahMenerima,
|
|
AppTheme.successColor,
|
|
Icons.check_circle,
|
|
)),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: _buildStatistikItem(
|
|
'Belum Menerima',
|
|
belumMenerima,
|
|
AppTheme.warningColor,
|
|
Icons.pending,
|
|
)),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatistikItem(
|
|
String label, int value, Color color, IconData icon) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: color.withOpacity(0.3)),
|
|
),
|
|
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: 14),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'$value',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: color,
|
|
),
|
|
),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFilterChip(String label, bool isSelected) {
|
|
String filterValue;
|
|
int count = 0;
|
|
|
|
// Konversi label ke nilai filter dan hitung jumlah
|
|
if (label == 'Semua') {
|
|
filterValue = 'SEMUA';
|
|
count = controller.penerimaPenyaluran.length;
|
|
} else if (label == 'Sudah Menerima') {
|
|
filterValue = 'DITERIMA';
|
|
count = controller.penerimaPenyaluran
|
|
.where((item) => item.statusPenerimaan?.toUpperCase() == 'DITERIMA')
|
|
.length;
|
|
} else {
|
|
filterValue = 'BELUM';
|
|
count = controller.penerimaPenyaluran
|
|
.where((item) => item.statusPenerimaan?.toUpperCase() != 'DITERIMA')
|
|
.length;
|
|
}
|
|
|
|
// Cek apakah filter ini yang aktif
|
|
isSelected = statusFilter.value == filterValue;
|
|
|
|
// Tentukan icon berdasarkan jenis filter
|
|
IconData filterIcon;
|
|
if (label == 'Semua') {
|
|
filterIcon = Icons.list_alt;
|
|
} else if (label == 'Sudah Menerima') {
|
|
filterIcon = Icons.check_circle;
|
|
} else {
|
|
filterIcon = Icons.pending;
|
|
}
|
|
|
|
return FilterChip(
|
|
avatar: Icon(
|
|
filterIcon,
|
|
size: 16,
|
|
color: isSelected ? Colors.white : AppTheme.primaryColor,
|
|
),
|
|
label: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(label),
|
|
const SizedBox(width: 4),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? Colors.white.withOpacity(0.3)
|
|
: AppTheme.primaryColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Text(
|
|
'$count',
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
color: isSelected ? Colors.white : AppTheme.primaryColor,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
selected: isSelected,
|
|
// checkmarkColor: Colors.white,
|
|
showCheckmark: false,
|
|
selectedColor: AppTheme.primaryColor,
|
|
labelStyle: TextStyle(
|
|
color: isSelected ? Colors.white : Colors.black87,
|
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
|
),
|
|
backgroundColor: Colors.white,
|
|
elevation: isSelected ? 0 : 1,
|
|
shadowColor: Colors.grey.withOpacity(0.2),
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side: BorderSide(
|
|
color: isSelected ? Colors.transparent : Colors.grey.shade300,
|
|
width: 1,
|
|
),
|
|
),
|
|
onSelected: (selected) {
|
|
if (selected) {
|
|
statusFilter.value = filterValue;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildPenerimaItem(
|
|
BuildContext context, PenerimaPenyaluranModel item) {
|
|
final warga = item.warga;
|
|
final bool sudahMenerima =
|
|
item.statusPenerimaan?.toUpperCase() == 'DITERIMA';
|
|
final Color cardColor = Colors.white;
|
|
final Color borderColor = Colors.grey.shade300;
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(color: borderColor, width: 1.5),
|
|
),
|
|
color: cardColor,
|
|
child: InkWell(
|
|
onTap: () => _showDetailPenerima(context, item),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// Avatar
|
|
CircleAvatar(
|
|
radius: 24,
|
|
backgroundColor: sudahMenerima
|
|
? AppTheme.successColor.withOpacity(0.2)
|
|
: AppTheme.primaryColor.withOpacity(0.1),
|
|
child: Text(
|
|
warga != null && warga['nama_lengkap'] != null
|
|
? warga['nama_lengkap']
|
|
.toString()
|
|
.substring(0, 1)
|
|
.toUpperCase()
|
|
: '?',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: sudahMenerima
|
|
? AppTheme.successColor
|
|
: AppTheme.primaryColor,
|
|
fontSize: 20,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
|
|
// Informasi penerima
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Nama dan NIK
|
|
Text(
|
|
warga != null
|
|
? warga['nama_lengkap'] ?? 'Nama tidak tersedia'
|
|
: 'Nama tidak tersedia',
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
'NIK: ${warga != null ? warga['nik'] ?? '-' : '-'}',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: Colors.grey[600],
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
|
|
const SizedBox(height: 4),
|
|
_buildStatusChipNew(item.statusPenerimaan ?? '-'),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Status dan icon
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.arrow_forward_ios,
|
|
size: 14,
|
|
color:
|
|
sudahMenerima ? AppTheme.successColor : Colors.grey),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusChipNew(String status) {
|
|
Color backgroundColor;
|
|
Color textColor = Colors.white;
|
|
String statusText = _getStatusPenerimaanText(status);
|
|
IconData iconData;
|
|
|
|
// Konversi status ke format yang diinginkan
|
|
if (status.toUpperCase() == 'DITERIMA') {
|
|
backgroundColor = AppTheme.successColor;
|
|
statusText = 'Sudah Menerima';
|
|
iconData = Icons.check_circle;
|
|
} else {
|
|
// Semua status selain DITERIMA dianggap sebagai BELUMMENERIMA
|
|
backgroundColor = AppTheme.warningColor;
|
|
statusText = 'Belum Menerima';
|
|
iconData = Icons.pending;
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: backgroundColor,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
iconData,
|
|
color: textColor,
|
|
size: 12,
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
statusText,
|
|
style: TextStyle(
|
|
color: textColor,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusBadge(String status) {
|
|
Color backgroundColor;
|
|
Color textColor = Colors.white;
|
|
String statusText = _getStatusText(status);
|
|
|
|
switch (status.toUpperCase()) {
|
|
case 'DIJADWALKAN':
|
|
backgroundColor = AppTheme.processedColor;
|
|
break;
|
|
case 'AKTIF':
|
|
backgroundColor = AppTheme.scheduledColor;
|
|
break;
|
|
case 'TERLAKSANA':
|
|
backgroundColor = AppTheme.completedColor;
|
|
break;
|
|
case 'BATALTERLAKSANA':
|
|
backgroundColor = AppTheme.errorColor;
|
|
break;
|
|
default:
|
|
backgroundColor = AppTheme.infoColor;
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: backgroundColor,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Text(
|
|
statusText,
|
|
style: TextStyle(
|
|
color: textColor,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildActionButtons(BuildContext context) {
|
|
final status = controller.penyaluran.value?.status?.toUpperCase() ?? '';
|
|
|
|
if (controller.isProcessing.value) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Container untuk tombol-tombol
|
|
Widget buildButtonContainer(List<Widget> children) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16.0),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.3),
|
|
spreadRadius: 1,
|
|
blurRadius: 5,
|
|
offset: const Offset(0, -3),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: children,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Tombol Batalkan yang digunakan berulang
|
|
Widget cancelButton = Expanded(
|
|
child: OutlinedButton.icon(
|
|
icon: const Icon(Icons.cancel),
|
|
label: const Text('Batalkan'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppTheme.errorColor,
|
|
side: const BorderSide(color: AppTheme.errorColor),
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
onPressed: () => _showBatalkanDialog(context),
|
|
),
|
|
);
|
|
|
|
if (status == 'AKTIF') {
|
|
return buildButtonContainer([
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
icon: const Icon(Icons.check_circle),
|
|
label: const Text('Selesaikan'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppTheme.successColor,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
onPressed: controller.selesaikanPenyaluran,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
cancelButton,
|
|
]);
|
|
} else if (status == 'DIJADWALKAN') {
|
|
return buildButtonContainer([cancelButton]);
|
|
}
|
|
|
|
// Untuk status lainnya tidak menampilkan tombol
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
Widget _buildInfoRow(String label, String value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12.0),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 100,
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showKonfirmasiPenerimaan(
|
|
BuildContext context, PenerimaPenyaluranModel penerima) {
|
|
// Dapatkan data jumlah bantuan dari penerima
|
|
final jumlahBantuan = penerima.jumlahBantuan?.toString() ?? '5';
|
|
|
|
// Navigasi ke halaman konfirmasi penerima
|
|
Get.to(
|
|
() => KonfirmasiPenerimaPage(
|
|
penerima: penerima,
|
|
bentukBantuan:
|
|
null, // Tidak ada data bentuk bantuan yang tersedia langsung
|
|
jumlahBantuan: jumlahBantuan,
|
|
tanggalPenyaluran: controller.penyaluran.value?.tanggalPenyaluran,
|
|
),
|
|
)?.then((result) {
|
|
if (result == true) {
|
|
// Refresh data jika konfirmasi berhasil
|
|
controller.refreshData();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _showBatalkanDialog(BuildContext context) {
|
|
final TextEditingController alasanController = TextEditingController();
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Batalkan Penyaluran'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text('Masukkan alasan pembatalan penyaluran:'),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: alasanController,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Alasan pembatalan',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
maxLines: 3,
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Get.back(),
|
|
child: const Text('Batal'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
if (alasanController.text.trim().isEmpty) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Alasan pembatalan tidak boleh kosong',
|
|
backgroundColor: Colors.red,
|
|
colorText: Colors.white,
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
);
|
|
return;
|
|
}
|
|
|
|
controller.batalkanPenyaluran(alasanController.text.trim());
|
|
Get.back();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppTheme.errorColor,
|
|
),
|
|
child: const Text('Batalkan'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showDetailPenerima(
|
|
BuildContext context, PenerimaPenyaluranModel penerima) {
|
|
final warga = penerima.warga;
|
|
final bool sudahMenerima =
|
|
penerima.statusPenerimaan?.toUpperCase() == 'DITERIMA';
|
|
final Color statusColor =
|
|
sudahMenerima ? AppTheme.successColor : AppTheme.warningColor;
|
|
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
constraints: BoxConstraints(
|
|
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
|
),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Handle untuk drag
|
|
Center(
|
|
child: Container(
|
|
width: 50,
|
|
height: 5,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[300],
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Header dengan avatar dan nama
|
|
Row(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 30,
|
|
backgroundColor: statusColor.withOpacity(0.2),
|
|
child: Text(
|
|
warga != null && warga['nama_lengkap'] != null
|
|
? warga['nama_lengkap']
|
|
.toString()
|
|
.substring(0, 1)
|
|
.toUpperCase()
|
|
: '?',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: statusColor,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
warga != null
|
|
? warga['nama_lengkap'] ?? 'Nama tidak tersedia'
|
|
: 'Nama tidak tersedia',
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
_buildStatusChipNew(penerima.statusPenerimaan ?? '-'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Informasi biodata
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Biodata Singkat',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const Divider(height: 24),
|
|
if (warga != null) ...[
|
|
_buildInfoRow('NIK', warga['nik'] ?? '-'),
|
|
_buildInfoRow('Alamat Lengkap',
|
|
'${warga['alamat'] ?? '-'} Desa ${warga['desa'] ?? '-'} Kecamatan ${warga['kecamatan'] ?? '-'} Kabupaten ${warga['kabupaten'] ?? '-'} Provinsi ${warga['provinsi'] ?? '-'}'),
|
|
_buildInfoRow(
|
|
'Jenis Kelamin', warga['jenis_kelamin'] ?? '-'),
|
|
_buildInfoRow('No. Telepon', warga['no_hp'] ?? '-'),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Informasi penerimaan
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: statusColor.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: statusColor.withOpacity(0.3)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
sudahMenerima ? Icons.check_circle : Icons.pending,
|
|
color: statusColor,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Informasi Penerimaan',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: statusColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Divider(height: 24),
|
|
_buildInfoRow(
|
|
'Status Penerimaan',
|
|
_getStatusPenerimaanText(
|
|
penerima.statusPenerimaan ?? '-')),
|
|
if (penerima.tanggalPenerimaan != null)
|
|
_buildInfoRow(
|
|
'Tanggal Penerimaan',
|
|
DateFormatter.formatDate(
|
|
penerima.tanggalPenerimaan!)),
|
|
if (penerima.jumlahBantuan != null)
|
|
_buildInfoRow('Jumlah Bantuan',
|
|
penerima.jumlahBantuan.toString()),
|
|
if (penerima.keterangan != null &&
|
|
penerima.keterangan!.isNotEmpty)
|
|
_buildInfoRow('Keterangan', penerima.keterangan!),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Bukti penerimaan
|
|
if (penerima.buktiPenerimaan != null &&
|
|
penerima.buktiPenerimaan!.isNotEmpty) ...[
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Bukti Penerimaan',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.network(
|
|
penerima.buktiPenerimaan!,
|
|
height: 200,
|
|
width: double.infinity,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
height: 200,
|
|
width: double.infinity,
|
|
color: Colors.grey[300],
|
|
child: const Center(
|
|
child: Text('Gagal memuat gambar'),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
// Tombol konfirmasi penerimaan
|
|
if (controller.penyaluran.value?.status?.toUpperCase() ==
|
|
'AKTIF' &&
|
|
penerima.statusPenerimaan?.toUpperCase() != 'DITERIMA') ...[
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
icon: const Icon(Icons.check_circle),
|
|
label: const Text(
|
|
'Konfirmasi Penerimaan',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppTheme.successColor,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
_showKonfirmasiPenerimaan(context, penerima);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
],
|
|
|
|
// Tombol tutup
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.grey.shade200,
|
|
foregroundColor: Colors.black87,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: const Text(
|
|
'Tutup',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
String _getStatusText(String status) {
|
|
switch (status.toUpperCase()) {
|
|
case 'DIJADWALKAN':
|
|
return 'Terjadwal';
|
|
case 'AKTIF':
|
|
return 'Aktif';
|
|
case 'TERLAKSANA':
|
|
return 'Terlaksana';
|
|
case 'BATALTERLAKSANA':
|
|
return 'Batal Terlaksana';
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
|
|
String _getStatusPenerimaanText(String status) {
|
|
// Konversi status ke format yang diinginkan
|
|
if (status.toUpperCase() == 'DITERIMA') {
|
|
return 'Sudah Menerima';
|
|
} else {
|
|
// Semua status selain DITERIMA dianggap sebagai BELUMMENERIMA
|
|
return 'Belum Menerima';
|
|
}
|
|
}
|
|
|
|
Widget _buildPembatalanSection(BuildContext context) {
|
|
final penyaluran = controller.penyaluran.value!;
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
side: BorderSide(
|
|
color: AppTheme.errorColor.withOpacity(0.5),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.cancel_outlined,
|
|
color: AppTheme.errorColor,
|
|
size: 24,
|
|
),
|
|
const SizedBox(width: 8),
|
|
const Text(
|
|
'Informasi Pembatalan',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.errorColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Divider(height: 24),
|
|
_buildInfoRow('Status', 'Batal Terlaksana'),
|
|
if (penyaluran.tanggalSelesai != null)
|
|
_buildInfoRow('Tanggal Pembatalan',
|
|
DateFormatter.formatDateTime(penyaluran.tanggalSelesai!)),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
'Alasan Pembatalan:',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 14,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.errorColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
penyaluran.alasanPembatalan!,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.red[700],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<PenerimaPenyaluranModel> _getFilteredPenerima() {
|
|
final query = searchQuery.value;
|
|
final status = statusFilter.value;
|
|
|
|
// Filter dasar berdasarkan query pencarian
|
|
List<PenerimaPenyaluranModel> filteredList = controller.penerimaPenyaluran;
|
|
|
|
if (query.isNotEmpty) {
|
|
filteredList = filteredList.where((item) {
|
|
final warga = item.warga;
|
|
if (warga == null) return false;
|
|
|
|
final nama = warga['nama_lengkap']?.toString().toLowerCase() ?? '';
|
|
final nik = warga['nik']?.toString().toLowerCase() ?? '';
|
|
final alamat = warga['alamat']?.toString().toLowerCase() ?? '';
|
|
final statusPenerimaan = item.statusPenerimaan?.toLowerCase() ?? '';
|
|
|
|
return nama.contains(query) ||
|
|
nik.contains(query) ||
|
|
alamat.contains(query) ||
|
|
statusPenerimaan.contains(query);
|
|
}).toList();
|
|
}
|
|
|
|
// Filter tambahan berdasarkan status
|
|
if (status != 'SEMUA') {
|
|
filteredList = filteredList.where((item) {
|
|
if (status == 'DITERIMA') {
|
|
return item.statusPenerimaan?.toUpperCase() == 'DITERIMA';
|
|
} else {
|
|
// Filter untuk yang belum menerima
|
|
return item.statusPenerimaan?.toUpperCase() != 'DITERIMA';
|
|
}
|
|
}).toList();
|
|
}
|
|
|
|
return filteredList;
|
|
}
|
|
}
|