fix : bug di riwayat user

This commit is contained in:
unknown 2025-05-19 00:32:26 +07:00
parent 6d87d8d0a1
commit f2af845287
4 changed files with 575 additions and 211 deletions

View File

@ -4,52 +4,52 @@ const moment = require('moment');
// Helper function to calculate Bayes probability // Helper function to calculate Bayes probability
function calculateBayesProbability(rules, entityType) { function calculateBayesProbability(rules, entityType) {
if (!rules || rules.length === 0) return null; if (!rules || rules.length === 0) return null;
const entityData = rules[0][entityType]; const entityData = rules[0][entityType];
const entityName = entityData.nama; const entityName = entityData.nama;
const entityId = entityType === 'penyakit' ? rules[0].id_penyakit : rules[0].id_hama; const entityId = entityType === 'penyakit' ? rules[0].id_penyakit : rules[0].id_hama;
// LANGKAH 1: Mencari nilai semesta P(E|Hi) untuk setiap gejala // LANGKAH 1: Mencari nilai semesta P(E|Hi) untuk setiap gejala
let nilai_semesta = 0; let nilai_semesta = 0;
const gejalaValues = {}; const gejalaValues = {};
for (const rule of rules) { for (const rule of rules) {
gejalaValues[rule.id_gejala] = rule.nilai_pakar; gejalaValues[rule.id_gejala] = rule.nilai_pakar;
nilai_semesta += rule.nilai_pakar; nilai_semesta += rule.nilai_pakar;
} }
// LANGKAH 2: Mencari hasil bobot P(Hi) untuk setiap gejala // LANGKAH 2: Mencari hasil bobot P(Hi) untuk setiap gejala
const bobotGejala = {}; const bobotGejala = {};
for (const [idGejala, nilai] of Object.entries(gejalaValues)) { for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
bobotGejala[idGejala] = nilai / nilai_semesta; bobotGejala[idGejala] = nilai / nilai_semesta;
} }
// LANGKAH 3: Hitung probabilitas H tanpa memandang Evidence P(E|Hi) × P(Hi) // LANGKAH 3: Hitung probabilitas H tanpa memandang Evidence P(E|Hi) × P(Hi)
const probTanpaEvidence = {}; const probTanpaEvidence = {};
for (const [idGejala, nilai] of Object.entries(gejalaValues)) { for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
probTanpaEvidence[idGejala] = nilai * bobotGejala[idGejala]; probTanpaEvidence[idGejala] = nilai * bobotGejala[idGejala];
} }
// Hitung total untuk digunakan di langkah 4 // Hitung total untuk digunakan di langkah 4
let totalProbTanpaEvidence = 0; let totalProbTanpaEvidence = 0;
for (const nilai of Object.values(probTanpaEvidence)) { for (const nilai of Object.values(probTanpaEvidence)) {
totalProbTanpaEvidence += nilai; totalProbTanpaEvidence += nilai;
} }
// LANGKAH 4: Hitung probabilitas H dengan memandang Evidence P(Hi|E) // LANGKAH 4: Hitung probabilitas H dengan memandang Evidence P(Hi|E)
const probDenganEvidence = {}; const probDenganEvidence = {};
for (const [idGejala, nilai] of Object.entries(probTanpaEvidence)) { for (const [idGejala, nilai] of Object.entries(probTanpaEvidence)) {
probDenganEvidence[idGejala] = nilai / totalProbTanpaEvidence; probDenganEvidence[idGejala] = nilai / totalProbTanpaEvidence;
} }
// LANGKAH 5: Hitung Nilai Bayes ∑bayes = ∑(P(E|Hi) × P(Hi|E)) // LANGKAH 5: Hitung Nilai Bayes ∑bayes = ∑(P(E|Hi) × P(Hi|E))
let nilaiBayes = 0; let nilaiBayes = 0;
const detailBayes = []; const detailBayes = [];
for (const [idGejala, nilai] of Object.entries(gejalaValues)) { for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
const bayes = nilai * probDenganEvidence[idGejala]; const bayes = nilai * probDenganEvidence[idGejala];
nilaiBayes += bayes; nilaiBayes += bayes;
detailBayes.push({ detailBayes.push({
id_gejala: parseInt(idGejala), id_gejala: parseInt(idGejala),
P_E_given_Hi: nilai, P_E_given_Hi: nilai,
@ -59,7 +59,7 @@ function calculateBayesProbability(rules, entityType) {
bayes_value: bayes bayes_value: bayes
}); });
} }
// Hasil akhir // Hasil akhir
const idField = entityType === 'penyakit' ? 'id_penyakit' : 'id_hama'; const idField = entityType === 'penyakit' ? 'id_penyakit' : 'id_hama';
return { return {
@ -122,41 +122,46 @@ exports.diagnosa = async (req, res) => {
hasilHama.push(hasil); hasilHama.push(hasil);
} }
} }
// Urutkan hasil berdasarkan probabilitas // Urutkan hasil berdasarkan probabilitas
const sortedPenyakit = hasilPenyakit.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen); const sortedPenyakit = hasilPenyakit.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
const sortedHama = hasilHama.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen); const sortedHama = hasilHama.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
// Gabung hasil dan ambil yang tertinggi (bisa penyakit atau hama) // Gabung hasil dan ambil yang tertinggi (bisa penyakit atau hama)
const allResults = [ const allResults = [
...sortedPenyakit.map(p => ({ type: 'penyakit', ...p })), ...sortedPenyakit.map(p => ({ type: 'penyakit', ...p })),
...sortedHama.map(h => ({ type: 'hama', ...h })) ...sortedHama.map(h => ({ type: 'hama', ...h }))
].sort((a, b) => b.probabilitas_persen - a.probabilitas_persen); ].sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
// Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa
// Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa // Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa
if (!userId) { if (!userId) {
console.error('ID user tidak ditemukan. Histori tidak dapat disimpan.'); console.error('ID user tidak ditemukan. Histori tidak dapat disimpan.');
} else { } else {
const semuaHasil = [...hasilPenyakit, ...hasilHama]; const semuaHasil = [...hasilPenyakit, ...hasilHama];
if (semuaHasil.length > 0) { if (semuaHasil.length > 0) {
const hasilTerbesar = semuaHasil.reduce((max, current) => { const hasilTerbesar = semuaHasil.reduce((max, current) => {
return current.probabilitas_persen > max.probabilitas_persen ? current : max; return current.probabilitas_persen > max.probabilitas_persen ? current : max;
}); });
// Dapatkan waktu saat ini dalam zona waktu Indonesia (GMT+7)
const now = new Date();
const jakartaTime = new Date(now.getTime() + (7 * 60 * 60 * 1000)); // GMT+7 (WIB)
const baseHistoriData = { const baseHistoriData = {
userId: userId, // harus ada userId: userId, // harus ada
tanggal_diagnosa: tanggal_diagnosa, // harus ada tanggal_diagnosa: jakartaTime, // Menggunakan waktu real-time Indonesia
hasil: hasilTerbesar.nilai_bayes, // harus ada, harus tipe FLOAT hasil: hasilTerbesar.nilai_bayes, // harus ada, harus tipe FLOAT
}; };
// Tambahkan id_penyakit / id_hama jika ada // Tambahkan id_penyakit / id_hama jika ada
if (hasilTerbesar.id_penyakit) { if (hasilTerbesar.id_penyakit) {
baseHistoriData.id_penyakit = hasilTerbesar.id_penyakit; baseHistoriData.id_penyakit = hasilTerbesar.id_penyakit;
} else if (hasilTerbesar.id_hama) { } else if (hasilTerbesar.id_hama) {
baseHistoriData.id_hama = hasilTerbesar.id_hama; baseHistoriData.id_hama = hasilTerbesar.id_hama;
} }
try { try {
const historiPromises = gejala.map(gejalaId => { const historiPromises = gejala.map(gejalaId => {
return Histori.create({ return Histori.create({
@ -164,9 +169,9 @@ exports.diagnosa = async (req, res) => {
id_gejala: parseInt(gejalaId) id_gejala: parseInt(gejalaId)
}); });
}); });
await Promise.all(historiPromises); await Promise.all(historiPromises);
console.log(`Histori berhasil disimpan untuk ${gejala.length} gejala.`); console.log(`Histori berhasil disimpan untuk ${gejala.length} gejala dengan waktu: ${jakartaTime.toISOString()}`);
} catch (error) { } catch (error) {
console.error('Gagal menyimpan histori:', error.message); console.error('Gagal menyimpan histori:', error.message);
} }
@ -174,8 +179,8 @@ exports.diagnosa = async (req, res) => {
console.log('Tidak ada hasil diagnosa untuk disimpan.'); console.log('Tidak ada hasil diagnosa untuk disimpan.');
} }
} }
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
message: 'Berhasil melakukan diagnosa', message: 'Berhasil melakukan diagnosa',
@ -186,7 +191,7 @@ exports.diagnosa = async (req, res) => {
hasil_tertinggi: allResults.length > 0 ? allResults[0] : null hasil_tertinggi: allResults.length > 0 ? allResults[0] : null
} }
}); });
} catch (error) { } catch (error) {
console.error('Error diagnosa:', error); console.error('Error diagnosa:', error);
return res.status(500).json({ return res.status(500).json({

View File

@ -10,9 +10,16 @@ class AdminHistoriPage extends StatefulWidget {
class _AdminHistoriPageState extends State<AdminHistoriPage> { class _AdminHistoriPageState extends State<AdminHistoriPage> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
List<Map<String, dynamic>> historiData = []; List<Map<String, dynamic>> historiData = [];
List<Map<String, dynamic>> groupedHistoriData = [];
bool isLoading = true; bool isLoading = true;
String? error; String? error;
// Pagination variables
int _rowsPerPage = 10;
int _currentPage = 0;
int _totalPages = 0;
List<Map<String, dynamic>> _currentPageData = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -28,18 +35,25 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
// Dapatkan semua histori terlebih dahulu // Dapatkan semua histori terlebih dahulu
final allHistori = await apiService.getAllHistori(); final allHistori = await apiService.getAllHistori();
// Kumpulkan semua hasil fetchHistoriDenganDetail untuk setiap user // Kumpulkan semua hasil fetchHistoriDenganDetail untuk setiap user
List<Map<String, dynamic>> detailedHistori = []; List<Map<String, dynamic>> detailedHistori = [];
for (var histori in allHistori) { for (var histori in allHistori) {
if (histori['userId'] != null) { if (histori['userId'] != null) {
final userHistori = await apiService.fetchHistoriDenganDetail(histori['userId'].toString()); final userHistori = await apiService.fetchHistoriDenganDetail(
histori['userId'].toString(),
);
detailedHistori.addAll(userHistori); detailedHistori.addAll(userHistori);
} }
} }
// Kelompokkan data berdasarkan user, diagnosa, dan waktu yang sama
final groupedData = _groupHistoriData(detailedHistori);
setState(() { setState(() {
historiData = detailedHistori; historiData = detailedHistori; // Simpan data asli jika perlu
groupedHistoriData = groupedData; // Data yang sudah dikelompokkan
_updatePagination(0); // Set halaman pertama
isLoading = false; isLoading = false;
}); });
} catch (e) { } catch (e) {
@ -50,6 +64,100 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
} }
} }
// Fungsi untuk mengelompokkan data berdasarkan userId, diagnosa, dan waktu
// 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' (sesuaikan dengan struktur data Anda)
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, // Menampilkan nama user, bukan ID
'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, // untuk pengurutan
};
}
// 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']);
}
}
// 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 = (groupedHistoriData.length / _rowsPerPage).ceil();
int startIndex = page * _rowsPerPage;
int endIndex = (page + 1) * _rowsPerPage;
if (endIndex > groupedHistoriData.length) {
endIndex = groupedHistoriData.length;
}
if (startIndex >= groupedHistoriData.length) {
_currentPageData = [];
} else {
_currentPageData = groupedHistoriData.sublist(startIndex, endIndex);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -57,90 +165,214 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
title: Text('Riwayat Diagnosa'), title: Text('Riwayat Diagnosa'),
backgroundColor: Color(0xFF9DC08D), backgroundColor: Color(0xFF9DC08D),
actions: [ actions: [
IconButton( IconButton(icon: Icon(Icons.refresh), onPressed: _loadHistoriData),
icon: Icon(Icons.refresh),
onPressed: _loadHistoriData,
),
], ],
), ),
body: isLoading body:
? Center(child: CircularProgressIndicator()) isLoading
: error != null ? Center(child: CircularProgressIndicator())
? Center(child: Text('Error: $error')) : error != null
: historiData.isEmpty ? Center(child: Text('Error: $error'))
? Center(child: Text('Tidak ada data riwayat diagnosa')) : groupedHistoriData.isEmpty
: SingleChildScrollView( ? Center(child: Text('Tidak ada data riwayat diagnosa'))
scrollDirection: Axis.horizontal, : Column(
child: SingleChildScrollView( children: [
child: DataTable( Expanded(
columnSpacing: 20, child: SingleChildScrollView(
headingRowColor: MaterialStateProperty.all( scrollDirection: Axis.horizontal,
Color(0xFF9DC08D).withOpacity(0.3), child: SingleChildScrollView(
), child: DataTable(
columns: [ columnSpacing: 20,
DataColumn(label: Text('Nama User', style: TextStyle(fontWeight: FontWeight.bold))), headingRowColor: MaterialStateProperty.all(
DataColumn(label: Text('Gejala', style: TextStyle(fontWeight: FontWeight.bold))), Color(0xFF9DC08D).withOpacity(0.3),
DataColumn(label: Text('Diagnosa', style: TextStyle(fontWeight: FontWeight.bold))), ),
DataColumn(label: Text('Hasil', style: TextStyle(fontWeight: FontWeight.bold))), columns: [
DataColumn(label: Text('Tanggal', style: TextStyle(fontWeight: FontWeight.bold))), DataColumn(
], label: Text(
rows: historiData.map((histori) { 'Nama User',
return DataRow( style: TextStyle(fontWeight: FontWeight.bold),
cells: [
DataCell(Text(histori['name'] ?? 'Unknown')), // Changed 'name' to 'user_name'
DataCell(
Container(
constraints: BoxConstraints(maxWidth: 200),
child: Tooltip(
message: histori['gejala_nama'] ?? 'Tidak ada gejala',
child: Text(
histori['gejala_nama'] ?? 'Tidak ada gejala',
overflow: TextOverflow.ellipsis,
),
), ),
), ),
), DataColumn(
DataCell( label: Text(
Text( 'Gejala',
_getDiagnosaText(histori), style: TextStyle(fontWeight: FontWeight.bold),
// Removed style with color formatting ),
) ),
), DataColumn(
DataCell(Text(_formatHasil(histori['hasil']))), label: Text(
DataCell(Text(_formatDate(histori['tanggal_diagnosa']))), 'Diagnosa',
], style: TextStyle(fontWeight: FontWeight.bold),
); ),
}).toList(), ),
DataColumn(
label: Text(
'Hasil',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Tanggal',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
rows:
_currentPageData.map((histori) {
// Gabungkan semua gejala menjadi satu string dengan koma
String gejalaText = "Tidak ada gejala";
if (histori['gejala'] != null &&
(histori['gejala'] as List).isNotEmpty) {
gejalaText = (histori['gejala'] as List).join(
', ',
);
}
return DataRow(
cells: [
DataCell(Text(histori['userName'] ?? 'User tidak ditemukan')),
DataCell(
Container(
constraints: BoxConstraints(
maxWidth: 200,
),
child: Tooltip(
message: gejalaText,
child: Text(
gejalaText,
overflow: TextOverflow.ellipsis,
),
),
),
),
DataCell(
Text(
histori['diagnosa'] ??
'Tidak ada diagnosa',
style: TextStyle(
color: _getDiagnosaColor(histori),
fontWeight: FontWeight.w500,
),
),
),
DataCell(
Text(_formatHasil(histori['hasil'])),
),
DataCell(
Text(histori['tanggal_display'] ?? ''),
),
],
);
}).toList(),
),
),
),
), ),
), // Pagination controls
Container(
padding: EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.first_page),
onPressed:
_currentPage > 0
? () {
setState(() {
_updatePagination(0);
});
}
: null,
),
IconButton(
icon: Icon(Icons.chevron_left),
onPressed:
_currentPage > 0
? () {
setState(() {
_updatePagination(_currentPage - 1);
});
}
: null,
),
SizedBox(width: 20),
Text(
'Halaman ${_currentPage + 1} dari $_totalPages',
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(width: 20),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed:
_currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_currentPage + 1);
});
}
: null,
),
IconButton(
icon: Icon(Icons.last_page),
onPressed:
_currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_totalPages - 1);
});
}
: null,
),
],
),
),
// Rows per page selector
Container(
padding: EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Rows per page: '),
DropdownButton<int>(
value: _rowsPerPage,
items:
[10, 20, 50, 100].map((value) {
return DropdownMenuItem<int>(
value: value,
child: Text('$value'),
);
}).toList(),
onChanged: (value) {
setState(() {
_rowsPerPage = value!;
_updatePagination(
0,
); // Kembali ke halaman pertama
});
},
),
],
),
),
],
), ),
); );
} }
String _getDiagnosaText(Map<String, dynamic> histori) { Color _getDiagnosaColor(Map<String, dynamic> histori) {
if (histori['penyakit_nama'] != null) { if (histori['penyakit_nama'] != null) {
return 'Penyakit: ${histori['penyakit_nama']}'; return Colors.red[700]!;
} else if (histori['hama_nama'] != null) { } else if (histori['hama_nama'] != null) {
return 'Hama: ${histori['hama_nama']}'; return Colors.amber[800]!;
} }
return 'Tidak ada diagnosa'; return Colors.black;
} }
// Removed _getDiagnosaColor method as it's no longer needed
String _formatHasil(dynamic hasil) { String _formatHasil(dynamic hasil) {
if (hasil == null) return '0%'; if (hasil == null) return '0%';
double hasilValue = double.tryParse(hasil.toString()) ?? 0.0; double hasilValue = double.tryParse(hasil.toString()) ?? 0.0;
return '${(hasilValue * 100).toStringAsFixed(2)}%'; return '${(hasilValue * 100).toStringAsFixed(2)}%';
} }
}
String _formatDate(dynamic tanggal) {
if (tanggal == null) return '';
try {
DateTime date = DateTime.parse(tanggal.toString());
return DateFormat('dd/MM/yyyy HH:mm').format(date);
} catch (e) {
return '';
}
}
}

View File

@ -170,15 +170,15 @@ class _AdminPageState extends State<AdminPage> {
); );
}, },
), ),
// ListTile( ListTile(
// title: Text('Halaman Histori User'), title: Text('Halaman Histori User'),
// onTap: () { onTap: () {
// Navigator.push( Navigator.push(
// context, context,
// MaterialPageRoute(builder: (context) => AdminHistoriPage()), MaterialPageRoute(builder: (context) => AdminHistoriPage()),
// ); );
// }, },
// ), ),
ListTile( ListTile(
title: Text('Data Pengguna'), title: Text('Data Pengguna'),
onTap: () { onTap: () {

View File

@ -150,73 +150,122 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
} }
} }
// Fungsi baru untuk mengelompokkan berdasarkan diagnosis dan waktu yang sama
List<Map<String, dynamic>> _groupHistoriByDiagnosis( List<Map<String, dynamic>> _groupHistoriByDiagnosis(
List<Map<String, dynamic>> data, List<Map<String, dynamic>> data,
) { ) {
final Map<String, Map<String, dynamic>> groupedData = {}; final Map<String, Map<String, dynamic>> groupedData = {};
print("Data mentah dari API: $data"); print("Data mentah dari API (setelah filter userId): $data");
for (var item in data) { for (var item in data) {
final String? penyakitNama = item['penyakit_nama']; final String? penyakitNama = item['penyakit_nama'];
final String? hamaNama = item['hama_nama']; final String? hamaNama = item['hama_nama'];
final String? tanggalDiagnosa = item['tanggal_diagnosa'];
final int? idPenyakit = item['id_penyakit']; final int? idPenyakit = item['id_penyakit'];
final int? idHama = item['id_hama']; final int? idHama = item['id_hama'];
final hasPenyakit = penyakitNama != null && penyakitNama.toString().isNotEmpty; final hasPenyakit =
final hasHama = hamaNama != null && hamaNama.toString().isNotEmpty; penyakitNama != null && penyakitNama.toString().isNotEmpty;
final hasHama = hamaNama != null && hamaNama.toString().isNotEmpty;
if (!hasPenyakit && !hasHama) { if (!hasPenyakit && !hasHama) {
print("Item dilewati karena tidak memiliki penyakit atau hama: $item"); print("Item dilewati karena tidak memiliki penyakit atau hama: $item");
continue; continue;
} }
// Gabungkan nama penyakit dan hama jika keduanya ada if (tanggalDiagnosa == null) {
String diagnosisKey = ''; print("Item dilewati karena tidak memiliki tanggal diagnosa: $item");
if (hasPenyakit && hasHama) { continue;
diagnosisKey = '$penyakitNama & $hamaNama'; }
} else if (hasPenyakit) {
diagnosisKey = penyakitNama!;
} else {
diagnosisKey = hamaNama!;
}
// Tentukan diagnosis_type hanya sebagai referensi // Gabungkan nama penyakit dan hama jika keduanya ada
String diagnosisType = String diagnosisKey = '';
hasPenyakit && hasHama if (hasPenyakit && hasHama) {
? 'penyakit & hama' diagnosisKey = '$penyakitNama & $hamaNama';
: hasPenyakit } else if (hasPenyakit) {
? 'penyakit' diagnosisKey = penyakitNama!;
: 'hama'; } else {
diagnosisKey = hamaNama!;
}
// Inisialisasi grup jika belum ada // Tentukan diagnosis_type
if (!groupedData.containsKey(diagnosisKey)) { String diagnosisType =
groupedData[diagnosisKey] = { hasPenyakit && hasHama
'diagnosis': diagnosisKey, ? 'penyakit & hama'
'diagnosis_type': diagnosisType, : hasPenyakit
'gejala': <String>[], ? 'penyakit'
'hasil': item['hasil'], : 'hama';
'tanggal_diagnosa': item['tanggal_diagnosa'],
'id_penyakit': idPenyakit,
'id_hama': idHama,
};
}
// Tambahkan gejala jika belum ada // Format tanggal waktu ke format Indonesia
if (item['gejala_nama'] != null) { String formattedDateTime = '';
final gejalaNama = item['gejala_nama']; String rawDateTime = '';
if (!groupedData[diagnosisKey]!['gejala'].contains(gejalaNama)) { DateTime dateTime;
groupedData[diagnosisKey]!['gejala'].add(gejalaNama);
try {
// Parsing tanggal dari string yang datang dari API
dateTime = DateTime.parse(tanggalDiagnosa);
// Buat format tanggal dan waktu yang lebih ramah pengguna
String day = dateTime.day.toString().padLeft(2, '0');
String month = dateTime.month.toString().padLeft(2, '0');
String year = dateTime.year.toString();
String hour = dateTime.hour.toString().padLeft(2, '0');
String minute = dateTime.minute.toString().padLeft(2, '0');
String second = dateTime.second.toString().padLeft(2, '0');
formattedDateTime = '$day-$month-$year $hour:$minute:$second WIB';
// Format timestamp untuk pengelompokan (tanpa detik untuk mengelompokkan waktu yang "sama")
rawDateTime = '$year-$month-$day $hour:$minute';
} catch (e) {
print("Error parsing date: $e for date: $tanggalDiagnosa");
formattedDateTime = 'Tanggal tidak valid';
rawDateTime = 'invalid';
continue; // Skip item with invalid date
}
// Buat composite key untuk pengelompokan (gabungan diagnosa dan waktu)
final compositeKey = '$diagnosisKey##$rawDateTime';
// Inisialisasi grup jika belum ada
if (!groupedData.containsKey(compositeKey)) {
groupedData[compositeKey] = {
'diagnosis': diagnosisKey,
'diagnosis_type': diagnosisType,
'gejala': <String>[],
'hasil': item['hasil'],
'tanggal_diagnosa': formattedDateTime,
'tanggal_diagnosa_raw': tanggalDiagnosa,
'id_penyakit': idPenyakit,
'id_hama': idHama,
'timestamp': rawDateTime, // menyimpan waktu untuk sorting
};
}
// Tambahkan gejala jika belum ada
if (item['gejala_nama'] != null) {
final gejalaNama = item['gejala_nama'];
if (!groupedData[compositeKey]!['gejala'].contains(gejalaNama)) {
groupedData[compositeKey]!['gejala'].add(gejalaNama);
}
} }
} }
// Konversi ke list dan urutkan berdasarkan timestamp terbaru
final result = groupedData.values.toList();
result.sort((a, b) {
final String timestampA = a['timestamp'] ?? '';
final String timestampB = b['timestamp'] ?? '';
return timestampB.compareTo(timestampA); // descending (terbaru dulu)
});
print(
"Hasil pengelompokan berdasarkan diagnosa dan waktu: ${result.length} entries",
);
return result;
} }
final result = groupedData.values.toList();
print("Hasil pengelompokan: $result");
return result;
}
Future<void> _fetchHistoriData() async { Future<void> _fetchHistoriData() async {
try { try {
print("Fetching histori dengan userId: $_userId"); print("Fetching histori dengan userId: $_userId");
@ -333,68 +382,146 @@ class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
? "Tidak ada gejala tercatat" ? "Tidak ada gejala tercatat"
: gejalaList.join(', '); : gejalaList.join(', ');
// Mendapatkan jenis diagnosis (penyakit/hama/keduanya)
final String diagnosisType =
riwayat['diagnosis_type'] ?? 'Tidak diketahui';
// Mendapatkan badge warna berdasarkan jenis diagnosis
Color badgeColor;
switch (diagnosisType) {
case 'penyakit':
badgeColor = Color(0xFF9DC08D); // Warna untuk penyakit
break;
case 'hama':
badgeColor = Color(
0xFF7A9A6D,
); // Warna lebih gelap untuk hama
break;
case 'penyakit & hama':
badgeColor = Color(
0xFF5C7452,
); // Warna paling gelap untuk kombinasi
break;
default:
badgeColor = Colors.grey[300]!;
}
return Card( return Card(
margin: EdgeInsets.only(bottom: 12.0), margin: EdgeInsets.only(bottom: 12.0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
elevation: 3, elevation: 3,
child: Padding( child: Column(
padding: const EdgeInsets.all(12.0), children: [
child: Column( // Header dengan waktu diagnosa
crossAxisAlignment: CrossAxisAlignment.start, Container(
children: [ width: double.infinity,
Text( padding: EdgeInsets.symmetric(
'Diagnosis: ${riwayat['diagnosis']}', horizontal: 12,
style: TextStyle( vertical: 8,
fontSize: 18, ),
fontWeight: FontWeight.bold, decoration: BoxDecoration(
color: Color(0xFFE1EDD5),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
), ),
), ),
SizedBox(height: 8), child: Row(
Text( mainAxisAlignment: MainAxisAlignment.spaceBetween,
'Gejala: $gejalaText', children: [
style: TextStyle(fontSize: 14), Icon(
), Icons.access_time,
SizedBox(height: 4), size: 18,
Text( color: Colors.grey[700],
'Hasil: ${(riwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}',
style: TextStyle(fontSize: 14),
),
SizedBox(height: 8),
Text(
'Tanggal: ${riwayat['tanggal_diagnosa'] ?? "-"}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
SizedBox(height: 12),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {
print("Navigating to DetailRiwayatPage with data: $riwayat");
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => DetailRiwayatPage(
detailRiwayat:
riwayat, // Kirim data riwayat ke halaman detail
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
foregroundColor: Colors.white,
), ),
child: Text('Lihat Detail'), SizedBox(width: 8),
), Expanded(
child: Text(
'${riwayat['tanggal_diagnosa'] ?? "Tanggal tidak tersedia"}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
),
// Badge jenis diagnosis
Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(12),
),
child: Text(
diagnosisType,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
],
), ),
], ),
), // Content
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Diagnosis: ${riwayat['diagnosis']}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'Gejala: $gejalaText',
style: TextStyle(fontSize: 14),
),
SizedBox(height: 4),
Text(
'Hasil: ${(riwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}',
style: TextStyle(fontSize: 14),
),
SizedBox(height: 12),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {
print(
"Navigating to DetailRiwayatPage with data: $riwayat",
);
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => DetailRiwayatPage(
detailRiwayat:
riwayat, // Kirim data riwayat ke halaman detail
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
foregroundColor: Colors.white,
),
child: Text('Lihat Detail'),
),
),
],
),
),
],
), ),
); );
}, },