TKK_E32230859/lib/app/widgets/modal_patient_add_edit.dart

493 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../modules/profile/controllers/profile_controller.dart';
class ModalPatientAddEdit extends StatefulWidget {
final Patient? patient;
final bool isEdit;
const ModalPatientAddEdit({super.key, this.patient, this.isEdit = false});
@override
State<ModalPatientAddEdit> createState() => _ModalPatientAddEditState();
}
class _ModalPatientAddEditState extends State<ModalPatientAddEdit> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _nameController;
late TextEditingController _roomController;
late TextEditingController _deviceIdController;
late TextEditingController _guardianController;
late TextEditingController _emailController;
late TextEditingController _passwordController;
bool _isSaving = false;
bool _obscurePassword = true;
@override
void initState() {
super.initState();
_initControllers();
}
void _initControllers() {
_nameController = TextEditingController(text: widget.patient?.name ?? '');
_roomController = TextEditingController(text: widget.patient?.room ?? '');
_deviceIdController = TextEditingController(text: widget.patient?.deviceId ?? '');
_guardianController = TextEditingController(text: widget.patient?.guardian ?? '');
_emailController = TextEditingController(text: widget.patient?.email ?? '');
_passwordController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
_roomController.dispose();
_deviceIdController.dispose();
_guardianController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 40),
child: Container(
constraints: const BoxConstraints(maxWidth: 500),
decoration: _dialogDecoration(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
_buildForm(),
_buildFooter(),
],
),
),
);
}
BoxDecoration _dialogDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF0091EA), Color(0xFF0277BD)],
),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
child: Row(
children: [
_buildHeaderIcon(),
const SizedBox(width: 16),
Expanded(
child: Text(
widget.isEdit ? 'Edit Pasien' : 'Tambah Pasien Baru',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
IconButton(
onPressed: _isSaving ? null : () => Get.back(),
icon: const Icon(Icons.close, color: Colors.white),
splashRadius: 20,
),
],
),
);
}
Widget _buildHeaderIcon() {
return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
widget.isEdit ? Icons.edit : Icons.person_add,
color: Colors.white,
size: 24,
),
);
}
Widget _buildForm() {
return Flexible(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildNameField(),
const SizedBox(height: 20),
_buildGuardianField(),
const SizedBox(height: 20),
_buildEmailField(),
if (!widget.isEdit) ...[
const SizedBox(height: 20),
_buildPasswordField(),
],
const SizedBox(height: 20),
_buildRoomField(),
const SizedBox(height: 20),
_buildDeviceIdField(),
],
),
),
),
);
}
Widget _buildNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Nama Pasien', Icons.person),
const SizedBox(height: 8),
_buildTextField(
controller: _nameController,
hint: 'Contoh: Mrs. Shinta',
icon: Icons.person_outline,
validator: (value) => value?.isEmpty ?? true ? 'Wajib diisi' : null,
),
],
);
}
Widget _buildGuardianField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Nama Wali', Icons.family_restroom),
const SizedBox(height: 8),
_buildTextField(
controller: _guardianController,
hint: 'Contoh: Budi Santoso',
icon: Icons.family_restroom_outlined,
validator: (value) => value?.isEmpty ?? true ? 'Wajib diisi' : null,
),
],
);
}
Widget _buildEmailField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Email Wali', Icons.email),
const SizedBox(height: 8),
_buildTextField(
controller: _emailController,
hint: 'Contoh: wali@example.com',
icon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
enabled: !widget.isEdit,
validator: (value) {
if (widget.isEdit) return null;
if (value?.isEmpty ?? true) return 'Wajib diisi';
if (!GetUtils.isEmail(value!)) return 'Email tidak valid';
return null;
},
),
if (widget.isEdit)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Email tidak dapat diubah',
style: TextStyle(
fontSize: 12,
color: Colors.orange[700],
fontStyle: FontStyle.italic,
),
),
),
],
);
}
Widget _buildPasswordField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Password', Icons.lock),
const SizedBox(height: 8),
_buildTextField(
controller: _passwordController,
hint: 'Minimal 6 karakter',
icon: Icons.lock_outline,
obscureText: _obscurePassword,
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
color: Colors.grey[600],
),
onPressed: () => setState(() => _obscurePassword = !_obscurePassword),
),
validator: (value) {
if (value?.isEmpty ?? true) return 'Wajib diisi';
if (value!.length < 6) return 'Minimal 6 karakter';
return null;
},
),
],
);
}
Widget _buildRoomField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Ruangan', Icons.meeting_room),
const SizedBox(height: 8),
_buildTextField(
controller: _roomController,
hint: 'Contoh: Ruang Mawar',
icon: Icons.meeting_room_outlined,
validator: (value) => value?.isEmpty ?? true ? 'Wajib diisi' : null,
),
const SizedBox(height: 4),
Text(
'ID otomatis: "Ruang Mawar" → ruang_mawar',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
);
}
Widget _buildDeviceIdField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabel('Device ID', Icons.devices),
const SizedBox(height: 8),
_buildTextField(
controller: _deviceIdController,
hint: 'Contoh: INF-ICU-001',
icon: Icons.router_outlined,
validator: (value) {
if (value?.isEmpty ?? true) return 'Wajib diisi';
if (!RegExp(r'^[A-Z]+-[A-Z]+-\d{3}$').hasMatch(value!)) {
return 'Format: INF-ROOM-XXX';
}
return null;
},
),
const SizedBox(height: 4),
Text(
'Format: INF-ROOM-XXX',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
);
}
Widget _buildFooter() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
child: Row(
children: [
Expanded(child: _buildCancelButton()),
const SizedBox(width: 12),
Expanded(child: _buildSaveButton()),
],
),
);
}
Widget _buildCancelButton() {
return OutlinedButton(
onPressed: _isSaving ? null : () => Get.back(),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Colors.grey.shade300, width: 2),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: Text(
'Batal',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: _isSaving ? Colors.grey[400] : Colors.grey[700],
),
),
);
}
Widget _buildSaveButton() {
return Container(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0091EA), Color(0xFF0277BD)],
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0xFF0091EA).withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: _isSaving ? null : _handleSave,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: _isSaving
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
)
: Text(
widget.isEdit ? 'Simpan' : 'Tambah',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
);
}
Widget _buildLabel(String text, IconData icon) {
return Row(
children: [
Icon(icon, size: 20, color: const Color(0xFF0091EA)),
const SizedBox(width: 8),
Text(
text,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF424242),
),
),
],
);
}
Widget _buildTextField({
required TextEditingController controller,
required String hint,
required IconData icon,
String? Function(String?)? validator,
bool obscureText = false,
Widget? suffixIcon,
TextInputType? keyboardType,
bool enabled = true,
}) {
return TextFormField(
controller: controller,
validator: validator,
enabled: !_isSaving && enabled,
obscureText: obscureText,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14),
filled: true,
fillColor: enabled ? Colors.grey[50] : Colors.grey[200],
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF0091EA), width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.redAccent),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.redAccent, width: 2),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
);
}
Future<void> _handleSave() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isSaving = true);
final controller = Get.find<ProfileController>();
try {
if (widget.isEdit && widget.patient != null) {
await controller.updatePatient(
patientId: widget.patient!.id,
patientName: _nameController.text.trim(),
guardianName: _guardianController.text.trim(),
roomName: _roomController.text.trim(),
deviceId: _deviceIdController.text.trim(),
email: _emailController.text.trim(),
newPassword: _passwordController.text.isNotEmpty
? _passwordController.text.trim()
: null,
);
} else {
await controller.addPatient(
patientName: _nameController.text.trim(),
guardianName: _guardianController.text.trim(),
roomName: _roomController.text.trim(),
deviceId: _deviceIdController.text.trim(),
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
}
} catch (e) {
setState(() => _isSaving = false);
}
}
}