integrasi add rules

This commit is contained in:
unknown 2025-05-02 03:04:00 +07:00
parent 4271210c03
commit 692bfeeb50
23 changed files with 1277 additions and 251 deletions

View File

@ -9,6 +9,7 @@ const gejalaRoutes = require('./routes/gejalaRoute');
const hamaRoutes = require('./routes/hamaRoutes');
const penyakitRoutes = require('./routes/penyakitRoutes');
const ruleRoutes = require('./routes/ruleRoutes');
const ruleHamaRoutes = require('./routes/ruleHamaRoutes');
const swaggerDocs = require('./swagger');
dotenv.config();
@ -25,7 +26,8 @@ app.use("/api/auth", authRoutes);
app.use("/api/gejala", gejalaRoutes);
app.use("/api/hama", hamaRoutes);
app.use("/api/penyakit", penyakitRoutes);
app.use("/api/rules", ruleRoutes);
app.use("/api/rules_penyakit", ruleRoutes);
app.use("/api/rules_hama", ruleHamaRoutes);
// Swagger Documentation

View File

@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken');
const argon2 = require('argon2');
const randomstring = require('randomstring');
const User = require('../models/user'); // Pastikan sesuai dengan struktur project
const {User} = require('../models'); // Pastikan sesuai dengan struktur project
require('dotenv').config();
// Fungsi untuk membuat token JWT
@ -13,7 +13,6 @@ const generateToken = (user) => {
);
};
// Menambahkan user baru dengan hashing Argon2
exports.register = async (req, res) => {
try {

View File

@ -1,9 +1,11 @@
const Gejala = require('../models/gejala');
const {Gejala} = require('../models');
// 🔹 Menampilkan semua data gejala
exports.getAllGejala = async (req, res) => {
try {
const gejala = await Gejala.findAll();
const gejala = await Gejala.findAll({
attributes: ['id', 'nama']
});
res.status(200).json(gejala);
} catch (error) {
res.status(500).json({ message: 'Terjadi kesalahan', error });

View File

@ -1,9 +1,11 @@
const Hama = require('../models/hama');
const {Hama} = require('../models');
// 🔹 Fungsi untuk mendapatkan semua data hama
exports.getAllHama = async (req, res) => {
try {
const dataHama = await Hama.findAll();
const dataHama = await Hama.findAll({
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan']
});
res.status(200).json({ message: 'Data hama berhasil diambil', data: dataHama });
} catch (error) {
res.status(500).json({ message: 'Gagal mengambil data hama', error });

View File

@ -1,9 +1,11 @@
const Penyakit = require('../models/penyakit');
const {Penyakit} = require('../models');
// 🔹 Fungsi untuk mendapatkan semua data penyakit
exports.getAllPenyakit = async (req, res) => {
try {
const dataPenyakit = await Penyakit.findAll();
const dataPenyakit = await Penyakit.findAll({
attributes: ['id', 'nama' , 'deskripsi' , 'penanganan']
});
res.status(200).json({ message: 'Data penyakit berhasil diambil', data: dataPenyakit });
} catch (error) {
res.status(500).json({ message: 'Gagal mengambil data penyakit', error });

View File

@ -1,19 +1,18 @@
const { Rule, gejala, penyakit, hama } = require('../models');
const { Rule_penyakit } = require('../models');
// 🔥 Buat aturan baru
exports.createRule = async (req, res) => {
exports.createRulePenyakit = async (req, res) => {
try {
const { id_gejala, id_penyakit, id_hama, nilai_pakar } = req.body;
const { id_gejala, id_penyakit, nilai_pakar } = req.body;
// Validasi minimal
if (!id_gejala || (!id_penyakit && !id_hama) || !nilai_pakar) {
if (!id_gejala || (!id_penyakit) || !nilai_pakar) {
return res.status(400).json({ message: 'Data tidak lengkap' });
}
const newRule = await Rule.create({
const newRule = await Rule_penyakit.create({
id_gejala,
id_penyakit,
id_hama,
nilai_pakar
});
@ -25,9 +24,9 @@ exports.createRule = async (req, res) => {
};
// 🔥 Ambil semua aturan
exports.getRules = async (req, res) => {
exports.getRulesPenyakit = async (req, res) => {
try {
const rules = await Rule.findAll();
const rules = await Rule_penyakit.findAll();
res.status(200).json({ message: 'Daftar Rules', data: rules });
} catch (error) {
@ -37,12 +36,12 @@ exports.getRules = async (req, res) => {
};
// 🔥 Update aturan
exports.updateRule = async (req, res) => {
exports.updateRulePenyakit = async (req, res) => {
try {
const { id } = req.params;
const { id_gejala, id_penyakit, id_hama, nilai_pakar } = req.body;
const { id_gejala, id_penyakit, nilai_pakar } = req.body;
const rule = await Rule.findByPk(id);
const rule = await Rule_penyakit.findByPk(id);
if (!rule) {
return res.status(404).json({ message: 'Rule tidak ditemukan' });
}
@ -50,7 +49,6 @@ exports.updateRule = async (req, res) => {
await rule.update({
id_gejala,
id_penyakit,
id_hama,
nilai_pakar
});
@ -62,11 +60,11 @@ exports.updateRule = async (req, res) => {
};
// 🔥 Hapus aturan
exports.deleteRule = async (req, res) => {
exports.deleteRulePenyakit = async (req, res) => {
try {
const { id } = req.params;
const rule = await Rule.findByPk(id);
const rule = await Rule_penyakit.findByPk(id);
if (!rule) {
return res.status(404).json({ message: 'Rule tidak ditemukan' });
}

View File

@ -0,0 +1,79 @@
const { Rule_hama } = require('../models');
// 🔥 Buat aturan baru
exports.createRuleHama = async (req, res) => {
try {
const { id_gejala, id_hama, nilai_pakar } = req.body;
// Validasi minimal
if (!id_gejala || (!id_hama) || !nilai_pakar) {
return res.status(400).json({ message: 'Data tidak lengkap' });
}
const newRule = await Rule_hama.create({
id_gejala,
id_hama,
nilai_pakar
});
res.status(201).json({ message: 'Rule berhasil dibuat', data: newRule });
} catch (error) {
console.error('Error createRule:', error);
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
}
};
// 🔥 Ambil semua aturan
exports.getRulesHama = async (req, res) => {
try {
const rules = await Rule_hama.findAll();
res.status(200).json({ message: 'Daftar Rules', data: rules });
} catch (error) {
console.error('Error getRules:', error);
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
}
};
// 🔥 Update aturan
exports.updateRuleHama = async (req, res) => {
try {
const { id } = req.params;
const { id_gejala, id_hama, nilai_pakar } = req.body;
const rule = await Rule_hama.findByPk(id);
if (!rule) {
return res.status(404).json({ message: 'Rule tidak ditemukan' });
}
await rule.update({
id_gejala,
id_hama,
nilai_pakar
});
res.status(200).json({ message: 'Rule berhasil diperbarui', data: rule });
} catch (error) {
console.error('Error updateRule:', error);
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
}
};
// 🔥 Hapus aturan
exports.deleteRuleHama = async (req, res) => {
try {
const { id } = req.params;
const rule = await Rule_hama.findByPk(id);
if (!rule) {
return res.status(404).json({ message: 'Rule tidak ditemukan' });
}
await rule.destroy();
res.status(200).json({ message: 'Rule berhasil dihapus' });
} catch (error) {
console.error('Error deleteRule:', error);
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
}
};

View File

@ -1,4 +1,4 @@
const User = require('../models/user');
const {User} = require('../models');
const argon2 = require('argon2');
const { Op } = require('sequelize');

View File

@ -2,7 +2,7 @@
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Rules', {
await queryInterface.createTable('Rules_penyakit', {
id: {
allowNull: false,
autoIncrement: true,
@ -27,16 +27,9 @@ module.exports = {
key:'id'
}
},
id_hama: {
type: Sequelize.INTEGER,
references:{
model: 'hama',
key: 'id'
}
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Rules');
await queryInterface.dropTable('Rules_penyakit');
}
};

View File

@ -0,0 +1,34 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Rules_hama', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
id_gejala: {
type: Sequelize.INTEGER,
references: {
model: 'gejala',
key: 'id'
}
},
nilai_pakar: {
type: Sequelize.FLOAT
},
id_hama: {
type: Sequelize.INTEGER,
references: {
model: 'hama',
key: 'id'
}
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Rules_hama');
}
};

View File

@ -1,4 +1,5 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Gejala extends Model {}

View File

@ -1,5 +1,5 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Hama extends Model {}

View File

@ -1,5 +1,5 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports =(sequelize) => {
class Penyakit extends Model {}

View File

@ -0,0 +1,57 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Rule_hama extends Model {
static associate(models) {
// Asosiasi dengan model Gejala
Rule_hama.belongsTo(models.Gejala, {
foreignKey: 'id_gejala',
as: 'gejala', // Nama asosiasi yang bisa digunakan saat melakukan query
});
// Asosiasi dengan model hama
Rule_hama.belongsTo(models.Hama, {
foreignKey: 'id_hama',
as: 'hama',
});
}
}
Rule_hama.init(
{
id_gejala: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'gejala', // Mengacu ke tabel 'gejala'
key: 'id', // Mengacu ke kolom 'id' pada tabel 'gejala'
},
},
nilai_pakar: {
type: DataTypes.FLOAT,
allowNull: false,
},
id_hama: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'hama',
key: 'id',
},
},
},
{
sequelize,
modelName: 'Rule_hama',
tableName: 'rules_hama',
timestamps: false,
paranoid: false,
}
);
return Rule_hama;
}

View File

@ -1,31 +1,26 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Rule extends Model {
class Rule_penyakit extends Model {
static associate(models) {
// Asosiasi dengan model Gejala
Rule.belongsTo(models.Gejala, {
Rule_penyakit.belongsTo(models.Gejala, {
foreignKey: 'id_gejala',
as: 'gejala', // Nama asosiasi yang bisa digunakan saat melakukan query
});
// Asosiasi dengan model Penyakit
Rule.belongsTo(models.Penyakit, {
Rule_penyakit.belongsTo(models.Penyakit, {
foreignKey: 'id_penyakit',
as: 'penyakit',
});
// Asosiasi dengan model Hama
Rule.belongsTo(models.Hama, {
foreignKey: 'id_hama',
as: 'hama',
});
}
}
Rule.init(
Rule_penyakit.init(
{
id_gejala: {
type: DataTypes.INTEGER,
@ -47,24 +42,16 @@ Rule.init(
key: 'id', // Mengacu ke kolom 'id' pada tabel 'penyakit'
},
},
id_hama: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'hama', // Mengacu ke tabel 'hama'
key: 'id', // Mengacu ke kolom 'id' pada tabel 'hama'
},
},
},
{
sequelize,
modelName: 'Rule',
tableName: 'rules',
modelName: 'Rule_penyakit',
tableName: 'rules_penyakit',
timestamps: false,
paranoid: false,
}
);
return Rule;
return Rule_penyakit;
}

View File

@ -1,5 +1,5 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class User extends Model {}

View File

@ -0,0 +1,119 @@
const express = require('express');
const router = express.Router();
const ruleController = require('../controller/rulesHamaController');
/**
* @swagger
* tags:
* name: Rules_hama
* description: API untuk mengelola aturan (rules)
*/
/**
* @swagger
* /api/rules_hama:
* post:
* summary: Membuat aturan baru
* tags: [Rules_hama]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id_gejala
* - nilai_pakar
* properties:
* id_gejala:
* type: integer
* id_hama:
* type: integer
* nilai_pakar:
* type: number
* format: float
* responses:
* 201:
* description: Rule berhasil dibuat
* 400:
* description: Data tidak lengkap
* 500:
* description: Terjadi kesalahan server
*/
router.post('/', ruleController.createRuleHama);
/**
* @swagger
* /api/rules_hama:
* get:
* summary: Menampilkan semua aturan
* tags: [Rules_hama]
* responses:
* 200:
* description: Daftar rules berhasil diambil
* 500:
* description: Terjadi kesalahan server
*/
router.get('/', ruleController.getRulesHama);
/**
* @swagger
* /api/rules_hama/{id}:
* put:
* summary: Memperbarui aturan berdasarkan ID
* tags: [Rules_hama]
* parameters:
* - in: path
* name: id
* required: true
* description: ID rule yang ingin diperbarui
* schema:
* type: integer
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* id_gejala:
* type: integer
* id_hama:
* type: integer
* nilai_pakar:
* type: number
* format: float
* responses:
* 200:
* description: Rule berhasil diperbarui
* 404:
* description: Rule tidak ditemukan
* 500:
* description: Terjadi kesalahan server
*/
router.put('/:id', ruleController.updateRuleHama);
/**
* @swagger
* /api/rules_hama/{id}:
* delete:
* summary: Menghapus aturan berdasarkan ID
* tags: [Rules_hama]
* parameters:
* - in: path
* name: id
* required: true
* description: ID rule yang ingin dihapus
* schema:
* type: integer
* responses:
* 200:
* description: Rule berhasil dihapus
* 404:
* description: Rule tidak ditemukan
* 500:
* description: Terjadi kesalahan server
*/
router.delete('/:id', ruleController.deleteRuleHama);
module.exports = router;

View File

@ -5,16 +5,16 @@ const ruleController = require('../controller/rulesController');
/**
* @swagger
* tags:
* name: Rules
* name: Rules_penyakit
* description: API untuk mengelola aturan (rules)
*/
/**
* @swagger
* /api/rules:
* /api/rules_penyakit:
* post:
* summary: Membuat aturan baru
* tags: [Rules]
* tags: [Rules_penyakit]
* requestBody:
* required: true
* content:
@ -29,8 +29,6 @@ const ruleController = require('../controller/rulesController');
* type: integer
* id_penyakit:
* type: integer
* id_hama:
* type: integer
* nilai_pakar:
* type: number
* format: float
@ -42,28 +40,28 @@ const ruleController = require('../controller/rulesController');
* 500:
* description: Terjadi kesalahan server
*/
router.post('/', ruleController.createRule);
router.post('/', ruleController.createRulePenyakit);
/**
* @swagger
* /api/rules:
* /api/rules_penyakit:
* get:
* summary: Menampilkan semua aturan
* tags: [Rules]
* tags: [Rules_penyakit]
* responses:
* 200:
* description: Daftar rules berhasil diambil
* 500:
* description: Terjadi kesalahan server
*/
router.get('/', ruleController.getRules);
router.get('/', ruleController.getRulesPenyakit);
/**
* @swagger
* /api/rules/{id}:
* /api/rules_penyakit/{id}:
* put:
* summary: Memperbarui aturan berdasarkan ID
* tags: [Rules]
* tags: [Rules_penyakit]
* parameters:
* - in: path
* name: id
@ -82,8 +80,6 @@ router.get('/', ruleController.getRules);
* type: integer
* id_penyakit:
* type: integer
* id_hama:
* type: integer
* nilai_pakar:
* type: number
* format: float
@ -95,14 +91,14 @@ router.get('/', ruleController.getRules);
* 500:
* description: Terjadi kesalahan server
*/
router.put('/:id', ruleController.updateRule);
router.put('/:id', ruleController.updateRulePenyakit);
/**
* @swagger
* /api/rules/{id}:
* /api/rules_penyakit/{id}:
* delete:
* summary: Menghapus aturan berdasarkan ID
* tags: [Rules]
* tags: [Rules_penyakit]
* parameters:
* - in: path
* name: id
@ -118,6 +114,6 @@ router.put('/:id', ruleController.updateRule);
* 500:
* description: Terjadi kesalahan server
*/
router.delete('/:id', ruleController.deleteRule);
router.delete('/:id', ruleController.deleteRulePenyakit);
module.exports = router;

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'hama_page.dart';
import 'penyakit_page.dart';
import 'gejala_page.dart';
import 'rule_page.dart';
import 'package:frontend/api_services/api_services.dart';
import 'package:frontend/user/login_page.dart';
@ -62,6 +63,15 @@ class AdminPage extends StatelessWidget {
);
},
),
ListTile(
title: Text('Halaman Aturan'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => RulePage()),
);
},
),
ListTile(title: Text('Logout'), onTap: () => _logout(context)),
],
),

View File

@ -68,134 +68,6 @@ class _PenyakitPageState extends State<PenyakitPage> {
);
}
// void _tambahPenyakit() {
// TextEditingController namaController = TextEditingController();
// TextEditingController penangananController = TextEditingController();
// TextEditingController deskripsiController = TextEditingController();
// showDialog(
// context: context,
// builder: (context) {
// return AlertDialog(
// title: Text('Tambah Penyakit 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'),
// ),
// ElevatedButton(
// onPressed: () async {
// if (namaController.text.isNotEmpty &&
// deskripsiController.text.isNotEmpty &&
// penangananController.text.isNotEmpty) {
// try {
// await apiService.createPenyakit(
// namaController.text,
// deskripsiController.text,
// penangananController.text,
// );
// _fetchPenyakit();
// Navigator.pop(context);
// } catch (e) {
// print("Error adding penyakit: $e");
// }
// }
// },
// child: Text('Simpan'),
// ),
// ],
// );
// },
// ).then((_) {
// namaController.dispose();
// deskripsiController.dispose();
// penangananController.dispose();
// });
// }
// void showEditDialog(BuildContext context, Map<String, dynamic> penyakit) {
// final TextEditingController editNamaController = TextEditingController(text: penyakit['nama'] ?? '');
// final TextEditingController editDeskripsiController = TextEditingController(text: penyakit['deskripsi'] ?? '');
// final TextEditingController editPenangananController = TextEditingController(text: penyakit['penanganan'] ?? '');
// showDialog(
// context: context,
// builder: (context) {
// return AlertDialog(
// title: Text(
// 'Edit Penyakit',
// ),
// 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',
// style: TextStyle(color: Colors.black),
// ),
// ),
// ElevatedButton(
// onPressed: () async {
// try {
// await apiService.updatePenyakit(
// penyakit['id'],
// editNamaController.text,
// editDeskripsiController.text,
// editPenangananController.text,
// );
// _fetchPenyakit();
// Navigator.pop(context);
// } catch (e) {
// print("Error updating penyakit: $e");
// }
// },
// child: Text('Simpan', style: TextStyle(color: Colors.black)),
// ),
// ],
// );
// },
// );
// }
//pagination
int currentPage = 0;
int rowsPerPage = 10;

View File

@ -0,0 +1,294 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:frontend/api_services/api_services.dart';
import 'tambah_rule_page.dart';
class RulePage extends StatefulWidget {
const RulePage({Key? key}) : super(key: key);
@override
_RulePageState createState() => _RulePageState();
}
class _RulePageState extends State<RulePage> {
List<Map<String, dynamic>> gejalaList = [];
List<Map<String, dynamic>> penyakitList = [];
List<Map<String, dynamic>> hamaList = [];
List<dynamic> rules = [];
bool isLoading = true;
@override
void initState() {
super.initState();
fetchRules();
}
void fetchRules() async {
final apiService = ApiService();
try {
// Ambil semua data referensi
gejalaList = await apiService.getGejala();
penyakitList = await apiService.getPenyakit();
hamaList = await apiService.getHama();
// Ambil rules penyakit dan hama secara terpisah
final rulesPenyakit = await apiService.getRulesPenyakit();
final rulesHama = await apiService.getRulesHama();
// Gabungkan dan proses keduanya
final enrichedRules = [
// Mengolah rules penyakit
...rulesPenyakit.map((rule) {
final gejala = gejalaList.firstWhere(
(item) => item['id'] == rule['id_gejala'],
orElse: () => {'nama': '-'},
);
final penyakit = penyakitList.firstWhere(
(item) => item['id'] == rule['id_penyakit'],
orElse: () => {'nama': '-'},
);
return {
'id': rule['id'],
'id_gejala': rule['id_gejala'],
'id_penyakit': rule['id_penyakit'],
'id_hama': null,
'nama_gejala': gejala['nama'],
'nama_penyakit': penyakit['nama'],
'nama_hama': null,
'nilai_pakar':
rule['nilai_pakar'], // Menambahkan nilai_pakar dari rule_penyakit
};
}),
// Mengolah rules hama
...rulesHama.map((rule) {
print(
"Rule id_gejala: ${rule['id_gejala']}, id_hama: ${rule['id_hama']}",
);
// Mencari gejala berdasarkan id
final gejala = gejalaList.firstWhere((item) {
print(
"Mencocokkan gejala id: ${item['id']} dengan ${rule['id_gejala']}",
);
return item['id'] == rule['id_gejala'];
}, orElse: () => {'nama': 'TIDAK DITEMUKAN'});
// Mencari hama berdasarkan id
final hama = hamaList.firstWhere(
(item) => item['id'] == rule['id_hama'],
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
);
print(
"Gejala ditemukan: ${gejala['nama']}, Hama ditemukan: ${hama['nama']}",
);
// Menampilkan isi dari gejalaList dan hamaList untuk debugging
print("Isi gejalaList:");
for (var item in gejalaList) {
print(item);
}
print("Isi hamaList:");
for (var item in hamaList) {
print(item);
}
return {
'id': rule['id'],
'id_gejala': rule['id_gejala'],
'id_penyakit': null,
'id_hama': rule['id_hama'],
'nama_gejala': gejala['nama'], // Memastikan nama gejala ditampilkan
'nama_penyakit': null,
'nama_hama': hama['nama'], // Memastikan nama hama ditampilkan
'nilai_pakar':
rule['nilai_pakar'], // Menambahkan nilai_pakar dari rule_hama
};
}),
];
setState(() {
rules = enrichedRules;
});
} catch (e) {
print('Terjadi kesalahan saat memuat data: $e');
for (var rule in rules) {
print("Mencari gejala untuk id_gejala: ${rule['id_gejala']}");
var gejala = gejalaList.firstWhere(
(item) => item['id'].toString() == rule['id_gejala'].toString(),
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
);
print("Gejala ditemukan: ${gejala['nama']}");
}
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> deleteRule(Map<String, dynamic> rule) async {
try {
http.Response res;
// Tentukan fungsi delete berdasarkan isi rule
if (rule['id_hama'] != null) {
res = await ApiService.deleteRuleHama(
rule['id'],
); // Fungsi API untuk delete hama
} else if (rule['id_penyakit'] != null) {
res = await ApiService.deleteRulePenyakit(
rule['id'],
); // Fungsi API untuk delete penyakit
} else {
throw Exception(
"Data rule tidak valid (tidak ada id_hama atau id_penyakit)",
);
}
if (res.statusCode == 200) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Rule berhasil dihapus")));
fetchRules(); // Refresh data setelah delete
} else {
throw Exception("Gagal menghapus rule");
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Terjadi kesalahan saat menghapus: $e")),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Data Rules')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: ElevatedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => TambahRulePage(
isEditing: false, // Menandakan mode tambah
isEditingHama:
true, // Atur sesuai dengan jenis rule
selectedRuleIds: [],
selectedGejalaIds: [],
nilaiPakarList: [],
selectedHamaId: null, // Hanya jika rule hama
selectedPenyakitId:
null, // Hanya jika rule penyakit
),
),
).then((_) => fetchRules());
},
icon: Icon(Icons.add),
label: Text("Tambah Rule"),
),
),
const SizedBox(height: 16),
isLoading
? const Center(child: CircularProgressIndicator())
: Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns: const [
DataColumn(label: Text('No')),
DataColumn(label: Text('Hama / Penyakit')),
DataColumn(label: Text('Gejala')),
DataColumn(label: Text('nilai pakar')),
DataColumn(label: Text('Aksi')),
],
rows: List.generate(rules.length, (index) {
final rule = rules[index];
final namaKategori =
rule['id_penyakit'] != null
? rule['nama_penyakit'] ?? '-'
: rule['nama_hama'] ?? '-';
final isPenyakit = rule['id_penyakit'] != null;
return DataRow(
cells: [
DataCell(Text((index + 1).toString())),
DataCell(Text(namaKategori)),
DataCell(Text(rule['nama_gejala'] ?? '-')),
DataCell(
Text(rule['nilai_pakar']?.toString() ?? '-'),
),
DataCell(
Row(
children: [
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.orange,
),
onPressed: () {
if (rule != null && rule.id != null) {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => TambahRulePage(
isEditing: true,
isEditingHama: true,
selectedRuleIds: [rule.id],
selectedGejalaIds: [
rule.idGejala,
],
nilaiPakarList: [
rule.nilaiPakar,
],
selectedHamaId: rule.idHama,
),
),
);
} else {
print(
'Data rule tidak lengkap atau null',
);
}
},
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
deleteRule(rule);
},
),
],
),
),
],
);
}),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,403 @@
import 'package:flutter/material.dart';
import 'package:frontend/api_services/api_services.dart';
import 'package:http/http.dart' as http;
class TambahRulePage extends StatefulWidget {
@override
_TambahRulePageState createState() => _TambahRulePageState();
final bool isEditing;
final bool isEditingHama;
final List<int> selectedRuleIds;
final List<int> selectedGejalaIds;
final List<double> nilaiPakarList;
final int? selectedHamaId;
final int? selectedPenyakitId;
const TambahRulePage({
Key? key,
required this.isEditing,
required this.isEditingHama,
required this.selectedRuleIds,
required this.selectedGejalaIds,
required this.nilaiPakarList,
this.selectedHamaId,
this.selectedPenyakitId,
}) : super(key: key);
}
class _TambahRulePageState extends State<TambahRulePage> {
int? selectedHamaId;
int? selectedPenyakitId;
List<int?> selectedGejalaIds = [null];
List<double> nilaiPakarList = [0.5];
List<int?> selectedRuleIds = []; // List paralel dengan selectedGejalaIds dan nilaiPakarList
bool isEditing = true; // atau false jika sedang edit penyakit
bool isLoading = true;
final api = ApiService();
// Deklarasi variabel untuk menampung data dari API
List<Map<String, dynamic>> hamaList = [];
List<Map<String, dynamic>> penyakitList = [];
List<Map<String, dynamic>> gejalaList = [];
void loadRulesForEditing() async {
try {
final fetchedRules = isEditing
? await api.getRulesHama()
: await api.getRulesPenyakit();
setState(() {
selectedRuleIds = fetchedRules.map<int>((rule) => rule['id']).toList();
selectedGejalaIds = fetchedRules.map<int>((rule) => rule['id_gejala']).toList();
nilaiPakarList = fetchedRules.map<double>((rule) => (rule['nilai_pakar'] as num).toDouble()).toList();
});
} catch (e) {
print("Gagal memuat data rule: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Gagal memuat data rule")),
);
}
}
// Fungsi untuk fetch data dari API
Future<void> fetchData() async {
setState(() {
isLoading = true; // Mengatur status loading saat mulai ambil data
});
try {
// Ambil data dari API
final hamaData = await api.getHama();
final penyakitData = await api.getPenyakit();
final gejalaData = await api.getGejala();
// Debugging: Cek apakah data yang diterima dari API
print("Hama Data: $hamaData");
print("Penyakit Data: $penyakitData");
print("Gejala Data: $gejalaData");
// Pengecekan jika data kosong
if (hamaData.isEmpty || penyakitData.isEmpty || gejalaData.isEmpty) {
print("Data kosong, periksa API atau koneksi.");
}
// Update data dan status loading
setState(() {
hamaList = hamaData;
penyakitList = penyakitData;
gejalaList = gejalaData;
isLoading = false; // Mengubah status loading setelah data diterima
});
} catch (e) {
// Menangani error jika fetch gagal
print("Error fetching data: $e");
setState(() {
isLoading =
false; // Mengubah status loading selesai meskipun terjadi error
});
}
}
@override
void initState() {
super.initState();
fetchData(); // Panggil fetchData saat halaman dibuka pertama kali
}
void saveRules() 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 idGejala = selectedGejalaIds[i];
final nilai = nilaiPakarList[i];
if (idGejala != null) {
if (selectedPenyakitId != null) {
final response = await ApiService.createRulePenyakit(
idGejala: idGejala,
idPenyakit: selectedPenyakitId,
nilaiPakar: nilai,
);
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception("Gagal menyimpan rule penyakit");
}
} else if (selectedHamaId != null) {
final response = await ApiService.createRuleHama(
idGejala: idGejala,
idHama: selectedHamaId,
nilaiPakar: nilai,
);
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception("Gagal menyimpan rule hama");
}
}
}
}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Data berhasil disimpan")));
Navigator.pop(context);
} catch (e) {
print('Gagal menyimpan data: $e');
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Gagal menyimpan data")));
}
}
void updateRules() async {
if (selectedPenyakitId == null && selectedHamaId == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Pilih minimal satu: Penyakit atau Hama")),
);
return;
}
try {
for (int i = 0; i < selectedGejalaIds.length; i++) {
final idRule = selectedRuleIds[i];
final idGejala = selectedGejalaIds[i];
final nilai = nilaiPakarList[i];
if (idRule != null && idGejala != null) {
http.Response response;
if (selectedPenyakitId != null) {
response = await ApiService.updateRulePenyakit(
id: idRule,
idGejala: idGejala,
idPenyakit: selectedPenyakitId,
nilaiPakar: nilai,
);
} else {
response = await ApiService.updateRuleHama(
id: idRule,
idGejala: idGejala,
idHama: selectedHamaId,
nilaiPakar: nilai,
);
}
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception("Gagal mengupdate rule");
}
}
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Data berhasil diperbarui")),
);
Navigator.pop(context);
} catch (e) {
print('Gagal memperbarui data: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Gagal memperbarui data")),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Tambah Rule")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child:
isLoading
? Center(child: CircularProgressIndicator())
: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
// Pilih Hama
Text("Pilih Hama"),
DropdownButton<int>(
isExpanded: true,
value: selectedHamaId,
hint: Text('Pilih Hama'),
items:
hamaList.isNotEmpty
? hamaList.map<DropdownMenuItem<int>>((hama) {
return DropdownMenuItem<int>(
value: hama['id'],
child: Text(hama['nama']),
);
}).toList()
: [
DropdownMenuItem<int>(
value: null,
child: Text("Data tidak tersedia"),
),
],
onChanged: (value) {
setState(() {
selectedHamaId = value;
});
},
),
SizedBox(height: 16),
// Pilih Penyakit
Text("Pilih Penyakit"),
DropdownButton<int>(
isExpanded: true,
value: selectedPenyakitId,
hint: Text('Pilih Penyakit'),
items:
penyakitList.isNotEmpty
? penyakitList.map<DropdownMenuItem<int>>((
penyakit,
) {
return DropdownMenuItem<int>(
value: penyakit['id'],
child: Text(penyakit['nama']),
);
}).toList()
: [
DropdownMenuItem<int>(
value: null,
child: Text("Data tidak tersedia"),
),
],
onChanged: (value) {
setState(() {
selectedPenyakitId = value;
});
},
),
SizedBox(height: 16),
// Pilih Gejala dan Nilai Pakar
Text("Pilih Gejala"),
...List.generate(
selectedGejalaIds.length,
(index) => Card(
elevation: 3,
margin: EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
// Dropdown untuk gejala
Expanded(
child: DropdownButton<int>(
isExpanded: true,
value: selectedGejalaIds[index],
hint: Text('Pilih Gejala'),
items:
gejalaList.isNotEmpty
? gejalaList.map<
DropdownMenuItem<int>
>((gejala) {
return DropdownMenuItem<int>(
value: gejala['id'],
child: Text(gejala['nama']),
);
}).toList()
: [
DropdownMenuItem<int>(
value: null,
child: Text(
"Data tidak tersedia",
),
),
],
onChanged: (value) {
setState(() {
selectedGejalaIds[index] = value;
});
},
),
),
// Input nilai pakar
SizedBox(width: 16),
SizedBox(
width: 80,
child: TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: "Nilai Pakar",
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
if (value.isNotEmpty) {
nilaiPakarList[index] =
double.tryParse(value) ?? 0.5;
}
});
},
),
),
// Tombol untuk menghapus gejala
IconButton(
icon: Icon(Icons.remove_circle_outline),
onPressed: () {
setState(() {
selectedGejalaIds.removeAt(index);
nilaiPakarList.removeAt(index);
});
},
),
],
),
),
),
),
// Tombol untuk menambah gejala
ElevatedButton(
onPressed: () {
setState(() {
selectedGejalaIds.add(null);
nilaiPakarList.add(
0.5,
); // Menambahkan nilai pakar default
});
},
child: Text('Tambah Gejala'),
),
SizedBox(height: 20),
// Tombol untuk menambah rule
ElevatedButton(
onPressed: () {
// Panggil fungsi saveRules untuk menyimpan data
saveRules();
},
child: Text('Tambah Rule'),
),
],
),
),
),
),
);
}
}

View File

@ -7,9 +7,15 @@ class ApiService {
static const String gejalaUrl = 'http://localhost:5000/api/gejala';
static const String hamaUrl = 'http://localhost:5000/api/hama';
static const String penyakitUrl = 'http://localhost:5000/api/penyakit';
static const String rulesPenyakitUrl =
'http://localhost:5000/api/rules_penyakit';
static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama';
// Fungsi Login (dengan perbaikan)
static Future<Map<String, dynamic>> loginUser(String email, String password) async {
// Fungsi Login (dengan perbaikan)
static Future<Map<String, dynamic>> loginUser(
String email,
String password,
) async {
try {
final response = await http.post(
Uri.parse("$baseUrl/login"),
@ -114,31 +120,38 @@ class ApiService {
// Ambil semua hama
Future<List<Map<String, dynamic>>> getHama() async {
try {
final response = await http.get(Uri.parse(ApiService.hamaUrl));
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
try {
final response = await http.get(Uri.parse(ApiService.hamaUrl));
// Pastikan "data" ada dan berupa List
if (responseData is Map<String, dynamic> && responseData.containsKey("data")) {
final List<dynamic> data = responseData["data"];
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
return List<Map<String, dynamic>>.from(data.map((item) => Map<String, dynamic>.from(item)));
// Pastikan "data" ada dan berupa List
if (responseData is Map<String, dynamic> &&
responseData.containsKey("data")) {
final List<dynamic> data = responseData["data"];
return List<Map<String, dynamic>>.from(
data.map((item) => Map<String, dynamic>.from(item)),
);
} else {
throw Exception("Format respons API tidak sesuai");
}
} else {
throw Exception("Format respons API tidak sesuai");
throw Exception("Gagal mengambil data hama");
}
} else {
} catch (e) {
print("Error getHama: $e");
throw Exception("Gagal mengambil data hama");
}
} catch (e) {
print("Error getHama: $e");
throw Exception("Gagal mengambil data hama");
}
}
// Tambah hama baru (kode otomatis)
Future<Map<String, dynamic>> createHama(String nama, String deskripsi, String penanganan) async {
Future<Map<String, dynamic>> createHama(
String nama,
String deskripsi,
String penanganan,
) async {
try {
final response = await http.post(
Uri.parse(hamaUrl),
@ -146,7 +159,7 @@ class ApiService {
body: jsonEncode({
"nama": nama,
"deskripsi": deskripsi,
"penanganan": penanganan
"penanganan": penanganan,
}),
);
@ -161,15 +174,21 @@ class ApiService {
}
}
// Update hama berdasarkan ID
Future<Map<String, dynamic>> updateHama(int id, String nama, String deskripsi, String penanganan) async {
// Update hama berdasarkan ID
Future<Map<String, dynamic>> updateHama(
int id,
String nama,
String deskripsi,
String penanganan,
) async {
try {
final response = await http.put(Uri.parse('$hamaUrl/$id'),
final response = await http.put(
Uri.parse('$hamaUrl/$id'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"nama": nama,
"deskripsi": deskripsi,
"penanganan": penanganan
"penanganan": penanganan,
}),
);
@ -199,39 +218,46 @@ class ApiService {
// Ambil semua penyakit
Future<List<Map<String, dynamic>>> getPenyakit() async {
try {
final response = await http.get(Uri.parse(ApiService.penyakitUrl));
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
try {
final response = await http.get(Uri.parse(ApiService.penyakitUrl));
// Pastikan "data" ada dan berupa List
if (responseData is Map<String, dynamic> && responseData.containsKey("data")) {
final List<dynamic> data = responseData["data"];
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
return List<Map<String, dynamic>>.from(data.map((item) => Map<String, dynamic>.from(item)));
// Pastikan "data" ada dan berupa List
if (responseData is Map<String, dynamic> &&
responseData.containsKey("data")) {
final List<dynamic> data = responseData["data"];
return List<Map<String, dynamic>>.from(
data.map((item) => Map<String, dynamic>.from(item)),
);
} else {
throw Exception("Format respons API tidak sesuai");
}
} else {
throw Exception("Format respons API tidak sesuai");
throw Exception("Gagal mengambil data penyakit");
}
} else {
} catch (e) {
print("Error getHama: $e");
throw Exception("Gagal mengambil data penyakit");
}
} catch (e) {
print("Error getHama: $e");
throw Exception("Gagal mengambil data penyakit");
}
}
// Tambah penyakit baru (kode otomatis)
Future<Map<String, dynamic>> createPenyakit(String nama, String deskripsi, String penanganan) async {
Future<Map<String, dynamic>> createPenyakit(
String nama,
String deskripsi,
String penanganan,
) async {
try {
final response = await http.post(
Uri.parse(penyakitUrl),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"nama": nama,
"deskripsi": deskripsi,
"penanganan": penanganan
"deskripsi": deskripsi,
"penanganan": penanganan,
}),
);
@ -246,15 +272,21 @@ class ApiService {
}
}
// Update penyakit berdasarkan ID
Future<Map<String, dynamic>> updatePenyakit(int id, String nama, String deskripsi, String penanganan) async {
// Update penyakit berdasarkan ID
Future<Map<String, dynamic>> updatePenyakit(
int id,
String nama,
String deskripsi,
String penanganan,
) async {
try {
final response = await http.put(Uri.parse('$penyakitUrl/$id'),
final response = await http.put(
Uri.parse('$penyakitUrl/$id'),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"nama": nama,
"deskripsi": deskripsi,
"penanganan": penanganan
"penanganan": penanganan,
}),
);
@ -282,7 +314,7 @@ class ApiService {
}
}
//registrasi
//registrasi
Future<void> registerUser({
required String name,
required String email,
@ -304,7 +336,9 @@ class ApiService {
);
if (response.statusCode != 201) {
throw Exception(jsonDecode(response.body)['message'] ?? 'Gagal mendaftar');
throw Exception(
jsonDecode(response.body)['message'] ?? 'Gagal mendaftar',
);
}
}
@ -323,9 +357,151 @@ class ApiService {
);
if (response.statusCode != 200) {
throw Exception(jsonDecode(response.body)['message'] ?? 'Gagal memperbarui password');
throw Exception(
jsonDecode(response.body)['message'] ?? 'Gagal memperbarui password',
);
}
}
}
// Create Rule penyakit
static Future<http.Response> createRulePenyakit({
required int idGejala,
int? idPenyakit,
required double nilaiPakar,
}) async {
final response = await http.post(
Uri.parse('$rulesPenyakitUrl'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'id_gejala': idGejala,
'id_penyakit': idPenyakit,
'nilai_pakar': nilaiPakar,
}),
);
return response;
}
//get all rules penyakit
Future<List<dynamic>> getRulesPenyakit() async {
final response = await http.get(Uri.parse(rulesPenyakitUrl));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['data'] == null) {
throw Exception('Data rules kosong');
}
return data['data'];
} else {
throw Exception('Gagal mengambil data rules: ${response.statusCode}');
}
}
// Update Rule penyakit
static Future<http.Response> updateRulePenyakit({
required int id,
required int idGejala,
int? idPenyakit,
required double nilaiPakar,
}) async {
final response = await http.put(
Uri.parse('$rulesPenyakitUrl/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'id_gejala': idGejala,
'id_penyakit': idPenyakit,
'nilai_pakar': nilaiPakar,
}),
);
return response;
}
// Delete Rule penyakit
static Future<http.Response> deleteRulePenyakit(int id) async {
final response = await http.delete(
Uri.parse('$rulesPenyakitUrl/$id'),
);
return response;
}
// Create Rule Hama
static Future<http.Response> createRuleHama({
required int idGejala,
int? idHama,
required double nilaiPakar,
}) async {
try {
// Mencetak URL untuk debugging
print("URL API: $rulesHamaUrl");
// Kirim request POST ke server
final response = await http.post(
Uri.parse('$rulesHamaUrl'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'id_gejala': idGejala,
'id_hama': idHama,
'nilai_pakar': nilaiPakar,
}),
);
// Pengecekan status response
if (response.statusCode == 200 || response.statusCode == 201) {
// Jika berhasil, kembalikan response
return response;
} else {
// Jika gagal, cetak error dan lempar exception
print("Gagal: ${response.statusCode} - ${response.body}");
throw Exception('Gagal menyimpan rule hama. ${response.body}');
}
} catch (e) {
print('Error: $e');
rethrow; // Rethrow exception agar bisa ditangani di tempat lain
}
}
//get all rules hama
Future<List<dynamic>> getRulesHama() async {
final response = await http.get(Uri.parse(rulesHamaUrl));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data['data'] == null) {
throw Exception('Data rules kosong');
}
return data['data'];
} else {
throw Exception('Gagal mengambil data rules: ${response.statusCode}');
}
}
// Update Rule hama
static Future<http.Response> updateRuleHama({
required int id,
required int idGejala,
int? idHama,
required double nilaiPakar,
}) async {
final response = await http.put(
Uri.parse('$rulesHamaUrl/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'id_gejala': idGejala,
'id_hama': idHama,
'nilai_pakar': nilaiPakar,
}),
);
return response;
}
// Delete Rule hama
static Future<http.Response> deleteRuleHama(int id) async {
final response = await http.delete(
Uri.parse('$rulesHamaUrl/$id'),
);
return response;
}
}