Tambahkan dependensi baru timeline_tile versi 2.0.0 ke dalam pubspec.yaml dan pubspec.lock. Perbarui model PengaduanModel dan TindakanPengaduanModel untuk mendukung struktur data yang lebih kompleks, termasuk penambahan properti baru. Modifikasi PengaduanController untuk menggunakan metode baru dalam mengambil data pengaduan dengan detail penerima penyaluran. Perbarui tampilan di PengaduanView untuk meningkatkan pengalaman pengguna dengan menampilkan informasi penyaluran bantuan yang lebih lengkap.
This commit is contained in:
449
lib/app/modules/warga/views/detail_pengaduan_view.dart
Normal file
449
lib/app/modules/warga/views/detail_pengaduan_view.dart
Normal file
@ -0,0 +1,449 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:penyaluran_app/app/data/models/pengaduan_model.dart';
|
||||
import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_model.dart';
|
||||
import 'package:penyaluran_app/app/modules/warga/controllers/warga_dashboard_controller.dart';
|
||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||
import 'package:timeline_tile/timeline_tile.dart';
|
||||
|
||||
class WargaDetailPengaduanView extends GetView<WargaDashboardController> {
|
||||
const WargaDetailPengaduanView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic> args = Get.arguments ?? {};
|
||||
final String pengaduanId = args['id'] ?? '';
|
||||
|
||||
if (pengaduanId.isEmpty) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Pengaduan'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('ID Pengaduan tidak valid'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Detail Pengaduan'),
|
||||
elevation: 0,
|
||||
),
|
||||
body: FutureBuilder<Map<String, dynamic>>(
|
||||
future: controller.getDetailPengaduan(pengaduanId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text('Error: ${snapshot.error}'),
|
||||
);
|
||||
}
|
||||
|
||||
final data = snapshot.data;
|
||||
if (data == null || data['pengaduan'] == null) {
|
||||
return const Center(
|
||||
child: Text('Data pengaduan tidak ditemukan'),
|
||||
);
|
||||
}
|
||||
|
||||
final pengaduan = PengaduanModel.fromJson(data['pengaduan']);
|
||||
final List<TindakanPengaduanModel> tindakanList =
|
||||
(data['tindakan'] as List)
|
||||
.map((item) => TindakanPengaduanModel.fromJson(item))
|
||||
.toList();
|
||||
|
||||
return _buildDetailContent(context, pengaduan, tindakanList);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailContent(
|
||||
BuildContext context,
|
||||
PengaduanModel pengaduan,
|
||||
List<TindakanPengaduanModel> tindakanList,
|
||||
) {
|
||||
// Tentukan status dan warna
|
||||
Color statusColor;
|
||||
String statusText;
|
||||
|
||||
switch (pengaduan.status?.toUpperCase()) {
|
||||
case 'MENUNGGU':
|
||||
statusColor = Colors.orange;
|
||||
statusText = 'Menunggu';
|
||||
break;
|
||||
case 'TINDAKAN':
|
||||
statusColor = Colors.blue;
|
||||
statusText = 'Tindakan';
|
||||
break;
|
||||
case 'SELESAI':
|
||||
statusColor = Colors.green;
|
||||
statusText = 'Selesai';
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.grey;
|
||||
statusText = pengaduan.status ?? 'Tidak Diketahui';
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header dengan status
|
||||
_buildHeaderWithStatus(context, pengaduan, statusColor, statusText),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Informasi penyaluran yang diadukan
|
||||
if (pengaduan.penerimaPenyaluran != null)
|
||||
_buildPenyaluranInfo(context, pengaduan),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Timeline tindakan
|
||||
_buildTindakanTimeline(context, tindakanList),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderWithStatus(
|
||||
BuildContext context,
|
||||
PengaduanModel pengaduan,
|
||||
Color statusColor,
|
||||
String statusText,
|
||||
) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
pengaduan.judul ?? 'Pengaduan',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
statusText,
|
||||
style: TextStyle(
|
||||
color: statusColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
pengaduan.deskripsi ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 16,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
pengaduan.tanggalPengaduan != null
|
||||
? DateFormat('dd MMMM yyyy', 'id_ID')
|
||||
.format(pengaduan.tanggalPengaduan!)
|
||||
: '-',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPenyaluranInfo(BuildContext context, PengaduanModel pengaduan) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Informasi Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('Nama Penyaluran', pengaduan.namaPenyaluran),
|
||||
_buildInfoRow('Jenis Bantuan', pengaduan.jenisBantuan),
|
||||
_buildInfoRow('Jumlah Bantuan', pengaduan.jumlahBantuan),
|
||||
_buildInfoRow('Deskripsi', pengaduan.deskripsiPenyaluran),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTindakanTimeline(
|
||||
BuildContext context,
|
||||
List<TindakanPengaduanModel> tindakanList,
|
||||
) {
|
||||
if (tindakanList.isEmpty) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Pengaduan Anda sedang menunggu tindakan dari petugas',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Riwayat Tindakan',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: tindakanList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tindakan = tindakanList[index];
|
||||
final bool isFirst = index == 0;
|
||||
final bool isLast = index == tindakanList.length - 1;
|
||||
|
||||
return _buildTimelineTile(
|
||||
context,
|
||||
tindakan,
|
||||
isFirst,
|
||||
isLast,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimelineTile(
|
||||
BuildContext context,
|
||||
TindakanPengaduanModel tindakan,
|
||||
bool isFirst,
|
||||
bool isLast,
|
||||
) {
|
||||
Color dotColor;
|
||||
switch (tindakan.statusTindakan) {
|
||||
case 'SELESAI':
|
||||
dotColor = Colors.green;
|
||||
break;
|
||||
case 'PROSES':
|
||||
dotColor = Colors.blue;
|
||||
break;
|
||||
default:
|
||||
dotColor = Colors.grey;
|
||||
}
|
||||
|
||||
return TimelineTile(
|
||||
alignment: TimelineAlign.start,
|
||||
isFirst: isFirst,
|
||||
isLast: isLast,
|
||||
indicatorStyle: IndicatorStyle(
|
||||
width: 20,
|
||||
color: dotColor,
|
||||
iconStyle: IconStyle(
|
||||
color: Colors.white,
|
||||
iconData:
|
||||
tindakan.statusTindakan == 'SELESAI' ? Icons.check : Icons.sync,
|
||||
),
|
||||
),
|
||||
endChild: Container(
|
||||
margin: const EdgeInsets.only(left: 16, bottom: 24),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
tindakan.kategoriTindakanText,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
tindakan.statusTindakanText,
|
||||
style: TextStyle(
|
||||
color: dotColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
tindakan.tindakan ?? '',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
if (tindakan.hasilTindakan != null &&
|
||||
tindakan.hasilTindakan!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Hasil: ${tindakan.hasilTindakan}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Oleh: ${tindakan.namaPetugas}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
tindakan.tanggalTindakan != null
|
||||
? DateFormat('dd MMM yyyy', 'id_ID')
|
||||
.format(tindakan.tanggalTindakan!)
|
||||
: '-',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
'$label:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user