1+ import React , { useEffect , useState } from "react" ;
2+ import { useForm } from "react-hook-form" ;
3+ import CheckBox from "../../shared/components/Checkbox.tsx" ;
4+ import Input from "../../staff/components/Input" ;
5+ import { Filters } from "../../types/opportunities.ts" ;
6+
7+ interface PopUpMenuProps {
8+ setFunction : ( ) => void ;
9+ reset : ( ) => void ;
10+ filters : Filters ;
11+ setFilters : ( activeFilters : string [ ] , filterMap : Filters ) => void ;
12+ }
13+
14+ interface Major {
15+ code : string ;
16+ name : string ;
17+ }
18+
19+ export default function PopUpMenu ( { setFunction, reset, filters, setFilters } : PopUpMenuProps ) {
20+ const [ majors , setMajors ] = useState < Major [ ] > ( ) ;
21+ const [ validYears , setValidYears ] = useState < string [ ] > ( [ ] ) ;
22+
23+ const checkboxes : [ string , string [ ] , "years" | "credits" ] [ ] = [
24+ [ "Class Year" , validYears , "years" ] ,
25+ [ "Credits" , [ "1" , "2" , "3" , "4" ] , "credits" ]
26+ ] ;
27+
28+ useEffect ( ( ) => {
29+ const fetchMajors = async ( ) => {
30+ const url = `${ process . env . REACT_APP_BACKEND_SERVER } /majors` ;
31+ const response = await fetch ( url ) ;
32+ if ( ! response . ok ) {
33+ console . log ( "Error fetching majors" ) ;
34+ } else {
35+ const data = await response . json ( ) ;
36+ setMajors ( data ) ;
37+ }
38+ }
39+ fetchMajors ( ) ;
40+ } , [ ] ) ;
41+
42+ useEffect ( ( ) => {
43+ const fetchYears = async ( ) => {
44+ const url = `${ process . env . REACT_APP_BACKEND_SERVER } /years` ;
45+ const response = await fetch ( url ) ;
46+ if ( ! response . ok ) {
47+ console . log ( "Error fetching valid years" ) ;
48+ } else {
49+ const data = await response . json ( ) ;
50+ setValidYears ( data ) ;
51+ }
52+ }
53+ fetchYears ( ) ;
54+ } , [ ] ) ;
55+
56+ const {
57+ register,
58+ handleSubmit,
59+ formState : { errors } ,
60+ } = useForm ( {
61+ defaultValues : {
62+ years : [ ] ,
63+ credits : [ ] ,
64+ hourlyPay : filters . hourlyPay ?? 0 ,
65+ majors : [ ]
66+ } ,
67+ } ) ;
68+
69+ interface FormData {
70+ years : string [ ] ,
71+ credits : string [ ] ,
72+ hourlyPay : number ,
73+ majors : string [ ]
74+ }
75+
76+ function formatCredits ( credits : string [ ] ) : string | null {
77+ if ( credits . length === 4 ) {
78+ return "1-4 Credits" ;
79+ } else if ( credits . length === 1 ) {
80+ return `${ credits [ 0 ] } ${ credits [ 0 ] === "1" ? "Credit" : "Credits" } ` ;
81+ } else if ( JSON . stringify ( credits ) === JSON . stringify ( [ "1" , "2" , "3" ] ) ) {
82+ return "1-3 Credits" ;
83+ } else if ( JSON . stringify ( credits ) === JSON . stringify ( [ "2" , "3" , "4" ] ) ) {
84+ return "2-4 Credits" ;
85+ } else if ( credits . length === 0 ) {
86+ return null ;
87+ } else {
88+ return `${ credits . join ( ", " ) } Credits` ;
89+ }
90+ }
91+
92+ function submitHandler ( data : FormData ) {
93+ const { years, credits, hourlyPay, majors } = data ;
94+ const newFilterMap : Filters = {
95+ years : years . map ( Number ) ,
96+ credits : credits ,
97+ hourlyPay : Number ( hourlyPay ) ,
98+ majors : majors
99+ }
100+
101+ const activeFilters : string [ ] = [
102+ ...years ,
103+ ...( formatCredits ( credits ) ? [ formatCredits ( credits ) ! ] : [ ] ) ,
104+ ...( Number ( hourlyPay ) > 0 ? [ `$${ Number ( hourlyPay ) . toFixed ( 2 ) } /hr+` ] : [ ] ) ,
105+ ...majors
106+ ] ;
107+ setFilters ( activeFilters , newFilterMap ) ;
108+ setFunction ( )
109+ } ;
110+
111+ console . log ( "Filters: " , filters ) ;
112+
113+ return (
114+ < section className = "relative z-10" aria-labelledby = "modal-title" role = "dialog" aria-modal = "true" >
115+ < div className = "fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden = "true" > </ div >
116+ < div className = "fixed inset-0 z-10 w-screen overflow-y-auto" >
117+ < div className = "flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0" >
118+ < div className = "relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-6xl" >
119+ < div className = "bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 border-4" >
120+ < div className = "text-2xl font-semibold text-center pb-3" > Filters</ div >
121+ < section className = "flex flex-col" >
122+ < form
123+ onSubmit = { handleSubmit ( ( data ) => {
124+ submitHandler ( data ) ;
125+ } ) }
126+ className = "form-container"
127+ > < section className = "flex flex-col max-h[100] overflow-y-auto" > { /* Added max-height and overflow-y-auto */ }
128+ < section className = "flex justify-center" >
129+ { checkboxes . map ( ( filter ) => (
130+ < div className = "w-1/3" key = { filter [ 2 ] } >
131+ < CheckBox
132+ errors = { errors }
133+ errorMessage = { filter [ 2 ] + " checkbox failed" }
134+ label = { filter [ 0 ] }
135+ options = { filter [ 1 ] }
136+ formHook = { { ...register ( filter [ 2 ] , { } ) } }
137+ name = { filter [ 2 ] }
138+ type = "checkbox"
139+ filters = { filters }
140+ />
141+ </ div >
142+ ) ) }
143+ </ section >
144+ < section className = "flex justify-center" >
145+ < Input
146+ errors = { errors }
147+ label = "Minimum Hourly Pay"
148+ name = { "hourlyPay" }
149+ errorMessage = { "Hourly pay must be at least 0" }
150+ formHook = { {
151+ ...register ( "hourlyPay" , {
152+ required : "Hourly pay is required" ,
153+ validate : value => value >= 0 || "Hourly pay must be greater or equal to 0" ,
154+ pattern : {
155+ value : / ^ [ 0 - 9 ] \d * $ / ,
156+ message : "Hourly pay must be a positive integer"
157+ }
158+ } )
159+ } }
160+ type = "number"
161+ options = { [ ] }
162+ placeHolder = "Enter minimum hourly pay"
163+ />
164+ </ section >
165+
166+ < section className = "pt-7 flex flex-col justify-center" >
167+ < h1 className = "font-semibold text-lg text-center" > Majors</ h1 >
168+ < section className = "flex justify-center py-4" >
169+ < select
170+ multiple
171+ size = { 5 }
172+ { ...register ( "majors" , { } ) }
173+ className = "form-multiselect block w-3/4 border-gray-300 rounded-md shadow-lg focus:border-blue-500 focus:ring focus:ring-blue-300 focus:ring-opacity-50 bg-white text-gray-700"
174+ >
175+ { majors &&
176+ majors . map ( ( major , index ) => (
177+ < option
178+ key = { index }
179+ value = { major . code }
180+ className = "py-2 px-3 hover:bg-blue-100"
181+ selected = { filters . majors . includes ( major . code ) }
182+ >
183+ { major . name }
184+ </ option >
185+ ) ) }
186+ </ select >
187+ </ section >
188+ </ section >
189+ </ section >
190+
191+ < section className = "flex flex-row justify-center" >
192+ < div className = "w-1/3 flex justify-center" >
193+ < button type = "button" onClick = { setFunction } className = "btn btn-primary border-black text-gray-700 bg-white w-1/2 hover:text-gray-900 hover:bg-gray-200 hover:border-black focus:text-gray-900 focus:bg-gray-100 focus:border-black" > Cancel</ button >
194+ </ div >
195+ < div className = "w-1/3 flex justify-center" >
196+ < button type = "button" onClick = { ( ) => { reset ( ) ; setFunction ( ) ; } } className = "btn btn-primary border-black text-gray-700 bg-white w-1/2 hover:text-gray-900 hover:bg-gray-200 hover:border-black focus:text-gray-900 focus:bg-gray-100 focus:border-black" > Reset</ button >
197+ </ div >
198+ < div className = "w-1/3 flex justify-center" >
199+ < input type = "submit" value = "Search" className = "btn btn-primary bg-blue-700 text-gray-100 w-1/2 hover:bg-blue-800 focus:bg-blue-800" />
200+ </ div >
201+ </ section >
202+
203+ </ form >
204+ </ section >
205+ </ div >
206+ </ div >
207+ </ div >
208+ </ div >
209+ </ section >
210+ ) ;
211+ }
0 commit comments