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