-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from JumboCode/create_edit_classes
Create edit classes
- Loading branch information
Showing
4 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |