-
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.
# Conflicts: # clients/core/src/LandingPage/LandingPage.tsx # clients/core/src/management/ManagementConsole.tsx # clients/core/src/network/course.ts # clients/core/src/network/queries/course.ts # clients/shared_library/network/course.ts
- Loading branch information
Showing
32 changed files
with
1,715 additions
and
25 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
138 changes: 138 additions & 0 deletions
138
clients/core/src/Course/AddingCourse/AddCourseAppearance.tsx
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,138 @@ | ||
import React from 'react' | ||
import { Button } from '@/components/ui/button' | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
} from '@/components/ui/form' | ||
import { | ||
Select, | ||
SelectContent, | ||
SelectItem, | ||
SelectTrigger, | ||
SelectValue, | ||
} from '@/components/ui/select' | ||
import { useForm } from 'react-hook-form' | ||
import { | ||
CourseAppearanceFormValues, | ||
courseAppearanceFormSchema, | ||
} from '../../validations/courseAppearance' | ||
import { zodResolver } from '@hookform/resolvers/zod' | ||
import { IconSelector } from './components/IconSelector' | ||
import DynamicIcon from '@/components/DynamicIcon' | ||
|
||
const subtleColors = [ | ||
'bg-red-100', | ||
'bg-yellow-100', | ||
'bg-green-100', | ||
'bg-blue-100', | ||
'bg-indigo-100', | ||
'bg-purple-100', | ||
'bg-pink-100', | ||
'bg-orange-100', | ||
'bg-teal-100', | ||
'bg-cyan-100', | ||
] | ||
|
||
interface AddCourseAppearanceProps { | ||
onBack: () => void | ||
onSubmit: (data: CourseAppearanceFormValues) => void | ||
} | ||
|
||
export const AddCourseAppearance: React.FC<AddCourseAppearanceProps> = ({ onBack, onSubmit }) => { | ||
const form = useForm<CourseAppearanceFormValues>({ | ||
resolver: zodResolver(courseAppearanceFormSchema), | ||
defaultValues: { | ||
color: '', | ||
icon: '', | ||
}, | ||
}) | ||
|
||
const selectedColor = form.watch('color') | ||
const selectedIcon = form.watch('icon') | ||
|
||
return ( | ||
<Form {...form}> | ||
<h3 className='text-lg font-semibold mb-2'>Preview</h3> | ||
<div className='flex items-center space-x-4'> | ||
<div | ||
className={` | ||
relative flex aspect-square size-12 items-center justify-center | ||
after:absolute after:inset-0 after:rounded-lg after:border-2 after:border-primary | ||
`} | ||
> | ||
<div | ||
className={` | ||
flex aspect-square items-center justify-center rounded-lg text-gray-800 | ||
size-12 | ||
${selectedColor || 'bg-gray-100'} | ||
`} | ||
> | ||
<DynamicIcon name={selectedIcon || 'circle-help'} /> | ||
</div> | ||
</div> | ||
<span className='text-sm text-gray-600'> | ||
This is how your course icon will appear in the sidebar. <br /> | ||
<b>Please note:</b> it will only be displayed while the course is active. | ||
</span> | ||
</div> | ||
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'> | ||
<FormField | ||
control={form.control} | ||
name='color' | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Select a Color</FormLabel> | ||
<Select onValueChange={field.onChange} defaultValue={field.value}> | ||
<FormControl> | ||
<SelectTrigger> | ||
<SelectValue placeholder='Select a color' /> | ||
</SelectTrigger> | ||
</FormControl> | ||
<SelectContent> | ||
{subtleColors.map((color) => ( | ||
<SelectItem key={color} value={color}> | ||
<div className='flex items-center'> | ||
<div className={`w-4 h-4 rounded mr-2 ${color}`}></div> | ||
{color.split('-')[1]} | ||
</div> | ||
</SelectItem> | ||
))} | ||
</SelectContent> | ||
</Select> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name='icon' | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Select an Icon</FormLabel> | ||
<Select onValueChange={field.onChange} defaultValue={field.value}> | ||
<FormControl> | ||
<SelectTrigger> | ||
<SelectValue placeholder='Select an icon' /> | ||
</SelectTrigger> | ||
</FormControl> | ||
<IconSelector /> | ||
</Select> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<div className='flex justify-between space-x-2'> | ||
<Button type='button' variant='outline' onClick={onBack}> | ||
Back | ||
</Button> | ||
<Button type='submit'>Add Course</Button> | ||
</div> | ||
</form> | ||
</Form> | ||
) | ||
} |
132 changes: 132 additions & 0 deletions
132
clients/core/src/Course/AddingCourse/AddCourseDialog.tsx
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,132 @@ | ||
import React, { useCallback, useEffect } from 'react' | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger, | ||
} from '@/components/ui/dialog' | ||
import { CourseFormValues } from '../../validations/course' | ||
import { AddCourseProperties } from './AddCourseProperties' | ||
import { AddCourseAppearance } from './AddCourseAppearance' | ||
import { CourseAppearanceFormValues } from 'src/validations/courseAppearance' | ||
import { useMutation, useQueryClient } from '@tanstack/react-query' | ||
import { PostCourse } from '@/interfaces/post_course' | ||
import { postNewCourse } from '../../network/mutations/postNewCourse' | ||
import { useNavigate } from 'react-router-dom' | ||
import { AlertCircle, Loader2 } from 'lucide-react' | ||
|
||
interface AddCourseDialogProps { | ||
children: React.ReactNode | ||
} | ||
|
||
export const AddCourseDialog: React.FC<AddCourseDialogProps> = ({ children }) => { | ||
const [isOpen, setIsOpen] = React.useState(false) | ||
const [currentPage, setCurrentPage] = React.useState(1) | ||
const [coursePropertiesFormValues, setCoursePropertiesFormValues] = | ||
React.useState<CourseFormValues | null>(null) | ||
|
||
const queryClient = useQueryClient() | ||
const navigate = useNavigate() | ||
|
||
const { mutate, isPending, error, isError, reset } = useMutation({ | ||
mutationFn: (course: PostCourse) => { | ||
return postNewCourse(course) | ||
}, | ||
onSuccess: (data: string | undefined) => { | ||
console.log('Received ID' + data) | ||
queryClient.invalidateQueries({ queryKey: ['courses'] }) | ||
|
||
setIsOpen(false) | ||
setIsOpen(false) | ||
navigate(`/management/course/${data}`) | ||
}, | ||
}) | ||
|
||
const onSubmit = (data: CourseAppearanceFormValues) => { | ||
const course: PostCourse = { | ||
name: coursePropertiesFormValues?.name || '', | ||
start_date: coursePropertiesFormValues?.dateRange?.from || new Date(), | ||
end_date: coursePropertiesFormValues?.dateRange?.to || new Date(), | ||
course_type: coursePropertiesFormValues?.course_type || '', | ||
ects: coursePropertiesFormValues?.ects || 0, | ||
semester_tag: coursePropertiesFormValues?.semester_tag || '', | ||
// eslint-disable-next-line prettier/prettier | ||
meta_data: { icon: data.icon, 'bg-color': data.color }, | ||
} | ||
|
||
console.log(course) | ||
// todo API call | ||
mutate(course) | ||
|
||
setCurrentPage(1) | ||
setCoursePropertiesFormValues(null) // reset the page 1 form | ||
} | ||
|
||
const handleNext = (data) => { | ||
setCoursePropertiesFormValues(data) | ||
setCurrentPage(2) | ||
} | ||
|
||
const handleBack = () => { | ||
setCurrentPage(1) | ||
} | ||
|
||
const handleCancel = useCallback(() => { | ||
setIsOpen(false) | ||
}, []) | ||
|
||
// makes sure that window is first closed, before resetting the form | ||
useEffect(() => { | ||
if (!isOpen) { | ||
reset() | ||
setCoursePropertiesFormValues(null) | ||
setCurrentPage(1) | ||
} | ||
}, [isOpen, reset]) | ||
|
||
const handleOpenChange = (open: boolean) => { | ||
if (!open) { | ||
handleCancel() | ||
} else { | ||
setIsOpen(open) | ||
} | ||
} | ||
|
||
return ( | ||
<Dialog open={isOpen} onOpenChange={handleOpenChange}> | ||
<DialogTrigger asChild>{children}</DialogTrigger> | ||
<DialogContent className='sm:max-w-[550px]'> | ||
{isPending ? ( | ||
<div className='flex flex-col items-center justify-center h-48'> | ||
<Loader2 className='h-10 w-10 text-primary animate-spin' /> | ||
<p className='mt-4 text-lg font-medium text-muted-foreground'>Loading course data...</p> | ||
</div> | ||
) : isError ? ( | ||
<div className='flex flex-col items-center justify-center h-48'> | ||
<AlertCircle className='h-10 w-10 text-destructive' /> | ||
<p className='mt-4 text-lg font-medium text-destructive'>Error: {error.message}</p> | ||
<p className='mt-2 text-sm text-muted-foreground'> | ||
Please try again or contact support if the problem persists. | ||
</p> | ||
</div> | ||
) : ( | ||
<> | ||
<DialogHeader> | ||
<DialogTitle className='text-2xl font-bold text-center'>Add a New Course</DialogTitle> | ||
</DialogHeader> | ||
{currentPage === 1 ? ( | ||
<AddCourseProperties | ||
onNext={handleNext} | ||
onCancel={handleCancel} | ||
initialValues={coursePropertiesFormValues || undefined} | ||
/> | ||
) : ( | ||
<AddCourseAppearance onBack={handleBack} onSubmit={onSubmit} /> | ||
)} | ||
</> | ||
)} | ||
</DialogContent> | ||
</Dialog> | ||
) | ||
} |
Oops, something went wrong.