lavoce #2
472
src/pages/user/component/DetailUser.jsx
Normal file
472
src/pages/user/component/DetailUser.jsx
Normal 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;
|
||||
@@ -13,6 +13,7 @@ import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Glo
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { deleteUser, getAllUser, approveUser } from '../../../api/user';
|
||||
import TableList from '../../../components/Global/TableList';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveDialog) => [
|
||||
{
|
||||
@@ -42,17 +43,20 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD
|
||||
},
|
||||
{
|
||||
title: 'Level',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
dataIndex: 'role_level',
|
||||
key: 'role_level',
|
||||
width: '8%',
|
||||
align: 'center',
|
||||
render: (role_level) => role_level || '-',
|
||||
},
|
||||
{
|
||||
title: 'Nama Role',
|
||||
dataIndex: 'role_name',
|
||||
key: 'role_name',
|
||||
width: '12%',
|
||||
render: (_, { role_name }) => (
|
||||
render: (_, { role_name }) => {
|
||||
if (!role_name) return <Tag color={'default'}>Belum Ada Role</Tag>;
|
||||
return (
|
||||
<>
|
||||
{role_name === 'administrator' && (
|
||||
<Tag color={'purple'} key={'role'}>
|
||||
@@ -75,38 +79,38 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
title: 'Status Approval',
|
||||
dataIndex: 'is_approve',
|
||||
key: 'is_approve',
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
render: (_, { status }) => (
|
||||
<>
|
||||
{status === 'active' && (
|
||||
render: (_, { is_approve, is_active }) => {
|
||||
// Status approval
|
||||
if (is_approve === false || is_approve === 0) {
|
||||
return (
|
||||
<Tag color={'orange'} key={'status'}>
|
||||
Pending Approval
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
// Jika sudah approve, cek active/inactive
|
||||
if (is_active === true || is_active === 1) {
|
||||
return (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Active
|
||||
</Tag>
|
||||
)}
|
||||
{status === 'pending' && (
|
||||
<Tag color={'orange'} key={'status'}>
|
||||
Pending
|
||||
</Tag>
|
||||
)}
|
||||
{status === 'inactive' && (
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tag color={'default'} key={'status'}>
|
||||
Inactive
|
||||
</Tag>
|
||||
)}
|
||||
{status === 'rejected' && (
|
||||
<Tag color={'red'} key={'status'}>
|
||||
Rejected
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
@@ -121,7 +125,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD
|
||||
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
/>
|
||||
{record.status === 'pending' && (
|
||||
{(record.is_approve === false || record.is_approve === 0) && (
|
||||
<Button
|
||||
type="text"
|
||||
style={{ borderColor: '#52c41a' }}
|
||||
@@ -203,12 +207,22 @@ const ListUser = memo(function ListUser(props) {
|
||||
};
|
||||
|
||||
const showApproveDialog = (param) => {
|
||||
NotifConfirmDialog({
|
||||
Swal.fire({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi Approve User',
|
||||
message: 'Apakah anda yakin approve user "' + param.fullname + '" ?',
|
||||
onConfirm: () => handleApprove(param.user_id),
|
||||
onCancel: () => props.setSelectedData(null),
|
||||
text: 'Apakah anda yakin approve user "' + param.user_fullname + '" ?',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: '#d33',
|
||||
cancelButtonText: 'Batal',
|
||||
confirmButtonColor: '#23A55A',
|
||||
confirmButtonText: 'Approve',
|
||||
reverseButtons: true,
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
handleApprove(param.user_id);
|
||||
} else if (result.dismiss) {
|
||||
props.setSelectedData(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -216,7 +230,7 @@ const ListUser = memo(function ListUser(props) {
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi',
|
||||
message: 'Apakah anda yakin hapus user "' + param.fullname + '" ?',
|
||||
message: 'Apakah anda yakin hapus user "' + param.user_fullname + '" ?',
|
||||
onConfirm: () => handleDelete(param.user_id),
|
||||
onCancel: () => props.setSelectedData(null),
|
||||
});
|
||||
@@ -340,6 +354,7 @@ const ListUser = memo(function ListUser(props) {
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user