Skip to content

Commit

Permalink
BREAKING CHANGES: db rename all chinese columns to english.
Browse files Browse the repository at this point in the history
feat(CourseDetails): updated UI for selecting courses
  • Loading branch information
ImJustChew committed Nov 6, 2023
1 parent d09cd22 commit 4c7cd19
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 121 deletions.
61 changes: 44 additions & 17 deletions src/app/[lang]/courses/[courseId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import Fade from "@/components/Animation/Fade";
import supabase from "@/config/supabase";
import { getDictionary } from "@/dictionaries/dictionaries";
import {getCourse, getCoursePTTReview} from '@/lib/course';
import { LangProps } from "@/types/pages";
import { Accordion, AccordionDetails, AccordionGroup, AccordionSummary, Alert, Chip, Divider } from "@mui/joy";
import {Accordion, AccordionDetails, AccordionGroup, AccordionSummary, Alert, Chip, Divider, Button} from '@mui/joy';
import { format } from "date-fns";
import { NextPage, ResolvingMetadata } from "next";
import { AlertTriangle } from "react-feather";
import { ResolvingMetadata } from "next";
import {AlertTriangle, Minus, Plus} from 'react-feather';
import { redirect } from 'next/navigation'
import CourseTagList from "@/components/Courses/CourseTagsList";
import {useSettings} from '@/hooks/contexts/settings';
import { useMemo } from "react";
import SelectCourseButton from '@/components/Courses/SelectCourseButton';
import { createTimetableFromCourses } from "@/helpers/timetable";
import Timetable from "@/components/Timetable/Timetable";
import { MinimalCourse } from "@/types/courses";

type PageProps = {
params: { courseId? : string }
Expand All @@ -27,25 +34,45 @@ const CourseDetailPage = async ({ params }: PageProps & LangProps) => {
const reviews = await getCoursePTTReview(courseId);
const dict = await getDictionary(params.lang);

if(!course) return redirect('/');

const timetableData = createTimetableFromCourses([course as MinimalCourse]);

return <Fade>
<div className="grid grid-cols-1 lg:grid-cols-[auto_320px] py-6 px-4">
<div className="grid grid-cols-1 lg:grid-cols-[auto_320px] py-6 px-4 text-gray-500 dark:text-gray-300">
<div className="space-y-2">
<h1 className="font-bold text-3xl mb-4 text-fuchsia-800">{`${course?.department} ${course?.course}-${course?.class}`}</h1>
<h2 className="font-semibold text-3xl text-gray-500 dark:text-gray-300 mb-2">{course!.name_zh} - {course?.teacher_zh?.join(',')?? ""}</h2>
<h2 className="font-semibold text-xl text-gray-500 dark:text-gray-300">{course!.name_en} - {course?.teacher_en?.join(',')?? ""}</h2>

<p>{dict.course.details.semesterid}: {course?.raw_id}{dict.course.credits}: {course?.credits}</p>
<p>{dict.course.details.language}: {course?.language == '英' ? 'English' : 'Chinese'}</p>
<p>{dict.course.details.capacity}: {course?.capacity}{dict.course.details.reserved}: {course?.reserve}</p>
<p>{dict.course.details.class}: {course?.class}</p>
<div className="flex flex-row items-end">
<div className="space-y-4 flex-1">
<div className="space-y-2">
<h4 className="font-semibold text-base text-gray-300">{course.semester} 學期</h4>
<h1 className="font-bold text-3xl mb-4 text-[#AF7BE4]">{`${course?.department} ${course?.course}-${course?.class}`}</h1>
<h2 className="font-semibold text-3xl text-gray-500 dark:text-gray-300 mb-2">{course!.name_zh} - {course?.teacher_zh?.join(',')?? ""}</h2>
<h2 className="font-semibold text-xl text-gray-500 dark:text-gray-300">{course!.name_en} - {course?.teacher_en?.join(',')?? ""}</h2>
</div>
<CourseTagList course={course}/>
</div>
<div className="space-y-4 w-[min(100vh,320px)]">
<div className="">
<h3 className="font-semibold text-base mb-2">時間地點</h3>
{course.venues?
course.venues.map((vn, i) => <p key={vn} className='text-blue-600 dark:text-blue-400 text-sm'>{vn} <span className='text-black dark:text-white'>{course.times![i]}</span></p>) :
<p>No Venues</p>
}
</div>
<SelectCourseButton courseId={course.raw_id}/>
</div>
</div>
<Divider/>
<div className="grid grid-cols-1 md:grid-cols-[auto_320px] gap-6">
<div className="grid grid-cols-1 md:grid-cols-[auto_320px] gap-6 ">
<div className="space-y-4">
<div className="">
<h3 className="font-semibold text-xl mb-2">{dict.course.details.description}</h3>
<p>{course?.備註}</p>
<p>{course?.note}</p>
</div>
<div className="">
<h3 className="font-semibold text-xl mb-2">時間表</h3>
<Timetable timetableData={timetableData}/>
</div>
<Divider/>
{reviews.length > 0 && <div className="">
<h3 className="font-semibold text-xl mb-2">{dict.course.details.ptt_title}</h3>
<Alert variant="soft" color="warning" className="mb-4" startDecorator={<AlertTriangle/>}>
Expand All @@ -66,7 +93,7 @@ const CourseDetailPage = async ({ params }: PageProps & LangProps) => {
<div className="space-y-2">
<div>
<h3 className="font-semibold text-base mb-2">{dict.course.details.restrictions}</h3>
<p>{course?.課程限制說明}</p>
<p>{course?.restrictions}</p>
</div>
<div>
<h3 className="font-semibold text-base mb-2">{dict.course.details.compulsory}</h3>
Expand Down
96 changes: 13 additions & 83 deletions src/components/Courses/CourseListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
'use client';
import { CourseDefinition } from '@/config/supabase';
import useDictionary from '@/dictionaries/useDictionary';
import { useSettings } from '@/hooks/contexts/settings';
import { Button, Chip, Tooltip } from '@mui/joy';
import { Button, Tooltip } from '@mui/joy';
import { FC, useMemo } from 'react';
import { Minus, Plus } from 'react-feather';
import Link from 'next/link';
import { DetailedHTMLProps, FC, HTMLAttributes, PropsWithChildren, useMemo } from 'react';
import { Minus, Plus, Users } from 'react-feather';
import {getGECType} from '@/helpers/courses';
import CourseTagList from './CourseTagsList';
import SelectCourseButton from './SelectCourseButton';

const HighlightItem: FC<PropsWithChildren<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>> = ({
children,
className = "",
...props
}) => {
return <div
className={`flex flex-row items-center justify-center min-w-[65px] space-x-2 px-2 py-2 select-none rounded-md bg-indigo-50 text-indigo-900 dark:bg-indigo-950 dark:text-indigo-100 ${className}`}
{...props}
>
{children}
</div>
}

const CourseListItem: FC<{ course: CourseDefinition }> = ({ course }) => {
const { courses, setCourses } = useSettings();
const dict = useDictionary();
const isCourseSelected = useMemo(() => courses.includes(course.raw_id ?? ""), [courses, course]);

return <div className="text-gray-600 dark:text-gray-400 px-4 border-b border-gray-200 dark:border-neutral-800 pb-4">
<div className="grid grid-cols-1 lg:grid-rows-none lg:grid-cols-[auto_250px] gap-4">
<div className='flex-1 space-y-4'>
Expand All @@ -32,12 +21,12 @@ const CourseListItem: FC<{ course: CourseDefinition }> = ({ course }) => {
<h3 className="text-sm text-gray-800 dark:text-gray-300 mt-0 break-words">{course.name_en} - <span className='w-max'>{(course.teacher_en ?? []).join(',')}</span></h3>
</div>
<div className="space-y-1 text-black dark:text-neutral-200">
<p className='text-sm whitespace-pre-line'>{course.課程限制說明}</p>
<p className='text-sm whitespace-pre-line'>{course.備註}</p>
{course.擋修說明 &&
<p className='text-sm whitespace-pre-line'>{course.restrictions}</p>
<p className='text-sm whitespace-pre-line'>{course.note}</p>
{course.prerequisites &&
<Tooltip
placement='bottom-start'
title={<p dangerouslySetInnerHTML={{ __html: course.擋修說明}}></p>}
title={<p dangerouslySetInnerHTML={{ __html: course.prerequisites}}></p>}
>
<p className='text-sm underline text-orange-600 select-none'>有儅修</p>
</Tooltip>}
Expand All @@ -51,67 +40,8 @@ const CourseListItem: FC<{ course: CourseDefinition }> = ({ course }) => {
<p>No Venues</p>
}
</div>
<div className='flex flex-row flex-wrap gap-1 text-sm'>
<HighlightItem>
<span className="">
{course.capacity ?? '-'}
{(course.reserve ?? 0) > 0 && <>
{` 保 ${course.reserve}`}
</>}
</span>

<Users className='w-5 h-5'/>
</HighlightItem>
<HighlightItem>
<span className="">{course.credits}</span>
<span className="">{dict.course.credits}</span>
</HighlightItem>
{course.tags.includes('16周') && <HighlightItem>
<span className="">16 週</span>
</HighlightItem>}
{course.tags.includes('18周') && <HighlightItem>
<span className="">18 週</span>
</HighlightItem>}
{course.language == '英' ?
<HighlightItem className='bg-cyan-50 text-cyan-900 dark:bg-cyan-950 dark:text-cyan-100'>
English
</HighlightItem>:
<HighlightItem className='bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-100'>
國語
</HighlightItem>
}
{/* bg-indigo-50 text-indigo-900 dark:bg-indigo-950 dark:text-indigo-100 */}
{course.tags.includes('X-Class') &&
<HighlightItem className='bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-100'>
X-Class
</HighlightItem>}
{(course.ge_target?.trim() || "").length > 0 &&
<HighlightItem className='bg-pink-50 text-pink-900 dark:bg-pink-950 dark:text-pink-100'>
{course.ge_target} 通識
</HighlightItem>}
{getGECType(course.ge_type || "") &&
<HighlightItem className='bg-green-50 text-green-900 dark:bg-green-950 dark:text-green-100'>
核通 {getGECType(course.ge_type!)}
</HighlightItem>}


</div>
{isCourseSelected ?
<Button
color="danger"
variant="outlined"
onClick={() => setCourses(courses => courses.filter(m => m != course.raw_id))}
startDecorator={<Minus/>}
>
{dict.course.item.remove_from_semester}
</Button> :
<Button
variant="outlined"
onClick={() => setCourses(courses => [...courses, course.raw_id ?? ""])}
startDecorator={<Plus/>}
>
{dict.course.item.add_to_semester}
</Button>}
<CourseTagList course={course}/>
<SelectCourseButton courseId={course.raw_id}/>
</div>
</div>
</div>
Expand Down
71 changes: 71 additions & 0 deletions src/components/Courses/CourseTagsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';
import { CourseDefinition } from '@/config/supabase';
import useDictionary from '@/dictionaries/useDictionary';
import { getGECType } from '@/helpers/courses';
import { DetailedHTMLProps, FC, HTMLAttributes, PropsWithChildren } from 'react';
import { Users } from 'react-feather';

const HighlightItem: FC<PropsWithChildren<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>> = ({
children,
className = "",
...props
}) => {
return <div
className={`flex flex-row items-center justify-center min-w-[65px] space-x-2 px-2 py-2 select-none rounded-md bg-indigo-50 text-indigo-900 dark:bg-indigo-950 dark:text-indigo-100 ${className}`}
{...props}
>
{children}
</div>
}
const CourseTagList = ({ course }: { course: CourseDefinition }) => {
const dict = useDictionary();
return (
<div className='flex flex-row flex-wrap gap-1 text-sm'>
<HighlightItem>
<span className="">
{course.capacity ?? '-'}
{(course.reserve ?? 0) > 0 && <>
{` 保 ${course.reserve}`}
</>}
</span>

<Users strokeWidth={2.5} className='w-5 h-5' />
</HighlightItem>
<HighlightItem>
<span className="">{course.credits}</span>
<span className="">{dict.course.credits}</span>
</HighlightItem>
{course.tags.includes('16周') && <HighlightItem>
<span className="">16 週</span>
</HighlightItem>}
{course.tags.includes('18周') && <HighlightItem>
<span className="">18 週</span>
</HighlightItem>}
{course.language == '英' ?
<HighlightItem className='bg-cyan-50 text-cyan-900 dark:bg-cyan-950 dark:text-cyan-100'>
English
</HighlightItem> :
<HighlightItem className='bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-100'>
國語
</HighlightItem>
}
{/* bg-indigo-50 text-indigo-900 dark:bg-indigo-950 dark:text-indigo-100 */}
{course.tags.includes('X-Class') &&
<HighlightItem className='bg-red-50 text-red-900 dark:bg-red-950 dark:text-red-100'>
X-Class
</HighlightItem>}
{(course.ge_target?.trim() || "").length > 0 &&
<HighlightItem className='bg-pink-50 text-pink-900 dark:bg-pink-950 dark:text-pink-100'>
{course.ge_target} 通識
</HighlightItem>}
{getGECType(course.ge_type || "") &&
<HighlightItem className='bg-green-50 text-green-900 dark:bg-green-950 dark:text-green-100'>
核通 {getGECType(course.ge_type!)}
</HighlightItem>}


</div>
)
}

export default CourseTagList;
32 changes: 32 additions & 0 deletions src/components/Courses/SelectCourseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';
import useDictionary from "@/dictionaries/useDictionary";
import { useSettings } from "@/hooks/contexts/settings"
import { RawCourseID } from "@/types/courses";
import { Button } from "@mui/joy";
import { useMemo } from "react";
import { Minus, Plus } from "react-feather";

const SelectCourseButton = ({ courseId }: { courseId: RawCourseID }) => {
const { courses, setCourses } = useSettings();
const dict = useDictionary();

const isCourseSelected = useMemo(() => courses.includes(courseId), [courses, courseId]);

if(isCourseSelected) return <Button
color="danger"
variant="outlined"
onClick={() => setCourses(courses => courses.filter(m => m != courseId))}
startDecorator={<Minus/>}
>
{dict.course.item.remove_from_semester}
</Button>
else return <Button
variant="outlined"
onClick={() => setCourses(courses => [...courses, courseId])}
startDecorator={<Plus/>}
>
{dict.course.item.add_to_semester}
</Button>
}

export default SelectCourseButton;
2 changes: 1 addition & 1 deletion src/components/FormComponents/SelectControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const SelectControl = <T extends FieldValues, OptionValue extends {}>({ control,
onChange={(e,v) => onChange(v)}
{...rest}
>
{options.map(option => <Option value={option.value}>{option.label}</Option>)}
{options.map(option => <Option key={option.value} value={option.value}>{option.label}</Option>)}
</Select>
)}
/>
Expand Down
40 changes: 20 additions & 20 deletions src/types/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,11 +610,11 @@ export interface Database {
teacher_zh: string[]
times: string[]
venues: string[]
停開註記: string | null
備註: string | null
必選修說明: string | null
擋修說明: string | null
課程限制說明: string | null
closed_mark: string | null
note: string | null
raw_compulsory_elective: string | null
prerequisites: string | null
restrictions: string | null
multilang_search: string | null
time_slots: unknown | null
}
Expand Down Expand Up @@ -651,11 +651,11 @@ export interface Database {
teacher_zh: string[]
times: string[]
venues: string[]
停開註記?: string | null
備註?: string | null
必選修說明?: string | null
擋修說明?: string | null
課程限制說明?: string | null
closed_mark?: string | null
note?: string | null
raw_compulsory_elective?: string | null
prerequisites?: string | null
restrictions?: string | null
}
Update: {
capacity?: number | null
Expand Down Expand Up @@ -690,11 +690,11 @@ export interface Database {
teacher_zh?: string[]
times?: string[]
venues?: string[]
停開註記?: string | null
備註?: string | null
必選修說明?: string | null
擋修說明?: string | null
課程限制說明?: string | null
closed_mark?: string | null
note?: string | null
raw_compulsory_elective?: string | null
prerequisites?: string | null
restrictions?: string | null
}
Relationships: []
}
Expand Down Expand Up @@ -1381,11 +1381,11 @@ export interface Database {
teacher_zh: string[]
times: string[]
venues: string[]
停開註記: string | null
備註: string | null
必選修說明: string | null
擋修說明: string | null
課程限制說明: string | null
closed_mark: string | null
note: string | null
raw_compulsory_elective: string | null
prerequisites: string | null
restrictions: string | null
}[]
}
set_limit: {
Expand Down

0 comments on commit 4c7cd19

Please sign in to comment.