Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(edit course, style courses on the profile card, course filter): #3

Merged
merged 1 commit into from
Oct 12, 2024
Merged
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
29 changes: 26 additions & 3 deletions src/components/Home/StudentFilter.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import useCourses from '@data/useCourses';
import useMajors from '@data/useMajors';
import {
Box,
Expand All @@ -16,10 +17,13 @@ import {
export default function StudentFilter({
selectedMajors,
setSelectedMajors,
selectedCourses,
setSelectedCourses,
selectedYears,
setSelectedYears,
}) {
const majorsList = useMajors();
const coursesList = useCourses();

return (
<Box sx={{ display: { xs: 'block', md: 'flex' }, gap: 2, mb: 2 }}>
Expand All @@ -36,7 +40,28 @@ export default function StudentFilter({
sx={{ flex: 1, mb: 2, minWidth: 200, maxWidth: '100%' }}
/>

{/* 2. Year filter */}
{/* 2. Course filter */}
<Autocomplete
multiple
options={coursesList}
getOptionLabel={(option) => option}
value={selectedCourses}
onChange={(event, newValue) => setSelectedCourses(newValue)}
renderInput={(params) => (
<TextField {...params} label="Filter by Course(s)" variant="outlined" />
)}
filterOptions={(options, { inputValue }) => {
// Convert inputValue to lowercase for case-insensitive matching
const lowercasedInput = inputValue.toLowerCase();

// Only include options that start with the input
return options.filter((option) => option.toLowerCase().startsWith(lowercasedInput));
}}
sx={{ flex: 1, mb: 2, minWidth: 200, maxWidth: '100%' }}
freeSolo
/>

{/* 3. Year filter */}
<FormControl sx={{ flex: 1, mb: 2, minWidth: 200, maxWidth: '100%' }}>
<InputLabel>Filter by Year(s)</InputLabel>
<Select
Expand All @@ -59,8 +84,6 @@ export default function StudentFilter({
))}
</Select>
</FormControl>

{/* 3. Course filter */}
</Box>
);
}
11 changes: 9 additions & 2 deletions src/components/Home/StudentList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default function StudentList({
setRequestedUsers,
matchedUserUids,
selectedMajors,
selectedCourses,
selectedYears,
}) {
const studentData = useStudentData();
Expand All @@ -22,6 +23,8 @@ export default function StudentList({
profile.uid !== userProfile.uid &&
!matchedUserUids.has(profile.uid) &&
(selectedMajors.length === 0 || selectedMajors.includes(profile.major)) &&
(selectedCourses.length === 0 ||
profile.courses?.some((course) => selectedCourses.includes(course))) &&
(selectedYears.length === 0 || selectedYears.includes(profile.year)),
);

Expand All @@ -30,12 +33,16 @@ export default function StudentList({
currentPage,
totalPages,
handlePageChange,
} = usePagination(filteredStudentData, 10);
} = usePagination(filteredStudentData || [], 10);

const handleMatch = async (studentUserProfile) => {
try {
await createMatch([studentUserProfile.uid, userProfile.uid], 'University Library');
setRequestedUsers((prev) => new Set(prev).add(studentUserProfile.uid));
setRequestedUsers((prev) => {
const newSet = new Set(prev);
newSet.add(studentUserProfile.uid);
return newSet;
});
} catch (error) {
console.error('Error creating match:', error);
}
Expand Down
4 changes: 4 additions & 0 deletions src/components/HomePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function HomePage() {
const { userProfile, requestedUsers, setRequestedUsers, matchedUserUids, loading } =
useUserProfile(user);
const [selectedMajors, setSelectedMajors] = useState([]);
const [selectedCourses, setSelectedCourses] = useState([]);
const [selectedYears, setSelectedYears] = useState([]);
const navigate = useNavigate();

Expand Down Expand Up @@ -61,6 +62,8 @@ export default function HomePage() {
<StudentFilter
selectedMajors={selectedMajors}
setSelectedMajors={setSelectedMajors}
selectedCourses={selectedCourses}
setSelectedCourses={setSelectedCourses}
selectedYears={selectedYears}
setSelectedYears={setSelectedYears}
/>
Expand All @@ -71,6 +74,7 @@ export default function HomePage() {
setRequestedUsers={setRequestedUsers}
matchedUserUids={matchedUserUids}
selectedMajors={selectedMajors}
selectedCourses={selectedCourses}
selectedYears={selectedYears}
/>
</Box>
Expand Down
41 changes: 35 additions & 6 deletions src/components/Profile/EditProfile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const EditProfile = () => {
majorsList,
selectedMajors,
setSelectedMajors,
coursesList,
selectedCourses,
setSelectedCourses,
handleInputChange,
errors,
isFormValid,
Expand Down Expand Up @@ -71,6 +74,7 @@ const EditProfile = () => {
'tel',
)}

{/* Major Selection */}
<Autocomplete
multiple
options={majorsList}
Expand All @@ -91,14 +95,39 @@ const EditProfile = () => {
)}
/>

{/* Course Selection */}
<Autocomplete
multiple
options={coursesList}
getOptionLabel={(option) => option}
value={selectedCourses}
onChange={(event, newValue) => setSelectedCourses(newValue)}
renderInput={(params) => (
<TextField
{...params}
label="Courses"
error={!!errors.courses}
helperText={errors.courses && 'Please select your course(s)'}
margin="normal"
fullWidth
/>
)}
filterOptions={(options, { inputValue }) => {
// Convert inputValue to lowercase for case-insensitive matching
const lowercasedInput = inputValue.toLowerCase();

// Only include options that start with the input
return options.filter((option) => option.toLowerCase().startsWith(lowercasedInput));
}}
sx={{ flex: 1, mb: 2, minWidth: 200, maxWidth: '100%' }}
freeSolo
/>

{/* Year Selection */}
{renderSelectField('Year', 'year', years, formData.year, handleInputChange, errors.year)}

{/* Description Field */}
{renderTextField('Description', 'description', formData.description, handleInputChange)}
{renderTextField(
'List of Courses (comma-separated)',
'listOfCourses',
formData.listOfCourses,
handleInputChange,
)}

<Button variant="contained" color="primary" type="submit" disabled={!isFormValid}>
Save Profile
Expand Down
17 changes: 9 additions & 8 deletions src/components/Profile/ProfilePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,6 @@ export default function ProfilePage() {
<ContentBox icon={Phone} title="Phone" content={profileData?.phoneNumber} />
</InfoSection>

<InfoSection title="Bio">
<Typography variant="body2" color="textSecondary">
{profileData?.description}
</Typography>
</InfoSection>

<InfoSection title="Study Info">
<ContentBox icon={CalendarToday} title="Year" content={profileData?.year} />
<CustomDivider />
Expand All @@ -60,6 +54,12 @@ export default function ProfilePage() {
<ContentBox icon={ListAlt} title="Courses" content={profileData?.listOfCourses} isCourses />
</InfoSection>

<InfoSection title="Bio">
<Typography variant="body2" color="textSecondary">
{profileData?.description}
</Typography>
</InfoSection>

<ActionButtons onEditClick={handleEditClick} onSignOutClick={handleSignOutDialogOpen} />

<SignOutDialog open={signOutDialogOpen} onClose={() => setSignOutDialogOpen(false)} />
Expand Down Expand Up @@ -95,7 +95,6 @@ const InfoSection = ({ title, children }) => (
</Card>
</>
);

const ContentBox = ({ icon: IconComponent, title, content, isCourses = false }) => (
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
Expand All @@ -105,7 +104,9 @@ const ContentBox = ({ icon: IconComponent, title, content, isCourses = false })
</Typography>
</Box>
{isCourses && Array.isArray(content) ? (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Box
sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, ml: 'auto', justifyContent: 'flex-end' }}
>
{content.map((course, index) => (
<Chip key={index} label={course} />
))}
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/data/useCourses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useEffect, useState } from 'react';

import { getCourses } from '@firestore/general';

export default function useCourses() {
const [CoursesList, setCoursesList] = useState([]);

useEffect(() => {
const fetchCourses = async () => {
try {
const Courses = await getCourses();
setCoursesList(Courses);
} catch (error) {
console.error('Error fetching Courses:', error);
}
};
fetchCourses();
}, []);

return CoursesList;
}
29 changes: 21 additions & 8 deletions src/hooks/useEditProfileForm.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useEffect, useState } from 'react';

import { getMajors } from '@firestore/general';
import { getMajors, getCourses } from '@firestore/general';
import { getUserProfile, updateUserProfile } from '@firestore/userProfile';

const useEditProfileForm = (user) => {
const [loading, setLoading] = useState(true);
const [majorsList, setMajorsList] = useState([]);
const [selectedMajors, setSelectedMajors] = useState([]);
const [coursesList, setCoursesList] = useState([]);
const [selectedCourses, setSelectedCourses] = useState([]);

const [formData, setFormData] = useState({
name: '',
email: '',
Expand All @@ -22,11 +25,16 @@ const useEditProfileForm = (user) => {
const [firstTimeUser, setFirstTimeUser] = useState(false);

useEffect(() => {
const fetchProfileAndMajors = async () => {
const fetchProfileMajorsAndCourses = async () => {
if (user && user.uid) {
try {
const [data, majorsFromDb] = await Promise.all([getUserProfile(user.uid), getMajors()]);
const [data, majorsFromDb, coursesFromDb] = await Promise.all([
getUserProfile(user.uid),
getMajors(),
getCourses(),
]);
setMajorsList(majorsFromDb);
setCoursesList(coursesFromDb); // Set available courses

if (data) {
setFormData({
Expand All @@ -36,24 +44,24 @@ const useEditProfileForm = (user) => {
major: data.major || '',
year: data.year || '',
description: data.description || '',
listOfCourses: data.listOfCourses.join(', ') || '',
});
setSelectedMajors(data.major ? data.major.split(',') : []);
setSelectedCourses(data.listOfCourses || []);
setFirstTimeUser(Boolean(data.major && data.year));
}
} catch (error) {
console.error('Error fetching profile and majors:', error);
console.error('Error fetching profile, majors, and courses:', error);
} finally {
setLoading(false);
}
}
};
fetchProfileAndMajors();
fetchProfileMajorsAndCourses();
}, [user]);

useEffect(() => {
validateForm();
}, [formData, selectedMajors]);
}, [formData, selectedMajors, selectedCourses]);

const handleInputChange = (name, value) => {
if (name === 'phoneNumber') {
Expand All @@ -78,6 +86,7 @@ const useEditProfileForm = (user) => {
email: !/^\S+@\S+\.\S+$/.test(formData.email),
phoneNumber: formData.phoneNumber.replace(/\D/g, '').length !== 10,
major: selectedMajors.length === 0,
listClasses: selectedCourses.length === 0,
year: !formData.year,
};
setErrors(newErrors);
Expand All @@ -90,7 +99,8 @@ const useEditProfileForm = (user) => {
const updatedProfileData = {
...formData,
major: selectedMajors.join(', '),
listOfCourses: formData.listOfCourses.split(',').map((course) => course.trim()),
listOfCourses: selectedCourses,
//listOfCourses: formData.listOfCourses.split(',').map((course) => course.trim()),
};
await updateUserProfile(userId, updatedProfileData);
return true; // Indicate success
Expand All @@ -108,6 +118,9 @@ const useEditProfileForm = (user) => {
majorsList,
selectedMajors,
setSelectedMajors,
coursesList,
selectedCourses,
setSelectedCourses,
handleInputChange,
errors,
isFormValid,
Expand Down
24 changes: 24 additions & 0 deletions src/utils/firestore/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,27 @@ export const getMajors = async () => {
return [];
}
};

// Get list of courses from Firestore and store them as an set
export const getCourses = async () => {
try {
const coursesCollectionRef = collection(db, 'courseData');
const coursesSnapshot = await getDocs(coursesCollectionRef);

const coursesSet = new Set();
coursesSnapshot.docs.forEach((doc) => {
const subject = doc.id;
const numbers = doc.data().numbers || [];

numbers.forEach((courseNumber) => {
coursesSet.add(`${subject} ${courseNumber}`); // Add unique combination to Set
});
});

// Convert Set to Array to eliminate duplicates
return Array.from(coursesSet);
} catch (error) {
console.error('Error fetching courses:', error);
return [];
}
};
Loading