feat: fungsi rule

This commit is contained in:
unknown 2025-04-29 01:56:27 +07:00
parent accd4ab167
commit 4271210c03
13 changed files with 552 additions and 238 deletions

View File

@ -8,6 +8,7 @@ const authRoutes = require('./routes/authRoutes');
const gejalaRoutes = require('./routes/gejalaRoute');
const hamaRoutes = require('./routes/hamaRoutes');
const penyakitRoutes = require('./routes/penyakitRoutes');
const ruleRoutes = require('./routes/ruleRoutes');
const swaggerDocs = require('./swagger');
dotenv.config();
@ -24,6 +25,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);
// Swagger Documentation
swaggerDocs(app); // Setup Swagger UI documentation

View File

@ -0,0 +1,81 @@
const { Rule, gejala, penyakit, hama } = require('../models');
// 🔥 Buat aturan baru
exports.createRule = async (req, res) => {
try {
const { id_gejala, id_penyakit, id_hama, nilai_pakar } = req.body;
// Validasi minimal
if (!id_gejala || (!id_penyakit && !id_hama) || !nilai_pakar) {
return res.status(400).json({ message: 'Data tidak lengkap' });
}
const newRule = await Rule.create({
id_gejala,
id_penyakit,
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.getRules = async (req, res) => {
try {
const rules = await Rule.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.updateRule = async (req, res) => {
try {
const { id } = req.params;
const { id_gejala, id_penyakit, id_hama, nilai_pakar } = req.body;
const rule = await Rule.findByPk(id);
if (!rule) {
return res.status(404).json({ message: 'Rule tidak ditemukan' });
}
await rule.update({
id_gejala,
id_penyakit,
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.deleteRule = async (req, res) => {
try {
const { id } = req.params;
const rule = await Rule.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

@ -0,0 +1,42 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Rules', {
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,
allowNull: false
},
id_penyakit: {
type: Sequelize.INTEGER,
references:{
model: 'penyakit',
key:'id'
}
},
id_hama: {
type: Sequelize.INTEGER,
references:{
model: 'hama',
key: 'id'
}
},
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('Rules');
}
};

View File

@ -1,6 +1,6 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Gejala extends Model {}
Gejala.init(
@ -29,4 +29,5 @@ Gejala.init(
}
);
module.exports = Gejala;
return Gejala; // Pastikan model dikembalikan dengan benar
};

View File

@ -1,6 +1,7 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/database');
module.exports = (sequelize) => {
class Hama extends Model {}
Hama.init(
@ -40,5 +41,5 @@ Hama.init(
paranoid: false, // Jika menggunakan soft delete, ubah ke true
}
);
module.exports = Hama;
return Hama;
}

View File

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

70
backend/models/rule.js Normal file
View File

@ -0,0 +1,70 @@
const { Model, DataTypes } = require('sequelize');
module.exports = (sequelize) => {
class Rule extends Model {
static associate(models) {
// Asosiasi dengan model Gejala
Rule.belongsTo(models.Gejala, {
foreignKey: 'id_gejala',
as: 'gejala', // Nama asosiasi yang bisa digunakan saat melakukan query
});
// Asosiasi dengan model Penyakit
Rule.belongsTo(models.Penyakit, {
foreignKey: 'id_penyakit',
as: 'penyakit',
});
// Asosiasi dengan model Hama
Rule.belongsTo(models.Hama, {
foreignKey: 'id_hama',
as: 'hama',
});
}
}
Rule.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_penyakit: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'penyakit', // Mengacu ke tabel 'penyakit'
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',
timestamps: false,
paranoid: false,
}
);
return Rule;
}

View File

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

View File

@ -0,0 +1,123 @@
const express = require('express');
const router = express.Router();
const ruleController = require('../controller/rulesController');
/**
* @swagger
* tags:
* name: Rules
* description: API untuk mengelola aturan (rules)
*/
/**
* @swagger
* /api/rules:
* post:
* summary: Membuat aturan baru
* tags: [Rules]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - id_gejala
* - nilai_pakar
* properties:
* id_gejala:
* type: integer
* id_penyakit:
* 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.createRule);
/**
* @swagger
* /api/rules:
* get:
* summary: Menampilkan semua aturan
* tags: [Rules]
* responses:
* 200:
* description: Daftar rules berhasil diambil
* 500:
* description: Terjadi kesalahan server
*/
router.get('/', ruleController.getRules);
/**
* @swagger
* /api/rules/{id}:
* put:
* summary: Memperbarui aturan berdasarkan ID
* tags: [Rules]
* 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_penyakit:
* 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.updateRule);
/**
* @swagger
* /api/rules/{id}:
* delete:
* summary: Menghapus aturan berdasarkan ID
* tags: [Rules]
* 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.deleteRule);
module.exports = router;

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
class DetailHamaPage extends StatelessWidget {
final Map<String, String> detailRiwayat;
final Map<String, dynamic> detailHama;
const DetailHamaPage({required this.detailRiwayat});
const DetailHamaPage({required this.detailHama});
@override
Widget build(BuildContext context) {
@ -25,11 +25,11 @@ class DetailHamaPage extends StatelessWidget {
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
if (detailRiwayat["gambar"] != null && detailRiwayat["gambar"]!.isNotEmpty)
if (detailHama["gambar"] != null && detailHama["gambar"]!.isNotEmpty)
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.asset(
detailRiwayat["gambar"]!,
detailHama["gambar"]!,
height: 200,
width: 200, // Biar gambar full lebar
fit: BoxFit.cover,
@ -54,7 +54,7 @@ class DetailHamaPage extends StatelessWidget {
),
SizedBox(height: 8),
Text(
detailRiwayat["nama hama"] ?? "Nama hama tidak tersedia",
detailHama["nama"] ?? "Nama hama tidak tersedia",
style: TextStyle(fontSize: 16),
),
],
@ -81,7 +81,7 @@ class DetailHamaPage extends StatelessWidget {
),
SizedBox(height: 8),
Text(
detailRiwayat["deskripsi"] ?? "Deskripsi tidak tersedia",
detailHama["deskripsi"] ?? "Deskripsi tidak tersedia",
style: TextStyle(fontSize: 16),
),
SizedBox(height: 16),
@ -91,7 +91,7 @@ class DetailHamaPage extends StatelessWidget {
),
SizedBox(height: 8),
Text(
detailRiwayat["penanganan"] ?? "Penanganan tidak tersedia",
detailHama["penanganan"] ?? "Penanganan tidak tersedia",
style: TextStyle(fontSize: 16),
),
],

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
class DetailPenyakitPage extends StatelessWidget {
final Map<String, String> detailPenyakit;
final Map<String, dynamic> detailPenyakit;
const DetailPenyakitPage({required this.detailPenyakit});
@ -54,7 +54,7 @@ class DetailPenyakitPage extends StatelessWidget {
),
SizedBox(height: 8),
Text(
detailPenyakit["nama penyakit"] ?? "Nama penyakit tidak tersedia",
detailPenyakit["nama"] ?? "Nama penyakit tidak tersedia",
style: TextStyle(fontSize: 16),
),
],

View File

@ -1,33 +1,20 @@
import 'package:flutter/material.dart';
import 'detail_hama_page.dart';
import 'package:frontend/api_services/api_services.dart';
class HamaPage extends StatelessWidget {
final List<Map<String, String>> hamaList = [
{
"nama hama": "Karat Putih",
"deskripsi": "Penyakit yang umum pada bayam.",
"penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.",
"gambar": "assets/images/karat putih.jpeg",
},
{
"nama hama": "Virus Keriting",
"deskripsi": "Disebabkan oleh infeksi virus.",
"penanganan": "Musnahkan tanaman terinfeksi dan kontrol vektor seperti kutu daun.",
"gambar": "assets/images/virus_keriting.jpeg",
},
{
"nama hama": "Kekurangan Mangan",
"deskripsi": "Kekurangan unsur hara mikro.",
"penanganan": "Tambahkan pupuk yang mengandung mangan (Mn).",
"gambar": "assets/images/kekurangan_mangan.jpeg",
},
{
"nama hama": "Downy Mildew",
"deskripsi": "Penyakit jamur pada bayam.",
"penanganan": "Gunakan fungisida berbahan aktif metalaxyl dan perbaiki drainase tanah.",
"gambar": "assets/images/downy_mildew.jpeg",
},
];
class HamaPage extends StatefulWidget {
@override
_HamaPageState createState() => _HamaPageState();
}
class _HamaPageState extends State<HamaPage> {
late Future<List<Map<String, dynamic>>> _hamaListFuture;
@override
void initState() {
super.initState();
_hamaListFuture = ApiService().getHama();
}
@override
Widget build(BuildContext context) {
@ -52,11 +39,19 @@ class HamaPage extends StatelessWidget {
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
SizedBox(height: 16),
Expanded(
child: ListView.builder(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _hamaListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator(color: Colors.white));
} else if (snapshot.hasError) {
return Center(child: Text('Gagal memuat data', style: TextStyle(color: Colors.white)));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('Tidak ada data tersedia', style: TextStyle(color: Colors.white)));
} else {
final hamaList = snapshot.data!;
return ListView.builder(
itemCount: hamaList.length,
itemBuilder: (context, index) {
final diagnosa = hamaList[index];
@ -65,7 +60,7 @@ class HamaPage extends StatelessWidget {
margin: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
title: Text(
diagnosa["nama hama"] ?? "Tidak ada data",
diagnosa["nama"] ?? "Tidak ada data",
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"),
@ -73,16 +68,16 @@ class HamaPage extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailHamaPage(detailRiwayat: diagnosa),
builder: (context) => DetailHamaPage(detailHama: diagnosa),
),
);
},
),
);
},
),
),
],
);
}
},
),
),
);

View File

@ -1,33 +1,20 @@
import 'package:flutter/material.dart';
import 'detail_penyakit_page.dart';
import 'package:frontend/api_services/api_services.dart';
class PenyakitPage extends StatelessWidget {
final List<Map<String, String>> penyakitList= [
{
"nama penyakit": "Karat Putih",
"deskripsi": "Penyakit yang umum pada bayam.",
"penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.",
"gambar": "assets/images/karat putih.jpeg",
},
{
"nama penyakit": "Virus Keriting",
"deskripsi": "Disebabkan oleh infeksi virus.",
"penanganan": "Musnahkan tanaman terinfeksi dan kontrol vektor seperti kutu daun.",
"gambar": "assets/images/virus_keriting.jpeg",
},
{
"nama penyakit": "Kekurangan Mangan",
"deskripsi": "Kekurangan unsur hara mikro.",
"penanganan": "Tambahkan pupuk yang mengandung mangan (Mn).",
"gambar": "assets/images/kekurangan_mangan.jpeg",
},
{
"nama penyakit": "Downy Mildew",
"deskripsi": "Penyakit jamur pada bayam.",
"penanganan": "Gunakan fungisida berbahan aktif metalaxyl dan perbaiki drainase tanah.",
"gambar": "assets/images/downy_mildew.jpeg",
},
];
class PenyakitPage extends StatefulWidget {
@override
_PenyakitPageState createState() => _PenyakitPageState();
}
class _PenyakitPageState extends State<PenyakitPage> {
late Future<List<Map<String, dynamic>>> _penyakitListFuture;
@override
void initState() {
super.initState();
_penyakitListFuture = ApiService().getPenyakit();
}
@override
Widget build(BuildContext context) {
@ -52,37 +39,45 @@ class PenyakitPage extends StatelessWidget {
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
SizedBox(height: 16),
Expanded(
child: ListView.builder(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _penyakitListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator(color: Colors.white));
} else if (snapshot.hasError) {
return Center(child: Text('Gagal memuat data', style: TextStyle(color: Colors.white)));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('Tidak ada data tersedia', style: TextStyle(color: Colors.white)));
} else {
final penyakitList = snapshot.data!;
return ListView.builder(
itemCount: penyakitList.length,
itemBuilder: (context, index) {
final diagnosa = penyakitList[index];
final penyakit = penyakitList[index];
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
title: Text(
diagnosa["nama penyakit"] ?? "Tidak ada data",
penyakit["nama"] ?? "Tidak ada data",
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"),
subtitle: Text(penyakit["deskripsi"] ?? "Deskripsi tidak tersedia"),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPenyakitPage(detailPenyakit: diagnosa),
builder: (context) => DetailPenyakitPage(detailPenyakit: penyakit),
),
);
},
),
);
},
),
),
],
);
}
},
),
),
);