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 { useCallApi } from '@/hooks';
|
||||||
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import {
|
||||||
export interface CustomerProps {
|
CustomerProps,
|
||||||
id: string;
|
initialStateCreateUser,
|
||||||
msisdn: string;
|
RoleListProps,
|
||||||
email: string;
|
validateFormCreateUser
|
||||||
fullname: string;
|
} from './Types';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const API_URL_CUSTOMER = apiConfig.service_customer;
|
const API_URL_CUSTOMER = apiConfig.service_customer;
|
||||||
const API_URL = apiConfig.service_dashboard;
|
const API_URL = apiConfig.service_dashboard;
|
||||||
@ -71,34 +52,30 @@ const AddDialog = () => {
|
|||||||
const { reload } = useDataGrid();
|
const { reload } = useDataGrid();
|
||||||
const { PostData, GetData } = useCallApi();
|
const { PostData, GetData } = useCallApi();
|
||||||
const [roles, setRoles] = useState<RoleListProps[]>([]);
|
const [roles, setRoles] = useState<RoleListProps[]>([]);
|
||||||
const [alert, setAlert] = useState({
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
show: false,
|
|
||||||
message: ''
|
const [formField, setFormField] = useState(initialStateCreateUser);
|
||||||
});
|
|
||||||
const initialState = {
|
|
||||||
email: '',
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
retype_password: '',
|
|
||||||
name: '',
|
|
||||||
id_role: '',
|
|
||||||
status: '',
|
|
||||||
customerid: ''
|
|
||||||
};
|
|
||||||
const [formField, setFormField] = useState(initialState);
|
|
||||||
const [showPassword, setShowPassword] = useState({
|
const [showPassword, setShowPassword] = useState({
|
||||||
password: false,
|
password: false,
|
||||||
retype_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 [messagePassword, setMessagePassword] = useState(true);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [passwordErrors, setPasswordErrors] = useState<string[]>([]);
|
const [passwordErrors, setPasswordErrors] = useState<string[]>([]);
|
||||||
|
const [notMatch, setNotMatch] = useState<string[]>([]);
|
||||||
|
|
||||||
const validatePassword = (password: string, confirmPassword: string) => {
|
const validatePassword = (password: string, confirmPassword: string) => {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
const notMatch: string[] = [];
|
||||||
|
|
||||||
if (password) {
|
if (password) {
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
@ -114,25 +91,29 @@ const AddDialog = () => {
|
|||||||
errors.push('Password must contain at least one special character');
|
errors.push('Password must contain at least one special character');
|
||||||
}
|
}
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
errors.push('Passwords do not match');
|
notMatch.push('Passwords do not match');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isValid: errors.length === 0,
|
isValid: errors.length === 0,
|
||||||
errors
|
errors,
|
||||||
|
notMatch
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormField(initialState);
|
setFormField(initialStateCreateUser);
|
||||||
setAlert({ show: false, message: '' });
|
setSelectedCustomerName('');
|
||||||
|
setSearchTerm('');
|
||||||
|
setErrors({});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* actions */
|
/* actions */
|
||||||
const doCreateUser = useCallback(
|
const doCreateUser = useCallback(
|
||||||
async (e: React.FormEvent<HTMLFormElement>) => {
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await PostData(`${API_URL}/user/create`, formField);
|
const response = await PostData(`${API_URL}/user/create`, formField);
|
||||||
@ -150,8 +131,7 @@ const AddDialog = () => {
|
|||||||
doSaveLogActivity(createActivity);
|
doSaveLogActivity(createActivity);
|
||||||
toast.success('Success Create User');
|
toast.success('Success Create User');
|
||||||
} else {
|
} else {
|
||||||
toast.error('Failed to create user');
|
toast.error(response?.message);
|
||||||
setAlert({ show: true, message: 'Failed to create user. Please try again.' });
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('Something went wrong');
|
toast.error('Something went wrong');
|
||||||
@ -175,80 +155,112 @@ const AddDialog = () => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
const response = await GetData(`${API_URL}/user_role/list`, params);
|
const response = await GetData(`${API_URL}/user_role/list`, params);
|
||||||
// console.log('ini data:', response);
|
|
||||||
if (response?.status) {
|
if (response?.status) {
|
||||||
const roleList = response.data?.list || [];
|
const roleList = response.data?.list || [];
|
||||||
setRoles(roleList);
|
setRoles(roleList);
|
||||||
} else {
|
} else {
|
||||||
setRoles(() => []);
|
setRoles(() => []);
|
||||||
}
|
}
|
||||||
// console.log('ini data user_role:', response?.data);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getCustomerList = async (sorting: any) => {
|
const getCustomerList = async (sorting: any, filterValue: string) => {
|
||||||
try {
|
const filter: any =
|
||||||
sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting;
|
filterValue?.trim().length === 0 ? {} : { fullname: { like: `%${filterValue}%` } };
|
||||||
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'
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
} 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(() => {
|
useEffect(() => {
|
||||||
fetchRoles();
|
fetchRoles();
|
||||||
}, [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(() => {
|
useEffect(() => {
|
||||||
const validation = validatePassword(formField.password, formField.retype_password);
|
const validation = validatePassword(formField.password, formField.retype_password);
|
||||||
setMessagePassword(validation.isValid);
|
setMessagePassword(validation.isValid);
|
||||||
setPasswordErrors(validation.errors);
|
setPasswordErrors(validation.errors);
|
||||||
|
setNotMatch(validation.notMatch);
|
||||||
}, [formField.password, formField.retype_password]);
|
}, [formField.password, formField.retype_password]);
|
||||||
|
|
||||||
const isButtonDisabled = !messagePassword || isSubmitting || passwordErrors.length > 0;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showAddDialog === false) {
|
if (showAddDialog === false) {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
setDropdownOpen(false);
|
||||||
}
|
}
|
||||||
}, [showAddDialog]);
|
}, [showAddDialog]);
|
||||||
|
|
||||||
useEffect(() => {
|
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) => {
|
const togglePassword = useCallback((event: MouseEvent<HTMLButtonElement>, key: string) => {
|
||||||
@ -258,14 +270,13 @@ const AddDialog = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
|
<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">
|
<DialogHeader className="p-0 border-0">
|
||||||
<DialogTitle></DialogTitle>
|
<DialogTitle></DialogTitle>
|
||||||
<DialogDescription></DialogDescription>
|
<DialogDescription></DialogDescription>
|
||||||
<div className="flex items-center justify-between flex-wrap grow">
|
<div className="flex items-center justify-between flex-wrap grow">
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
<h1 className="text-xl font-semibold leading-none text-gray-900">User - Create</h1>
|
<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>
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer hover:opacity-100 opacity-50"
|
className="cursor-pointer hover:opacity-100 opacity-50"
|
||||||
@ -278,249 +289,255 @@ const AddDialog = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogHeader>
|
</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">
|
<DialogBody className="scrollable-y p-5 pb-0" ref={parentRef}>
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<form onSubmit={handleSubmit} className="grid gap-5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">Username</label>
|
{/* Name */}
|
||||||
<Input
|
<div className="flex gap-2.5">
|
||||||
className="input"
|
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
|
||||||
type="text"
|
<div className="grow flex flex-col">
|
||||||
autoComplete="off"
|
<Input
|
||||||
value={formField.username}
|
className={`input ${errors.name ? 'border-red-500' : ''}`}
|
||||||
onChange={({ target }) =>
|
type="text"
|
||||||
setFormField((prev) => ({ ...prev, username: target.value }))
|
autoComplete="off"
|
||||||
}
|
value={formField.name}
|
||||||
/>
|
onChange={({ target }) => {
|
||||||
</div>
|
setFormField((prev) => ({ ...prev, name: target.value }));
|
||||||
</div>
|
setErrors((prev) => ({ ...prev, name: '' }));
|
||||||
|
}}
|
||||||
<div className="w-full">
|
/>
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
{errors.name && <span className="text-red-500 text-xs mt-1">{errors.name}</span>}
|
||||||
<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>
|
|
||||||
</div>
|
</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>
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { CustomerProps } from './AddDialog';
|
|
||||||
import { useUserContext } from '../hooks';
|
import { useUserContext } from '../hooks';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -33,25 +32,16 @@ import { Alert, KeenIcon, useDataGrid } from '@/components';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useCallApi } from '@/hooks';
|
import { useCallApi } from '@/hooks';
|
||||||
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
|
import {
|
||||||
interface RoleListProps {
|
CustomerProps,
|
||||||
id: string;
|
initialStateCreateUser,
|
||||||
name: string;
|
RoleListProps,
|
||||||
status: string;
|
validateFormCreateUser
|
||||||
}
|
} from './Types';
|
||||||
|
|
||||||
const API_URL_CUSTOMER = apiConfig.service_customer;
|
const API_URL_CUSTOMER = apiConfig.service_customer;
|
||||||
const API_URL = apiConfig.service_dashboard;
|
const API_URL = apiConfig.service_dashboard;
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
name: '',
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
id_role: '',
|
|
||||||
status: '',
|
|
||||||
customerid: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditDialog = () => {
|
const EditDialog = () => {
|
||||||
const parentRef = useRef<any | null>(null);
|
const parentRef = useRef<any | null>(null);
|
||||||
const { showEditDialog, selectedUser, handleEditDialog } = useUserContext();
|
const { showEditDialog, selectedUser, handleEditDialog } = useUserContext();
|
||||||
@ -61,16 +51,22 @@ const EditDialog = () => {
|
|||||||
const [roles, setRoles] = useState<RoleListProps[]>([]);
|
const [roles, setRoles] = useState<RoleListProps[]>([]);
|
||||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [alert, setAlert] = useState({
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
show: false,
|
|
||||||
message: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
const [formField, setFormField] = useState(initialState);
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedCustomerName, setSelectedCustomerName] = useState('');
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
|
const [isLoadingCustomer, setIsLoadingCustomer] = useState(false);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [formField, setFormField] = useState(initialStateCreateUser);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormField(initialState);
|
setFormField(initialStateCreateUser);
|
||||||
setAlert({ show: false, message: '' });
|
setSelectedCustomerName('');
|
||||||
|
setSearchTerm('');
|
||||||
|
setErrors({});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* actions */
|
/* actions */
|
||||||
@ -79,15 +75,8 @@ const EditDialog = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
if (
|
if (!validateFormCreateUser(formField, setErrors, 'update')) {
|
||||||
!formField.name.trim() ||
|
setIsSubmitting(false);
|
||||||
!formField.username.trim() ||
|
|
||||||
!formField.email.trim() ||
|
|
||||||
!formField.id_role ||
|
|
||||||
!formField.status ||
|
|
||||||
!formField.customerid
|
|
||||||
) {
|
|
||||||
setAlert({ show: true, message: 'Please fill all required field' });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +98,7 @@ const EditDialog = () => {
|
|||||||
|
|
||||||
doSaveLogActivity(createActivity);
|
doSaveLogActivity(createActivity);
|
||||||
} else {
|
} else {
|
||||||
setAlert((prev) => ({ ...prev, show: true, message: response?.message }));
|
toast.error(response?.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Something went wrong, please try again.');
|
toast.error('Something went wrong, please try again.');
|
||||||
@ -131,35 +120,48 @@ const EditDialog = () => {
|
|||||||
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC'
|
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);
|
setRoles(response?.data.list);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching role', error);
|
console.error('Error fetching role', error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getCustomerList = async (sorting: any) => {
|
const getCustomerList = async (sorting: any, filterValue: string) => {
|
||||||
try {
|
const filter: any =
|
||||||
sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting;
|
filterValue?.trim().length === 0 ? {} : { fullname: { like: `%${filterValue}%` } };
|
||||||
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'
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log('CUSTOMER: ', response?.data);
|
sorting = sorting.length == 0 ? [{ id: 'created_at', desc: false }] : sorting;
|
||||||
setCustomers(response?.data.list);
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
console.error('Error fetching customer', error);
|
toast.error('Failed to fetch customer list');
|
||||||
|
} finally {
|
||||||
|
setIsLoadingCustomer(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const doFetchUserData = useCallback(async (id: string) => {
|
const doFetchUserData = useCallback(async (id: string) => {
|
||||||
|
setIsFetching(true);
|
||||||
|
|
||||||
const response = await GetData(`${API_URL}/user/detail/${id}`, { id });
|
const response = await GetData(`${API_URL}/user/detail/${id}`, { id });
|
||||||
// console.log('User detail response:', response?.data);
|
|
||||||
|
|
||||||
if (response?.status) {
|
if (response?.status) {
|
||||||
setFormField((prev) => ({
|
setFormField((prev) => ({
|
||||||
@ -171,44 +173,71 @@ const EditDialog = () => {
|
|||||||
status: response.data.status,
|
status: response.data.status,
|
||||||
customerid: response.data.customer?.id || ''
|
customerid: response.data.customer?.id || ''
|
||||||
}));
|
}));
|
||||||
// console.log('Customer ID from API:', response?.data.customerid);
|
|
||||||
} else {
|
} else {
|
||||||
setFormField((prev) => ({
|
setFormField((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
name: '',
|
name: '',
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
id_role: '0',
|
id_role: '',
|
||||||
status: '',
|
status: '',
|
||||||
customerid: ''
|
customerid: ''
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// console.log('Fetched ID Role:', response?.data.id_role);
|
setIsFetching(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleCustomerSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (showEditDialog === false) {
|
if (showEditDialog === false) {
|
||||||
resetForm();
|
resetForm();
|
||||||
|
setDropdownOpen(false);
|
||||||
}
|
}
|
||||||
}, [showEditDialog]);
|
}, [showEditDialog]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAllData = async () => {
|
getCustomerList([{ id: 'created_at', desc: false }], '');
|
||||||
await getCustomerList([{ id: 'id', desc: false }]);
|
doFetchUserRole([{ id: 'name', desc: false }]);
|
||||||
await doFetchUserRole([{ id: 'name', desc: false }]);
|
}, []);
|
||||||
if (selectedUser) {
|
|
||||||
await doFetchUserData(selectedUser);
|
useEffect(() => {
|
||||||
|
if (selectedUser) {
|
||||||
|
doFetchUserData(selectedUser);
|
||||||
|
}
|
||||||
|
}, [selectedUser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: any) => {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||||||
|
setDropdownOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAllData();
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
}, [selectedUser]);
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
// console.log('ini role: ', roles);
|
};
|
||||||
// useEffect(() => {
|
}, []);
|
||||||
// console.log('Selected User ID Role:', formField.id_role);
|
|
||||||
// // console.log('Available Roles:', roles);
|
|
||||||
// }, [formField.id_role, roles]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
|
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
|
||||||
@ -231,160 +260,184 @@ const EditDialog = () => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
|
<DialogBody className="scrollable-y px-0 pb-0" ref={parentRef}>
|
||||||
<div className="flex flex-col px-0">
|
<div className="flex flex-col px-0">
|
||||||
{alert.show && (
|
{isFetching ? (
|
||||||
<Alert variant="danger">
|
<div className="flex flex-col items-center justify-center p-8">
|
||||||
<h3>{alert.message}</h3>
|
<div className="animate-pulse flex space-x-4 w-full">
|
||||||
</Alert>
|
<div className="flex-1 space-y-4 py-1">
|
||||||
)}
|
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||||||
<form onSubmit={doUpdateUser}>
|
<div className="space-y-2">
|
||||||
<div className="card-body grid gap-5 p-0">
|
<div className="h-4 bg-gray-200 rounded"></div>
|
||||||
<div className="w-full">
|
<div className="h-4 bg-gray-200 rounded w-5/6"></div>
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 text-gray-500">Loading User Details...</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={doUpdateUser}>
|
||||||
|
<div className="card-body grid gap-5 p-0">
|
||||||
|
<div className="flex gap-2.5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
|
<label className="form-label flex items-center gap-1 max-w-56">Name</label>
|
||||||
<Input
|
<div className="grow flex flex-col">
|
||||||
className="input"
|
<Input
|
||||||
type="text"
|
className={`input ${errors.name ? 'border-red-500' : ''}`}
|
||||||
value={formField.name}
|
type="text"
|
||||||
onChange={({ target }) =>
|
autoComplete="off"
|
||||||
setFormField((prev) => ({ ...prev, name: target.value }))
|
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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
{/* Username */}
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<div className="flex gap-2.5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">Username</label>
|
<label className="form-label flex items-center gap-1 max-w-56">Username</label>
|
||||||
<Input
|
<div className="grow flex flex-col">
|
||||||
className="input"
|
<Input
|
||||||
type="text"
|
className={`input ${errors.username ? 'border-red-500' : ''}`}
|
||||||
value={formField.username}
|
type="text"
|
||||||
onChange={({ target }) =>
|
autoComplete="off"
|
||||||
setFormField((prev) => ({ ...prev, username: target.value }))
|
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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
{/* Customer */}
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<div className="flex gap-2.5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">Customer</label>
|
<label className="form-label flex items-center gap-1 max-w-56">Customer</label>
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<div className="grow flex flex-col" ref={dropdownRef}>
|
||||||
<PopoverTrigger asChild>
|
<Input
|
||||||
<button
|
id="customer"
|
||||||
type="button"
|
type="text"
|
||||||
className="input col-span-5 text-left flex justify-between"
|
value={selectedCustomerName || searchTerm}
|
||||||
style={{ color: 'inherit' }}
|
onChange={handleCustomerSearch}
|
||||||
>
|
placeholder="Search Customer"
|
||||||
<span>
|
onClick={() => setDropdownOpen(true)}
|
||||||
{customers.find((customer) => customer.id === formField.customerid)
|
className={`input ${errors.customerid ? 'border-red-500' : ''}`}
|
||||||
?.username || 'Select Customer'}
|
/>
|
||||||
</span>
|
{dropdownOpen && (
|
||||||
<ChevronDown className="w-4 h-4 opacity-70" />
|
<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">
|
||||||
</button>
|
{filteredCustomer.length > 0 ? (
|
||||||
</PopoverTrigger>
|
filteredCustomer.map((item) => (
|
||||||
<PopoverContent className="w-[400px] p-0">
|
<div
|
||||||
<Command>
|
key={item.id}
|
||||||
<CommandInput placeholder="Search Customer..." />
|
className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
||||||
<CommandList
|
onClick={() => handleCustomerSelect(item)}
|
||||||
className="max-h-[300px] overflow-y-auto"
|
>
|
||||||
style={{ touchAction: 'pan-y' }}
|
{item.fullname}
|
||||||
onWheel={(e) => {
|
</div>
|
||||||
e.currentTarget.scrollTop += e.deltaY;
|
))
|
||||||
}}
|
) : (
|
||||||
>
|
<div className="px-4 py-2 text-gray-500">
|
||||||
<CommandEmpty>No Customer found.</CommandEmpty>
|
{isLoadingCustomer ? 'Loading...' : 'No results found'}
|
||||||
<CommandGroup>
|
</div>
|
||||||
{customers.map((customer) => (
|
)}
|
||||||
<CommandItem
|
</div>
|
||||||
key={customer.id}
|
)}
|
||||||
value={customer.username}
|
{errors.customerid && (
|
||||||
onSelect={() => {
|
<span className="text-red-500 text-xs mt-1">{errors.customerid}</span>
|
||||||
setFormField({
|
)}
|
||||||
...formField,
|
</div>
|
||||||
customerid: customer.id
|
|
||||||
});
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{customer.username}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
{/* Email */}
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<div className="flex gap-2.5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">Email</label>
|
<label className="form-label flex items-center gap-1 max-w-56">Email</label>
|
||||||
<Input
|
<div className="grow flex flex-col">
|
||||||
className="input"
|
<Input
|
||||||
type="email"
|
className={`input ${errors.email ? 'border-red-500' : ''}`}
|
||||||
value={formField.email}
|
type="email"
|
||||||
onChange={({ target }) =>
|
autoComplete="off"
|
||||||
setFormField((prev) => ({ ...prev, email: target.value }))
|
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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full">
|
{/* Role */}
|
||||||
<div className="flex items-center flex-wrap gap-2.5">
|
<div className="flex gap-2.5">
|
||||||
<label className="form-label max-w-56">Role</label>
|
<label className="form-label flex items-center gap-1 max-w-56">Role</label>
|
||||||
|
<div className="grow flex flex-col">
|
||||||
<div className="grow">
|
|
||||||
<Select
|
<Select
|
||||||
value={formField.id_role}
|
value={formField.id_role}
|
||||||
onValueChange={(id_role) => {
|
onValueChange={(id_role) => {
|
||||||
// console.log('Role changed to:', id_role);
|
setTimeout(() => {
|
||||||
setFormField((prev) => ({ ...prev, id_role }));
|
setFormField((prev) => ({ ...prev, id_role }));
|
||||||
|
setErrors((prev) => ({ ...prev, id_role: '' }));
|
||||||
|
}, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className={errors.id_role ? 'border-red-500' : ''}>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{roles.map((role, idx) => (
|
{roles.map((role) => (
|
||||||
<SelectItem value={role.id} key={role.id}>
|
<SelectItem value={role.id} key={role.id}>
|
||||||
{role.name}
|
{role.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
{errors.id_role && (
|
||||||
|
<span className="text-red-500 text-xs mt-1">{errors.id_role}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center flex-wrap gap-2.5">
|
{/* Status */}
|
||||||
<label className="form-label max-w-56">Status</label>
|
<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>
|
||||||
|
|
||||||
<div className="grow">
|
<div className="flex justify-end pt-2.5">
|
||||||
<Select
|
<Button className="btn btn-primary" type="submit" disabled={isSubmitting}>
|
||||||
value={formField.status}
|
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
onValueChange={(status) => setFormField((prev) => ({ ...prev, status }))}
|
</Button>
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Y">Active</SelectItem>
|
|
||||||
<SelectItem value="N">Non Active</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
<div className="flex justify-end pt-2.5">
|
)}
|
||||||
<Button className="btn btn-primary" type="submit" disabled={isSubmitting}>
|
|
||||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogBody>
|
</DialogBody>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
91
src/pages/settings/user/manage-user/blocks/Types.ts
Normal file
91
src/pages/settings/user/manage-user/blocks/Types.ts
Normal file
@ -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<React.SetStateAction<Record<string, string>>>,
|
||||||
|
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<string, string> = {};
|
||||||
|
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;
|
||||||
|
};
|
||||||
@ -232,8 +232,6 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode })
|
|||||||
handleDeleteDialog
|
handleDeleteDialog
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toaster expand visibleToasts={9} duration={3000} />
|
|
||||||
|
|
||||||
<DataGridProvider
|
<DataGridProvider
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{ size: 10 }}
|
pagination={{ size: 10 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user