All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 36s
356 lines
14 KiB
JavaScript
356 lines
14 KiB
JavaScript
import React, { useMemo, useState, useEffect } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Button, Modal, Spinner } from "react-bootstrap";
|
|
import { AuthService, EmployeeService, EventsService } from "../../services";
|
|
import { EMPLOYEE_PERMISSION_GROUPS } from "../../shared";
|
|
|
|
const SYSTEM_ACCESS_PERMISSION = "System Access";
|
|
|
|
const EmployeeList = () => {
|
|
const navigate = useNavigate();
|
|
const currentSite = EventsService.site || 3;
|
|
const [employees, setEmployees] = useState([]);
|
|
const [keyword, setKeyword] = useState('');
|
|
const [showInactive, setShowInactive] = useState(false);
|
|
const [isHrLoading, setIsHrLoading] = useState(false);
|
|
const [isSavingHrPermission, setIsSavingHrPermission] = useState(false);
|
|
const [hrKeyword, setHrKeyword] = useState('');
|
|
const [hrPermissionMap, setHrPermissionMap] = useState({});
|
|
const [hrPermissionRecords, setHrPermissionRecords] = useState([]);
|
|
const [editingHrUser, setEditingHrUser] = useState(undefined);
|
|
const [showHrPermissionModal, setShowHrPermissionModal] = useState(false);
|
|
const [selectedHrPermissions, setSelectedHrPermissions] = useState([SYSTEM_ACCESS_PERMISSION]);
|
|
|
|
useEffect(() => {
|
|
if (!AuthService.canViewEmployees()) {
|
|
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.')
|
|
AuthService.logout();
|
|
navigate(`/login`);
|
|
}
|
|
EmployeeService.getAllEmployees().then((data) =>
|
|
setEmployees(data.data)
|
|
);
|
|
loadHrPermissionsBySite(currentSite);
|
|
}, []);
|
|
|
|
const loadHrPermissionsBySite = (site) => {
|
|
setIsHrLoading(true);
|
|
EmployeeService.getExternalUserPermissionsList(site)
|
|
.then((response) => {
|
|
const records = Array.isArray(response?.data) ? response.data : [];
|
|
const nextMap = {};
|
|
records.forEach((item) => {
|
|
const key = item?.external_user_id;
|
|
if (!key) return;
|
|
nextMap[key] = Array.isArray(item?.permissions) ? item.permissions : [];
|
|
});
|
|
setHrPermissionMap(nextMap);
|
|
setHrPermissionRecords(records);
|
|
})
|
|
.catch(() => {
|
|
setHrPermissionMap({});
|
|
setHrPermissionRecords([]);
|
|
})
|
|
.finally(() => {
|
|
setIsHrLoading(false);
|
|
});
|
|
};
|
|
|
|
const redirectToAdmin = () => {
|
|
navigate(`/admin/customer-report`)
|
|
}
|
|
|
|
const goToEdit = (id) => {
|
|
navigate(`/employees/edit/${id}`)
|
|
}
|
|
|
|
|
|
const goToView = (id) => {
|
|
navigate(`/employees/${id}`)
|
|
}
|
|
|
|
const goToCreate = () => {
|
|
navigate(`/employees`)
|
|
}
|
|
|
|
const goToExternalImport = () => {
|
|
navigate(`/employees/external-import`);
|
|
}
|
|
|
|
const getExternalUserId = (item) => item?.external_user_id || item?.employee_id || '';
|
|
|
|
const filteredHrUsers = useMemo(() => {
|
|
return (hrPermissionRecords || [])
|
|
.filter((item) => Number(item?.allow_site) === Number(currentSite))
|
|
.filter((item) => {
|
|
if (!hrKeyword) return true;
|
|
const key = hrKeyword.toLowerCase();
|
|
return (
|
|
(item?.username || '').toLowerCase().includes(key) ||
|
|
(item?.name || '').toLowerCase().includes(key)
|
|
);
|
|
});
|
|
}, [hrPermissionRecords, hrKeyword, currentSite]);
|
|
|
|
const openHrPermissionModal = (hrUser) => {
|
|
setEditingHrUser(hrUser);
|
|
const externalUserId = getExternalUserId(hrUser);
|
|
const existingPermissions = hrPermissionMap?.[externalUserId] || [];
|
|
setSelectedHrPermissions(Array.from(new Set([SYSTEM_ACCESS_PERMISSION, ...existingPermissions])));
|
|
setShowHrPermissionModal(true);
|
|
};
|
|
|
|
const closeHrPermissionModal = () => {
|
|
if (isSavingHrPermission) return;
|
|
setShowHrPermissionModal(false);
|
|
setEditingHrUser(undefined);
|
|
setSelectedHrPermissions([SYSTEM_ACCESS_PERMISSION]);
|
|
};
|
|
|
|
const toggleHrPermission = (permissionKey) => {
|
|
if (permissionKey === SYSTEM_ACCESS_PERMISSION) return;
|
|
setSelectedHrPermissions((prev) => {
|
|
if (prev.includes(permissionKey)) {
|
|
return prev.filter((item) => item !== permissionKey);
|
|
}
|
|
return [...prev, permissionKey];
|
|
});
|
|
};
|
|
|
|
const isHrPermissionGroupFullySelected = (permissionItems = []) => {
|
|
return permissionItems.length > 0 && permissionItems.every((permissionKey) => selectedHrPermissions.includes(permissionKey));
|
|
};
|
|
|
|
const toggleHrPermissionGroup = (permissionItems = [], checked) => {
|
|
if (!Array.isArray(permissionItems) || permissionItems.length === 0) return;
|
|
setSelectedHrPermissions((prev) => {
|
|
const nextSet = new Set(prev);
|
|
permissionItems.forEach((permissionKey) => {
|
|
if (permissionKey === SYSTEM_ACCESS_PERMISSION) {
|
|
nextSet.add(SYSTEM_ACCESS_PERMISSION);
|
|
return;
|
|
}
|
|
if (checked) {
|
|
nextSet.add(permissionKey);
|
|
} else {
|
|
nextSet.delete(permissionKey);
|
|
}
|
|
});
|
|
return Array.from(nextSet);
|
|
});
|
|
};
|
|
|
|
const saveHrPermissions = () => {
|
|
const externalUserId = getExternalUserId(editingHrUser);
|
|
if (!externalUserId) return;
|
|
setIsSavingHrPermission(true);
|
|
EmployeeService.saveExternalUserPermission({
|
|
external_user_id: externalUserId,
|
|
username: editingHrUser.username || '',
|
|
name: editingHrUser.name || '',
|
|
email: editingHrUser.email || '',
|
|
allow_site: Number(currentSite),
|
|
permissions: selectedHrPermissions
|
|
})
|
|
.then(() => {
|
|
setHrPermissionMap((prev) => ({
|
|
...prev,
|
|
[externalUserId]: Array.from(new Set([SYSTEM_ACCESS_PERMISSION, ...selectedHrPermissions]))
|
|
}));
|
|
loadHrPermissionsBySite(currentSite);
|
|
closeHrPermissionModal();
|
|
})
|
|
.catch((error) => {
|
|
window.alert(error?.response?.data?.message || 'Failed to save HR user permissions.');
|
|
})
|
|
.finally(() => {
|
|
setIsSavingHrPermission(false);
|
|
});
|
|
};
|
|
|
|
const revokeHrPermissions = (hrUser) => {
|
|
const externalUserId = getExternalUserId(hrUser);
|
|
if (!externalUserId) return;
|
|
if (!window.confirm(`Revoke all permissions for ${hrUser?.username || 'this HR user'} on Site ${currentSite}?`)) {
|
|
return;
|
|
}
|
|
EmployeeService.revokeExternalUserPermission(externalUserId, Number(currentSite))
|
|
.then(() => {
|
|
setHrPermissionMap((prev) => {
|
|
const next = { ...prev };
|
|
delete next[externalUserId];
|
|
return next;
|
|
});
|
|
loadHrPermissionsBySite(currentSite);
|
|
})
|
|
.catch((error) => {
|
|
window.alert(error?.response?.data?.message || 'Failed to revoke HR user permissions.');
|
|
});
|
|
};
|
|
|
|
|
|
return (
|
|
<>
|
|
<div className="list row mb-4">
|
|
<div className="col-md-12 text-primary">
|
|
<h5>
|
|
All Employees
|
|
{AuthService.canAddOrEditEmployees() && (
|
|
<button className="btn btn-primary btn-sm ms-2" onClick={() => goToCreate()}>
|
|
Add New Employee
|
|
</button>
|
|
)}
|
|
{AuthService.canAddOrEditEmployees() && (
|
|
<button className="btn btn-primary btn-sm ms-2" onClick={() => goToExternalImport()}>
|
|
Add New Employee From HR System
|
|
</button>
|
|
)}
|
|
</h5>
|
|
</div>
|
|
</div>
|
|
<div className="list row mb-4">
|
|
<div className="col-md-12">
|
|
<h6 className="text-primary">HR System Users</h6>
|
|
<div className="mb-3 d-flex align-items-center" style={{ gap: '12px', flexWrap: 'wrap' }}>
|
|
<label>
|
|
Site: <strong className="ms-2">{currentSite}</strong>
|
|
</label>
|
|
<label>
|
|
Filter:
|
|
<input className="ms-2" type="text" value={hrKeyword} onChange={(e) => setHrKeyword(e.currentTarget.value)} />
|
|
</label>
|
|
<button className="btn btn-primary btn-sm" onClick={() => loadHrPermissionsBySite(currentSite)} disabled={isHrLoading}>
|
|
{isHrLoading ? 'Loading...' : 'Refresh HR Users'}
|
|
</button>
|
|
</div>
|
|
<table className="personnel-info-table mb-5">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Name</th>
|
|
<th>Title</th>
|
|
<th>Site</th>
|
|
<th>Configured Permissions</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{isHrLoading && (
|
|
<tr>
|
|
<td colSpan={6}><Spinner size="sm" className="me-2" />Loading HR users...</td>
|
|
</tr>
|
|
)}
|
|
{!isHrLoading && filteredHrUsers.map((hrUser) => {
|
|
const externalUserId = getExternalUserId(hrUser);
|
|
const configuredPermissions = hrPermissionMap?.[externalUserId] || hrUser?.permissions || [];
|
|
return (
|
|
<tr key={`${externalUserId}-${hrUser?.allow_site}`}>
|
|
<td>{hrUser?.username}</td>
|
|
<td>{hrUser?.name}</td>
|
|
<td>{hrUser?.title || '-'}</td>
|
|
<td>{hrUser?.allow_site || currentSite}</td>
|
|
<td>{configuredPermissions.length > 0 ? configuredPermissions.join(', ') : '-'}</td>
|
|
<td>
|
|
<button className="btn btn-primary btn-sm me-2" onClick={() => openHrPermissionModal(hrUser)}>Edit</button>
|
|
<button className="btn btn-danger btn-sm" onClick={() => revokeHrPermissions(hrUser)}>Revoke</button>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
{!isHrLoading && filteredHrUsers.length === 0 && (
|
|
<tr>
|
|
<td colSpan={6} style={{ textAlign: 'center' }}>No HR users found for selected site.</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
|
|
<h6 className="text-primary">Internal Employees</h6>
|
|
<div className="mb-4">Filter By Name: <input type="text" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)}/></div>
|
|
<input className="mb-4 me-2" type="checkbox" value={showInactive} checked={showInactive === true} onChange={() => setShowInactive(!showInactive)} />
|
|
Show Inactive Employees
|
|
<table className="personnel-info-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Preferred Name</th>
|
|
<th>Username</th>
|
|
<th>Status</th>
|
|
<th>Permissions</th>
|
|
<th></th>
|
|
</tr>
|
|
|
|
</thead>
|
|
<tbody>
|
|
{
|
|
employees && employees.filter(item => showInactive ? item : item.status === 'active').filter((item)=> item?.name.toLowerCase().includes(keyword.toLowerCase())).map(employee => <tr key={employee.id}>
|
|
<td>{employee?.name}</td>
|
|
<td>{employee?.name_cn}</td>
|
|
<td>{employee?.username}</td>
|
|
<td>{employee?.status}</td>
|
|
<td>{employee?.permissions?.join(', ') || '-'}</td>
|
|
<td>
|
|
{AuthService.canAddOrEditEmployees() && <button className="btn btn-primary btn-sm me-2" onClick={() => goToEdit(employee?.id)}>Edit</button> }
|
|
{AuthService.canViewEmployees() && <button className="btn btn-default btn-sm me-2" onClick={() => goToView(employee?.id)}>View</button>}
|
|
</td>
|
|
</tr>)
|
|
}
|
|
</tbody>
|
|
</table>
|
|
|
|
</div>
|
|
</div>
|
|
<Modal show={showHrPermissionModal} onHide={closeHrPermissionModal} size="lg">
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>Edit HR User Permissions - {editingHrUser?.username}</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
<div className="mb-3">
|
|
<strong>Allow Site:</strong> {currentSite}
|
|
</div>
|
|
{Object.entries(EMPLOYEE_PERMISSION_GROUPS).map(([groupName, permissionItems]) => (
|
|
<div key={groupName} className="mb-3">
|
|
<div className="d-flex align-items-center justify-content-between mb-1" style={{ gap: '16px' }}>
|
|
<div className="text-primary">{groupName}</div>
|
|
<label>
|
|
<input
|
|
type="checkbox"
|
|
className="me-2"
|
|
checked={isHrPermissionGroupFullySelected(permissionItems)}
|
|
onChange={(e) => toggleHrPermissionGroup(permissionItems, e.currentTarget.checked)}
|
|
disabled={groupName === 'System'}
|
|
/>
|
|
Select All
|
|
</label>
|
|
</div>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px 18px' }}>
|
|
{permissionItems.map((permissionKey) => (
|
|
<label key={permissionKey} style={{ minWidth: '280px' }}>
|
|
<input
|
|
type="checkbox"
|
|
className="me-2"
|
|
checked={selectedHrPermissions.includes(permissionKey)}
|
|
onChange={() => toggleHrPermission(permissionKey)}
|
|
disabled={permissionKey === SYSTEM_ACCESS_PERMISSION}
|
|
/>
|
|
{permissionKey}{permissionKey === SYSTEM_ACCESS_PERMISSION ? ' (required)' : ''}
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</Modal.Body>
|
|
<Modal.Footer>
|
|
<Button variant="secondary" onClick={closeHrPermissionModal} disabled={isSavingHrPermission}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="primary" onClick={saveHrPermissions} disabled={isSavingHrPermission}>
|
|
{isSavingHrPermission ? <><Spinner size="sm" className="me-2" />Saving...</> : 'Done'}
|
|
</Button>
|
|
</Modal.Footer>
|
|
</Modal>
|
|
</>
|
|
)
|
|
};
|
|
|
|
export default EmployeeList; |