update
This commit is contained in:
parent
4da9ee269f
commit
c90c5356e5
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
|
|
@ -0,0 +1,2 @@
|
||||||
|
const String baseUrl = 'http://192.168.1.6:8000/api';
|
||||||
|
const String baseImageurl = 'http://192.168.1.6:8000/';
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
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:sijentik/api/api.dart';
|
||||||
|
|
||||||
class RegisterResult {
|
class RegisterResult {
|
||||||
final bool success;
|
final bool success;
|
||||||
|
|
@ -10,7 +11,6 @@ class RegisterResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegisterKaderController extends ChangeNotifier {
|
class RegisterKaderController extends ChangeNotifier {
|
||||||
|
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
final namaController = TextEditingController();
|
final namaController = TextEditingController();
|
||||||
|
|
@ -24,9 +24,6 @@ class RegisterKaderController extends ChangeNotifier {
|
||||||
bool obscureKonfirmasiPassword = true;
|
bool obscureKonfirmasiPassword = true;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
||||||
/// GANTI DENGAN URL API LARAVEL ANDA
|
|
||||||
final String baseUrl = "http://192.168.1.6:8000/api/register";
|
|
||||||
|
|
||||||
void togglePassword() {
|
void togglePassword() {
|
||||||
obscurePassword = !obscurePassword;
|
obscurePassword = !obscurePassword;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
@ -85,24 +82,17 @@ class RegisterKaderController extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RegisterResult> register() async {
|
Future<RegisterResult> register() async {
|
||||||
|
|
||||||
if (!formKey.currentState!.validate()) {
|
if (!formKey.currentState!.validate()) {
|
||||||
return RegisterResult(
|
return RegisterResult(success: false, message: "Form belum lengkap");
|
||||||
success: false,
|
|
||||||
message: "Form belum lengkap",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(baseUrl),
|
Uri.parse('$baseUrl/register'), // 🔥 pakai baseUrl global
|
||||||
headers: {
|
headers: {"Content-Type": "application/json"},
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: jsonEncode({
|
body: jsonEncode({
|
||||||
"name": namaController.text,
|
"name": namaController.text,
|
||||||
"email": emailController.text,
|
"email": emailController.text,
|
||||||
|
|
@ -112,34 +102,24 @@ class RegisterKaderController extends ChangeNotifier {
|
||||||
"password_confirmation": konfirmasiPasswordController.text,
|
"password_confirmation": konfirmasiPasswordController.text,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
if (response.statusCode == 201) {
|
||||||
|
|
||||||
return RegisterResult(
|
return RegisterResult(
|
||||||
success: true,
|
success: true,
|
||||||
message: data["message"] ?? "Register berhasil",
|
message: data["message"] ?? "Register berhasil",
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
return RegisterResult(
|
return RegisterResult(
|
||||||
success: false,
|
success: false,
|
||||||
message: data["message"] ??
|
message: data["message"] ?? data["error"] ?? "Register gagal",
|
||||||
data["error"] ??
|
|
||||||
"Register gagal",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
return RegisterResult(
|
return RegisterResult(
|
||||||
success: false,
|
success: false,
|
||||||
message: "Tidak dapat terhubung ke server",
|
message: "Tidak dapat terhubung ke server",
|
||||||
);
|
);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
@ -159,4 +139,4 @@ class RegisterKaderController extends ChangeNotifier {
|
||||||
konfirmasiPasswordController.dispose();
|
konfirmasiPasswordController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class RegisterResult {
|
class RegisterResult {
|
||||||
final bool success;
|
final bool success;
|
||||||
|
|
@ -14,9 +15,6 @@ class RegisterResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
// Android Emulator => 10.0.2.2
|
|
||||||
// HP fisik => ganti dengan IP laptop/PC, misalnya 192.168.1.10
|
|
||||||
static const String baseUrl = 'http://192.168.1.6:8000';
|
|
||||||
|
|
||||||
Future<RegisterResult> registerKader({
|
Future<RegisterResult> registerKader({
|
||||||
required String name,
|
required String name,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class KaderService {
|
class KaderService {
|
||||||
|
|
||||||
final String baseUrl = "http://192.168.1.6:8000/api";
|
|
||||||
|
|
||||||
Future<bool> approveKader(int id) async {
|
Future<bool> approveKader(int id) async {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
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:sijentik/api/api.dart';
|
||||||
|
|
||||||
class BuatKataSandiBaruPage extends StatefulWidget {
|
class BuatKataSandiBaruPage extends StatefulWidget {
|
||||||
final String email;
|
final String email;
|
||||||
|
|
@ -22,11 +23,6 @@ class _BuatKataSandiBaruPageState extends State<BuatKataSandiBaruPage> {
|
||||||
bool obscurePassword = true;
|
bool obscurePassword = true;
|
||||||
bool obscureConfirmPassword = true;
|
bool obscureConfirmPassword = true;
|
||||||
|
|
||||||
// Android Emulator: 10.0.2.2
|
|
||||||
// HP Fisik: ganti dengan IP laptop, misalnya 192.168.1.8
|
|
||||||
static const String resetPasswordUrl =
|
|
||||||
'http://192.168.1.6:8000/api/reset-password';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
passwordController.dispose();
|
passwordController.dispose();
|
||||||
|
|
@ -70,10 +66,7 @@ class _BuatKataSandiBaruPageState extends State<BuatKataSandiBaruPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
Image.asset(
|
Image.asset('assets/images/sandidua.png', width: 150),
|
||||||
'assets/images/sandidua.png',
|
|
||||||
width: 150,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -198,8 +191,7 @@ class _BuatKataSandiBaruPageState extends State<BuatKataSandiBaruPage> {
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
obscureConfirmPassword =
|
obscureConfirmPassword = !obscureConfirmPassword;
|
||||||
!obscureConfirmPassword;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -304,17 +296,14 @@ class _BuatKataSandiBaruPageState extends State<BuatKataSandiBaruPage> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(resetPasswordUrl),
|
Uri.parse('$baseUrl/reset-password'), // 🔥 pakai baseUrl global
|
||||||
headers: {
|
headers: {'Accept': 'application/json'},
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: {
|
body: {
|
||||||
'email': widget.email,
|
'email': widget.email,
|
||||||
'password': passwordController.text.trim(),
|
'password': passwordController.text.trim(),
|
||||||
'password_confirmation': confirmPasswordController.text.trim(),
|
'password_confirmation': confirmPasswordController.text.trim(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final dynamic data = jsonDecode(response.body);
|
final dynamic data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -365,4 +354,4 @@ class _BuatKataSandiBaruPageState extends State<BuatKataSandiBaruPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class DashboardService {
|
class DashboardService {
|
||||||
static const String baseUrl = "http://192.168.1.6:8000/api";
|
|
||||||
// kalau pakai HP asli ganti ke IP laptop, misalnya:
|
|
||||||
// static const String baseUrl = "http://192.168.1.8:8000/api";
|
|
||||||
|
|
||||||
static Future<Map<String, dynamic>?> getDashboard() async {
|
static Future<Map<String, dynamic>?> getDashboard() async {
|
||||||
try {
|
try {
|
||||||
final url = Uri.parse("$baseUrl/dashboard-petugas");
|
final url = Uri.parse("$baseUrl/dashboard-petugas");
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:sijentik/component/app_theme.dart';
|
||||||
import 'package:sijentik/screens/auth/registerkader.dart';
|
import 'package:sijentik/screens/auth/registerkader.dart';
|
||||||
import 'package:sijentik/screens/auth/lupasandiemail.dart';
|
import 'package:sijentik/screens/auth/lupasandiemail.dart';
|
||||||
import 'package:sijentik/screens/kader/homekader.dart';
|
import 'package:sijentik/screens/kader/homekader.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
import 'package:sijentik/screens/petugas/dashboardpetugas.dart';
|
import 'package:sijentik/screens/petugas/dashboardpetugas.dart';
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
|
|
@ -25,8 +26,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
bool obscurePassword = true;
|
bool obscurePassword = true;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
||||||
static const String baseUrl = 'http://192.168.1.6:8000/api/login';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
emailController.dispose();
|
emailController.dispose();
|
||||||
|
|
@ -39,10 +38,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
// =============================
|
// =============================
|
||||||
// SIMPAN DATA USER
|
// SIMPAN DATA USER
|
||||||
// =============================
|
// =============================
|
||||||
Future<void> saveUserData(Map user) async {
|
Future<void> saveUserData(Map<String, dynamic> user) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
await prefs.setInt('id', user['id']);
|
await prefs.setInt('id', int.tryParse(user['id'].toString()) ?? 0);
|
||||||
await prefs.setString('name', user['name'] ?? '');
|
await prefs.setString('name', user['name'] ?? '');
|
||||||
await prefs.setString('email', user['email'] ?? '');
|
await prefs.setString('email', user['email'] ?? '');
|
||||||
await prefs.setString('address', user['address'] ?? '');
|
await prefs.setString('address', user['address'] ?? '');
|
||||||
|
|
@ -51,6 +50,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
await prefs.setString('status', user['status'] ?? '');
|
await prefs.setString('status', user['status'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// LOGIN
|
||||||
|
// =============================
|
||||||
Future<void> _login() async {
|
Future<void> _login() async {
|
||||||
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
||||||
_showErrorDialog('Email dan kata sandi harus diisi');
|
_showErrorDialog('Email dan kata sandi harus diisi');
|
||||||
|
|
@ -68,7 +70,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(baseUrl),
|
Uri.parse('$baseUrl/login'),
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -79,6 +81,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
print("STATUS LOGIN: ${response.statusCode}");
|
||||||
|
print("BODY LOGIN: ${response.body}");
|
||||||
|
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -88,9 +93,18 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final user = data['user'];
|
final user = Map<String, dynamic>.from(data['user'] ?? {});
|
||||||
|
|
||||||
// simpan data user dari backend
|
// 🔥 VALIDASI WAJIB
|
||||||
|
if (user['id'] == null) {
|
||||||
|
_showErrorDialog('ID user tidak ditemukan dari server');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("LOGIN SUCCESS");
|
||||||
|
print("USER FINAL: $user");
|
||||||
|
|
||||||
|
// SIMPAN
|
||||||
await saveUserData(user);
|
await saveUserData(user);
|
||||||
|
|
||||||
final String role = (user['role'] ?? '').toString().toLowerCase();
|
final String role = (user['role'] ?? '').toString().toLowerCase();
|
||||||
|
|
@ -98,7 +112,13 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
if (role == 'petugas') {
|
if (role == 'petugas') {
|
||||||
_navigateToDashboardPetugas();
|
_navigateToDashboardPetugas();
|
||||||
} else if (role == 'kader') {
|
} else if (role == 'kader') {
|
||||||
_navigateToHomeKader(user);
|
_navigateToHomeKader({
|
||||||
|
'id': user['id'],
|
||||||
|
'name': user['name'],
|
||||||
|
'email': user['email'],
|
||||||
|
'address': user['address'],
|
||||||
|
'role': user['role'],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
_showErrorDialog('Role pengguna tidak dikenali');
|
_showErrorDialog('Role pengguna tidak dikenali');
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +136,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// ERROR DIALOG
|
||||||
|
// =============================
|
||||||
void _showErrorDialog(String message) {
|
void _showErrorDialog(String message) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -197,7 +220,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
// EMAIL
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -225,7 +247,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// PASSWORD
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -264,7 +285,23 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
onSubmitted: (_) => _login(),
|
onSubmitted: (_) => _login(),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _navigateLupaKataSandi,
|
||||||
|
child: const Text(
|
||||||
|
'Lupa Kata Sandi?',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF1E88E5),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|
@ -283,24 +320,24 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
const SizedBox(height: 25),
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
'Belum punya akun? ',
|
'Belum punya akun? ',
|
||||||
style: TextStyle(color: Colors.grey[700]),
|
style: TextStyle(color: Colors.black87),
|
||||||
),
|
),
|
||||||
TextButton(
|
GestureDetector(
|
||||||
onPressed: _navigateToRegister,
|
onTap: _navigateToRegister,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Daftar disini',
|
'Daftar',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFF1E88E5),
|
color: Color(0xFF1E88E5),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -313,7 +350,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -324,7 +360,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToHomeKader(Map<String, dynamic> user) {
|
void _navigateToHomeKader(Map<String, dynamic> user) {
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => HomeKaderPage(user: user)),
|
MaterialPageRoute(builder: (context) => HomeKaderPage(user: user)),
|
||||||
|
|
@ -333,7 +368,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToDashboardPetugas() {
|
void _navigateToDashboardPetugas() {
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
Navigator.pushAndRemoveUntil(
|
Navigator.pushAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const DashboardPetugas()),
|
MaterialPageRoute(builder: (context) => const DashboardPetugas()),
|
||||||
|
|
@ -347,4 +381,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
MaterialPageRoute(builder: (context) => const RegisterKaderPage()),
|
MaterialPageRoute(builder: (context) => const RegisterKaderPage()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _navigateLupaKataSandi() {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => const LupaKataSandiEmailPage()),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
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 'lupasandiotp.dart';
|
import 'lupasandiotp.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class LupaKataSandiEmailPage extends StatefulWidget {
|
class LupaKataSandiEmailPage extends StatefulWidget {
|
||||||
const LupaKataSandiEmailPage({super.key});
|
const LupaKataSandiEmailPage({super.key});
|
||||||
|
|
@ -14,11 +15,6 @@ class _LupaKataSandiEmailPageState extends State<LupaKataSandiEmailPage> {
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
final FocusNode emailFocusNode = FocusNode();
|
final FocusNode emailFocusNode = FocusNode();
|
||||||
|
|
||||||
// Ganti sesuai device yang dipakai
|
|
||||||
// Android Emulator: 10.0.2.2
|
|
||||||
// HP Fisik: pakai IP laptop, misalnya 192.168.1.8
|
|
||||||
static const String requestOtpUrl = 'http://192.168.1.6:8000/api/RequestOtp';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
emailController.dispose();
|
emailController.dispose();
|
||||||
|
|
@ -226,11 +222,10 @@ class _LupaKataSandiEmailPageState extends State<LupaKataSandiEmailPage> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(requestOtpUrl),
|
Uri.parse('$baseUrl/request-otp'), // 🔥 pakai baseUrl global
|
||||||
headers: {'Accept': 'application/json'},
|
headers: {'Accept': 'application/json'},
|
||||||
body: {'email': emailController.text.trim(), 'type': 'email'},
|
body: {'email': emailController.text.trim(), 'type': 'email'},
|
||||||
);
|
);
|
||||||
|
|
||||||
final dynamic data = jsonDecode(response.body);
|
final dynamic data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
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 'buatsandibaru.dart';
|
import 'buatsandibaru.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class LupaKataSandiOtpPage extends StatefulWidget {
|
class LupaKataSandiOtpPage extends StatefulWidget {
|
||||||
final String email;
|
final String email;
|
||||||
|
|
@ -24,10 +25,8 @@ class _LupaKataSandiOtpPageState extends State<LupaKataSandiOtpPage> {
|
||||||
(index) => FocusNode(),
|
(index) => FocusNode(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Android Emulator: 10.0.2.2
|
String get verifyOtpUrl => '$baseUrl/verify-otp';
|
||||||
// HP Fisik: ganti dengan IP laptop, misalnya 192.168.1.8
|
String get requestOtpUrl => '$baseUrl/RequestOtp';
|
||||||
static const String verifyOtpUrl = 'http://192.168.1.6:8000/api/verify-otp';
|
|
||||||
static const String requestOtpUrl = 'http://192.168.1.6:8000/api/RequestOtp';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:sijentik/models/report_model.dart';
|
import 'package:sijentik/models/report_model.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class AddReportPage extends StatefulWidget {
|
class AddReportPage extends StatefulWidget {
|
||||||
const AddReportPage({super.key});
|
const AddReportPage({super.key, required userId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddReportPage> createState() => _AddReportPageState();
|
State<AddReportPage> createState() => _AddReportPageState();
|
||||||
|
|
@ -29,8 +31,6 @@ class _AddReportPageState extends State<AddReportPage> {
|
||||||
|
|
||||||
final ImagePicker picker = ImagePicker();
|
final ImagePicker picker = ImagePicker();
|
||||||
|
|
||||||
static const String baseUrl = 'http://192.168.1.6:8000/api';
|
|
||||||
|
|
||||||
Future<void> simpanLaporan() async {
|
Future<void> simpanLaporan() async {
|
||||||
if (judulController.text.trim().isEmpty) {
|
if (judulController.text.trim().isEmpty) {
|
||||||
_showMessage('Judul laporan wajib diisi');
|
_showMessage('Judul laporan wajib diisi');
|
||||||
|
|
@ -52,12 +52,25 @@ class _AddReportPageState extends State<AddReportPage> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 🔥 AMBIL TOKEN
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final token = prefs.getString('token');
|
||||||
|
final userId = prefs.getInt('id'); // 🔥
|
||||||
|
|
||||||
final request = http.MultipartRequest(
|
final request = http.MultipartRequest(
|
||||||
'POST',
|
'POST',
|
||||||
Uri.parse('$baseUrl/laporan'),
|
Uri.parse('$baseUrl/laporan'),
|
||||||
);
|
);
|
||||||
|
|
||||||
request.headers['Accept'] = 'application/json';
|
request.headers['Accept'] = 'application/json';
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
request.headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔥 TAMBAH INI
|
||||||
|
request.fields['user_id'] = userId.toString();
|
||||||
|
|
||||||
request.fields['judul'] = judulController.text.trim();
|
request.fields['judul'] = judulController.text.trim();
|
||||||
request.fields['ada_jentik'] = adaJentik! ? '1' : '0';
|
request.fields['ada_jentik'] = adaJentik! ? '1' : '0';
|
||||||
request.fields['tanggal'] = DateFormat(
|
request.fields['tanggal'] = DateFormat(
|
||||||
|
|
@ -76,7 +89,6 @@ class _AddReportPageState extends State<AddReportPage> {
|
||||||
final streamedResponse = await request.send();
|
final streamedResponse = await request.send();
|
||||||
final response = await http.Response.fromStream(streamedResponse);
|
final response = await http.Response.fromStream(streamedResponse);
|
||||||
|
|
||||||
// PERBAIKAN: data sebagai variable non-final
|
|
||||||
Map<String, dynamic>? data;
|
Map<String, dynamic>? data;
|
||||||
if (response.body.isNotEmpty) {
|
if (response.body.isNotEmpty) {
|
||||||
data = jsonDecode(response.body);
|
data = jsonDecode(response.body);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
import 'package:sijentik/component/app_theme.dart';
|
import 'package:sijentik/component/app_theme.dart';
|
||||||
import 'history_page.dart';
|
import 'history_page.dart';
|
||||||
import 'add_report.dart';
|
import 'add_report.dart';
|
||||||
|
|
@ -17,13 +20,19 @@ class DashboardPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DashboardPageState extends State<DashboardPage> {
|
class _DashboardPageState extends State<DashboardPage> {
|
||||||
|
|
||||||
String address = '';
|
String address = '';
|
||||||
|
|
||||||
|
Map<String, dynamic>? dashboardData;
|
||||||
|
|
||||||
|
// 🔥 TAMBAHKAN INI
|
||||||
|
List<dynamic> recentReports = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
loadUserData();
|
loadUserData();
|
||||||
|
fetchDashboard();
|
||||||
|
fetchRecentReports();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadUserData() async {
|
Future<void> loadUserData() async {
|
||||||
|
|
@ -34,58 +43,77 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data statistik dummy
|
Future<void> fetchDashboard() async {
|
||||||
final Map<String, dynamic> stats = const {
|
final prefs = await SharedPreferences.getInstance();
|
||||||
'totalReports': 24,
|
final userId = prefs.getInt('id');
|
||||||
'approved': 18,
|
|
||||||
'pending': 4,
|
|
||||||
'rejected': 2,
|
|
||||||
'percentage': 75,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Data laporan terbaru dummy
|
print("ID DARI STORAGE: $userId");
|
||||||
final List<Map<String, dynamic>> recentReports = const [
|
|
||||||
{
|
if (userId == null) {
|
||||||
'id': 'LP001',
|
print("ID NULL DARI STORAGE ❌");
|
||||||
'address': 'Jl. Merdeka No. 12',
|
return;
|
||||||
'date': '12 Mar 2024',
|
}
|
||||||
'status': 'Disetujui',
|
|
||||||
'statusColor': Colors.green,
|
final response = await http.get(
|
||||||
},
|
Uri.parse('http://192.168.1.6:8000/api/dashboard/$userId'),
|
||||||
{
|
);
|
||||||
'id': 'LP002',
|
|
||||||
'address': 'Jl. Sudirman No. 45',
|
print("STATUS: ${response.statusCode}");
|
||||||
'date': '11 Mar 2024',
|
print("BODY: ${response.body}");
|
||||||
'status': 'Menunggu',
|
|
||||||
'statusColor': Colors.orange,
|
if (response.statusCode == 200) {
|
||||||
},
|
final data = json.decode(response.body);
|
||||||
{
|
|
||||||
'id': 'LP003',
|
setState(() {
|
||||||
'address': 'Jl. Gatot Subroto No. 8',
|
dashboardData = data;
|
||||||
'date': '10 Mar 2024',
|
});
|
||||||
'status': 'Disetujui',
|
}
|
||||||
'statusColor': Colors.green,
|
}
|
||||||
},
|
|
||||||
];
|
Future<void> fetchRecentReports() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final userId = prefs.getInt('id');
|
||||||
|
|
||||||
|
if (userId == null) return;
|
||||||
|
|
||||||
|
final response = await http.get(Uri.parse('$baseUrl/laporan/user/$userId'));
|
||||||
|
|
||||||
|
print("RECENT STATUS: ${response.statusCode}");
|
||||||
|
print("RECENT BODY: ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
recentReports = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final stats = dashboardData?['stats'];
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// ===== HEADER SELAMAT DATANG =====
|
// ===== HEADER (GRADIENT) =====
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF206E97),
|
gradient: const LinearGradient(
|
||||||
borderRadius: BorderRadius.circular(16),
|
colors: [Color(0xFF206E97), Color(0xFF4BA3C3)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withOpacity(0.2),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 5),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -96,8 +124,8 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Selamat Datang,',
|
'Selamat Datang 👋',
|
||||||
style: TextStyle(fontSize: 16, color: Colors.white70),
|
style: TextStyle(fontSize: 14, color: Colors.white70),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -108,38 +136,22 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Wilayah: ${widget.user['address']}',
|
'Wilayah: ${dashboardData?['user']['wilayah'] ?? '-'}',
|
||||||
style: TextStyle(
|
style: const TextStyle(color: Colors.white),
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white.withOpacity(0.9),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
Text(
|
||||||
'Total Laporan: ${stats['totalReports']}',
|
'Total Laporan: ${stats?['total_laporan'] ?? 0}',
|
||||||
style: TextStyle(
|
style: const TextStyle(color: Colors.white),
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.white.withOpacity(0.9),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const CircleAvatar(
|
||||||
Container(
|
radius: 30,
|
||||||
width: 80,
|
backgroundColor: Colors.white24,
|
||||||
height: 80,
|
child: Icon(Icons.person, color: Colors.white, size: 30),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.medical_services_outlined,
|
|
||||||
size: 40,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -147,14 +159,10 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
|
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
|
|
||||||
// ===== STATISTIK CARD =====
|
// ===== STATISTIK =====
|
||||||
const Text(
|
const Text(
|
||||||
'Statistik Laporan',
|
"Statistik Laporan",
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
|
|
@ -162,32 +170,32 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 15,
|
crossAxisSpacing: 12,
|
||||||
mainAxisSpacing: 15,
|
mainAxisSpacing: 12,
|
||||||
childAspectRatio: 1.2,
|
childAspectRatio: 1.3,
|
||||||
children: [
|
children: [
|
||||||
_buildStatCard(
|
_buildStatCard(
|
||||||
title: 'Total Laporan',
|
title: 'Total',
|
||||||
value: '${stats['totalReports']}',
|
value: '${stats?['total_laporan'] ?? 0}',
|
||||||
icon: Icons.assignment_outlined,
|
icon: Icons.assignment,
|
||||||
color: const Color(0xFF206E97),
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
_buildStatCard(
|
_buildStatCard(
|
||||||
title: 'Disetujui',
|
title: 'Disetujui',
|
||||||
value: '${stats['approved']}',
|
value: '${stats?['disetujui'] ?? 0}',
|
||||||
icon: Icons.check_circle_outline,
|
icon: Icons.check_circle,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
),
|
),
|
||||||
_buildStatCard(
|
_buildStatCard(
|
||||||
title: 'Menunggu',
|
title: 'Menunggu',
|
||||||
value: '${stats['pending']}',
|
value: '${stats?['menunggu'] ?? 0}',
|
||||||
icon: Icons.access_time_outlined,
|
icon: Icons.schedule,
|
||||||
color: Colors.orange,
|
color: Colors.orange,
|
||||||
),
|
),
|
||||||
_buildStatCard(
|
_buildStatCard(
|
||||||
title: 'Ditolak',
|
title: 'Ditolak',
|
||||||
value: '${stats['rejected']}',
|
value: '${stats?['ditolak'] ?? 0}',
|
||||||
icon: Icons.cancel_outlined,
|
icon: Icons.cancel,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -195,15 +203,15 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
|
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
|
|
||||||
// ===== PERSENTASE DISETUJUI =====
|
// ===== PROGRESS =====
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: Colors.black.withOpacity(0.08),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
),
|
),
|
||||||
|
|
@ -218,153 +226,104 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value: stats['percentage'] / 100,
|
value: (stats?['persentase_disetujui'] ?? 0) / 100,
|
||||||
strokeWidth: 8,
|
strokeWidth: 8,
|
||||||
backgroundColor: Colors.grey[200],
|
backgroundColor: Colors.grey[200],
|
||||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
valueColor: const AlwaysStoppedAnimation(
|
||||||
Color(0xFF206E97),
|
Color(0xFF206E97),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${stats['percentage']}%',
|
'${stats?['persentase_disetujui'] ?? 0}%',
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Column(
|
child: Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
"Persentase laporan yang telah disetujui",
|
||||||
children: [
|
style: TextStyle(fontSize: 14),
|
||||||
Text(
|
|
||||||
'Persentase Disetujui',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
'75% laporan Anda telah disetujui petugas. Pertahankan!',
|
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 25),
|
|
||||||
|
|
||||||
// ===== LAPORAN TERBARU =====
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Laporan Terbaru',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Navigasi ke halaman riwayat
|
|
||||||
// Navigator.push(context, MaterialPageRoute(builder: (context) => HistoryPage()));
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'Lihat Semua',
|
|
||||||
style: TextStyle(color: Color(0xFF206E97)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
Container(
|
recentReports.isEmpty
|
||||||
decoration: BoxDecoration(
|
? const Center(
|
||||||
color: Colors.white,
|
child: Padding(
|
||||||
borderRadius: BorderRadius.circular(12),
|
padding: EdgeInsets.all(20),
|
||||||
boxShadow: [
|
child: Text(
|
||||||
BoxShadow(
|
"Belum ada laporan",
|
||||||
color: Colors.black.withOpacity(0.05),
|
style: TextStyle(color: Colors.grey),
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: recentReports.map((report) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF206E97).withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.home_outlined,
|
|
||||||
color: Color(0xFF206E97),
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
report['address'],
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
'ID: ${report['id']} • ${report['date']}',
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
trailing: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 6,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: report['statusColor'].withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
report['status'],
|
|
||||||
style: TextStyle(
|
|
||||||
color: report['statusColor'],
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
// Navigasi ke detail laporan
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (report != recentReports.last)
|
),
|
||||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
)
|
||||||
],
|
: Column(
|
||||||
);
|
children: recentReports.map((report) {
|
||||||
}).toList(),
|
return Container(
|
||||||
),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 6,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Icons.home, color: Colors.blue),
|
||||||
|
|
||||||
const SizedBox(height: 25),
|
// 🔥 FIX FIELD DARI API
|
||||||
|
title: Text(report['judul'] ?? '-'),
|
||||||
|
subtitle: Text(report['alamat'] ?? '-'),
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
trailing: _buildStatusBadge(report['status']),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildStatusBadge(String? status) {
|
||||||
|
Color color;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'diterima':
|
||||||
|
color = Colors.green;
|
||||||
|
break;
|
||||||
|
case 'proses':
|
||||||
|
color = Colors.orange;
|
||||||
|
break;
|
||||||
|
case 'ditolak':
|
||||||
|
color = Colors.red;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = Colors.grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
status ?? '-',
|
||||||
|
style: TextStyle(color: color, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildStatCard({
|
Widget _buildStatCard({
|
||||||
required String title,
|
required String title,
|
||||||
required String value,
|
required String value,
|
||||||
|
|
@ -372,107 +331,26 @@ class _DashboardPageState extends State<DashboardPage> {
|
||||||
required Color color,
|
required Color color,
|
||||||
}) {
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 8),
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Icon(icon, color: color, size: 26),
|
||||||
children: [
|
const Spacer(),
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(icon, color: color, size: 20),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
Text(
|
||||||
title,
|
value,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||||
fontSize: 13,
|
|
||||||
color: Colors.grey,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
Text(title, style: const TextStyle(color: Colors.grey)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildQuickActionCard({
|
|
||||||
required IconData icon,
|
|
||||||
required String title,
|
|
||||||
required String subtitle,
|
|
||||||
required Color color,
|
|
||||||
required VoidCallback onTap,
|
|
||||||
}) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(icon, color: color, size: 24),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ class HomeKaderPage extends StatefulWidget {
|
||||||
|
|
||||||
class _HomeKaderPageState extends State<HomeKaderPage> {
|
class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
// Data dummy kader
|
// Data dummy kader
|
||||||
|
|
||||||
// Pages untuk bottom navigation
|
// Pages untuk bottom navigation
|
||||||
late final List<Widget> _pages = [
|
late final List<Widget> _pages = [
|
||||||
const DashboardPage(user: {}),
|
const DashboardPage(user: {}),
|
||||||
|
|
@ -34,7 +34,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF5F9FF),
|
backgroundColor: const Color(0xFFF5F9FF),
|
||||||
|
|
||||||
// APPBAR
|
// APPBAR
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: AppColors.button,
|
backgroundColor: AppColors.button,
|
||||||
|
|
@ -61,10 +61,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Kader: ${widget.user['name']}',
|
'Kader: ${widget.user['name']}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 12, color: Colors.white70),
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white70,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -95,26 +92,27 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// BODY
|
// BODY
|
||||||
body: _pages[_selectedIndex],
|
body: _pages[_selectedIndex],
|
||||||
|
|
||||||
// FLOATING ACTION BUTTON (hanya di halaman Laporan)
|
// FLOATING ACTION BUTTON (hanya di halaman Laporan)
|
||||||
floatingActionButton: _selectedIndex == 1
|
floatingActionButton: _selectedIndex == 1
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
backgroundColor: const Color(0xFF206E97),
|
backgroundColor: const Color(0xFF206E97),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const AddReportPage(),
|
builder: (context) =>
|
||||||
|
AddReportPage(userId: widget.user['id']),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.add, color: Colors.white),
|
child: const Icon(Icons.add, color: Colors.white),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
// BOTTOM NAVIGATION BAR
|
// BOTTOM NAVIGATION BAR
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: _selectedIndex,
|
currentIndex: _selectedIndex,
|
||||||
|
|
@ -154,7 +152,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showNotifications() {
|
void _showNotifications() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -191,7 +189,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNotificationItem({
|
Widget _buildNotificationItem({
|
||||||
required String title,
|
required String title,
|
||||||
required String message,
|
required String message,
|
||||||
|
|
@ -217,10 +215,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(message),
|
Text(message),
|
||||||
Text(
|
Text(time, style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||||||
time,
|
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: !isRead
|
trailing: !isRead
|
||||||
|
|
@ -235,7 +230,7 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _confirmLogout() {
|
void _confirmLogout() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -252,19 +247,16 @@ class _HomeKaderPageState extends State<HomeKaderPage> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context); // Tutup dialog
|
Navigator.pop(context); // Tutup dialog
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
Navigator.pushNamedAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
'/login',
|
'/login',
|
||||||
(route) => false
|
(route) => false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text('Logout', style: TextStyle(color: Colors.red)),
|
||||||
'Logout',
|
|
||||||
style: TextStyle(color: Colors.red),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
var response = await request.send();
|
var response = await request.send();
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
setState(() {
|
||||||
|
_imageFile = null; // reset biar ambil dari server
|
||||||
|
});
|
||||||
getProfile();
|
getProfile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +92,13 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
getProfile();
|
getProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔥 Tambahan supaya refresh saat balik ke halaman ini
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
getProfile();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
@ -119,8 +129,12 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
backgroundImage: _imageFile != null
|
backgroundImage: _imageFile != null
|
||||||
? FileImage(File(_imageFile!.path))
|
? FileImage(File(_imageFile!.path))
|
||||||
: user!['photo_url'] != null
|
: user!['photo_url'] != null
|
||||||
? NetworkImage(user!['photo_url'])
|
// 🔥 CACHE BUSTER DI SINI
|
||||||
: null,
|
? NetworkImage(
|
||||||
|
user!['photo_url'] +
|
||||||
|
"?t=${DateTime.now().millisecondsSinceEpoch}",
|
||||||
|
)
|
||||||
|
: null,
|
||||||
child: user!['photo_url'] == null && _imageFile == null
|
child: user!['photo_url'] == null && _imageFile == null
|
||||||
? const Icon(Icons.person, size: 60)
|
? const Icon(Icons.person, size: 60)
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -206,4 +220,4 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'add_report.dart';
|
import 'add_report.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class ReportsPage extends StatelessWidget {
|
class ReportsPage extends StatelessWidget {
|
||||||
const ReportsPage({super.key});
|
const ReportsPage({super.key});
|
||||||
|
|
||||||
|
Future<int> getUserId() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getInt('id') ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|
@ -28,7 +34,7 @@ class ReportsPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
// Card Panduan
|
// Card Panduan
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
|
@ -73,19 +79,25 @@ class ReportsPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
// Tombol Mulai
|
// Tombol Mulai
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 50,
|
height: 50,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
|
int userId = await getUserId();
|
||||||
|
|
||||||
|
print("USER ID: $userId"); // debug
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const AddReportPage(),
|
builder: (context) => AddReportPage(
|
||||||
|
userId: userId, // ✅ FIX DI SINI
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
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:sijentik/component/app_theme.dart';
|
import 'package:sijentik/component/app_theme.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class DaftarKaderPage extends StatefulWidget {
|
class DaftarKaderPage extends StatefulWidget {
|
||||||
const DaftarKaderPage({super.key});
|
const DaftarKaderPage({super.key});
|
||||||
|
|
@ -14,8 +15,6 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
List<dynamic> kaderList = [];
|
List<dynamic> kaderList = [];
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
|
||||||
final String baseUrl = "http://192.168.1.6:8000/api";
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -25,6 +24,9 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
Future<void> fetchKader() async {
|
Future<void> fetchKader() async {
|
||||||
final response = await http.get(Uri.parse("$baseUrl/users/kader"));
|
final response = await http.get(Uri.parse("$baseUrl/users/kader"));
|
||||||
|
|
||||||
|
print("STATUS: ${response.statusCode}");
|
||||||
|
print("BODY: ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
|
|
@ -35,109 +37,65 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> deleteKader(int id) async {
|
||||||
Widget build(BuildContext context) {
|
final response = await http.delete(Uri.parse("$baseUrl/users/$id"));
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: AppColors.button,
|
|
||||||
title: const Text("Daftar Kader"),
|
|
||||||
),
|
|
||||||
|
|
||||||
body: isLoading
|
if (response.statusCode == 200) {
|
||||||
? const Center(child: CircularProgressIndicator())
|
fetchKader();
|
||||||
: ListView.builder(
|
|
||||||
padding: const EdgeInsets.all(16),
|
ScaffoldMessenger.of(
|
||||||
itemCount: kaderList.length,
|
context,
|
||||||
itemBuilder: (context, index) {
|
).showSnackBar(const SnackBar(content: Text("Kader berhasil dihapus")));
|
||||||
final kader = kaderList[index];
|
} else {
|
||||||
return buildKaderCard(kader);
|
ScaffoldMessenger.of(
|
||||||
},
|
context,
|
||||||
),
|
).showSnackBar(const SnackBar(content: Text("Gagal menghapus kader")));
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildKaderCard(dynamic kader) {
|
void showDeleteDialog(dynamic kader) {
|
||||||
return Card(
|
showDialog(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
context: context,
|
||||||
child: Padding(
|
builder: (_) => AlertDialog(
|
||||||
padding: const EdgeInsets.all(16),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
title: const Text("Hapus Kader"),
|
||||||
child: Column(
|
content: Text("Yakin ingin menghapus ${kader['name']}?"),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
actions: [
|
||||||
children: [
|
TextButton(
|
||||||
Row(
|
onPressed: () => Navigator.pop(context),
|
||||||
children: [
|
child: const Text("Batal"),
|
||||||
CircleAvatar(child: Text(kader['name'][0])),
|
),
|
||||||
|
ElevatedButton(
|
||||||
const SizedBox(width: 12),
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
Expanded(
|
shape: RoundedRectangleBorder(
|
||||||
child: Column(
|
borderRadius: BorderRadius.circular(10),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
kader['name'],
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
|
|
||||||
Text(
|
|
||||||
kader['email'],
|
|
||||||
style: const TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit),
|
|
||||||
onPressed: () {
|
|
||||||
showEditDialog(kader);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
onPressed: () async {
|
||||||
const SizedBox(height: 12),
|
Navigator.pop(context);
|
||||||
|
await deleteKader(kader['id']);
|
||||||
Row(
|
},
|
||||||
children: [
|
child: const Text("Hapus"),
|
||||||
const Icon(Icons.location_on, size: 16),
|
),
|
||||||
const SizedBox(width: 5),
|
],
|
||||||
|
|
||||||
Text("RT/RW : ${kader['rtrw']}"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
|
|
||||||
Text("Alamat : ${kader['address']}"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showEditDialog(dynamic kader) {
|
void showEditDialog(dynamic kader) {
|
||||||
TextEditingController name = TextEditingController(text: kader['name']);
|
TextEditingController name = TextEditingController(text: kader['name']);
|
||||||
|
|
||||||
TextEditingController email = TextEditingController(text: kader['email']);
|
TextEditingController email = TextEditingController(text: kader['email']);
|
||||||
|
|
||||||
TextEditingController address = TextEditingController(
|
TextEditingController address = TextEditingController(
|
||||||
text: kader['address'],
|
text: kader['address'],
|
||||||
);
|
);
|
||||||
|
|
||||||
TextEditingController rtrw = TextEditingController(text: kader['rtrw']);
|
TextEditingController rtrw = TextEditingController(text: kader['rtrw']);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => AlertDialog(
|
builder: (_) => AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
title: const Text("Edit Data Kader"),
|
title: const Text("Edit Data Kader"),
|
||||||
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -145,23 +103,17 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
controller: name,
|
controller: name,
|
||||||
decoration: const InputDecoration(labelText: "Nama"),
|
decoration: const InputDecoration(labelText: "Nama"),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: email,
|
controller: email,
|
||||||
decoration: const InputDecoration(labelText: "Email"),
|
decoration: const InputDecoration(labelText: "Email"),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: address,
|
controller: address,
|
||||||
decoration: const InputDecoration(labelText: "Alamat"),
|
decoration: const InputDecoration(labelText: "Alamat"),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: rtrw,
|
controller: rtrw,
|
||||||
decoration: const InputDecoration(labelText: "RT/RW"),
|
decoration: const InputDecoration(labelText: "RT/RW"),
|
||||||
|
|
@ -169,13 +121,11 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text("Batal"),
|
child: const Text("Batal"),
|
||||||
),
|
),
|
||||||
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await http.put(
|
await http.put(
|
||||||
|
|
@ -189,7 +139,6 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
||||||
fetchKader();
|
fetchKader();
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|
@ -202,4 +151,166 @@ class _DaftarKaderPageState extends State<DaftarKaderPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildKaderCard(dynamic kader) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.08),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 26,
|
||||||
|
backgroundColor: AppColors.button,
|
||||||
|
child: ClipOval(
|
||||||
|
child:
|
||||||
|
kader['profile_photo'] != null &&
|
||||||
|
kader['profile_photo'].toString().isNotEmpty
|
||||||
|
? Image.network(
|
||||||
|
baseImageurl + kader['profile_photo'],
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
print("ERROR IMAGE: $error");
|
||||||
|
return Text(
|
||||||
|
kader['name'][0].toUpperCase(),
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
kader['name'][0].toUpperCase(),
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
kader['name'],
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
kader['email'],
|
||||||
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit, color: Colors.blue),
|
||||||
|
onPressed: () => showEditDialog(kader),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () => showDeleteDialog(kader),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.home, size: 16, color: Colors.grey),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text("RT/RW: ${kader['rtrw']}"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_on, size: 16, color: Colors.grey),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
kader['address'],
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF4F7FB),
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: AppColors.button,
|
||||||
|
title: const Text(
|
||||||
|
"Daftar Kader",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.button,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(25),
|
||||||
|
bottomRight: Radius.circular(25),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"Total Kader: ${kaderList.length}",
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: kaderList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return buildKaderCard(kaderList[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,8 @@ class _DashboardPetugasState extends State<DashboardPetugas> {
|
||||||
children: [
|
children: [
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: AppColors.button,
|
||||||
child: Icon(Icons.person, size: 30, color: Colors.deepPurple),
|
backgroundImage: AssetImage('assets/images/logo_petugas.png'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
||||||
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:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
import 'package:sijentik/component/app_theme.dart';
|
||||||
|
|
||||||
class LaporanPetugasPage extends StatefulWidget {
|
class LaporanPetugasPage extends StatefulWidget {
|
||||||
const LaporanPetugasPage({super.key});
|
const LaporanPetugasPage({super.key});
|
||||||
|
|
@ -11,7 +13,6 @@ class LaporanPetugasPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LaporanPetugasPageState extends State<LaporanPetugasPage> {
|
class _LaporanPetugasPageState extends State<LaporanPetugasPage> {
|
||||||
static const String baseUrl = 'http://192.168.1.6:8000/api';
|
|
||||||
|
|
||||||
List<dynamic> laporanList = [];
|
List<dynamic> laporanList = [];
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
|
@ -217,8 +218,14 @@ class _LaporanPetugasPageState extends State<LaporanPetugasPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Laporan Petugas'),
|
title: const Text(
|
||||||
backgroundColor: Colors.blue,
|
'Laporan Petugas',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: AppColors.button,
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
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:sijentik/api/api.dart' as Api;
|
||||||
import 'package:sijentik/component/app_theme.dart';
|
import 'package:sijentik/component/app_theme.dart';
|
||||||
|
import 'package:sijentik/api/api.dart';
|
||||||
|
|
||||||
class VerifikasiKaderPage extends StatefulWidget {
|
class VerifikasiKaderPage extends StatefulWidget {
|
||||||
const VerifikasiKaderPage({super.key});
|
const VerifikasiKaderPage({super.key});
|
||||||
|
|
@ -11,129 +13,103 @@ class VerifikasiKaderPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
||||||
|
|
||||||
List kaderList = [];
|
List kaderList = [];
|
||||||
bool isLoadingList = false;
|
bool isLoadingList = false;
|
||||||
|
|
||||||
static const String baseUrl = 'http://192.168.1.6:8000/api';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fetchKader();
|
fetchKader();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// FETCH KADER
|
||||||
// FETCH KADER PENDING
|
|
||||||
// =========================
|
|
||||||
Future<void> fetchKader() async {
|
|
||||||
|
|
||||||
|
Future<void> fetchKader() async {
|
||||||
|
if (!mounted) return;
|
||||||
setState(() => isLoadingList = true);
|
setState(() => isLoadingList = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse('$baseUrl/kader/pending'),
|
Uri.parse("${Api.baseUrl}/kader/pending"),
|
||||||
headers: {'Accept': 'application/json'},
|
headers: {'Accept': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|
||||||
final data = jsonDecode(response.body);
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
kaderList = data['data'];
|
kaderList = data['data'];
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
_showMessage(data['message'] ?? 'Gagal mengambil data', success: false);
|
||||||
final data = jsonDecode(response.body);
|
|
||||||
_showMessage(data['message'] ?? 'Gagal mengambil data');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
_showMessage('Error: $e');
|
_showMessage('Error: $e', success: false);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
if (!mounted) return;
|
||||||
setState(() => isLoadingList = false);
|
setState(() => isLoadingList = false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// APPROVE
|
||||||
// TERIMA KADER
|
|
||||||
// =========================
|
|
||||||
Future<void> approveKader(int id) async {
|
Future<void> approveKader(int id) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('$baseUrl/kader/approve/$id'),
|
Uri.parse("${Api.baseUrl}/kader/approve/$id"),
|
||||||
headers: {'Accept': 'application/json'},
|
headers: {'Accept': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
_showMessage(data['message'] ?? 'Kader diterima');
|
_showMessage(data['message'] ?? 'Kader diterima');
|
||||||
fetchKader();
|
fetchKader();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
_showMessage(data['message'] ?? 'Gagal menerima kader', success: false);
|
||||||
_showMessage(data['message'] ?? 'Gagal menerima kader');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
_showMessage('Error: $e');
|
_showMessage('Error: $e', success: false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// TOLAK KADER
|
// REJECT
|
||||||
// =========================
|
// =========================
|
||||||
Future<void> rejectKader(int id) async {
|
Future<void> rejectKader(int id) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse('$baseUrl/kader/reject/$id'),
|
Uri.parse("${Api.baseUrl}/kader/reject/$id"),
|
||||||
headers: {'Accept': 'application/json'},
|
headers: {'Accept': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
_showMessage(data['message'] ?? 'Kader ditolak');
|
_showMessage(data['message'] ?? 'Kader ditolak');
|
||||||
fetchKader();
|
fetchKader();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
_showMessage(data['message'] ?? 'Gagal menolak kader', success: false);
|
||||||
_showMessage(data['message'] ?? 'Gagal menolak kader');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
_showMessage('Error: $e');
|
_showMessage('Error: $e', success: false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// MESSAGE
|
// MESSAGE (ANTI ERROR)
|
||||||
// =========================
|
// =========================
|
||||||
void _showMessage(String message, {bool success = true}) {
|
void _showMessage(String message, {bool success = true}) {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
@ -141,14 +117,12 @@ class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
||||||
backgroundColor: success ? Colors.green : Colors.red,
|
backgroundColor: success ? Colors.green : Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// LIST KADER
|
// UI LIST
|
||||||
// =========================
|
// =========================
|
||||||
Widget buildKaderList() {
|
Widget buildKaderList() {
|
||||||
|
|
||||||
if (isLoadingList) {
|
if (isLoadingList) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
@ -161,20 +135,15 @@ class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: kaderList.length,
|
itemCount: kaderList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
|
||||||
final kader = kaderList[index];
|
final kader = kaderList[index];
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
kader['name'] ?? '-',
|
kader['name'] ?? '-',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
|
@ -182,30 +151,21 @@ class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
Text('Email: ${kader['email'] ?? '-'}'),
|
Text('Email: ${kader['email'] ?? '-'}'),
|
||||||
|
|
||||||
Text('RT/RW: ${kader['rtrw'] ?? '-'}'),
|
Text('RT/RW: ${kader['rtrw'] ?? '-'}'),
|
||||||
|
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
Text('Alamat: ${kader['address'] ?? '-'}'),
|
Text('Alamat: ${kader['address'] ?? '-'}'),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
|
|
||||||
onPressed: () => approveKader(kader['id']),
|
onPressed: () => approveKader(kader['id']),
|
||||||
|
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Terima',
|
'Terima',
|
||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
|
|
@ -220,41 +180,35 @@ class _VerifikasiKaderPageState extends State<VerifikasiKaderPage> {
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
|
|
||||||
onPressed: () => rejectKader(kader['id']),
|
onPressed: () => rejectKader(kader['id']),
|
||||||
|
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Tolak',
|
'Tolak',
|
||||||
style: TextStyle(color: Colors.white),
|
style: TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Verifikasi Kader'),
|
title: const Text(
|
||||||
|
'Verifikasi Kader',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
|
||||||
|
),
|
||||||
|
|
||||||
backgroundColor: AppColors.button,
|
backgroundColor: AppColors.button,
|
||||||
),
|
),
|
||||||
|
|
||||||
body: buildKaderList(),
|
body: buildKaderList(),
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/images/logo_petugas.png
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue