import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../layout/main_layout.dart'; import '../pages/petugas_drawer.dart'; import '../pages/login_page.dart'; import '../pages/tambah_desa.dart'; import '../pages/edit_desa.dart'; class DataDesaPage extends StatefulWidget { const DataDesaPage({super.key}); @override State createState() => _DataDesaPageState(); } class _DataDesaPageState extends State { final TextEditingController _searchController = TextEditingController(); Timer? _debounce; int _rowsPerPage = 10; int _currentPage = 0; int _totalData = 0; List> _allData = []; bool _isLoading = false; bool _isAuthorized = false; final String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api/desa/get_desa.php"; final String deleteUrl = "http://ta.myhost.id/E31230549/mposyandu_api/desa/delete_desa.php"; @override void initState() { super.initState(); checkLogin(); } @override void dispose() { _debounce?.cancel(); _searchController.dispose(); super.dispose(); } Future checkLogin() async { final prefs = await SharedPreferences.getInstance(); final isLogin = prefs.getBool("isLogin") ?? false; if (!isLogin) { if (!mounted) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)), (route) => false, ); return; } setState(() => _isAuthorized = true); await fetchDesa(); } Future fetchDesa() async { if (!_isAuthorized || !mounted) return; setState(() => _isLoading = true); try { final url = Uri.parse(baseUrl).replace(queryParameters: { "page": (_currentPage + 1).toString(), "limit": _rowsPerPage.toString(), "search": _searchController.text.trim(), }); final response = await http.get(url); if (response.statusCode == 200) { final decoded = json.decode(response.body); if (decoded["success"] == true) { setState(() { _allData = List>.from(decoded["data"] ?? []); _totalData = decoded["total"] ?? 0; }); } } } catch (e) { debugPrint("ERROR FETCH DESA: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } Future _deleteDesa(String id) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: Text("Konfirmasi", style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.bold)), content: Text("Yakin ingin menghapus desa ini?", style: GoogleFonts.poppins(fontSize: 13)), actions: [ TextButton(onPressed: () => Navigator.pop(context, false), child: const Text("Batal")), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text("Hapus", style: TextStyle(color: Colors.white)), ), ], ), ); if (confirm != true) return; try { final res = await http.post(Uri.parse(deleteUrl), body: {"id": id}); if (json.decode(res.body)["success"] == true) { fetchDesa(); } } catch (e) { debugPrint("Delete error: $e"); } } @override Widget build(BuildContext context) { int totalPages = (_totalData / _rowsPerPage).ceil(); if (totalPages == 0) totalPages = 1; return MainLayout( title: "", drawer: const DrawerPetugas(), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ // Judul Halaman Text("Data Desa", style: GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), // Search + Tambah Row( children: [ Expanded( child: TextField( controller: _searchController, style: GoogleFonts.poppins(fontSize: 13), decoration: InputDecoration( hintText: "Cari nama desa...", prefixIcon: const Icon(Icons.search, size: 20), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), contentPadding: const EdgeInsets.symmetric(horizontal: 10), ), onChanged: (v) { _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 500), () { _currentPage = 0; fetchDesa(); }); }, ), ), const SizedBox(width: 8), ElevatedButton( onPressed: () async { final res = await Navigator.push(context, MaterialPageRoute(builder: (_) => const TambahDesaPage())); if (res == true) fetchDesa(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, padding: const EdgeInsets.all(12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), child: const Icon(Icons.add, color: Colors.white), ) ], ), const SizedBox(height: 16), // Card List Area Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _allData.isEmpty ? const Center(child: Text("Data tidak ditemukan")) : ListView.builder( itemCount: _allData.length, itemBuilder: (context, index) { final item = _allData[index]; return Card( elevation: 0, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: Colors.grey.shade300, width: 1), ), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // Konten (Nama Desa) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Nama Desa", style: GoogleFonts.poppins(fontSize: 10, color: Colors.grey)), Text(item["nama_desa"] ?? "", style: GoogleFonts.poppins(fontWeight: FontWeight.bold, fontSize: 15)), ], ), ), // Tombol Aksi (Edit & Hapus) _actionButton(Icons.edit, Colors.orange, () async { final res = await Navigator.push(context, MaterialPageRoute(builder: (_) => EditDesaPage(data: item))); if (res == true) fetchDesa(); }), const SizedBox(width: 8), _actionButton(Icons.delete, Colors.red, () => _deleteDesa(item["id"].toString())), ], ), ), ); }, ), ), // Pagination Area Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Halaman ${_currentPage + 1} dari $totalPages", style: GoogleFonts.poppins(fontSize: 12)), Row( children: [ IconButton( icon: const Icon(Icons.chevron_left), onPressed: _currentPage == 0 ? null : () { setState(() => _currentPage--); fetchDesa(); }, ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: _currentPage >= totalPages - 1 ? null : () { setState(() => _currentPage++); fetchDesa(); }, ), ], ) ], ) ], ), ), ); } // Widget Tombol Aksi dengan Border Widget _actionButton(IconData icon, Color color, VoidCallback onTap) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: color, width: 1.5), ), child: Icon(icon, color: color, size: 18), ), ), ); } }