diff --git a/.eslintrc.json b/.eslintrc.json index 3724af8..b1d40e9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,10 +5,14 @@ "prettier/prettier": ["error", {"endOfLine": "auto"}], "node/no-unsupported-features/es-syntax": [ "error", - {"ignores": ["modules"]} + { + "version": "18.17.0", + "ignores": ["modules"] + } ], "node/no-unpublished-import": "off", - "node/no-missing-import": "off" + "node/no-missing-import": "off", + "node/no-unsupported-features/node-builtins": "off" }, "env": { "browser": true, diff --git a/next.config.js b/next.config.js index cdf82b6..b6af18b 100644 --- a/next.config.js +++ b/next.config.js @@ -5,7 +5,11 @@ module.exports = { return config; }, images: { - domains: ['lh3.googleusercontent.com', 'd3sbrbqucv1146.cloudfront.net'], + domains: [ + 'lh3.googleusercontent.com', + 'd3sbrbqucv1146.cloudfront.net', + 'd1s5j3nmszux84.cloudfront.net', + ], }, reactStrictMode: false, }; diff --git a/package.json b/package.json index 921cf50..be9bcf7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "axios": "^1.6.5", "next": "14.0.4", "react": "^18", + "react-calendar": "^4.8.0", "react-dom": "^18", "react-infinite-scroller": "^1.2.6", "react-markdown": "^9.0.1", diff --git a/src/api/classSchedule/deleteClassSchedule.ts b/src/api/classSchedule/deleteClassSchedule.ts new file mode 100644 index 0000000..81ec489 --- /dev/null +++ b/src/api/classSchedule/deleteClassSchedule.ts @@ -0,0 +1,13 @@ +import req from '../apiUtils'; + +const deleteClassSchedule = async (sid: number, cid: number, uid: number) => { + const response = await req( + `/cs/${sid}?cid=${cid}&uid=${uid}`, + 'delete', + 'gin' + ); + + return response.data; +}; + +export default deleteClassSchedule; diff --git a/src/api/classSchedule/getClassSchedule.ts b/src/api/classSchedule/getClassSchedule.ts new file mode 100644 index 0000000..79b6add --- /dev/null +++ b/src/api/classSchedule/getClassSchedule.ts @@ -0,0 +1,9 @@ +import req from '../apiUtils'; + +const getClassSchedule = async (scheduleCode: number) => { + const response = await req(`/cs/${scheduleCode}`, 'get', 'gin'); + + return response.data; +}; + +export default getClassSchedule; diff --git a/src/api/classSchedule/getClassScheduleList.ts b/src/api/classSchedule/getClassScheduleList.ts new file mode 100644 index 0000000..cf0b023 --- /dev/null +++ b/src/api/classSchedule/getClassScheduleList.ts @@ -0,0 +1,9 @@ +import req from '../apiUtils'; + +const getClassScheduleList = async (classCode: number) => { + const response = await req(`/cs?cid=${classCode}`, 'get', 'gin'); + + return response; +}; + +export default getClassScheduleList; diff --git a/src/api/classSchedule/index.ts b/src/api/classSchedule/index.ts new file mode 100644 index 0000000..c3aa254 --- /dev/null +++ b/src/api/classSchedule/index.ts @@ -0,0 +1,15 @@ +import getClassSchedule from './getClassSchedule'; +import getClassScheduleList from './getClassScheduleList'; +import postCreateClassSchedule from './postCreateClassSchedule'; +import deleteClassSchedule from './deleteClassSchedule'; +import putClassSchedule from './putClassSchedule'; + +const classScheduleAPI = { + getClassSchedule, + getClassScheduleList, + postCreateClassSchedule, + deleteClassSchedule, + putClassSchedule, +}; + +export default classScheduleAPI; diff --git a/src/api/classSchedule/postCreateClassSchedule.ts b/src/api/classSchedule/postCreateClassSchedule.ts new file mode 100644 index 0000000..8fc8eb1 --- /dev/null +++ b/src/api/classSchedule/postCreateClassSchedule.ts @@ -0,0 +1,18 @@ +import req from '../apiUtils'; +import {PostCreateScheduleData} from '@/src/interfaces/api/_class'; + +const postCreateClassSchedule = async (postData: PostCreateScheduleData) => { + const data = { + cid: postData.cid, + ended_at: postData.ended_at, + is_live: postData.is_live, + started_at: postData.started_at, + title: postData.title, + }; + + const response = await req(`/cs?uid=${postData.uid}`, 'post', 'gin', data); + + return response; +}; + +export default postCreateClassSchedule; diff --git a/src/api/classSchedule/putClassSchedule.ts b/src/api/classSchedule/putClassSchedule.ts new file mode 100644 index 0000000..44b9c62 --- /dev/null +++ b/src/api/classSchedule/putClassSchedule.ts @@ -0,0 +1,21 @@ +import req from '../apiUtils'; +import {PutClassScheduleData} from '@/src/interfaces/api/_class'; + +const putClassSchedule = async (requestData: PutClassScheduleData) => { + const requestForm = { + ended_at: requestData.ended_at, + is_live: requestData.is_live, + started_at: requestData.started_at, + title: requestData.title, + }; + const response = await req( + `/cs/${requestData.sid}?cid=${requestData.cid}&uid=${requestData.uid}`, + 'put', + 'gin', + requestForm + ); + + return response; +}; + +export default putClassSchedule; diff --git a/src/app/[className]/components/card/ScheduleCard.tsx b/src/app/[className]/components/card/ScheduleCard.tsx index 7b93e0a..bd3cb5f 100644 --- a/src/app/[className]/components/card/ScheduleCard.tsx +++ b/src/app/[className]/components/card/ScheduleCard.tsx @@ -1,25 +1,82 @@ -import Image from 'next/image'; +'use client'; + +import React, {useState} from 'react'; +import {ClassEditSchedule} from '../modal'; +import {Dropdown} from '@/src/app/components/_class/dropdown'; import {ScheduleCardProps} from '@/src/interfaces/_class'; import icons from '@/public/svgs/_class'; -const ScheduleCard = ({scheduleName, time, managerRole}: ScheduleCardProps) => { +const ScheduleCard = ({ + scheduleName, + startTime, + endTime, + managerRole, + isLive, + zIndex, + scheduleId, + deleteSchedule, +}: ScheduleCardProps & {zIndex: number} & { + deleteSchedule: (scheduleId: number) => void; +}) => { + const dropdownItems = [ + { + modalId: 'scheduleEdit', + icon: icons.edit, + alt: 'Edit Icon', + text: 'Edit', + }, + { + modalId: 'scheduleDelete', + icon: icons.delete, + alt: 'Delete Icon', + text: 'Delete', + }, + ]; + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedModalId, setSelectedModalId] = useState(null); + + const setActiveModalId = (modalId: string) => { + setSelectedModalId(modalId); + setIsModalOpen(true); + if (modalId === 'scheduleDelete') { + deleteSchedule(scheduleId); + } + }; + return ( <>
-
-
-

{scheduleName}

+
+
+

+ {isLive ? `[Live] ${scheduleName}` : `${scheduleName}`} +

{managerRole && ( - {'moreVert'} + <> +
+ +
+ )}
-

{time}

+
+

Date

+

{startTime}

+

{endTime}

+
+ {isModalOpen && selectedModalId === 'scheduleEdit' && ( + + )}
); diff --git a/src/app/[className]/components/main/Main.tsx b/src/app/[className]/components/main/Main.tsx index 8dce88a..69df91d 100644 --- a/src/app/[className]/components/main/Main.tsx +++ b/src/app/[className]/components/main/Main.tsx @@ -1,13 +1,105 @@ +'use client'; + +import {useState} from 'react'; import Image from 'next/image'; import {Notice, Post, Schedule} from '.'; +import { + ClassCreatePost, + ClassCreateSchedule, +} from '@/src/app/[className]/components/modal'; import {RoleProps} from '@/src/interfaces/_class'; import icons from '@/public/svgs/_class'; -const Main = ({managerRole}: RoleProps) => { +const Main = ({managerRole, classId}: RoleProps) => { + const [showDropdown, setShowDropdown] = useState>({ + Notice: false, + Schedule: false, + Posts: false, + }); + const [showScheduleModal, setShowScheduleModal] = useState(false); + const [showPostModal, setShowPostModal] = useState(false); + + const handleDropdown = (sectionTitle: string) => { + setShowDropdown(prevState => { + const newState = Object.assign({}, prevState); + newState[sectionTitle] = !prevState[sectionTitle]; + return newState; + }); + }; + + const handleOpenScheduleModal = () => { + setShowDropdown(prevState => ({...prevState, Schedule: true})); + setShowScheduleModal(true); + }; + + const handleCloseScheduleModal = () => { + setShowDropdown(prevState => ({...prevState, Schedule: false})); + setShowScheduleModal(false); + }; + + const handleOpenPostModal = () => { + setShowDropdown(prevState => ({ + ...prevState, + Posts: true, + Notice: !prevState['Notice'], + })); + setShowPostModal(true); + }; + + const handleClosePostModal = () => { + setShowDropdown(prevState => ({ + ...prevState, + Posts: false, + Notice: !prevState['Notice'], + })); + setShowPostModal(false); + }; + const mainSections = [ - {title: 'Notice', component: }, - {title: 'Schedule', component: }, - {title: 'Posts', component: }, + { + title: 'Notice', + component: ( + + ), + }, + { + title: 'Schedule', + component: ( + <> + + {showScheduleModal && ( + + )} + + ), + openModal: handleOpenScheduleModal, + }, + { + title: 'Posts', + component: ( + <> + + {showPostModal && ( + + )} + + ), + openModal: handleOpenPostModal, + }, ]; return ( <> @@ -20,24 +112,36 @@ const Main = ({managerRole}: RoleProps) => { alt={'dropdown'} width={0} height={0} - className="me-2 w-auto h-auto max-w-6 max-h-6" + className={`me-2 w-auto h-auto max-w-6 max-h-6 hover:cursor-pointer ${ + showDropdown[section.title] ? '' : '-rotate-90' + }`} + onClick={() => handleDropdown(section.title)} /> -
-

{section.title}

+
+

handleDropdown(section.title)} + > + {section.title} +

{managerRole && (section.title === 'Schedule' || section.title === 'Posts') && ( - {'addButton'} +
+ {'addButton'} +
)}
-
{section.component}
+
+ {showDropdown[section.title] &&
{section.component}
} +
))}
diff --git a/src/app/[className]/components/main/Schedule.tsx b/src/app/[className]/components/main/Schedule.tsx index 37fe16f..98bd4f8 100644 --- a/src/app/[className]/components/main/Schedule.tsx +++ b/src/app/[className]/components/main/Schedule.tsx @@ -1,25 +1,118 @@ +'use client'; + +import {useState, useEffect} from 'react'; import {ScheduleCard} from '../card'; +import getClassScheduleList from '@/src/api/classSchedule/getClassScheduleList'; +import getClassSchedule from '@/src/api/classSchedule/getClassSchedule'; +import DeleteClassSchedule from '@/src/api/classSchedule/deleteClassSchedule'; +import {ClassSchedule} from '@/src/app/[className]/components/modal'; import {RoleProps} from '@/src/interfaces/_class'; +import User from '@/src/model/User'; + +const Schedule = ({ + managerRole, + classId, + isOpen, +}: RoleProps & {isOpen: boolean}) => { + const deleteSchedule = async (scheduleId: number) => { + if (classId !== undefined) { + try { + if (confirm('Are you sure you want to delete this schedule?')) { + await DeleteClassSchedule(scheduleId, classId, User.uid); + alert('Schedule deleted successfully!'); + const schedules = await getClassScheduleList(classId); + if (Array.isArray(schedules.data)) { + setClassSchedules(schedules.data); + } else { + console.error('getClassSchedules did not return an array'); + } + } + } catch (error) { + console.error(error); + alert('Failed to delete schedule'); + } + } else { + alert('Failed to delete schedule'); + } + }; + const toKST = (date: string) => { + const utcDate = new Date(date); + const kstDate = new Date(utcDate.getTime() + 9 * 60 * 60); + return new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }).format(kstDate); + }; + const [classSchedules, setClassSchedules] = useState< + { + ID: number; + Title: string; + StartedAt: string; + EndedAt: string; + IsLive: boolean; + }[] + >([]); + const [selectedSchedule, setSelectedSchedule] = useState<{ + ID: number; + Title: string; + StartedAt: string; + EndedAt: string; + IsLive: boolean; + }>({ID: 0, Title: '', StartedAt: '', EndedAt: '', IsLive: false}); + const [isModalOpen, setIsModalOpen] = useState(false); -const Schedule = ({managerRole}: RoleProps) => { - const scheduleArray = [ - {scheduleName: 'Project Management1', time: 'Mar 3rd ~ 5th'}, - {scheduleName: 'Project Management2', time: 'Mar 6th ~ 9th'}, - {scheduleName: 'Project Management3', time: 'Mar 11th ~ 14th'}, - {scheduleName: 'Project Management4', time: 'Mar 15th ~ 18th'}, - {scheduleName: 'Project Management5', time: 'Mar 21st ~ 25th'}, - ]; + useEffect(() => { + if (classId !== undefined && isOpen) { + getClassScheduleList(classId).then(schedules => { + if (Array.isArray(schedules.data)) { + console.log(schedules.data); + setClassSchedules(schedules.data); + } else { + console.error('getClassSchedules did not return an array'); + } + }); + } + }, [classId, isOpen]); return ( -
- {scheduleArray.map((schedule, index) => ( - + {classSchedules.length === 0 && ( +
+
+

No Schedule in class

+
+
+ )} + {classSchedules.map((schedule, index) => ( +
+ onClick={async (event: React.MouseEvent) => { + event.stopPropagation; + setSelectedSchedule(await getClassSchedule(schedule.ID)); + setIsModalOpen(true); + }} + > + +
))} + {isModalOpen && ( + + )}
); }; diff --git a/src/app/[className]/components/modal/ClassCreateSchedule.tsx b/src/app/[className]/components/modal/ClassCreateSchedule.tsx new file mode 100644 index 0000000..4d4b974 --- /dev/null +++ b/src/app/[className]/components/modal/ClassCreateSchedule.tsx @@ -0,0 +1,189 @@ +'use client'; + +import React, {useState} from 'react'; +import ReactCalendar from 'react-calendar'; +import {useSearchParams} from 'next/navigation'; +import {TimePicker} from '@/src/app/[className]/components/schedule'; +import PostCreateClassSchedule from '@/src/api/classSchedule/postCreateClassSchedule'; +import {ClassScheduleShowProps} from '@/src/interfaces/_class/modal'; +import User from '@/src/model/User'; +import 'react-calendar/dist/Calendar.css'; +import '@/src/styles/calendar.css'; + +const ClassCreateSchedule = ({ + setShowScheduleModal, +}: ClassScheduleShowProps) => { + const [dates, setDates] = useState<[Date, Date]>([new Date(), new Date()]); + const [times, setTimes] = useState(['00:00', '00:00']); + const [checkboxChecked, setCheckboxChecked] = useState(false); + const [title, setTitle] = useState(''); + const searchParams = useSearchParams(); + const uid = User.uid; + const cid = Number(searchParams.get('id')); + + const handleDateChange = (newDates: Date | Date[]) => { + if (Array.isArray(newDates)) { + setDates([newDates[0], newDates[1]]); + } else { + setDates([newDates, newDates]); + } + }; + + const handleTimeChange = (index: number) => (newTime: string) => { + const newTimes = [...times]; + newTimes[index] = newTime; + setTimes(newTimes); + }; + + const resetDates = () => { + setDates([new Date(), new Date()]); + setTimes(['00:00', '00:00']); + setCheckboxChecked(false); + }; + + const handleCheckboxChange = () => { + setCheckboxChecked(!checkboxChecked); + }; + + const handleClose = () => { + setShowScheduleModal(false); + }; + + const toKSDate = (date: Date, index: number) => { + const kstOffset = 9 * 60; + const kstDate = new Date(date.getTime() + kstOffset * 60000); + const year = kstDate.getUTCFullYear(); + const month = String(kstDate.getUTCMonth() + 1).padStart(2, '0'); + const day = String(kstDate.getUTCDate()).padStart(2, '0'); + const time = String(times[index]); + const seconds = '00'; + + return `${year}-${month}-${day}T${time}:${seconds}+09:00`; + }; + + const handleCreate = async () => { + const postData = { + uid: uid, + cid: cid, + ended_at: toKSDate(new Date(dates[1]), 1), + is_live: checkboxChecked, + started_at: toKSDate(new Date(dates[0]), 0), + title: title, + }; + try { + await PostCreateClassSchedule(postData); + alert('Schedule created successfully'); + setShowScheduleModal(false); + } catch (error) { + alert('Failed to create schedule'); + console.error(error); + } + }; + + return ( +
+
+ +
+ ); +}; + +export default ClassCreateSchedule; diff --git a/src/app/[className]/components/modal/ClassEditSchedule.tsx b/src/app/[className]/components/modal/ClassEditSchedule.tsx new file mode 100644 index 0000000..6e55468 --- /dev/null +++ b/src/app/[className]/components/modal/ClassEditSchedule.tsx @@ -0,0 +1,145 @@ +import {SetStateAction, Dispatch, useState} from 'react'; +import {TimePicker} from '../schedule'; +import PutClassSchedule from '@/src/api/classSchedule/putClassSchedule'; + +const ClassEditSchedule = ({ + setShowScheduleModal, +}: { + setShowScheduleModal: Dispatch>; +}) => { + const [times, setTimes] = useState(['00:00', '00:00']); + const [checkboxChecked, setCheckboxChecked] = useState(false); + + const handleTimeChange = (index: number) => (newTime: string) => { + const newTimes = [...times]; + newTimes[index] = newTime; + setTimes(newTimes); + }; + + const handleCheckboxChange = () => { + setCheckboxChecked(!checkboxChecked); + }; + + const handleClose = () => { + setShowScheduleModal(false); + }; + + const handleEdit = async () => { + const test = { + sid: 2, + uid: 14, + cid: 3, + startTime: '2024-04-08T09:00:00+09:00', + isLive: false, + endTime: '2024-04-08T05:00:00+09:00', + title: 'PutTest', + }; + try { + await PutClassSchedule({ + sid: test.sid, + uid: test.uid, + cid: test.cid, + started_at: test.startTime, + is_live: test.isLive, + ended_at: test.endTime, + title: test.title, + }); + alert('Schedule created successfully'); + setShowScheduleModal(false); + } catch (error) { + alert('Failed to create schedule'); + console.error(error); + } + }; + + const handleEditPageClick = (event: React.MouseEvent) => { + event.stopPropagation(); + }; + + return ( +
+
+ +
+ ); +}; + +export default ClassEditSchedule; diff --git a/src/app/[className]/components/modal/ClassSchedule.tsx b/src/app/[className]/components/modal/ClassSchedule.tsx new file mode 100644 index 0000000..accfcd3 --- /dev/null +++ b/src/app/[className]/components/modal/ClassSchedule.tsx @@ -0,0 +1,89 @@ +import ReactCalendar from 'react-calendar'; +import {usePathname} from 'next/navigation'; +import {ClassScheduleProps} from '@/src/interfaces/_class/modal'; +import 'react-calendar/dist/Calendar.css'; +import '@/src/styles/calendar.css'; + +const ClassSchedule = ({ + setScheduleModalOpen, + selectedSchedule, +}: ClassScheduleProps) => { + const handleClose = () => { + setScheduleModalOpen(false); + }; + const scheduleData = selectedSchedule; + const {StartedAt, EndedAt} = scheduleData; + const startedDate = new Date(StartedAt); + const endedDate = new Date(EndedAt); + const [startedYear, startedMonth, startedDay] = [ + startedDate.getFullYear(), + startedDate.getMonth(), + startedDate.getDate(), + ]; + const [endedYear, endedMonth, endedDay] = [ + endedDate.getFullYear(), + endedDate.getMonth(), + endedDate.getDate(), + ]; + const pathname = usePathname().substring(1); + const decodedPathname = decodeURIComponent(pathname); + const handleEntry = () => { + const url = `${decodedPathname}/show?id=${scheduleData.ID}`; + window.location.href = url; + }; + return ( +
+
+ +
+ ); +}; +export default ClassSchedule; diff --git a/src/app/[className]/components/modal/index.ts b/src/app/[className]/components/modal/index.ts new file mode 100644 index 0000000..f851f9a --- /dev/null +++ b/src/app/[className]/components/modal/index.ts @@ -0,0 +1,15 @@ +import ClassCreateSchedule from './ClassCreateSchedule'; +import ClassCreatePost from './ClassCreatePost'; +import ClassSchedule from './ClassSchedule'; +import ClassPost from './ClassPost'; +import ClassEditSchedule from './ClassEditSchedule'; +import ClassEditPost from './ClassEditPost'; + +export { + ClassCreateSchedule, + ClassCreatePost, + ClassSchedule, + ClassPost, + ClassEditSchedule, + ClassEditPost, +}; diff --git a/src/app/[className]/components/schedule/TimePicker.tsx b/src/app/[className]/components/schedule/TimePicker.tsx new file mode 100644 index 0000000..fdf651f --- /dev/null +++ b/src/app/[className]/components/schedule/TimePicker.tsx @@ -0,0 +1,23 @@ +const TimePicker = ({ + value, + onChange, +}: { + value: string; + onChange: (value: string) => void; +}) => { + const handleTimeChange = (event: React.ChangeEvent) => { + onChange(event.target.value); + }; + return ( +
+ +
+ ); +}; + +export default TimePicker; diff --git a/src/app/[className]/components/schedule/index.ts b/src/app/[className]/components/schedule/index.ts new file mode 100644 index 0000000..3ec4515 --- /dev/null +++ b/src/app/[className]/components/schedule/index.ts @@ -0,0 +1,3 @@ +import TimePicker from './TimePicker'; + +export {TimePicker}; diff --git a/src/interfaces/_class/modal/classPostCreateProps.ts b/src/interfaces/_class/modal/classPostCreateProps.ts new file mode 100644 index 0000000..c923d2e --- /dev/null +++ b/src/interfaces/_class/modal/classPostCreateProps.ts @@ -0,0 +1,7 @@ +import {Dispatch, SetStateAction} from 'react'; + +interface ClassPostCreateProps { + setShowPostModal: Dispatch>; +} + +export default ClassPostCreateProps; diff --git a/src/interfaces/_class/modal/classPostProps.ts b/src/interfaces/_class/modal/classPostProps.ts new file mode 100644 index 0000000..c68d1fd --- /dev/null +++ b/src/interfaces/_class/modal/classPostProps.ts @@ -0,0 +1,13 @@ +import {Dispatch, SetStateAction} from 'react'; + +interface ClassPostProps { + setShowPostModal: Dispatch>; + selectedPost: { + ID: number; + Title: string; + Image: string; + Content: string; + }; +} + +export default ClassPostProps; diff --git a/src/interfaces/_class/modal/classScheduleProps.ts b/src/interfaces/_class/modal/classScheduleProps.ts new file mode 100644 index 0000000..d0688c0 --- /dev/null +++ b/src/interfaces/_class/modal/classScheduleProps.ts @@ -0,0 +1,14 @@ +import {Dispatch, SetStateAction} from 'react'; + +interface ClassScheduleProps { + setScheduleModalOpen: Dispatch>; + selectedSchedule: { + ID: number; + Title: string; + StartedAt: string; + EndedAt: string; + IsLive: boolean; + }; +} + +export default ClassScheduleProps; diff --git a/src/interfaces/_class/modal/classScheduleShowProps.ts b/src/interfaces/_class/modal/classScheduleShowProps.ts new file mode 100644 index 0000000..9066de0 --- /dev/null +++ b/src/interfaces/_class/modal/classScheduleShowProps.ts @@ -0,0 +1,7 @@ +import {Dispatch, SetStateAction} from 'react'; + +interface ClassScheduleShowProps { + setShowScheduleModal: Dispatch>; +} + +export default ClassScheduleShowProps; diff --git a/src/interfaces/_class/scheduleCardProps.ts b/src/interfaces/_class/scheduleCardProps.ts index 4635c99..ba4c412 100644 --- a/src/interfaces/_class/scheduleCardProps.ts +++ b/src/interfaces/_class/scheduleCardProps.ts @@ -2,8 +2,11 @@ import {RoleProps} from '.'; interface ScheduleCardProps extends RoleProps { scheduleName: string; - time: string; + startTime: string; + endTime: string; managerRole: boolean; + isLive: boolean; + scheduleId: number; } export default ScheduleCardProps; diff --git a/src/interfaces/api/_class/index.ts b/src/interfaces/api/_class/index.ts new file mode 100644 index 0000000..ae722b9 --- /dev/null +++ b/src/interfaces/api/_class/index.ts @@ -0,0 +1,11 @@ +import PostBoardData from './postBoardData'; +import PostCreateClassData from './postCreateClassData'; +import PostCreateScheduleData from './postCreateScheduleData'; +import PutClassScheduleData from './putScheduleData'; + +export type { + PostBoardData, + PostCreateClassData, + PostCreateScheduleData, + PutClassScheduleData, +}; diff --git a/src/interfaces/api/_class/postCreateScheduleData.ts b/src/interfaces/api/_class/postCreateScheduleData.ts new file mode 100644 index 0000000..dc59ece --- /dev/null +++ b/src/interfaces/api/_class/postCreateScheduleData.ts @@ -0,0 +1,10 @@ +interface PostCreateScheduleData { + uid: number; + cid: number; + ended_at: string; + is_live: boolean; + started_at: string; + title: string; +} + +export default PostCreateScheduleData; diff --git a/src/interfaces/api/_class/putScheduleData.ts b/src/interfaces/api/_class/putScheduleData.ts new file mode 100644 index 0000000..a199fed --- /dev/null +++ b/src/interfaces/api/_class/putScheduleData.ts @@ -0,0 +1,11 @@ +interface PutClassScheduleData { + sid: number; + cid: number; + uid: number; + ended_at: string; + is_live: boolean; + started_at: string; + title: string; +} + +export default PutClassScheduleData; diff --git a/src/styles/calendar.css b/src/styles/calendar.css new file mode 100644 index 0000000..ca661ec --- /dev/null +++ b/src/styles/calendar.css @@ -0,0 +1,84 @@ +.react-calendar { + width: 400px; + max-width: 100%; + background-color: #fff; + color: #222; + border-radius: 8px; + font-family: Arial, Helvetica, sans-serif; + line-height: 1.125em; +} +.react-calendar__navigation button { + color: #6f48eb; + min-width: 44px; + background: none; + font-size: 16px; + margin-top: 8px; +} +.react-calendar__navigation button:enabled:hover, +.react-calendar__navigation button:enabled:focus { + background-color: #f8f8faeb; +} +.react-calendar__navigation button[disabled] { + background-color: #fcfcfc; +} +abbr[title] { + text-decoration: none; +} +.react-calendar__tile:enabled:hover, +.react-calendar__tile:enabled:focus { + background: #f8f8faeb; + color: #6f48eb; + border-radius: 6px; +} +.react-calendar__tile--now { + background: #6f48eb33; + border-radius: 6px; + font-weight: bold; + color: #6f48eb; +} +.react-calendar__tile--now:enabled:hover, +.react-calendar__tile--now:enabled:focus { + background: #6f48eb33; + border-radius: 6px; + font-weight: bold; + color: #6f48eb; +} +.react-calendar__tile--hasActive:enabled:hover, +.react-calendar__tile--hasActive:enabled:focus { + background: #f8f8faeb; +} +.react-calendar__tile--active { + background: #6f48eb; + border-radius: 6px; + font-weight: bold; + color: white; +} +.react-calendar__tile--active:enabled:hover, +.react-calendar__tile--active:enabled:focus { + background: #6f48eb; + color: white; +} +.react-calendar--selectRange .react-calendar__tile--hover { + background-color: #f8f8faeb; +} +.react-calendar__tile--range { + background: #f8f8fa; + color: #6f48eb; + border-radius: 0; +} +.react-calendar__tile--rangeStart { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + background: #6f48eb; + color: white; +} +.react-calendar__tile--rangeEnd { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + background: #6f48eb; + color: white; +} diff --git a/yarn.lock b/yarn.lock index 20f5851..929ded8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1411,6 +1411,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@wojtekmaj/date-utils@^1.1.3": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b" + integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww== + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -3280,6 +3285,13 @@ get-tsconfig@^4.5.0: dependencies: resolve-pkg-maps "^1.0.0" +get-user-locale@^2.2.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.2.tgz#d37ae6e670c2b57d23a96fb4d91e04b2059d52cf" + integrity sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ== + dependencies: + mem "^8.0.0" + git-raw-commits@^2.0.11: version "2.0.11" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" @@ -4749,6 +4761,13 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -4864,6 +4883,14 @@ mdast-util-to-string@^4.0.0: dependencies: "@types/mdast" "^4.0.0" +mem@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" + integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.1.0" + meow@^12.0.1: version "12.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" @@ -5138,6 +5165,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + mimic-fn@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" @@ -5488,6 +5520,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -5747,7 +5784,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5796,6 +5833,17 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +react-calendar@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.8.0.tgz#61edbba6d17e7ef8a8012de9143b5e5ff41104c8" + integrity sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA== + dependencies: + "@wojtekmaj/date-utils" "^1.1.3" + clsx "^2.0.0" + get-user-locale "^2.2.1" + prop-types "^15.6.0" + warning "^4.0.0" + react-dom@^18: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -6359,16 +6407,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6452,14 +6491,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7165,16 +7197,7 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==