From 2d2b1a6b0c7c0aa337b40db614e15668014ecd7d Mon Sep 17 00:00:00 2001 From: vinix Date: Wed, 15 Oct 2025 19:42:47 +0700 Subject: [PATCH] pending reject handle --- src/api/user.jsx | 15 +- src/pages/user/component/DetailUser.jsx | 582 +++++++++++++++++++++--- src/pages/user/component/ListUser.jsx | 115 +++-- 3 files changed, 606 insertions(+), 106 deletions(-) diff --git a/src/api/user.jsx b/src/api/user.jsx index a437190..f771f6a 100644 --- a/src/api/user.jsx +++ b/src/api/user.jsx @@ -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 response = await SendRequest({ method: 'put', @@ -175,4 +188,4 @@ const changePassword = async (user_id, new_password) => { }; }; -export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser, changePassword }; \ No newline at end of file +export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser, rejectUser, changePassword }; \ No newline at end of file diff --git a/src/pages/user/component/DetailUser.jsx b/src/pages/user/component/DetailUser.jsx index 9403a99..34cccd5 100644 --- a/src/pages/user/component/DetailUser.jsx +++ b/src/pages/user/component/DetailUser.jsx @@ -2,7 +2,7 @@ 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 } from '../../../api/user'; +import { createUser, updateUser, changePassword } from '../../../api/user'; import { getAllRole } from '../../../api/role'; const { Text } = Typography; @@ -23,6 +23,8 @@ const DetailUser = (props) => { is_active: true, password: '', confirmPassword: '', + newPassword: '', + confirmNewPassword: '', }; const [FormData, setFormData] = useState(defaultData); @@ -38,6 +40,15 @@ const DetailUser = (props) => { 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'); @@ -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 phoneRegex = /^(?:\+62|0)8\d{7,10}$/; 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); return Object.keys(newErrors).length === 0; }; @@ -218,13 +254,48 @@ const DetailUser = (props) => { // 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'}.`, - }); + // If in edit mode and newPassword is provided, change password + 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); @@ -260,6 +331,11 @@ const DetailUser = (props) => { 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({ @@ -408,6 +484,39 @@ const DetailUser = (props) => { User ID + {FormData.user_id && ( +
+ +
+ Status Aktif +
+
+
+ + handleSwitchChange('is_active', checked) + } + /> +
+
+ {FormData.is_active ? 'Aktif' : 'Nonaktif'} +
+
+
+ )}
Nama Lengkap @@ -502,57 +611,198 @@ const DetailUser = (props) => { )} {/* Password Requirements Indicator */} -
- Password harus memenuhi: +
+ + 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 (!@#$%^&*(),.?":{}|<>) + + Minimal 1 karakter spesial + (!@#$%^&*(),.?":{}|<>)
@@ -579,6 +829,243 @@ const DetailUser = (props) => { )} + {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} + + )} +
+ + )} +
@@ -599,39 +1086,6 @@ const DetailUser = (props) => { ))}
- - {FormData.user_id && ( -
-
- Status Aktif -
-
-
- - handleSwitchChange('is_active', checked) - } - /> -
-
- {FormData.is_active ? 'Aktif' : 'Nonaktif'} -
-
-
- )}
)} diff --git a/src/pages/user/component/ListUser.jsx b/src/pages/user/component/ListUser.jsx index e160d08..e1d2ea1 100644 --- a/src/pages/user/component/ListUser.jsx +++ b/src/pages/user/component/ListUser.jsx @@ -8,11 +8,10 @@ import { SearchOutlined, CheckOutlined, CloseOutlined, - KeyOutlined, } from '@ant-design/icons'; import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; 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 Swal from 'sweetalert2'; @@ -55,7 +54,7 @@ const columns = ( showEditModal, showDeleteDialog, showApproveDialog, - showChangePasswordModal + showRejectDialog ) => [ { title: 'ID', @@ -112,28 +111,62 @@ const columns = ( title: 'Status Approval', dataIndex: 'is_approve', key: 'is_approve', - width: '10%', + width: '15%', align: 'center', - render: (_, { is_approve, is_active }) => { - // Status approval - if (is_approve === false || is_approve === 0) { + render: (_, record) => { + // is_approve: 0 = Rejected, 1 = Pending, 2 = Approved + if (record.is_approve === 1) { + // Pending - show both Approve and Reject buttons return ( - - Pending Approval - - ); - } - // Jika sudah approve, cek active/inactive - if (is_active === true || is_active === 1) { - return ( - - Active + + + + + ); + } else if (record.is_approve === 0) { + // Rejected + return ( + + Rejected + + ); + } else if (record.is_approve === 2) { + // Approved - check active/inactive status + if (record.is_active === true || record.is_active === 1) { + return ( + + Active + + ); + } + return ( + + Inactive ); } + // Default fallback return ( - - Inactive + + Pending ); }, @@ -142,7 +175,7 @@ const columns = ( title: 'Aksi', key: 'aksi', align: 'center', - width: '18%', + width: '12%', render: (_, record) => (