From 823492a381385e89456690d59bb6de6cd2a2a279 Mon Sep 17 00:00:00 2001 From: vinix Date: Thu, 9 Oct 2025 22:53:06 +0700 Subject: [PATCH] add detailuser file --- src/pages/user/component/DetailUser.jsx | 472 ++++++++++++++++++++++++ src/pages/user/component/ListUser.jsx | 127 ++++--- 2 files changed, 543 insertions(+), 56 deletions(-) create mode 100644 src/pages/user/component/DetailUser.jsx diff --git a/src/pages/user/component/DetailUser.jsx b/src/pages/user/component/DetailUser.jsx new file mode 100644 index 0000000..6a60cce --- /dev/null +++ b/src/pages/user/component/DetailUser.jsx @@ -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 ( + + + + + + {!props.readOnly && ( + + )} + + , + ]} + width={600} + > + {FormData && ( +
+ + +
+ 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} + + )} +
+ +
+ Konfirmasi Password + * + + {errors.confirmPassword && ( + + {errors.confirmPassword} + + )} +
+ + )} + + + +
+ Role + +
+ + {FormData.user_id && ( +
+
+ Status Aktif +
+
+
+ + handleSwitchChange('is_active', checked) + } + /> +
+
+ {FormData.is_active ? 'Aktif' : 'Nonaktif'} +
+
+
+ )} +
+ )} +
+ ); +}; + +export default DetailUser; diff --git a/src/pages/user/component/ListUser.jsx b/src/pages/user/component/ListUser.jsx index 7f6b15f..b5530af 100644 --- a/src/pages/user/component/ListUser.jsx +++ b/src/pages/user/component/ListUser.jsx @@ -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,71 +43,74 @@ 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 }) => ( - <> - {role_name === 'administrator' && ( - - Administrator - - )} - {role_name === 'operator' && ( - - Operator - - )} - {role_name === 'engineer' && ( - - Engineer - - )} - {role_name === 'guest' && ( - - Guest - - )} - - ), + render: (_, { role_name }) => { + if (!role_name) return Belum Ada Role; + return ( + <> + {role_name === 'administrator' && ( + + Administrator + + )} + {role_name === 'operator' && ( + + Operator + + )} + {role_name === 'engineer' && ( + + Engineer + + )} + {role_name === 'guest' && ( + + Guest + + )} + + ); + }, }, { - 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 ( + + Pending Approval + + ); + } + // Jika sudah approve, cek active/inactive + if (is_active === true || is_active === 1) { + return ( Active - )} - {status === 'pending' && ( - - Pending - - )} - {status === 'inactive' && ( - - Inactive - - )} - {status === 'rejected' && ( - - Rejected - - )} - - ), + ); + } + return ( + + Inactive + + ); + }, }, { title: 'Aksi', @@ -121,7 +125,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD icon={} onClick={() => showPreviewModal(record)} /> - {record.status === 'pending' && ( + {(record.is_approve === false || record.is_approve === 0) && (