diff --git a/src/actions/bmdashboard/projectMemberAction.js b/src/actions/bmdashboard/projectMemberAction.js new file mode 100644 index 0000000000..e0d28ebbc7 --- /dev/null +++ b/src/actions/bmdashboard/projectMemberAction.js @@ -0,0 +1,31 @@ +import axios from "axios"; + +import { ENDPOINTS } from "utils/URL"; +import GET_BM_PROJECT_MEMBERS from "constants/bmdashboard/projectMemberConstants"; +import { GET_ERRORS } from "constants/errors"; + +export const fetchBMProjectMembers = projectId => { + return async dispatch => { + axios.get(ENDPOINTS.BM_PROJECT_MEMBERS(projectId)) + .then(res => { + dispatch(setProjectMembers(res.data)) + }) + .catch(err => { + dispatch(setErrors(err)) + }) + } +} + +export const setProjectMembers = payload => { + return { + type: GET_BM_PROJECT_MEMBERS, + payload + } +} + +export const setErrors = payload => { + return { + type: GET_ERRORS, + payload + } +} \ No newline at end of file diff --git a/src/actions/bmdashboard/timeLoggerActions.js b/src/actions/bmdashboard/timeLoggerActions.js new file mode 100644 index 0000000000..aa5814c8bc --- /dev/null +++ b/src/actions/bmdashboard/timeLoggerActions.js @@ -0,0 +1,98 @@ +import axios from 'axios'; +import { ENDPOINTS } from '../../utils/URL'; +import { GET_ERRORS } from '../../constants/errors'; +import { + START_TIME_LOG, + PAUSE_TIME_LOG, + STOP_TIME_LOG, + GET_CURRENT_TIME_LOG, +} from '../../constants/bmdashboard/timeLoggerConstants'; + +// Start Time Log Action +export const startTimeLog = (projectId, memberId, task) => { + return async dispatch => { + try { + const response = await axios.post(ENDPOINTS.TIME_LOGGER_START(projectId, memberId), { + task, + }); + + dispatch({ + type: START_TIME_LOG, + payload: { ...response.data.timeLog, memberId, projectId }, + }); + } catch (error) { + dispatch({ + type: GET_ERRORS, + payload: error.response ? error.response.data : { message: 'Error starting time log' }, + }); + } + }; +}; + +// Pause Time Log Action +export const pauseTimeLog = (projectId, timeLogId, memberId) => { + return async dispatch => { + try { + const response = await axios.post(ENDPOINTS.TIME_LOGGER_PAUSE(projectId, memberId), { + timeLogId, + }); + + dispatch({ + type: PAUSE_TIME_LOG, + payload: { ...response.data.timeLog, memberId, projectId }, + }); + } catch (error) { + dispatch({ + type: GET_ERRORS, + payload: error.response ? error.response.data : { message: 'Error pausing time log' }, + }); + } + }; +}; + +// Stop Time Log Action +export const stopTimeLog = (projectId, timeLogId, memberId) => { + return async dispatch => { + try { + const response = await axios.post(ENDPOINTS.TIME_LOGGER_STOP(projectId, memberId), { + timeLogId, + }); + + dispatch({ + type: STOP_TIME_LOG, + payload: { ...response.data.timeLog, memberId, projectId }, + }); + } catch (error) { + dispatch({ + type: GET_ERRORS, + payload: error.response ? error.response.data : { message: 'Error stopping time log' }, + }); + } + }; +}; + +// Get Current Time Log Action +export const getCurrentTimeLog = (projectId, memberId) => { + return async dispatch => { + try { + const response = await axios.get(ENDPOINTS.TIME_LOGGER_LOGS(projectId, memberId)); + + // Find the ongoing or paused time log + const currentTimeLog = response.data.find( + log => log.status === 'ongoing' || log.status === 'paused', + ); + + dispatch({ + type: GET_CURRENT_TIME_LOG, + payload: { currentTimeLog, memberId, projectId } || null, + }); + } catch (error) { + dispatch({ + type: GET_ERRORS, + payload: error.response + ? error.response.data + : { message: 'Error fetching current time log' }, + }); + } + }; +}; diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.css b/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.css new file mode 100644 index 0000000000..af4380a215 --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.css @@ -0,0 +1,213 @@ +.bm-timelogger__header h1 { + font-size: 1.5em; + text-align: left; + margin-top: 2em; +} + +.bm-dashboard__button.btn.btn-secondary { + background-color: #2e5061; +} + +@media (min-width: 650px) { + .bm-dashboard__button.btn.btn-secondary { + padding-left: 0; + padding-right: 0; + max-height: 38px; + } +} + +.member-card-container { + width: 100%; + display: flex; +} + + +.member-card { + width: 100%; + align-items: center; +} + +.member-card-header { + width: 100%; + align-items: center; +} + +.member-card-name { + text-align: center; + color: white; +} + +.stopwatch-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + width: 100%; +} + +.member-stopwatch { + background-color: #4b5055 !important; + border-radius: 15px !important; + height: 3rem; + font-size: 1.5rem !important; + text-align: center; + color: white; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; +} + +.font-color-gray { + color: #4b5055; +} + +.member-start { + background-color: #01987a !important; + border-radius: 10px !important; + width: 5rem; +} + +.member-pause { + background-color: #168dc0 !important; + border-radius: 10px !important; + width: 5rem; +} + + +.member-stop { + background-color: #980101 !important; + border-radius: 10px !important; + width: 5rem; +} + +.member-clear { + background-color: #a9b7be !important; + border-radius: 10px !important; + width: 5rem; +} +/* project summaries */ +.projects-list { + width: 80%; + list-style-type: none; + max-height: 80vh; + overflow-x: hidden; + overflow-y: auto; + padding:0; +} + +.project-summary { + background-color: #e8f4f9; + min-height: 20vh; + height: auto; + margin-bottom: 2em; + padding: 1em; + border-radius: 5px; +} + +.project-summary_header { + font-size: clamp(0.8em, 3vw, 1.3em); + font-weight: bold; + text-align: center; + justify-content: center; + /* color: #3ea0cb; */ +} + +.project-summary_content { + display: block; + flex-wrap: wrap; + text-align: start; + margin: 0; + font-size: clamp(0.9em, 2.5vw, 1em); +} + +.project-summary_item { + padding: 8px; + align-items: center; +} + +.project-summary_label { + margin-right: 1px; +} + +.display-inline { + display: inline-block; + white-space: no-wrap; +} + +/* error page */ + +.bm-error-page { + width: 100%; + max-width: 1536px; + margin: 1rem auto; + padding: 0 1rem; +} + +.bm-error-page section { + margin-top: 2rem; +} + +/* forms */ + +.inv-form-page-container { + width: 100%; + max-width: 800px; + margin: 1rem auto; + padding: 2rem; + border: 1px solid #ccc; + border-radius: 40px; +} + +.inv-form { + margin: 2rem auto; +} + +.inv-form-info { + display: flex; + gap: 0.5rem; + align-items: center; + margin: 1rem 0; +} + +.inv-form-group { + margin: 1.5rem auto; +} + +.inv-form input, +.inv-form select, +.inv-form textarea { + background-color: rgb(249, 249, 249); +} + +.inv-form-btn-group { + display: flex; + gap: 1rem; + margin: 4rem auto 2rem; +} + +.inv-form-btn-group button { + width: 50%; +} + +.inv-form-required::after { + content: '*'; + color: red; +} + +@media screen and (max-width: 800px) { + .inv-form-page-container { + width: 95%; + } +} + +@media screen and (max-width: 480px) { + .inv-form-page-container h2 { + font-size: 1.7rem; + } +} + +.form-footer { +font-size: 0.875em; +margin-top:3px; +} diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.jsx b/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.jsx new file mode 100644 index 0000000000..fbfe22a216 --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogCard.jsx @@ -0,0 +1,61 @@ +import { useState, useEffect } from 'react'; +import { Container, Row, Col } from 'reactstrap'; +import { useSelector, useDispatch } from 'react-redux'; +import BMError from '../shared/BMError'; +import { fetchBMProjectMembers } from '../../../actions/bmdashboard/projectMemberAction'; +import BMTimeLogDisplayMember from './BMTimeLogDisplayMember'; + +// function BMTimeLogCard({ selectedProject }) { +function BMTimeLogCard(props) { + // const state = useSelector(); + + const [isError, setIsError] = useState(false); + const [memberList, setMemberList] = useState([]); + const [isMemberFetched, setIsMemberFetched] = useState(false); + + const dispatch = useDispatch(); + const errors = useSelector(state => state.errors); + const projectInfo = useSelector(state => state.bmProjectMembers); + + useEffect(() => { + dispatch(fetchBMProjectMembers(props.selectedProject)); + }, [props.selectedProject, dispatch]); + + useEffect(() => { + if (projectInfo && projectInfo.members) { + setMemberList(projectInfo.members); + setIsMemberFetched(true); + } + }, [projectInfo]); + + // trigger an error state if there is an errors object + useEffect(() => { + if (Object.entries(errors).length > 0) { + setIsError(true); + } + }, [errors]); + + return ( + + {isMemberFetched && ( + + {memberList.map((value, index) => ( + + + + ))} + + )} + {isError && } + + ); +} + +export default BMTimeLogCard; diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogDisplayMember.jsx b/src/components/BMDashboard/BMTimeLogger/BMTimeLogDisplayMember.jsx new file mode 100644 index 0000000000..5f6cdc0565 --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogDisplayMember.jsx @@ -0,0 +1,34 @@ +import { CardHeader, Card } from 'reactstrap'; +import BMTimeLogStopWatch from './BMTimeLogStopWatch'; +import './BMTimeLogCard.css'; + +// function BMTimeLogCard({ selectedProject }) { +function BMTimeLogDisplayMember({ firstName, lastName, role, memberId, projectId }) { + const roleColors = { + volunteer: '#78bdda', // light blue + core: '#ecb16c', // light orange + manager: '#6cc3b2', // light green + mentor: '#6cc3b2', // light green + owner: '#c36c6c', // light red + }; + + const roleKey = role.toLowerCase(); + const cardColor = roleColors[roleKey] || '#78bdda'; + const borderProperty = `2px solid ${cardColor}`; + + return ( +
+ + +
+ {firstName} {lastName} +
+
+ + +
+
+ ); +} + +export default BMTimeLogDisplayMember; diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogStopWatch.jsx b/src/components/BMDashboard/BMTimeLogger/BMTimeLogStopWatch.jsx new file mode 100644 index 0000000000..d4e99c368b --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogStopWatch.jsx @@ -0,0 +1,145 @@ +import { Button, CardBody, Row, Col, Container } from 'reactstrap'; +import { useState, useEffect, useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import moment from 'moment'; +import './BMTimeLogCard.css'; +import { + startTimeLog, + pauseTimeLog, + stopTimeLog, + getCurrentTimeLog, +} from '../../../actions/bmdashboard/timeLoggerActions'; + +function BMTimeLogStopWatch({ projectId, memberId }) { + const dispatch = useDispatch(); + const currentTimeLog = useSelector( + state => state.bmTimeLogger?.bmTimeLogs?.[`${memberId}_${projectId}`] || null, + ); + + // const initialElapsedTimeRef = useRef(0); + const [time, setTime] = useState(0); + const [currentTime, setCurrentTime] = useState(''); + const [startButtonText, setStartButtonText] = useState('START'); + const [isStarted, setIsStarted] = useState(false); + + const formatTime = useCallback(totalSeconds => { + const hrs = Math.floor(totalSeconds / 3600); + const mins = Math.floor((totalSeconds % 3600) / 60); + const secs = totalSeconds % 60; + return { hr: hrs, min: mins, sec: secs }; + }, []); + + // Fetch current time log on component mount + useEffect(() => { + dispatch(getCurrentTimeLog(projectId, memberId)); + }, [dispatch, projectId, memberId]); + + // Sync time with backend time log + useEffect(() => { + if (currentTimeLog) { + if (currentTimeLog.status === 'ongoing' || currentTimeLog.status === 'paused') { + const elapsedTime = Math.floor((currentTimeLog.totalElapsedTime || 0) / 1000); + // initialElapsedTimeRef.current = elapsedTime; + setTime(elapsedTime); + setIsStarted(currentTimeLog.status === 'ongoing'); + setStartButtonText(currentTimeLog.status === 'ongoing' ? 'PAUSE' : 'START'); + setCurrentTime(moment(currentTimeLog.createdAt).format('hh:mm:ss A')); + } + } + }, [currentTimeLog]); + + // eslint-disable-next-line consistent-return + useEffect(() => { + let intervalId; + if (isStarted) { + intervalId = setInterval(() => { + setTime(prevTime => prevTime + 1); + }, 1000); + } + return () => { + if (intervalId) clearInterval(intervalId); + }; + }, [isStarted]); + + // Start/Pause Handler + const startStop = () => { + if (!currentTime) { + setCurrentTime(moment().format('hh:mm:ss A')); + } + + if (isStarted) { + // Pause the time log + dispatch(pauseTimeLog(projectId, currentTimeLog._id, memberId)); + setStartButtonText('START'); + } else { + // Start or resume time log + if (currentTimeLog) { + // Resume existing time log + dispatch(startTimeLog(projectId, memberId, currentTimeLog.task)); + } else { + // Start new time log + dispatch(startTimeLog(projectId, memberId, 'Default Task')); + } + setStartButtonText('PAUSE'); + } + setIsStarted(!isStarted); + }; + + // Stop Handler + const stop = () => { + if (currentTimeLog) { + dispatch(stopTimeLog(projectId, currentTimeLog._id, memberId)); + } + setTime(0); + setCurrentTime(''); + setStartButtonText('START'); + setIsStarted(false); + // initialElapsedTimeRef.current = 0; + }; + + // Clear Handler + const clear = () => { + stop(); + }; + + const { hr, min, sec } = formatTime(time); + + return ( + + + + + + + + + + + + + + + Start at: + + {currentTime} + + + {/* Task: */} + + + + + + ); +} + +export default BMTimeLogStopWatch; diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.css b/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.css new file mode 100644 index 0000000000..dbd8e512f3 --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.css @@ -0,0 +1,143 @@ +.bm-timelogger__header h1 { + font-size: 1.5em; + text-align: left; + margin-top: 2em; +} + +.bm-dashboard__button.btn.btn-secondary { + background-color: #2e5061; +} + +@media (min-width: 650px) { + .bm-dashboard__button.btn.btn-secondary { + padding-left: 0; + padding-right: 0; + max-height: 38px; + } +} + +/* project summaries */ +.projects-list { + width: 80%; + list-style-type: none; + max-height: 80vh; + overflow-x: hidden; + overflow-y: auto; + padding:0; +} + +.project-summary { + background-color: #e8f4f9; + min-height: 20vh; + height: auto; + margin-bottom: 2em; + padding: 1em; + border-radius: 5px; +} + +.project-summary_header { + font-size: clamp(0.8em, 3vw, 1.3em); + font-weight: bold; + text-align: center; + justify-content: center; + /* color: #3ea0cb; */ +} + +.project-summary_content { + display: block; + flex-wrap: wrap; + text-align: start; + margin: 0; + font-size: clamp(0.9em, 2.5vw, 1em); +} + +.project-summary_item { + padding: 8px; + align-items: center; +} + +.project-summary_label { + margin-right: 1px; +} + +.display-inline { + display: inline-block; + white-space: no-wrap; +} + +/* error page */ + +.bm-error-page { + width: 100%; + max-width: 1536px; + margin: 1rem auto; + padding: 0 1rem; +} + +.bm-error-page section { + margin-top: 2rem; +} + +/* forms */ + +.inv-form-page-container { + width: 100%; + max-width: 800px; + margin: 1rem auto; + padding: 2rem; + border: 1px solid #ccc; + border-radius: 40px; +} + +.inv-form { + margin: 2rem auto; +} + +.inv-form-info { + display: flex; + gap: 0.5rem; + align-items: center; + margin: 1rem 0; +} + +.inv-form-group { + margin: 1.5rem auto; +} + +.inv-form input, +.inv-form select, +.inv-form textarea { + background-color: rgb(249, 249, 249); +} + +.inv-form-btn-group { + display: flex; + gap: 1rem; + margin: 4rem auto 2rem; +} + +.inv-form-btn-group button { + width: 50%; +} + +.inv-form-required::after { + content: '*'; + color: red; +} + +@media screen and (max-width: 800px) { + .inv-form-page-container { + width: 95%; + } +} + +@media screen and (max-width: 480px) { + .inv-form-page-container h2 { + font-size: 1.7rem; + } +} + +.form-footer { +font-size: 0.875em; +margin-top:3px; +} diff --git a/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.jsx b/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.jsx new file mode 100644 index 0000000000..199c08a648 --- /dev/null +++ b/src/components/BMDashboard/BMTimeLogger/BMTimeLogger.jsx @@ -0,0 +1,71 @@ +import { useState, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +// import Select from 'react-select'; +import moment from 'moment'; +import { Row, Container, Col, Input } from 'reactstrap'; +import { fetchBMProjects } from '../../../actions/bmdashboard/projectActions'; +import BMTimeLogCard from './BMTimeLogCard'; +import BMError from '../shared/BMError'; +import './BMTimeLogger.css'; + +function BMTimeLogger() { + const [isError, setIsError] = useState(false); + const [selectedProject, setselectedProject] = useState(null); + + const dispatch = useDispatch(); + const errors = useSelector(state => state.errors); + const projects = useSelector(state => state.bmProjects); + // fetch projects data on pageload + useEffect(() => { + dispatch(fetchBMProjects()); + }, [dispatch]); + + // trigger an error state if there is an errors object + useEffect(() => { + if (Object.entries(errors).length > 0) { + setIsError(true); + } + }, [errors]); + + return ( + +
+ +

Member Group Check In

+
+
+ + {/* select project dropdown */} + + Date: {moment().format('MM/DD/YY')} + +

Project:

+ + + { + const { value } = e.target; + setselectedProject(value); + }} + > + + {projects.map(project => ( + + ))} + + +
+ + {selectedProject && } + {isError && } +
+ ); +} + +export default BMTimeLogger; diff --git a/src/constants/bmdashboard/projectMemberConstants.js b/src/constants/bmdashboard/projectMemberConstants.js new file mode 100644 index 0000000000..0a9d5c78fa --- /dev/null +++ b/src/constants/bmdashboard/projectMemberConstants.js @@ -0,0 +1,3 @@ +const GET_BM_PROJECT_MEMBERS = 'GET_BM_PROJECT_MEMBERS'; +export const GET_BM_PROJECT_BY_ID = 'GET_BM_PROJECT_BY_ID'; +export default GET_BM_PROJECT_MEMBERS; diff --git a/src/constants/bmdashboard/timeLoggerConstants.js b/src/constants/bmdashboard/timeLoggerConstants.js new file mode 100644 index 0000000000..cf6aa8d9bd --- /dev/null +++ b/src/constants/bmdashboard/timeLoggerConstants.js @@ -0,0 +1,4 @@ +export const START_TIME_LOG = 'START_TIME_LOG'; +export const PAUSE_TIME_LOG = 'PAUSE_TIME_LOG'; +export const STOP_TIME_LOG = 'STOP_TIME_LOG'; +export const GET_CURRENT_TIME_LOG = 'GET_CURRENT_TIME_LOG'; diff --git a/src/reducers/bmdashboard/projectMemberReducer.js b/src/reducers/bmdashboard/projectMemberReducer.js new file mode 100644 index 0000000000..020896b3f5 --- /dev/null +++ b/src/reducers/bmdashboard/projectMemberReducer.js @@ -0,0 +1,11 @@ +import GET_BM_PROJECT_MEMBERS from 'constants/bmdashboard/projectMemberConstants'; + +// eslint-disable-next-line default-param-last +export const bmProjectMemberReducer = (materials = [], action) => { + if (action.type === GET_BM_PROJECT_MEMBERS) { + return action.payload; + } + return materials; +}; + +export default bmProjectMemberReducer; diff --git a/src/reducers/bmdashboard/timeLoggerReducer.js b/src/reducers/bmdashboard/timeLoggerReducer.js new file mode 100644 index 0000000000..b3ac9980dd --- /dev/null +++ b/src/reducers/bmdashboard/timeLoggerReducer.js @@ -0,0 +1,64 @@ +/* eslint-disable no-case-declarations */ +import { + START_TIME_LOG, + PAUSE_TIME_LOG, + STOP_TIME_LOG, + GET_CURRENT_TIME_LOG, +} from '../../constants/bmdashboard/timeLoggerConstants'; + +const initialState = { + bmTimeLogs: {}, // A map of { memberId_projectId: timeLog } + bmTimeLogHistory: [], +}; + +// eslint-disable-next-line default-param-last +export const bmTimeLoggerReducer = (state = initialState, action) => { + switch (action.type) { + case START_TIME_LOG: + const startKey = `${action.payload.memberId}_${action.payload.projectId}`; + return { + ...state, + bmTimeLogs: { + ...state.bmTimeLogs, + [startKey]: action.payload, // Store time log specific to member and project + }, + bmTimeLogHistory: [...state.bmTimeLogHistory, action.payload], + }; + + case PAUSE_TIME_LOG: + const pauseKey = `${action.payload.memberId}_${action.payload.projectId}`; + return { + ...state, + bmTimeLogs: { + ...state.bmTimeLogs, + [pauseKey]: action.payload, // Update specific member's time log + }, + }; + + case STOP_TIME_LOG: + const stopKey = `${action.payload.memberId}_${action.payload.projectId}`; + return { + ...state, + bmTimeLogs: { + ...state.bmTimeLogs, + [stopKey]: null, // Reset specific member's time log + }, + bmTimeLogHistory: [...state.bmTimeLogHistory, action.payload], + }; + + case GET_CURRENT_TIME_LOG: + const getKey = `${action.payload.memberId}_${action.payload.projectId}`; + return { + ...state, + bmTimeLogs: { + ...state.bmTimeLogs, + [getKey]: action.payload, // Get specific member's time log + }, + }; + + default: + return state; + } +}; + +export default bmTimeLoggerReducer; diff --git a/src/reducers/index.js b/src/reducers/index.js index 0b2d09c5da..2efc7e6e1c 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -51,6 +51,9 @@ import { bmInvUnitReducer } from './bmdashboard/inventoryUnitReducer'; import { consumablesReducer } from './bmdashboard/consumablesReducer'; import { toolReducer } from './bmdashboard/toolReducer'; import { equipmentReducer } from './bmdashboard/equipmentReducer'; +import { bmProjectMemberReducer } from './bmdashboard/projectMemberReducer'; +import { bmTimeLoggerReducer } from './bmdashboard/timeLoggerReducer'; + import dashboardReducer from './dashboardReducer'; import { timeOffRequestsReducer } from './timeOffRequestReducer'; import { totalOrgSummaryReducer } from './totalOrgSummaryReducer'; @@ -104,6 +107,8 @@ const localReducers = { bmConsumables: consumablesReducer, bmReusables: reusablesReducer, dashboard: dashboardReducer, + bmProjectMembers: bmProjectMemberReducer, + bmTimeLogger: bmTimeLoggerReducer, }; const sessionReducers = { diff --git a/src/routes.js b/src/routes.js index c81deee3c6..e427d41aed 100644 --- a/src/routes.js +++ b/src/routes.js @@ -55,6 +55,7 @@ import CheckTypes from './components/BMDashboard/shared/CheckTypes'; import Toolslist from './components/BMDashboard/Tools/ToolsList'; import AddTool from './components/BMDashboard/Tools/AddTool'; import AddTeamMember from './components/BMDashboard/AddTeamMember/AddTeamMember'; +import BMTimeLogger from './components/BMDashboard/BMTimeLogger/BMTimeLogger'; // Community Portal import CPProtectedRoute from './components/common/CPDashboard/CPProtectedRoute'; @@ -86,6 +87,7 @@ const PurchaseReusables = lazy(() => // const PurchaseEquipment = lazy(() => // import('./components/BMDashboard/PurchaseRequests/EquipmentPurchaseRequest'), // ); +const BMTimeLogCard = lazy(() => import('./components/BMDashboard/BMTimeLogger/BMTimeLogCard')); const ProjectDetails = lazy(() => import('./components/BMDashboard/Projects/ProjectDetails/ProjectDetails'), ); @@ -415,6 +417,13 @@ export default ( exact component={WeeklyProjectSummary} /> + + + {/* Community Portal Routes */} diff --git a/src/utils/URL.js b/src/utils/URL.js index 3d87963e91..b2600441d6 100644 --- a/src/utils/URL.js +++ b/src/utils/URL.js @@ -199,7 +199,7 @@ export const ENDPOINTS = { BM_EQUIPMENT_TYPES: `${APIEndpoint}/bm/invtypes/equipments`, BM_EQUIPMENT_PURCHASE: `${APIEndpoint}/bm/equipment/purchase`, BM_PROJECTS: `${APIEndpoint}/bm/projects`, - BM_PROJECT_BY_ID: projectId => `${APIEndpoint}/project/${projectId}`, + BM_PROJECT_BY_ID: projectId => `${APIEndpoint}/bm/project/${projectId}`, BM_UPDATE_MATERIAL: `${APIEndpoint}/bm/updateMaterialRecord`, BM_UPDATE_MATERIAL_BULK: `${APIEndpoint}/bm/updateMaterialRecordBulk`, BM_UPDATE_MATERIAL_STATUS: `${APIEndpoint}/bm/updateMaterialStatus`, @@ -225,6 +225,18 @@ export const ENDPOINTS = { BM_TAG_ADD: `${APIEndpoint}/bm/tags`, BM_TAGS_DELETE: `${APIEndpoint}/bm/tags`, + BM_PROJECT_MEMBERS: projectId => `${APIEndpoint}/bm/project/${projectId}/users`, + + // bm time logger endpoints + TIME_LOGGER_START: (projectId, memberId) => + `${APIEndpoint}/bm/timelogger/${projectId}/${memberId}/start`, + TIME_LOGGER_PAUSE: (projectId, memberId) => + `${APIEndpoint}/bm/timelogger/${projectId}/${memberId}/pause`, + TIME_LOGGER_STOP: (projectId, memberId) => + `${APIEndpoint}/bm/timelogger/${projectId}/${memberId}/stop`, + TIME_LOGGER_LOGS: (projectId, memberId) => + `${APIEndpoint}/bm/timelogger/${projectId}/${memberId}/logs`, + GET_TIME_OFF_REQUESTS: () => `${APIEndpoint}/getTimeOffRequests`, ADD_TIME_OFF_REQUEST: () => `${APIEndpoint}/setTimeOffRequest`, UPDATE_TIME_OFF_REQUEST: id => `${APIEndpoint}/updateTimeOffRequest/${id}`,