update layout admin for get data

This commit is contained in:
unknown 2025-06-14 23:11:05 +07:00
parent 213eb6273c
commit db83fb1c2f
15 changed files with 2569 additions and 1355 deletions

View File

@ -19,8 +19,6 @@ dotenv.config();
const app = express();
// Middlewares
app.use(express.json());
app.use(cors());

View File

@ -11,7 +11,7 @@ const swaggerOptions = {
},
servers: [
{
url: 'https://backend-sistem-pakar-diagnosa-penya.vercel.app',
url: 'https://localhost:5000', // Development server URL
description: 'Production Server'
},
],

View File

@ -11,9 +11,14 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> historiData = [];
List<Map<String, dynamic>> groupedHistoriData = [];
List<Map<String, dynamic>> filteredHistoriData = []; // Data yang sudah difilter
bool isLoading = true;
String? error;
// Search variables
TextEditingController searchController = TextEditingController();
String searchQuery = '';
// Pagination variables
int _rowsPerPage = 10;
int _currentPage = 0;
@ -24,6 +29,35 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
void initState() {
super.initState();
_loadHistoriData();
searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
searchController.removeListener(_onSearchChanged);
searchController.dispose();
super.dispose();
}
void _onSearchChanged() {
setState(() {
searchQuery = searchController.text.toLowerCase();
_filterData();
_updatePagination(0); // Reset ke halaman pertama saat search
});
}
void _filterData() {
if (searchQuery.isEmpty) {
filteredHistoriData = List.from(groupedHistoriData);
} else {
filteredHistoriData = groupedHistoriData.where((histori) {
final userName = (histori['userName'] ?? '').toString().toLowerCase();
final diagnosa = (histori['diagnosa'] ?? '').toString().toLowerCase();
return userName.contains(searchQuery) || diagnosa.contains(searchQuery);
}).toList();
}
}
Future<void> _loadHistoriData() async {
@ -34,7 +68,9 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
});
// Dapatkan semua histori terlebih dahulu
final allHistori = await apiService.getAllHistori(); // Kumpulkan semua userIds yang unik
final allHistori = await apiService.getAllHistori();
// Kumpulkan semua userIds yang unik
Set<String> uniqueUserIds = allHistori
.where((histori) => histori['userId'] != null)
.map((histori) => histori['userId'].toString())
@ -60,6 +96,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
setState(() {
historiData = detailedHistori; // Simpan data asli jika perlu
groupedHistoriData = groupedData; // Data yang sudah dikelompokkan
filteredHistoriData = List.from(groupedData); // Initialize filtered data
_updatePagination(0); // Set halaman pertama
isLoading = false;
});
@ -104,7 +141,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
diagnosa = 'Tidak ada diagnosa';
}
// Ambil nama user dari kolom 'nama' atau 'name' (sesuaikan dengan struktur data Anda)
// Ambil nama user dari kolom 'nama' atau 'name'
String userName = item['name']?.toString() ?? 'User ID: ${item['userId']}';
// Buat composite key: userId + waktu + diagnosa
@ -116,7 +153,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
groupedMap[key] = {
'userId': item['userId'],
'userName': userName, // Menampilkan nama user, bukan ID
'userName': userName,
'diagnosa': diagnosa,
'tanggal_diagnosa': item['tanggal_diagnosa'],
'tanggal_display': displayDate,
@ -124,7 +161,8 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
'hasil': item['hasil'],
'penyakit_nama': item['penyakit_nama'],
'hama_nama': item['hama_nama'],
'sortTime': dateTime.millisecondsSinceEpoch, // untuk pengurutan
'sortTime': dateTime.millisecondsSinceEpoch,
'detailData': [], // Menyimpan semua item detail untuk halaman detail
};
}
@ -133,6 +171,9 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
!groupedMap[key]!['gejala'].contains(item['gejala_nama'])) {
groupedMap[key]!['gejala'].add(item['gejala_nama']);
}
// Simpan data detail untuk halaman detail
groupedMap[key]!['detailData'].add(item);
}
// Konversi map ke list dan urutkan berdasarkan waktu terbaru
@ -147,22 +188,32 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
// Update pagination
void _updatePagination(int page) {
_currentPage = page;
_totalPages = (groupedHistoriData.length / _rowsPerPage).ceil();
_totalPages = (filteredHistoriData.length / _rowsPerPage).ceil();
int startIndex = page * _rowsPerPage;
int endIndex = (page + 1) * _rowsPerPage;
if (endIndex > groupedHistoriData.length) {
endIndex = groupedHistoriData.length;
if (endIndex > filteredHistoriData.length) {
endIndex = filteredHistoriData.length;
}
if (startIndex >= groupedHistoriData.length) {
if (startIndex >= filteredHistoriData.length) {
_currentPageData = [];
} else {
_currentPageData = groupedHistoriData.sublist(startIndex, endIndex);
_currentPageData = filteredHistoriData.sublist(startIndex, endIndex);
}
}
// Navigasi ke halaman detail
void _navigateToDetail(Map<String, dynamic> histori) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailHistoriPage(histori: histori),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -170,8 +221,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
title: Text('Riwayat Diagnosa'),
backgroundColor: Color(0xFF9DC08D),
),
body:
isLoading
body: isLoading
? Center(child: CircularProgressIndicator())
: error != null
? Center(child: Text('Error: $error'))
@ -179,98 +229,147 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
? Center(child: Text('Tidak ada data riwayat diagnosa'))
: Column(
children: [
// Search Bar
Container(
margin: EdgeInsets.all(16),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'Cari berdasarkan nama user atau diagnosa...',
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9DC08D),
),
suffixIcon: searchQuery.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () {
searchController.clear();
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF9DC08D)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF9DC08D), width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
child: DataTable(
columnSpacing: 20,
headingRowColor: MaterialStateProperty.all(
Color(0xFF9DC08D).withOpacity(0.3),
child: filteredHistoriData.isEmpty && searchQuery.isNotEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: Colors.grey[400],
),
columns: [
DataColumn(
label: Text(
'Nama User',
style: TextStyle(fontWeight: FontWeight.bold),
SizedBox(height: 16),
Text(
'Tidak ada hasil untuk "${searchController.text}"',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
DataColumn(
label: Text(
'Gejala',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Diagnosa',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Hasil',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Tanggal',
style: TextStyle(fontWeight: FontWeight.bold),
SizedBox(height: 8),
Text(
'Coba gunakan kata kunci yang berbeda',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
],
rows:
_currentPageData.map((histori) {
// Gabungkan semua gejala menjadi satu string dengan koma
String gejalaText = "Tidak ada gejala";
if (histori['gejala'] != null &&
(histori['gejala'] as List).isNotEmpty) {
gejalaText = (histori['gejala'] as List).join(
', ',
);
}
),
)
: ListView.builder(
itemCount: _currentPageData.length,
itemBuilder: (context, index) {
final histori = _currentPageData[index];
return DataRow(
cells: [
DataCell(Text(histori['userName'] ?? 'User tidak ditemukan')),
DataCell(
Container(
constraints: BoxConstraints(
maxWidth: 200,
),
child: Tooltip(
message: gejalaText,
child: Text(
gejalaText,
overflow: TextOverflow.ellipsis,
),
),
),
),
DataCell(
return Container(
margin: EdgeInsets.only(bottom: 12, left: 16, right: 16),
child: Row(
children: [
// Card dengan informasi histori
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => _navigateToDetail(histori),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Nama User
Text(
histori['diagnosa'] ??
'Tidak ada diagnosa',
histori['userName'] ?? 'User tidak ditemukan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8),
// Diagnosa
Text(
histori['diagnosa'] ?? 'Tidak ada diagnosa',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
// Tanggal
Text(
histori['tanggal_display'] ?? '',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
DataCell(
Text(_formatHasil(histori['hasil'])),
),
DataCell(
Text(histori['tanggal_display'] ?? ''),
),
],
),
),
),
),
),
SizedBox(width: 8),
// Button detail di luar card
Container(
decoration: BoxDecoration(
color: Color(0xFF9DC08D),
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.info_outline, color: Colors.white),
onPressed: () => _navigateToDetail(histori),
tooltip: 'Lihat Detail',
),
),
],
),
);
}).toList(),
},
),
),
),
), // Pagination controls
// Pagination controls
Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
child: Row(
@ -284,8 +383,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
minWidth: 32,
minHeight: 32,
),
onPressed:
_currentPage > 0
onPressed: _currentPage > 0
? () {
setState(() {
_updatePagination(0);
@ -300,8 +398,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
minWidth: 32,
minHeight: 32,
),
onPressed:
_currentPage > 0
onPressed: _currentPage > 0
? () {
setState(() {
_updatePagination(_currentPage - 1);
@ -325,8 +422,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
minWidth: 32,
minHeight: 32,
),
onPressed:
_currentPage < _totalPages - 1
onPressed: _currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_currentPage + 1);
@ -341,8 +437,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
minWidth: 32,
minHeight: 32,
),
onPressed:
_currentPage < _totalPages - 1
onPressed: _currentPage < _totalPages - 1
? () {
setState(() {
_updatePagination(_totalPages - 1);
@ -352,7 +447,9 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
),
],
),
), // Rows per page selector
),
// Rows per page selector
Container(
padding: EdgeInsets.only(bottom: 8),
child: Row(
@ -367,8 +464,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
value: _rowsPerPage,
isDense: true,
menuMaxHeight: 200,
items:
[10, 20, 50, 100].map((value) {
items: [10, 20, 50, 100].map((value) {
return DropdownMenuItem<int>(
value: value,
child: Text('$value'),
@ -377,9 +473,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
onChanged: (value) {
setState(() {
_rowsPerPage = value!;
_updatePagination(
0,
); // Kembali ke halaman pertama
_updatePagination(0);
});
},
),
@ -406,3 +500,213 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
return '${(hasilValue * 100).toStringAsFixed(2)}%';
}
}
// Halaman Detail Histori
class DetailHistoriPage extends StatelessWidget {
final Map<String, dynamic> histori;
const DetailHistoriPage({Key? key, required this.histori}) : super(key: key);
@override
Widget build(BuildContext context) {
// Gabungkan semua gejala menjadi satu string
String gejalaText = "Tidak ada gejala";
if (histori['gejala'] != null && (histori['gejala'] as List).isNotEmpty) {
gejalaText = (histori['gejala'] as List).join(', ');
}
return Scaffold(
appBar: AppBar(
title: Text('Detail Riwayat Diagnosa'),
backgroundColor: Color(0xFF9DC08D),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFF9DC08D).withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Informasi Diagnosa',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF9DC08D),
),
textAlign: TextAlign.center,
),
),
SizedBox(height: 20),
// Nama User
_buildDetailRow(
'Nama User',
histori['userName'] ?? 'User tidak ditemukan',
Icons.person,
),
SizedBox(height: 16),
// Tanggal Diagnosa
_buildDetailRow(
'Tanggal Diagnosa',
histori['tanggal_display'] ?? '',
Icons.calendar_today,
),
SizedBox(height: 16),
// Diagnosa
_buildDetailRow(
'Diagnosa',
histori['diagnosa'] ?? 'Tidak ada diagnosa',
Icons.medical_services,
),
SizedBox(height: 16),
// Hasil
_buildDetailRow(
'Hasil',
_formatHasil(histori['hasil']),
Icons.analytics,
),
SizedBox(height: 16),
// Gejala
_buildDetailSection(
'Gejala yang Dipilih',
gejalaText,
Icons.list_alt,
),
],
),
),
),
),
);
}
Widget _buildDetailRow(
String label,
String value,
IconData icon, {
Color? valueColor,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
icon,
size: 20,
color: Color(0xFF9DC08D),
),
SizedBox(width: 12),
Expanded(
flex: 2,
child: Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: Colors.grey[700],
),
),
),
Text(
': ',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
Expanded(
flex: 3,
child: Text(
value,
style: TextStyle(
fontSize: 14,
color: valueColor ?? Colors.black87,
fontWeight: valueColor != null ? FontWeight.w500 : FontWeight.normal,
),
),
),
],
);
}
Widget _buildDetailSection(
String label,
String value,
IconData icon,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
icon,
size: 20,
color: Color(0xFF9DC08D),
),
SizedBox(width: 12),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: Colors.grey[700],
),
),
],
),
SizedBox(height: 8),
Container(
width: double.infinity,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: Text(
value,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
),
],
);
}
Color _getDiagnosaColor(Map<String, dynamic> histori) {
if (histori['penyakit_nama'] != null) {
return Colors.red[700]!;
} else if (histori['hama_nama'] != null) {
return Colors.amber[800]!;
}
return Colors.black;
}
String _formatHasil(dynamic hasil) {
if (hasil == null) return '0%';
double hasilValue = double.tryParse(hasil.toString()) ?? 0.0;
return '${(hasilValue * 100).toStringAsFixed(2)}%';
}
}

View File

@ -48,6 +48,25 @@ class _AdminPageState extends State<AdminPage> {
await prefs.setInt(LAST_KNOWN_COUNT_KEY, _lastKnownDiagnosisCount);
}
// Method untuk menghitung jumlah diagnosa berdasarkan tanggal unik
int _countUniqueByDate(List<dynamic> historiList) {
Set<String> uniqueDates = {};
for (var histori in historiList) {
// Ambil tanggal_diagnosa dari data
String? tanggalDiagnosa = histori['tanggal_diagnosa']?.toString();
if (tanggalDiagnosa != null && tanggalDiagnosa.isNotEmpty) {
// Extract hanya tanggal (YYYY-MM-DD) tanpa waktu
String dateOnly = tanggalDiagnosa.split(' ')[0];
uniqueDates.add(dateOnly);
}
}
print("Unique dates found: ${uniqueDates.toList()}");
return uniqueDates.length;
}
// Method untuk memuat data dashboard dari API
Future<void> _loadDashboardData() async {
try {
@ -78,21 +97,33 @@ class _AdminPageState extends State<AdminPage> {
pestCount = hamaList.length;
print("Jumlah hama: $pestCount");
// Modified diagnosis count logic
print("Fetching histori data...");
// Mengambil data histori dan hitung berdasarkan tanggal unik
final allHistori = await ApiService().getAllHistori();
int currentCount = allHistori.length;
print("Total histori records: ${allHistori.length}");
if (currentCount > _lastKnownDiagnosisCount) {
int newDiagnoses = currentCount - _lastKnownDiagnosisCount;
// Hitung jumlah diagnosa berdasarkan tanggal unik
int currentUniqueCount = _countUniqueByDate(allHistori);
print("Unique diagnosis dates: $currentUniqueCount");
// Update diagnosis count berdasarkan tanggal unik
if (currentUniqueCount > _lastKnownDiagnosisCount) {
int newDiagnoses = currentUniqueCount - _lastKnownDiagnosisCount;
diagnosisCount += newDiagnoses;
_lastKnownDiagnosisCount = currentCount;
_lastKnownDiagnosisCount = currentUniqueCount;
// Save the updated counts
await _saveCounts();
print("New diagnoses added: $newDiagnoses");
print("New unique diagnosis dates added: $newDiagnoses");
print("Total diagnosis count: $diagnosisCount");
} else {
// Jika tidak ada penambahan, set ke current unique count
diagnosisCount = currentUniqueCount;
_lastKnownDiagnosisCount = currentUniqueCount;
await _saveCounts();
}
} catch (e) {
print("Error loading dashboard data: $e");
} finally {

View File

@ -288,24 +288,24 @@ class _EditHamaPageState extends State<EditHamaPage> {
maxLines: 3,
),
SizedBox(height: 20),
TextField(
controller: _nilaiPakarController,
decoration: InputDecoration(
labelText: 'Nilai Pakar',
hintText: 'Contoh: 0.5',
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
// Validate as user types (optional)
try {
if (value.isNotEmpty) {
double.parse(value.replaceAll(',', '.'));
}
} catch (e) {
// Could show validation error here
}
},
),
// TextField(
// controller: _nilaiPakarController,
// decoration: InputDecoration(
// labelText: 'Nilai Pakar',
// hintText: 'Contoh: 0.5',
// ),
// keyboardType: TextInputType.numberWithOptions(decimal: true),
// onChanged: (value) {
// // Validate as user types (optional)
// try {
// if (value.isNotEmpty) {
// double.parse(value.replaceAll(',', '.'));
// }
// } catch (e) {
// // Could show validation error here
// }
// },
// ),
SizedBox(height: 20),
Text(
'Foto Hama',

View File

@ -287,24 +287,24 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
maxLines: 3,
),
SizedBox(height: 20),
TextField(
controller: _nilaiPakarController,
decoration: InputDecoration(
labelText: 'Nilai Pakar',
hintText: 'Contoh: 0.5',
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
// Validate as user types (optional)
try {
if (value.isNotEmpty) {
double.parse(value.replaceAll(',', '.'));
}
} catch (e) {
// Could show validation error here
}
},
),
// TextField(
// controller: _nilaiPakarController,
// decoration: InputDecoration(
// labelText: 'Nilai Pakar',
// hintText: 'Contoh: 0.5',
// ),
// keyboardType: TextInputType.numberWithOptions(decimal: true),
// onChanged: (value) {
// // Validate as user types (optional)
// try {
// if (value.isNotEmpty) {
// double.parse(value.replaceAll(',', '.'));
// }
// } catch (e) {
// // Could show validation error here
// }
// },
// ),
SizedBox(height: 20),
Text(
'Foto Penyakit',

View File

@ -9,13 +9,22 @@ class GejalaPage extends StatefulWidget {
class _GejalaPageState extends State<GejalaPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> gejalaList = [];
List<Map<String, dynamic>> filteredGejalaList = [];
TextEditingController searchController = TextEditingController();
bool isSearchVisible = false;
@override
void initState() {
super.initState();
fetchGejala();
searchController.addListener(_filterGejala);
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
// 🔹 Ambil data gejala dari API
Future<void> fetchGejala() async {
@ -23,12 +32,35 @@ class _GejalaPageState extends State<GejalaPage> {
final data = await apiService.getGejala();
setState(() {
gejalaList = data;
filteredGejalaList = data;
});
} catch (e) {
print('Error fetching gejala: $e');
}
}
void _filterGejala() {
String query = searchController.text.toLowerCase();
setState(() {
filteredGejalaList = gejalaList.where((gejala) {
String nama = (gejala['nama'] ?? '').toLowerCase();
String kode = (gejala['kode'] ?? '').toLowerCase();
return nama.contains(query) || kode.contains(query);
}).toList();
currentPage = 0; // Reset pagination saat search
});
}
void _toggleSearch() {
setState(() {
isSearchVisible = !isSearchVisible;
if (!isSearchVisible) {
searchController.clear();
filteredGejalaList = gejalaList;
}
});
}
// 🔹 Tambah gejala baru ke API
void _tambahGejala() {
TextEditingController namaController = TextEditingController();
@ -76,9 +108,7 @@ class _GejalaPageState extends State<GejalaPage> {
context: context,
builder: (context) {
return AlertDialog(
title: Text(
'Edit Hama',
),
title: Text('Edit Gejala'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -154,8 +184,6 @@ class _GejalaPageState extends State<GejalaPage> {
);
}
//pagination
int currentPage = 0;
int rowsPerPage = 10;
@ -163,85 +191,130 @@ class _GejalaPageState extends State<GejalaPage> {
@override
Widget build(BuildContext context) {
int start = currentPage * rowsPerPage;
int end = (start + rowsPerPage < gejalaList.length)
int end = (start + rowsPerPage < filteredGejalaList.length)
? start + rowsPerPage
: gejalaList.length;
List currentPageData = gejalaList.sublist(start, end);
: filteredGejalaList.length;
List currentPageData = filteredGejalaList.sublist(start, end);
return Scaffold(
appBar: AppBar(
title: Text('Halaman Gejala'),
backgroundColor: Color(0xFF9DC08D),
),
body: Column(
children: [
SizedBox(height: 20),
Row(
SizedBox(height: 16),
// Search Button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: 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]),
),
ElevatedButton.icon(
onPressed: _toggleSearch,
icon: Icon(Icons.search),
label: Text('Cari'),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
foregroundColor: Colors.white,
),
),
],
),
SizedBox(height: 20),
),
// Search Field (conditional)
if (isSearchVisible)
Container(
margin: EdgeInsets.all(16),
child: TextField(
controller: searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Cari nama atau kode gejala...',
prefixIcon: Icon(Icons.search),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: _toggleSearch,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: Colors.grey[100],
),
),
),
Expanded(
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: 80, child: Text('Kode'))),
DataColumn(label: SizedBox(width: 150, child: Text('Nama'))),
DataColumn(label: SizedBox(width: 80, child: Text('Aksi'))),
],
rows: [
...currentPageData.map(
(gejala) => DataRow(
cells: [
DataCell(Text((gejalaList.indexOf(gejala) + 1).toString())),
DataCell(Text(gejala['kode'] ?? '-')),
DataCell(Text(gejala['nama'] ?? '-')),
DataCell(
Row(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
IconButton(
icon: Icon(Icons.edit, color: Color(0xFF9DC08D)),
onPressed: () => showEditDialog(context, gejala),
// Data List
Expanded(
child: ListView.builder(
itemCount: currentPageData.length,
itemBuilder: (context, index) {
final gejala = currentPageData[index];
return Container(
margin: EdgeInsets.only(bottom: 12),
child: Row(
children: [
// Card dengan nama gejala
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => showEditDialog(context, gejala),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
gejala['nama'] ?? '-',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
),
SizedBox(height: 4),
Text(
gejala['kode'] ?? '-',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
),
),
),
SizedBox(width: 8),
// Button hapus di luar card
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.delete, color: Colors.white),
onPressed: () => _konfirmasiHapus(gejala['id']),
),
],
),
),
],
),
);
},
),
DataRow(
cells: [
DataCell(Container()),
DataCell(Container()),
DataCell(
Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
),
SizedBox(height: 16),
// Pagination
if (filteredGejalaList.length > rowsPerPage)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.chevron_left),
@ -249,30 +322,29 @@ Widget build(BuildContext context) {
? () => setState(() => currentPage--)
: null,
),
Text(' ${currentPage + 1}'),
Text(
'Halaman ${currentPage + 1}',
style: TextStyle(fontSize: 16),
),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed:
(currentPage + 1) * rowsPerPage < gejalaList.length
onPressed: (currentPage + 1) * rowsPerPage < filteredGejalaList.length
? () => setState(() => currentPage++)
: null,
),
],
),
),
),
DataCell(Container()),
],
),
],
),
),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _tambahGejala,
child: Icon(Icons.add),
backgroundColor: Color(0xFF9DC08D),
),
);
}
}

View File

@ -11,11 +11,21 @@ class HamaPage extends StatefulWidget {
class _HamaPageState extends State<HamaPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> hamaList = [];
List<Map<String, dynamic>> filteredHamaList = [];
TextEditingController searchController = TextEditingController();
bool isSearchVisible = false;
@override
void initState() {
super.initState();
_fetchHama();
searchController.addListener(_filterHama);
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
Future<void> _fetchHama() async {
@ -23,12 +33,35 @@ class _HamaPageState extends State<HamaPage> {
List<Map<String, dynamic>> data = await apiService.getHama();
setState(() {
hamaList = data;
filteredHamaList = data;
});
} catch (e) {
print("Error fetching data: $e");
}
}
void _filterHama() {
String query = searchController.text.toLowerCase();
setState(() {
filteredHamaList = hamaList.where((hama) {
String nama = (hama['nama'] ?? '').toLowerCase();
String kode = (hama['kode'] ?? '').toLowerCase();
return nama.contains(query) || kode.contains(query);
}).toList();
currentPage = 0; // Reset pagination saat search
});
}
void _toggleSearch() {
setState(() {
isSearchVisible = !isSearchVisible;
if (!isSearchVisible) {
searchController.clear();
filteredHamaList = hamaList;
}
});
}
// 🔹 Hapus gejala dari API
void _hapusHama(int id) async {
try {
@ -45,7 +78,7 @@ class _HamaPageState extends State<HamaPage> {
builder: (context) {
return AlertDialog(
title: Text('Konfirmasi Hapus'),
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
content: Text('Apakah Anda yakin ingin menghapus hama ini?'),
actions: [
TextButton(
onPressed: () {
@ -67,6 +100,71 @@ class _HamaPageState extends State<HamaPage> {
);
}
void _navigateToEdit(Map<String, dynamic> hama) {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (hama['nilai_pakar'] != null) {
// Coba parse jika string
if (hama['nilai_pakar'] is String) {
try {
String nilaiStr = hama['nilai_pakar'].toString().trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(nilaiStr.replaceAll(',', '.'));
}
} catch (e) {
print("Error parsing nilai_pakar: $e");
}
}
// Langsung gunakan jika sudah double
else if (hama['nilai_pakar'] is double) {
nilaiPakar = hama['nilai_pakar'];
}
// Jika int, konversi ke double
else if (hama['nilai_pakar'] is int) {
nilaiPakar = hama['nilai_pakar'].toDouble();
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditHamaPage(
idHama: hama['id'],
namaAwal: hama['nama'] ?? '',
deskripsiAwal: hama['deskripsi'] ?? '',
penangananAwal: hama['penanganan'] ?? '',
gambarUrl: hama['foto'] ?? '',
nilai_pakar: nilaiPakar,
onHamaUpdated: _fetchHama,
),
),
);
}
void _showPopupMenu(BuildContext context, int id) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Pilih Aksi'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.delete, color: Colors.red),
title: Text('Hapus Hama'),
onTap: () {
Navigator.pop(context);
_konfirmasiHapus(id);
},
),
],
),
);
},
);
}
//pagination
int currentPage = 0;
int rowsPerPage = 10;
@ -74,203 +172,169 @@ class _HamaPageState extends State<HamaPage> {
@override
Widget build(BuildContext context) {
int start = currentPage * rowsPerPage;
int end =
(start + rowsPerPage < hamaList.length)
int end = (start + rowsPerPage < filteredHamaList.length)
? start + rowsPerPage
: hamaList.length;
List currentPageData = hamaList.sublist(start, end);
: filteredHamaList.length;
List currentPageData = filteredHamaList.sublist(start, end);
return Scaffold(
appBar: AppBar(title: Text('Halaman Hama'), backgroundColor: Color(0xFF9DC08D)),
appBar: AppBar(
title: Text('Halaman Hama'),
backgroundColor: Color(0xFF9DC08D),
),
body: Column(
children: [
SizedBox(height: 20),
Row(
SizedBox(height: 16),
// Search Button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: ElevatedButton(
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]),
),
ElevatedButton.icon(
onPressed: _toggleSearch,
icon: Icon(Icons.search),
label: Text('Cari'),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
foregroundColor: Colors.white,
),
),
],
),
SizedBox(height: 20),
),
// Search Field (conditional)
if (isSearchVisible)
Container(
margin: EdgeInsets.all(16),
child: TextField(
controller: searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Cari nama hama...',
prefixIcon: Icon(Icons.search),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: _toggleSearch,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: Colors.grey[100],
),
),
),
Expanded(
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),
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Data List
Expanded(
child: ListView.builder(
itemCount: currentPageData.length,
itemBuilder: (context, index) {
final hama = currentPageData[index];
return Container(
margin: EdgeInsets.only(bottom: 12),
child: Row(
children: [
// Card dengan nama hama
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => _navigateToEdit(hama),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
hama['nama'] ?? '-',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
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')),
SizedBox(height: 4),
Text(
hama['kode'] ?? '-',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
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: () {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (hama['nilai_pakar'] != null) {
// Coba parse jika string
if (hama['nilai_pakar'] is String) {
try {
String nilaiStr =
hama['nilai_pakar']
.toString()
.trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(
nilaiStr.replaceAll(',', '.'),
);
}
} catch (e) {
print(
"Error parsing nilai_pakar: $e",
);
}
}
// Langsung gunakan jika sudah double
else if (hama['nilai_pakar']
is double) {
nilaiPakar = hama['nilai_pakar'];
}
// Jika int, konversi ke double
else if (hama['nilai_pakar'] is int) {
nilaiPakar =
hama['nilai_pakar'].toDouble();
}
}
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'] ?? '',
gambarUrl: hama['foto'] ?? '',
nilai_pakar: nilaiPakar,
onHamaUpdated:
_fetchHama, // fungsi untuk refresh list setelah update
),
),
),
SizedBox(width: 8),
// Button hapus di luar card
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.delete, color: Colors.white),
onPressed: () => _konfirmasiHapus(hama['id']),
),
),
],
),
);
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed:
() => _konfirmasiHapus(hama['id']),
),
],
),
),
],
),
),
DataRow(
cells: [
DataCell(Container()),
DataCell(Container()),
DataCell(
Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
SizedBox(height: 16),
// Pagination
if (filteredHamaList.length > rowsPerPage)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.chevron_left),
onPressed:
currentPage > 0
? () =>
setState(() => currentPage--)
onPressed: currentPage > 0
? () => setState(() => currentPage--)
: null,
),
Text(' ${currentPage + 1}'),
Text(
'Halaman ${currentPage + 1}',
style: TextStyle(fontSize: 16),
),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed:
(currentPage + 1) * rowsPerPage <
hamaList.length
? () =>
setState(() => currentPage++)
onPressed: (currentPage + 1) * rowsPerPage < filteredHamaList.length
? () => setState(() => currentPage++)
: null,
),
],
),
),
),
DataCell(Container()),
DataCell(Container()),
DataCell(Container()),
],
),
],
),
),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TambahHamaPage(
onHamaAdded: _fetchHama,
),
),
);
},
child: Icon(Icons.add),
backgroundColor: Color(0xFF9DC08D),
),
);
}
}

View File

@ -12,11 +12,21 @@ class PenyakitPage extends StatefulWidget {
class _PenyakitPageState extends State<PenyakitPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> penyakitList = [];
List<Map<String, dynamic>> filteredPenyakitList = [];
TextEditingController searchController = TextEditingController();
bool isSearchVisible = false;
@override
void initState() {
super.initState();
_fetchPenyakit();
searchController.addListener(_filterPenyakit);
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
Future<void> _fetchPenyakit() async {
@ -24,19 +34,42 @@ class _PenyakitPageState extends State<PenyakitPage> {
List<Map<String, dynamic>> data = await apiService.getPenyakit();
setState(() {
penyakitList = data;
filteredPenyakitList = data;
});
} catch (e) {
print("Error fetching data: $e");
}
}
// 🔹 Hapus gejala dari API
void _filterPenyakit() {
String query = searchController.text.toLowerCase();
setState(() {
filteredPenyakitList = penyakitList.where((penyakit) {
String nama = (penyakit['nama'] ?? '').toLowerCase();
String kode = (penyakit['kode'] ?? '').toLowerCase();
return nama.contains(query) || kode.contains(query);
}).toList();
currentPage = 0; // Reset pagination saat search
});
}
void _toggleSearch() {
setState(() {
isSearchVisible = !isSearchVisible;
if (!isSearchVisible) {
searchController.clear();
filteredPenyakitList = penyakitList;
}
});
}
// 🔹 Hapus penyakit dari API
void _hapusPenyakit(int id) async {
try {
await apiService.deletePenyakit(id);
_fetchPenyakit(); // Refresh data setelah hapus
} catch (e) {
print('Error hapus gejala: $e');
print('Error hapus penyakit: $e');
}
}
@ -46,7 +79,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
builder: (context) {
return AlertDialog(
title: Text('Konfirmasi Hapus'),
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
content: Text('Apakah Anda yakin ingin menghapus penyakit ini?'),
actions: [
TextButton(
onPressed: () {
@ -68,6 +101,47 @@ class _PenyakitPageState extends State<PenyakitPage> {
);
}
void _navigateToEdit(Map<String, dynamic> penyakit) {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (penyakit['nilai_pakar'] != null) {
// Coba parse jika string
if (penyakit['nilai_pakar'] is String) {
try {
String nilaiStr = penyakit['nilai_pakar'].toString().trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(nilaiStr.replaceAll(',', '.'));
}
} catch (e) {
print("Error parsing nilai_pakar: $e");
}
}
// Langsung gunakan jika sudah double
else if (penyakit['nilai_pakar'] is double) {
nilaiPakar = penyakit['nilai_pakar'];
}
// Jika int, konversi ke double
else if (penyakit['nilai_pakar'] is int) {
nilaiPakar = penyakit['nilai_pakar'].toDouble();
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditPenyakitPage(
idPenyakit: penyakit['id'],
namaAwal: penyakit['nama'] ?? '',
deskripsiAwal: penyakit['deskripsi'] ?? '',
penangananAwal: penyakit['penanganan'] ?? '',
gambarUrl: penyakit['foto'] ?? '',
nilai_pakar: nilaiPakar,
onPenyakitUpdated: _fetchPenyakit,
),
),
);
}
//pagination
int currentPage = 0;
int rowsPerPage = 10;
@ -75,203 +149,169 @@ class _PenyakitPageState extends State<PenyakitPage> {
@override
Widget build(BuildContext context) {
int start = currentPage * rowsPerPage;
int end =
(start + rowsPerPage < penyakitList.length)
int end = (start + rowsPerPage < filteredPenyakitList.length)
? start + rowsPerPage
: penyakitList.length;
List currentPageData = penyakitList.sublist(start, end);
: filteredPenyakitList.length;
List currentPageData = filteredPenyakitList.sublist(start, end);
return Scaffold(
appBar: AppBar(title: Text('Halaman Penyakit'), backgroundColor: Color(0xFF9DC08D)),
appBar: AppBar(
title: Text('Halaman Penyakit'),
backgroundColor: Color(0xFF9DC08D),
),
body: Column(
children: [
SizedBox(height: 20),
Row(
SizedBox(height: 16),
// Search Button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 20.0),
child: ElevatedButton(
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]),
),
ElevatedButton.icon(
onPressed: _toggleSearch,
icon: Icon(Icons.search),
label: Text('Cari'),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
foregroundColor: Colors.white,
),
),
],
),
SizedBox(height: 20),
),
// Search Field (conditional)
if (isSearchVisible)
Container(
margin: EdgeInsets.all(16),
child: TextField(
controller: searchController,
autofocus: true,
decoration: InputDecoration(
hintText: 'Cari nama atau kode penyakit...',
prefixIcon: Icon(Icons.search),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: _toggleSearch,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: Colors.grey[100],
),
),
),
Expanded(
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),
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Data List
Expanded(
child: ListView.builder(
itemCount: currentPageData.length,
itemBuilder: (context, index) {
final penyakit = currentPageData[index];
return Container(
margin: EdgeInsets.only(bottom: 12),
child: Row(
children: [
// Card dengan nama penyakit
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => _navigateToEdit(penyakit),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
penyakit['nama'] ?? '-',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
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')),
SizedBox(height: 4),
Text(
penyakit['kode'] ?? '-',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
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: () {
// Parse nilai_pakar dengan aman
double nilaiPakar = 0.0;
if (penyakit['nilai_pakar'] != null) {
// Coba parse jika string
if (penyakit['nilai_pakar'] is String) {
try {
String nilaiStr =
penyakit['nilai_pakar']
.toString()
.trim();
if (nilaiStr.isNotEmpty) {
nilaiPakar = double.parse(
nilaiStr.replaceAll(',', '.'),
);
}
} catch (e) {
print(
"Error parsing nilai_pakar: $e",
);
}
}
// Langsung gunakan jika sudah double
else if (penyakit['nilai_pakar']
is double) {
nilaiPakar = penyakit['nilai_pakar'];
}
// Jika int, konversi ke double
else if (penyakit['nilai_pakar'] is int) {
nilaiPakar =
penyakit['nilai_pakar'].toDouble();
}
}
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'] ?? '',
gambarUrl:
penyakit['foto'] ?? '',
nilai_pakar: nilaiPakar,
onPenyakitUpdated:
_fetchPenyakit, // fungsi untuk refresh list setelah update
),
),
),
SizedBox(width: 8),
// Button hapus di luar card
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.delete, color: Colors.white),
onPressed: () => _konfirmasiHapus(penyakit['id']),
),
),
],
),
);
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed:
() => _konfirmasiHapus(penyakit['id']),
),
],
),
),
],
),
),
DataRow(
cells: [
DataCell(Container()),
DataCell(Container()),
DataCell(
Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
SizedBox(height: 16),
// Pagination
if (filteredPenyakitList.length > rowsPerPage)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.chevron_left),
onPressed:
currentPage > 0
? () =>
setState(() => currentPage--)
onPressed: currentPage > 0
? () => setState(() => currentPage--)
: null,
),
Text(' ${currentPage + 1}'),
Text(
'Halaman ${currentPage + 1}',
style: TextStyle(fontSize: 16),
),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed:
(currentPage + 1) * rowsPerPage <
penyakitList.length
? () =>
setState(() => currentPage++)
onPressed: (currentPage + 1) * rowsPerPage < filteredPenyakitList.length
? () => setState(() => currentPage++)
: null,
),
],
),
),
),
DataCell(Container()),
DataCell(Container()),
DataCell(Container()),
],
),
],
),
),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TambahPenyakitPage(
onPenyakitAdded: _fetchPenyakit,
),
),
);
},
child: Icon(Icons.add),
backgroundColor: Color(0xFF9DC08D),
),
);
}
}

View File

@ -3,7 +3,6 @@ import 'package:SIBAYAM/admin/edit_rule_page.dart';
import 'package:http/http.dart' as http;
import 'package:SIBAYAM/api_services/api_services.dart';
import 'tambah_rule_page.dart';
import 'edit_hama_page.dart';
class RulePage extends StatefulWidget {
const RulePage({Key? key}) : super(key: key);
@ -18,8 +17,13 @@ class _RulePageState extends State<RulePage> {
List<Map<String, dynamic>> hamaList = [];
List<dynamic> rules = [];
List<dynamic> filteredRules = [];
bool isLoading = true;
// Search and filter variables
TextEditingController searchController = TextEditingController();
String selectedFilter = 'Semua'; // 'Semua', 'Penyakit', 'Hama'
// Pagination variables
int currentPage = 0;
int rowsPerPage = 10;
@ -28,9 +32,57 @@ class _RulePageState extends State<RulePage> {
void initState() {
super.initState();
fetchRules();
searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
searchController.removeListener(_onSearchChanged);
searchController.dispose();
super.dispose();
}
void _onSearchChanged() {
_filterRules();
}
void _filterRules() {
setState(() {
filteredRules = rules.where((rule) {
// Filter berdasarkan kategori
bool categoryMatch = true;
if (selectedFilter == 'Penyakit') {
categoryMatch = rule['id_penyakit'] != null;
} else if (selectedFilter == 'Hama') {
categoryMatch = rule['id_hama'] != null;
}
// Filter berdasarkan search text
bool searchMatch = true;
if (searchController.text.isNotEmpty) {
final searchText = searchController.text.toLowerCase();
final namaKategori = rule['id_penyakit'] != null
? (rule['nama_penyakit'] ?? '').toLowerCase()
: (rule['nama_hama'] ?? '').toLowerCase();
final namaGejala = (rule['nama_gejala'] ?? '').toLowerCase();
searchMatch = namaKategori.contains(searchText) ||
namaGejala.contains(searchText);
}
return categoryMatch && searchMatch;
}).toList();
// Reset ke halaman pertama setelah filter
currentPage = 0;
});
}
void fetchRules() async {
setState(() {
isLoading = true;
});
final apiService = ApiService();
try {
@ -49,12 +101,12 @@ class _RulePageState extends State<RulePage> {
...rulesPenyakit.map((rule) {
final gejala = gejalaList.firstWhere(
(item) => item['id'] == rule['id_gejala'],
orElse: () => {'nama': '-'},
orElse: () => {'nama': 'Gejala tidak ditemukan'},
);
final penyakit = penyakitList.firstWhere(
(item) => item['id'] == rule['id_penyakit'],
orElse: () => {'nama': '-'},
orElse: () => {'nama': 'Penyakit tidak ditemukan'},
);
return {
@ -66,20 +118,19 @@ class _RulePageState extends State<RulePage> {
'nama_penyakit': penyakit['nama'],
'nama_hama': null,
'nilai_pakar': rule['nilai_pakar'],
'type': 'Penyakit',
};
}),
// Mengolah rules hama
...rulesHama.map((rule) {
// Mencari gejala berdasarkan id
final gejala = gejalaList.firstWhere(
(item) => item['id'] == rule['id_gejala'],
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
orElse: () => {'nama': 'Gejala tidak ditemukan'},
);
// Mencari hama berdasarkan id
final hama = hamaList.firstWhere(
(item) => item['id'] == rule['id_hama'],
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
orElse: () => {'nama': 'Hama tidak ditemukan'},
);
return {
@ -91,15 +142,23 @@ class _RulePageState extends State<RulePage> {
'nama_penyakit': null,
'nama_hama': hama['nama'],
'nilai_pakar': rule['nilai_pakar'],
'type': 'Hama',
};
}),
];
setState(() {
rules = enrichedRules;
filteredRules = enrichedRules;
});
} catch (e) {
print('Terjadi kesalahan saat memuat data: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal memuat data: $e'),
backgroundColor: Colors.red,
),
);
} finally {
setState(() {
isLoading = false;
@ -108,21 +167,47 @@ class _RulePageState extends State<RulePage> {
}
Future<void> deleteRule(Map<String, dynamic> rule) async {
// Tampilkan dialog konfirmasi
bool? confirm = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Konfirmasi Hapus'),
content: Text('Apakah Anda yakin ingin menghapus rule ini?'),
actions: [
TextButton(
child: Text('Batal'),
onPressed: () => Navigator.of(context).pop(false),
),
TextButton(
child: Text('Hapus', style: TextStyle(color: Colors.red)),
onPressed: () => Navigator.of(context).pop(true),
),
],
);
},
);
if (confirm != true) return;
try {
http.Response res;
// Tentukan fungsi delete berdasarkan isi rule
if (rule['id_hama'] != null) {
res = await ApiService.deleteRuleHama(rule['id']); // Fungsi API untuk delete hama
res = await ApiService.deleteRuleHama(rule['id']);
} else if (rule['id_penyakit'] != null) {
res = await ApiService.deleteRulePenyakit(rule['id']); // Fungsi API untuk delete penyakit
res = await ApiService.deleteRulePenyakit(rule['id']);
} else {
throw Exception("Data rule tidak valid (tidak ada id_hama atau id_penyakit)");
throw Exception("Data rule tidak valid");
}
if (res.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Rule berhasil dihapus"))
SnackBar(
content: Text("Rule berhasil dihapus"),
backgroundColor: Colors.green,
),
);
fetchRules(); // Refresh data setelah delete
} else {
@ -130,7 +215,10 @@ class _RulePageState extends State<RulePage> {
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Terjadi kesalahan saat menghapus: $e")),
SnackBar(
content: Text("Terjadi kesalahan saat menghapus: $e"),
backgroundColor: Colors.red,
),
);
}
}
@ -138,27 +226,80 @@ class _RulePageState extends State<RulePage> {
// Get paginated data
List<dynamic> get paginatedRules {
final startIndex = currentPage * rowsPerPage;
final endIndex = startIndex + rowsPerPage > rules.length ? rules.length : startIndex + rowsPerPage;
final endIndex = startIndex + rowsPerPage > filteredRules.length
? filteredRules.length
: startIndex + rowsPerPage;
if (startIndex >= rules.length) {
if (startIndex >= filteredRules.length) {
return [];
}
return rules.sublist(startIndex, endIndex);
return filteredRules.sublist(startIndex, endIndex);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Data Rules'), backgroundColor: Color(0xFF9DC08D)),
body: Padding(
Widget _buildSearchAndFilter() {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Button untuk tambah rule hama
Expanded(
flex: 2,
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: 'Cari berdasarkan nama penyakit, hama, atau gejala...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
),
),
SizedBox(width: 16),
Expanded(
child: DropdownButtonFormField<String>(
value: selectedFilter,
decoration: InputDecoration(
labelText: 'Filter Kategori',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
),
items: ['Semua', 'Penyakit', 'Hama'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
selectedFilter = newValue!;
_filterRules();
});
},
),
),
],
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total: ${filteredRules.length} rule(s)',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
Row(
children: [
ElevatedButton.icon(
onPressed: () {
Navigator.push(
@ -166,31 +307,27 @@ class _RulePageState extends State<RulePage> {
MaterialPageRoute(
builder: (context) => TambahRulePage(
isEditing: false,
isEditingHama: true, // Menandakan ini adalah rule hama
isEditingHama: true,
selectedRuleIds: [],
selectedGejalaIds: [],
nilaiPakarList: [],
selectedHamaId: null,
selectedPenyakitId: null,
showHamaOnly: true, // Parameter baru untuk menampilkan hanya dropdown hama
showHamaOnly: true,
),
),
).then((_) => fetchRules());
},
icon: Icon(Icons.bug_report, size: 16,),
label: Text(
"Tambah Rule Hama",
style: TextStyle(fontSize: 12)),
icon: Icon(Icons.bug_report, size: 16),
label: Text("Rule Hama", style: TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6), // Padding lebih kecil
minimumSize: Size(0, 32), // Tinggi minimum lebih kecil
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // Mengurangi area tap
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
minimumSize: Size(0, 36),
),
),
SizedBox(width: 10),
// Button untuk tambah rule penyakit
SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () {
Navigator.push(
@ -198,85 +335,194 @@ class _RulePageState extends State<RulePage> {
MaterialPageRoute(
builder: (context) => TambahRulePage(
isEditing: false,
isEditingHama: false, // Menandakan ini adalah rule penyakit
isEditingHama: false,
selectedRuleIds: [],
selectedGejalaIds: [],
nilaiPakarList: [],
selectedHamaId: null,
selectedPenyakitId: null,
showPenyakitOnly: true, // Parameter baru untuk menampilkan hanya dropdown penyakit
showPenyakitOnly: true,
),
),
).then((_) => fetchRules());
},
icon: Icon(Icons.healing, size: 16,),
label: Text(
"Tambah Rule Penyakit",
style: TextStyle(fontSize: 12),),
icon: Icon(Icons.healing, size: 16),
label: Text("Rule Penyakit", style: TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6), // Padding lebih kecil
minimumSize: Size(0, 32), // Tinggi minimum lebih kecil
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // Mengurangi area tap
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
minimumSize: Size(0, 36),
),
),
],
),
const SizedBox(height: 16),
isLoading
? const Center(child: CircularProgressIndicator())
: Expanded(
],
),
],
),
),
);
}
Widget _buildDataList() {
if (paginatedRules.isEmpty) {
return Container(
padding: EdgeInsets.all(32),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
headingRowColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
return Color(0xFF9DC08D); // Apply color to all header rows
},
Icon(Icons.inbox, size: 64, color: Colors.grey[400]),
SizedBox(height: 16),
Text(
'Tidak ada data rule',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
if (searchController.text.isNotEmpty || selectedFilter != 'Semua')
TextButton(
onPressed: () {
setState(() {
searchController.clear();
selectedFilter = 'Semua';
_filterRules();
});
},
child: Text('Reset Filter'),
),
columns: const [
DataColumn(label: Text('No')),
DataColumn(label: Text('Hama & Penyakit')),
DataColumn(label: Text('Gejala')),
DataColumn(label: Text('Nilai Pakar')),
DataColumn(label: Text('Aksi')),
],
rows: List.generate(paginatedRules.length, (index) {
),
);
}
return ListView.builder(
itemCount: paginatedRules.length,
itemBuilder: (context, index) {
final rule = paginatedRules[index];
final displayIndex = currentPage * rowsPerPage + index + 1;
final namaKategori = rule['id_penyakit'] != null
? rule['nama_penyakit'] ?? '-'
: rule['nama_hama'] ?? '-';
final isPenyakit = rule['id_penyakit'] != null;
final kategori = rule['type'] ?? 'Unknown';
final isHama = rule['id_hama'] != null;
return DataRow(
cells: [
DataCell(Text(displayIndex.toString())),
DataCell(Text(namaKategori)),
DataCell(Text(rule['nama_gejala'] ?? '-')),
DataCell(Text(rule['nilai_pakar']?.toString() ?? '-')),
DataCell(
return Container(
margin: EdgeInsets.only(bottom: 12),
child: Row(
children: [
// Card dengan data rule
Expanded(
child: Card(
elevation: 2,
child: InkWell(
onTap: () => _navigateToEdit(rule),
child: Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Kategori badge dan nama
Row(
children: [
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.orange,
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isHama ? Colors.green[100] : Colors.blue[100],
borderRadius: BorderRadius.circular(12),
),
onPressed: () {
child: Text(
kategori,
style: TextStyle(
color: isHama ? Colors.green[800] : Colors.blue[800],
fontWeight: FontWeight.w500,
fontSize: 10,
),
),
),
SizedBox(width: 8),
Expanded(
child: Text(
namaKategori,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
SizedBox(height: 8),
// Gejala
Text(
'Gejala: ${rule['nama_gejala'] ?? '-'}',
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
// Nilai pakar
Row(
children: [
Text(
'Nilai Pakar: ',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFF9DC08D),
borderRadius: BorderRadius.circular(8),
),
child: Text(
rule['nilai_pakar']?.toString() ?? '-',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFF),
fontSize: 12,
),
),
),
],
),
],
),
),
),
),
),
SizedBox(width: 8),
// Button hapus di luar card
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: IconButton(
icon: Icon(Icons.delete, color: Colors.white),
onPressed: () => deleteRule(rule),
),
),
],
),
);
},
);
}
void _navigateToEdit(Map<String, dynamic> rule) {
if (rule != null &&
rule['id'] != null &&
rule['id_gejala'] != null &&
rule['nilai_pakar'] != null) {
// Tentukan jenis rule untuk editing
final bool editingHama = rule['id_hama'] != null;
Navigator.push(
@ -290,66 +536,104 @@ class _RulePageState extends State<RulePage> {
nilaiPakarList: [(rule['nilai_pakar'] as num).toDouble()],
selectedHamaId: rule['id_hama'] as int?,
selectedPenyakitId: rule['id_penyakit'] as int?,
// Tambahkan parameter untuk menentukan dropdown yang ditampilkan
showHamaOnly: editingHama,
showPenyakitOnly: !editingHama,
),
),
).then((_) => fetchRules());
} else {
// Tampilkan pesan error jika data rule tidak lengkap
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Data rule tidak lengkap atau tidak valid"),
content: Text("Data rule tidak lengkap"),
backgroundColor: Colors.red,
),
);
// Debug info
print("Rule data: $rule");
}
},
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
deleteRule(rule);
},
),
],
),
),
],
);
}),
),
),
),
),
// Pagination controls
Container(
padding: EdgeInsets.symmetric(vertical: 8.0),
}
Widget _buildPaginationControls() {
final totalPages = (filteredRules.length / rowsPerPage).ceil();
if (totalPages <= 1) return SizedBox.shrink();
return Card(
elevation: 1,
child: Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Menampilkan ${(currentPage * rowsPerPage) + 1} - ${((currentPage + 1) * rowsPerPage > filteredRules.length) ? filteredRules.length : (currentPage + 1) * rowsPerPage} dari ${filteredRules.length}',
style: TextStyle(color: Colors.grey[600]),
),
Row(
children: [
IconButton(
icon: Icon(Icons.first_page),
onPressed: currentPage > 0
? () => setState(() => currentPage = 0)
: null,
),
IconButton(
icon: Icon(Icons.chevron_left),
onPressed: currentPage > 0
? () => setState(() => currentPage--)
: null,
),
Text('Halaman ${currentPage + 1} dari ${(rules.length / rowsPerPage).ceil()}'),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Text('${currentPage + 1} / $totalPages'),
),
IconButton(
icon: Icon(Icons.chevron_right),
onPressed: (currentPage + 1) * rowsPerPage < rules.length
onPressed: (currentPage + 1) * rowsPerPage < filteredRules.length
? () => setState(() => currentPage++)
: null,
),
IconButton(
icon: Icon(Icons.last_page),
onPressed: (currentPage + 1) * rowsPerPage < filteredRules.length
? () => setState(() => currentPage = totalPages - 1)
: null,
),
],
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Halaman Aturan'),
backgroundColor: Color(0xFF9DC08D),
elevation: 0,
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: fetchRules,
tooltip: 'Refresh Data',
),
],
),
body: isLoading
? Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_buildSearchAndFilter(),
SizedBox(height: 16),
Expanded(
child: Column(
children: [
Expanded(child: _buildDataList()),
SizedBox(height: 8),
_buildPaginationControls(),
],
),
),

View File

@ -42,33 +42,40 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
deskripsiController.text.isNotEmpty &&
penangananController.text.isNotEmpty) {
try {
double? nilaipakar;
if (nilaiPakarController.text.isNotEmpty) {
// Kirim nilai pakar sebagai double, 0.0 jika tidak diisi
double nilaiPakar = 0.0;
if (nilaiPakarController.text.trim().isNotEmpty) {
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
nilaipakar = double.parse(nilaiInput);
nilaiPakar = double.parse(nilaiInput);
}
print("Debug - Nilai Pakar Double: $nilaiPakar");
print("Debug - Nama: ${namaController.text}");
print("Debug - Deskripsi: ${deskripsiController.text}");
print("Debug - Penanganan: ${penangananController.text}");
print("Debug - Nilai Pakar: $nilaiPakar");
print("Debug - Image File: $_pickedFile");
await apiService.createHama(
namaController.text,
deskripsiController.text,
penangananController.text,
_pickedFile,
nilaipakar, // boleh null
nilaiPakar,
);
widget.onHamaAdded();
Navigator.pop(context);
_showDialog('Berhasil', 'Data hama berhasil ditambahkan.');
} catch (e) {
_showDialog('Gagal', 'Gagal menambahkan data hama.');
_showDialog('Gagal', 'Gagal menambahkan data hama: $e');
print("Error adding hama: $e");
}
} else {
_showDialog('Error', 'Semua field harus diisi (kecuali nilai pakar).');
_showDialog('Error', 'Nama, deskripsi, dan penanganan hama harus diisi.');
}
}
void _showDialog(String title, String message) {
showDialog(
context: context,
@ -149,8 +156,11 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
SizedBox(height: 15),
// TextField(
// controller: nilaiPakarController,
// decoration: InputDecoration(labelText: 'Nilai Pakar'),
// maxLines: 3,
// decoration: InputDecoration(
// labelText: 'Nilai Pakar (Optional)',
// hintText: 'Masukkan nilai pakar (opsional)',
// ),
// keyboardType: TextInputType.numberWithOptions(decimal: true),
// ),
SizedBox(height: 15),
Text('Foto'),

View File

@ -39,11 +39,23 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
Future<void> _simpanPenyakit() async {
if (namaController.text.isNotEmpty &&
deskripsiController.text.isNotEmpty &&
penangananController.text.isNotEmpty &&
nilaiPakarController.text.isNotEmpty) {
penangananController.text.isNotEmpty) {
try {
// Kirim nilai pakar sebagai double, 0.0 jika tidak diisi
double nilaiPakar = 0.0;
if (nilaiPakarController.text.trim().isNotEmpty) {
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
double nilaiPakar = double.parse(nilaiInput);
nilaiPakar = double.parse(nilaiInput);
}
print("Debug - Nilai Pakar Double: $nilaiPakar");
print("Debug - Nama: ${namaController.text}");
print("Debug - Deskripsi: ${deskripsiController.text}");
print("Debug - Penanganan: ${penangananController.text}");
print("Debug - Nilai Pakar: $nilaiPakar");
print("Debug - Image File: $_pickedFile");
await apiService.createPenyakit(
namaController.text,
deskripsiController.text,
@ -53,13 +65,13 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
);
widget.onPenyakitAdded();
Navigator.pop(context);
_showDialog('Berhasil', 'Data penyakit berhasil ditambahkan.');
_showDialog('Berhasil', 'Data hama berhasil ditambahkan.');
} catch (e) {
_showDialog('Gagal', 'Gagal menambahkan data penyakit.');
print("Error adding penyakit: $e");
_showDialog('Gagal', 'Gagal menambahkan data hama: $e');
print("Error adding hama: $e");
}
} else {
_showDialog('Error', 'Semua field harus diisi.');
_showDialog('Error', 'Nama, deskripsi, dan penanganan hama harus diisi.');
}
}
@ -141,11 +153,11 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
maxLines: 3,
),
SizedBox(height: 15),
TextField(
controller: nilaiPakarController,
decoration: InputDecoration(labelText: 'nilai pakar'),
maxLines: 3,
),
// TextField(
// controller: nilaiPakarController,
// decoration: InputDecoration(labelText: 'nilai pakar'),
// maxLines: 3,
// ),
SizedBox(height: 15),
(_webImage != null)
? Image.memory(

View File

@ -9,6 +9,7 @@ class UserListPage extends StatefulWidget {
class _UserListPageState extends State<UserListPage> {
final ApiService apiService = ApiService();
List<Map<String, dynamic>> users = [];
List<Map<String, dynamic>> filteredUsers = [];
bool isLoading = true;
final _formKey = GlobalKey<FormState>();
@ -17,6 +18,7 @@ class _UserListPageState extends State<UserListPage> {
final _passwordController = TextEditingController();
final _alamatController = TextEditingController();
final _nomorTeleponController = TextEditingController();
final _searchController = TextEditingController();
@override
void initState() {
@ -26,12 +28,12 @@ class _UserListPageState extends State<UserListPage> {
@override
void dispose() {
// Dispose controllers in dispose method
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_alamatController.dispose();
_nomorTeleponController.dispose();
_searchController.dispose();
super.dispose();
}
@ -45,17 +47,14 @@ class _UserListPageState extends State<UserListPage> {
nomorTelepon: _nomorTeleponController.text,
);
// Clear form
_nameController.clear();
_emailController.clear();
_passwordController.clear();
_alamatController.clear();
_nomorTeleponController.clear();
// Refresh user list
await _loadUsers();
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('User berhasil ditambahkan'),
@ -74,7 +73,6 @@ class _UserListPageState extends State<UserListPage> {
Future<void> _updateUser(Map<String, dynamic> user) async {
try {
// Hanya kirim password jika diisi
String? newPassword =
_passwordController.text.isEmpty ? null : _passwordController.text;
@ -82,22 +80,19 @@ class _UserListPageState extends State<UserListPage> {
id: user['id'],
name: _nameController.text,
email: _emailController.text,
password: newPassword, // Kirim null jika password kosong
password: newPassword,
alamat: _alamatController.text,
nomorTelepon: _nomorTeleponController.text,
);
// Clear form
_nameController.clear();
_emailController.clear();
_passwordController.clear();
_alamatController.clear();
_nomorTeleponController.clear();
// Refresh user list
await _loadUsers();
// Show success message with password info
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
@ -118,31 +113,10 @@ class _UserListPageState extends State<UserListPage> {
}
}
Future<void> _deleteUser(int userId) async {
try {
await apiService.deleteUser(userId);
await _loadUsers();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('User berhasil dihapus'),
backgroundColor: Colors.green,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal menghapus user: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
void _showDeleteConfirmation(Map<String, dynamic> user) {
showDialog(
context: context,
builder:
(context) => AlertDialog(
builder: (context) => AlertDialog(
title: Text('Konfirmasi Hapus'),
content: Text(
'Apakah Anda yakin ingin menghapus user ${user['name']}?',
@ -155,9 +129,9 @@ class _UserListPageState extends State<UserListPage> {
ElevatedButton(
onPressed: () async {
try {
Navigator.pop(context); // Close dialog first
Navigator.pop(context);
await apiService.deleteUser(user['id']);
await _loadUsers(); // Refresh the list
await _loadUsers();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -183,17 +157,15 @@ class _UserListPageState extends State<UserListPage> {
}
void _showUpdateDialog(Map<String, dynamic> user) {
// Pre-fill form with existing user data
_nameController.text = user['name'] ?? '';
_emailController.text = user['email'] ?? '';
_alamatController.text = user['alamat'] ?? '';
_nomorTeleponController.text = user['nomorTelepon'] ?? '';
_passwordController.text = ''; // Empty for security
_passwordController.text = '';
showDialog(
context: context,
builder:
(context) => AlertDialog(
builder: (context) => AlertDialog(
title: Text('Update User'),
content: SingleChildScrollView(
child: Form(
@ -204,18 +176,14 @@ class _UserListPageState extends State<UserListPage> {
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Nama'),
validator:
(value) =>
value?.isEmpty ?? true
? 'Nama tidak boleh kosong'
: null,
validator: (value) =>
value?.isEmpty ?? true ? 'Nama tidak boleh kosong' : null,
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value?.isEmpty ?? true)
return 'Email tidak boleh kosong';
if (value?.isEmpty ?? true) return 'Email tidak boleh kosong';
if (!value!.contains('@')) return 'Email tidak valid';
return null;
},
@ -224,14 +192,12 @@ class _UserListPageState extends State<UserListPage> {
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password Baru',
helperText:
'Kosongkan jika tidak ingin mengubah password',
helperText: 'Kosongkan jika tidak ingin mengubah password',
),
obscureText: true,
validator: (value) {
if (value?.isNotEmpty ?? false) {
if (value!.length < 6)
return 'Password minimal 6 karakter';
if (value!.length < 6) return 'Password minimal 6 karakter';
}
return null;
},
@ -239,21 +205,15 @@ class _UserListPageState extends State<UserListPage> {
TextFormField(
controller: _alamatController,
decoration: InputDecoration(labelText: 'Alamat'),
validator:
(value) =>
value?.isEmpty ?? true
? 'Alamat tidak boleh kosong'
: null,
validator: (value) =>
value?.isEmpty ?? true ? 'Alamat tidak boleh kosong' : null,
),
TextFormField(
controller: _nomorTeleponController,
decoration: InputDecoration(labelText: 'Nomor Telepon'),
keyboardType: TextInputType.phone,
validator:
(value) =>
value?.isEmpty ?? true
? 'Nomor telepon tidak boleh kosong'
: null,
validator: (value) =>
value?.isEmpty ?? true ? 'Nomor telepon tidak boleh kosong' : null,
),
],
),
@ -282,10 +242,15 @@ class _UserListPageState extends State<UserListPage> {
}
void _showAddUserDialog() {
_nameController.clear();
_emailController.clear();
_passwordController.clear();
_alamatController.clear();
_nomorTeleponController.clear();
showDialog(
context: context,
builder:
(context) => AlertDialog(
builder: (context) => AlertDialog(
title: Text('Tambah User Baru'),
content: SingleChildScrollView(
child: Form(
@ -382,6 +347,7 @@ class _UserListPageState extends State<UserListPage> {
final userList = await apiService.getUsers();
setState(() {
users = userList;
filteredUsers = userList;
isLoading = false;
});
} catch (e) {
@ -392,6 +358,35 @@ class _UserListPageState extends State<UserListPage> {
}
}
void _filterUsers(String query) {
setState(() {
if (query.isEmpty) {
filteredUsers = users;
} else {
filteredUsers = users.where((user) {
final name = user['name']?.toString().toLowerCase() ?? '';
final role = user['role']?.toString().toLowerCase() ?? '';
final searchQuery = query.toLowerCase();
return name.contains(searchQuery) || role.contains(searchQuery);
}).toList();
}
});
}
void _navigateToUserDetail(Map<String, dynamic> user) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserDetailPage(
user: user,
onUserUpdated: _loadUsers,
onUserDeleted: _loadUsers,
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -404,77 +399,475 @@ class _UserListPageState extends State<UserListPage> {
backgroundColor: Color(0xFF9DC08D),
child: Icon(Icons.add),
),
body:
isLoading
body: isLoading
? Center(child: CircularProgressIndicator())
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
child: DataTable(
columnSpacing: 20,
columns: [
DataColumn(label: Text('Nama')),
DataColumn(label: Text('Email')),
DataColumn(label: Text('Alamat')),
// DataColumn(label: Text('No. Telepon')),
DataColumn(label: Text('Role')),
DataColumn(label: Text('Aksi')),
],
rows:
users.map((user) {
return DataRow(
cells: [
DataCell(Text(user['name'] ?? '-')),
DataCell(Text(user['email'] ?? '-')),
DataCell(Text(user['alamat'] ?? '-')),
// DataCell(Text(user['nomorTelepon'] ?? '-')),
DataCell(
: Column(
children: [
// Search Bar
Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
padding: EdgeInsets.all(16),
child: TextField(
controller: _searchController,
onChanged: _filterUsers,
decoration: InputDecoration(
hintText: 'Cari berdasarkan nama atau role...',
prefixIcon: Icon(Icons.search, color: Color(0xFF9DC08D)),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, color: Colors.grey),
onPressed: () {
_searchController.clear();
_filterUsers('');
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
decoration: BoxDecoration(
color:
user['role'] == 'admin'
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Color(0xFF9DC08D), width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey[300]!),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
// User List
Expanded(
child: filteredUsers.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_searchController.text.isNotEmpty
? Icons.search_off
: Icons.people_outline,
size: 64,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
_searchController.text.isNotEmpty
? 'Tidak ada pengguna yang ditemukan'
: 'Tidak ada pengguna',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
if (_searchController.text.isNotEmpty) ...[
SizedBox(height: 8),
Text(
'Coba kata kunci lain',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
],
),
)
: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16),
itemCount: filteredUsers.length,
itemBuilder: (context, index) {
final user = filteredUsers[index];
return Card(
elevation: 2,
margin: EdgeInsets.only(bottom: 12),
child: ListTile(
leading: CircleAvatar(
backgroundColor: user['role'] == 'admin'
? Colors.blue.withOpacity(0.2)
: Colors.green.withOpacity(0.2),
child: Icon(
user['role'] == 'admin' ? Icons.admin_panel_settings : Icons.person,
color: user['role'] == 'admin' ? Colors.blue : Colors.green,
),
),
title: Text(
user['name'] ?? 'Nama tidak tersedia',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
subtitle: Container(
margin: EdgeInsets.only(top: 4),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: user['role'] == 'admin'
? Colors.blue.withOpacity(0.1)
: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
user['role'] ?? 'user',
style: TextStyle(
color:
user['role'] == 'admin'
? Colors.blue
: Colors.green,
color: user['role'] == 'admin' ? Colors.blue : Colors.green,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
trailing: Icon(Icons.arrow_forward_ios, size: 16),
onTap: () => _navigateToUserDetail(user),
),
DataCell(
Row(
);
},
),
),
],
),
);
}
}
// Halaman Detail User
class UserDetailPage extends StatelessWidget {
final Map<String, dynamic> user;
final VoidCallback onUserUpdated;
final VoidCallback onUserDeleted;
const UserDetailPage({
Key? key,
required this.user,
required this.onUserUpdated,
required this.onUserDeleted,
}) : super(key: key);
void _showDeleteConfirmation(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Konfirmasi Hapus'),
content: Text(
'Apakah Anda yakin ingin menghapus user ${user['name']}?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Batal'),
),
ElevatedButton(
onPressed: () async {
try {
Navigator.pop(context); // Close dialog
final apiService = ApiService();
await apiService.deleteUser(user['id']);
Navigator.pop(context); // Go back to list
onUserDeleted(); // Refresh list
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('User berhasil dihapus'),
backgroundColor: Colors.green,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal menghapus user: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: Text('Hapus'),
),
],
),
);
}
void _showUpdateDialog(BuildContext context) {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController(text: user['name'] ?? '');
final _emailController = TextEditingController(text: user['email'] ?? '');
final _alamatController = TextEditingController(text: user['alamat'] ?? '');
final _nomorTeleponController = TextEditingController(text: user['nomorTelepon'] ?? '');
final _passwordController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Update User'),
content: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Nama'),
validator: (value) =>
value?.isEmpty ?? true ? 'Nama tidak boleh kosong' : null,
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value?.isEmpty ?? true) return 'Email tidak boleh kosong';
if (!value!.contains('@')) return 'Email tidak valid';
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password Baru',
helperText: 'Kosongkan jika tidak ingin mengubah password',
),
obscureText: true,
validator: (value) {
if (value?.isNotEmpty ?? false) {
if (value!.length < 6) return 'Password minimal 6 karakter';
}
return null;
},
),
TextFormField(
controller: _alamatController,
decoration: InputDecoration(labelText: 'Alamat'),
validator: (value) =>
value?.isEmpty ?? true ? 'Alamat tidak boleh kosong' : null,
),
TextFormField(
controller: _nomorTeleponController,
decoration: InputDecoration(labelText: 'Nomor Telepon'),
keyboardType: TextInputType.phone,
validator: (value) =>
value?.isEmpty ?? true ? 'Nomor telepon tidak boleh kosong' : null,
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Batal'),
),
ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
try {
Navigator.pop(context);
String? newPassword = _passwordController.text.isEmpty
? null
: _passwordController.text;
final apiService = ApiService();
await apiService.updateUser(
id: user['id'],
name: _nameController.text,
email: _emailController.text,
password: newPassword,
alamat: _alamatController.text,
nomorTelepon: _nomorTeleponController.text,
);
Navigator.pop(context); // Go back to list
onUserUpdated(); // Refresh list
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
newPassword != null
? 'User berhasil diperbarui termasuk password'
: 'User berhasil diperbarui',
),
backgroundColor: Colors.green,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal memperbarui user: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF9DC08D),
),
child: Text('Update'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Detail Pengguna'),
backgroundColor: Color(0xFF9DC08D),
actions: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _showUpdateDialog(user),
onPressed: () => _showUpdateDialog(context),
),
IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _showDeleteConfirmation(context),
),
],
),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile Header
Container(
width: double.infinity,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Color(0xFF9DC08D).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
CircleAvatar(
radius: 40,
backgroundColor: user['role'] == 'admin'
? Colors.blue.withOpacity(0.2)
: Colors.green.withOpacity(0.2),
child: Icon(
user['role'] == 'admin'
? Icons.admin_panel_settings
: Icons.person,
size: 40,
color: user['role'] == 'admin' ? Colors.blue : Colors.green,
),
),
SizedBox(height: 12),
Text(
user['name'] ?? 'Nama tidak tersedia',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: user['role'] == 'admin'
? Colors.blue.withOpacity(0.2)
: Colors.green.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: Text(
user['role'] ?? 'user',
style: TextStyle(
color: user['role'] == 'admin' ? Colors.blue : Colors.green,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
SizedBox(height: 24),
// Detail Information
Text(
'Informasi Detail',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
SizedBox(height: 16),
_buildDetailItem(
icon: Icons.email,
title: 'Email',
value: user['email'] ?? 'Email tidak tersedia',
),
_buildDetailItem(
icon: Icons.location_on,
title: 'Alamat',
value: user['alamat'] ?? 'Alamat tidak tersedia',
),
_buildDetailItem(
icon: Icons.phone,
title: 'Nomor Telepon',
value: user['nomorTelepon'] ?? 'Nomor telepon tidak tersedia',
),
],
),
),
);
}
Widget _buildDetailItem({
required IconData icon,
required String title,
required String value,
}) {
return Container(
margin: EdgeInsets.only(bottom: 16),
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[200]!),
),
child: Row(
children: [
Icon(
icon,
color: Color(0xFF9DC08D),
size: 24,
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
onPressed:
() => _showDeleteConfirmation(user),
),
],
),
),
],
);
}).toList(),
),
),
),
);
}

View File

@ -899,7 +899,7 @@ Future<List<Map<String, dynamic>>> getAllHistori() async {
String deskripsi,
String penanganan,
XFile? pickedFile,
double nilai_pakar
double? nilai_pakar
) async {
try {
var uri = Uri.parse(penyakitUrl);

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'package:SIBAYAM/user/before_login.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
@ -62,7 +63,9 @@ class _LoginPageState extends State<LoginPage> {
}
try {
var url = Uri.parse("https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/auth/login");
var url = Uri.parse(
"https://beckend-sistem-pakar-diagnosa-penyakit.onrender.com/api/auth/login",
);
var response = await http.post(
url,
headers: {"Content-Type": "application/json"},
@ -120,7 +123,10 @@ class _LoginPageState extends State<LoginPage> {
child: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => BeforeLogin()),
);
},
),
),