hasil diagnosa riwayat
This commit is contained in:
parent
3a4c8bfeb0
commit
8e92a58e49
12
backend/.env
12
backend/.env
|
@ -4,9 +4,9 @@ DB_USER=root
|
||||||
DB_NAME=sibayam
|
DB_NAME=sibayam
|
||||||
API_URL=http://localhost:5000
|
API_URL=http://localhost:5000
|
||||||
JWT_SECRET=2c5t0ny38989t03cr4ny904r8xy12jc
|
JWT_SECRET=2c5t0ny38989t03cr4ny904r8xy12jc
|
||||||
EMAIL_HOST=sandbox.smtp.mailtrap.io
|
EMAIL_HOST=
|
||||||
EMAIL_PORT=""
|
EMAIL_PORT=
|
||||||
EMAIL_USER=""
|
EMAIL_USER=
|
||||||
EMAIL_PASS=""
|
EMAIL_PASS=
|
||||||
SENDGRID_API_KEY=""
|
SENDGRID_API_KEY=
|
||||||
EMAIL_FROM=""
|
EMAIL_FROM=
|
|
@ -11,6 +11,7 @@ const penyakitRoutes = require('./routes/penyakitRoutes');
|
||||||
const ruleRoutes = require('./routes/ruleRoutes');
|
const ruleRoutes = require('./routes/ruleRoutes');
|
||||||
const ruleHamaRoutes = require('./routes/ruleHamaRoutes');
|
const ruleHamaRoutes = require('./routes/ruleHamaRoutes');
|
||||||
const diagnosaRoute = require('./routes/diagnosaRoutes');
|
const diagnosaRoute = require('./routes/diagnosaRoutes');
|
||||||
|
const historiRoutes = require('./routes/historiRoutes');
|
||||||
const swaggerDocs = require('./swagger');
|
const swaggerDocs = require('./swagger');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
@ -35,6 +36,7 @@ app.use("/api/penyakit", penyakitRoutes);
|
||||||
app.use("/api/rules_penyakit", ruleRoutes);
|
app.use("/api/rules_penyakit", ruleRoutes);
|
||||||
app.use("/api/rules_hama", ruleHamaRoutes);
|
app.use("/api/rules_hama", ruleHamaRoutes);
|
||||||
app.use("/api/diagnosa", diagnosaRoute);
|
app.use("/api/diagnosa", diagnosaRoute);
|
||||||
|
app.use("/api/histori", historiRoutes);
|
||||||
|
|
||||||
|
|
||||||
// Swagger Documentation
|
// Swagger Documentation
|
||||||
|
|
|
@ -65,6 +65,8 @@ exports.login = async (req, res) => {
|
||||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '1d' }
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '1d' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("User ID dari backend:", user.id);
|
||||||
|
|
||||||
// 🔹 Kirim response dengan token dan role
|
// 🔹 Kirim response dengan token dan role
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: "Login berhasil",
|
message: "Login berhasil",
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
const { Histori, Gejala, Penyakit, Hama } = require('../models');
|
||||||
|
|
||||||
|
// Ambil semua histori
|
||||||
|
exports.getAllHistori = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const histori = await Histori.findAll();
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Data Histori', data: histori });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getHistori:', error);
|
||||||
|
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ambil histori berdasarkan ID user
|
||||||
|
exports.getHistoriByUserId = async (req, res) => {
|
||||||
|
const { userId } = req.params; // Ambil ID user dari parameter URL
|
||||||
|
|
||||||
|
if (!userId || userId === 'null') {
|
||||||
|
return res.status(400).json({ message: 'User ID tidak valid' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Menerima request untuk userId:", userId);
|
||||||
|
try {
|
||||||
|
const histori = await Histori.findAll({
|
||||||
|
where: { userId }, // Filter berdasarkan ID user
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Gejala,
|
||||||
|
as: 'gejala',
|
||||||
|
attributes: ['id', 'kode', 'nama']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Penyakit,
|
||||||
|
as: 'penyakit',
|
||||||
|
attributes: ['id', 'nama']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Hama,
|
||||||
|
as: 'hama',
|
||||||
|
attributes: ['id', 'nama']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [['tanggal_diagnosa', 'DESC']] // Urutkan berdasarkan tanggal diagnosa terbaru
|
||||||
|
});
|
||||||
|
|
||||||
|
if (histori.length === 0) {
|
||||||
|
return res.status(404).json({ message: 'Tidak ada histori untuk user ini' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Data Histori User', data: histori });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getHistoriByUserId:', error);
|
||||||
|
res.status(500).json({ message: 'Terjadi kesalahan server', error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { getAllHistori, getHistoriByUserId } = require('../controller/historiController');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/histori:
|
||||||
|
* get:
|
||||||
|
* summary: Ambil semua data histori
|
||||||
|
* tags:
|
||||||
|
* - Histori
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Berhasil mengambil data histori
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* example: Data Histori
|
||||||
|
* data:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* example: 1
|
||||||
|
* userId:
|
||||||
|
* type: integer
|
||||||
|
* example: 2
|
||||||
|
* hasil:
|
||||||
|
* type: float
|
||||||
|
* example: 0.85
|
||||||
|
* tanggal_diagnosa:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* example: 2025-05-12T10:00:00Z
|
||||||
|
* 500:
|
||||||
|
* description: Terjadi kesalahan server
|
||||||
|
*/
|
||||||
|
router.get('/', getAllHistori);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/histori/user/{userId}:
|
||||||
|
* get:
|
||||||
|
* summary: Ambil data histori berdasarkan ID user
|
||||||
|
* tags:
|
||||||
|
* - Histori
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: userId
|
||||||
|
* required: true
|
||||||
|
* description: ID user untuk mengambil histori
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* example: 1
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Berhasil mengambil data histori user
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* message:
|
||||||
|
* type: string
|
||||||
|
* example: Data Histori User
|
||||||
|
* data:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* example: 1
|
||||||
|
* userId:
|
||||||
|
* type: integer
|
||||||
|
* example: 1
|
||||||
|
* hasil:
|
||||||
|
* type: float
|
||||||
|
* example: 0.85
|
||||||
|
* tanggal_diagnosa:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* example: 2025-05-12T10:00:00Z
|
||||||
|
* 404:
|
||||||
|
* description: Tidak ada histori untuk user ini
|
||||||
|
* 500:
|
||||||
|
* description: Terjadi kesalahan server
|
||||||
|
*/
|
||||||
|
router.get('/user/:userId', getHistoriByUserId);
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -15,37 +15,31 @@ class ApiService {
|
||||||
static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama';
|
static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama';
|
||||||
static const String userUrl = 'http://localhost:5000/api/users';
|
static const String userUrl = 'http://localhost:5000/api/users';
|
||||||
static const String diagnosaUrl = 'http://localhost:5000/api/diagnosa';
|
static const String diagnosaUrl = 'http://localhost:5000/api/diagnosa';
|
||||||
|
static const String historiUrl = 'http://localhost:5000/api/histori';
|
||||||
static const Duration timeout = Duration(seconds: 15);
|
static const Duration timeout = Duration(seconds: 15);
|
||||||
|
|
||||||
/// Fungsi untuk mengirim gejala dan menerima hasil diagnosa
|
/// Fungsi untuk mengirim gejala dan menerima hasil diagnosa
|
||||||
// Kirim gejala dan dapatkan hasil diagnosa
|
Future<Map<String, dynamic>> diagnosa(List<String> gejalaIds) async {
|
||||||
Future<Map<String, dynamic>> diagnosa(List<String> gejalIds) async {
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
// Konversi string ID menjadi integer jika backend Anda membutuhkan integer
|
final token = prefs.getString('token');
|
||||||
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) {
|
List<dynamic> parsedGejala;
|
||||||
return json.decode(response.body);
|
try {
|
||||||
} else {
|
// Coba konversi ke integer jika bisa
|
||||||
throw Exception('Gagal melakukan diagnosa: ${response.statusCode} - ${response.body}');
|
parsedGejala = gejalaIds.map((id) => int.parse(id)).toList();
|
||||||
}
|
} catch (e) {
|
||||||
|
print("Konversi ke integer gagal, gunakan string ID.");
|
||||||
|
parsedGejala = gejalaIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika konversi berhasil, gunakan ID numerik
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('$diagnosaUrl'),
|
Uri.parse(diagnosaUrl),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {
|
||||||
body: json.encode({'gejala': gejalaNumerik}),
|
'Content-Type': 'application/json',
|
||||||
);
|
if (token != null) 'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
body: json.encode({'gejala': parsedGejala}),
|
||||||
|
).timeout(timeout);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return json.decode(response.body);
|
return json.decode(response.body);
|
||||||
|
@ -54,32 +48,108 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getHistoriDiagnosa(String userId) async {
|
||||||
|
try {
|
||||||
|
// Ambil token dari SharedPreferences
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final token = prefs.getString('token');
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
throw Exception("Token tidak valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
final url = '$historiUrl/user/$userId';
|
||||||
|
print("Fetching histori from URL: $url");
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
|
||||||
|
print("Response Status Code: ${response.statusCode}");
|
||||||
|
print("Response Body: ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final Map<String, dynamic> responseData = json.decode(response.body);
|
||||||
|
|
||||||
|
if (responseData.containsKey('data') && responseData['data'] is List) {
|
||||||
|
return List<Map<String, dynamic>>.from(responseData['data']);
|
||||||
|
} else {
|
||||||
|
throw Exception('Format respons tidak valid.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Gagal memuat histori: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error fetching data: $e");
|
||||||
|
throw Exception('Terjadi kesalahan saat mengambil histori: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fetchHistoriDenganDetail(String userId) async {
|
||||||
|
try {
|
||||||
|
// Panggil API untuk mendapatkan data histori
|
||||||
|
final historiResponse = await getHistoriDiagnosa(userId);
|
||||||
|
|
||||||
|
// Proses data histori
|
||||||
|
List<Map<String, dynamic>> result = historiResponse.map((histori) {
|
||||||
|
// Tangani properti null dengan default value
|
||||||
|
final gejala = histori['gejala'] ?? {};
|
||||||
|
final penyakit = histori['penyakit'] ?? {};
|
||||||
|
final hama = histori['hama'] ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": histori['id'],
|
||||||
|
"userId": histori['userId'],
|
||||||
|
"tanggal_diagnosa": histori['tanggal_diagnosa'],
|
||||||
|
"hasil": histori['hasil'],
|
||||||
|
"gejala_nama": gejala['nama'] ?? "Tidak diketahui",
|
||||||
|
"penyakit_nama": penyakit['nama'] ,
|
||||||
|
"hama_nama": hama['nama'] ,
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
print("Processed Histori Data: $result");
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print("Error fetching histori dengan detail: $e");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fungsi Login (dengan perbaikan)
|
// Fungsi Login (dengan perbaikan)
|
||||||
static Future<Map<String, dynamic>> loginUser(
|
static Future<Map<String, dynamic>> loginUser(
|
||||||
String email,
|
String email,
|
||||||
String password,
|
String password,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse("$baseUrl/login"),
|
Uri.parse("$baseUrl/login"),
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
body: jsonEncode({'email': email, 'password': password}),
|
body: jsonEncode({'email': email, 'password': password}),
|
||||||
);
|
);
|
||||||
|
|
||||||
print("Response Status: ${response.statusCode}");
|
print("Response Status: ${response.statusCode}");
|
||||||
print("Response Body: ${response.body}");
|
print("Response Body: ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return jsonDecode(response.body);
|
final responseData = jsonDecode(response.body);
|
||||||
} else {
|
|
||||||
throw Exception("Login gagal: ${response.body}");
|
// Simpan userId ke SharedPreferences
|
||||||
}
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
} catch (e) {
|
print("User ID dari respons login: ${responseData['userId']}"); // Tambahkan log
|
||||||
print("Error: $e");
|
await prefs.setString('userId', responseData['userId'].toString());
|
||||||
throw Exception("Terjadi kesalahan saat login");
|
|
||||||
|
return responseData;
|
||||||
|
} else {
|
||||||
|
throw Exception("Login gagal: ${response.body}");
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error: $e");
|
||||||
|
throw Exception("Terjadi kesalahan saat login");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fungsi Logout
|
// Fungsi Logout
|
||||||
static Future<void> logoutUser() async {
|
static Future<void> logoutUser() async {
|
||||||
|
@ -283,7 +353,6 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Tambah hama baru (kode otomatis)
|
// Tambah hama baru (kode otomatis)
|
||||||
Future<Map<String, dynamic>> createHama(
|
Future<Map<String, dynamic>> createHama(
|
||||||
String nama,
|
String nama,
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/api_services/api_services.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
class DetailRiwayatPage extends StatelessWidget {
|
||||||
|
final Map<String, dynamic> detailRiwayat;
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
DetailRiwayatPage({required this.detailRiwayat}) {
|
||||||
|
print("Detail Riwayat Data: $detailRiwayat");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchDetailData() async {
|
||||||
|
final diagnosisType = detailRiwayat['diagnosis_type'];
|
||||||
|
final diagnosisName = detailRiwayat['diagnosis'];
|
||||||
|
|
||||||
|
print("Diagnosis Type: $diagnosisType, Name: $diagnosisName");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (diagnosisType == 'penyakit') {
|
||||||
|
// Dapatkan daftar semua penyakit
|
||||||
|
final penyakitList = await apiService.getPenyakit();
|
||||||
|
// Cari penyakit berdasarkan nama
|
||||||
|
final penyakit = penyakitList.firstWhere(
|
||||||
|
(p) =>
|
||||||
|
p['nama'].toString().toLowerCase() ==
|
||||||
|
diagnosisName.toString().toLowerCase(),
|
||||||
|
orElse: () => throw Exception('Penyakit tidak ditemukan'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final id = penyakit['id'];
|
||||||
|
print("Found Penyakit ID: $id");
|
||||||
|
|
||||||
|
// Ambil gambar jika ID ditemukan
|
||||||
|
final imageBytes =
|
||||||
|
id != null ? await apiService.getPenyakitImageBytes(id) : null;
|
||||||
|
|
||||||
|
return {...penyakit, 'imageBytes': imageBytes};
|
||||||
|
} else if (diagnosisType == 'hama') {
|
||||||
|
// Dapatkan daftar semua hama
|
||||||
|
final hamaList = await apiService.getHama();
|
||||||
|
// Cari hama berdasarkan nama
|
||||||
|
final hama = hamaList.firstWhere(
|
||||||
|
(h) =>
|
||||||
|
h['nama'].toString().toLowerCase() ==
|
||||||
|
diagnosisName.toString().toLowerCase(),
|
||||||
|
orElse: () => throw Exception('Hama tidak ditemukan'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final id = hama['id'];
|
||||||
|
print("Found Hama ID: $id");
|
||||||
|
|
||||||
|
// Ambil gambar jika ID ditemukan
|
||||||
|
final imageBytes =
|
||||||
|
id != null ? await apiService.getHamaImageBytes(id) : null;
|
||||||
|
|
||||||
|
return {...hama, 'imageBytes': imageBytes};
|
||||||
|
} else {
|
||||||
|
throw Exception('Tipe diagnosis tidak valid');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error in fetchDetailData: $e");
|
||||||
|
throw Exception("Gagal mengambil detail: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final gejalaList = (detailRiwayat['gejala'] as List).join(', ');
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
title: Text(
|
||||||
|
'Detail Riwayat Diagnosa',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: FutureBuilder<Map<String, dynamic>>(
|
||||||
|
future: fetchDetailData(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return Center(child: Text("Error: ${snapshot.error}"));
|
||||||
|
} else if (!snapshot.hasData || snapshot.data == null) {
|
||||||
|
return Center(child: Text("Data tidak ditemukan"));
|
||||||
|
}
|
||||||
|
|
||||||
|
final detailData = snapshot.data!;
|
||||||
|
final imageBytes = detailData['imageBytes'] as Uint8List?;
|
||||||
|
final deskripsi =
|
||||||
|
detailData['deskripsi'] ?? 'Deskripsi tidak tersedia';
|
||||||
|
final penanganan =
|
||||||
|
detailData['penanganan'] ?? 'Penanganan tidak tersedia';
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 3,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Diagnosis: ${detailRiwayat['diagnosis']}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
if (imageBytes != null)
|
||||||
|
// Ganti bagian Container untuk gambar dengan kode berikut
|
||||||
|
if (imageBytes != null)
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: 250, // Tinggi maksimal yang konsisten
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color:
|
||||||
|
Colors
|
||||||
|
.grey[200], // Warna background untuk area gambar
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Center(
|
||||||
|
// Menambahkan Center widget
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio:
|
||||||
|
16 / 9, // Rasio aspek yang konsisten
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: MemoryImage(imageBytes),
|
||||||
|
fit:
|
||||||
|
BoxFit
|
||||||
|
.contain, // Menggunakan contain untuk menjaga aspek rasio
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Text('Gejala: $gejalaList', style: TextStyle(fontSize: 16)),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Hasil: ${(detailRiwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Tanggal: ${detailRiwayat['tanggal_diagnosa'] ?? "-"}',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Deskripsi:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(deskripsi, style: TextStyle(fontSize: 16)),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Penanganan:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(penanganan, style: TextStyle(fontSize: 16)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,36 @@ import 'diagnosa_page.dart';
|
||||||
import 'riwayat_diagnosa_page.dart';
|
import 'riwayat_diagnosa_page.dart';
|
||||||
import 'profile_page.dart';
|
import 'profile_page.dart';
|
||||||
import 'basis_pengetahuan_page.dart';
|
import 'basis_pengetahuan_page.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
class HomePage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_HomePageState createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage> {
|
||||||
|
String userId = ''; // Variabel untuk menyimpan userId
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> navigateToRiwayatDiagnosaPage(BuildContext context) async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final userId = prefs.getString('userId') ?? '';
|
||||||
|
|
||||||
|
print("Navigating to RiwayatDiagnosaPage with userId: $userId");
|
||||||
|
|
||||||
|
if (userId.isEmpty) {
|
||||||
|
print("Error: User ID tidak ditemukan di SharedPreferences");
|
||||||
|
// Tampilkan pesan error atau arahkan ke halaman login
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -89,21 +115,25 @@ class HomePage extends StatelessWidget {
|
||||||
height: 48,
|
height: 48,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
navigateToRiwayatDiagnosaPage(context);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => RiwayatDiagnosaPage(),
|
builder:
|
||||||
), // Perbaikan di sini
|
(context) => RiwayatDiagnosaPage(
|
||||||
|
userId: userId,
|
||||||
|
), // Kirimkan userId sebagai String
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ButtonMenu(
|
ButtonMenu(
|
||||||
title: "Profile",
|
title: "Profile",
|
||||||
customIcon: Image.asset(
|
customIcon: Image.asset(
|
||||||
'assets/images/Test Account.png',
|
'assets/images/Test Account.png',
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -122,10 +152,10 @@ class HomePage extends StatelessWidget {
|
||||||
ButtonMenu(
|
ButtonMenu(
|
||||||
title: "Basis Pengetahuan",
|
title: "Basis Pengetahuan",
|
||||||
customIcon: Image.asset(
|
customIcon: Image.asset(
|
||||||
'assets/images/Literature.png',
|
'assets/images/Literature.png',
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -194,11 +224,11 @@ class ButtonMenu extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
customIcon,
|
customIcon,
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -51,10 +51,12 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// Simpan token & role ke SharedPreferences
|
// Simpan token & role ke SharedPreferences
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
print("User ID dari respons login: ${responseData['userId']}");
|
||||||
await prefs.setString('token', responseData['token']);
|
await prefs.setString('token', responseData['token']);
|
||||||
await prefs.setString('role', responseData['role']);
|
await prefs.setString('role', responseData['role']);
|
||||||
await prefs.setString('email', email);
|
await prefs.setString('email', email);
|
||||||
|
await prefs.setString('userId', responseData['userId'].toString());
|
||||||
|
|
||||||
// Redirect berdasarkan role
|
// Redirect berdasarkan role
|
||||||
if (responseData['role'] == 'admin') {
|
if (responseData['role'] == 'admin') {
|
||||||
|
|
|
@ -1,37 +1,292 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/api_services/api_services.dart';
|
||||||
|
import 'detail_riwayat_page.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'login_page.dart';
|
||||||
|
|
||||||
class RiwayatDiagnosaPage extends StatelessWidget {
|
class RiwayatDiagnosaPage extends StatefulWidget {
|
||||||
final List<Map<String, String>> diagnosaList = [
|
final String? userId;
|
||||||
{
|
|
||||||
"nama": "Karat Putih",
|
RiwayatDiagnosaPage({this.userId});
|
||||||
"deskripsi": "Penyakit yang umum pada bayam.",
|
|
||||||
"penyakit": "Karat Putih",
|
@override
|
||||||
"hama": "Tidak ada hama spesifik",
|
_RiwayatDiagnosaPageState createState() => _RiwayatDiagnosaPageState();
|
||||||
"penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.",
|
}
|
||||||
"gambar": "assets/images/karat putih.jpeg",
|
|
||||||
},
|
class _RiwayatDiagnosaPageState extends State<RiwayatDiagnosaPage> {
|
||||||
{
|
List<Map<String, dynamic>> _riwayatData = [];
|
||||||
"nama": "Virus Keriting",
|
final ApiService apiService = ApiService();
|
||||||
"deskripsi": "Disebabkan oleh infeksi virus.",
|
bool _isLoading = true;
|
||||||
"penyakit": "Virus Keriting",
|
String? _errorMessage;
|
||||||
"hama": "Tidak ada hama spesifik",
|
String? _userId;
|
||||||
"penanganan": "Musnahkan tanaman terinfeksi dan kontrol vektor seperti kutu daun."
|
String? _token;
|
||||||
},
|
String? _email;
|
||||||
{
|
|
||||||
"nama": "Kekurangan Mangan",
|
@override
|
||||||
"deskripsi": "Kekurangan unsur hara mikro.",
|
void initState() {
|
||||||
"penyakit": "Kekurangan Mangan",
|
super.initState();
|
||||||
"hama": "Tidak ada hama spesifik",
|
_loadUserDataAndFetchHistori();
|
||||||
"penanganan": "Tambahkan pupuk yang mengandung mangan (Mn)."
|
}
|
||||||
},
|
|
||||||
{
|
Future<void> _loadUserDataAndFetchHistori() async {
|
||||||
"nama": "Downy Mildew",
|
try {
|
||||||
"deskripsi": "Penyakit jamur pada bayam.",
|
setState(() {
|
||||||
"penyakit": "Downy Mildew",
|
_isLoading = true;
|
||||||
"hama": "Tidak ada hama spesifik",
|
_errorMessage = null;
|
||||||
"penanganan": "Gunakan fungisida berbahan aktif metalaxyl dan perbaiki drainase tanah."
|
});
|
||||||
},
|
|
||||||
];
|
// Ambil data user yang sedang login dari SharedPreferences
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
_token = prefs.getString('token');
|
||||||
|
_email = prefs.getString('email');
|
||||||
|
|
||||||
|
// Check if already has userId from widget constructor
|
||||||
|
_userId = widget.userId;
|
||||||
|
|
||||||
|
print("Token from SharedPreferences: $_token");
|
||||||
|
print("Email from SharedPreferences: $_email");
|
||||||
|
print("Initial userId: $_userId");
|
||||||
|
|
||||||
|
// If no token or email, we can't proceed
|
||||||
|
if (_token == null || _email == null) {
|
||||||
|
throw Exception('Sesi login tidak ditemukan, silahkan login kembali');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have userId yet, we need to fetch user data first
|
||||||
|
if (_userId == null || _userId!.isEmpty) {
|
||||||
|
await _fetchUserData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double-check if userId is still null after fetching
|
||||||
|
if (_userId == null || _userId!.isEmpty) {
|
||||||
|
throw Exception('Gagal mendapatkan ID pengguna');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have userId, fetch the history data
|
||||||
|
await _fetchHistoriData();
|
||||||
|
} catch (e) {
|
||||||
|
print("Error in _loadUserDataAndFetchHistori: $e");
|
||||||
|
|
||||||
|
// Check if the error is about authentication
|
||||||
|
if (e.toString().contains('login') || e.toString().contains('sesi')) {
|
||||||
|
// Clear any existing user data and redirect to login
|
||||||
|
await ApiService.logoutUser();
|
||||||
|
|
||||||
|
// Navigate to login page in the next frame
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => LoginPage()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_errorMessage = e.toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user data to get user ID
|
||||||
|
Future<void> _fetchUserData() async {
|
||||||
|
try {
|
||||||
|
// 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("Users data from server: ${users.length} 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 found: ${currentUser['name']} with ID: ${currentUser['id']}",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
throw Exception('Data pengguna tidak ditemukan');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save userId to SharedPreferences for future use
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setString('userId', currentUser['id'].toString());
|
||||||
|
|
||||||
|
// Update state with user ID
|
||||||
|
setState(() {
|
||||||
|
_userId = currentUser!['id'].toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
print("User ID set to: $_userId");
|
||||||
|
} else if (response.statusCode == 401) {
|
||||||
|
// Token tidak valid atau expired
|
||||||
|
await ApiService.logoutUser();
|
||||||
|
throw Exception('Sesi habis, silahkan login kembali');
|
||||||
|
} else {
|
||||||
|
throw Exception('Gagal mengambil data: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error fetching user data: $e");
|
||||||
|
throw e; // Re-throw for the caller to handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _groupHistoriByDiagnosis(
|
||||||
|
List<Map<String, dynamic>> data,
|
||||||
|
) {
|
||||||
|
final Map<String, Map<String, dynamic>> groupedData = {};
|
||||||
|
print("Data mentah dari API: $data");
|
||||||
|
|
||||||
|
for (var item in data) {
|
||||||
|
final String? penyakitNama = item['penyakit_nama'];
|
||||||
|
final String? hamaNama = item['hama_nama'];
|
||||||
|
|
||||||
|
final int? idPenyakit = item['id_penyakit'];
|
||||||
|
final int? idHama = item['id_hama'];
|
||||||
|
|
||||||
|
final hasPenyakit = penyakitNama != null && penyakitNama.toString().isNotEmpty;
|
||||||
|
final hasHama = hamaNama != null && hamaNama.toString().isNotEmpty;
|
||||||
|
|
||||||
|
if (!hasPenyakit && !hasHama) {
|
||||||
|
print("Item dilewati karena tidak memiliki penyakit atau hama: $item");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gabungkan nama penyakit dan hama jika keduanya ada
|
||||||
|
String diagnosisKey = '';
|
||||||
|
if (hasPenyakit && hasHama) {
|
||||||
|
diagnosisKey = '$penyakitNama & $hamaNama';
|
||||||
|
} else if (hasPenyakit) {
|
||||||
|
diagnosisKey = penyakitNama!;
|
||||||
|
} else {
|
||||||
|
diagnosisKey = hamaNama!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tentukan diagnosis_type hanya sebagai referensi
|
||||||
|
String diagnosisType =
|
||||||
|
hasPenyakit && hasHama
|
||||||
|
? 'penyakit & hama'
|
||||||
|
: hasPenyakit
|
||||||
|
? 'penyakit'
|
||||||
|
: 'hama';
|
||||||
|
|
||||||
|
// Inisialisasi grup jika belum ada
|
||||||
|
if (!groupedData.containsKey(diagnosisKey)) {
|
||||||
|
groupedData[diagnosisKey] = {
|
||||||
|
'diagnosis': diagnosisKey,
|
||||||
|
'diagnosis_type': diagnosisType,
|
||||||
|
'gejala': <String>[],
|
||||||
|
'hasil': item['hasil'],
|
||||||
|
'tanggal_diagnosa': item['tanggal_diagnosa'],
|
||||||
|
'id_penyakit': idPenyakit,
|
||||||
|
'id_hama': idHama,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan gejala jika belum ada
|
||||||
|
if (item['gejala_nama'] != null) {
|
||||||
|
final gejalaNama = item['gejala_nama'];
|
||||||
|
if (!groupedData[diagnosisKey]!['gejala'].contains(gejalaNama)) {
|
||||||
|
groupedData[diagnosisKey]!['gejala'].add(gejalaNama);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = groupedData.values.toList();
|
||||||
|
print("Hasil pengelompokan: $result");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> _fetchHistoriData() async {
|
||||||
|
try {
|
||||||
|
print("Fetching histori dengan userId: $_userId");
|
||||||
|
|
||||||
|
// Panggil API untuk mendapatkan data histori
|
||||||
|
final historiResponse = await apiService.fetchHistoriDenganDetail(
|
||||||
|
_userId!,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Kelompokkan data berdasarkan diagnosis
|
||||||
|
final groupedData = _groupHistoriByDiagnosis(historiResponse);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_riwayatData =
|
||||||
|
groupedData; // Use groupedData instead of historiResponse
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("Successfully fetched ${_riwayatData.length} history records");
|
||||||
|
} catch (e) {
|
||||||
|
print("Error fetching histori data: $e");
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_errorMessage = "Gagal memuat data riwayat: ${e.toString()}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorWidget() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.error_outline, size: 60, color: Colors.white70),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
_errorMessage ?? 'Terjadi kesalahan',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 24),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _loadUserDataAndFetchHistori,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Color(0xFF9DC08D),
|
||||||
|
),
|
||||||
|
child: Text('Coba Lagi'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyHistoryWidget() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.history, size: 60, color: Colors.white70),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Belum ada riwayat diagnosa.',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -39,154 +294,112 @@ class RiwayatDiagnosaPage extends StatelessWidget {
|
||||||
backgroundColor: Color(0xFF9DC08D),
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Color(0xFF9DC08D),
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
title: Align(
|
title: Text(
|
||||||
alignment: Alignment.topCenter,
|
"Riwayat Diagnosa",
|
||||||
child: Padding(
|
style: TextStyle(
|
||||||
padding: const EdgeInsets.only(right: 30), // Geser ke kiri
|
fontSize: 24,
|
||||||
child: Text(
|
fontWeight: FontWeight.bold,
|
||||||
"Riwayat Diagnosa",
|
color: Colors.white,
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
icon: Icon(Icons.arrow_back, color: Colors.white),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child:
|
||||||
children: [
|
_isLoading
|
||||||
SizedBox(height: 16),
|
? Center(child: CircularProgressIndicator(color: Colors.white))
|
||||||
Expanded(
|
: _errorMessage != null
|
||||||
child: ListView.builder(
|
? _buildErrorWidget()
|
||||||
itemCount: diagnosaList.length,
|
: _riwayatData.isEmpty
|
||||||
itemBuilder: (context, index) {
|
? _buildEmptyHistoryWidget()
|
||||||
final diagnosa = diagnosaList[index];
|
: ListView.builder(
|
||||||
return Card(
|
itemCount: _riwayatData.length,
|
||||||
elevation: 4,
|
itemBuilder: (context, index) {
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
final riwayat = _riwayatData[index];
|
||||||
child: ListTile(
|
|
||||||
title: Text(
|
// Safely handle potential null values
|
||||||
diagnosa["nama"] ?? "Tidak ada data",
|
List<dynamic> gejalaList = [];
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
if (riwayat.containsKey('gejala') &&
|
||||||
|
riwayat['gejala'] != null) {
|
||||||
|
gejalaList = riwayat['gejala'] as List<dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
final gejalaText =
|
||||||
|
gejalaList.isEmpty
|
||||||
|
? "Tidak ada gejala tercatat"
|
||||||
|
: gejalaList.join(', ');
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.only(bottom: 12.0),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"),
|
elevation: 3,
|
||||||
onTap: () {
|
child: Padding(
|
||||||
Navigator.push(
|
padding: const EdgeInsets.all(12.0),
|
||||||
context,
|
child: Column(
|
||||||
MaterialPageRoute(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
builder: (context) => DetailRiwayatPage(
|
children: [
|
||||||
detailRiwayat: {
|
Text(
|
||||||
"penyakit": diagnosa["penyakit"] ?? "",
|
'Diagnosis: ${riwayat['diagnosis']}',
|
||||||
"hama": diagnosa["hama"] ?? "",
|
style: TextStyle(
|
||||||
"penanganan": diagnosa["penanganan"] ?? "",
|
fontSize: 18,
|
||||||
"gambar": diagnosa["gambar"] ?? "",
|
fontWeight: FontWeight.bold,
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
);
|
Text(
|
||||||
},
|
'Gejala: $gejalaText',
|
||||||
),
|
style: TextStyle(fontSize: 14),
|
||||||
);
|
),
|
||||||
},
|
SizedBox(height: 4),
|
||||||
),
|
Text(
|
||||||
),
|
'Hasil: ${(riwayat['hasil'] as num?)?.toStringAsFixed(2) ?? "-"}',
|
||||||
],
|
style: TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Tanggal: ${riwayat['tanggal_diagnosa'] ?? "-"}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
print("Navigating to DetailRiwayatPage with data: $riwayat");
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder:
|
||||||
|
(context) => DetailRiwayatPage(
|
||||||
|
detailRiwayat:
|
||||||
|
riwayat, // Kirim data riwayat ke halaman detail
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text('Lihat Detail'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DetailRiwayatPage extends StatelessWidget {
|
|
||||||
final Map<String, String> detailRiwayat;
|
|
||||||
|
|
||||||
DetailRiwayatPage({required this.detailRiwayat});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Color(0xFF9DC08D),
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Color(0xFF9DC08D),
|
|
||||||
title: Align(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 30),
|
|
||||||
child: Text(
|
|
||||||
"Hasil Diagnosa",
|
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: IconButton(
|
|
||||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (detailRiwayat['gambar'] != null)
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Image.asset(
|
|
||||||
detailRiwayat['gambar']!,
|
|
||||||
height: 200,
|
|
||||||
width: 200,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Card(
|
|
||||||
elevation: 6,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Nama Penyakit: ${detailRiwayat['penyakit']}",
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
"Nama Hama: ${detailRiwayat['hama']}",
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Card(
|
|
||||||
elevation: 6,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Cara Penanganan:",
|
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black),
|
|
||||||
),
|
|
||||||
SizedBox(height: 10),
|
|
||||||
Text(
|
|
||||||
detailRiwayat['penanganan'] ?? "Data tidak tersedia",
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue