penambahan fungsi implementasi data
This commit is contained in:
parent
90e496d495
commit
a61ffed9cd
|
@ -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' });
|
||||
}
|
||||
};
|
|
@ -4,7 +4,7 @@ const {Gejala} = require('../models');
|
|||
exports.getAllGejala = async (req, res) => {
|
||||
try {
|
||||
const gejala = await Gejala.findAll({
|
||||
attributes: ['id', 'nama']
|
||||
attributes: ['id', 'nama', 'kode']
|
||||
});
|
||||
res.status(200).json(gejala);
|
||||
} catch (error) {
|
||||
|
|
|
@ -6,7 +6,7 @@ const fs = require('fs');
|
|||
exports.getAllHama = async (req, res) => {
|
||||
try {
|
||||
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 });
|
||||
} catch (error) {
|
||||
|
@ -51,7 +51,7 @@ exports.getHamaById = async (req, res) => {
|
|||
// Pastikan sudah import 'Hama' model dan multer middleware sebelumnya
|
||||
exports.createHama = async (req, res) => {
|
||||
try {
|
||||
const { nama, deskripsi, penanganan } = req.body;
|
||||
const { nama, deskripsi, penanganan, nilai_pakar } = req.body;
|
||||
const file = req.file;
|
||||
|
||||
// Cek kode terakhir
|
||||
|
@ -71,10 +71,11 @@ exports.createHama = async (req, res) => {
|
|||
const newHama = await Hama.create({
|
||||
kode: newKode,
|
||||
nama,
|
||||
kategori: 'hama', // Default kategori
|
||||
kategori: 'hama',
|
||||
deskripsi,
|
||||
penanganan,
|
||||
foto: fotoPath, // ⬅️ Masukkan nama file ke database
|
||||
foto: fotoPath,
|
||||
nilai_pakar
|
||||
});
|
||||
|
||||
res.status(201).json({ message: 'Hama berhasil ditambahkan', data: newHama });
|
||||
|
@ -88,7 +89,7 @@ exports.createHama = async (req, res) => {
|
|||
exports.updateHama = async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
if (!hama) {
|
||||
|
@ -101,7 +102,7 @@ exports.updateHama = async (req, res) => {
|
|||
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 });
|
||||
} catch (error) {
|
||||
|
|
|
@ -6,7 +6,7 @@ const fs = require('fs');
|
|||
exports.getAllPenyakit = async (req, res) => {
|
||||
try {
|
||||
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 });
|
||||
} catch (error) {
|
||||
|
@ -50,7 +50,7 @@ exports.getPenyakitById = async (req, res) => {
|
|||
// 🔹 Fungsi untuk menambahkan penyakit baru (kode otomatis & kategori default)
|
||||
exports.createPenyakit = async (req, res) => {
|
||||
try {
|
||||
const { nama, deskripsi, penanganan } = req.body;
|
||||
const { nama, deskripsi, penanganan, nilai_pakar } = req.body;
|
||||
const file = req.file;
|
||||
|
||||
// Cek kode terakhir
|
||||
|
@ -74,6 +74,7 @@ exports.createPenyakit = async (req, res) => {
|
|||
deskripsi,
|
||||
penanganan,
|
||||
foto: fotoPath,
|
||||
nilai_pakar
|
||||
});
|
||||
|
||||
res.status(201).json({ message: 'Penyakit berhasil ditambahkan', data: newPenyakit });
|
||||
|
@ -86,7 +87,7 @@ exports.createPenyakit = async (req, res) => {
|
|||
exports.updatePenyakit = async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
if (!penyakit) {
|
||||
|
@ -99,7 +100,7 @@ exports.updatePenyakit = async (req, res) => {
|
|||
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 });
|
||||
} catch (error) {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
|
@ -1,4 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const { sequelize } = require('../models');
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
|
@ -24,6 +27,10 @@ module.exports = {
|
|||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
nilai_pakar: {
|
||||
type: Sequelize.FLOAT,
|
||||
allowNull: true
|
||||
},
|
||||
});
|
||||
},
|
||||
async down(queryInterface, Sequelize) {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
|
@ -31,6 +31,10 @@ module.exports = (sequelize) => {
|
|||
foto: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
nilai_pakar: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -31,6 +31,10 @@ module.exports =(sequelize) => {
|
|||
foto: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
nilai_pakar: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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;
|
|
@ -68,6 +68,9 @@ router.get('/:id/image', hamaController.getHamaById);
|
|||
* type: string
|
||||
* format: binary
|
||||
* description: Foto hama (JPG, JPEG, PNG, GIF)
|
||||
* nilai_pakar:
|
||||
* type: number
|
||||
* format: float
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Hama berhasil ditambahkan
|
||||
|
@ -105,6 +108,9 @@ router.post('/', uploadHamaGambar.single('foto'), hamaController.createHama);
|
|||
* foto:
|
||||
* type: string
|
||||
* format: binary
|
||||
* nilai_pakar:
|
||||
* type: number
|
||||
* format: float
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Hama berhasil diperbarui
|
||||
|
|
|
@ -69,6 +69,9 @@ router.get('/:id/image', penyakitController.getPenyakitById);
|
|||
* type: string
|
||||
* format: binary
|
||||
* description: Foto penyakit (JPG, JPEG, PNG, GIF)
|
||||
* nilai_pakar:
|
||||
* type: number
|
||||
* format: float
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Hama berhasil ditambahkan
|
||||
|
@ -106,6 +109,9 @@ router.post('/', uploadPenyakitGambar.single('foto'), penyakitController.createP
|
|||
* foto:
|
||||
* type: string
|
||||
* format: binary
|
||||
* nilai_pakar:
|
||||
* type: number
|
||||
* format: float
|
||||
* responses:
|
||||
* 200:
|
||||
* description: penyakit berhasil diperbarui
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
import 'hama_page.dart';
|
||||
import 'penyakit_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/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 {
|
||||
await ApiService.logoutUser();
|
||||
Navigator.pushReplacement(
|
||||
|
@ -18,7 +77,10 @@ class AdminPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Admin Dashboard')),
|
||||
appBar: AppBar(
|
||||
title: Text('Admin Dashboard'),
|
||||
backgroundColor: Color(0xFF9DC08D),
|
||||
),
|
||||
drawer: Drawer(
|
||||
child: Container(
|
||||
color: Color(0xFFFFFFFF),
|
||||
|
@ -88,21 +150,41 @@ class AdminPage extends StatelessWidget {
|
|||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Column(
|
||||
isLoading
|
||||
? Center(
|
||||
child: CircularProgressIndicator(color: Color(0xFF9DC08D)),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildCard('Jumlah User', '10'),
|
||||
_buildCard('Jumlah Diagnosa', '25'),
|
||||
_buildCard(
|
||||
'Jumlah User',
|
||||
userCount.toString(),
|
||||
Icons.people,
|
||||
),
|
||||
_buildCard(
|
||||
'Jumlah Diagnosa',
|
||||
diagnosisCount.toString(),
|
||||
Icons.assignment,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16), // Spasi antar baris
|
||||
SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildCard('Penyakit', '15'),
|
||||
_buildCard('Hama', '15'),
|
||||
_buildCard(
|
||||
'Penyakit',
|
||||
diseaseCount.toString(),
|
||||
Icons.sick,
|
||||
),
|
||||
_buildCard(
|
||||
'Hama',
|
||||
pestCount.toString(),
|
||||
Icons.bug_report,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -114,7 +196,7 @@ class AdminPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(String title, String count) {
|
||||
Widget _buildCard(String title, String count, IconData icon) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
|
@ -125,13 +207,22 @@ class AdminPage extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 40, color: Color(0xFF9DC08D)),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(count, style: TextStyle(fontSize: 20, color: Colors.green)),
|
||||
Text(
|
||||
count,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Color(0xFF9DC08D),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.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:typed_data';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
@ -11,6 +11,7 @@ class EditHamaPage extends StatefulWidget {
|
|||
final String namaAwal;
|
||||
final String deskripsiAwal;
|
||||
final String penangananAwal;
|
||||
final double nilai_pakar;
|
||||
final String gambarUrl;
|
||||
final VoidCallback onHamaUpdated;
|
||||
|
||||
|
@ -20,6 +21,7 @@ class EditHamaPage extends StatefulWidget {
|
|||
required this.namaAwal,
|
||||
required this.deskripsiAwal,
|
||||
required this.penangananAwal,
|
||||
required this.nilai_pakar,
|
||||
required this.gambarUrl,
|
||||
required this.onHamaUpdated,
|
||||
}) : super(key: key);
|
||||
|
@ -32,6 +34,7 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
final TextEditingController _namaController = TextEditingController();
|
||||
final TextEditingController _deskripsiController = TextEditingController();
|
||||
final TextEditingController _penangananController = TextEditingController();
|
||||
final TextEditingController _nilaiPakarController = TextEditingController();
|
||||
final ApiService apiService = ApiService();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
|
@ -41,6 +44,8 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
String? _errorMessage;
|
||||
bool _isImageLoading = false;
|
||||
Uint8List? _currentImageBytes;
|
||||
// Default value for nilai_pakar to prevent empty string issues
|
||||
double _currentNilaiPakar = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -49,6 +54,10 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
_deskripsiController.text = widget.deskripsiAwal;
|
||||
_penangananController.text = widget.penangananAwal;
|
||||
|
||||
// Ensure nilai_pakar is properly initialized
|
||||
_currentNilaiPakar = widget.nilai_pakar;
|
||||
_nilaiPakarController.text = widget.nilai_pakar.toString();
|
||||
|
||||
// Load existing image
|
||||
_loadExistingImage();
|
||||
}
|
||||
|
@ -89,9 +98,25 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
_namaController.dispose();
|
||||
_deskripsiController.dispose();
|
||||
_penangananController.dispose();
|
||||
_nilaiPakarController.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 {
|
||||
try {
|
||||
setState(() {
|
||||
|
@ -99,12 +124,18 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
_errorMessage = null;
|
||||
});
|
||||
|
||||
// Get nilai_pakar value with safety check
|
||||
double nilaiPakar = _parseNilaiPakar();
|
||||
|
||||
print("Updating hama with nilai_pakar: $nilaiPakar");
|
||||
|
||||
await apiService.updateHama(
|
||||
widget.idHama,
|
||||
_namaController.text,
|
||||
_deskripsiController.text,
|
||||
_penangananController.text,
|
||||
_pickedFile,
|
||||
nilaiPakar,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
|
@ -257,6 +288,25 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
maxLines: 3,
|
||||
),
|
||||
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(
|
||||
'Foto Hama',
|
||||
style: TextStyle(
|
||||
|
@ -281,7 +331,10 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
if (_isLoading)
|
||||
CircularProgressIndicator()
|
||||
else
|
||||
ElevatedButton(
|
||||
onPressed: _updateHama,
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
@ -292,6 +345,14 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
|||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
if (_errorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -12,6 +12,7 @@ class EditPenyakitPage extends StatefulWidget {
|
|||
final String namaAwal;
|
||||
final String deskripsiAwal;
|
||||
final String penangananAwal;
|
||||
final double nilai_pakar;
|
||||
final String gambarUrl;
|
||||
final VoidCallback onPenyakitUpdated;
|
||||
|
||||
|
@ -21,6 +22,7 @@ class EditPenyakitPage extends StatefulWidget {
|
|||
required this.namaAwal,
|
||||
required this.deskripsiAwal,
|
||||
required this.penangananAwal,
|
||||
required this.nilai_pakar,
|
||||
required this.gambarUrl,
|
||||
required this.onPenyakitUpdated,
|
||||
}) : super(key: key);
|
||||
|
@ -33,6 +35,7 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
final TextEditingController _namaController = TextEditingController();
|
||||
final TextEditingController _deskripsiController = TextEditingController();
|
||||
final TextEditingController _penangananController = TextEditingController();
|
||||
final TextEditingController _nilaiPakarController = TextEditingController();
|
||||
final ApiService apiService = ApiService();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
|
@ -42,6 +45,7 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
String? _errorMessage;
|
||||
bool _isImageLoading = false;
|
||||
Uint8List? _currentImageBytes;
|
||||
double _currentNilaiPakar = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -49,6 +53,8 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
_namaController.text = widget.namaAwal;
|
||||
_deskripsiController.text = widget.deskripsiAwal;
|
||||
_penangananController.text = widget.penangananAwal;
|
||||
_currentNilaiPakar = widget.nilai_pakar;
|
||||
_nilaiPakarController.text = widget.nilai_pakar.toString();
|
||||
_loadExistingImage();
|
||||
}
|
||||
|
||||
|
@ -91,6 +97,21 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
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 {
|
||||
try {
|
||||
setState(() {
|
||||
|
@ -98,12 +119,18 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
_errorMessage = null;
|
||||
});
|
||||
|
||||
// Get nilai_pakar value with safety check
|
||||
double nilaiPakar = _parseNilaiPakar();
|
||||
|
||||
print("Updating hama with nilai_pakar: $nilaiPakar");
|
||||
|
||||
await apiService.updatePenyakit(
|
||||
widget.idPenyakit,
|
||||
_namaController.text,
|
||||
_deskripsiController.text,
|
||||
_penangananController.text,
|
||||
_pickedFile,
|
||||
nilaiPakar,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
|
@ -260,6 +287,25 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
|||
maxLines: 3,
|
||||
),
|
||||
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(
|
||||
'Foto Penyakit',
|
||||
style: TextStyle(
|
||||
|
|
|
@ -13,6 +13,8 @@ class EditRulePage extends StatefulWidget {
|
|||
final List<double> nilaiPakarList;
|
||||
final int? selectedHamaId;
|
||||
final int? selectedPenyakitId;
|
||||
final bool showHamaOnly; // Tambahkan ini
|
||||
final bool showPenyakitOnly; // Tambahkan ini
|
||||
|
||||
const EditRulePage({
|
||||
Key? key,
|
||||
|
@ -21,6 +23,8 @@ class EditRulePage extends StatefulWidget {
|
|||
required this.selectedRuleIds,
|
||||
required this.selectedGejalaIds,
|
||||
required this.nilaiPakarList,
|
||||
this.showHamaOnly = false, // Tambahkan default value
|
||||
this.showPenyakitOnly = false,
|
||||
this.selectedHamaId,
|
||||
this.selectedPenyakitId,
|
||||
}) : super(key: key);
|
||||
|
@ -37,6 +41,9 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
|
||||
bool isLoading = true;
|
||||
|
||||
bool showHamaOnly = false;
|
||||
bool showPenyakitOnly = false;
|
||||
|
||||
final api = ApiService();
|
||||
|
||||
// Deklarasi variabel untuk menampung data dari API
|
||||
|
@ -154,6 +161,8 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
showHamaOnly = widget.showHamaOnly;
|
||||
showPenyakitOnly = widget.showPenyakitOnly;
|
||||
fetchData(); // Panggil fetchData saat halaman dibuka pertama kali
|
||||
|
||||
// Inisialisasi dari widget parent
|
||||
|
@ -250,6 +259,7 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
child: ListView(
|
||||
children: [
|
||||
// Pilih Hama
|
||||
if (!showPenyakitOnly) ...[
|
||||
Text("Pilih Hama"),
|
||||
DropdownButton<int>(
|
||||
isExpanded: true,
|
||||
|
@ -257,7 +267,9 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
hint: Text('Pilih Hama'),
|
||||
items:
|
||||
hamaList.isNotEmpty
|
||||
? hamaList.map<DropdownMenuItem<int>>((hama) {
|
||||
? hamaList.map<DropdownMenuItem<int>>((
|
||||
hama,
|
||||
) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: hama['id'],
|
||||
child: Text(hama['nama']),
|
||||
|
@ -276,8 +288,10 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Pilih Penyakit
|
||||
if (!showHamaOnly) ...[
|
||||
Text("Pilih Penyakit"),
|
||||
DropdownButton<int>(
|
||||
isExpanded: true,
|
||||
|
@ -306,6 +320,7 @@ class _EditRulePageState extends State<EditRulePage> {
|
|||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Pilih Gejala dan Nilai Pakar
|
||||
Text("Pilih Gejala"),
|
||||
|
|
|
@ -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
|
||||
int currentPage = 0;
|
||||
int rowsPerPage = 10;
|
||||
|
@ -283,6 +161,38 @@ class _HamaPageState extends State<HamaPage> {
|
|||
color: Color(0xFF9DC08D),
|
||||
),
|
||||
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(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
@ -296,6 +206,7 @@ class _HamaPageState extends State<HamaPage> {
|
|||
penangananAwal:
|
||||
hama['penanganan'] ?? '',
|
||||
gambarUrl: hama['foto'] ?? '',
|
||||
nilai_pakar: nilaiPakar,
|
||||
onHamaUpdated:
|
||||
_fetchHama, // fungsi untuk refresh list setelah update
|
||||
),
|
||||
|
|
|
@ -162,6 +162,38 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
|||
color: Color(0xFF9DC08D),
|
||||
),
|
||||
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(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
@ -176,6 +208,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
|||
penyakit['penanganan'] ?? '',
|
||||
gambarUrl:
|
||||
penyakit['foto'] ?? '',
|
||||
nilai_pakar: nilaiPakar,
|
||||
onPenyakitUpdated:
|
||||
_fetchPenyakit, // fungsi untuk refresh list setelah update
|
||||
),
|
||||
|
|
|
@ -176,32 +176,64 @@ class _RulePageState extends State<RulePage> {
|
|||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ElevatedButton.icon(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// Button untuk tambah rule hama
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => TambahRulePage(
|
||||
isEditing: false, // Menandakan mode tambah
|
||||
isEditingHama:
|
||||
true, // Atur sesuai dengan jenis rule
|
||||
builder: (context) => TambahRulePage(
|
||||
isEditing: false,
|
||||
isEditingHama: true, // Menandakan ini adalah rule hama
|
||||
selectedRuleIds: [],
|
||||
selectedGejalaIds: [],
|
||||
nilaiPakarList: [],
|
||||
selectedHamaId: null, // Hanya jika rule hama
|
||||
selectedPenyakitId:
|
||||
null, // Hanya jika rule penyakit
|
||||
selectedHamaId: null,
|
||||
selectedPenyakitId: null,
|
||||
showHamaOnly: true, // Parameter baru untuk menampilkan hanya dropdown hama
|
||||
),
|
||||
),
|
||||
).then((_) => fetchRules());
|
||||
},
|
||||
icon: Icon(Icons.add),
|
||||
label: Text("Tambah Rule"),
|
||||
icon: Icon(Icons.bug_report),
|
||||
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),
|
||||
isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
|
@ -247,13 +279,15 @@ class _RulePageState extends State<RulePage> {
|
|||
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(
|
||||
builder: (context) => EditRulePage(
|
||||
isEditing: true,
|
||||
isEditingHama: true,
|
||||
isEditingHama: editingHama,
|
||||
selectedRuleIds: [
|
||||
rule['id'] as int,
|
||||
],
|
||||
|
@ -265,12 +299,14 @@ class _RulePageState extends State<RulePage> {
|
|||
.toDouble(),
|
||||
],
|
||||
selectedHamaId:
|
||||
rule['id_hama']
|
||||
as int?,
|
||||
selectedPenyakitId: rule['id_penyakit'] as int?, // Tambahkan type cast ke int?
|
||||
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(
|
||||
|
@ -289,7 +325,6 @@ class _RulePageState extends State<RulePage> {
|
|||
}
|
||||
},
|
||||
),
|
||||
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
|
|
|
@ -19,6 +19,7 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
|||
final TextEditingController namaController = TextEditingController();
|
||||
final TextEditingController deskripsiController = TextEditingController();
|
||||
final TextEditingController penangananController = TextEditingController();
|
||||
final TextEditingController nilaiPakarController = TextEditingController();
|
||||
final ApiService apiService = ApiService();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
File? _imageFile;
|
||||
|
@ -32,19 +33,24 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
|||
namaController.dispose();
|
||||
deskripsiController.dispose();
|
||||
penangananController.dispose();
|
||||
nilaiPakarController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _simpanHama() async {
|
||||
if (namaController.text.isNotEmpty &&
|
||||
deskripsiController.text.isNotEmpty &&
|
||||
penangananController.text.isNotEmpty) {
|
||||
penangananController.text.isNotEmpty &&
|
||||
nilaiPakarController.text.isNotEmpty) {
|
||||
try {
|
||||
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
||||
double nilaiPakar = double.parse(nilaiInput);
|
||||
await apiService.createHama(
|
||||
namaController.text,
|
||||
deskripsiController.text,
|
||||
penangananController.text,
|
||||
_pickedFile,
|
||||
nilaiPakar,
|
||||
);
|
||||
widget.onHamaAdded();
|
||||
Navigator.pop(context);
|
||||
|
@ -136,6 +142,12 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
|||
maxLines: 3,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
TextField(
|
||||
controller: nilaiPakarController,
|
||||
decoration: InputDecoration(labelText: 'Nilai Pakar'),
|
||||
maxLines: 3,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Text('Foto'),
|
||||
(_webImage != null)
|
||||
? Image.memory(
|
||||
|
|
|
@ -18,6 +18,7 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
|
|||
final TextEditingController namaController = TextEditingController();
|
||||
final TextEditingController deskripsiController = TextEditingController();
|
||||
final TextEditingController penangananController = TextEditingController();
|
||||
final TextEditingController nilaiPakarController = TextEditingController();
|
||||
final ApiService apiService = ApiService();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
File? _imageFile;
|
||||
|
@ -31,19 +32,24 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
|
|||
namaController.dispose();
|
||||
deskripsiController.dispose();
|
||||
penangananController.dispose();
|
||||
nilaiPakarController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _simpanPenyakit() async {
|
||||
if (namaController.text.isNotEmpty &&
|
||||
deskripsiController.text.isNotEmpty &&
|
||||
penangananController.text.isNotEmpty) {
|
||||
penangananController.text.isNotEmpty &&
|
||||
nilaiPakarController.text.isNotEmpty) {
|
||||
try {
|
||||
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
||||
double nilaiPakar = double.parse(nilaiInput);
|
||||
await apiService.createPenyakit(
|
||||
namaController.text,
|
||||
deskripsiController.text,
|
||||
penangananController.text,
|
||||
_pickedFile,
|
||||
nilaiPakar,
|
||||
);
|
||||
widget.onPenyakitAdded();
|
||||
Navigator.pop(context);
|
||||
|
@ -135,6 +141,12 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
|
|||
maxLines: 3,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
TextField(
|
||||
controller: nilaiPakarController,
|
||||
decoration: InputDecoration(labelText: 'nilai pakar'),
|
||||
maxLines: 3,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
(_webImage != null)
|
||||
? Image.memory(
|
||||
_webImage!,
|
||||
|
|
|
@ -13,6 +13,8 @@ class TambahRulePage extends StatefulWidget {
|
|||
final List<double> nilaiPakarList;
|
||||
final int? selectedHamaId;
|
||||
final int? selectedPenyakitId;
|
||||
final bool showHamaOnly; // Tambahkan ini
|
||||
final bool showPenyakitOnly;
|
||||
|
||||
const TambahRulePage({
|
||||
Key? key,
|
||||
|
@ -23,6 +25,8 @@ class TambahRulePage extends StatefulWidget {
|
|||
required this.nilaiPakarList,
|
||||
this.selectedHamaId,
|
||||
this.selectedPenyakitId,
|
||||
this.showHamaOnly = false, // Tambahkan default value
|
||||
this.showPenyakitOnly = false,
|
||||
}) : super(key: key);
|
||||
}
|
||||
|
||||
|
@ -31,12 +35,16 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
int? selectedPenyakitId;
|
||||
List<int?> selectedGejalaIds = [null];
|
||||
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 isLoading = true;
|
||||
|
||||
bool showHamaOnly = false;
|
||||
bool showPenyakitOnly = false;
|
||||
|
||||
final api = ApiService();
|
||||
|
||||
// Deklarasi variabel untuk menampung data dari API
|
||||
|
@ -119,6 +127,8 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
showHamaOnly = widget.showHamaOnly;
|
||||
showPenyakitOnly = widget.showPenyakitOnly;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -243,6 +202,7 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
child: ListView(
|
||||
children: [
|
||||
// Pilih Hama
|
||||
if (!showPenyakitOnly) ...[
|
||||
Text("Pilih Hama"),
|
||||
DropdownButton<int>(
|
||||
isExpanded: true,
|
||||
|
@ -250,7 +210,9 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
hint: Text('Pilih Hama'),
|
||||
items:
|
||||
hamaList.isNotEmpty
|
||||
? hamaList.map<DropdownMenuItem<int>>((hama) {
|
||||
? hamaList.map<DropdownMenuItem<int>>((
|
||||
hama,
|
||||
) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: hama['id'],
|
||||
child: Text(hama['nama']),
|
||||
|
@ -269,8 +231,10 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Pilih Penyakit
|
||||
if (!showHamaOnly) ...[
|
||||
Text("Pilih Penyakit"),
|
||||
DropdownButton<int>(
|
||||
isExpanded: true,
|
||||
|
@ -299,6 +263,7 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Pilih Gejala dan Nilai Pakar
|
||||
Text("Pilih Gejala"),
|
||||
|
@ -398,7 +363,33 @@ class _TambahRulePageState extends State<TambahRulePage> {
|
|||
// Tombol untuk menambah rule
|
||||
ElevatedButton(
|
||||
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();
|
||||
},
|
||||
child: Text('Tambah Rule'),
|
||||
|
|
|
@ -13,6 +13,7 @@ class ApiService {
|
|||
static const String penyakitUrl = 'http://localhost:5000/api/penyakit';
|
||||
static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit';
|
||||
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);
|
||||
|
||||
// Fungsi Login (dengan perbaikan)
|
||||
|
@ -123,7 +124,6 @@ class ApiService {
|
|||
}
|
||||
|
||||
// Ambil semua hama
|
||||
// Get all hama
|
||||
Future<List<Map<String, dynamic>>> getHama() async {
|
||||
try {
|
||||
final response = await http.get(Uri.parse(hamaUrl)).timeout(timeout);
|
||||
|
@ -251,6 +251,7 @@ class ApiService {
|
|||
String deskripsi,
|
||||
String penanganan,
|
||||
XFile? pickedFile,
|
||||
double nilai_pakar
|
||||
) async {
|
||||
try {
|
||||
var uri = Uri.parse(hamaUrl);
|
||||
|
@ -259,6 +260,7 @@ class ApiService {
|
|||
request.fields['nama'] = nama;
|
||||
request.fields['deskripsi'] = deskripsi;
|
||||
request.fields['penanganan'] = penanganan;
|
||||
request.fields['nilai_pakar'] = nilai_pakar.toString();
|
||||
|
||||
print('Mengirim request ke: $uri');
|
||||
print('Dengan fields: ${request.fields}');
|
||||
|
@ -330,6 +332,7 @@ class ApiService {
|
|||
String deskripsi,
|
||||
String penanganan,
|
||||
XFile? pickedFile,
|
||||
double nilai_pakar
|
||||
) async {
|
||||
try {
|
||||
var uri = Uri.parse('$hamaUrl/$id');
|
||||
|
@ -339,6 +342,7 @@ class ApiService {
|
|||
request.fields['nama'] = nama;
|
||||
request.fields['deskripsi'] = deskripsi;
|
||||
request.fields['penanganan'] = penanganan;
|
||||
request.fields['nilai_pakar'] = nilai_pakar.toString();
|
||||
|
||||
// Log untuk debugging
|
||||
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)
|
||||
Future<Map<String, dynamic>> createPenyakit(
|
||||
String nama,
|
||||
String deskripsi,
|
||||
String penanganan,
|
||||
XFile? pickedFile,
|
||||
double nilai_pakar
|
||||
) async {
|
||||
try {
|
||||
var uri = Uri.parse(penyakitUrl);
|
||||
|
@ -538,6 +563,7 @@ class ApiService {
|
|||
request.fields['nama'] = nama;
|
||||
request.fields['deskripsi'] = deskripsi;
|
||||
request.fields['penanganan'] = penanganan;
|
||||
request.fields['nilai_pakar'] = nilai_pakar.toString();
|
||||
|
||||
print('Mengirim request ke: $uri');
|
||||
print('Dengan fields: ${request.fields}');
|
||||
|
@ -609,6 +635,7 @@ class ApiService {
|
|||
String deskripsi,
|
||||
String penanganan,
|
||||
XFile? pickedFile,
|
||||
double nilai_pakar
|
||||
) async {
|
||||
try {
|
||||
var uri = Uri.parse('$penyakitUrl/$id');
|
||||
|
@ -618,6 +645,7 @@ class ApiService {
|
|||
request.fields['nama'] = nama;
|
||||
request.fields['deskripsi'] = deskripsi;
|
||||
request.fields['penanganan'] = penanganan;
|
||||
request.fields['nilai_pakar'] = nilai_pakar.toString();
|
||||
|
||||
// Log untuk debugging
|
||||
print('Mengirim request ke: $uri');
|
||||
|
@ -889,4 +917,39 @@ class ApiService {
|
|||
final response = await http.delete(Uri.parse('$rulesHamaUrl/$id'));
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,24 +57,41 @@ class _DetailHamaPageState extends State<DetailHamaPage> {
|
|||
// Widget untuk menampilkan gambar dengan penanganan error yang lebih baik
|
||||
Widget _buildImageWidget(String? filename) {
|
||||
if (filename == null || filename.isEmpty) {
|
||||
return Text("Tidak ada gambar tersedia");
|
||||
return _buildPlaceholderImage(
|
||||
"Tidak ada gambar tersedia",
|
||||
Icons.image_not_supported,
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder<Uint8List?>(
|
||||
future: ApiService().getHamaImageBytesByFilename(filename),
|
||||
builder: (context, snapshot) {
|
||||
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) {
|
||||
return Text("Gagal memuat gambar");
|
||||
return _buildPlaceholderImage(
|
||||
"Gagal memuat gambar",
|
||||
Icons.broken_image,
|
||||
);
|
||||
} 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 _buildPlaceholderImage(String message, IconData icon) {
|
||||
return Container(
|
||||
|
|
|
@ -1,9 +1,122 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frontend/api_services/api_services.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class DetailPenyakitPage extends StatelessWidget {
|
||||
final Map<String, dynamic> detailPenyakit;
|
||||
class DetailPenyakitPage extends StatefulWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -11,38 +124,55 @@ class DetailPenyakitPage extends StatelessWidget {
|
|||
backgroundColor: Color(0xFF9DC08D),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Color(0xFF9DC08D),
|
||||
title: Text(
|
||||
"Detail Penyakit",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
title: Text("Detail Penyakit", style: TextStyle(color: Colors.white)),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
body: FutureBuilder<Map<String, dynamic>>(
|
||||
future: _detailPenyakitFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(color: Colors.white),
|
||||
);
|
||||
}
|
||||
|
||||
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: [
|
||||
if (detailPenyakit["gambar"] != null && detailPenyakit["gambar"]!.isNotEmpty)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.asset(
|
||||
detailPenyakit["gambar"]!,
|
||||
height: 200,
|
||||
width: 200, // Biar gambar full lebar
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
// Tampilkan foto dari database dengan penanganan error yang lebih baik
|
||||
_buildImageWidget(detailData["foto"]),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Card Nama Penyakit
|
||||
// Card Nama Hama
|
||||
SizedBox(
|
||||
width: double.infinity, // Bikin card full lebar
|
||||
width: double.infinity,
|
||||
child: Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
@ -50,11 +180,14 @@ class DetailPenyakitPage extends StatelessWidget {
|
|||
children: [
|
||||
Text(
|
||||
"Nama Penyakit:",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
detailPenyakit["nama"] ?? "Nama penyakit tidak tersedia",
|
||||
detailData["nama"] ?? "Nama hama tidak tersedia",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
|
@ -66,10 +199,12 @@ class DetailPenyakitPage extends StatelessWidget {
|
|||
|
||||
// Card Deskripsi + Penanganan
|
||||
SizedBox(
|
||||
width: double.infinity, // Bikin card full lebar
|
||||
width: double.infinity,
|
||||
child: Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
@ -77,21 +212,27 @@ class DetailPenyakitPage extends StatelessWidget {
|
|||
children: [
|
||||
Text(
|
||||
"Deskripsi:",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
detailPenyakit["deskripsi"] ?? "Deskripsi tidak tersedia",
|
||||
detailData["deskripsi"] ?? "Deskripsi tidak tersedia",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
"Penanganan:",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
detailPenyakit["penanganan"] ?? "Penanganan tidak tersedia",
|
||||
detailData["penanganan"] ?? "Penanganan tidak tersedia",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
|
@ -102,7 +243,6 @@ class DetailPenyakitPage extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DetailPenyakitPage(detailPenyakit: penyakit),
|
||||
builder: (context) => DetailPenyakitPage(DetailPenyakit: penyakit),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue