diff --git a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx index afff2f6..f50022b 100644 --- a/src/pages/settings/user/manage-user/blocks/AddDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/AddDialog.tsx @@ -34,31 +34,12 @@ import { toast } from 'sonner'; import { useCallApi } from '@/hooks'; import { doSaveLogActivity } from '@/actions/GlobalActions'; import clsx from 'clsx'; - -export interface CustomerProps { - id: string; - msisdn: string; - email: string; - fullname: string; - username: string; -} - -interface RoleListProps { - id: string; - name: string; - roles: string; - status: string; -} - -interface CreateUserParams { - email: string; - username: string; - password: string; - retype_password: string; - name: string; - id_role: string; - status: string; -} +import { + CustomerProps, + initialStateCreateUser, + RoleListProps, + validateFormCreateUser +} from './Types'; const API_URL_CUSTOMER = apiConfig.service_customer; const API_URL = apiConfig.service_dashboard; @@ -71,34 +52,30 @@ const AddDialog = () => { const { reload } = useDataGrid(); const { PostData, GetData } = useCallApi(); const [roles, setRoles] = useState([]); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); - const initialState = { - email: '', - username: '', - password: '', - retype_password: '', - name: '', - id_role: '', - status: '', - customerid: '' - }; - const [formField, setFormField] = useState(initialState); + const [errors, setErrors] = useState>({}); + + const [formField, setFormField] = useState(initialStateCreateUser); const [showPassword, setShowPassword] = useState({ password: false, retype_password: false }); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCustomerName, setSelectedCustomerName] = useState(''); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const dropdownRef = useRef(null); + const [messagePassword, setMessagePassword] = useState(true); const [open, setOpen] = useState(false); const [customers, setCustomers] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [passwordErrors, setPasswordErrors] = useState([]); + const [notMatch, setNotMatch] = useState([]); const validatePassword = (password: string, confirmPassword: string) => { const errors: string[] = []; + const notMatch: string[] = []; if (password) { if (password.length < 8) { @@ -114,25 +91,29 @@ const AddDialog = () => { errors.push('Password must contain at least one special character'); } if (password !== confirmPassword) { - errors.push('Passwords do not match'); + notMatch.push('Passwords do not match'); } } return { isValid: errors.length === 0, - errors + errors, + notMatch }; }; const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateCreateUser); + setSelectedCustomerName(''); + setSearchTerm(''); + setErrors({}); }; /* actions */ const doCreateUser = useCallback( async (e: React.FormEvent) => { e.preventDefault(); + setIsSubmitting(true); try { const response = await PostData(`${API_URL}/user/create`, formField); @@ -150,8 +131,7 @@ const AddDialog = () => { doSaveLogActivity(createActivity); toast.success('Success Create User'); } else { - toast.error('Failed to create user'); - setAlert({ show: true, message: 'Failed to create user. Please try again.' }); + toast.error(response?.message); } } catch (err) { toast.error('Something went wrong'); @@ -175,80 +155,112 @@ const AddDialog = () => { }) }; const response = await GetData(`${API_URL}/user_role/list`, params); - // console.log('ini data:', response); if (response?.status) { const roleList = response.data?.list || []; setRoles(roleList); } else { setRoles(() => []); } - // console.log('ini data user_role:', response?.data); }, []); - const getCustomerList = async (sorting: any) => { - try { - sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; - const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { - limit: 100, - page: 1, - with_deleted: false, - order_field: sorting[0].id, - order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' - }); + const getCustomerList = async (sorting: any, filterValue: string) => { + const filter: any = + filterValue?.trim().length === 0 ? {} : { fullname: { like: `%${filterValue}%` } }; - setCustomers(response?.data.list); + sorting = sorting.length == 0 ? [{ id: 'created_at', desc: false }] : sorting; + + const query: any = { + limit: 5, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }; + + if (filter && Object.keys(filter).length > 0) { + query.filter = JSON.stringify(filter); + } + try { + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, query); + + if (response?.status) { + setCustomers(response?.data.list); + } else { + toast.error(response?.message); + } } catch (error) { - console.error('Error fetching customer', error); + toast.error('Failed to fetch customer list'); + } finally { + setIsLoading(false); } }; + const handleCustomerSearch = (e: React.ChangeEvent) => { + setIsLoading(true); + setSearchTerm(e.target.value); + setSelectedCustomerName(e.target.value); + setDropdownOpen(true); + const timer = setTimeout(() => { + getCustomerList([{ id: 'fullname', desc: false }], e.target.value); + }, 500); + return () => clearTimeout(timer); + }; + + const handleCustomerSelect = (customer: CustomerProps) => { + setFormField({ ...formField, customerid: customer.id }); + setSelectedCustomerName(customer.fullname); + setDropdownOpen(false); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (isSubmitting) return; + + if (!validateFormCreateUser(formField, setErrors, 'create')) { + return; + } + + doCreateUser(e); + }; + + const isButtonDisabled = !messagePassword || isSubmitting || passwordErrors.length > 0; + + const filteredCustomer = customers + .filter((item) => item.fullname.toLowerCase().includes(searchTerm.toLowerCase())) + .slice(0, 10); + useEffect(() => { fetchRoles(); }, [fetchRoles]); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - // console.log('Form data before submit:', formField); - - if (isSubmitting) return; - - if ( - formField.email.trim() === '' || - formField.username.trim() === '' || - formField.password.trim() === '' || - formField.retype_password.trim() === '' || - formField.name.trim() === '' || - formField.id_role.trim() === '' || - formField.status.trim() === '' || - formField.customerid.trim() === '' - ) { - setAlert({ show: true, message: 'Please fill name field.' }); - return; - } - - setIsSubmitting(true); - doCreateUser(e); - // console.log(formField); - setAlert({ show: false, message: '' }); - }; - useEffect(() => { const validation = validatePassword(formField.password, formField.retype_password); setMessagePassword(validation.isValid); setPasswordErrors(validation.errors); + setNotMatch(validation.notMatch); }, [formField.password, formField.retype_password]); - const isButtonDisabled = !messagePassword || isSubmitting || passwordErrors.length > 0; - useEffect(() => { if (showAddDialog === false) { resetForm(); + setDropdownOpen(false); } }, [showAddDialog]); useEffect(() => { - getCustomerList([{ id: 'id', desc: false }]); + getCustomerList([{ id: 'created_at', desc: false }], ''); + + const handleClickOutside = (event: any) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; }, []); const togglePassword = useCallback((event: MouseEvent, key: string) => { @@ -258,14 +270,13 @@ const AddDialog = () => { return ( handleAddDialog(open)}> - +

User - Create

-
{
- -
- {alert.show && ( - -

{alert.message}

-
- )} -
-
-
-
- - - setFormField((prev) => ({ ...prev, name: target.value })) - } - /> -
-
-
-
- - - setFormField((prev) => ({ ...prev, username: target.value })) - } - /> -
-
- -
-
- - - - - - - - - { - e.currentTarget.scrollTop += e.deltaY; - }} - > - No Customer found. - - {customers.map((customer) => ( - { - setFormField({ - ...formField, - customerid: customer.id - }); - setOpen(false); - }} - > - {customer.username} - - ))} - - - - - -
-
- -
-
- - - setFormField((prev) => ({ ...prev, email: target.value })) - } - /> -
-
- -
-
- - -
- -
-
-
- -
- - -
- -
-
- -
-
- -
- - setFormField((prev) => ({ ...prev, password: target.value })) - } - /> - -
-
-
-
-
- -
-
- - setFormField((prev) => ({ ...prev, retype_password: target.value })) - } - /> - -
-
- {passwordErrors.length > 0 && ( -
- {passwordErrors.map((error, index) => ( -

{error}

- ))} -
- )} -
-
-
-
- -
- -
+ + + {/* Name */} +
+ +
+ { + setFormField((prev) => ({ ...prev, name: target.value })); + setErrors((prev) => ({ ...prev, name: '' })); + }} + /> + {errors.name && {errors.name}}
- -
+
+ + {/* Username */} +
+ +
+ { + setFormField((prev) => ({ ...prev, username: target.value })); + setErrors((prev) => ({ ...prev, username: '' })); + }} + /> + {errors.username && ( + {errors.username} + )} +
+
+ + {/* Customer */} +
+ +
+ setDropdownOpen(true)} + className={`input ${errors.customerid ? 'border-red-500' : ''}`} + /> + {dropdownOpen && ( +
+ {filteredCustomer.length > 0 ? ( + filteredCustomer.map((item) => ( +
handleCustomerSelect(item)} + > + {item.fullname} +
+ )) + ) : ( +
+ {isLoading ? 'Loading...' : 'No results found'} +
+ )} +
+ )} + {errors.customerid && ( + {errors.customerid} + )} +
+
+ + {/* Email */} +
+ +
+ { + setFormField((prev) => ({ ...prev, email: target.value })); + setErrors((prev) => ({ ...prev, email: '' })); + }} + /> + {errors.email && {errors.email}} +
+
+ + {/* Role */} +
+ +
+ + {errors.id_role && ( + {errors.id_role} + )} +
+
+ + {/* Status */} +
+ +
+ + {errors.status && ( + {errors.status} + )} +
+
+ + {/* Password */} +
+ +
+
+ { + setFormField((prev) => ({ ...prev, password: target.value })); + setErrors((prev) => ({ ...prev, password: '' })); + }} + /> + +
+ {passwordErrors.length > 0 && ( +
+ {passwordErrors.map((error, index) => ( +

{error}

+ ))} +
+ )} + {errors.password && ( + {errors.password} + )} +
+
+ + {/* Confirm Password */} +
+ +
+
+ 0 ? 'border-red-500' : ''}`} + type={showPassword.retype_password ? 'text' : 'password'} + autoComplete="off" + value={formField.retype_password} + onChange={({ target }) => { + setFormField((prev) => ({ ...prev, retype_password: target.value })); + setErrors((prev) => ({ ...prev, retype_password: '' })); + }} + /> + +
+ {notMatch.length > 0 && ( +
+ {notMatch.map((error, index) => ( +

{error}

+ ))} +
+ )} + {errors.retype_password && ( + {errors.retype_password} + )} +
+
+ + {/* Submit Button */} +
+ +
+
diff --git a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx index 2193020..e26b20e 100644 --- a/src/pages/settings/user/manage-user/blocks/EditDialog.tsx +++ b/src/pages/settings/user/manage-user/blocks/EditDialog.tsx @@ -23,7 +23,6 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { CustomerProps } from './AddDialog'; import { useUserContext } from '../hooks'; import { ChevronDown } from 'lucide-react'; import { Input } from '@/components/ui/input'; @@ -33,25 +32,16 @@ import { Alert, KeenIcon, useDataGrid } from '@/components'; import { toast } from 'sonner'; import { useCallApi } from '@/hooks'; import { doSaveLogActivity } from '@/actions/GlobalActions'; - -interface RoleListProps { - id: string; - name: string; - status: string; -} +import { + CustomerProps, + initialStateCreateUser, + RoleListProps, + validateFormCreateUser +} from './Types'; const API_URL_CUSTOMER = apiConfig.service_customer; const API_URL = apiConfig.service_dashboard; -const initialState = { - name: '', - username: '', - email: '', - id_role: '', - status: '', - customerid: '' -}; - const EditDialog = () => { const parentRef = useRef(null); const { showEditDialog, selectedUser, handleEditDialog } = useUserContext(); @@ -61,16 +51,22 @@ const EditDialog = () => { const [roles, setRoles] = useState([]); const [customers, setCustomers] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); - const [alert, setAlert] = useState({ - show: false, - message: '' - }); + const [isFetching, setIsFetching] = useState(false); - const [formField, setFormField] = useState(initialState); + const [errors, setErrors] = useState>({}); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCustomerName, setSelectedCustomerName] = useState(''); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [isLoadingCustomer, setIsLoadingCustomer] = useState(false); + const dropdownRef = useRef(null); + + const [formField, setFormField] = useState(initialStateCreateUser); const resetForm = () => { - setFormField(initialState); - setAlert({ show: false, message: '' }); + setFormField(initialStateCreateUser); + setSelectedCustomerName(''); + setSearchTerm(''); + setErrors({}); }; /* actions */ @@ -79,15 +75,8 @@ const EditDialog = () => { e.preventDefault(); setIsSubmitting(true); - if ( - !formField.name.trim() || - !formField.username.trim() || - !formField.email.trim() || - !formField.id_role || - !formField.status || - !formField.customerid - ) { - setAlert({ show: true, message: 'Please fill all required field' }); + if (!validateFormCreateUser(formField, setErrors, 'update')) { + setIsSubmitting(false); return; } @@ -109,7 +98,7 @@ const EditDialog = () => { doSaveLogActivity(createActivity); } else { - setAlert((prev) => ({ ...prev, show: true, message: response?.message })); + toast.error(response?.message); } } catch (error) { toast.error('Something went wrong, please try again.'); @@ -131,35 +120,48 @@ const EditDialog = () => { order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' }); - // console.log('User ID_ROLE:', response?.data.id_role); - // console.log('Role: ', response?.data.list); setRoles(response?.data.list); } catch (error) { console.error('Error fetching role', error); } }, []); - const getCustomerList = async (sorting: any) => { - try { - sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; - const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, { - limit: 100, - page: 1, - with_deleted: false, - order_field: sorting[0].id, - order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' - }); + const getCustomerList = async (sorting: any, filterValue: string) => { + const filter: any = + filterValue?.trim().length === 0 ? {} : { fullname: { like: `%${filterValue}%` } }; - // console.log('CUSTOMER: ', response?.data); - setCustomers(response?.data.list); + sorting = sorting.length == 0 ? [{ id: 'created_at', desc: false }] : sorting; + + const query: any = { + limit: 5, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'DESC' : 'ASC' + }; + + if (filter && Object.keys(filter).length > 0) { + query.filter = JSON.stringify(filter); + } + try { + const response = await GetData(`${API_URL_CUSTOMER}/customer/list`, query); + + if (response?.status) { + setCustomers(response?.data.list); + } else { + toast.error(response?.message); + } } catch (error) { - console.error('Error fetching customer', error); + toast.error('Failed to fetch customer list'); + } finally { + setIsLoadingCustomer(false); } }; const doFetchUserData = useCallback(async (id: string) => { + setIsFetching(true); + const response = await GetData(`${API_URL}/user/detail/${id}`, { id }); - // console.log('User detail response:', response?.data); if (response?.status) { setFormField((prev) => ({ @@ -171,44 +173,71 @@ const EditDialog = () => { status: response.data.status, customerid: response.data.customer?.id || '' })); - // console.log('Customer ID from API:', response?.data.customerid); } else { setFormField((prev) => ({ ...prev, name: '', username: '', email: '', - id_role: '0', + id_role: '', status: '', customerid: '' })); } - // console.log('Fetched ID Role:', response?.data.id_role); + setIsFetching(false); }, []); + const handleCustomerSearch = (e: React.ChangeEvent) => { + setIsLoadingCustomer(true); + setSearchTerm(e.target.value); + setSelectedCustomerName(e.target.value); + setDropdownOpen(true); + const timer = setTimeout(() => { + getCustomerList([{ id: 'fullname', desc: false }], e.target.value); + }, 500); + return () => clearTimeout(timer); + }; + + const handleCustomerSelect = (customer: CustomerProps) => { + setFormField({ ...formField, customerid: customer.id }); + setSelectedCustomerName(customer.fullname); + setDropdownOpen(false); + }; + + const filteredCustomer = customers + .filter((item) => item.fullname.toLowerCase().includes(searchTerm.toLowerCase())) + .slice(0, 10); + useEffect(() => { if (showEditDialog === false) { resetForm(); + setDropdownOpen(false); } }, [showEditDialog]); useEffect(() => { - const fetchAllData = async () => { - await getCustomerList([{ id: 'id', desc: false }]); - await doFetchUserRole([{ id: 'name', desc: false }]); - if (selectedUser) { - await doFetchUserData(selectedUser); + getCustomerList([{ id: 'created_at', desc: false }], ''); + doFetchUserRole([{ id: 'name', desc: false }]); + }, []); + + useEffect(() => { + if (selectedUser) { + doFetchUserData(selectedUser); + } + }, [selectedUser]); + + useEffect(() => { + const handleClickOutside = (event: any) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setDropdownOpen(false); } }; - fetchAllData(); - }, [selectedUser]); - - // console.log('ini role: ', roles); - // useEffect(() => { - // console.log('Selected User ID Role:', formField.id_role); - // // console.log('Available Roles:', roles); - // }, [formField.id_role, roles]); + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); return ( handleEditDialog(open, null)}> @@ -231,160 +260,184 @@ const EditDialog = () => {
- {alert.show && ( - -

{alert.message}

-
- )} -
-
-
-
+ {isFetching ? ( +
+
+
+
+
+
+
+
+
+
+

Loading User Details...

+
+ ) : ( + +
+
- - setFormField((prev) => ({ ...prev, name: target.value })) - } - /> +
+ { + setFormField((prev) => ({ ...prev, name: target.value })); + setErrors((prev) => ({ ...prev, name: '' })); + }} + /> + {errors.name && ( + {errors.name} + )} +
-
-
-
+ {/* Username */} +
- - setFormField((prev) => ({ ...prev, username: target.value })) - } - /> +
+ { + setFormField((prev) => ({ ...prev, username: target.value })); + setErrors((prev) => ({ ...prev, username: '' })); + }} + /> + {errors.username && ( + {errors.username} + )} +
-
-
-
+ {/* Customer */} +
- - - - - - - - { - e.currentTarget.scrollTop += e.deltaY; - }} - > - No Customer found. - - {customers.map((customer) => ( - { - setFormField({ - ...formField, - customerid: customer.id - }); - setOpen(false); - }} - > - {customer.username} - - ))} - - - - - +
+ setDropdownOpen(true)} + className={`input ${errors.customerid ? 'border-red-500' : ''}`} + /> + {dropdownOpen && ( +
+ {filteredCustomer.length > 0 ? ( + filteredCustomer.map((item) => ( +
handleCustomerSelect(item)} + > + {item.fullname} +
+ )) + ) : ( +
+ {isLoadingCustomer ? 'Loading...' : 'No results found'} +
+ )} +
+ )} + {errors.customerid && ( + {errors.customerid} + )} +
-
-
-
+ {/* Email */} +
- - setFormField((prev) => ({ ...prev, email: target.value })) - } - /> +
+ { + setFormField((prev) => ({ ...prev, email: target.value })); + setErrors((prev) => ({ ...prev, email: '' })); + }} + /> + {errors.email && ( + {errors.email} + )} +
-
-
-
- - -
+ {/* Role */} +
+ +
+ {errors.id_role && ( + {errors.id_role} + )}
-
-
- + {/* Status */} +
+ +
+ + {errors.status && ( + {errors.status} + )} +
+
-
- +
+
- -
- -
-
- + + )}
diff --git a/src/pages/settings/user/manage-user/blocks/Types.ts b/src/pages/settings/user/manage-user/blocks/Types.ts new file mode 100644 index 0000000..e0fa958 --- /dev/null +++ b/src/pages/settings/user/manage-user/blocks/Types.ts @@ -0,0 +1,91 @@ +import { toast } from 'sonner'; + +export interface CustomerProps { + id: string; + msisdn: string; + email: string; + fullname: string; + username: string; +} + +export interface RoleListProps { + id: string; + name: string; + roles: string; + status: string; +} + +export interface CreateUserParams { + email: string; + username: string; + password: string; + retype_password: string; + name: string; + id_role: string; + status: string; +} + +export const initialStateCreateUser: { + email: string; + username: string; + password: string; + retype_password: string; + name: string; + id_role: string; + status: string; + customerid: string; +} = { + email: '', + username: '', + password: '', + retype_password: '', + name: '', + id_role: '', + status: '', + customerid: '' +}; + +export const validateFormCreateUser = ( + formField: typeof initialStateCreateUser, + setErrors: React.Dispatch>>, + mode: 'create' | 'update' = 'create' +) => { + const requiredFields = + mode === 'create' + ? [ + { key: 'name', label: 'Name' }, + { key: 'username', label: 'Username' }, + { key: 'email', label: 'Email' }, + { key: 'customerid', label: 'Customer ID' }, + { key: 'id_role', label: 'Role' }, + { key: 'status', label: 'Status' }, + { key: 'password', label: 'Password' }, + { key: 'retype_password', label: 'Retype Password' } + ] + : [ + { key: 'name', label: 'Name' }, + { key: 'username', label: 'Username' }, + { key: 'customerid', label: 'Customer ID' }, + { key: 'email', label: 'Email' }, + { key: 'id_role', label: 'Role' }, + { key: 'status', label: 'Status' } + ]; + + const newErrors: Record = {}; + let isValid = true; + + requiredFields.forEach(({ key, label }) => { + if ( + formField[key as keyof typeof formField] === '' || + formField[key as keyof typeof formField] === null || + formField[key as keyof typeof formField] === undefined + ) { + newErrors[key] = `${label} is required`; + toast.error(`${label} is required`); + isValid = false; + } + }); + + setErrors(newErrors); + return isValid; +}; diff --git a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx index 77f68a9..3fd9aec 100644 --- a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx +++ b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx @@ -232,8 +232,6 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) handleDeleteDialog }} > - -