Tambahkan fitur konfirmasi penyaluran bantuan untuk Petugas Desa
- Tambahkan kontroler untuk mengelola proses konfirmasi penerima - Buat tampilan konfirmasi penyaluran dengan validasi input - Tambahkan fitur pemilihan tanggal, foto bukti, dan tanda tangan - Perbarui rute untuk mendukung halaman konfirmasi - Integrasikan intl package untuk format tanggal dalam bahasa Indonesia
This commit is contained in:
@ -1,10 +1,21 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class PenerimaController extends GetxController {
|
class PenerimaController extends GetxController {
|
||||||
final RxList<Map<String, dynamic>> daftarPenerima =
|
final RxList<Map<String, dynamic>> daftarPenerima =
|
||||||
<Map<String, dynamic>>[].obs;
|
<Map<String, dynamic>>[].obs;
|
||||||
final RxBool isLoading = false.obs;
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Variabel untuk halaman konfirmasi penerima
|
||||||
|
final RxBool isKonfirmasiChecked = false.obs;
|
||||||
|
final RxBool isIdentitasChecked = false.obs;
|
||||||
|
final RxBool isDataValidChecked = false.obs;
|
||||||
|
final RxString tanggalPenyaluran = ''.obs;
|
||||||
|
final RxString fotoBuktiPath = ''.obs;
|
||||||
|
final RxString tandaTanganPath = ''.obs;
|
||||||
|
final TextEditingController catatanController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@ -20,6 +31,12 @@ class PenerimaController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
catatanController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
void fetchDaftarPenerima() {
|
void fetchDaftarPenerima() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
@ -149,4 +166,147 @@ class PenerimaController extends GetxController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk memilih tanggal penyaluran
|
||||||
|
Future<void> pilihTanggalPenyaluran(BuildContext context) async {
|
||||||
|
final DateTime? picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: DateTime.now(),
|
||||||
|
firstDate: DateTime(2020),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
builder: (context, child) {
|
||||||
|
return Theme(
|
||||||
|
data: ThemeData.light().copyWith(
|
||||||
|
colorScheme: const ColorScheme.light(
|
||||||
|
primary: Color(0xFF2E5077),
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
surface: Colors.white,
|
||||||
|
onSurface: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked != null) {
|
||||||
|
tanggalPenyaluran.value =
|
||||||
|
DateFormat('dd MMMM yyyy', 'id_ID').format(picked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk memilih foto bukti
|
||||||
|
void pilihFotoBukti() {
|
||||||
|
// Simulasi pemilihan foto
|
||||||
|
// Dalam implementasi nyata, gunakan image_picker
|
||||||
|
fotoBuktiPath.value = 'assets/images/bukti_penyaluran.jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menghapus foto bukti
|
||||||
|
void hapusFotoBukti() {
|
||||||
|
fotoBuktiPath.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk membuka signature pad
|
||||||
|
void bukaSignaturePad(BuildContext context) {
|
||||||
|
// Simulasi tanda tangan
|
||||||
|
// Dalam implementasi nyata, gunakan signature_pad atau library serupa
|
||||||
|
tandaTanganPath.value = 'assets/images/tanda_tangan.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menghapus tanda tangan
|
||||||
|
void hapusTandaTangan() {
|
||||||
|
tandaTanganPath.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk konfirmasi penyaluran
|
||||||
|
void konfirmasiPenyaluran(String id) {
|
||||||
|
// Validasi input
|
||||||
|
if (!isKonfirmasiChecked.value) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Anda harus mengkonfirmasi penyaluran bantuan',
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isIdentitasChecked.value) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Anda harus memverifikasi identitas penerima',
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDataValidChecked.value) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Anda harus menyatakan kebenaran data',
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fotoBuktiPath.value.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Bukti foto penyaluran harus diunggah',
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tandaTanganPath.value.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Tanda tangan penerima harus diisi',
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulasi proses konfirmasi
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
// Dalam implementasi nyata, kirim data ke API
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
// Update status penerima
|
||||||
|
final index =
|
||||||
|
daftarPenerima.indexWhere((penerima) => penerima['id'] == id);
|
||||||
|
if (index != -1) {
|
||||||
|
final updatedPenerima =
|
||||||
|
Map<String, dynamic>.from(daftarPenerima[index]);
|
||||||
|
updatedPenerima['status'] = 'Selesai';
|
||||||
|
daftarPenerima[index] = updatedPenerima;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false;
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
isKonfirmasiChecked.value = false;
|
||||||
|
isIdentitasChecked.value = false;
|
||||||
|
isDataValidChecked.value = false;
|
||||||
|
fotoBuktiPath.value = '';
|
||||||
|
tandaTanganPath.value = '';
|
||||||
|
catatanController.clear();
|
||||||
|
|
||||||
|
// Tampilkan pesan sukses
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Konfirmasi penyaluran bantuan berhasil disimpan',
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Kembali ke halaman sebelumnya
|
||||||
|
Get.back();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,14 +38,6 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Detail Penerima'),
|
title: const Text('Detail Penerima'),
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit),
|
|
||||||
onPressed: () {
|
|
||||||
// Implementasi edit penerima
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -413,7 +405,9 @@ class DetailPenerimaView extends GetView<PenerimaController> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Implementasi konfirmasi penyaluran
|
// Navigasi ke halaman konfirmasi penerima
|
||||||
|
Get.toNamed('/daftar-penerima/konfirmasi',
|
||||||
|
arguments: penerima['id']);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.check_circle),
|
icon: const Icon(Icons.check_circle),
|
||||||
label: const Text('Konfirmasi Penyaluran'),
|
label: const Text('Konfirmasi Penyaluran'),
|
||||||
|
652
lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart
Normal file
652
lib/app/modules/petugas_desa/views/konfirmasi_penerima_view.dart
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
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 KonfirmasiPenerimaView extends GetView<PenerimaController> {
|
||||||
|
const KonfirmasiPenerimaView({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('Konfirmasi Penerima'),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final penerima = controller.getPenerimaById(id);
|
||||||
|
|
||||||
|
if (penerima == null) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Konfirmasi Penerima'),
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text('Data penerima tidak ditemukan'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Konfirmasi Penerima'),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header dengan foto dan nama
|
||||||
|
_buildHeader(penerima),
|
||||||
|
|
||||||
|
// Detail informasi penerima
|
||||||
|
_buildDetailInfo(penerima),
|
||||||
|
|
||||||
|
// Detail jadwal dan bantuan
|
||||||
|
_buildDetailJadwalBantuan(penerima),
|
||||||
|
|
||||||
|
// Form konfirmasi
|
||||||
|
_buildKonfirmasiForm(context, 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: 40,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
child: penerima['foto'] != null
|
||||||
|
? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
child: Image.asset(
|
||||||
|
penerima['foto'],
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return const Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 40,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 40,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Nama penerima
|
||||||
|
Text(
|
||||||
|
penerima['nama'] ?? 'Nama tidak tersedia',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
// NIK
|
||||||
|
Text(
|
||||||
|
penerima['nik'] ?? 'NIK tidak tersedia',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// Badge status
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: penerima['status'] == 'Terjadwal'
|
||||||
|
? AppTheme.scheduledColor
|
||||||
|
: AppTheme.completedColor,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
penerima['status'] == 'Terjadwal'
|
||||||
|
? Icons.event_available
|
||||||
|
: Icons.check_circle,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
penerima['status'] ?? 'Status tidak tersedia',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildDetailRow('NIK', penerima['nik'] ?? '-'),
|
||||||
|
_buildDetailRow('No KK', penerima['noKK'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'No Handphone', penerima['noHandphone'] ?? '-'),
|
||||||
|
_buildDetailRow('Email', penerima['email'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'Jenis Kelamin', penerima['jenisKelamin'] ?? '-'),
|
||||||
|
_buildDetailRow('Agama', penerima['agama'] ?? '-'),
|
||||||
|
_buildDetailRow('Tempat, Tanggal Lahir',
|
||||||
|
penerima['tempatTanggalLahir'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'Alamat Lengkap', penerima['alamatLengkap'] ?? '-'),
|
||||||
|
_buildDetailRow('Pekerjaan', penerima['pekerjaan'] ?? '-'),
|
||||||
|
_buildDetailRow('Pendidikan Terakhir',
|
||||||
|
penerima['pendidikanTerakhir'] ?? '-'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailJadwalBantuan(Map<String, dynamic> penerima) {
|
||||||
|
// Simulasi data jadwal dan bantuan
|
||||||
|
final jadwalBantuan = {
|
||||||
|
'tanggal': '15 Agustus 2023',
|
||||||
|
'waktu': '09:00 - 12:00 WIB',
|
||||||
|
'lokasi': 'Balai Desa Gunung Putri, Jl. Raya Gunung Putri No. 10',
|
||||||
|
'jenisBantuan': 'Bantuan Sosial Tunai (BST)',
|
||||||
|
'nilaiNominal': 'Rp 600.000',
|
||||||
|
'keterangan': 'Bantuan diberikan dalam bentuk tunai'
|
||||||
|
};
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Detail Jadwal & Bantuan',
|
||||||
|
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: Column(
|
||||||
|
children: [
|
||||||
|
_buildDetailRow(
|
||||||
|
'Tanggal Penyaluran', jadwalBantuan['tanggal'] ?? '-'),
|
||||||
|
_buildDetailRow('Waktu', jadwalBantuan['waktu'] ?? '-'),
|
||||||
|
_buildDetailRow('Lokasi', jadwalBantuan['lokasi'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'Jenis Bantuan', jadwalBantuan['jenisBantuan'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'Nilai Nominal', jadwalBantuan['nilaiNominal'] ?? '-'),
|
||||||
|
_buildDetailRow(
|
||||||
|
'Keterangan', jadwalBantuan['keterangan'] ?? '-'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailRow(String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 140,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildKonfirmasiForm(
|
||||||
|
BuildContext context, Map<String, dynamic> penerima) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Konfirmasi Penyaluran Bantuan',
|
||||||
|
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: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Status penyaluran
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.infoColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.infoColor.withOpacity(0.2),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
color: AppTheme.infoColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Pastikan penerima hadir dan menerima bantuan sesuai dengan ketentuan.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppTheme.infoColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Checkbox persetujuan petugas
|
||||||
|
const Text(
|
||||||
|
'Persetujuan Petugas',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Checkbox 1
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: controller.isKonfirmasiChecked.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
controller.isKonfirmasiChecked.value =
|
||||||
|
value ?? false;
|
||||||
|
},
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
)),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Saya konfirmasi bahwa penerima ini telah hadir dan menerima bantuan sesuai dengan ketentuan',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checkbox 2
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: controller.isIdentitasChecked.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
controller.isIdentitasChecked.value =
|
||||||
|
value ?? false;
|
||||||
|
},
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
)),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Saya telah memverifikasi identitas penerima sesuai dengan KTP/KK yang ditunjukkan',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checkbox 3
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Obx(() => Checkbox(
|
||||||
|
value: controller.isDataValidChecked.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
controller.isDataValidChecked.value =
|
||||||
|
value ?? false;
|
||||||
|
},
|
||||||
|
activeColor: AppTheme.primaryColor,
|
||||||
|
)),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Saya menyatakan bahwa data yang diinput adalah benar dan dapat dipertanggungjawabkan',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Form bukti foto
|
||||||
|
const Text(
|
||||||
|
'Bukti Foto Penyaluran',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => controller.pilihFotoBukti(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border:
|
||||||
|
Border.all(color: Colors.grey.shade300, width: 1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Obx(() => controller.fotoBuktiPath.value.isEmpty
|
||||||
|
? Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.camera_alt,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Tambahkan Foto Bukti',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Format: JPG, PNG (Maks. 5MB)',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Stack(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.asset(
|
||||||
|
controller.fotoBuktiPath.value,
|
||||||
|
height: 200,
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => controller.hapusFotoBukti(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Tanda tangan digital penerima
|
||||||
|
const Text(
|
||||||
|
'Tanda Tangan Digital Penerima',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => controller.bukaSignaturePad(context),
|
||||||
|
child: Container(
|
||||||
|
height: 150,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Obx(() => controller.tandaTanganPath.value.isEmpty
|
||||||
|
? const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.draw,
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Tap untuk menambahkan tanda tangan',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Stack(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.asset(
|
||||||
|
controller.tandaTanganPath.value,
|
||||||
|
height: 150,
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => controller.hapusTandaTangan(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Form catatan
|
||||||
|
const Text(
|
||||||
|
'Catatan Penyaluran (Opsional)',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.catatanController,
|
||||||
|
maxLines: 3,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Masukkan catatan penyaluran jika ada',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBottomButtons(Map<String, dynamic> penerima) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: const Offset(0, -3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Kembali'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() => ElevatedButton(
|
||||||
|
onPressed: controller.isKonfirmasiChecked.value &&
|
||||||
|
controller.isIdentitasChecked.value &&
|
||||||
|
controller.isDataValidChecked.value &&
|
||||||
|
controller.fotoBuktiPath.value.isNotEmpty &&
|
||||||
|
controller.tandaTanganPath.value.isNotEmpty
|
||||||
|
? () => controller.konfirmasiPenyaluran(penerima['id'])
|
||||||
|
: null,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
disabledBackgroundColor: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
child: const Text('Konfirmasi'),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,8 @@ import 'package:penyaluran_app/app/modules/petugas_desa/bindings/petugas_desa_bi
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/permintaan_penjadwalan_view.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/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/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/views/konfirmasi_penerima_view.dart';
|
||||||
|
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
@ -68,5 +69,10 @@ class AppPages {
|
|||||||
page: () => const DetailPenerimaView(),
|
page: () => const DetailPenerimaView(),
|
||||||
binding: PenerimaBinding(),
|
binding: PenerimaBinding(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: _Paths.konfirmasiPenerima,
|
||||||
|
page: () => const KonfirmasiPenerimaView(),
|
||||||
|
binding: PenerimaBinding(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ abstract class Routes {
|
|||||||
static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan;
|
static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan;
|
||||||
static const daftarPenerima = _Paths.daftarPenerima;
|
static const daftarPenerima = _Paths.daftarPenerima;
|
||||||
static const detailPenerima = _Paths.detailPenerima;
|
static const detailPenerima = _Paths.detailPenerima;
|
||||||
|
static const konfirmasiPenerima = _Paths.konfirmasiPenerima;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Paths {
|
abstract class _Paths {
|
||||||
@ -28,4 +29,5 @@ abstract class _Paths {
|
|||||||
static const permintaanPenjadwalan = '/permintaan-penjadwalan';
|
static const permintaanPenjadwalan = '/permintaan-penjadwalan';
|
||||||
static const daftarPenerima = '/daftar-penerima';
|
static const daftarPenerima = '/daftar-penerima';
|
||||||
static const detailPenerima = '/daftar-penerima/detail';
|
static const detailPenerima = '/daftar-penerima/detail';
|
||||||
|
static const konfirmasiPenerima = '/daftar-penerima/konfirmasi';
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.0"
|
||||||
jwt_decode:
|
jwt_decode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -52,6 +52,9 @@ dependencies:
|
|||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
flutter_svg: ^2.0.17
|
flutter_svg: ^2.0.17
|
||||||
|
|
||||||
|
# Untuk format tanggal dalam bahasa Indonesia
|
||||||
|
intl: ^0.19.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Reference in New Issue
Block a user