diff --git a/backend/controller/hamaController.js b/backend/controller/hamaController.js index 61e2d6d..42deecf 100644 --- a/backend/controller/hamaController.js +++ b/backend/controller/hamaController.js @@ -24,10 +24,12 @@ exports.getHamaById = async (req, res) => { } }; -// 🔹 Fungsi untuk menambahkan hama baru (kode otomatis & kategori default) +// Pastikan sudah import 'Hama' model dan multer middleware sebelumnya + exports.createHama = async (req, res) => { try { const { nama, deskripsi, penanganan } = req.body; + const file = req.file; // Cek kode terakhir const lastHama = await Hama.findOne({ order: [['id', 'DESC']] }); @@ -37,20 +39,28 @@ exports.createHama = async (req, res) => { newKode = `H${lastNumber.toString().padStart(2, '0')}`; } + // Cek kalau ada file yang diupload + let fotoPath = ''; + if (file) { + fotoPath = file.filename; + } + const newHama = await Hama.create({ kode: newKode, nama, kategori: 'hama', // Default kategori deskripsi, penanganan, + foto: fotoPath, // ⬅️ Masukkan nama file ke database }); res.status(201).json({ message: 'Hama berhasil ditambahkan', data: newHama }); } catch (error) { - res.status(500).json({ message: 'Gagal menambahkan hama', error }); + res.status(500).json({ message: 'Gagal menambahkan hama', error: error.message }); } }; + // 🔹 Fungsi untuk mengupdate hama berdasarkan ID exports.updateHama = async (req, res) => { try { diff --git a/backend/migrations/20250318213037-create-hama.js b/backend/migrations/20250318213037-create-hama.js index 1893852..2e89447 100644 --- a/backend/migrations/20250318213037-create-hama.js +++ b/backend/migrations/20250318213037-create-hama.js @@ -2,7 +2,7 @@ /** @type {import('sequelize-cli').Migration} */ module.exports = { async up(queryInterface, Sequelize) { - await queryInterface.createTable('hamas', { + await queryInterface.createTable('hama', { id: { allowNull: false, autoIncrement: true, @@ -12,7 +12,6 @@ module.exports = { kode: { type: Sequelize.STRING, allowNull: true - }, nama: { type: Sequelize.STRING, @@ -28,6 +27,6 @@ module.exports = { }); }, async down(queryInterface, Sequelize) { - await queryInterface.dropTable('hamas'); + await queryInterface.dropTable('hama'); } }; \ No newline at end of file diff --git a/backend/migrations/20250426132425-add-foto-to-hama.js b/backend/migrations/20250426132425-add-foto-to-hama.js new file mode 100644 index 0000000..bd7425c --- /dev/null +++ b/backend/migrations/20250426132425-add-foto-to-hama.js @@ -0,0 +1,14 @@ +'use strict'; +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn('hama', 'foto', { + type: Sequelize.STRING, + allowNull: true + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn('hama', 'foto'); + } +}; diff --git a/backend/models/hama.js b/backend/models/hama.js index 03ef990..5e0d7fb 100644 --- a/backend/models/hama.js +++ b/backend/models/hama.js @@ -27,6 +27,10 @@ Hama.init( type: DataTypes.STRING, allowNull: true, }, + foto: { + type: DataTypes.STRING, + allowNull: false, + } }, { sequelize, diff --git a/frontend/assets/images/Businessman.png b/frontend/assets/images/Businessman.png new file mode 100644 index 0000000..6df1f80 Binary files /dev/null and b/frontend/assets/images/Businessman.png differ diff --git a/frontend/assets/images/Caterpillar.png b/frontend/assets/images/Caterpillar.png new file mode 100644 index 0000000..066df53 Binary files /dev/null and b/frontend/assets/images/Caterpillar.png differ diff --git a/frontend/assets/images/Literature.png b/frontend/assets/images/Literature.png new file mode 100644 index 0000000..be8a070 Binary files /dev/null and b/frontend/assets/images/Literature.png differ diff --git a/frontend/assets/images/Order History.png b/frontend/assets/images/Order History.png new file mode 100644 index 0000000..2b9818f Binary files /dev/null and b/frontend/assets/images/Order History.png differ diff --git a/frontend/assets/images/Test Account.png b/frontend/assets/images/Test Account.png new file mode 100644 index 0000000..185047d Binary files /dev/null and b/frontend/assets/images/Test Account.png differ diff --git a/frontend/assets/images/Virus.png b/frontend/assets/images/Virus.png new file mode 100644 index 0000000..8062d90 Binary files /dev/null and b/frontend/assets/images/Virus.png differ diff --git a/frontend/assets/images/karat putih.jpeg b/frontend/assets/images/karat putih.jpeg new file mode 100644 index 0000000..6038492 Binary files /dev/null and b/frontend/assets/images/karat putih.jpeg differ diff --git a/frontend/lib/admin/admin_page.dart b/frontend/lib/admin/admin_page.dart index ed828bc..8168c53 100644 --- a/frontend/lib/admin/admin_page.dart +++ b/frontend/lib/admin/admin_page.dart @@ -6,56 +6,125 @@ import 'package:frontend/api_services/api_services.dart'; import 'package:frontend/user/login_page.dart'; class AdminPage extends StatelessWidget { - Future _logout(BuildContext context) async { + Future _logout(BuildContext context) async { await ApiService.logoutUser(); - Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginPage())); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => LoginPage()), + ); } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('Admin Dashboard'), + appBar: AppBar(title: Text('Admin Dashboard')), + drawer: Drawer( + child: Container( + color: Color(0xFFFFFFFF), + child: ListView( + padding: EdgeInsets.zero, + children: [ + SizedBox( + height: 70, + child: DrawerHeader( + decoration: BoxDecoration(color: Color(0xFF9DC08D)), + child: Text( + 'Menu Admin', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ), + ), + + ListTile( + title: Text('Halaman Hama'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => HamaPage()), + ); + }, + ), + ListTile( + title: Text('Halaman Penyakit'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => PenyakitPage()), + ); + }, + ), + ListTile( + title: Text('Halaman Gejala'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => GejalaPage()), + ); + }, + ), + ListTile(title: Text('Logout'), onTap: () => _logout(context)), + ], + ), + ), ), - body: Center( + body: Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Selamat datang Admin!', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + SizedBox(height: 24), + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildCard('Jumlah User', '10'), + _buildCard('Jumlah Diagnosa', '25'), + ], + ), + SizedBox(height: 16), // Spasi antar baris + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildCard('Penyakit', '15'), + _buildCard('Hama', '15'), + ], + ), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _buildCard(String title, String count) { + return Card( + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: 160, + height: 160, + padding: EdgeInsets.all(12), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => HamaPage()), - ); - }, - child: Text('Halaman Hama'), - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => PenyakitPage()), - ); - }, - child: Text('Halaman Penyakit'), - ), - SizedBox(height: 20), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => GejalaPage()), - ); - }, - child: Text('Halaman Gejala'), - ), - ElevatedButton( - onPressed: () => _logout(context), - child: Text('Logout'), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), ), + SizedBox(height: 10), + Text(count, style: TextStyle(fontSize: 20, color: Colors.green)), ], ), ), ); } -} \ No newline at end of file +} diff --git a/frontend/lib/admin/edit_hama_page.dart b/frontend/lib/admin/edit_hama_page.dart new file mode 100644 index 0000000..9e51a5e --- /dev/null +++ b/frontend/lib/admin/edit_hama_page.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Pastikan ini di-import ya + +class EditHamaPage extends StatefulWidget { + final int idHama; + final String namaAwal; + final String deskripsiAwal; + final String penangananAwal; + final VoidCallback onHamaUpdated; + + const EditHamaPage({ + Key? key, + required this.idHama, + required this.namaAwal, + required this.deskripsiAwal, + required this.penangananAwal, + required this.onHamaUpdated, + }) : super(key: key); + + @override + _EditHamaPageState createState() => _EditHamaPageState(); +} + +class _EditHamaPageState extends State { + final TextEditingController _namaController = TextEditingController(); + final TextEditingController _deskripsiController = TextEditingController(); + final TextEditingController _penangananController = TextEditingController(); + final ApiService apiService = ApiService(); + + @override + void initState() { + super.initState(); + _namaController.text = widget.namaAwal; + _deskripsiController.text = widget.deskripsiAwal; + _penangananController.text = widget.penangananAwal; + } + + @override + void dispose() { + _namaController.dispose(); + _deskripsiController.dispose(); + _penangananController.dispose(); + super.dispose(); + } + + Future _updateHama() async { + try { + await apiService.updateHama( + widget.idHama, + _namaController.text, + _deskripsiController.text, + _penangananController.text, + ); + widget.onHamaUpdated(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Data hama berhasil diperbarui')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal memperbarui data: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Edit Data Hama'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Card( + elevation: 5, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _namaController, + decoration: InputDecoration(labelText: 'Nama Hama'), + ), + SizedBox(height: 12), + TextField( + controller: _deskripsiController, + decoration: InputDecoration(labelText: 'Deskripsi'), + maxLines: 3, + ), + SizedBox(height: 12), + TextField( + controller: _penangananController, + decoration: InputDecoration(labelText: 'Penanganan'), + maxLines: 3, + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: _updateHama, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[300], + ), + child: Text( + 'Simpan Perubahan', + style: TextStyle(color: Colors.black), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/frontend/lib/admin/edit_penyakit_page.dart b/frontend/lib/admin/edit_penyakit_page.dart new file mode 100644 index 0000000..cddfc6f --- /dev/null +++ b/frontend/lib/admin/edit_penyakit_page.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Pastikan ini di-import ya + +class EditPenyakitPage extends StatefulWidget { + final int idPenyakit; + final String namaAwal; + final String deskripsiAwal; + final String penangananAwal; + final VoidCallback onPenyakitUpdated; + + const EditPenyakitPage({ + Key? key, + required this.idPenyakit, + required this.namaAwal, + required this.deskripsiAwal, + required this.penangananAwal, + required this.onPenyakitUpdated, + }) : super(key: key); + + @override + _EditPenyakitPageState createState() => _EditPenyakitPageState(); +} + +class _EditPenyakitPageState extends State { + final TextEditingController _namaController = TextEditingController(); + final TextEditingController _deskripsiController = TextEditingController(); + final TextEditingController _penangananController = TextEditingController(); + final ApiService apiService = ApiService(); + + @override + void initState() { + super.initState(); + _namaController.text = widget.namaAwal; + _deskripsiController.text = widget.deskripsiAwal; + _penangananController.text = widget.penangananAwal; + } + + @override + void dispose() { + _namaController.dispose(); + _deskripsiController.dispose(); + _penangananController.dispose(); + super.dispose(); + } + + Future _updatePenyakit() async { + try { + await apiService.updatePenyakit( + widget.idPenyakit, + _namaController.text, + _deskripsiController.text, + _penangananController.text, + ); + widget.onPenyakitUpdated(); + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Data penyakit berhasil diperbarui')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal memperbarui data: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Edit Data Penyakit'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Card( + elevation: 5, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _namaController, + decoration: InputDecoration(labelText: 'Nama Penyakit'), + ), + SizedBox(height: 12), + TextField( + controller: _deskripsiController, + decoration: InputDecoration(labelText: 'Deskripsi'), + maxLines: 3, + ), + SizedBox(height: 12), + TextField( + controller: _penangananController, + decoration: InputDecoration(labelText: 'Penanganan'), + maxLines: 3, + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: _updatePenyakit, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[300], + ), + child: Text( + 'Simpan Perubahan', + style: TextStyle(color: Colors.black), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/frontend/lib/admin/gejala_page.dart b/frontend/lib/admin/gejala_page.dart index 4fdd69e..8606b0d 100644 --- a/frontend/lib/admin/gejala_page.dart +++ b/frontend/lib/admin/gejala_page.dart @@ -68,6 +68,53 @@ class _GejalaPageState extends State { }, ); } + + void showEditDialog(BuildContext context, Map gejala) { + final TextEditingController editNamaController = TextEditingController(text: gejala['nama'] ?? ''); + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text( + 'Edit Hama', + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: editNamaController, + decoration: InputDecoration( + labelText: 'Nama', + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('Batal'), + ), + ElevatedButton( + onPressed: () async { + try { + await apiService.updateGejala( + gejala['id'], + editNamaController.text + ); + fetchGejala(); + Navigator.pop(context); + } catch (e) { + print("Error updating gejala: $e"); + } + }, + child: Text('Simpan', style: TextStyle(color: Colors.black)), + ), + ], + ); + }, + ); +} // 🔹 Hapus gejala dari API void _hapusGejala(int id) async { @@ -108,59 +155,124 @@ class _GejalaPageState extends State { } + +//pagination + int currentPage = 0; + int rowsPerPage = 10; + @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Halaman Gejala')), - body: Column( - children: [ - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(right: 20.0), - child: ElevatedButton( - onPressed: _tambahGejala, - child: Text('Tambah Gejala'), +Widget build(BuildContext context) { + int start = currentPage * rowsPerPage; + int end = (start + rowsPerPage < gejalaList.length) + ? start + rowsPerPage + : gejalaList.length; + List currentPageData = gejalaList.sublist(start, end); + + return Scaffold( + appBar: AppBar( + title: Text('Halaman Gejala'), + ), + body: Column( + children: [ + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: ElevatedButton( + onPressed: _tambahGejala, + child: Text( + 'Tambah Gejala', + style: TextStyle(color: Colors.green[200]), ), ), - ], - ), - SizedBox(height: 20), - Expanded( + ), + ], + ), + SizedBox(height: 20), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.9, + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: DataTable( columnSpacing: 20, - headingRowColor: MaterialStateColor.resolveWith((states) => Colors.grey[300]!), + headingRowColor: MaterialStateColor.resolveWith( + (states) => const Color(0xFF9DC08D), + ), columns: [ - DataColumn(label: SizedBox(width: 50, child: Text('No'))), + DataColumn(label: SizedBox(width: 35, child: Text('No'))), DataColumn(label: SizedBox(width: 80, child: Text('Kode'))), DataColumn(label: SizedBox(width: 150, child: Text('Nama'))), DataColumn(label: SizedBox(width: 80, child: Text('Aksi'))), ], - rows: gejalaList.map( - (gejala) => DataRow(cells: [ - DataCell(Text((gejalaList.indexOf(gejala) + 1).toString())), // Nomor - DataCell(Text(gejala['kode'])), // Kode Gejala - DataCell(Text(gejala['nama'])), // Nama Gejala - DataCell( - IconButton( - icon: Icon(Icons.delete, color: Colors.red), - onPressed: () => _konfirmasiHapus(gejala['id']), // Hapus data - ), + rows: [ + ...currentPageData.map( + (gejala) => DataRow( + cells: [ + DataCell(Text((gejalaList.indexOf(gejala) + 1).toString())), + DataCell(Text(gejala['kode'] ?? '-')), + DataCell(Text(gejala['nama'] ?? '-')), + DataCell( + Row( + children: [ + IconButton( + icon: Icon(Icons.edit, color: Color(0xFF9DC08D)), + onPressed: () => showEditDialog(context, gejala), + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: () => _konfirmasiHapus(gejala['id']), + ), + ], + ), + ), + ], ), - ]), - ).toList(), + ), + DataRow( + cells: [ + DataCell(Container()), + DataCell(Container()), + DataCell( + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.chevron_left), + onPressed: currentPage > 0 + ? () => setState(() => currentPage--) + : null, + ), + Text(' ${currentPage + 1}'), + IconButton( + icon: Icon(Icons.chevron_right), + onPressed: + (currentPage + 1) * rowsPerPage < gejalaList.length + ? () => setState(() => currentPage++) + : null, + ), + ], + ), + ), + ), + DataCell(Container()), + ], + ), + ], ), ), ), ), - ], - ), - ); - } + ), + ], + ), + ); +} + } diff --git a/frontend/lib/admin/hama_page.dart b/frontend/lib/admin/hama_page.dart index 700d3c2..c980dc2 100644 --- a/frontend/lib/admin/hama_page.dart +++ b/frontend/lib/admin/hama_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/api_services/api_services.dart'; - +import 'tambah_hama_page.dart'; +import 'edit_hama_page.dart'; class HamaPage extends StatefulWidget { @override @@ -39,98 +40,167 @@ class _HamaPageState extends State { } void _konfirmasiHapus(int id) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Konfirmasi Hapus'), - content: Text('Apakah Anda yakin ingin menghapus gejala ini?'), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); // Tutup pop-up tanpa menghapus - }, - child: Text('Tidak'), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); // Tutup pop-up - _hapusHama(id); // Lanjutkan proses hapus - }, - child: Text('Ya, Hapus'), - style: ElevatedButton.styleFrom(backgroundColor: Colors.red), - ), - ], - ); - }, - ); -} - - void _tambahHama() { - TextEditingController namaController = TextEditingController(); - TextEditingController penangananController = TextEditingController(); - TextEditingController deskripsiController = TextEditingController(); - showDialog( context: context, builder: (context) { return AlertDialog( - title: Text('Tambah Hama Baru'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: namaController, - decoration: InputDecoration(labelText: 'Nama'), - ), - TextField( - controller: deskripsiController, - decoration: InputDecoration(labelText: 'Deskripsi'), - ), - TextField( - controller: penangananController, - decoration: InputDecoration(labelText: 'Penanganan'), - ), - ], - ), + title: Text('Konfirmasi Hapus'), + content: Text('Apakah Anda yakin ingin menghapus gejala ini?'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), - child: Text('Batal'), + onPressed: () { + Navigator.pop(context); // Tutup pop-up tanpa menghapus + }, + child: Text('Tidak'), ), ElevatedButton( - onPressed: () async { - if (namaController.text.isNotEmpty && - deskripsiController.text.isNotEmpty && - penangananController.text.isNotEmpty) { - try { - await apiService.createHama( - namaController.text, - deskripsiController.text, - penangananController.text, - ); - _fetchHama(); - Navigator.pop(context); - } catch (e) { - print("Error adding hama: $e"); - } - } + onPressed: () { + Navigator.pop(context); // Tutup pop-up + _hapusHama(id); // Lanjutkan proses hapus }, - child: Text('Simpan'), + child: Text('Ya, Hapus'), + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), ), ], ); }, - ).then((_) { - namaController.dispose(); - deskripsiController.dispose(); - penangananController.dispose(); - }); + ); } + // void _tambahHama() { + // TextEditingController namaController = TextEditingController(); + // TextEditingController penangananController = TextEditingController(); + // TextEditingController deskripsiController = TextEditingController(); + + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // title: Text('Tambah Hama Baru'), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // TextField( + // controller: namaController, + // decoration: InputDecoration(labelText: 'Nama'), + // ), + // TextField( + // controller: deskripsiController, + // decoration: InputDecoration(labelText: 'Deskripsi'), + // ), + // TextField( + // controller: penangananController, + // decoration: InputDecoration(labelText: 'Penanganan'), + // ), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text('Batal', style: TextStyle(color: Colors.black)), + // ), + // ElevatedButton( + // onPressed: () async { + // if (namaController.text.isNotEmpty && + // deskripsiController.text.isNotEmpty && + // penangananController.text.isNotEmpty) { + // try { + // await apiService.createHama( + // namaController.text, + // deskripsiController.text, + // penangananController.text, + // ); + // _fetchHama(); + // Navigator.pop(context); + // } catch (e) { + // print("Error adding hama: $e"); + // } + // } + // }, + // child: Text('Simpan', style: TextStyle(color: Colors.black)), + // ), + // ], + // ); + // }, + // ).then((_) { + // namaController.dispose(); + // deskripsiController.dispose(); + // penangananController.dispose(); + // }); + // } + + // void showEditDialog(BuildContext context, Map hama) { + // final TextEditingController editNamaController = TextEditingController( + // text: hama['nama'] ?? '', + // ); + // final TextEditingController editDeskripsiController = TextEditingController( + // text: hama['deskripsi'] ?? '', + // ); + // final TextEditingController editPenangananController = + // TextEditingController(text: hama['penanganan'] ?? ''); + + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // title: Text('Edit Hama'), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // TextField( + // controller: editNamaController, + // decoration: InputDecoration(labelText: 'Nama'), + // ), + // TextField( + // controller: editDeskripsiController, + // decoration: InputDecoration(labelText: 'Deskripsi'), + // ), + // TextField( + // controller: editPenangananController, + // decoration: InputDecoration(labelText: 'Penanganan'), + // ), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text('Batal'), + // ), + // ElevatedButton( + // onPressed: () async { + // try { + // await apiService.updateHama( + // hama['id'], + // editNamaController.text, + // editDeskripsiController.text, + // editPenangananController.text, + // ); + // _fetchHama(); + // Navigator.pop(context); + // } catch (e) { + // print("Error updating hama: $e"); + // } + // }, + // child: Text('Simpan', style: TextStyle(color: Colors.black)), + // ), + // ], + // ); + // }, + // ); + // } + + //pagination + int currentPage = 0; + int rowsPerPage = 10; @override Widget build(BuildContext context) { + int start = currentPage * rowsPerPage; + int end = + (start + rowsPerPage < hamaList.length) + ? start + rowsPerPage + : hamaList.length; + List currentPageData = hamaList.sublist(start, end); return Scaffold( appBar: AppBar(title: Text('Halaman Hama')), body: Column( @@ -142,45 +212,147 @@ class _HamaPageState extends State { Padding( padding: const EdgeInsets.only(right: 20.0), child: ElevatedButton( - onPressed: _tambahHama, // Fungsi untuk menambah data hama - child: Text('Tambah Hama'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => TambahHamaPage( + onHamaAdded: + _fetchHama, // Panggil fungsi refresh setelah tambah + ), + ), + ); + }, + child: Text( + 'Tambah Hama', + style: TextStyle(color: Colors.green[200]), + ), ), ), ], ), SizedBox(height: 20), Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.9, - child: DataTable( - columnSpacing: 20, - headingRowColor: - MaterialStateColor.resolveWith((states) => Colors.grey[300]!), - columns: [ - DataColumn(label: SizedBox(width: 35, child: Text('No'))), - DataColumn(label: SizedBox(width: 50, child: Text('Kode'))), - DataColumn(label: SizedBox(width: 100, child: Text('Nama'))), - DataColumn(label: SizedBox(width: 100, child: Text('Deskripsi'))), - DataColumn(label: SizedBox(width: 100, child: Text('Penanganan'))), - DataColumn(label: SizedBox(width: 50, child: Text('Aksi'))), - ], - rows: hamaList.map( - (hama) => DataRow(cells: [ - DataCell(Text((hamaList.indexOf(hama) + 1).toString())), // Nomor - DataCell(Text(hama['kode'] ?? '-')), // Kode Hama - DataCell(Text(hama['nama'] ?? '-')), // Nama Hama - DataCell(Text(hama['deskripsi'] ?? '-')), // Deskripsi - DataCell(Text(hama['penanganan'] ?? '-')), // Penanganan - DataCell( - IconButton( - icon: Icon(Icons.delete, color: Colors.red), - onPressed: () => _konfirmasiHapus(hama['id']), // Hapus data + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + columnSpacing: 20, + headingRowColor: MaterialStateColor.resolveWith( + (states) => const Color(0xFF9DC08D), + ), + columns: [ + DataColumn(label: SizedBox(width: 35, child: Text('No'))), + DataColumn( + label: SizedBox(width: 50, child: Text('Kode')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Nama')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Deskripsi')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Penanganan')), + ), + DataColumn( + label: SizedBox(width: 50, child: Text('Aksi')), + ), + ], + rows: [ + ...currentPageData.map( + (hama) => DataRow( + cells: [ + DataCell( + Text((hamaList.indexOf(hama) + 1).toString()), + ), + DataCell(Text(hama['kode'] ?? '-')), + DataCell(Text(hama['nama'] ?? '-')), + DataCell(Text(hama['deskripsi'] ?? '-')), + DataCell(Text(hama['penanganan'] ?? '-')), + DataCell( + Row( + children: [ + IconButton( + icon: Icon( + Icons.edit, + color: Color(0xFF9DC08D), + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => EditHamaPage( + idHama: + hama['id'], // pastikan 'hama' adalah Map dari API kamu + namaAwal: hama['nama'] ?? '', + deskripsiAwal: + hama['deskripsi'] ?? '', + penangananAwal: + hama['penanganan'] ?? '', + onHamaUpdated: + _fetchHama, // fungsi untuk refresh list setelah update + ), + ), + ); + }, + ), + + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: + () => _konfirmasiHapus(hama['id']), + ), + ], + ), + ), + ], ), ), - ]), - ).toList(), + DataRow( + cells: [ + DataCell(Container()), + DataCell(Container()), + DataCell( + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.chevron_left), + onPressed: + currentPage > 0 + ? () => + setState(() => currentPage--) + : null, + ), + Text(' ${currentPage + 1}'), + IconButton( + icon: Icon(Icons.chevron_right), + onPressed: + (currentPage + 1) * rowsPerPage < + hamaList.length + ? () => + setState(() => currentPage++) + : null, + ), + ], + ), + ), + ), + DataCell(Container()), + DataCell(Container()), + DataCell(Container()), + ], + ), + ], + ), ), ), ), @@ -189,5 +361,4 @@ class _HamaPageState extends State { ), ); } - } diff --git a/frontend/lib/admin/penyakit_page.dart b/frontend/lib/admin/penyakit_page.dart index eb10bed..7ab7069 100644 --- a/frontend/lib/admin/penyakit_page.dart +++ b/frontend/lib/admin/penyakit_page.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:frontend/admin/edit_penyakit_page.dart'; import 'package:frontend/api_services/api_services.dart'; +import 'tambah_penyakit_page.dart'; +import 'edit_penyakit_page.dart'; class PenyakitPage extends StatefulWidget { @override @@ -7,10 +10,10 @@ class PenyakitPage extends StatefulWidget { } class _PenyakitPageState extends State { - final ApiService apiService = ApiService(); + final ApiService apiService = ApiService(); List> penyakitList = []; - @override + @override void initState() { super.initState(); _fetchPenyakit(); @@ -38,97 +41,173 @@ class _PenyakitPageState extends State { } void _konfirmasiHapus(int id) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Konfirmasi Hapus'), - content: Text('Apakah Anda yakin ingin menghapus gejala ini?'), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); // Tutup pop-up tanpa menghapus - }, - child: Text('Tidak'), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); // Tutup pop-up - _hapusPenyakit(id); // Lanjutkan proses hapus - }, - child: Text('Ya, Hapus'), - style: ElevatedButton.styleFrom(backgroundColor: Colors.red), - ), - ], - ); - }, - ); -} - - void _tambahPenyakit() { - TextEditingController namaController = TextEditingController(); - TextEditingController penangananController = TextEditingController(); - TextEditingController deskripsiController = TextEditingController(); - showDialog( context: context, builder: (context) { return AlertDialog( - title: Text('Tambah Penyakit Baru'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: namaController, - decoration: InputDecoration(labelText: 'Nama'), - ), - TextField( - controller: deskripsiController, - decoration: InputDecoration(labelText: 'Deskripsi'), - ), - TextField( - controller: penangananController, - decoration: InputDecoration(labelText: 'Penanganan'), - ), - ], - ), + title: Text('Konfirmasi Hapus'), + content: Text('Apakah Anda yakin ingin menghapus gejala ini?'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), - child: Text('Batal'), + onPressed: () { + Navigator.pop(context); // Tutup pop-up tanpa menghapus + }, + child: Text('Tidak'), ), ElevatedButton( - onPressed: () async { - if (namaController.text.isNotEmpty && - deskripsiController.text.isNotEmpty && - penangananController.text.isNotEmpty) { - try { - await apiService.createPenyakit( - namaController.text, - deskripsiController.text, - penangananController.text, - ); - _fetchPenyakit(); - Navigator.pop(context); - } catch (e) { - print("Error adding penyakit: $e"); - } - } + onPressed: () { + Navigator.pop(context); // Tutup pop-up + _hapusPenyakit(id); // Lanjutkan proses hapus }, - child: Text('Simpan'), + child: Text('Ya, Hapus'), + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), ), ], ); }, - ).then((_) { - namaController.dispose(); - deskripsiController.dispose(); - penangananController.dispose(); - }); + ); } + // void _tambahPenyakit() { + // TextEditingController namaController = TextEditingController(); + // TextEditingController penangananController = TextEditingController(); + // TextEditingController deskripsiController = TextEditingController(); + + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // title: Text('Tambah Penyakit Baru'), + // content: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // TextField( + // controller: namaController, + // decoration: InputDecoration(labelText: 'Nama'), + // ), + // TextField( + // controller: deskripsiController, + // decoration: InputDecoration(labelText: 'Deskripsi'), + // ), + // TextField( + // controller: penangananController, + // decoration: InputDecoration(labelText: 'Penanganan'), + // ), + // ], + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.pop(context), + // child: Text('Batal'), + // ), + // ElevatedButton( + // onPressed: () async { + // if (namaController.text.isNotEmpty && + // deskripsiController.text.isNotEmpty && + // penangananController.text.isNotEmpty) { + // try { + // await apiService.createPenyakit( + // namaController.text, + // deskripsiController.text, + // penangananController.text, + // ); + // _fetchPenyakit(); + // Navigator.pop(context); + // } catch (e) { + // print("Error adding penyakit: $e"); + // } + // } + // }, + // child: Text('Simpan'), + // ), + // ], + // ); + // }, + // ).then((_) { + // namaController.dispose(); + // deskripsiController.dispose(); + // penangananController.dispose(); + // }); + // } + +// void showEditDialog(BuildContext context, Map penyakit) { +// final TextEditingController editNamaController = TextEditingController(text: penyakit['nama'] ?? ''); +// final TextEditingController editDeskripsiController = TextEditingController(text: penyakit['deskripsi'] ?? ''); +// final TextEditingController editPenangananController = TextEditingController(text: penyakit['penanganan'] ?? ''); + +// showDialog( +// context: context, +// builder: (context) { +// return AlertDialog( +// title: Text( +// 'Edit Penyakit', +// ), +// content: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// TextField( +// controller: editNamaController, +// decoration: InputDecoration( +// labelText: 'Nama', +// ), +// ), +// TextField( +// controller: editDeskripsiController, +// decoration: InputDecoration( +// labelText: 'Deskripsi', +// ), +// ), +// TextField( +// controller: editPenangananController, +// decoration: InputDecoration( +// labelText: 'Penanganan', +// ), +// ), +// ], +// ), +// actions: [ +// TextButton( +// onPressed: () => Navigator.pop(context), +// child: Text( +// 'Batal', +// style: TextStyle(color: Colors.black), +// ), +// ), +// ElevatedButton( +// onPressed: () async { +// try { +// await apiService.updatePenyakit( +// penyakit['id'], +// editNamaController.text, +// editDeskripsiController.text, +// editPenangananController.text, +// ); +// _fetchPenyakit(); +// Navigator.pop(context); +// } catch (e) { +// print("Error updating penyakit: $e"); +// } +// }, +// child: Text('Simpan', style: TextStyle(color: Colors.black)), +// ), +// ], +// ); +// }, +// ); +// } + + //pagination + int currentPage = 0; + int rowsPerPage = 10; + @override Widget build(BuildContext context) { + int start = currentPage * rowsPerPage; + int end = + (start + rowsPerPage < penyakitList.length) + ? start + rowsPerPage + : penyakitList.length; + List currentPageData = penyakitList.sublist(start, end); return Scaffold( appBar: AppBar(title: Text('Halaman Penyakit')), body: Column( @@ -140,45 +219,146 @@ class _PenyakitPageState extends State { Padding( padding: const EdgeInsets.only(right: 20.0), child: ElevatedButton( - onPressed: _tambahPenyakit, // Fungsi untuk menambah data penyakit - child: Text('Tambah Penyakit'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => TambahPenyakitPage( + onPenyakitAdded: + _fetchPenyakit, // Panggil fungsi refresh setelah tambah + ), + ), + ); + }, // Fungsi untuk menambah data penyakit + child: Text( + 'Tambah Penyakit', + style: TextStyle(color: Colors.green[200]), + ), ), ), ], ), SizedBox(height: 20), Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.95, - child: DataTable( - columnSpacing: 5, - headingRowColor: - MaterialStateColor.resolveWith((states) => Colors.grey[300]!), - columns: [ - DataColumn(label: SizedBox(width: 35, child: Text('No'))), - DataColumn(label: SizedBox(width: 50, child: Text('Kode'))), - DataColumn(label: SizedBox(width: 100, child: Text('Nama'))), - DataColumn(label: SizedBox(width: 100, child: Text('Deskripsi'))), - DataColumn(label: SizedBox(width: 100, child: Text('Penanganan'))), - DataColumn(label: SizedBox(width: 50, child: Text('Aksi'))), - ], - rows: penyakitList.map( - (penyakit) => DataRow(cells: [ - DataCell(Text((penyakitList.indexOf(penyakit) + 1).toString())), // Nomor - DataCell(Text(penyakit['kode'] ?? '-')), // Kode Penyakit - DataCell(Text(penyakit['nama'] ?? '-')), // Nama Penyakit - DataCell(Text(penyakit['deskripsi'] ?? '-')), // Deskripsi - DataCell(Text(penyakit['penanganan'] ?? '-')), // Penanganan - DataCell( - IconButton( - icon: Icon(Icons.delete, color: Colors.red), - onPressed: () => _konfirmasiHapus(penyakit['id']), // Hapus data + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + columnSpacing: 20, + headingRowColor: MaterialStateColor.resolveWith( + (states) => const Color(0xFF9DC08D), + ), + columns: [ + DataColumn(label: SizedBox(width: 35, child: Text('No'))), + DataColumn( + label: SizedBox(width: 50, child: Text('Kode')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Nama')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Deskripsi')), + ), + DataColumn( + label: SizedBox(width: 100, child: Text('Penanganan')), + ), + DataColumn( + label: SizedBox(width: 50, child: Text('Aksi')), + ), + ], + rows: [ + ...currentPageData.map( + (penyakit) => DataRow( + cells: [ + DataCell( + Text((penyakitList.indexOf(penyakit) + 1).toString()), + ), + DataCell(Text(penyakit['kode'] ?? '-')), + DataCell(Text(penyakit['nama'] ?? '-')), + DataCell(Text(penyakit['deskripsi'] ?? '-')), + DataCell(Text(penyakit['penanganan'] ?? '-')), + DataCell( + Row( + children: [ + IconButton( + icon: Icon( + Icons.edit, + color: Color(0xFF9DC08D), + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => EditPenyakitPage( + idPenyakit: + penyakit['id'], // pastikan 'hama' adalah Map dari API kamu + namaAwal: penyakit['nama'] ?? '', + deskripsiAwal: + penyakit['deskripsi'] ?? '', + penangananAwal: + penyakit['penanganan'] ?? '', + onPenyakitUpdated: + _fetchPenyakit, // fungsi untuk refresh list setelah update + ), + ), + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: + () => _konfirmasiHapus(penyakit['id']), + ), + ], + ), + ), + ], ), ), - ]), - ).toList(), + DataRow( + cells: [ + DataCell(Container()), + DataCell(Container()), + DataCell( + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.chevron_left), + onPressed: + currentPage > 0 + ? () => + setState(() => currentPage--) + : null, + ), + Text(' ${currentPage + 1}'), + IconButton( + icon: Icon(Icons.chevron_right), + onPressed: + (currentPage + 1) * rowsPerPage < + penyakitList.length + ? () => + setState(() => currentPage++) + : null, + ), + ], + ), + ), + ), + DataCell(Container()), + DataCell(Container()), + DataCell(Container()), + ], + ), + ], + ), ), ), ), diff --git a/frontend/lib/admin/tambah_hama_page.dart b/frontend/lib/admin/tambah_hama_page.dart new file mode 100644 index 0000000..d10e247 --- /dev/null +++ b/frontend/lib/admin/tambah_hama_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; + +class TambahHamaPage extends StatefulWidget { + final VoidCallback onHamaAdded; + + TambahHamaPage({required this.onHamaAdded}); + + @override + _TambahHamaPageState createState() => _TambahHamaPageState(); +} + +class _TambahHamaPageState extends State { + final TextEditingController namaController = TextEditingController(); + final TextEditingController deskripsiController = TextEditingController(); + final TextEditingController penangananController = TextEditingController(); + final ApiService apiService = ApiService(); + + @override + void dispose() { + namaController.dispose(); + deskripsiController.dispose(); + penangananController.dispose(); + super.dispose(); + } + + Future _simpanHama() async { + if (namaController.text.isNotEmpty && + deskripsiController.text.isNotEmpty && + penangananController.text.isNotEmpty) { + try { + await apiService.createHama( + namaController.text, + deskripsiController.text, + penangananController.text, + ); + widget.onHamaAdded(); + Navigator.pop(context); + _showDialog('Berhasil', 'Data hama berhasil ditambahkan.'); + } catch (e) { + _showDialog('Gagal', 'Gagal menambahkan data hama.'); + print("Error adding hama: $e"); + } + } else { + _showDialog('Error', 'Semua field harus diisi.'); + } + } + + void _showDialog(String title, String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('OK'), + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Tambah Hama Baru'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + child: Container( + width: 320, // atur lebar card box + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + TextField( + controller: namaController, + decoration: InputDecoration(labelText: 'Nama Hama'), + ), + SizedBox(height: 15), + TextField( + controller: deskripsiController, + decoration: InputDecoration(labelText: 'Deskripsi Hama'), + maxLines: 3, // Biar lebih panjang untuk deskripsi + ), + SizedBox(height: 15), + TextField( + controller: penangananController, + decoration: InputDecoration(labelText: 'Penanganan Hama'), + maxLines: 3, + ), + SizedBox(height: 15), + ], + ), + ), + ), + SizedBox(height: 30), // jarak antara card dan tombol + ElevatedButton( + onPressed: _simpanHama, + child: Text('Simpan Data'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[300], + padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), + textStyle: TextStyle(fontSize: 16, color: Colors.black), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/lib/admin/tambah_penyakit_page.dart b/frontend/lib/admin/tambah_penyakit_page.dart new file mode 100644 index 0000000..d6ee55c --- /dev/null +++ b/frontend/lib/admin/tambah_penyakit_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; + +class TambahPenyakitPage extends StatefulWidget { + final VoidCallback onPenyakitAdded; + + TambahPenyakitPage({required this.onPenyakitAdded}); + + @override + _TambahPenyakitPageState createState() => _TambahPenyakitPageState(); +} + +class _TambahPenyakitPageState extends State { + final TextEditingController namaController = TextEditingController(); + final TextEditingController deskripsiController = TextEditingController(); + final TextEditingController penangananController = TextEditingController(); + final ApiService apiService = ApiService(); + + @override + void dispose() { + namaController.dispose(); + deskripsiController.dispose(); + penangananController.dispose(); + super.dispose(); + } + + Future _simpanPenyakit() async { + if (namaController.text.isNotEmpty && + deskripsiController.text.isNotEmpty && + penangananController.text.isNotEmpty) { + try { + await apiService.createPenyakit( + namaController.text, + deskripsiController.text, + penangananController.text, + ); + widget.onPenyakitAdded(); + Navigator.pop(context); + _showDialog('Berhasil', 'Data penyakit berhasil ditambahkan.'); + } catch (e) { + _showDialog('Gagal', 'Gagal menambahkan data penyakit.'); + print("Error adding penyakit: $e"); + } + } else { + _showDialog('Error', 'Semua field harus diisi.'); + } + } + + void _showDialog(String title, String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text('OK'), + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Tambah Penyakit Baru'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Card( + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + child: Container( + width: 320, // atur lebar card box + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + TextField( + controller: namaController, + decoration: InputDecoration(labelText: 'Nama Penyakit'), + ), + SizedBox(height: 15), + TextField( + controller: deskripsiController, + decoration: InputDecoration(labelText: 'Deskripsi Penyakit'), + maxLines: 3, // Biar lebih panjang untuk deskripsi + ), + SizedBox(height: 15), + TextField( + controller: penangananController, + decoration: InputDecoration(labelText: 'Penanganan Penyakit'), + maxLines: 3, + ), + SizedBox(height: 15), + ], + ), + ), + ), + SizedBox(height: 30), // jarak antara card dan tombol + ElevatedButton( + onPressed: _simpanPenyakit, + child: Text('Simpan Data'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[300], + padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), + textStyle: TextStyle(fontSize: 16, color: Colors.black), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/lib/api_services/api_services.dart b/frontend/lib/api_services/api_services.dart index 6456806..4a5a7b2 100644 --- a/frontend/lib/api_services/api_services.dart +++ b/frontend/lib/api_services/api_services.dart @@ -281,5 +281,51 @@ class ApiService { throw Exception('Gagal menghapus penyakit'); } } + + //registrasi + Future registerUser({ + required String name, + required String email, + required String password, + required String alamat, + required String nomorTelepon, + }) async { + final response = await http.post( + Uri.parse('$baseUrl/register'), // Endpoint register + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ + 'name': name, + 'email': email, + 'password': password, + 'alamat': alamat, + 'nomorTelepon': nomorTelepon, + 'role': 'user', // role default + }), + ); + + if (response.statusCode != 201) { + throw Exception(jsonDecode(response.body)['message'] ?? 'Gagal mendaftar'); + } + } + + // Fungsi untuk lupa password + Future forgotPassword({ + required String email, + required String newPassword, + }) async { + final response = await http.post( + Uri.parse('$baseUrl/forgot-password'), + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ + 'email': email, + 'password': newPassword, // Kirim password baru + }), + ); + + if (response.statusCode != 200) { + throw Exception(jsonDecode(response.body)['message'] ?? 'Gagal memperbarui password'); + } + } + } diff --git a/frontend/lib/user/basis_pengetahuan_page.dart b/frontend/lib/user/basis_pengetahuan_page.dart index d41eb14..c4e4bb1 100644 --- a/frontend/lib/user/basis_pengetahuan_page.dart +++ b/frontend/lib/user/basis_pengetahuan_page.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'hama_page.dart'; +import 'penyakit_page.dart'; class BasisPengetahuanPage extends StatelessWidget { @override @@ -31,7 +33,12 @@ class BasisPengetahuanPage extends StatelessWidget { // Button pertama ElevatedButton( onPressed: () { - // Aksi untuk button pertama + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HamaPage(), + ), // Perbaikan di sini + ); }, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( @@ -67,7 +74,12 @@ class BasisPengetahuanPage extends StatelessWidget { // Button kedua ElevatedButton( onPressed: () { - // Aksi untuk button kedua + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PenyakitPage(), + ), + ); }, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( diff --git a/frontend/lib/user/detail_hama_page.dart b/frontend/lib/user/detail_hama_page.dart new file mode 100644 index 0000000..a4fee49 --- /dev/null +++ b/frontend/lib/user/detail_hama_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; + +class DetailHamaPage extends StatelessWidget { + final Map detailRiwayat; + + const DetailHamaPage({required this.detailRiwayat}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFF9DC08D), + appBar: AppBar( + backgroundColor: Color(0xFF9DC08D), + title: Text( + "Detail Hama", + style: TextStyle(color: Colors.white), + ), + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + if (detailRiwayat["gambar"] != null && detailRiwayat["gambar"]!.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.asset( + detailRiwayat["gambar"]!, + height: 200, + width: 200, // Biar gambar full lebar + fit: BoxFit.cover, + ), + ), + SizedBox(height: 16), + + // Card Nama Hama + SizedBox( + width: double.infinity, // Bikin card full lebar + child: 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 Hama:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailRiwayat["nama hama"] ?? "Nama hama tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ), + ), + SizedBox(height: 16), + + // Card Deskripsi + Penanganan + SizedBox( + width: double.infinity, // Bikin card full lebar + child: Card( + elevation: 6, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Deskripsi:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailRiwayat["deskripsi"] ?? "Deskripsi tidak tersedia", + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 16), + Text( + "Penanganan:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailRiwayat["penanganan"] ?? "Penanganan tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/lib/user/detail_penyakit_page.dart b/frontend/lib/user/detail_penyakit_page.dart new file mode 100644 index 0000000..d8ccd70 --- /dev/null +++ b/frontend/lib/user/detail_penyakit_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; + +class DetailPenyakitPage extends StatelessWidget { + final Map detailPenyakit; + + const DetailPenyakitPage({required this.detailPenyakit}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFF9DC08D), + appBar: AppBar( + backgroundColor: Color(0xFF9DC08D), + title: Text( + "Detail Penyakit", + style: TextStyle(color: Colors.white), + ), + leading: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + if (detailPenyakit["gambar"] != null && detailPenyakit["gambar"]!.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.asset( + detailPenyakit["gambar"]!, + height: 200, + width: 200, // Biar gambar full lebar + fit: BoxFit.cover, + ), + ), + SizedBox(height: 16), + + // Card Nama Penyakit + SizedBox( + width: double.infinity, // Bikin card full lebar + child: 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:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailPenyakit["nama penyakit"] ?? "Nama penyakit tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ), + ), + SizedBox(height: 16), + + // Card Deskripsi + Penanganan + SizedBox( + width: double.infinity, // Bikin card full lebar + child: Card( + elevation: 6, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Deskripsi:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailPenyakit["deskripsi"] ?? "Deskripsi tidak tersedia", + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 16), + Text( + "Penanganan:", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + detailPenyakit["penanganan"] ?? "Penanganan tidak tersedia", + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/lib/user/forgot_password_page.dart b/frontend/lib/user/forgot_password_page.dart index ac3078f..62627ef 100644 --- a/frontend/lib/user/forgot_password_page.dart +++ b/frontend/lib/user/forgot_password_page.dart @@ -1,7 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Pastikan path-nya sesuai + +class ForgotPasswordPage extends StatefulWidget { + @override + _ForgotPasswordPageState createState() => _ForgotPasswordPageState(); +} + +class _ForgotPasswordPageState extends State { + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final ApiService apiService = ApiService(); + + bool isLoading = false; + + void handleForgotPassword() async { + setState(() { + isLoading = true; + }); + + try { + await apiService.forgotPassword( + email: emailController.text.trim(), + newPassword: passwordController.text.trim(), + ); + + showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text('Berhasil'), + content: Text('Password berhasil direset.'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + Navigator.of(context).pop(); // tutup dialog + Navigator.of(context).pop(); // kembali ke halaman sebelumnya + }, + ), + ], + ), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toString())), + ); + } finally { + setState(() { + isLoading = false; + }); + } + } -// Halaman Lupa Password -class ForgotPasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -24,6 +73,7 @@ class ForgotPasswordPage extends StatelessWidget { child: Column( children: [ TextField( + controller: emailController, decoration: InputDecoration( labelText: 'Email', border: OutlineInputBorder( @@ -32,6 +82,17 @@ class ForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 20), + TextField( + controller: passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Password Baru', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + SizedBox(height: 20), SizedBox( width: double.infinity, height: 50, @@ -42,17 +103,17 @@ class ForgotPasswordPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), - onPressed: () { - // Logic reset password - }, - child: Text( - 'Reset Password', - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), + onPressed: isLoading ? null : handleForgotPassword, + child: isLoading + ? CircularProgressIndicator(color: Colors.white) + : Text( + 'Reset Password', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), ), ), ], @@ -64,4 +125,4 @@ class ForgotPasswordPage extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/frontend/lib/user/hama_page.dart b/frontend/lib/user/hama_page.dart new file mode 100644 index 0000000..bd57c8d --- /dev/null +++ b/frontend/lib/user/hama_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'detail_hama_page.dart'; + +class HamaPage extends StatelessWidget { + final List> hamaList = [ + { + "nama hama": "Karat Putih", + "deskripsi": "Penyakit yang umum pada bayam.", + "penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.", + "gambar": "assets/images/karat putih.jpeg", + }, + { + "nama hama": "Virus Keriting", + "deskripsi": "Disebabkan oleh infeksi virus.", + "penanganan": "Musnahkan tanaman terinfeksi dan kontrol vektor seperti kutu daun.", + "gambar": "assets/images/virus_keriting.jpeg", + }, + { + "nama hama": "Kekurangan Mangan", + "deskripsi": "Kekurangan unsur hara mikro.", + "penanganan": "Tambahkan pupuk yang mengandung mangan (Mn).", + "gambar": "assets/images/kekurangan_mangan.jpeg", + }, + { + "nama hama": "Downy Mildew", + "deskripsi": "Penyakit jamur pada bayam.", + "penanganan": "Gunakan fungisida berbahan aktif metalaxyl dan perbaiki drainase tanah.", + "gambar": "assets/images/downy_mildew.jpeg", + }, + ]; + + @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( + "Hama", + 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: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + SizedBox(height: 16), + Expanded( + child: ListView.builder( + itemCount: hamaList.length, + itemBuilder: (context, index) { + final diagnosa = hamaList[index]; + return Card( + elevation: 4, + margin: const EdgeInsets.symmetric(vertical: 8), + child: ListTile( + title: Text( + diagnosa["nama hama"] ?? "Tidak ada data", + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailHamaPage(detailRiwayat: diagnosa), + ), + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/user/home_page.dart b/frontend/lib/user/home_page.dart index e9472e0..5ebce63 100644 --- a/frontend/lib/user/home_page.dart +++ b/frontend/lib/user/home_page.dart @@ -73,14 +73,21 @@ class HomePage extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( + child: Wrap( + spacing: 20, // Jarak horizontal antar tombol + runSpacing: 20, // Jarak vertikal antar baris tombol + alignment: WrapAlignment.center, // Menempatkan tombol di tengah children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ButtonMenu( title: "Riwayat Diagnosa", - icon: Icons.medical_services, + customIcon: Image.asset( + 'assets/images/Order History.png', + width: 48, + height: 48, + ), onTap: () { Navigator.push( context, @@ -92,7 +99,11 @@ class HomePage extends StatelessWidget { ), ButtonMenu( title: "Profile", - icon: Icons.bug_report, + customIcon: Image.asset( + 'assets/images/Test Account.png', + width: 48, + height: 48, + ), onTap: () { Navigator.push( context, @@ -110,7 +121,11 @@ class HomePage extends StatelessWidget { children: [ ButtonMenu( title: "Basis Pengetahuan", - icon: Icons.info, + customIcon: Image.asset( + 'assets/images/Literature.png', + width: 48, + height: 48, + ), onTap: () { Navigator.push( context, @@ -122,7 +137,11 @@ class HomePage extends StatelessWidget { ), ButtonMenu( title: "Info Pakar", - icon: Icons.exit_to_app, + customIcon: Image.asset( + 'assets/images/Businessman.png', + width: 48, + height: 48, + ), onTap: () { Navigator.push( context, @@ -148,12 +167,12 @@ class HomePage extends StatelessWidget { // Widget untuk tombol menu class ButtonMenu extends StatelessWidget { final String title; - final IconData icon; + final Widget customIcon; final VoidCallback onTap; const ButtonMenu({ required this.title, - required this.icon, + required this.customIcon, required this.onTap, Key? key, }) : super(key: key); @@ -175,10 +194,11 @@ class ButtonMenu extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, size: 30, color: Colors.green), // Ikon + customIcon, const SizedBox(height: 5), Text( title, + textAlign: TextAlign.center, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ], diff --git a/frontend/lib/user/penyakit_page.dart b/frontend/lib/user/penyakit_page.dart new file mode 100644 index 0000000..51b7973 --- /dev/null +++ b/frontend/lib/user/penyakit_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'detail_penyakit_page.dart'; + +class PenyakitPage extends StatelessWidget { + final List> penyakitList= [ + { + "nama penyakit": "Karat Putih", + "deskripsi": "Penyakit yang umum pada bayam.", + "penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.", + "gambar": "assets/images/karat putih.jpeg", + }, + { + "nama penyakit": "Virus Keriting", + "deskripsi": "Disebabkan oleh infeksi virus.", + "penanganan": "Musnahkan tanaman terinfeksi dan kontrol vektor seperti kutu daun.", + "gambar": "assets/images/virus_keriting.jpeg", + }, + { + "nama penyakit": "Kekurangan Mangan", + "deskripsi": "Kekurangan unsur hara mikro.", + "penanganan": "Tambahkan pupuk yang mengandung mangan (Mn).", + "gambar": "assets/images/kekurangan_mangan.jpeg", + }, + { + "nama penyakit": "Downy Mildew", + "deskripsi": "Penyakit jamur pada bayam.", + "penanganan": "Gunakan fungisida berbahan aktif metalaxyl dan perbaiki drainase tanah.", + "gambar": "assets/images/downy_mildew.jpeg", + }, + ]; + + @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( + "Penyakit", + 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: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + SizedBox(height: 16), + Expanded( + child: ListView.builder( + itemCount: penyakitList.length, + itemBuilder: (context, index) { + final diagnosa = penyakitList[index]; + return Card( + elevation: 4, + margin: const EdgeInsets.symmetric(vertical: 8), + child: ListTile( + title: Text( + diagnosa["nama penyakit"] ?? "Tidak ada data", + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text(diagnosa["deskripsi"] ?? "Deskripsi tidak tersedia"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailPenyakitPage(detailPenyakit: diagnosa), + ), + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/user/register_page.dart b/frontend/lib/user/register_page.dart index 4c23bc5..5516a5b 100644 --- a/frontend/lib/user/register_page.dart +++ b/frontend/lib/user/register_page.dart @@ -1,7 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:frontend/api_services/api_services.dart'; // Halaman Pendaftaran class RegisterPage extends StatelessWidget { + final TextEditingController nameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController alamatController = TextEditingController(); + final TextEditingController nomorHpController = TextEditingController(); + + final ApiService apiService = ApiService(); + @override Widget build(BuildContext context) { return Scaffold( @@ -30,6 +39,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: nameController, ), SizedBox(height: 20), TextField( @@ -39,6 +49,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: emailController, ), SizedBox(height: 20), TextField( @@ -49,6 +60,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: passwordController, ), SizedBox(height: 20), TextField( @@ -58,6 +70,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: emailController, ), SizedBox(height: 20), TextField( @@ -67,6 +80,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: alamatController, ), SizedBox(height: 20), TextField( @@ -76,6 +90,7 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), + controller: nomorHpController, ), SizedBox(height: 20), SizedBox( @@ -88,8 +103,24 @@ class RegisterPage extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), ), - onPressed: () { - // Logic Pendaftaran + onPressed: () async { + try { + await apiService.registerUser( + name: nameController.text, + email: emailController.text, + password: passwordController.text, + alamat: alamatController.text, + nomorTelepon: nomorHpController.text, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Registrasi berhasil!')), + ); + Navigator.pop(context); // kembali ke login page + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Registrasi gagal: $e')), + ); + } }, child: Text( 'Daftar', @@ -110,4 +141,4 @@ class RegisterPage extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/frontend/lib/user/riwayat_diagnosa_page.dart b/frontend/lib/user/riwayat_diagnosa_page.dart index 24a90dc..e636722 100644 --- a/frontend/lib/user/riwayat_diagnosa_page.dart +++ b/frontend/lib/user/riwayat_diagnosa_page.dart @@ -7,7 +7,8 @@ class RiwayatDiagnosaPage extends StatelessWidget { "deskripsi": "Penyakit yang umum pada bayam.", "penyakit": "Karat Putih", "hama": "Tidak ada hama spesifik", - "penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi." + "penanganan": "Gunakan fungisida sesuai anjuran dan potong daun yang terinfeksi.", + "gambar": "assets/images/karat putih.jpeg", }, { "nama": "Virus Keriting", @@ -76,11 +77,12 @@ class RiwayatDiagnosaPage extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => HasilDiagnosaPage( - hasilDiagnosa: { + builder: (context) => DetailRiwayatPage( + detailRiwayat: { "penyakit": diagnosa["penyakit"] ?? "", "hama": diagnosa["hama"] ?? "", "penanganan": diagnosa["penanganan"] ?? "", + "gambar": diagnosa["gambar"] ?? "", }, ), ), @@ -98,23 +100,24 @@ class RiwayatDiagnosaPage extends StatelessWidget { } } -class HasilDiagnosaPage extends StatelessWidget { - final Map hasilDiagnosa; +class DetailRiwayatPage extends StatelessWidget { + final Map detailRiwayat; - HasilDiagnosaPage({required this.hasilDiagnosa}); + 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), // Geser ke kiri + padding: const EdgeInsets.only(right: 30), child: Text( "Hasil Diagnosa", - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white), ), ), ), @@ -123,53 +126,67 @@ class HasilDiagnosaPage extends StatelessWidget { onPressed: () => Navigator.of(context).pop(), ), ), - body: Container( - color: Color(0xFF9DC08D), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: Card( - elevation: 6, - shape: RoundedRectangleBorder( + 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, + ), ), - child: Container( - width: 400, - height: 300, // Mengatur ukuran card - padding: EdgeInsets.all(16), + SizedBox(height: 16), + Card( + elevation: 6, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(16.0), child: Column( - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Nama Penyakit: ${hasilDiagnosa['penyakit']}", - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), + "Nama Penyakit: ${detailRiwayat['penyakit']}", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), ), - SizedBox(height: 10), + SizedBox(height: 16), Text( - "Nama Hama: ${hasilDiagnosa['hama']}", - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), + "Nama Hama: ${detailRiwayat['hama']}", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black), ), - SizedBox(height: 20), + ], + ), + ), + ), + 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), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black), ), SizedBox(height: 10), Text( - hasilDiagnosa['penanganan'] ?? "Data tidak tersedia", + detailRiwayat['penanganan'] ?? "Data tidak tersedia", style: TextStyle(fontSize: 16, color: Colors.black), ), ], ), ), ), - ), + ], ), ), ); } -} +} \ No newline at end of file diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 2f9ba48..3ba5131 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -62,6 +62,13 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/bayam.png + - assets/images/Businessman.png + - assets/images/Literature.png + - assets/images/Order History.png + - assets/images/Test Account.png + - assets/images/Virus.png + - assets/images/Caterpillar.png + - assets/images/karat putih.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/frontend/test/widget_test.dart b/frontend/test/widget_test.dart index 812c978..e69de29 100644 --- a/frontend/test/widget_test.dart +++ b/frontend/test/widget_test.dart @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:frontend/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}