412 lines
14 KiB
Dart
412 lines
14 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class TambahIbuPage extends StatefulWidget {
|
|
const TambahIbuPage({super.key});
|
|
|
|
@override
|
|
State<TambahIbuPage> createState() => _TambahIbuPageState();
|
|
}
|
|
|
|
class _TambahIbuPageState extends State<TambahIbuPage> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
// ================= CONTROLLER =================
|
|
final nikC = TextEditingController();
|
|
final noKkC = TextEditingController();
|
|
final namaC = TextEditingController();
|
|
final namaSuamiC = TextEditingController();
|
|
final tempatLahirC = TextEditingController();
|
|
final tglC = TextEditingController();
|
|
final pendidikanC = TextEditingController();
|
|
final pekerjaanC = TextEditingController();
|
|
final alamatC = TextEditingController();
|
|
final hpC = TextEditingController();
|
|
final emailC = TextEditingController();
|
|
final passwordC = TextEditingController();
|
|
|
|
final desaC = TextEditingController();
|
|
final dusunC = TextEditingController();
|
|
|
|
bool _isSaving = false;
|
|
String? _emailError; // Menyimpan pesan error email dari server
|
|
|
|
String? selectedGolDarah;
|
|
String? selectedAgama;
|
|
String? selectedDesa;
|
|
String? selectedDusun;
|
|
String? currentKaderId;
|
|
|
|
bool isKader = false;
|
|
|
|
final List<String> golDarahList = const ['A', 'B', 'AB', 'O', '-'];
|
|
final List<String> agamaList = const [
|
|
'Islam',
|
|
'Kristen',
|
|
'Katolik',
|
|
'Hindu',
|
|
'Buddha',
|
|
'Konghucu'
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_checkUserRole();
|
|
|
|
nikC.addListener(() {
|
|
String nikValue = nikC.text.trim();
|
|
if (nikValue.length >= 6) {
|
|
passwordC.text = nikValue.substring(nikValue.length - 6);
|
|
} else {
|
|
passwordC.text = "";
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
nikC.dispose();
|
|
noKkC.dispose();
|
|
namaC.dispose();
|
|
namaSuamiC.dispose();
|
|
tempatLahirC.dispose();
|
|
tglC.dispose();
|
|
pendidikanC.dispose();
|
|
pekerjaanC.dispose();
|
|
alamatC.dispose();
|
|
hpC.dispose();
|
|
emailC.dispose();
|
|
passwordC.dispose();
|
|
desaC.dispose();
|
|
dusunC.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _checkUserRole() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final role = prefs.getString('role');
|
|
final sessionDesaId = prefs.get('desa_id')?.toString();
|
|
final sessionDusunId = prefs.get('dusun_id')?.toString();
|
|
final sessionIdUser = prefs.getString('id_user');
|
|
final sessionNamaDesa = prefs.getString('nama_desa') ?? "";
|
|
final sessionNamaDusun = prefs.getString('nama_dusun') ?? "";
|
|
|
|
if (role == 'kader' && sessionDusunId != null) {
|
|
if (mounted) {
|
|
setState(() {
|
|
isKader = true;
|
|
selectedDesa = sessionDesaId;
|
|
selectedDusun = sessionDusunId;
|
|
currentKaderId = sessionIdUser;
|
|
desaC.text = sessionNamaDesa;
|
|
dusunC.text = sessionNamaDusun;
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint("Error Check Role: $e");
|
|
}
|
|
}
|
|
|
|
dynamic _safeJson(String body) {
|
|
try {
|
|
if (body.trim().startsWith("<")) return null;
|
|
return json.decode(body);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _simpan() async {
|
|
// Reset status error email sebelum mencoba simpan
|
|
setState(() => _emailError = null);
|
|
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
if (selectedDesa == null || selectedDusun == null) {
|
|
_showMsg("Wilayah tidak terdeteksi");
|
|
return;
|
|
}
|
|
|
|
if (currentKaderId == null) {
|
|
_showMsg("Sesi login Kader tidak valid, silakan login ulang");
|
|
return;
|
|
}
|
|
|
|
setState(() => _isSaving = true);
|
|
|
|
try {
|
|
final res = await http.post(
|
|
Uri.parse("http://ta.myhost.id/E31230549/mposyandu_api/ibu/tambah_ibu.php"),
|
|
body: {
|
|
"nik": nikC.text.trim(),
|
|
"no_kk": noKkC.text.trim(),
|
|
"nama": namaC.text.trim(),
|
|
"nama_suami": namaSuamiC.text.trim(),
|
|
"tempat_lahir": tempatLahirC.text.trim(),
|
|
"tanggal_lahir": tglC.text.trim(),
|
|
"golongan_darah": selectedGolDarah ?? "-",
|
|
"pendidikan": pendidikanC.text.trim(),
|
|
"pekerjaan": pekerjaanC.text.trim(),
|
|
"agama": selectedAgama ?? "",
|
|
"no_hp": hpC.text.trim(),
|
|
"email": emailC.text.trim(),
|
|
"password": passwordC.text.trim(),
|
|
"desa_id": selectedDesa,
|
|
"dusun_id": selectedDusun,
|
|
"alamat_detail": alamatC.text.trim(),
|
|
"kader_id": currentKaderId,
|
|
},
|
|
);
|
|
|
|
final data = _safeJson(res.body);
|
|
if (data != null && data["success"] == true) {
|
|
_showMsg("Data berhasil disimpan");
|
|
if (mounted) Navigator.pop(context, true);
|
|
} else {
|
|
String serverMsg = data?["message"] ?? "Gagal menyimpan";
|
|
|
|
// Logika pengecekan email terdaftar
|
|
if (serverMsg.toLowerCase().contains("email")) {
|
|
setState(() => _emailError = "Email sudah terdaftar");
|
|
} else {
|
|
_showMsg(serverMsg);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
_showMsg("Gagal terhubung ke server");
|
|
}
|
|
|
|
if (mounted) setState(() => _isSaving = false);
|
|
}
|
|
|
|
void _showMsg(String msg) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(msg, style: GoogleFonts.poppins(fontSize: 12))),
|
|
);
|
|
}
|
|
|
|
Future<void> _pickDate() async {
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
firstDate: DateTime(1950),
|
|
lastDate: DateTime.now(),
|
|
initialDate: DateTime(2000),
|
|
);
|
|
if (picked != null) {
|
|
tglC.text = picked.toIso8601String().split("T").first;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text("",
|
|
style:
|
|
GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600)),
|
|
backgroundColor: Colors.blue,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
backgroundColor: const Color(0xfff4f6fb),
|
|
body: Center(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 900),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
blurRadius: 16,
|
|
color: Colors.black12,
|
|
offset: Offset(0, 8))
|
|
],
|
|
),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
children: [
|
|
Text("Tambah Data Ibu",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 24),
|
|
Wrap(
|
|
spacing: 16,
|
|
runSpacing: 16,
|
|
children: [
|
|
_field("NIK", nikC,
|
|
keyboard: TextInputType.number, isNik: true),
|
|
_field("No KK", noKkC,
|
|
keyboard: TextInputType.number, isKk: true),
|
|
_field("Nama Lengkap", namaC),
|
|
_field("Nama Suami", namaSuamiC),
|
|
_field("Tempat Lahir", tempatLahirC),
|
|
SizedBox(
|
|
width: 260,
|
|
child: TextFormField(
|
|
controller: tglC,
|
|
readOnly: true,
|
|
onTap: _pickDate,
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
decoration: _decoration("Tanggal Lahir").copyWith(
|
|
suffixIcon: const Icon(Icons.calendar_today,
|
|
size: 18, color: Colors.black),
|
|
),
|
|
validator: (v) => v!.isEmpty ? "Wajib diisi" : null,
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: 260,
|
|
child: DropdownButtonFormField<String>(
|
|
value: selectedGolDarah,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12, color: Colors.black),
|
|
decoration: _decoration("Golongan Darah"),
|
|
items: golDarahList
|
|
.map((e) =>
|
|
DropdownMenuItem(value: e, child: Text(e)))
|
|
.toList(),
|
|
onChanged: (v) =>
|
|
setState(() => selectedGolDarah = v),
|
|
validator: (v) =>
|
|
v == null ? "Wajib dipilih" : null,
|
|
),
|
|
),
|
|
_field("Pendidikan", pendidikanC),
|
|
_field("Pekerjaan", pekerjaanC),
|
|
SizedBox(
|
|
width: 260,
|
|
child: DropdownButtonFormField<String>(
|
|
value: selectedAgama,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12, color: Colors.black),
|
|
decoration: _decoration("Agama"),
|
|
items: agamaList
|
|
.map((e) =>
|
|
DropdownMenuItem(value: e, child: Text(e)))
|
|
.toList(),
|
|
onChanged: (v) => setState(() => selectedAgama = v),
|
|
validator: (v) =>
|
|
v == null ? "Agama wajib dipilih" : null,
|
|
),
|
|
),
|
|
_field("Desa", desaC, readOnly: true),
|
|
_field("Dusun", dusunC, readOnly: true),
|
|
_field("No HP", hpC,
|
|
keyboard: TextInputType.phone, isPhone: true),
|
|
_field("Email", emailC,
|
|
keyboard: TextInputType.emailAddress,
|
|
isEmail: true),
|
|
SizedBox(
|
|
width: 260,
|
|
child: TextFormField(
|
|
controller: passwordC,
|
|
obscureText: true,
|
|
readOnly: true,
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
decoration:
|
|
_decoration("Password akan terisi otomatis"),
|
|
validator: (v) => (v == null || v.isEmpty)
|
|
? "Password belum terisi (isi NIK dulu)"
|
|
: null,
|
|
),
|
|
),
|
|
_field("Alamat Detail", alamatC),
|
|
],
|
|
),
|
|
const SizedBox(height: 32),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: OutlinedButton(
|
|
onPressed: _isSaving ? null : _simpan,
|
|
style: OutlinedButton.styleFrom(
|
|
side: const BorderSide(color: Colors.blue, width: 2),
|
|
shape: const StadiumBorder(),
|
|
),
|
|
child: _isSaving
|
|
? const Center(
|
|
child: SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2, color: Colors.blue)),
|
|
)
|
|
: Text("Simpan Data Ibu",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue)),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _field(String label, TextEditingController c,
|
|
{TextInputType keyboard = TextInputType.text,
|
|
bool isNik = false,
|
|
bool isKk = false,
|
|
bool isPhone = false,
|
|
bool isEmail = false,
|
|
bool readOnly = false}) {
|
|
return SizedBox(
|
|
width: 260,
|
|
child: TextFormField(
|
|
controller: c,
|
|
keyboardType: keyboard,
|
|
readOnly: readOnly,
|
|
onChanged: (v) {
|
|
// Menghilangkan pesan error saat user mulai mengetik ulang di kolom email
|
|
if (isEmail && _emailError != null) {
|
|
setState(() => _emailError = null);
|
|
}
|
|
},
|
|
inputFormatters: (isNik || isKk || isPhone)
|
|
? [FilteringTextInputFormatter.digitsOnly]
|
|
: null,
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
decoration: _decoration(label).copyWith(
|
|
fillColor: readOnly ? Colors.grey[100] : Colors.white,
|
|
// Tampilan teks merah di bawah kolom jika error email ditemukan
|
|
errorText: isEmail ? _emailError : null,
|
|
errorStyle: GoogleFonts.poppins(color: Colors.red, fontSize: 10),
|
|
),
|
|
validator: (v) {
|
|
if (v == null || v.isEmpty) return "$label wajib diisi";
|
|
if ((isNik || isKk) && v.length != 16) return "Harus 16 digit";
|
|
if (isPhone && (v.length < 10 || v.length > 13)) return "Digit 10-13";
|
|
return null;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
InputDecoration _decoration(String label) {
|
|
return InputDecoration(
|
|
filled: true,
|
|
fillColor: Colors.white,
|
|
labelText: label,
|
|
labelStyle: GoogleFonts.poppins(fontSize: 12),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
);
|
|
}
|
|
}
|