Compare commits

..

3 Commits

Author SHA1 Message Date
7a8a46ee64 fix: remove duplicate /roles in API endpoints
- Fix getAllRole endpoint from roles/roles to roles
- Fix getRoleById endpoint from roles/roles/:id to roles/:id
- Fix createRole endpoint from roles/roles to roles
- Fix updateRole endpoint from roles/roles/:id to roles/:id
- Fix deleteRole endpoint from roles/roles/:id to roles/:id
- Add try-catch error handling in getAllRole
- Support both server-side and client-side pagination

This fixes SQL error: Conversion failed when converting the nvarchar value 'roles' to data type int
Backend was interpreting duplicate /roles as an ID parameter
2025-10-10 15:45:39 +07:00
c3b5ec2121 feat: refactor role display with dynamic colors and capitalize
- Add helper function capitalizeFirstLetter for consistent formatting
- Add helper function getRoleColor to determine tag color by role_level or role_name
- Refactor role_name column render to use dynamic helpers
- Replace 30+ lines of hardcoded conditions with 8 lines of clean code
- Support any role name from database without code changes
- Display role names with first letter capitalized
- Color mapping: Level 1=purple, Level 2=blue, Level 3=cyan, Level 4=green
- Add KeyOutlined icon import for change password feature
2025-10-10 15:44:46 +07:00
59c90c3519 feat: integrate dynamic role selection in user form
- Add getAllRole API import to fetch roles dynamically
- Add roleList and loadingRoles state management
- Implement fetchRoles function to get all roles from API
- Update Select component to display roles dynamically from database
- Display role format: Role Name (Level X)
- Add loading indicator while fetching roles
- Fetch roles automatically when modal opens
2025-10-10 15:44:21 +07:00
3 changed files with 102 additions and 41 deletions

View File

@@ -1,49 +1,76 @@
import { SendRequest } from '../components/Global/ApiRequest';
const getAllRole = async (queryParams) => {
const response = await SendRequest({
method: 'get',
prefix: `roles/roles?${queryParams.toString()}`,
});
try {
const response = await SendRequest({
method: 'get',
prefix: `roles?${queryParams.toString()}`,
});
console.log('Role API Response:', response);
console.log('Role API Response:', response);
// Parse query params to get page and limit
const params = Object.fromEntries(queryParams);
const currentPage = parseInt(params.page) || 1;
const currentLimit = parseInt(params.limit) || 10;
// Check if backend returns paginated data
if (response.paging) {
// Backend already provides pagination info
return {
status: response.statusCode || 200,
data: {
data: response.data || [],
paging: response.paging,
total: response.paging.total || 0
}
};
}
// Backend returns all data, so we need to do client-side pagination
const allData = response.data || [];
const totalData = allData.length;
// Fallback: If backend returns all data without pagination
const params = Object.fromEntries(queryParams);
const currentPage = parseInt(params.page) || 1;
const currentLimit = parseInt(params.limit) || 10;
// Calculate start and end index for current page
const startIndex = (currentPage - 1) * currentLimit;
const endIndex = startIndex + currentLimit;
const allData = response.data || [];
const totalData = allData.length;
// Slice data for current page
const paginatedData = allData.slice(startIndex, endIndex);
// Client-side pagination
const startIndex = (currentPage - 1) * currentLimit;
const endIndex = startIndex + currentLimit;
const paginatedData = allData.slice(startIndex, endIndex);
// Transform response to match TableList expected structure
return {
status: response.statusCode || 200,
data: {
data: paginatedData,
paging: {
page: currentPage,
limit: currentLimit,
return {
status: response.statusCode || 200,
data: {
data: paginatedData,
paging: {
page: currentPage,
limit: currentLimit,
total: totalData,
page_total: Math.ceil(totalData / currentLimit),
},
total: totalData,
page_total: Math.ceil(totalData / currentLimit),
},
total: totalData,
},
};
};
} catch (error) {
console.error('getAllRole error:', error);
return {
status: 500,
data: {
data: [],
paging: {
page: 1,
limit: 10,
total: 0,
page_total: 0
},
total: 0
},
error: error.message
};
}
};
const getRoleById = async (id) => {
const response = await SendRequest({
method: 'get',
prefix: `roles/roles/${id}`,
prefix: `roles/${id}`,
});
return response.data;
};
@@ -51,7 +78,7 @@ const getRoleById = async (id) => {
const createRole = async (queryParams) => {
const response = await SendRequest({
method: 'post',
prefix: `roles/roles`,
prefix: `roles`,
params: queryParams,
});
@@ -90,7 +117,7 @@ const createRole = async (queryParams) => {
const updateRole = async (role_id, queryParams) => {
const response = await SendRequest({
method: 'put',
prefix: `roles/roles/${role_id}`,
prefix: `roles/${role_id}`,
params: queryParams,
});
@@ -129,7 +156,7 @@ const updateRole = async (role_id, queryParams) => {
const deleteRole = async (queryParams) => {
const response = await SendRequest({
method: 'delete',
prefix: `roles/roles/${queryParams}`,
prefix: `roles/${queryParams}`,
});
console.log('Delete API Response:', response);

View File

@@ -41,11 +41,32 @@ const DetailUser = (props) => {
const validatePassword = (password) => {
if (!password) return 'Password wajib diisi';
if (password.length < 8) return 'Password minimal 8 karakter';
if (!/[A-Z]/.test(password)) return 'Password harus ada huruf besar';
if (!/[a-z]/.test(password)) return 'Password harus ada huruf kecil';
if (!/\d/.test(password)) return 'Password harus ada angka';
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) return 'Password harus ada karakter spesial';
// Must be at least 8 characters long
if (password.length < 8) {
return 'Password must be at least 8 characters long';
}
// Must contain at least one uppercase letter
if (!/[A-Z]/.test(password)) {
return 'Password must contain at least one uppercase letter';
}
// Must contain at least one lowercase letter
if (!/[a-z]/.test(password)) {
return 'Password must contain at least one lowercase letter';
}
// Must contain at least one number
if (!/\d/.test(password)) {
return 'Password must contain at least one number';
}
// Must contain at least one special character
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
return 'Password must contain at least one special character';
}
return null;
};

View File

@@ -8,6 +8,7 @@ import {
SearchOutlined,
CheckOutlined,
CloseOutlined,
KeyOutlined,
} from '@ant-design/icons';
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
import { useNavigate } from 'react-router-dom';
@@ -49,7 +50,7 @@ const getRoleColor = (role_name, role_level) => {
return 'default';
};
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveDialog) => [
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveDialog, showChangePasswordModal) => [
{
title: 'ID',
dataIndex: 'user_id',
@@ -135,7 +136,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD
title: 'Aksi',
key: 'aksi',
align: 'center',
width: '15%',
width: '18%',
render: (_, record) => (
<Space>
<Button
@@ -158,6 +159,12 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveD
icon={<EditOutlined style={{ color: '#faad14' }} />}
onClick={() => showEditModal(record)}
/>
<Button
type="text"
style={{ borderColor: '#722ed1' }}
icon={<KeyOutlined style={{ color: '#722ed1' }} />}
onClick={() => showChangePasswordModal(record)}
/>
<Button
type="text"
danger
@@ -255,6 +262,11 @@ const ListUser = memo(function ListUser(props) {
});
};
const showChangePasswordModal = (param) => {
props.setSelectedUserForPassword(param);
props.setShowChangePasswordModal(true);
};
const handleApprove = async (user_id) => {
const response = await approveUser(user_id);
if (response.statusCode == 200) {
@@ -366,7 +378,8 @@ const ListUser = memo(function ListUser(props) {
showPreviewModal,
showEditModal,
showDeleteDialog,
showApproveDialog
showApproveDialog,
showChangePasswordModal
)}
triger={trigerFilter}
/>