Skip to content

Commit

Permalink
Merge pull request #58 from JumboCode/create_edit_classes
Browse files Browse the repository at this point in the history
Create edit classes
  • Loading branch information
myix765 authored Dec 11, 2024
2 parents 0fd3328 + 8812a47 commit 777b56d
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 0 deletions.
53 changes: 53 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,59 @@ app.get('/api/class', async (req, res) => {
}
})

// Create or Edit Class
app.post('/api/classes', async (req, res) => {
try {
const { title, level, ageGroup, instructor, schedule } = req.body;

// Check if class already exists by title (assuming title is unique)
const existingClass = await Class.findOne({ title });

if (existingClass) {
// If class exists, update it while preserving the roster
const updatedClass = await Class.findByIdAndUpdate(
existingClass._id,
{
$set: {
title,
level,
ageGroup,
instructor,
schedule
}
},
{
new: true, // Return the updated document
runValidators: true // Run schema validators
}
);

return res.status(200).json({
message: 'Class updated successfully',
class: updatedClass
});
} else {
// If class doesn't exist, create a new one with empty roster
const newClass = new Class({
title,
level,
ageGroup,
instructor,
schedule,
});

await newClass.save();
return res.status(201).json({
message: 'Class created successfully',
class: newClass
});
}
} catch (error) {
console.error('Error creating/updating class:', error);
return res.status(500).json({ message: 'Error creating/updating class' });
}
});

// Enroll in a class
app.put('/api/users/:id/enroll', async (req, res) => {
const { classId } = req.body
Expand Down
12 changes: 12 additions & 0 deletions src/api/class-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ const getLevels = async (query = "") => {
}
}

// classData should be an object containing title, level, ageGroup, instructor, and schedule
const createOrUpdateClass = async (classData) => {
try {
const response = await axios.post(apiUrl('/api/classes'), classData);
return response.data;
} catch (error) {
console.error('Error creating/updating class:', error);
throw error;
}
}

const getConversations = async () => {
try {
const response = await axios.get("/api/conversations/")
Expand Down Expand Up @@ -82,6 +93,7 @@ const getClassById = async (classId) => {
export {
getClasses,
getLevels,
createOrUpdateClass,
getConversations,
enrollInClass,
unenrollInClass,
Expand Down
137 changes: 137 additions & 0 deletions src/components/CreateClassForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useState } from 'react';
import { createOrUpdateClass } from '../api/class-wrapper';

const CreateClassForm = () => {
const [formData, setFormData] = useState({
title: '',
level: '',
ageGroup: '',
instructor: '',
schedule: [{ day: '', time: '' }]
});

const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await createOrUpdateClass(formData);
alert(response.message); // Show success message
// Reset form
setFormData({
title: '',
level: '',
ageGroup: '',
instructor: '',
schedule: [{ day: '', time: '' }]
});
} catch (error) {
alert('Error creating/updating class: ' + error.message);
}
};

const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};

const handleScheduleChange = (index, field, value) => {
const newSchedule = [...formData.schedule];
newSchedule[index] = {
...newSchedule[index],
[field]: value
};
setFormData({
...formData,
schedule: newSchedule
});
};

return (
<div className="max-w-md mx-auto bg-white p-6 rounded-lg shadow">
<h2 className="text-2xl font-bold text-dark-blue-800 mb-4">Create/Edit Class</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">Title</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-700">Level</label>
<input
type="text"
name="level"
value={formData.level}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-700">Age Group</label>
<input
type="text"
name="ageGroup"
value={formData.ageGroup}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-700">Instructor</label>
<input
type="text"
name="instructor"
value={formData.instructor}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>

{formData.schedule.map((scheduleItem, index) => (
<div key={index} className="space-y-2">
<h3 className="text-sm font-medium text-gray-700">Schedule {index + 1}</h3>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
placeholder="Day"
value={scheduleItem.day}
onChange={(e) => handleScheduleChange(index, 'day', e.target.value)}
className="rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
<input
type="text"
placeholder="Time"
value={scheduleItem.time}
onChange={(e) => handleScheduleChange(index, 'time', e.target.value)}
className="rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required
/>
</div>
</div>
))}

<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Create/Update Class
</button>
</form>
</div>
);
};

export default CreateClassForm;
118 changes: 118 additions & 0 deletions src/pages/Classes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useState, useEffect } from 'react';
import { useSearch, useLocation } from 'wouter';
import Class from '../components/Class';
import Level from '../components/Level';
import CreateClassForm from '../components/CreateClassForm';
import { getClasses, getLevels } from '../api/class-wrapper';

const Classes = () => {
const [classes, setClasses] = useState([]);
const [level, setLevel] = useState();
const [allLevels, setAllLevels] = useState([]);
const [loading, setLoading] = useState(true);
const queryString = useSearch();
const [location, setLocation] = useLocation();
const classFilter = new URLSearchParams(queryString);

useEffect(() => {
if (queryString === "") {
setLocation("/levels");
}

const fetchData = async () => {
setLoading(true);
const classData = await getClasses(classFilter.toString());
setClasses(classData);
const levelData = await getLevels();
setAllLevels(levelData);
setLevel(levelData.find(l => l.level === parseInt(classFilter.get("level"))));
setLoading(false);
};
fetchData();
}, []);

if (loading || !level) return <></>;

return (
<div className="min-h-screen bg-white">
{/* Banner Section */}
<div className="bg-gradient-to-r from-[#FFFFFF] from-5% via-[#D3EDFC] via-35% via-[#B2A0FA] via-75% via-[#8AC7F4] to-[#3F96EA] py-12">
<div className="max-w-7xl mx-auto px-8">
<p className="text-lg text-dark-blue-700 mb-2">Level {level.level}</p>
<h1 className='text-4xl font-bold text-dark-blue-800 mb-4'>{level.name}</h1>

<div className="flex items-center gap-2 mb-6">
<div className="flex -space-x-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-300 to-blue-400 border-2 border-white"></div>
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-blue-200 to-blue-300 border-2 border-white"></div>
</div>
<span className="text-neutral-400">{level.enrolledCount || 46} students enrolled</span>
</div>

<p className="text-neutral-600 max-w-2xl mb-8">
This class is for those with little to no experience in English. It will be going over
the alphabet, basic vocabulary, and simple grammar rules.
</p>

<div className="flex gap-4">
<span className="px-6 py-2.5 bg-white rounded-full text-neutral-500 text-sm hover:bg-opacity-90 transition-colors">
Basic Conversations
</span>
<span className="px-6 py-2.5 bg-white rounded-full text-neutral-500 text-sm hover:bg-opacity-90 transition-colors">
The Alphabet
</span>
</div>
</div>
</div>

{/* Content Section */}
<div className="max-w-7xl mx-auto px-8 py-12">
{/* Create/Edit Class Form */}
<div className="mb-20">
<CreateClassForm />
</div>

{/* Open Classes */}
<div className="mb-20">
<h2 className="text-2xl font-bold text-dark-blue-800 mb-4">Open Classes</h2>
<p className="text-neutral-600 mb-8">
Here are the open classes in this level. More information will be given by the instructor after you sign up!
</p>
<div className='grid grid-cols-1 md:grid-cols-4 gap-6'>
{classes.map((classObj, classIndex) => (
<Class key={classIndex} classObj={classObj} />
))}
</div>
</div>

{/* Other Levels */}
<div>
<h2 className="text-2xl font-bold text-dark-blue-800 mb-4">Other Levels</h2>
<p className="text-neutral-600 mb-8">
If this level isn't a good fit for you, take a look at the other available levels!
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{allLevels
.filter(l => l.level !== level.level)
.map((level, index) => (
<div key={index} className="bg-white shadow-level rounded-lg border border-neutral-100 overflow-hidden hover:shadow-md transition-shadow">
<div className="p-6">
<h3 className="text-lg font-semibold text-dark-blue-800 mb-2">Level {level.level}</h3>
<p className="text-neutral-600 text-sm mb-4">{level.description || "New Concept Book 1 -- Ch. 1-72"}</p>
<button
onClick={() => setLocation(`/classes?level=${level.level}`)}
className="text-blue-500 text-sm font-medium hover:text-blue-600"
>
View Level →
</button>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};

export default Classes;

0 comments on commit 777b56d

Please sign in to comment.