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) => {
|
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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 |
|
@ -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) {
|
||||||
|
|
|
@ -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: {
|
foto: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
},
|
||||||
|
nilai_pakar: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
* 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
),
|
),
|
||||||
|
|
|
@ -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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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!,
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue