Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e637725
convert to typescript
RafaelCenzano Mar 18, 2025
904dc2c
Combine pages together for an opportunities page
RafaelCenzano Mar 18, 2025
a246c87
cleaned up typescript stuff for filters
RafaelCenzano Mar 18, 2025
664359d
remove uneeded page
RafaelCenzano Mar 18, 2025
43ea735
Update to new opportunities page
RafaelCenzano Mar 18, 2025
d193f7b
update to new opportunities page
RafaelCenzano Mar 18, 2025
8d2d001
Make improvements to filtering by connecting majors
RafaelCenzano Mar 18, 2025
bec341a
move list
RafaelCenzano Mar 18, 2025
0a8c3f0
remove uneeded file
RafaelCenzano Mar 18, 2025
f63e846
Show no results found if empty
RafaelCenzano Mar 18, 2025
8e4678b
Move filter menu to it's own component
RafaelCenzano Mar 25, 2025
98d7acc
Clean up filters fields to use new filters
RafaelCenzano Mar 28, 2025
b5a5f58
clean up logging
RafaelCenzano Mar 29, 2025
eed21f7
Update filters and use effects
RafaelCenzano Mar 29, 2025
1fce2bf
move check box logic and add filters as an option for default check
RafaelCenzano Mar 29, 2025
c788ba6
move checkbox component
RafaelCenzano Mar 29, 2025
7678b19
Updates to the reducer and what was passed into components
RafaelCenzano Mar 29, 2025
ff9a1c7
add new types use for opportunities
RafaelCenzano Apr 5, 2025
1cfa5a0
update Checkbox to use new type
RafaelCenzano Apr 5, 2025
b4ec374
move type to type file
RafaelCenzano Apr 5, 2025
3508cb2
Remove semester option and clean up on submit and credit formatting
RafaelCenzano Apr 5, 2025
c4f84cd
Fix up reducer to work with popupmenu and interface as is
RafaelCenzano Apr 5, 2025
ff21dbf
add saved status
RafaelCenzano Apr 8, 2025
165f4f4
connect to backend route
RafaelCenzano Apr 8, 2025
9b60971
update to include save status and view button
RafaelCenzano Apr 8, 2025
ebecbea
Merge branch 'main' of github.com:LabConnect-RCOS/LabConnect-Frontend…
RafaelCenzano Apr 14, 2025
494263a
add credits and rename to lab managers
RafaelCenzano Apr 15, 2025
e19b7bd
add lab managers and credits
RafaelCenzano Apr 15, 2025
b8e6e3f
fix remove hourly page from filter map
RafaelCenzano Apr 15, 2025
5af05bc
adjust ordering of imports
RafaelCenzano Apr 15, 2025
899ed9e
rename to temp
RafaelCenzano Apr 15, 2025
2797b1b
rename files
RafaelCenzano Apr 15, 2025
11a8df0
rename file to proper capitlization
RafaelCenzano Apr 15, 2025
0bdb989
rename file to proper capitlization
RafaelCenzano Apr 15, 2025
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
14 changes: 5 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ import React from "react";
import { Routes, Route } from "react-router-dom";
import "./style/App.css";

import Opportunities from "./opportunities/pages/opportunities.tsx";

import Home from "./shared/pages/Home.tsx";
import PageNotFound from "./shared/pages/404.tsx";
import MainNavigation from "./shared/components/Navigation/MainNavigation.tsx";
import Jobs from "./opportunities/pages/Jobs.tsx";
import StickyFooter from "./shared/components/Navigation/StickyFooter.tsx";
import ProfilePage from "./shared/pages/Profile.tsx";
import Departments from "./staff/pages/Departments.tsx";
import StaffPage from "./staff/pages/Staff.tsx";
import Department from "./staff/pages/Department.tsx";
import CreatePost from "./staff/pages/CreatePost.tsx";
import IndividualPost from "./opportunities/pages/IndividualPost.tsx";
import ProfilePage from "./shared/pages/Profile.tsx";
import LoginRedirection from "./auth/Login.tsx";
import LogoutRedirection from "./auth/Logout.tsx";
import StickyFooter from "./shared/components/Navigation/StickyFooter.tsx";
import Token from "./auth/Token.tsx";
import Opportunities from "./opportunities/pages/Opportunities.tsx";
import IndividualPost from "./opportunities/pages/IndividualPost.tsx";
import { HelmetProvider } from 'react-helmet-async';
import { AuthProvider } from './context/AuthContext.tsx';

Expand All @@ -37,10 +35,8 @@ function App() {
<Route path="/login" element={<LoginRedirection />} />
<Route path="/signout" element={<LogoutRedirection />} />
<Route path="/logout" element={<LogoutRedirection />} />

<Route path="/opportunities" element={<Opportunities />} />

<Route path="/jobs" element={<Jobs />} />
<Route path="/opportunities" element={<Opportunities />} />
<Route path="/profile" element={<ProfilePage />} />
<Route
path="/staff/department/:department"
Expand Down
28 changes: 12 additions & 16 deletions src/opportunities/components/FiltersField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import GroupedComponents from "../../shared/components/UIElements/GroupedCompone
import HorizontalIconButton from "./HorizontalIconButton.tsx";
import { PiSlidersHorizontal } from "react-icons/pi";
import { MdCancel } from "react-icons/md";
import PropTypes from "prop-types";

const FiltersField = ({ resetFilters, deleteFilter, filters, setPopUpMenu }) => {

interface FiltersFieldProps {
resetFilters: () => void;
deleteFilter: (filter: string) => void;
filters: string[];
setPopUpMenu: () => void;
}

export default function FiltersField({ resetFilters, deleteFilter, filters, setPopUpMenu }: FiltersFieldProps) {
return (
<div>
<hr />
Expand All @@ -17,15 +22,15 @@ const FiltersField = ({ resetFilters, deleteFilter, filters, setPopUpMenu }) =>
<SearchBar />

<SmallTextButton className="" onClick={setPopUpMenu} special={true}>
<PiSlidersHorizontal className="pr-1"/>
<PiSlidersHorizontal className="pr-1" />
Change Filters
<PiSlidersHorizontal className="pl-1"/>
<PiSlidersHorizontal className="pl-1" />
</SmallTextButton>

{/* Fix rendering with new filters = [ [],[],[] ]*/}
<GroupedComponents gap={2}>
{filters[1].map((filter) => {
return(
{filters.map((filter) => {
return (
<HorizontalIconButton
onClick={deleteFilter}
icon={<MdCancel />}
Expand All @@ -47,12 +52,3 @@ const FiltersField = ({ resetFilters, deleteFilter, filters, setPopUpMenu }) =>
</div>
);
};

FiltersField.propTypes = {
resetFilters: PropTypes.func.isRequired,
deleteFilter: PropTypes.func.isRequired,
filters: PropTypes.arrayOf(PropTypes.array),
setPopUpMenu: PropTypes.func.isRequired,
};

export default FiltersField;
65 changes: 65 additions & 0 deletions src/opportunities/components/OpportunitiesDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from "react";
import { OpportunityList } from "../../types/opportunities.ts";

interface OpportunitiesListProps {
opportunities: OpportunityList[];
}

export default function OpportunitiesList({ opportunities }: OpportunitiesListProps) {
return (
<div className="p-4">
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
{/* Column Headers */}
<tr className="bg-gray-100">
<th className="p-3 text-left border">Position</th>
<th className="p-3 text-left border">Description</th>
<th className="p-3 text-left border">Location</th>
<th className="p-3 text-left border">Pay</th>
<th className="p-3 text-left border">Credits</th>
<th className="p-3 text-left border">Lab Managers</th>
<th className="p-3 text-left border">Term</th>
<th className="p-3 text-left border">View</th>
<th className="p-3 text-left border">Save</th>
</tr>
</thead>
<tbody>
{/* Info about the opportunities */}
{opportunities.length > 0 ? (
opportunities.map((opportunity) => (
<tr key={opportunity.id} className="hover:bg-gray-50">
<td className="p-3 border font-medium">{opportunity.name}</td>
<td className="p-3 border">{opportunity.description}</td>
<td className="p-3 border">{opportunity.location}</td>
<td className="p-3 border">{opportunity.pay ? `$${opportunity.pay}/hr` : ""}</td>
<td className="p-3 border">{opportunity.credits}</td>
<td className="p-3 border">{opportunity.lab_managers}</td>
<td className="p-3 border">
{opportunity.semester} {opportunity.year}
</td>
<td className="p-3 border">
<button className="bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600 mr-2">
View
</button>
</td>
<td className="p-3 border">
<button className="bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600">
{opportunity.saved ? "Unsave" : "Save"}
</button>
</td>
</tr>
))
) : (
<tr>
<td colSpan={9} className="p-3 border text-center">
No results found.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
};
211 changes: 211 additions & 0 deletions src/opportunities/components/PopUpMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import CheckBox from "../../shared/components/Checkbox.tsx";
import Input from "../../staff/components/Input";
import { Filters } from "../../types/opportunities.ts";

interface PopUpMenuProps {
setFunction: () => void;
reset: () => void;
filters: Filters;
setFilters: (activeFilters: string[], filterMap: Filters) => void;
}

interface Major {
code: string;
name: string;
}

export default function PopUpMenu({ setFunction, reset, filters, setFilters }: PopUpMenuProps) {
const [majors, setMajors] = useState<Major[]>();
const [validYears, setValidYears] = useState<string[]>([]);

const checkboxes: [string, string[], "years" | "credits"][] = [
["Class Year", validYears, "years"],
["Credits", ["1", "2", "3", "4"], "credits"]
];

useEffect(() => {
const fetchMajors = async () => {
const url = `${process.env.REACT_APP_BACKEND_SERVER}/majors`;
const response = await fetch(url);
if (!response.ok) {
console.log("Error fetching majors");
} else {
const data = await response.json();
setMajors(data);
}
}
fetchMajors();
}, []);

useEffect(() => {
const fetchYears = async () => {
const url = `${process.env.REACT_APP_BACKEND_SERVER}/years`;
const response = await fetch(url);
if (!response.ok) {
console.log("Error fetching valid years");
} else {
const data = await response.json();
setValidYears(data);
}
}
fetchYears();
}, []);

const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: {
years: [],
credits: [],
hourlyPay: filters.hourlyPay ?? 0,
majors: []
},
});

interface FormData {
years: string[],
credits: string[],
hourlyPay: number,
majors: string[]
}

function formatCredits(credits: string[]): string | null {
if (credits.length === 4) {
return "1-4 Credits";
} else if (credits.length === 1) {
return `${credits[0]} ${credits[0] === "1" ? "Credit" : "Credits"}`;
} else if (JSON.stringify(credits) === JSON.stringify(["1", "2", "3"])) {
return "1-3 Credits";
} else if (JSON.stringify(credits) === JSON.stringify(["2", "3", "4"])) {
return "2-4 Credits";
} else if (credits.length === 0) {
return null;
} else {
return `${credits.join(", ")} Credits`;
}
}

function submitHandler(data: FormData) {
const { years, credits, hourlyPay, majors } = data;
const newFilterMap: Filters = {
years: years.map(Number),
credits: credits,
hourlyPay: Number(hourlyPay),
majors: majors
}

const activeFilters: string[] = [
...years,
...(formatCredits(credits) ? [formatCredits(credits)!] : []),
...(Number(hourlyPay) > 0 ? [`$${Number(hourlyPay).toFixed(2)}/hr+`] : []),
...majors
];
setFilters(activeFilters, newFilterMap);
setFunction()
};

console.log("Filters: ", filters);

return (
<section className="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div className="fixed inset-0 bg-gray-500/75 transition-opacity" aria-hidden="true"></div>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<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">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 border-4">
<div className="text-2xl font-semibold text-center pb-3">Filters</div>
<section className="flex flex-col">
<form
onSubmit={handleSubmit((data) => {
submitHandler(data);
})}
className="form-container"
> <section className="flex flex-col max-h[100] overflow-y-auto"> {/* Added max-height and overflow-y-auto */}
<section className="flex justify-center">
{checkboxes.map((filter) => (
<div className="w-1/3" key={filter[2]}>
<CheckBox
errors={errors}
errorMessage={filter[2] + " checkbox failed"}
label={filter[0]}
options={filter[1]}
formHook={{ ...register(filter[2], {}) }}
name={filter[2]}
type="checkbox"
filters={filters}
/>
</div>
))}
</section>
<section className="flex justify-center">
<Input
errors={errors}
label="Minimum Hourly Pay"
name={"hourlyPay"}
errorMessage={"Hourly pay must be at least 0"}
formHook={{
...register("hourlyPay", {
required: "Hourly pay is required",
validate: value => value >= 0 || "Hourly pay must be greater or equal to 0",
pattern: {
value: /^[0-9]\d*$/,
message: "Hourly pay must be a positive integer"
}
})
}}
type="number"
options={[]}
placeHolder="Enter minimum hourly pay"
/>
</section>

<section className="pt-7 flex flex-col justify-center">
<h1 className="font-semibold text-lg text-center">Majors</h1>
<section className="flex justify-center py-4">
<select
multiple
size={5}
{...register("majors", {})}
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"
>
{majors &&
majors.map((major, index) => (
<option
key={index}
value={major.code}
className="py-2 px-3 hover:bg-blue-100"
selected={filters.majors.includes(major.code)}
>
{major.name}
</option>
))}
</select>
</section>
</section>
</section>

<section className="flex flex-row justify-center">
<div className="w-1/3 flex justify-center">
<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>
</div>
<div className="w-1/3 flex justify-center">
<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>
</div>
<div className="w-1/3 flex justify-center">
<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" />
</div>
</section>

</form>
</section>
</div>
</div>
</div>
</div>
</section>
);
}
Loading