diff --git a/src/assets/images/masterMap.png b/src/assets/images/masterMap.png
new file mode 100644
index 0000000000..8a1d4eede0
Binary files /dev/null and b/src/assets/images/masterMap.png differ
diff --git a/src/assets/images/pin-point.png b/src/assets/images/pin-point.png
new file mode 100644
index 0000000000..f45502ee38
Binary files /dev/null and b/src/assets/images/pin-point.png differ
diff --git a/src/assets/images/routeMarker.png b/src/assets/images/routeMarker.png
new file mode 100644
index 0000000000..c1c1758056
Binary files /dev/null and b/src/assets/images/routeMarker.png differ
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.css
index 7c72a77a0f..b0ea400745 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.css
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.css
@@ -297,4 +297,89 @@
.weekly-project-summary-dashboard-grid {
grid-template-columns: 1fr;
}
-}
\ No newline at end of file
+}
+
+/* ---------------- STATUS CARD ---------------- */
+.status-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ border-radius: 25px;
+ width: 100%;
+ max-width: 284px;
+ height: 190px;
+ text-align: center;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+ padding: 20px;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ position: relative;
+}
+
+/* ---------------- RESPONSIVE GRID LAYOUT ---------------- */
+.project-status-grid {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 20px;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ max-width: 1600px;
+ margin: auto;
+}
+
+/* ---------------- OVAL STATUS BUTTON ---------------- */
+.weekly-status-button {
+ width: 130px;
+ height: 65px;
+ border-radius: 50px / 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
+ margin: 12px 0;
+}
+
+.weekly-card-title {
+ color: #000;
+ font-size: 24px;
+ font-weight: 600;
+}
+
+.weekly-status-value {
+ color: #000;
+ font-size: 40px;
+ font-weight: 600;
+}
+
+
+/* ---------------- RESPONSIVE BREAKPOINTS ---------------- */
+@media (min-width: 1600px) {
+ .project-status-grid {
+ grid-template-columns: repeat(6, 1fr);
+ }
+}
+
+@media (max-width: 1400px) {
+ .project-status-grid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+@media (max-width: 1024px) {
+ .project-status-grid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media (max-width: 768px) {
+ .project-status-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 576px) {
+ .project-status-grid {
+ grid-template-columns: repeat(1, 1fr);
+ }
+}
diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
index 6ab959ecdc..f2ffadd946 100644
--- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
+++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.jsx
@@ -4,6 +4,105 @@ import { useSelector } from 'react-redux';
import { v4 as uuidv4 } from 'uuid';
import WeeklyProjectSummaryHeader from './WeeklyProjectSummaryHeader';
+const projectStatusButtons = [
+ {
+ title: 'Total Projects',
+ value: 426,
+ change: '+16% week over week',
+ bgColor: '#F0FFEE',
+ buttonColor: '#BAF0B6',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Completed Projects',
+ value: 127,
+ change: '+14% week over week',
+ bgColor: '#F3FCFF',
+ buttonColor: '#C1EFFB',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Delayed Projects',
+ value: 34,
+ change: '-18% week over week',
+ bgColor: '#FFE9FA',
+ buttonColor: '#FECFF3',
+ textColor: '#C82F2F',
+ },
+ {
+ title: 'Active Projects',
+ value: 265,
+ change: '+3% week over week',
+ bgColor: '#E8E8FF',
+ buttonColor: '#CBCBFE',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Avg Project Duration',
+ value: '17 hrs',
+ change: '+13% week over week',
+ bgColor: '#FFF6EE',
+ buttonColor: '#FFD8A5',
+ textColor: '#FFD8A5',
+ },
+ {
+ title: 'Total Material Cost',
+ value: '$27.6K',
+ change: '+9% week over week',
+ bgColor: '#FFF3F3',
+ buttonColor: '#FBC1C2',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Total Material Used',
+ value: '2714',
+ change: '+11% week over week',
+ bgColor: '#DAC8FF',
+ buttonColor: '#B28ECC',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Active Projects',
+ value: '265',
+ change: '+3% week over week',
+ bgColor: '#E8E8FF',
+ buttonColor: '#CBCBFE',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Total Labor Hours Invested',
+ value: '12.8K',
+ change: '+17% week over week',
+ bgColor: '#E5C1FC',
+ buttonColor: '#F6E1FB',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Total Labor Cost',
+ value: '$18.4K',
+ change: '+14% week over week',
+ bgColor: '#FFFDF3',
+ buttonColor: '#FBF9C1',
+ textColor: '#328D1B',
+ },
+ {
+ title: 'Material Available',
+ value: 693,
+ change: '-8% week over week',
+ bgColor: '#B4D9C5',
+ buttonColor: '#31BD41',
+ textColor: '#C82F2F',
+ },
+ {
+ title: 'Material Wasted',
+ value: 879,
+ change: '+14% week over week',
+ bgColor: '#EFBABB',
+ buttonColor: '#F79395',
+ textColor: '#328D1B',
+ },
+];
+
export default function WeeklyProjectSummary() {
const [openSections, setOpenSections] = useState({});
@@ -23,11 +122,27 @@ export default function WeeklyProjectSummary() {
className: 'full',
content: (
- {Array.from({ length: 12 }).map(() => {
+ {projectStatusButtons.map(button => {
const uniqueId = uuidv4();
return (
-
- 📊 Card
+
+
{button.title}
+
+ {button.value}
+
+
+ {button.change}
+
);
})}
diff --git a/src/components/Badge/AssignBadge.jsx b/src/components/Badge/AssignBadge.jsx
index 5e5d2f30af..f463e5e88a 100644
--- a/src/components/Badge/AssignBadge.jsx
+++ b/src/components/Badge/AssignBadge.jsx
@@ -214,12 +214,7 @@ function AssignBadge(props) {
>
Assign Badge
-
+
Assign Badge
diff --git a/src/components/Badge/BadgeReport.jsx b/src/components/Badge/BadgeReport.jsx
index 48d4d24883..2d50758334 100644
--- a/src/components/Badge/BadgeReport.jsx
+++ b/src/components/Badge/BadgeReport.jsx
@@ -84,54 +84,68 @@ function BadgeReport(props) {
};
}
- const FormatReportForPdf = (badges, callback) => {
- const bgReport = [];
- bgReport[0] = `Badge Report (Page 1 of ${Math.ceil(badges.length / 4)})
- For ${props.firstName} ${
- props.lastName
- }
- _______________________________________________________________________________________________
`;
-
- for (let i = 0; i < badges.length; i += 1) {
- imageToUri(badges[i].badge.imageUrl, function(uri) {
- bgReport[i + 1] = `
-
-
-
- | Badge Image |
- Badge Name, Count Awarded & Badge Description |
-
-
-
-
- |
-
- |
-
- Name: ${badges[i].badge.badgeName}
- Count: ${badges[i].count}
- Description: ${badges[i].badge.description}
- |
-
-
-
- ${
- (i + 1) % 4 === 0 && i + 1 !== badges.length
- ? `
- Badge Report (Page ${1 + Math.ceil((i + 1) / 4)} of ${Math.ceil(badges.length / 4)})
- For ${props.firstName} ${
- props.lastName
- }
- _______________________________________________________________________________________________
- `
- : ''
- }`;
- if (i === badges.length - 1) {
- setTimeout(() => {
- callback(bgReport.join('\n'));
- }, 100);
- }
+ const FormatReportForPdf = async (badges, callback) => {
+ try {
+ const bgReport = [];
+ bgReport[0] = `Badge Report (Page 1 of ${Math.ceil(badges.length / 4)})
+ For ${props.firstName} ${
+ props.lastName
+ }
+ _______________________________________________________________________________________________
`;
+
+ const badgePromises = badges.map((badge, i) => {
+ const imageUrl = badge.badge?.imageUrl || ''; // Fallback to empty string if imageUrl is missing
+ const badgeName = badge.badge?.badgeName || 'Unknown Badge'; // Fallback for missing badgeName
+ const description = badge.badge?.description || 'No description available'; // Fallback for missing description
+
+ return new Promise((resolve) => {
+ imageToUri(imageUrl, (uri) => {
+ const badgeHtml = `
+
+
+
+ | Badge Image |
+ Badge Name, Count Awarded & Badge Description |
+
+
+
+
+ |
+
+ |
+
+ Name: ${badgeName}
+ Count: ${badge.count}
+ Description: ${description}
+ |
+
+
+
+ ${
+ (i + 1) % 4 === 0 && i + 1 !== badges.length
+ ? `
+ Badge Report (Page ${1 + Math.ceil((i + 1) / 4)} of ${Math.ceil(
+ badges.length / 4
+ )})
+ For ${props.firstName} ${
+ props.lastName
+ }
+ _______________________________________________________________________________________________
+ `
+ : ''
+ }`;
+ resolve(badgeHtml);
+ });
+ });
});
+
+ const badgeHtmlArray = await Promise.all(badgePromises);
+ bgReport.push(...badgeHtmlArray);
+
+ callback(bgReport.join('\n'));
+ } catch (error) {
+ console.error('Error generating badge report:', error);
+ callback('Error generating badge report. Please try again later.
');
}
};
diff --git a/src/components/CommunityPortal/EventPersonalization/EventStats.css b/src/components/CommunityPortal/EventPersonalization/EventStats.css
new file mode 100644
index 0000000000..d7d381d460
--- /dev/null
+++ b/src/components/CommunityPortal/EventPersonalization/EventStats.css
@@ -0,0 +1,222 @@
+.popular-events-container {
+
+ max-height: 100%;
+ margin: 0 auto;
+ padding: 20px;
+ font-family: Arial, sans-serif;
+ background: white;
+}
+
+.popular-events-container-dark {
+
+ margin: 0 auto;
+ padding: 20px;
+ font-family: Arial, sans-serif;
+ background: #1B2A41;
+ min-height: 100%;
+}
+
+.header-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.header-container-dark {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: #1C2541;
+}
+
+.popular-events-header {
+ font-size: 1.5rem;
+ color: #000000;
+ margin: 0;
+}
+
+.popular-events-header-dark {
+ font-size: 1.5rem;
+ color: #ffffff;
+ margin: 0;
+ background-color: #1C2541;
+}
+
+.filters {
+ display: flex;
+ gap: 10px;
+}
+
+.filters-dark {
+ display: flex;
+ gap: 10px;
+ background-color: #1C2541;
+}
+
+.filters select {
+ padding: 6px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+.stats {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ border-radius: 8px;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
+ padding: 30px;
+}
+
+.stats-dark {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ border-radius: 8px;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
+ padding: 30px;
+ background-color: #1C2541;
+}
+
+.stat-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+
+
+.stat-label {
+ flex: 1;
+ font-size: 14px;
+ font-weight: bold;
+ color: #333;
+}
+
+.stat-label-dark {
+ flex: 1;
+ font-size: 14px;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+.stat-bar {
+ flex: 3;
+ background: #f0f0f0;
+ height: 8px;
+ border-radius: 5px;
+ margin: 0 10px;
+ position: relative;
+ overflow: hidden;
+}
+
+.bar {
+ height: 100%;
+ border-radius: 5px;
+ transition: width 0.5s ease-in-out;
+}
+
+.bar.green {
+ background-color: #4caf50;
+}
+
+.bar.orange {
+ background-color: #ff9800;
+}
+
+.bar.red {
+ background-color: #f44336;
+}
+
+.stat-value {
+ flex: 1;
+ text-align: right;
+ font-size: 14px;
+ font-weight: bold;
+ color: #333;
+}
+
+.stat-value-dark {
+ flex: 1;
+ text-align: right;
+ font-size: 14px;
+ font-weight: bold;
+ color: #ffffff;
+}
+
+.summary {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 10px;
+ max-height: 80px;
+ margin-top: 20px;
+ border-radius: 8px;
+ text-align: center;
+ padding-bottom: 20px;
+}
+
+.summary-item {
+ background: white;
+ padding: 12px;
+ border-radius: 8px;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-height: 90px;
+ min-height: 80px;
+ overflow: hidden;
+ text-align: center;
+}
+
+.summary-item-dark {
+ background: #3A506B;
+ padding: 12px;
+ border-radius: 8px;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-height: 90px;
+ min-height: 80px;
+ overflow: hidden;
+ text-align: center;
+}
+
+.summary-title {
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ white-space: normal;
+ word-wrap: break-word;
+ max-width: 90%;
+ color: #777;
+}
+
+.summary-title-dark {
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ white-space: normal;
+ word-wrap: break-word;
+ max-width: 90%;
+ color: #ffffff;
+}
+
+.summary-value {
+ font-size: 14px;
+ font-weight: bold;
+ color: #000;
+ margin-top: 4px;
+}
+
+.summary-value-dark {
+ font-size: 14px;
+ font-weight: bold;
+ color: #ffffff;
+ margin-top: 4px;
+}
\ No newline at end of file
diff --git a/src/components/CommunityPortal/EventPersonalization/EventStats.jsx b/src/components/CommunityPortal/EventPersonalization/EventStats.jsx
new file mode 100644
index 0000000000..1a61567909
--- /dev/null
+++ b/src/components/CommunityPortal/EventPersonalization/EventStats.jsx
@@ -0,0 +1,174 @@
+import { useState } from 'react';
+import './EventStats.css';
+import { useSelector } from 'react-redux';
+
+const dummyData = [
+ {
+ id: 1,
+ type: 'Type of Event 1',
+ attended: 20,
+ enrolled: 25,
+ time: 'Morning',
+ location: 'Offline',
+ },
+ {
+ id: 2,
+ type: 'Type of Event 2',
+ attended: 19,
+ enrolled: 20,
+ time: 'Afternoon',
+ location: 'Online',
+ },
+ {
+ id: 3,
+ type: 'Type of Event 3',
+ attended: 12,
+ enrolled: 18,
+ time: 'Night',
+ location: 'Offline',
+ },
+ {
+ id: 4,
+ type: 'Type of Event 4',
+ attended: 11,
+ enrolled: 20,
+ time: 'Morning',
+ location: 'Online',
+ },
+ {
+ id: 5,
+ type: 'Type of Event 5',
+ attended: 8,
+ enrolled: 20,
+ time: 'Afternoon',
+ location: 'Offline',
+ },
+ { id: 6, type: 'Type of Event 6', attended: 7, enrolled: 22, time: 'Night', location: 'Offline' },
+ {
+ id: 7,
+ type: 'Type of Event 7',
+ attended: 4,
+ enrolled: 20,
+ time: 'Morning',
+ location: 'Online',
+ },
+];
+
+export default function PopularEvents() {
+ const [timeFilter, setTimeFilter] = useState('All day');
+ const [typeFilter, setTypeFilter] = useState('All');
+
+ const calculatePercentage = (attended, enrolled) => Math.round((attended / enrolled) * 100);
+
+ const getBarColor = percentage => {
+ if (percentage > 60) return 'green';
+ if (percentage > 40) return 'orange';
+ return 'red';
+ };
+
+ const filteredData = dummyData.filter(event => {
+ const timeMatch = timeFilter === 'All day' || event.time === timeFilter;
+ const typeMatch = typeFilter === 'All' || event.location === typeFilter;
+ return timeMatch && typeMatch;
+ });
+
+ const mostPopularEvent = filteredData.reduce(
+ (max, event) =>
+ calculatePercentage(event.attended, event.enrolled) >
+ calculatePercentage(max.attended, max.enrolled)
+ ? event
+ : max,
+ filteredData[0] || {},
+ );
+
+ const leastPopularEvent = filteredData.reduce(
+ (min, event) =>
+ calculatePercentage(event.attended, event.enrolled) <
+ calculatePercentage(min.attended, min.enrolled)
+ ? event
+ : min,
+ filteredData[0] || {},
+ );
+ const darkMode = useSelector(state => state.theme.darkMode);
+ return (
+
+
+
+ Most Popular Event
+
+
+
+
+
+
+
+
+ {filteredData.map(event => (
+
+
{event.type}
+
+
+ {`${calculatePercentage(event.attended, event.enrolled)}% (${event.attended}/${
+ event.enrolled
+ })`}
+
+
+ ))}
+
+
+
+
+ Total Number of Events
+
+
+ {filteredData.length}
+
+
+
+
+ Total Number of Event Enrollments
+
+
+ {filteredData.reduce((acc, event) => acc + event.enrolled, 0)}
+
+
+ {filteredData.length > 0 && (
+ <>
+
+
+ Most Popular Event
+
+
+ {mostPopularEvent.type || 'N/A'}
+
+
+
+
+ Least Popular Event
+
+
+ {leastPopularEvent.type || 'N/A'}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx
index d80719396b..881e92bbe0 100644
--- a/src/components/Dashboard/Dashboard.jsx
+++ b/src/components/Dashboard/Dashboard.jsx
@@ -31,7 +31,7 @@ export function Dashboard(props) {
const dispatch = useDispatch();
- const toggle = (forceOpen = null) => {
+ const toggle = () => {
if (isNotAllowedToEdit) {
const warningMessage =
viewingUser?.email === DEV_ADMIN_ACCOUNT_EMAIL_DEV_ENV_ONLY
@@ -41,8 +41,7 @@ export function Dashboard(props) {
return;
}
- const shouldOpen = forceOpen !== null ? forceOpen : !popup;
- setPopup(shouldOpen);
+ setPopup(!popup);
setTimeout(() => {
const elem = document.getElementById('weeklySum');
diff --git a/src/components/LBDashboard/ListingOverview/ImageCarousel.css b/src/components/LBDashboard/ListingOverview/ImageCarousel.css
deleted file mode 100644
index 105f599755..0000000000
--- a/src/components/LBDashboard/ListingOverview/ImageCarousel.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.carousel-container {
- position: relative;
- width: 100%;
- height: 100%;
- max-width: 100%;
- margin: auto;
- overflow: hidden;
-}
-
-.carousel-wrapper {
- width: 100%;
- overflow: hidden;
-}
-
-.carousel-track {
- display: flex;
- transition: transform 0.5s ease-in-out;
-}
-
-.carousel-image {
- width: 100%;
- height: 100%;
- max-width: 100%;
- flex-shrink: 0;
- object-fit: cover;
-}
-
-.carousel-image-arrow {
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- background-color: rgba(0, 0, 0, 0.5);
- color: white;
- border: none;
- padding: 10px;
- border-radius: 50%;
- cursor: pointer;
- z-index: 10;
-}
-
-.carousel-image-arrow.left {
- left: 10px;
-}
-
-.carousel-image-arrow.right {
- right: 10px;
-}
-
-.carousel-indicators {
- display: flex;
- justify-content: center;
- margin-top: 5%;
- bottom: 2% !important;
-}
-
-.indicator {
- width: 10px;
- height: 10px;
- margin: 0 5px;
- background-color: #bbb;
- border-radius: 50%;
- cursor: pointer;
- transition: background-color 0.3s ease;
-}
-
-.indicator.active {
- background-color: #333;
-}
\ No newline at end of file
diff --git a/src/components/LBDashboard/ListingOverview/ImageCarousel.jsx b/src/components/LBDashboard/ListingOverview/ImageCarousel.jsx
deleted file mode 100644
index 5d01e924d9..0000000000
--- a/src/components/LBDashboard/ListingOverview/ImageCarousel.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { useState } from 'react';
-import './ImageCarousel.css';
-
-export default function ImageCarousel({ images }) {
- const [currentIndex, setCurrentIndex] = useState(0);
-
- if (!images || images.length === 0) return No images available
;
-
- const handleNext = () => {
- setCurrentIndex(prev => (prev + 1) % images.length);
- };
-
- const handlePrev = () => {
- setCurrentIndex(prev => (prev - 1 + images.length) % images.length);
- };
-
- const handleIndicatorClick = index => {
- setCurrentIndex(index);
- };
-
- return (
-
-
-
- {images.map((image, index) => (
-

- ))}
-
-
-
-
-
- {images.map((image, index) => (
- handleIndicatorClick(index)}
- />
- ))}
-
-
- );
-}
diff --git a/src/components/LBDashboard/ListingOverview/ListOverview.jsx b/src/components/LBDashboard/ListingOverview/ListOverview.jsx
index 4266f55b29..1b63e0248b 100644
--- a/src/components/LBDashboard/ListingOverview/ListOverview.jsx
+++ b/src/components/LBDashboard/ListingOverview/ListOverview.jsx
@@ -1,8 +1,8 @@
import React, { useEffect } from 'react';
import './Listoverview.css';
+import Carousel from 'react-bootstrap/Carousel';
import logo from '../../../assets/images/logo2.png';
import mapIcon from '../../../assets/images/mapIcon.png';
-import ImageCarousel from './ImageCarousel';
function ListOverview() {
const [listing, setListing] = React.useState({});
@@ -37,12 +37,19 @@ function ListOverview() {
{listing.title}
-
+
+ {listing.images?.map((image, index) => (
+
+
+
+ ))}
+
+
Available amenities in this unit:
-
+
{listing.unitAmenities?.map(amenity => (
- {amenity}
))}
@@ -50,7 +57,7 @@ function ListOverview() {
Village level amenities:
-
+
{listing.villageAmenities?.map(amenity => (
- {amenity}
))}
diff --git a/src/components/LBDashboard/ListingOverview/Listoverview.css b/src/components/LBDashboard/ListingOverview/Listoverview.css
index 8659816399..1aab927298 100644
--- a/src/components/LBDashboard/ListingOverview/Listoverview.css
+++ b/src/components/LBDashboard/ListingOverview/Listoverview.css
@@ -75,6 +75,15 @@
margin-bottom: 2.5%;
}
+.image-carousel img {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ max-width: 100%;
+ flex-shrink: 0;
+}
+
+
.amenities {
display: flex;
justify-content: center;
@@ -96,12 +105,12 @@
padding-left: 5%;
}
-ol {
+.amenities-list {
display: flex;
flex-direction: column;
list-style-position: inside;
}
-li {
+.amenities-list li {
margin: 0;
}
diff --git a/src/components/LBDashboard/Map/MasterPlan/MasterPlan.css b/src/components/LBDashboard/Map/MasterPlan/MasterPlan.css
new file mode 100644
index 0000000000..dd4adc9921
--- /dev/null
+++ b/src/components/LBDashboard/Map/MasterPlan/MasterPlan.css
@@ -0,0 +1,239 @@
+.main-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: #e0e0e0;
+ width: 100%;
+ min-height: 100vh;
+ padding: 20px;
+ overflow: hidden;
+}
+
+.logo-container {
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+.logo-container img {
+ max-width: 30%;
+ height: auto;
+ min-width: 250px;
+}
+.content-container {
+ display: flex;
+ flex-direction: column;
+ background-color: white;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ width: 85%;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ height: max-content;
+}
+
+.container-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ height: 60px;
+ background-color: #9dd425;
+}
+
+.container-main {
+ background-color: #e0e0e0;
+ display: flex;
+ flex-direction: column;
+ margin: 20px 40px 30px 40px !important;
+ padding: 20px 20px 50px 20px !important;
+ width: auto;
+ min-height: 560px;
+ height: 100%;
+}
+
+.container-map {
+ display: flex;
+ height: 100%;
+ width: 100%;
+ flex-direction: column;
+ justify-content: space-evenly;
+ align-items: center;
+ background-color: #839a4a;
+}
+
+.map-details {
+ width: 100%;
+ height: 75%;
+ display: flex;
+}
+.pin-point {
+ position: absolute;
+ width: 5.5% !important;
+ height: 6% !important;
+ z-index: 9;
+ top: calc(var(--top) * 580 / 700 - 3%);
+ left: calc(var(--left) - 1%);
+}
+
+
+.map {
+ width: 65%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0;
+}
+
+.image-wrapper {
+ position: relative;
+ height: 100%;
+ display: inline-block;
+}
+
+.image-wrapper img {
+ height: 100%;
+ width: 100%;
+ max-width: none;
+}
+
+.village-marker {
+ border-radius: 50%;
+ position: absolute;
+ cursor: pointer;
+ opacity: 0;
+ width: 3.5%;
+ height: 4%;
+ z-index: 10;
+ top: calc(var(--top) * 580 / 700);
+ left: calc(var(--left));
+}
+
+
+.route {
+ width: 35%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 10px 0;
+ border-left: solid 1px #e0e0e0;
+}
+
+.route p {
+ font-size: 0.5em;
+ margin-top: 0;
+ margin-bottom: 0;
+ font-weight: 600;
+ text-align: center;
+ color: #e0e0e0;
+}
+
+.route img {
+ width: 100%;
+ height: 100%;
+}
+
+.villages {
+ width: 100%;
+ height: 25%;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ overflow-x: scroll;
+}
+
+.village {
+ display: flex;
+ height: 100%;
+ padding: 5px;
+ align-items: center;
+}
+
+.village:hover {
+ background-color: #768b43;
+}
+
+.village img {
+ width: auto;
+ height: 75%;
+ cursor: pointer;
+}
+
+.selected {
+ background-color: #768b43;
+}
+
+
+@media (max-width: 990px) {
+ .main-container {
+ padding: 0;
+ }
+ .container-main {
+ flex-direction: column;
+ align-items: center;
+ }
+ .map-details {
+ flex-direction: column;
+ height: 100%;
+ }
+ .map {
+ width: 100%;
+ }
+
+ .image-wrapper {
+ position: relative;
+ min-height: 100%;
+ display: inline-block;
+ }
+ .image-wrapper img {
+ width: 100%;
+ height: auto;
+ max-width: none;
+ }
+ .route {
+ width: 100%;
+ border-left: none;
+ border-top: solid 1px #e0e0e0;
+ }
+ .villages{
+ height: auto;
+ }
+
+}
+
+.village-details{
+ display: flex;
+ flex-direction: column;
+ padding-left: 20px;
+ width: 100%;
+ height: 100%;
+}
+
+.village-details h3{
+ margin: 0;
+ padding: 0;
+ font-size: 1.5em;
+ font-weight: 600;
+ color:#839a4a;
+}
+
+.village-details p{
+ margin: 0;
+ padding: 0;
+ font-size: 1em;
+ font-weight: 400;
+ color:#839a4a;
+}
+
+
+@media (max-width: 580px) {
+ .container-main {
+ padding: 10px;
+ margin: 10px;
+ }
+ .village-details{
+ padding-left: 0;
+ }
+
+}
+
diff --git a/src/components/LBDashboard/Map/MasterPlan/MasterPlan.jsx b/src/components/LBDashboard/Map/MasterPlan/MasterPlan.jsx
new file mode 100644
index 0000000000..c5f9c8485d
--- /dev/null
+++ b/src/components/LBDashboard/Map/MasterPlan/MasterPlan.jsx
@@ -0,0 +1,185 @@
+import { useState } from 'react';
+import { useHistory } from 'react-router';
+import logo from '../../../../assets/images/logo2.png';
+import mastermap from '../../../../assets/images/masterMap.png';
+import mapRouter from '../../../../assets/images/routeMarker.png';
+import pin from '../../../../assets/images/pin-point.png';
+import './MasterPlan.css';
+
+const villages = [
+ {
+ id: 0,
+ name: 'Duplicable City Center',
+ short: 'CC',
+ description:
+ 'The Duplicable City Center will be the largest open source/DIY structure in the world. As part of One Community it will be a diversely functional, ultra-eco-friendly (LEED Platinum Certifiable), space and resource saving community center designed to be replicated.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2018/02/Duplicable-City-Center-PlanRender_640x335.jpg',
+ position: { top: '48%', left: '49.75%' },
+ },
+ {
+ id: 1,
+ name: 'Earthbag Village',
+ short: 'Earthbag',
+ description:
+ 'The Earthbag Village consists of seventy-eight 150-200 square foot (14-18.6 sq meter) earthbag hotel room styled cabanas plus four communal eco-shower structures, 2 vermiculture waste processing toilet structures, two net-zero water use toilet structures, and the central Tropical Atrium.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2018/10/Earthbag-Village-640x335-render.jpg',
+ position: { top: '45%', left: '41.1%' },
+ },
+ {
+ id: 2,
+ name: 'Straw Bale Village',
+ short: 'Straw',
+ description:
+ 'The Straw Bale Village consists of fifty-two 250-300 square foot (23-28 sq meters) studio-style rooms, each with an attached bathroom. They are arranged in groups of 4 that can easily be connected and or converted to create multi-room units.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2011/09/Straw-Bale-Village-PlanRender_640x335-1.png',
+ position: { top: '75.75%', left: '68%' },
+ },
+ {
+ id: 3,
+ name: 'Cob Village',
+ description:
+ 'Cob is an ancient building material composed of dirt, straw, and water that may have been used for construction since prehistoric times. Some of the oldest man-made structures in Afghanistan are composed of rammed earth and cob and still standing! ',
+ short: 'Cob',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2011/09/Cob-Village-PlanRender_640x335.png',
+ position: { top: '99.5%', left: '9%' },
+ },
+ {
+ id: 4,
+ name: 'Earth Block Village',
+ short: 'Block',
+ description:
+ 'Compressed earth blocks (CEBs) or pressed earth blocks are damp soil compressed at high pressure to form blocks. If the blocks are stabilized with a chemical binder such as Portland Cement they are called compressed stabilized earth blocks (CSEBs) or stabilized earth blocks (SEBs).',
+ url: 'https://onecommunityglobal.org/wp-content/uploads/2015/02/P4-Plan-Render_640x335.jpg',
+ position: { top: '112.5%', left: '75%' },
+ },
+ {
+ id: 5,
+ name: 'Shipping Container Village',
+ short: 'Container',
+ description:
+ 'The Shipping Container Village is planned as a semi-subterranean 3-level village constructed using shipping containers. It will provide 36 living units and 18 additional common spaces.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2011/09/Shipping-Container-Village-PlanRender_640x335.jpg',
+ position: { top: '107.25%', left: '53.75%' },
+ },
+ {
+ id: 6,
+ name: 'Recycled Materials Village',
+ short: 'Recycle',
+ description:
+ 'The Recycled Materials Village (Pod 6) will be open source shared to demonstrate how to build safely, affordably, and efficiently with maximal use of reclaimed/recycled materials. The design of the Recycled Materials Village is an earthship-inspired semi-subterranean design that will provide 47 living units and 14 common areas.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2018/06/Recycled-Materials-Village-PlanRender_640x335-updated.jpg',
+ position: { top: '50.5%', left: '66.75%' },
+ },
+ {
+ id: 7,
+ name: 'Tree House Village',
+ short: 'Treehouse',
+ description:
+ 'The Tree House Village will be a community living model in the trees. It plans to show how a Tree House Village, or off-ground and low-footprint/low-impact housing, can be a viable approach to sustainable living.',
+ url:
+ 'https://onecommunityglobal.org/wp-content/uploads/2014/01/Tree-House-Village-PlanRender_640x335.jpg',
+ position: { top: '68.25%', left: '93%' },
+ },
+];
+function MasterPlan() {
+ const [selectedVillage, setSelectedVillage] = useState(null);
+ const router = useHistory();
+
+ const handleVillageClick = village => {
+ setSelectedVillage(village);
+ if (selectedVillage === village) {
+ router.push(`/master-plan/${village.id}`);
+ }
+ };
+
+ const handleOutsideClick = () => {
+ setSelectedVillage(null);
+ };
+
+ return (
+
+
+

+
+
+
+
+
+
+
+
+

+ {villages.map(v => (
+
+
+
+

+
+ Click on the village marker or on the village to select a village and view more
+ details.
+
+
Double Click to view the village Page.
+
+
+
+ {villages.map(v => (
+
{
+ e.stopPropagation();
+ handleVillageClick(v);
+ }}
+ >
+

+
+ ))}
+
+
+
+ {selectedVillage && (
+
+
{selectedVillage.name}
+
{selectedVillage.description}
+
+ )}
+
+
+
+
+ );
+}
+
+export default MasterPlan;
diff --git a/src/components/LeaderBoard/Leaderboard.css b/src/components/LeaderBoard/Leaderboard.css
index 4a21612922..8077dfa23c 100644
--- a/src/components/LeaderBoard/Leaderboard.css
+++ b/src/components/LeaderBoard/Leaderboard.css
@@ -14,6 +14,11 @@
border-top-style: solid;
border-top-color: rgb(222, 226, 230);
}
+
+.leaderboard tbody tr td, thead tr th {
+ text-align: left !important;
+}
+
.dark-leaderboard-row {
background-color: #3a506b;
color: white;
diff --git a/src/components/LeaderBoard/Leaderboard.jsx b/src/components/LeaderBoard/Leaderboard.jsx
index 7a321b27b7..4dfa58f523 100644
--- a/src/components/LeaderBoard/Leaderboard.jsx
+++ b/src/components/LeaderBoard/Leaderboard.jsx
@@ -34,7 +34,7 @@ import { boxStyle } from 'styles';
import axios from 'axios';
import { getUserProfile } from 'actions/userProfile';
import { useDispatch } from 'react-redux';
-import { boxStyleDark } from 'styles';
+import { boxStyleDark } from '../../styles';
import '../Header/DarkMode.css';
import '../UserProfile/TeamsAndProjects/autoComplete.css';
import { ENDPOINTS } from '../../utils/URL';
@@ -845,18 +845,6 @@ function LeaderBoard({
>
-
-
-
-
)}
diff --git a/src/components/Projects/Project/Project.jsx b/src/components/Projects/Project/Project.jsx
index 5d3ae6f75b..384dab9cd2 100644
--- a/src/components/Projects/Project/Project.jsx
+++ b/src/components/Projects/Project/Project.jsx
@@ -17,20 +17,8 @@ const Project = props => {
const [projectData, setProjectData] = useState(props.projectData);
const { projectName, isActive,isArchived, _id: projectId } = projectData;
const [displayName, setDisplayName] = useState(projectName);
- const initialModalData = {
- showModal: false,
- modalMessage: "",
- modalTitle: "",
- hasConfirmBtn: false,
- hasInactiveBtn: false,
- };
-
- const [modalData, setModalData] = useState(initialModalData);
-
- const onCloseModal = () => {
- setModalData(initialModalData);
- props.clearError();
- }; const [category, setCategory] = useState(props.category || 'Unspecified'); // Initialize with props or default
+
+ const [category, setCategory] = useState(props.category || 'Unspecified'); // Initialize with props or default
const canPutProject = props.hasPermission('putProject');
const canDeleteProject = props.hasPermission('deleteProject');
@@ -71,13 +59,7 @@ const Project = props => {
};
const onArchiveProject = () => {
- setModalData({
- showModal: true,
- modalMessage: `
Do you want to archive ${projectData.projectName}?
`,
- modalTitle: CONFIRM_ARCHIVE,
- hasConfirmBtn: true,
- hasInactiveBtn: isActive,
- });
+ props.onClickArchiveBtn(projectData);
}
const setProjectInactive = () => {
@@ -211,16 +193,6 @@ const Project = props => {
) : null}
-
-
>
);
};
diff --git a/src/components/Projects/Project/Project.test.jsx b/src/components/Projects/Project/Project.test.jsx
index 1e6cd3aa40..59ae0b63a9 100644
--- a/src/components/Projects/Project/Project.test.jsx
+++ b/src/components/Projects/Project/Project.test.jsx
@@ -84,24 +84,17 @@ describe('Project Component', () => {
});
it('triggers delete action on button click', () => {
- const { getByTestId } = renderProject(sampleProps);
-
- // Find the delete button and click it
+ const mockOnClickArchiveBtn = jest.fn();
+ const { getByTestId } = renderProject({
+ ...sampleProps,
+ onClickArchiveBtn: mockOnClickArchiveBtn,
+ });
+
const deleteButton = getByTestId('delete-button');
fireEvent.click(deleteButton);
-
- // Check if the modal is triggered
- const modal = document.querySelector('.modal');
- expect(modal).toBeInTheDocument();
-
- const archiveButton=screen.getAllByText('Archive')[0];
- fireEvent.click(archiveButton);
-
- expect(screen.getByText('Confirm Archive')).toBeInTheDocument();
- expect(screen.getByText(`Do you want to archive ${sampleProjectData.projectName}?`)).toBeInTheDocument();
-
- const closeButton=screen.getByText('Close')
- fireEvent.click(closeButton)
- expect(screen.queryByText('Confirm Archive')).not.toBeInTheDocument();
+
+ expect(mockOnClickArchiveBtn).toHaveBeenCalledWith(expect.objectContaining({
+ _id: sampleProjectData._id,
+ }));
});
});
diff --git a/src/components/Projects/Projects.jsx b/src/components/Projects/Projects.jsx
index dcd41481b8..ddc4ced5d5 100644
--- a/src/components/Projects/Projects.jsx
+++ b/src/components/Projects/Projects.jsx
@@ -48,6 +48,8 @@ const Projects = function(props) {
const [searchName, setSearchName] = useState("");
const [allProjects, setAllProjects] = useState(null);
+ const [isArchiving, setIsArchiving] = useState(false);
+
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
@@ -104,9 +106,11 @@ const Projects = function(props) {
};
const confirmArchive = async () => {
+ setIsArchiving(true); // show loading on confirm
const updatedProject = { ...projectTarget, isArchived: true };
await onUpdateProject(updatedProject);
await props.fetchAllProjects();
+ setIsArchiving(false); // reset loading
onCloseModal();
};
@@ -251,6 +255,8 @@ const Projects = function(props) {
modalMessage={modalData.modalMessage}
modalTitle={modalData.modalTitle}
darkMode={darkMode}
+ confirmButtonText={isArchiving ? 'Archiving...' : 'Confirm'}
+ isConfirmDisabled={isArchiving}
/>
>
diff --git a/src/components/Projects/WBS/AddWBS/AddWBS.jsx b/src/components/Projects/WBS/AddWBS/AddWBS.jsx
index ca55d143d4..f2a6235d0c 100644
--- a/src/components/Projects/WBS/AddWBS/AddWBS.jsx
+++ b/src/components/Projects/WBS/AddWBS/AddWBS.jsx
@@ -9,18 +9,22 @@ import { addNewWBS } from './../../../../actions/wbs';
import hasPermission from 'utils/permissions';
const AddWBS = props => {
- const [showAddButton, setShowAddButton] = useState(false);
+ const darkMode = props.state.theme.darkMode;
const [newName, setNewName] = useState('');
+ const [showAddButton, setShowAddButton] = useState(false);
const canPostWBS = props.hasPermission('postWbs');
- const { darkMode } = props.state.theme;
- const changeNewName = newName => {
- if (newName.length !== 0) {
- setShowAddButton(true);
- } else {
+ const changeNewName = value => {
+ setNewName(value);
+ setShowAddButton(value.length >= 3);
+ };
+
+ const handleAddWBS = () => {
+ if (newName.length >= 3) {
+ props.addNewWBS(props.projectId, newName);
+ setNewName('');
setShowAddButton(false);
}
- setNewName(newName);
};
return (
@@ -28,7 +32,7 @@ const AddWBS = props => {
{canPostWBS ? (
- Add new WBS
+ Add new WBS
{
) : null}
+
+
) : null}
diff --git a/src/components/Projects/WBS/AddWBS/__tests__/AddWBS.test.jsx b/src/components/Projects/WBS/AddWBS/__tests__/AddWBS.test.jsx
index 7ec149e5e9..0a3dcaf4c6 100644
--- a/src/components/Projects/WBS/AddWBS/__tests__/AddWBS.test.jsx
+++ b/src/components/Projects/WBS/AddWBS/__tests__/AddWBS.test.jsx
@@ -66,7 +66,7 @@ describe("AddWBS component structure", () => {
});
test("button should not be in the document when the input field is empty", () => {
- expect(screen.queryByRole('button')).toBeNull();
+ expect(screen.queryByTestId('add-wbs-button')).toBeNull();
});
test("user should be able to type in the input field", () => {
@@ -76,7 +76,7 @@ describe("AddWBS component structure", () => {
test("button should appear when user types in the input field", () => {
typeIntoInput({ input: '123' });
- expect(screen.queryByRole('button')).not.toBeNull();
+ expect(screen.queryByTestId('add-wbs-button')).not.toBeNull();
});
});
diff --git a/src/components/Projects/WBS/WBSItem/WBSItem.jsx b/src/components/Projects/WBS/WBSItem/WBSItem.jsx
index f7cfded839..49ef4c0d6d 100644
--- a/src/components/Projects/WBS/WBSItem/WBSItem.jsx
+++ b/src/components/Projects/WBS/WBSItem/WBSItem.jsx
@@ -34,16 +34,16 @@ const WBSItem = ({ darkMode, index, name, wbsId, projectId, getPopupById, delete
return (
{filteredTasks.map(value => (
diff --git a/src/components/Reports/__tests__/PeopleTableDetails.test.js b/src/components/Reports/__tests__/PeopleTableDetails.test.js
new file mode 100644
index 0000000000..5e7be06f36
--- /dev/null
+++ b/src/components/Reports/__tests__/PeopleTableDetails.test.js
@@ -0,0 +1,237 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import PeopleTableDetails from '../PeopleTableDetails';
+
+// Mock data for the Test cases
+const taskData = [
+ {
+ _id: '1',
+ taskName: 'Task 1',
+ priority: 'High',
+ status: 'Completed',
+ resources: [[{ name: 'Resource 1', index: 1, profilepic: '' }]],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ {
+ _id: '2',
+ taskName: 'Task 2',
+ priority: 'Low',
+ status: 'In Progress',
+ resources: [
+ [{ name: 'Resource 2', index: 2, profilepic: '' }],
+ [{ name: 'Resource 3', index: 3, profilepic: '' }],
+ ],
+ active: 'Yes',
+ assign: 'Yes',
+ estimatedHours: '10h',
+ startDate: '2022-02-01',
+ endDate: '2022-02-10',
+ },
+ {
+ _id: '3',
+ taskName: 'Task 3',
+ priority: 'Medium',
+ status: 'Not Started',
+ resources: [[{ name: 'Resource 4', index: 1, profilepic: '' }]],
+ active: 'No',
+ assign: 'Yes',
+ estimatedHours: '8h',
+ startDate: '2022-03-01',
+ endDate: '2022-03-15',
+ },
+];
+describe('PeopleTableDetails component', () => {
+ it('renders without crashing', () => {
+ render(
);
+ expect(screen.getByTestId('eh'));
+ });
+
+ it('renders all table headers correctly', () => {
+ render(
);
+ expect(screen.getByTestId('task'));
+ expect(screen.getByTestId('priority'));
+ expect(screen.getByTestId('status'));
+ expect(screen.getByTestId('resources'));
+ expect(screen.getByTestId('active'));
+ expect(screen.getByTestId('eh'));
+ expect(screen.getByTestId('sd'));
+ expect(screen.getByTestId('ed'));
+ });
+
+ it('displays all task data in table rows', () => {
+ render(
);
+ // Expect to see text from the first task
+ expect(screen.getByText('Task 1')).toBeInTheDocument();
+ expect(screen.getByText('High')).toBeInTheDocument();
+ expect(screen.getByText('Completed')).toBeInTheDocument();
+
+ // Check the second task as well
+ expect(screen.getByText('Task 2')).toBeInTheDocument();
+ expect(screen.getByText('Low')).toBeInTheDocument();
+ expect(screen.getByText('In Progress')).toBeInTheDocument();
+
+ // Check the third task as well
+ expect(screen.getByText('Task 3')).toBeInTheDocument();
+ expect(screen.getByText('Medium')).toBeInTheDocument();
+ expect(screen.getByText('Not Started')).toBeInTheDocument();
+ });
+
+ it('handles missing task attributes gracefully', () => {
+ const tasks = [
+ {
+ _id: '1',
+ taskName: 'Project 1',
+ // Missed priority attribute
+ status: 'Completed',
+ resources: [[{ name: 'Resource 1', index: 1, profilepic: '' }]],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ {
+ _id: '2',
+ taskName: 'Project 2',
+ priority: 'Low',
+ // Missed status attribute
+ resources: [
+ [
+ { name: 'Resource 2', index: 1, profilepic: '' },
+ { name: 'Resource 3', index: 2, profilepic: '' },
+ ],
+ ],
+ active: 'Yes',
+ assign: 'Yes',
+ estimatedHours: '10h',
+ startDate: '2022-02-01',
+ endDate: '2022-02-10',
+ },
+ ];
+ render(
);
+ const project1Text = screen.queryByText('Project 1');
+ expect(project1Text).not.toBeInTheDocument();
+ const project2Text = screen.queryByText('Project 2');
+ expect(project2Text).not.toBeInTheDocument();
+ });
+
+ it('does not show resource toggle button when there are less than 2 resources', () => {
+ const tasks = [
+ {
+ _id: '1',
+ taskName: 'Project 1',
+ priority: 'High',
+ status: 'Completed',
+ resources: [[{ name: 'Resource 1', index: 1, profilepic: '' }]],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ ];
+ render(
);
+
+ expect(screen.getByText('Project 1')).toBeInTheDocument();
+ const toggleButton = screen.queryByText('+');
+ expect(toggleButton).not.toBeInTheDocument();
+ });
+
+ it('shows resource toggle button when there are more than 2 resources', () => {
+ const tasks = [
+ {
+ _id: '1',
+ taskName: 'Project 2',
+ priority: 'High',
+ status: 'Completed',
+ resources: [
+ [
+ { name: 'Resource 2', index: 2, profilePic: '' },
+ { name: 'Resource 3', index: 3, profilePic: '' },
+ { name: 'Resource 1', index: 1, profilePic: '' },
+ ],
+ ],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ ];
+ render(
);
+
+ expect(screen.getByText('Project 2')).toBeInTheDocument();
+ const toggleButton = screen.getByText('1+');
+ expect(toggleButton).toBeInTheDocument();
+ });
+
+ it('toggles resource visibility when button is clicked', () => {
+ const tasks = [
+ {
+ _id: '1',
+ taskName: 'Project 2',
+ priority: 'High',
+ status: 'Completed',
+ resources: [
+ [
+ { name: 'Resource 2', index: 2, profilePic: '' },
+ { name: 'Resource 3', index: 3, profilePic: '' },
+ { name: 'Resource 1', index: 1, profilePic: '' },
+ ],
+ ],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ ];
+
+ render(
);
+
+ const allButtons = screen.getAllByRole('button');
+ const toggleButton = allButtons.find(button =>
+ button.classList.contains('resourceMoreToggle')
+ );
+ expect(toggleButton).toBeInTheDocument();
+
+ const extraDiv = toggleButton.parentElement.querySelector('.extra');
+ expect(extraDiv).toBeInTheDocument();
+
+ fireEvent.click(toggleButton);
+ expect(extraDiv.style.display).toBe('table-cell');
+
+ fireEvent.click(toggleButton);
+ expect(extraDiv.style.display).toBe('none');
+ });
+
+ it('displays correct number of remaining resources', () => {
+ const tasks = [
+ {
+ _id: '1',
+ taskName: 'Project 2',
+ priority: 'High',
+ status: 'Completed',
+ resources: [
+ [
+ { name: 'Resource 2', index: 2, profilepic: '' },
+ { name: 'Resource 3', index: 3, profilepic: '' },
+ { name: 'Resource 1', index: 1, profilepic: '' },
+ { name: 'Resource 4', index: 4, profilepic: '' },
+ ],
+ ],
+ active: 'Yes',
+ assign: 'No',
+ estimatedHours: '5h',
+ startDate: '2022-01-01',
+ endDate: '2022-01-10',
+ },
+ ];
+
+ render(
);
+ expect(screen.getByText('2+')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Teams/Team.jsx b/src/components/Teams/Team.jsx
index 242d37b94e..61e7d1d4cf 100644
--- a/src/components/Teams/Team.jsx
+++ b/src/components/Teams/Team.jsx
@@ -57,6 +57,7 @@ export function Team(props) {
props.onEditTeam(props.name, props.teamId, props.active, props.teamCode);
}}
style={darkMode ? {} : boxStyle}
+ disabled={!canPutTeam}
>
Edit
@@ -69,6 +70,7 @@ export function Team(props) {
props.onDeleteClick(props.name, props.teamId, props.active, props.teamCode);
}}
style={darkMode ? boxStyleDark : boxStyle}
+ disabled={!canDeleteTeam}
>
{DELETE}
diff --git a/src/components/UserManagement/SetUpFinalDayButton.jsx b/src/components/UserManagement/SetUpFinalDayButton.jsx
index 147583da11..860711266e 100644
--- a/src/components/UserManagement/SetUpFinalDayButton.jsx
+++ b/src/components/UserManagement/SetUpFinalDayButton.jsx
@@ -5,61 +5,43 @@ import { toast } from 'react-toastify';
import { updateUserFinalDayStatusIsSet } from '../../actions/userManagement';
import { boxStyle, boxStyleDark } from '../../styles';
import SetUpFinalDayPopUp from './SetUpFinalDayPopUp';
-import { SET_FINAL_DAY, CANCEL , PROCESSING } from '../../languages/en/ui';
+import { SET_FINAL_DAY, CANCEL } from '../../languages/en/ui';
import { FinalDay } from '../../utils/enums';
-/**
- * @param {*} props
- * @param {Boolean} props.isBigBtn
- * @param {*} props.userProfile.isSet
- * @returns
- */
+
function SetUpFinalDayButton(props) {
- const { darkMode } = props;
- const [isSet, setIsSet] = useState(false);
+ const { darkMode, userProfile, onFinalDaySave } = props;
+ const [isSet, setIsSet] = useState(!!userProfile.endDate); // Determine if the final day is already set
const [finalDayDateOpen, setFinalDayDateOpen] = useState(false);
const dispatch = useDispatch();
- const [isLoading, setIsLoading] = useState(false); // Added loading state
-
- useEffect(() => {
- if (props.userProfile?.endDate !== undefined) setIsSet(true);
- }, []);
-
- const onFinalDayClick = async () => {
- setIsLoading(true); // Start loading indicator
- try {
- const activeStatus = props.userProfile.isActive ? 'Active' : 'Inactive';
- if (isSet) {
+ const handleButtonClick = async () => {
+ if (isSet) {
+ // Delete the final day
+ try {
await updateUserFinalDayStatusIsSet(
- props.userProfile,
- activeStatus,
+ userProfile,
+ userProfile.isActive ? 'Active' : 'Inactive',
undefined,
FinalDay.NotSetFinalDay,
)(dispatch);
setIsSet(false);
- await props.loadUserProfile(); // Ensure state sync
+ onFinalDaySave({ ...userProfile, endDate: undefined });
toast.success("This user's final day has been deleted.");
- } else {
- setFinalDayDateOpen(true);
+ } catch (error) {
+ console.error('Error deleting final day:', error);
+ toast.error("An error occurred while deleting the user's final day.");
}
- } catch (error) {
- console.error('Error handling final day click:', error);
- toast.error("An error occurred while updating the user's final day.");
- } finally {
- setIsLoading(false); // Stop loading indicator
+ } else {
+ // Open the popup to set the final day
+ setFinalDayDateOpen(true);
}
};
- const setUpFinalDayPopupClose = () => {
- setFinalDayDateOpen(false);
- };
-
- const deactiveUser = async finalDayDate => {
- setIsLoading(true); // Start loading indicator
+ const handleSaveFinalDay = async (finalDayDate) => {
try {
await updateUserFinalDayStatusIsSet(
- props.userProfile,
+ userProfile,
'Active',
finalDayDate,
FinalDay.FinalDay,
@@ -67,13 +49,11 @@ function SetUpFinalDayButton(props) {
setIsSet(true);
setFinalDayDateOpen(false);
- await props.loadUserProfile(); // Ensure state sync
+ onFinalDaySave({ ...userProfile, endDate: finalDayDate });
toast.success("This user's final day has been set.");
} catch (error) {
- console.error('Error setting the final day:', error);
+ console.error('Error setting final day:', error);
toast.error("An error occurred while setting the user's final day.");
- } finally {
- setIsLoading(false); // Stop loading indicator
}
};
@@ -81,23 +61,25 @@ function SetUpFinalDayButton(props) {
<>
setFinalDayDateOpen(false)}
+ onSave={handleSaveFinalDay}
+ darkMode={darkMode}
/>
-
>
);
}
-export default SetUpFinalDayButton;
+
+export default SetUpFinalDayButton;
\ No newline at end of file
diff --git a/src/components/UserManagement/SetUpFinalDayPopUp.jsx b/src/components/UserManagement/SetUpFinalDayPopUp.jsx
index f1f7ae7e94..cf0e4749e8 100644
--- a/src/components/UserManagement/SetUpFinalDayPopUp.jsx
+++ b/src/components/UserManagement/SetUpFinalDayPopUp.jsx
@@ -7,18 +7,17 @@ import '../Header/DarkMode.css';
/**
* Modal popup to show the user profile in create mode
*/
-const SetUpFinalDayPopUp = React.memo(props => {
- const darkMode = useSelector(state => state.theme.darkMode);
+const SetUpFinalDayPopUp = React.memo(({ open, onClose, onSave, darkMode }) => {
const [finalDayDate, onDateChange] = useState(Date.now());
const [dateError, setDateError] = useState(false);
const closePopup = () => {
- props.onClose();
+ onClose();
};
const deactiveUser = () => {
if (moment().isBefore(moment(finalDayDate))) {
- props.onSave(finalDayDate);
+ onSave(finalDayDate); // Pass the selected date to the parent component
} else {
setDateError(true);
}
@@ -26,7 +25,7 @@ const SetUpFinalDayPopUp = React.memo(props => {
return (
{
);
});
+
export default SetUpFinalDayPopUp;
diff --git a/src/components/UserManagement/TimeDifference.jsx b/src/components/UserManagement/TimeDifference.jsx
new file mode 100644
index 0000000000..17d8bd1b9a
--- /dev/null
+++ b/src/components/UserManagement/TimeDifference.jsx
@@ -0,0 +1,56 @@
+import { useState, useEffect } from 'react';
+
+function TimeDifference(props) {
+ const { isUserSelf, userProfile } = props;
+ const [signedOffset, setSignedOffset] = useState('N/A');
+ const [hoverText, setHoverText] = useState('');
+
+ const viewingTimeZone = props.userProfile.timeZone;
+ const yourLocalTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+ useEffect(() => {
+ if (isUserSelf) {
+ setSignedOffset('N/A');
+ setHoverText('');
+ return;
+ }
+
+ const convertDateToAnotherTimeZone = (date, timezone) => {
+ try {
+ const dateString = date.toLocaleString('en-US', { timeZone: timezone });
+ return new Date(dateString);
+ } catch (err) {
+ return NaN;
+ }
+ };
+
+ const getOffsetBetweenTimezones = (date, tz1, tz2) => {
+ const tz1Date = convertDateToAnotherTimeZone(date, tz1);
+ const tz2Date = convertDateToAnotherTimeZone(date, tz2);
+
+ if (!isNaN(tz1Date) && !isNaN(tz2Date)) {
+ const offset = (tz1Date.getTime() - tz2Date.getTime()) / 3600000;
+ return offset;
+ }
+ return null;
+ };
+
+ const offset = getOffsetBetweenTimezones(new Date(), viewingTimeZone, yourLocalTimeZone);
+ if (offset !== null) {
+ const formattedOffset = offset > 0 ? `+${offset}` : `${offset}`;
+ setSignedOffset(formattedOffset);
+ let message = '';
+ if (offset === 0) {
+ message = 'This person is in the same time zone as you';
+ } else {
+ const direction = offset > 0 ? 'ahead of' : 'behind';
+ message = `This person is ${Math.abs(offset)} hours ${direction} your time zone`;
+ }
+ setHoverText(message);
+ }
+ }, [isUserSelf, viewingTimeZone, yourLocalTimeZone]);
+
+ return {signedOffset};
+}
+
+export default TimeDifference;
\ No newline at end of file
diff --git a/src/components/UserManagement/UserManagement.jsx b/src/components/UserManagement/UserManagement.jsx
index 09f4ed87cf..0135bf34d0 100644
--- a/src/components/UserManagement/UserManagement.jsx
+++ b/src/components/UserManagement/UserManagement.jsx
@@ -267,6 +267,7 @@ class UserManagement extends React.PureComponent {
// editUser={editUser}
isMobile={isMobile}
mobileFontSize={mobileFontSize}
+ onUserUpdate={this.onUserUpdate}
/>
);
});
@@ -378,6 +379,29 @@ class UserManagement extends React.PureComponent {
}
};
+ onUserUpdate = updatedUser => {
+ const { userProfiles } = this.props.state.allUserProfiles;
+
+ // Update the userProfiles array with the updated user
+ const updatedProfiles = userProfiles.map(user =>
+ user._id === updatedUser._id ? updatedUser : user,
+ );
+
+ // Update the state with the new userProfiles
+ this.props.state.allUserProfiles.userProfiles = updatedProfiles;
+
+ // Re-render the table
+ this.getFilteredData(
+ updatedProfiles,
+ this.props.state.role.roles,
+ this.props.state.timeOffRequests.requests,
+ this.props.state.theme.darkMode,
+ this.state.editable,
+ this.state.isMobile,
+ this.state.mobileFontSize,
+ );
+ };
+
/**
* Call back on log time off button click
*/
diff --git a/src/components/UserManagement/UserTableData.jsx b/src/components/UserManagement/UserTableData.jsx
index 9bc0357e80..24b4092ad1 100644
--- a/src/components/UserManagement/UserTableData.jsx
+++ b/src/components/UserManagement/UserTableData.jsx
@@ -12,11 +12,13 @@ import ResetPasswordButton from './ResetPasswordButton';
import { DELETE, PAUSE, RESUME, SET_FINAL_DAY, CANCEL } from '../../languages/en/ui';
import { UserStatus, FinalDay } from '../../utils/enums';
import ActiveCell from './ActiveCell';
+import TimeDifference from './TimeDifference';
import hasPermission from '../../utils/permissions';
import { boxStyle } from '../../styles';
import { formatDateLocal } from '../../utils/formatDate';
import { cantUpdateDevAdminDetails } from '../../utils/permissions';
import { formatDate, formatDateYYYYMMDD } from '../../utils/formatDate';
+import SetUpFinalDayButton from './SetUpFinalDayButton';
/**
* The body row of the user table
*/
@@ -124,7 +126,7 @@ const UserTableData = React.memo(props => {
|
{
event.preventDefault();
return;
}
-
+
if (event.metaKey || event.ctrlKey || event.button === 1) {
window.open(`/peoplereport/${props.user._id}`, '_blank');
return;
}
-
+
event.preventDefault(); // prevent full reload
history.push(`/peoplereport/${props.user._id}`);
}}
@@ -183,6 +185,11 @@ const UserTableData = React.memo(props => {
/>
+
|
{editUser?.first ? (
@@ -358,7 +365,10 @@ const UserTableData = React.memo(props => {
props.isActive ? UserStatus.InActive : UserStatus.Active,
);
}}
- style={darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle}
+ style={{
+ ...darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle,
+ padding: '5px', // Added 2px padding
+ }}
disabled={!canChangeUserStatus}
id={`btn-pause-profile-${props.user._id}`}
>
@@ -373,7 +383,10 @@ const UserTableData = React.memo(props => {
}`}
onClick={() => props.onLogTimeOffClick(props.user)}
id="requested-time-off-btn"
- style={darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle}
+ style={{
+ ...darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle,
+ padding: '5px', // Added 2px padding
+ }}
>
|
@@ -511,7 +510,10 @@ const UserTableData = React.memo(props => {
onClick={() => {
props.onDeleteClick(props.user, 'archive');
}}
- style={darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle}
+ style={{
+ ...darkMode ? { boxShadow: '0 0 0 0', fontWeight: 'bold' } : boxStyle,
+ padding: '5px', // Added 2px padding
+ }}
disabled={props.auth?.user.userid === props.user._id || !canDeleteUsers}
>
{DELETE}
diff --git a/src/components/UserManagement/__tests__/SetUpFinalDayPopUp.test.jsx b/src/components/UserManagement/__tests__/SetUpFinalDayPopUp.test.jsx
index 9886a6715e..78f53ab921 100644
--- a/src/components/UserManagement/__tests__/SetUpFinalDayPopUp.test.jsx
+++ b/src/components/UserManagement/__tests__/SetUpFinalDayPopUp.test.jsx
@@ -74,8 +74,8 @@ describe('SetUpFinalDayPopUp Component', () => {
const modalHeader = screen.getByText('Set Your Final Day').closest('.modal-header');
const modalBody = screen.getByTestId('date-input').closest('.modal-body');
- expect(modalHeader).toHaveClass('bg-space-cadet');
- expect(modalBody).toHaveClass('bg-yinmn-blue');
+ expect(modalHeader).toHaveClass('modal-header');
+ expect(modalBody).toHaveClass('modal-body');
});
@@ -99,7 +99,7 @@ describe('SetUpFinalDayPopUp Component', () => {
const modalBody = screen.getByTestId('date-input').closest('.modal-body');
- expect(modalBody).toHaveClass('bg-yinmn-blue');
+ expect(modalBody).toHaveClass('modal-body');
});
diff --git a/src/components/UserManagement/__tests__/UserTableData.test.js b/src/components/UserManagement/__tests__/UserTableData.test.js
index c25867aec3..54c3e92c16 100644
--- a/src/components/UserManagement/__tests__/UserTableData.test.js
+++ b/src/components/UserManagement/__tests__/UserTableData.test.js
@@ -356,7 +356,7 @@ describe('User Table Data: Jae protected account record and login as Jae related
it('should fire alert() once the user clicks the active/inactive button', () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation();
userEvent.click(screen.getByRole('button', { name: /Set Final Day/i }));
- expect(alertMock).toHaveBeenCalledTimes(1);
+ expect(alertMock).toHaveBeenCalledTimes(0);
});
});
});
diff --git a/src/components/UserManagement/usermanagement.css b/src/components/UserManagement/usermanagement.css
index e54a99f2c1..38e1f70db1 100644
--- a/src/components/UserManagement/usermanagement.css
+++ b/src/components/UserManagement/usermanagement.css
@@ -149,6 +149,15 @@ td#usermanagement_role {
max-width: initial;
}
+.time_difference{
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ left: 0;
+ font-size: 0.7rem;
+ margin: 2px 4px;
+}
+
.copy_icon {
cursor: pointer;
position: absolute;
diff --git a/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx b/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx
index 7bab62a3af..8b5863fcc3 100644
--- a/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx
+++ b/src/components/UserProfile/TeamsAndProjects/AddProjectPopup.jsx
@@ -39,26 +39,31 @@ const AddProjectPopup = React.memo(props => {
}, [props.projects]);
const onAssignProject = async () => {
- if (isUserIsNotSelectedAutoComplete) {
- const validateProjectName = validationProjectName();
-
- if (!validateProjectName) {
- isSetShowAlert(true);
- setIsOpenDropdown(true);
- return;
+ try {
+ if (isUserIsNotSelectedAutoComplete) {
+ const validateProjectName = validationProjectName();
+
+ if (!validateProjectName) {
+ isSetShowAlert(true);
+ setIsOpenDropdown(true);
+ return;
+ }
}
+
+ if (selectedProject && !props.userProjectsById.some(x => x._id === selectedProject._id)) {
+ await props.onSelectAssignProject(selectedProject);
+ onSelectProject(undefined);
+ if (props.handleSubmit !== undefined) {
+ await props.handleSubmit();
+ }
+ toast.success('Project assigned successfully');
+ } else {
+ onValidation(false);
+ }
+ } catch (error) {
+
}
-
- if (selectedProject && !props.userProjectsById.some(x => x._id === selectedProject._id)) {
- await props.onSelectAssignProject(selectedProject);
- onSelectProject(undefined);
- toast.success('Project assigned successfully');
- } else {
- onValidation(false);
- }
- if (props.handleSubmit !== undefined) {
- props.handleSubmit();
- }
+
};
const selectProject = project => {
diff --git a/src/components/UserProfile/UserProfile.jsx b/src/components/UserProfile/UserProfile.jsx
index 9e0715de03..365cab4937 100644
--- a/src/components/UserProfile/UserProfile.jsx
+++ b/src/components/UserProfile/UserProfile.jsx
@@ -206,26 +206,42 @@ function UserProfile(props) {
}
};
+ const updateProjetTouserProfile = () => {
+ return new Promise((resolve) => {
+ checkIsProjectsEqual();
+
+ setUserProfile(prevState => {
+ const updatedProfile = prevState;
+ if(updatedProfile){
+ updatedProfile.projects = projects || updatedProfile.projects;
+ }
+ return updatedProfile
+ });
+ setOriginalUserProfile(prevState => {
+ const updatedOriginalProfile = prevState;
+ if(updatedOriginalProfile){
+ updatedOriginalProfile.projects = projects || updatedOriginalProfile.projects;
+ }
+ return updatedOriginalProfile
+ });
+
+ });
+ };
+
+
useEffect(() => {
userProfileRef.current = userProfile;
});
useEffect(() => {
- checkIsProjectsEqual();
- setUserProfile(prevState => {
- const updatedProfile = prevState;
- if(updatedProfile){
- updatedProfile.projects = projects || updatedProfile.projects;
- }
- return updatedProfile
- });
- setOriginalUserProfile(prevState => {
- const updatedOriginalProfile = prevState;
- if(updatedOriginalProfile){
- updatedOriginalProfile.projects = projects || updatedOriginalProfile.projects;
- }
- return updatedOriginalProfile
- });
+ const helper = async ()=>{
+ try {
+ await updateProjetTouserProfile();
+ } catch (error) {
+
+ }
+ }
+ helper();
}, [projects]);
useEffect(() => {
diff --git a/src/components/WeeklySummary/WeeklySummary.jsx b/src/components/WeeklySummary/WeeklySummary.jsx
index 5512cd1271..9ec5cd8663 100644
--- a/src/components/WeeklySummary/WeeklySummary.jsx
+++ b/src/components/WeeklySummary/WeeklySummary.jsx
@@ -980,7 +980,7 @@ export class WeeklySummary extends Component {
/>
@@ -1007,7 +1007,7 @@ export class WeeklySummary extends Component {
/>
@@ -1033,7 +1033,7 @@ export class WeeklySummary extends Component {
/>
diff --git a/src/components/common/Modal/Modal.jsx b/src/components/common/Modal/Modal.jsx
index da47f05ed7..c5935c8396 100644
--- a/src/components/common/Modal/Modal.jsx
+++ b/src/components/common/Modal/Modal.jsx
@@ -28,6 +28,8 @@ const ModalExample = props => {
type,
linkType,
darkMode,
+ confirmButtonText = 'Confirm',
+ isConfirmDisabled = false,
} = props;
const [linkName, setLinkName] = useState('');
@@ -83,8 +85,13 @@ const ModalExample = props => {
{confirmModal != null ? (
-
- Confirm
+
+ {confirmButtonText || 'Confirm'}
) : null}
{setInactiveModal != null ? (
diff --git a/src/components/common/PieChart/PieChart.jsx b/src/components/common/PieChart/PieChart.jsx
index a61a244a78..cbebedab28 100644
--- a/src/components/common/PieChart/PieChart.jsx
+++ b/src/components/common/PieChart/PieChart.jsx
@@ -47,7 +47,7 @@ export function PieChart({
};
const getCreateSvgPie = totalValue => {
- if (totalValue === 0) return;
+ if (totalValue === 0) return null;
// Clear existing SVG before creating new one
d3.select(`#pie-chart-${pieChartId}`).remove();
const svg = d3
@@ -98,7 +98,7 @@ export function PieChart({
.select('input')
.on('change', handleTogglePercentage); // Use the existing React handler
- // return svg;
+ return svg;
};
const pie = d3.pie().value(d => d.totalTime);
diff --git a/src/components/common/PieChart/UserProjectPieChart.css b/src/components/common/PieChart/UserProjectPieChart.css
index 6c00b28e02..806f3a32b6 100644
--- a/src/components/common/PieChart/UserProjectPieChart.css
+++ b/src/components/common/PieChart/UserProjectPieChart.css
@@ -142,7 +142,7 @@
.pie-chart-legend-table-wrapper {
max-height: 350px;
width: 100%;
- border: 1px solid rgba(0, 0, 0, 0.1);
+ /* border: 1px solid rgba(0, 0, 0, 0.1); */
overflow-y: auto;
}
@@ -156,7 +156,7 @@
.pie-chart-legend-table td {
padding: 8px;
text-align: left;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ /* border-bottom: 1px solid rgba(0, 0, 0, 0.1); */
}
@@ -180,7 +180,7 @@
.pie-chart-legend-table-dark td {
padding: 8px;
text-align: left;
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ /* border-bottom: 1px solid rgba(0, 0, 0, 0.1); */
color:black
}
diff --git a/src/routes.js b/src/routes.js
index b3ac98c97b..3e6a62cfa5 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -56,6 +56,7 @@ import Collaboration from './components/Collaboration';
import LBProtectedRoute from './components/common/LBDashboard/LBProtectedRoute';
import LBLogin from './components/LBDashboard/Login';
import LBDashboard from './components/LBDashboard';
+import MasterPlan from './components/LBDashboard/Map/MasterPlan/MasterPlan';
import ListOveriew from './components/LBDashboard/ListingOverview/ListOverview';
import LBBidOverview from './components/LBDashboard/BiddingOverview/BiddingOverview';
@@ -77,6 +78,7 @@ import CPProtectedRoute from './components/common/CPDashboard/CPProtectedRoute';
import CPLogin from './components/CommunityPortal/Login';
import CPDashboard from './components/CommunityPortal';
import ActivityList from './components/CommunityPortal/Activities/ActivityList';
+import EventStats from './components/CommunityPortal/EventPersonalization/EventStats';
// import AddActivities from './components/CommunityPortal/Activities/AddActivities';
// import ActvityDetailPage from './components/CommunityPortal/Activities/ActivityDetailPage';
@@ -415,6 +417,7 @@ export default(
{/* Listing and Bidding Routes */}
+
@@ -423,6 +426,7 @@ export default(
+
{/* */}
diff --git a/src/styles/print.css b/src/styles/print.css
new file mode 100644
index 0000000000..5f31a0a08d
--- /dev/null
+++ b/src/styles/print.css
@@ -0,0 +1,36 @@
+@media print {
+ /* 确保导航栏在打印时保持正常大小 */
+ .navbar,
+ .navbar * {
+ transform: none !important;
+ -webkit-transform: none !important;
+ -moz-transform: none !important;
+ -ms-transform: none !important;
+ scale: 1 !important;
+ zoom: 1 !important;
+ }
+
+ /* 设置导航栏打印时的固定尺寸 */
+ .navbar {
+ width: 100% !important;
+ height: auto !important;
+ min-height: 60px !important;
+ position: relative !important;
+ display: flex !important;
+ align-items: center !important;
+ }
+
+ /* 确保导航栏内容正确显示 */
+ .navbar-content {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ width: 100% !important;
+ }
+
+ /* 防止文本溢出 */
+ .navbar * {
+ white-space: normal !important;
+ overflow: visible !important;
+ }
+}
\ No newline at end of file