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',
'Add_New Vehicle',
'Archive_Vehicle',
'Delete_Vehicle',
'Export_Vehicle Report',
'View_Transportation Schedule_Route Overview',
'Create&Edit_Transportation Schedule',
@@ -145,4 +144,31 @@ exports.login = (req, res) => {
} else {
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 => {
const auth = require("../controllers/auth.controller");
const { authJwt } = require("../middlewares");
app.use(function(req, res, next) {
res.header(
"Access-Control-Allow-Headers",
@@ -8,4 +9,5 @@ module.exports = app => {
next();
});
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() {
const [showMenu, setShowMenu] = useState(false);
const [isPermissionReady, setIsPermissionReady] = useState(false);
// 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");
// }
@@ -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 (
<>
{/* <div className="logo">

View File

@@ -7,6 +7,7 @@ import moment from 'moment';
const InfoScreen = () => {
const navigate = useNavigate();
const canEditInfoScreen = AuthService.canEditInfoScreen();
const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar');
const [isLoading, setIsLoading] = useState(true);
const [medicalEvents, setMedicalEvents] = useState([]);
@@ -793,14 +794,16 @@ const InfoScreen = () => {
<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>
</div>
<Button
variant="link"
className="p-0"
onClick={handleEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
{canEditInfoScreen && (
<Button
variant="link"
className="p-0"
onClick={handleEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
)}
</div>
{/* Content Layout */}
@@ -848,14 +851,16 @@ const InfoScreen = () => {
{/* Header with Title and Edit Button */}
<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>
<Button
variant="link"
className="p-0"
onClick={handleCarouselEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
{canEditInfoScreen && (
<Button
variant="link"
className="p-0"
onClick={handleCarouselEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
)}
</div>
{/* Image Carousel */}
@@ -1173,9 +1178,11 @@ const InfoScreen = () => {
{!isFullScreen && (
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={handleBackgroundSelect}>
<FileImage size={16} className="me-2"></FileImage>Choose Background
</button>
{canEditInfoScreen && (
<button className="btn btn-primary me-2" onClick={handleBackgroundSelect}>
<FileImage size={16} className="me-2"></FileImage>Choose Background
</button>
)}
<button className="btn btn-primary me-2" onClick={handleFullScreen}>
<ArrowsFullscreen size={16} className="me-2"></ArrowsFullscreen>Full Screen
</button>
@@ -1185,7 +1192,7 @@ const InfoScreen = () => {
</div>
{/* Edit AttendanceNote Modal */}
<Modal show={showEditModal} onHide={handleCloseModal} size="lg">
{canEditInfoScreen && <Modal show={showEditModal} onHide={handleCloseModal} size="lg">
<Modal.Header closeButton>
<Modal.Title>Edit Attendance Note</Modal.Title>
</Modal.Header>
@@ -1264,10 +1271,10 @@ const InfoScreen = () => {
Save Changes
</Button>
</Modal.Footer>
</Modal>
</Modal>}
{/* Gallery Edit Modal */}
<Modal show={showGalleryModal} onHide={handleGalleryClose} size="lg">
{canEditInfoScreen && <Modal show={showGalleryModal} onHide={handleGalleryClose} size="lg">
<Modal.Header closeButton>
<Modal.Title>Update Images</Modal.Title>
</Modal.Header>
@@ -1333,10 +1340,10 @@ const InfoScreen = () => {
Save Changes
</Button>
</Modal.Footer>
</Modal>
</Modal>}
{/* Background Selection Modal */}
<Modal show={showBackgroundModal} onHide={handleBackgroundClose} size="md">
{canEditInfoScreen && <Modal show={showBackgroundModal} onHide={handleBackgroundClose} size="md">
<Modal.Header closeButton>
<Modal.Title>Choose Background Image</Modal.Title>
</Modal.Header>
@@ -1386,7 +1393,7 @@ const InfoScreen = () => {
Save Background
</Button>
</Modal.Footer>
</Modal>
</Modal>}
{/* Full Screen Hint */}
{isFullScreen && (

View File

@@ -11,6 +11,7 @@ import 'react-datepicker/dist/react-datepicker.css';
const MessageList = () => {
const navigate = useNavigate();
const canEditMessagingTemplate = AuthService.hasPermission('Create&Edit_Messaging Template');
const [messages, setMessages] = useState([]);
const [activeTab, setActiveTab] = useState('allMessages');
@@ -90,6 +91,7 @@ const MessageList = () => {
// ---- Custom Template CRUD ----
const openCreateTemplateModal = () => {
if (!canEditMessagingTemplate) return;
setEditingTemplate(null);
setTemplateTitle('');
setTemplateChinese('');
@@ -98,6 +100,7 @@ const MessageList = () => {
};
const openEditTemplateModal = (template) => {
if (!canEditMessagingTemplate) return;
setEditingTemplate(template);
setTemplateTitle(template.title || '');
setTemplateChinese(template.chinese || '');
@@ -114,6 +117,7 @@ const MessageList = () => {
};
const handleSaveTemplate = () => {
if (!canEditMessagingTemplate) return;
if (!templateTitle.trim()) { window.alert('Template Title 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; }
@@ -131,6 +135,7 @@ const MessageList = () => {
};
const handleDeleteTemplate = (id) => {
if (!canEditMessagingTemplate) return;
if (window.confirm('Are you sure you want to delete this template?')) {
MessageService.deleteCustomTemplate(id).then(() => { fetchCustomTemplates(); });
}
@@ -399,7 +404,7 @@ const MessageList = () => {
<th>Template Title</th>
<th>Message Content (Chinese)</th>
<th>Message Content (English)</th>
<th style={{ width: '80px' }}>Actions</th>
{canEditMessagingTemplate && <th style={{ width: '80px' }}>Actions</th>}
</tr>
</thead>
<tbody>
@@ -409,14 +414,14 @@ const MessageList = () => {
<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?.english}</td>
<td>
{canEditMessagingTemplate && <td>
<PencilSquare size={16} className="clickable me-2" onClick={() => openEditTemplateModal(template)} />
<Trash size={16} className="clickable" color="#dc3545" onClick={() => handleDeleteTemplate(template.id)} />
</td>
</td>}
</tr>
))}
{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>
</table>
@@ -482,14 +487,14 @@ const MessageList = () => {
</>
)}
{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>
{/* Create / Edit Message Template Modal */}
<Modal show={showCreateTemplateModal} onHide={closeTemplateModal} centered>
<Modal show={canEditMessagingTemplate && showCreateTemplateModal} onHide={closeTemplateModal} centered>
<Modal.Header closeButton>
<Modal.Title>{editingTemplate ? 'Edit Message Template' : 'Create New Message Template'}</Modal.Title>
</Modal.Header>

View File

@@ -18,6 +18,7 @@ import {
const UpdateVehicle = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const canArchiveVehicle = AuthService.canArchiveVehicle();
const params = useParams();
const [searchParams] = useSearchParams();
const validTabs = ['basicInfo', 'complianceDeadlines'];
@@ -209,6 +210,7 @@ const UpdateVehicle = () => {
}
const triggerShowDeleteModal = () => {
if (!canArchiveVehicle) return;
setShowDeleteModal(true);
}
@@ -217,6 +219,7 @@ const UpdateVehicle = () => {
}
const deactivateVehicle = () => {
if (!canArchiveVehicle) return;
const data = buildVehicleData();
data.status = 'inactive';
dispatch(deleteVehicle({id: params.id, data}));
@@ -451,7 +454,7 @@ const UpdateVehicle = () => {
</Tabs>
<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>

View File

@@ -4,6 +4,20 @@ const 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) => {
if (shouldClearSession) {
localStorage.removeItem('user');
@@ -162,10 +176,9 @@ const canViewVehicleRepairs = () => hasAnyPermission(['View_Vehicle info_Repair
const canEditVehicleRepairs = () => hasPermission('Edit_Vehicle info_Repair Records');
const canAddVehicle = () => hasPermission('Add_New Vehicle');
const canArchiveVehicle = () => hasPermission('Archive_Vehicle');
const canDeleteVehicle = () => hasPermission('Delete_Vehicle');
const canExportVehicleReport = () => hasPermission('Export_Vehicle Report');
const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle() || canExportVehicleReport();
const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle();
const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canExportVehicleReport();
const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle();
const canViewDashboard = () => {
return hasPermission('Dashboard');
@@ -178,6 +191,10 @@ const canViewInfoScreen = () => {
]);
}
const canEditInfoScreen = () => {
return hasPermission('Edit_Info Screen');
}
const canViewCalendar = () => {
return canViewAnyCalendarTab() || canEditAnyCalendarTab();
}
@@ -395,10 +412,13 @@ const getLocalAccessToken = () => {
export const AuthService = {
login,
getCurrentUserFromServer,
refreshCurrentUserPermissions,
logout,
hasPermission,
canViewDashboard,
canViewInfoScreen,
canEditInfoScreen,
canViewCalendarTab,
canEditCalendarTab,
canViewAnyCalendarTab,
@@ -443,7 +463,6 @@ export const AuthService = {
canEditVehicleRepairs,
canAddVehicle,
canArchiveVehicle,
canDeleteVehicle,
canExportVehicleReport,
canViewVehicleAny,
canEditVehicleAny,

View File

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