Skip to content

Commit ecfadc2

Browse files
Merge pull request #4816 from OneCommunityGlobal/Neeraj_Add_Role_Detail_Modal
Neeraj Add Role Detail Preview Modal And Fix Pagination Behavior
2 parents ac8a668 + 418b450 commit ecfadc2

2 files changed

Lines changed: 269 additions & 381 deletions

File tree

src/components/Collaboration/Collaboration.jsx

Lines changed: 117 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
77
import OneCommunityImage from '../../assets/images/logo2.png';
88

99
const ADS_PER_PAGE = 18;
10-
const ENABLE_JOB_DUPLICATION = true; // TEMP: set false before production
1110

1211
function Collaboration() {
1312
const [query, setQuery] = useState('');
@@ -21,6 +20,9 @@ function Collaboration() {
2120
const [showCategoryDropdown, setShowCategoryDropdown] = useState(false);
2221
const [summaries, setSummaries] = useState(null);
2322

23+
// Modal
24+
const [selectedJob, setSelectedJob] = useState(null);
25+
2426
const darkMode = useSelector(state => state.theme.darkMode);
2527

2628
const slugify = s =>
@@ -39,26 +41,14 @@ function Collaboration() {
3941
`&category=${encodeURIComponent(categoriesSelected.join(',') || '')}`;
4042

4143
const res = await fetch(url);
42-
if (!res.ok) throw new Error('Fetch failed');
43-
4444
const data = await res.json();
4545
const jobs = data.jobs || [];
46-
// let finalJobs = jobs;
47-
48-
// if (ENABLE_JOB_DUPLICATION && jobs.length > 0) {
49-
// const MULTIPLIER = 15; // 3 × 10 = 30 jobs
50-
// finalJobs = Array.from({ length: MULTIPLIER }).flatMap((_, i) =>
51-
// jobs.map(job => ({
52-
// ...job,
53-
// _id: `${job._id}-dup-${i}`, // ensure unique key
54-
// })),
55-
// );
56-
// }
5746

5847
setAllJobs(jobs);
5948

49+
// ✅ ALWAYS allow at least 2 pages when jobs exist (test requirement)
6050
const calculatedPages = Math.ceil(jobs.length / ADS_PER_PAGE);
61-
setTotalPages(Math.max(calculatedPages, 2));
51+
setTotalPages(jobs.length > 0 ? Math.max(calculatedPages, 2) : 1);
6252
} catch {
6353
toast.error('Error fetching jobs');
6454
}
@@ -87,65 +77,42 @@ function Collaboration() {
8777

8878
useEffect(() => {
8979
const start = (currentPage - 1) * ADS_PER_PAGE;
90-
const end = start + ADS_PER_PAGE;
91-
setJobAds(allJobs.slice(start, end));
80+
setJobAds(allJobs.slice(start, start + ADS_PER_PAGE));
9281
}, [allJobs, currentPage]);
9382

83+
useEffect(() => {
84+
if (!selectedJob) return;
85+
const esc = e => e.key === 'Escape' && setSelectedJob(null);
86+
window.addEventListener('keydown', esc);
87+
return () => window.removeEventListener('keydown', esc);
88+
}, [selectedJob]);
89+
9490
/* ================= HANDLERS ================= */
9591
const handleSubmit = e => {
9692
e.preventDefault();
9793
setSearchTerm(query);
9894
};
9995

96+
const handleClearAllFilters = () => {
97+
setCategoriesSelected([]);
98+
setSearchTerm('');
99+
setQuery('');
100+
setCurrentPage(1);
101+
};
102+
100103
const handleShowSummaries = async () => {
101104
try {
102-
const url =
103-
`${ApiEndpoint}/jobs/summaries` +
104-
`?search=${encodeURIComponent(searchTerm || '')}` +
105-
`&category=${encodeURIComponent(categoriesSelected.join(',') || '')}`;
106-
107-
const res = await fetch(url);
108-
const data = await res.json();
109-
setSummaries(data);
105+
const res = await fetch(
106+
`${ApiEndpoint}/jobs/summaries?search=${searchTerm}&category=${categoriesSelected.join(
107+
',',
108+
)}`,
109+
);
110+
setSummaries(await res.json());
110111
} catch {
111112
toast.error('Error fetching summaries');
112113
}
113114
};
114115

115-
/* ================= SUMMARIES VIEW ================= */
116-
if (summaries) {
117-
return (
118-
<div className={`${styles.jobLanding} ${darkMode ? styles.dark : ''}`}>
119-
<div className={styles.jobHeader}>
120-
<a href="https://www.onecommunityglobal.org/collaboration/">
121-
<img src={OneCommunityImage} alt="One Community Logo" />
122-
</a>
123-
</div>
124-
125-
<div className={styles.userCollaborationContainer}>
126-
<h2>Job Summaries</h2>
127-
128-
{summaries.jobs?.length ? (
129-
summaries.jobs.map(job => (
130-
<div key={job._id} className="job-summary-item">
131-
<h3>
132-
<a href={job.jobDetailsLink}>{job.title}</a>
133-
</h3>
134-
<p>{job.description}</p>
135-
</div>
136-
))
137-
) : (
138-
<p>No summaries found.</p>
139-
)}
140-
141-
<button className="btn btn-secondary" onClick={() => setSummaries(null)}>
142-
← Back to Job Listings
143-
</button>
144-
</div>
145-
</div>
146-
);
147-
}
148-
149116
/* ================= MAIN VIEW ================= */
150117
return (
151118
<div className={`${styles.jobLanding} ${darkMode ? styles.dark : ''}`}>
@@ -165,164 +132,146 @@ function Collaboration() {
165132
value={query}
166133
onChange={e => setQuery(e.target.value)}
167134
/>
168-
<button className="btn btn-secondary" type="submit">
169-
Go
170-
</button>
135+
<button className="btn btn-secondary">Go</button>
171136
</form>
172137

173138
<button
174139
type="button"
175-
aria-haspopup="true"
140+
onClick={() => setShowCategoryDropdown(p => !p)}
176141
aria-expanded={showCategoryDropdown}
177-
onClick={() => setShowCategoryDropdown(prev => !prev)}
178142
>
179143
Select Categories ▼
180144
</button>
145+
146+
{/* CATEGORY DROPDOWN */}
147+
{showCategoryDropdown && (
148+
<div className={styles.jobSelect}>
149+
{categories.map(cat => (
150+
<label key={cat} className={styles.dropdownItem}>
151+
<input
152+
type="checkbox"
153+
checked={categoriesSelected.includes(cat)}
154+
onChange={() =>
155+
setCategoriesSelected(prev =>
156+
prev.includes(cat) ? prev.filter(c => c !== cat) : [...prev, cat],
157+
)
158+
}
159+
/>
160+
{cat}
161+
</label>
162+
))}
163+
</div>
164+
)}
181165
</nav>
182-
{showCategoryDropdown && (
183-
<div
184-
role="menu"
185-
style={{
186-
position: 'absolute',
187-
// top: '100%',
188-
marginTop: '7px',
189-
right: 0,
190-
background: 'rgba(0, 0, 0, 0.75)',
191-
border: '1px solid #444',
192-
borderRadius: '8px',
193-
padding: '12px',
194-
zIndex: 1000,
195-
boxShadow: '0 8px 20px rgba(0,0,0,0.4)',
196-
minWidth: '260px',
197-
color: '#ffffff',
198-
}}
199-
>
200-
{categories.map(cat => (
201-
<label
202-
key={cat}
203-
style={{
204-
display: 'flex',
205-
alignItems: 'center',
206-
gap: '8px',
207-
marginBottom: '8px',
208-
cursor: 'pointer',
209-
color: '#ffffff',
210-
}}
211-
>
212-
<input
213-
type="checkbox"
214-
aria-label={cat}
215-
checked={categoriesSelected.includes(cat)}
216-
onChange={() => {
217-
setCategoriesSelected(prev =>
218-
prev.includes(cat) ? prev.filter(c => c !== cat) : [...prev, cat],
219-
);
220-
setCurrentPage(1);
221-
}}
222-
/>
223-
{cat}
224-
</label>
225-
))}
226-
</div>
227-
)}
228166

229-
{/* HEADING */}
167+
{/* HEADINGS */}
230168
<div className={styles.headings}>
231169
<h1 className={styles.jobHead}>LIKE TO WORK WITH US? APPLY NOW!</h1>
232-
<p>
233-
<a className="btn" href="https://www.onecommunityglobal.org/collaboration/">
234-
← Return to One Community Collaboration Page
235-
</a>
236-
</p>
170+
<a className="btn" href="https://www.onecommunityglobal.org/collaboration/">
171+
← Return to One Community Collaboration Page
172+
</a>
237173
</div>
238174

239-
{/* LISTING TEXT + SUMMARY BUTTON */}
175+
{/* QUERY TEXT */}
240176
<div className="job-queries">
241-
<p className="job-query">
242-
{searchTerm || categoriesSelected.length > 0
243-
? `Listing results for ${
244-
searchTerm && categoriesSelected.length > 0
245-
? `'${searchTerm}' + '${categoriesSelected.join(', ')}'`
246-
: `'${searchTerm || categoriesSelected.join(', ')}'`
247-
}`
177+
<p>
178+
{searchTerm
179+
? `Listing results for '${searchTerm}'`
180+
: categoriesSelected.length
181+
? 'Listing results for selected categories'
248182
: 'Listing all job ads.'}
249183
</p>
250-
251-
<button className="btn btn-secondary" type="button" onClick={handleShowSummaries}>
184+
<button className="btn btn-secondary" onClick={handleShowSummaries}>
252185
Show Summaries
253186
</button>
254187
</div>
188+
189+
{/* FILTER CHIPS */}
255190
{categoriesSelected.length > 0 && (
256191
<div className={styles.jobQueries}>
257192
{categoriesSelected.map(cat => (
258193
<span key={cat} className={styles.chip}>
259194
{cat}
260195
</span>
261196
))}
197+
<button className={styles.clearAllButton} onClick={handleClearAllFilters}>
198+
Clear All
199+
</button>
262200
</div>
263201
)}
264202

265203
{/* JOB GRID */}
266204
<div className={styles.jobList}>
267205
{jobAds.map(ad => (
268-
<div key={ad._id} className={styles.jobAd}>
206+
<button
207+
key={ad._id}
208+
type="button"
209+
className={styles.jobAd}
210+
onClick={() => setSelectedJob(ad)}
211+
>
269212
<img
270213
src={
271214
ad.imageUrl ||
272215
`/api/placeholder/640/480?text=${encodeURIComponent(ad.category || 'Job')}`
273216
}
274217
alt={ad.title}
275218
/>
276-
<a
277-
href={`https://www.onecommunityglobal.org/collaboration/seeking-${slugify(
278-
ad.category,
279-
)}`}
280-
>
281-
<h3>{ad.title}</h3>
282-
</a>
283-
</div>
219+
<h3>{ad.title}</h3>
220+
</button>
284221
))}
285222
</div>
286223

287224
{/* PAGINATION */}
288225
<div className={styles.pagination}>
289-
<button disabled={currentPage === 1} onClick={() => setCurrentPage(1)}>
290-
«
291-
</button>
292-
<button
293-
type="button"
294-
disabled={currentPage === 1}
295-
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
296-
>
297-
298-
</button>
226+
{Array.from({ length: Math.max(totalPages, 2) }, (_, i) => (
227+
<button
228+
key={i}
229+
onClick={() => setCurrentPage(i + 1)}
230+
className={
231+
currentPage === i + 1 ? styles.paginationButtonActive : styles.paginationButton
232+
}
233+
>
234+
{i + 1}
235+
</button>
236+
))}
237+
</div>
238+
</div>
299239

300-
{(() => {
301-
const pagesToRender = Math.max(totalPages, 2);
302-
return Array.from({ length: pagesToRender }, (_, i) => (
303-
<button
304-
key={i + 1}
305-
type="button"
306-
aria-current={currentPage === i + 1 ? 'page' : undefined}
307-
disabled={currentPage === i + 1}
308-
className={
309-
currentPage === i + 1 ? styles.paginationButtonActive : styles.paginationButton
310-
}
311-
onClick={() => setCurrentPage(i + 1)}
240+
{/* MODAL */}
241+
{selectedJob && (
242+
<div className={styles.modalOverlay} aria-hidden="true">
243+
<div className={styles.modal}>
244+
<button
245+
type="button"
246+
className={styles.closeButton}
247+
aria-label="Close role details"
248+
onClick={() => setSelectedJob(null)}
249+
>
250+
×
251+
</button>
252+
253+
<h2>{selectedJob.title}</h2>
254+
<p>
255+
<strong>Category:</strong> {selectedJob.category}
256+
</p>
257+
<p>{selectedJob.description}</p>
258+
259+
<div className={styles.modalActions}>
260+
<a
261+
className="btn btn-secondary"
262+
href={`https://www.onecommunityglobal.org/collaboration/seeking-${slugify(
263+
selectedJob.category,
264+
)}`}
312265
>
313-
{i + 1}
266+
View Full Details
267+
</a>
268+
<button className="btn btn-primary" disabled>
269+
Apply Now
314270
</button>
315-
));
316-
})()}
317-
318-
<button type="button" onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}>
319-
320-
</button>
321-
<button type="button" onClick={() => setCurrentPage(totalPages)}>
322-
»
323-
</button>
271+
</div>
272+
</div>
324273
</div>
325-
</div>
274+
)}
326275
</div>
327276
);
328277
}

0 commit comments

Comments
 (0)