diff --git a/src/components/admin/new-timetable-config/Index.jsx b/src/components/admin/new-timetable-config/Index.jsx index 415d0e8..f28a599 100644 --- a/src/components/admin/new-timetable-config/Index.jsx +++ b/src/components/admin/new-timetable-config/Index.jsx @@ -1,7 +1,16 @@ import TimeTable from '@/components/timetable' +import DisplayAI from "./aiTImetable" import Details from '@/components/admin/new-timetable-config/details' import { useState } from 'react'; -import Defaults from '../settings/defaults' + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + } from "@/components/ui/card" // TODO: // Cool loading component that covers the timetable form creator @@ -9,22 +18,20 @@ import Defaults from '../settings/defaults' const Index = () => { const [schedule, setSchedule] = useState({}); - return ( -
-
-

Create new timetable

-
+ + + Create new timetable +

Timetable is created by a scheduling algorithm which uses all the saved courses and priority.

Want to start from an existing timetable template? Import previous timetables

-
-
-
-
- -
- {!!schedule?.startDay ? : null} -
+ + + + {!!schedule?.startDay ? :
} + {/*
*/} + + ) }; diff --git a/src/components/admin/new-timetable-config/aiTImetable.jsx b/src/components/admin/new-timetable-config/aiTImetable.jsx new file mode 100644 index 0000000..d3e7a43 --- /dev/null +++ b/src/components/admin/new-timetable-config/aiTImetable.jsx @@ -0,0 +1,38 @@ +import Timetable from '../../timetable' +import { Skeleton } from '@/components/ui/skeleton'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, + } from "@/components/ui/card" + + +export default function dispalyAITimetable({ schedule }) { + + return ( + + +
+

+ {(!!schedule?.name) + ? (schedule?.name??'Timetable Name') + : } +

+
+

+ {(!!schedule?.description) + ? (schedule?.description??'Timetable Name') + : (schedule?.description === "") ? null : } +

+
+
+
+ + + +
+ ) +} diff --git a/src/components/admin/new-timetable-config/createTimetableBtn.jsx b/src/components/admin/new-timetable-config/createTimetableBtn.jsx index 0920c01..21e5852 100644 --- a/src/components/admin/new-timetable-config/createTimetableBtn.jsx +++ b/src/components/admin/new-timetable-config/createTimetableBtn.jsx @@ -4,26 +4,66 @@ import { useState } from 'react' const serverLink = import.meta.env.VITE_SERVER_LINK -export default function createTimetableBtn({setSchedule, details, ...neededDetails}) { +export default function createTimetableBtn({setSchedule, details}) { const [loading, setLoading] = useState(false) + const [anerror, setAnerror] = useState(false) + const date = new Date() return ( + }}>{anerror ?'Retry' : 'Create'} ) } -async function askGPT({ courses, departments, venues, startTime, endTime, interval, breakTime }){ +async function askGPT({ courses, departments, venues, startTime, endTime, interval, breakTime }, promptDetails){ let prompt = ` Before you start reading the prompt, be FLEXIBLE with the format of details given!!!, DO NOT return the details given as a response, unless you state the problem and reason & any response you submit MUST be in a JSON format!!!! The instructions are simple and possiblefor you to do @@ -70,7 +110,7 @@ async function askGPT({ courses, departments, venues, startTime, endTime, interv I will enter a list of courses in an array, departments in an array, venues in an array and the time contraints i.e the time to start inputting the activites of the day usually by 8:00 unless specified something else, time to end the activites of the day usually by 18:00 unless specified something else, time for a break, and the interval to which you'll be working with #### - Once you are done with your scheduling, I want you to only return your answer in a JSON format and output as your response just the JSON, nothing else, following this pattern: + Once you are done with your scheduling, I want you to only return your SCHEDULED answer in a JSON format and output as your response just the JSON, nothing else, following this pattern: Your response should only be on what you have scheduled [ @@ -86,9 +126,9 @@ async function askGPT({ courses, departments, venues, startTime, endTime, interv ##### - const courses = ['CSC 222', 'CSC 227', 'CSC 212']; - const departments = ['Computer Science', 'Mathematics']; - const venues = ['LT1', 'LT2', 'B01']; + const courses = ${promptDetails.courses}; + const departments = ${promptDetails.departments}; + const venues = ${promptDetails.venues}; const startTime = '8:00'; const endTime = '18:00'; const interval = 60; // in minutes @@ -102,7 +142,10 @@ async function askGPT({ courses, departments, venues, startTime, endTime, interv - Courses can only repeat once in the schedule - AND MOST IMPORTANT RULE OF ALL: output ONLY JSON as your response, NOTHING ELSE!!! ` + + console.log(prompt) let response = await gpt.ask(prompt); + console.log('gpt response:', response) const cleanedJSON = parseAndRemoveJsonTags(response) return cleanedJSON; } @@ -120,12 +163,12 @@ async function convertToCleanSchedule(input) { try{ for (const course of input) { - const code = course.course.split(' ')[0].toLowerCase(); + // const code = course.course.split(' ')[0].toLowerCase(); const response = await axios.post(`${serverLink}/course/get-course`, { code: course.course }); - const department = code // response?.data?.courses[0]?.departmentId?.code; + const department = response?.data?.courses[0]?.departmentId?.code; + console.log('Original departmental code: ', response?.data?.courses[0]?.departmentId?.code, 1111122222111) - const courseNumber = course.course.split(' ')[1]; const departmentNumber = Math.floor(parseInt(courseNumber) / 100) * 100; @@ -164,31 +207,41 @@ async function convertToCleanSchedule(input) { return output; } +function turnArrayDirectlyToString(array){ + const stringWithSingleQuotes = JSON.stringify(array, (key, value) => { + if (typeof value === 'string') { + return `'${value}'`; + } + return value; + }); + + return stringWithSingleQuotes; +} -// Example usage: -const input = [ - { - "course": "CSC 222", - "day": "Monday", - "startTime": "2024-04-22T08:00:00", - "endTime": "2024-04-22T09:00:00", - "venue": "B12" - }, - { - "course": "CSC 241", - "day": "Monday", - "startTime": "2024-04-22T09:00:00", - "endTime": "2024-04-22T10:00:00", - "venue": "B13" - }, - { - "course": "CSC 141", - "day": "Monday", - "startTime": "2024-04-22T09:00:00", - "endTime": "2024-04-22T10:00:00", - "venue": "B12" - }, -]; - -const output = async () => await convertToCleanSchedule(input); -console.log(JSON.stringify(output(), null, 2), 8787); // To print the output JSON with indentation +// // Example usage: +// const input = [ +// { +// "course": "CSC 222", +// "day": "Monday", +// "startTime": "2024-04-22T08:00:00", +// "endTime": "2024-04-22T09:00:00", +// "venue": "B12" +// }, +// { +// "course": "CSC 241", +// "day": "Monday", +// "startTime": "2024-04-22T09:00:00", +// "endTime": "2024-04-22T10:00:00", +// "venue": "B13" +// }, +// { +// "course": "CSC 141", +// "day": "Monday", +// "startTime": "2024-04-22T09:00:00", +// "endTime": "2024-04-22T10:00:00", +// "venue": "B12" +// }, +// ]; + +// const output = async () => await convertToCleanSchedule(input); +// console.log(JSON.stringify(output(), null, 2), 8787); // To print the output JSON with indentation diff --git a/src/components/admin/new-timetable-config/details.jsx b/src/components/admin/new-timetable-config/details.jsx index f6cdc67..78901dc 100644 --- a/src/components/admin/new-timetable-config/details.jsx +++ b/src/components/admin/new-timetable-config/details.jsx @@ -9,72 +9,393 @@ import { } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" - +import { + Select, SelectContent, SelectGroup, + SelectItem, SelectTrigger, SelectValue, +} from "@/components/ui/select" import DepartmentSelection from "@/components/departmentSelection" import LevelSelection from "@/components/levelSelection" import Create from "@/components/admin/new-timetable-config/createTimetableBtn" import { useState, useEffect, useRef } from 'react' -import axios from 'axios' +import Switch from "../settings/switch" + const details = ({ setSchedule }) => { - const nameRef = useRef(null) - const descriptionRef = useRef(null) + const currentDate = new Date() + const daysOfWeek = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; + + const [dates, setDates] = useState([...getAcademicYears(currentDate, -7).reverse().slice(0,-1), ...getAcademicYears(currentDate, 5)]) + + const [name, setName] = useState(`Timtable built on ${currentDate.toLocaleString().split(',').toString().replace(',','')}`) + const [description, setDescription] = useState('') + + const departmentState = useState("all") + const fetchedDepartmentsState = useState([]) + const [ level, setLevel ] = useState("all") + + const [ current, setCurrent ] = useState(true); + const [ examMode, setExamMode ] = useState(false); + + const [ interval, setInterval ] = useState('60') + const timeStrings = getHour('2024-04-19T00:00:00.000Z', '2024-04-19T24:00:00.000Z', interval) + const [ startTime, setStartTime ] = useState('08:00') + const [ endTime, setEndTime ] = useState('18:00') + + const [ startDay, setStartDay ] = useState('Monday') + const [endDay, setEndDay] = useState('Friday') + + const [ currentYear, setCurrentYear ] = useState(computeAcademicYear(currentDate)) + const [ semester, setSemester ] = useState(isAlpha() ? 'alpha' : 'omega') + + const form = { + name, description, current, examMode, interval, startTime, + endTime, startDay, endDay, currentYear, semester, level, + selectedDepartment: departmentState[0], allDepartments: fetchedDepartmentsState[0]} + + const QuerySettings = () => { + return ( + + + +

Select Department & Level

+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ ) + } + + const Timing = () => { + return ( + + + Timing + + Adjust the timing for any new timetable you create - set defaults when creating timetables + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ ) + } return ( - -
setName(value)} placeholder="Timetable Name" name="name"/>
- + setDescription(value)} placeholder="Timetable Description" name="description"/>
- - - -

Settings

-
-
- -
-
- - -
-
- - -
-
-
-
+
+
+ + +
+
+ + +
+
+
+ setExamMode(value)} + label="Activate examination mode"/> + setCurrent(value)} + label="Set to Current"/> + +
+ +
- - + + +
) } +function computeAcademicYear(input) { + let year; + if (input instanceof Date) { + year = input.getFullYear(); + } else if (typeof input === 'string') { + year = parseInt(input); + } else { + throw new Error("Input must be a Date object or a string representing a year."); + } + + // Check if the input represents the starting year of the academic year + let startingYear; + if (new Date(`${year}-09-01`).getTime() <= input.getTime()) { + startingYear = year; + } else { + startingYear = year - 1; + } + + // Calculate the academic year range + const endingYear = startingYear + 1; + + return `${startingYear}/${endingYear}`; +} + +function getAcademicYears(startDate, numberOfYears) { + const academicYears = []; + + for (let i = 0; i < Math.abs(numberOfYears); i++) { + let currentDate; + if (numberOfYears < 0) { + currentDate = new Date(startDate.getFullYear() - i, startDate.getMonth(), startDate.getDate()); + } else { + currentDate = new Date(startDate.getFullYear() + i, startDate.getMonth(), startDate.getDate()); + } + const academicYear = computeAcademicYear(currentDate); + academicYears.push(academicYear); + } + + return academicYears; +} + +function isAlpha() { + const date = new Date(); // Assume it's April 22, 2024 + const month = date.getMonth(); + return month >= 8 && month <= 1; // Months are 0-indexed (0: January, 1: February, ..., 8: September) +} + +function roundTimeToInterval(timeString, interval) { + const time = new Date(timeString); + const minutes = time.getMinutes(); + const remainder = minutes % interval; + + // console.log('Before: ', {timeString, interval, time, ret: time.toISOString(), minutes, remainder}) + if (remainder === 0) return time.toISOString(); + + // Round the time to the nearest interval + if (remainder < interval / 2) { + time.setMinutes(minutes - remainder); + } else { + time.setMinutes(minutes + (interval - remainder)); + } + + // console.log('After: ', {timeString, interval, time, ret: time.toISOString(), minutes, remainder}) + return time.toISOString(); +} + +function removeTimeZone(time){ + // console.log('Before: ', time) + const offset = time.getTimezoneOffset(); + const serialTime = time.getTime(); + + // refactors the currrent time in case of timezone difference since it doesn't + // matter what timezone one is on at setting up timetable + // console.log('Before: ', time) + time.setTime(serialTime + offset*(60000)) + // console.log('AFter: ', time) + + // console.log('After: ', time) + return time +} + +function getHour(startTime, endTime, interval=30) { + // Round start time and end time to nearest interval + startTime = roundTimeToInterval(startTime, interval); + endTime = roundTimeToInterval(endTime, interval); + + // Convert time strings to Date objects + let endDate = new Date(`${endTime}`); + let startDate = new Date(`${startTime}`); + + // console.log(startDate, 323) + // refactors the currrent time in case of timezone difference since it doesn't + // matter what timezone one is on at setting up timetable + endDate = removeTimeZone(endDate) + startDate = removeTimeZone(startDate) + + // Array to store hour + const hour = []; + + // Loop through each hour with the specified interval + let currentDate = startDate; + while (currentDate < endDate) { + const startHour = currentDate.getHours().toString().padStart(2, '0'); + const startMinute = currentDate.getMinutes().toString().padStart(2, '0'); + + currentDate.setHours(currentDate.getHours(), currentDate.getMinutes() + interval); + + const endHour = currentDate.getHours().toString().padStart(2, '0'); + const endMinute = currentDate.getMinutes().toString().padStart(2, '0'); + + hour.push(`${startHour}:${startMinute}`); + } + + // console.log({startTime, endTime, startDate, endDate}, 444) + + // console.log(6346, startTime, endTime, hour) + return hour; +} + export default details \ No newline at end of file diff --git a/src/components/admin/settings/aikey.jsx b/src/components/admin/settings/aikey.jsx index 29da024..743f87c 100644 --- a/src/components/admin/settings/aikey.jsx +++ b/src/components/admin/settings/aikey.jsx @@ -9,6 +9,7 @@ import { } from "@/components/ui/card" import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" +import { Tooltip } from "@/components/ui/tooltip" import { useState } from "react" export default function Component() { @@ -20,13 +21,14 @@ export default function Component() { AI Plugin - Add your own OpenAi api key for better quality generated timetables. + Add your own OpenAI api key for better quality generated timetables.
{ setKey(value); diff --git a/src/components/admin/settings/defaults.jsx b/src/components/admin/settings/defaults.jsx index 0a84f91..3bd0f01 100644 --- a/src/components/admin/settings/defaults.jsx +++ b/src/components/admin/settings/defaults.jsx @@ -67,9 +67,9 @@ export default function defaults() { - +
- +
diff --git a/src/components/admin/settings/general/Index.jsx b/src/components/admin/settings/general/Index.jsx index 1d3110d..02a2dc8 100644 --- a/src/components/admin/settings/general/Index.jsx +++ b/src/components/admin/settings/general/Index.jsx @@ -3,7 +3,7 @@ import Table from './recordViews' export default function Index() { return (
-

View of Records

+

View of Records

) diff --git a/src/components/admin/settings/general/addNewRecords.jsx b/src/components/admin/settings/general/addNewRecords.jsx index f6be118..8edb896 100644 --- a/src/components/admin/settings/general/addNewRecords.jsx +++ b/src/components/admin/settings/general/addNewRecords.jsx @@ -1,22 +1,41 @@ -import { Button } from "@/components/ui/button" -import { PlusCircle } from "lucide-react" -import { Link } from "react-router-dom" - +import Add from './addRecordModal' +import { Input } from "@/components/ui/input" +import { Label } from '@/components/ui/label' const newRecord = ({ name }) => { + const formToShow = { + courses: courseForm(), + departments: departmentForm() + }; + + if(!(!!formToShow)) return null; + return ( - - - - ) + + {formToShow[name]} + + ); } -export default newRecord \ No newline at end of file + +const courseForm = () => { + return ( +
+ + +
+ ); +}; + +const departmentForm = () => { + return ( +
+ + +
+ ); +}; + +export default newRecord; \ No newline at end of file diff --git a/src/components/admin/settings/general/addRecordModal.jsx b/src/components/admin/settings/general/addRecordModal.jsx new file mode 100644 index 0000000..d821a90 --- /dev/null +++ b/src/components/admin/settings/general/addRecordModal.jsx @@ -0,0 +1,49 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, + } from "@/components/ui/alert-dialog" + import { Button } from "@/components/ui/button" +import { PlusCircle } from "lucide-react" + + + export default function AddRecords({children, title, action, name}) { + + return ( + + + + + + + {title} + {children} + + + Cancel + { + !!action && action(); + window.location.reload(); + }}>Add {name} + + + + ) + } + \ No newline at end of file diff --git a/src/components/admin/settings/general/recordViews.jsx b/src/components/admin/settings/general/recordViews.jsx index 6d5c6de..846f8b9 100644 --- a/src/components/admin/settings/general/recordViews.jsx +++ b/src/components/admin/settings/general/recordViews.jsx @@ -1,5 +1,4 @@ -"use client" - +import { Input } from "@/components/ui/input" import * as React from "react" import { flexRender, @@ -22,7 +21,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { Input } from "@/components/ui/input" import { Table, TableBody, @@ -31,6 +29,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table" +import AddRecord from "./addNewRecords" import { Select, SelectContent, @@ -405,7 +404,7 @@ export default function DataTableDemo() { return (
-
+
- @@ -445,6 +446,7 @@ export default function DataTableDemo() { +
+ + } @@ -149,8 +155,4 @@ function convertRawSchedule(inputData) { } return output; -} - -function rollback(id){ - console.log('Timetable ID: ', id) } \ No newline at end of file diff --git a/src/components/admin/show-timetables/index.jsx b/src/components/admin/show-timetables/index.jsx index 42f03f1..c658c91 100644 --- a/src/components/admin/show-timetables/index.jsx +++ b/src/components/admin/show-timetables/index.jsx @@ -1,26 +1,27 @@ import { - Card, - CardContent, - CardFooter, -} from "@/components/ui/card" - -import Header from "./header" -import List from './list' - -export default function Component() { - return ( - -
- - - - - {/* pagination property */} -
- Showing 1-10 of 32 products -
- Load more... -
- - ) -} + Card, + CardContent, + CardFooter, + } from "@/components/ui/card" + + import Header from "./header" + import List from './list' + + export default function Component() { + return ( + +
+ + + + {/* + **** pagination property **** +
+ Showing 1-10 of 32 products +
+ Load more... +
*/} + + ) + } + \ No newline at end of file diff --git a/src/components/admin/show-timetables/timetableOption.jsx b/src/components/admin/show-timetables/timetableOption.jsx index dd893c7..1e85a74 100644 --- a/src/components/admin/show-timetables/timetableOption.jsx +++ b/src/components/admin/show-timetables/timetableOption.jsx @@ -22,7 +22,7 @@ export default function timetableOption({id, status, createdAt, name, current}) {createdAt} - + ) diff --git a/src/components/departmentSelection.jsx b/src/components/departmentSelection.jsx index 48355df..768c82c 100644 --- a/src/components/departmentSelection.jsx +++ b/src/components/departmentSelection.jsx @@ -19,14 +19,15 @@ import axios from 'axios' const serverLink = import.meta.env.VITE_SERVER_LINK -const departmentSelection = ({ contentClassName, triggerClassName, enabled, enableSelectAll, setDepartment }) => { +const departmentSelection = ({ contentClassName, triggerClassName, enabled, enableSelectAll, setDepartment, outsideValueState, outsideDepartmentsState }) => { const [open, setOpen] = useState(false); - const [value, setValue] = useState(enableSelectAll ? "all" : ""); - const [departments, setDepartments] = useState([]) + const [value, setValue] = outsideValueState??useState(enableSelectAll ? "all" : ""); + const [departments, setDepartments] = outsideDepartmentsState??useState([]) // Find a way to fix this logic so that department is disabled when everything else is loading ----> console.log(((departments?.length === 0) && (!enabled)), departments?.length === 0, !enabled, 8398238) useEffect(() => { async function fetchDepartment(){ + console.log('ftetchin...') try { const response = await axios.post(`${serverLink}/department/get-department`, {}) @@ -37,7 +38,7 @@ const departmentSelection = ({ contentClassName, triggerClassName, enabled, enab } } - fetchDepartment(); + (departments.length === 0) && fetchDepartment(); }, []) const shownValue = value @@ -54,7 +55,7 @@ const departmentSelection = ({ contentClassName, triggerClassName, enabled, enab role="combobox" aria-expanded={open} className={cn("max-w-48 min-w-20 flex justify-between [&>span]:line-clamp-1", triggerClassName)} - disabled={(departments?.length === 0)} + // disabled={(departments?.length === 0)} > {shownValue} @@ -83,7 +84,7 @@ const departmentSelection = ({ contentClassName, triggerClassName, enabled, enab /> {value === 'all' ? 'Deselect All' : 'Select All'} : null} - {departments?.map((department) => ( + {(departments?.length === 0) ? 'Loading...' : departments?.map((department) => ( { +const levelSelection = ({ className, enabled, enableSelectAll, value, setLevel }) => { + console.log('levelselect:', enabled) return (