penambahan fungsi implementasi data

This commit is contained in:
unknown 2025-05-05 13:01:42 +07:00
parent 90e496d495
commit a61ffed9cd
27 changed files with 1184 additions and 570 deletions

View File

@ -0,0 +1,102 @@
const { Rule_penyakit, Rule_hama, Gejala, Penyakit, Hama } = require('../models');
exports.diagnosa = async (req, res) => {
const { gejala } = req.body; // array of id_gejala
if (!gejala || !Array.isArray(gejala)) {
return res.status(400).json({ message: 'Gejala harus berupa array' });
}
try {
// ===================== Penyakit =====================
const allPenyakitRules = await Rule_penyakit.findAll({
where: {
id_gejala: gejala,
},
include: [
{
model: Penyakit,
as: 'penyakit',
},
],
});
const penyakitScores = {};
allPenyakitRules.forEach(rule => {
const idPenyakit = rule.id_penyakit;
const nilaiPakarGejala = rule.nilai_pakar; // P(E|H)
const nilaiPakarPenyakit = rule.penyakit.nilai_pakar; // P(H)
if (!penyakitScores[idPenyakit]) {
// === Menginisialisasi: P(E|H) * P(H) ===
penyakitScores[idPenyakit] = {
penyakit: rule.penyakit.nama,
total: nilaiPakarGejala * nilaiPakarPenyakit, // ← Rumus Bayes awal
};
} else {
// === Mengalikan P(E|H) berikutnya (jika diasumsikan independen) ===
penyakitScores[idPenyakit].total *= nilaiPakarGejala;
}
});
// ===================== Hama =====================
const allHamaRules = await Rule_hama.findAll({
where: {
id_gejala: gejala,
},
include: [
{
model: Hama,
as: 'hama',
},
],
});
const hamaScores = {};
allHamaRules.forEach(rule => {
const idHama = rule.id_hama;
const nilaiPakarGejala = rule.nilai_pakar; // P(E|H)
const nilaiPakarHama = rule.hama.nilai_pakar; // P(H)
if (!hamaScores[idHama]) {
// === Menginisialisasi: P(E|H) * P(H) ===
hamaScores[idHama] = {
hama: rule.hama.nama,
total: nilaiPakarGejala * nilaiPakarHama, // ← Rumus Bayes awal
};
} else {
// === Mengalikan P(E|H) berikutnya ===
hamaScores[idHama].total *= nilaiPakarGejala;
}
});
// ===================== Normalisasi (opsional) =====================
const totalPenyakit = Object.values(penyakitScores).reduce((acc, cur) => acc + cur.total, 0);
const totalHama = Object.values(hamaScores).reduce((acc, cur) => acc + cur.total, 0);
const normalizedPenyakit = Object.values(penyakitScores).map(p => ({
...p,
probabilitas: (p.total / totalPenyakit) || 0, // Probabilitas akhir
}));
const normalizedHama = Object.values(hamaScores).map(h => ({
...h,
probabilitas: (h.total / totalHama) || 0,
}));
// Sorting
const sortedPenyakit = normalizedPenyakit.sort((a, b) => b.probabilitas - a.probabilitas);
const sortedHama = normalizedHama.sort((a, b) => b.probabilitas - a.probabilitas);
res.json({
penyakit: sortedPenyakit,
hama: sortedHama,
});
} catch (error) {
console.error('Error dalam perhitungan Bayes:', error);
res.status(500).json({ message: 'Terjadi kesalahan dalam proses diagnosa' });
}
};

View File

@ -4,7 +4,7 @@ const {Gejala} = require('../models');
exports.getAllGejala = async (req, res) => { exports.getAllGejala = async (req, res) => {
try { try {
const gejala = await Gejala.findAll({ const gejala = await Gejala.findAll({
attributes: ['id', 'nama'] attributes: ['id', 'nama', 'kode']
}); });
res.status(200).json(gejala); res.status(200).json(gejala);
} catch (error) { } catch (error) {

View File

@ -6,7 +6,7 @@ const fs = require('fs');
exports.getAllHama = async (req, res) => { exports.getAllHama = async (req, res) => {
try { try {
const dataHama = await Hama.findAll({ const dataHama = await Hama.findAll({
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan', 'foto'] attributes: ['id', 'nama' , 'deskripsi' , 'penanganan', 'foto', 'kode', 'nilai_pakar']
}); });
res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama }); res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama });
} catch (error) { } catch (error) {
@ -51,7 +51,7 @@ exports.getHamaById = async (req, res) => {
// Pastikan sudah import 'Hama' model dan multer middleware sebelumnya // Pastikan sudah import 'Hama' model dan multer middleware sebelumnya
exports.createHama = async (req, res) => { exports.createHama = async (req, res) => {
try { try {
const { nama, deskripsi, penanganan } = req.body; const { nama, deskripsi, penanganan, nilai_pakar } = req.body;
const file = req.file; const file = req.file;
// Cek kode terakhir // Cek kode terakhir
@ -71,10 +71,11 @@ exports.createHama = async (req, res) => {
const newHama = await Hama.create({ const newHama = await Hama.create({
kode: newKode, kode: newKode,
nama, nama,
kategori: 'hama', // Default kategori kategori: 'hama',
deskripsi, deskripsi,
penanganan, penanganan,
foto: fotoPath, // ⬅️ Masukkan nama file ke database foto: fotoPath,
nilai_pakar
}); });
res.status(201).json({ message: 'Hama berhasil ditambahkan', data: newHama }); res.status(201).json({ message: 'Hama berhasil ditambahkan', data: newHama });
@ -88,7 +89,7 @@ exports.createHama = async (req, res) => {
exports.updateHama = async (req, res) => { exports.updateHama = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { nama, kategori, deskripsi, penanganan } = req.body; const { nama, kategori, deskripsi, penanganan, nilai_pakar, } = req.body;
const hama = await Hama.findByPk(id); const hama = await Hama.findByPk(id);
if (!hama) { if (!hama) {
@ -101,7 +102,7 @@ exports.updateHama = async (req, res) => {
foto = req.file.filename; foto = req.file.filename;
} }
await hama.update({ nama, kategori, deskripsi, penanganan, foto }); await hama.update({ nama, kategori, deskripsi, penanganan, foto, nilai_pakar });
res.status(200).json({ message: 'Hama berhasil diperbarui', data: hama }); res.status(200).json({ message: 'Hama berhasil diperbarui', data: hama });
} catch (error) { } catch (error) {

View File

@ -6,7 +6,7 @@ const fs = require('fs');
exports.getAllPenyakit = async (req, res) => { exports.getAllPenyakit = async (req, res) => {
try { try {
const dataPenyakit = await Penyakit.findAll({ const dataPenyakit = await Penyakit.findAll({
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan' , 'foto'] attributes: ['id', 'nama' , 'deskripsi' , 'penanganan' , 'foto', 'kode', 'nilai_pakar']
}); });
res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit }); res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit });
} catch (error) { } catch (error) {
@ -50,7 +50,7 @@ exports.getPenyakitById = async (req, res) => {
// 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default) // 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default)
exports.createPenyakit = async (req, res) => { exports.createPenyakit = async (req, res) => {
try { try {
const { nama, deskripsi, penanganan } = req.body; const { nama, deskripsi, penanganan, nilai_pakar } = req.body;
const file = req.file; const file = req.file;
// Cek kode terakhir // Cek kode terakhir
@ -74,6 +74,7 @@ exports.createPenyakit = async (req, res) => {
deskripsi, deskripsi,
penanganan, penanganan,
foto: fotoPath, foto: fotoPath,
nilai_pakar
}); });
res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit }); res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit });
@ -86,7 +87,7 @@ exports.createPenyakit = async (req, res) => {
exports.updatePenyakit = async (req, res) => { exports.updatePenyakit = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { nama, kategori, deskripsi, penanganan } = req.body; const { nama, kategori, deskripsi, penanganan, nilai_pakar } = req.body;
const penyakit = await Penyakit.findByPk(id); const penyakit = await Penyakit.findByPk(id);
if (!penyakit) { if (!penyakit) {
@ -99,7 +100,7 @@ exports.updatePenyakit = async (req, res) => {
foto = req.file.filename; foto = req.file.filename;
} }
await penyakit.update({ nama, kategori, deskripsi, penanganan, foto }); await penyakit.update({ nama, kategori, deskripsi, penanganan, foto, nilai_pakar });
res.status(200).json({ message: 'Penyakit berhasil diperbarui', data: penyakit }); res.status(200).json({ message: 'Penyakit berhasil diperbarui', data: penyakit });
} catch (error) { } catch (error) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,4 +1,7 @@
'use strict'; 'use strict';
const { sequelize } = require('../models');
/** @type {import('sequelize-cli').Migration} */ /** @type {import('sequelize-cli').Migration} */
module.exports = { module.exports = {
async up(queryInterface, Sequelize) { async up(queryInterface, Sequelize) {
@ -24,6 +27,10 @@ module.exports = {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: true allowNull: true
}, },
nilai_pakar: {
type: Sequelize.FLOAT,
allowNull: true
},
}); });
}, },
async down(queryInterface, Sequelize) { async down(queryInterface, Sequelize) {

View File

@ -0,0 +1,14 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('penyakit', 'nilai_pakar', {
type: Sequelize.FLOAT,
allowNull: true,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('penyakit', 'nilai_pakar');
}
};

View File

@ -31,6 +31,10 @@ module.exports = (sequelize) => {
foto: { foto: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
},
nilai_pakar: {
type: DataTypes.FLOAT,
allowNull: true
} }
}, },
{ {

View File

@ -31,6 +31,10 @@ module.exports =(sequelize) => {
foto: { foto: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
},
nilai_pakar: {
type: DataTypes.FLOAT,
allowNull: true
} }
}, },
{ {

View File

@ -0,0 +1,42 @@
const express = require('express');
const router = express.Router();
const diagnosaController = require('../controller/diagnosaController');
/**
* @swagger
* /api/diagnosa/bayes:
* post:
* summary: Melakukan diagnosa penyakit dan hama menggunakan Teorema Bayes
* tags: [Diagnosa]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* gejala:
* type: array
* items:
* type: integer
* example: [1, 2, 3]
* responses:
* 200:
* description: Hasil diagnosa berhasil dikembalikan
* content:
* application/json:
* schema:
* type: object
* properties:
* penyakit:
* type: object
* hama:
* type: object
* 400:
* description: Permintaan tidak valid
* 500:
* description: Terjadi kesalahan pada server
*/
router.post('/bayes', diagnosaController.diagnosaBayes);
module.exports = router;

View File

@ -68,6 +68,9 @@ router.get('/:id/image', hamaController.getHamaById);
* type: string * type: string
* format: binary * format: binary
* description: Foto hama (JPG, JPEG, PNG, GIF) * description: Foto hama (JPG, JPEG, PNG, GIF)
* nilai_pakar:
* type: number
* format: float
* responses: * responses:
* 201: * 201:
* description: Hama berhasil ditambahkan * description: Hama berhasil ditambahkan
@ -105,6 +108,9 @@ router.post('/', uploadHamaGambar.single('foto'), hamaController.createHama);
* foto: * foto:
* type: string * type: string
* format: binary * format: binary
* nilai_pakar:
* type: number
* format: float
* responses: * responses:
* 200: * 200:
* description: Hama berhasil diperbarui * description: Hama berhasil diperbarui

View File

@ -69,6 +69,9 @@ router.get('/:id/image', penyakitController.getPenyakitById);
* type: string * type: string
* format: binary * format: binary
* description: Foto penyakit (JPG, JPEG, PNG, GIF) * description: Foto penyakit (JPG, JPEG, PNG, GIF)
* nilai_pakar:
* type: number
* format: float
* responses: * responses:
* 201: * 201:
* description: Hama berhasil ditambahkan * description: Hama berhasil ditambahkan
@ -106,6 +109,9 @@ router.post('/', uploadPenyakitGambar.single('foto'), penyakitController.createP
* foto: * foto:
* type: string * type: string
* format: binary * format: binary
* nilai_pakar:
* type: number
* format: float
* responses: * responses:
* 200: * 200:
* description: penyakit berhasil diperbarui * description: penyakit berhasil diperbarui

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async';
import 'hama_page.dart'; import 'hama_page.dart';
import 'penyakit_page.dart'; import 'penyakit_page.dart';
import 'gejala_page.dart'; import 'gejala_page.dart';
@ -6,7 +7,65 @@ import 'rule_page.dart';
import 'package:frontend/api_services/api_services.dart'; import 'package:frontend/api_services/api_services.dart';
import 'package:frontend/user/login_page.dart'; import 'package:frontend/user/login_page.dart';
class AdminPage extends StatelessWidget { class AdminPage extends StatefulWidget {
@override
_AdminPageState createState() => _AdminPageState();
}
class _AdminPageState extends State<AdminPage> {
// Data counters
int userCount = 0;
int diagnosisCount = 0;
int diseaseCount = 0;
int pestCount = 0;
bool isLoading = true;
@override
void initState() {
super.initState();
_loadDashboardData();
}
// Method untuk memuat data dashboard dari API
Future<void> _loadDashboardData() async {
try {
setState(() {
isLoading = true;
});
print("Fetching users with role 'user'...");
// Mengambil jumlah user dengan role 'user'
final userList = await ApiService().getUsers(role: 'user');
if (userList != null && userList.isNotEmpty) {
userCount = userList.length;
print("Jumlah user: $userCount");
} else {
print("Tidak ada user dengan role 'user'.");
}
print("Fetching data penyakit...");
// Mengambil data penyakit menggunakan fungsi yang sudah ada
final penyakitList = await ApiService().getPenyakit();
diseaseCount = penyakitList.length;
print("Jumlah penyakit: $diseaseCount");
print("Fetching data hama...");
// Mengambil data hama menggunakan fungsi yang sudah ada
final hamaList = await ApiService().getHama();
pestCount = hamaList.length;
print("Jumlah hama: $pestCount");
} catch (e) {
print("Error loading dashboard data: $e");
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> _logout(BuildContext context) async { Future<void> _logout(BuildContext context) async {
await ApiService.logoutUser(); await ApiService.logoutUser();
Navigator.pushReplacement( Navigator.pushReplacement(
@ -18,7 +77,10 @@ class AdminPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text('Admin Dashboard')), appBar: AppBar(
title: Text('Admin Dashboard'),
backgroundColor: Color(0xFF9DC08D),
),
drawer: Drawer( drawer: Drawer(
child: Container( child: Container(
color: Color(0xFFFFFFFF), color: Color(0xFFFFFFFF),
@ -80,41 +142,61 @@ class AdminPage extends StatelessWidget {
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Selamat datang Admin!', 'Selamat datang Admin!',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 24), SizedBox(height: 24),
Column( isLoading
children: [ ? Center(
Row( child: CircularProgressIndicator(color: Color(0xFF9DC08D)),
mainAxisAlignment: MainAxisAlignment.spaceEvenly, )
children: [ : Column(
_buildCard('Jumlah User', '10'), children: [
_buildCard('Jumlah Diagnosa', '25'), Row(
], mainAxisAlignment: MainAxisAlignment.spaceEvenly,
), children: [
SizedBox(height: 16), // Spasi antar baris _buildCard(
Row( 'Jumlah User',
mainAxisAlignment: MainAxisAlignment.spaceEvenly, userCount.toString(),
children: [ Icons.people,
_buildCard('Penyakit', '15'), ),
_buildCard('Hama', '15'), _buildCard(
], 'Jumlah Diagnosa',
), diagnosisCount.toString(),
], Icons.assignment,
), ),
], ],
), ),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildCard(
'Penyakit',
diseaseCount.toString(),
Icons.sick,
),
_buildCard(
'Hama',
pestCount.toString(),
Icons.bug_report,
),
],
),
],
),
],
),
), ),
), ),
); );
} }
Widget _buildCard(String title, String count) { Widget _buildCard(String title, String count, IconData icon) {
return Card( return Card(
elevation: 4, elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@ -125,13 +207,22 @@ class AdminPage extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(icon, size: 40, color: Color(0xFF9DC08D)),
SizedBox(height: 10),
Text( Text(
title, title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
SizedBox(height: 10), SizedBox(height: 10),
Text(count, style: TextStyle(fontSize: 20, color: Colors.green)), Text(
count,
style: TextStyle(
fontSize: 24,
color: Color(0xFF9DC08D),
fontWeight: FontWeight.bold,
),
),
], ],
), ),
), ),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:frontend/api_services/api_services.dart'; import 'package:frontend/api_services/api_services.dart';
import 'image_utilities.dart'; // Import file baru import 'image_utilities.dart';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
@ -11,6 +11,7 @@ class EditHamaPage extends StatefulWidget {
final String namaAwal; final String namaAwal;
final String deskripsiAwal; final String deskripsiAwal;
final String penangananAwal; final String penangananAwal;
final double nilai_pakar;
final String gambarUrl; final String gambarUrl;
final VoidCallback onHamaUpdated; final VoidCallback onHamaUpdated;
@ -20,6 +21,7 @@ class EditHamaPage extends StatefulWidget {
required this.namaAwal, required this.namaAwal,
required this.deskripsiAwal, required this.deskripsiAwal,
required this.penangananAwal, required this.penangananAwal,
required this.nilai_pakar,
required this.gambarUrl, required this.gambarUrl,
required this.onHamaUpdated, required this.onHamaUpdated,
}) : super(key: key); }) : super(key: key);
@ -32,6 +34,7 @@ class _EditHamaPageState extends State<EditHamaPage> {
final TextEditingController _namaController = TextEditingController(); final TextEditingController _namaController = TextEditingController();
final TextEditingController _deskripsiController = TextEditingController(); final TextEditingController _deskripsiController = TextEditingController();
final TextEditingController _penangananController = TextEditingController(); final TextEditingController _penangananController = TextEditingController();
final TextEditingController _nilaiPakarController = TextEditingController();
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
@ -41,6 +44,8 @@ class _EditHamaPageState extends State<EditHamaPage> {
String? _errorMessage; String? _errorMessage;
bool _isImageLoading = false; bool _isImageLoading = false;
Uint8List? _currentImageBytes; Uint8List? _currentImageBytes;
// Default value for nilai_pakar to prevent empty string issues
double _currentNilaiPakar = 0.0;
@override @override
void initState() { void initState() {
@ -49,6 +54,10 @@ class _EditHamaPageState extends State<EditHamaPage> {
_deskripsiController.text = widget.deskripsiAwal; _deskripsiController.text = widget.deskripsiAwal;
_penangananController.text = widget.penangananAwal; _penangananController.text = widget.penangananAwal;
// Ensure nilai_pakar is properly initialized
_currentNilaiPakar = widget.nilai_pakar;
_nilaiPakarController.text = widget.nilai_pakar.toString();
// Load existing image // Load existing image
_loadExistingImage(); _loadExistingImage();
} }
@ -89,9 +98,25 @@ class _EditHamaPageState extends State<EditHamaPage> {
_namaController.dispose(); _namaController.dispose();
_deskripsiController.dispose(); _deskripsiController.dispose();
_penangananController.dispose(); _penangananController.dispose();
_nilaiPakarController.dispose();
super.dispose(); super.dispose();
} }
// Validate and parse nilai_pakar input
double _parseNilaiPakar() {
if (_nilaiPakarController.text.isEmpty) {
return _currentNilaiPakar; // Return current value if field is empty
}
try {
String input = _nilaiPakarController.text.trim().replaceAll(',', '.');
return double.parse(input);
} catch (e) {
print("Error parsing nilai_pakar: $e");
return _currentNilaiPakar; // Return current value if parsing fails
}
}
Future<void> _updateHama() async { Future<void> _updateHama() async {
try { try {
setState(() { setState(() {
@ -99,12 +124,18 @@ class _EditHamaPageState extends State<EditHamaPage> {
_errorMessage = null; _errorMessage = null;
}); });
// Get nilai_pakar value with safety check
double nilaiPakar = _parseNilaiPakar();
print("Updating hama with nilai_pakar: $nilaiPakar");
await apiService.updateHama( await apiService.updateHama(
widget.idHama, widget.idHama,
_namaController.text, _namaController.text,
_deskripsiController.text, _deskripsiController.text,
_penangananController.text, _penangananController.text,
_pickedFile, _pickedFile,
nilaiPakar,
); );
setState(() { setState(() {
@ -257,41 +288,71 @@ class _EditHamaPageState extends State<EditHamaPage> {
maxLines: 3, maxLines: 3,
), ),
SizedBox(height: 20), SizedBox(height: 20),
Text( TextField(
'Foto Hama', controller: _nilaiPakarController,
style: TextStyle( decoration: InputDecoration(
fontWeight: FontWeight.bold, labelText: 'Nilai Pakar',
fontSize: 16, hintText: 'Contoh: 0.5',
),
),
SizedBox(height: 8),
_buildImagePreview(),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _pickImage,
icon: Icon(Icons.photo_library),
label: Text('Pilih Gambar'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
],
),
ElevatedButton(
onPressed: _updateHama,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[300],
), ),
child: Text( keyboardType: TextInputType.numberWithOptions(decimal: true),
'Simpan Perubahan', onChanged: (value) {
style: TextStyle(color: Colors.black), // Validate as user types (optional)
try {
if (value.isNotEmpty) {
double.parse(value.replaceAll(',', '.'));
}
} catch (e) {
// Could show validation error here
}
},
),
SizedBox(height: 20),
Text(
'Foto Hama',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
), ),
), ),
SizedBox(height: 8),
_buildImagePreview(),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _pickImage,
icon: Icon(Icons.photo_library),
label: Text('Pilih Gambar'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
],
),
SizedBox(height: 20),
if (_isLoading)
CircularProgressIndicator()
else
ElevatedButton(
onPressed: _updateHama,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[300],
),
child: Text(
'Simpan Perubahan',
style: TextStyle(color: Colors.black),
),
),
if (_errorMessage != null)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Text(
_errorMessage!,
style: TextStyle(color: Colors.red),
),
),
], ],
), ),
), ),
@ -301,4 +362,4 @@ class _EditHamaPageState extends State<EditHamaPage> {
), ),
); );
} }
} }

View File

@ -12,6 +12,7 @@ class EditPenyakitPage extends StatefulWidget {
final String namaAwal; final String namaAwal;
final String deskripsiAwal; final String deskripsiAwal;
final String penangananAwal; final String penangananAwal;
final double nilai_pakar;
final String gambarUrl; final String gambarUrl;
final VoidCallback onPenyakitUpdated; final VoidCallback onPenyakitUpdated;
@ -21,6 +22,7 @@ class EditPenyakitPage extends StatefulWidget {
required this.namaAwal, required this.namaAwal,
required this.deskripsiAwal, required this.deskripsiAwal,
required this.penangananAwal, required this.penangananAwal,
required this.nilai_pakar,
required this.gambarUrl, required this.gambarUrl,
required this.onPenyakitUpdated, required this.onPenyakitUpdated,
}) : super(key: key); }) : super(key: key);
@ -33,6 +35,7 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
final TextEditingController _namaController = TextEditingController(); final TextEditingController _namaController = TextEditingController();
final TextEditingController _deskripsiController = TextEditingController(); final TextEditingController _deskripsiController = TextEditingController();
final TextEditingController _penangananController = TextEditingController(); final TextEditingController _penangananController = TextEditingController();
final TextEditingController _nilaiPakarController = TextEditingController();
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
@ -42,6 +45,7 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
String? _errorMessage; String? _errorMessage;
bool _isImageLoading = false; bool _isImageLoading = false;
Uint8List? _currentImageBytes; Uint8List? _currentImageBytes;
double _currentNilaiPakar = 0.0;
@override @override
void initState() { void initState() {
@ -49,6 +53,8 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
_namaController.text = widget.namaAwal; _namaController.text = widget.namaAwal;
_deskripsiController.text = widget.deskripsiAwal; _deskripsiController.text = widget.deskripsiAwal;
_penangananController.text = widget.penangananAwal; _penangananController.text = widget.penangananAwal;
_currentNilaiPakar = widget.nilai_pakar;
_nilaiPakarController.text = widget.nilai_pakar.toString();
_loadExistingImage(); _loadExistingImage();
} }
@ -91,6 +97,21 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
super.dispose(); super.dispose();
} }
// Validate and parse nilai_pakar input
double _parseNilaiPakar() {
if (_nilaiPakarController.text.isEmpty) {
return _currentNilaiPakar; // Return current value if field is empty
}
try {
String input = _nilaiPakarController.text.trim().replaceAll(',', '.');
return double.parse(input);
} catch (e) {
print("Error parsing nilai_pakar: $e");
return _currentNilaiPakar; // Return current value if parsing fails
}
}
Future<void> _updatePenyakit() async { Future<void> _updatePenyakit() async {
try { try {
setState(() { setState(() {
@ -98,12 +119,18 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
_errorMessage = null; _errorMessage = null;
}); });
// Get nilai_pakar value with safety check
double nilaiPakar = _parseNilaiPakar();
print("Updating hama with nilai_pakar: $nilaiPakar");
await apiService.updatePenyakit( await apiService.updatePenyakit(
widget.idPenyakit, widget.idPenyakit,
_namaController.text, _namaController.text,
_deskripsiController.text, _deskripsiController.text,
_penangananController.text, _penangananController.text,
_pickedFile, _pickedFile,
nilaiPakar,
); );
setState(() { setState(() {
@ -260,6 +287,25 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
maxLines: 3, maxLines: 3,
), ),
SizedBox(height: 20), SizedBox(height: 20),
TextField(
controller: _nilaiPakarController,
decoration: InputDecoration(
labelText: 'Nilai Pakar',
hintText: 'Contoh: 0.5',
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
// Validate as user types (optional)
try {
if (value.isNotEmpty) {
double.parse(value.replaceAll(',', '.'));
}
} catch (e) {
// Could show validation error here
}
},
),
SizedBox(height: 20),
Text( Text(
'Foto Penyakit', 'Foto Penyakit',
style: TextStyle( style: TextStyle(

View File

@ -13,6 +13,8 @@ class EditRulePage extends StatefulWidget {
final List<double> nilaiPakarList; final List<double> nilaiPakarList;
final int? selectedHamaId; final int? selectedHamaId;
final int? selectedPenyakitId; final int? selectedPenyakitId;
final bool showHamaOnly; // Tambahkan ini
final bool showPenyakitOnly; // Tambahkan ini
const EditRulePage({ const EditRulePage({
Key? key, Key? key,
@ -21,6 +23,8 @@ class EditRulePage extends StatefulWidget {
required this.selectedRuleIds, required this.selectedRuleIds,
required this.selectedGejalaIds, required this.selectedGejalaIds,
required this.nilaiPakarList, required this.nilaiPakarList,
this.showHamaOnly = false, // Tambahkan default value
this.showPenyakitOnly = false,
this.selectedHamaId, this.selectedHamaId,
this.selectedPenyakitId, this.selectedPenyakitId,
}) : super(key: key); }) : super(key: key);
@ -37,6 +41,9 @@ class _EditRulePageState extends State<EditRulePage> {
bool isLoading = true; bool isLoading = true;
bool showHamaOnly = false;
bool showPenyakitOnly = false;
final api = ApiService(); final api = ApiService();
// Deklarasi variabel untuk menampung data dari API // Deklarasi variabel untuk menampung data dari API
@ -154,6 +161,8 @@ class _EditRulePageState extends State<EditRulePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
showHamaOnly = widget.showHamaOnly;
showPenyakitOnly = widget.showPenyakitOnly;
fetchData(); // Panggil fetchData saat halaman dibuka pertama kali fetchData(); // Panggil fetchData saat halaman dibuka pertama kali
// Inisialisasi dari widget parent // Inisialisasi dari widget parent
@ -250,62 +259,68 @@ class _EditRulePageState extends State<EditRulePage> {
child: ListView( child: ListView(
children: [ children: [
// Pilih Hama // Pilih Hama
Text("Pilih Hama"), if (!showPenyakitOnly) ...[
DropdownButton<int>( Text("Pilih Hama"),
isExpanded: true, DropdownButton<int>(
value: selectedHamaId, isExpanded: true,
hint: Text('Pilih Hama'), value: selectedHamaId,
items: hint: Text('Pilih Hama'),
hamaList.isNotEmpty items:
? hamaList.map<DropdownMenuItem<int>>((hama) { hamaList.isNotEmpty
return DropdownMenuItem<int>( ? hamaList.map<DropdownMenuItem<int>>((
value: hama['id'], hama,
child: Text(hama['nama']), ) {
); return DropdownMenuItem<int>(
}).toList() value: hama['id'],
: [ child: Text(hama['nama']),
DropdownMenuItem<int>( );
value: null, }).toList()
child: Text("Data tidak tersedia"), : [
), DropdownMenuItem<int>(
], value: null,
onChanged: (value) { child: Text("Data tidak tersedia"),
setState(() { ),
selectedHamaId = value; ],
}); onChanged: (value) {
}, setState(() {
), selectedHamaId = value;
SizedBox(height: 16), });
},
),
SizedBox(height: 16),
],
// Pilih Penyakit // Pilih Penyakit
Text("Pilih Penyakit"), if (!showHamaOnly) ...[
DropdownButton<int>( Text("Pilih Penyakit"),
isExpanded: true, DropdownButton<int>(
value: selectedPenyakitId, isExpanded: true,
hint: Text('Pilih Penyakit'), value: selectedPenyakitId,
items: hint: Text('Pilih Penyakit'),
penyakitList.isNotEmpty items:
? penyakitList.map<DropdownMenuItem<int>>(( penyakitList.isNotEmpty
penyakit, ? penyakitList.map<DropdownMenuItem<int>>((
) { penyakit,
return DropdownMenuItem<int>( ) {
value: penyakit['id'], return DropdownMenuItem<int>(
child: Text(penyakit['nama']), value: penyakit['id'],
); child: Text(penyakit['nama']),
}).toList() );
: [ }).toList()
DropdownMenuItem<int>( : [
value: null, DropdownMenuItem<int>(
child: Text("Data tidak tersedia"), value: null,
), child: Text("Data tidak tersedia"),
], ),
onChanged: (value) { ],
setState(() { onChanged: (value) {
selectedPenyakitId = value; setState(() {
}); selectedPenyakitId = value;
}, });
), },
SizedBox(height: 16), ),
SizedBox(height: 16),
],
// Pilih Gejala dan Nilai Pakar // Pilih Gejala dan Nilai Pakar
Text("Pilih Gejala"), Text("Pilih Gejala"),

View File

@ -67,128 +67,6 @@ class _HamaPageState extends State<HamaPage> {
); );
} }
// void _tambahHama() {
// TextEditingController namaController = TextEditingController();
// TextEditingController penangananController = TextEditingController();
// TextEditingController deskripsiController = TextEditingController();
// showDialog(
// context: context,
// builder: (context) {
// return AlertDialog(
// title: Text('Tambah Hama Baru'),
// content: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// TextField(
// controller: namaController,
// decoration: InputDecoration(labelText: 'Nama'),
// ),
// TextField(
// controller: deskripsiController,
// decoration: InputDecoration(labelText: 'Deskripsi'),
// ),
// TextField(
// controller: penangananController,
// decoration: InputDecoration(labelText: 'Penanganan'),
// ),
// ],
// ),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(context),
// child: Text('Batal', style: TextStyle(color: Colors.black)),
// ),
// ElevatedButton(
// onPressed: () async {
// if (namaController.text.isNotEmpty &&
// deskripsiController.text.isNotEmpty &&
// penangananController.text.isNotEmpty) {
// try {
// await apiService.createHama(
// namaController.text,
// deskripsiController.text,
// penangananController.text,
// );
// _fetchHama();
// Navigator.pop(context);
// } catch (e) {
// print("Error adding hama: $e");
// }
// }
// },
// child: Text('Simpan', style: TextStyle(color: Colors.black)),
// ),
// ],
// );
// },
// ).then((_) {
// namaController.dispose();
// deskripsiController.dispose();
// penangananController.dispose();
// });
// }
// void showEditDialog(BuildContext context, Map<String, dynamic> hama) {
// final TextEditingController editNamaController = TextEditingController(
// text: hama['nama'] ?? '',
// );
// final TextEditingController editDeskripsiController = TextEditingController(
// text: hama['deskripsi'] ?? '',
// );
// final TextEditingController editPenangananController =
// TextEditingController(text: hama['penanganan'] ?? '');
// showDialog(
// context: context,
// builder: (context) {
// return AlertDialog(
// title: Text('Edit Hama'),
// content: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// TextField(
// controller: editNamaController,
// decoration: InputDecoration(labelText: 'Nama'),
// ),
// TextField(
// controller: editDeskripsiController,
// decoration: InputDecoration(labelText: 'Deskripsi'),
// ),
// TextField(
// controller: editPenangananController,
// decoration: InputDecoration(labelText: 'Penanganan'),
// ),
// ],
// ),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(context),
// child: Text('Batal'),
// ),
// ElevatedButton(
// onPressed: () async {
// try {
// await apiService.updateHama(
// hama['id'],
// editNamaController.text,
// editDeskripsiController.text,
// editPenangananController.text,
// );
// _fetchHama();
// Navigator.pop(context);
// } catch (e) {
// print("Error updating hama: $e");
// }
// },
// child: Text('Simpan', style: TextStyle(color: Colors.black)),
// ),
// ],
// );
// },
// );
// }
//pagination //pagination
int currentPage = 0; int currentPage = 0;
int rowsPerPage = 10; int rowsPerPage = 10;
@ -283,6 +161,38 @@ class _HamaPageState extends State<HamaPage> {
color: Color(0xFF9DC08D), color: Color(0xFF9DC08D),
), ),
onPressed: () { onPressed: () {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (hama['nilai_pakar'] != null) {
// Coba parse jika string
if (hama['nilai_pakar'] is String) {
try {
String nilaiStr =
hama['nilai_pakar']
.toString()
.trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(
nilaiStr.replaceAll(',', '.'),
);
}
} catch (e) {
print(
"Error parsing nilai_pakar: $e",
);
}
}
// Langsung gunakan jika sudah double
else if (hama['nilai_pakar']
is double) {
nilaiPakar = hama['nilai_pakar'];
}
// Jika int, konversi ke double
else if (hama['nilai_pakar'] is int) {
nilaiPakar =
hama['nilai_pakar'].toDouble();
}
}
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -296,6 +206,7 @@ class _HamaPageState extends State<HamaPage> {
penangananAwal: penangananAwal:
hama['penanganan'] ?? '', hama['penanganan'] ?? '',
gambarUrl: hama['foto'] ?? '', gambarUrl: hama['foto'] ?? '',
nilai_pakar: nilaiPakar,
onHamaUpdated: onHamaUpdated:
_fetchHama, // fungsi untuk refresh list setelah update _fetchHama, // fungsi untuk refresh list setelah update
), ),

View File

@ -162,6 +162,38 @@ class _PenyakitPageState extends State<PenyakitPage> {
color: Color(0xFF9DC08D), color: Color(0xFF9DC08D),
), ),
onPressed: () { onPressed: () {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (penyakit['nilai_pakar'] != null) {
// Coba parse jika string
if (penyakit['nilai_pakar'] is String) {
try {
String nilaiStr =
penyakit['nilai_pakar']
.toString()
.trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(
nilaiStr.replaceAll(',', '.'),
);
}
} catch (e) {
print(
"Error parsing nilai_pakar: $e",
);
}
}
// Langsung gunakan jika sudah double
else if (penyakit['nilai_pakar']
is double) {
nilaiPakar = penyakit['nilai_pakar'];
}
// Jika int, konversi ke double
else if (penyakit['nilai_pakar'] is int) {
nilaiPakar =
penyakit['nilai_pakar'].toDouble();
}
}
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -176,6 +208,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
penyakit['penanganan'] ?? '', penyakit['penanganan'] ?? '',
gambarUrl: gambarUrl:
penyakit['foto'] ?? '', penyakit['foto'] ?? '',
nilai_pakar: nilaiPakar,
onPenyakitUpdated: onPenyakitUpdated:
_fetchPenyakit, // fungsi untuk refresh list setelah update _fetchPenyakit, // fungsi untuk refresh list setelah update
), ),

View File

@ -176,141 +176,176 @@ class _RulePageState extends State<RulePage> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
children: [ children: [
Align( Row(
alignment: Alignment.centerRight, mainAxisAlignment: MainAxisAlignment.end,
child: ElevatedButton.icon( children: [
onPressed: () { // Button untuk tambah rule hama
Navigator.push( ElevatedButton.icon(
context, onPressed: () {
MaterialPageRoute( Navigator.push(
builder: context,
(context) => TambahRulePage( MaterialPageRoute(
isEditing: false, // Menandakan mode tambah builder: (context) => TambahRulePage(
isEditingHama: isEditing: false,
true, // Atur sesuai dengan jenis rule isEditingHama: true, // Menandakan ini adalah rule hama
selectedRuleIds: [], selectedRuleIds: [],
selectedGejalaIds: [], selectedGejalaIds: [],
nilaiPakarList: [], nilaiPakarList: [],
selectedHamaId: null, // Hanya jika rule hama selectedHamaId: null,
selectedPenyakitId: selectedPenyakitId: null,
null, // Hanya jika rule penyakit showHamaOnly: true, // Parameter baru untuk menampilkan hanya dropdown hama
), ),
), ),
).then((_) => fetchRules()); ).then((_) => fetchRules());
}, },
icon: Icon(Icons.add), icon: Icon(Icons.bug_report),
label: Text("Tambah Rule"), label: Text("Tambah Rule Hama"),
), style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
SizedBox(width: 10),
// Button untuk tambah rule penyakit
ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TambahRulePage(
isEditing: false,
isEditingHama: false, // Menandakan ini adalah rule penyakit
selectedRuleIds: [],
selectedGejalaIds: [],
nilaiPakarList: [],
selectedHamaId: null,
selectedPenyakitId: null,
showPenyakitOnly: true, // Parameter baru untuk menampilkan hanya dropdown penyakit
),
),
).then((_) => fetchRules());
},
icon: Icon(Icons.healing),
label: Text("Tambah Rule Penyakit"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
isLoading isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Expanded( : Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: DataTable( child: DataTable(
columns: const [ columns: const [
DataColumn(label: Text('No')), DataColumn(label: Text('No')),
DataColumn(label: Text('Hama / Penyakit')), DataColumn(label: Text('Hama / Penyakit')),
DataColumn(label: Text('Gejala')), DataColumn(label: Text('Gejala')),
DataColumn(label: Text('nilai pakar')), DataColumn(label: Text('nilai pakar')),
DataColumn(label: Text('Aksi')), DataColumn(label: Text('Aksi')),
], ],
rows: List.generate(rules.length, (index) { rows: List.generate(rules.length, (index) {
final rule = rules[index]; final rule = rules[index];
final namaKategori = final namaKategori =
rule['id_penyakit'] != null rule['id_penyakit'] != null
? rule['nama_penyakit'] ?? '-' ? rule['nama_penyakit'] ?? '-'
: rule['nama_hama'] ?? '-'; : rule['nama_hama'] ?? '-';
final isPenyakit = rule['id_penyakit'] != null; final isPenyakit = rule['id_penyakit'] != null;
return DataRow( return DataRow(
cells: [ cells: [
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
DataCell(Text(namaKategori)), DataCell(Text(namaKategori)),
DataCell(Text(rule['nama_gejala'] ?? '-')), DataCell(Text(rule['nama_gejala'] ?? '-')),
DataCell( DataCell(
Text(rule['nilai_pakar']?.toString() ?? '-'), Text(rule['nilai_pakar']?.toString() ?? '-'),
),
DataCell(
Row(
children: [
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.orange,
),
onPressed: () {
if (rule != null &&
rule['id'] != null &&
rule['id_gejala'] != null &&
rule['nilai_pakar'] != null) {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => EditRulePage(
isEditing: true,
isEditingHama: true,
selectedRuleIds: [
rule['id'] as int,
],
selectedGejalaIds: [
rule['id_gejala'] as int,
],
nilaiPakarList: [
(rule['nilai_pakar'] as num)
.toDouble(),
],
selectedHamaId:
rule['id_hama']
as int?,
selectedPenyakitId: rule['id_penyakit'] as int?, // Tambahkan type cast ke int?
),
),
);
} else {
// Tampilkan pesan error jika data rule tidak lengkap
ScaffoldMessenger.of(
context,
).showSnackBar(
SnackBar(
content: Text(
"Data rule tidak lengkap atau tidak valid",
),
backgroundColor: Colors.red,
),
);
// Debug info
print("Rule data: $rule");
}
},
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
deleteRule(rule);
},
),
],
), ),
), DataCell(
], Row(
); children: [
}), IconButton(
icon: const Icon(
Icons.edit,
color: Colors.orange,
),
onPressed: () {
if (rule != null &&
rule['id'] != null &&
rule['id_gejala'] != null &&
rule['nilai_pakar'] != null) {
// Tentukan jenis rule untuk editing
final bool editingHama = rule['id_hama'] != null;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditRulePage(
isEditing: true,
isEditingHama: editingHama,
selectedRuleIds: [
rule['id'] as int,
],
selectedGejalaIds: [
rule['id_gejala'] as int,
],
nilaiPakarList: [
(rule['nilai_pakar'] as num)
.toDouble(),
],
selectedHamaId:
rule['id_hama'] as int?,
selectedPenyakitId: rule['id_penyakit'] as int?,
// Tambahkan parameter untuk menentukan dropdown yang ditampilkan
showHamaOnly: editingHama,
showPenyakitOnly: !editingHama,
),
),
).then((_) => fetchRules());
} else {
// Tampilkan pesan error jika data rule tidak lengkap
ScaffoldMessenger.of(
context,
).showSnackBar(
SnackBar(
content: Text(
"Data rule tidak lengkap atau tidak valid",
),
backgroundColor: Colors.red,
),
);
// Debug info
print("Rule data: $rule");
}
},
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
deleteRule(rule);
},
),
],
),
),
],
);
}),
),
), ),
), ),
),
], ],
), ),
), ),
); );
} }
} }

View File

@ -19,6 +19,7 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
final TextEditingController namaController = TextEditingController(); final TextEditingController namaController = TextEditingController();
final TextEditingController deskripsiController = TextEditingController(); final TextEditingController deskripsiController = TextEditingController();
final TextEditingController penangananController = TextEditingController(); final TextEditingController penangananController = TextEditingController();
final TextEditingController nilaiPakarController = TextEditingController();
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
File? _imageFile; File? _imageFile;
@ -32,19 +33,24 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
namaController.dispose(); namaController.dispose();
deskripsiController.dispose(); deskripsiController.dispose();
penangananController.dispose(); penangananController.dispose();
nilaiPakarController.dispose();
super.dispose(); super.dispose();
} }
Future<void> _simpanHama() async { Future<void> _simpanHama() async {
if (namaController.text.isNotEmpty && if (namaController.text.isNotEmpty &&
deskripsiController.text.isNotEmpty && deskripsiController.text.isNotEmpty &&
penangananController.text.isNotEmpty) { penangananController.text.isNotEmpty &&
nilaiPakarController.text.isNotEmpty) {
try { try {
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
double nilaiPakar = double.parse(nilaiInput);
await apiService.createHama( await apiService.createHama(
namaController.text, namaController.text,
deskripsiController.text, deskripsiController.text,
penangananController.text, penangananController.text,
_pickedFile, _pickedFile,
nilaiPakar,
); );
widget.onHamaAdded(); widget.onHamaAdded();
Navigator.pop(context); Navigator.pop(context);
@ -136,6 +142,12 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
maxLines: 3, maxLines: 3,
), ),
SizedBox(height: 15), SizedBox(height: 15),
TextField(
controller: nilaiPakarController,
decoration: InputDecoration(labelText: 'Nilai Pakar'),
maxLines: 3,
),
SizedBox(height: 15),
Text('Foto'), Text('Foto'),
(_webImage != null) (_webImage != null)
? Image.memory( ? Image.memory(

View File

@ -18,6 +18,7 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
final TextEditingController namaController = TextEditingController(); final TextEditingController namaController = TextEditingController();
final TextEditingController deskripsiController = TextEditingController(); final TextEditingController deskripsiController = TextEditingController();
final TextEditingController penangananController = TextEditingController(); final TextEditingController penangananController = TextEditingController();
final TextEditingController nilaiPakarController = TextEditingController();
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
File? _imageFile; File? _imageFile;
@ -31,19 +32,24 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
namaController.dispose(); namaController.dispose();
deskripsiController.dispose(); deskripsiController.dispose();
penangananController.dispose(); penangananController.dispose();
nilaiPakarController.dispose();
super.dispose(); super.dispose();
} }
Future<void> _simpanPenyakit() async { Future<void> _simpanPenyakit() async {
if (namaController.text.isNotEmpty && if (namaController.text.isNotEmpty &&
deskripsiController.text.isNotEmpty && deskripsiController.text.isNotEmpty &&
penangananController.text.isNotEmpty) { penangananController.text.isNotEmpty &&
nilaiPakarController.text.isNotEmpty) {
try { try {
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
double nilaiPakar = double.parse(nilaiInput);
await apiService.createPenyakit( await apiService.createPenyakit(
namaController.text, namaController.text,
deskripsiController.text, deskripsiController.text,
penangananController.text, penangananController.text,
_pickedFile, _pickedFile,
nilaiPakar,
); );
widget.onPenyakitAdded(); widget.onPenyakitAdded();
Navigator.pop(context); Navigator.pop(context);
@ -135,6 +141,12 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
maxLines: 3, maxLines: 3,
), ),
SizedBox(height: 15), SizedBox(height: 15),
TextField(
controller: nilaiPakarController,
decoration: InputDecoration(labelText: 'nilai pakar'),
maxLines: 3,
),
SizedBox(height: 15),
(_webImage != null) (_webImage != null)
? Image.memory( ? Image.memory(
_webImage!, _webImage!,

View File

@ -13,6 +13,8 @@ class TambahRulePage extends StatefulWidget {
final List<double> nilaiPakarList; final List<double> nilaiPakarList;
final int? selectedHamaId; final int? selectedHamaId;
final int? selectedPenyakitId; final int? selectedPenyakitId;
final bool showHamaOnly; // Tambahkan ini
final bool showPenyakitOnly;
const TambahRulePage({ const TambahRulePage({
Key? key, Key? key,
@ -23,6 +25,8 @@ class TambahRulePage extends StatefulWidget {
required this.nilaiPakarList, required this.nilaiPakarList,
this.selectedHamaId, this.selectedHamaId,
this.selectedPenyakitId, this.selectedPenyakitId,
this.showHamaOnly = false, // Tambahkan default value
this.showPenyakitOnly = false,
}) : super(key: key); }) : super(key: key);
} }
@ -31,12 +35,16 @@ class _TambahRulePageState extends State<TambahRulePage> {
int? selectedPenyakitId; int? selectedPenyakitId;
List<int?> selectedGejalaIds = [null]; List<int?> selectedGejalaIds = [null];
List<double> nilaiPakarList = [0.5]; List<double> nilaiPakarList = [0.5];
List<int?> selectedRuleIds = []; // List paralel dengan selectedGejalaIds dan nilaiPakarList List<int?> selectedRuleIds =
[]; // List paralel dengan selectedGejalaIds dan nilaiPakarList
bool isEditing = true; // atau false jika sedang edit penyakit bool isEditing = true; // atau false jika sedang edit penyakit
bool isLoading = true; bool isLoading = true;
bool showHamaOnly = false;
bool showPenyakitOnly = false;
final api = ApiService(); final api = ApiService();
// Deklarasi variabel untuk menampung data dari API // Deklarasi variabel untuk menampung data dari API
@ -119,6 +127,8 @@ class _TambahRulePageState extends State<TambahRulePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
showHamaOnly = widget.showHamaOnly;
showPenyakitOnly = widget.showPenyakitOnly;
fetchData(); // Panggil fetchData saat halaman dibuka pertama kali fetchData(); // Panggil fetchData saat halaman dibuka pertama kali
} }
@ -173,57 +183,6 @@ class _TambahRulePageState extends State<TambahRulePage> {
} }
} }
// void updateRules() async {
// if (selectedPenyakitId == null && selectedHamaId == null) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text("Pilih minimal satu: Penyakit atau Hama")),
// );
// return;
// }
// try {
// for (int i = 0; i < selectedGejalaIds.length; i++) {
// final idRule = selectedRuleIds[i];
// final idGejala = selectedGejalaIds[i];
// final nilai = nilaiPakarList[i];
// if (idRule != null && idGejala != null) {
// http.Response response;
// if (selectedPenyakitId != null) {
// response = await ApiService.updateRulePenyakit(
// id: idRule,
// idGejala: idGejala,
// idPenyakit: selectedPenyakitId,
// nilaiPakar: nilai,
// );
// } else {
// response = await ApiService.updateRuleHama(
// id: idRule,
// idGejala: idGejala,
// idHama: selectedHamaId,
// nilaiPakar: nilai,
// );
// }
// if (response.statusCode != 200 && response.statusCode != 201) {
// throw Exception("Gagal mengupdate rule");
// }
// }
// }
// ScaffoldMessenger.of(
// context,
// ).showSnackBar(SnackBar(content: Text("Data berhasil diperbarui")));
// Navigator.pop(context);
// } catch (e) {
// print('Gagal memperbarui data: $e');
// ScaffoldMessenger.of(
// context,
// ).showSnackBar(SnackBar(content: Text("Gagal memperbarui data")));
// }
// }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -243,62 +202,68 @@ class _TambahRulePageState extends State<TambahRulePage> {
child: ListView( child: ListView(
children: [ children: [
// Pilih Hama // Pilih Hama
Text("Pilih Hama"), if (!showPenyakitOnly) ...[
DropdownButton<int>( Text("Pilih Hama"),
isExpanded: true, DropdownButton<int>(
value: selectedHamaId, isExpanded: true,
hint: Text('Pilih Hama'), value: selectedHamaId,
items: hint: Text('Pilih Hama'),
hamaList.isNotEmpty items:
? hamaList.map<DropdownMenuItem<int>>((hama) { hamaList.isNotEmpty
return DropdownMenuItem<int>( ? hamaList.map<DropdownMenuItem<int>>((
value: hama['id'], hama,
child: Text(hama['nama']), ) {
); return DropdownMenuItem<int>(
}).toList() value: hama['id'],
: [ child: Text(hama['nama']),
DropdownMenuItem<int>( );
value: null, }).toList()
child: Text("Data tidak tersedia"), : [
), DropdownMenuItem<int>(
], value: null,
onChanged: (value) { child: Text("Data tidak tersedia"),
setState(() { ),
selectedHamaId = value; ],
}); onChanged: (value) {
}, setState(() {
), selectedHamaId = value;
SizedBox(height: 16), });
},
),
SizedBox(height: 16),
],
// Pilih Penyakit // Pilih Penyakit
Text("Pilih Penyakit"), if (!showHamaOnly) ...[
DropdownButton<int>( Text("Pilih Penyakit"),
isExpanded: true, DropdownButton<int>(
value: selectedPenyakitId, isExpanded: true,
hint: Text('Pilih Penyakit'), value: selectedPenyakitId,
items: hint: Text('Pilih Penyakit'),
penyakitList.isNotEmpty items:
? penyakitList.map<DropdownMenuItem<int>>(( penyakitList.isNotEmpty
penyakit, ? penyakitList.map<DropdownMenuItem<int>>((
) { penyakit,
return DropdownMenuItem<int>( ) {
value: penyakit['id'], return DropdownMenuItem<int>(
child: Text(penyakit['nama']), value: penyakit['id'],
); child: Text(penyakit['nama']),
}).toList() );
: [ }).toList()
DropdownMenuItem<int>( : [
value: null, DropdownMenuItem<int>(
child: Text("Data tidak tersedia"), value: null,
), child: Text("Data tidak tersedia"),
], ),
onChanged: (value) { ],
setState(() { onChanged: (value) {
selectedPenyakitId = value; setState(() {
}); selectedPenyakitId = value;
}, });
), },
SizedBox(height: 16), ),
SizedBox(height: 16),
],
// Pilih Gejala dan Nilai Pakar // Pilih Gejala dan Nilai Pakar
Text("Pilih Gejala"), Text("Pilih Gejala"),
@ -398,7 +363,33 @@ class _TambahRulePageState extends State<TambahRulePage> {
// Tombol untuk menambah rule // Tombol untuk menambah rule
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
// Panggil fungsi saveRules untuk menyimpan data // Cek duplikasi gejala
final uniqueGejala = selectedGejalaIds.toSet();
if (uniqueGejala.length !=
selectedGejalaIds.length) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Terdapat gejala yang sama, harap pilih gejala yang berbeda.',
),
),
);
return; // Gagal simpan
}
// Cek apakah semua nilai gejala sudah dipilih (tidak null)
if (selectedGejalaIds.contains(null)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Harap lengkapi semua pilihan gejala.',
),
),
);
return;
}
// Panggil fungsi saveRules jika valid
saveRules(); saveRules();
}, },
child: Text('Tambah Rule'), child: Text('Tambah Rule'),

View File

@ -13,6 +13,7 @@ class ApiService {
static const String penyakitUrl = 'http://localhost:5000/api/penyakit'; static const String penyakitUrl = 'http://localhost:5000/api/penyakit';
static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit'; static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit';
static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama'; static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama';
static const String userUrl = 'http://localhost:5000/api/users';
static const Duration timeout = Duration(seconds: 15); static const Duration timeout = Duration(seconds: 15);
// Fungsi Login (dengan perbaikan) // Fungsi Login (dengan perbaikan)
@ -123,7 +124,6 @@ class ApiService {
} }
// Ambil semua hama // Ambil semua hama
// Get all hama
Future<List<Map<String, dynamic>>> getHama() async { Future<List<Map<String, dynamic>>> getHama() async {
try { try {
final response = await http.get(Uri.parse(hamaUrl)).timeout(timeout); final response = await http.get(Uri.parse(hamaUrl)).timeout(timeout);
@ -251,6 +251,7 @@ class ApiService {
String deskripsi, String deskripsi,
String penanganan, String penanganan,
XFile? pickedFile, XFile? pickedFile,
double nilai_pakar
) async { ) async {
try { try {
var uri = Uri.parse(hamaUrl); var uri = Uri.parse(hamaUrl);
@ -259,6 +260,7 @@ class ApiService {
request.fields['nama'] = nama; request.fields['nama'] = nama;
request.fields['deskripsi'] = deskripsi; request.fields['deskripsi'] = deskripsi;
request.fields['penanganan'] = penanganan; request.fields['penanganan'] = penanganan;
request.fields['nilai_pakar'] = nilai_pakar.toString();
print('Mengirim request ke: $uri'); print('Mengirim request ke: $uri');
print('Dengan fields: ${request.fields}'); print('Dengan fields: ${request.fields}');
@ -330,6 +332,7 @@ class ApiService {
String deskripsi, String deskripsi,
String penanganan, String penanganan,
XFile? pickedFile, XFile? pickedFile,
double nilai_pakar
) async { ) async {
try { try {
var uri = Uri.parse('$hamaUrl/$id'); var uri = Uri.parse('$hamaUrl/$id');
@ -339,6 +342,7 @@ class ApiService {
request.fields['nama'] = nama; request.fields['nama'] = nama;
request.fields['deskripsi'] = deskripsi; request.fields['deskripsi'] = deskripsi;
request.fields['penanganan'] = penanganan; request.fields['penanganan'] = penanganan;
request.fields['nilai_pakar'] = nilai_pakar.toString();
// Log untuk debugging // Log untuk debugging
print('Mengirim request ke: $uri'); print('Mengirim request ke: $uri');
@ -524,12 +528,33 @@ class ApiService {
} }
} }
Future<Uint8List?> getPenyakitImageBytesByFilename(String filename) async {
try {
final url = Uri.parse('http://localhost:5000/image_penyakit/$filename');
print('Fetching image from: $url');
final response = await http.get(url);
if (response.statusCode == 200) {
return response.bodyBytes;
} else {
print('Failed to fetch image. Status: ${response.statusCode}');
print('Response body: ${response.body}');
return null;
}
} catch (e) {
print('Error fetching image by filename: $e');
return null;
}
}
// Tambah penyakit baru (kode otomatis) // Tambah penyakit baru (kode otomatis)
Future<Map<String, dynamic>> createPenyakit( Future<Map<String, dynamic>> createPenyakit(
String nama, String nama,
String deskripsi, String deskripsi,
String penanganan, String penanganan,
XFile? pickedFile, XFile? pickedFile,
double nilai_pakar
) async { ) async {
try { try {
var uri = Uri.parse(penyakitUrl); var uri = Uri.parse(penyakitUrl);
@ -538,6 +563,7 @@ class ApiService {
request.fields['nama'] = nama; request.fields['nama'] = nama;
request.fields['deskripsi'] = deskripsi; request.fields['deskripsi'] = deskripsi;
request.fields['penanganan'] = penanganan; request.fields['penanganan'] = penanganan;
request.fields['nilai_pakar'] = nilai_pakar.toString();
print('Mengirim request ke: $uri'); print('Mengirim request ke: $uri');
print('Dengan fields: ${request.fields}'); print('Dengan fields: ${request.fields}');
@ -609,6 +635,7 @@ class ApiService {
String deskripsi, String deskripsi,
String penanganan, String penanganan,
XFile? pickedFile, XFile? pickedFile,
double nilai_pakar
) async { ) async {
try { try {
var uri = Uri.parse('$penyakitUrl/$id'); var uri = Uri.parse('$penyakitUrl/$id');
@ -618,6 +645,7 @@ class ApiService {
request.fields['nama'] = nama; request.fields['nama'] = nama;
request.fields['deskripsi'] = deskripsi; request.fields['deskripsi'] = deskripsi;
request.fields['penanganan'] = penanganan; request.fields['penanganan'] = penanganan;
request.fields['nilai_pakar'] = nilai_pakar.toString();
// Log untuk debugging // Log untuk debugging
print('Mengirim request ke: $uri'); print('Mengirim request ke: $uri');
@ -889,4 +917,39 @@ class ApiService {
final response = await http.delete(Uri.parse('$rulesHamaUrl/$id')); final response = await http.delete(Uri.parse('$rulesHamaUrl/$id'));
return response; return response;
} }
//get users
Future<List<Map<String, dynamic>>> getUsers({String? role}) async {
try {
String url = ApiService.userUrl;
if (role != null) {
url += '?role=$role';
}
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final List<dynamic> responseData = jsonDecode(response.body);
// Filter berdasarkan role jika perlu
if (role != null) {
// Mengambil user dengan role yang sesuai
final filteredData = responseData.where((user) => user['role'] == role).toList();
return List<Map<String, dynamic>>.from(filteredData);
}
// Jika tidak ada filter role, kembalikan semua data
return List<Map<String, dynamic>>.from(responseData);
} else {
throw Exception("Gagal mengambil data user: ${response.statusCode}");
}
} catch (e) {
print("Error getUsers: $e");
throw Exception("Gagal mengambil data user");
}
} }
}

View File

@ -57,23 +57,40 @@ class _DetailHamaPageState extends State<DetailHamaPage> {
// Widget untuk menampilkan gambar dengan penanganan error yang lebih baik // Widget untuk menampilkan gambar dengan penanganan error yang lebih baik
Widget _buildImageWidget(String? filename) { Widget _buildImageWidget(String? filename) {
if (filename == null || filename.isEmpty) { if (filename == null || filename.isEmpty) {
return Text("Tidak ada gambar tersedia"); return _buildPlaceholderImage(
"Tidak ada gambar tersedia",
Icons.image_not_supported,
);
} }
return FutureBuilder<Uint8List?>( return FutureBuilder<Uint8List?>(
future: ApiService().getHamaImageBytesByFilename(filename), future: ApiService().getHamaImageBytesByFilename(filename),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); return SizedBox(
height: 200,
width: double.infinity,
child: Center(child: CircularProgressIndicator()),
);
} else if (snapshot.hasError || snapshot.data == null) { } else if (snapshot.hasError || snapshot.data == null) {
return Text("Gagal memuat gambar"); return _buildPlaceholderImage(
"Gagal memuat gambar",
Icons.broken_image,
);
} else { } else {
return Image.memory(snapshot.data!, fit: BoxFit.cover); return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.memory(
snapshot.data!,
height: 200,
width: double.infinity,
fit: BoxFit.contain, // untuk memastikan proporsional & penuh
),
);
} }
}, },
); );
} }
// Widget untuk placeholder gambar // Widget untuk placeholder gambar
Widget _buildPlaceholderImage(String message, IconData icon) { Widget _buildPlaceholderImage(String message, IconData icon) {

View File

@ -1,9 +1,122 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:frontend/api_services/api_services.dart';
import 'dart:typed_data';
class DetailPenyakitPage extends StatelessWidget { class DetailPenyakitPage extends StatefulWidget {
final Map<String, dynamic> detailPenyakit; final Map<String, dynamic> DetailPenyakit;
final int? penyakitId;
const DetailPenyakitPage({required this.detailPenyakit}); const DetailPenyakitPage({Key? key, required this.DetailPenyakit, this.penyakitId})
: super(key: key);
@override
_DetailPenyakitPageState createState() => _DetailPenyakitPageState();
}
class _DetailPenyakitPageState extends State<DetailPenyakitPage> {
late Future<Map<String, dynamic>> _detailPenyakitFuture;
late Map<String, dynamic> _currentDetailPenyakit;
@override
void initState() {
super.initState();
_currentDetailPenyakit = widget.DetailPenyakit;
// Jika hamaId tersedia, fetch data terbaru dari API
if (widget.penyakitId != null) {
_detailPenyakitFuture = _fetchDetailPenyakit(widget.penyakitId!);
} else {
// Jika tidak ada ID, gunakan data yang sudah diberikan
_detailPenyakitFuture = Future.value(widget.DetailPenyakit);
}
}
Future<Map<String, dynamic>> _fetchDetailPenyakit(int id) async {
try {
final detailData = await ApiService().getPenyakitById(id);
setState(() {
_currentDetailPenyakit = detailData;
});
return detailData;
} catch (e) {
print('Error fetching detail penyakit: $e');
// Jika gagal fetch, gunakan data yang sudah ada
return widget.DetailPenyakit;
}
}
// Fungsi untuk memvalidasi URL gambar
bool _isValidImageUrl(String? url) {
if (url == null || url.isEmpty) return false;
// Periksa apakah URL berakhir dengan ekstensi gambar yang umum
final validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
return validExtensions.any((ext) => url.toLowerCase().endsWith(ext));
}
// Widget untuk menampilkan gambar dengan penanganan error yang lebih baik
Widget _buildImageWidget(String? filename) {
if (filename == null || filename.isEmpty) {
return _buildPlaceholderImage(
"Tidak ada gambar tersedia",
Icons.image_not_supported,
);
}
return FutureBuilder<Uint8List?>(
future: ApiService().getPenyakitImageBytesByFilename(filename),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return SizedBox(
height: 200,
width: double.infinity,
child: Center(child: CircularProgressIndicator()),
);
} else if (snapshot.hasError || snapshot.data == null) {
return _buildPlaceholderImage(
"Gagal memuat gambar",
Icons.broken_image,
);
} else {
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.memory(
snapshot.data!,
height: 200,
width: double.infinity,
fit: BoxFit.contain, // untuk memastikan proporsional & penuh
),
);
}
},
);
}
// Widget untuk placeholder gambar
Widget _buildPlaceholderImage(String message, IconData icon) {
return Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 64, color: Colors.grey[600]),
SizedBox(height: 8),
Text(
message,
style: TextStyle(
color: Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -11,96 +124,123 @@ class DetailPenyakitPage extends StatelessWidget {
backgroundColor: Color(0xFF9DC08D), backgroundColor: Color(0xFF9DC08D),
appBar: AppBar( appBar: AppBar(
backgroundColor: Color(0xFF9DC08D), backgroundColor: Color(0xFF9DC08D),
title: Text( title: Text("Detail Penyakit", style: TextStyle(color: Colors.white)),
"Detail Penyakit",
style: TextStyle(color: Colors.white),
),
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white), icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
), ),
body: SingleChildScrollView( body: FutureBuilder<Map<String, dynamic>>(
child: Padding( future: _detailPenyakitFuture,
padding: const EdgeInsets.all(16.0), builder: (context, snapshot) {
child: Column( if (snapshot.connectionState == ConnectionState.waiting) {
children: [ return Center(
if (detailPenyakit["gambar"] != null && detailPenyakit["gambar"]!.isNotEmpty) child: CircularProgressIndicator(color: Colors.white),
ClipRRect( );
}
if (snapshot.hasError) {
print('Error: ${snapshot.error}');
// Tampilkan data yang sudah ada jika terjadi error
return _buildDetailContent(_currentDetailPenyakit);
}
print("Snapshot data runtimeType: ${snapshot.data.runtimeType}");
print("Snapshot data content: ${snapshot.data}");
// Jika berhasil fetch data baru, tampilkan data tersebut
final detailData = snapshot.data ?? _currentDetailPenyakit;
return _buildDetailContent(detailData);
},
),
);
}
Widget _buildDetailContent(Map<String, dynamic> detailData) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Tampilkan foto dari database dengan penanganan error yang lebih baik
_buildImageWidget(detailData["foto"]),
SizedBox(height: 16),
// Card Nama Hama
SizedBox(
width: double.infinity,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: Image.asset(
detailPenyakit["gambar"]!,
height: 200,
width: 200, // Biar gambar full lebar
fit: BoxFit.cover,
),
), ),
SizedBox(height: 16), child: Padding(
padding: const EdgeInsets.all(16.0),
// Card Nama Penyakit child: Column(
SizedBox( crossAxisAlignment: CrossAxisAlignment.start,
width: double.infinity, // Bikin card full lebar children: [
child: Card( Text(
elevation: 6, "Nama Penyakit:",
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), style: TextStyle(
child: Padding( fontSize: 18,
padding: const EdgeInsets.all(16.0), fontWeight: FontWeight.bold,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Nama Penyakit:",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 8), ),
Text( SizedBox(height: 8),
detailPenyakit["nama"] ?? "Nama penyakit tidak tersedia", Text(
style: TextStyle(fontSize: 16), detailData["nama"] ?? "Nama hama tidak tersedia",
), style: TextStyle(fontSize: 16),
], ),
), ],
), ),
), ),
), ),
SizedBox(height: 16), ),
SizedBox(height: 16),
// Card Deskripsi + Penanganan // Card Deskripsi + Penanganan
SizedBox( SizedBox(
width: double.infinity, // Bikin card full lebar width: double.infinity,
child: Card( child: Card(
elevation: 6, elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(
child: Padding( borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.all(16.0), ),
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16.0),
children: [ child: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
"Deskripsi:", children: [
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), Text(
"Deskripsi:",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
), ),
SizedBox(height: 8), ),
Text( SizedBox(height: 8),
detailPenyakit["deskripsi"] ?? "Deskripsi tidak tersedia", Text(
style: TextStyle(fontSize: 16), detailData["deskripsi"] ?? "Deskripsi tidak tersedia",
style: TextStyle(fontSize: 16),
),
SizedBox(height: 16),
Text(
"Penanganan:",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
), ),
SizedBox(height: 16), ),
Text( SizedBox(height: 8),
"Penanganan:", Text(
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), detailData["penanganan"] ?? "Penanganan tidak tersedia",
), style: TextStyle(fontSize: 16),
SizedBox(height: 8), ),
Text( ],
detailPenyakit["penanganan"] ?? "Penanganan tidak tersedia",
style: TextStyle(fontSize: 16),
),
],
),
), ),
), ),
), ),
], ),
), ],
), ),
), ),
); );

View File

@ -68,7 +68,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DetailPenyakitPage(detailPenyakit: penyakit), builder: (context) => DetailPenyakitPage(DetailPenyakit: penyakit),
), ),
); );
}, },