11// src/pages/Collaboration/Collaboration.jsx
2- import { useEffect , useState } from 'react' ;
2+ import { useEffect , useState , useMemo , useRef } from 'react' ;
33import styles from './Collaboration.module.css' ;
44import { toast } from 'react-toastify' ;
55import { ApiEndpoint } from '~/utils/URL' ;
@@ -18,7 +18,13 @@ function Collaboration() {
1818 const [ totalPages , setTotalPages ] = useState ( 1 ) ;
1919 const [ categories , setCategories ] = useState ( [ ] ) ;
2020 const [ showCategoryDropdown , setShowCategoryDropdown ] = useState ( false ) ;
21+ const [ showPositionDropdown , setShowPositionDropdown ] = useState ( false ) ;
2122 const [ summaries , setSummaries ] = useState ( null ) ;
23+ // const [positions, setPositions] = useState([]);
24+ const [ selectedPosition , setSelectedPosition ] = useState ( '' ) ;
25+ const [ selectedCategory , setSelectedCategory ] = useState ( '' ) ;
26+ const categoryRef = useRef ( null ) ;
27+ const positionRef = useRef ( null ) ;
2228
2329 // Modal
2430 const [ selectedJob , setSelectedJob ] = useState ( null ) ;
@@ -38,33 +44,38 @@ function Collaboration() {
3844 const url =
3945 `${ ApiEndpoint } /jobs` +
4046 `?search=${ encodeURIComponent ( searchTerm || '' ) } ` +
41- `&category=${ encodeURIComponent ( categoriesSelected . join ( ',' ) || '' ) } ` ;
47+ `&category=${ encodeURIComponent ( selectedCategory || '' ) } ` ;
4248
4349 const res = await fetch ( url ) ;
4450 const data = await res . json ( ) ;
45- const jobs = data . jobs || [ ] ;
46-
47- setAllJobs ( jobs ) ;
48-
49- // ✅ ALWAYS allow at least 2 pages when jobs exist (test requirement)
50- const calculatedPages = Math . ceil ( jobs . length / ADS_PER_PAGE ) ;
51- setTotalPages ( jobs . length > 0 ? Math . max ( calculatedPages , 2 ) : 1 ) ;
51+ setAllJobs ( data . jobs || [ ] ) ;
5252 } catch {
5353 toast . error ( 'Error fetching jobs' ) ;
5454 }
5555 } ;
5656
57- /* ================= FETCH CATEGORIES ================= */
5857 const fetchCategories = async ( ) => {
5958 try {
6059 const res = await fetch ( `${ ApiEndpoint } /jobs/categories` ) ;
6160 const data = await res . json ( ) ;
62- setCategories ( ( data . categories || [ ] ) . sort ( ) ) ;
61+ setCategories ( ( data . categories || [ ] ) . sort ( ( a , b ) => a . localeCompare ( b ) ) ) ;
6362 } catch {
6463 toast . error ( 'Error fetching categories' ) ;
6564 }
6665 } ;
6766
67+ /* ================= FETCH CATEGORIES ================= */
68+ const fetchPositions = async ( ) => {
69+ try {
70+ const res = await fetch ( `${ ApiEndpoint } /jobs/positions` ) ;
71+ const data = await res . json ( ) ;
72+
73+ setPositions ( ( data . positions || [ ] ) . sort ( ( a , b ) => a . localeCompare ( b ) ) ) ;
74+ } catch {
75+ toast . error ( 'Error fetching positions' ) ;
76+ }
77+ } ;
78+
6879 /* ================= EFFECTS ================= */
6980 useEffect ( ( ) => {
7081 fetchCategories ( ) ;
@@ -73,12 +84,39 @@ function Collaboration() {
7384 useEffect ( ( ) => {
7485 setCurrentPage ( 1 ) ;
7586 fetchJobs ( ) ;
76- } , [ searchTerm , categoriesSelected ] ) ;
87+ } , [ searchTerm , selectedCategory ] ) ;
88+
89+ const filteredJobs = useMemo ( ( ) => {
90+ if ( ! selectedPosition ) return allJobs ;
91+
92+ return allJobs . filter ( job =>
93+ ( job . position || job . title || '' ) . toLowerCase ( ) . includes ( selectedPosition . toLowerCase ( ) ) ,
94+ ) ;
95+ } , [ allJobs , selectedPosition ] ) ;
96+
97+ const positions = useMemo ( ( ) => {
98+ const uniquePositions = [
99+ ...new Set (
100+ allJobs
101+ . filter (
102+ job =>
103+ ! selectedCategory || job . category ?. toLowerCase ( ) === selectedCategory . toLowerCase ( ) ,
104+ )
105+ . map ( job => job . position || job . title )
106+ . filter ( Boolean ) ,
107+ ) ,
108+ ] ;
109+
110+ return uniquePositions . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
111+ } , [ allJobs , selectedCategory ] ) ;
77112
78113 useEffect ( ( ) => {
79114 const start = ( currentPage - 1 ) * ADS_PER_PAGE ;
80- setJobAds ( allJobs . slice ( start , start + ADS_PER_PAGE ) ) ;
81- } , [ allJobs , currentPage ] ) ;
115+ setJobAds ( filteredJobs . slice ( start , start + ADS_PER_PAGE ) ) ;
116+
117+ const calculatedPages = Math . ceil ( filteredJobs . length / ADS_PER_PAGE ) ;
118+ setTotalPages ( Math . max ( calculatedPages , 1 ) ) ;
119+ } , [ filteredJobs , currentPage ] ) ;
82120
83121 useEffect ( ( ) => {
84122 if ( ! selectedJob ) return ;
@@ -87,14 +125,33 @@ function Collaboration() {
87125 return ( ) => window . removeEventListener ( 'keydown' , esc ) ;
88126 } , [ selectedJob ] ) ;
89127
128+ useEffect ( ( ) => {
129+ const handleClickOutside = event => {
130+ if ( categoryRef . current && ! categoryRef . current . contains ( event . target ) ) {
131+ setShowCategoryDropdown ( false ) ;
132+ }
133+
134+ if ( positionRef . current && ! positionRef . current . contains ( event . target ) ) {
135+ setShowPositionDropdown ( false ) ;
136+ }
137+ } ;
138+
139+ document . addEventListener ( 'mousedown' , handleClickOutside ) ;
140+
141+ return ( ) => {
142+ document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
143+ } ;
144+ } , [ ] ) ;
145+
90146 /* ================= HANDLERS ================= */
91147 const handleSubmit = e => {
92148 e . preventDefault ( ) ;
93149 setSearchTerm ( query ) ;
94150 } ;
95151
96152 const handleClearAllFilters = ( ) => {
97- setCategoriesSelected ( [ ] ) ;
153+ setSelectedCategory ( '' ) ;
154+ setSelectedPosition ( '' ) ;
98155 setSearchTerm ( '' ) ;
99156 setQuery ( '' ) ;
100157 setCurrentPage ( 1 ) ;
@@ -103,9 +160,7 @@ function Collaboration() {
103160 const handleShowSummaries = async ( ) => {
104161 try {
105162 const res = await fetch (
106- `${ ApiEndpoint } /jobs/summaries?search=${ searchTerm } &category=${ categoriesSelected . join (
107- ',' ,
108- ) } `,
163+ `${ ApiEndpoint } /jobs/summaries?search=${ searchTerm } &category=${ selectedCategory } ` ,
109164 ) ;
110165 setSummaries ( await res . json ( ) ) ;
111166 } catch {
@@ -169,33 +224,76 @@ function Collaboration() {
169224 < button className = "btn btn-secondary" > Go</ button >
170225 </ form >
171226
172- < button
173- type = "button"
174- onClick = { ( ) => setShowCategoryDropdown ( p => ! p ) }
175- aria-expanded = { showCategoryDropdown }
176- >
177- Select Categories ▼
178- </ button >
227+ < div className = { styles . dropdownWrapper } ref = { categoryRef } >
228+ < button
229+ type = "button"
230+ onClick = { ( ) => {
231+ setShowCategoryDropdown ( prev => ! prev ) ;
232+ setShowPositionDropdown ( false ) ;
233+ } }
234+ aria-expanded = { showCategoryDropdown }
235+ >
236+ { selectedCategory || 'Select Categories' } ▼
237+ </ button >
179238
180- { /* CATEGORY DROPDOWN */ }
181- { showCategoryDropdown && (
182- < div className = { styles . jobSelect } >
183- { categories . map ( cat => (
184- < label key = { cat } className = { styles . dropdownItem } >
185- < input
186- type = "checkbox"
187- checked = { categoriesSelected . includes ( cat ) }
188- onChange = { ( ) =>
189- setCategoriesSelected ( prev =>
190- prev . includes ( cat ) ? prev . filter ( c => c !== cat ) : [ ...prev , cat ] ,
191- )
192- }
193- />
194- { cat }
195- </ label >
196- ) ) }
197- </ div >
198- ) }
239+ { showCategoryDropdown && (
240+ < div className = { styles . jobSelect } >
241+ { categories . map ( cat => (
242+ < button
243+ key = { cat }
244+ type = "button"
245+ className = { styles . dropdownItem }
246+ onClick = { ( ) => {
247+ setSelectedCategory ( cat ) ;
248+ setSelectedPosition ( '' ) ;
249+ setShowCategoryDropdown ( false ) ;
250+ setCurrentPage ( 1 ) ;
251+ } }
252+ >
253+ { cat }
254+ </ button >
255+ ) ) }
256+ </ div >
257+ ) }
258+ </ div >
259+
260+ < div className = { styles . dropdownWrapper } ref = { positionRef } >
261+ < button
262+ type = "button"
263+ disabled = { ! selectedCategory }
264+ onClick = { ( ) => {
265+ if ( ! selectedCategory ) return ;
266+ setShowPositionDropdown ( prev => ! prev ) ;
267+ setShowCategoryDropdown ( false ) ;
268+ } }
269+ aria-expanded = { showPositionDropdown }
270+ >
271+ { selectedPosition || 'Select Positions' } ▼
272+ </ button >
273+
274+ { showPositionDropdown && selectedCategory && (
275+ < div className = { styles . jobSelect } >
276+ { positions . length > 0 ? (
277+ positions . map ( pos => (
278+ < button
279+ key = { pos }
280+ type = "button"
281+ className = { styles . dropdownItem }
282+ onClick = { ( ) => {
283+ setSelectedPosition ( pos ) ;
284+ setShowPositionDropdown ( false ) ;
285+ setCurrentPage ( 1 ) ;
286+ } }
287+ >
288+ { pos }
289+ </ button >
290+ ) )
291+ ) : (
292+ < div className = { styles . dropdownItem } > No positions found</ div >
293+ ) }
294+ </ div >
295+ ) }
296+ </ div >
199297 </ nav >
200298
201299 { /* HEADINGS */ }
@@ -211,8 +309,10 @@ function Collaboration() {
211309 < p >
212310 { searchTerm
213311 ? `Listing results for '${ searchTerm } '`
214- : categoriesSelected . length
215- ? 'Listing results for selected categories'
312+ : selectedPosition
313+ ? `Listing results for '${ selectedPosition } ' in '${ selectedCategory } '`
314+ : selectedCategory
315+ ? `Listing results for '${ selectedCategory } '`
216316 : 'Listing all job ads.' }
217317 </ p >
218318 < button className = "btn btn-secondary" onClick = { handleShowSummaries } >
@@ -221,13 +321,10 @@ function Collaboration() {
221321 </ div >
222322
223323 { /* FILTER CHIPS */ }
224- { categoriesSelected . length > 0 && (
324+ { ( selectedCategory || selectedPosition ) && (
225325 < div className = { styles . jobQueries } >
226- { categoriesSelected . map ( cat => (
227- < span key = { cat } className = { styles . chip } >
228- { cat }
229- </ span >
230- ) ) }
326+ { selectedCategory && < span className = { styles . chip } > { selectedCategory } </ span > }
327+ { selectedPosition && < span className = { styles . chip } > { selectedPosition } </ span > }
231328 < button className = { styles . clearAllButton } onClick = { handleClearAllFilters } >
232329 Clear All
233330 </ button >
@@ -257,7 +354,7 @@ function Collaboration() {
257354
258355 { /* PAGINATION */ }
259356 < div className = { styles . pagination } >
260- { Array . from ( { length : Math . max ( totalPages , 2 ) } , ( _ , i ) => (
357+ { Array . from ( { length : totalPages } , ( _ , i ) => (
261358 < button
262359 key = { i }
263360 onClick = { ( ) => setCurrentPage ( i + 1 ) }
0 commit comments