- {hasNotification && (
-
- You’ve completed {getFormattedEffort()} out of the {weeklycommittedHours} you need.
- Only {getFormattedLeftToWork()} left to go. Hurry up, there are less than 48 hours
- left to finish your tasks!
+ {hasHoursAlert && (
+
+
⏰ Hours Reminder
+ You've completed {getFormattedEffort()} out of the {weeklycommittedHours} hours
+ you need. Only {getFormattedLeftToWork()} left to go.
+ {hoursLeft > 0 &&
+ ` There are ${hoursLeft} hour${hoursLeft !== 1 ? 's' : ''} left in this week.`}
+
+
+
)}
+
+ {hasTaskAlerts && (
+
+
📊 Task Progress Alerts
+ {taskHoursAlerts.map(a => (
+
+
+ Hey! Your {a.name} is {a.bucket}% complete.
+
+
+ How are you doing? Please communicate with your manager if you need more time.
+ If you do, include the specific reason.
+
+
+ 📈 Progress: {a.percent}% ({a.logged.toFixed(1)} / {a.estimate.toFixed(1)} hrs)
+
+
+ ))}
+
+
+
+
+ )}
+
{hasMessageNotification && (
-
New Messages:
+
💬 New Messages
{allNotifications.map((notification, index) => (
+ // eslint-disable-next-line react/no-array-index-key
{notification.message || notification}
))}
)}
- {!hasNotification && !hasMessageNotification &&
No new notifications.
}
+
+ {!hasHoursAlert && !hasTaskAlerts && !hasMessageNotification && (
+
+ No new notifications.
+
+ )}
)}
>
);
}
+// ===== (Optional) Console test helpers =====
+// Keep your existing bellTest block if you like. You can also add helpers to clear per-task seen:
+// Object.keys(localStorage)
+// .filter(k => k.includes('::task::') && k.includes('::seen::'))
+// .forEach(k => localStorage.removeItem(k));
+
+// To test the feature time travel in browser you can paste this iife in the console and then you can use function calls for bellTest.goto(48)/(24)
+// ==== Bell test helpers (no reloads needed) ====
+// (() => {
+// const toLA = d => new Date(d.toLocaleString('en-US',{ timeZone: 'America/Los_Angeles' }));
+// const startOfPSTWeek = d => {
+// const laNow = toLA(d);
+// const laStart = new Date(laNow);
+// laStart.setHours(0,0,0,0);
+// laStart.setDate(laStart.getDate() - laNow.getDay());
+// const offset = d.getTime() - laNow.getTime();
+// return new Date(laStart.getTime() + offset); // true Sun 00:00 PT instant
+// };
+// const endOfPSTWeek = d => new Date(startOfPSTWeek(d).getTime() + 7*24*3600*1000);
+
+// window.bellTest = {
+// toLA, startOfPSTWeek, endOfPSTWeek,
+// setNow(iso) {
+// if (!window.__setBellNow) throw new Error('Component not mounted yet');
+// window.__setBellNow(iso);
+// return iso;
+// },
+// keys() {
+// const uid =
+// Object.keys(localStorage).find(k => k.endsWith('::lastWeekKey'))?.split('::')[0] || null;
+// const now = new Date(window.__BELL_TEST_NOW || Date.now());
+// const wk = startOfPSTWeek(now).toISOString();
+// const base = uid ? `${uid}::${wk}` : null;
+// return { uid, wk, base };
+// },
+// listKeys() {
+// const { uid } = bellTest.keys();
+// return Object.keys(localStorage).filter(k =>
+// (uid ? k.startsWith(uid + '::') : true) &&
+// (k.includes('::hours::') || k.endsWith('::lastWeekKey'))
+// );
+// },
+// clearSeen() {
+// const { base } = bellTest.keys();
+// if (!base) return [];
+// const ks = [`${base}::hours::seen48`, `${base}::hours::seen24`];
+// ks.forEach(k => localStorage.removeItem(k));
+// return ks;
+// },
+// nukeAllHours() {
+// const { uid } = bellTest.keys();
+// const removed = [];
+// Object.keys(localStorage).forEach(k => {
+// if (k.startsWith(`${uid}::`) && (k.includes('::hours::') || k.endsWith('::lastWeekKey'))) {
+// removed.push(k);
+// localStorage.removeItem(k);
+// }
+// });
+// return removed;
+// },
+// // convenience: jump relative to this week's PT deadline
+// goTo(hoursBefore = 0, minutesBefore = 0) {
+// const baseNow = new Date(window.__BELL_TEST_NOW || Date.now());
+// const end = endOfPSTWeek(baseNow);
+// const t = new Date(end.getTime() - (hoursBefore*3600 + minutesBefore*60)*1000);
+// return bellTest.setNow(t.toISOString());
+// }
+// };
+// console.log('bellTest loaded:', bellTest);
+// })();
diff --git a/src/components/Header/DarkModeButton.jsx b/src/components/Header/DarkModeButton.jsx
index 12716dc58c..9167204482 100644
--- a/src/components/Header/DarkModeButton.jsx
+++ b/src/components/Header/DarkModeButton.jsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
-import './DarkMode.css';
+import './index.css';
import { Tooltip } from 'reactstrap';
import sunIcon from './images/sunIcon.png';
import nightIcon from './images/nightIcon.png';
diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx
index 37a2cdf4d4..b45ed0a2ea 100644
--- a/src/components/Header/Header.jsx
+++ b/src/components/Header/Header.jsx
@@ -79,6 +79,7 @@ export function Header(props) {
const [displayUserId, setDisplayUserId] = useState(user.userid);
const [popup, setPopup] = useState(false);
const [isAuthUser, setIsAuthUser] = useState(true);
+ const [isAckLoading, setIsAckLoading] = useState(false);
const [ showPromotionsPopup, setShowPromotionsPopup ] = useState(false);
const ALLOWED_ROLES_TO_INTERACT = useMemo(() => ['Owner', 'Administrator'], []);
diff --git a/src/components/Header/DarkMode.css b/src/components/Header/index.css
similarity index 62%
rename from src/components/Header/DarkMode.css
rename to src/components/Header/index.css
index 767b9eb4f8..b98fba9d2d 100644
--- a/src/components/Header/DarkMode.css
+++ b/src/components/Header/index.css
@@ -19,44 +19,88 @@
opacity: 1;
}
/* Custom styles for datepicker in darkMode */
-.dark-mode .react-datepicker {
- background-color: #3A506B;
- color: #ffffff;
-}
-.dark-mode .react-datepicker__header {
- background-color: #1C2541;
- border-bottom: 1px solid #555555;
-}
-.dark-mode .react-datepicker__current-month {
- color: #ffffff;
+.dark-mode .react-datepicker,
+.text-light .react-datepicker {
+ background-color: #1f2937 !important;
+ color: #ffffff !important;
+ border: 1px solid #374151 !important;
+ border-radius: 0 !important;
+}
+.dark-mode .react-datepicker__header,
+.text-light .react-datepicker__header {
+ background-color: #111827 !important;
+ border-bottom: 1px solid #374151 !important;
+ border-radius: 0 !important;
+}
+.dark-mode .react-datepicker__current-month,
+.text-light .react-datepicker__current-month {
+ color: #ffffff !important;
}
.dark-mode .react-datepicker__navigation--previous::before,
-.dark-mode .react-datepicker__navigation--next::before {
- border-color: #ffffff;
+.dark-mode .react-datepicker__navigation--next::before,
+.text-light .react-datepicker__navigation--previous::before,
+.text-light .react-datepicker__navigation--next::before {
+ border-color: #ffffff !important;
}
-.dark-mode .react-datepicker__month-container {
- background-color: #3A506B;
+.dark-mode .react-datepicker__month-container,
+.text-light .react-datepicker__month-container {
+ background-color: #1f2937 !important;
+ border-radius: 0 !important;
}
.dark-mode .react-datepicker__day,
-.dark-mode .react-datepicker__time-name {
- color: #ffffff;
-}
-.dark-mode .react-datepicker__day-name {
- background-color: #1C2541;
- color: #ffffff;
+.dark-mode .react-datepicker__time-name,
+.text-light .react-datepicker__day,
+.text-light .react-datepicker__time-name {
+ color: #ffffff !important;
+ border-radius: 0 !important;
+}
+.dark-mode .react-datepicker__day-name,
+.text-light .react-datepicker__day-name {
+ background-color: #111827 !important;
+ color: #ffffff !important;
+ border-radius: 0 !important;
}
.dark-mode .react-datepicker__day--selected,
.dark-mode .react-datepicker__day--in-selecting-range,
.dark-mode .react-datepicker__day--in-range,
-.dark-mode .react-datepicker__day:hover {
- background-color: #1C2541;
- color: #ffffff;
+.dark-mode .react-datepicker__day:hover,
+.text-light .react-datepicker__day--selected,
+.text-light .react-datepicker__day--in-selecting-range,
+.text-light .react-datepicker__day--in-range,
+.text-light .react-datepicker__day:hover {
+ background-color: #374151 !important;
+ color: #ffffff !important;
+ border-radius: 0 !important;
+}
+.dark-mode .react-datepicker__navigation,
+.text-light .react-datepicker__navigation {
+ background: none !important;
+}
+.dark-mode .react-datepicker__navigation::before,
+.text-light .react-datepicker__navigation::before {
+ border-color: #ffffff !important;
}
-.dark-mode .react-datepicker__navigation {
- background: none;
+
+/* Dark mode styles for react-datepicker input field */
+.text-light .react-datepicker__input-container input,
+.text-light .hgn-datepicker-dark,
+.text-light .react-datepicker-wrapper input {
+ color: #e5e7eb !important;
+ background-color: #1f2937 !important;
}
-.dark-mode .react-datepicker__navigation::before {
- border-color: #ffffff;
+
+.text-light .react-datepicker__input-container input::placeholder,
+.text-light .hgn-datepicker-dark::placeholder,
+.text-light .react-datepicker-wrapper input::placeholder {
+ color: #9ca3af !important;
+ opacity: 1 !important;
+}
+
+.text-light .react-datepicker__input-container input:focus,
+.text-light .hgn-datepicker-dark:focus,
+.text-light .react-datepicker-wrapper input:focus {
+ color: #e5e7eb !important;
+ background-color: #374151 !important;
}
.no-hover tr:hover {
diff --git a/src/components/LeaderBoard/Leaderboard.jsx b/src/components/LeaderBoard/Leaderboard.jsx
index 89534c25e3..8b2fe6958b 100644
--- a/src/components/LeaderBoard/Leaderboard.jsx
+++ b/src/components/LeaderBoard/Leaderboard.jsx
@@ -37,7 +37,7 @@ import axios from 'axios';
import { getUserProfile } from '~/actions/userProfile';
import { useDispatch, useSelector } from 'react-redux';
import { boxStyleDark } from '../../styles';
-import '../Header/DarkMode.css';
+import '../Header/index.css';
import '../UserProfile/TeamsAndProjects/autoComplete.css';
import { ENDPOINTS } from '~/utils/URL';
diff --git a/src/components/Logout/Logout.jsx b/src/components/Logout/Logout.jsx
index 023660bc21..8a1954a60f 100644
--- a/src/components/Logout/Logout.jsx
+++ b/src/components/Logout/Logout.jsx
@@ -2,7 +2,7 @@ import { Redirect } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import { boxStyle, boxStyleDark } from '~/styles';
-import '../Header/DarkMode.css';
+import '../Header/index.css';
import { logoutUser } from '../../actions/authActions';
function Logout({ setLogoutPopup, open }) {
diff --git a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css
index 591b945da3..37e35c7b6a 100644
--- a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css
+++ b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css
@@ -31,7 +31,6 @@
display: flex;
justify-content: space-around;
}
-
.riFilterItem {
display: flex;
flex-direction: column;
@@ -172,7 +171,6 @@
box-shadow: 2px 2px 4px 1px black;
border: none;
}
-
.riActionDoneGraph {
width: 50%;
display: flex;
@@ -200,7 +198,6 @@
.riGraph canvas {
height: 100% !important;
}
-
.riLoading {
text-align: center;
font-size: 18px;
diff --git a/src/components/PermissionsManagement/PermissionsManagement.jsx b/src/components/PermissionsManagement/PermissionsManagement.jsx
index 323e3106eb..085e85da58 100644
--- a/src/components/PermissionsManagement/PermissionsManagement.jsx
+++ b/src/components/PermissionsManagement/PermissionsManagement.jsx
@@ -10,7 +10,7 @@ import ReactTooltip from 'react-tooltip'; // Importing react-tooltip for tooltip
// eslint-disable-next-line import/named
import { updateUserProfile, getUserProfile } from '../../actions/userProfile';
import { boxStyle, boxStyleDark } from '../../styles';
-import '../Header/DarkMode.css';
+import '../Header/index.css';
import { ENDPOINTS } from '~/utils/URL';
import { ModalContext } from '~/context/ModalContext';
import EditableInfoModal from '../UserProfile/EditableModal/EditableInfoModal';
diff --git a/src/components/PermissionsManagement/__tests__/UserRoleTab.test.jsx b/src/components/PermissionsManagement/__tests__/UserRoleTab.test.jsx
index d68a5a5170..9ee3a427fc 100644
--- a/src/components/PermissionsManagement/__tests__/UserRoleTab.test.jsx
+++ b/src/components/PermissionsManagement/__tests__/UserRoleTab.test.jsx
@@ -1,7 +1,7 @@
// eslint-disable-next-line no-unused-vars
import React from 'react';
// eslint-disable-next-line no-unused-vars
-import { render, screen, fireEvent, waitFor, act, wait } from '@testing-library/react';
+import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import thunk from 'redux-thunk';
import mockAdminState from '__tests__/mockAdminState';
diff --git a/src/components/Projects/AddProject/AddProject.jsx b/src/components/Projects/AddProject/AddProject.jsx
index e04dc2a26e..0bf6a6999d 100644
--- a/src/components/Projects/AddProject/AddProject.jsx
+++ b/src/components/Projects/AddProject/AddProject.jsx
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import { connect } from 'react-redux';
-import '../../Header/DarkMode.css';
+import '../../Header/index.css';
import { addNewWBS } from './../../../actions/wbs';
import { postNewProject } from './../../../actions/projects';
import { findUserProfiles, assignProject } from './../../../actions/projectMembers';
diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx
index fe7071ec3b..c42d957501 100644
--- a/src/components/Projects/Projects.jsx
+++ b/src/components/Projects/Projects.jsx
@@ -54,6 +54,7 @@ const projectFetchStatus = useSelector(state => state.allProjects.status);
const [allProjects, setAllProjects] = useState(null);
const [isChangingStatus, setIsChangingStatus] = useState(false);
const [isArchiving, setIsArchiving] = useState(false);
+ const [searchMode, setSearchMode] = useState('person');
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
@@ -258,23 +259,61 @@ const projectFetchStatus = useSelector(state => state.allProjects.status);
// }, [fetched, categorySelectedForSort, showStatus, sortedByName, props.state.theme.darkMode]);
useEffect(() => {
- const fetchProjects = async () => {
- if (debouncedSearchName) {
- const projects = await props.getProjectsByUsersName(debouncedSearchName);
- if (projects && allReduxProjects) {
- const newProjectList = allProjects.filter(project =>
- projects.some(p => p === project._id)
- );
- setProjectList(newProjectList);
- } else {
- setProjectList(allProjects);
- }
- } else {
- setProjectList(allProjects);
- }
- };
- fetchProjects();
- }, [debouncedSearchName, allProjects, allReduxProjects]);
+ const fetchProjects = async () => {
+ if (!debouncedSearchName) {
+ setProjectList(allProjects);
+ return;
+ }
+
+ // Mode 1: Search by user
+ if (searchMode === 'person') {
+ const userProjects = await props.getProjectsByUsersName(debouncedSearchName);
+
+ const filteredProjects = allReduxProjects.filter(p =>
+ userProjects.includes(p._id)
+ );
+
+ const mapped = filteredProjects.map((project, index) => (
+
+ ));
+
+ setProjectList(mapped);
+ return;
+ }
+
+ // Mode 2: Search by project name
+ if (searchMode === 'project') {
+ const filteredProjects = allReduxProjects.filter(p =>
+ p.projectName?.toLowerCase().includes(debouncedSearchName.toLowerCase())
+ );
+
+ const mapped = filteredProjects.map((project, index) => (
+
+ ));
+
+ setProjectList(mapped);
+ return;
+ }
+ };
+
+ fetchProjects();
+}, [debouncedSearchName, searchMode, allProjects, allReduxProjects]);
const handleSearchName = searchNameInput => {
setSearchName(searchNameInput);
@@ -298,8 +337,27 @@ const projectFetchStatus = useSelector(state => state.allProjects.status);
{canPostProject ?
: null}
-
-