import React, { useEffect, useState } from 'react'; import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd'; import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; import { createUser, updateUser, changePassword } from '../../../api/user'; import { getAllRole } from '../../../api/role'; const { Text } = Typography; const { Option } = Select; const DetailUser = (props) => { const [confirmLoading, setConfirmLoading] = useState(false); const [roleList, setRoleList] = useState([]); const [loadingRoles, setLoadingRoles] = useState(false); const defaultData = { user_id: '', user_name: '', user_fullname: '', user_email: '', user_phone: '', role_id: null, is_active: true, password: '', confirmPassword: '', newPassword: '', confirmNewPassword: '', }; const [FormData, setFormData] = useState(defaultData); const [originalEmail, setOriginalEmail] = useState(''); // Track original email const [errors, setErrors] = useState({}); // Password requirements state const [passwordRequirements, setPasswordRequirements] = useState({ minLength: false, hasUppercase: false, hasLowercase: false, hasNumber: false, hasSpecialChar: false, }); // New password requirements state for edit mode const [newPasswordRequirements, setNewPasswordRequirements] = useState({ minLength: false, hasUppercase: false, hasLowercase: false, hasNumber: false, hasSpecialChar: false, }); const handleCancel = () => { props.setSelectedData(null); props.setActionMode('list'); setFormData(defaultData); setErrors({}); setPasswordRequirements({ minLength: false, hasUppercase: false, hasLowercase: false, hasNumber: false, hasSpecialChar: false, }); }; // Check password requirements const checkPasswordRequirements = (password) => { setPasswordRequirements({ minLength: password.length >= 8, hasUppercase: /[A-Z]/.test(password), hasLowercase: /[a-z]/.test(password), hasNumber: /\d/.test(password), hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(password), }); }; // Check new password requirements for edit mode const checkNewPasswordRequirements = (password) => { setNewPasswordRequirements({ minLength: password.length >= 8, hasUppercase: /[A-Z]/.test(password), hasLowercase: /[a-z]/.test(password), hasNumber: /\d/.test(password), hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(password), }); }; const validatePhone = (phone) => { const phoneRegex = /^(?:\+62|0)8\d{7,10}$/; return phoneRegex.test(phone); }; const validatePassword = (password) => { if (!password) return 'Password wajib diisi'; // Must be at least 8 characters long if (password.length < 8) { return 'Password must be at least 8 characters long'; } // Must contain at least one uppercase letter if (!/[A-Z]/.test(password)) { return 'Password must contain at least one uppercase letter'; } // Must contain at least one lowercase letter if (!/[a-z]/.test(password)) { return 'Password must contain at least one lowercase letter'; } // Must contain at least one number if (!/\d/.test(password)) { return 'Password must contain at least one number'; } // Must contain at least one special character if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { return 'Password must contain at least one special character'; } return null; }; const validateForm = () => { const newErrors = {}; // Required fields validation if (!FormData.user_fullname) { newErrors.user_fullname = 'Nama lengkap wajib diisi'; } else if (FormData.user_fullname.length < 3) { newErrors.user_fullname = 'Nama minimal 3 karakter'; } else if (FormData.user_fullname.length > 100) { newErrors.user_fullname = 'Nama maksimal 100 karakter'; } if (!FormData.user_name) { newErrors.user_name = 'Username wajib diisi'; } else if (FormData.user_name.length < 3) { newErrors.user_name = 'Username minimal 3 karakter'; } else if (FormData.user_name.length > 50) { newErrors.user_name = 'Username maksimal 50 karakter'; } else if (!/^[a-zA-Z0-9]+$/.test(FormData.user_name)) { newErrors.user_name = 'Username hanya boleh huruf dan angka'; } if (!FormData.user_email) { newErrors.user_email = 'Email wajib diisi'; } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(FormData.user_email)) { newErrors.user_email = 'Format email tidak valid'; } if (!FormData.user_phone) { newErrors.user_phone = 'Nomor telepon wajib diisi'; } else if (!validatePhone(FormData.user_phone)) { newErrors.user_phone = 'Nomor harus format Indonesia (08xxxxxxxx atau +628xxxxxxxx)'; } // Role validation - make role required if (!FormData.role_id) { newErrors.role_id = 'Role wajib dipilih'; } // Password validation for add mode if (!FormData.user_id) { const passwordError = validatePassword(FormData.password); if (passwordError) { newErrors.password = passwordError; } if (!FormData.confirmPassword) { newErrors.confirmPassword = 'Konfirmasi password wajib diisi'; } else if (FormData.password !== FormData.confirmPassword) { newErrors.confirmPassword = 'Password tidak cocok'; } } // Password validation for edit mode (optional) if (FormData.user_id && FormData.newPassword) { const passwordError = validatePassword(FormData.newPassword); if (passwordError) { newErrors.newPassword = passwordError; } if (!FormData.confirmNewPassword) { newErrors.confirmNewPassword = 'Konfirmasi password wajib diisi'; } else if (FormData.newPassword !== FormData.confirmNewPassword) { newErrors.confirmNewPassword = 'Password tidak cocok'; } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSave = async () => { if (!validateForm()) { NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Mohon periksa kembali form Anda', }); return; } setConfirmLoading(true); // Format phone number let phone = FormData.user_phone; if (phone.startsWith('0')) { phone = '+62' + phone.slice(1); } else if (!phone.startsWith('+')) { phone = '+62' + phone; } // Backend expects field names with 'user_' prefix const payload = { user_fullname: FormData.user_fullname, user_phone: phone, }; // For update mode: only send email if it has changed if (FormData.user_id) { if (FormData.user_email !== originalEmail) { payload.user_email = FormData.user_email; } payload.is_active = FormData.is_active; } else { payload.user_email = FormData.user_email; } if (FormData.role_id) { payload.role_id = FormData.role_id; } // Add password and name for new user (create mode) if (!FormData.user_id) { payload.user_name = FormData.user_name; payload.user_password = FormData.password; } try { // console.log('Payload being sent:', payload); let response; if (!FormData.user_id) { response = await createUser(payload); } else { response = await updateUser(FormData.user_id, payload); } // console.log('Save User Response:', response); // Check if response is successful if (response && (response.statusCode === 200 || response.statusCode === 201)) { if (FormData.user_id && FormData.newPassword) { try { const passwordResponse = await changePassword( FormData.user_id, FormData.newPassword ); if (passwordResponse && passwordResponse.statusCode === 200) { NotifOk({ icon: 'success', title: 'Berhasil', message: `User "${ response.data?.user_fullname || FormData.user_fullname }" berhasil diubah dan password berhasil diperbarui.`, }); } else { NotifAlert({ icon: 'warning', title: 'Perhatian', message: `User berhasil diubah, namun gagal mengubah password: ${ passwordResponse?.message || 'Unknown error' }`, }); } } catch (passwordError) { console.error('Change Password Error:', passwordError); NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'User berhasil diubah, namun gagal mengubah password.', }); } } else { NotifOk({ icon: 'success', title: 'Berhasil', message: `User "${ response.data?.user_fullname || FormData.user_fullname }" berhasil ${FormData.user_id ? 'diubah' : 'ditambahkan'}.`, }); } props.setActionMode('list'); setFormData(defaultData); setErrors({}); } else { NotifAlert({ icon: 'error', title: 'Gagal', message: response?.message || 'Terjadi kesalahan saat menyimpan data.', }); } } catch (error) { console.error('Save User Error:', error); NotifAlert({ icon: 'error', title: 'Error', message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.', }); } setConfirmLoading(false); }; const handleInputChange = (e) => { const { name, value } = e.target; setFormData({ ...FormData, [name]: value, }); // Check password requirements on password change if (name === 'password') { checkPasswordRequirements(value); } // Check new password requirements on new password change (for edit mode) if (name === 'newPassword') { checkNewPasswordRequirements(value); } // Clear error for this field if (errors[name]) { setErrors({ ...errors, [name]: null, }); } }; const handleSelectChange = (value) => { setFormData({ ...FormData, role_id: value, }); // Clear role error when user selects a role if (errors.role_id) { setErrors({ ...errors, role_id: null, }); } }; const handleSwitchChange = (name, checked) => { setFormData({ ...FormData, [name]: checked, }); }; // Fetch all roles when component mounts or modal opens const fetchRoles = async () => { setLoadingRoles(true); try { // Create query params for fetching all roles const queryParams = new URLSearchParams({ page: 1, limit: 100, search: '', }); // console.log('Fetching roles with params:', queryParams.toString()); const response = await getAllRole(queryParams); // console.log('Fetched roles response:', response); // Handle different response structures if (response && response.data) { let roles = []; if (response.data.data && Array.isArray(response.data.data)) { roles = response.data.data; } else if (Array.isArray(response.data)) { roles = response.data; } else { // Add mock data as fallback for testing console.warn('Unexpected role data structure, using mock data'); roles = [ { role_id: 1, role_name: 'Admin', role_level: 1 }, { role_id: 2, role_name: 'Manager', role_level: 2 }, { role_id: 3, role_name: 'User', role_level: 3 }, ]; } setRoleList(roles); // console.log('Setting role list:', roles); } else { // Add mock data as fallback console.warn('No response data, using mock data'); const mockRoles = [ { role_id: 1, role_name: 'Admin', role_level: 1 }, { role_id: 2, role_name: 'Manager', role_level: 2 }, { role_id: 3, role_name: 'User', role_level: 3 }, ]; setRoleList(mockRoles); // console.log('Setting mock role list:', mockRoles); } } catch (error) { console.error('Error fetching roles:', error); // Add mock data as fallback on error const mockRoles = [ { role_id: 1, role_name: 'Admin', role_level: 1 }, { role_id: 2, role_name: 'Manager', role_level: 2 }, { role_id: 3, role_name: 'User', role_level: 3 }, ]; setRoleList(mockRoles); // console.log('Setting mock role list due to error:', mockRoles); // Only show error notification if we don't have fallback data if (process.env.NODE_ENV === 'development') { console.warn('Using mock role data due to API error'); } else { NotifAlert({ icon: 'error', title: 'Error', message: 'Gagal memuat daftar role, menggunakan data default', }); } } finally { setLoadingRoles(false); } }; useEffect(() => { const token = localStorage.getItem('token'); if (token) { if (props.selectedData != null) { // Format phone untuk display let displayPhone = props.selectedData.user_phone || ''; if (displayPhone.startsWith('+62')) { displayPhone = '0' + displayPhone.slice(3); } setFormData({ user_id: props.selectedData.user_id || '', user_name: props.selectedData.user_name || '', user_fullname: props.selectedData.user_fullname || '', user_email: props.selectedData.user_email || '', user_phone: displayPhone, role_id: props.selectedData.role_id || null, is_active: props.selectedData.is_active === 1 || props.selectedData.is_active === true, password: '', confirmPassword: '', }); // Store original email for comparison setOriginalEmail(props.selectedData.user_email || ''); } else { setFormData(defaultData); setOriginalEmail(''); } setErrors({}); // Fetch roles when modal opens fetchRoles(); } }, [props.showModal]); return ( {!props.readOnly && ( )} , ]} width={600} > {FormData && (
{FormData.user_id && (
Status Aktif
handleSwitchChange('is_active', checked) } />
{FormData.is_active ? 'Aktif' : 'Nonaktif'}
)}
Nama Lengkap * {errors.user_fullname && ( {errors.user_fullname} )}
Username * {errors.user_name && ( {errors.user_name} )}
Email * {errors.user_email && ( {errors.user_email} )}
Nomor WhatsApp * {errors.user_phone && ( {errors.user_phone} )}
{!FormData.user_id && ( <>
Password * {errors.password && ( {errors.password} )} {/* Password Requirements Indicator */}
Password harus memenuhi:
{passwordRequirements.minLength ? ( ) : ( )} Minimal 8 karakter
{passwordRequirements.hasUppercase ? ( ) : ( )} Minimal 1 huruf besar (A-Z)
{passwordRequirements.hasLowercase ? ( ) : ( )} Minimal 1 huruf kecil (a-z)
{passwordRequirements.hasNumber ? ( ) : ( )} Minimal 1 angka (0-9)
{passwordRequirements.hasSpecialChar ? ( ) : ( )} Minimal 1 karakter spesial (!@#$%^&*(),.?":{}|<>)
Konfirmasi Password * {errors.confirmPassword && ( {errors.confirmPassword} )}
)} {FormData.user_id && !props.readOnly && ( <> Ubah Password (Opsional)
Password Baru {errors.newPassword && ( {errors.newPassword} )} {FormData.newPassword && (
Password harus memenuhi:
{newPasswordRequirements.minLength ? ( ) : ( )} Minimal 8 karakter
{newPasswordRequirements.hasUppercase ? ( ) : ( )} Minimal 1 huruf besar (A-Z)
{newPasswordRequirements.hasLowercase ? ( ) : ( )} Minimal 1 huruf kecil (a-z)
{newPasswordRequirements.hasNumber ? ( ) : ( )} Minimal 1 angka (0-9)
{newPasswordRequirements.hasSpecialChar ? ( ) : ( )} Minimal 1 karakter spesial (!@#$%^&*(),.?":{}|<>)
)}
Konfirmasi Password Baru {errors.confirmNewPassword && ( {errors.confirmNewPassword} )}
)}
Role * {errors.role_id && ( {errors.role_id} )}
)}
); }; export default DetailUser;