Tambahkan fitur pengaduan dan perbarui navigasi Petugas Desa
- Tambahkan kontroler untuk manajemen data pengaduan - Buat tampilan PengaduanView untuk menampilkan daftar pengaduan - Perbarui navigasi dengan menambahkan tab dan item baru untuk pengaduan - Tambahkan logika untuk menghitung dan menampilkan jumlah pengaduan yang diproses - Integrasikan fitur pengaduan ke dalam drawer dan bottom navigation bar
This commit is contained in:
@ -6,6 +6,7 @@ class PetugasDesaBinding extends Bindings {
|
||||
void dependencies() {
|
||||
Get.lazyPut<PetugasDesaController>(
|
||||
() => PetugasDesaController(),
|
||||
fenix: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,166 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
|
||||
class JadwalSectionWidget extends StatelessWidget {
|
||||
final PetugasDesaController controller;
|
||||
final String title;
|
||||
final List<Map<String, dynamic>> jadwalList;
|
||||
final String status;
|
||||
|
||||
const JadwalSectionWidget({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.title,
|
||||
required this.jadwalList,
|
||||
required this.status,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() {
|
||||
final currentJadwalList = _getCurrentJadwalList();
|
||||
|
||||
if (currentJadwalList.isEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Tidak ada jadwal $title',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: currentJadwalList
|
||||
.map((jadwal) => _buildJadwalItem(textTheme, jadwal))
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _getCurrentJadwalList() {
|
||||
switch (title) {
|
||||
case 'Hari Ini':
|
||||
return controller.jadwalHariIni;
|
||||
case 'Mendatang':
|
||||
return controller.jadwalMendatang;
|
||||
case 'Selesai':
|
||||
return controller.jadwalSelesai;
|
||||
default:
|
||||
return jadwalList;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildJadwalItem(TextTheme textTheme, Map<String, dynamic> jadwal) {
|
||||
Color statusColor;
|
||||
switch (status) {
|
||||
case 'Aktif':
|
||||
statusColor = Colors.green;
|
||||
break;
|
||||
case 'Terjadwal':
|
||||
statusColor = Colors.blue;
|
||||
break;
|
||||
case 'Selesai':
|
||||
statusColor = Colors.grey;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.orange;
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
jadwal['lokasi'] ?? '',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Jenis Bantuan: ${jadwal['jenis_bantuan'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tanggal: ${jadwal['tanggal'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Waktu: ${jadwal['waktu'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
if (jadwal['jumlah_penerima'] != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Jumlah Penerima: ${jadwal['jumlah_penerima']}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
||||
final PetugasDesaController controller;
|
||||
|
||||
const PermintaanPenjadwalanSummaryWidget({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Obx(() {
|
||||
final jumlahPermintaan = controller.jumlahPermintaanPenjadwalan.value;
|
||||
final permintaanList = controller.permintaanPenjadwalan;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(30),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: jumlahPermintaan > 0
|
||||
? Colors.orange.withAlpha(50)
|
||||
: Colors.grey.withAlpha(30),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Permintaan Penjadwalan',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: jumlahPermintaan > 0
|
||||
? Colors.red.withAlpha(26)
|
||||
: Colors.grey.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'$jumlahPermintaan',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: jumlahPermintaan > 0 ? Colors.red : Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (jumlahPermintaan == 0)
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.event_note,
|
||||
size: 48,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tidak ada permintaan penjadwalan',
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Column(
|
||||
children: [
|
||||
...permintaanList.take(1).map((permintaan) =>
|
||||
_buildPermintaanPreview(textTheme, permintaan)),
|
||||
if (jumlahPermintaan > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
'+ ${jumlahPermintaan - 1} permintaan lainnya',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => Get.toNamed(Routes.permintaanPenjadwalan),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.visibility),
|
||||
label: const Text('Lihat Semua Permintaan'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildPermintaanPreview(
|
||||
TextTheme textTheme, Map<String, dynamic> permintaan) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withAlpha(15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
permintaan['nama'] ?? '',
|
||||
style: textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Menunggu',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.orange,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Jenis: ${permintaan['jenis_bantuan'] ?? ''}',
|
||||
style: textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'Tanggal: ${permintaan['tanggal_permintaan'] ?? ''}',
|
||||
style: textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PermintaanPenjadwalanWidget extends StatelessWidget {
|
||||
final PetugasDesaController controller;
|
||||
|
||||
const PermintaanPenjadwalanWidget({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Permintaan Penjadwalan',
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Obx(() => Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${controller.jumlahPermintaanPenjadwalan.value}',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() {
|
||||
final permintaanList = controller.permintaanPenjadwalan;
|
||||
|
||||
// Jika tidak ada permintaan, tampilkan pesan kosong
|
||||
if (permintaanList.isEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.event_note,
|
||||
size: 48,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Tidak ada permintaan penjadwalan',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: permintaanList
|
||||
.map(
|
||||
(permintaan) => _buildPermintaanItem(textTheme, permintaan))
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Widget untuk menampilkan item permintaan penjadwalan
|
||||
Widget _buildPermintaanItem(
|
||||
TextTheme textTheme, Map<String, dynamic> permintaan) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: Colors.orange.withAlpha(50),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
permintaan['nama'] ?? '',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'Menunggu',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.orange,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'NIK: ${permintaan['nik'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Jenis Bantuan: ${permintaan['jenis_bantuan'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tanggal Permintaan: ${permintaan['tanggal_permintaan'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Alamat: ${permintaan['alamat'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => _showTolakDialog(permintaan),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
side: const BorderSide(color: Colors.red),
|
||||
),
|
||||
child: const Text('Tolak'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => _showKonfirmasiDialog(permintaan),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Konfirmasi'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Dialog untuk konfirmasi permintaan
|
||||
void _showKonfirmasiDialog(Map<String, dynamic> permintaan) {
|
||||
String? selectedJadwalId;
|
||||
|
||||
// Data jadwal yang tersedia dari controller
|
||||
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: jadwal['id'],
|
||||
child: Text(
|
||||
'${jadwal['tanggal']} - ${jadwal['lokasi']} (${jadwal['jenis_bantuan']})'),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// Tambahkan opsi jadwal lain jika diperlukan
|
||||
jadwalOptions.add(
|
||||
const DropdownMenuItem<String>(
|
||||
value: '3',
|
||||
child: Text('25 April 2023 - Kantor Kepala Desa (Beras)'),
|
||||
),
|
||||
);
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Konfirmasi Permintaan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama']}.'),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Pilih jadwal penyaluran:'),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
items: jadwalOptions,
|
||||
onChanged: (value) {
|
||||
selectedJadwalId = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (selectedJadwalId != null) {
|
||||
// Panggil metode konfirmasi di controller
|
||||
controller.konfirmasiPermintaanPenjadwalan(
|
||||
permintaan['id'],
|
||||
selectedJadwalId!,
|
||||
);
|
||||
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Permintaan penjadwalan berhasil dikonfirmasi',
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Peringatan',
|
||||
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
||||
backgroundColor: Colors.orange,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
),
|
||||
child: const Text('Konfirmasi'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Dialog untuk menolak permintaan
|
||||
void _showTolakDialog(Map<String, dynamic> permintaan) {
|
||||
final TextEditingController alasanController = TextEditingController();
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Tolak Permintaan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama']}.'),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Alasan penolakan:'),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: alasanController,
|
||||
maxLines: 3,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Masukkan alasan penolakan',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (alasanController.text.trim().isNotEmpty) {
|
||||
// Panggil metode tolak di controller
|
||||
controller.tolakPermintaanPenjadwalan(
|
||||
permintaan['id'],
|
||||
alasanController.text.trim(),
|
||||
);
|
||||
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Permintaan penjadwalan berhasil ditolak',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Peringatan',
|
||||
'Silakan masukkan alasan penolakan',
|
||||
backgroundColor: Colors.orange,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text('Tolak'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -31,6 +31,11 @@ class PetugasDesaController extends GetxController {
|
||||
final RxList<Map<String, dynamic>> jadwalSelesai =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
|
||||
// Data untuk permintaan penjadwalan
|
||||
final RxList<Map<String, dynamic>> permintaanPenjadwalan =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
final RxInt jumlahPermintaanPenjadwalan = 0.obs;
|
||||
|
||||
// Data untuk notifikasi
|
||||
final RxList<Map<String, dynamic>> notifikasiBelumDibaca =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
@ -50,6 +55,13 @@ class PetugasDesaController extends GetxController {
|
||||
final RxInt jumlahTerverifikasi = 0.obs;
|
||||
final RxInt jumlahDitolak = 0.obs;
|
||||
|
||||
// Data untuk pengaduan
|
||||
final RxList<Map<String, dynamic>> daftarPengaduan =
|
||||
<Map<String, dynamic>>[].obs;
|
||||
final RxInt jumlahDiproses = 0.obs;
|
||||
final RxInt jumlahTindakan = 0.obs;
|
||||
final RxInt jumlahSelesai = 0.obs;
|
||||
|
||||
// Controller untuk pencarian
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
|
||||
@ -60,12 +72,19 @@ class PetugasDesaController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// Inisialisasi manual untuk pengaduan (untuk debugging)
|
||||
jumlahDiproses.value = 3;
|
||||
print('onInit - Jumlah pengaduan diproses: ${jumlahDiproses.value}');
|
||||
|
||||
loadRoleData();
|
||||
loadDashboardData();
|
||||
loadJadwalData();
|
||||
loadPermintaanPenjadwalanData();
|
||||
loadNotifikasiData();
|
||||
loadInventarisData();
|
||||
loadPenitipanData();
|
||||
loadPengaduanData();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -187,6 +206,43 @@ class PetugasDesaController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadPermintaanPenjadwalanData() async {
|
||||
try {
|
||||
// Simulasi data untuk permintaan penjadwalan
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
|
||||
permintaanPenjadwalan.value = [
|
||||
{
|
||||
'id': '1',
|
||||
'nama': 'Ahmad Sulaiman',
|
||||
'nik': '3201234567890001',
|
||||
'jenis_bantuan': 'Beras',
|
||||
'tanggal_permintaan': '14 April 2023',
|
||||
'alamat': 'Dusun Sukamaju RT 02/03',
|
||||
'status': 'menunggu',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'nama': 'Siti Aminah',
|
||||
'nik': '3201234567890002',
|
||||
'jenis_bantuan': 'Sembako',
|
||||
'tanggal_permintaan': '13 April 2023',
|
||||
'alamat': 'Dusun Sukamaju RT 01/03',
|
||||
'status': 'menunggu',
|
||||
},
|
||||
];
|
||||
|
||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
||||
|
||||
// Di implementasi nyata, data akan diambil dari Supabase
|
||||
// final result = await _supabaseService.getPermintaanPenjadwalanData();
|
||||
// permintaanPenjadwalan.value = result ?? [];
|
||||
// jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
||||
} catch (e) {
|
||||
print('Error loading permintaan penjadwalan data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadNotifikasiData() async {
|
||||
try {
|
||||
// Simulasi data untuk notifikasi
|
||||
@ -348,6 +404,126 @@ class PetugasDesaController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadPengaduanData() async {
|
||||
try {
|
||||
// Simulasi data untuk pengaduan
|
||||
await Future.delayed(const Duration(milliseconds: 650));
|
||||
|
||||
// Pastikan data pengaduan tidak kosong
|
||||
daftarPengaduan.value = [
|
||||
{
|
||||
'id': '1',
|
||||
'nama': 'Budi Santoso',
|
||||
'nik': '3201020107030011',
|
||||
'jenis_pengaduan': 'Bantuan Tidak Diterima',
|
||||
'deskripsi':
|
||||
'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu',
|
||||
'tanggal': '15 April 2023',
|
||||
'status': 'Diproses',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'nama': 'Siti Rahayu',
|
||||
'nik': '3201020107030010',
|
||||
'jenis_pengaduan': 'Kualitas Bantuan',
|
||||
'deskripsi':
|
||||
'Beras yang diterima berkualitas buruk dan tidak layak konsumsi',
|
||||
'tanggal': '14 April 2023',
|
||||
'status': 'Tindakan',
|
||||
'tindakan':
|
||||
'Pengecekan kualitas beras di gudang dan pengambilan sampel',
|
||||
},
|
||||
{
|
||||
'id': '3',
|
||||
'nama': 'Ahmad Fauzi',
|
||||
'nik': '3201020107030013',
|
||||
'jenis_pengaduan': 'Jumlah Bantuan',
|
||||
'deskripsi':
|
||||
'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan',
|
||||
'tanggal': '13 April 2023',
|
||||
'status': 'Tindakan',
|
||||
'tindakan':
|
||||
'Verifikasi data penerima dan jumlah bantuan yang seharusnya diterima',
|
||||
},
|
||||
{
|
||||
'id': '4',
|
||||
'nama': 'Dewi Lestari',
|
||||
'nik': '3201020107030012',
|
||||
'jenis_pengaduan': 'Jadwal Penyaluran',
|
||||
'deskripsi':
|
||||
'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan',
|
||||
'tanggal': '10 April 2023',
|
||||
'status': 'Selesai',
|
||||
'tindakan':
|
||||
'Koordinasi dengan tim penyaluran untuk perbaikan sistem pemberitahuan',
|
||||
'hasil':
|
||||
'Implementasi sistem notifikasi perubahan jadwal melalui SMS dan pengumuman di balai desa',
|
||||
},
|
||||
// Tambahkan data pengaduan dengan status 'Diproses' untuk memastikan counter muncul
|
||||
{
|
||||
'id': '5',
|
||||
'nama': 'Joko Widodo',
|
||||
'nik': '3201020107030014',
|
||||
'jenis_pengaduan': 'Bantuan Tidak Sesuai',
|
||||
'deskripsi':
|
||||
'Bantuan yang diterima tidak sesuai dengan yang dijanjikan',
|
||||
'tanggal': '16 April 2023',
|
||||
'status': 'Diproses',
|
||||
},
|
||||
{
|
||||
'id': '6',
|
||||
'nama': 'Anita Sari',
|
||||
'nik': '3201020107030015',
|
||||
'jenis_pengaduan': 'Bantuan Tidak Tepat Sasaran',
|
||||
'deskripsi':
|
||||
'Bantuan diberikan kepada warga yang tidak berhak menerima',
|
||||
'tanggal': '17 April 2023',
|
||||
'status': 'Diproses',
|
||||
},
|
||||
];
|
||||
|
||||
// Hitung jumlah pengaduan berdasarkan status
|
||||
int jumlahDiprosesTemp =
|
||||
daftarPengaduan.where((p) => p['status'] == 'Diproses').length;
|
||||
int jumlahTindakanTemp =
|
||||
daftarPengaduan.where((p) => p['status'] == 'Tindakan').length;
|
||||
int jumlahSelesaiTemp =
|
||||
daftarPengaduan.where((p) => p['status'] == 'Selesai').length;
|
||||
|
||||
// Update nilai Rx
|
||||
jumlahDiproses.value = jumlahDiprosesTemp;
|
||||
jumlahTindakan.value = jumlahTindakanTemp;
|
||||
jumlahSelesai.value = jumlahSelesaiTemp;
|
||||
|
||||
// Print untuk debugging
|
||||
print('Data pengaduan dimuat:');
|
||||
print('Jumlah pengaduan diproses: ${jumlahDiproses.value}');
|
||||
print('Jumlah pengaduan tindakan: ${jumlahTindakan.value}');
|
||||
print('Jumlah pengaduan selesai: ${jumlahSelesai.value}');
|
||||
print('Total pengaduan: ${daftarPengaduan.length}');
|
||||
|
||||
// Perbarui UI secara manual
|
||||
update();
|
||||
|
||||
// Di implementasi nyata, data akan diambil dari Supabase
|
||||
// final result = await _supabaseService.getPengaduanData();
|
||||
// daftarPengaduan.value = result ?? [];
|
||||
// jumlahDiproses.value = daftarPengaduan.where((p) => p['status'] == 'Diproses').length;
|
||||
// jumlahTindakan.value = daftarPengaduan.where((p) => p['status'] == 'Tindakan').length;
|
||||
// jumlahSelesai.value = daftarPengaduan.where((p) => p['status'] == 'Selesai').length;
|
||||
} catch (e) {
|
||||
print('Error loading pengaduan data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Method untuk memperbarui jumlah pengaduan secara manual (untuk debugging)
|
||||
void updatePengaduanCounter() {
|
||||
jumlahDiproses.value = 5; // Set nilai secara manual
|
||||
update(); // Perbarui UI
|
||||
print(
|
||||
'Counter pengaduan diperbarui secara manual: ${jumlahDiproses.value}');
|
||||
}
|
||||
|
||||
void tandaiNotifikasiDibaca(String id) {
|
||||
// Implementasi untuk menandai notifikasi sebagai dibaca
|
||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status notifikasi
|
||||
@ -394,6 +570,24 @@ class PetugasDesaController extends GetxController {
|
||||
loadPenitipanData();
|
||||
}
|
||||
|
||||
void prosesPengaduan(String id, String tindakan) {
|
||||
// Implementasi untuk memproses pengaduan
|
||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan
|
||||
// await _supabaseService.processPengaduan(id, tindakan);
|
||||
|
||||
// Perbarui data lokal
|
||||
loadPengaduanData();
|
||||
}
|
||||
|
||||
void selesaikanPengaduan(String id, String hasil) {
|
||||
// Implementasi untuk menyelesaikan pengaduan
|
||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan
|
||||
// await _supabaseService.completePengaduan(id, hasil);
|
||||
|
||||
// Perbarui data lokal
|
||||
loadPengaduanData();
|
||||
}
|
||||
|
||||
void logout() {
|
||||
_authController.logout();
|
||||
}
|
||||
@ -401,4 +595,82 @@ class PetugasDesaController extends GetxController {
|
||||
void changeTab(int index) {
|
||||
activeTabIndex.value = index;
|
||||
}
|
||||
|
||||
// Metode untuk konfirmasi permintaan penjadwalan
|
||||
Future<void> konfirmasiPermintaanPenjadwalan(
|
||||
String id, String jadwalId) async {
|
||||
try {
|
||||
if (id.isEmpty || jadwalId.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'ID permintaan atau jadwal tidak valid',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
// Simulasi proses konfirmasi
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
|
||||
// Hapus permintaan dari daftar
|
||||
permintaanPenjadwalan.removeWhere((item) => item['id'] == id);
|
||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
||||
|
||||
// Di implementasi nyata, data akan diupdate ke Supabase
|
||||
// await _supabaseService.konfirmasiPermintaanPenjadwalan(id, jadwalId);
|
||||
// await loadPermintaanPenjadwalanData();
|
||||
// await loadJadwalData();
|
||||
} catch (e) {
|
||||
print('Error konfirmasi permintaan penjadwalan: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Terjadi kesalahan saat mengkonfirmasi permintaan',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Metode untuk menolak permintaan penjadwalan
|
||||
Future<void> tolakPermintaanPenjadwalan(String id, String alasan) async {
|
||||
try {
|
||||
if (id.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'ID permintaan tidak valid',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
// Simulasi proses penolakan
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
|
||||
// Hapus permintaan dari daftar
|
||||
permintaanPenjadwalan.removeWhere((item) => item['id'] == id);
|
||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
||||
|
||||
// Di implementasi nyata, data akan diupdate ke Supabase
|
||||
// await _supabaseService.tolakPermintaanPenjadwalan(id, alasan);
|
||||
// await loadPermintaanPenjadwalanData();
|
||||
} catch (e) {
|
||||
print('Error tolak permintaan penjadwalan: $e');
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Terjadi kesalahan saat menolak permintaan',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,289 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class JadwalView extends GetView<PetugasDesaController> {
|
||||
const JadwalView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Ringkasan jadwal
|
||||
_buildJadwalSummary(context),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
// Jadwal hari ini
|
||||
_buildJadwalSection(
|
||||
textTheme,
|
||||
title: 'Hari Ini',
|
||||
jadwalList: [
|
||||
{
|
||||
'lokasi': 'Kantor Kepala Desa',
|
||||
'jenisBantuan': 'Beras',
|
||||
'tanggal': '15 April 2023',
|
||||
'waktu': '13:00 - 14:00',
|
||||
'status': 'Aktif',
|
||||
},
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal mendatang
|
||||
_buildJadwalSection(
|
||||
textTheme,
|
||||
title: 'Mendatang',
|
||||
jadwalList: [
|
||||
{
|
||||
'lokasi': 'Balai Desa A',
|
||||
'jenisBantuan': 'Sembako',
|
||||
'tanggal': '17 April 2023',
|
||||
'waktu': '13:00 - 14:00',
|
||||
'status': 'Terjadwal',
|
||||
},
|
||||
{
|
||||
'lokasi': 'Balai Desa B',
|
||||
'jenisBantuan': 'Uang Tunai',
|
||||
'tanggal': '20 April 2023',
|
||||
'waktu': '10:00 - 12:00',
|
||||
'status': 'Terjadwal',
|
||||
},
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal selesai
|
||||
_buildJadwalSection(
|
||||
textTheme,
|
||||
title: 'Selesai',
|
||||
jadwalList: [
|
||||
{
|
||||
'lokasi': 'Kantor Kepala Desa',
|
||||
'jenisBantuan': 'Beras',
|
||||
'tanggal': '10 April 2023',
|
||||
'waktu': '13:00 - 14:00',
|
||||
'status': 'Selesai',
|
||||
},
|
||||
{
|
||||
'lokasi': 'Balai Desa C',
|
||||
'jenisBantuan': 'Sembako',
|
||||
'tanggal': '5 April 2023',
|
||||
'waktu': '09:00 - 11:00',
|
||||
'status': 'Selesai',
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJadwalSummary(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppTheme.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ringkasan Jadwal',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.pending_actions,
|
||||
title: 'Terjadwal',
|
||||
value: '5',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.event_available,
|
||||
title: 'Aktif',
|
||||
value: '1',
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.event_busy,
|
||||
title: 'Selesai',
|
||||
value: '12',
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryItem(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String value,
|
||||
required Color color,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJadwalSection(
|
||||
TextTheme textTheme, {
|
||||
required String title,
|
||||
required List<Map<String, String>> jadwalList,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...jadwalList.map((jadwal) => _buildJadwalItem(textTheme, jadwal)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJadwalItem(TextTheme textTheme, Map<String, String> jadwal) {
|
||||
Color statusColor;
|
||||
switch (jadwal['status']) {
|
||||
case 'Aktif':
|
||||
statusColor = Colors.green;
|
||||
break;
|
||||
case 'Terjadwal':
|
||||
statusColor = Colors.blue;
|
||||
break;
|
||||
case 'Selesai':
|
||||
statusColor = Colors.grey;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.orange;
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
jadwal['lokasi'] ?? '',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
jadwal['status'] ?? '',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Jenis Bantuan: ${jadwal['jenisBantuan'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Tanggal: ${jadwal['tanggal'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Waktu: ${jadwal['waktu'] ?? ''}',
|
||||
style: textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
649
lib/app/modules/petugas_desa/views/pengaduan_view.dart
Normal file
649
lib/app/modules/petugas_desa/views/pengaduan_view.dart
Normal file
@ -0,0 +1,649 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PengaduanView extends GetView<PetugasDesaController> {
|
||||
const PengaduanView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Ringkasan pengaduan
|
||||
_buildPengaduanSummary(context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Filter dan pencarian
|
||||
_buildFilterSearch(context),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Daftar pengaduan
|
||||
_buildPengaduanList(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPengaduanSummary(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppTheme.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ringkasan Pengaduan',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.pending_actions,
|
||||
title: 'Diproses',
|
||||
value: '3',
|
||||
color: Colors.orange,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.engineering,
|
||||
title: 'Tindakan',
|
||||
value: '2',
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.check_circle,
|
||||
title: 'Selesai',
|
||||
value: '8',
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryItem(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String value,
|
||||
required Color color,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterSearch(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Cari pengaduan...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey.shade100,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
// Tampilkan dialog filter
|
||||
_showFilterDialog(context);
|
||||
},
|
||||
icon: const Icon(Icons.filter_list),
|
||||
tooltip: 'Filter',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showFilterDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Filter Pengaduan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
title: const Text('Diproses'),
|
||||
value: true,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Tindakan'),
|
||||
value: true,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Selesai'),
|
||||
value: true,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Terapkan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPengaduanList(BuildContext context) {
|
||||
final List<Map<String, dynamic>> pengaduanList = [
|
||||
{
|
||||
'id': '1',
|
||||
'nama': 'Budi Santoso',
|
||||
'nik': '3201020107030011',
|
||||
'jenis_pengaduan': 'Bantuan Tidak Diterima',
|
||||
'deskripsi':
|
||||
'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu',
|
||||
'tanggal': '15 April 2023',
|
||||
'status': 'Diproses',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'nama': 'Siti Rahayu',
|
||||
'nik': '3201020107030010',
|
||||
'jenis_pengaduan': 'Kualitas Bantuan',
|
||||
'deskripsi':
|
||||
'Beras yang diterima berkualitas buruk dan tidak layak konsumsi',
|
||||
'tanggal': '14 April 2023',
|
||||
'status': 'Tindakan',
|
||||
},
|
||||
{
|
||||
'id': '3',
|
||||
'nama': 'Ahmad Fauzi',
|
||||
'nik': '3201020107030013',
|
||||
'jenis_pengaduan': 'Jumlah Bantuan',
|
||||
'deskripsi':
|
||||
'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan',
|
||||
'tanggal': '13 April 2023',
|
||||
'status': 'Tindakan',
|
||||
},
|
||||
{
|
||||
'id': '4',
|
||||
'nama': 'Dewi Lestari',
|
||||
'nik': '3201020107030012',
|
||||
'jenis_pengaduan': 'Jadwal Penyaluran',
|
||||
'deskripsi':
|
||||
'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan',
|
||||
'tanggal': '10 April 2023',
|
||||
'status': 'Selesai',
|
||||
},
|
||||
];
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Daftar Pengaduan',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...pengaduanList.map((item) => _buildPengaduanItem(context, item)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPengaduanItem(BuildContext context, Map<String, dynamic> item) {
|
||||
Color statusColor;
|
||||
IconData statusIcon;
|
||||
|
||||
switch (item['status']) {
|
||||
case 'Diproses':
|
||||
statusColor = Colors.orange;
|
||||
statusIcon = Icons.pending_actions;
|
||||
break;
|
||||
case 'Tindakan':
|
||||
statusColor = Colors.blue;
|
||||
statusIcon = Icons.engineering;
|
||||
break;
|
||||
case 'Selesai':
|
||||
statusColor = Colors.green;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.grey;
|
||||
statusIcon = Icons.help_outline;
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withAlpha(26),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item['nama'] ?? '',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
statusIcon,
|
||||
size: 16,
|
||||
color: statusColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item['status'] ?? '',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'NIK: ${item['nik'] ?? ''}',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
item['jenis_pengaduan'] ?? '',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
item['deskripsi'] ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
item['tanggal'] ?? '',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _buildActionButtons(context, item),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildActionButtons(
|
||||
BuildContext context, Map<String, dynamic> item) {
|
||||
final status = item['status'];
|
||||
|
||||
if (status == 'Diproses') {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk memproses pengaduan
|
||||
_showTindakanDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.engineering, size: 18),
|
||||
label: const Text('Tindakan'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk melihat detail pengaduan
|
||||
_showDetailDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (status == 'Tindakan') {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk menyelesaikan pengaduan
|
||||
_showSelesaikanDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.check_circle, size: 18),
|
||||
label: const Text('Selesaikan'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.green,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk melihat detail pengaduan
|
||||
_showDetailDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
// Implementasi untuk melihat detail pengaduan
|
||||
_showDetailDialog(context, item);
|
||||
},
|
||||
icon: const Icon(Icons.info_outline, size: 18),
|
||||
label: const Text('Detail'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetailDialog(BuildContext context, Map<String, dynamic> item) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Detail Pengaduan: ${item['id']}'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildDetailItem('Nama', item['nama'] ?? ''),
|
||||
_buildDetailItem('NIK', item['nik'] ?? ''),
|
||||
_buildDetailItem(
|
||||
'Jenis Pengaduan', item['jenis_pengaduan'] ?? ''),
|
||||
_buildDetailItem('Tanggal', item['tanggal'] ?? ''),
|
||||
_buildDetailItem('Status', item['status'] ?? ''),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Deskripsi:',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(item['deskripsi'] ?? ''),
|
||||
if (item['status'] == 'Tindakan' ||
|
||||
item['status'] == 'Selesai') ...[
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Tindakan:',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(item['tindakan'] ??
|
||||
'Pengecekan ke lokasi dan verifikasi data penerima'),
|
||||
],
|
||||
if (item['status'] == 'Selesai') ...[
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Hasil:',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(item['hasil'] ??
|
||||
'Pengaduan telah diselesaikan dengan penyaluran ulang bantuan'),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Tutup'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailItem(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Expanded(child: Text(value)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showTindakanDialog(BuildContext context, Map<String, dynamic> item) {
|
||||
final TextEditingController tindakanController = TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Tindakan Pengaduan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Pengaduan dari: ${item['nama']}'),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: tindakanController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Tindakan yang dilakukan',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Implementasi untuk menyimpan tindakan
|
||||
Navigator.pop(context);
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Status pengaduan berhasil diubah menjadi Tindakan',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.blue,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
},
|
||||
child: const Text('Simpan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSelesaikanDialog(BuildContext context, Map<String, dynamic> item) {
|
||||
final TextEditingController hasilController = TextEditingController();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Selesaikan Pengaduan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Pengaduan dari: ${item['nama']}'),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: hasilController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Hasil penyelesaian',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Implementasi untuk menyimpan hasil
|
||||
Navigator.pop(context);
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Status pengaduan berhasil diubah menjadi Selesai',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
},
|
||||
child: const Text('Selesaikan'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
159
lib/app/modules/petugas_desa/views/penyaluran_view.dart
Normal file
159
lib/app/modules/petugas_desa/views/penyaluran_view.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart';
|
||||
|
||||
class PenyaluranView extends GetView<PetugasDesaController> {
|
||||
const PenyaluranView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Ringkasan jadwal
|
||||
_buildJadwalSummary(context),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Ringkasan Permintaan Penjadwalan
|
||||
PermintaanPenjadwalanSummaryWidget(controller: controller),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal hari ini
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
title: 'Hari Ini',
|
||||
jadwalList: controller.jadwalHariIni,
|
||||
status: 'Aktif',
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal mendatang
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
title: 'Mendatang',
|
||||
jadwalList: controller.jadwalMendatang,
|
||||
status: 'Terjadwal',
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Jadwal selesai
|
||||
JadwalSectionWidget(
|
||||
controller: controller,
|
||||
title: 'Selesai',
|
||||
jadwalList: controller.jadwalSelesai,
|
||||
status: 'Selesai',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJadwalSummary(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppTheme.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ringkasan Jadwal',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Obx(() => _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.pending_actions,
|
||||
title: 'Terjadwal',
|
||||
value: '${controller.jadwalMendatang.length}',
|
||||
color: Colors.blue,
|
||||
)),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(() => _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.event_available,
|
||||
title: 'Aktif',
|
||||
value: '${controller.jadwalHariIni.length}',
|
||||
color: Colors.green,
|
||||
)),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(() => _buildSummaryItem(
|
||||
context,
|
||||
icon: Icons.event_busy,
|
||||
title: 'Selesai',
|
||||
value: '${controller.jadwalSelesai.length}',
|
||||
color: Colors.grey,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryItem(
|
||||
BuildContext context, {
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String value,
|
||||
required Color color,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,341 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
||||
const PermintaanPenjadwalanView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Pastikan controller sudah diinisialisasi
|
||||
if (!Get.isRegistered<PetugasDesaController>()) {
|
||||
Get.put(PetugasDesaController());
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Permintaan Penjadwalan'),
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
body: Obx(() {
|
||||
final permintaanList = controller.permintaanPenjadwalan;
|
||||
|
||||
if (permintaanList.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: permintaanList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final permintaan = permintaanList[index];
|
||||
return _buildPermintaanItem(context, permintaan);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.event_note,
|
||||
size: 80,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Tidak ada permintaan penjadwalan',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Semua permintaan penjadwalan akan muncul di sini',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPermintaanItem(
|
||||
BuildContext context, Map<String, dynamic> permintaan) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(
|
||||
color: Colors.orange.withAlpha(50),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
permintaan['nama'] ?? '',
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withAlpha(26),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'Menunggu',
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: Colors.orange,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoRow(Icons.person, 'NIK: ${permintaan['nik'] ?? ''}'),
|
||||
_buildInfoRow(Icons.category,
|
||||
'Jenis Bantuan: ${permintaan['jenis_bantuan'] ?? ''}'),
|
||||
_buildInfoRow(Icons.calendar_today,
|
||||
'Tanggal Permintaan: ${permintaan['tanggal_permintaan'] ?? ''}'),
|
||||
_buildInfoRow(
|
||||
Icons.location_on, 'Alamat: ${permintaan['alamat'] ?? ''}'),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _showTolakDialog(permintaan),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
side: const BorderSide(color: Colors.red),
|
||||
),
|
||||
icon: const Icon(Icons.close),
|
||||
label: const Text('Tolak'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _showKonfirmasiDialog(permintaan),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
icon: const Icon(Icons.check),
|
||||
label: const Text('Konfirmasi'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(IconData icon, String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Dialog untuk konfirmasi permintaan
|
||||
void _showKonfirmasiDialog(Map<String, dynamic> permintaan) {
|
||||
String? selectedJadwalId;
|
||||
|
||||
// Data jadwal yang tersedia dari controller
|
||||
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: jadwal['id'],
|
||||
child: Text(
|
||||
'${jadwal['tanggal'] ?? ''} - ${jadwal['lokasi'] ?? ''} (${jadwal['jenis_bantuan'] ?? ''})'),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
// Tambahkan opsi jadwal lain jika diperlukan
|
||||
jadwalOptions.add(
|
||||
const DropdownMenuItem<String>(
|
||||
value: '3',
|
||||
child: Text('25 April 2023 - Kantor Kepala Desa (Beras)'),
|
||||
),
|
||||
);
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Konfirmasi Permintaan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Pilih jadwal penyaluran:'),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
items: jadwalOptions,
|
||||
onChanged: (value) {
|
||||
selectedJadwalId = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (selectedJadwalId != null) {
|
||||
// Panggil metode konfirmasi di controller
|
||||
controller.konfirmasiPermintaanPenjadwalan(
|
||||
permintaan['id'] ?? '',
|
||||
selectedJadwalId ?? '',
|
||||
);
|
||||
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Permintaan penjadwalan berhasil dikonfirmasi',
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Peringatan',
|
||||
'Silakan pilih jadwal penyaluran terlebih dahulu',
|
||||
backgroundColor: Colors.orange,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
),
|
||||
child: const Text('Konfirmasi'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Dialog untuk menolak permintaan
|
||||
void _showTolakDialog(Map<String, dynamic> permintaan) {
|
||||
final TextEditingController alasanController = TextEditingController();
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Tolak Permintaan'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Alasan penolakan:'),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: alasanController,
|
||||
maxLines: 3,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Masukkan alasan penolakan',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (alasanController.text.trim().isNotEmpty) {
|
||||
// Panggil metode tolak di controller
|
||||
controller.tolakPermintaanPenjadwalan(
|
||||
permintaan['id'] ?? '',
|
||||
alasanController.text.trim(),
|
||||
);
|
||||
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Permintaan penjadwalan berhasil ditolak',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Peringatan',
|
||||
'Silakan masukkan alasan penolakan',
|
||||
backgroundColor: Colors.orange,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text('Tolak'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/jadwal_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/penyaluran_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/inventaris_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/penitipan_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/pengaduan_view.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
@ -15,6 +16,12 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
Widget build(BuildContext context) {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
// Perbarui counter pengaduan secara manual saat aplikasi dimulai
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
controller.updatePengaduanCounter();
|
||||
print('Counter pengaduan diperbarui saat aplikasi dimulai');
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
appBar: AppBar(
|
||||
@ -23,11 +30,13 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
case 0:
|
||||
return const Text('Dashboard');
|
||||
case 1:
|
||||
return const Text('Jadwal Penyaluran');
|
||||
return const Text('Penyaluran');
|
||||
case 2:
|
||||
return const Text('Inventaris');
|
||||
case 3:
|
||||
return const Text('Penitipan');
|
||||
case 3:
|
||||
return const Text('Pengaduan');
|
||||
case 4:
|
||||
return const Text('Inventaris');
|
||||
default:
|
||||
return const Text('Petugas Desa');
|
||||
}
|
||||
@ -115,6 +124,35 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
notificationButton,
|
||||
],
|
||||
);
|
||||
} else if (activeTab == 4) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: 'Tambah Pengaduan',
|
||||
onPressed: () {
|
||||
// Implementasi untuk menambah pengaduan baru
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_list),
|
||||
tooltip: 'Filter Pengaduan',
|
||||
onPressed: () {
|
||||
// Implementasi untuk filter pengaduan
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Perbarui Counter',
|
||||
onPressed: () {
|
||||
// Perbarui counter pengaduan secara manual
|
||||
controller.updatePengaduanCounter();
|
||||
},
|
||||
),
|
||||
notificationButton,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return notificationButton;
|
||||
}
|
||||
@ -127,11 +165,14 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
case 0:
|
||||
return const DashboardView();
|
||||
case 1:
|
||||
return const JadwalView();
|
||||
return const PenyaluranView();
|
||||
|
||||
case 2:
|
||||
return const InventarisView();
|
||||
case 3:
|
||||
return const PenitipanView();
|
||||
case 3:
|
||||
return const PengaduanView();
|
||||
case 4:
|
||||
return const InventarisView();
|
||||
default:
|
||||
return const DashboardView();
|
||||
}
|
||||
@ -193,7 +234,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
)),
|
||||
Obx(() => ListTile(
|
||||
leading: const Icon(Icons.calendar_today_outlined),
|
||||
title: const Text('Jadwal Penyaluran'),
|
||||
title: const Text('Penyaluran'),
|
||||
selected: controller.activeTabIndex.value == 1,
|
||||
selectedColor: AppTheme.primaryColor,
|
||||
onTap: () {
|
||||
@ -201,16 +242,6 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
Obx(() => ListTile(
|
||||
leading: const Icon(Icons.inventory_2_outlined),
|
||||
title: const Text('Inventaris'),
|
||||
selected: controller.activeTabIndex.value == 2,
|
||||
selectedColor: AppTheme.primaryColor,
|
||||
onTap: () {
|
||||
controller.changeTab(2);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
Obx(() => ListTile(
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
@ -243,10 +274,65 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
],
|
||||
),
|
||||
title: const Text('Penitipan'),
|
||||
selected: controller.activeTabIndex.value == 3,
|
||||
selected: controller.activeTabIndex.value == 2,
|
||||
selectedColor: AppTheme.primaryColor,
|
||||
onTap: () {
|
||||
controller.changeTab(3);
|
||||
controller.changeTab(2);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
Obx(() {
|
||||
final int jumlahPengaduanDiproses = controller.jumlahDiproses.value;
|
||||
print(
|
||||
'Drawer - Jumlah pengaduan diproses: $jumlahPengaduanDiproses');
|
||||
|
||||
return ListTile(
|
||||
leading: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.report_problem_outlined),
|
||||
// Selalu tampilkan badge untuk debugging
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
jumlahPengaduanDiproses.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: const Text('Pengaduan'),
|
||||
selected: controller.activeTabIndex.value == 3,
|
||||
selectedColor: AppTheme.primaryColor,
|
||||
onTap: () {
|
||||
controller.changeTab(3);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
Obx(() => ListTile(
|
||||
leading: const Icon(Icons.inventory_2_outlined),
|
||||
title: const Text('Inventaris'),
|
||||
selected: controller.activeTabIndex.value == 4,
|
||||
selectedColor: AppTheme.primaryColor,
|
||||
onTap: () {
|
||||
controller.changeTab(4);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)),
|
||||
@ -323,150 +409,222 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
}
|
||||
|
||||
Widget _buildBottomNavigationBar() {
|
||||
return Obx(() => BottomNavigationBar(
|
||||
currentIndex: controller.activeTabIndex.value,
|
||||
onTap: controller.changeTab,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: AppTheme.primaryColor,
|
||||
unselectedItemColor: Colors.grey,
|
||||
items: [
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.dashboard_outlined),
|
||||
activeIcon: Icon(Icons.dashboard),
|
||||
label: 'Dashboard',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.calendar_today_outlined),
|
||||
if (controller.jadwalHariIni.isNotEmpty)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jadwalHariIni.length.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
// Tambahkan print statement untuk debugging
|
||||
print('Jumlah pengaduan diproses: ${controller.jumlahDiproses.value}');
|
||||
print('Jumlah jadwal hari ini: ${controller.jadwalHariIni.length}');
|
||||
|
||||
return Obx(() {
|
||||
// Hitung jumlah pengaduan yang diproses
|
||||
final int jumlahPengaduanDiproses = controller.jumlahDiproses.value;
|
||||
|
||||
return BottomNavigationBar(
|
||||
currentIndex: controller.activeTabIndex.value,
|
||||
onTap: controller.changeTab,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: AppTheme.primaryColor,
|
||||
unselectedItemColor: Colors.grey,
|
||||
items: [
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.dashboard_outlined),
|
||||
activeIcon: Icon(Icons.dashboard),
|
||||
label: 'Dashboard',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.calendar_today_outlined),
|
||||
if (controller.jadwalHariIni.isNotEmpty)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jadwalHariIni.length.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
activeIcon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.calendar_today),
|
||||
if (controller.jadwalHariIni.isNotEmpty)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jadwalHariIni.length.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
activeIcon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.calendar_today),
|
||||
if (controller.jadwalHariIni.isNotEmpty)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jadwalHariIni.length.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
label: 'Jadwal',
|
||||
),
|
||||
],
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.inventory_2_outlined),
|
||||
activeIcon: Icon(Icons.inventory_2),
|
||||
label: 'Inventaris',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.handshake_outlined),
|
||||
if (controller.jumlahMenunggu.value > 0)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahMenunggu.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
label: 'Penyaluran',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.handshake_outlined),
|
||||
if (controller.jumlahMenunggu.value > 0)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahMenunggu.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
activeIcon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.handshake),
|
||||
if (controller.jumlahMenunggu.value > 0)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahMenunggu.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
activeIcon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.handshake),
|
||||
if (controller.jumlahMenunggu.value > 0)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.orange,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahMenunggu.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
label: 'Penitipan',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
));
|
||||
label: 'Penitipan',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.report_problem_outlined),
|
||||
// Selalu tampilkan badge untuk debugging
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahDiproses.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
activeIcon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const Icon(Icons.report_problem),
|
||||
// Selalu tampilkan badge untuk debugging
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 12,
|
||||
minHeight: 12,
|
||||
),
|
||||
child: Text(
|
||||
controller.jumlahDiproses.value.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
label: 'Pengaduan',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.inventory_2_outlined),
|
||||
activeIcon: Icon(Icons.inventory_2),
|
||||
label: 'Inventaris',
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
8
lib/app/modules/splash/bindings/splash_binding.dart
Normal file
8
lib/app/modules/splash/bindings/splash_binding.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SplashBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Tidak perlu menambahkan controller khusus untuk splash screen
|
||||
}
|
||||
}
|
83
lib/app/modules/splash/views/splash_view.dart
Normal file
83
lib/app/modules/splash/views/splash_view.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
|
||||
class SplashView extends StatefulWidget {
|
||||
const SplashView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SplashView> createState() => _SplashViewState();
|
||||
}
|
||||
|
||||
class _SplashViewState extends State<SplashView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_navigateToLogin();
|
||||
}
|
||||
|
||||
_navigateToLogin() async {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
Get.offAllNamed(Routes.login);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppTheme.primaryGradient,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 120,
|
||||
height: 120,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.people,
|
||||
size: 60,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'Aplikasi Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Bantuan Sosial',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
const CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import 'package:penyaluran_app/app/modules/home/bindings/home_binding.dart';
|
||||
import 'package:penyaluran_app/app/modules/dashboard/bindings/dashboard_binding.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/petugas_desa_view.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/petugas_desa_binding.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/permintaan_penjadwalan_view.dart';
|
||||
|
||||
part 'app_routes.dart';
|
||||
|
||||
@ -48,5 +49,10 @@ class AppPages {
|
||||
page: () => const DonaturDashboardView(),
|
||||
binding: DashboardBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: _Paths.permintaanPenjadwalan,
|
||||
page: () => const PermintaanPenjadwalanView(),
|
||||
binding: PetugasDesaBinding(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ abstract class Routes {
|
||||
static const petugasVerifikasiDashboard = _Paths.petugasVerifikasiDashboard;
|
||||
static const petugasDesaDashboard = _Paths.petugasDesaDashboard;
|
||||
static const donaturDashboard = _Paths.donaturDashboard;
|
||||
static const splash = _Paths.splash;
|
||||
static const permintaanPenjadwalan = _Paths.permintaanPenjadwalan;
|
||||
}
|
||||
|
||||
abstract class _Paths {
|
||||
@ -20,4 +22,6 @@ abstract class _Paths {
|
||||
static const petugasVerifikasiDashboard = '/petugas-verifikasi-dashboard';
|
||||
static const petugasDesaDashboard = '/petugas-desa-dashboard';
|
||||
static const donaturDashboard = '/donatur-dashboard';
|
||||
static const splash = '/splash';
|
||||
static const permintaanPenjadwalan = '/permintaan-penjadwalan';
|
||||
}
|
||||
|
Reference in New Issue
Block a user