update layout admin for get data
This commit is contained in:
parent
213eb6273c
commit
db83fb1c2f
|
@ -19,8 +19,6 @@ dotenv.config();
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Middlewares
|
// Middlewares
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
|
@ -11,7 +11,7 @@ const swaggerOptions = {
|
||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: 'https://backend-sistem-pakar-diagnosa-penya.vercel.app',
|
url: 'https://localhost:5000', // Development server URL
|
||||||
description: 'Production Server'
|
description: 'Production Server'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -11,9 +11,14 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
List<Map<String, dynamic>> historiData = [];
|
List<Map<String, dynamic>> historiData = [];
|
||||||
List<Map<String, dynamic>> groupedHistoriData = [];
|
List<Map<String, dynamic>> groupedHistoriData = [];
|
||||||
|
List<Map<String, dynamic>> filteredHistoriData = []; // Data yang sudah difilter
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
String? error;
|
String? error;
|
||||||
|
|
||||||
|
// Search variables
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
String searchQuery = '';
|
||||||
|
|
||||||
// Pagination variables
|
// Pagination variables
|
||||||
int _rowsPerPage = 10;
|
int _rowsPerPage = 10;
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
@ -24,6 +29,35 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadHistoriData();
|
_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 {
|
Future<void> _loadHistoriData() async {
|
||||||
|
@ -34,7 +68,9 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dapatkan semua histori terlebih dahulu
|
// 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
|
Set<String> uniqueUserIds = allHistori
|
||||||
.where((histori) => histori['userId'] != null)
|
.where((histori) => histori['userId'] != null)
|
||||||
.map((histori) => histori['userId'].toString())
|
.map((histori) => histori['userId'].toString())
|
||||||
|
@ -60,6 +96,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
setState(() {
|
setState(() {
|
||||||
historiData = detailedHistori; // Simpan data asli jika perlu
|
historiData = detailedHistori; // Simpan data asli jika perlu
|
||||||
groupedHistoriData = groupedData; // Data yang sudah dikelompokkan
|
groupedHistoriData = groupedData; // Data yang sudah dikelompokkan
|
||||||
|
filteredHistoriData = List.from(groupedData); // Initialize filtered data
|
||||||
_updatePagination(0); // Set halaman pertama
|
_updatePagination(0); // Set halaman pertama
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
@ -104,8 +141,8 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
diagnosa = 'Tidak ada diagnosa';
|
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']}';
|
String userName = item['name']?.toString() ?? 'User ID: ${item['userId']}';
|
||||||
|
|
||||||
// Buat composite key: userId + waktu + diagnosa
|
// Buat composite key: userId + waktu + diagnosa
|
||||||
String key = '${item['userId']}_${formattedTime}_$diagnosa';
|
String key = '${item['userId']}_${formattedTime}_$diagnosa';
|
||||||
|
@ -116,7 +153,7 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
|
|
||||||
groupedMap[key] = {
|
groupedMap[key] = {
|
||||||
'userId': item['userId'],
|
'userId': item['userId'],
|
||||||
'userName': userName, // Menampilkan nama user, bukan ID
|
'userName': userName,
|
||||||
'diagnosa': diagnosa,
|
'diagnosa': diagnosa,
|
||||||
'tanggal_diagnosa': item['tanggal_diagnosa'],
|
'tanggal_diagnosa': item['tanggal_diagnosa'],
|
||||||
'tanggal_display': displayDate,
|
'tanggal_display': displayDate,
|
||||||
|
@ -124,7 +161,8 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
'hasil': item['hasil'],
|
'hasil': item['hasil'],
|
||||||
'penyakit_nama': item['penyakit_nama'],
|
'penyakit_nama': item['penyakit_nama'],
|
||||||
'hama_nama': item['hama_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'].contains(item['gejala_nama'])) {
|
||||||
groupedMap[key]!['gejala'].add(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
|
// Konversi map ke list dan urutkan berdasarkan waktu terbaru
|
||||||
|
@ -147,22 +188,32 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
// Update pagination
|
// Update pagination
|
||||||
void _updatePagination(int page) {
|
void _updatePagination(int page) {
|
||||||
_currentPage = page;
|
_currentPage = page;
|
||||||
_totalPages = (groupedHistoriData.length / _rowsPerPage).ceil();
|
_totalPages = (filteredHistoriData.length / _rowsPerPage).ceil();
|
||||||
|
|
||||||
int startIndex = page * _rowsPerPage;
|
int startIndex = page * _rowsPerPage;
|
||||||
int endIndex = (page + 1) * _rowsPerPage;
|
int endIndex = (page + 1) * _rowsPerPage;
|
||||||
|
|
||||||
if (endIndex > groupedHistoriData.length) {
|
if (endIndex > filteredHistoriData.length) {
|
||||||
endIndex = groupedHistoriData.length;
|
endIndex = filteredHistoriData.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startIndex >= groupedHistoriData.length) {
|
if (startIndex >= filteredHistoriData.length) {
|
||||||
_currentPageData = [];
|
_currentPageData = [];
|
||||||
} else {
|
} 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -170,224 +221,477 @@ class _AdminHistoriPageState extends State<AdminHistoriPage> {
|
||||||
title: Text('Riwayat Diagnosa'),
|
title: Text('Riwayat Diagnosa'),
|
||||||
backgroundColor: Color(0xFF9DC08D),
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
),
|
),
|
||||||
body:
|
body: isLoading
|
||||||
isLoading
|
? Center(child: CircularProgressIndicator())
|
||||||
? Center(child: CircularProgressIndicator())
|
: error != null
|
||||||
: error != null
|
? Center(child: Text('Error: $error'))
|
||||||
? Center(child: Text('Error: $error'))
|
: groupedHistoriData.isEmpty
|
||||||
: groupedHistoriData.isEmpty
|
? Center(child: Text('Tidak ada data riwayat diagnosa'))
|
||||||
? Center(child: Text('Tidak ada data riwayat diagnosa'))
|
: Column(
|
||||||
: Column(
|
children: [
|
||||||
children: [
|
// Search Bar
|
||||||
Expanded(
|
Container(
|
||||||
child: SingleChildScrollView(
|
margin: EdgeInsets.all(16),
|
||||||
scrollDirection: Axis.horizontal,
|
child: TextField(
|
||||||
child: SingleChildScrollView(
|
controller: searchController,
|
||||||
child: DataTable(
|
decoration: InputDecoration(
|
||||||
columnSpacing: 20,
|
hintText: 'Cari berdasarkan nama user atau diagnosa...',
|
||||||
headingRowColor: MaterialStateProperty.all(
|
prefixIcon: Icon(
|
||||||
Color(0xFF9DC08D).withOpacity(0.3),
|
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: filteredHistoriData.isEmpty && searchQuery.isNotEmpty
|
||||||
|
? Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.search_off,
|
||||||
|
size: 64,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Tidak ada hasil untuk "${searchController.text}"',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Coba gunakan kata kunci yang berbeda',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[500],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
columns: [
|
)
|
||||||
DataColumn(
|
: ListView.builder(
|
||||||
label: Text(
|
itemCount: _currentPageData.length,
|
||||||
'Nama User',
|
itemBuilder: (context, index) {
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
final histori = _currentPageData[index];
|
||||||
),
|
|
||||||
),
|
return Container(
|
||||||
DataColumn(
|
margin: EdgeInsets.only(bottom: 12, left: 16, right: 16),
|
||||||
label: Text(
|
child: Row(
|
||||||
'Gejala',
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
// Card dengan informasi histori
|
||||||
),
|
Expanded(
|
||||||
),
|
child: Card(
|
||||||
DataColumn(
|
elevation: 2,
|
||||||
label: Text(
|
child: InkWell(
|
||||||
'Diagnosa',
|
onTap: () => _navigateToDetail(histori),
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
child: Container(
|
||||||
),
|
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||||
),
|
child: Column(
|
||||||
DataColumn(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
label: Text(
|
children: [
|
||||||
'Hasil',
|
// Nama User
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
Text(
|
||||||
),
|
histori['userName'] ?? 'User tidak ditemukan',
|
||||||
),
|
style: TextStyle(
|
||||||
DataColumn(
|
fontSize: 16,
|
||||||
label: Text(
|
fontWeight: FontWeight.w600,
|
||||||
'Tanggal',
|
),
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
),
|
// Diagnosa
|
||||||
],
|
Text(
|
||||||
rows:
|
histori['diagnosa'] ?? 'Tidak ada diagnosa',
|
||||||
_currentPageData.map((histori) {
|
style: TextStyle(
|
||||||
// Gabungkan semua gejala menjadi satu string dengan koma
|
fontSize: 14,
|
||||||
String gejalaText = "Tidak ada gejala";
|
fontWeight: FontWeight.w500,
|
||||||
if (histori['gejala'] != null &&
|
),
|
||||||
(histori['gejala'] as List).isNotEmpty) {
|
),
|
||||||
gejalaText = (histori['gejala'] as List).join(
|
SizedBox(height: 4),
|
||||||
', ',
|
// Tanggal
|
||||||
);
|
Text(
|
||||||
}
|
histori['tanggal_display'] ?? '',
|
||||||
|
style: TextStyle(
|
||||||
return DataRow(
|
fontSize: 12,
|
||||||
cells: [
|
color: Colors.grey[600],
|
||||||
DataCell(Text(histori['userName'] ?? 'User tidak ditemukan')),
|
),
|
||||||
DataCell(
|
),
|
||||||
Container(
|
],
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxWidth: 200,
|
|
||||||
),
|
|
||||||
child: Tooltip(
|
|
||||||
message: gejalaText,
|
|
||||||
child: Text(
|
|
||||||
gejalaText,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DataCell(
|
),
|
||||||
Text(
|
SizedBox(width: 8),
|
||||||
histori['diagnosa'] ??
|
// Button detail di luar card
|
||||||
'Tidak ada diagnosa',
|
Container(
|
||||||
style: TextStyle(
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFF9DC08D),
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
DataCell(
|
child: IconButton(
|
||||||
Text(_formatHasil(histori['hasil'])),
|
icon: Icon(Icons.info_outline, color: Colors.white),
|
||||||
|
onPressed: () => _navigateToDetail(histori),
|
||||||
|
tooltip: 'Lihat Detail',
|
||||||
),
|
),
|
||||||
DataCell(
|
),
|
||||||
Text(histori['tanggal_display'] ?? ''),
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), // Pagination controls
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.first_page, size: 18),
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 32,
|
|
||||||
minHeight: 32,
|
|
||||||
),
|
|
||||||
onPressed:
|
|
||||||
_currentPage > 0
|
|
||||||
? () {
|
|
||||||
setState(() {
|
|
||||||
_updatePagination(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.chevron_left, size: 18),
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 32,
|
|
||||||
minHeight: 32,
|
|
||||||
),
|
|
||||||
onPressed:
|
|
||||||
_currentPage > 0
|
|
||||||
? () {
|
|
||||||
setState(() {
|
|
||||||
_updatePagination(_currentPage - 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
'${_currentPage + 1} / $_totalPages',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.chevron_right, size: 18),
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 32,
|
|
||||||
minHeight: 32,
|
|
||||||
),
|
|
||||||
onPressed:
|
|
||||||
_currentPage < _totalPages - 1
|
|
||||||
? () {
|
|
||||||
setState(() {
|
|
||||||
_updatePagination(_currentPage + 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.last_page, size: 18),
|
|
||||||
padding: EdgeInsets.all(4),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 32,
|
|
||||||
minHeight: 32,
|
|
||||||
),
|
|
||||||
onPressed:
|
|
||||||
_currentPage < _totalPages - 1
|
|
||||||
? () {
|
|
||||||
setState(() {
|
|
||||||
_updatePagination(_totalPages - 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
), // Rows per page selector
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Rows per page: ',
|
|
||||||
style: TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
DropdownButton<int>(
|
|
||||||
value: _rowsPerPage,
|
|
||||||
isDense: true,
|
|
||||||
menuMaxHeight: 200,
|
|
||||||
items:
|
|
||||||
[10, 20, 50, 100].map((value) {
|
|
||||||
return DropdownMenuItem<int>(
|
|
||||||
value: value,
|
|
||||||
child: Text('$value'),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_rowsPerPage = value!;
|
|
||||||
_updatePagination(
|
|
||||||
0,
|
|
||||||
); // Kembali ke halaman pertama
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
|
// Pagination controls
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.first_page, size: 18),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 32,
|
||||||
|
minHeight: 32,
|
||||||
|
),
|
||||||
|
onPressed: _currentPage > 0
|
||||||
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_updatePagination(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_left, size: 18),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 32,
|
||||||
|
minHeight: 32,
|
||||||
|
),
|
||||||
|
onPressed: _currentPage > 0
|
||||||
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_updatePagination(_currentPage - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'${_currentPage + 1} / $_totalPages',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_right, size: 18),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 32,
|
||||||
|
minHeight: 32,
|
||||||
|
),
|
||||||
|
onPressed: _currentPage < _totalPages - 1
|
||||||
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_updatePagination(_currentPage + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.last_page, size: 18),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 32,
|
||||||
|
minHeight: 32,
|
||||||
|
),
|
||||||
|
onPressed: _currentPage < _totalPages - 1
|
||||||
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_updatePagination(_totalPages - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
|
||||||
|
// Rows per page selector
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Rows per page: ',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
DropdownButton<int>(
|
||||||
|
value: _rowsPerPage,
|
||||||
|
isDense: true,
|
||||||
|
menuMaxHeight: 200,
|
||||||
|
items: [10, 20, 50, 100].map((value) {
|
||||||
|
return DropdownMenuItem<int>(
|
||||||
|
value: value,
|
||||||
|
child: Text('$value'),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_rowsPerPage = value!;
|
||||||
|
_updatePagination(0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,25 @@ class _AdminPageState extends State<AdminPage> {
|
||||||
await prefs.setInt(LAST_KNOWN_COUNT_KEY, _lastKnownDiagnosisCount);
|
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
|
// Method untuk memuat data dashboard dari API
|
||||||
Future<void> _loadDashboardData() async {
|
Future<void> _loadDashboardData() async {
|
||||||
try {
|
try {
|
||||||
|
@ -78,21 +97,33 @@ class _AdminPageState extends State<AdminPage> {
|
||||||
pestCount = hamaList.length;
|
pestCount = hamaList.length;
|
||||||
print("Jumlah hama: $pestCount");
|
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();
|
final allHistori = await ApiService().getAllHistori();
|
||||||
int currentCount = allHistori.length;
|
print("Total histori records: ${allHistori.length}");
|
||||||
|
|
||||||
|
// Hitung jumlah diagnosa berdasarkan tanggal unik
|
||||||
|
int currentUniqueCount = _countUniqueByDate(allHistori);
|
||||||
|
print("Unique diagnosis dates: $currentUniqueCount");
|
||||||
|
|
||||||
if (currentCount > _lastKnownDiagnosisCount) {
|
// Update diagnosis count berdasarkan tanggal unik
|
||||||
int newDiagnoses = currentCount - _lastKnownDiagnosisCount;
|
if (currentUniqueCount > _lastKnownDiagnosisCount) {
|
||||||
|
int newDiagnoses = currentUniqueCount - _lastKnownDiagnosisCount;
|
||||||
diagnosisCount += newDiagnoses;
|
diagnosisCount += newDiagnoses;
|
||||||
_lastKnownDiagnosisCount = currentCount;
|
_lastKnownDiagnosisCount = currentUniqueCount;
|
||||||
|
|
||||||
// Save the updated counts
|
// Save the updated counts
|
||||||
await _saveCounts();
|
await _saveCounts();
|
||||||
|
|
||||||
print("New diagnoses added: $newDiagnoses");
|
print("New unique diagnosis dates added: $newDiagnoses");
|
||||||
print("Total diagnosis count: $diagnosisCount");
|
print("Total diagnosis count: $diagnosisCount");
|
||||||
|
} else {
|
||||||
|
// Jika tidak ada penambahan, set ke current unique count
|
||||||
|
diagnosisCount = currentUniqueCount;
|
||||||
|
_lastKnownDiagnosisCount = currentUniqueCount;
|
||||||
|
await _saveCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error loading dashboard data: $e");
|
print("Error loading dashboard data: $e");
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -282,4 +313,4 @@ class _AdminPageState extends State<AdminPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -288,24 +288,24 @@ class _EditHamaPageState extends State<EditHamaPage> {
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
TextField(
|
// TextField(
|
||||||
controller: _nilaiPakarController,
|
// controller: _nilaiPakarController,
|
||||||
decoration: InputDecoration(
|
// decoration: InputDecoration(
|
||||||
labelText: 'Nilai Pakar',
|
// labelText: 'Nilai Pakar',
|
||||||
hintText: 'Contoh: 0.5',
|
// hintText: 'Contoh: 0.5',
|
||||||
),
|
// ),
|
||||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
// keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
// Validate as user types (optional)
|
// // Validate as user types (optional)
|
||||||
try {
|
// try {
|
||||||
if (value.isNotEmpty) {
|
// if (value.isNotEmpty) {
|
||||||
double.parse(value.replaceAll(',', '.'));
|
// double.parse(value.replaceAll(',', '.'));
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
// Could show validation error here
|
// // Could show validation error here
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
'Foto Hama',
|
'Foto Hama',
|
||||||
|
|
|
@ -287,24 +287,24 @@ class _EditPenyakitPageState extends State<EditPenyakitPage> {
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
TextField(
|
// TextField(
|
||||||
controller: _nilaiPakarController,
|
// controller: _nilaiPakarController,
|
||||||
decoration: InputDecoration(
|
// decoration: InputDecoration(
|
||||||
labelText: 'Nilai Pakar',
|
// labelText: 'Nilai Pakar',
|
||||||
hintText: 'Contoh: 0.5',
|
// hintText: 'Contoh: 0.5',
|
||||||
),
|
// ),
|
||||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
// keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
// Validate as user types (optional)
|
// // Validate as user types (optional)
|
||||||
try {
|
// try {
|
||||||
if (value.isNotEmpty) {
|
// if (value.isNotEmpty) {
|
||||||
double.parse(value.replaceAll(',', '.'));
|
// double.parse(value.replaceAll(',', '.'));
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
// Could show validation error here
|
// // Could show validation error here
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
'Foto Penyakit',
|
'Foto Penyakit',
|
||||||
|
|
|
@ -9,13 +9,22 @@ class GejalaPage extends StatefulWidget {
|
||||||
class _GejalaPageState extends State<GejalaPage> {
|
class _GejalaPageState extends State<GejalaPage> {
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
List<Map<String, dynamic>> gejalaList = [];
|
List<Map<String, dynamic>> gejalaList = [];
|
||||||
|
List<Map<String, dynamic>> filteredGejalaList = [];
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
bool isSearchVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fetchGejala();
|
fetchGejala();
|
||||||
|
searchController.addListener(_filterGejala);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 🔹 Ambil data gejala dari API
|
// 🔹 Ambil data gejala dari API
|
||||||
Future<void> fetchGejala() async {
|
Future<void> fetchGejala() async {
|
||||||
|
@ -23,12 +32,35 @@ class _GejalaPageState extends State<GejalaPage> {
|
||||||
final data = await apiService.getGejala();
|
final data = await apiService.getGejala();
|
||||||
setState(() {
|
setState(() {
|
||||||
gejalaList = data;
|
gejalaList = data;
|
||||||
|
filteredGejalaList = data;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching gejala: $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
|
// 🔹 Tambah gejala baru ke API
|
||||||
void _tambahGejala() {
|
void _tambahGejala() {
|
||||||
TextEditingController namaController = TextEditingController();
|
TextEditingController namaController = TextEditingController();
|
||||||
|
@ -70,51 +102,49 @@ class _GejalaPageState extends State<GejalaPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showEditDialog(BuildContext context, Map<String, dynamic> gejala) {
|
void showEditDialog(BuildContext context, Map<String, dynamic> gejala) {
|
||||||
final TextEditingController editNamaController = TextEditingController(text: gejala['nama'] ?? '');
|
final TextEditingController editNamaController = TextEditingController(text: gejala['nama'] ?? '');
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(
|
title: Text('Edit Gejala'),
|
||||||
'Edit Hama',
|
content: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
content: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
TextField(
|
||||||
children: [
|
controller: editNamaController,
|
||||||
TextField(
|
decoration: InputDecoration(
|
||||||
controller: editNamaController,
|
labelText: 'Nama',
|
||||||
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)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
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
|
// 🔹 Hapus gejala dari API
|
||||||
void _hapusGejala(int id) async {
|
void _hapusGejala(int id) async {
|
||||||
|
@ -127,152 +157,194 @@ class _GejalaPageState extends State<GejalaPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _konfirmasiHapus(int id) {
|
void _konfirmasiHapus(int id) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Konfirmasi Hapus'),
|
title: Text('Konfirmasi Hapus'),
|
||||||
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
|
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context); // Tutup pop-up tanpa menghapus
|
Navigator.pop(context); // Tutup pop-up tanpa menghapus
|
||||||
},
|
},
|
||||||
child: Text('Tidak'),
|
child: Text('Tidak'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context); // Tutup pop-up
|
Navigator.pop(context); // Tutup pop-up
|
||||||
_hapusGejala(id); // Lanjutkan proses hapus
|
_hapusGejala(id); // Lanjutkan proses hapus
|
||||||
},
|
},
|
||||||
child: Text('Ya, Hapus'),
|
child: Text('Ya, Hapus'),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//pagination
|
||||||
|
|
||||||
//pagination
|
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int rowsPerPage = 10;
|
int rowsPerPage = 10;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int start = currentPage * rowsPerPage;
|
int start = currentPage * rowsPerPage;
|
||||||
int end = (start + rowsPerPage < gejalaList.length)
|
int end = (start + rowsPerPage < filteredGejalaList.length)
|
||||||
? start + rowsPerPage
|
? start + rowsPerPage
|
||||||
: gejalaList.length;
|
: filteredGejalaList.length;
|
||||||
List currentPageData = gejalaList.sublist(start, end);
|
List currentPageData = filteredGejalaList.sublist(start, end);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Halaman Gejala'),
|
title: Text('Halaman Gejala'),
|
||||||
),
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
body: Column(
|
),
|
||||||
children: [
|
body: Column(
|
||||||
SizedBox(height: 20),
|
children: [
|
||||||
Row(
|
SizedBox(height: 16),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
// Search Button
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
padding: const EdgeInsets.only(right: 20.0),
|
child: Row(
|
||||||
child: ElevatedButton(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
onPressed: _tambahGejala,
|
children: [
|
||||||
child: Text(
|
ElevatedButton.icon(
|
||||||
'Tambah Gejala',
|
onPressed: _toggleSearch,
|
||||||
style: TextStyle(color: Colors.green[200]),
|
icon: Icon(Icons.search),
|
||||||
|
label: Text('Cari'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 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(
|
||||||
SizedBox(height: 20),
|
padding: const EdgeInsets.all(16.0),
|
||||||
Expanded(
|
child: Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(8.0),
|
// Data List
|
||||||
child: SingleChildScrollView(
|
Expanded(
|
||||||
scrollDirection: Axis.vertical,
|
child: ListView.builder(
|
||||||
child: SingleChildScrollView(
|
itemCount: currentPageData.length,
|
||||||
scrollDirection: Axis.horizontal,
|
itemBuilder: (context, index) {
|
||||||
child: DataTable(
|
final gejala = currentPageData[index];
|
||||||
columnSpacing: 20,
|
|
||||||
headingRowColor: MaterialStateColor.resolveWith(
|
return Container(
|
||||||
(states) => const Color(0xFF9DC08D),
|
margin: EdgeInsets.only(bottom: 12),
|
||||||
),
|
child: Row(
|
||||||
columns: [
|
children: [
|
||||||
DataColumn(label: SizedBox(width: 35, child: Text('No'))),
|
// Card dengan nama gejala
|
||||||
DataColumn(label: SizedBox(width: 80, child: Text('Kode'))),
|
Expanded(
|
||||||
DataColumn(label: SizedBox(width: 150, child: Text('Nama'))),
|
child: Card(
|
||||||
DataColumn(label: SizedBox(width: 80, child: Text('Aksi'))),
|
elevation: 2,
|
||||||
],
|
child: InkWell(
|
||||||
rows: [
|
onTap: () => showEditDialog(context, gejala),
|
||||||
...currentPageData.map(
|
child: Container(
|
||||||
(gejala) => DataRow(
|
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||||
cells: [
|
child: Column(
|
||||||
DataCell(Text((gejalaList.indexOf(gejala) + 1).toString())),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
DataCell(Text(gejala['kode'] ?? '-')),
|
children: [
|
||||||
DataCell(Text(gejala['nama'] ?? '-')),
|
Text(
|
||||||
DataCell(
|
gejala['nama'] ?? '-',
|
||||||
Row(
|
style: TextStyle(
|
||||||
children: [
|
fontSize: 16,
|
||||||
IconButton(
|
fontWeight: FontWeight.w500,
|
||||||
icon: Icon(Icons.edit, color: Color(0xFF9DC08D)),
|
),
|
||||||
onPressed: () => showEditDialog(context, gejala),
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
gejala['kode'] ?? '-',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
icon: Icon(Icons.delete, color: Colors.red),
|
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']),
|
onPressed: () => _konfirmasiHapus(gejala['id']),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
DataRow(
|
),
|
||||||
cells: [
|
SizedBox(height: 16),
|
||||||
DataCell(Container()),
|
// Pagination
|
||||||
DataCell(Container()),
|
if (filteredGejalaList.length > rowsPerPage)
|
||||||
DataCell(
|
Row(
|
||||||
Align(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
alignment: Alignment.centerRight,
|
children: [
|
||||||
child: Row(
|
IconButton(
|
||||||
mainAxisSize: MainAxisSize.min,
|
icon: Icon(Icons.chevron_left),
|
||||||
children: [
|
onPressed: currentPage > 0
|
||||||
IconButton(
|
? () => setState(() => currentPage--)
|
||||||
icon: Icon(Icons.chevron_left),
|
: null,
|
||||||
onPressed: currentPage > 0
|
),
|
||||||
? () => setState(() => currentPage--)
|
Text(
|
||||||
: null,
|
'Halaman ${currentPage + 1}',
|
||||||
),
|
style: TextStyle(fontSize: 16),
|
||||||
Text(' ${currentPage + 1}'),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.chevron_right),
|
icon: Icon(Icons.chevron_right),
|
||||||
onPressed:
|
onPressed: (currentPage + 1) * rowsPerPage < filteredGejalaList.length
|
||||||
(currentPage + 1) * rowsPerPage < gejalaList.length
|
? () => setState(() => currentPage++)
|
||||||
? () => setState(() => currentPage++)
|
: null,
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
DataCell(Container()),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
floatingActionButton: FloatingActionButton(
|
||||||
);
|
onPressed: _tambahGejala,
|
||||||
}
|
child: Icon(Icons.add),
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
}
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,11 +11,21 @@ class HamaPage extends StatefulWidget {
|
||||||
class _HamaPageState extends State<HamaPage> {
|
class _HamaPageState extends State<HamaPage> {
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
List<Map<String, dynamic>> hamaList = [];
|
List<Map<String, dynamic>> hamaList = [];
|
||||||
|
List<Map<String, dynamic>> filteredHamaList = [];
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
bool isSearchVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchHama();
|
_fetchHama();
|
||||||
|
searchController.addListener(_filterHama);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchHama() async {
|
Future<void> _fetchHama() async {
|
||||||
|
@ -23,12 +33,35 @@ class _HamaPageState extends State<HamaPage> {
|
||||||
List<Map<String, dynamic>> data = await apiService.getHama();
|
List<Map<String, dynamic>> data = await apiService.getHama();
|
||||||
setState(() {
|
setState(() {
|
||||||
hamaList = data;
|
hamaList = data;
|
||||||
|
filteredHamaList = data;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error fetching data: $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
|
// 🔹 Hapus gejala dari API
|
||||||
void _hapusHama(int id) async {
|
void _hapusHama(int id) async {
|
||||||
try {
|
try {
|
||||||
|
@ -45,7 +78,7 @@ class _HamaPageState extends State<HamaPage> {
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Konfirmasi Hapus'),
|
title: Text('Konfirmasi Hapus'),
|
||||||
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
|
content: Text('Apakah Anda yakin ingin menghapus hama ini?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
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
|
//pagination
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int rowsPerPage = 10;
|
int rowsPerPage = 10;
|
||||||
|
@ -74,203 +172,169 @@ class _HamaPageState extends State<HamaPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int start = currentPage * rowsPerPage;
|
int start = currentPage * rowsPerPage;
|
||||||
int end =
|
int end = (start + rowsPerPage < filteredHamaList.length)
|
||||||
(start + rowsPerPage < hamaList.length)
|
? start + rowsPerPage
|
||||||
? start + rowsPerPage
|
: filteredHamaList.length;
|
||||||
: hamaList.length;
|
List currentPageData = filteredHamaList.sublist(start, end);
|
||||||
List currentPageData = hamaList.sublist(start, end);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Halaman Hama'), backgroundColor: Color(0xFF9DC08D)),
|
appBar: AppBar(
|
||||||
|
title: Text('Halaman Hama'),
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 16),
|
||||||
Row(
|
// Search Button
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.only(right: 20.0),
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
child: ElevatedButton(
|
children: [
|
||||||
onPressed: () {
|
ElevatedButton.icon(
|
||||||
Navigator.push(
|
onPressed: _toggleSearch,
|
||||||
context,
|
icon: Icon(Icons.search),
|
||||||
MaterialPageRoute(
|
label: Text('Cari'),
|
||||||
builder:
|
style: ElevatedButton.styleFrom(
|
||||||
(context) => TambahHamaPage(
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
onHamaAdded:
|
foregroundColor: Colors.white,
|
||||||
_fetchHama, // Panggil fungsi refresh setelah tambah
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Tambah Hama',
|
|
||||||
style: TextStyle(color: Colors.green[200]),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 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],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
scrollDirection: Axis.vertical,
|
children: [
|
||||||
child: SingleChildScrollView(
|
// Data List
|
||||||
scrollDirection: Axis.horizontal,
|
Expanded(
|
||||||
child: DataTable(
|
child: ListView.builder(
|
||||||
columnSpacing: 20,
|
itemCount: currentPageData.length,
|
||||||
headingRowColor: MaterialStateColor.resolveWith(
|
itemBuilder: (context, index) {
|
||||||
(states) => const Color(0xFF9DC08D),
|
final hama = currentPageData[index];
|
||||||
),
|
|
||||||
columns: [
|
return Container(
|
||||||
DataColumn(label: SizedBox(width: 35, child: Text('No'))),
|
margin: EdgeInsets.only(bottom: 12),
|
||||||
DataColumn(
|
child: Row(
|
||||||
label: SizedBox(width: 50, child: Text('Kode')),
|
children: [
|
||||||
),
|
// Card dengan nama hama
|
||||||
DataColumn(
|
Expanded(
|
||||||
label: SizedBox(width: 100, child: Text('Nama')),
|
child: Card(
|
||||||
),
|
elevation: 2,
|
||||||
DataColumn(
|
child: InkWell(
|
||||||
label: SizedBox(width: 100, child: Text('Deskripsi')),
|
onTap: () => _navigateToEdit(hama),
|
||||||
),
|
child: Container(
|
||||||
DataColumn(
|
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
|
||||||
label: SizedBox(width: 100, child: Text('Penanganan')),
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
DataColumn(
|
children: [
|
||||||
label: SizedBox(width: 50, child: Text('Aksi')),
|
Text(
|
||||||
),
|
hama['nama'] ?? '-',
|
||||||
],
|
style: TextStyle(
|
||||||
rows: [
|
fontSize: 16,
|
||||||
...currentPageData.map(
|
fontWeight: FontWeight.w500,
|
||||||
(hama) => DataRow(
|
),
|
||||||
cells: [
|
),
|
||||||
DataCell(
|
SizedBox(height: 4),
|
||||||
Text((hamaList.indexOf(hama) + 1).toString()),
|
Text(
|
||||||
),
|
hama['kode'] ?? '-',
|
||||||
DataCell(Text(hama['kode'] ?? '-')),
|
style: TextStyle(
|
||||||
DataCell(Text(hama['nama'] ?? '-')),
|
fontSize: 14,
|
||||||
DataCell(Text(hama['deskripsi'] ?? '-')),
|
color: Colors.grey[600],
|
||||||
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
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.delete, color: Colors.red),
|
|
||||||
onPressed:
|
|
||||||
() => _konfirmasiHapus(hama['id']),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: 8),
|
||||||
],
|
// Button hapus di luar card
|
||||||
),
|
Container(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
DataRow(
|
color: Colors.red,
|
||||||
cells: [
|
borderRadius: BorderRadius.circular(8),
|
||||||
DataCell(Container()),
|
),
|
||||||
DataCell(Container()),
|
child: IconButton(
|
||||||
DataCell(
|
icon: Icon(Icons.delete, color: Colors.white),
|
||||||
Align(
|
onPressed: () => _konfirmasiHapus(hama['id']),
|
||||||
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()),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 16),
|
||||||
|
// Pagination
|
||||||
|
if (filteredHamaList.length > rowsPerPage)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_left),
|
||||||
|
onPressed: currentPage > 0
|
||||||
|
? () => setState(() => currentPage--)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Halaman ${currentPage + 1}',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_right),
|
||||||
|
onPressed: (currentPage + 1) * rowsPerPage < filteredHamaList.length
|
||||||
|
? () => setState(() => currentPage++)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TambahHamaPage(
|
||||||
|
onHamaAdded: _fetchHama,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,11 +12,21 @@ class PenyakitPage extends StatefulWidget {
|
||||||
class _PenyakitPageState extends State<PenyakitPage> {
|
class _PenyakitPageState extends State<PenyakitPage> {
|
||||||
final ApiService apiService = ApiService();
|
final ApiService apiService = ApiService();
|
||||||
List<Map<String, dynamic>> penyakitList = [];
|
List<Map<String, dynamic>> penyakitList = [];
|
||||||
|
List<Map<String, dynamic>> filteredPenyakitList = [];
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
bool isSearchVisible = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchPenyakit();
|
_fetchPenyakit();
|
||||||
|
searchController.addListener(_filterPenyakit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPenyakit() async {
|
Future<void> _fetchPenyakit() async {
|
||||||
|
@ -24,19 +34,42 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
||||||
List<Map<String, dynamic>> data = await apiService.getPenyakit();
|
List<Map<String, dynamic>> data = await apiService.getPenyakit();
|
||||||
setState(() {
|
setState(() {
|
||||||
penyakitList = data;
|
penyakitList = data;
|
||||||
|
filteredPenyakitList = data;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error fetching data: $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 {
|
void _hapusPenyakit(int id) async {
|
||||||
try {
|
try {
|
||||||
await apiService.deletePenyakit(id);
|
await apiService.deletePenyakit(id);
|
||||||
_fetchPenyakit(); // Refresh data setelah hapus
|
_fetchPenyakit(); // Refresh data setelah hapus
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error hapus gejala: $e');
|
print('Error hapus penyakit: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +79,7 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Konfirmasi Hapus'),
|
title: Text('Konfirmasi Hapus'),
|
||||||
content: Text('Apakah Anda yakin ingin menghapus gejala ini?'),
|
content: Text('Apakah Anda yakin ingin menghapus penyakit ini?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
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
|
//pagination
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int rowsPerPage = 10;
|
int rowsPerPage = 10;
|
||||||
|
@ -75,203 +149,169 @@ class _PenyakitPageState extends State<PenyakitPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int start = currentPage * rowsPerPage;
|
int start = currentPage * rowsPerPage;
|
||||||
int end =
|
int end = (start + rowsPerPage < filteredPenyakitList.length)
|
||||||
(start + rowsPerPage < penyakitList.length)
|
? start + rowsPerPage
|
||||||
? start + rowsPerPage
|
: filteredPenyakitList.length;
|
||||||
: penyakitList.length;
|
List currentPageData = filteredPenyakitList.sublist(start, end);
|
||||||
List currentPageData = penyakitList.sublist(start, end);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Halaman Penyakit'), backgroundColor: Color(0xFF9DC08D)),
|
appBar: AppBar(
|
||||||
|
title: Text('Halaman Penyakit'),
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 16),
|
||||||
Row(
|
// Search Button
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.only(right: 20.0),
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
child: ElevatedButton(
|
children: [
|
||||||
onPressed: () {
|
ElevatedButton.icon(
|
||||||
Navigator.push(
|
onPressed: _toggleSearch,
|
||||||
context,
|
icon: Icon(Icons.search),
|
||||||
MaterialPageRoute(
|
label: Text('Cari'),
|
||||||
builder:
|
style: ElevatedButton.styleFrom(
|
||||||
(context) => TambahPenyakitPage(
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
onPenyakitAdded:
|
foregroundColor: Colors.white,
|
||||||
_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: 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: () {
|
|
||||||
// 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
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
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()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 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(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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
penyakit['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(penyakit['id']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
// Pagination
|
||||||
|
if (filteredPenyakitList.length > rowsPerPage)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_left),
|
||||||
|
onPressed: currentPage > 0
|
||||||
|
? () => setState(() => currentPage--)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Halaman ${currentPage + 1}',
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_right),
|
||||||
|
onPressed: (currentPage + 1) * rowsPerPage < filteredPenyakitList.length
|
||||||
|
? () => setState(() => currentPage++)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TambahPenyakitPage(
|
||||||
|
onPenyakitAdded: _fetchPenyakit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
backgroundColor: Color(0xFF9DC08D),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,6 @@ import 'package:SIBAYAM/admin/edit_rule_page.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:SIBAYAM/api_services/api_services.dart';
|
import 'package:SIBAYAM/api_services/api_services.dart';
|
||||||
import 'tambah_rule_page.dart';
|
import 'tambah_rule_page.dart';
|
||||||
import 'edit_hama_page.dart';
|
|
||||||
|
|
||||||
class RulePage extends StatefulWidget {
|
class RulePage extends StatefulWidget {
|
||||||
const RulePage({Key? key}) : super(key: key);
|
const RulePage({Key? key}) : super(key: key);
|
||||||
|
@ -18,8 +17,13 @@ class _RulePageState extends State<RulePage> {
|
||||||
List<Map<String, dynamic>> hamaList = [];
|
List<Map<String, dynamic>> hamaList = [];
|
||||||
|
|
||||||
List<dynamic> rules = [];
|
List<dynamic> rules = [];
|
||||||
|
List<dynamic> filteredRules = [];
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
|
||||||
|
// Search and filter variables
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
String selectedFilter = 'Semua'; // 'Semua', 'Penyakit', 'Hama'
|
||||||
|
|
||||||
// Pagination variables
|
// Pagination variables
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
int rowsPerPage = 10;
|
int rowsPerPage = 10;
|
||||||
|
@ -28,9 +32,57 @@ class _RulePageState extends State<RulePage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fetchRules();
|
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 {
|
void fetchRules() async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
final apiService = ApiService();
|
final apiService = ApiService();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -49,12 +101,12 @@ class _RulePageState extends State<RulePage> {
|
||||||
...rulesPenyakit.map((rule) {
|
...rulesPenyakit.map((rule) {
|
||||||
final gejala = gejalaList.firstWhere(
|
final gejala = gejalaList.firstWhere(
|
||||||
(item) => item['id'] == rule['id_gejala'],
|
(item) => item['id'] == rule['id_gejala'],
|
||||||
orElse: () => {'nama': '-'},
|
orElse: () => {'nama': 'Gejala tidak ditemukan'},
|
||||||
);
|
);
|
||||||
|
|
||||||
final penyakit = penyakitList.firstWhere(
|
final penyakit = penyakitList.firstWhere(
|
||||||
(item) => item['id'] == rule['id_penyakit'],
|
(item) => item['id'] == rule['id_penyakit'],
|
||||||
orElse: () => {'nama': '-'},
|
orElse: () => {'nama': 'Penyakit tidak ditemukan'},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -66,20 +118,19 @@ class _RulePageState extends State<RulePage> {
|
||||||
'nama_penyakit': penyakit['nama'],
|
'nama_penyakit': penyakit['nama'],
|
||||||
'nama_hama': null,
|
'nama_hama': null,
|
||||||
'nilai_pakar': rule['nilai_pakar'],
|
'nilai_pakar': rule['nilai_pakar'],
|
||||||
|
'type': 'Penyakit',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
// Mengolah rules hama
|
// Mengolah rules hama
|
||||||
...rulesHama.map((rule) {
|
...rulesHama.map((rule) {
|
||||||
// Mencari gejala berdasarkan id
|
|
||||||
final gejala = gejalaList.firstWhere(
|
final gejala = gejalaList.firstWhere(
|
||||||
(item) => item['id'] == rule['id_gejala'],
|
(item) => item['id'] == rule['id_gejala'],
|
||||||
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
|
orElse: () => {'nama': 'Gejala tidak ditemukan'},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mencari hama berdasarkan id
|
|
||||||
final hama = hamaList.firstWhere(
|
final hama = hamaList.firstWhere(
|
||||||
(item) => item['id'] == rule['id_hama'],
|
(item) => item['id'] == rule['id_hama'],
|
||||||
orElse: () => {'nama': 'TIDAK DITEMUKAN'},
|
orElse: () => {'nama': 'Hama tidak ditemukan'},
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -91,15 +142,23 @@ class _RulePageState extends State<RulePage> {
|
||||||
'nama_penyakit': null,
|
'nama_penyakit': null,
|
||||||
'nama_hama': hama['nama'],
|
'nama_hama': hama['nama'],
|
||||||
'nilai_pakar': rule['nilai_pakar'],
|
'nilai_pakar': rule['nilai_pakar'],
|
||||||
|
'type': 'Hama',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
rules = enrichedRules;
|
rules = enrichedRules;
|
||||||
|
filteredRules = enrichedRules;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Terjadi kesalahan saat memuat data: $e');
|
print('Terjadi kesalahan saat memuat data: $e');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Gagal memuat data: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
@ -108,21 +167,47 @@ class _RulePageState extends State<RulePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteRule(Map<String, dynamic> rule) async {
|
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 {
|
try {
|
||||||
http.Response res;
|
http.Response res;
|
||||||
|
|
||||||
// Tentukan fungsi delete berdasarkan isi rule
|
// Tentukan fungsi delete berdasarkan isi rule
|
||||||
if (rule['id_hama'] != null) {
|
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) {
|
} else if (rule['id_penyakit'] != null) {
|
||||||
res = await ApiService.deleteRulePenyakit(rule['id']); // Fungsi API untuk delete penyakit
|
res = await ApiService.deleteRulePenyakit(rule['id']);
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Data rule tidak valid (tidak ada id_hama atau id_penyakit)");
|
throw Exception("Data rule tidak valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("Rule berhasil dihapus"))
|
SnackBar(
|
||||||
|
content: Text("Rule berhasil dihapus"),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
fetchRules(); // Refresh data setelah delete
|
fetchRules(); // Refresh data setelah delete
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,7 +215,10 @@ class _RulePageState extends State<RulePage> {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("Terjadi kesalahan saat menghapus: $e")),
|
SnackBar(
|
||||||
|
content: Text("Terjadi kesalahan saat menghapus: $e"),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,224 +226,420 @@ class _RulePageState extends State<RulePage> {
|
||||||
// Get paginated data
|
// Get paginated data
|
||||||
List<dynamic> get paginatedRules {
|
List<dynamic> get paginatedRules {
|
||||||
final startIndex = currentPage * rowsPerPage;
|
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 [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules.sublist(startIndex, endIndex);
|
return filteredRules.sublist(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildSearchAndFilter() {
|
||||||
Widget build(BuildContext context) {
|
return Card(
|
||||||
return Scaffold(
|
elevation: 2,
|
||||||
appBar: AppBar(title: const Text('Data Rules'), backgroundColor: Color(0xFF9DC08D)),
|
child: Padding(
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
children: [
|
||||||
// Button untuk tambah rule hama
|
Expanded(
|
||||||
ElevatedButton.icon(
|
flex: 2,
|
||||||
onPressed: () {
|
child: TextField(
|
||||||
Navigator.push(
|
controller: searchController,
|
||||||
context,
|
decoration: InputDecoration(
|
||||||
MaterialPageRoute(
|
hintText: 'Cari berdasarkan nama penyakit, hama, atau gejala...',
|
||||||
builder: (context) => TambahRulePage(
|
prefixIcon: Icon(Icons.search),
|
||||||
isEditing: false,
|
border: OutlineInputBorder(
|
||||||
isEditingHama: true, // Menandakan ini adalah rule hama
|
borderRadius: BorderRadius.circular(8),
|
||||||
selectedRuleIds: [],
|
|
||||||
selectedGejalaIds: [],
|
|
||||||
nilaiPakarList: [],
|
|
||||||
selectedHamaId: null,
|
|
||||||
selectedPenyakitId: null,
|
|
||||||
showHamaOnly: true, // Parameter baru untuk menampilkan hanya dropdown hama
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).then((_) => fetchRules());
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
},
|
),
|
||||||
icon: Icon(Icons.bug_report, size: 16,),
|
|
||||||
label: Text(
|
|
||||||
"Tambah 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
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 16),
|
||||||
// Button untuk tambah rule penyakit
|
Expanded(
|
||||||
ElevatedButton.icon(
|
child: DropdownButtonFormField<String>(
|
||||||
onPressed: () {
|
value: selectedFilter,
|
||||||
Navigator.push(
|
decoration: InputDecoration(
|
||||||
context,
|
labelText: 'Filter Kategori',
|
||||||
MaterialPageRoute(
|
border: OutlineInputBorder(
|
||||||
builder: (context) => TambahRulePage(
|
borderRadius: BorderRadius.circular(8),
|
||||||
isEditing: false,
|
|
||||||
isEditingHama: false, // Menandakan ini adalah rule penyakit
|
|
||||||
selectedRuleIds: [],
|
|
||||||
selectedGejalaIds: [],
|
|
||||||
nilaiPakarList: [],
|
|
||||||
selectedHamaId: null,
|
|
||||||
selectedPenyakitId: null,
|
|
||||||
showPenyakitOnly: true, // Parameter baru untuk menampilkan hanya dropdown penyakit
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
).then((_) => fetchRules());
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
},
|
),
|
||||||
icon: Icon(Icons.healing, size: 16,),
|
items: ['Semua', 'Penyakit', 'Hama'].map((String value) {
|
||||||
label: Text(
|
return DropdownMenuItem<String>(
|
||||||
"Tambah Rule Penyakit",
|
value: value,
|
||||||
style: TextStyle(fontSize: 12),),
|
child: Text(value),
|
||||||
style: ElevatedButton.styleFrom(
|
);
|
||||||
backgroundColor: Colors.blue,
|
}).toList(),
|
||||||
foregroundColor: Colors.white,
|
onChanged: (String? newValue) {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6), // Padding lebih kecil
|
setState(() {
|
||||||
minimumSize: Size(0, 32), // Tinggi minimum lebih kecil
|
selectedFilter = newValue!;
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap, // Mengurangi area tap
|
_filterRules();
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
isLoading
|
Row(
|
||||||
? const Center(child: CircularProgressIndicator())
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
: Expanded(
|
children: [
|
||||||
child: Column(
|
Text(
|
||||||
children: [
|
'Total: ${filteredRules.length} rule(s)',
|
||||||
Expanded(
|
style: TextStyle(
|
||||||
child: SingleChildScrollView(
|
fontWeight: FontWeight.w500,
|
||||||
scrollDirection: Axis.horizontal,
|
color: Colors.grey[600],
|
||||||
child: SingleChildScrollView(
|
),
|
||||||
scrollDirection: Axis.vertical,
|
),
|
||||||
child: DataTable(
|
Row(
|
||||||
headingRowColor: MaterialStateProperty.resolveWith<Color>(
|
children: [
|
||||||
(Set<MaterialState> states) {
|
ElevatedButton.icon(
|
||||||
return Color(0xFF9DC08D); // Apply color to all header rows
|
onPressed: () {
|
||||||
},
|
Navigator.push(
|
||||||
),
|
context,
|
||||||
columns: const [
|
MaterialPageRoute(
|
||||||
DataColumn(label: Text('No')),
|
builder: (context) => TambahRulePage(
|
||||||
DataColumn(label: Text('Hama & Penyakit')),
|
isEditing: false,
|
||||||
DataColumn(label: Text('Gejala')),
|
isEditingHama: true,
|
||||||
DataColumn(label: Text('Nilai Pakar')),
|
selectedRuleIds: [],
|
||||||
DataColumn(label: Text('Aksi')),
|
selectedGejalaIds: [],
|
||||||
],
|
nilaiPakarList: [],
|
||||||
rows: List.generate(paginatedRules.length, (index) {
|
selectedHamaId: null,
|
||||||
final rule = paginatedRules[index];
|
selectedPenyakitId: null,
|
||||||
final displayIndex = currentPage * rowsPerPage + index + 1;
|
showHamaOnly: true,
|
||||||
|
|
||||||
final namaKategori = rule['id_penyakit'] != null
|
|
||||||
? rule['nama_penyakit'] ?? '-'
|
|
||||||
: rule['nama_hama'] ?? '-';
|
|
||||||
|
|
||||||
final isPenyakit = rule['id_penyakit'] != null;
|
|
||||||
|
|
||||||
return DataRow(
|
|
||||||
cells: [
|
|
||||||
DataCell(Text(displayIndex.toString())),
|
|
||||||
DataCell(Text(namaKategori)),
|
|
||||||
DataCell(Text(rule['nama_gejala'] ?? '-')),
|
|
||||||
DataCell(Text(rule['nilai_pakar']?.toString() ?? '-')),
|
|
||||||
DataCell(
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
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(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => EditRulePage(
|
|
||||||
isEditing: true,
|
|
||||||
isEditingHama: editingHama,
|
|
||||||
selectedRuleIds: [rule['id'] as int],
|
|
||||||
selectedGejalaIds: [rule['id_gejala'] as int],
|
|
||||||
nilaiPakarList: [(rule['nilai_pakar'] as num).toDouble()],
|
|
||||||
selectedHamaId: rule['id_hama'] as int?,
|
|
||||||
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"),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Debug info
|
|
||||||
print("Rule data: $rule");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
deleteRule(rule);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).then((_) => fetchRules());
|
||||||
),
|
},
|
||||||
// Pagination controls
|
icon: Icon(Icons.bug_report, size: 16),
|
||||||
Container(
|
label: Text("Rule Hama", style: TextStyle(fontSize: 12)),
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
style: ElevatedButton.styleFrom(
|
||||||
child: Row(
|
backgroundColor: Colors.green,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
foregroundColor: Colors.white,
|
||||||
children: [
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
IconButton(
|
minimumSize: Size(0, 36),
|
||||||
icon: Icon(Icons.chevron_left),
|
),
|
||||||
onPressed: currentPage > 0
|
|
||||||
? () => setState(() => currentPage--)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
Text('Halaman ${currentPage + 1} dari ${(rules.length / rowsPerPage).ceil()}'),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.chevron_right),
|
|
||||||
onPressed: (currentPage + 1) * rowsPerPage < rules.length
|
|
||||||
? () => setState(() => currentPage++)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: 8),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => TambahRulePage(
|
||||||
|
isEditing: false,
|
||||||
|
isEditingHama: false,
|
||||||
|
selectedRuleIds: [],
|
||||||
|
selectedGejalaIds: [],
|
||||||
|
nilaiPakarList: [],
|
||||||
|
selectedHamaId: null,
|
||||||
|
selectedPenyakitId: null,
|
||||||
|
showPenyakitOnly: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((_) => fetchRules());
|
||||||
|
},
|
||||||
|
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: 12, vertical: 8),
|
||||||
|
minimumSize: Size(0, 36),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDataList() {
|
||||||
|
if (paginatedRules.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.all(32),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: paginatedRules.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final rule = paginatedRules[index];
|
||||||
|
|
||||||
|
final namaKategori = rule['id_penyakit'] != null
|
||||||
|
? rule['nama_penyakit'] ?? '-'
|
||||||
|
: rule['nama_hama'] ?? '-';
|
||||||
|
|
||||||
|
final kategori = rule['type'] ?? 'Unknown';
|
||||||
|
final isHama = rule['id_hama'] != null;
|
||||||
|
|
||||||
|
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: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isHama ? Colors.green[100] : Colors.blue[100],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
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) {
|
||||||
|
final bool editingHama = rule['id_hama'] != null;
|
||||||
|
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EditRulePage(
|
||||||
|
isEditing: true,
|
||||||
|
isEditingHama: editingHama,
|
||||||
|
selectedRuleIds: [rule['id'] as int],
|
||||||
|
selectedGejalaIds: [rule['id_gejala'] as int],
|
||||||
|
nilaiPakarList: [(rule['nilai_pakar'] as num).toDouble()],
|
||||||
|
selectedHamaId: rule['id_hama'] as int?,
|
||||||
|
selectedPenyakitId: rule['id_penyakit'] as int?,
|
||||||
|
showHamaOnly: editingHama,
|
||||||
|
showPenyakitOnly: !editingHama,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((_) => fetchRules());
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text("Data rule tidak lengkap"),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
child: Text('${currentPage + 1} / $totalPages'),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.chevron_right),
|
||||||
|
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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -38,36 +38,43 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _simpanHama() async {
|
Future<void> _simpanHama() async {
|
||||||
if (namaController.text.isNotEmpty &&
|
if (namaController.text.isNotEmpty &&
|
||||||
deskripsiController.text.isNotEmpty &&
|
deskripsiController.text.isNotEmpty &&
|
||||||
penangananController.text.isNotEmpty) {
|
penangananController.text.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
double? nilaipakar;
|
// Kirim nilai pakar sebagai double, 0.0 jika tidak diisi
|
||||||
if (nilaiPakarController.text.isNotEmpty) {
|
double nilaiPakar = 0.0;
|
||||||
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
if (nilaiPakarController.text.trim().isNotEmpty) {
|
||||||
nilaipakar = double.parse(nilaiInput);
|
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
widget.onHamaAdded();
|
||||||
|
Navigator.pop(context);
|
||||||
|
_showDialog('Berhasil', 'Data hama berhasil ditambahkan.');
|
||||||
|
} catch (e) {
|
||||||
|
_showDialog('Gagal', 'Gagal menambahkan data hama: $e');
|
||||||
|
print("Error adding hama: $e");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
await apiService.createHama(
|
_showDialog('Error', 'Nama, deskripsi, dan penanganan hama harus diisi.');
|
||||||
namaController.text,
|
|
||||||
deskripsiController.text,
|
|
||||||
penangananController.text,
|
|
||||||
_pickedFile,
|
|
||||||
nilaipakar, // boleh null
|
|
||||||
);
|
|
||||||
|
|
||||||
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 (kecuali nilai pakar).');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void _showDialog(String title, String message) {
|
void _showDialog(String title, String message) {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -149,8 +156,11 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
// TextField(
|
// TextField(
|
||||||
// controller: nilaiPakarController,
|
// controller: nilaiPakarController,
|
||||||
// decoration: InputDecoration(labelText: 'Nilai Pakar'),
|
// decoration: InputDecoration(
|
||||||
// maxLines: 3,
|
// labelText: 'Nilai Pakar (Optional)',
|
||||||
|
// hintText: 'Masukkan nilai pakar (opsional)',
|
||||||
|
// ),
|
||||||
|
// keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
// ),
|
// ),
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
Text('Foto'),
|
Text('Foto'),
|
||||||
|
@ -197,4 +207,4 @@ class _TambahHamaPageState extends State<TambahHamaPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,27 +39,39 @@ class _TambahPenyakitPageState extends State<TambahPenyakitPage> {
|
||||||
Future<void> _simpanPenyakit() async {
|
Future<void> _simpanPenyakit() async {
|
||||||
if (namaController.text.isNotEmpty &&
|
if (namaController.text.isNotEmpty &&
|
||||||
deskripsiController.text.isNotEmpty &&
|
deskripsiController.text.isNotEmpty &&
|
||||||
penangananController.text.isNotEmpty &&
|
penangananController.text.isNotEmpty) {
|
||||||
nilaiPakarController.text.isNotEmpty) {
|
|
||||||
try {
|
try {
|
||||||
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
// Kirim nilai pakar sebagai double, 0.0 jika tidak diisi
|
||||||
double nilaiPakar = double.parse(nilaiInput);
|
double nilaiPakar = 0.0;
|
||||||
|
if (nilaiPakarController.text.trim().isNotEmpty) {
|
||||||
|
String nilaiInput = nilaiPakarController.text.replaceAll(',', '.');
|
||||||
|
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(
|
await apiService.createPenyakit(
|
||||||
namaController.text,
|
namaController.text,
|
||||||
deskripsiController.text,
|
deskripsiController.text,
|
||||||
penangananController.text,
|
penangananController.text,
|
||||||
_pickedFile,
|
_pickedFile,
|
||||||
nilaiPakar,
|
nilaiPakar,
|
||||||
);
|
);
|
||||||
widget.onPenyakitAdded();
|
widget.onPenyakitAdded();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_showDialog('Berhasil', 'Data penyakit berhasil ditambahkan.');
|
_showDialog('Berhasil', 'Data hama berhasil ditambahkan.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showDialog('Gagal', 'Gagal menambahkan data penyakit.');
|
_showDialog('Gagal', 'Gagal menambahkan data hama: $e');
|
||||||
print("Error adding penyakit: $e");
|
print("Error adding hama: $e");
|
||||||
}
|
}
|
||||||
} else {
|
} 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,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
TextField(
|
// TextField(
|
||||||
controller: nilaiPakarController,
|
// controller: nilaiPakarController,
|
||||||
decoration: InputDecoration(labelText: 'nilai pakar'),
|
// decoration: InputDecoration(labelText: 'nilai pakar'),
|
||||||
maxLines: 3,
|
// maxLines: 3,
|
||||||
),
|
// ),
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
(_webImage != null)
|
(_webImage != null)
|
||||||
? Image.memory(
|
? Image.memory(
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -899,7 +899,7 @@ Future<List<Map<String, dynamic>>> getAllHistori() async {
|
||||||
String deskripsi,
|
String deskripsi,
|
||||||
String penanganan,
|
String penanganan,
|
||||||
XFile? pickedFile,
|
XFile? pickedFile,
|
||||||
double nilai_pakar
|
double? nilai_pakar
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var uri = Uri.parse(penyakitUrl);
|
var uri = Uri.parse(penyakitUrl);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:SIBAYAM/user/before_login.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -62,7 +63,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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(
|
var response = await http.post(
|
||||||
url,
|
url,
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
|
@ -120,7 +123,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(Icons.arrow_back, color: Colors.white),
|
icon: Icon(Icons.arrow_back, color: Colors.white),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => BeforeLogin()),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue