From accd4ab1675e3b2aecf99b9c06af436556b709e1 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 27 Apr 2025 01:30:39 +0700 Subject: [PATCH] last update admin page --- backend/controller/hamaController.js | 14 +- .../migrations/20250318213037-create-hama.js | 5 +- .../20250426132425-add-foto-to-hama.js | 14 + backend/models/hama.js | 4 + frontend/assets/images/Businessman.png | Bin 0 -> 719 bytes frontend/assets/images/Caterpillar.png | Bin 0 -> 1452 bytes frontend/assets/images/Literature.png | Bin 0 -> 707 bytes frontend/assets/images/Order History.png | Bin 0 -> 871 bytes frontend/assets/images/Test Account.png | Bin 0 -> 952 bytes frontend/assets/images/Virus.png | Bin 0 -> 1588 bytes frontend/assets/images/karat putih.jpeg | Bin 0 -> 10679 bytes frontend/lib/admin/admin_page.dart | 145 +++++-- frontend/lib/admin/edit_hama_page.dart | 119 ++++++ frontend/lib/admin/edit_penyakit_page.dart | 119 ++++++ frontend/lib/admin/gejala_page.dart | 190 +++++++-- frontend/lib/admin/hama_page.dart | 385 ++++++++++++----- frontend/lib/admin/penyakit_page.dart | 394 +++++++++++++----- frontend/lib/admin/tambah_hama_page.dart | 124 ++++++ frontend/lib/admin/tambah_penyakit_page.dart | 124 ++++++ frontend/lib/api_services/api_services.dart | 46 ++ frontend/lib/user/basis_pengetahuan_page.dart | 16 +- frontend/lib/user/detail_hama_page.dart | 108 +++++ frontend/lib/user/detail_penyakit_page.dart | 108 +++++ frontend/lib/user/forgot_password_page.dart | 89 +++- frontend/lib/user/hama_page.dart | 90 ++++ frontend/lib/user/home_page.dart | 36 +- frontend/lib/user/penyakit_page.dart | 90 ++++ frontend/lib/user/register_page.dart | 37 +- frontend/lib/user/riwayat_diagnosa_page.dart | 85 ++-- frontend/pubspec.yaml | 7 + frontend/test/widget_test.dart | 30 -- 31 files changed, 1992 insertions(+), 387 deletions(-) create mode 100644 backend/migrations/20250426132425-add-foto-to-hama.js create mode 100644 frontend/assets/images/Businessman.png create mode 100644 frontend/assets/images/Caterpillar.png create mode 100644 frontend/assets/images/Literature.png create mode 100644 frontend/assets/images/Order History.png create mode 100644 frontend/assets/images/Test Account.png create mode 100644 frontend/assets/images/Virus.png create mode 100644 frontend/assets/images/karat putih.jpeg create mode 100644 frontend/lib/admin/edit_hama_page.dart create mode 100644 frontend/lib/admin/edit_penyakit_page.dart create mode 100644 frontend/lib/admin/tambah_hama_page.dart create mode 100644 frontend/lib/admin/tambah_penyakit_page.dart create mode 100644 frontend/lib/user/detail_hama_page.dart create mode 100644 frontend/lib/user/detail_penyakit_page.dart create mode 100644 frontend/lib/user/hama_page.dart create mode 100644 frontend/lib/user/penyakit_page.dart 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 0000000000000000000000000000000000000000..6df1f806c5bdc84081991e59e0499f92641043c6 GIT binary patch literal 719 zcmV;=0x`m zBtZ~;-97DMJ}}G=hLLd?W@b5xE?g-dK?Kpu2%bTY;6e~V#1p7XMQ|yCAN+x+8^vF@ z-9}Imbef%ARasW{Mjt$~Ei*D+Mn*+s0k3E;3ZBoOy>Jpr#2T?qd`tXG{6c&nZV^w2 zMfranZ9-W_7P3UVF$5^*jrj%S5Wyt8k+I(pQL{un$hb}%XqPx%{D&dP7Nxv5es2>y z5FKqWQH)i%BjsXM#zkw5-0>0dk#dpeR)`Y_zL?eqmMY9+XWB! z3VTih*L)`XI@*-cr)btK+uTu-aR=!WU%4-~!AeFi(w7Rm00&L}-|&YXn%$+@P&=ftLjz~EhTd#Rvxd#V|HkAQEZ z33bwq_`b={dn}va=#X&{t&dV~6lG*U)}3g3sm9EyT62R#->gr>Q?!oQ1)}S>X%hrR zOWc_VsG#))@!VP?H-Y9|oyR}K32~?;fv?efB9yr+&r}>gn?&>_Wv-hkjf#dKZbQ?E zKFM5lsxE>R|Ii#cc3rB{7J;Zkb{d+}xFcOQeM(Hc@ z7LCZnRd|ic5OEOhNZA54+U(<-%7G+sUT{wB zqmd(rh+c%UI86ed+Evn|taqk@002ovPDHLkV1jVj BJ@Nnm literal 0 HcmV?d00001 diff --git a/frontend/assets/images/Caterpillar.png b/frontend/assets/images/Caterpillar.png new file mode 100644 index 0000000000000000000000000000000000000000..066df53f35cc87bea542c421c52ecb59ab3e7b2d GIT binary patch literal 1452 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 literal 0 HcmV?d00001 diff --git a/frontend/assets/images/Virus.png b/frontend/assets/images/Virus.png new file mode 100644 index 0000000000000000000000000000000000000000..8062d90470403b4ccbd23970c559335be5174444 GIT binary patch literal 1588 zcmV-42Fv-0P)J^4G2mUG>9Uo zQ4|$%U(gRXT*rwbVsHr>V_at1ds21j+x4nm_sr{=SDnm(L-(t`ua;Z4ZrxfMn8q}A zRP=S2Rtshz5A+Kg!o!7E3QraOB>W8-DXVVu`_HR{|G+7X{1;AXR%WNipm5ByfmUS= zA_L^oQ4f7^9D4fwxDMXe zNr_553T{%s1nMzqprTm02Zh_<8n#Jzh55acJct!2Dr$xQt-pi!PY`~Mro!J4?jOk1 zighSj0lB!%d$#g>!CB-t=KW#e!NNI-cPuFPJ6b0K@^j%X$N+~4|3ho#R9SCFMNr3rEfSW9Iv>aMrOLPN88w#A*~fO21!#3~&sJ6|@#XXoGb4%o5 O-W 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 literal 0 HcmV?d00001 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); - }); -}