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
170 changes: 62 additions & 108 deletions src/components/Collaboration/Collaboration.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// src/pages/Collaboration/Collaboration.jsx
import { useEffect, useState, useMemo, useRef } from 'react';
import { useHistory } from 'react-router-dom';

Check warning on line 3 in src/components/Collaboration/Collaboration.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'useHistory'.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ3IT-Rc1jXx2_A3naIJ&open=AZ3IT-Rc1jXx2_A3naIJ&pullRequest=5077
import styles from './Collaboration.module.css';
import { toast } from 'react-toastify';
import { ApiEndpoint } from '~/utils/URL';
Expand All @@ -19,15 +19,12 @@
const [totalPages, setTotalPages] = useState(1);
const [categories, setCategories] = useState([]);
const [showCategoryDropdown, setShowCategoryDropdown] = useState(false);
const [showPositionDropdown, setShowPositionDropdown] = useState(false);
const [summaries, setSummaries] = useState(null);
// const [positions, setPositions] = useState([]);
const [selectedPosition, setSelectedPosition] = useState('');
const [selectedCategory, setSelectedCategory] = useState('');
const categoryRef = useRef(null);
const positionRef = useRef(null);

const history = useHistory();
const dropdownRef = useRef(null);
const [selectedJob, setSelectedJob] = useState(null);

const darkMode = useSelector(state => state.theme.darkMode);

const slugify = s =>
Expand All @@ -43,7 +40,7 @@
const url =
`${ApiEndpoint}/jobs` +
`?search=${encodeURIComponent(searchTerm || '')}` +
`&category=${encodeURIComponent(selectedCategory || '')}`;
`&category=${encodeURIComponent(JSON.stringify(categoriesSelected))}`;

const res = await fetch(url);
const data = await res.json();
Expand Down Expand Up @@ -71,8 +68,9 @@
useEffect(() => {
setCurrentPage(1);
fetchJobs();
}, [searchTerm, selectedCategory]);
}, [searchTerm, categoriesSelected]);

/* ================= FILTERED JOBS ================= */
const filteredJobs = useMemo(() => {
if (!selectedPosition) return allJobs;

Expand All @@ -81,22 +79,7 @@
);
}, [allJobs, selectedPosition]);

const positions = useMemo(() => {
const uniquePositions = [
...new Set(
allJobs
.filter(
job =>
!selectedCategory || job.category?.toLowerCase() === selectedCategory.toLowerCase(),
)
.map(job => job.position || job.title)
.filter(Boolean),
),
];

return uniquePositions.sort((a, b) => a.localeCompare(b));
}, [allJobs, selectedCategory]);

/* ================= PAGINATION ================= */
useEffect(() => {
const start = (currentPage - 1) * ADS_PER_PAGE;
setJobAds(filteredJobs.slice(start, start + ADS_PER_PAGE));
Expand All @@ -105,26 +88,24 @@
setTotalPages(Math.max(calculatedPages, 1));
}, [filteredJobs, currentPage]);

/* ================= ESC CLOSE MODAL ================= */
useEffect(() => {
// no-op placeholder; keep hook list stable if needed in future
}, []);
if (!selectedJob) return;
const esc = e => e.key === 'Escape' && setSelectedJob(null);
window.addEventListener('keydown', esc);

Check warning on line 95 in src/components/Collaboration/Collaboration.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ3IT-Rc1jXx2_A3naIK&open=AZ3IT-Rc1jXx2_A3naIK&pullRequest=5077
return () => window.removeEventListener('keydown', esc);

Check warning on line 96 in src/components/Collaboration/Collaboration.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ3IT-Rc1jXx2_A3naIL&open=AZ3IT-Rc1jXx2_A3naIL&pullRequest=5077
}, [selectedJob]);

/* ================= CLICK OUTSIDE DROPDOWN ================= */
useEffect(() => {
const handleClickOutside = event => {
if (categoryRef.current && !categoryRef.current.contains(event.target)) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setShowCategoryDropdown(false);
}

if (positionRef.current && !positionRef.current.contains(event.target)) {
setShowPositionDropdown(false);
}
};

document.addEventListener('mousedown', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

/* ================= HANDLERS ================= */
Expand All @@ -134,7 +115,7 @@
};

const handleClearAllFilters = () => {
setSelectedCategory('');
setCategoriesSelected([]);
setSelectedPosition('');
setSearchTerm('');
setQuery('');
Expand All @@ -144,7 +125,9 @@
const handleShowSummaries = async () => {
try {
const res = await fetch(
`${ApiEndpoint}/jobs/summaries?search=${searchTerm}&category=${selectedCategory}`,
`${ApiEndpoint}/jobs/summaries?search=${searchTerm}&category=${encodeURIComponent(
JSON.stringify(categoriesSelected),
)}`,
);
setSummaries(await res.json());
} catch {
Expand Down Expand Up @@ -183,7 +166,7 @@

{summaries.jobs?.length ? (
summaries.jobs.map(job => (
<div key={job._id} className="job-summary-item">
<div key={job._id}>
<h4>
<a href={job.jobDetailsLink}>{job.title}</a>
</h4>
Expand Down Expand Up @@ -224,96 +207,47 @@
<button className="btn btn-secondary">Go</button>
</form>

<div className={styles.dropdownWrapper} ref={categoryRef}>
<button
type="button"
onClick={() => {
setShowCategoryDropdown(prev => !prev);
setShowPositionDropdown(false);
}}
aria-expanded={showCategoryDropdown}
>
{selectedCategory || 'Select Categories'} ▼
<div ref={dropdownRef} style={{ position: 'relative' }}>
<button type="button" onClick={() => setShowCategoryDropdown(p => !p)}>
Select Categories ▼
</button>

{showCategoryDropdown && (
<div className={styles.jobSelect}>
{categories.map(cat => (
<button
key={cat}
type="button"
className={styles.dropdownItem}
onClick={() => {
setSelectedCategory(cat);
setSelectedPosition('');
setShowCategoryDropdown(false);
setCurrentPage(1);
}}
>
<label key={cat} className={styles.dropdownItem}>
<input
type="checkbox"
checked={categoriesSelected.includes(cat)}
onChange={() =>
setCategoriesSelected(prev =>
prev.includes(cat) ? prev.filter(c => c !== cat) : [...prev, cat],

Check failure on line 224 in src/components/Collaboration/Collaboration.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ2E3h-NUwL1W3ZIhVz-&open=AZ2E3h-NUwL1W3ZIhVz-&pullRequest=5077
)
}
/>
{cat}
</button>
</label>
))}
</div>
)}
</div>

<div className={styles.dropdownWrapper} ref={positionRef}>
<button
type="button"
disabled={!selectedCategory}
onClick={() => {
if (!selectedCategory) return;
setShowPositionDropdown(prev => !prev);
setShowCategoryDropdown(false);
}}
aria-expanded={showPositionDropdown}
>
{selectedPosition || 'Select Positions'} ▼
</button>

{showPositionDropdown && selectedCategory && (
<div className={styles.jobSelect}>
{positions.length > 0 ? (
positions.map(pos => (
<button
key={pos}
type="button"
className={styles.dropdownItem}
onClick={() => {
setSelectedPosition(pos);
setShowPositionDropdown(false);
setCurrentPage(1);
}}
>
{pos}
</button>
))
) : (
<div className={styles.dropdownItem}>No positions found</div>
)}
</div>
)}
</div>
</nav>

{/* HEADINGS */}
<div className={styles.headings}>
<h1 className={styles.jobHead}>LIKE TO WORK WITH US? APPLY NOW!</h1>
<a className="btn" href="https://www.onecommunityglobal.org/collaboration/">
← Return to One Community Collaboration Page
</a>
</div>

{/* QUERY TEXT */}
<div className="job-queries">
<div>
<p>
{searchTerm
? `Listing results for '${searchTerm}'`
: selectedPosition
? `Listing results for '${selectedPosition}' in '${selectedCategory}'`
: selectedCategory
? `Listing results for '${selectedCategory}'`
? `Listing results for '${selectedPosition}'`
: categoriesSelected.length
? 'Listing results for selected categories'
: 'Listing all job ads.'}

Check warning on line 250 in src/components/Collaboration/Collaboration.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ2FB7xD4KakTOi7WuGv&open=AZ2FB7xD4KakTOi7WuGv&pullRequest=5077
</p>
<button className="btn btn-secondary" onClick={handleShowSummaries}>
Show Summaries
Expand All @@ -321,10 +255,13 @@
</div>

{/* FILTER CHIPS */}
{(selectedCategory || selectedPosition) && (
{(categoriesSelected.length > 0 || selectedPosition) && (
<div className={styles.jobQueries}>
{selectedCategory && <span className={styles.chip}>{selectedCategory}</span>}
{selectedPosition && <span className={styles.chip}>{selectedPosition}</span>}
{categoriesSelected.map(cat => (
<span key={cat} className={styles.chip}>
{cat}
</span>
))}
<button className={styles.clearAllButton} onClick={handleClearAllFilters}>
Clear All
</button>
Expand Down Expand Up @@ -367,6 +304,23 @@
))}
</div>
</div>

{/* MODAL */}
{selectedJob && (
<div className={styles.modalOverlay}>
<div className={styles.modal}>
<button className={styles.closeButton} onClick={() => setSelectedJob(null)}>
×
</button>

<h2>{selectedJob.title}</h2>
<p>
<strong>Category:</strong> {selectedJob.category}
</p>
<p>{selectedJob.description}</p>
</div>
</div>
)}
</div>
);
}
Expand Down
30 changes: 14 additions & 16 deletions src/components/Collaboration/Collaboration.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
background-color: #84cc16;
}

.dropdownItem input {
width: 16px;
height: 16px;
flex-shrink: 0;
}

.dark .navbar button,
.dark .navbar input {
color: #020617;
Expand Down Expand Up @@ -152,17 +158,15 @@
position: absolute;
top: calc(100% + 6px);
left: 0;
width: max-content;
max-width: min(100%, 280px);
min-width: 180px;
max-height: 300px;
width: 240px;
max-height: 280px;
overflow-y: auto;
background-color: #fff;
padding: 8px 0;
border-radius: 8px;
border: 1px solid #ccc;
z-index: 2000;
box-shadow: 0 8px 20px rgb(0 0 0 / 20%);
z-index: 9999;
box-shadow: 0 8px 20px rgb(0 0 0 / 15%);
animation: dropdownFade 0.15s ease-in-out;
}

.dropdownWrapper {
Expand All @@ -172,19 +176,13 @@
}

.dropdownItem {
width: 100%;
text-align: left;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
cursor: pointer;
font-size: 14px;
background: transparent;
border: none;
outline: none;
color: #333;
transition: background 0.2s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.dropdownItem:hover {
Expand Down
Loading
Loading