diff --git a/app/util/convertRawData.py b/app/util/convertRawData.py index ba312d5..389ba47 100644 --- a/app/util/convertRawData.py +++ b/app/util/convertRawData.py @@ -6,7 +6,8 @@ import docx def putMasterTimetable(timetable: dict) -> None: pass -def putScheduleToWord(courses: dict, student: dict, output_dir: str='../../output/final/student_schedules') -> None: +def putScheduleToWord(courses: dict, student: dict, output_dir: str='./output/final/student_schedules') -> None: + # create an instance of a word doc. doc = docx.Document() diff --git a/app/util/globals.py b/app/util/globals.py index ad7c1e8..8234d96 100644 --- a/app/util/globals.py +++ b/app/util/globals.py @@ -17,3 +17,10 @@ class Error: def __init__(self, title: str, description: str): self.Title = title self.Description = description + + # return dict version of self + def toDict(self) -> dict: + return { + "Title": self.Title, + "Description": self.Description + } diff --git a/main.py b/main.py index 8d3e915..ee64eec 100755 --- a/main.py +++ b/main.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3.11 import eel import os +import json from app.generator import generateScheduleV3 from app.util.globals import Error from app.util.getCourses import getCourses from app.util.getStudents import getStudents +from app.util.convertRawData import putScheduleToWord, putMasterTimetable eel.init('template') - + @eel.expose def start( raw_file_data: str, @@ -15,8 +17,9 @@ def start( class_cap: int, block_class_limit: int, total_blocks: int, -) -> Error: - +) -> dict: + + eel.post_data('Ensuring Directories Exists...') # Ensure output paths exists if not os.path.exists('output'): os.makedirs('output') if not os.path.exists('output/temp'): os.makedirs('output/temp') @@ -26,9 +29,9 @@ def start( raw_data_dir = './output/temp/course_selection_data.csv' + eel.post_data('Saving raw data to local file...') # save raw file data to local file with open(raw_data_dir, 'w') as raw_file: - raw_file_data = raw_file_data.replace('\n', '') raw_file.write(raw_file_data) # Ensure params are of correct type @@ -37,8 +40,7 @@ def start( block_class_limit = int(block_class_limit) total_blocks = int(total_blocks) - err = None - + eel.post_data('Collecting student information...') # call pre-algorithm functions read raw data into a processable format students = getStudents( raw_data_dir, @@ -46,14 +48,16 @@ def start( totalBlocks=total_blocks, log_dir='./output/raw/students.json' ) + eel.post_data('Collecting course information...') courses = getCourses( raw_data_dir, log=True, log_dir='./output/raw/courses.json' ) + eel.post_data('Generating timetables...') # call algorithm to sort data - _, err = generateScheduleV3( + master_timetable, err = generateScheduleV3( students, courses, minReq=min_req, @@ -64,12 +68,22 @@ def start( conflictsDir='./output/raw/conflicts.json' ) - print(err) + + if err is not None: + eel.post_data(f'An error has occured while generating the timetable: {err.Title}') + return err.toDict() + + eel.post_data('Gathering latest data...')() + # Get updated students + with open('./output/raw/students.json', 'r') as studentFile: students = json.load(studentFile) + + eel.post_data('Writing timetables to .docx files...') + # call post-algorithm functions to present sorted data + for student in students: + putScheduleToWord(courses, student, './output/final/student_schedules') # TODO: log master_timetable - # TODO: call post-algorithm functions to present sorted data - - return err + return err.toDict() if err is not None else None eel.start('index.html', size=(800, 1000)) diff --git a/template/index.html b/template/index.html index c2e590a..4dfd3a4 100644 --- a/template/index.html +++ b/template/index.html @@ -9,8 +9,8 @@ - +
@@ -21,15 +21,18 @@

Schedule Generator

Minimum students per class Maximum students per class Total blocks (10 or 8) - +
-
-
-
+

Processing...

+
+
    +
    \ No newline at end of file diff --git a/template/script.js b/template/script.js index bd6237b..b7a311f 100644 --- a/template/script.js +++ b/template/script.js @@ -1,10 +1,15 @@ +function e(n) {return n != undefined && n != null && n != ""} + function start() { let file = document.getElementById("input_file").files[0]; let minReq = document.getElementById("min_req").value; let classCap = document.getElementById("class_cap").value; let blockClassLimit = document.getElementById("classrooms").value; let totalBlocks = document.getElementById("total_blocks").value; - + + // ensure data is passed before continuing + if (!e(file) || !e(minReq) || !e(classCap) || !e(blockClassLimit) || !e(totalBlocks)) return alert('Please ensure all fields aren\'t empty.'); + // start animation document.getElementById('main').style.display = 'none'; document.getElementById('processing').style.display = 'block'; @@ -12,7 +17,7 @@ function start() { let reader = new FileReader() reader.addEventListener("load", () => { let raw = reader.result; - eel.start(raw, minReq, classCap, blockClassLimit, totalBlocks)(start_callback); + eel.start(raw, minReq, classCap, blockClassLimit, totalBlocks)(finish_callback); }, false); if (file) { @@ -20,6 +25,38 @@ function start() { } } -function start_callback(error) { - console.log(error, 'done') -} \ No newline at end of file +eel.expose(post_data); +function post_data(msg) { + let newMessage = document.createElement('li'); + newMessage.innerText = msg; + newMessage.className = 'nostyle' + document.getElementById('progress').appendChild(newMessage) +} + +function finish_callback(error) { + if (error) { + post_data(`Error: ${error.Title}`); + post_data(error.Description); + } else { + document.getElementById('processing-header').innerText = 'Finished Processing!'; + let data = document.getElementById('progress'); + while (data.firstChild) { + data.removeChild(data.firstChild); + } + post_data('Individual student schedules can be found in the \'/output/final/student_schedules/\' folder and the master timetable as well as student with conflicts can be found in the \'/output/final/\' folder.') + } + + document.getElementById('loader').style.display = 'none'; + document.getElementById('retryBtn').style.display = 'block'; +} + +function retry() { + // start animation + document.getElementById('processing').style.display = 'none'; + document.getElementById('main').style.display = 'block'; + document.getElementById('loader').style.display = 'block'; + let data = document.getElementById('progress'); + while (data.firstChild) { + data.removeChild(data.firstChild); + } +} diff --git a/template/style.css b/template/style.css index 209fb60..b1fac1d 100644 --- a/template/style.css +++ b/template/style.css @@ -63,15 +63,15 @@ body{ border: none; } .main h3{ - font-size: 32px; - font-weight: 500; + font-size: 26px; + font-weight: 1000; line-height: 42px; text-align: center; } label{ display: block; - margin-top: 30px; + margin-top: 12px; font-size: 16px; font-weight: 500; } @@ -82,16 +82,44 @@ label{ background-color: rgba(255,255,255,0.07); border-radius: 3px; padding: 0 10px; - margin-top: 8px; + margin-top: 0px; font-size: 14px; font-weight: 300; } ::placeholder{ color: #e5e5e5; } -.main button{ +.processing{ + display: none; + height: 600px; + width: 400px; + background-color: rgba(255,255,255,0.13); + position: absolute; + transform: translate(-50%,-50%); + top: 50%; + left: 50%; + border-radius: 10px; + backdrop-filter: blur(10px); + border: 2px solid rgba(255,255,255,0.1); + box-shadow: 0 0 40px rgba(8,7,16,0.6); + padding: 50px 35px; +} +.processing *{ + font-family: 'Poppins',sans-serif; + color: #ffffff; + letter-spacing: 0.5px; + outline: none; + border: none; +} +.processing h3{ + font-size: 32px; + font-weight: 500; + line-height: 42px; + text-align: center; +} +.btn { transition: 0.05s; - margin-top: 50px; + margin-top: 20px; width: 100%; background-color: #ffffff; color: #080710; @@ -101,17 +129,37 @@ label{ border-radius: 5px; cursor: pointer; } -.processing { +.loader { + margin-left: 95px; + margin-top: 40px; + border: 16px solid #f3f3f3; + border-radius: 50%; + border-top: 16px solid #3498db; + width: 120px; + height: 120px; + -webkit-animation: spin 2s linear infinite; /* Safari */ + animation: spin 2s linear infinite; +} + +li.nostyle { + list-style-type: none; +} + +#retryBtn { display: none; } -#myProgress { - width: 100%; - background-color: #ddd; +/* Safari */ +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } } -#myBar { - width: 1%; - height: 30px; - background-color: #04AA6D; +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +#progress { + margin-top: 10px; } \ No newline at end of file