fix alert empty validation on manage user
This commit is contained in:
@ -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<RoleListProps[]>([]);
|
||||
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<Record<string, string>>({});
|
||||
|
||||
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<HTMLDivElement>(null);
|
||||
|
||||
const [messagePassword, setMessagePassword] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [passwordErrors, setPasswordErrors] = useState<string[]>([]);
|
||||
const [notMatch, setNotMatch] = useState<string[]>([]);
|
||||
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLInputElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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<HTMLButtonElement>, key: string) => {
|
||||
@ -258,14 +270,13 @@ const AddDialog = () => {
|
||||
|
||||
return (
|
||||
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
|
||||
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden [&>button]:hidden">
|
||||
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-10 overflow-hidden [&>button]:hidden">
|
||||
<DialogHeader className="p-0 border-0">
|
||||
<DialogTitle></DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
<div className="flex items-center justify-between flex-wrap grow">
|
||||
<div className="flex flex-col justify-center">
|
||||
<h1 className="text-xl font-semibold leading-none text-gray-900">User - Create</h1>
|
||||
<div className="flex items-center gap-2 text-sm font-normal text-gray-700"></div>
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer hover:opacity-100 opacity-50"
|
||||
@ -278,249 +289,255 @@ const AddDialog = () => {
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
|
||||
<div className="flex flex-col px-0">
|
||||
{alert.show && (
|
||||
<Alert variant="danger" className="mb-3">
|
||||
<h3>{alert.message}</h3>
|
||||
</Alert>
|
||||
)}
|
||||
<form action="" onSubmit={handleSubmit}>
|
||||
<div className="card-body grid gap-5 p-0">
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
|
||||
<Input
|
||||
className="input"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={formField.name}
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, name: target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Username</label>
|
||||
<Input
|
||||
className="input"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={formField.username}
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, username: target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Customer</label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="input col-span-5 text-left flex justify-between"
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
<span>
|
||||
{customers.find((customer) => customer.id === formField.customerid)
|
||||
?.username || 'Select Customer'}
|
||||
</span>
|
||||
<ChevronDown className="w-4 h-4 opacity-70" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[400px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search Customer..." />
|
||||
<CommandList
|
||||
className="max-h-[300px] overflow-y-auto"
|
||||
style={{ touchAction: 'pan-y' }}
|
||||
onWheel={(e) => {
|
||||
e.currentTarget.scrollTop += e.deltaY;
|
||||
}}
|
||||
>
|
||||
<CommandEmpty>No Customer found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{customers.map((customer) => (
|
||||
<CommandItem
|
||||
key={customer.id}
|
||||
value={customer.username}
|
||||
onSelect={() => {
|
||||
setFormField({
|
||||
...formField,
|
||||
customerid: customer.id
|
||||
});
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{customer.username}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Email</label>
|
||||
<Input
|
||||
className="input"
|
||||
type="email"
|
||||
autoComplete="off"
|
||||
value={formField.email}
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, email: target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-center flex-wrap gap-2.5">
|
||||
<label className="form-label max-w-56">Role</label>
|
||||
|
||||
<div className="grow">
|
||||
<Select
|
||||
value={formField.id_role}
|
||||
onValueChange={(id_role) => {
|
||||
setTimeout(() => {
|
||||
setFormField((prev) => ({ ...prev, id_role }));
|
||||
}, 0);
|
||||
// console.log('Role selected:', value);`
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{roles.map((role, idx) => (
|
||||
<SelectItem value={role.id} key={role.id}>
|
||||
{role.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-wrap gap-2.5">
|
||||
<label className="form-label max-w-56">Status</label>
|
||||
|
||||
<div className="grow">
|
||||
<Select
|
||||
value={formField.status}
|
||||
onValueChange={(status) => {
|
||||
setTimeout(() => {
|
||||
setFormField((prev) => ({ ...prev, status }));
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Y">Active</SelectItem>
|
||||
<SelectItem value="N">Non Active</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Password</label>
|
||||
<div className="input">
|
||||
<input
|
||||
className="form-control"
|
||||
type={showPassword.password ? 'text' : 'password'}
|
||||
value={formField.password}
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, password: target.value }))
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-icon"
|
||||
onClick={(e) => togglePassword(e, 'password')}
|
||||
>
|
||||
<KeenIcon
|
||||
icon="eye"
|
||||
className={clsx('text-gray-500', { hidden: showPassword.password })}
|
||||
/>
|
||||
<KeenIcon
|
||||
icon="eye-slash"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: !showPassword.password
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">
|
||||
Confirm Password
|
||||
</label>
|
||||
<div className="w-full">
|
||||
<div className="input block">
|
||||
<input
|
||||
className="form-control"
|
||||
autoComplete="off"
|
||||
type={showPassword.retype_password ? 'text' : 'password'}
|
||||
value={formField.retype_password}
|
||||
onChange={({ target }) =>
|
||||
setFormField((prev) => ({ ...prev, retype_password: target.value }))
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-icon"
|
||||
onClick={(e) => togglePassword(e, 'retype_password')}
|
||||
>
|
||||
<KeenIcon
|
||||
icon="eye"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: showPassword.retype_password
|
||||
})}
|
||||
/>
|
||||
<KeenIcon
|
||||
icon="eye-slash"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: !showPassword.retype_password
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
{passwordErrors.length > 0 && (
|
||||
<div className="text-xs text-red-500 mt-2">
|
||||
{passwordErrors.map((error, index) => (
|
||||
<p key={index}>{error}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-2.5">
|
||||
<Button className="btn btn-primary" type="submit" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
<DialogBody className="scrollable-y p-5 pb-0" ref={parentRef}>
|
||||
<form onSubmit={handleSubmit} className="grid gap-5">
|
||||
{/* Name */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
|
||||
<div className="grow flex flex-col">
|
||||
<Input
|
||||
className={`input ${errors.name ? 'border-red-500' : ''}`}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={formField.name}
|
||||
onChange={({ target }) => {
|
||||
setFormField((prev) => ({ ...prev, name: target.value }));
|
||||
setErrors((prev) => ({ ...prev, name: '' }));
|
||||
}}
|
||||
/>
|
||||
{errors.name && <span className="text-red-500 text-xs mt-1">{errors.name}</span>}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Username */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Username</label>
|
||||
<div className="grow flex flex-col">
|
||||
<Input
|
||||
className={`input ${errors.username ? 'border-red-500' : ''}`}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={formField.username}
|
||||
onChange={({ target }) => {
|
||||
setFormField((prev) => ({ ...prev, username: target.value }));
|
||||
setErrors((prev) => ({ ...prev, username: '' }));
|
||||
}}
|
||||
/>
|
||||
{errors.username && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.username}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customer */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Customer</label>
|
||||
<div className="grow flex flex-col" ref={dropdownRef}>
|
||||
<Input
|
||||
id="customer"
|
||||
type="text"
|
||||
value={selectedCustomerName || searchTerm}
|
||||
onChange={handleCustomerSearch}
|
||||
placeholder="Search Customer"
|
||||
onClick={() => setDropdownOpen(true)}
|
||||
className={`input ${errors.customerid ? 'border-red-500' : ''}`}
|
||||
/>
|
||||
{dropdownOpen && (
|
||||
<div className="absolute z-10 w-[60%] mt-11 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-y-auto">
|
||||
{filteredCustomer.length > 0 ? (
|
||||
filteredCustomer.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
||||
onClick={() => handleCustomerSelect(item)}
|
||||
>
|
||||
{item.fullname}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="px-4 py-2 text-gray-500">
|
||||
{isLoading ? 'Loading...' : 'No results found'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{errors.customerid && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.customerid}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Email</label>
|
||||
<div className="grow flex flex-col">
|
||||
<Input
|
||||
className={`input ${errors.email ? 'border-red-500' : ''}`}
|
||||
type="email"
|
||||
autoComplete="off"
|
||||
value={formField.email}
|
||||
onChange={({ target }) => {
|
||||
setFormField((prev) => ({ ...prev, email: target.value }));
|
||||
setErrors((prev) => ({ ...prev, email: '' }));
|
||||
}}
|
||||
/>
|
||||
{errors.email && <span className="text-red-500 text-xs mt-1">{errors.email}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Role */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Role</label>
|
||||
<div className="grow flex flex-col">
|
||||
<Select
|
||||
value={formField.id_role}
|
||||
onValueChange={(id_role) => {
|
||||
setTimeout(() => {
|
||||
setFormField((prev) => ({ ...prev, id_role }));
|
||||
setErrors((prev) => ({ ...prev, id_role: '' }));
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className={errors.id_role ? 'border-red-500' : ''}>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{roles.map((role) => (
|
||||
<SelectItem value={role.id} key={role.id}>
|
||||
{role.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.id_role && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.id_role}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Status</label>
|
||||
<div className="grow flex flex-col">
|
||||
<Select
|
||||
value={formField.status}
|
||||
onValueChange={(status) => {
|
||||
setTimeout(() => {
|
||||
setFormField((prev) => ({ ...prev, status }));
|
||||
setErrors((prev) => ({ ...prev, status: '' }));
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className={errors.status ? 'border-red-500' : ''}>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Y">Active</SelectItem>
|
||||
<SelectItem value="N">Non Active</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.status && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.status}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Password */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">Password</label>
|
||||
<div className="grow flex flex-col">
|
||||
<div className="input">
|
||||
<input
|
||||
className={`w-full ${errors.password ? 'border-red-500' : ''}`}
|
||||
type={showPassword.password ? 'text' : 'password'}
|
||||
value={formField.password}
|
||||
onChange={({ target }) => {
|
||||
setFormField((prev) => ({ ...prev, password: target.value }));
|
||||
setErrors((prev) => ({ ...prev, password: '' }));
|
||||
}}
|
||||
/>
|
||||
<button className="btn btn-icon" onClick={(e) => togglePassword(e, 'password')}>
|
||||
<KeenIcon
|
||||
icon="eye"
|
||||
className={clsx('text-gray-500', { hidden: showPassword.password })}
|
||||
/>
|
||||
<KeenIcon
|
||||
icon="eye-slash"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: !showPassword.password
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{passwordErrors.length > 0 && (
|
||||
<div className="text-xs text-red-500 mt-2">
|
||||
{passwordErrors.map((error, index) => (
|
||||
<p key={index}>{error}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{errors.password && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.password}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Confirm Password */}
|
||||
<div className="flex gap-2.5">
|
||||
<label className="form-label flex items-center gap-1 max-w-56">
|
||||
Confirm Password
|
||||
</label>
|
||||
<div className="grow flex flex-col">
|
||||
<div className="input">
|
||||
<input
|
||||
className={`form-control w-full ${passwordErrors.length > 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: '' }));
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-icon"
|
||||
onClick={(e) => togglePassword(e, 'retype_password')}
|
||||
>
|
||||
<KeenIcon
|
||||
icon="eye"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: showPassword.retype_password
|
||||
})}
|
||||
/>
|
||||
<KeenIcon
|
||||
icon="eye-slash"
|
||||
className={clsx('text-gray-500', {
|
||||
hidden: !showPassword.retype_password
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{notMatch.length > 0 && (
|
||||
<div className="text-xs text-red-500 mt-2">
|
||||
{notMatch.map((error, index) => (
|
||||
<p key={index}>{error}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{errors.retype_password && (
|
||||
<span className="text-red-500 text-xs mt-1">{errors.retype_password}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end pt-2.5">
|
||||
<Button className="btn btn-primary" type="submit" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user