Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/components/Projects/Project/Project.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ const Project = props => {
placement="top"
overlay={<Tooltip>Active members</Tooltip>}
>
<span className={styles['project-member-badge']}>
{/*
Applied the dedicated modular CSS class 'badgeTextBlack' to permanently
lock the active member count text color to black, ensuring high contrast
and satisfying requirement 1.3 across both light and dark modes.
*/}
<span className={`${styles['project-member-badge']} ${styles.badgeTextBlack}`}>
{props.activeMemberCounts}
</span>
</OverlayTrigger>
Expand Down
122 changes: 76 additions & 46 deletions src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@
import EditableInfoModal from '~/components/UserProfile/EditableModal/EditableInfoModal';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowUp, faArrowDown, faSortDown } from '@fortawesome/free-solid-svg-icons';
import { Dropdown,DropdownButton } from 'react-bootstrap';

// import DropdownButton from 'react-bootstrap/DropdownButton';
import { Dropdown } from 'react-bootstrap';
import { boxStyle, boxStyleDark } from '~/styles';
import { Button } from 'reactstrap';


const ProjectTableHeader = props => {
const { role, darkMode } = props;
const canDeleteProject = hasPermission('deleteProject')
const canDeleteProject = hasPermission('deleteProject');

const categoryList = ['Unspecified', 'Food', 'Energy', 'Housing', 'Education', 'Society', 'Economics', 'Stewardship', 'Other'];
const statusList = ['Active', 'Inactive'];
// 如果 Inventory 也有自己的列表数据,可以在这里定义(例如 inventoryList = [...])
const inventoryList = ['All', 'In Stock', 'Out of Stock']; // 可根据实际需求调整

const getSortIcon = column => {
if (props.sorted.column !== column || props.sorted.direction === "DEFAULT") return faSortDown;
Expand All @@ -36,83 +35,113 @@
return faSortDown;
};

// Standardized sort button rendering function with fixed 30x30px dimensions and center alignment
const renderSortButton = column => (
<Button
size="sm"
className="ml-3 mb-1"
className="btn-sm ml-3 d-flex align-items-center justify-content-center"
style={{ width: '30px', height: '30px', boxShadow: 'none' }}
id={`${column.toLowerCase()}_sort`}
onClick={() => props.handleSort(column)}
>
<FontAwesomeIcon icon={getSortIcon(column)} pointerEvents="none" />
</Button>
);

// Standardized dropdown filter button helper to ensure uniform size (30x30px), exact icon matching, and clean layout
const renderDropdownFilterButton = (selectedValue, onChange, list, placeholder, isDark) => (
<Dropdown className="ml-3 d-inline-block">
{/* Used native button toggle via Dropdown.Toggle to bypass component ref conflicts and eliminate unwanted default caret/shadow artifacts */}
<Dropdown.Toggle
as="button"
className="btn btn-secondary btn-sm px-0 py-0 d-flex align-items-center justify-content-center"
style={{ ...(isDark ? boxStyleDark : boxStyle), width: '30px', height: '30px', border: 'none', boxShadow: 'none' }}
>
<FontAwesomeIcon icon={faSortDown} pointerEvents="none" />
</Dropdown.Toggle>

<Dropdown.Menu align="right">
<Dropdown.Item default eventKey="" disabled={!selectedValue} className={isDark ? 'bg-darkmode-liblack text-light border-0' : ''}>
{selectedValue ? 'Clear filter' : placeholder}
</Dropdown.Item>
<Dropdown.Divider />
{list.map((item, index) =>
<Dropdown.Item key={index} eventKey={item} active={selectedValue === item} className={isDark ? 'bg-darkmode-liblack text-light border-0' : ''}>

Check warning on line 69 in src/components/Projects/ProjectTableHeader/ProjectTableHeader.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AaBtUTo0NpHOiCCp9c8F&open=AaBtUTo0NpHOiCCp9c8F&pullRequest=5504
{item}
</Dropdown.Item>
)}
</Dropdown.Menu>
</Dropdown>
);

return (
<tr className={darkMode ? 'bg-space-cadet text-light' : ''}>
<th scope="col" id="projects__order" style={{ textAlign: 'center' }}>
#
</th>
{/* <th scope="col">{PROJECT_NAME}</th> */}

{/* 1. Project Name column header */}
<th scope="col" className='align-middle'>
<span className='d-flex justify-content-between align-middle mt-1'>
{PROJECT_NAME}
<div>
{renderSortButton('PROJECTS')}
</div>
<span className="d-flex justify-content-between align-items-center">
<span>{PROJECT_NAME}</span>
<div>{renderSortButton('PROJECTS')}</div>
</span>
</th>

{/* 2. Category column header */}
<th scope="col" id="projects__category" className='align-middle'>
{/* This span holds the header-name and a dropDown component */}
<span className='d-flex justify-content-between align-middle mt-1'>
{PROJECT_CATEGORY}
<DropdownButton id="" title="" size='sm'style={darkMode ? boxStyleDark : boxStyle} variant='info' value={props.selectedValue} onSelect={props.onChange} menuAlign="right">
<Dropdown.Item default eventKey="" disabled={!props.selectedValue} className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}>{props.selectedValue ? 'Clear filter' : 'Choose category'}</Dropdown.Item>
<Dropdown.Divider />
{categoryList.map((category, index) =>
<Dropdown.Item key={index} eventKey={category} active={props.selectedValue === category} className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}>{category}</Dropdown.Item>
)}
</DropdownButton>
</span>
<span className="d-flex justify-content-between align-items-center">
<span>{PROJECT_CATEGORY}</span>
{/* Utilized the standardized render function for consistent category filtering button layout */}
{renderDropdownFilterButton(props.selectedValue, props.onChange, categoryList, 'Choose category', darkMode)}
</span>
</th>

{/* 3. Active status column header */}
<th scope="col" id="projects__active" className='align-middle'>
<span className='d-flex justify-content-between align-middle mt-1'>
{ACTIVE}
<DropdownButton className='ml-2 align-middle' id="" title="" size='sm'style={darkMode ? boxStyleDark : boxStyle} variant='info' value={props.showStatus} onSelect={props.selectStatus} menuAlign="right" >
<Dropdown.Item default value="" disabled={!props.showStatus} className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}>{props.showStatus ? 'Clear filter' : 'Choose Status'}</Dropdown.Item>
{statusList.map((status, index) =>
<Dropdown.Item key={index} eventKey={status} active={props.showStatus === status} className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}>{status}</Dropdown.Item>
)}
</DropdownButton>
</span>
<span className="d-flex justify-content-between align-items-center">
<span>{ACTIVE}</span>
{/* Utilized the standardized render function for consistent active status filtering button layout */}
{renderDropdownFilterButton(props.showStatus, props.selectStatus, statusList, 'Choose Status', darkMode)}
</span>
</th>

{/* 4. Inventory column header */}
<th scope="col" id="projects__inv" className='align-middle'>
<span className='d-flex justify-content-between'>
{INVENTORY}
<div>
{renderSortButton('INVENTORY')}
</div>
<span className='d-flex justify-content-between align-items-center'>
<span>{INVENTORY}</span>
{/* Converted Inventory to use the standardized dropdown filter button layout for visual consistency */}
{renderDropdownFilterButton(props.inventoryValue, props.onInventoryChange, inventoryList, 'Choose Inventory', darkMode)}
</span>
</th>

{/* 5. Members column header */}
<th scope="col" id="projects__members" className='align-middle'>
<span className='d-flex'>
{MEMBERS}
{renderSortButton('MEMBERS')}
<span className='d-flex justify-content-between align-items-center'>
<span>{MEMBERS}</span>
<div>
{renderSortButton('MEMBERS')}
</div>
</span>
</th>

{/* 6. WBS column header */}
<th scope="col" id="projects__wbs" className='align-middle'>
<div className="d-flex align-items-center">
<span className="mr-2">{WBS}</span>
<div className="d-flex align-items-center justify-content-between">
<span>{WBS}</span>
<EditableInfoModal
areaName="ProjectTableHeaderWBS"
areaTitle="WBS"
fontSize={24}
isPermissionPage={true}
role={role}
className="p-2" // Add Bootstrap padding class to the EditableInfoModal
className="p-1 mb-1"
darkMode={darkMode}
/>
</div>
</th>

{/* 7. Archive column header */}
{canDeleteProject ? (
<th scope="col" id="projects__delete" className='align-middle'>
{ARCHIVE}
Expand All @@ -127,8 +156,10 @@
darkMode: PropTypes.bool,
selectedValue: PropTypes.string,
showStatus: PropTypes.string,
inventoryValue: PropTypes.string,
onChange: PropTypes.func.isRequired,
selectStatus: PropTypes.func.isRequired,
onInventoryChange: PropTypes.func.isRequired,
handleSort: PropTypes.func.isRequired,
sorted: PropTypes.shape({
column: PropTypes.string.isRequired,
Expand All @@ -137,8 +168,7 @@
};

const mapStateToProps = state => ({
role: state.userProfile.role, // Map 'role' from Redux state to 'role' prop
role: state.userProfile.role,
});

export default connect(mapStateToProps)(ProjectTableHeader)

export default connect(mapStateToProps)(ProjectTableHeader);
19 changes: 14 additions & 5 deletions src/components/Projects/Projects.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,23 @@ const Projects = function(props) {
}
}

if (column === "MEMBERS") {
if (column === "MEMBERS") {
// Helper function to safely retrieve active member count, supporting various ID types (string, number, or object)
const getCount = (projectId) => {
if (!projectId) return 0;
return activeMemberCounts[projectId] ||
activeMemberCounts[String(projectId)] ||
activeMemberCounts[projectId.toString()] || 0;
};

const countA = getCount(a._id);
const countB = getCount(b._id);

if (direction === "ASC") {
const countA = activeMemberCounts[a._id] || 0;
const countB = activeMemberCounts[b._id] || 0;
// Sort in ascending order based on active member counts
return countA - countB;
} else if (direction === "DESC") {
const countA = activeMemberCounts[a._id] || 0;
const countB = activeMemberCounts[b._id] || 0;
// Sort in descending order based on active member counts
return countB - countA;
} else {
return 0; // Default: keep same order as PROJECT sorting
Expand Down
50 changes: 44 additions & 6 deletions src/components/Projects/projects.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,15 @@
.project-member-badge {
position: absolute;
top: -10px;
right: -32px;
min-width: 22px;
height: 22px;
right: -48px;
/* Replaced fixed dimensions with padding so the badge expands gracefully for multi-digit numbers */
padding: 2px 5px;
/* min-width: 22px; */
min-height: 22px;
background: lawngreen;
color: #111;
border-radius: 50%;
color: #111 !important;
border-radius: 50px; /* Adapts from a circle to a clean pill shape as text length increases */

display: flex;
align-items: center;
justify-content: center;
Expand All @@ -165,6 +168,15 @@
z-index: 2;
}

/* :global(.bg-oxford-blue) span.project-member-badge,
span.project-member-badge {
color: #111 !important;
} */
.badgeTextBlack {
color: #111 !important;
}


.project-member-btn .fa-users {
font-size: 1.5rem;
line-height: 1;
Expand Down Expand Up @@ -221,6 +233,31 @@ tr:hover {
background-color: #0056b3;
}


/* Standardize dropdown toggle button dimensions and alignment */
:global(.dropdown-toggle) {
width: 30px !important;
height: 30px !important;
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}

/* Completely remove default dropdown arrows, pseudo-elements, and borders */
:global(.dropdown-toggle)::after {
display: none !important;
content: none !important;
border: none !important;
}

@media (width <= 375px) {
#new_project .form-control {
flex: 1 1 auto;
width: 50%;
}
}

@media (width <= 375px) {
#new_project .form-control {
flex: 1 1 auto;
Expand All @@ -229,4 +266,5 @@ tr:hover {
}
.searchLabelDark {
background-color: #1f3b63 !important;
}
}

Loading
Loading