fix: halaman hasiil diagnosa dan tampil data di profile
|
@ -10,6 +10,7 @@ const hamaRoutes = require('./routes/hamaRoutes');
|
|||
const penyakitRoutes = require('./routes/penyakitRoutes');
|
||||
const ruleRoutes = require('./routes/ruleRoutes');
|
||||
const ruleHamaRoutes = require('./routes/ruleHamaRoutes');
|
||||
const diagnosaRoute = require('./routes/diagnosaRoutes');
|
||||
const swaggerDocs = require('./swagger');
|
||||
|
||||
dotenv.config();
|
||||
|
@ -33,6 +34,7 @@ app.use("/api/hama", hamaRoutes);
|
|||
app.use("/api/penyakit", penyakitRoutes);
|
||||
app.use("/api/rules_penyakit", ruleRoutes);
|
||||
app.use("/api/rules_hama", ruleHamaRoutes);
|
||||
app.use("/api/diagnosa", diagnosaRoute);
|
||||
|
||||
|
||||
// Swagger Documentation
|
||||
|
|
|
@ -8,95 +8,217 @@ exports.diagnosa = async (req, res) => {
|
|||
}
|
||||
|
||||
try {
|
||||
// ===================== Penyakit =====================
|
||||
// Mengambil semua data yang dibutuhkan sekaligus
|
||||
const allGejala = await Gejala.findAll({
|
||||
where: { id: gejala }
|
||||
});
|
||||
|
||||
// ========== HITUNG TOTAL P(E) UNTUK SEMUA GEJALA ==========
|
||||
// P(E) seharusnya sama untuk semua penyakit dan hama yang memiliki gejala yang sama
|
||||
|
||||
// Object untuk menyimpan P(E) untuk setiap gejala
|
||||
const evidenceProbabilities = {};
|
||||
|
||||
// Hitung P(E) untuk PENYAKIT
|
||||
for (const idGejala of gejala) {
|
||||
// Dapatkan semua rule untuk gejala ini di semua penyakit
|
||||
const penyakitRulesForGejala = await Rule_penyakit.findAll({
|
||||
where: { id_gejala: idGejala },
|
||||
include: [{ model: Penyakit, as: 'penyakit' }]
|
||||
});
|
||||
|
||||
let evidenceProbForGejala = 0;
|
||||
|
||||
// Hitung P(E) = Σ [P(E|Hi) * P(Hi)] untuk penyakit
|
||||
for (const rule of penyakitRulesForGejala) {
|
||||
const pHi = rule.penyakit.nilai_pakar; // P(Hi)
|
||||
const pEgivenHi = rule.nilai_pakar; // P(E|Hi)
|
||||
|
||||
evidenceProbForGejala += pEgivenHi * pHi;
|
||||
}
|
||||
|
||||
// Dapatkan semua rule untuk gejala ini di semua hama
|
||||
const hamaRulesForGejala = await Rule_hama.findAll({
|
||||
where: { id_gejala: idGejala },
|
||||
include: [{ model: Hama, as: 'hama' }]
|
||||
});
|
||||
|
||||
// Hitung P(E) = Σ [P(E|Hi) * P(Hi)] untuk hama
|
||||
for (const rule of hamaRulesForGejala) {
|
||||
const pHi = rule.hama.nilai_pakar; // P(Hi)
|
||||
const pEgivenHi = rule.nilai_pakar; // P(E|Hi)
|
||||
|
||||
evidenceProbForGejala += pEgivenHi * pHi;
|
||||
}
|
||||
|
||||
// Simpan P(E) untuk gejala ini
|
||||
evidenceProbabilities[idGejala] = evidenceProbForGejala;
|
||||
}
|
||||
|
||||
// Hitung total P(E) untuk semua gejala yang diinput
|
||||
let totalEvidenceProbability = 0;
|
||||
for (const idGejala of gejala) {
|
||||
totalEvidenceProbability += evidenceProbabilities[idGejala] || 0;
|
||||
}
|
||||
|
||||
// Pastikan total P(E) tidak nol untuk menghindari division by zero
|
||||
if (totalEvidenceProbability === 0) {
|
||||
totalEvidenceProbability = 1.0;
|
||||
}
|
||||
|
||||
// ========== PENYAKIT ==========
|
||||
const allPenyakitRules = await Rule_penyakit.findAll({
|
||||
where: {
|
||||
id_gejala: gejala,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Penyakit,
|
||||
as: 'penyakit',
|
||||
},
|
||||
],
|
||||
where: { id_gejala: gejala },
|
||||
include: [{ model: Penyakit, as: 'penyakit' }]
|
||||
});
|
||||
|
||||
const penyakitScores = {};
|
||||
// Mendapatkan semua penyakit unik yang memiliki gejala yang dipilih
|
||||
const uniquePenyakitIds = [...new Set(allPenyakitRules.map(rule => rule.id_penyakit))];
|
||||
|
||||
allPenyakitRules.forEach(rule => {
|
||||
const idPenyakit = rule.id_penyakit;
|
||||
const nilaiPakarGejala = rule.nilai_pakar; // P(E|H)
|
||||
const nilaiPakarPenyakit = rule.penyakit.nilai_pakar; // P(H)
|
||||
// Hasil perhitungan untuk setiap penyakit
|
||||
const hasilPenyakit = [];
|
||||
|
||||
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;
|
||||
// Hitung untuk setiap penyakit
|
||||
for (const idPenyakit of uniquePenyakitIds) {
|
||||
// Filter rules yang berhubungan dengan penyakit ini
|
||||
const penyakitRules = allPenyakitRules.filter(rule => rule.id_penyakit === idPenyakit);
|
||||
|
||||
if (penyakitRules.length > 0) {
|
||||
const dataPenyakit = penyakitRules[0].penyakit;
|
||||
const namaPenyakit = dataPenyakit.nama;
|
||||
const priorProbability = dataPenyakit.nilai_pakar; // P(H) - prior probability penyakit
|
||||
|
||||
// Menghitung P(E|H) untuk setiap gejala
|
||||
const evidenceGivenHypothesis = {};
|
||||
for (const rule of penyakitRules) {
|
||||
evidenceGivenHypothesis[rule.id_gejala] = rule.nilai_pakar; // P(E|H) untuk setiap gejala
|
||||
}
|
||||
});
|
||||
|
||||
// ===================== Hama =====================
|
||||
// Menghitung P(H|E) = [P(E|H) * P(H)] / P(E) untuk semua gejala
|
||||
let posteriorNumerator = priorProbability; // Inisialisasi dengan P(H)
|
||||
const evidencesUsed = [];
|
||||
|
||||
// Mengalikan dengan nilai P(E|H) untuk setiap gejala yang ada
|
||||
for (const idGejala of gejala) {
|
||||
if (evidenceGivenHypothesis[idGejala]) {
|
||||
posteriorNumerator *= evidenceGivenHypothesis[idGejala];
|
||||
evidencesUsed.push({
|
||||
id_gejala: parseInt(idGejala),
|
||||
P_E_given_H: evidenceGivenHypothesis[idGejala],
|
||||
nilai_P_E: evidenceProbabilities[idGejala] || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Posterior probability adalah P(H|E)
|
||||
const posteriorProbability = posteriorNumerator / totalEvidenceProbability;
|
||||
|
||||
hasilPenyakit.push({
|
||||
id_penyakit: idPenyakit,
|
||||
nama: namaPenyakit,
|
||||
P_H: priorProbability,
|
||||
P_E: totalEvidenceProbability,
|
||||
evidences: evidencesUsed,
|
||||
posterior_numerator: posteriorNumerator,
|
||||
posterior_probability: posteriorProbability,
|
||||
probabilitas: posteriorProbability
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== HAMA ==========
|
||||
const allHamaRules = await Rule_hama.findAll({
|
||||
where: {
|
||||
id_gejala: gejala,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Hama,
|
||||
as: 'hama',
|
||||
},
|
||||
],
|
||||
where: { id_gejala: gejala },
|
||||
include: [{ model: Hama, as: 'hama' }]
|
||||
});
|
||||
|
||||
const hamaScores = {};
|
||||
// Mendapatkan semua hama unik yang memiliki gejala yang dipilih
|
||||
const uniqueHamaIds = [...new Set(allHamaRules.map(rule => rule.id_hama))];
|
||||
|
||||
allHamaRules.forEach(rule => {
|
||||
const idHama = rule.id_hama;
|
||||
const nilaiPakarGejala = rule.nilai_pakar; // P(E|H)
|
||||
const nilaiPakarHama = rule.hama.nilai_pakar; // P(H)
|
||||
// Hasil perhitungan untuk setiap hama
|
||||
const hasilHama = [];
|
||||
|
||||
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;
|
||||
// Hitung untuk setiap hama
|
||||
for (const idHama of uniqueHamaIds) {
|
||||
// Filter rules yang berhubungan dengan hama ini
|
||||
const hamaRules = allHamaRules.filter(rule => rule.id_hama === idHama);
|
||||
|
||||
if (hamaRules.length > 0) {
|
||||
const dataHama = hamaRules[0].hama;
|
||||
const namaHama = dataHama.nama;
|
||||
const priorProbability = dataHama.nilai_pakar; // P(H) - prior probability hama
|
||||
|
||||
// Menghitung P(E|H) untuk setiap gejala
|
||||
const evidenceGivenHypothesis = {};
|
||||
for (const rule of hamaRules) {
|
||||
evidenceGivenHypothesis[rule.id_gejala] = rule.nilai_pakar; // P(E|H) untuk setiap gejala
|
||||
}
|
||||
|
||||
// Menghitung P(H|E) = [P(E|H) * P(H)] / P(E) untuk semua gejala
|
||||
let posteriorNumerator = priorProbability; // Inisialisasi dengan P(H)
|
||||
const evidencesUsed = [];
|
||||
|
||||
// Mengalikan dengan nilai P(E|H) untuk setiap gejala yang ada
|
||||
for (const idGejala of gejala) {
|
||||
if (evidenceGivenHypothesis[idGejala]) {
|
||||
posteriorNumerator *= evidenceGivenHypothesis[idGejala];
|
||||
evidencesUsed.push({
|
||||
id_gejala: parseInt(idGejala),
|
||||
P_E_given_H: evidenceGivenHypothesis[idGejala],
|
||||
nilai_P_E: evidenceProbabilities[idGejala] || 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Posterior probability adalah P(H|E)
|
||||
const posteriorProbability = posteriorNumerator / totalEvidenceProbability;
|
||||
|
||||
hasilHama.push({
|
||||
id_hama: idHama,
|
||||
nama: namaHama,
|
||||
P_H: priorProbability,
|
||||
P_E: totalEvidenceProbability,
|
||||
evidences: evidencesUsed,
|
||||
posterior_numerator: posteriorNumerator,
|
||||
posterior_probability: posteriorProbability,
|
||||
probabilitas: posteriorProbability
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Urutkan hasil berdasarkan probabilitas
|
||||
const sortedPenyakit = hasilPenyakit.sort((a, b) => b.probabilitas - a.probabilitas);
|
||||
const sortedHama = hasilHama.sort((a, b) => b.probabilitas - a.probabilitas);
|
||||
|
||||
// Buat ringkasan gejala yang dimasukkan
|
||||
const gejalaSummary = await Gejala.findAll({
|
||||
where: { id: gejala },
|
||||
attributes: ['id', 'kode', 'nama']
|
||||
});
|
||||
|
||||
// ===================== 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);
|
||||
|
||||
// Kirim hasil perhitungan sebagai respons
|
||||
res.json({
|
||||
input_gejala: gejalaSummary,
|
||||
total_evidence_probability: totalEvidenceProbability,
|
||||
evidence_per_gejala: evidenceProbabilities,
|
||||
penyakit: sortedPenyakit,
|
||||
hama: sortedHama,
|
||||
detail_perhitungan: {
|
||||
keterangan: "Menggunakan teorema Bayes: P(H|E) = [P(E|H) * P(H)] / P(E)",
|
||||
formula: {
|
||||
P_H: "Prior probability (nilai pakar untuk penyakit/hama)",
|
||||
P_E_given_H: "Likelihood (nilai pakar untuk gejala terhadap penyakit/hama)",
|
||||
P_E: "Evidence probability = Σ [P(E|Hi) * P(Hi)] untuk semua hipotesis",
|
||||
P_H_given_E: "Posterior probability (hasil akhir)"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error dalam perhitungan Bayes:', error);
|
||||
res.status(500).json({ message: 'Terjadi kesalahan dalam proses diagnosa' });
|
||||
res.status(500).json({
|
||||
message: 'Terjadi kesalahan dalam proses diagnosa',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -16,7 +16,6 @@ class Rule_hama extends Model {
|
|||
foreignKey: 'id_hama',
|
||||
as: 'hama',
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const diagnosaController = require('../controller/diagnosaController');
|
||||
const { diagnosa } = require('../controller/diagnosaController');
|
||||
console.log('Diagnosa function:', diagnosa);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/diagnosa/bayes:
|
||||
* /api/diagnosa:
|
||||
* post:
|
||||
* summary: Melakukan diagnosa penyakit dan hama menggunakan Teorema Bayes
|
||||
* tags: [Diagnosa]
|
||||
|
@ -28,15 +29,129 @@ const diagnosaController = require('../controller/diagnosaController');
|
|||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* input_gejala:
|
||||
* type: array
|
||||
* description: Daftar gejala yang diinputkan user
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* kode:
|
||||
* type: string
|
||||
* nama:
|
||||
* type: string
|
||||
* hasil_gabungan:
|
||||
* type: array
|
||||
* description: Hasil gabungan hama dan penyakit berdasarkan probabilitas tertinggi
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [penyakit, hama]
|
||||
* nama:
|
||||
* type: string
|
||||
* probabilitas:
|
||||
* type: number
|
||||
* P_H:
|
||||
* type: number
|
||||
* description: Prior probability
|
||||
* P_E:
|
||||
* type: number
|
||||
* description: Evidence probability
|
||||
* penyakit:
|
||||
* type: array
|
||||
* description: Hasil diagnosa penyakit dengan perhitungan Bayes
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id_penyakit:
|
||||
* type: integer
|
||||
* nama:
|
||||
* type: string
|
||||
* P_H:
|
||||
* type: number
|
||||
* description: Prior probability penyakit
|
||||
* P_E:
|
||||
* type: number
|
||||
* description: Evidence probability
|
||||
* evidences:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id_gejala:
|
||||
* type: integer
|
||||
* P_E_given_H:
|
||||
* type: number
|
||||
* description: Likelihood (nilai pakar gejala)
|
||||
* posterior_numerator:
|
||||
* type: number
|
||||
* description: Hasil perkalian P(E|H) * P(H)
|
||||
* posterior_probability:
|
||||
* type: number
|
||||
* description: P(H|E) - Hasil akhir teorema Bayes
|
||||
* probabilitas:
|
||||
* type: number
|
||||
* description: Nilai probabilitas untuk kompatibilitas
|
||||
* hama:
|
||||
* type: array
|
||||
* description: Hasil diagnosa hama dengan perhitungan Bayes
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id_hama:
|
||||
* type: integer
|
||||
* nama:
|
||||
* type: string
|
||||
* P_H:
|
||||
* type: number
|
||||
* description: Prior probability hama
|
||||
* P_E:
|
||||
* type: number
|
||||
* description: Evidence probability
|
||||
* evidences:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id_gejala:
|
||||
* type: integer
|
||||
* P_E_given_H:
|
||||
* type: number
|
||||
* description: Likelihood (nilai pakar gejala)
|
||||
* posterior_numerator:
|
||||
* type: number
|
||||
* description: Hasil perkalian P(E|H) * P(H)
|
||||
* posterior_probability:
|
||||
* type: number
|
||||
* description: P(H|E) - Hasil akhir teorema Bayes
|
||||
* probabilitas:
|
||||
* type: number
|
||||
* description: Nilai probabilitas untuk kompatibilitas
|
||||
* detail_perhitungan:
|
||||
* type: object
|
||||
* description: Informasi tentang metode perhitungan yang digunakan
|
||||
* properties:
|
||||
* keterangan:
|
||||
* type: string
|
||||
* formula:
|
||||
* type: object
|
||||
* properties:
|
||||
* P_H:
|
||||
* type: string
|
||||
* P_E_given_H:
|
||||
* type: string
|
||||
* P_E:
|
||||
* type: string
|
||||
* P_H_given_E:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: Permintaan tidak valid
|
||||
* 500:
|
||||
* description: Terjadi kesalahan pada server
|
||||
*/
|
||||
router.post('/bayes', diagnosaController.diagnosaBayes);
|
||||
router.post('/', diagnosa);
|
||||
|
||||
module.exports = router;
|
|
@ -20,6 +20,10 @@ class _RulePageState extends State<RulePage> {
|
|||
List<dynamic> rules = [];
|
||||
bool isLoading = true;
|
||||
|
||||
// Pagination variables
|
||||
int currentPage = 0;
|
||||
int rowsPerPage = 10;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -61,23 +65,16 @@ class _RulePageState extends State<RulePage> {
|
|||
'nama_gejala': gejala['nama'],
|
||||
'nama_penyakit': penyakit['nama'],
|
||||
'nama_hama': null,
|
||||
'nilai_pakar':
|
||||
rule['nilai_pakar'], // Menambahkan nilai_pakar dari rule_penyakit
|
||||
'nilai_pakar': rule['nilai_pakar'],
|
||||
};
|
||||
}),
|
||||
// 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']}",
|
||||
final gejala = gejalaList.firstWhere(
|
||||
(item) => item['id'] == rule['id_gejala'],
|
||||
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
|
||||
);
|
||||
return item['id'] == rule['id_gejala'];
|
||||
}, orElse: () => {'nama': 'TIDAK DITEMUKAN'});
|
||||
|
||||
// Mencari hama berdasarkan id
|
||||
final hama = hamaList.firstWhere(
|
||||
|
@ -85,31 +82,15 @@ class _RulePageState extends State<RulePage> {
|
|||
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_gejala': gejala['nama'],
|
||||
'nama_penyakit': null,
|
||||
'nama_hama': hama['nama'], // Memastikan nama hama ditampilkan
|
||||
'nilai_pakar':
|
||||
rule['nilai_pakar'], // Menambahkan nilai_pakar dari rule_hama
|
||||
'nama_hama': hama['nama'],
|
||||
'nilai_pakar': rule['nilai_pakar'],
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
@ -119,14 +100,6 @@ class _RulePageState extends State<RulePage> {
|
|||
});
|
||||
} 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;
|
||||
|
@ -140,23 +113,17 @@ class _RulePageState extends State<RulePage> {
|
|||
|
||||
// Tentukan fungsi delete berdasarkan isi rule
|
||||
if (rule['id_hama'] != null) {
|
||||
res = await ApiService.deleteRuleHama(
|
||||
rule['id'],
|
||||
); // Fungsi API untuk delete hama
|
||||
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
|
||||
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)",
|
||||
);
|
||||
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")));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Rule berhasil dihapus"))
|
||||
);
|
||||
fetchRules(); // Refresh data setelah delete
|
||||
} else {
|
||||
throw Exception("Gagal menghapus rule");
|
||||
|
@ -168,6 +135,18 @@ class _RulePageState extends State<RulePage> {
|
|||
}
|
||||
}
|
||||
|
||||
// Get paginated data
|
||||
List<dynamic> get paginatedRules {
|
||||
final startIndex = currentPage * rowsPerPage;
|
||||
final endIndex = startIndex + rowsPerPage > rules.length ? rules.length : startIndex + rowsPerPage;
|
||||
|
||||
if (startIndex >= rules.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return rules.sublist(startIndex, endIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -238,21 +217,31 @@ class _RulePageState extends State<RulePage> {
|
|||
isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: DataTable(
|
||||
headingRowColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
return Color(0xFF9DC08D); // Apply color to all header rows
|
||||
},
|
||||
),
|
||||
columns: const [
|
||||
DataColumn(label: Text('No')),
|
||||
DataColumn(label: Text('Hama / Penyakit')),
|
||||
DataColumn(label: Text('Hama & Penyakit')),
|
||||
DataColumn(label: Text('Gejala')),
|
||||
DataColumn(label: Text('nilai pakar')),
|
||||
DataColumn(label: Text('Nilai Pakar')),
|
||||
DataColumn(label: Text('Aksi')),
|
||||
],
|
||||
rows: List.generate(rules.length, (index) {
|
||||
final rule = rules[index];
|
||||
rows: List.generate(paginatedRules.length, (index) {
|
||||
final rule = paginatedRules[index];
|
||||
final displayIndex = currentPage * rowsPerPage + index + 1;
|
||||
|
||||
final namaKategori =
|
||||
rule['id_penyakit'] != null
|
||||
final namaKategori = rule['id_penyakit'] != null
|
||||
? rule['nama_penyakit'] ?? '-'
|
||||
: rule['nama_hama'] ?? '-';
|
||||
|
||||
|
@ -260,12 +249,10 @@ class _RulePageState extends State<RulePage> {
|
|||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(Text(displayIndex.toString())),
|
||||
DataCell(Text(namaKategori)),
|
||||
DataCell(Text(rule['nama_gejala'] ?? '-')),
|
||||
DataCell(
|
||||
Text(rule['nilai_pakar']?.toString() ?? '-'),
|
||||
),
|
||||
DataCell(Text(rule['nilai_pakar']?.toString() ?? '-')),
|
||||
DataCell(
|
||||
Row(
|
||||
children: [
|
||||
|
@ -288,18 +275,10 @@ class _RulePageState extends State<RulePage> {
|
|||
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?,
|
||||
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,
|
||||
|
@ -309,17 +288,12 @@ class _RulePageState extends State<RulePage> {
|
|||
).then((_) => fetchRules());
|
||||
} else {
|
||||
// Tampilkan pesan error jika data rule tidak lengkap
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Data rule tidak lengkap atau tidak valid",
|
||||
),
|
||||
content: Text("Data rule tidak lengkap atau tidak valid"),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
|
||||
// Debug info
|
||||
print("Rule data: $rule");
|
||||
}
|
||||
|
@ -343,6 +317,32 @@ class _RulePageState extends State<RulePage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Pagination controls
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_left),
|
||||
onPressed: currentPage > 0
|
||||
? () => setState(() => currentPage--)
|
||||
: null,
|
||||
),
|
||||
Text('Halaman ${currentPage + 1} dari ${(rules.length / rowsPerPage).ceil()}'),
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_right),
|
||||
onPressed: (currentPage + 1) * rowsPerPage < rules.length
|
||||
? () => setState(() => currentPage++)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -14,8 +14,47 @@ class ApiService {
|
|||
static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit';
|
||||
static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama';
|
||||
static const String userUrl = 'http://localhost:5000/api/users';
|
||||
static const String diagnosaUrl = 'http://localhost:5000/api/diagnosa';
|
||||
static const Duration timeout = Duration(seconds: 15);
|
||||
|
||||
/// Fungsi untuk mengirim gejala dan menerima hasil diagnosa
|
||||
// Kirim gejala dan dapatkan hasil diagnosa
|
||||
Future<Map<String, dynamic>> diagnosa(List<String> gejalIds) async {
|
||||
// Konversi string ID menjadi integer jika backend Anda membutuhkan integer
|
||||
List<int> gejalaNumerik = [];
|
||||
try {
|
||||
gejalaNumerik = gejalIds.map((id) => int.parse(id)).toList();
|
||||
} catch (e) {
|
||||
print("Error saat konversi ID gejala ke integer: $e");
|
||||
// Jika konversi gagal, gunakan ID string saja
|
||||
final response = await http.post(
|
||||
Uri.parse('$diagnosaUrl'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'gejala': gejalIds}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
throw Exception('Gagal melakukan diagnosa: ${response.statusCode} - ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
// Jika konversi berhasil, gunakan ID numerik
|
||||
final response = await http.post(
|
||||
Uri.parse('$diagnosaUrl'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'gejala': gejalaNumerik}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return json.decode(response.body);
|
||||
} else {
|
||||
throw Exception('Gagal melakukan diagnosa: ${response.statusCode} - ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fungsi Login (dengan perbaikan)
|
||||
static Future<Map<String, dynamic>> loginUser(
|
||||
String email,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'hasil_diagnosa_page.dart';
|
||||
import 'package:frontend/api_services/api_services.dart'; // Import API service untuk ambil data
|
||||
import 'package:frontend/api_services/api_services.dart';
|
||||
|
||||
class DiagnosaPage extends StatefulWidget {
|
||||
@override
|
||||
|
@ -9,50 +9,98 @@ class DiagnosaPage extends StatefulWidget {
|
|||
|
||||
class _DiagnosaPageState extends State<DiagnosaPage> {
|
||||
List<Map<String, dynamic>> gejalaList = [];
|
||||
List<String> gejalaTerpilih = [];
|
||||
List<String> gejalaTerpilihIds = []; // Menyimpan ID gejala
|
||||
List<String> gejalaTerpilihNames = []; // Menyimpan nama gejala untuk tampilan
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchGejala(); // Ambil data dari MySQL saat halaman dibuka
|
||||
fetchGejala();
|
||||
}
|
||||
|
||||
void fetchGejala() async {
|
||||
try {
|
||||
setState(() {
|
||||
isLoading = true; // Tampilkan loading sebelum data diambil
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
List<Map<String, dynamic>> data = await ApiService().getGejala();
|
||||
|
||||
setState(() {
|
||||
gejalaList = data; // Simpan data ke dalam state
|
||||
isLoading = false; // Matikan loading setelah data berhasil diambil
|
||||
gejalaList = data;
|
||||
isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
print("Error mengambil data gejala: $e");
|
||||
setState(() {
|
||||
isLoading = false; // Pastikan loading berhenti meskipun terjadi error
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pilihGejala(String gejala) {
|
||||
void pilihGejala(String gejalaId, String gejalaName) {
|
||||
setState(() {
|
||||
if (!gejalaTerpilih.contains(gejala)) {
|
||||
gejalaTerpilih.add(gejala);
|
||||
if (!gejalaTerpilihIds.contains(gejalaId)) {
|
||||
gejalaTerpilihIds.add(gejalaId);
|
||||
gejalaTerpilihNames.add(gejalaName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void hapusGejala(String gejala) {
|
||||
void hapusGejala(int index) {
|
||||
setState(() {
|
||||
gejalaTerpilih.remove(gejala);
|
||||
gejalaTerpilihIds.removeAt(index);
|
||||
gejalaTerpilihNames.removeAt(index);
|
||||
});
|
||||
}
|
||||
|
||||
void prosesHasilDiagnosa() async {
|
||||
if (gejalaTerpilihIds.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Silakan pilih minimal satu gejala')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Tampilkan indikator loading
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Panggil API untuk diagnosa
|
||||
final hasilDiagnosa = await ApiService().diagnosa(gejalaTerpilihIds);
|
||||
|
||||
// Tutup dialog loading
|
||||
Navigator.pop(context);
|
||||
|
||||
// Navigasi ke halaman hasil
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => HasilDiagnosaPage(
|
||||
hasilDiagnosa: hasilDiagnosa,
|
||||
gejalaTerpilih: gejalaTerpilihNames,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
// Tutup dialog loading
|
||||
Navigator.pop(context);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Terjadi kesalahan: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -90,7 +138,7 @@ Widget build(BuildContext context) {
|
|||
),
|
||||
Expanded(
|
||||
child: isLoading
|
||||
? Center(child: CircularProgressIndicator()) // Tampilkan loading saat mengambil data
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: Center(
|
||||
child: Card(
|
||||
margin: EdgeInsets.all(20),
|
||||
|
@ -110,40 +158,48 @@ Widget build(BuildContext context) {
|
|||
),
|
||||
SizedBox(height: 20),
|
||||
SizedBox(
|
||||
height: 200, // Perbaiki ukuran list
|
||||
height: 200,
|
||||
child: gejalaList.isEmpty
|
||||
? Center(child: Text("Tidak ada data gejala"))
|
||||
: ListView.builder(
|
||||
itemCount: gejalaList.length,
|
||||
itemBuilder: (context, index) {
|
||||
String gejalaId = (gejalaList[index]['id'] ?? "").toString();
|
||||
String namaGejala = (gejalaList[index]['nama'] ?? "Tidak diketahui").toString();
|
||||
return ListTile(
|
||||
title: Text(namaGejala),
|
||||
trailing: Icon(Icons.add_circle, color: Colors.green),
|
||||
onTap: () => pilihGejala(namaGejala),
|
||||
onTap: () => pilihGejala(gejalaId, namaGejala),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(color: Colors.grey),
|
||||
Text(
|
||||
"Gejala Terpilih",
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: SingleChildScrollView(
|
||||
child: gejalaTerpilihNames.isEmpty
|
||||
? Center(child: Text("Belum ada gejala yang dipilih"))
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: gejalaTerpilih.map((item) {
|
||||
children: List.generate(gejalaTerpilihNames.length, (index) {
|
||||
return ListTile(
|
||||
title: Text(item, style: TextStyle(color: Colors.black)),
|
||||
title: Text(gejalaTerpilihNames[index], style: TextStyle(color: Colors.black)),
|
||||
trailing: Icon(Icons.delete, color: Colors.red),
|
||||
onTap: () => hapusGejala(item),
|
||||
onTap: () => hapusGejala(index),
|
||||
);
|
||||
}).toList(),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
height: 30,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
|
@ -151,14 +207,7 @@ Widget build(BuildContext context) {
|
|||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => HasilDiagnosaPage(gejalaTerpilih: gejalaTerpilih),
|
||||
),
|
||||
);
|
||||
},
|
||||
onPressed: prosesHasilDiagnosa,
|
||||
child: Text(
|
||||
"Lihat Hasil Diagnosa",
|
||||
style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
||||
|
|
|
@ -1,113 +1,733 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:frontend/api_services/api_services.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class HasilDiagnosaPage extends StatelessWidget {
|
||||
class HasilDiagnosaPage extends StatefulWidget {
|
||||
final Map<String, dynamic> hasilDiagnosa;
|
||||
final List<String> gejalaTerpilih;
|
||||
|
||||
// Constructor untuk menerima data gejala dari halaman sebelumnya
|
||||
HasilDiagnosaPage({required this.gejalaTerpilih});
|
||||
HasilDiagnosaPage({
|
||||
required this.hasilDiagnosa,
|
||||
required this.gejalaTerpilih,
|
||||
});
|
||||
|
||||
// Data contoh untuk penyakit, hama, dan cara penanganan
|
||||
final Map<String, Map<String, String>> database = {
|
||||
"Daun berlubang": {
|
||||
"penyakit": "Ulat Grayak",
|
||||
"hama": "Ulat Daun",
|
||||
"penanganan": "Gunakan pestisida alami atau buatan untuk mengendalikan ulat daun."
|
||||
},
|
||||
"Daun kecoklatan": {
|
||||
"penyakit": "Karat Daun",
|
||||
"hama": "Kumbang Penggerek",
|
||||
"penanganan": "Semprotkan fungisida untuk karat daun dan gunakan perangkap kumbang."
|
||||
},
|
||||
"Tepi daun keriting": {
|
||||
"penyakit": "Virus Keriting",
|
||||
"hama": "Thrips",
|
||||
"penanganan": "Buang daun yang terinfeksi dan gunakan insektisida untuk mengendalikan thrips."
|
||||
},
|
||||
};
|
||||
@override
|
||||
_HasilDiagnosaPageState createState() => _HasilDiagnosaPageState();
|
||||
}
|
||||
|
||||
class _HasilDiagnosaPageState extends State<HasilDiagnosaPage> {
|
||||
// Maps to store additional data fetched for each item
|
||||
Map<String, Map<String, dynamic>> penyakitDetails = {};
|
||||
Map<String, Map<String, dynamic>> hamaDetails = {};
|
||||
bool isLoading = true;
|
||||
|
||||
// Create an instance of your ApiService
|
||||
final ApiService _apiService = ApiService();
|
||||
|
||||
List<Map<String, dynamic>> semuaPenyakit = [];
|
||||
List<Map<String, dynamic>> semuaHama = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchAdditionalData();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Variabel untuk menghitung hasil berdasarkan gejala yang dipilih
|
||||
String namaPenyakit = "";
|
||||
String namaHama = "";
|
||||
String caraPenanganan = "";
|
||||
// Get the first penyakit and hama (if any)
|
||||
Map<String, dynamic>? firstPenyakit;
|
||||
Map<String, dynamic>? firstHama;
|
||||
|
||||
// Menentukan hasil berdasarkan gejala yang dipilih
|
||||
for (var gejala in gejalaTerpilih) {
|
||||
if (database.containsKey(gejala)) {
|
||||
namaPenyakit = database[gejala]?["penyakit"] ?? "";
|
||||
namaHama = database[gejala]?["hama"] ?? "";
|
||||
caraPenanganan = database[gejala]?["penanganan"] ?? "";
|
||||
break; // Mengambil data untuk gejala pertama yang cocok
|
||||
List<dynamic> penyakitList = widget.hasilDiagnosa['penyakit'] ?? [];
|
||||
List<dynamic> hamaList = widget.hasilDiagnosa['hama'] ?? [];
|
||||
|
||||
if (penyakitList.isNotEmpty) {
|
||||
firstPenyakit = penyakitList.first;
|
||||
}
|
||||
|
||||
if (hamaList.isNotEmpty) {
|
||||
firstHama = hamaList.first;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Hasil Diagnosa"),
|
||||
backgroundColor: Color(0xFF9DC08D),
|
||||
title: Text('Hasil Diagnosa'),
|
||||
backgroundColor: Color(0xFFEDF1D6),
|
||||
foregroundColor: Color(0xFF40513B),
|
||||
),
|
||||
body: Center(
|
||||
child: Card(
|
||||
margin: EdgeInsets.all(20),
|
||||
elevation: 5,
|
||||
body: Container(
|
||||
color: Color(0xFFEDF1D6),
|
||||
child:
|
||||
isLoading
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Main result display
|
||||
_buildDetailedResult(context, firstPenyakit, firstHama),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Selected symptoms section
|
||||
_buildSection(
|
||||
context,
|
||||
'Gejala yang Dipilih',
|
||||
widget.gejalaTerpilih.isEmpty
|
||||
? _buildEmptyResult('Tidak ada gejala yang dipilih')
|
||||
: Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:
|
||||
widget.gejalaTerpilih
|
||||
.map(
|
||||
(gejala) => Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: Text(gejala)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Other possible diseases section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Penyakit Lainnya',
|
||||
penyakitList.length <= 1
|
||||
? _buildEmptyResult(
|
||||
'Tidak ada kemungkinan penyakit lainnya',
|
||||
)
|
||||
: Column(
|
||||
children:
|
||||
penyakitList
|
||||
.skip(
|
||||
1,
|
||||
) // Skip the first one as it's already shown
|
||||
.map(
|
||||
(penyakit) => _buildItemCard(
|
||||
penyakit,
|
||||
'penyakit',
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Other possible pests section
|
||||
_buildSection(
|
||||
context,
|
||||
'Kemungkinan Hama Lainnya',
|
||||
hamaList.length <= 1
|
||||
? _buildEmptyResult(
|
||||
'Tidak ada kemungkinan hama lainnya',
|
||||
)
|
||||
: Column(
|
||||
children:
|
||||
hamaList
|
||||
.skip(
|
||||
1,
|
||||
) // Skip the first one as it's already shown
|
||||
.map(
|
||||
(hama) => _buildItemCard(hama, 'hama'),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchAdditionalData() async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
print('\n=== DEBUG - STARTING DATA FETCH ===');
|
||||
print('DEBUG - hasilDiagnosa input: ${widget.hasilDiagnosa}');
|
||||
|
||||
// Fetch all disease and pest data first
|
||||
print('DEBUG - Fetching all penyakit and hama data from API...');
|
||||
// Fetch all disease and pest data first
|
||||
semuaPenyakit = await _apiService.getPenyakit();
|
||||
semuaHama = await _apiService.getHama();
|
||||
|
||||
print('\nDEBUG - API Data Summary:');
|
||||
print(
|
||||
'Fetched ${semuaPenyakit.length} penyakit and ${semuaHama.length} hama from API',
|
||||
);
|
||||
|
||||
// Debug log what we got from the API
|
||||
print(
|
||||
'Fetched ${semuaPenyakit.length} penyakit and ${semuaHama.length} hama from API',
|
||||
);
|
||||
|
||||
// Get the lists from the diagnosis result
|
||||
List<dynamic> penyakitList = widget.hasilDiagnosa['penyakit'] ?? [];
|
||||
List<dynamic> hamaList = widget.hasilDiagnosa['hama'] ?? [];
|
||||
|
||||
// Process diseases
|
||||
for (var penyakit in penyakitList) {
|
||||
// Make sure the ID exists and convert to string for consistent comparison
|
||||
var penyakitId = penyakit['id'];
|
||||
if (penyakitId == null) continue;
|
||||
print('\nDEBUG - Sample penyakit from API:');
|
||||
|
||||
String penyakitIdStr = penyakitId.toString();
|
||||
|
||||
// Find the matching disease in our complete list
|
||||
var detail = semuaPenyakit.firstWhere(
|
||||
(item) => item['id'].toString() == penyakitIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
// Store the complete details
|
||||
penyakitDetails[penyakitIdStr] = {...detail};
|
||||
|
||||
// Debug log
|
||||
print(
|
||||
'Found details for penyakit ID $penyakitIdStr: ${penyakitDetails[penyakitIdStr]}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Process pests
|
||||
for (var hama in hamaList) {
|
||||
// Make sure the ID exists and convert to string for consistent comparison
|
||||
var hamaId = hama['id'];
|
||||
if (hamaId == null) continue;
|
||||
|
||||
String hamaIdStr = hamaId.toString();
|
||||
print('\nDEBUG - Sample hama from API:');
|
||||
|
||||
// Find the matching pest in our complete list
|
||||
var detail = semuaHama.firstWhere(
|
||||
(item) => item['id'].toString() == hamaIdStr,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
|
||||
if (detail.isNotEmpty) {
|
||||
// Store the complete details
|
||||
hamaDetails[hamaIdStr] = {...detail};
|
||||
|
||||
// Debug log
|
||||
print(
|
||||
'Found details for hama ID $hamaIdStr: ${hamaDetails[hamaIdStr]}',
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error fetching additional data: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getCompleteItemData(
|
||||
Map<String, dynamic> item,
|
||||
String type,
|
||||
) {
|
||||
// Create a new map for the result
|
||||
Map<String, dynamic> result = {...item};
|
||||
// Debug input item
|
||||
print('DEBUG - _getCompleteItemData input item: $item');
|
||||
print('DEBUG - _getCompleteItemData type: $type');
|
||||
|
||||
// Get the ID and make sure it's a string
|
||||
var id = type == 'penyakit' ? item['id_penyakit'] : item['id_hama'];
|
||||
print('DEBUG - ID from item: $id (${id?.runtimeType})');
|
||||
if (id == null) {
|
||||
print('DEBUG - ID is null, returning original item');
|
||||
return result;
|
||||
}
|
||||
|
||||
String idStr = id.toString();
|
||||
|
||||
// Get the detailed information based on type
|
||||
Map<String, dynamic>? details;
|
||||
if (type == 'penyakit') {
|
||||
print('DEBUG - Looking for penyakit details with ID: $idStr');
|
||||
print('DEBUG - Available penyakit keys: ${penyakitDetails.keys}');
|
||||
details = penyakitDetails[idStr];
|
||||
print('DEBUG - Found penyakit details: $details');
|
||||
|
||||
// Check raw API data if not found
|
||||
if (details == null || details.isEmpty) {
|
||||
print('DEBUG - Checking raw API data for penyakit ID: $idStr');
|
||||
for (var p in semuaPenyakit) {
|
||||
print(
|
||||
' Checking penyakit with ID ${p['id']} (${p['id'].runtimeType})',
|
||||
);
|
||||
if (p['id'].toString() == idStr) {
|
||||
print(' MATCH FOUND in raw API data: $p');
|
||||
details = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type == 'hama') {
|
||||
print('DEBUG - Looking for hama details with ID: $idStr');
|
||||
print('DEBUG - Available hama keys: ${hamaDetails.keys}');
|
||||
details = hamaDetails[idStr];
|
||||
print('DEBUG - Found hama details: $details');
|
||||
|
||||
// Check raw API data if not found
|
||||
if (details == null || details.isEmpty) {
|
||||
print('DEBUG - Checking raw API data for hama ID: $idStr');
|
||||
for (var h in semuaHama) {
|
||||
print(' Checking hama with ID ${h['id']} (${h['id'].runtimeType})');
|
||||
if (h['id'].toString() == idStr) {
|
||||
print(' MATCH FOUND in raw API data: $h');
|
||||
details = h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have details, merge them with the result
|
||||
if (details != null && details.isNotEmpty) {
|
||||
print('DEBUG - Merging details into result:');
|
||||
print(' Original result before merge: $result');
|
||||
print(' Details to merge: $details');
|
||||
// Make sure details have priority but preserve the original probabilitas
|
||||
double? originalProbabilitas =
|
||||
result['probabilitas'] is num
|
||||
? (result['probabilitas'] as num).toDouble()
|
||||
: null;
|
||||
|
||||
// Merge the details
|
||||
result.addAll(details);
|
||||
|
||||
// Restore original probabilitas if it existed
|
||||
if (originalProbabilitas != null) {
|
||||
result['probabilitas'] = originalProbabilitas;
|
||||
}
|
||||
print('DEBUG - Final merged result: $result');
|
||||
|
||||
// Specifically check key fields
|
||||
print('DEBUG - Key fields in result:');
|
||||
print(' nama: ${result['nama']} (${result['nama']?.runtimeType})');
|
||||
print(
|
||||
' deskripsi: ${result['deskripsi']} (${result['deskripsi']?.runtimeType})',
|
||||
);
|
||||
print(
|
||||
' penanganan: ${result['penanganan']} (${result['penanganan']?.runtimeType})',
|
||||
);
|
||||
print(' foto: ${result['foto']} (${result['foto']?.runtimeType})');
|
||||
|
||||
// Debug log
|
||||
print('Complete data for $type ID $idStr: $result');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildDetailedResult(
|
||||
BuildContext context,
|
||||
Map<String, dynamic>? penyakit,
|
||||
Map<String, dynamic>? hama,
|
||||
) {
|
||||
// If we have no data, show a message
|
||||
if (penyakit == null && hama == null) {
|
||||
return _buildEmptyResult('Tidak ada hasil diagnosa yang tersedia');
|
||||
}
|
||||
|
||||
// Determine which has higher probability
|
||||
bool isPenyakitHigher = false;
|
||||
Map<String, dynamic>? highest;
|
||||
String type = '';
|
||||
|
||||
// Log the incoming data to debug
|
||||
print('DEBUG - Incoming penyakit: $penyakit');
|
||||
print('DEBUG - Incoming hama: $hama');
|
||||
|
||||
// Compare probabilities to determine which to show
|
||||
if (penyakit != null && hama != null) {
|
||||
double pProbabilitas = _getProbabilitas(penyakit);
|
||||
double hProbabilitas = _getProbabilitas(hama);
|
||||
|
||||
isPenyakitHigher = pProbabilitas >= hProbabilitas;
|
||||
highest = isPenyakitHigher ? penyakit : hama;
|
||||
type = isPenyakitHigher ? 'penyakit' : 'hama';
|
||||
} else if (penyakit != null) {
|
||||
highest = penyakit;
|
||||
isPenyakitHigher = true;
|
||||
type = 'penyakit';
|
||||
} else if (hama != null) {
|
||||
highest = hama;
|
||||
isPenyakitHigher = false;
|
||||
type = 'hama';
|
||||
}
|
||||
|
||||
// Safety check
|
||||
if (highest == null) {
|
||||
return _buildEmptyResult('Tidak ada hasil diagnosa yang tersedia');
|
||||
}
|
||||
|
||||
// Get the complete data for the highest item
|
||||
final completeData = _getCompleteItemData(highest, type);
|
||||
|
||||
// Debug log
|
||||
print('Detail result using: $completeData');
|
||||
|
||||
// Extract the data we need with safe access
|
||||
final nama = completeData['nama'] ?? 'Tidak diketahui';
|
||||
final deskripsi = completeData['deskripsi'] ?? 'Tidak tersedia';
|
||||
final penanganan = completeData['penanganan'] ?? 'Tidak tersedia';
|
||||
final foto = completeData['foto'];
|
||||
final probabilitas = _getProbabilitas(completeData);
|
||||
|
||||
// Debug log specific fields that should be displayed
|
||||
print('DEBUG - nama: $nama (${nama.runtimeType})');
|
||||
print('DEBUG - deskripsi: $deskripsi (${deskripsi.runtimeType})');
|
||||
print('DEBUG - penanganan: $penanganan (${penanganan.runtimeType})');
|
||||
print('DEBUG - foto: $foto (${foto?.runtimeType})');
|
||||
print('DEBUG - probabilitas: $probabilitas (${probabilitas.runtimeType})');
|
||||
return Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
side: BorderSide(
|
||||
color:
|
||||
isPenyakitHigher ? Colors.red.shade300 : Colors.orange.shade300,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Nama Penyakit dan Hama
|
||||
Text(
|
||||
"Nama Penyakit: ${namaPenyakit.isNotEmpty ? namaPenyakit : "Tidak Diketahui"}",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isPenyakitHigher
|
||||
? Icons.coronavirus_outlined
|
||||
: Icons.bug_report,
|
||||
color:
|
||||
isPenyakitHigher
|
||||
? Colors.red.shade700
|
||||
: Colors.orange.shade700,
|
||||
size: 28,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
"Nama Hama: ${namaHama.isNotEmpty ? namaHama : "Tidak Diketahui"}",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
|
||||
// Berdasarkan skor
|
||||
Text(
|
||||
"Berdasarkan perhitungan skor yang telah dilakukan maka tanaman Anda terkena:",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
namaPenyakit.isNotEmpty ? namaPenyakit : "Tidak Diketahui",
|
||||
textAlign: TextAlign.center,
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Kemungkinan Terbesar: $nama',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
color:
|
||||
isPenyakitHigher
|
||||
? Colors.red.shade700
|
||||
: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
),
|
||||
_buildProbabilityIndicator(probabilitas),
|
||||
],
|
||||
),
|
||||
Divider(thickness: 1, height: 24),
|
||||
|
||||
// Cara Penanganan
|
||||
Text(
|
||||
"Cara Penanganan:",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
FutureBuilder<Uint8List?>(
|
||||
future: ApiService().getPenyakitImageBytesByFilename(
|
||||
foto.toString(),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Container(
|
||||
height: 180,
|
||||
color: Colors.grey.shade200,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else if (snapshot.hasData && snapshot.data != null) {
|
||||
return Container(
|
||||
height: 180,
|
||||
color: Colors.grey.shade200,
|
||||
child: Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.memory(
|
||||
snapshot.data!,
|
||||
fit: BoxFit.contain, // ✅ agar gambar tidak dipotong
|
||||
width: double.infinity,
|
||||
height: 180,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
height: 180,
|
||||
color: Colors.grey.shade200,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.image_not_supported,
|
||||
size: 40,
|
||||
color: Colors.grey,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
caraPenanganan.isNotEmpty
|
||||
? caraPenanganan
|
||||
: "Tidak ada informasi penanganan yang tersedia.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 16),
|
||||
'Gambar tidak tersedia',
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
'Deskripsi',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(deskripsi, style: TextStyle(fontSize: 14)),
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Treatment
|
||||
Text(
|
||||
'Penanganan',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(penanganan, style: TextStyle(fontSize: 14)),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Button to see more details
|
||||
// Center(
|
||||
// child: TextButton.icon(
|
||||
// icon: Icon(Icons.info_outline),
|
||||
// label: Text('Lihat Detail Lengkap'),
|
||||
// onPressed: () => _showDetailDialog(context, completeData, type),
|
||||
// style: TextButton.styleFrom(
|
||||
// foregroundColor: isPenyakitHigher ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemCard(Map<String, dynamic> item, String type) {
|
||||
// Get the complete data for this item
|
||||
final completeData = _getCompleteItemData(item, type);
|
||||
|
||||
final nama = completeData['nama'] ?? 'Tidak diketahui';
|
||||
final probabilitas = _getProbabilitas(completeData);
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
type == 'penyakit' ? Icons.coronavirus_outlined : Icons.bug_report,
|
||||
color:
|
||||
type == 'penyakit' ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
),
|
||||
title: Text(nama),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildProbabilityIndicator(probabilitas),
|
||||
SizedBox(width: 8),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.info_outline),
|
||||
// onPressed: () => _showDetailDialog(context, completeData, type),
|
||||
// color: type == 'penyakit' ? Colors.red.shade700 : Colors.orange.shade700,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(BuildContext context, String title, Widget content) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF40513B),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
content,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyResult(String message) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProbabilityIndicator(double value) {
|
||||
final Color indicatorColor =
|
||||
value > 0.7
|
||||
? Colors.red
|
||||
: value > 0.4
|
||||
? Colors.orange
|
||||
: Colors.green;
|
||||
|
||||
return Container(
|
||||
width: 60,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: indicatorColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${(value * 100).toStringAsFixed(0)}%',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _getProbabilitas(Map<String, dynamic>? item) {
|
||||
// If item is null or doesn't contain probabilitas, return 0.0
|
||||
if (item == null ||
|
||||
!item.containsKey('probabilitas') ||
|
||||
item['probabilitas'] == null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Handle different types of probabilitas value
|
||||
var probValue = item['probabilitas'];
|
||||
|
||||
if (probValue is double) {
|
||||
return probValue;
|
||||
} else if (probValue is int) {
|
||||
return probValue.toDouble();
|
||||
} else if (probValue is String) {
|
||||
return double.tryParse(probValue) ?? 0.0;
|
||||
} else {
|
||||
// For any other type, try to convert to string first then parse
|
||||
try {
|
||||
return double.tryParse(probValue.toString()) ?? 0.0;
|
||||
} catch (e) {
|
||||
print('Error parsing probabilitas: $e');
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildEmptyResult(String message) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProbabilityIndicator(double value) {
|
||||
Color color;
|
||||
if (value >= 0.7) {
|
||||
color = Colors.red;
|
||||
} else if (value >= 0.4) {
|
||||
color = Colors.orange;
|
||||
} else {
|
||||
color = Colors.green;
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: 60,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${(value * 100).toStringAsFixed(0)}%',
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
final TextEditingController _passwordController = TextEditingController();
|
||||
bool _isLoading = false;
|
||||
|
||||
|
||||
Future<void> _login() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
|
@ -53,6 +54,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('token', responseData['token']);
|
||||
await prefs.setString('role', responseData['role']);
|
||||
await prefs.setString('email', email);
|
||||
|
||||
// Redirect berdasarkan role
|
||||
if (responseData['role'] == 'admin') {
|
||||
|
|
|
@ -1,12 +1,118 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'login_page.dart';
|
||||
import 'package:frontend/api_services/api_services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ProfilPage extends StatelessWidget {
|
||||
Future<void> _logout(BuildContext context) async {
|
||||
await ApiService.logoutUser();
|
||||
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage()));
|
||||
class ProfilPage extends StatefulWidget {
|
||||
@override
|
||||
_ProfilPageState createState() => _ProfilPageState();
|
||||
}
|
||||
|
||||
class _ProfilPageState extends State<ProfilPage> {
|
||||
// State untuk menyimpan data user
|
||||
bool isLoading = true;
|
||||
Map<String, dynamic>? userData;
|
||||
String? errorMessage;
|
||||
String? userRole;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Panggil API untuk mendapatkan data user saat halaman dibuka
|
||||
if (userData == null) {
|
||||
_loadUserData();
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk memuat data pengguna yang login
|
||||
Future<void> _loadUserData() async {
|
||||
try {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
errorMessage = null;
|
||||
});
|
||||
|
||||
// Ambil data user yang sedang login dari SharedPreferences
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? email = prefs.getString('email');
|
||||
String? token = prefs.getString('token');
|
||||
userRole = prefs.getString('role');
|
||||
|
||||
if (email == null || token == null) {
|
||||
throw Exception('Sesi login tidak ditemukan, silahkan login kembali');
|
||||
}
|
||||
|
||||
// Buat URL untuk endpoint user API
|
||||
var url = Uri.parse("http://localhost:5000/api/users");
|
||||
|
||||
// Kirim permintaan GET dengan token autentikasi
|
||||
var response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer $token",
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Parse data respons
|
||||
List<dynamic> users = jsonDecode(response.body);
|
||||
print("Email login: $email");
|
||||
print("Data user dari server: $users");
|
||||
|
||||
// Cari user dengan email yang sama dengan yang login
|
||||
Map<String, dynamic>? currentUser;
|
||||
for (var user in users) {
|
||||
if (user['email'].toString().toLowerCase() == email.toLowerCase()) {
|
||||
currentUser = Map<String, dynamic>.from(user);
|
||||
print("User ditemukan: $currentUser");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser == null) {
|
||||
print("User dengan email $email tidak ditemukan di response.");
|
||||
throw Exception('Data pengguna tidak ditemukan');
|
||||
}
|
||||
|
||||
setState(() {
|
||||
userData = currentUser;
|
||||
userRole = currentUser?['role']; // safe access
|
||||
isLoading = false;
|
||||
print('User dengan email $email tidak ditemukan di response');
|
||||
});
|
||||
} else if (response.statusCode == 401) {
|
||||
// Token tidak valid atau expired
|
||||
await ApiService.logoutUser(); // Logout user
|
||||
throw Exception('Sesi habis, silahkan login kembali');
|
||||
} else {
|
||||
throw Exception('Gagal mengambil data: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
errorMessage = "Gagal memuat data profil: ${e.toString()}";
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> _logout(BuildContext context) async {
|
||||
try {
|
||||
await ApiService.logoutUser();
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => LoginPage()),
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text("Gagal logout: ${e.toString()}")));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -17,7 +123,7 @@ class ProfilPage extends StatelessWidget {
|
|||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 40.0), // Jarak dari atas layar
|
||||
padding: const EdgeInsets.only(top: 40.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
|
@ -29,7 +135,7 @@ class ProfilPage extends StatelessWidget {
|
|||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 80), // Menambah jarak antara judul dan card box
|
||||
SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -42,7 +148,7 @@ class ProfilPage extends StatelessWidget {
|
|||
child: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: Colors.white, size: 28),
|
||||
onPressed: () {
|
||||
Navigator.pop(context); // Kembali ke halaman sebelumnya
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -56,8 +162,8 @@ class ProfilPage extends StatelessWidget {
|
|||
children: [
|
||||
// Card box untuk data pengguna
|
||||
Container(
|
||||
height: 400, // Atur tinggi card box
|
||||
width: 450, // Atur lebar card box
|
||||
height: 400,
|
||||
width: 450,
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
|
@ -65,33 +171,27 @@ class ProfilPage extends StatelessWidget {
|
|||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildProfileItem("Nama: John Doe"),
|
||||
Divider(color: Colors.black), // Garis hitam pemisah
|
||||
_buildProfileItem("Username: johndoe"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Password: ********"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Email: johndoe@gmail.com"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Alamat: Jl. Mawar No. 5"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Nomor Telepon: 081234567890"),
|
||||
],
|
||||
),
|
||||
child:
|
||||
isLoading
|
||||
? _buildLoadingState()
|
||||
: errorMessage != null
|
||||
? _buildErrorState()
|
||||
: _buildUserInfoCard(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
|
||||
// Button di bawah Card box untuk update data profil
|
||||
// Button untuk update data profil
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Aksi ketika button ditekan (misalnya membuka halaman update data)
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Fitur Update Profil Sedang Dikembangkan")),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Fitur Update Profil Sedang Dikembangkan",
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
@ -99,7 +199,10 @@ class ProfilPage extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Update Data Profil",
|
||||
|
@ -111,6 +214,8 @@ class ProfilPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SizedBox(height: 30),
|
||||
|
||||
// Button untuk logout
|
||||
ElevatedButton(
|
||||
onPressed: () => _logout(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
@ -118,7 +223,10 @@ class ProfilPage extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Logout",
|
||||
|
@ -138,16 +246,77 @@ class ProfilPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
// Widget untuk menampilkan loading spinner
|
||||
Widget _buildLoadingState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: Color(0xFF9DC08D)),
|
||||
SizedBox(height: 16),
|
||||
Text("Memuat data profil..."),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget untuk menampilkan pesan error
|
||||
Widget _buildErrorState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
errorMessage ?? "Terjadi kesalahan",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextButton(onPressed: _loadUserData, child: Text("Coba Lagi")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget untuk menampilkan informasi user yang berhasil dimuat
|
||||
Widget _buildUserInfoCard() {
|
||||
if (userData == null) {
|
||||
return Center(child: Text("Data pengguna belum dimuat."));
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildProfileItem("Nama: ${userData?['name'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Email: ${userData?['email'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Password: ${userData?['password'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
_buildProfileItem("Alamat: ${userData?['alamat'] ?? '-'}"),
|
||||
Divider(color: Colors.black),
|
||||
],
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk memformat tanggal
|
||||
String _formatDate(String dateString) {
|
||||
try {
|
||||
final date = DateTime.parse(dateString);
|
||||
return "${date.day}/${date.month}/${date.year}";
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk membuat item dalam Card box
|
||||
Widget _buildProfileItem(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: Text(text, style: TextStyle(fontSize: 18)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|