add detailuser file

This commit is contained in:
2025-10-09 22:53:06 +07:00
parent 25e31c58bd
commit 823492a381
2 changed files with 543 additions and 56 deletions

View File

@@ -0,0 +1,472 @@
import React, { useEffect, useState } from 'react';
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd';
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
import { createUser, updateUser } from '../../../api/user';
const { Text } = Typography;
const { Option } = Select;
const DetailUser = (props) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const defaultData = {
user_id: '',
user_name: '',
user_fullname: '',
user_email: '',
user_phone: '',
role_id: null,
is_active: true,
password: '',
confirmPassword: '',
};
const [FormData, setFormData] = useState(defaultData);
const [errors, setErrors] = useState({});
const handleCancel = () => {
props.setSelectedData(null);
props.setActionMode('list');
setFormData(defaultData);
setErrors({});
};
const validatePhone = (phone) => {
const phoneRegex = /^(?:\+62|0)8\d{7,10}$/;
return phoneRegex.test(phone);
};
const validatePassword = (password) => {
if (!password) return 'Password wajib diisi';
if (password.length < 8) return 'Password minimal 8 karakter';
if (!/[A-Z]/.test(password)) return 'Password harus ada huruf besar';
if (!/[a-z]/.test(password)) return 'Password harus ada huruf kecil';
if (!/\d/.test(password)) return 'Password harus ada angka';
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) return 'Password harus ada karakter spesial';
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)';
}
// 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';
}
}
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;
}
const payload = {
fullname: FormData.user_fullname,
name: FormData.user_name,
email: FormData.user_email,
phone: phone,
role_id: FormData.role_id || null,
};
// Add password for new user (create mode)
if (!FormData.user_id) {
payload.password = FormData.password;
// Don't send confirmPassword, is_sa, is_active for create
} else {
// Only send is_active for update mode (is_sa is immutable)
payload.is_active = FormData.is_active ? 1 : 0;
}
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)) {
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,
});
// Clear error for this field
if (errors[name]) {
setErrors({
...errors,
[name]: null,
});
}
};
const handleSelectChange = (value) => {
setFormData({
...FormData,
role_id: value,
});
};
const handleSwitchChange = (name, checked) => {
setFormData({
...FormData,
[name]: checked,
});
};
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: '',
});
} else {
setFormData(defaultData);
}
setErrors({});
}
}, [props.showModal]);
return (
<Modal
title={`${
props.actionMode === 'add'
? 'Tambah'
: props.actionMode === 'preview'
? 'Preview'
: 'Edit'
} User`}
open={props.showModal}
onCancel={handleCancel}
footer={[
<React.Fragment key="modal-footer">
<ConfigProvider
theme={{
token: { colorBgContainer: '#E9F6EF' },
components: {
Button: {
defaultBg: 'white',
defaultColor: '#23A55A',
defaultBorderColor: '#23A55A',
defaultHoverColor: '#23A55A',
defaultHoverBorderColor: '#23A55A',
},
},
}}
>
<Button onClick={handleCancel}>Batal</Button>
</ConfigProvider>
<ConfigProvider
theme={{
token: {
colorBgContainer: '#209652',
},
components: {
Button: {
defaultBg: '#23a55a',
defaultColor: '#FFFFFF',
defaultBorderColor: '#23a55a',
defaultHoverColor: '#FFFFFF',
defaultHoverBorderColor: '#23a55a',
},
},
}}
>
{!props.readOnly && (
<Button loading={confirmLoading} onClick={handleSave}>
Simpan
</Button>
)}
</ConfigProvider>
</React.Fragment>,
]}
width={600}
>
{FormData && (
<div>
<div hidden>
<Text strong>User ID</Text>
<Input name="user_id" value={FormData.user_id} disabled />
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Nama Lengkap</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input
name="user_fullname"
value={FormData.user_fullname}
onChange={handleInputChange}
placeholder="Masukkan nama lengkap"
readOnly={props.readOnly}
status={errors.user_fullname ? 'error' : ''}
/>
{errors.user_fullname && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.user_fullname}
</Text>
)}
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Username</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input
name="user_name"
value={FormData.user_name}
onChange={handleInputChange}
placeholder="Masukkan username"
readOnly={props.readOnly || FormData.user_id !== ''}
disabled={FormData.user_id !== ''}
status={errors.user_name ? 'error' : ''}
/>
{errors.user_name && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.user_name}
</Text>
)}
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Email</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input
name="user_email"
value={FormData.user_email}
onChange={handleInputChange}
placeholder="Masukkan email"
readOnly={props.readOnly}
status={errors.user_email ? 'error' : ''}
/>
{errors.user_email && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.user_email}
</Text>
)}
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Nomor WhatsApp</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input
name="user_phone"
value={FormData.user_phone}
onChange={handleInputChange}
placeholder="08xxxxxxxxxx atau +628xxxxxxxxxx"
readOnly={props.readOnly}
status={errors.user_phone ? 'error' : ''}
/>
{errors.user_phone && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.user_phone}
</Text>
)}
</div>
{!FormData.user_id && (
<>
<div style={{ marginBottom: 12 }}>
<Text strong>Password</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input.Password
name="password"
value={FormData.password}
onChange={handleInputChange}
placeholder="Masukkan password"
readOnly={props.readOnly}
status={errors.password ? 'error' : ''}
/>
{errors.password && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.password}
</Text>
)}
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Konfirmasi Password</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input.Password
name="confirmPassword"
value={FormData.confirmPassword}
onChange={handleInputChange}
placeholder="Konfirmasi password"
readOnly={props.readOnly}
status={errors.confirmPassword ? 'error' : ''}
/>
{errors.confirmPassword && (
<Text style={{ color: 'red', fontSize: '12px' }}>
{errors.confirmPassword}
</Text>
)}
</div>
</>
)}
<Divider style={{ margin: '12px 0' }} />
<div style={{ marginBottom: 12 }}>
<Text strong>Role</Text>
<Select
value={FormData.role_id}
onChange={handleSelectChange}
disabled={props.readOnly}
style={{ width: '100%' }}
placeholder="Pilih role"
allowClear
>
<Option value={1}>Administrator</Option>
<Option value={2}>Operator</Option>
<Option value={3}>Engineer</Option>
<Option value={4}>Guest</Option>
</Select>
</div>
{FormData.user_id && (
<div style={{ marginBottom: 12 }}>
<div>
<Text strong>Status Aktif</Text>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: '8px',
}}
>
<div style={{ marginRight: '8px' }}>
<Switch
disabled={props.readOnly}
style={{
backgroundColor: FormData.is_active
? '#23A55A'
: '#bfbfbf',
}}
checked={FormData.is_active}
onChange={(checked) =>
handleSwitchChange('is_active', checked)
}
/>
</div>
<div>
<Text>{FormData.is_active ? 'Aktif' : 'Nonaktif'}</Text>
</div>
</div>
</div>
)}
</div>
)}
</Modal>
);
};
export default DetailUser;