fix
Some checks failed
Build And Deploy Main / build-and-deploy (push) Has been cancelled

This commit is contained in:
2026-03-12 16:36:45 -04:00
parent 213de526c4
commit d17984fa51
8 changed files with 127 additions and 39 deletions

View File

@@ -47,7 +47,6 @@ const ALL_PERMISSIONS = [
'Edit_Vehicle info_Repair Records', 'Edit_Vehicle info_Repair Records',
'Add_New Vehicle', 'Add_New Vehicle',
'Archive_Vehicle', 'Archive_Vehicle',
'Delete_Vehicle',
'Export_Vehicle Report', 'Export_Vehicle Report',
'View_Transportation Schedule_Route Overview', 'View_Transportation Schedule_Route Overview',
'Create&Edit_Transportation Schedule', 'Create&Edit_Transportation Schedule',
@@ -145,4 +144,31 @@ exports.login = (req, res) => {
} else { } else {
throw(Error('email or username is required')); throw(Error('email or username is required'));
} }
} }
exports.me = (req, res) => {
const condition = splitSite.splitSiteGet(req, { _id: req.userId });
Employee.findOne(condition)
.then((employee) => {
if (!employee) {
return res.status(404).send({ message: "User not found" });
}
if (!isEmployeeActive(employee)) {
return res.status(403).send({ message: "User is not activated" });
}
return res.send({
username: employee.username,
email: employee.email,
roles: employee.roles,
permissions: getEffectivePermissions(employee),
id: employee.id,
name: employee.name,
name_cn: employee.name_cn
});
})
.catch((err) => {
res.status(500).send({
message: err.message || "Failed to fetch current user"
});
});
};

View File

@@ -1,5 +1,6 @@
module.exports = app => { module.exports = app => {
const auth = require("../controllers/auth.controller"); const auth = require("../controllers/auth.controller");
const { authJwt } = require("../middlewares");
app.use(function(req, res, next) { app.use(function(req, res, next) {
res.header( res.header(
"Access-Control-Allow-Headers", "Access-Control-Allow-Headers",
@@ -8,4 +9,5 @@ module.exports = app => {
next(); next();
}); });
app.post('/api/auth/login', auth.login); app.post('/api/auth/login', auth.login);
app.get('/api/auth/me', [authJwt.verifyToken], auth.me);
}; };

View File

@@ -89,6 +89,7 @@ import { AuthService } from "./services";
function App() { function App() {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [isPermissionReady, setIsPermissionReady] = useState(false);
// const getLogo = () => { // const getLogo = () => {
// return (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2') || window.location.host.includes('ws2') ||window.location.hostname.includes('localhost')) ? "/images/logo2.png" : ((window.location.hostname.includes('worldshine3.mayo.llc') ||window.location.hostname.includes('site3') || window.location.hostname.includes('ws3')) ? "/images/logo3.png" : "/images/logo1.png"); // return (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2') || window.location.host.includes('ws2') ||window.location.hostname.includes('localhost')) ? "/images/logo2.png" : ((window.location.hostname.includes('worldshine3.mayo.llc') ||window.location.hostname.includes('site3') || window.location.hostname.includes('ws3')) ? "/images/logo3.png" : "/images/logo1.png");
// } // }
@@ -158,6 +159,32 @@ function App() {
}; };
}, []); }, []);
useEffect(() => {
let isMounted = true;
const recheckPermissions = async () => {
try {
if (localStorage.getItem('token')) {
await AuthService.refreshCurrentUserPermissions();
}
} catch (error) {
// Token failures are handled by axios interceptor (logout + redirect).
console.log(error);
} finally {
if (isMounted) {
setIsPermissionReady(true);
}
}
};
recheckPermissions();
return () => {
isMounted = false;
};
}, []);
if (!isPermissionReady) {
return null;
}
return ( return (
<> <>
{/* <div className="logo"> {/* <div className="logo">

View File

@@ -7,6 +7,7 @@ import moment from 'moment';
const InfoScreen = () => { const InfoScreen = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const canEditInfoScreen = AuthService.canEditInfoScreen();
const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar'); const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar');
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [medicalEvents, setMedicalEvents] = useState([]); const [medicalEvents, setMedicalEvents] = useState([]);
@@ -793,14 +794,16 @@ const InfoScreen = () => {
<img src="/images/logo-trans.png" alt="Worldshine Logo" style={{ height: '30px', marginRight: '10px' }} /> <img src="/images/logo-trans.png" alt="Worldshine Logo" style={{ height: '30px', marginRight: '10px' }} />
<strong className="logo-worldshine" style={{ color: '#0066B1', fontSize: '16px' }}>Worldshine</strong> <strong className="logo-worldshine" style={{ color: '#0066B1', fontSize: '16px' }}>Worldshine</strong>
</div> </div>
<Button {canEditInfoScreen && (
variant="link" <Button
className="p-0" variant="link"
onClick={handleEditClick} className="p-0"
style={{ color: '#666' }} onClick={handleEditClick}
> style={{ color: '#666' }}
<PencilSquare size={16} /> >
</Button> <PencilSquare size={16} />
</Button>
)}
</div> </div>
{/* Content Layout */} {/* Content Layout */}
@@ -848,14 +851,16 @@ const InfoScreen = () => {
{/* Header with Title and Edit Button */} {/* Header with Title and Edit Button */}
<div className="d-flex justify-content-between align-items-center mb-3"> <div className="d-flex justify-content-between align-items-center mb-3">
<h6 className="text-black mb-0" style={{ fontSize: '14px', fontWeight: '500' }}>Gallery</h6> <h6 className="text-black mb-0" style={{ fontSize: '14px', fontWeight: '500' }}>Gallery</h6>
<Button {canEditInfoScreen && (
variant="link" <Button
className="p-0" variant="link"
onClick={handleCarouselEditClick} className="p-0"
style={{ color: '#666' }} onClick={handleCarouselEditClick}
> style={{ color: '#666' }}
<PencilSquare size={16} /> >
</Button> <PencilSquare size={16} />
</Button>
)}
</div> </div>
{/* Image Carousel */} {/* Image Carousel */}
@@ -1173,9 +1178,11 @@ const InfoScreen = () => {
{!isFullScreen && ( {!isFullScreen && (
<div className="list-func-panel"> <div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={handleBackgroundSelect}> {canEditInfoScreen && (
<FileImage size={16} className="me-2"></FileImage>Choose Background <button className="btn btn-primary me-2" onClick={handleBackgroundSelect}>
</button> <FileImage size={16} className="me-2"></FileImage>Choose Background
</button>
)}
<button className="btn btn-primary me-2" onClick={handleFullScreen}> <button className="btn btn-primary me-2" onClick={handleFullScreen}>
<ArrowsFullscreen size={16} className="me-2"></ArrowsFullscreen>Full Screen <ArrowsFullscreen size={16} className="me-2"></ArrowsFullscreen>Full Screen
</button> </button>
@@ -1185,7 +1192,7 @@ const InfoScreen = () => {
</div> </div>
{/* Edit AttendanceNote Modal */} {/* Edit AttendanceNote Modal */}
<Modal show={showEditModal} onHide={handleCloseModal} size="lg"> {canEditInfoScreen && <Modal show={showEditModal} onHide={handleCloseModal} size="lg">
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>Edit Attendance Note</Modal.Title> <Modal.Title>Edit Attendance Note</Modal.Title>
</Modal.Header> </Modal.Header>
@@ -1264,10 +1271,10 @@ const InfoScreen = () => {
Save Changes Save Changes
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>}
{/* Gallery Edit Modal */} {/* Gallery Edit Modal */}
<Modal show={showGalleryModal} onHide={handleGalleryClose} size="lg"> {canEditInfoScreen && <Modal show={showGalleryModal} onHide={handleGalleryClose} size="lg">
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>Update Images</Modal.Title> <Modal.Title>Update Images</Modal.Title>
</Modal.Header> </Modal.Header>
@@ -1333,10 +1340,10 @@ const InfoScreen = () => {
Save Changes Save Changes
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>}
{/* Background Selection Modal */} {/* Background Selection Modal */}
<Modal show={showBackgroundModal} onHide={handleBackgroundClose} size="md"> {canEditInfoScreen && <Modal show={showBackgroundModal} onHide={handleBackgroundClose} size="md">
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>Choose Background Image</Modal.Title> <Modal.Title>Choose Background Image</Modal.Title>
</Modal.Header> </Modal.Header>
@@ -1386,7 +1393,7 @@ const InfoScreen = () => {
Save Background Save Background
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>}
{/* Full Screen Hint */} {/* Full Screen Hint */}
{isFullScreen && ( {isFullScreen && (

View File

@@ -11,6 +11,7 @@ import 'react-datepicker/dist/react-datepicker.css';
const MessageList = () => { const MessageList = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const canEditMessagingTemplate = AuthService.hasPermission('Create&Edit_Messaging Template');
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]);
const [activeTab, setActiveTab] = useState('allMessages'); const [activeTab, setActiveTab] = useState('allMessages');
@@ -90,6 +91,7 @@ const MessageList = () => {
// ---- Custom Template CRUD ---- // ---- Custom Template CRUD ----
const openCreateTemplateModal = () => { const openCreateTemplateModal = () => {
if (!canEditMessagingTemplate) return;
setEditingTemplate(null); setEditingTemplate(null);
setTemplateTitle(''); setTemplateTitle('');
setTemplateChinese(''); setTemplateChinese('');
@@ -98,6 +100,7 @@ const MessageList = () => {
}; };
const openEditTemplateModal = (template) => { const openEditTemplateModal = (template) => {
if (!canEditMessagingTemplate) return;
setEditingTemplate(template); setEditingTemplate(template);
setTemplateTitle(template.title || ''); setTemplateTitle(template.title || '');
setTemplateChinese(template.chinese || ''); setTemplateChinese(template.chinese || '');
@@ -114,6 +117,7 @@ const MessageList = () => {
}; };
const handleSaveTemplate = () => { const handleSaveTemplate = () => {
if (!canEditMessagingTemplate) return;
if (!templateTitle.trim()) { window.alert('Template Title is required.'); return; } if (!templateTitle.trim()) { window.alert('Template Title is required.'); return; }
if (!templateChinese.trim()) { window.alert('Message Content (Chinese) is required.'); return; } if (!templateChinese.trim()) { window.alert('Message Content (Chinese) is required.'); return; }
if (!templateEnglish.trim()) { window.alert('Message Content (English) is required.'); return; } if (!templateEnglish.trim()) { window.alert('Message Content (English) is required.'); return; }
@@ -131,6 +135,7 @@ const MessageList = () => {
}; };
const handleDeleteTemplate = (id) => { const handleDeleteTemplate = (id) => {
if (!canEditMessagingTemplate) return;
if (window.confirm('Are you sure you want to delete this template?')) { if (window.confirm('Are you sure you want to delete this template?')) {
MessageService.deleteCustomTemplate(id).then(() => { fetchCustomTemplates(); }); MessageService.deleteCustomTemplate(id).then(() => { fetchCustomTemplates(); });
} }
@@ -399,7 +404,7 @@ const MessageList = () => {
<th>Template Title</th> <th>Template Title</th>
<th>Message Content (Chinese)</th> <th>Message Content (Chinese)</th>
<th>Message Content (English)</th> <th>Message Content (English)</th>
<th style={{ width: '80px' }}>Actions</th> {canEditMessagingTemplate && <th style={{ width: '80px' }}>Actions</th>}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -409,14 +414,14 @@ const MessageList = () => {
<td>{template?.title}</td> <td>{template?.title}</td>
<td style={{ maxWidth: '300px', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{template?.chinese}</td> <td style={{ maxWidth: '300px', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{template?.chinese}</td>
<td style={{ maxWidth: '300px', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{template?.english}</td> <td style={{ maxWidth: '300px', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{template?.english}</td>
<td> {canEditMessagingTemplate && <td>
<PencilSquare size={16} className="clickable me-2" onClick={() => openEditTemplateModal(template)} /> <PencilSquare size={16} className="clickable me-2" onClick={() => openEditTemplateModal(template)} />
<Trash size={16} className="clickable" color="#dc3545" onClick={() => handleDeleteTemplate(template.id)} /> <Trash size={16} className="clickable" color="#dc3545" onClick={() => handleDeleteTemplate(template.id)} />
</td> </td>}
</tr> </tr>
))} ))}
{customTemplates.length === 0 && ( {customTemplates.length === 0 && (
<tr><td colSpan="5" style={{ textAlign: 'center', color: '#999', padding: '24px' }}>No templates yet. Create one to get started.</td></tr> <tr><td colSpan={canEditMessagingTemplate ? 5 : 4} style={{ textAlign: 'center', color: '#999', padding: '24px' }}>No templates yet. Create one to get started.</td></tr>
)} )}
</tbody> </tbody>
</table> </table>
@@ -482,14 +487,14 @@ const MessageList = () => {
</> </>
)} )}
{activeTab === 'messageTemplate' && ( {activeTab === 'messageTemplate' && (
<button className="btn btn-primary btn-sm" onClick={openCreateTemplateModal}><Plus size={16} className="me-1" />Create Message Template</button> canEditMessagingTemplate && <button className="btn btn-primary btn-sm" onClick={openCreateTemplateModal}><Plus size={16} className="me-1" />Create Message Template</button>
)} )}
</div> </div>
</div> </div>
</div> </div>
{/* Create / Edit Message Template Modal */} {/* Create / Edit Message Template Modal */}
<Modal show={showCreateTemplateModal} onHide={closeTemplateModal} centered> <Modal show={canEditMessagingTemplate && showCreateTemplateModal} onHide={closeTemplateModal} centered>
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>{editingTemplate ? 'Edit Message Template' : 'Create New Message Template'}</Modal.Title> <Modal.Title>{editingTemplate ? 'Edit Message Template' : 'Create New Message Template'}</Modal.Title>
</Modal.Header> </Modal.Header>

View File

@@ -18,6 +18,7 @@ import {
const UpdateVehicle = () => { const UpdateVehicle = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const canArchiveVehicle = AuthService.canArchiveVehicle();
const params = useParams(); const params = useParams();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const validTabs = ['basicInfo', 'complianceDeadlines']; const validTabs = ['basicInfo', 'complianceDeadlines'];
@@ -209,6 +210,7 @@ const UpdateVehicle = () => {
} }
const triggerShowDeleteModal = () => { const triggerShowDeleteModal = () => {
if (!canArchiveVehicle) return;
setShowDeleteModal(true); setShowDeleteModal(true);
} }
@@ -217,6 +219,7 @@ const UpdateVehicle = () => {
} }
const deactivateVehicle = () => { const deactivateVehicle = () => {
if (!canArchiveVehicle) return;
const data = buildVehicleData(); const data = buildVehicleData();
data.status = 'inactive'; data.status = 'inactive';
dispatch(deleteVehicle({id: params.id, data})); dispatch(deleteVehicle({id: params.id, data}));
@@ -451,7 +454,7 @@ const UpdateVehicle = () => {
</Tabs> </Tabs>
<div className="list-func-panel"> <div className="list-func-panel">
<button className="btn btn-primary" onClick={() => triggerShowDeleteModal()}><Archive size={16} className="me-2"></Archive>Archive</button> {canArchiveVehicle && <button className="btn btn-primary" onClick={() => triggerShowDeleteModal()}><Archive size={16} className="me-2"></Archive>Archive</button>}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,6 +4,20 @@ const login = (data) => {
return http.post('/auth/login', data); return http.post('/auth/login', data);
}; };
const getCurrentUserFromServer = () => {
return http.get('/auth/me');
};
const refreshCurrentUserPermissions = async () => {
const token = localStorage.getItem('token');
if (!token) return null;
const { data } = await getCurrentUserFromServer();
if (data) {
localStorage.setItem('user', JSON.stringify(data));
}
return data;
};
const logout = (data, shouldClearSession = false) => { const logout = (data, shouldClearSession = false) => {
if (shouldClearSession) { if (shouldClearSession) {
localStorage.removeItem('user'); localStorage.removeItem('user');
@@ -162,10 +176,9 @@ const canViewVehicleRepairs = () => hasAnyPermission(['View_Vehicle info_Repair
const canEditVehicleRepairs = () => hasPermission('Edit_Vehicle info_Repair Records'); const canEditVehicleRepairs = () => hasPermission('Edit_Vehicle info_Repair Records');
const canAddVehicle = () => hasPermission('Add_New Vehicle'); const canAddVehicle = () => hasPermission('Add_New Vehicle');
const canArchiveVehicle = () => hasPermission('Archive_Vehicle'); const canArchiveVehicle = () => hasPermission('Archive_Vehicle');
const canDeleteVehicle = () => hasPermission('Delete_Vehicle');
const canExportVehicleReport = () => hasPermission('Export_Vehicle Report'); const canExportVehicleReport = () => hasPermission('Export_Vehicle Report');
const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle() || canExportVehicleReport(); const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canExportVehicleReport();
const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle(); const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle();
const canViewDashboard = () => { const canViewDashboard = () => {
return hasPermission('Dashboard'); return hasPermission('Dashboard');
@@ -178,6 +191,10 @@ const canViewInfoScreen = () => {
]); ]);
} }
const canEditInfoScreen = () => {
return hasPermission('Edit_Info Screen');
}
const canViewCalendar = () => { const canViewCalendar = () => {
return canViewAnyCalendarTab() || canEditAnyCalendarTab(); return canViewAnyCalendarTab() || canEditAnyCalendarTab();
} }
@@ -395,10 +412,13 @@ const getLocalAccessToken = () => {
export const AuthService = { export const AuthService = {
login, login,
getCurrentUserFromServer,
refreshCurrentUserPermissions,
logout, logout,
hasPermission, hasPermission,
canViewDashboard, canViewDashboard,
canViewInfoScreen, canViewInfoScreen,
canEditInfoScreen,
canViewCalendarTab, canViewCalendarTab,
canEditCalendarTab, canEditCalendarTab,
canViewAnyCalendarTab, canViewAnyCalendarTab,
@@ -443,7 +463,6 @@ export const AuthService = {
canEditVehicleRepairs, canEditVehicleRepairs,
canAddVehicle, canAddVehicle,
canArchiveVehicle, canArchiveVehicle,
canDeleteVehicle,
canExportVehicleReport, canExportVehicleReport,
canViewVehicleAny, canViewVehicleAny,
canEditVehicleAny, canEditVehicleAny,

View File

@@ -180,7 +180,6 @@ export const EMPLOYEE_PERMISSION_GROUPS = {
'Edit_Vehicle info_Repair Records', 'Edit_Vehicle info_Repair Records',
'Add_New Vehicle', 'Add_New Vehicle',
'Archive_Vehicle', 'Archive_Vehicle',
'Delete_Vehicle',
'Export_Vehicle Report' 'Export_Vehicle Report'
], ],
Transportation: [ Transportation: [