Sistem-Pakar-Diagnosa-Penya.../frontend/lib/admin/admin_histori_page.dart

712 lines
24 KiB
Dart

import 'package:flutter/material.dart';
import 'package:SIBAYAM/api_services/api_services.dart';
import 'package:intl/intl.dart';
class AdminHistoriPage extends StatefulWidget {
@override
_AdminHistoriPageState createState() => _AdminHistoriPageState();
}
class _AdminHistoriPageState extends State<AdminHistoriPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> historiData = [];
List<Map<String, dynamic>> groupedHistoriData = [];
List<Map<String, dynamic>> filteredHistoriData = []; // Data yang sudah difilter
bool isLoading = true;
String? error;
// Search variables
TextEditingController searchController = TextEditingController();
String searchQuery = '';
// Pagination variables
int _rowsPerPage = 10;
int _currentPage = 0;
int _totalPages = 0;
List<Map<String, dynamic>> _currentPageData = [];
@override
void initState() {
super.initState();
_loadHistoriData();
searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
searchController.removeListener(_onSearchChanged);
searchController.dispose();
super.dispose();
}
void _onSearchChanged() {
setState(() {
searchQuery = searchController.text.toLowerCase();
_filterData();
_updatePagination(0); // Reset ke halaman pertama saat search
});
}
void _filterData() {
if (searchQuery.isEmpty) {
filteredHistoriData = List.from(groupedHistoriData);
} else {
filteredHistoriData = groupedHistoriData.where((histori) {
final userName = (histori['userName'] ?? '').toString().toLowerCase();
final diagnosa = (histori['diagnosa'] ?? '').toString().toLowerCase();
return userName.contains(searchQuery) || diagnosa.contains(searchQuery);
}).toList();
}
}
Future<void> _loadHistoriData() async {
try {
setState(() {
isLoading = true;
error = null;
});
// Dapatkan semua histori terlebih dahulu
final allHistori = await apiService.getAllHistori();
// Kumpulkan semua userIds yang unik
Set<String> uniqueUserIds = allHistori
.where((histori) => histori['userId'] != null)
.map((histori) => histori['userId'].toString())
.toSet();
// Jalankan semua fetchHistoriDenganDetail secara paralel
List<Future<List<Map<String, dynamic>>>> futures = uniqueUserIds
.map((userId) => apiService.fetchHistoriDenganDetail(userId))
.toList();
// Tunggu semua futures selesai
List<List<Map<String, dynamic>>> results = await Future.wait(futures);
// Gabungkan semua hasil
List<Map<String, dynamic>> detailedHistori = [];
for (var result in results) {
detailedHistori.addAll(result);
}
// Kelompokkan data berdasarkan user, diagnosa, dan waktu yang sama
final groupedData = _groupHistoriData(detailedHistori);
setState(() {
historiData = detailedHistori; // Simpan data asli jika perlu
groupedHistoriData = groupedData; // Data yang sudah dikelompokkan
filteredHistoriData = List.from(groupedData); // Initialize filtered data
_updatePagination(0); // Set halaman pertama
isLoading = false;
});
} catch (e) {
setState(() {
error = e.toString();
isLoading = false;
});
}
}
// Fungsi untuk mengelompokkan data berdasarkan userId, diagnosa, dan waktu
List<Map<String, dynamic>> _groupHistoriData(
List<Map<String, dynamic>> data,
) {
Map<String, Map<String, dynamic>> groupedMap = {};
for (var item in data) {
if (item['userId'] == null || item['tanggal_diagnosa'] == null) continue;
// Parse tanggal
DateTime dateTime;
try {
dateTime = DateTime.parse(item['tanggal_diagnosa']);
} catch (e) {
print("Error parsing date: $e");
continue;
}
// Format tanggal untuk pengelompokan (menit, bukan detik)
String formattedTime = DateFormat('yyyy-MM-dd HH:mm').format(dateTime);
// Identifikasi diagnosa
String diagnosa = '';
if (item['penyakit_nama'] != null &&
item['penyakit_nama'].toString().isNotEmpty) {
diagnosa = 'Penyakit: ${item['penyakit_nama']}';
} else if (item['hama_nama'] != null &&
item['hama_nama'].toString().isNotEmpty) {
diagnosa = 'Hama: ${item['hama_nama']}';
} else {
diagnosa = 'Tidak ada diagnosa';
}
// Ambil nama user dari kolom 'nama' atau 'name'
String userName = item['name']?.toString() ?? 'User ID: ${item['userId']}';
// Buat composite key: userId + waktu + diagnosa
String key = '${item['userId']}_${formattedTime}_$diagnosa';
if (!groupedMap.containsKey(key)) {
// Format tanggal yang lebih ramah untuk tampilan
String displayDate = DateFormat('dd/MM/yyyy HH:mm').format(dateTime);
groupedMap[key] = {
'userId': item['userId'],
'userName': userName,
'diagnosa': diagnosa,
'tanggal_diagnosa': item['tanggal_diagnosa'],
'tanggal_display': displayDate,
'gejala': [],
'hasil': item['hasil'],
'penyakit_nama': item['penyakit_nama'],
'hama_nama': item['hama_nama'],
'sortTime': dateTime.millisecondsSinceEpoch,
'detailData': [], // Menyimpan semua item detail untuk halaman detail
};
}
// Tambahkan gejala jika belum ada dalam list
if (item['gejala_nama'] != null &&
!groupedMap[key]!['gejala'].contains(item['gejala_nama'])) {
groupedMap[key]!['gejala'].add(item['gejala_nama']);
}
// Simpan data detail untuk halaman detail
groupedMap[key]!['detailData'].add(item);
}
// Konversi map ke list dan urutkan berdasarkan waktu terbaru
List<Map<String, dynamic>> result = groupedMap.values.toList();
result.sort(
(a, b) => (b['sortTime'] as int).compareTo(a['sortTime'] as int),
);
return result;
}
// Update pagination
void _updatePagination(int page) {
_currentPage = page;
_totalPages = (filteredHistoriData.length / _rowsPerPage).ceil();
int startIndex = page * _rowsPerPage;
int endIndex = (page + 1) * _rowsPerPage;
if (endIndex > filteredHistoriData.length) {
endIndex = filteredHistoriData.length;
}
if (startIndex >= filteredHistoriData.length) {
_currentPageData = [];
} else {
_currentPageData = filteredHistoriData.sublist(startIndex, endIndex);
}
}
// Navigasi ke halaman detail
void _navigateToDetail(Map<String, dynamic> histori) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailHistoriPage(histori: histori),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Riwayat Diagnosa'),
backgroundColor: Color(0xFF9DC08D),
),
body: isLoading
? Center(child: CircularProgressIndicator())
: error != null
? Center(child: Text('Error: $error'))
: groupedHistoriData.isEmpty
? Center(child: Text('Tidak ada data riwayat diagnosa'))
: Column(
children: [
// Search Bar
Container(
margin: EdgeInsets.all(16),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'Cari berdasarkan nama user atau diagnosa...',
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9DC08D),
),
suffixIcon: searchQuery.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () {
searchController.clear();
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF9DC08D)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF9DC08D), width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
Expanded(
child: filteredHistoriData.isEmpty && searchQuery.isNotEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: Colors.grey[400],
),
SizedBox(height: 16),
Text(
'Tidak ada hasil untuk "${searchController.text}"',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 8),
Text(
'Coba gunakan kata kunci yang berbeda',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
],
),
)
: ListView.builder(
itemCount: _currentPageData.length,
itemBuilder: (context, index) {
final histori = _currentPageData[index];
return Container(
margin: EdgeInsets.only(bottom: 12, left: 16, right: 16),
child: Row(
children: [
// Card dengan informasi histori
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => _navigateToDetail(histori),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Nama User
Text(
histori['userName'] ?? 'User tidak ditemukan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8),
// Diagnosa
Text(
histori['diagnosa'] ?? 'Tidak ada diagnosa',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
// Tanggal
Text(
histori['tanggal_display'] ?? '',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
),
),
),
SizedBox(width: 8),
// Button detail di luar card
Container(
decoration: BoxDecoration(
color: Color(0xFF9DC08D),
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.info_outline, color: Colors.white),
onPressed: () => _navigateToDetail(histori),
tooltip: 'Lihat Detail',
),
),
],
),
);
},
),
),
// Pagination controls
Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.first_page, size: 18),
padding: EdgeInsets.all(4),
constraints: BoxConstraints(
minWidth: 32,
minHeight: 32,
),
onPressed: _currentPage > 0
? () {
setState(() {
_updatePagination(0);
});
}
: null,
),
IconButton(
icon: Icon(Icons.chevron_left, size: 18),
padding: EdgeInsets.all(4),
constraints: BoxConstraints(
minWidth: 32,
minHeight: 32,
),
onPressed: _currentPage > 0
? () {
setState(() {
_updatePagination(_currentPage - 1);
});
}
: null,
),
SizedBox(width: 8),
Text(
'${_currentPage + 1} / $_totalPages',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 8),
IconButton(
icon: Icon(Icons.chevron_right, size: 18),
padding: EdgeInsets.all(4),
constraints: BoxConstraints(
minWidth: 32,
minHeight: 32,
),
onPressed: _currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_currentPage + 1);
});
}
: null,
),
IconButton(
icon: Icon(Icons.last_page, size: 18),
padding: EdgeInsets.all(4),
constraints: BoxConstraints(
minWidth: 32,
minHeight: 32,
),
onPressed: _currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_totalPages - 1);
});
}
: null,
),
],
),
),
// Rows per page selector
Container(
padding: EdgeInsets.only(bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Rows per page: ',
style: TextStyle(fontSize: 12),
),
DropdownButton<int>(
value: _rowsPerPage,
isDense: true,
menuMaxHeight: 200,
items: [10, 20, 50, 100].map((value) {
return DropdownMenuItem<int>(
value: value,
child: Text('$value'),
);
}).toList(),
onChanged: (value) {
setState(() {
_rowsPerPage = value!;
_updatePagination(0);
});
},
),
],
),
),
],
),
);
}
Color _getDiagnosaColor(Map<String, dynamic> histori) {
if (histori['penyakit_nama'] != null) {
return Colors.red[700]!;
} else if (histori['hama_nama'] != null) {
return Colors.amber[800]!;
}
return Colors.black;
}
String _formatHasil(dynamic hasil) {
if (hasil == null) return '0%';
double hasilValue = double.tryParse(hasil.toString()) ?? 0.0;
return '${(hasilValue * 100).toStringAsFixed(2)}%';
}
}
// Halaman Detail Histori
class DetailHistoriPage extends StatelessWidget {
final Map<String, dynamic> histori;
const DetailHistoriPage({Key? key, required this.histori}) : super(key: key);
@override
Widget build(BuildContext context) {
// Gabungkan semua gejala menjadi satu string
String gejalaText = "Tidak ada gejala";
if (histori['gejala'] != null && (histori['gejala'] as List).isNotEmpty) {
gejalaText = (histori['gejala'] as List).join(', ');
}
return Scaffold(
appBar: AppBar(
title: Text('Detail Riwayat Diagnosa'),
backgroundColor: Color(0xFF9DC08D),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFF9DC08D).withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Informasi Diagnosa',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF9DC08D),
),
textAlign: TextAlign.center,
),
),
SizedBox(height: 20),
// Nama User
_buildDetailRow(
'Nama User',
histori['userName'] ?? 'User tidak ditemukan',
Icons.person,
),
SizedBox(height: 16),
// Tanggal Diagnosa
_buildDetailRow(
'Tanggal Diagnosa',
histori['tanggal_display'] ?? '',
Icons.calendar_today,
),
SizedBox(height: 16),
// Diagnosa
_buildDetailRow(
'Diagnosa',
histori['diagnosa'] ?? 'Tidak ada diagnosa',
Icons.medical_services,
),
SizedBox(height: 16),
// Hasil
_buildDetailRow(
'Hasil',
_formatHasil(histori['hasil']),
Icons.analytics,
),
SizedBox(height: 16),
// Gejala
_buildDetailSection(
'Gejala yang Dipilih',
gejalaText,
Icons.list_alt,
),
],
),
),
),
),
);
}
Widget _buildDetailRow(
String label,
String value,
IconData icon, {
Color? valueColor,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
icon,
size: 20,
color: Color(0xFF9DC08D),
),
SizedBox(width: 12),
Expanded(
flex: 2,
child: Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: Colors.grey[700],
),
),
),
Text(
': ',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
Expanded(
flex: 3,
child: Text(
value,
style: TextStyle(
fontSize: 14,
color: valueColor ?? Colors.black87,
fontWeight: valueColor != null ? FontWeight.w500 : FontWeight.normal,
),
),
),
],
);
}
Widget _buildDetailSection(
String label,
String value,
IconData icon,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
icon,
size: 20,
color: Color(0xFF9DC08D),
),
SizedBox(width: 12),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: Colors.grey[700],
),
),
],
),
SizedBox(height: 8),
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: Text(
value,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
),
],
);
}
Color _getDiagnosaColor(Map<String, dynamic> histori) {
if (histori['penyakit_nama'] != null) {
return Colors.red[700]!;
} else if (histori['hama_nama'] != null) {
return Colors.amber[800]!;
}
return Colors.black;
}
String _formatHasil(dynamic hasil) {
if (hasil == null) return '0%';
double hasilValue = double.tryParse(hasil.toString()) ?? 0.0;
return '${(hasilValue * 100).toStringAsFixed(2)}%';
}
}