pending reject handle
This commit is contained in:
@@ -156,6 +156,19 @@ const approveUser = async (user_id) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rejectUser = async (user_id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/${user_id}/reject`,
|
||||||
|
});
|
||||||
|
// Return full response with statusCode
|
||||||
|
return {
|
||||||
|
statusCode: response.statusCode || 200,
|
||||||
|
data: response.data,
|
||||||
|
message: response.message
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const changePassword = async (user_id, new_password) => {
|
const changePassword = async (user_id, new_password) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'put',
|
method: 'put',
|
||||||
@@ -175,4 +188,4 @@ const changePassword = async (user_id, new_password) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser, changePassword };
|
export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser, rejectUser, changePassword };
|
||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd';
|
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd';
|
||||||
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
|
||||||
import { createUser, updateUser } from '../../../api/user';
|
import { createUser, updateUser, changePassword } from '../../../api/user';
|
||||||
import { getAllRole } from '../../../api/role';
|
import { getAllRole } from '../../../api/role';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -23,6 +23,8 @@ const DetailUser = (props) => {
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const [FormData, setFormData] = useState(defaultData);
|
const [FormData, setFormData] = useState(defaultData);
|
||||||
@@ -38,6 +40,15 @@ const DetailUser = (props) => {
|
|||||||
hasSpecialChar: 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 = () => {
|
const handleCancel = () => {
|
||||||
props.setSelectedData(null);
|
props.setSelectedData(null);
|
||||||
props.setActionMode('list');
|
props.setActionMode('list');
|
||||||
@@ -63,6 +74,17 @@ const DetailUser = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 validatePhone = (phone) => {
|
||||||
const phoneRegex = /^(?:\+62|0)8\d{7,10}$/;
|
const phoneRegex = /^(?:\+62|0)8\d{7,10}$/;
|
||||||
return phoneRegex.test(phone);
|
return phoneRegex.test(phone);
|
||||||
@@ -147,6 +169,20 @@ const DetailUser = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
};
|
};
|
||||||
@@ -218,13 +254,48 @@ const DetailUser = (props) => {
|
|||||||
|
|
||||||
// Check if response is successful
|
// Check if response is successful
|
||||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
NotifOk({
|
// If in edit mode and newPassword is provided, change password
|
||||||
icon: 'success',
|
if (FormData.user_id && FormData.newPassword) {
|
||||||
title: 'Berhasil',
|
try {
|
||||||
message: `User "${
|
const passwordResponse = await changePassword(
|
||||||
response.data?.user_fullname || FormData.user_fullname
|
FormData.user_id,
|
||||||
}" berhasil ${FormData.user_id ? 'diubah' : 'ditambahkan'}.`,
|
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');
|
props.setActionMode('list');
|
||||||
setFormData(defaultData);
|
setFormData(defaultData);
|
||||||
@@ -260,6 +331,11 @@ const DetailUser = (props) => {
|
|||||||
checkPasswordRequirements(value);
|
checkPasswordRequirements(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check new password requirements on new password change (for edit mode)
|
||||||
|
if (name === 'newPassword') {
|
||||||
|
checkNewPasswordRequirements(value);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear error for this field
|
// Clear error for this field
|
||||||
if (errors[name]) {
|
if (errors[name]) {
|
||||||
setErrors({
|
setErrors({
|
||||||
@@ -408,6 +484,39 @@ const DetailUser = (props) => {
|
|||||||
<Text strong>User ID</Text>
|
<Text strong>User ID</Text>
|
||||||
<Input name="user_id" value={FormData.user_id} disabled />
|
<Input name="user_id" value={FormData.user_id} disabled />
|
||||||
</div>
|
</div>
|
||||||
|
{FormData.user_id && (
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Divider style={{ margin: '12px 0' }} />
|
||||||
|
<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 style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Nama Lengkap</Text>
|
<Text strong>Nama Lengkap</Text>
|
||||||
@@ -502,57 +611,198 @@ const DetailUser = (props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Password Requirements Indicator */}
|
{/* Password Requirements Indicator */}
|
||||||
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
|
<div
|
||||||
<Text style={{ fontSize: '12px', fontWeight: '500', color: '#666' }}>Password harus memenuhi:</Text>
|
style={{
|
||||||
|
marginTop: '8px',
|
||||||
|
padding: '8px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#666',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Password harus memenuhi:
|
||||||
|
</Text>
|
||||||
<div style={{ marginTop: '4px' }}>
|
<div style={{ marginTop: '4px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{passwordRequirements.minLength ? (
|
{passwordRequirements.minLength ? (
|
||||||
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontSize: '12px', color: passwordRequirements.minLength ? '#52c41a' : '#ff4d4f' }}>
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: passwordRequirements.minLength
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Minimal 8 karakter
|
Minimal 8 karakter
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{passwordRequirements.hasUppercase ? (
|
{passwordRequirements.hasUppercase ? (
|
||||||
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontSize: '12px', color: passwordRequirements.hasUppercase ? '#52c41a' : '#ff4d4f' }}>
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: passwordRequirements.hasUppercase
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Minimal 1 huruf besar (A-Z)
|
Minimal 1 huruf besar (A-Z)
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{passwordRequirements.hasLowercase ? (
|
{passwordRequirements.hasLowercase ? (
|
||||||
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontSize: '12px', color: passwordRequirements.hasLowercase ? '#52c41a' : '#ff4d4f' }}>
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: passwordRequirements.hasLowercase
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Minimal 1 huruf kecil (a-z)
|
Minimal 1 huruf kecil (a-z)
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{passwordRequirements.hasNumber ? (
|
{passwordRequirements.hasNumber ? (
|
||||||
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontSize: '12px', color: passwordRequirements.hasNumber ? '#52c41a' : '#ff4d4f' }}>
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: passwordRequirements.hasNumber
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Minimal 1 angka (0-9)
|
Minimal 1 angka (0-9)
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{passwordRequirements.hasSpecialChar ? (
|
{passwordRequirements.hasSpecialChar ? (
|
||||||
<CheckCircleFilled style={{ color: '#52c41a', fontSize: '14px', marginRight: '6px' }} />
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleFilled style={{ color: '#ff4d4f', fontSize: '14px', marginRight: '6px' }} />
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontSize: '12px', color: passwordRequirements.hasSpecialChar ? '#52c41a' : '#ff4d4f' }}>
|
<Text
|
||||||
Minimal 1 karakter spesial (!@#$%^&*(),.?":{}|<>)
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: passwordRequirements.hasSpecialChar
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 1 karakter spesial
|
||||||
|
(!@#$%^&*(),.?":{}|<>)
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -579,6 +829,243 @@ const DetailUser = (props) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{FormData.user_id && !props.readOnly && (
|
||||||
|
<>
|
||||||
|
<Divider style={{ margin: '12px 0' }}>Ubah Password (Opsional)</Divider>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Password Baru</Text>
|
||||||
|
<Input.Password
|
||||||
|
name="newPassword"
|
||||||
|
value={FormData.newPassword}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Kosongkan jika tidak ingin mengubah password"
|
||||||
|
status={errors.newPassword ? 'error' : ''}
|
||||||
|
/>
|
||||||
|
{errors.newPassword && (
|
||||||
|
<Text style={{ color: 'red', fontSize: '12px' }}>
|
||||||
|
{errors.newPassword}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{FormData.newPassword && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '8px',
|
||||||
|
padding: '8px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#666',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Password harus memenuhi:
|
||||||
|
</Text>
|
||||||
|
<div style={{ marginTop: '4px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newPasswordRequirements.minLength ? (
|
||||||
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: newPasswordRequirements.minLength
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 8 karakter
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newPasswordRequirements.hasUppercase ? (
|
||||||
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: newPasswordRequirements.hasUppercase
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 1 huruf besar (A-Z)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newPasswordRequirements.hasLowercase ? (
|
||||||
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: newPasswordRequirements.hasLowercase
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 1 huruf kecil (a-z)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newPasswordRequirements.hasNumber ? (
|
||||||
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: newPasswordRequirements.hasNumber
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 1 angka (0-9)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newPasswordRequirements.hasSpecialChar ? (
|
||||||
|
<CheckCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#52c41a',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CloseCircleFilled
|
||||||
|
style={{
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginRight: '6px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: newPasswordRequirements.hasSpecialChar
|
||||||
|
? '#52c41a'
|
||||||
|
: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Minimal 1 karakter spesial
|
||||||
|
(!@#$%^&*(),.?":{}|<>)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Konfirmasi Password Baru</Text>
|
||||||
|
<Input.Password
|
||||||
|
name="confirmNewPassword"
|
||||||
|
value={FormData.confirmNewPassword}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Konfirmasi password baru"
|
||||||
|
status={errors.confirmNewPassword ? 'error' : ''}
|
||||||
|
/>
|
||||||
|
{errors.confirmNewPassword && (
|
||||||
|
<Text style={{ color: 'red', fontSize: '12px' }}>
|
||||||
|
{errors.confirmNewPassword}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Divider style={{ margin: '12px 0' }} />
|
<Divider style={{ margin: '12px 0' }} />
|
||||||
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
@@ -599,39 +1086,6 @@ const DetailUser = (props) => {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ import {
|
|||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
CheckOutlined,
|
CheckOutlined,
|
||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
KeyOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { deleteUser, getAllUser, approveUser } from '../../../api/user';
|
import { deleteUser, getAllUser, approveUser, rejectUser } from '../../../api/user';
|
||||||
import TableList from '../../../components/Global/TableList';
|
import TableList from '../../../components/Global/TableList';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ const columns = (
|
|||||||
showEditModal,
|
showEditModal,
|
||||||
showDeleteDialog,
|
showDeleteDialog,
|
||||||
showApproveDialog,
|
showApproveDialog,
|
||||||
showChangePasswordModal
|
showRejectDialog
|
||||||
) => [
|
) => [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
@@ -112,28 +111,62 @@ const columns = (
|
|||||||
title: 'Status Approval',
|
title: 'Status Approval',
|
||||||
dataIndex: 'is_approve',
|
dataIndex: 'is_approve',
|
||||||
key: 'is_approve',
|
key: 'is_approve',
|
||||||
width: '10%',
|
width: '15%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, { is_approve, is_active }) => {
|
render: (_, record) => {
|
||||||
// Status approval
|
// is_approve: 0 = Rejected, 1 = Pending, 2 = Approved
|
||||||
if (is_approve === false || is_approve === 0) {
|
if (record.is_approve === 1) {
|
||||||
|
// Pending - show both Approve and Reject buttons
|
||||||
return (
|
return (
|
||||||
<Tag color={'orange'} key={'status'}>
|
<Space size="small">
|
||||||
Pending Approval
|
<Button
|
||||||
</Tag>
|
danger
|
||||||
);
|
size="small"
|
||||||
}
|
icon={<CloseOutlined />}
|
||||||
// Jika sudah approve, cek active/inactive
|
onClick={() => showRejectDialog(record)}
|
||||||
if (is_active === true || is_active === 1) {
|
>
|
||||||
return (
|
Reject
|
||||||
<Tag color={'green'} key={'status'}>
|
</Button>
|
||||||
Active
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon={<CheckOutlined />}
|
||||||
|
onClick={() => showApproveDialog(record)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#52c41a',
|
||||||
|
borderColor: '#52c41a',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
} else if (record.is_approve === 0) {
|
||||||
|
// Rejected
|
||||||
|
return (
|
||||||
|
<Tag color={'red'} key={'status'}>
|
||||||
|
Rejected
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
} else if (record.is_approve === 2) {
|
||||||
|
// Approved - check active/inactive status
|
||||||
|
if (record.is_active === true || record.is_active === 1) {
|
||||||
|
return (
|
||||||
|
<Tag color={'green'} key={'status'}>
|
||||||
|
Active
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tag color={'default'} key={'status'}>
|
||||||
|
Inactive
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Default fallback
|
||||||
return (
|
return (
|
||||||
<Tag color={'default'} key={'status'}>
|
<Tag color={'orange'} key={'status'}>
|
||||||
Inactive
|
Pending
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -142,7 +175,7 @@ const columns = (
|
|||||||
title: 'Aksi',
|
title: 'Aksi',
|
||||||
key: 'aksi',
|
key: 'aksi',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '18%',
|
width: '12%',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
@@ -151,26 +184,12 @@ const columns = (
|
|||||||
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
||||||
onClick={() => showPreviewModal(record)}
|
onClick={() => showPreviewModal(record)}
|
||||||
/>
|
/>
|
||||||
{(record.is_approve === false || record.is_approve === 0) && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ borderColor: '#52c41a' }}
|
|
||||||
icon={<CheckOutlined style={{ color: '#52c41a' }} />}
|
|
||||||
onClick={() => showApproveDialog(record)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ borderColor: '#faad14' }}
|
style={{ borderColor: '#faad14' }}
|
||||||
icon={<EditOutlined style={{ color: '#faad14' }} />}
|
icon={<EditOutlined style={{ color: '#faad14' }} />}
|
||||||
onClick={() => showEditModal(record)}
|
onClick={() => showEditModal(record)}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ borderColor: '#722ed1' }}
|
|
||||||
icon={<KeyOutlined style={{ color: '#722ed1' }} />}
|
|
||||||
onClick={() => showChangePasswordModal(record)}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
danger
|
danger
|
||||||
@@ -258,6 +277,26 @@ const ListUser = memo(function ListUser(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showRejectDialog = (param) => {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Konfirmasi Reject User',
|
||||||
|
text: 'Apakah anda yakin reject user "' + param.user_fullname + '" ?',
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: '#23A55A',
|
||||||
|
cancelButtonText: 'Batal',
|
||||||
|
confirmButtonColor: '#d33',
|
||||||
|
confirmButtonText: 'Reject',
|
||||||
|
reverseButtons: true,
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
handleReject(param.user_id);
|
||||||
|
} else if (result.dismiss) {
|
||||||
|
props.setSelectedData(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const showDeleteDialog = (param) => {
|
const showDeleteDialog = (param) => {
|
||||||
NotifConfirmDialog({
|
NotifConfirmDialog({
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
@@ -268,11 +307,6 @@ const ListUser = memo(function ListUser(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showChangePasswordModal = (param) => {
|
|
||||||
props.setSelectedUserForPassword(param);
|
|
||||||
props.setShowChangePasswordModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleApprove = async (user_id) => {
|
const handleApprove = async (user_id) => {
|
||||||
const response = await approveUser(user_id);
|
const response = await approveUser(user_id);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
@@ -384,8 +418,7 @@ const ListUser = memo(function ListUser(props) {
|
|||||||
showPreviewModal,
|
showPreviewModal,
|
||||||
showEditModal,
|
showEditModal,
|
||||||
showDeleteDialog,
|
showDeleteDialog,
|
||||||
showApproveDialog,
|
showApproveDialog
|
||||||
showChangePasswordModal
|
|
||||||
)}
|
)}
|
||||||
triger={trigerFilter}
|
triger={trigerFilter}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user