diff --git a/App.config b/App.config index 1c75772..b212aff 100644 --- a/App.config +++ b/App.config @@ -1,6 +1,26 @@ - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BSCpE_ProgramHeadSetup.vb b/BSCpE_ProgramHeadSetup.vb index dae4385..e06d18f 100644 --- a/BSCpE_ProgramHeadSetup.vb +++ b/BSCpE_ProgramHeadSetup.vb @@ -1,4 +1,7 @@ -Imports System.Resources +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions Imports Svg Public Class BSCpE_ProgramHeadSetup @@ -35,7 +38,7 @@ Public Class BSCpE_ProgramHeadSetup Me.FormPanel.Controls.Add(Logo) Dim Intro As New Transparent.Label With { - .Text = "Continue by registering the BSCpE Program head.", + .Text = "Continue by registering the BSCpE Program Head.", .MaximumSize = New Size(Me.FormPanel.Width, 0), .MinimumSize = New Size(Me.FormPanel.Width, 0), .AutoSize = True, @@ -45,11 +48,22 @@ Public Class BSCpE_ProgramHeadSetup } Me.FormPanel.Controls.Add(Intro) - Dim NameInput As New BaseTextInput With { - .Name = "Name", + Dim NamePanel As New Transparent.Panel With { .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } - Me.FormPanel.Controls.Add(NameInput) + Me.FormPanel.Controls.Add(NamePanel) + Dim FirstNameInput As New BaseTextInput With { + .Name = "First Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + FirstNameInput.Location = New Point(0, 0) + NamePanel.Controls.Add(FirstNameInput) + Dim LastNameInput As New BaseTextInput With { + .Name = "Last Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + LastNameInput.Location = New Point(NamePanel.Width - LastNameInput.Width, 0) + NamePanel.Controls.Add(LastNameInput) Dim EmailInput As New BaseTextInput With { .Name = "Email", .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) @@ -70,11 +84,77 @@ Public Class BSCpE_ProgramHeadSetup Dim ContinueButton As New BaseButton With { .Text = "Continue", - .Name = "Continue" + .Name = "Continue", + .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } Me.FormPanel.Controls.Add(ContinueButton) AddHandler ContinueButton.Click, Sub() - Me.GoToForm(New BSCpE_SetupData) + If FirstNameInput.Text = "" And FirstNameInput.Text.Length < 3 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "First Name must be at least 3 characters." + } + Modal.ShowDialog() + FirstNameInput.Alert() + Exit Sub + End If + If LastNameInput.Text = "" And LastNameInput.Text.Length < 2 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Last Name must be at least 2 characters." + } + Modal.ShowDialog() + LastNameInput.Alert() + Exit Sub + End If + If EmailInput.Text = "" And Not New Regex("^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").IsMatch(EmailInput.Text) Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Invalid email address." + } + Modal.ShowDialog() + EmailInput.Alert() + Exit Sub + End If + If PasswordInput.Text = "" And PasswordInput.Text.Length < 8 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Password must be at least 8 characters." + } + Modal.ShowDialog() + PasswordInput.Alert() + Exit Sub + End If + If PasswordInput.Text <> ConfirmPasswordInput.Text Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Passwords do not match." + } + Modal.ShowDialog() + ConfirmPasswordInput.Alert() + Exit Sub + End If + + Dim data As New Dictionary(Of String, String) From { + {"firstName", FirstNameInput.Text}, + {"lastName", LastNameInput.Text}, + {"email", EmailInput.Text}, + {"password", PasswordInput.Text}, + {"role", "bscpe"} + } + Try + Dim response As String = Globals.API("POST", "setup/admin", Globals.DictionaryToJSON(data)) + Me.GoToForm(New BSCpE_SetupData) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try End Sub @@ -122,7 +202,7 @@ Public Class BSCpE_ProgramHeadSetup Dim Background As New Bitmap(Me.Contents.Width, Me.Contents.Height) - Dim SetupGraphics = Globals.LoadSvgFromResource("Setup Graphics").Draw() + Dim SetupGraphics = Globals.LoadSvgFromResource("BSCpE Setup Graphics").Draw() Dim HalfTrapezoid = Globals.LoadSvgFromResource("Half Trapezoid").Draw() Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() diff --git a/BSCpE_SetupData.vb b/BSCpE_SetupData.vb index 9b22a24..8500cc1 100644 --- a/BSCpE_SetupData.vb +++ b/BSCpE_SetupData.vb @@ -1,5 +1,7 @@ Imports System.Resources Imports Svg +Imports System.IO +Imports System.Net Public Class BSCpE_SetupData Inherits BaseForm @@ -39,28 +41,22 @@ Public Class BSCpE_SetupData Dim CoursesInput As New FileInputPanel With { .Label = "Courses", .Description = "Upload .csv file of courses.", - .Format = "courseCode, description, units" + .Format = "courseCode, description, units, yearLevel" } Me.FormPanel.Controls.Add(CoursesInput) - Dim FacultyInput As New FileInputPanel With { - .Label = "Faculty", - .Description = "Upload .csv file of faculty.", - .Format = "facultyID, firstName, lastName" + Dim FacultiesInput As New FileInputPanel With { + .Label = "Faculties", + .Description = "Upload .csv file of faculties.", + .Format = "facultiesID, firstName, lastName, email" } - Me.FormPanel.Controls.Add(FacultyInput) - - Dim FacilitiesInput As New FileInputPanel With { - .Label = "Facilities", - .Description = "Upload .csv file of facilities.", - .Format = "facilityID, name, description" - } - Me.FormPanel.Controls.Add(FacilitiesInput) + Me.FormPanel.Controls.Add(FacultiesInput) Dim StudentsInput As New FileInputPanel With { .Label = "Students", .Description = "Upload .csv file of students.", - .Format = "studentID, firstName, lastName, section, courses, regular" + .Format = "studentID, firstName, lastName, email, section", + .Name = "StudentsInput" } Me.FormPanel.Controls.Add(StudentsInput) @@ -84,6 +80,14 @@ Public Class BSCpE_SetupData End If i = i + 1 Next + StudentsInput.MinimumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) + StudentsInput.MaximumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) Dim SubmitButton As New BaseButton With { .Name = "Submit", @@ -94,6 +98,84 @@ Public Class BSCpE_SetupData Me.FormPanel.Bottom + Globals.Unit(1) ) AddHandler SubmitButton.Click, Sub() + Dim Data As New Dictionary(Of String, String) + If CoursesInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for courses." + } + Modal.ShowDialog() + CoursesInput.Alert() + Exit Sub + Else + Try + Data.Add("courses", File.ReadAllText(CoursesInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + CoursesInput.Alert() + Exit Sub + End Try + End If + If FacultiesInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for faculties." + } + Modal.ShowDialog() + FacultiesInput.Alert() + Exit Sub + Else + Try + Data.Add("faculties", File.ReadAllText(FacultiesInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + FacultiesInput.Alert() + Exit Sub + End Try + End If + If StudentsInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for students." + } + Modal.ShowDialog() + StudentsInput.Alert() + Exit Sub + Else + Try + Data.Add("students", File.ReadAllText(StudentsInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + StudentsInput.Alert() + Exit Sub + End Try + End If + Data.Add("program", "bscpe") + Try + Dim response As String = Globals.API("POST", "setup/program", Globals.DictionaryToJSON(Data)) + Me.GoToForm(New Facility_SetupData) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try End Sub Me.Contents.Controls.Add(SubmitButton) @@ -125,6 +207,14 @@ Public Class BSCpE_SetupData Control.MaximumSize.Height ) Next + Me.FormPanel.Controls("StudentsInput").MinimumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) + Me.FormPanel.Controls("StudentsInput").MaximumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) Me.Contents.Controls("Submit").Location = New Point( Me.FormPanel.Right - Me.Contents.Controls("Submit").Width, Me.FormPanel.Bottom + Globals.Unit(1) @@ -199,14 +289,16 @@ Public Class BSCpE_SetupData End Set End Property + Public FilePath As String + Public Sub New() Me.AutoSize = True Dim Label As New Transparent.Label With { .Text = "File", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(1)), - .MaximumSize = New Size(Me.Width, Globals.Unit(1)), + .MinimumSize = New Size(Me.Width, Globals.Unit(1.25)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1.25)), .Location = New Point(0, 0), .Font = Globals.GetFont("Raleway", Globals.Unit(0.75), FontStyle.Bold), .ForeColor = Globals.Palette("Secondary"), @@ -218,8 +310,8 @@ Public Class BSCpE_SetupData Dim Description As New Transparent.Label With { .Text = "Upload .csv file of courses.", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), .Location = New Point(0, Globals.Unit(1.5)), .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), .ForeColor = Globals.Palette("Plain Dark"), @@ -231,8 +323,8 @@ Public Class BSCpE_SetupData Dim FormatLabel As New Transparent.Label With { .Text = "Format:", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), .Location = New Point(0, Globals.Unit(2.5)), .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), .ForeColor = Globals.Palette("Secondary"), @@ -243,8 +335,8 @@ Public Class BSCpE_SetupData Dim FormatDescription As New Transparent.Label With { .Text = "Course Code, Course Title, Course Description, Course Units", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(1)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1)), .Location = New Point(0, Globals.Unit(3)), .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), .ForeColor = Globals.Palette("Plain Dark"), @@ -255,12 +347,15 @@ Public Class BSCpE_SetupData Dim FileInput As New BaseFileInput With { .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(4)), - .MaximumSize = New Size(Me.Width, Globals.Unit(4)), + .MaximumSize = New Size(Me.Width, Globals.Unit(3)), + .MinimumSize = New Size(Me.Width, Globals.Unit(3)), .Location = New Point(0, Globals.Unit(4)), .Name = "FileInput" } Me.Controls.Add(FileInput) + AddHandler FileInput.FileSelected, Sub() + Me.FilePath = FileInput.FilePath + End Sub End Sub Protected Sub FileInputPanel_Resize(sender As Object, e As EventArgs) Handles Me.Resize @@ -269,5 +364,10 @@ Public Class BSCpE_SetupData Control.MaximumSize = New Size(Me.Width, Control.MaximumSize.Height) Next End Sub + + Public Sub Alert() + Dim FileInput As BaseFileInput = Me.Controls("FileInput") + FileInput.Alert() + End Sub End Class End Class \ No newline at end of file diff --git a/BSIT_ProgramHeadSetup.vb b/BSIT_ProgramHeadSetup.vb index 484bcf4..5665349 100644 --- a/BSIT_ProgramHeadSetup.vb +++ b/BSIT_ProgramHeadSetup.vb @@ -1,4 +1,7 @@ -Imports System.Resources +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions Imports Svg Public Class BSIT_ProgramHeadSetup @@ -35,7 +38,7 @@ Public Class BSIT_ProgramHeadSetup Me.FormPanel.Controls.Add(Logo) Dim Intro As New Transparent.Label With { - .Text = "Continue by registering the BSIT Program head.", + .Text = "Continue by registering the BSIT Program Head.", .MaximumSize = New Size(Me.FormPanel.Width, 0), .MinimumSize = New Size(Me.FormPanel.Width, 0), .AutoSize = True, @@ -45,11 +48,22 @@ Public Class BSIT_ProgramHeadSetup } Me.FormPanel.Controls.Add(Intro) - Dim NameInput As New BaseTextInput With { - .Name = "Name", + Dim NamePanel As New Transparent.Panel With { .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } - Me.FormPanel.Controls.Add(NameInput) + Me.FormPanel.Controls.Add(NamePanel) + Dim FirstNameInput As New BaseTextInput With { + .Name = "First Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + FirstNameInput.Location = New Point(0, 0) + NamePanel.Controls.Add(FirstNameInput) + Dim LastNameInput As New BaseTextInput With { + .Name = "Last Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + LastNameInput.Location = New Point(NamePanel.Width - LastNameInput.Width, 0) + NamePanel.Controls.Add(LastNameInput) Dim EmailInput As New BaseTextInput With { .Name = "Email", .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) @@ -70,11 +84,77 @@ Public Class BSIT_ProgramHeadSetup Dim ContinueButton As New BaseButton With { .Text = "Continue", - .Name = "Continue" + .Name = "Continue", + .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } Me.FormPanel.Controls.Add(ContinueButton) AddHandler ContinueButton.Click, Sub() - Me.GoToForm(New BSIT_SetupData) + If FirstNameInput.Text = "" And FirstNameInput.Text.Length < 3 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "First Name must be at least 3 characters." + } + Modal.ShowDialog() + FirstNameInput.Alert() + Exit Sub + End If + If LastNameInput.Text = "" And LastNameInput.Text.Length < 2 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Last Name must be at least 2 characters." + } + Modal.ShowDialog() + LastNameInput.Alert() + Exit Sub + End If + If EmailInput.Text = "" And Not New Regex("^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").IsMatch(EmailInput.Text) Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Invalid email address." + } + Modal.ShowDialog() + EmailInput.Alert() + Exit Sub + End If + If PasswordInput.Text = "" And PasswordInput.Text.Length < 8 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Password must be at least 8 characters." + } + Modal.ShowDialog() + PasswordInput.Alert() + Exit Sub + End If + If PasswordInput.Text <> ConfirmPasswordInput.Text Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Passwords do not match." + } + Modal.ShowDialog() + ConfirmPasswordInput.Alert() + Exit Sub + End If + + Dim data As New Dictionary(Of String, String) From { + {"firstName", FirstNameInput.Text}, + {"lastName", LastNameInput.Text}, + {"email", EmailInput.Text}, + {"password", PasswordInput.Text}, + {"role", "bsit"} + } + Try + Dim response As String = Globals.API("POST", "setup/admin", Globals.DictionaryToJSON(data)) + Me.GoToForm(New BSIT_SetupData) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try End Sub @@ -122,7 +202,7 @@ Public Class BSIT_ProgramHeadSetup Dim Background As New Bitmap(Me.Contents.Width, Me.Contents.Height) - Dim SetupGraphics = Globals.LoadSvgFromResource("Setup Graphics").Draw() + Dim SetupGraphics = Globals.LoadSvgFromResource("BSIT Setup Graphics").Draw() Dim HalfTrapezoid = Globals.LoadSvgFromResource("Half Trapezoid").Draw() Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() diff --git a/BSIT_SetupData.vb b/BSIT_SetupData.vb index c06d25a..91ab82e 100644 --- a/BSIT_SetupData.vb +++ b/BSIT_SetupData.vb @@ -1,5 +1,7 @@ Imports System.Resources Imports Svg +Imports System.IO +Imports System.Net Public Class BSIT_SetupData Inherits BaseForm @@ -39,28 +41,22 @@ Public Class BSIT_SetupData Dim CoursesInput As New FileInputPanel With { .Label = "Courses", .Description = "Upload .csv file of courses.", - .Format = "courseCode, description, units" + .Format = "courseCode, description, units, yearLevel" } Me.FormPanel.Controls.Add(CoursesInput) - Dim FacultyInput As New FileInputPanel With { - .Label = "Faculty", - .Description = "Upload .csv file of faculty.", - .Format = "facultyID, firstName, lastName" + Dim FacultiesInput As New FileInputPanel With { + .Label = "Faculties", + .Description = "Upload .csv file of faculties.", + .Format = "facultiesID, firstName, lastName, email" } - Me.FormPanel.Controls.Add(FacultyInput) - - Dim FacilitiesInput As New FileInputPanel With { - .Label = "Facilities", - .Description = "Upload .csv file of facilities.", - .Format = "facilityID, name, description" - } - Me.FormPanel.Controls.Add(FacilitiesInput) + Me.FormPanel.Controls.Add(FacultiesInput) Dim StudentsInput As New FileInputPanel With { .Label = "Students", .Description = "Upload .csv file of students.", - .Format = "studentID, firstName, lastName, section, courses, regularity" + .Format = "studentID, firstName, lastName, email, section", + .Name = "StudentsInput" } Me.FormPanel.Controls.Add(StudentsInput) @@ -84,6 +80,14 @@ Public Class BSIT_SetupData End If i = i + 1 Next + StudentsInput.MinimumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) + StudentsInput.MaximumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + 0 + ) Dim SubmitButton As New BaseButton With { .Name = "Submit", @@ -94,7 +98,84 @@ Public Class BSIT_SetupData Me.FormPanel.Bottom + Globals.Unit(1) ) AddHandler SubmitButton.Click, Sub() - Me.GoToForm(New BSCpE_ProgramHeadSetup) + Dim Data As New Dictionary(Of String, String) + If CoursesInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for courses." + } + Modal.ShowDialog() + CoursesInput.Alert() + Exit Sub + Else + Try + Data.Add("courses", File.ReadAllText(CoursesInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + CoursesInput.Alert() + Exit Sub + End Try + End If + If FacultiesInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for faculties." + } + Modal.ShowDialog() + FacultiesInput.Alert() + Exit Sub + Else + Try + Data.Add("faculties", File.ReadAllText(FacultiesInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + FacultiesInput.Alert() + Exit Sub + End Try + End If + If StudentsInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for students." + } + Modal.ShowDialog() + StudentsInput.Alert() + Exit Sub + Else + Try + Data.Add("students", File.ReadAllText(StudentsInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + StudentsInput.Alert() + Exit Sub + End Try + End If + Data.Add("program", "bsit") + Try + Dim response As String = Globals.API("POST", "setup/program", Globals.DictionaryToJSON(Data)) + Me.GoToForm(New BSCpE_ProgramHeadSetup) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try End Sub Me.Contents.Controls.Add(SubmitButton) @@ -126,6 +207,14 @@ Public Class BSIT_SetupData Control.MaximumSize.Height ) Next + Me.FormPanel.Controls("StudentsInput").MinimumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + Me.FormPanel.Controls("StudentsInput").MaximumSize.Height + ) + Me.FormPanel.Controls("StudentsInput").MaximumSize = New Size( + (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth), + Me.FormPanel.Controls("StudentsInput").MaximumSize.Height + ) Me.Contents.Controls("Submit").Location = New Point( Me.FormPanel.Right - Me.Contents.Controls("Submit").Width, Me.FormPanel.Bottom + Globals.Unit(1) @@ -200,14 +289,16 @@ Public Class BSIT_SetupData End Set End Property + Public FilePath As String + Public Sub New() Me.AutoSize = True Dim Label As New Transparent.Label With { .Text = "File", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(1)), - .MaximumSize = New Size(Me.Width, Globals.Unit(1)), + .MinimumSize = New Size(Me.Width, Globals.Unit(1.25)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1.25)), .Location = New Point(0, 0), .Font = Globals.GetFont("Raleway", Globals.Unit(0.75), FontStyle.Bold), .ForeColor = Globals.Palette("Secondary"), @@ -219,8 +310,8 @@ Public Class BSIT_SetupData Dim Description As New Transparent.Label With { .Text = "Upload .csv file of courses.", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), .Location = New Point(0, Globals.Unit(1.5)), .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), .ForeColor = Globals.Palette("Plain Dark"), @@ -232,8 +323,8 @@ Public Class BSIT_SetupData Dim FormatLabel As New Transparent.Label With { .Text = "Format:", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), .Location = New Point(0, Globals.Unit(2.5)), .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), .ForeColor = Globals.Palette("Secondary"), @@ -244,8 +335,8 @@ Public Class BSIT_SetupData Dim FormatDescription As New Transparent.Label With { .Text = "Course Code, Course Title, Course Description, Course Units", .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(0.5)), - .MaximumSize = New Size(Me.Width, Globals.Unit(0.5)), + .MinimumSize = New Size(Me.Width, Globals.Unit(1)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1)), .Location = New Point(0, Globals.Unit(3)), .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), .ForeColor = Globals.Palette("Plain Dark"), @@ -256,12 +347,15 @@ Public Class BSIT_SetupData Dim FileInput As New BaseFileInput With { .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(4)), - .MaximumSize = New Size(Me.Width, Globals.Unit(4)), + .MaximumSize = New Size(Me.Width, Globals.Unit(3)), + .MinimumSize = New Size(Me.Width, Globals.Unit(3)), .Location = New Point(0, Globals.Unit(4)), .Name = "FileInput" } Me.Controls.Add(FileInput) + AddHandler FileInput.FileSelected, Sub() + Me.FilePath = FileInput.FilePath + End Sub End Sub Protected Sub FileInputPanel_Resize(sender As Object, e As EventArgs) Handles Me.Resize @@ -270,5 +364,10 @@ Public Class BSIT_SetupData Control.MaximumSize = New Size(Me.Width, Control.MaximumSize.Height) Next End Sub + + Public Sub Alert() + Dim FileInput As BaseFileInput = Me.Controls("FileInput") + FileInput.Alert() + End Sub End Class End Class \ No newline at end of file diff --git a/BackendSocket.vb b/BackendSocket.vb new file mode 100644 index 0000000..b296251 --- /dev/null +++ b/BackendSocket.vb @@ -0,0 +1,53 @@ +Imports WebSocketSharp + +Module BackendSocket + Public ws As WebSocket + Public connected As Boolean = False + Public clientRoom As String + Public Sub Connect(room As String) + ws = New WebSocket("ws://localhost:" & Globals.PORT) + AddHandler ws.OnOpen, Sub() + RaiseEvent OnOpen() + ws.Send( + Globals.DictionaryToJSON(New Dictionary(Of String, String) From {{"room", room}, {"type", "join"}}) + ) + End Sub + AddHandler ws.OnMessage, Sub(sender, e) RaiseEvent OnMessage(e.Data) + AddHandler ws.OnClose, Sub(sender, e) RaiseEvent OnClose(sender, e) + AddHandler ws.OnError, Sub(sender, e) + MsgBox(e) + ws.Connect() + End Sub + + ws.Connect() + clientRoom = room + + 'Timer every 5 seconds to check if the connection is still alive + Dim timer As New System.Timers.Timer(5000) + AddHandler timer.Elapsed, Sub() + If Not ws.IsAlive Then + ws.Connect() + End If + End Sub + timer.Start() + End Sub + + Public Event OnOpen() + Public Event OnMessage(data As String) + Public Event OnClose(sender As Object, e As CloseEventArgs) + + Public Sub Send(data As String) + ws.Send(data) + End Sub + + Public Sub Close() + Try + ws.Send( + Globals.DictionaryToJSON(New Dictionary(Of String, String) From {{"room", clientRoom}, {"type", "leave"}}) + ) + Catch ex As Exception + + End Try + ws.Close() + End Sub +End Module diff --git a/BaseButton.vb b/BaseButton.vb index 0393adc..c9f5e8b 100644 --- a/BaseButton.vb +++ b/BaseButton.vb @@ -4,6 +4,18 @@ Imports System.IO Public Class BaseButton Inherits Transparent.Button + Property SubButton As Boolean + Get + Return SubButton + End Get + Set(value As Boolean) + Me.ForeColor = Globals.Palette("Plain Light") + Me.BackColor = Globals.Palette("Plain Dark") + Me.FlatAppearance.MouseOverBackColor = Globals.Palette("Secondary") + Me.FlatAppearance.MouseDownBackColor = Globals.Palette("Secondary") + End Set + End Property + Public Sub New() Me.Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold) Me.ForeColor = Globals.Palette("Plain Light") @@ -12,8 +24,15 @@ Public Class BaseButton Me.FlatStyle = FlatStyle.Flat Me.FlatAppearance.BorderSize = 0 Me.FlatAppearance.BorderColor = Nothing - Me.FlatAppearance.MouseOverBackColor = Globals.Palette("Primary") - Me.FlatAppearance.MouseDownBackColor = Globals.Palette("Primary") + Me.FlatAppearance.MouseOverBackColor = Globals.Palette("Plain Dark") + Me.FlatAppearance.MouseDownBackColor = Globals.Palette("Plain Dark") + + If SubButton Then + Me.ForeColor = Globals.Palette("Plain Light") + Me.BackColor = Globals.Palette("Plain Dark") + Me.FlatAppearance.MouseOverBackColor = Globals.Palette("Secondary") + Me.FlatAppearance.MouseDownBackColor = Globals.Palette("Secondary") + End If Me.MinimumSize = New Size(0, Globals.Unit(1)) Me.MaximumSize = New Size(0, Globals.Unit(1)) @@ -22,15 +41,21 @@ Public Class BaseButton Me.AutoSize = True End Sub - Protected Overrides Sub OnPaint(e As PaintEventArgs) - Dim path As New GraphicsPath - Dim radius As Integer = Me.Height * 0.5 - path.AddArc(0, 0, radius * 2, radius * 2, 90, 180) - path.AddArc(Me.Width - radius * 2, 0, radius * 2, radius * 2, 270, 180) - path.CloseAllFigures() - Me.Region = New Region(path) + Protected Sub BaseButton_GotFocus(sender As Object, e As EventArgs) Handles Me.GotFocus + Me.BackColor = Globals.Palette("Plain Dark") + + If SubButton Then + Me.ForeColor = Globals.Palette("Plain Light") + Me.BackColor = Globals.Palette("Plain Dark") + End If + End Sub + Protected Sub BaseButton_LostFocus(sender As Object, e As EventArgs) Handles Me.LostFocus + Me.BackColor = Globals.Palette("Secondary") - MyBase.OnPaint(e) + If SubButton Then + Me.ForeColor = Globals.Palette("Plain Light") + Me.BackColor = Globals.Palette("Plain Dark") + End If End Sub End Class diff --git a/BaseComboBox.vb b/BaseComboBox.vb new file mode 100644 index 0000000..043a9ea --- /dev/null +++ b/BaseComboBox.vb @@ -0,0 +1,3 @@ +Public Class BaseComboBox + +End Class diff --git a/BaseDropDown.vb b/BaseDropDown.vb new file mode 100644 index 0000000..a331ede --- /dev/null +++ b/BaseDropDown.vb @@ -0,0 +1,170 @@ +Public Class BaseDropDown + Inherits Transparent.Panel + + Private WithEvents ComboBox As ComboBox + Private WithEvents Label As Label + Private WithEvents PlaceHolderElement As Label + Private WithEvents DropDownArrow As Transparent.PictureBox + + Property Items As List(Of String) + Get + Dim toReturm As New List(Of String) + For Each item In ComboBox.Items + toReturm.Add(item) + Next + Return toReturm + End Get + Set + ComboBox.Items.Clear() + For Each item In Value + ComboBox.Items.Add(item) + Next + End Set + End Property + + Property SelectedIndex As Integer + Get + Return ComboBox.SelectedIndex + End Get + Set(value As Integer) + ComboBox.SelectedIndex = value + End Set + End Property + + Property SelectedItem As String + Get + Return ComboBox.SelectedItem + End Get + Set(value As String) + ComboBox.SelectedItem = value + PlaceHodlder = value + RaiseEvent SelectedIndexChanged(Me, Nothing) + End Set + End Property + + Property Name As String + Get + Return Label.Text + End Get + Set(value As String) + Label.Text = value + End Set + End Property + + Property PlaceHodlder As String + Get + Return PlaceHolderElement.Text + End Get + Set(value As String) + PlaceHolderElement.Text = value + End Set + End Property + + Sub New() + Me.BorderStyle = BorderStyle.None + Me.Padding = New Padding(0) + + Me.DropDownArrow = New Transparent.PictureBox With { + .Image = Globals.LoadSvgFromResource("DropDown Arrow", New Size(Globals.Unit(1), Globals.Unit(1))).Draw(), + .BackColor = Globals.Palette("Plain Light"), + .Size = New Size(Globals.Unit(0.75), Globals.Unit(0.5)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand + } + Me.Controls.Add(Me.DropDownArrow) + + Me.Label = New Label With { + .Location = New Point(0, 0), + .Size = New Size(Me.Width, Globals.Unit(0.5)), + .Text = "ComboBox", + .TextAlign = ContentAlignment.MiddleLeft, + .ForeColor = Globals.Palette("Plain Dark"), + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold) + } + Me.Controls.Add(Me.Label) + + Me.PlaceHolderElement = New Label With { + .Location = New Point(0, Label.Height + Globals.Unit(0.25)), + .Size = New Size(Me.Width, Globals.Unit(0.75)), + .TextAlign = ContentAlignment.MiddleLeft, + .BackColor = Globals.Palette("Plain Light"), + .ForeColor = Globals.Palette("Plain Dark"), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .Padding = New Padding(Globals.Unit(0.25), 0, Globals.Unit(0.25), 0) + } + Me.Controls.Add(Me.PlaceHolderElement) + + Me.ComboBox = New ComboBox With { + .Location = New Point(0, Label.Height + Globals.Unit(0.25)), + .Size = New Size(Me.Width, Globals.Unit(0.75)), + .DropDownStyle = ComboBoxStyle.DropDownList, + .FlatStyle = FlatStyle.Flat, + .BackColor = Globals.Palette("Plain Light"), + .ForeColor = Globals.Palette("Plain Dark"), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular) + } + Me.Controls.Add(Me.ComboBox) + + AddHandler PlaceHolderElement.Click, Sub() + Me.ComboBox.DroppedDown = True + End Sub + Me.DropDownArrow.Location = New Point( + Me.Width - Me.DropDownArrow.Width - Globals.Unit(0.25), + Me.PlaceHolderElement.Top + (Me.PlaceHolderElement.Height / 2 - Me.DropDownArrow.Height / 2) + ) + End Sub + + Protected Sub ComboBox_DropDownClosed(sender As Object, e As EventArgs) Handles ComboBox.DropDownClosed + If Me.ComboBox.SelectedIndex = -1 Then + Me.PlaceHolderElement.Text = Me.PlaceHodlder + Me.SelectedItem = "" + Else + Me.PlaceHolderElement.Text = Me.ComboBox.SelectedItem + Me.SelectedItem = Me.ComboBox.SelectedItem + End If + + RaiseEvent SelectedIndexChanged(Me, e) + End Sub + + Public Event SelectedIndexChanged(sender As Object, e As EventArgs) + + Protected Sub ComboBox_Resized(sender As Object, e As EventArgs) Handles Me.Resize + Me.Label.Size = New Size(Me.Width, Me.Label.Height) + Me.PlaceHolderElement.Size = New Size(Me.Width, Me.PlaceHolderElement.Height) + Me.ComboBox.Size = New Size(Me.Width, Me.ComboBox.Height) + Me.DropDownArrow.Location = New Point( + Me.Width - Me.DropDownArrow.Width - Globals.Unit(0.25), + Me.PlaceHolderElement.Top + (Me.PlaceHolderElement.Height / 2 - Me.DropDownArrow.Height / 2) + ) + End Sub + + Private Timer As Timer + Private TimerToStop As Timer + Public Sub Alert() + Dim Alerted As Boolean = False + Me.Timer = New Timer With { + .Interval = Globals.Unit(3) + } + AddHandler Timer.Tick, Sub() + If Alerted Then + Me.PlaceHolderElement.BackColor = Globals.Palette("Plain Light") + Alerted = False + Else + Me.PlaceHolderElement.BackColor = Globals.Palette("Primary Compliment") + Alerted = True + End If + End Sub + Timer.Start() + Me.TimerToStop = New Timer With { + .Interval = Globals.Unit(50) + } + AddHandler TimerToStop.Tick, Sub() + Timer.Stop() + TimerToStop.Stop() + Me.PlaceHolderElement.BackColor = Globals.Palette("Plain Light") + End Sub + TimerToStop.Start() + Me.ComboBox.DroppedDown = True + My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk) + End Sub +End Class diff --git a/BaseFileInput.vb b/BaseFileInput.vb index cfe04ee..d853d55 100644 --- a/BaseFileInput.vb +++ b/BaseFileInput.vb @@ -1,151 +1,269 @@ Imports System.Drawing.Drawing2D +Imports System.IO Public Class BaseFileInput Inherits Transparent.Panel Private color As Color = Globals.Palette("Secondary") + Public FilePath As String = "" Public Sub New() Me.BackColor = Globals.Palette("Plain Light") Me.BorderStyle = BorderStyle.None - Me.MinimumSize = New Size(Me.Width, Globals.Unit(4)) - Me.MaximumSize = New Size(Me.Width, Globals.Unit(4)) Me.AutoSize = True + Me.MaximumSize = New Size(Globals.Unit(7), Globals.Unit(3)) + Me.MinimumSize = New Size(Globals.Unit(7), Globals.Unit(3)) + Dim Icon As New PictureBox With { .Size = New Size(Globals.Unit(1), Globals.Unit(1)), .Image = Globals.LoadSvgFromResource("File Input Icon", New Size(Globals.Unit(1), Globals.Unit(1))).Draw(), .SizeMode = PictureBoxSizeMode.Zoom, .Name = "Icon" } - AddHandler Icon.MouseEnter, AddressOf BaseFileInput_MouseEnter - AddHandler Icon.MouseLeave, AddressOf BaseFileInput_MouseLeave - Icon.Location = New Point(Me.Width * 0.5 - Icon.Width * 0.5, Globals.Unit(1)) + Icon.Location = New Point((Me.Width - Icon.Width) / 0.5, Globals.Unit(0.75)) Me.Controls.Add(Icon) - Dim Label As New Transparent.Label With { - .Text = "Click or Drag and Drop .csv file.", - .AutoSize = True, - .MinimumSize = New Size(Me.Width, Globals.Unit(1)), - .MaximumSize = New Size(Me.Width, Globals.Unit(1)), - .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + Dim Label As New Label With { + .Text = "Click to upload a .csv file.", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), .ForeColor = color, - .TextAlign = ContentAlignment.MiddleCenter, - .Name = "Label" + .AutoSize = True, + .Name = "Label", + .TextAlign = ContentAlignment.MiddleCenter } - Label.Location = New Point(Me.Width * 0.5 - Label.Width * 0.5, Globals.Unit(2.5)) - AddHandler Label.MouseEnter, AddressOf BaseFileInput_MouseEnter - AddHandler Label.MouseLeave, AddressOf BaseFileInput_MouseLeave + Label.Location = New Point((Me.Width - Label.Width) / 2, Globals.Unit(2)) Me.Controls.Add(Label) + + For Each Control As Control In Me.Controls + AddHandler Control.MouseEnter, AddressOf BaseFileInput_MouseEnter + AddHandler Control.MouseLeave, AddressOf BaseFileInput_MouseLeave + AddHandler Control.Click, AddressOf BaseFileInput_Click + Next End Sub Protected Sub BaseFileInput_Resize(sender As Object, e As EventArgs) Handles Me.Resize For Each Control As Control In Me.Controls - If Control.Name = "Label" Then - Control.MinimumSize = New Size(Me.Width, Globals.Unit(1)) - Control.MaximumSize = New Size(Me.Width, Globals.Unit(1)) - End If - - Control.Location = New Point(Me.Width * 0.5 - Control.Width * 0.5, Control.Location.Y) + Control.Location = New Point((Me.Width - Control.Width) / 2, Control.Location.Y) Next End Sub Protected Sub BaseFileInput_MouseEnter(sender As Object, e As EventArgs) Handles Me.MouseEnter - Me.color = Globals.Palette("Plain Dark") - Me.Controls("Label").ForeColor = Me.color - Dim icon As PictureBox = Me.Controls("Icon") - icon.Image = Globals.LoadSvgFromResource("File Input Icon Hovered", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + color = Globals.Palette("Plain Dark") + Dim Icon As PictureBox = Me.Controls("Icon") + Icon.Image = Globals.LoadSvgFromResource("File Input Icon Hovered", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + For Each Control As Control In Me.Controls + Control.ForeColor = color + Next Me.Refresh() End Sub + Protected Sub BaseFileInput_MouseLeave(sender As Object, e As EventArgs) Handles Me.MouseLeave - Me.color = Globals.Palette("Secondary") - Me.Controls("Label").ForeColor = Me.color - Dim icon As PictureBox = Me.Controls("Icon") - icon.Image = Globals.LoadSvgFromResource("File Input Icon", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + color = Globals.Palette("Secondary") + Dim Icon As PictureBox = Me.Controls("Icon") + Icon.Image = Globals.LoadSvgFromResource("File Input Icon", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + For Each Control As Control In Me.Controls + Control.ForeColor = color + Next Me.Refresh() End Sub Protected Overrides Sub OnPaint(e As PaintEventArgs) MyBase.OnPaint(e) - Dim g As Graphics = e.Graphics Dim Pen As New Pen(color, Globals.Unit(0.1)) + 'TopLeft g.DrawArc( - Pen, - 0, - 0, - CInt(Globals.Unit(1)), - CInt(Globals.Unit(1)), - 180, - 90 - ) + Pen, + Pen.Width / 2, + Pen.Width / 2, + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 180, + 90 + ) + 'Left g.DrawLine( - Pen, - CInt(Globals.Unit(0.5)), - 0, - CInt(Me.Width - Globals.Unit(0.5)), - 0 - ) + Pen, + Pen.Width / 2, + CInt(Pen.Width / 2 + Globals.Unit(0.25)), + Pen.Width / 2, + CInt(Me.Height - Pen.Width / 2 - Globals.Unit(0.25)) + ) + 'BottomLeft g.DrawArc( - Pen, - CInt(Me.Width - Globals.Unit(1)), - 0, - CInt(Globals.Unit(1)), - CInt(Globals.Unit(1)), - 270, - 90 - ) + Pen, + Pen.Width / 2, + CInt(Me.Height - Globals.Unit(0.5) - Pen.Width), + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 90, + 90 + ) + 'Bottom g.DrawLine( - Pen, - Me.Width, - CInt(Globals.Unit(0.5)), - Me.Width, - CInt(Me.Height - Globals.Unit(0.5)) - ) + Pen, + CInt(Pen.Width / 2 + Globals.Unit(0.25)), + CInt(Me.Height - Pen.Width / 2), + CInt(Me.Width - Pen.Width / 2 - Globals.Unit(0.25)), + CInt(Me.Height - Pen.Width / 2) + ) + 'BottomRight g.DrawArc( - Pen, - CInt(Me.Width - Globals.Unit(1)), - CInt(Me.Height - Globals.Unit(1)), - CInt(Globals.Unit(1)), - CInt(Globals.Unit(1)), - 0, - 90 - ) + Pen, + CInt(Me.Width - Globals.Unit(0.5) - Pen.Width), + CInt(Me.Height - Globals.Unit(0.5) - Pen.Width), + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 0, + 90 + ) + 'Right g.DrawLine( - Pen, - CInt(Me.Width - Globals.Unit(0.5)), - Me.Height, - CInt(Globals.Unit(0.5)), - Me.Height - ) + Pen, + CInt(Me.Width - Pen.Width / 2), + CInt(Me.Height - Globals.Unit(0.25) - Pen.Width / 2), + CInt(Me.Width - Pen.Width / 2), + CInt(Pen.Width / 2 + Globals.Unit(0.25)) + ) + 'TopRight g.DrawArc( - Pen, - 0, - CInt(Me.Height - Globals.Unit(1)), - CInt(Globals.Unit(1)), - CInt(Globals.Unit(1)), - 90, - 90 - ) + Pen, + CInt(Me.Width - Globals.Unit(0.5) - Pen.Width), + Pen.Width / 2, + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 270, + 90 + ) + 'Top g.DrawLine( - Pen, - 0, - CInt(Me.Height - Globals.Unit(0.5)), - 0, - CInt(Globals.Unit(0.5)) - ) - g.Dispose() - - - - Dim path As New GraphicsPath - Dim radius As Integer = Globals.Unit(0.5) - path.AddArc(0, 0, radius * 2, radius * 2, 180, 90) - path.AddArc(Me.Width - radius * 2, 0, radius * 2, radius * 2, 270, 90) - path.AddArc(Me.Width - radius * 2, Me.Height - radius * 2, radius * 2, radius * 2, 0, 90) - path.AddArc(0, Me.Height - radius * 2, radius * 2, radius * 2, 90, 90) - path.CloseAllFigures() - Me.Region = New Region(path) + Pen, + CInt(Me.Width - Globals.Unit(0.25)), + Pen.Width / 2, + CInt(Pen.Width / 2 + Globals.Unit(0.25)), + Pen.Width / 2 + ) + + Dim Path As New GraphicsPath + Path.AddArc( + 0, + 0, + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 180, + 90 + ) + Path.AddLine( + 0, + CInt(Globals.Unit(0.25)), + 0, + CInt(Me.Height - Globals.Unit(0.25)) + ) + Path.AddArc( + 0, + CInt(Me.Height - Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 90, + 90 + ) + Path.AddLine( + CInt(Globals.Unit(0.25)), + Me.Height, + CInt(Me.Width - Globals.Unit(0.25)), + Me.Height + ) + Path.AddArc( + CInt(Me.Width - Globals.Unit(0.5)), + Me.Height - CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 0, + 90 + ) + Path.AddLine( + Me.Width, + CInt(Me.Height - Globals.Unit(0.25)), + Me.Width, + CInt(Globals.Unit(0.25)) + ) + Path.AddArc( + CInt(Me.Width - Globals.Unit(0.5)), + 0, + CInt(Globals.Unit(0.5)), + CInt(Globals.Unit(0.5)), + 270, + 90 + ) + Path.AddLine( + CInt(Me.Width - Globals.Unit(0.25)), + 0, + CInt(Globals.Unit(0.25)), + 0 + ) + + Me.Region = New Region(Path) + End Sub + + Protected Sub BaseFileInput_Click(sender As Object, e As EventArgs) Handles Me.Click + Dim Dialog As New OpenFileDialog With { + .Filter = "CSV Files (*.csv)|*.csv", + .Title = "Select a CSV File" + } + If Dialog.ShowDialog() = DialogResult.OK Then + Dim Label As Label = Me.Controls("Label") + Label.Text = Path.GetFileName(Dialog.FileName) + Me.FilePath = Dialog.FileName + RaiseEvent FileSelected(Me, New EventArgs) + BaseFileInput_Resize(Me, New EventArgs) + End If + End Sub + + Public Event FileSelected(sender As Object, e As EventArgs) + + + Private Timer As Timer + Private TimerToStop As Timer + Public Sub Alert() + Dim Alerted As Boolean = False + Me.Timer = New Timer With { + .Interval = Globals.Unit(3) + } + AddHandler Timer.Tick, Sub() + If Alerted Then + Me.color = Globals.Palette("Secondary") + Dim Label As Label = Me.Controls("Label") + Label.ForeColor = Me.color + Dim Icon As PictureBox = Me.Controls("Icon") + Icon.Image = Globals.LoadSvgFromResource("File Input Icon", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + Alerted = False + Else + Me.color = Globals.Palette("Primary Compliment") + Dim Label As Label = Me.Controls("Label") + Label.ForeColor = Me.color + Dim Icon As PictureBox = Me.Controls("Icon") + Icon.Image = Globals.LoadSvgFromResource("File Input Icon Alerted", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + Alerted = True + End If + Me.Refresh() + End Sub + Timer.Start() + Me.TimerToStop = New Timer With { + .Interval = Globals.Unit(50) + } + AddHandler TimerToStop.Tick, Sub() + Timer.Stop() + TimerToStop.Stop() + Me.color = Globals.Palette("Secondary") + Dim Label As Label = Me.Controls("Label") + Label.ForeColor = Me.color + Dim Icon As PictureBox = Me.Controls("Icon") + Icon.Image = Globals.LoadSvgFromResource("File Input Icon", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() + Me.Refresh() + End Sub + TimerToStop.Start() + My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk) End Sub End Class diff --git a/BaseForm.vb b/BaseForm.vb index fed4dc9..e84664f 100644 --- a/BaseForm.vb +++ b/BaseForm.vb @@ -1,5 +1,8 @@ Imports System.Reflection Imports System.Resources +Imports System.Net.WebSockets +Imports System.Text +Imports System.Threading Public Class BaseForm Inherits Form @@ -7,15 +10,14 @@ Public Class BaseForm Public Sub New() InitializeComponent() SetStyle(ControlStyles.SupportsTransparentBackColor, True) - End Sub - + Me.ShowInTaskbar = False + Me.DoubleBuffered = True + End Sub Public BackgroundBitmap As Bitmap Public Loaded As Boolean = False - - Public Overloads Property Name As String Get Return Me.Text @@ -26,7 +28,7 @@ Public Class BaseForm End Set End Property - Public Sub GoToForm(Form As Form) + Public Sub GoToForm(Form As BaseForm) Dim Location = MyBase.Location Globals.FormLocation = Location Dim Size = MyBase.Size @@ -47,15 +49,14 @@ Public Class BaseForm Me.Icon = My.Resources.ResourceManager.GetObject("Icon") - Me.ShowInTaskbar = True - - Dim HeaderBar As New Panel With { + Me.HeaderBar = New Panel With { .Dock = DockStyle.Top, .Height = Globals.Unit(1), .Cursor = Cursors.SizeAll, - .BackColor = Globals.Palette("Plain Light") + .BackColor = Globals.Palette("Plain Light"), + .Name = "HaderBar" } - Me.Controls.Add(HeaderBar) + Me.Controls.Add(Me.HeaderBar) Dim Title As New Label With { .Name = "titleLabel", @@ -64,12 +65,12 @@ Public Class BaseForm .Text = "BaseForm", .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), .ForeColor = Me.ForeColor, - .BackColor = HeaderBar.BackColor, + .BackColor = Me.HeaderBar.BackColor, .Padding = New Padding(0), .Margin = New Padding(0), .TextAlign = ContentAlignment.MiddleCenter } - HeaderBar.Controls.Add(Title) + Me.HeaderBar.Controls.Add(Title) Dim LogoBox As New FlowLayoutPanel With { .AutoSize = True, @@ -111,9 +112,9 @@ Public Class BaseForm .Image = Globals.LoadSvgFromResource("Header Triangle TitleBox", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() } - HeaderBar.Controls.Add(NameTriangle) - HeaderBar.Controls.Add(NameLabel) - HeaderBar.Controls.Add(LogoBox) + Me.HeaderBar.Controls.Add(NameTriangle) + Me.HeaderBar.Controls.Add(NameLabel) + Me.HeaderBar.Controls.Add(LogoBox) Dim ControlBox As New FlowLayoutPanel With { .Dock = DockStyle.Right, @@ -148,6 +149,7 @@ Public Class BaseForm AddHandler SizeButton.Click, Sub() If Me.WindowState = FormWindowState.Maximized Then Me.WindowState = FormWindowState.Normal + Me.Size = Globals.FormSize Else Me.WindowState = FormWindowState.Maximized End If @@ -189,64 +191,68 @@ Public Class BaseForm .Image = Globals.LoadSvgFromResource("Header Triangle ControlBox", New Size(Globals.Unit(1), Globals.Unit(1))).Draw() } - HeaderBar.Controls.Add(ControlBoxTriangle) - HeaderBar.Controls.Add(ControlBox) + Me.HeaderBar.Controls.Add(ControlBoxTriangle) + Me.HeaderBar.Controls.Add(ControlBox) - AddHandler HeaderBar.MouseDown, AddressOf HeaderBar_MouseDown - AddHandler HeaderBar.MouseMove, AddressOf HeaderBar_MouseMove - AddHandler HeaderBar.MouseUp, AddressOf HeaderBar_MouseUp + AddHandler Me.HeaderBar.MouseDown, AddressOf HeaderBar_MouseDown + AddHandler Me.HeaderBar.MouseMove, AddressOf HeaderBar_MouseMove + AddHandler Me.HeaderBar.MouseUp, AddressOf HeaderBar_MouseUp - Dim resizerBarTop As New Panel With { + Me.resizerBarTop = New Panel With { .Dock = DockStyle.Top, .Height = Globals.Unit(0.1), .Cursor = Cursors.SizeNS, - .BackColor = Me.ForeColor + .BackColor = Me.ForeColor, + .Name = "resizerBarTop" } Me.Controls.Add(resizerBarTop) - AddHandler resizerBarTop.MouseDown, AddressOf ResizerBarTop_MouseDown - AddHandler resizerBarTop.MouseMove, AddressOf ResizerBarTop_MouseMove - AddHandler resizerBarTop.MouseUp, AddressOf ResizerBarTop_MouseUp - Dim resizerBarBottom As New Panel With { + AddHandler Me.resizerBarTop.MouseDown, AddressOf ResizerBarTop_MouseDown + AddHandler Me.resizerBarTop.MouseMove, AddressOf ResizerBarTop_MouseMove + AddHandler Me.resizerBarTop.MouseUp, AddressOf ResizerBarTop_MouseUp + Me.resizerBarBottom = New Panel With { .Dock = DockStyle.Bottom, .Height = Globals.Unit(0.1), .Cursor = Cursors.SizeNS, - .BackColor = Me.ForeColor + .BackColor = Me.ForeColor, + .Name = "resizerBarBottom" } Me.Controls.Add(resizerBarBottom) - AddHandler resizerBarBottom.MouseDown, AddressOf ResizerBarBottom_MouseDown - AddHandler resizerBarBottom.MouseMove, AddressOf ResizerBarBottom_MouseMove - AddHandler resizerBarBottom.MouseUp, AddressOf ResizerBarBottom_MouseUp + AddHandler Me.resizerBarBottom.MouseDown, AddressOf ResizerBarBottom_MouseDown + AddHandler Me.resizerBarBottom.MouseMove, AddressOf ResizerBarBottom_MouseMove + AddHandler Me.resizerBarBottom.MouseUp, AddressOf ResizerBarBottom_MouseUp - Dim resizerBarLeft As New Panel With { + Me.resizerBarLeft = New Panel With { .Dock = DockStyle.Left, .Width = Globals.Unit(0.1), .Cursor = Cursors.SizeWE, - .BackColor = Me.ForeColor + .BackColor = Me.ForeColor, + .Name = "resizerBarLeft" } Me.Controls.Add(resizerBarLeft) - AddHandler resizerBarLeft.MouseDown, AddressOf ResizerBarLeft_MouseDown - AddHandler resizerBarLeft.MouseMove, AddressOf ResizerBarLeft_MouseMove - AddHandler resizerBarLeft.MouseUp, AddressOf ResizerBarLeft_MouseUp - Dim resizerBarRight As New Panel With { + AddHandler Me.resizerBarLeft.MouseDown, AddressOf ResizerBarLeft_MouseDown + AddHandler Me.resizerBarLeft.MouseMove, AddressOf ResizerBarLeft_MouseMove + AddHandler Me.resizerBarLeft.MouseUp, AddressOf ResizerBarLeft_MouseUp + Me.resizerBarRight = New Panel With { .Dock = DockStyle.Right, .Width = Globals.Unit(0.1), .Cursor = Cursors.SizeWE, - .BackColor = Me.ForeColor + .BackColor = Me.ForeColor, + .Name = "resizerBarRight" } Me.Controls.Add(resizerBarRight) - AddHandler resizerBarRight.MouseDown, AddressOf ResizerBarRight_MouseDown - AddHandler resizerBarRight.MouseMove, AddressOf ResizerBarRight_MouseMove - AddHandler resizerBarRight.MouseUp, AddressOf ResizerBarRight_MouseUp + AddHandler Me.resizerBarRight.MouseDown, AddressOf ResizerBarRight_MouseDown + AddHandler Me.resizerBarRight.MouseMove, AddressOf ResizerBarRight_MouseMove + AddHandler Me.resizerBarRight.MouseUp, AddressOf ResizerBarRight_MouseUp Me.Contents = New Panel With { - .Dock = Dock.Fill, .BackColor = Me.BackColor, .AutoScroll = True, - .BackgroundImageLayout = ImageLayout.Zoom + .BackgroundImageLayout = ImageLayout.Zoom, + .Dock = DockStyle.Fill } AddHandler Me.Resize, Sub() Me.Contents.BackgroundImage = BackgroundBitmap @@ -259,12 +265,17 @@ Public Class BaseForm LoopChildrenAddMouseHeaderEvents(HeaderBar) - Me.ShowInTaskbar = False Me.ShowInTaskbar = True End Sub Public Contents As Panel + Public HeaderBar As Panel + Public resizerBarTop As Panel + Public resizerBarBottom As Panel + Public resizerBarLeft As Panel + Public resizerBarRight As Panel + Private Sub LoopChildrenAddMouseHeaderEvents(Control As Control) AddHandler Control.MouseDown, AddressOf HeaderBar_MouseDown AddHandler Control.MouseMove, AddressOf HeaderBar_MouseMove diff --git a/BaseModal.vb b/BaseModal.vb new file mode 100644 index 0000000..eb1d339 --- /dev/null +++ b/BaseModal.vb @@ -0,0 +1,292 @@ +Imports System.Reflection +Imports System.Resources + +Public Class BaseModal + Inherits Form + + Property Title As String + Property Message As String + Property Buttons As Dictionary(Of String, DialogResult) + + Private Timer As Timer + Private TimerToStop As Timer + + Public Sub New() + If Buttons Is Nothing Then + Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK} + } + End If + If Buttons.Count > 2 Then + Throw New Exception("Only 2 buttons are allowed") + End If + End Sub + + Protected Sub BaseModal_Load(sender As Object, e As EventArgs) Handles Me.Load + Me.BackColor = Globals.Palette("White") + Me.ForeColor = Globals.Palette("Secondary") + Me.Size = New Size(Globals.Unit(15), Globals.Unit(10)) + Me.Location = New Point( + (Screen.PrimaryScreen.WorkingArea.Width - Me.Width) / 2, + (Screen.PrimaryScreen.WorkingArea.Height - Me.Height) / 2 + ) + Me.FormBorderStyle = FormBorderStyle.None + Me.ControlBox = False + + Me.Icon = My.Resources.ResourceManager.GetObject("Icon") + + Me.ShowInTaskbar = True + + My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk) + + + + Dim Highlighted As Boolean = False + + + + Dim HeaderBar As New Panel With { + .Size = New Size(Me.Width, Globals.Unit(1)), + .Dock = DockStyle.Top, + .BackColor = Me.ForeColor + } + Me.Controls.Add(HeaderBar) + + Dim TitleLabel As New Label With { + .Text = Me.Title, + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Me.BackColor, + .Size = New Size( + HeaderBar.Width - Globals.Unit(4), + HeaderBar.Height + ), + .AutoSize = False, + .TextAlign = ContentAlignment.MiddleCenter + } + TitleLabel.Location = New Point( + (HeaderBar.Width - TitleLabel.Width) / 2, + (HeaderBar.Height - TitleLabel.Height) / 2 + ) + HeaderBar.Controls.Add(TitleLabel) + + Dim Logo As New PictureBox With { + .SizeMode = PictureBoxSizeMode.Zoom, + .Name = "Logo", + .Size = New Size(Globals.Unit(0.75), Globals.Unit(0.75)), + .Location = New Point(Globals.Unit(1.125), Globals.Unit(0.125)) + } + Dim resourcesManager As ResourceManager = My.Resources.ResourceManager + Dim LogoImage As Image = resourcesManager.GetObject("Logo") + Logo.Image = LogoImage + HeaderBar.Controls.Add(Logo) + + Dim CloseButton As New PictureBox With { + .Name = "CloseButton", + .Size = New Size(Globals.Unit(1), Globals.Unit(1)), + .SizeMode = PictureBoxSizeMode.CenterImage, + .Padding = New Padding(0), + .Margin = New Padding(0), + .Image = Globals.LoadSvgFromResource("Close Window", New Size(Globals.Unit(1), Globals.Unit(1))).Draw(), + .Location = New Point(HeaderBar.Width - Globals.Unit(2), 0) + } + AddHandler CloseButton.MouseEnter, Sub() + Try + CloseButton.BackColor = Color.FromArgb( + Me.ForeColor.R + Globals.Unit(1), + Me.ForeColor.G + Globals.Unit(1), + Me.ForeColor.B + Globals.Unit(1) + ) + Catch ex As Exception + + End Try + End Sub + AddHandler CloseButton.MouseLeave, Sub() + CloseButton.BackColor = Me.ForeColor + End Sub + AddHandler CloseButton.Click, Sub() + Me.DialogResult = DialogResult.Cancel + End Sub + HeaderBar.Controls.Add(CloseButton) + + For Each Control As Control In New List(Of Control) From {HeaderBar, TitleLabel, Logo, CloseButton} + Control.Cursor = Cursors.SizeAll + AddHandler Control.MouseDown, AddressOf HeaderBar_MouseDown + AddHandler Control.MouseMove, AddressOf HeaderBar_MouseMove + AddHandler Control.MouseUp, AddressOf HeaderBar_MouseUp + Next + + Dim MessageLabelPanel As New Panel With { + .Size = New Size( + Me.Width - Globals.Unit(2), + Globals.Unit(3) + ), + .AutoScroll = True + } + MessageLabelPanel.Location = New Point( + (Me.Width - MessageLabelPanel.Width) / 2, + HeaderBar.Height + Globals.Unit(1) + ) + Me.Controls.Add(MessageLabelPanel) + + Dim MessageLabel As New Label With { + .Text = Me.Message, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .MinimumSize = New Size(Me.Width - Globals.Unit(2) - (SystemInformation.VerticalScrollBarWidth * 2), 0), + .MaximumSize = New Size(Me.Width - Globals.Unit(2) - (SystemInformation.VerticalScrollBarWidth * 2), 0), + .AutoSize = True, + .TextAlign = ContentAlignment.MiddleCenter, + .Location = New Point(SystemInformation.VerticalScrollBarWidth, 0) + } + MessageLabelPanel.Controls.Add(MessageLabel) + + Dim ButtonPanel As New Panel With { + .Size = New Size(Me.Width - Globals.Unit(2), Globals.Unit(1)) + } + ButtonPanel.Location = New Point( + (Me.Width - ButtonPanel.Width) / 2, + MessageLabelPanel.Bottom + Globals.Unit(1) + ) + Me.Controls.Add(ButtonPanel) + + Dim Button1 As New BaseButton With { + .Text = Buttons.Keys(0), + .Name = Buttons.Keys(0), + .Size = New Size(Globals.Unit(5), Globals.Unit(1)), + .Location = New Point( + (ButtonPanel.Width - Globals.Unit(5)) / 2, + 0 + ) + } + AddHandler Button1.Click, Sub() + Me.DialogResult = Buttons.Values(0) + End Sub + ButtonPanel.Controls.Add(Button1) + + Dim Button2 As BaseButton + If Buttons.Count = 2 Then + Button2 = New BaseButton With { + .Text = Buttons.Keys(1), + .Name = Buttons.Keys(1), + .Size = New Size(Globals.Unit(5), Globals.Unit(1)) + } + AddHandler Button2.Click, Sub() + Me.DialogResult = Buttons.Values(1) + End Sub + ButtonPanel.Controls.Add(Button2) + + Button1.Location = New Point(0, 0) + Button2.Location = New Point(ButtonPanel.Width - Button2.Width, 0) + End If + + Dim BorderLeft As New Panel With { + .Size = New Size(Globals.Unit(0.1), Me.Height), + .Location = New Point(0, 0), + .BackColor = Me.ForeColor + } + Me.Controls.Add(BorderLeft) + Dim BorderRight As New Panel With { + .Size = New Size(Globals.Unit(0.1), Me.Height), + .Location = New Point(Me.Width - Globals.Unit(0.1), 0), + .BackColor = Me.ForeColor + } + Me.Controls.Add(BorderRight) + Dim BorderBottom As New Panel With { + .Size = New Size(Me.Width, Globals.Unit(0.1)), + .Location = New Point(0, Me.Height - Globals.Unit(0.1)), + .BackColor = Me.ForeColor + } + Me.Controls.Add(BorderBottom) + + Me.Timer = New Timer With { + .Interval = Globals.Unit(3) + } + AddHandler Timer.Tick, Sub() + If Highlighted Then + Me.ForeColor = Globals.Palette("Primary Compliment") + HeaderBar.BackColor = Globals.Palette("Primary Compliment") + Button1.BackColor = Globals.Palette("Primary Compliment") + If Buttons.Count = 2 Then + Button2.BackColor = Globals.Palette("Primary Compliment") + End If + BorderLeft.BackColor = Globals.Palette("Primary Compliment") + BorderRight.BackColor = Globals.Palette("Primary Compliment") + BorderBottom.BackColor = Globals.Palette("Primary Compliment") + Highlighted = False + Else + Me.ForeColor = Globals.Palette("Secondary") + HeaderBar.BackColor = Globals.Palette("Secondary") + Button1.BackColor = Globals.Palette("Secondary") + If Buttons.Count = 2 Then + Button2.BackColor = Globals.Palette("Secondary") + End If + BorderLeft.BackColor = Globals.Palette("Secondary") + BorderRight.BackColor = Globals.Palette("Secondary") + BorderBottom.BackColor = Globals.Palette("Secondary") + Highlighted = True + End If + End Sub + Timer.Start() + Me.TimerToStop = New Timer With { + .Interval = Globals.Unit(50) + } + AddHandler TimerToStop.Tick, Sub() + Timer.Stop() + Timer.Dispose() + TimerToStop.Stop() + TimerToStop.Dispose() + Me.ForeColor = Globals.Palette("Secondary") + HeaderBar.BackColor = Globals.Palette("Secondary") + BorderLeft.BackColor = Globals.Palette("Secondary") + BorderRight.BackColor = Globals.Palette("Secondary") + BorderBottom.BackColor = Globals.Palette("Secondary") + End Sub + TimerToStop.Start() + End Sub + + + + Protected Is_HeaderBar_MouseDown As Boolean = False + Protected MouseLocationInsideTheForm As Point + Protected Sub HeaderBar_MouseDown(sender As Object, e As MouseEventArgs) + Is_HeaderBar_MouseDown = True + MouseLocationInsideTheForm = Me.PointToClient(Cursor.Position) + End Sub + Protected Sub HeaderBar_MouseMove(sender As Object, e As MouseEventArgs) + If Is_HeaderBar_MouseDown Then + If Me.WindowState = FormWindowState.Maximized Then + Me.WindowState = FormWindowState.Normal + End If + + Dim CursorLocation = Cursor.Position + + Dim NewLocation As New Point( + CursorLocation.X - MouseLocationInsideTheForm.X, + CursorLocation.Y - MouseLocationInsideTheForm.Y + ) + + If NewLocation.X < 0 Then + NewLocation = New Point( + 0, + NewLocation.Y + ) + End If + If NewLocation.Y < 0 Then + NewLocation = New Point( + NewLocation.X, + 0 + ) + End If + + Me.Location = NewLocation + End If + End Sub + Protected Sub HeaderBar_MouseUp(sender As Object, e As MouseEventArgs) + Is_HeaderBar_MouseDown = False + + If Me.Location.Y = 0 Then + Me.WindowState = FormWindowState.Maximized + End If + End Sub + +End Class diff --git a/BaseTextInput.vb b/BaseTextInput.vb index fb5883d..c18c057 100644 --- a/BaseTextInput.vb +++ b/BaseTextInput.vb @@ -1,8 +1,8 @@ Public Class BaseTextInput Inherits Control - Public Label As New Transparent.Label - Public TextBox As New Transparent.TextBox + Private Label As New Transparent.Label + Private TextBox As New Transparent.TextBox Private ReadOnly Underline As New Panel Overloads Property Name As String @@ -38,7 +38,6 @@ Me.Controls.Add(Me.TextBox) Me.Controls.Add(Me.Underline) - 'check if intialization doesn't have a size If Me.Size = New Size(0, 0) Then Me.Size = New Size(Globals.Unit(10), Globals.Unit(1)) End If @@ -54,11 +53,10 @@ AddHandler Me.Label.Click, AddressOf FocusTextBox Me.TextBox.Location = New Point(Globals.Unit(0.25), Globals.Unit(0.25)) - Me.TextBox.Size = New Size(Me.Width - Globals.Unit(0.5), Me.Height - Globals.Unit(0.5)) + Me.TextBox.Size = New Size(Me.Width - Globals.Unit(0.5), Me.Height - Globals.Unit(0.25)) Me.TextBox.AutoSize = False Me.TextBox.Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular) Me.TextBox.ForeColor = Globals.Palette("Plain Dark") - Me.TextBox.BackColor = Color.Transparent Me.TextBox.BorderStyle = BorderStyle.None Me.TextBox.TextAlign = HorizontalAlignment.Left @@ -68,6 +66,11 @@ Me.Underline.Location = New Point(0, Me.Height - Globals.Unit(0.05)) Me.Underline.Size = New Size(Me.Width, Globals.Unit(0.05)) Me.Underline.BackColor = Globals.Palette("Plain Dark") + Me.Underline.BringToFront() + + AddHandler Me.GotFocus, Sub() + Me.TextBox.Focus() + End Sub End Sub Private Sub FocusTextBox(sender As Object, e As EventArgs) @@ -95,9 +98,39 @@ Me.Underline.BackColor = Globals.Palette("Plain Dark") End Sub - Protected Sub TextInput_Resize(sender As Object, e As EventArgs) Handles Me.Resize + Protected Sub BaseTextInput_Resize(sender As Object, e As EventArgs) Handles Me.Resize Me.Label.Size = New Size(Me.Width - Globals.Unit(0.5), Globals.Unit(0.5)) - Me.TextBox.Size = New Size(Me.Width - Globals.Unit(0.5), Me.Height - Globals.Unit(0.5)) + Me.TextBox.Size = New Size(Me.Width - Globals.Unit(0.5), Me.Height - Globals.Unit(0.25)) Me.Underline.Size = New Size(Me.Width, Globals.Unit(0.05)) End Sub + + Private Timer As Timer + Private TimerToStop As Timer + Public Sub Alert() + Dim Alerted As Boolean = False + Me.Timer = New Timer With { + .Interval = Globals.Unit(3) + } + AddHandler Timer.Tick, Sub() + If Alerted Then + Me.Underline.BackColor = Globals.Palette("Secondary") + Alerted = False + Else + Me.Underline.BackColor = Globals.Palette("Primary Compliment") + Alerted = True + End If + End Sub + Timer.Start() + Me.TimerToStop = New Timer With { + .Interval = Globals.Unit(50) + } + AddHandler TimerToStop.Tick, Sub() + Timer.Stop() + TimerToStop.Stop() + Me.Underline.BackColor = Globals.Palette("Plain Dark") + End Sub + TimerToStop.Start() + Me.TextBox.Focus() + My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk) + End Sub End Class diff --git a/Dashboard.vb b/Dashboard.vb new file mode 100644 index 0000000..49c3aac --- /dev/null +++ b/Dashboard.vb @@ -0,0 +1,361 @@ +Imports System.Drawing.Drawing2D +Imports System.Resources +Imports System.Text.RegularExpressions +Imports Svg + +Public Class Dashboard + Inherits BaseForm + + Private MainPanel As FlowLayoutPanel + + Class DashboardItem + Inherits Transparent.Panel + + Public VisitButton As New BaseButton With { + .Text = "Visit", + .Location = New Point(Globals.Unit(5), Globals.Unit(8)) + } + + Property ProgramHead As String + Get + Return Me.Controls("ProgramHead").Text + End Get + Set(value As String) + Me.Controls("ProgramHead").Text = value + End Set + End Property + Property Courses As String + Get + Return Me.Controls("Courses").Text + End Get + Set(value As String) + Me.Controls("Courses").Text = value + End Set + End Property + Property Faculties As String + Get + Return Me.Controls("Faculties").Text + End Get + Set(value As String) + Me.Controls("Faculties").Text = value + End Set + End Property + Property Facilities As String + Get + Return Me.Controls("Facilities").Text + End Get + Set(value As String) + Me.Controls("Facilities").Text = value + End Set + End Property + Property Students As String + Get + Return Me.Controls("Students").Text + End Get + Set(value As String) + Me.Controls("Students").Text = value + End Set + End Property + + Property Header As String + Get + Return Me.Controls("Header").Text + End Get + Set(value As String) + Me.Controls("Header").Text = value + End Set + End Property + + Public Sub New() + Me.Width = Globals.Unit(13) + Me.Height = Globals.Unit(10) + Me.Margin = New Padding(0) + Me.Padding = New Padding(0) + Me.BorderStyle = BorderStyle.None + + Dim Header As New Label With { + .Text = "Dashboard Item", + .Font = Globals.GetFont("Raleway", Globals.Unit(1.5 / 2), FontStyle.Bold), + .BackColor = Globals.Palette("Secondary"), + .ForeColor = Globals.Palette("Plain Light"), + .Height = Globals.Unit(1), + .Width = Me.Width, + .TextAlign = ContentAlignment.MiddleCenter, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Name = "Header" + } + Me.Controls.Add(Header) + + Me.Controls.Add(New Label With { + .Text = "Program Head", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleLeft, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(1), Globals.Unit(2)) + }) + Me.Controls.Add(New Label With { + .Text = "Courses", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleLeft, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(1), Globals.Unit(3)) + }) + Me.Controls.Add(New Label With { + .Text = "Faculties", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleLeft, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(1), Globals.Unit(4)) + }) + Me.Controls.Add(New Label With { + .Text = "Facilities", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleLeft, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(1), Globals.Unit(5)) + }) + Me.Controls.Add(New Label With { + .Text = "Students", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleLeft, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(1), Globals.Unit(6)) + }) + + Me.Controls.Add(New Label With { + .Text = "Dr. Juan Dela Cruz", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleRight, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(6), Globals.Unit(2)), + .Name = "ProgramHead" + }) + Me.Controls.Add(New Label With { + .Text = "10", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleRight, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(6), Globals.Unit(3)), + .Name = "Courses" + }) + Me.Controls.Add(New Label With { + .Text = "5", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleRight, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(6), Globals.Unit(4)), + .Name = "Faculties" + }) + Me.Controls.Add(New Label With { + .Text = "3", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleRight, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(6), Globals.Unit(5)), + .Name = "Facilities" + }) + Me.Controls.Add(New Label With { + .Text = "100", + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .Height = Globals.Unit(1), + .Width = Globals.Unit(5), + .TextAlign = ContentAlignment.MiddleRight, + .Margin = New Padding(0), + .Padding = New Padding(0), + .Location = New Point(Globals.Unit(6), Globals.Unit(6)), + .Name = "Students" + }) + + Me.Controls.Add(VisitButton) + End Sub + + Protected Overrides Sub OnPaint(e As PaintEventArgs) + MyBase.OnPaint(e) + Dim g As Graphics = e.Graphics + Dim Pen As New Pen(Globals.Palette("Secondary"), Globals.Unit(0.1)) + + g.DrawRectangle(Pen, 0, 0, Me.Width - Pen.Width, Me.Height - Pen.Width) + End Sub + End Class + + Private BSIT_DashboardItem As DashboardItem + Private BSCpE_DashboardItem As DashboardItem + + Protected Sub Dashboard_Load(sender As Object, e As EventArgs) Handles MyBase.Load + Me.Name = "Dashboard" + + MainPanel = New FlowLayoutPanel With { + .Height = Globals.Unit(10), + .Width = Globals.Unit(28) + } + Me.Contents.Controls.Add(MainPanel) + + Dim BSIT_DashboardItem As New DashboardItem With { + .Header = "BSIT Dashboard", + .Margin = New Padding(0, 0, Globals.Unit(2), 0) + } + AddHandler BSIT_DashboardItem.VisitButton.Click, Sub() + Globals.PROGRAM = "bsit" + Me.GoToForm(New Dashboard_Calendar) + End Sub + Invoke(Sub() + Try + Dim response = Globals.API("GET", "admin/dashboard/program/bsit", Nothing) + Dim data = Globals.JSONToDictionary(response) + + BSIT_DashboardItem.ProgramHead = data("programHead") + BSIT_DashboardItem.Courses = data("courses") + BSIT_DashboardItem.Faculties = data("faculties") + BSIT_DashboardItem.Facilities = data("facilities") + BSIT_DashboardItem.Students = data("students") + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "An error occurred while fetching the data.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK} + } + } + Modal.ShowDialog() + End Try + End Sub) + MainPanel.Controls.Add(BSIT_DashboardItem) + Dim BSCpE_DashboardItem As New DashboardItem With { + .Header = "BSCpE Dashboard" + } + AddHandler BSCpE_DashboardItem.VisitButton.Click, Sub() + Globals.PROGRAM = "bscpe" + Me.GoToForm(New Dashboard_Calendar) + End Sub + Invoke(Sub() + Try + Dim response = Globals.API("GET", "admin/dashboard/program/bscpe", Nothing) + Dim data = Globals.JSONToDictionary(response) + + BSCpE_DashboardItem.ProgramHead = data("programHead") + BSCpE_DashboardItem.Courses = data("courses") + BSCpE_DashboardItem.Faculties = data("faculties") + BSCpE_DashboardItem.Facilities = data("facilities") + BSCpE_DashboardItem.Students = data("students") + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "An error occurred while fetching the data.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK} + } + } + Modal.ShowDialog() + End Try + End Sub) + MainPanel.Controls.Add(BSCpE_DashboardItem) + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "System Setup", + .Message = "The system is not set up. Please set up the system before logging in.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK}, + {"Cancel", DialogResult.Cancel} + } + } + If Modal.ShowDialog() = DialogResult.OK Then + Me.GoToForm(New DeanSetup) + Else + Me.Close() + End If + End Try + Loaded = True + Me.Size = Globals.FormSize + End Sub + + Protected Sub Dashboard_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Me.MainPanel.Location = New Point( + (Me.Contents.Width - Me.MainPanel.Width) / 2, + (Me.Contents.Height - Me.MainPanel.Height) / 2 + ) + + Dim Background As New Bitmap(Me.Contents.Width, Me.Contents.Height) + Dim HalfTrapezoid = Globals.LoadSvgFromResource("Half Trapezoid").Draw() + Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() + Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() + Dim BarCompliment_Bottom = Globals.LoadSvgFromResource("Bar Complement Bottom").Draw() + Dim Bar_Bottom = Globals.LoadSvgFromResource("Bar Bottom").Draw() + + Using g As Graphics = Graphics.FromImage(Background) + g.DrawImage( + BarCompliment_Top, + -CInt(Me.Width * 0.25), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Top, + CInt(Me.Width - Bar_Top.Width), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + BarCompliment_Bottom, + 0, + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Bottom, + CInt(Me.Width * 0.75), + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + End Using + + Me.BackgroundBitmap = Background + End If + End Sub +End Class \ No newline at end of file diff --git a/Dashboard_Calendar.vb b/Dashboard_Calendar.vb new file mode 100644 index 0000000..be7a079 --- /dev/null +++ b/Dashboard_Calendar.vb @@ -0,0 +1,1140 @@ +Imports System.Drawing.Drawing2D +Imports System.Globalization +Imports System.Resources +Imports System.Text.RegularExpressions +Imports System.Net +Imports Svg +Imports System.IO + +Public Class Dashboard_Calendar + Inherits BaseForm + + Private SidePanel As FlowLayoutPanel + Private MainPanel As Transparent.Panel + Private Calendar As Transparent.Panel + + Private Class BaseComboBox + Inherits Transparent.Panel + + Private WithEvents ComboBoxElement As Transparent.ComboBox + Private WithEvents PlaceHolderElement As Label + + Property PlaceHolder As String + Get + Return PlaceHolderElement.Text + End Get + Set(value As String) + PlaceHolderElement.Text = value + End Set + End Property + + Property Items As List(Of String) + Get + Dim toReturm As New List(Of String) + For Each item In ComboBoxElement.Items + toReturm.Add(item) + Next + Return toReturm + End Get + Set + ComboBoxElement.Items.Clear() + For Each item In Value + ComboBoxElement.Items.Add(item) + Next + End Set + End Property + + Property SelectedIndex As Integer + Get + Return ComboBoxElement.SelectedIndex + End Get + Set(value As Integer) + ComboBoxElement.SelectedIndex = value + BaseComboBoxDropDownClosed(Me, Nothing) + End Set + End Property + + Property SelectedItem As String + Get + Return ComboBoxElement.SelectedItem + End Get + Set(value As String) + ComboBoxElement.SelectedItem = value + End Set + End Property + + Public Sub New() + Me.BackColor = Color.Yellow + Me.PlaceHolderElement = New Transparent.Label With { + .Size = Me.Size, + .Location = New Point(0, 0), + .ForeColor = Globals.Palette("Plain Light"), + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .BackColor = Globals.Palette("Primary"), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(Me.PlaceHolderElement) + + Me.ComboBoxElement = New Transparent.ComboBox With { + .Size = Me.Size, + .Location = New Point(0, 0), + .DropDownWidth = Me.Width, + .DropDownStyle = ComboBoxStyle.DropDownList, + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("Plain Light"), + .FlatStyle = FlatStyle.Flat + } + Me.Controls.Add(Me.ComboBoxElement) + + AddHandler Me.PlaceHolderElement.Click, Sub() + Me.ComboBoxElement.DroppedDown = True + End Sub + End Sub + + Protected Sub BaseComboBoxResize(sender As Object, e As EventArgs) Handles Me.Resize + Me.PlaceHolderElement.Size = Me.Size + Me.ComboBoxElement.Size = Me.Size + Me.ComboBoxElement.DropDownWidth = Me.Width + End Sub + + Protected Sub BaseComboBoxDropDownClosed(sender As Object, e As EventArgs) Handles ComboBoxElement.DropDownClosed + Me.PlaceHolderElement.Text = Me.ComboBoxElement.SelectedItem + If Me.ComboBoxElement.Items.Count = 0 Then + Me.PlaceHolderElement.Text = Me.PlaceHolder + End If + RaiseEvent SelectedIndexChanged(Me, e) + End Sub + + Public Event SelectedIndexChanged(sender As Object, e As EventArgs) + End Class + + Shared TimestampLabelHeight As Integer = 0 + + Private Class SchedulePanel + Inherits Transparent.Panel + + Public Header As Label + Public Description As Label + Public Identifier As Label + + Property Color As Color + Get + Return Me.BackColor + End Get + Set(value As Color) + Me.BorderTop.BackColor = value + Me.BorderBottom.BackColor = value + Me.BorderLeft.BackColor = value + Me.BorderRight.BackColor = value + + Me.Header.BackColor = value + End Set + End Property + + Private BorderTop As Panel + Private BorderBottom As Panel + Private BorderLeft As Panel + Private BorderRight As Panel + + Property HeaderText As String + Get + Return Me.Header.Text + End Get + Set(value As String) + Me.Header.Text = value + End Set + End Property + Property DescriptionText As String + Get + Return Me.Description.Text + End Get + Set(value As String) + Me.Description.Text = value + End Set + End Property + Property IdentifierText As String + Get + Return Me.Identifier.Text + End Get + Set(value As String) + Me.Identifier.Text = value + End Set + End Property + Property ID As String + Property YearLevel As String + Property Section As String + Property FacultyID As String + Property Facility As String + Property Day As String + Property StartTime As String + Property EndTime As String + + Public Sub New( + HeaderText As String, + DescriptionText As String, + IdentifierText As String, + ID As String, + YearLevel As String, + Section As String, + FacultyID As String, + Facility As String, + Day As String, + StartTime As String, + EndTime As String + ) + Me.BackColor = Globals.Palette("Plain Light") + + Me.BorderTop = New Panel With { + .Size = New Size(Me.Width, Globals.Unit(0.1)), + .Location = New Point(0, 0), + .BackColor = Me.Color + } + Me.Controls.Add(Me.BorderTop) + Me.BorderBottom = New Panel With { + .Size = New Size(Me.Width, Globals.Unit(0.1)), + .Location = New Point(0, Me.Height - Globals.Unit(0.1)), + .BackColor = Me.Color + } + Me.Controls.Add(Me.BorderBottom) + Me.BorderLeft = New Panel With { + .Size = New Size(Globals.Unit(0.1), Me.Height), + .Location = New Point(0, 0), + .BackColor = Me.Color + } + Me.Controls.Add(Me.BorderLeft) + Me.BorderRight = New Panel With { + .Size = New Size(Globals.Unit(0.1), Me.Height), + .Location = New Point(Me.Width - Globals.Unit(0.1), 0), + .BackColor = Me.Color + } + Me.Controls.Add(Me.BorderRight) + + Me.Header = New Label With { + .Text = HeaderText, + .Size = New Size(Me.Width, TimestampLabelHeight), + .Location = New Point(0, 0), + .Font = Globals.GetFont("Raleway", Globals.Unit(0.25) + (Globals.Unit(0.25) / 2), FontStyle.Bold), + .TextAlign = ContentAlignment.MiddleCenter, + .ForeColor = Globals.Palette("Plain Light") + } + Me.Controls.Add(Me.Header) + + Me.Description = New Label With { + .Text = DescriptionText, + .Size = New Size(Me.Width, TimestampLabelHeight), + .Location = New Point(0, TimestampLabelHeight), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.25) + (Globals.Unit(0.25) / 2), FontStyle.Regular), + .TextAlign = ContentAlignment.MiddleCenter, + .ForeColor = Globals.Palette("Plain Dark") + } + Me.Controls.Add(Me.Description) + + Me.Identifier = New Label With { + .Text = IdentifierText, + .Size = New Size(Me.Width, TimestampLabelHeight), + .Location = New Point(0, TimestampLabelHeight * 2), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.25) + (Globals.Unit(0.25) / 2), FontStyle.Regular), + .TextAlign = ContentAlignment.MiddleCenter, + .ForeColor = Globals.Palette("Plain Dark") + } + Me.Controls.Add(Me.Identifier) + + AddHandler Me.Header.Click, AddressOf SchedulePanelClick + AddHandler Me.Description.Click, AddressOf SchedulePanelClick + AddHandler Me.Identifier.Click, AddressOf SchedulePanelClick + AddHandler Me.BorderTop.Click, AddressOf SchedulePanelClick + AddHandler Me.BorderBottom.Click, AddressOf SchedulePanelClick + AddHandler Me.BorderLeft.Click, AddressOf SchedulePanelClick + AddHandler Me.BorderRight.Click, AddressOf SchedulePanelClick + + Me.ID = ID + Me.YearLevel = YearLevel + Me.Section = Section + Me.FacultyID = FacultyID + Me.Facility = Facility + Me.Day = Day + Me.StartTime = StartTime + Me.EndTime = EndTime + End Sub + + Protected Sub SchedulePanelResize(sender As Object, e As EventArgs) Handles Me.Resize + Me.BorderTop.Size = New Size(Me.Width, Globals.Unit(0.1)) + Me.BorderBottom.Size = New Size(Me.Width, Globals.Unit(0.5)) + Me.BorderLeft.Size = New Size(Globals.Unit(0.1), Me.Height) + Me.BorderRight.Size = New Size(Globals.Unit(0.1), Me.Height) + + Me.BorderBottom.Location = New Point(0, Me.Height - Globals.Unit(0.1)) + Me.BorderRight.Location = New Point(Me.Width - Globals.Unit(0.1), 0) + + Me.Header.Size = New Size(Me.Width, TimestampLabelHeight) + Me.Description.Size = New Size(Me.Width, TimestampLabelHeight) + Me.Identifier.Size = New Size(Me.Width, TimestampLabelHeight) + + Me.Description.Location = New Point(0, TimestampLabelHeight) + Me.Identifier.Location = New Point(0, TimestampLabelHeight * 2) + End Sub + + Protected Sub SchedulePanelClick(sender As Object, e As EventArgs) Handles Me.Click + Dim Parent As Panel = Me.Parent + Dim PreviousPopupPanel As Panel = Parent.Controls.Find("PopUpPanel", True).FirstOrDefault() + Dim SchedulesBehind As New List(Of SchedulePanel) + If PreviousPopupPanel IsNot Nothing Then + Parent.Controls.Remove(PreviousPopupPanel) + End If + Dim PopUpPanel As New Panel With { + .Size = New Size( + Me.Width * 2, + ( + (Globals.Unit(1.5) + Globals.Unit(0.25)) * 6 + ) + ( + (Globals.Unit(1) + Globals.Unit(0.25)) * 3 + ) + ), + .BackColor = Me.Color, + .Name = "PopUpPanel", + .TabIndex = TabIndex + 1 + } + Dim PopUpX As Integer = Me.Right + Dim PopUpY As Integer = Me.Top + If PopUpPanel.Width > Parent.Width - Me.Right Then + PopUpX = Me.Left - PopUpPanel.Width + End If + If PopUpPanel.Height + Me.Top > Parent.Height - Globals.Unit(1) Then + PopUpY = Parent.Height - PopUpPanel.Height - Globals.Unit(1) + End If + PopUpPanel.Location = New Point(PopUpX, PopUpY) + AddHandler Me.Resize, Sub() + PopUpPanel.Size = New Size( + Me.Width * 2, + ( + (Globals.Unit(1.5) + Globals.Unit(0.25)) * 6 + ) + ( + (Globals.Unit(1) + Globals.Unit(0.25)) * 3 + ) + Globals.Unit(0.25) + ) + + PopUpX = Me.Right + PopUpY = Me.Top + If PopUpPanel.Width > Parent.Width - Me.Right Then + PopUpY = Parent.Height - PopUpPanel.Height + End If + If PopUpPanel.Height + Me.Top > Parent.Height - Globals.Unit(1) Then + PopUpY = Parent.Height - PopUpPanel.Height - Globals.Unit(1) + End If + PopUpPanel.Location = New Point(PopUpX, PopUpY) + End Sub + Parent.Controls.Add(PopUpPanel) + Me.Parent.Controls.SetChildIndex(PopUpPanel, 0) + + Dim FacultyDropDown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), Globals.Unit(0.25)), + .Name = "Faculty", + .PlaceHodlder = "Select Faculty" + } + AddHandler Me.Resize, Sub() + FacultyDropDown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + Invoke(Sub() + FacultyDropDown.Items.Clear() + FacultyDropDown.PlaceHodlder = "Select Faculty" + Try + Dim tempItems = New List(Of String) + FacultyDropDown.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/faculties", Nothing) + Dim data = Globals.JSONToDictionary(response) + + For i As Integer = 0 To data.Keys.Count - 1 + tempItems.Add(data.Keys(i) & " - " & data.Values(i)) + Next + + FacultyDropDown.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub) + PopUpPanel.Controls.Add(FacultyDropDown) + + Dim SectionDropDown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), FacultyDropDown.Bottom + Globals.Unit(0.25)), + .Name = "Section", + .PlaceHodlder = "Select Section" + } + AddHandler Me.Resize, Sub() + SectionDropDown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + Invoke(Sub() + SectionDropDown.Items.Clear() + SectionDropDown.PlaceHodlder = "Select Section" + Try + Dim tempItems = New List(Of String) + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/sections/" & Me.YearLevel, Nothing) + Dim data = Globals.JSONToDictionary(response) + + For Each value In data.Values + tempItems.Add(value) + Next + + SectionDropDown.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub) + PopUpPanel.Controls.Add(SectionDropDown) + + Dim FacilityDropDown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), SectionDropDown.Bottom + Globals.Unit(0.25)), + .Name = "Facility", + .PlaceHodlder = "Select Facility" + } + AddHandler Me.Resize, Sub() + FacilityDropDown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + Invoke(Sub() + FacilityDropDown.Items.Clear() + FacilityDropDown.PlaceHodlder = "Select Facility" + Try + Dim tempItems = New List(Of String) + FacilityDropDown.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/facilities", Nothing) + Dim data = Globals.JSONToDictionary(response) + + For i As Integer = 0 To data.Keys.Count - 1 + tempItems.Add(data.Keys(i) & " - " & data.Values(i)) + Next + + FacilityDropDown.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub) + PopUpPanel.Controls.Add(FacilityDropDown) + + Dim DayDropdown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), FacilityDropDown.Bottom + Globals.Unit(0.25)), + .Name = "Day", + .PlaceHodlder = "Select Day" + } + DayDropdown.Items = New List(Of String) From { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + } + AddHandler Me.Resize, Sub() + DayDropdown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + PopUpPanel.Controls.Add(DayDropdown) + + Dim StartTimeDropDown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), DayDropdown.Bottom + Globals.Unit(0.25)), + .Name = "StartTime", + .PlaceHodlder = "Select Start Time" + } + StartTimeDropDown.Items = New List(Of String) From { + "07:00 AM", + "07:30 AM", + "08:00 AM", + "08:30 AM", + "09:00 AM", + "09:30 AM", + "10:00 AM", + "10:30 AM", + "11:00 AM", + "11:30 AM", + "12:00 PM", + "12:30 PM", + "01:00 PM", + "01:30 PM", + "02:00 PM", + "02:30 PM", + "03:00 PM", + "03:30 PM", + "04:00 PM", + "04:30 PM", + "05:00 PM", + "05:30 PM", + "06:00 PM", + "06:30 PM", + "07:00 PM" + } + AddHandler Me.Resize, Sub() + StartTimeDropDown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + PopUpPanel.Controls.Add(StartTimeDropDown) + + Dim EndTimeDropDown As New BaseDropDown With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)), + .Location = New Point(Globals.Unit(0.25), StartTimeDropDown.Bottom + Globals.Unit(0.25)), + .Name = "EndTime", + .PlaceHodlder = "Select End Time" + } + AddHandler Me.Resize, Sub() + EndTimeDropDown.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1.5)) + End Sub + AddHandler StartTimeDropDown.SelectedIndexChanged, Sub() + If StartTimeDropDown.SelectedIndex = -1 Then + Exit Sub + End If + EndTimeDropDown.Items.Clear() + EndTimeDropDown.PlaceHodlder = "Select End Time" + Dim TimeItems = New List(Of String) + Dim StartTimeIndex As Integer = StartTimeDropDown.SelectedIndex + For i As Integer = StartTimeIndex + 1 To StartTimeDropDown.Items.Count - 1 + TimeItems.Add(StartTimeDropDown.Items(i)) + Next + EndTimeDropDown.Items = TimeItems + End Sub + PopUpPanel.Controls.Add(EndTimeDropDown) + + Dim SubmitButton As New BaseButton With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)), + .Location = New Point(Globals.Unit(0.25), EndTimeDropDown.Bottom + Globals.Unit(0.25)), + .Text = "Submit" + } + AddHandler Me.Resize, Sub() + SubmitButton.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)) + End Sub + PopUpPanel.Controls.Add(SubmitButton) + + Dim DeleteButton As New BaseButton With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)), + .Location = New Point(Globals.Unit(0.25), SubmitButton.Bottom + Globals.Unit(0.25)), + .Text = "Delete", + .SubButton = True + } + AddHandler Me.Resize, Sub() + DeleteButton.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)) + End Sub + PopUpPanel.Controls.Add(DeleteButton) + + Dim CancelButton As New BaseButton With { + .Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)), + .Location = New Point(Globals.Unit(0.25), DeleteButton.Bottom + Globals.Unit(0.25)), + .Text = "Cancel", + .SubButton = True + } + AddHandler Me.Resize, Sub() + CancelButton.Size = New Size((Me.Width * 2) - Globals.Unit(0.5), Globals.Unit(1)) + CancelButton.PerformClick() + End Sub + AddHandler CancelButton.Click, Sub() + Parent.Controls.Remove(PopUpPanel) + End Sub + PopUpPanel.Controls.Add(CancelButton) + + FacultyDropDown.SelectedItem = FacultyDropDown.Items.Find(Function(x) x.Contains(Me.FacultyID)) + SectionDropDown.SelectedItem = SectionDropDown.Items.Find(Function(x) x.Contains(Me.Section)) + FacilityDropDown.SelectedItem = FacilityDropDown.Items.Find(Function(x) x.Contains(Me.Facility)) + DayDropdown.SelectedItem = DayDropdown.Items.Find(Function(x) x.Contains(Me.Day)) + 'Convert Military time to 12 hour time format + ' 08:00:00 to 08:00 AM + Dim StartTime As String = Me.StartTime + Dim StartTimeHour As Integer = CInt(StartTime.Split(":")(0)) + Dim StartTimeMinute As Integer = CInt(StartTime.Split(":")(1)) + Dim StartTimePeriod As String = "AM" + If StartTimeHour > 12 Then + StartTimeHour -= 12 + StartTimePeriod = "PM" + End If + StartTime = $"{StartTimeHour.ToString("00")}:{StartTimeMinute.ToString("00")} {StartTimePeriod}" + StartTimeDropDown.SelectedItem = StartTimeDropDown.Items.Find(Function(x) x.Contains(StartTime)) + Dim EndTime As String = Me.EndTime + Dim EndTimeHour As Integer = CInt(EndTime.Split(":")(0)) + Dim EndTimeMinute As Integer = CInt(EndTime.Split(":")(1)) + Dim EndTimePeriod As String = "AM" + If EndTimeHour > 12 Then + EndTimeHour -= 12 + EndTimePeriod = "PM" + End If + EndTime = EndTimeHour.ToString("00") & ":" & EndTimeMinute.ToString("00") & " " & EndTimePeriod + EndTimeDropDown.SelectedItem = EndTimeDropDown.Items.Find(Function(x) x.Contains(EndTime)) + For Each Control As Control In Parent.Controls + Control.Refresh() + Next + + AddHandler SubmitButton.Click, Sub() + Try + Dim FacultyID As String = FacultyDropDown.SelectedItem.Split(" - ")(0) + Dim Section As String = SectionDropDown.SelectedItem + Dim Facility As String = FacilityDropDown.SelectedItem.Split(" - ")(0) + Dim Day As String = DayDropdown.SelectedItem + Dim StartTimeData As String = StartTimeDropDown.SelectedItem + Dim EndTimeData As String = EndTimeDropDown.SelectedItem + If EndTimeDropDown.SelectedIndex = -1 Then + Dim EndTimeModal As New BaseModal With { + .Title = "Error", + .Message = "Please select an end time." + } + EndTimeModal.ShowDialog() + Exit Sub + End If + Dim data As New Dictionary(Of String, String) From { + {"facultyID", FacultyID}, + {"facilityID", Facility}, + {"section", Section}, + {"day", Day}, + {"startTime", StartTimeData}, + {"endTime", EndTimeData} + } + Dim response As String = Globals.API("POST", "admin/dashboard/program/" & Globals.PROGRAM & "/update/schedule/" & Me.ID, Globals.DictionaryToJSON(data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = response + } + Modal.ShowDialog() + Parent.Controls.Remove(PopUpPanel) + Parent.Controls.Find("Schedule", True).ToList().ForEach(Sub(x) Parent.Controls.Remove(x)) + + Dim ScheduleSelection As BaseComboBox = Parent.Controls.Find("ScheduleSelection", True).FirstOrDefault() + Dim ScheduleSelectionIndex As Integer = ScheduleSelection.SelectedIndex + ScheduleSelection.SelectedIndex = ScheduleSelectionIndex - 1 + ScheduleSelection.SelectedIndex = ScheduleSelectionIndex + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + + AddHandler DeleteButton.Click, Sub() + Try + Dim data As New Dictionary(Of String, String) From { + {"facultyID", Me.FacultyID}, + {"facilityID", Me.Facility}, + {"section", Me.Section}, + {"day", Me.Day}, + {"startTime", Me.StartTime}, + {"endTime", Me.EndTime} + } + Dim response As String = Globals.API("POST", "admin/dashboard/program/" & Globals.PROGRAM & "/delete/schedule/" & Me.ID, Globals.DictionaryToJSON(data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = response + } + Modal.ShowDialog() + Parent.Controls.Remove(PopUpPanel) + Parent.Controls.Find("Schedule", True).ToList().ForEach(Sub(x) Parent.Controls.Remove(x)) + + Dim ScheduleSelection As BaseComboBox = Parent.Controls.Find("ScheduleSelection", True).FirstOrDefault() + Dim ScheduleSelectionIndex As Integer = ScheduleSelection.SelectedIndex + ScheduleSelection.SelectedIndex = ScheduleSelectionIndex - 1 + ScheduleSelection.SelectedIndex = ScheduleSelectionIndex + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + End Sub + End Class + + Private Schedules As New List(Of SchedulePanel) + + Protected Sub Dashboard_Load(sender As Object, e As EventArgs) Handles MyBase.Load + BackendSocket.Connect("calendar_admin") + Me.Name = "Dashboard" + + Me.SidePanel = New FlowLayoutPanel With { + .Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)), + .BackColor = Globals.Palette("Plain Light"), + .FlowDirection = FlowDirection.TopDown, + .WrapContents = False, + .AutoScroll = True, + .Padding = New Padding(0, 0, 0, 0), + .Location = New Point( + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.SidePanel) + + Dim CallendarIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Calendar Icon Active", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + Me.SidePanel.Controls.Add(CallendarIcon) + Dim CreateIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Create Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler CreateIcon.Click, Sub() + BackendSocket.Close() + Me.GoToForm(New Dashboard_Create) + End Sub + Me.SidePanel.Controls.Add(CreateIcon) + Dim NotificationIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Notification Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler NotificationIcon.Click, Sub() + BackendSocket.Close() + Me.GoToForm(New Dashboard_Notification) + End Sub + Me.SidePanel.Controls.Add(NotificationIcon) + + Dim LogoutIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Logout Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Padding = New Padding(0) + } + LogoutIcon.Margin = New Padding( + 0, + (Me.SidePanel.Height - NotificationIcon.Bottom) - LogoutIcon.Height, + 0, + 0 + ) + AddHandler Me.SidePanel.Resize, Sub() + LogoutIcon.Margin = New Padding( + 0, + (Me.SidePanel.Height - NotificationIcon.Bottom) - LogoutIcon.Height, + 0, + 0 + ) + End Sub + AddHandler LogoutIcon.Click, Sub() + Globals.TOKEN = "" + Globals.PROGRAM = "" + BackendSocket.Close() + Me.GoToForm(New Login) + End Sub + Me.SidePanel.Controls.Add(LogoutIcon) + + Me.MainPanel = New Transparent.Panel With { + .Size = New Size( + Me.Contents.Width - Me.SidePanel.Width, + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height + Me.resizerBarBottom.Height) + ), + .AutoScroll = True, + .Location = New Point( + Me.SidePanel.Width + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.MainPanel) + + Me.Calendar = New Transparent.Panel With { + .Size = New Size( + Me.MainPanel.Width - Globals.Unit(2), + Me.MainPanel.Height - Globals.Unit(2) + ), + .Location = New Point(Globals.Unit(1), Globals.Unit(1)) + } + Me.MainPanel.Controls.Add(Me.Calendar) + + Dim ScheduleSelection As New BaseComboBox With { + .Size = New Size(Me.Calendar.Width, Globals.Unit(1)), + .Location = New Point(0, 0), + .PlaceHolder = "Select Calendar", + .Name = "ScheduleSelection" + } + AddHandler Me.Calendar.Resize, Sub() + ScheduleSelection.Size = New Size(Me.Calendar.Width, Globals.Unit(1)) + End Sub + Me.Calendar.Controls.Add(ScheduleSelection) + + AddHandler BackendSocket.OnMessage, Sub(data As String) + ScheduleSelection.SelectedIndex = ScheduleSelection.SelectedIndex - 1 + ScheduleSelection.SelectedIndex = ScheduleSelection.SelectedIndex + 1 + End Sub + + Dim PopupPanel As Panel = Me.Calendar.Controls.Find("PopUpPanel", True).FirstOrDefault() + If PopupPanel IsNot Nothing Then + Me.Calendar.Controls.Remove(PopupPanel) + End If + Dim HeaderPanel As New Transparent.FlowLayoutPanel With { + .Size = New Size( + Me.MainPanel.Width - Globals.Unit(2), + Me.MainPanel.Height - Globals.Unit(2) + ), + .Location = New Point(0, Globals.Unit(1)), + .FlowDirection = FlowDirection.LeftToRight, + .WrapContents = False, + .BackColor = Globals.Palette("Secondary"), + .Padding = New Padding(0, 0, 0, 0), + .Margin = New Padding(0, 0, 0, 0), + .Name = "HeaderPanel" + } + AddHandler Me.Calendar.Resize, Sub() + HeaderPanel.Size = New Size( + Me.Calendar.Width, + Globals.Unit(1) + ) + End Sub + Me.Calendar.Controls.Add(HeaderPanel) + + Dim Days As New List(Of String) From { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + } + For Each Day In Days + Dim DayLabel As New Transparent.Label With { + .Text = Day, + .Name = Day, + .Size = New Size( + (Me.Calendar.Width / Days.Count) - (Globals.Unit(0.5) * (Days.Count - 1)), + Globals.Unit(1) + ), + .AutoSize = False, + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .TextAlign = ContentAlignment.MiddleCenter, + .Margin = New Padding(0), + .Padding = New Padding(0), + .BackColor = Globals.Palette("Secondary"), + .ForeColor = Globals.Palette("Plain Light") + } + AddHandler Me.Calendar.Resize, Sub() + HeaderPanel.Padding = New Padding(CInt(Me.Calendar.Width / 6.4), 0, 0, 0) + DayLabel.Size = New Size( + (Me.Calendar.Width - (Me.Calendar.Width / 6.4)) / Days.Count, + Globals.Unit(1) + ) + End Sub + HeaderPanel.Controls.Add(DayLabel) + Next + + Dim TimestampPanel As New Transparent.FlowLayoutPanel With { + .Size = New Size( + Me.Calendar.Width / 6.4, + Me.Calendar.Height - Globals.Unit(3) + ), + .Location = New Point(0, Globals.Unit(2)), + .FlowDirection = FlowDirection.TopDown, + .WrapContents = False, + .Padding = New Padding(0, Globals.Unit(0.5), 0, Globals.Unit(0.5)), + .Margin = New Padding(0, 0, 0, 0), + .Name = "TimestampPanel" + } + Me.Calendar.Controls.Add(TimestampPanel) + Dim Times As New List(Of String) From { + "07:00 - 07:30 AM", + "07:30 - 08:00 AM", + "08:00 - 08:30 AM", + "08:30 - 09:00 AM", + "09:00 - 09:30 AM", + "09:30 - 10:00 AM", + "10:00 - 10:30 AM", + "10:30 - 11:00 AM", + "11:00 - 11:30 AM", + "11:30 - 12:00 NN", + "12:00 - 12:30 PM", + "12:30 - 01:00 PM", + "01:00 - 01:30 PM", + "01:30 - 02:00 PM", + "02:00 - 02:30 PM", + "02:30 - 03:00 PM", + "03:00 - 03:30 PM", + "03:30 - 04:00 PM", + "04:00 - 04:30 PM", + "04:30 - 05:00 PM", + "05:00 - 05:30 PM", + "05:30 - 06:00 PM", + "06:00 - 06:30 PM", + "06:30 - 07:00 PM" + } + For Each Time In Times + Dim TimeLabel As New Transparent.Label With { + .Text = Time, + .Name = Time, + .Size = New Size( + TimestampPanel.Width, + ((TimestampPanel.Height - Globals.Unit(1)) / Times.Count) - Globals.Unit(0.25) / 2 + ), + .AutoSize = False, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5) - (Globals.Unit(0.25) / 2), FontStyle.Regular), + .TextAlign = ContentAlignment.MiddleCenter, + .Margin = New Padding(0, 0, 0, Globals.Unit(0.25) / 2), + .Padding = New Padding(0), + .ForeColor = Globals.Palette("Plain Dark") + } + AddHandler Me.Calendar.Resize, Sub() + TimestampPanel.Size = New Size( + Me.Calendar.Width / 6.4, + Me.Calendar.Height - Globals.Unit(3) + ) + TimeLabel.Size = New Size( + TimestampPanel.Width, + ((TimestampPanel.Height - Globals.Unit(1)) / Times.Count) - Globals.Unit(0.25) / 2 + ) + TimestampLabelHeight = TimeLabel.Height + End Sub + TimestampPanel.Controls.Add(TimeLabel) + Next + Dim FooterPanel As New Panel With { + .Size = New Size(Me.Calendar.Width, Globals.Unit(1)), + .Location = New Point(0, Me.Calendar.Height - Globals.Unit(1)), + .BackColor = Globals.Palette("Primary") + } + AddHandler Me.Calendar.Resize, Sub() + FooterPanel.Size = New Size(Me.Calendar.Width, Globals.Unit(1)) + FooterPanel.Location = New Point(0, Me.Calendar.Height - Globals.Unit(1)) + End Sub + Me.Calendar.Controls.Add(FooterPanel) + + Dim Identifiers = New Dictionary(Of String, Dictionary(Of String, String)) + + Dim getCalendars = Sub() + Try + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/calendars/", Nothing) + Dim data = Globals.JSONToDictionary(response, True) + Dim Selections As New List(Of String) + For Each item In data.Values + Selections.Add(item("name")) + Identifiers.Add(item("name"), New Dictionary(Of String, String) From { + {"key", item("key")}, + {"identifier", item("identifier")}, + {"name", item("name")} + }) + + Next + ScheduleSelection.Items = Selections + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + getCalendars() + + 'AddHandler BackendSocket.OnMessage, Sub(message) + ' MsgBox(message) + ' If message = "refresh" Then + ' Dim calendarPreviousIndex As Integer = ScheduleSelection.SelectedIndex + ' ScheduleSelection.Items.Clear() + ' getCalendars() + ' ScheduleSelection.SelectedIndex = calendarPreviousIndex + ' End If + ' End Sub + + Dim DisplaySchedules = Sub() + 'Check if ScheduleSelection is empty + If ScheduleSelection.Items.Count = 0 Then + ScheduleSelection.PlaceHolder = "No Calendars Available" + Exit Sub + End If + If ScheduleSelection.SelectedIndex = -1 Then + ScheduleSelection.PlaceHolder = "No Calendars Selected" + Exit Sub + End If + Me.Calendar.Controls.Find("Schedule", True).ToList().ForEach(Sub(x) Me.Calendar.Controls.Remove(x)) + Me.Calendar.Controls.Find("PopUpPanel", True).ToList().ForEach(Sub(x) Me.Calendar.Controls.Remove(x)) + + Dim Key As String = Identifiers(ScheduleSelection.SelectedItem)("key") + Dim Identifier As String = Identifiers(ScheduleSelection.SelectedItem)("identifier") + Dim PossibleTimes As New List(Of String) From { + "07:00:00", + "07:30:00", + "08:00:00", + "08:30:00", + "09:00:00", + "09:30:00", + "10:00:00", + "10:30:00", + "11:00:00", + "11:30:00", + "12:00:00", + "12:30:00", + "13:00:00", + "13:30:00", + "14:00:00", + "14:30:00", + "15:00:00", + "15:30:00", + "16:00:00", + "16:30:00", + "17:00:00", + "17:30:00", + "18:00:00", + "18:30:00", + "19:00:00" + } + Dim response As String + Try + response = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/schedule/" & Identifier & "/" & Key) + Dim data = Globals.JSONToDictionary(response, True) + Dim SchedulesPerDay As New Dictionary(Of String, List(Of SchedulePanel)) + + Dim itemIndex = 0 + For Each item In data.Values + Dim ScheduleDay As String = item("day") + Dim ScheduleStartTime As String = item("startTime") + Dim ScheduleEndTime As String = item("endTime") + Dim ScheduleCourse As String = item("course") + Dim ScheduleDescription As String = item("description") + Dim ScheduleIdentifier As String = item("identifier") + Dim ScheduleID As String = data.Keys(itemIndex) + Dim YearLevel As String = item("section").ToString().ToCharArray()(0) + Dim Section As String = item("section") + Dim FacultyID As String = item("faculty") + Dim Facility As String = item("facility") + + Dim SchedulePanel As New SchedulePanel( + ScheduleCourse, + ScheduleDescription, + ScheduleIdentifier, + ScheduleID, + YearLevel, + Section, + FacultyID, + Facility, + ScheduleDay, + ScheduleStartTime, + ScheduleEndTime + ) + SchedulePanel.Name = "Schedule" + Dim Left As Integer = HeaderPanel.Controls(ScheduleDay).Left + Globals.Unit(0.125 / 2) + Dim Right As Integer = HeaderPanel.Controls(ScheduleDay).Right + Dim Width As Integer = (Right - Left) - Globals.Unit(0.125) + + Dim Top As Integer = TimestampPanel.Controls(PossibleTimes.IndexOf(ScheduleStartTime)).Top + TimestampPanel.Top + Dim Bottom As Integer = TimestampPanel.Controls(PossibleTimes.IndexOf(ScheduleEndTime) - 1).Bottom + TimestampPanel.Top + Dim Height As Integer = Bottom - Top + + SchedulePanel.Location = New Point(Left, Top) + SchedulePanel.Size = New Size(Width, Height) + AddHandler Me.Resize, Sub() + Left = HeaderPanel.Controls(ScheduleDay).Left + Globals.Unit(0.125 / 2) + Right = HeaderPanel.Controls(ScheduleDay).Right + Width = (Right - Left) - Globals.Unit(0.125) + + Top = TimestampPanel.Controls(PossibleTimes.IndexOf(ScheduleStartTime)).Top + TimestampPanel.Top + Bottom = TimestampPanel.Controls(PossibleTimes.IndexOf(ScheduleEndTime) - 1).Bottom + TimestampPanel.Top + Height = Bottom - Top + + SchedulePanel.Location = New Point(Left, Top) + SchedulePanel.Size = New Size(Width, Height) + End Sub + Me.Calendar.Controls.Add(SchedulePanel) + Me.Schedules.Add(SchedulePanel) + + If Not SchedulesPerDay.ContainsKey(ScheduleDay) Then + SchedulesPerDay.Add(ScheduleDay, New List(Of SchedulePanel)) + End If + SchedulesPerDay(ScheduleDay).Add(SchedulePanel) + itemIndex = itemIndex + 1 + Next + + 'Loop through each day + For Each Day In SchedulesPerDay.Values + 'Sort + Day.Sort(Function(x, y) + Return x.Location.Y.CompareTo(y.Location.Y) + End Function) + + Dim ScheduleIndex As Integer = 2 + For Each Schedule In Day + If ScheduleIndex Mod 2 = 0 Then + Schedule.Color = Globals.Palette("Secondary") + Else + Schedule.Color = Globals.Palette("Plain Dark") + End If + ScheduleIndex = ScheduleIndex + 1 + Next + Next + Catch ex As Exception + End Try + End Sub + + AddHandler ScheduleSelection.SelectedIndexChanged, Sub() + DisplaySchedules() + End Sub + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "System Setup", + .Message = "The system is not set up. Please set up the system before logging in.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK}, + {"Cancel", DialogResult.Cancel} + } + } + If Modal.ShowDialog() = DialogResult.OK Then + Me.GoToForm(New DeanSetup) + Else + Me.Close() + End If + End Try + Loaded = True + Me.Size = Globals.FormSize + End Sub + + Protected Sub Dashboard_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Me.SidePanel.Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)) + Me.MainPanel.Size = New Size( + Me.Contents.Width - (Me.SidePanel.Width + Me.resizerBarLeft.Width + Me.resizerBarRight.Width), + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height) + ) + Me.Calendar.Size = New Size( + Me.MainPanel.Width - Globals.Unit(2), + Me.MainPanel.Height - Globals.Unit(2) + ) + End If + End Sub +End Class \ No newline at end of file diff --git a/Dashboard_Create.vb b/Dashboard_Create.vb new file mode 100644 index 0000000..704352d --- /dev/null +++ b/Dashboard_Create.vb @@ -0,0 +1,428 @@ +Imports System.Drawing.Drawing2D +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions +Imports Svg + +Public Class Dashboard_Create + Inherits BaseForm + + Private SidePanel As FlowLayoutPanel + Private MainPanel As Transparent.Panel + + Protected Sub Dashboard_Load(sender As Object, e As EventArgs) Handles MyBase.Load + Me.Name = "Dashboard" + + Me.SidePanel = New FlowLayoutPanel With { + .Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)), + .BackColor = Globals.Palette("Plain Light"), + .FlowDirection = FlowDirection.TopDown, + .WrapContents = False, + .AutoScroll = True, + .Padding = New Padding(0, 0, 0, 0), + .Location = New Point( + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.SidePanel) + + Dim CallendarIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Calendar Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler CallendarIcon.Click, Sub() + Me.GoToForm(New Dashboard_Calendar) + End Sub + Me.SidePanel.Controls.Add(CallendarIcon) + Dim CreateIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Create Icon Active", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + Me.SidePanel.Controls.Add(CreateIcon) + Dim NotificationIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Notification Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler NotificationIcon.Click, Sub() + Me.GoToForm(New Dashboard_Notification) + End Sub + Me.SidePanel.Controls.Add(NotificationIcon) + + Me.MainPanel = New Transparent.Panel With { + .Size = New Size( + Me.Contents.Width - Me.SidePanel.Width, + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height + Me.resizerBarBottom.Height) + ), + .AutoScroll = True, + .Location = New Point( + Me.SidePanel.Width + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.MainPanel) + + + + Dim YearLevel As New BaseDropDown With { + .Items = New List(Of String), + .Name = "Year Level", + .Location = New Point(Globals.Unit(1), Globals.Unit(1)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Year Level" + } + Invoke(Sub() + Try + Dim tempItems = New List(Of String) + YearLevel.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/yearlevels/", Nothing) + Dim data = Globals.JSONToDictionary(response) + + For Each Values In data.Values + tempItems.Add(Values) + Next + + YearLevel.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub) + Me.MainPanel.Controls.Add(YearLevel) + + Dim Course As New BaseDropDown With { + .Items = New List(Of String), + .Name = "Course", + .Location = New Point(Globals.Unit(1), Globals.Unit(3)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Course" + } + AddHandler YearLevel.SelectedIndexChanged, Sub() + Course.Items.Clear() + Course.PlaceHodlder = "Select Course" + If YearLevel.SelectedIndex = -1 Then + Exit Sub + End If + Try + Dim tempItems = New List(Of String) + Course.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/courses/" & YearLevel.SelectedIndex + 1, Nothing) + Dim data = Globals.JSONToDictionary(response) + + For i As Integer = 0 To data.Keys.Count - 1 + tempItems.Add(data.Keys(i) & " - " & data.Values(i)) + Next + + Course.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.MainPanel.Controls.Add(Course) + + Dim Faculty As New BaseDropDown With { + .Items = New List(Of String), + .Name = "Faculty", + .Location = New Point(Globals.Unit(1), Globals.Unit(5)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Faculty" + } + AddHandler YearLevel.SelectedIndexChanged, Sub() + Faculty.Items.Clear() + Faculty.PlaceHodlder = "Select Faculty" + If YearLevel.SelectedIndex = -1 Then + Exit Sub + End If + Try + Dim tempItems = New List(Of String) + Faculty.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/faculties", Nothing) + Dim data = Globals.JSONToDictionary(response) + + For i As Integer = 0 To data.Keys.Count - 1 + tempItems.Add(data.Keys(i) & " - " & data.Values(i)) + Next + + Faculty.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.MainPanel.Controls.Add(Faculty) + + Dim Facility As New BaseDropDown With { + .Items = New List(Of String), + .Name = "Facility", + .Location = New Point(Globals.Unit(1), Globals.Unit(7)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Facility" + } + AddHandler YearLevel.SelectedIndexChanged, Sub() + Facility.Items.Clear() + Facility.PlaceHodlder = "Select Facility" + If YearLevel.SelectedIndex = -1 Then + Exit Sub + End If + Try + Dim tempItems = New List(Of String) + Facility.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/facilities", Nothing) + Dim data = Globals.JSONToDictionary(response) + + For i As Integer = 0 To data.Keys.Count - 1 + tempItems.Add(data.Keys(i) & " - " & data.Values(i)) + Next + + Facility.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.MainPanel.Controls.Add(Facility) + + Dim Section As New BaseDropDown With { + .Items = New List(Of String), + .Name = "Section", + .Location = New Point(Globals.Unit(1), Globals.Unit(9)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Section" + } + AddHandler YearLevel.SelectedIndexChanged, Sub() + Section.Items.Clear() + Section.PlaceHodlder = "Select Section" + If YearLevel.SelectedIndex = -1 Then + Exit Sub + End If + Try + Dim tempItems = New List(Of String) + Section.Items.Clear() + Dim response As String = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/sections/" & YearLevel.SelectedIndex + 1, Nothing) + Dim data = Globals.JSONToDictionary(response) + + For Each Values In data.Values + tempItems.Add(Values) + Next + + Section.Items = tempItems + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.MainPanel.Controls.Add(Section) + + Dim Day As New BaseDropDown With { + .Items = New List(Of String) From { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + }, + .Name = "Day", + .Location = New Point(Globals.Unit(1), Globals.Unit(11)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Day" + } + Me.MainPanel.Controls.Add(Day) + + Dim StartTime As New BaseDropDown With { + .Items = New List(Of String) From { + "07:00 AM", + "07:30 AM", + "08:00 AM", + "08:30 AM", + "09:00 AM", + "09:30 AM", + "10:00 AM", + "10:30 AM", + "11:00 AM", + "11:30 AM", + "12:00 PM", + "12:30 PM", + "01:00 PM", + "01:30 PM", + "02:00 PM", + "02:30 PM", + "03:00 PM", + "03:30 PM", + "04:00 PM", + "04:30 PM", + "05:00 PM", + "05:30 PM", + "06:00 PM", + "06:30 PM", + "07:00 PM" + }, + .Name = "Start Time", + .Location = New Point(Globals.Unit(1), Globals.Unit(13)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select Start Time" + } + Me.MainPanel.Controls.Add(StartTime) + + Dim EndTime As New BaseDropDown With { + .Items = New List(Of String), + .Name = "End Time", + .Location = New Point(Globals.Unit(1), Globals.Unit(15)), + .Size = New Size(Globals.Unit(12), Globals.Unit(1.5)), + .PlaceHodlder = "Select End Time" + } + AddHandler StartTime.SelectedIndexChanged, Sub() + If StartTime.SelectedIndex = -1 Then + Exit Sub + End If + EndTime.Items.Clear() + EndTime.PlaceHodlder = "Select End Time" + Dim TimeItems = New List(Of String) + EndTime.Items.Clear() + Dim StartTimeIndex As Integer = StartTime.SelectedIndex + For i As Integer = StartTimeIndex + 1 To StartTime.Items.Count - 1 + TimeItems.Add(StartTime.Items(i)) + Next + EndTime.Items = TimeItems + End Sub + Me.MainPanel.Controls.Add(EndTime) + + Dim SubmitButton As New BaseButton With { + .Text = "Submit", + .Name = "Submit", + .Location = New Point(Globals.Unit(1), Globals.Unit(17)) + } + AddHandler SubmitButton.Click, Sub() + If YearLevel.SelectedIndex = -1 Then + YearLevel.Alert() + Exit Sub + ElseIf Course.SelectedIndex = -1 Then + Course.Alert() + Exit Sub + ElseIf Faculty.SelectedIndex = -1 Then + Faculty.Alert() + Exit Sub + ElseIf Facility.SelectedIndex = -1 Then + Facility.Alert() + Exit Sub + ElseIf Section.SelectedIndex = -1 Then + Section.Alert() + Exit Sub + ElseIf Day.SelectedIndex = -1 Then + Day.Alert() + Exit Sub + ElseIf StartTime.SelectedIndex = -1 Then + StartTime.Alert() + Exit Sub + ElseIf EndTime.SelectedIndex = -1 Then + EndTime.Alert() + Exit Sub + End If + Dim Data As New Dictionary(Of String, String) From { + {"yearLevel", YearLevel.SelectedItem}, + {"course", Course.SelectedItem}, + {"faculty", Faculty.SelectedItem}, + {"facility", Facility.SelectedItem}, + {"section", Section.SelectedItem}, + {"day", Day.SelectedItem}, + {"startTime", StartTime.SelectedItem}, + {"endTime", EndTime.SelectedItem} + } + Try + Dim response As String = Globals.API("POST", "admin/dashboard/program/" & Globals.PROGRAM & "/schedule/", Globals.DictionaryToJSON(Data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Schedule created successfully." + } + Modal.ShowDialog() + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.MainPanel.Controls.Add(SubmitButton) + + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "System Setup", + .Message = "The system is not set up. Please set up the system before logging in.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK}, + {"Cancel", DialogResult.Cancel} + } + } + If Modal.ShowDialog() = DialogResult.OK Then + Me.GoToForm(New DeanSetup) + Else + Me.Close() + End If + End Try + Loaded = True + Me.Size = Globals.FormSize + End Sub + + Protected Sub Dashboard_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Me.SidePanel.Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)) + Me.MainPanel.Size = New Size( + Me.Contents.Width - (Me.SidePanel.Width + Me.resizerBarLeft.Width + Me.resizerBarRight.Width), + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height) + ) + End If + End Sub +End Class \ No newline at end of file diff --git a/Dashboard_Notification.vb b/Dashboard_Notification.vb new file mode 100644 index 0000000..90bde8d --- /dev/null +++ b/Dashboard_Notification.vb @@ -0,0 +1,497 @@ +Imports System.Drawing.Drawing2D +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions +Imports Svg + +Public Class Dashboard_Notification + Inherits BaseForm + + Private SidePanel As FlowLayoutPanel + Private MainPanel As Transparent.Panel + + Private Class RequestTable + Inherits Transparent.Panel + + Sub New( + courseCode As Object, + facultyName As Object, + originalFacilityName As Object, + originalDay As Object, + originalStartTime As Object, + originalEndTime As Object, + requestFacilityName As Object, + requestDay As Object, + requestStartTime As Object, + requestEndTime As Object, + status As Object, + requestReason As Object, + rejectReason As Object, + requestDate As Object, + scheduleID As Object, + requestID As Object + ) + Me.BorderStyle = BorderStyle.None + Me.Margin = New Padding(0, 0, 0, Globals.Unit(2)) + + Dim Header As New Label With { + .Text = courseCode & " - " & facultyName, + .Font = Globals.GetFont("Raleway", Globals.Unit(0.75), FontStyle.Bold), + .ForeColor = Globals.Palette("White"), + .BackColor = Globals.Palette("Primary"), + .AutoSize = False, + .Size = New Size(Me.Width, Globals.Unit(1)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(Header) + + Dim OriginalLabel As New Label With { + .Text = "Original", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("White"), + .BackColor = Globals.Palette("Secondary"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Header.Bottom), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(OriginalLabel) + + Dim OriginalFacilityLabel As New Label With { + .Text = "Facility: " & originalFacilityName, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("Plain Light"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(2)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(OriginalFacilityLabel) + + Dim OriginalDayLabel As New Label With { + .Text = "Day: " & originalDay, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("White"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(3)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(OriginalDayLabel) + + Dim OriginalTimeLabel As New Label With { + .Text = "Time: " & originalStartTime & " - " & originalEndTime, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("Plain Light"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(4)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(OriginalTimeLabel) + + Dim RequestLabel As New Label With { + .Text = "Request", + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("White"), + .BackColor = Globals.Palette("Secondary"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Header.Bottom), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(RequestLabel) + + Dim RequestFacilityLabel As New Label With { + .Text = "Facility: " & requestFacilityName, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("White"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Globals.Unit(2)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(RequestFacilityLabel) + + Dim RequestDayLabel As New Label With { + .Text = "Day: " & requestDay, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("Plain Light"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Globals.Unit(3)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(RequestDayLabel) + + Dim RequestTimeLabel As New Label With { + .Text = "Time: " & requestStartTime & " - " & requestEndTime, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("White"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Globals.Unit(4)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(RequestTimeLabel) + + Dim ReasonLabel As New Label With { + .Text = "Request Reason: " & requestReason, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("White"), + .AutoSize = False, + .Size = New Size(Me.Width, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(5)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(ReasonLabel) + + Dim StatusLabel As New Label With { + .Text = "Status: " & status, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("Plain Light"), + .AutoSize = False, + .Size = New Size(Me.Width, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(6)), + .TextAlign = ContentAlignment.MiddleCenter + } + Me.Controls.Add(StatusLabel) + + If rejectReason = Nothing Then + rejectReason = "" + End If + + Dim RejectReasonLabel As New Label With { + .Text = "Rejection Rason: " & rejectReason, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark"), + .BackColor = Globals.Palette("White"), + .AutoSize = False, + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Globals.Unit(6)), + .TextAlign = ContentAlignment.MiddleCenter + } + + If status = "rejected" Then + StatusLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + Me.Controls.Add(RejectReasonLabel) + End If + + AddHandler Me.Resize, Sub() + Header.Size = New Size(Me.Width, Globals.Unit(1)) + + OriginalLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + OriginalLabel.Location = New Point(0, Header.Bottom) + + OriginalFacilityLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + OriginalFacilityLabel.Location = New Point(0, Globals.Unit(2)) + + OriginalDayLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + OriginalDayLabel.Location = New Point(0, Globals.Unit(3)) + + OriginalTimeLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + OriginalTimeLabel.Location = New Point(0, Globals.Unit(4)) + + RequestLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RequestLabel.Location = New Point(Me.Width / 2, Header.Bottom) + + RequestFacilityLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RequestFacilityLabel.Location = New Point(Me.Width / 2, Globals.Unit(2)) + + RequestDayLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RequestDayLabel.Location = New Point(Me.Width / 2, Globals.Unit(3)) + + RequestTimeLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RequestTimeLabel.Location = New Point(Me.Width / 2, Globals.Unit(4)) + + ReasonLabel.Size = New Size(Me.Width, Globals.Unit(1)) + + StatusLabel.Size = New Size(Me.Width, Globals.Unit(1)) + + If status = "rejected" Then + StatusLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RejectReasonLabel.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RejectReasonLabel.Location = New Point(Me.Width / 2, Globals.Unit(6)) + End If + End Sub + + If status = "rejected" Or status = "approved" Then + Dim Footer As New Transparent.Panel With { + .Size = New Size(Me.Width, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(7)), + .BackColor = Globals.Palette("Primary") + } + Me.Controls.Add(Footer) + + AddHandler Me.Resize, Sub() + Footer.Size = New Size(Me.Width, Globals.Unit(1)) + Footer.Location = New Point(0, Globals.Unit(7)) + End Sub + Return + End If + + Dim ApproveButton As New BaseButton With { + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(7)), + .Text = "Approve" + } + AddHandler ApproveButton.Click, Sub() + Try + Dim data As New Dictionary(Of String, String) From { + {"requestID", requestID} + } + Dim response = Globals.API("POST", "admin/dashboard/program/" & Globals.PROGRAM & "/requests/" & requestID & "/approve/", Globals.DictionaryToJSON(data)) + + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Request approved successfully." + } + Modal.ShowDialog() + + RaiseEvent Updated(Me, New EventArgs()) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.Controls.Add(ApproveButton) + + Dim RejectTextInput As New BaseTextInput With { + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(7)), + .Name = "Reason for rejection", + .Visible = False + } + Me.Controls.Add(RejectTextInput) + + Dim RejectButton As New BaseButton With { + .Size = New Size(Me.Width / 2, Globals.Unit(1)), + .Location = New Point(Me.Width / 2, Globals.Unit(7)), + .Text = "Reject", + .SubButton = True + } + AddHandler RejectButton.Click, Sub() + If RejectTextInput.Visible Then + If RejectTextInput.Text = "" Then + RejectTextInput.Alert() + Else + Try + Dim data As New Dictionary(Of String, String) From { + {"rejectReason", RejectTextInput.Text} + } + Dim response = Globals.API("POST", "admin/dashboard/program/" & Globals.PROGRAM & "/requests/" & requestID & "/reject/", Globals.DictionaryToJSON(data)) + + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Request rejected successfully." + } + Modal.ShowDialog() + + RaiseEvent Updated(Me, New EventArgs()) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End If + Else + RejectTextInput.Visible = True + RejectButton.Text = "Confirm" + + ApproveButton.Visible = False + End If + End Sub + Me.Controls.Add(RejectButton) + + AddHandler Me.Resize, Sub() + ApproveButton.Size = New Size(Me.Width / 2, Globals.Unit(1)) + ApproveButton.Location = New Point(0, Globals.Unit(7)) + + RejectButton.Size = New Size(Me.Width / 2, Globals.Unit(1)) + RejectButton.Location = New Point(Me.Width / 2, Globals.Unit(7)) + + RejectTextInput.Size = New Size(Me.Width / 2, Globals.Unit(1)) + End Sub + End Sub + + Public Event Updated(sender As Object, e As EventArgs) + End Class + + Protected Sub Dashboard_Load(sender As Object, e As EventArgs) Handles MyBase.Load + BackendSocket.Connect("notifications_admin") + Me.Name = "Dashboard" + + Me.SidePanel = New FlowLayoutPanel With { + .Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)), + .BackColor = Globals.Palette("Plain Light"), + .FlowDirection = FlowDirection.TopDown, + .WrapContents = False, + .AutoScroll = True, + .Padding = New Padding(0, 0, 0, 0), + .Location = New Point( + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.SidePanel) + + Dim CallendarIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Calendar Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler CallendarIcon.Click, Sub() + BackendSocket.Close() + Me.GoToForm(New Dashboard_Calendar) + End Sub + Me.SidePanel.Controls.Add(CallendarIcon) + Dim CreateIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Create Icon", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + AddHandler CreateIcon.Click, Sub() + BackendSocket.Close() + Me.GoToForm(New Dashboard_Create) + End Sub + Me.SidePanel.Controls.Add(CreateIcon) + Dim NotificationIcon As New PictureBox With { + .Image = Globals.LoadSvgFromResource("Notification Icon Active", New Size(Globals.Unit(2), Globals.Unit(2))).Draw(), + .Size = New Size(Globals.Unit(2), Globals.Unit(2)), + .SizeMode = PictureBoxSizeMode.Zoom, + .Cursor = Cursors.Hand, + .Margin = New Padding(0), + .Padding = New Padding(0) + } + Me.SidePanel.Controls.Add(NotificationIcon) + + Me.MainPanel = New Transparent.Panel With { + .Size = New Size( + Me.Contents.Width - Me.SidePanel.Width, + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height + Me.resizerBarBottom.Height) + ), + .AutoScroll = True, + .Location = New Point( + Me.SidePanel.Width + Me.resizerBarRight.Width, + Me.HeaderBar.Height + Me.resizerBarTop.Height + ) + } + Me.Contents.Controls.Add(Me.MainPanel) + + Dim RequestsPanel As New FlowLayoutPanel With { + .MinimumSize = New Size(Me.MainPanel.Width - Globals.Unit(2), 0), + .MaximumSize = New Size(Me.MainPanel.Width - Globals.Unit(2), 0), + .Location = New Point(Globals.Unit(1), Globals.Unit(1)), + .FlowDirection = FlowDirection.TopDown, + .WrapContents = False, + .AutoSize = True, + .Padding = New Padding(0, 0, 0, 0) + } + AddHandler Me.Resize, Sub() + RequestsPanel.MinimumSize = New Size(Me.MainPanel.Width - Globals.Unit(2), 0) + RequestsPanel.MaximumSize = New Size(Me.MainPanel.Width - Globals.Unit(2), 0) + End Sub + Me.MainPanel.Controls.Add(RequestsPanel) + + Me.DisplayRequests(RequestsPanel) + + AddHandler BackendSocket.OnMessage, Sub(data As String) + Me.DisplayRequests(RequestsPanel) + End Sub + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "System Setup", + .Message = "The system is not set up. Please set up the system before logging in.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK}, + {"Cancel", DialogResult.Cancel} + } + } + If Modal.ShowDialog() = DialogResult.OK Then + BackendSocket.Close() + Me.GoToForm(New DeanSetup) + Else + Me.Close() + End If + End Try + Loaded = True + Me.Size = Globals.FormSize + End Sub + + Protected Sub DisplayRequests(RequestsPanel As FlowLayoutPanel) + RequestsPanel.Controls.Clear() + Dim response = Globals.API("GET", "admin/dashboard/program/" & Globals.PROGRAM & "/requests/", Nothing) + Dim data = Globals.JSONToDictionary(response, True) + + For Each request In data.Values + Dim RequestTable As New RequestTable( + request("courseCode"), + request("facultyName"), + request("original")("facilityName"), + request("original")("day"), + request("original")("startTime"), + request("original")("endTime"), + request("request")("facilityName"), + request("request")("day"), + request("request")("startTime"), + request("request")("endTime"), + request("status"), + request("requestReason"), + request("rejectReason"), + request("requestDate"), + request("scheduleID"), + request("requestID") + ) + AddHandler RequestsPanel.Resize, Sub() + RequestTable.Size = New Size(RequestsPanel.Width, Globals.Unit(8)) + End Sub + RequestsPanel.Controls.Add(RequestTable) + + AddHandler RequestTable.Updated, Sub() + RequestsPanel.Size = New Size(RequestsPanel.Width + 1, RequestsPanel.Height + 1) + Me.DisplayRequests(RequestsPanel) + RequestsPanel.Size = New Size(RequestsPanel.Width - 1, RequestsPanel.Height - 1) + End Sub + Next + End Sub + + Protected Sub Dashboard_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Me.SidePanel.Size = New Size(Globals.Unit(2), Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height)) + Me.MainPanel.Size = New Size( + Me.Contents.Width - (Me.SidePanel.Width + Me.resizerBarLeft.Width + Me.resizerBarRight.Width), + Me.Contents.Height - (Me.HeaderBar.Height + Me.resizerBarTop.Height) + ) + End If + End Sub +End Class \ No newline at end of file diff --git a/DeanSetup.vb b/DeanSetup.vb index 2c314fb..2bdf471 100644 --- a/DeanSetup.vb +++ b/DeanSetup.vb @@ -1,4 +1,7 @@ -Imports System.Resources +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions Imports Svg Public Class DeanSetup @@ -45,11 +48,21 @@ Public Class DeanSetup } Me.FormPanel.Controls.Add(Intro) - Dim NameInput As New BaseTextInput With { - .Name = "Name", + Dim NamePanel As New Transparent.Panel With { .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } - Me.FormPanel.Controls.Add(NameInput) + Me.FormPanel.Controls.Add(NamePanel) + Dim FirstNameInput As New BaseTextInput With { + .Name = "First Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + NamePanel.Controls.Add(FirstNameInput) + Dim LastNameInput As New BaseTextInput With { + .Name = "Last Name", + .Size = New Size((NamePanel.Width / 2) - Globals.Unit(0.25), Globals.Unit(1)) + } + LastNameInput.Location = New Point(NamePanel.Width - LastNameInput.Width, 0) + NamePanel.Controls.Add(LastNameInput) Dim EmailInput As New BaseTextInput With { .Name = "Email", .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) @@ -70,17 +83,96 @@ Public Class DeanSetup Dim SetupButton As New BaseButton With { .Text = "Setup", - .Name = "Setup" + .Name = "Setup", + .Size = New Size(Me.FormPanel.Width - Globals.Unit(2), Globals.Unit(1)) } Me.FormPanel.Controls.Add(SetupButton) AddHandler SetupButton.Click, Sub() - Me.GoToForm(New BSIT_ProgramHeadSetup) - End Sub - - + If FirstNameInput.Text = "" And FirstNameInput.Text.Length < 3 Then + FirstNameInput.Alert() + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "First Name must be at least 3 characters." + } + Modal.ShowDialog() + Exit Sub + End If + If LastNameInput.Text = "" And LastNameInput.Text.Length < 2 Then + LastNameInput.Alert() + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Last Name must be at least 2 characters." + } + Modal.ShowDialog() + Exit Sub + End If + If EmailInput.Text = "" And Not New Regex("^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").IsMatch(EmailInput.Text) Then + EmailInput.Alert() + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Invalid email address." + } + Modal.ShowDialog() + Exit Sub + End If + If PasswordInput.Text = "" And PasswordInput.Text.Length < 8 Then + PasswordInput.Alert() + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Password must be at least 8 characters." + } + Modal.ShowDialog() + Exit Sub + End If + If PasswordInput.Text <> ConfirmPasswordInput.Text Then + ConfirmPasswordInput.Alert() + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Passwords do not match." + } + Modal.ShowDialog() + Exit Sub + End If + Dim data As New Dictionary(Of String, String) From { + {"firstName", FirstNameInput.Text}, + {"lastName", LastNameInput.Text}, + {"email", EmailInput.Text}, + {"password", PasswordInput.Text}, + {"role", "dean"} + } + Try + Dim response As String = Globals.API("POST", "setup/admin", Globals.DictionaryToJSON(data)) + Me.GoToForm(New BSIT_ProgramHeadSetup) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub Loaded = True Me.Size = Globals.FormSize + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Dim Modal As New BaseModal With { + .Title = "System Setup", + .Message = "The system is already set up. Please log in.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK} + } + } + If Modal.ShowDialog() Then + Me.GoToForm(New Login) + End If + Catch ex As Exception + End Try End Sub Protected Sub DeanSetup_Resize(sender As Object, e As EventArgs) Handles Me.Resize diff --git a/Facility_SetupData.vb b/Facility_SetupData.vb new file mode 100644 index 0000000..4de311d --- /dev/null +++ b/Facility_SetupData.vb @@ -0,0 +1,299 @@ +Imports System.Resources +Imports Svg +Imports System.IO +Imports System.Net + +Public Class Facility_SetupData + Inherits BaseForm + + Private FormPanel As New Transparent.FlowLayoutPanel + + Protected Sub Facility_SetupData_Load(sender As Object, e As EventArgs) Handles MyBase.Load + Me.Name = "Setup Facility Data" + + Dim Title As New Label With { + .Text = "Import Facility Data", + .AutoSize = True, + .MinimumSize = New Size(Me.Width - Globals.Unit(4), Globals.Unit(2)), + .MaximumSize = New Size(Me.Width - Globals.Unit(4), Globals.Unit(2)), + .Font = Globals.GetFont("Raleway", Globals.Unit(1.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Primary"), + .TextAlign = ContentAlignment.MiddleCenter, + .Name = "Title" + } + Title.Location = New Point( + CInt(Me.Width * 0.5 - Title.Width * 0.5), + Globals.Unit(2) + ) + Me.Contents.Controls.Add(Title) + + Me.FormPanel.AutoScroll = True + Me.FormPanel.Size = New Size( + Me.Width - Globals.Unit(4) + SystemInformation.VerticalScrollBarWidth, + Me.Height - Globals.Unit(9) + ) + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth) * 0.5), + Globals.Unit(5) + ) + Me.Contents.Controls.Add(Me.FormPanel) + + Dim FacilitiesInput As New FileInputPanel With { + .Label = "Facilities", + .Description = "Upload .csv file of facilities.", + .Format = "facilityID, name, description" + } + Me.FormPanel.Controls.Add(FacilitiesInput) + + Dim i As Integer = 0 + For Each Control As Control In Me.FormPanel.Controls + Control.MinimumSize = New Size(Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth, 0) + Control.MaximumSize = New Size(Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth, 0) + + If i Mod 2 = 0 Then + Control.Margin = New Padding(0, 0, 0, 0) + Else + Control.Margin = New Padding(Globals.Unit(2), 0, 0, 0) + End If + If i > 1 Then + Control.Margin = New Padding( + Control.Margin.Left, + Globals.Unit(1), + 0, + 0 + ) + End If + i = i + 1 + Next + + Dim SubmitButton As New BaseButton With { + .Name = "Submit", + .Text = "Submit" + } + SubmitButton.Location = New Point( + Me.FormPanel.Right - SubmitButton.Width, + Me.FormPanel.Bottom + Globals.Unit(1) + ) + AddHandler SubmitButton.Click, Sub() + Dim Data As New Dictionary(Of String, String) + If FacilitiesInput.FilePath = "" Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Please upload a .csv file for courses." + } + Modal.ShowDialog() + FacilitiesInput.Alert() + Exit Sub + Else + Try + Data.Add("facilities", File.ReadAllText(FacilitiesInput.FilePath)) + Catch ex As Exception + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = ex.Message + } + Modal.ShowDialog() + FacilitiesInput.Alert() + Exit Sub + End Try + End If + Try + Dim response As String = Globals.API("POST", "setup/facilities", Globals.DictionaryToJSON(Data)) + Me.GoToForm(New Login) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + Me.Contents.Controls.Add(SubmitButton) + + Loaded = True + Me.Size = Globals.FormSize + End Sub + + Protected Sub Facility_SetupData_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Me.Contents.Controls("Title").Location = New Point( + CInt(Me.Width * 0.5 - Me.Contents.Controls("Title").Width * 0.5), + Globals.Unit(2) + ) + Me.FormPanel.Size = New Size( + Me.Width - Globals.Unit(4) + SystemInformation.VerticalScrollBarWidth, + Me.Height - Globals.Unit(9) + ) + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - (Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth) * 0.5), + Globals.Unit(5) + ) + For Each Control As Control In Me.FormPanel.Controls + Control.MinimumSize = New Size( + Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth, + Control.MaximumSize.Height + ) + Control.MaximumSize = New Size( + Me.FormPanel.Width - SystemInformation.VerticalScrollBarWidth, + Control.MaximumSize.Height + ) + Next + Me.Contents.Controls("Submit").Location = New Point( + Me.FormPanel.Right - Me.Contents.Controls("Submit").Width, + Me.FormPanel.Bottom + Globals.Unit(1) + ) + + Dim Background As New Bitmap(Me.Contents.Width, Me.Contents.Height) + Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() + Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() + Dim BarCompliment_Bottom = Globals.LoadSvgFromResource("Bar Complement Bottom").Draw() + Dim Bar_Bottom = Globals.LoadSvgFromResource("Bar Bottom").Draw() + + Using g As Graphics = Graphics.FromImage(Background) + g.DrawImage( + BarCompliment_Top, + -CInt(Me.Width * 0.25), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Top, + CInt(Me.Width * 0.5), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + BarCompliment_Bottom, + -CInt(Me.Width * 0.25), + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Bottom, + CInt(Me.Width * 0.875), + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + End Using + + Me.BackgroundBitmap = Background + End If + End Sub + + Private Class FileInputPanel + Inherits Transparent.Panel + + Public Property Label As String + Get + Return Me.Controls("Label").Text + End Get + Set(value As String) + Me.Controls("Label").Text = value + End Set + End Property + Public Property Description As String + Get + Return Me.Controls("Description").Text + End Get + Set(value As String) + Me.Controls("Description").Text = value + End Set + End Property + Public Property Format As String + Get + Return Me.Controls("Format").Text + End Get + Set(value As String) + Me.Controls("Format").Text = value + End Set + End Property + + Public FilePath As String + + Public Sub New() + Me.AutoSize = True + + Dim Label As New Transparent.Label With { + .Text = "File", + .AutoSize = True, + .MinimumSize = New Size(Me.Width, Globals.Unit(1.25)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1.25)), + .Location = New Point(0, 0), + .Font = Globals.GetFont("Raleway", Globals.Unit(0.75), FontStyle.Bold), + .ForeColor = Globals.Palette("Secondary"), + .TextAlign = ContentAlignment.MiddleLeft, + .Name = "Label" + } + Me.Controls.Add(Label) + + Dim Description As New Transparent.Label With { + .Text = "Upload .csv file of courses.", + .AutoSize = True, + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), + .Location = New Point(0, Globals.Unit(1.5)), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .TextAlign = ContentAlignment.MiddleLeft, + .Name = "Description" + } + Me.Controls.Add(Description) + + Dim FormatLabel As New Transparent.Label With { + .Text = "Format:", + .AutoSize = True, + .MinimumSize = New Size(Me.Width, Globals.Unit(0.75)), + .MaximumSize = New Size(Me.Width, Globals.Unit(0.75)), + .Location = New Point(0, Globals.Unit(2.5)), + .Font = Globals.GetFont("Raleway", Globals.Unit(0.5), FontStyle.Bold), + .ForeColor = Globals.Palette("Secondary"), + .TextAlign = ContentAlignment.MiddleLeft, + .Name = "FormatLabel" + } + Me.Controls.Add(FormatLabel) + Dim FormatDescription As New Transparent.Label With { + .Text = "Course Code, Course Title, Course Description, Course Units", + .AutoSize = True, + .MinimumSize = New Size(Me.Width, Globals.Unit(1)), + .MaximumSize = New Size(Me.Width, Globals.Unit(1)), + .Location = New Point(0, Globals.Unit(3)), + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark"), + .TextAlign = ContentAlignment.MiddleLeft, + .Name = "Format" + } + Me.Controls.Add(FormatDescription) + + Dim FileInput As New BaseFileInput With { + .AutoSize = True, + .MaximumSize = New Size(Me.Width, Globals.Unit(3)), + .MinimumSize = New Size(Me.Width, Globals.Unit(3)), + .Location = New Point(0, Globals.Unit(4)), + .Name = "FileInput" + } + Me.Controls.Add(FileInput) + AddHandler FileInput.FileSelected, Sub() + Me.FilePath = FileInput.FilePath + End Sub + End Sub + + Protected Sub FileInputPanel_Resize(sender As Object, e As EventArgs) Handles Me.Resize + For Each Control As Control In Me.Controls + Control.MinimumSize = New Size(Me.Width, Control.MaximumSize.Height) + Control.MaximumSize = New Size(Me.Width, Control.MaximumSize.Height) + Next + End Sub + + Public Sub Alert() + Dim FileInput As BaseFileInput = Me.Controls("FileInput") + FileInput.Alert() + End Sub + End Class +End Class \ No newline at end of file diff --git a/ForgotPassword.vb b/ForgotPassword.vb new file mode 100644 index 0000000..82b28be --- /dev/null +++ b/ForgotPassword.vb @@ -0,0 +1,364 @@ +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions +Imports Svg + +Public Class ForgotPassword + Inherits BaseForm + + Private FormPanel As New Transparent.FlowLayoutPanel + Private MainFormPanel As New Transparent.FlowLayoutPanel + + Protected Sub ForgotPassword_Load(sender As Object, e As EventArgs) Handles MyBase.Load + Me.Name = "Forgot Password" + + Me.FormPanel.MinimumSize = New Size( + Me.Width * 0.375, + 0 + ) + Me.FormPanel.MaximumSize = New Size( + Me.Width * 0.375, + 0 + ) + Me.FormPanel.AutoSize = True + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - Me.FormPanel.Width * 0.5), + CInt(Me.Height * 0.5 - Me.FormPanel.Height * 0.5) + ) + Me.Contents.Controls.Add(Me.FormPanel) + + Dim Logo As New PictureBox With { + .SizeMode = PictureBoxSizeMode.Zoom, + .Name = "Logo" + } + Dim resourcesManager As ResourceManager = My.Resources.ResourceManager + Dim LogoImage As Image = resourcesManager.GetObject("CdMSMS-ICS Logo") + Logo.Image = LogoImage + Logo.Size = New Size(Me.FormPanel.Width, CInt(LogoImage.Size.Height * (Me.FormPanel.Width / LogoImage.Size.Width))) + Me.FormPanel.Controls.Add(Logo) + + Dim ResetPasswordLabel As New Label With { + .Text = "Reset Password", + .AutoSize = True, + .MinimumSize = New Size(Me.FormPanel.Width, 0), + .MaximumSize = New Size(Me.FormPanel.Width, 0), + .Name = "ResetPassword", + .TextAlign = ContentAlignment.MiddleCenter, + .Font = Globals.GetFont("Raleway", Globals.Unit(1), FontStyle.Bold), + .ForeColor = Globals.Palette("Plain Dark") + } + Me.FormPanel.Controls.Add(ResetPasswordLabel) + + Me.MainFormPanel = New Transparent.FlowLayoutPanel With { + .AutoSize = True, + .MinimumSize = New Size(Me.FormPanel.Width - Globals.Unit(2), 0), + .MaximumSize = New Size(Me.FormPanel.Width - Globals.Unit(2), 0) + } + Me.FormPanel.Controls.Add(Me.MainFormPanel) + + Dim EmailInput As New BaseTextInput With { + .Name = "Email", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)) + } + Me.MainFormPanel.Controls.Add(EmailInput) + + Dim ForgotPasswordCodeInput As New BaseTextInput With { + .Name = "Forgot Password Code", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .Visible = False + } + Me.MainFormPanel.Controls.Add(ForgotPasswordCodeInput) + + Dim NewPasswordInput As New BaseTextInput With { + .Name = "New Password", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .Visible = False, + .PasswordChar = "*" + } + Me.MainFormPanel.Controls.Add(NewPasswordInput) + + Dim SubmitRequestButton As New BaseButton With { + .Text = "Submit Request", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .Name = "SubmitRequest" + } + Dim SubmitCodeButton As New BaseButton With { + .Text = "Submit Code", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .Name = "SubmitCode", + .Visible = False + } + Dim SubmitNewPasswordButton As New BaseButton With { + .Text = "Submit New Password", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .Name = "SubmitNewPassword", + .Visible = False + } + Me.MainFormPanel.Controls.Add(SubmitRequestButton) + Me.MainFormPanel.Controls.Add(SubmitCodeButton) + Me.MainFormPanel.Controls.Add(SubmitNewPasswordButton) + AddHandler SubmitRequestButton.Click, Sub() + Dim EmailValue As String = EmailInput.Text + If Not Regex.IsMatch(EmailValue, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Invalid email address" + } + Modal.ShowDialog() + Return + End If + + Dim data As New Dictionary(Of String, String) From { + {"email", EmailValue} + } + Try + Dim response = Globals.API("POST", "user/forgotPassword/", Globals.DictionaryToJSON(data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Password reset request sent" + } + Modal.ShowDialog() + + EmailInput.Visible = False + SubmitRequestButton.Visible = False + ForgotPasswordCodeInput.Visible = True + SubmitCodeButton.Visible = True + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + AddHandler SubmitCodeButton.Click, Sub() + Dim EmailValue As String = EmailInput.Text + Dim CodeValue As String = ForgotPasswordCodeInput.Text + 'Match text + '000-000-000 + If Not Regex.IsMatch(CodeValue, "^\d{3}-\d{3}-\d{3}$") Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Invalid code format" + } + Modal.ShowDialog() + Return + End If + + Dim data As New Dictionary(Of String, String) From { + {"email", EmailValue}, + {"forgotPasswordCode", CodeValue} + } + Try + Dim response = Globals.API("POST", "user/resetPassword/", Globals.DictionaryToJSON(data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Code verified" + } + Modal.ShowDialog() + + ForgotPasswordCodeInput.Visible = False + SubmitCodeButton.Visible = False + NewPasswordInput.Visible = True + SubmitNewPasswordButton.Visible = True + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + AddHandler SubmitNewPasswordButton.Click, Sub() + Dim EmailValue As String = EmailInput.Text + Dim CodeValue As String = ForgotPasswordCodeInput.Text + Dim NewPasswordValue As String = NewPasswordInput.Text + If NewPasswordValue = "" And NewPasswordValue.Length < 8 Then + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = "Password must be at least 8 characters." + } + Modal.ShowDialog() + Exit Sub + End If + + Dim data As New Dictionary(Of String, String) From { + {"email", EmailValue}, + {"forgotPasswordCode", CodeValue}, + {"password", NewPasswordValue} + } + Try + Dim response = Globals.API("POST", "user/changePassword/", Globals.DictionaryToJSON(data)) + Dim Modal As New BaseModal With { + .Title = "Success", + .Message = "Password reset" + } + Modal.ShowDialog() + + Me.GoToForm(New Login) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + + Dim i As Integer = 0 + For Each Control As Control In Me.MainFormPanel.Controls + Control.Margin = New Padding( + (Me.MainFormPanel.Width - Control.Width) * 0.5, + 0, + 0, + Globals.Unit(0.5) + ) + + i = i + 1 + Next + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin = New Padding(0) + + Dim ForgotPasswordLabel As New Transparent.Label With { + .Text = "ForgotPassword?", + .AutoSize = True, + .MinimumSize = New Size(Me.MainFormPanel.Width, 0), + .MaximumSize = New Size(Me.MainFormPanel.Width, 0), + .Name = "ForgotPassword", + .TextAlign = ContentAlignment.MiddleCenter, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark") + } + Me.FormPanel.Controls.Add(ForgotPasswordLabel) + + Loaded = True + Me.Size = Globals.FormSize + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + + Me.GoToForm(New DeanSetup) + End Try + End Sub + + Protected Sub ForgotPassword_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Dim i As Integer = 0 + For Each Control As Control In Me.FormPanel.Controls + If Control.Name = "Logo" Then + Dim Logo As PictureBox = Control + Dim resourcesManager As ResourceManager = My.Resources.ResourceManager + Dim LogoImage As Image = resourcesManager.GetObject("CdMSMS-ICS Logo") + Logo.Image = LogoImage + Logo.Size = New Size(Me.FormPanel.Width, CInt(LogoImage.Height * (Me.FormPanel.Width / LogoImage.Width))) + End If + + Control.Margin = New Padding( + (Me.FormPanel.Width - Control.Width) * 0.5, + 0, + 0, + 0 + ) + + If i > 0 Then + Control.Margin = New Padding( + Control.Margin.Left, + Globals.Unit(1), + 0, + 0 + ) + End If + + i = i + 1 + Next + Dim j As Integer = 0 + For Each Control As Control In Me.MainFormPanel.Controls + Control.Margin = New Padding( + (Me.MainFormPanel.Width - Control.Width) * 0.5, + 0, + 0, + Globals.Unit(1) + ) + + Control.Margin = New Padding( + Control.Margin.Left, + 0, + 0, + Globals.Unit(1) + ) + + j = j + 1 + Next + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin = New Padding( + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin.Left, + 0, + 0, + 0 + ) + + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - Me.FormPanel.Width * 0.5), + CInt(Me.Height * 0.5 - Me.FormPanel.Height * 0.5) + ) + + Dim Background As New Bitmap(Me.Width, Me.Height) + Dim HalfTrapezoid = Globals.LoadSvgFromResource("Half Trapezoid").Draw() + Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() + Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() + Dim BarCompliment_Bottom = Globals.LoadSvgFromResource("Bar Complement Bottom").Draw() + Dim Bar_Bottom = Globals.LoadSvgFromResource("Bar Bottom").Draw() + + Using g As Graphics = Graphics.FromImage(Background) + g.DrawImage( + BarCompliment_Top, + -CInt(Me.Width * 0.25), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Top, + CInt(Me.Width - Bar_Top.Width), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + BarCompliment_Bottom, + 0, + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Bottom, + CInt(Me.Width * 0.75), + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + End Using + + Me.BackgroundBitmap = Background + End If + End Sub +End Class \ No newline at end of file diff --git a/Form1.Designer.vb b/Form1.Designer.vb deleted file mode 100644 index 6723d05..0000000 --- a/Form1.Designer.vb +++ /dev/null @@ -1,27 +0,0 @@ - _ -Partial Class Form1 - Inherits BaseForm - - 'Form overrides dispose to clean up the component list. - _ - Protected Overrides Sub Dispose(ByVal disposing As Boolean) - Try - If disposing AndAlso components IsNot Nothing Then - components.Dispose() - End If - Finally - MyBase.Dispose(disposing) - End Try - End Sub - - 'Required by the Windows Form Designer - Private components As System.ComponentModel.IContainer - - 'NOTE: The following procedure is required by the Windows Form Designer - 'It can be modified using the Windows Form Designer. - 'Do not modify it using the code editor. - - Private Sub InitializeComponent() - End Sub - -End Class diff --git a/Form1.resx b/Form1.resx deleted file mode 100644 index 1af7de1..0000000 --- a/Form1.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Form1.vb b/Form1.vb deleted file mode 100644 index 9892d26..0000000 --- a/Form1.vb +++ /dev/null @@ -1,6 +0,0 @@ -Public Class Form1 - Inherits BaseForm - - Protected Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load - End Sub -End Class diff --git a/Globals.vb b/Globals.vb index fdf1022..11ee3b6 100644 --- a/Globals.vb +++ b/Globals.vb @@ -2,6 +2,10 @@ Imports System.Drawing.Text Imports System.Drawing Imports System.Runtime.InteropServices +Imports System.Net +Imports System.IO +Imports System.Text +Imports Newtonsoft.Json Module Globals Public Function HexToColor(hex As String) As Color @@ -22,6 +26,26 @@ Module Globals ) End Function + Public Function Desaturate(color As Color) As Color + Dim r As Integer = color.R + Dim g As Integer = color.G + Dim b As Integer = color.B + Dim gray As Integer = CInt(r * 0.299 + g * 0.587 + b * 0.114) + Return Color.FromArgb(gray, gray, gray) + End Function + + Public Function DimColor(color As Color, factor As Double) As Color + Dim r As Integer = CInt(color.R) * factor + Dim g As Integer = CInt(color.G) * factor + Dim b As Integer = CInt(color.B) * factor + Dim a As Integer = CInt(color.A) * factor + If r > 255 Then r = 255 + If g > 255 Then g = 255 + If b > 255 Then b = 255 + If a > 255 Then a = 255 + Return Color.FromArgb(a, r, g, b) + End Function + Public ReadOnly Palette As New Dictionary(Of String, Color) From { {"Primary", HexToColor("106A2E")}, {"Secondary", HexToColor("0D7856")}, @@ -43,7 +67,7 @@ Module Globals Return 30 * units End Function - Public MinimumFormSize As New Size(Unit(20), Unit(20)) + Public MinimumFormSize As New Size(Unit(25), Unit(20)) Public FormSize As New Size(Unit(30), Unit(20)) Public FormLocation As New Point( (Screen.PrimaryScreen.WorkingArea.Width - FormSize.Width) / 2, @@ -128,4 +152,78 @@ Module Globals Return GetFont("Open Sans", size, style) End Select End Function + + Public TOKEN As String = Nothing + Public PROGRAM As String = "bsit" + Public WHERE As String = "" + + Public Function Fetch(uri As String, method As String, Optional data As String = "{}") As String + Dim request As HttpWebRequest = WebRequest.Create(uri) + If TOKEN IsNot Nothing Then + request.Headers.Add("Authorization: " & TOKEN) + End If + + request.Method = method + Select Case method + Case "GET" + Dim response As HttpWebResponse = request.GetResponse() + Dim reader As New StreamReader(response.GetResponseStream()) + Return reader.ReadToEnd() + Case "POST" + request.ContentType = "application/json" + Dim dataBytes As Byte() = Encoding.UTF8.GetBytes(data) + request.ContentLength = dataBytes.Length + Dim requestStream As Stream = request.GetRequestStream() + requestStream.Write(dataBytes, 0, dataBytes.Length) + requestStream.Close() + Dim response As HttpWebResponse = request.GetResponse() + Dim reader As New StreamReader(response.GetResponseStream()) + Return reader.ReadToEnd() + Case "UPDATE" + request.Method = "PUT" + request.ContentType = "application/json" + Dim dataBytes As Byte() = Encoding.UTF8.GetBytes(data) + request.ContentLength = dataBytes.Length + Dim requestStream As Stream = request.GetRequestStream() + requestStream.Write(dataBytes, 0, dataBytes.Length) + requestStream.Close() + Dim response As HttpWebResponse = request.GetResponse() + Dim reader As New StreamReader(response.GetResponseStream()) + Return reader.ReadToEnd() + Case Else + Return Nothing + End Select + End Function + + Public PORT As Integer = 3090 + + Public Function API(method As String, path As String, Optional data As String = "{}") As String + Return Fetch("http://localhost:" & PORT & "/api/" & path, method, data) + End Function + + Public Function DictionaryToJSON(dictionary As Dictionary(Of String, String)) As String + Dim json As String = "{" + For Each item In dictionary + json &= $"{Chr(34)}{item.Key}{Chr(34)}: " & $"{Chr(34)}{ item.Value.Replace(Chr(34), "\" & Chr(34)).Replace(Environment.NewLine, "\n") }{Chr(34)}," + Next + json = json.TrimEnd(",") + json &= "}" + Return json + End Function + + Public Function JSONToDictionary(json As String) As Dictionary(Of String, String) + Dim dictionary As New Dictionary(Of String, String) + Dim jsonItems As String() = json.Replace("{", "").Replace("}", "").Split(",") + For Each item In jsonItems + Dim key As String = item.Split(":")(0).Trim().Replace(Chr(34), "") + Dim value As String = item.Split(":")(1).Trim().Replace(Chr(34), "") + dictionary.Add(key, value) + Next + Return dictionary + End Function + + Friend Function JSONToDictionary(json As String, recursive As Integer) As Dictionary(Of String, Object) + Dim dictionary As Dictionary(Of String, Object) = JsonConvert.DeserializeObject(Of Dictionary(Of String, Object))(json) + Return dictionary + End Function End Module diff --git a/Login.vb b/Login.vb new file mode 100644 index 0000000..62d3b66 --- /dev/null +++ b/Login.vb @@ -0,0 +1,261 @@ +Imports System.IO +Imports System.Net +Imports System.Resources +Imports System.Text.RegularExpressions +Imports Svg + +Public Class Login + Inherits BaseForm + + Private FormPanel As New Transparent.FlowLayoutPanel + Private MainFormPanel As New Transparent.FlowLayoutPanel + + Protected Sub Login_Load(sender As Object, e As EventArgs) Handles MyBase.Load + Me.Name = "Login" + + Me.FormPanel.MinimumSize = New Size( + Me.Width * 0.375, + 0 + ) + Me.FormPanel.MaximumSize = New Size( + Me.Width * 0.375, + 0 + ) + Me.FormPanel.AutoSize = True + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - Me.FormPanel.Width * 0.5), + CInt(Me.Height * 0.5 - Me.FormPanel.Height * 0.5) + ) + Me.Contents.Controls.Add(Me.FormPanel) + + Dim Logo As New PictureBox With { + .SizeMode = PictureBoxSizeMode.Zoom, + .Name = "Logo" + } + Dim resourcesManager As ResourceManager = My.Resources.ResourceManager + Dim LogoImage As Image = resourcesManager.GetObject("CdMSMS-ICS Logo") + Logo.Image = LogoImage + Logo.Size = New Size(Me.FormPanel.Width, CInt(LogoImage.Size.Height * (Me.FormPanel.Width / LogoImage.Size.Width))) + Me.FormPanel.Controls.Add(Logo) + + Me.MainFormPanel = New Transparent.FlowLayoutPanel With { + .AutoSize = True, + .MinimumSize = New Size(Me.FormPanel.Width - Globals.Unit(2), 0), + .MaximumSize = New Size(Me.FormPanel.Width - Globals.Unit(2), 0) + } + Me.FormPanel.Controls.Add(Me.MainFormPanel) + + Dim EmailInput As New BaseTextInput With { + .Name = "Email", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)) + } + Me.MainFormPanel.Controls.Add(EmailInput) + Dim PasswordInput As New BaseTextInput With { + .Name = "Password", + .Size = New Size(Me.MainFormPanel.Width, Globals.Unit(1)), + .PasswordChar = "*" + } + Me.MainFormPanel.Controls.Add(PasswordInput) + Dim LoginButton As New BaseButton With { + .Text = "Login", + .Name = "Login" + } + Me.MainFormPanel.Controls.Add(LoginButton) + + AddHandler LoginButton.Click, Sub() + Dim Email As String = EmailInput.Text + Dim Password As String = PasswordInput.Text + + If Email = "" Or Password = "" Then + Dim Modal As New BaseModal With { + .Title = "Login", + .Message = "Please fill in all fields.", + .Buttons = New Dictionary(Of String, DialogResult) From { + {"OK", DialogResult.OK} + } + } + Modal.ShowDialog() + Exit Sub + End If + + Dim Data As New Dictionary(Of String, String) From { + {"email", Email}, + {"password", Password} + } + + Try + Dim response = Globals.API("POST", "user/login", Globals.DictionaryToJSON(Data)) + + Dim user = Globals.JSONToDictionary(response) + Globals.TOKEN = user("token") + + If user("role") = "dean" Then + Me.GoToForm(New Dashboard) + ElseIf user("role") = "bsit" Then + Globals.PROGRAM = "bsit" + Me.GoToForm(New Dashboard_Calendar) + ElseIf user("role") = "bscpe" Then + Globals.PROGRAM = "bscpe" + Me.GoToForm(New Dashboard_Calendar) + End If + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + End Try + End Sub + + Dim i As Integer = 0 + For Each Control As Control In Me.MainFormPanel.Controls + Control.Margin = New Padding( + (Me.MainFormPanel.Width - Control.Width) * 0.5, + 0, + 0, + Globals.Unit(1) + ) + + i = i + 1 + Next + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin = New Padding(0) + + Dim ForgotPasswordLabel As New Transparent.Label With { + .Text = "Forgot Password?", + .AutoSize = True, + .MinimumSize = New Size(Me.MainFormPanel.Width, 0), + .MaximumSize = New Size(Me.MainFormPanel.Width, 0), + .Name = "ForgotPassword", + .TextAlign = ContentAlignment.MiddleCenter, + .Font = Globals.GetFont("Open Sans", Globals.Unit(0.5), FontStyle.Regular), + .ForeColor = Globals.Palette("Plain Dark") + } + Me.FormPanel.Controls.Add(ForgotPasswordLabel) + + Loaded = True + Me.Size = Globals.FormSize + + 'Check if the system is already set up + Try + Dim response = Globals.API("GET", "setup", Nothing) + Catch ex As WebException + Dim rep As HttpWebResponse = ex.Response + Using rdr As New StreamReader(rep.GetResponseStream()) + Dim Modal As New BaseModal With { + .Title = "Error", + .Message = rep.StatusCode & ": " & rdr.ReadToEnd() + } + Modal.ShowDialog() + End Using + + Me.GoToForm(New DeanSetup) + End Try + Me.ShowInTaskbar = False + Me.ShowInTaskbar = True + End Sub + + Protected Sub Login_Resize(sender As Object, e As EventArgs) Handles Me.Resize + If Me.Loaded Then + Dim i As Integer = 0 + For Each Control As Control In Me.FormPanel.Controls + If Control.Name = "Logo" Then + Dim Logo As PictureBox = Control + Dim resourcesManager As ResourceManager = My.Resources.ResourceManager + Dim LogoImage As Image = resourcesManager.GetObject("CdMSMS-ICS Logo") + Logo.Image = LogoImage + Logo.Size = New Size(Me.FormPanel.Width, CInt(LogoImage.Height * (Me.FormPanel.Width / LogoImage.Width))) + End If + + Control.Margin = New Padding( + (Me.FormPanel.Width - Control.Width) * 0.5, + 0, + 0, + 0 + ) + + If i > 0 Then + Control.Margin = New Padding( + Control.Margin.Left, + Globals.Unit(2), + 0, + 0 + ) + End If + + i = i + 1 + Next + Dim j As Integer = 0 + For Each Control As Control In Me.MainFormPanel.Controls + Control.Margin = New Padding( + (Me.MainFormPanel.Width - Control.Width) * 0.5, + 0, + 0, + Globals.Unit(1) + ) + + Control.Margin = New Padding( + Control.Margin.Left, + 0, + 0, + Globals.Unit(1) + ) + + j = j + 1 + Next + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin = New Padding( + Me.MainFormPanel.Controls(Me.MainFormPanel.Controls.Count - 1).Margin.Left, + 0, + 0, + 0 + ) + + Me.FormPanel.Location = New Point( + CInt(Me.Width * 0.5 - Me.FormPanel.Width * 0.5), + CInt(Me.Height * 0.5 - Me.FormPanel.Height * 0.5) + ) + + Dim Background As New Bitmap(Me.Width, Me.Height) + Dim HalfTrapezoid = Globals.LoadSvgFromResource("Half Trapezoid").Draw() + Dim BarCompliment_Top = Globals.LoadSvgFromResource("Bar Complement").Draw() + Dim Bar_Top = Globals.LoadSvgFromResource("Bar").Draw() + Dim BarCompliment_Bottom = Globals.LoadSvgFromResource("Bar Complement Bottom").Draw() + Dim Bar_Bottom = Globals.LoadSvgFromResource("Bar Bottom").Draw() + + Using g As Graphics = Graphics.FromImage(Background) + g.DrawImage( + BarCompliment_Top, + -CInt(Me.Width * 0.25), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Top, + CInt(Me.Width - Bar_Top.Width), + -CInt(Globals.Unit(12)), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + BarCompliment_Bottom, + 0, + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + g.DrawImage( + Bar_Bottom, + CInt(Me.Width * 0.75), + CInt(Me.Height * 0.75), + CInt(Globals.Unit(13)), + CInt(Globals.Unit(15)) + ) + End Using + + Me.BackgroundBitmap = Background + End If + End Sub +End Class \ No newline at end of file diff --git a/My Project/Application.Designer.vb b/My Project/Application.Designer.vb index 136e2d7..0650ad2 100644 --- a/My Project/Application.Designer.vb +++ b/My Project/Application.Designer.vb @@ -24,15 +24,15 @@ Namespace My _ Public Sub New() MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows) - Me.IsSingleInstance = true + Me.IsSingleInstance = false Me.EnableVisualStyles = false Me.SaveMySettingsOnExit = true - Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFormCloses + Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterAllFormsClose End Sub _ Protected Overrides Sub OnCreateMainForm() - Me.MainForm = Global.Schedule_Management_System.DeanSetup + Me.MainForm = Global.Schedule_Management_System.Login End Sub _ diff --git a/My Project/Application.myapp b/My Project/Application.myapp index 134d757..bdc939e 100644 --- a/My Project/Application.myapp +++ b/My Project/Application.myapp @@ -1,9 +1,9 @@  true - DeanSetup - true - 0 + Login + false + 1 false 0 0 diff --git a/My Project/Resources.Designer.vb b/My Project/Resources.Designer.vb index 8691487..fd3a601 100644 --- a/My Project/Resources.Designer.vb +++ b/My Project/Resources.Designer.vb @@ -100,6 +100,46 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property BSCpE_Setup_Graphics() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("BSCpE Setup Graphics", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property BSIT_Setup_Graphics() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("BSIT Setup Graphics", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Calendar_Icon() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Calendar Icon", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Calendar_Icon_Active() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Calendar Icon Active", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Drawing.Bitmap. ''' @@ -120,6 +160,36 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Create_Icon() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Create Icon", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Create_Icon_Active() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Create Icon Active", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property DropDown_Arrow() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("DropDown Arrow", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Byte[]. ''' @@ -130,6 +200,16 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property File_Input_Icon_Alerted() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("File Input Icon Alerted", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Byte[]. ''' @@ -200,6 +280,16 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Logout_Icon() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Logout Icon", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Byte[]. ''' @@ -220,6 +310,26 @@ Namespace My.Resources End Get End Property + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Notification_Icon() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Notification Icon", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Byte[]. + ''' + Friend ReadOnly Property Notification_Icon_Active() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Notification Icon Active", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' Looks up a localized resource of type System.Byte[]. ''' diff --git a/My Project/Resources.resx b/My Project/Resources.resx index ba18347..7ad0c45 100644 --- a/My Project/Resources.resx +++ b/My Project/Resources.resx @@ -130,15 +130,39 @@ ..\Resources\Bar Complement Bottom.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\BSCpE Setup Graphics.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\BSIT Setup Graphics.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\Calendar Icon.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\Calendar Icon Active.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\CdMSMS-ICS Logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\Close Window.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\Create Icon.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\Create Icon Active.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\DropDown Arrow.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\File Input Icon.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\File Input Icon Alerted.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\File Input Icon Hovered.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -160,12 +184,21 @@ ..\Resources\Logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Logout Icon.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\Maximize Window.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\Minimize Window.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\Notification Icon.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\Notification Icon Active.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\OpenSans-Bold.ttf;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/My Project/Settings.Designer.vb b/My Project/Settings.Designer.vb index 2d7d8b3..2f0ee93 100644 --- a/My Project/Settings.Designer.vb +++ b/My Project/Settings.Designer.vb @@ -13,42 +13,42 @@ Option Explicit On Namespace My - - _ + + _ Partial Friend NotInheritable Class MySettings Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings), MySettings) - + + Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) + #Region "My.Settings Auto-Save Functionality" #If _MyType = "WindowsForms" Then - Private Shared addedHandler As Boolean + Private Shared addedHandler As Boolean - Private Shared addedHandlerLockObject As New Object + Private Shared addedHandlerLockObject As New Object - _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub + _ + Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) + If My.Application.SaveMySettingsOnExit Then + My.Settings.Save() + End If + End Sub #End If #End Region - + Public Shared ReadOnly Property [Default]() As MySettings Get - + #If _MyType = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If + If Not addedHandler Then + SyncLock addedHandlerLockObject + If Not addedHandler Then + AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings + addedHandler = True + End If + End SyncLock + End If #End If Return defaultInstance End Get @@ -57,13 +57,13 @@ Namespace My End Namespace Namespace My - - _ + + _ Friend Module MySettingsProperty - - _ + + _ Friend ReadOnly Property Settings() As Global.Schedule_Management_System.My.MySettings Get Return Global.Schedule_Management_System.My.MySettings.Default diff --git a/Resources/BSCpE Setup Graphics.svg b/Resources/BSCpE Setup Graphics.svg new file mode 100644 index 0000000..0c12497 --- /dev/null +++ b/Resources/BSCpE Setup Graphics.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/BSIT Setup Graphics.svg b/Resources/BSIT Setup Graphics.svg new file mode 100644 index 0000000..6ac64b5 --- /dev/null +++ b/Resources/BSIT Setup Graphics.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/Calendar Icon Active.svg b/Resources/Calendar Icon Active.svg new file mode 100644 index 0000000..a018405 --- /dev/null +++ b/Resources/Calendar Icon Active.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/Calendar Icon.svg b/Resources/Calendar Icon.svg new file mode 100644 index 0000000..fbf587e --- /dev/null +++ b/Resources/Calendar Icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Resources/Create Icon Active.svg b/Resources/Create Icon Active.svg new file mode 100644 index 0000000..13f7912 --- /dev/null +++ b/Resources/Create Icon Active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Resources/Create Icon.svg b/Resources/Create Icon.svg new file mode 100644 index 0000000..4738df4 --- /dev/null +++ b/Resources/Create Icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Resources/DropDown Arrow.svg b/Resources/DropDown Arrow.svg new file mode 100644 index 0000000..d627719 --- /dev/null +++ b/Resources/DropDown Arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/File Input Icon Alerted.svg b/Resources/File Input Icon Alerted.svg new file mode 100644 index 0000000..ed0303c --- /dev/null +++ b/Resources/File Input Icon Alerted.svg @@ -0,0 +1,3 @@ + + + diff --git a/Resources/Logout Icon.svg b/Resources/Logout Icon.svg new file mode 100644 index 0000000..04c9b66 --- /dev/null +++ b/Resources/Logout Icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Resources/Notification Icon Active.svg b/Resources/Notification Icon Active.svg new file mode 100644 index 0000000..d545f90 --- /dev/null +++ b/Resources/Notification Icon Active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Resources/Notification Icon.svg b/Resources/Notification Icon.svg new file mode 100644 index 0000000..bc0a42f --- /dev/null +++ b/Resources/Notification Icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Schedule Management System.vbproj b/Schedule Management System.vbproj index 6fa89f2..42564c4 100644 --- a/Schedule Management System.vbproj +++ b/Schedule Management System.vbproj @@ -8,12 +8,14 @@ WinExe Schedule_Management_System.My.MyApplication Schedule_Management_System - Schedule Management System + CdMSMS-ICS 512 WindowsForms v4.8 true true + + AnyCPU @@ -22,7 +24,7 @@ true true bin\Debug\ - Schedule Management System.xml + CdMSMS-ICS.xml 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 @@ -32,7 +34,7 @@ true true bin\Release\ - Schedule Management System.xml + CdMSMS-ICS.xml 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 @@ -51,9 +53,24 @@ Icon.ico + + packages\EngineIoClientDotNet.0.9.22\lib\net45\EngineIoClientDotNet.dll + packages\ExCSS.4.2.3\lib\net48\ExCSS.dll + + packages\H.Engine.IO.1.2.16\lib\net462\H.Engine.IO.dll + + + packages\H.WebSockets.1.2.16\lib\net451\H.WebSockets.dll + + + packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + packages\Svg.3.4.7\lib\net472\Svg.dll @@ -71,8 +88,20 @@ packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll + + + packages\System.Text.Json.7.0.3\lib\net462\System.Text.Json.dll + + + packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll @@ -81,6 +110,12 @@ + + packages\WebSocketSharp-netstandard.1.0.1\lib\net45\websocket-sharp.dll + + + packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + @@ -96,21 +131,41 @@ + + + + Component + Component + + Form + Form Form + + Form + Form Form + + Form + + + Form + + + Form + DeanSetup.vb @@ -129,14 +184,16 @@ Component - + Form - - Form1.vb + Form + + Form + True @@ -159,9 +216,6 @@ BaseForm.vb - - Form1.vb - VbMyResourcesResXFileCodeGenerator Resources.Designer.vb @@ -255,8 +309,26 @@ + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/Transparent.vb b/Transparent.vb index 76d3918..ca49fd9 100644 --- a/Transparent.vb +++ b/Transparent.vb @@ -2,7 +2,7 @@ Public Class Panel Inherits System.Windows.Forms.Panel Public Sub New() - SetStyle(ControlStyles.SupportsTransparentBackColor, True) + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) Me.BackColor = Color.Transparent End Sub End Class @@ -10,7 +10,7 @@ Public Class FlowLayoutPanel Inherits System.Windows.Forms.FlowLayoutPanel Public Sub New() - SetStyle(ControlStyles.SupportsTransparentBackColor, True) + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) Me.BackColor = Color.Transparent End Sub End Class @@ -18,7 +18,7 @@ Public Class Label Inherits System.Windows.Forms.Label Public Sub New() - SetStyle(ControlStyles.SupportsTransparentBackColor, True) + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) Me.BackColor = Color.Transparent End Sub End Class @@ -26,15 +26,38 @@ Public Class TextBox Inherits System.Windows.Forms.TextBox Public Sub New() - SetStyle(ControlStyles.SupportsTransparentBackColor, True) + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) Me.BackColor = Color.Transparent End Sub + Protected Overrides ReadOnly Property CreateParams() As CreateParams + Get + Dim CP As CreateParams = MyBase.CreateParams + CP.ExStyle = CP.ExStyle Or &H20 + Return CP + End Get + End Property End Class Public Class Button Inherits System.Windows.Forms.Button Public Sub New() - SetStyle(ControlStyles.SupportsTransparentBackColor, True) + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) + Me.BackColor = Color.Transparent + End Sub + End Class + + Public Class ComboBox + Inherits System.Windows.Forms.ComboBox + Public Sub New() + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) + Me.BackColor = Color.Transparent + End Sub + End Class + + Public Class PictureBox + Inherits System.Windows.Forms.PictureBox + Public Sub New() + SetStyle(ControlStyles.SupportsTransparentBackColor Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True) Me.BackColor = Color.Transparent End Sub End Class diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..1760c04 --- /dev/null +++ b/backend/.env @@ -0,0 +1,3 @@ +GMAIL_ADDRESS="cdmsms.ics@gmail.com" +GMAIL_PASSWORD="urah xthy pjet tspm" +PORT="3090" \ No newline at end of file diff --git a/backend/api/admin.js b/backend/api/admin.js new file mode 100644 index 0000000..ee5d9a6 --- /dev/null +++ b/backend/api/admin.js @@ -0,0 +1,1273 @@ + +const globals = require('../utils/globals.js'); + +const bcrypt = require('bcrypt'); + +const express = require('express'); +const admin = express.Router(); + +const connection = require('../utils/databaseConnection.js'); + +// Program Info +admin.get('/dashboard/program/:program', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = { + programHead: '', + courses: 0, + faculties: 0, + facilities: 0, + students: 0 + }; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE role = ?', [program], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + data.programHead = 'Not yet set'; + } else { + data.programHead = `${results[0].firstName} ${results[0].lastName}`; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM courses', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.courses = results.length; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE programs = ? OR programs = ?', [program, 'both'], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.faculties = results.length; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM facilities', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.facilities = results.length; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE program = ?', [program], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.students = results.length; + }; + resolve(); + }); + }); + + res.send(data); +}); + +// Program Year Levels +admin.get('/dashboard/program/:program/yearlevels/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = []; + await new Promise((resolve, reject) => { + connection.query('SELECT yearLevel FROM courses WHERE courseCode LIKE ?', [`${program.replace('bs', '').toUpperCase()}%`], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + if (!data.includes(result.yearLevel)) { + data.push(result.yearLevel); + }; + }; + }; + resolve(); + }); + }); + + data.sort((a, b) => a - b); + + const stringsToReturn = {}; + + for (const yearLevel of data) { + const postfixes = ['st', 'nd', 'rd', 'th']; + let postfix = ''; + if (yearLevel < 4) { + postfix = postfixes[yearLevel - 1]; + } else { + postfix = postfixes[3]; + }; + stringsToReturn[yearLevel] = `${yearLevel}${postfix} Year`; + }; + + res.send(stringsToReturn); +}); + +// Program Courses +admin.get('/dashboard/program/:program/courses/:yearLevel', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const yearLevel = parseInt(req.params['yearLevel']); + if (isNaN(yearLevel)) { + res.status(400).send('Invalid year level'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM courses WHERE courseCode LIKE ? AND yearLevel = ?', [`${program.replace('bs', '').toUpperCase()}%`, yearLevel], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + data.push(result); + }; + }; + resolve(); + }); + }); + + const toReturn = {}; + + for (const course of data) { + toReturn[course.courseCode] = course.description; + }; + + res.send(toReturn); +}); + +// Program Faculties +admin.get('/dashboard/program/:program/faculties/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE programs = ? OR programs = ?', [program, 'both'], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + data.push(result); + }; + }; + resolve(); + }); + }); + + const toReturn = {}; + + for (const faculty of data) { + toReturn[faculty.facultyID] = `${faculty.firstName} ${faculty.lastName}`; + }; + + res.send(toReturn); +}); + +// Program Facilities +admin.get('/dashboard/program/:program/facilities/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM facilities', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + data.push(result); + }; + }; + resolve(); + }); + }); + + const toReturn = {}; + + for (const facility of data) { + toReturn[facility.facilityID] = facility.name; + }; + + res.send(toReturn); +}); + +// Program Sections +admin.get('/dashboard/program/:program/sections/:yearLevel', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const yearLevel = parseInt(req.params['yearLevel']); + if (isNaN(yearLevel)) { + res.status(400).send('Invalid year level'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE program = ?', [program], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + if (!data.includes(result.section)) { + data.push(result.section); + }; + }; + }; + resolve(); + }); + }); + + const filtered = data.filter(section => section.startsWith(`${yearLevel}`)); + + const sorted = filtered.sort((a, b) => a.localeCompare(b)); + + const toReturn = {}; + + for (const section of sorted) { + toReturn[section] = section; + }; + + res.send(toReturn); +}); + +// Create Schedule +admin.post('/dashboard/program/:program/schedule/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + /** @type {import('../utils/docs.js').Schedule} */ + const schedule = { + scheduleID: '', + courseCode: '', + facultyID: '', + facilityID: '', + section: '', + day: '', + startTime: '', + endTime: '', + }; + + schedule.scheduleID = `${globals.randomString(10)}`; + schedule.courseCode = `${req.body['course'].split(' - ')[0]}`.trim(); + schedule.facultyID = `${req.body['faculty'].split(' - ')[0]}`.trim(); + schedule.facilityID = `${req.body['facility'].split(' - ')[0]}`.trim(); + schedule.day = req.body['day'].trim(); + schedule.startTime = globals.convertTime12to24(req.body['startTime'].trim()); + schedule.endTime = globals.convertTime12to24(req.body['endTime'].trim()); + schedule.section = req.body['section'].trim(); + + // check for conflicts + const conflicts = []; + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facultyID = '${schedule.facultyID}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + reason: 'Faculty conflict' + }); + }; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facilityID = '${schedule.facilityID}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + reason: 'Facility conflict' + }); + }; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE section = '${schedule.section}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + reason: 'Section conflict' + }); + }; + }; + resolve(); + }); + }); + + if (conflicts.length > 0) { + // Show conflictive schedules + res.status(409).send(`Conflicts detected with the following schedules:\n\n${conflicts.map(conflict => `${conflict.reason}:\n| ${conflict.courseCode} | ${program === 'bsit' ? 'BSIT' : 'BSCpE'} ${conflict.section} | ${conflict.day} |\n| ${conflict.startTime} - ${conflict.endTime} |`).join('\n\n') + }`); + return; + } else { + await new Promise((resolve, reject) => { + connection.query('INSERT INTO schedules SET ?', schedule, (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + + // update the scheduleID of the Facility, add this scheduleID to the list + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM facilities WHERE facilityID = ?', [schedule.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules ? results[0].schedules.split(',') : []; + schedules.push(schedule.scheduleID); + connection.query('UPDATE facilities SET schedules = ? WHERE facilityID = ?', [schedules.join(','), schedule.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + // update the scheduleID of the Faculty, add this scheduleID to the list + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM faculties WHERE facultyID = ?', [schedule.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules ? results[0].schedules.split(',') : []; + schedules.push(schedule.scheduleID); + connection.query('UPDATE faculties SET schedules = ? WHERE facultyID = ?', [schedules.join(','), schedule.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + // update the scheduleID of the Section, add this scheduleID to the list + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM students WHERE section = ?', [schedule.section], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules ? results[0].schedules.split(',') : []; + schedules.push(schedule.scheduleID); + connection.query('UPDATE students SET schedules = ? WHERE section = ?', [schedules.join(','), schedule.section], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + }; + + res.send('Success'); + + for (const ws of globals.rooms['calendar_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['calendar_user']) { + ws.send("refresh"); + }; +}); + +// Get Schedule +admin.get('/dashboard/program/:program/schedule/:identifier/:key', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const identifier = req.params['identifier']; + if (identifier !== 'facultyID' && identifier !== 'facilityID' && identifier !== 'section') { + res.status(400).send('Invalid identifier'); + return; + }; + const key = req.params['key']; + if (!key) { + res.status(400).send('Invalid key'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM schedules WHERE ${identifier} = ?`, [key], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + data.push(result); + }; + }; + resolve(); + }); + }); + + const toReturn = {}; + + for (const datum of data) { + toReturn[datum.scheduleID] = { + course: datum.courseCode, + faculty: datum.facultyID, + facility: datum.facilityID, + section: datum.section, + day: datum.day, + startTime: datum.startTime, + endTime: datum.endTime, + description: '', + identifier: '' + }; + if (identifier === 'facultyID') { + toReturn[datum.scheduleID].description = program === 'bsit' ? `BSIT ${datum.section}` : `BSCpE ${datum.section}`; + // Get Facility Name + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [datum.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + toReturn[datum.scheduleID].identifier += `${results[0].name}`; + }; + resolve(); + }); + }); + } else if (identifier === 'facilityID') { + toReturn[datum.scheduleID].description = program === 'bsit' ? `BSIT ${datum.section}` : `BSCpE ${datum.section}`; + // Get Faculty Name + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [datum.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + toReturn[datum.scheduleID].identifier += `${results[0].firstName} ${results[0].lastName}`; + }; + resolve(); + }); + }); + } else if (identifier === 'section') { + // Get Faculty Name + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [datum.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + toReturn[datum.scheduleID].description = `${results[0].firstName} ${results[0].lastName}`; + }; + resolve(); + }); + }); + // Get Facility Name + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [datum.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + toReturn[datum.scheduleID].identifier += `${results[0].name}`; + }; + resolve(); + }); + }); + }; + }; + + res.send(toReturn); +}); + +// Get Calendar +admin.get('/dashboard/program/:program/calendars/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM schedules', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + if (!data.includes(result.section)) { + data.push({ + key: result.section, + name: `${program === 'bsit' ? 'BSIT' : 'BSCpE'} ${result.section}`, + identifier: 'section' + }); + }; + }; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT facilityID FROM schedules', async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [result.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.push({ + key: result.facilityID, + name: results[0].name, + identifier: 'facilityID' + }); + }; + resolve(); + }); + }); + }; + }; + resolve(); + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT facultyID FROM schedules', async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [result.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.push({ + key: result.facultyID, + name: `${results[0].firstName} ${results[0].lastName}`, + identifier: 'facultyID' + }); + }; + resolve(); + }); + }); + }; + }; + resolve(); + }); + }); + + const toReturn = {}; + + for (const datum of data) { + toReturn[datum.name] = { + name: datum.name, + key: datum.key, + identifier: datum.identifier + }; + }; + + res.send(toReturn); +}); + +// Update Schedule +admin.post('/dashboard/program/:program/update/schedule/:scheduleID', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const scheduleID = req.params['scheduleID']; + if (!scheduleID) { + res.status(400).send('Invalid schedule ID'); + return; + }; + + const schedule = { + facultyID: '', + facilityID: '', + section: '', + day: '', + startTime: '', + endTime: '' + }; + + schedule.facultyID = `${req.body['facultyID'].split(' - ')[0]}`.trim(); + schedule.facilityID = `${req.body['facilityID'].split(' - ')[0]}`.trim(); + schedule.section = req.body['section'].trim(); + schedule.day = req.body['day'].trim(); + schedule.startTime = globals.convertTime12to24(req.body['startTime'].trim()); + schedule.endTime = globals.convertTime12to24(req.body['endTime'].trim()); + + + + // Check for conflicts + const conflicts = []; + // Faculty + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facultyID = '${schedule.facultyID}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}')) + AND NOT scheduleID = '${scheduleID}'`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Faculty conflict' + }); + }; + }; + resolve(); + }); + }); + // Facility + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facilityID = '${schedule.facilityID}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}')) + AND NOT scheduleID = '${scheduleID}'`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Facility conflict' + }); + }; + }; + resolve(); + }); + }); + // Section + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE section = '${schedule.section}' + AND day = '${schedule.day}' + AND ((startTime < '${schedule.endTime}') AND (endTime > '${schedule.startTime}')) + AND NOT scheduleID = '${scheduleID}'`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Section conflict' + }); + }; + }; + resolve(); + }); + }); + + // Remove this schedule from the conflicts + conflicts.filter(conflict => conflict.scheduleID !== scheduleID); + + if (conflicts.length > 0) { + // Show conflictive schedules + res.status(409).send(`Conflicts detected with the following schedules:\n\n${conflicts.map(conflict => `${conflict.reason}:\n| ${conflict.courseCode} | ${program === 'bsit' ? 'BSIT' : 'BSCpE'} ${conflict.section} | ${conflict.day} |\n| ${conflict.startTime} - ${conflict.endTime} |`).join('\n\n') + }`); + return; + } else { + await new Promise((resolve, reject) => { + connection.query('UPDATE schedules SET ? WHERE scheduleID = ?', [schedule, scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + }; + + res.send('Schedule updated'); + + for (const ws of globals.rooms['calendar_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['calendar_user']) { + ws.send("refresh"); + }; +}); + +// Delete Schedule +admin.post('/dashboard/program/:program/delete/schedule/:scheduleID', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const scheduleID = req.params['scheduleID']; + if (!scheduleID) { + res.status(400).send('Invalid schedule ID'); + return; + }; + + const data = {}; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM schedules WHERE scheduleID = ?', [scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.facultyID = results[0].facultyID; + data.facilityID = results[0].facilityID; + data.section = results[0].section; + }; + resolve(); + }); + }); + + await new Promise((resolve, reject) => { + connection.query('DELETE FROM schedules WHERE scheduleID = ?', [scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + + // Remove this scheduleID from the Faculty + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM faculties WHERE facultyID = ?', [data.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules.split(','); + schedules.splice(schedules.indexOf(scheduleID), 1); + connection.query('UPDATE faculties SET schedules = ? WHERE facultyID = ?', [schedules.join(','), data.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + + // Remove this scheduleID from the Facility + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM facilities WHERE facilityID = ?', [data.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules.split(','); + schedules.splice(schedules.indexOf(scheduleID), 1); + connection.query('UPDATE facilities SET schedules = ? WHERE facilityID = ?', [schedules.join(','), data.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + + // Remove this scheduleID from the Section + await new Promise((resolve, reject) => { + connection.query('SELECT schedules FROM students WHERE section = ?', [data.section], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + const schedules = results[0].schedules.split(','); + schedules.splice(schedules.indexOf(scheduleID), 1); + connection.query('UPDATE students SET schedules = ? WHERE section = ?', [schedules.join(','), data.section], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }; + }); + }); + + res.send('Schedule deleted'); + + for (const ws of globals.rooms['calendar_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['calendar_user']) { + ws.send("refresh"); + }; +}); + +// Get Requests +admin.get('/dashboard/program/:program/requests/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + if (program !== 'bsit' && program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + const data = []; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM requests WHERE program = ?', [program], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + data.push(result); + }; + }; + resolve(); + }); + }); + + // Sort by date + data.sort((a, b) => new Date(b.requestDate) - new Date(a.requestDate)); + + const toReturn = {}; + + for (const datum of data) { + const request = { + requestID: datum.requestID, + facultyID: datum.facultyID, + facultyName: '', + original: { + facilityID: datum.original_facilityID, + facilityName: '', + day: datum.original_day, + startTime: datum.original_startTime, + endTime: datum.original_endTime + }, + request: { + facilityID: datum.request_facilityID, + facilityName: '', + day: datum.request_day, + startTime: datum.request_startTime, + endTime: datum.request_endTime + }, + status: datum.status, + requestReason: datum.requestReason, + rejectReason: datum.rejectReason, + requestDate: datum.requestDate, + program: datum.program, + courseCode: datum.courseCode, + scheduleID: datum.scheduleID + }; + + // Get faculty name + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [datum.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + request.facultyName = `${results[0].firstName} ${results[0].lastName}`; + }; + resolve(); + }); + }); + // Get original facility name + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [request.original.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + request.original.facilityName = results[0].name; + }; + resolve(); + }); + }); + // Get request facility name + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [request.request.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + request.request.facilityName = results[0].name; + }; + resolve(); + }); + }); + toReturn[datum.requestID] = request; + }; + + res.send(toReturn); +}); + +// Reject Request +admin.post('/dashboard/program/:program/requests/:requestID/reject/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + const requestID = req.params['requestID']; + + const rejectReason = req.body['rejectReason']; + + if (!rejectReason) { + res.status(400).send('Invalid reject reason'); + return; + }; + + await new Promise((resolve, reject) => { + connection.query('UPDATE requests SET status = ?, rejectReason = ? WHERE requestID = ?', ['rejected', rejectReason, requestID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + + res.send('Request rejected'); + for (const ws of globals.rooms['notifications_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['notifications_user']) { + ws.send("refresh"); + }; +}); + +// Approve Request +admin.post('/dashboard/program/:program/requests/:requestID/approve/', async (req, res) => { + const program = req.params['program'].toLowerCase(); + const requestID = req.params['requestID']; + + // Get the request + const request = { + requestID: '', + facultyID: '', + facultyName: '', + original: { + facilityID: '', + facilityName: '', + day: '', + startTime: '', + endTime: '' + }, + request: { + facilityID: '', + facilityName: '', + day: '', + startTime: '', + endTime: '' + }, + status: '', + requestReason: '', + rejectReason: '', + requestDate: '', + program: '', + courseCode: '', + scheduleID: '' + }; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM requests WHERE requestID = ?', [requestID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + request.requestID = results[0].requestID; + request.facultyID = results[0].facultyID; + request.original.facilityID = results[0].original_facilityID; + request.original.day = results[0].original_day; + request.original.startTime = results[0].original_startTime; + request.original.endTime = results[0].original_endTime; + request.request.facilityID = results[0].request_facilityID; + request.request.day = results[0].request_day; + request.request.startTime = results[0].request_startTime; + request.request.endTime = results[0].request_endTime; + request.status = results[0].status; + request.requestReason = results[0].requestReason; + request.rejectReason = results[0].rejectReason; + request.requestDate = results[0].requestDate; + request.program = results[0].program; + request.courseCode = results[0].courseCode; + request.scheduleID = results[0].scheduleID; + }; + resolve(); + }); + }); + + // Get section + let section = ''; + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM schedules WHERE scheduleID = ?', [request.scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + section = results[0].section; + }; + resolve(); + }); + }); + + // Check for conflicts + let conflicts = []; + // Faculty + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facultyID = '${request.facultyID}' + AND day = '${request.request.day}' + AND ((startTime < '${request.request.endTime}') AND (endTime > '${request.request.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Faculty conflict' + }); + }; + }; + resolve(); + }); + }); + // Facility + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facilityID = '${request.request.facilityID}' + AND day = '${request.request.day}' + AND ((startTime < '${request.request.endTime}') AND (endTime > '${request.request.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Facility conflict' + }); + }; + }; + resolve(); + }); + }); + // Section + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE section = '${section}' + AND day = '${request.request.day}' + AND ((startTime < '${request.request.endTime}') AND (endTime > '${request.request.startTime}'))`, + (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push({ + courseCode: result.courseCode, + day: result.day, + startTime: result.startTime, + endTime: result.endTime, + section: result.section, + scheduleID: result.scheduleID, + reason: 'Section conflict' + }); + }; + }; + resolve(); + }); + }); + + // Remove this schedule from the conflicts + conflicts = conflicts.filter(conflict => conflict.scheduleID !== request.scheduleID); + + if (conflicts.length > 0) { + // Show conflictive schedules + res.status(409).send(`Conflicts detected with the following schedules:\n\n${conflicts.map(conflict => `${conflict.reason}:\n| ${conflict.courseCode} | ${program === 'bsit' ? 'BSIT' : 'BSCpE'} ${conflict.section} | ${conflict.day} |\n| ${conflict.startTime} - ${conflict.endTime} |`).join('\n\n') + }`); + return; + } else { + // Update the schedule + await new Promise((resolve, reject) => { + connection.query('UPDATE schedules SET facultyID = ?, facilityID = ?, day = ?, startTime = ?, endTime = ? WHERE scheduleID = ?', [request.facultyID, request.request.facilityID, request.request.day, request.request.startTime, request.request.endTime, request.scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + + // Update the request status + await new Promise((resolve, reject) => { + connection.query('UPDATE requests SET status = ? WHERE requestID = ?', ['approved', requestID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + }; + resolve(); + }); + }); + }; + + res.send('Request approved'); + + for (const ws of globals.rooms['calendar_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['calendar_user']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['notifications_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['notifications_user']) { + ws.send("refresh"); + }; +}); + +module.exports = admin; \ No newline at end of file diff --git a/backend/api/setup.js b/backend/api/setup.js new file mode 100644 index 0000000..60de0b7 --- /dev/null +++ b/backend/api/setup.js @@ -0,0 +1,455 @@ + +const globals = require('../utils/globals.js'); +const mail = require('../utils/mail.js'); +const Readable = require('stream').Readable; + +const bcrypt = require('bcrypt'); +const csv = require('csv-parser'); + +const express = require('express'); +const setup = express.Router(); + +const connection = require('../utils/databaseConnection.js'); + +setup.get('/', async (req, res) => { + // Check if all table exists and if they have data + let setup = true; + // Admin + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + setup = false; + }; + resolve(); + }); + }); + // Courses + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM courses', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + setup = false; + }; + resolve(); + }); + }); + // Faculties + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + setup = false; + }; + resolve(); + }); + }); + // Facilities + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM facilities', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + setup = false; + }; + resolve(); + }); + }); + // Students + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students', (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + setup = false; + }; + resolve(); + }); + }); + + if (setup) { + res.send('Database is already setup. Login to the system to continue'); + } else { + res.status(400).send('Not yet setup. Please setup first'); + // Empty all tables + await new Promise((resolve, reject) => { + connection.query('DELETE FROM admins', (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + await new Promise((resolve, reject) => { + connection.query('DELETE FROM courses', (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + await new Promise((resolve, reject) => { + connection.query('DELETE FROM faculties', (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + await new Promise((resolve, reject) => { + connection.query('DELETE FROM facilities', (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + await new Promise((resolve, reject) => { + connection.query('DELETE FROM students', (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + }; +}); + +setup.post('/admin', async (req, res) => { + /** + * @type {{ + * firstName: String, + * lastName: String, + * email: String, + * password: String, + * role: 'dean' | 'bsit' | 'bscpe' + * }} + */ + const admin = req.body; + + if (admin.firstName === undefined || admin.lastName === undefined || admin.email === undefined || admin.password === undefined || admin.role === undefined) { + res.status(400).send('Missing required fields'); + return; + }; + + if (admin.firstName.length < 3) { + res.status(400).send('First Name too short'); + return; + }; + if (admin.lastName.length < 2) { + res.status(400).send('Last Name too short'); + return; + }; + if (!/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(admin.email)) { + res.status(400).send('Invalid Email'); + return; + }; + if (admin.password.length < 8) { + res.status(400).send('Password too short'); + return; + }; + + admin.password = await bcrypt.hash(admin.password, 10); + + const adminID = globals.randomID(); + + // Check if email is already in use + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE email = ?', [admin.email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + res.status(400).send('Email already in use'); + } else { + resolve(); + }; + }); + }); + + // Generate verification code + let verificationID = `${adminID}-${globals.randomString(30)}`; + + // Send verification email + await mail(admin.email, 'Verify your account', `Click this link to verify your account: ${req.protocol}://${req.get('host')}/api/user/verify/${verificationID}`, `Click this link to verify your account: Verify`); + + // Generate token + const token = `${globals.randomString(10)}.${globals.randomString(30)}`; + + connection.query('INSERT INTO admins (adminID, firstName, lastName, email, password, role, verificationID, verified, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [adminID, admin.firstName, admin.lastName, admin.email, admin.password, admin.role, verificationID, false, token], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + return; + }; + res.send('success'); + }); +}); + +/** + * @type {(string: String) => Readable} + */ +const stringToStream = (string) => { + const stream = new Readable(); + stream.push(string); + stream.push(null); + return stream; +}; + +setup.post('/program', async (req, res) => { + const data = req.body; + + /** + * @type {{ + * courses: { + * courseCode: String, + * description: String, + * units: Number, + * yearLevel: Number + * }[], + * faculties: { + * facultyId: String, + * firstName: String, + * lastName: String, + * email: String, + * programs: 'bsit' | 'bscpe' | 'both' + * }[], + * facilities: { + * facilityID: String, + * name: String, + * description: String + * }[], + * students: { + * studentID: String, + * firstName: String, + * lastName: String, + * email: String, + * section: String + * }[], + * program: 'bsit' | 'bscpe' + * }} + */ + const jsonData = {}; + + for (const key in data) { + const row = []; + const promise = new Promise((resolve, reject) => { + stringToStream(data[key]) + .pipe(csv()) + .on('data', (data) => { + row.push(data); + }) + .on('end', () => { + resolve(); + }) + .on('error', (err) => { + res.status(400).send('Invalid CSV'); + reject(err); + }); + }); + await promise; + jsonData[key] = row; + }; + + for (const faculty of jsonData.faculties) { + if (faculty.facultyId === undefined || faculty.firstName === undefined || faculty.lastName === undefined || faculty.email === undefined) { + res.status(400).send(`Missing required fields in faculties: ${JSON.stringify(faculty)}`); + return; + }; + }; + + // for (const facility of jsonData.facilities) { + // if (facility.facilityID === undefined || facility.name === undefined || facility.description === undefined) { + // res.status(400).send(`Missing required fields in facilities: ${JSON.stringify(facility)}`); + // return; + // }; + // }; + + for (const student of jsonData.students) { + if (student.studentID === undefined || student.firstName === undefined || student.lastName === undefined || student.email === undefined) { + res.status(400).send(`Missing required fields in students: ${JSON.stringify(student)}`); + return; + }; + }; + + jsonData.program = data.program; + if (jsonData.program !== 'bsit' && jsonData.program !== 'bscpe') { + res.status(400).send('Invalid program'); + return; + }; + + await new Promise((resolve, reject) => { + for (const course of jsonData.courses) { + connection.query('INSERT INTO courses (courseCode, description, units, yearLevel) VALUES (?, ?, ?, ?)', [course.courseCode, course.description, course.units, course.yearLevel], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }; + }); + + await new Promise((resolve, reject) => { + for (const faculty of jsonData.faculties) { + // Check if faculty already exists in the database + // If it does, this means the faculty is already in the bsit program + // Just change it to 'both' + // If it doesn't, insert it as a new faculty + // Then set the programs field to 'bsit' or 'bscpe' depending on the jsonData.program + connection.query('SELECT * FROM faculties WHERE facultyID = ?', [faculty.facultyId], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + connection.query('INSERT INTO faculties (facultyID, firstName, lastName, email, programs) VALUES (?, ?, ?, ?, ?)', [faculty.facultyId, faculty.firstName, faculty.lastName, faculty.email, jsonData.program], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + } else { + connection.query('UPDATE faculties SET programs = ? WHERE facultyID = ?', [results[0].programs === 'bsit' ? 'both' : results[0].programs, faculty.facultyId], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }; + }); + }; + }); + + // await new Promise((resolve, reject) => { + // for (const facility of jsonData.facilities) { + // // Check if facility already exists in the database + // // If it does, skip + // // If it doesn't, insert it as a new facility + // connection.query('SELECT * FROM facilities WHERE facilityID = ?', [facility.facilityID], (err, results) => { + // if (err) { + // res.status(500).send('Internal Server Error'); + // reject(err); + // } else if (results.length === 0) { + // connection.query('INSERT INTO facilities (facilityID, name, description) VALUES (?, ?, ?)', [facility.facilityID, facility.name, facility.description], (err) => { + // if (err) { + // res.status(500).send('Internal Server Error'); + // reject(err); + // } else { + // resolve(); + // }; + // }); + // } else { + // resolve(); + // }; + // }); + // }; + // }); + + await new Promise((resolve, reject) => { + for (const student of jsonData.students) { + connection.query('INSERT INTO students (studentID, firstName, lastName, email, program, section) VALUES (?, ?, ?, ?, ?, ?)', [student.studentID, student.firstName, student.lastName, student.email, jsonData.program, student.section], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }; + }); + + res.send('success'); +}); + +setup.post('/facilities', async (req, res) => { + const data = req.body; + + /** + * @type {{ + * facilities: { + * facilityID: String, + * name: String, + * description: String + * }[] + * }} + */ + const jsonData = {}; + + for (const key in data) { + const row = []; + const promise = new Promise((resolve, reject) => { + stringToStream(data[key]) + .pipe(csv()) + .on('data', (data) => { + row.push(data); + }) + .on('end', () => { + resolve(); + }) + .on('error', (err) => { + res.status(400).send('Invalid CSV'); + reject(err); + }); + }); + await promise; + jsonData[key] = row; + }; + + for (const facility of jsonData.facilities) { + if (facility.facilityID === undefined || facility.name === undefined || facility.description === undefined) { + res.status(400).send(`Missing required fields in facilities: ${JSON.stringify(facility)}`); + return; + }; + }; + + await new Promise((resolve, reject) => { + for (const facility of jsonData.facilities) { + // Check if facility already exists in the database + // If it does, skip + // If it doesn't, insert it as a new facility + connection.query('SELECT * FROM facilities WHERE facilityID = ?', [facility.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length === 0) { + connection.query('INSERT INTO facilities (facilityID, name, description) VALUES (?, ?, ?)', [facility.facilityID, facility.name, facility.description], (err) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + } else { + resolve(); + }; + }); + }; + }); + + res.send('success'); +}); + +module.exports = setup; diff --git a/backend/api/user.js b/backend/api/user.js new file mode 100644 index 0000000..8cd4f27 --- /dev/null +++ b/backend/api/user.js @@ -0,0 +1,1442 @@ + +const globals = require('../utils/globals.js'); +const mail = require('../utils/mail.js'); + +const bcrypt = require('bcrypt'); + +const express = require('express'); +const user = express.Router(); + +const connection = require('../utils/databaseConnection.js'); + +user.post('/login', async (req, res) => { + const { email, password } = req.body; + + // Check if email exists in admins, faculties, or students + let found = false; + let where = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'admins'; + resolve(); + } else { + resolve(); + }; + }); + }); + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'faculties'; + resolve(); + } else { + resolve(); + }; + }); + }); + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'students'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) { + res.status(404).send('Email not found'); + return; + }; + + // Check if password is correct + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE email = ?`, [email], async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + /** + * @type {import('../utils/docs.js').Admin | import('../utils/docs.js').Faculty | import('../utils/docs.js').Student} + */ + const user = results[0]; + if (email !== user.email) { + res.status(404).send('Email not found'); + return; + }; + if (!user.password) { + res.status(401).send('User hasn\'t set a registered yet'); + return; + }; + const comparison = await bcrypt.compare(password, user.password); + if (comparison) { + // Check if user is verified + if (user.verified) { + const userToReturn = { + ...user, + password: undefined, + studentID: undefined, + facultyID: undefined, + adminID: undefined + }; + if (where === 'admins') userToReturn['identifier'] = 'adminID'; + if (where === 'faculties') userToReturn['identifier'] = 'facultyID'; + if (where === 'students') userToReturn['identifier'] = 'studentID'; + + userToReturn[userToReturn['identifier']] = user[userToReturn['identifier']]; + + res.send(userToReturn); + } else { + res.status(401).send('Account not verified. Please check your email for the verification code'); + }; + } else { + res.status(401).send('Incorrect password'); + }; + resolve(); + }; + }); + }); +}); + +user.get('/verify/:id', async (req, res) => { + const id = req.params.id; + + // Check if verification code exists in admins, faculties, or students + let found = false; + let where = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE verificationID = ?', [id], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'admins'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE verificationID = ?', [id], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'faculties'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE verificationID = ?', [id], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'students'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) { + res.status(404).send('Verification code not found'); + return; + }; + + // Set verified to true + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET verified = true WHERE verificationID = ?`, [id], async (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET verificationID = NULL WHERE verificationID = ?`, [id], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + res.send('Account verified'); + resolve(); + }; + }); + }); +}); + +user.post('/forgotPassword/', async (req, res) => { + const email = req.body['email']; + + if (!email) { + res.status(400).send('Email is required'); + return; + }; + + // Check if email exists in admins, faculties, or students + let found = false; + let where = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'admins'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'faculties'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'students'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) { + res.status(404).send('Email not found'); + return; + }; + + // Get user + /** @type {import('../utils/docs.js').Admin | import('../utils/docs.js').Faculty | import('../utils/docs.js').Student} */ + let user = null; + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE email = ?`, [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + user = results[0]; + resolve(); + }; + }); + }); + + // Generate new forgotPasswordCode + // 000-000-000 + const forgotPasswordCode = `${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}-${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}-${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`; + + // Set forgotPasswordCode + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET forgotPasswordCode = ? WHERE email = ?`, [forgotPasswordCode, email], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + + // Send email + const subject = 'Forgot Password'; + const text = `Your forgot password code is ${forgotPasswordCode}`; + await mail(email, subject, text); + + res.send('Email sent'); +}); + +user.post('/resetPassword/', async (req, res) => { + const email = req.body['email']; + const forgotPasswordCode = req.body['forgotPasswordCode']; + + if (!email) { + res.status(400).send('Email is required'); + return; + }; + + if (!forgotPasswordCode) { + res.status(400).send('Forgot password code is required'); + return; + }; + + // Check if email exists in admins, faculties, or students + let found = false; + let where = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'admins'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'faculties'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'students'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) { + res.status(404).send('Email not found'); + return; + }; + + // Get user + /** @type {import('../utils/docs.js').Admin | import('../utils/docs.js').Faculty | import('../utils/docs.js').Student} */ + let user = null; + + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE email = ?`, [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + user = results[0]; + resolve(); + }; + }); + }); + + // Check if forgotPasswordCode is correct + if (forgotPasswordCode !== user.forgotPasswordCode) { + res.status(401).send('Incorrect forgot password code'); + return; + }; + + res.send('Correct forgot password code'); +}); + +user.post('/changePassword/', async (req, res) => { + const email = req.body['email']; + const forgotPasswordCode = req.body['forgotPasswordCode']; + const password = req.body['password']; + + if (!email) { + res.status(400).send('Email is required'); + return; + }; + if (!forgotPasswordCode) { + res.status(400).send('Forgot password code is required'); + return; + }; + if (!password) { + res.status(400).send('Password is required'); + return; + }; + + // Check if email exists in admins, faculties, or students + let found = false; + let where = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM admins WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'admins'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'faculties'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE email = ?', [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else if (results.length > 0) { + found = true; + where = 'students'; + resolve(); + } else { + resolve(); + }; + }); + }); + + if (!found) { + res.status(404).send('Email not found'); + return; + }; + + // Get user + /** @type {import('../utils/docs.js').Admin | import('../utils/docs.js').Faculty | import('../utils/docs.js').Student} */ + let user = null; + + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE email = ?`, [email], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + user = results[0]; + resolve(); + }; + }); + }); + + // Check if forgotPasswordCode is correct + if (forgotPasswordCode !== user.forgotPasswordCode) { + res.status(401).send('Incorrect forgot password code'); + return; + }; + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Set password + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET password = ? WHERE email = ?`, [hashedPassword, email], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + + // Set forgotPasswordCode to null + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET forgotPasswordCode = NULL WHERE email = ?`, [email], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + + res.send('Password changed'); +}); + +user.post('/register/', async (req, res) => { + const user = { + firstName: req.body['firstName'], + lastName: req.body['lastName'], + userID: req.body['userID'], + password: req.body['password'], + email: req.body['email'] + }; + + if (!user.firstName) { + res.status(400).send('First name is required'); + return; + }; + if (!user.lastName) { + res.status(400).send('Last name is required'); + return; + }; + if (!user.userID) { + res.status(400).send('User ID is required'); + return; + }; + if (!user.password) { + res.status(400).send('Password is required'); + return; + }; + if (!user.email) { + res.status(400).send('Email is required'); + return; + }; + + // Check if user exists in faculties or students + let found = false; + let where = ''; + let identifier = ''; + + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE facultyID = ?', [user.userID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + identifier = 'facultyID'; + }; + resolve(); + }; + }); + }); + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE studentID = ?', [user.userID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'students'; + identifier = 'studentID'; + }; + resolve(); + }; + }); + }); + + if (!found) { + // User does not exist in the database + // Reject registration + res.status(401).send('User ID not found'); + return; + }; + + // Get user + /** @type {import('../utils/docs.js').Faculty | import('../utils/docs.js').Student}*/ + let userDB = null; + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE ${identifier} = ?`, [user.userID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + userDB = results[0]; + resolve(); + }; + }); + }); + console.log(userDB); + + // Check if user is already verified or pending for verification + if (userDB.verified) { + res.status(401).send('User already exists and is verified'); + return; + }; + if (userDB.verificationID) { + res.status(401).send('User already exists and is pending for verification'); + return; + }; + + // Generate verificationID + // userID.0000000000.0000000000 + const verificationID = `${user.userID}.${globals.randomString(10)}.${globals.randomString(10)}`; + + // Hash password + const hashedPassword = await bcrypt.hash(user.password, 10); + + // GenerateToken + const token = `${user.userID}.${globals.randomString(20)}.${globals.randomString(10)}`; + + // Set verificationID and password + await new Promise((resolve, reject) => { + connection.query(`UPDATE ${where} SET verificationID = ?, password = ?, token = ? WHERE ${identifier} = ?`, [verificationID, hashedPassword, token, user.userID], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + + // Send email + const subject = 'Verification'; + const text = `Click this link to verify your account: ${req.protocol}://${req.get('host')}/api/user/verify/${verificationID}`; + await mail(user.email, subject, text); + + res.send('Verification email sent'); +}); + +user.post('/check/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + const where = data.identifier === 'adminID' ? 'admins' : data.identifier === 'facultyID' ? 'faculties' : data.identifier === 'studentID' ? 'students' : null; + if (!where) { + res.status(400).send('Invalid identifier'); + return; + }; + + // Get user + /** @type {import('../utils/docs.js').Admin | import('../utils/docs.js').Faculty | import('../utils/docs.js').Student}*/ + let user = null; + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM ${where} WHERE token = ? AND ${data.identifier} = ?`, [data.token, data[data.identifier]], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + user = results[0]; + resolve(); + }; + }); + }); + + if (!user) { + res.status(401).send('Unauthorized'); + return; + }; + + res.send(user); +}); + +user.post('/schedules/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + // if identifier is studentID, change it to the student's section + if (data.identifier === 'studentID') { + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE studentID = ?', [data.studentID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.studentID = undefined; + data.section = results[0].section; + data.identifier = 'section'; + resolve(); + }; + }); + }); + }; + + // Check if token exists in faculties or students + let found = false; + let where = ''; + let user = null; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + user = results[0]; + }; + resolve(); + }; + }); + }); + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'students'; + user = results[0]; + }; + resolve(); + }; + }); + }); + // if user is not found, reject + if (!found) { + res.status(401).send('Unauthorized'); + return; + }; + + // Get schedules + const schedules = []; + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM schedules WHERE ${data.identifier} = ?`, [data[data.identifier]], async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + // If identifier is section, get faculty name using facultyID + for (const schedule of results) { + if (data.identifier === 'section') { + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [schedule.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + schedule.facultyName = `${results[0].firstName} ${results[0].lastName}`; + resolve(); + }; + }); + }); + }; + if (data.identifier === 'facultyID') { + schedule.section = schedule.courseCode.split('-')[0] === 'IT' ? `BSIT ${schedule.section}` : `BSCpE ${schedule.section}`; + } + schedules.push(schedule); + }; + resolve(); + }; + }); + }); + + // Get facility name using facilityID + for (const schedule of schedules) { + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [schedule.facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + schedule.facilityName = results[0].name; + resolve(); + }; + }); + }); + }; + + res.send(schedules); +}); + +user.post('/identifierHeader/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + // if identifier is studentID, change it to the student's section + if (data.identifier === 'studentID') { + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE studentID = ?', [data.studentID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.studentID = undefined; + data.section = results[0].section; + data.identifier = 'section'; + resolve(); + }; + }); + }); + }; + + // Check if token exists in faculties or students + let found = false; + let where = ''; + let user = null; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + user = results[0]; + }; + resolve(); + }; + }); + }); + if (!found) await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'students'; + user = results[0]; + }; + resolve(); + }; + }); + }); + // if user is not found, reject + if (!found) { + res.status(401).send('Unauthorized'); + return; + }; + + // Get Header for the Identifier + if (data.identifier === 'section') { + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM students WHERE section = ?', [data.section], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + res.send(results[0].program === 'bsit' ? `BSIT ${data.section}` : `BSCpE ${data.section}`); + resolve(); + }; + }); + }); + }; + if (data.identifier === 'facultyID') { + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE facultyID = ?', [data.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + res.send(`${results[0].firstName} ${results[0].lastName}`); + resolve(); + }; + }); + }); + }; +}); + +user.post('/facilities/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + // if identifier is studentID, change it to the student's section + if (data.identifier === 'studentID') { + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE studentID = ?', [data.studentID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.studentID = undefined; + data.section = results[0].section; + data.identifier = 'section'; + resolve(); + }; + }); + }); + }; + + // Check if token exists in faculties + let found = false; + let where = ''; + let user = null; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + user = results[0]; + }; + resolve(); + }; + }); + }); + // if user is not found, reject + if (!found) { + res.status(401).send('Unauthorized'); + return; + }; + + // Get facilities + const facilities = []; + await new Promise((resolve, reject) => { + connection.query(`SELECT * FROM facilities`, async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const facility of results) { + facilities.push(facility); + }; + resolve(); + }; + }); + }); + + res.send(facilities); +}); + +user.post('/request/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + // if identifier is studentID, change it to the student's section + if (data.identifier === 'studentID') { + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE studentID = ?', [data.studentID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.studentID = undefined; + data.section = results[0].section; + data.identifier = 'section'; + resolve(); + }; + }); + }); + }; + + // Check if token exists in faculties + let found = false; + let where = ''; + let user = null; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + user = results[0]; + }; + resolve(); + }; + }); + }); + // if user is not found, reject + if (!found) { + res.status(401).send('Unauthorized'); + return; + }; + + const requestData = { + scheduleID: req.body['scheduleID'], + facultyID: data[data['identifier']], + facilityID: req.body['facilityID'], + day: req.body['day'], + startTime: `${globals.convertTime12to24(req.body['startTime'])}:00`, + endTime: `${globals.convertTime12to24(req.body['endTime'])}:00`, + reason: req.body['reason'], + section: req.body['section'] + }; + + if (!requestData.scheduleID || !requestData.facultyID || !requestData.facilityID || !requestData.day || !requestData.startTime || !requestData.endTime || !requestData.reason) { + res.status(400).send('All fields are required'); + return; + }; + + let schedule = {}; + + // Get schedule + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM schedules WHERE scheduleID = ?', [requestData.scheduleID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + schedule = results[0]; + }; + resolve(); + }; + }); + }); + + if (!schedule) { + res.status(404).send('Schedule not found'); + return; + }; + + if ( + (requestData.facilityID === schedule.facilityID) + & (requestData.day === schedule.day) + & (requestData.startTime === schedule.startTime) + & (requestData.endTime === schedule.endTime) + ) { + res.status(409).send('Schedule unchanged'); + return; + }; + + // Check if schedule is already requested + let requested = false; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM requests WHERE scheduleID = ? AND status = ?', [requestData.scheduleID, 'pending'], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + requested = true; + }; + resolve(); + }; + }); + }); + + if (requested) { + res.status(409).send('Schedule already requested'); + return; + }; + + // Check for conflicts + let conflicts = []; + // Faculty + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facultyID = ? + AND day = ? + AND ( + (startTime BETWEEN ? AND ?) + OR (endTime BETWEEN ? AND ?) + )`, [requestData.facultyID, requestData.day, requestData.startTime, requestData.endTime, requestData.startTime, requestData.endTime], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push(result); + }; + resolve(); + }; + }); + }); + // Facility + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE facilityID = ? + AND day = ? + AND ( + (startTime BETWEEN ? AND ?) + OR (endTime BETWEEN ? AND ?) + )`, [requestData.facilityID, requestData.day, requestData.startTime, requestData.endTime, requestData.startTime, requestData.endTime], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push(result); + }; + resolve(); + }; + }); + }); + // Section + await new Promise((resolve, reject) => { + connection.query(` + SELECT * FROM schedules + WHERE section = ? + AND day = ? + AND ( + (startTime BETWEEN ? AND ?) + OR (endTime BETWEEN ? AND ?) + )`, [requestData.section, requestData.day, requestData.startTime, requestData.endTime, requestData.startTime, requestData.endTime], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const result of results) { + conflicts.push(result); + }; + resolve(); + }; + }); + }); + + // Remove this schedule from conflicts + conflicts = conflicts.filter(conflict => conflict.scheduleID !== requestData.scheduleID); + + if (conflicts.length > 0) { + res.status(409).send('Conflicts found'); + return; + }; + + const request = { + requestID: globals.randomString(10), + facultyID: requestData.facultyID, + original: { + facilityID: schedule.facilityID, + day: schedule.day, + startTime: schedule.startTime, + endTime: schedule.endTime + }, + request: { + facilityID: requestData.facilityID, + day: requestData.day, + startTime: requestData.startTime, + endTime: requestData.endTime + }, + status: 'pending', + requestReason: requestData.reason, + rejectReason: null, + requestDate: new Date().toISOString(), + program: schedule.courseCode.split('-')[0] === 'IT' ? 'bsit' : 'bscpe', + courseCode: schedule.courseCode, + scheduleID: requestData.scheduleID + }; + + // Insert request + await new Promise((resolve, reject) => { + connection.query(` + INSERT INTO requests ( + requestID, + facultyID, + original_facilityID, + original_day, + original_startTime, + original_endTime, + request_facilityID, + request_day, + request_startTime, + request_endTime, + status, + requestReason, + rejectReason, + requestDate, + program, + courseCode, + scheduleID + ) + VALUES (?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?)`, [ + request.requestID, + request.facultyID, + request.original.facilityID, + request.original.day, + request.original.startTime, + request.original.endTime, + request.request.facilityID, + request.request.day, + request.request.startTime, + request.request.endTime, + request.status, + request.requestReason, + request.rejectReason, + request.requestDate, + request.program, + request.courseCode, + request.scheduleID + ], (err, result) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + resolve(); + }; + }); + }); + + res.send('Request sent'); + console.log(globals.rooms); + for (const ws of globals.rooms['notifications_admin']) { + ws.send("refresh"); + }; + for (const ws of globals.rooms['notifications_user']) { + ws.send("refresh"); + }; +}); + +user.post('/requests/', async (req, res) => { + const data = { + token: req.body['token'], + identifier: req.body['identifier'], + [req.body['identifier']]: req.body[req.body['identifier']] + }; + + if (!data.token) { + res.status(400).send('Token is required'); + return; + }; + if (!data.identifier) { + res.status(400).send('Identifier is required'); + return; + }; + + // if identifier is studentID, change it to the student's section + if (data.identifier === 'studentID') { + await new Promise((resolve, reject) => { + connection.query('SELECT section FROM students WHERE studentID = ?', [data.studentID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + data.studentID = undefined; + data.section = results[0].section; + data.identifier = 'section'; + resolve(); + }; + }); + }); + }; + + // Check if token exists in faculties + let found = false; + let where = ''; + let user = null; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM faculties WHERE token = ?', [data.token], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + if (results.length > 0) { + found = true; + where = 'faculties'; + user = results[0]; + }; + resolve(); + }; + }); + }); + // if user is not found, reject + if (!found) { + res.status(401).send('Unauthorized'); + return; + }; + + // Get requests + const requests = []; + await new Promise((resolve, reject) => { + connection.query('SELECT * FROM requests WHERE facultyID = ?', [data[data.identifier]], async (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + for (const request of results) { + requests.push(request); + }; + resolve(); + }; + }); + }); + + const toReturn = []; + + for (const request of requests) { + const requestToReturn = { + requestID: request.requestID, + facultyID: request.facultyID, + facultyName: '', + original: { + facilityID: request.original_facilityID, + facilityName: '', + day: request.original_day, + startTime: request.original_startTime, + endTime: request.original_endTime + }, + request: { + facilityID: request.request_facilityID, + facilityName: '', + day: request.request_day, + startTime: request.request_startTime, + endTime: request.request_endTime + }, + status: request.status, + requestReason: request.requestReason, + rejectReason: request.rejectReason, + requestDate: request.requestDate, + program: request.program, + courseCode: request.courseCode, + scheduleID: request.scheduleID + }; + + // Get faculty name + await new Promise((resolve, reject) => { + connection.query('SELECT firstName, lastName FROM faculties WHERE facultyID = ?', [request.facultyID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + requestToReturn.facultyName = `${results[0].firstName} ${results[0].lastName}`; + resolve(); + }; + }); + }); + + // Get facility name + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [request.original_facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + requestToReturn.original.facilityName = results[0].name; + resolve(); + }; + }); + }); + await new Promise((resolve, reject) => { + connection.query('SELECT name FROM facilities WHERE facilityID = ?', [request.request_facilityID], (err, results) => { + if (err) { + res.status(500).send('Internal Server Error'); + reject(err); + } else { + requestToReturn.request.facilityName = results[0].name; + resolve(); + }; + }); + }); + + toReturn.push(requestToReturn); + }; + + // Sort requests by requestDate + toReturn.sort((a, b) => new Date(b.requestDate) - new Date(a.requestDate)); + + res.send(toReturn); +}); +module.exports = user; \ No newline at end of file diff --git a/backend/main.js b/backend/main.js index bbea289..a43d9be 100644 --- a/backend/main.js +++ b/backend/main.js @@ -1,65 +1,114 @@ -const mysql = require('mysql2'); -const connection = mysql.createConnection({ - host: 'localhost', - user: 'root', - password: '' -}); +const connection = require('./utils/databaseConnection.js'); +const logger = require('./utils/logger.js'); +const globals = require('./utils/globals.js'); -connection.connect((err) => { - if (err) throw err; - console.log('Connected!'); -}); +const express = require('express'); +const WebSocket = require('ws'); -// Check if database exists, if not create it -connection.query('CREATE DATABASE IF NOT EXISTS cdmsms_ics', (err) => { - if (err) throw err; - console.log('Created Database: cdmsms_ics'); -}); +const bodyParser = require('body-parser'); +const app = express(); -// Select the database -connection.query('USE cdmsms_ics', (err) => { - if (err) throw err; - console.log('Selected Database: cdmsms_ics'); +// Allow CORS from all origins +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); + res.header('Access-Control-Allow-Headers', 'Content-Type'); + next(); }); -// Database Structure -// Tables: admins, courses, faculties, facilities, students, schedules -// courses: courseCode: VARCHAR(10), description: TEXT, units: INT -// faculties: facultyID: VARCHAR(10), firstName: VARCHAR(50), lastName: VARCHAR(50), email: VARCHAR(50), password: VARCHAR(50), courses: TEXT, schedules: TEXT -// facilities: facilityID: VARCHAR(10), name: VARCHAR(50), description: TEXT, schedules: TEXT -// students: studentID: VARCHAR(10), firstName: VARCHAR(50), lastName: VARCHAR(50), courses: TEXT, regular: BOOLEAN, email: VARCHAR(50), password: VARCHAR(50), schedules: TEXT -// schedules: scheduleID: VARCHAR(10), courseCode: VARCHAR(10), facultyID: VARCHAR(10), facilityID: VARCHAR(10), day: VARCHAR(10), startTime: TIME, endTime: TIME -connection.query('CREATE TABLE IF NOT EXISTS courses (courseCode VARCHAR(10), description TEXT, units INT)', (err) => { - if (err) throw err; - console.log('\tCreated Table: courses'); -}); -connection.query('CREATE TABLE IF NOT EXISTS faculties (facultyID VARCHAR(10), firstName VARCHAR(50), lastName VARCHAR(50), email VARCHAR(50), password VARCHAR(50), courses TEXT, schedules TEXT)', (err) => { - if (err) throw err; - console.log('\tCreated Table: faculties'); -}); -connection.query('CREATE TABLE IF NOT EXISTS facilities (facilityID VARCHAR(10), name VARCHAR(50), description TEXT, schedules TEXT)', (err) => { - if (err) throw err; - console.log('\tCreated Table: facilities'); +app.use(bodyParser.json({ limit: '50mb' })); +app.use(bodyParser.urlencoded({ extended: true })); + +// Middleware +app.use('*', (req, res, next) => { + logger.log(`${req.method} ${req.originalUrl}`); + next(); }); -connection.query('CREATE TABLE IF NOT EXISTS students (studentID VARCHAR(10), firstName VARCHAR(50), lastName VARCHAR(50), courses TEXT, regular BOOLEAN, email VARCHAR(50), password VARCHAR(50), schedules TEXT)', (err) => { - if (err) throw err; - console.log('\tCreated Table: students'); + +app.get('/', (req, res) => { + res.send('Hello World!'); }); -connection.query('CREATE TABLE IF NOT EXISTS schedules (scheduleID VARCHAR(10), courseCode VARCHAR(10), facultyID VARCHAR(10), facilityID VARCHAR(10), day VARCHAR(10), startTime TIME, endTime TIME)', (err) => { - if (err) throw err; - console.log('\tCreated Table: schedules'); + +// Routers +app.use('/api/setup', require('./api/setup.js')); +app.use('/api/user', require('./api/user.js')); +app.use('/api/admin', require('./api/admin.js')); + +const port = process.env.PORT || 3090; +const server = app.listen(port, () => { + logger.log(`Server running on port ${port}`); }); -const express = require('express'); -const app = express(); +// Websocket +const wss = new WebSocket.Server({ server }); -app.get('/', (req, res) => { - res.send('Hello World!'); + + +wss.on('connection', (ws, req) => { + logger.log('New Connection'); + ws.on('message', (message) => { + const data = JSON.parse(message); + + if (data.type === 'join') { + globals.rooms[data.room].push(ws); + logger.log(`User joined room: ${logger.colors.green(data.room)} with ${globals.rooms[data.room].length} users`); + }; + if (data.type === 'leave') { + globals.rooms[data.room] = globals.rooms[data.room].filter((room) => room !== ws); + logger.log(`User left room: ${logger.colors.red(data.room)} with ${globals.rooms[data.room].length} users`); + }; + }); + + ws.on('close', () => { + for (const room in globals.rooms) { + // if user exists in room + // remove user from room + if (globals.rooms[room].includes(ws)) { + globals.rooms[room] = globals.rooms[room].filter((room) => room !== ws); + logger.log(`User left room: ${logger.colors.red(room)}`); + }; + }; + }); }); -const port = process.env.PORT || 3090; -app.listen(port, () => { - console.log(`Server started on http://localhost:${port}`); + + + +process.stdin.on('data', (data) => { + const input = data.toString(); + const args = input.trim().split(/ +/g); + const commandName = args.shift().toLowerCase(); + switch (commandName) { + case 'reset': + connection.query('DROP DATABASE IF EXISTS cdmsms_ics', (err, results, fields) => { + if (err) { + logger.error(err); + return; + }; + logger.log(`Dropped Database: ${logger.colors.red('cdmsms_ics')}`); + process.exit(0); + }); + break; + case 'eval': + // Get the content inside quotes + const quote = input.match(/"(.*?)"/g); + if (!quote) { + logger.error('No quote found'); + return; + }; + const code = quote[0].replace(/"/g, ''); + eval(code); + break; + case 'ping': + logger.log('pong'); + for (const connection of globals.connections) { + connection.ws.send('ping'); + }; + break; + case 'exit': + process.exit(0); + break; + }; }); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index b72f0d7..70bbc45 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,10 +9,57 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@types/websocket": "^1.0.10", + "bcrypt": "^5.1.1", + "body-parser": "^1.20.2", + "csv-parser": "^3.0.0", + "dotenv": "^16.4.5", "express": "^4.19.2", - "mysql2": "^3.9.7" + "mysql2": "^3.9.7", + "nodemailer": "^6.9.13", + "ws": "^8.17.1" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/websocket": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz", + "integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -25,11 +72,86 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -64,6 +186,15 @@ "node": ">=0.10.0" } }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -90,6 +221,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -122,6 +279,20 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/csv-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", + "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -146,6 +317,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -171,11 +347,35 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -290,6 +490,33 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -298,6 +525,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -324,6 +570,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -368,6 +633,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -394,6 +664,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -405,6 +708,15 @@ "node": ">=0.10.0" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -418,6 +730,14 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -436,6 +756,28 @@ "node": ">=16.14" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -487,6 +829,67 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -537,6 +940,71 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -556,6 +1024,14 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -564,6 +1040,14 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -628,6 +1112,33 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -652,6 +1163,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -699,6 +1221,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -737,6 +1264,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -753,6 +1285,54 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -761,6 +1341,11 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -773,6 +1358,11 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -781,6 +1371,11 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -796,6 +1391,58 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/backend/package.json b/backend/package.json index 1016d64..f186f54 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,14 @@ "author": "", "license": "ISC", "dependencies": { + "@types/websocket": "^1.0.10", + "bcrypt": "^5.1.1", + "body-parser": "^1.20.2", + "csv-parser": "^3.0.0", + "dotenv": "^16.4.5", "express": "^4.19.2", - "mysql2": "^3.9.7" + "mysql2": "^3.9.7", + "nodemailer": "^6.9.13", + "ws": "^8.17.1" } -} \ No newline at end of file +} diff --git a/backend/utils/databaseConnection.js b/backend/utils/databaseConnection.js new file mode 100644 index 0000000..87ec72c --- /dev/null +++ b/backend/utils/databaseConnection.js @@ -0,0 +1,158 @@ + +const logger = require('./logger.js'); + +const mysql = require('mysql2'); + +const connection = mysql.createConnection({ + host: 'localhost', + user: 'root', + password: '' +}); + +connection.connect((err) => { + if (err) throw err; + logger.log('Connected to MySQL Server'); +}); + +// Check if database exists, if not create it +connection.query('CREATE DATABASE IF NOT EXISTS cdmsms_ics', (err) => { + if (err) throw err; +}); + +// Select the database +connection.query('USE cdmsms_ics', (err) => { + if (err) throw err; + logger.log(`Database: ${logger.colors.blue('cdmsms_ics')}`); +}); + +// Database Structure +// Tables: admins, courses, faculties, facilities, students, schedules +// admins: adminID: VARCHAR(10), firstName: VARCHAR(50), lastName: VARCHAR(50), email: VARCHAR(50), password: VARCHAR(50), role: VARCHAR(10), verificationID: VARCHAR(50), verified: BOOLEAN, forgotPasswordCode: VARCHAR(50), token: VARCHAR(50) +// primary key: adminID +// courses: courseCode: VARCHAR(20), description: TEXT, units: INT, yearLevel: INT +// primary key: courseCode +// faculties: facultyID: VARCHAR(10), firstName: VARCHAR(50), lastName: VARCHAR(50), email: VARCHAR(50), password: VARCHAR(50), courses: TEXT, schedules: TEXT, programs: TEXT, verificationID: VARCHAR(50), verified: BOOLEAN, forgotPasswordCode: VARCHAR(50), token: VARCHAR(50) +// primary key: facultyID +// facilities: facilityID: VARCHAR(10), name: VARCHAR(50), description: TEXT, schedules: TEXT +// primary key: facilityID +// students: studentID: VARCHAR(10), firstName: VARCHAR(50), lastName: VARCHAR(50), courses: TEXT, email: VARCHAR(50), password: VARCHAR(50), program: VARCHAR(10), schedules: TEXT, section: VARCHAR(10), verificationID: VARCHAR(50), verified: BOOLEAN, forgotPasswordCode: VARCHAR(50), token: VARCHAR(50) +// primary key: studentID +// schedules: scheduleID: VARCHAR(10), courseCode: VARCHAR(10), facultyID: VARCHAR(10), facilityID: VARCHAR(10), day: VARCHAR(10), startTime: TIME, endTime: TIME, section: VARCHAR(10) +// primary key: scheduleID +// requests: requestID: VARCHAR(10), facultyID: VARCHAR(10), original_facilityID VARCHAR(10), original_day VARCHAR(10), original_startTime TIME, original_endTime TIME, request_facilityID VARCHAR(10), request_day VARCHAR(10), request_startTime TIME, request_endTime TIME, status VARCHAR(10), requestReason TEXT, rejectReason TEXT, requestDate DATETIME, program VARCHAR(10), courseCode VARCHAR(10), scheduleID VARCHAR(10) + +connection.query(` + CREATE TABLE IF NOT EXISTS admins ( + adminID VARCHAR(10) PRIMARY KEY NOT NULL, + firstName VARCHAR(50), + lastName VARCHAR(50), + email VARCHAR(50), + password VARCHAR(200), + role VARCHAR(10), + verificationID VARCHAR(50), + verified BOOLEAN, + forgotPasswordCode VARCHAR(50), + token VARCHAR(50) + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('admins')}`); +}); +connection.query(` + CREATE TABLE IF NOT EXISTS courses ( + courseCode VARCHAR(20) PRIMARY KEY NOT NULL, + description TEXT, + yearLevel INT, + units INT + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('courses')}`); +}); +connection.query(` + CREATE TABLE IF NOT EXISTS faculties ( + facultyID VARCHAR(10) PRIMARY KEY NOT NULL, + firstName VARCHAR(50), + lastName VARCHAR(50), + email VARCHAR(50), + password VARCHAR(200), + schedules TEXT, + programs TEXT, + verificationID VARCHAR(50), + verified BOOLEAN, + forgotPasswordCode VARCHAR(50), + token VARCHAR(50) + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('faculties')}`); +}); +connection.query(` + CREATE TABLE IF NOT EXISTS facilities ( + facilityID VARCHAR(10) PRIMARY KEY NOT NULL, + name VARCHAR(50), + description TEXT, + schedules TEXT + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('facilities')}`); +}); +connection.query(` + CREATE TABLE IF NOT EXISTS students ( + studentID VARCHAR(10) PRIMARY KEY NOT NULL, + firstName VARCHAR(50), + lastName VARCHAR(50), + email VARCHAR(50), + password VARCHAR(200), + program VARCHAR(10), + schedules TEXT, + section VARCHAR(10), + verificationID VARCHAR(50), + verified BOOLEAN, + forgotPasswordCode VARCHAR(50), + token VARCHAR(50) + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('students')}`); +}); + +connection.query(` + CREATE TABLE IF NOT EXISTS schedules ( + scheduleID VARCHAR(10) PRIMARY KEY NOT NULL, + courseCode VARCHAR(10) NOT NULL, + facultyID VARCHAR(10) NOT NULL, + facilityID VARCHAR(10) NOT NULL, + day VARCHAR(10), + startTime TIME, + endTime TIME, + section VARCHAR(10) + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('schedules')}`); +}); + +connection.query(` + CREATE TABLE IF NOT EXISTS requests ( + requestID VARCHAR(10) PRIMARY KEY NOT NULL, + facultyID VARCHAR(10) NOT NULL, + + original_facilityID VARCHAR(10) NOT NULL, + original_day VARCHAR(10), + original_startTime TIME, + original_endTime TIME, + + request_facilityID VARCHAR(10) NOT NULL, + request_day VARCHAR(10), + request_startTime TIME, + request_endTime TIME, + + status VARCHAR(10), + requestReason TEXT, + rejectReason TEXT, + requestDate DATETIME, + program VARCHAR(10), + courseCode VARCHAR(10), + scheduleID VARCHAR(10) + )`, (err) => { + if (err) throw err; + logger.log(`\tTable: ${logger.colors.green('requests')}`); +}); + +module.exports = connection; \ No newline at end of file diff --git a/backend/utils/docs.js b/backend/utils/docs.js new file mode 100644 index 0000000..0b3e520 --- /dev/null +++ b/backend/utils/docs.js @@ -0,0 +1,93 @@ + +/** + * @typedef {{ + * adminID: String, + * firstName: String, + * lastName: String, + * email: String, + * password: String, + * role: String, + * verificationID: String, + * verified: Boolean, + * forgotPasswordCode: String, + * token: String + * }} Admin + */ + +/** + * @typedef {{ + * courseCode: String, + * description: String, + * yearLevel: Number, + * units: Number + * }} Course + */ + +/** + * @typedef {{ + * facultyID: String, + * firstName: String, + * lastName: String, + * email: String, + * password: String, + * schedules: String, + * programs: String, + * verificationID: String, + * verified: Boolean, + * forgotPasswordCode: String, + * token: String + * }} Faculty + */ + +/** + * @typedef {{ + * facilityID: String, + * name: String, + * description: String, + * schedules: String + * }} Facility + */ + +/** + * @typedef {{ + * studentID: String, + * firstName: String, + * lastName: String, + * email: String, + * password: String, + * program: String, + * schedules: String, + * section: String, + * verificationID: String, + * verified: Boolean, + * forgotPasswordCode: String, + * token: String + * }} Student + */ + +/** + * @typedef {{ + * scheduleID: String, + * courseCode: String, + * facultyID: String, + * facilityID: String, + * day: 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday', + * startTime: String, + * endTime: String, + * section: String + * }} Schedule + */ + +/** + * @typedef {{ + * requestID: String, + * facultyID: String, + * facilityID: String, + * day: 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday', + * startTime: String, + * endTime: String, + * status: String + * }} Request + */ + +module.exports = {}; \ No newline at end of file diff --git a/backend/utils/globals.js b/backend/utils/globals.js new file mode 100644 index 0000000..e75dced --- /dev/null +++ b/backend/utils/globals.js @@ -0,0 +1,54 @@ + +/** + * @type {(length: Number) => String} + */ +const randomString = (length) => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + }; + return result; +}; +/** + * @type {(length: String | Null) => String} + */ +const randomID = (length = 10) => { + let result = ''; + while (result.length < length) { + let char = randomString(1); + if (char.match(/[0-9]/)) { + result += char; + }; + }; + return result; +}; + +/** + * @type {(String) => String} + */ +const convertTime12to24 = (time12h) => { + const [time, modifier] = time12h.split(' '); + let [hours, minutes] = time.split(':'); + if (hours === '12') { + hours = '00'; + }; + if (modifier === 'PM') { + hours = parseInt(hours, 10) + 12; + }; + return `${hours}:${minutes}`; +}; + +const rooms = { + 'calendar_admin': [], + 'notifications_admin': [], + 'calendar_user': [], + 'notifications_user': [] +}; + +module.exports = { + randomString, + randomID, + convertTime12to24, + rooms +}; \ No newline at end of file diff --git a/backend/utils/logger.js b/backend/utils/logger.js new file mode 100644 index 0000000..bacb017 --- /dev/null +++ b/backend/utils/logger.js @@ -0,0 +1,77 @@ + +const fs = require('fs'); + +let index = 0; + +const logger = { + colors: { + /** @type { (str: String) => String } */ + red(str) { return `\x1b[31m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + green(str) { return `\x1b[32m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + yellow(str) { return `\x1b[33m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + blue(str) { return `\x1b[34m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + magenta(str) { return `\x1b[35m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + cyan(str) { return `\x1b[36m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + white(str) { return `\x1b[37m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + gray(str) { return `\x1b[90m${str}\x1b[0m` }, + + /** @type { (str: String) => String } */ + black(str) { return `\x1b[30m${str}\x1b[0m` } + }, + /** @type { (withColor: Boolean) => String } */ + getTimestamp(withColor) { + const time = new Date().toLocaleTimeString('en-US', { + timeZone: 'Asia/Manila', + hour: 'numeric', + hour12: true, + minute: 'numeric', + second: 'numeric' + }); + return withColor ? this.colors.magenta(`${time} PHT`) : `${time} PHT`; + }, + + /** @type { (...strs: Any) => void } */ + log(...strs) { + const timestamp = this.getTimestamp(true); + console.log(`[${this.colors.green(index)}] ${timestamp}`, ...strs); + + index = index + 1; + }, + + /** @type { (...strs: Any) => void } */ + warn(...strs) { + const timestamp = this.getTimestamp(true); + console.warn(`[${this.colors.yellow(index)}] ${timestamp}`, ...strs); + + index = index + 1; + }, + + /** @type { (...strs: Any) => void } */ + error(...strs) { + const timestamp = this.getTimestamp(true); + console.error(`[${this.colors.red(index)}] ${timestamp}`, ...strs); + + index = index + 1; + }, + + /** @type { (...strs: Any) => void } */ + write(...strs) { + console.log(...strs); + } +}; + +module.exports = logger; \ No newline at end of file diff --git a/backend/utils/mail.js b/backend/utils/mail.js new file mode 100644 index 0000000..e2902b0 --- /dev/null +++ b/backend/utils/mail.js @@ -0,0 +1,38 @@ +require('dotenv').config(); + +const nodemailer = require('nodemailer'); + +const config = { + host: 'smtp.gmail.com', + port: 465, + secure: true, + auth: { + user: process.env['GMAIL_ADDRESS'], + pass: process.env['GMAIL_PASSWORD'] + } +}; + +const transporter = nodemailer.createTransport(config); + +/** + * @type { ( to: String, subject: String, text: String, html: String ) => Promise} + */ +const send = (to, subject, text, html) => { + return new Promise((resolve, reject) => { + const mailOptions = { + from: process.env['GMAIL_ADDRESS'], + to, + subject, + text, + html + }; + + transporter.sendMail(mailOptions, (err, info) => { + if (err) { + reject(err); + }; + resolve(info); + }); + }); +}; +module.exports = send; \ No newline at end of file diff --git a/csv/BSCpE Courses.csv b/csv/BSCpE Courses.csv new file mode 100644 index 0000000..1f90459 --- /dev/null +++ b/csv/BSCpE Courses.csv @@ -0,0 +1,25 @@ +"courseCode","description","units","yearLevel" +"CPE-TCCALC2","Calculus 2 (Integral Calculus)","3","1" +"CPE-TCEPHY1","Physics for Engineers","4","1" +"CPE-PRO2","Object Oriented Programming","2","1" +"CPE-TCEDATA","Engineering Data Analysis","3","1" +"CPE-MATH","Discrete Mathematics","3","1" +"CPE-HISTORY","Reading in Philippine History","3","1" +"CPE-GENDSOC","Gender and Society","3","1" +"CPE-PATFIT2","Physical Education 2","2","1" +"CPE-NSTP-02","National Service Training Program 2","3","1" +"CPE-NUME","Numerical Methods","3","2" +"CPE-SOFD","Software Design","4","2" +"CPE-PURPCOM","Purposive Communication","3","2" +"CPE-TCTRONC","Fundamentals of Electronic Circuits","4","2" +"CPE-JOSERIZ","Life Works & Writings of Dr. Jose Rizal","3","2" +"CPE-CONWRLD","The Contemporary World","3","2" +"CPE-PATFIT4","Physical Education 4","2","2" +"CPE-BOHS","Basic Occupational Health and Safety","3","3" +"CPE-CANAS","Computer and Networks and Security","4","3" +"CPE-MICRO","Microprocessor","4","3" +"CPE-MOFR","Methods of Research","2","3" +"CPE-IHDL","Introduction to HDL","1","3" +"CPE-ETHICS1","Ethics","3","3" +"CPE-LAWP","CpE- Laws and Professional Practice","2","3" +"CPE-SAES","COG Elective 2(Stand-Alone Embedded System)","3","3" \ No newline at end of file diff --git a/csv/BSCpE Faculty.csv b/csv/BSCpE Faculty.csv new file mode 100644 index 0000000..ec05f3b --- /dev/null +++ b/csv/BSCpE Faculty.csv @@ -0,0 +1,31 @@ +"facultyId","firstName","lastName","email" +"64-02303","Lesa","Watson","lesa.watson@example.com" +"83-05722","Jenny","Andrews","jenny.andrews@example.com" +"28-44834","Madison","Perez","madison.perez@example.com" +"42-72402","Jennifer","Curtis","jennifer.curtis@example.com" +"00-53205","Clara","Hanson","clara.hanson@example.com" +"47-32762","Lois","Simpson","lois.simpson@example.com" +"57-30014","Mattie","Graves","mattie.graves@example.com" +"01-44310","Cherly","Mitchelle","cherly.mitchelle@example.com" +"26-42645","Lillian","Watkins","lillian.watkins@example.com" +"40-35724","Nina","Cole","nina.cole@example.com" +"28-76531","Fernando","Foster","fernando.foster@example.com" +"55-30243","Earl","Gregory","earl.gregory@example.com" +"45-62142","Jeffrey","Sutton","jeffrey.sutton@example.com" +"48-66567","Darlene","Lee","darlene.lee@example.com" +"85-37548","Elsie","Little","elsie.little@example.com" +"46-50148","Steve","Steward","steve.steward@example.com" +"14-64201","Gertrude","Carlson","gertrude.carlson@example.com" +"86-70048","Carlos","West","carlos.west@example.com" +"60-22874","Edgar","Robertson","edgar.robertson@example.com" +"18-76538","Emily","Woods","emily.woods@example.com" +"47-37806","Jacob","Reid","jacob.reid@example.com" +"71-84411","Edna","Alvarez","edna.alvarez@example.com" +"02-82875","Judith","Wilson","judith.wilson@example.com" +"13-47658","Wayne","Howard","wayne.howard@example.com" +"60-60585","Aubree","Walker","aubree.walker@example.com" +"00-36625","Sara","Allen","sara.allen@example.com" +"07-43674","Ivan","Parker","ivan.parker@example.com" +"43-53746","Martin","Jacobs","martin.jacobs@example.com" +"15-08415","Melanie","Mitchelle","melanie.mitchelle@example.com" +"65-60057","Andrea","Sims","andrea.sims@example.com" diff --git a/csv/BSCpE Students.csv b/csv/BSCpE Students.csv new file mode 100644 index 0000000..1016626 --- /dev/null +++ b/csv/BSCpE Students.csv @@ -0,0 +1,1001 @@ +studentID,firstName,lastName,email,section +13-58616,Fire,Dantsm,firedantsm@gmail.com,1a +55-75224,Liam,Pierce,liam.pierce@example.com,1a +34-31365,Jackson,Duncan,jackson.duncan@example.com,1a +45-53734,Earl,Lawson,earl.lawson@example.com,1a +21-60112,Gary,Russell,gary.russell@example.com,1a +17-67654,Marsha,Alvarez,marsha.alvarez@example.com,1a +34-72734,Anne,Jones,anne.jones@example.com,1a +25-17506,Stacey,Fletcher,stacey.fletcher@example.com,1a +26-63667,Samantha,King,samantha.king@example.com,1a +10-73576,Elaine,Curtis,elaine.curtis@example.com,1a +13-76353,Todd,Long,todd.long@example.com,1a +40-65006,Mathew,Gibson,mathew.gibson@example.com,1a +43-23832,Arron,Stone,arron.stone@example.com,1a +34-53787,Jessica,Lawson,jessica.lawson@example.com,1a +78-54106,Tanya,Simmmons,tanya.simmmons@example.com,1a +32-61273,Paula,Simmmons,paula.simmmons@example.com,1a +33-43687,Lorraine,Miller,lorraine.miller@example.com,1a +00-31886,Marcia,Morales,marcia.morales@example.com,1a +36-82367,Aubrey,Washington,aubrey.washington@example.com,1a +73-72647,Constance,Scott,constance.scott@example.com,1a +44-62154,Byron,Russell,byron.russell@example.com,1a +27-47535,Louise,Jones,louise.jones@example.com,1a +88-62374,Mia,Foster,mia.foster@example.com,1a +52-27015,Karl,Duncan,karl.duncan@example.com,1a +47-24050,Kathy,Herrera,kathy.herrera@example.com,1a +22-24322,Jean,Snyder,jean.snyder@example.com,1a +38-67374,Sherri,Sanders,sherri.sanders@example.com,1a +66-78600,Lewis,Beck,lewis.beck@example.com,1a +26-85124,Gavin,Warren,gavin.warren@example.com,1a +50-38421,Wanda,Lucas,wanda.lucas@example.com,1a +08-23302,Dennis,Stephens,dennis.stephens@example.com,1a +06-02243,John,Gomez,john.gomez@example.com,1a +50-68065,Katrina,Campbell,katrina.campbell@example.com,1a +10-62172,Tracey,Ramos,tracey.ramos@example.com,1a +84-52643,Jeffery,Patterson,jeffery.patterson@example.com,1a +82-23725,Peggy,Rodriquez,peggy.rodriquez@example.com,1a +13-23553,Bryan,Sanders,bryan.sanders@example.com,1a +05-52835,Jimmy,Thomas,jimmy.thomas@example.com,1a +44-42734,Kyle,Steeves ,kyle.steeves@example.com,1a +78-33530,Sharlene,Freeman,sharlene.freeman@example.com,1a +74-67505,Andrea,Matthews,andrea.matthews@example.com,1a +70-07741,Paula,Martin,paula.martin@example.com,1a +50-25281,Glen,Perkins,glen.perkins@example.com,1a +24-12731,Hugh,Mills,hugh.mills@example.com,1a +70-43773,Reginald,Silva,reginald.silva@example.com,1a +61-24236,Corey,Ryan,corey.ryan@example.com,1a +13-53238,Isabella,Smith,isabella.smith@example.com,1a +55-77162,Brittany,Hoffman,brittany.hoffman@example.com,1a +45-51734,Herbert,Douglas,herbert.douglas@example.com,1a +36-75836,Caroline,May,caroline.may@example.com,1a +42-48758,Jackie,Martinez,jackie.martinez@example.com,1b +47-01226,Anne,Campbell,anne.campbell@example.com,1b +36-61636,Daryl,Garrett,daryl.garrett@example.com,1b +55-81808,Seth,Dean,seth.dean@example.com,1b +50-27648,Jerome,Lowe,jerome.lowe@example.com,1b +16-16138,Sophie,Patterson,sophie.patterson@example.com,1b +80-68808,Eli,Prescott,eli.prescott@example.com,1b +21-18010,Aaron,Spencer,aaron.spencer@example.com,1b +75-77661,Beverly,Hanson,beverly.hanson@example.com,1b +37-17300,Brandie,Watts,brandie.watts@example.com,1b +80-07240,Carla,Lawrence,carla.lawrence@example.com,1b +27-38762,Willie,Flores,willie.flores@example.com,1b +08-35200,Brian,Hill,brian.hill@example.com,1b +33-63340,Aaron,Griffin,aaron.griffin@example.com,1b +71-33482,Jacqueline,Williams,jacqueline.williams@example.com,1b +73-20512,Hailey,Medina,hailey.medina@example.com,1b +46-35838,Lance,Taylor,lance.taylor@example.com,1b +42-31383,Marsha,Steward,marsha.steward@example.com,1b +82-68406,Mike,Fernandez,mike.fernandez@example.com,1b +56-41674,Ella,Alvarez,ella.alvarez@example.com,1b +74-82057,Alex,Morgan,alex.morgan@example.com,1b +37-38051,Karl,Lopez,karl.lopez@example.com,1b +35-57577,Joan,Little,joan.little@example.com,1b +00-27782,Sandra,Moreno,sandra.moreno@example.com,1b +77-47841,Eric,Caldwell,eric.caldwell@example.com,1b +70-40266,Amy,Cole,amy.cole@example.com,1b +32-00043,Noah,Chapman,noah.chapman@example.com,1b +30-15307,Tracy,Perkins,tracy.perkins@example.com,1b +24-64311,Cindy,Elliott,cindy.elliott@example.com,1b +63-51674,Dwight,Frazier,dwight.frazier@example.com,1b +08-56054,Milton,Stanley,milton.stanley@example.com,1b +78-06323,Chad,Wagner,chad.wagner@example.com,1b +67-46613,Lorraine,Gonzalez,lorraine.gonzalez@example.com,1b +31-60518,Melanie,Lane,melanie.lane@example.com,1b +74-55837,Melinda,Perez,melinda.perez@example.com,1b +25-30436,Milton,Brown,milton.brown@example.com,1b +55-47581,Kristina,Elliott,kristina.elliott@example.com,1b +75-51222,Lynn,Griffin,lynn.griffin@example.com,1b +53-44705,Billy,Bell,billy.bell@example.com,1b +08-03653,Abigail,Holt,abigail.holt@example.com,1b +41-67384,Jamie,Carlson,jamie.carlson@example.com,1b +34-31686,Terrence,Garrett,terrence.garrett@example.com,1b +13-06740,Rick,Olson,rick.olson@example.com,1b +20-30330,Melissa,Nelson,melissa.nelson@example.com,1b +54-44748,Beverly,Chapman,beverly.chapman@example.com,1b +08-88757,Rita,Hall,rita.hall@example.com,1b +52-15377,Joe,Walker,joe.walker@example.com,1b +20-84403,Monica,Kuhn,monica.kuhn@example.com,1b +05-61730,Ricardo,Jenkins,ricardo.jenkins@example.com,1b +15-01754,Darryl,Murphy,darryl.murphy@example.com,1b +64-67004,Jessie,Barnett,jessie.barnett@example.com,1c +64-51721,Alan,Watts,alan.watts@example.com,1c +80-22561,Terri,Bowman,terri.bowman@example.com,1c +13-11653,Linda,Garza,linda.garza@example.com,1c +42-51603,Vanessa,Stone,vanessa.stone@example.com,1c +54-77327,Harry,Ward,harry.ward@example.com,1c +46-68601,Ronnie,Fowler,ronnie.fowler@example.com,1c +80-22528,Anne,Richards,anne.richards@example.com,1c +22-37152,Randall,Mitchell,randall.mitchell@example.com,1c +47-83747,Harry,Vasquez,harry.vasquez@example.com,1c +01-52753,Stacy,Carroll,stacy.carroll@example.com,1c +30-05847,Tomothy,Ward,tomothy.ward@example.com,1c +01-46724,Nathan,Brooks,nathan.brooks@example.com,1c +57-34260,Maurice,Washington,maurice.washington@example.com,1c +40-72783,Melissa,Austin,melissa.austin@example.com,1c +70-14057,Herbert,Jones,herbert.jones@example.com,1c +01-58744,Brianna,Mcdonalid,brianna.mcdonalid@example.com,1c +71-05581,Fernando,Soto,fernando.soto@example.com,1c +41-14577,Dwayne,Shaw,dwayne.shaw@example.com,1c +00-81217,Antonio,Gilbert,antonio.gilbert@example.com,1c +31-18265,Dustin,Wagner,dustin.wagner@example.com,1c +16-47402,Alexa,Ellis,alexa.ellis@example.com,1c +80-52527,Scarlett,Watson,scarlett.watson@example.com,1c +78-47008,Stanley,Ward,stanley.ward@example.com,1c +81-50030,Kristina,Rivera,kristina.rivera@example.com,1c +84-44300,Brandy,Bell,brandy.bell@example.com,1c +15-36808,Bryan,Morales,bryan.morales@example.com,1c +42-78380,Heidi,Cooper,heidi.cooper@example.com,1c +21-00646,Tracy,Gibson,tracy.gibson@example.com,1c +27-66781,Lynn,Elliott,lynn.elliott@example.com,1c +86-64253,Gavin,Peters,gavin.peters@example.com,1c +40-55647,Samuel,Peters,samuel.peters@example.com,1c +12-08086,Lucille,Stephens,lucille.stephens@example.com,1c +08-18746,Gilbert,Stevens,gilbert.stevens@example.com,1c +47-66471,Josephine,Lane,josephine.lane@example.com,1c +46-33343,Madison,Campbell,madison.campbell@example.com,1c +24-35232,Ella,Sutton,ella.sutton@example.com,1c +52-42628,Vincent,Jacobs,vincent.jacobs@example.com,1c +55-86133,Kurt,Harper,kurt.harper@example.com,1c +45-28845,Andy,Elliott,andy.elliott@example.com,1c +48-25768,Gabriel,Harvey,gabriel.harvey@example.com,1c +81-83728,Kenzi,Steeves ,kenzi.steeves@example.com,1c +24-11335,Judd,Pierce,judd.pierce@example.com,1c +46-80247,Cecil,Freeman,cecil.freeman@example.com,1c +17-15001,Camila,Hunt,camila.hunt@example.com,1c +22-33876,Elaine,Wilson,elaine.wilson@example.com,1c +51-07750,Adam,Carroll,adam.carroll@example.com,1c +50-18134,Bernard,Meyer,bernard.meyer@example.com,1c +58-64245,Edgar,Ramirez,edgar.ramirez@example.com,1c +37-40876,Ray,Byrd,ray.byrd@example.com,1c +33-65205,Calvin,Curtis,calvin.curtis@example.com,1d +27-86887,Tonya,Douglas,tonya.douglas@example.com,1d +84-61650,Edna,Fowler,edna.fowler@example.com,1d +27-44826,Leslie,Stewart,leslie.stewart@example.com,1d +01-38573,Derek,Torres,derek.torres@example.com,1d +46-18277,Esther,Williamson,esther.williamson@example.com,1d +43-33475,Landon,Byrd,landon.byrd@example.com,1d +20-74123,Douglas,Dean,douglas.dean@example.com,1d +72-75060,Christine,Lambert,christine.lambert@example.com,1d +76-67858,Sonia,Wagner,sonia.wagner@example.com,1d +04-64707,Mary,Beck,mary.beck@example.com,1d +10-07276,Dave,Webb,dave.webb@example.com,1d +10-83687,Claire,Harvey,claire.harvey@example.com,1d +62-78614,Vicki,Gonzalez,vicki.gonzalez@example.com,1d +02-35662,Louis,Harvey,louis.harvey@example.com,1d +64-00328,Lauren,Duncan,lauren.duncan@example.com,1d +44-87784,Carter,Rhodes,carter.rhodes@example.com,1d +65-84852,Marion,Horton,marion.horton@example.com,1d +61-83117,Janet,Pena,janet.pena@example.com,1d +63-53206,April,Byrd,april.byrd@example.com,1d +25-13241,Mason,Simmons,mason.simmons@example.com,1d +21-35615,Keith,Ward,keith.ward@example.com,1d +74-80513,Edwin,Lane,edwin.lane@example.com,1d +37-58176,Connor,Montgomery,connor.montgomery@example.com,1d +16-45424,Charlotte,Woods,charlotte.woods@example.com,1d +83-07258,Ken,Cole,ken.cole@example.com,1d +32-07312,Tonya,Gomez,tonya.gomez@example.com,1d +53-27865,Tara,Morales,tara.morales@example.com,1d +25-32866,Philip,Gonzales,philip.gonzales@example.com,1d +36-74138,Jerry,Hopkins,jerry.hopkins@example.com,1d +12-01571,Lena,Lawrence,lena.lawrence@example.com,1d +28-75812,Vanessa,Vargas,vanessa.vargas@example.com,1d +60-00363,Manuel,Duncan,manuel.duncan@example.com,1d +28-26748,Wilma,Herrera,wilma.herrera@example.com,1d +21-10274,Tyler,Lane,tyler.lane@example.com,1d +11-87866,Sylvia,Stanley,sylvia.stanley@example.com,1d +46-27377,Jon,Foster,jon.foster@example.com,1d +85-42074,Micheal,Terry,micheal.terry@example.com,1d +23-30284,Pamela,Kuhn,pamela.kuhn@example.com,1d +72-64865,Sergio,Wallace,sergio.wallace@example.com,1d +85-58118,Derek,Austin,derek.austin@example.com,1d +86-45465,Andrew,Craig,andrew.craig@example.com,1d +06-14200,Delores,Arnold,delores.arnold@example.com,1d +40-73242,Matthew,Rice,matthew.rice@example.com,1d +50-75234,Freddie,Wheeler,freddie.wheeler@example.com,1d +06-00844,Letitia,Reyes,letitia.reyes@example.com,1d +36-57251,Ramona,Brooks,ramona.brooks@example.com,1d +35-20434,Alyssa,Alexander,alyssa.alexander@example.com,1d +52-25586,Alyssa,Warren,alyssa.warren@example.com,1d +24-47423,Christina,Pierce,christina.pierce@example.com,1d +50-71845,Zachary,Jones,zachary.jones@example.com,1e +65-56223,Ann,Collins,ann.collins@example.com,1e +02-47525,Darlene,Hansen,darlene.hansen@example.com,1e +62-85820,Frances,Elliott,frances.elliott@example.com,1e +71-85204,Sebastian,Green,sebastian.green@example.com,1e +64-62160,Avery,Ramos,avery.ramos@example.com,1e +01-18244,Tracy,Ruiz,tracy.ruiz@example.com,1e +27-88671,Virgil,Medina,virgil.medina@example.com,1e +82-50325,Jeanne,Flores,jeanne.flores@example.com,1e +10-31785,Roy,Duncan,roy.duncan@example.com,1e +48-43588,Clarence,Griffin,clarence.griffin@example.com,1e +64-46370,Chloe,Carroll,chloe.carroll@example.com,1e +00-38386,Ronnie,Barrett,ronnie.barrett@example.com,1e +26-58287,Kristin,Fowler,kristin.fowler@example.com,1e +66-68474,Juanita,Lawrence,juanita.lawrence@example.com,1e +70-60737,Alberto,Graham,alberto.graham@example.com,1e +76-32730,Raul,Harris,raul.harris@example.com,1e +67-72477,Landon,Williams,landon.williams@example.com,1e +67-56776,Emma,Watkins,emma.watkins@example.com,1e +48-57078,Sue,Mckinney,sue.mckinney@example.com,1e +83-47110,Dennis,Lowe,dennis.lowe@example.com,1e +26-78060,Jacob,Alexander,jacob.alexander@example.com,1e +03-71680,Bill,Barrett,bill.barrett@example.com,1e +05-45517,Johnni,Freeman,johnni.freeman@example.com,1e +55-88346,Pearl,Lee,pearl.lee@example.com,1e +83-87078,Bessie,West,bessie.west@example.com,1e +55-73447,Carmen,Simmmons,carmen.simmmons@example.com,1e +76-15668,Harry,Adams,harry.adams@example.com,1e +21-62713,Joel,Jimenez,joel.jimenez@example.com,1e +78-20706,Alma,Hall,alma.hall@example.com,1e +44-12456,Terry,Graves,terry.graves@example.com,1e +48-85782,Charlene,Lawson,charlene.lawson@example.com,1e +74-40080,Floyd,Hunt,floyd.hunt@example.com,1e +54-48075,Seth,Wright,seth.wright@example.com,1e +40-32508,Victoria,Mcdonalid,victoria.mcdonalid@example.com,1e +27-28334,Neil,Craig,neil.craig@example.com,1e +77-40483,Ava,Ryan,ava.ryan@example.com,1e +45-76047,Ricky,Ruiz,ricky.ruiz@example.com,1e +08-78851,Jacqueline,Thompson,jacqueline.thompson@example.com,1e +38-07321,Rita,Evans,rita.evans@example.com,1e +31-61773,Kaylee,Butler,kaylee.butler@example.com,1e +84-55631,Willie,Bishop,willie.bishop@example.com,1e +80-28313,Eric,Thomas,eric.thomas@example.com,1e +66-11330,Taylor,Russell,taylor.russell@example.com,1e +88-40101,Raymond,Lowe,raymond.lowe@example.com,1e +43-56743,Jeremiah,Patterson,jeremiah.patterson@example.com,1e +70-38403,Rose,West,rose.west@example.com,1e +14-00347,Kelly,Hill,kelly.hill@example.com,1e +61-28054,Deanna,Rose,deanna.rose@example.com,1e +03-17766,Amelia,Hughes,amelia.hughes@example.com,1e +12-77426,Glen,Hawkins,glen.hawkins@example.com,1f +28-17331,Sophia,Curtis,sophia.curtis@example.com,1f +70-70737,Tonya,Evans,tonya.evans@example.com,1f +31-10053,Andrew,Coleman,andrew.coleman@example.com,1f +47-61516,Roberta,Mitchelle,roberta.mitchelle@example.com,1f +05-30741,Nicholas,Franklin,nicholas.franklin@example.com,1f +43-06416,Courtney,Graham,courtney.graham@example.com,1f +16-40581,Jamie,Simmmons,jamie.simmmons@example.com,1f +17-81018,Harold,Black,harold.black@example.com,1f +13-47847,Willard,Jennings,willard.jennings@example.com,1f +84-46188,Allison,Murray,allison.murray@example.com,1f +47-65416,Pat,Jones,pat.jones@example.com,1f +26-41428,Julie,Obrien,julie.obrien@example.com,1f +20-35300,Judd,Vargas,judd.vargas@example.com,1f +61-64350,Andre,Cole,andre.cole@example.com,1f +77-16826,Flenn,Payne,flenn.payne@example.com,1f +83-28843,Jimmie,Brewer,jimmie.brewer@example.com,1f +31-37652,Phillip,Mills,phillip.mills@example.com,1f +84-56471,Kirk,Shelton,kirk.shelton@example.com,1f +00-51280,Larry,Fowler,larry.fowler@example.com,1f +63-84018,Hector,Collins,hector.collins@example.com,1f +17-38512,Emily,Spencer,emily.spencer@example.com,1f +51-83814,Gary,Alexander,gary.alexander@example.com,1f +44-42285,Kelly,Price,kelly.price@example.com,1f +24-16268,Johnny,Jensen,johnny.jensen@example.com,1f +50-70841,Norman,Hicks,norman.hicks@example.com,1f +26-47237,Dawn,Murphy,dawn.murphy@example.com,1f +44-28300,Dave,Wilson,dave.wilson@example.com,1f +40-68568,Andre,Stephens,andre.stephens@example.com,1f +21-68866,Carlos,Snyder,carlos.snyder@example.com,1f +57-87635,Liam,Craig,liam.craig@example.com,1f +28-17356,Michael,Taylor,michael.taylor@example.com,1f +74-11217,Allan,Watkins,allan.watkins@example.com,1f +34-80314,Nicholas,Lawson,nicholas.lawson@example.com,1f +32-70166,Corey,Young,corey.young@example.com,1f +30-71707,Seth,Willis,seth.willis@example.com,1f +83-23236,Clyde,Olson,clyde.olson@example.com,1f +60-36004,Jackson,Miller,jackson.miller@example.com,1f +37-82742,Ann,Kim,ann.kim@example.com,1f +43-43772,Isaac,King,isaac.king@example.com,1f +54-12003,Cody,Walters,cody.walters@example.com,1f +13-41776,Alexis,Young,alexis.young@example.com,1f +73-42587,Meghan,Torres,meghan.torres@example.com,1f +43-66275,Justin,Curtis,justin.curtis@example.com,1f +21-74625,Leona,Holmes,leona.holmes@example.com,1f +51-68252,Donald,Simmmons,donald.simmmons@example.com,1f +38-70615,Vicki,Hall,vicki.hall@example.com,1f +03-87175,Hannah,Dean,hannah.dean@example.com,1f +48-83512,Wilma,Morris,wilma.morris@example.com,1f +35-60773,Curtis,Collins,curtis.collins@example.com,1f +43-68863,Katrina,Duncan,katrina.duncan@example.com,2a +06-38204,Jimmie,Vasquez,jimmie.vasquez@example.com,2a +61-48373,Zoey,Moreno,zoey.moreno@example.com,2a +35-28782,Nora,Chavez,nora.chavez@example.com,2a +25-82684,Christine,Stone,christine.stone@example.com,2a +10-50636,Ritthy,Andrews,ritthy.andrews@example.com,2a +71-43300,Rosa,Byrd,rosa.byrd@example.com,2a +66-40013,Jayden,Kim,jayden.kim@example.com,2a +70-80215,Lawrence,Jackson,lawrence.jackson@example.com,2a +05-17377,Russell,Curtis,russell.curtis@example.com,2a +23-07137,Dana,Wagner,dana.wagner@example.com,2a +62-44145,Jose,Pierce,jose.pierce@example.com,2a +16-45712,Joe,Silva,joe.silva@example.com,2a +88-17513,Sylvia,Rogers,sylvia.rogers@example.com,2a +68-08370,Howard,Garcia,howard.garcia@example.com,2a +67-54754,Jeremy,Griffin,jeremy.griffin@example.com,2a +24-63320,Flenn,Gardner,flenn.gardner@example.com,2a +41-63256,Camila,Oliver,camila.oliver@example.com,2a +36-56482,Cathy,Ramirez,cathy.ramirez@example.com,2a +01-62720,Jill,Lopez,jill.lopez@example.com,2a +32-62778,Michelle,Reed,michelle.reed@example.com,2a +72-32327,Soham,Perez,soham.perez@example.com,2a +26-82584,Vera,Walters,vera.walters@example.com,2a +13-78412,Louise,Newman,louise.newman@example.com,2a +51-21664,Jennifer,Hernandez,jennifer.hernandez@example.com,2a +27-11561,Sebastian,Williamson,sebastian.williamson@example.com,2a +86-88080,Paula,Coleman,paula.coleman@example.com,2a +35-81371,Tara,Morrison,tara.morrison@example.com,2a +75-71347,Ashley,Banks,ashley.banks@example.com,2a +35-17743,Gregory,Grant,gregory.grant@example.com,2a +81-21133,Cody,Jones,cody.jones@example.com,2a +42-14801,Dianne,Sanders,dianne.sanders@example.com,2a +80-46680,Sergio,Ellis,sergio.ellis@example.com,2a +83-80445,Eleanor,Chapman,eleanor.chapman@example.com,2a +57-04466,Tony,Sims,tony.sims@example.com,2a +61-64181,Tiffany,Berry,tiffany.berry@example.com,2a +35-51231,Amber,Fields,amber.fields@example.com,2a +57-33538,Lawrence,Fernandez,lawrence.fernandez@example.com,2a +03-63577,Maureen,Hunter,maureen.hunter@example.com,2a +07-48612,Zoe,Flores,zoe.flores@example.com,2a +50-56420,Diane,Wheeler,diane.wheeler@example.com,2a +46-58832,Jesse,Gordon,jesse.gordon@example.com,2a +03-55711,Ken,Steward,ken.steward@example.com,2a +61-12460,Jesse,Carlson,jesse.carlson@example.com,2a +15-21317,Harry,Burton,harry.burton@example.com,2a +12-64725,Jason,Ramos,jason.ramos@example.com,2a +74-62822,Rafael,Price,rafael.price@example.com,2a +82-17136,Tyrone,Ferguson,tyrone.ferguson@example.com,2a +35-50757,Renee,Patterson,renee.patterson@example.com,2a +70-61454,Nathan,Ramirez,nathan.ramirez@example.com,2a +23-52524,Myrtle,Riley,myrtle.riley@example.com,2b +46-01363,Georgia,Henry,georgia.henry@example.com,2b +45-77061,Nicole,Long,nicole.long@example.com,2b +82-48765,Tommy,Lane,tommy.lane@example.com,2b +23-55646,Susan,Hernandez,susan.hernandez@example.com,2b +31-32004,Taylor,Herrera,taylor.herrera@example.com,2b +45-33027,Andrew,Carroll,andrew.carroll@example.com,2b +05-33208,Aiden,Jimenez,aiden.jimenez@example.com,2b +44-35874,Rebecca,Long,rebecca.long@example.com,2b +28-80720,Perry,Webb,perry.webb@example.com,2b +22-60646,Martin,Wilson,martin.wilson@example.com,2b +66-74334,Kaylee,Robertson,kaylee.robertson@example.com,2b +32-35356,Ida,Ramos,ida.ramos@example.com,2b +37-35211,Lisa,Jimenez,lisa.jimenez@example.com,2b +08-46020,Monica,Kelly,monica.kelly@example.com,2b +33-46205,Joshua,Rodriguez,joshua.rodriguez@example.com,2b +25-38370,Sean,Carlson,sean.carlson@example.com,2b +61-68103,Richard,Sullivan,richard.sullivan@example.com,2b +65-42250,Rodney,Rice,rodney.rice@example.com,2b +60-25522,Flenn,Kim,flenn.kim@example.com,2b +16-36036,Sharlene,Price,sharlene.price@example.com,2b +35-05230,Vickie,Tucker,vickie.tucker@example.com,2b +03-52441,Eva,Lewis,eva.lewis@example.com,2b +47-61477,Beatrice,Reynolds,beatrice.reynolds@example.com,2b +84-10054,Jessie,Kennedy,jessie.kennedy@example.com,2b +53-58787,Ashley,Wilson,ashley.wilson@example.com,2b +46-67707,Vincent,Kuhn,vincent.kuhn@example.com,2b +22-57456,Michele,Pena,michele.pena@example.com,2b +78-13047,Brandie,Spencer,brandie.spencer@example.com,2b +02-47367,Clayton,Harvey,clayton.harvey@example.com,2b +04-24753,Johnny,Wallace,johnny.wallace@example.com,2b +61-07038,Cory,Matthews,cory.matthews@example.com,2b +25-61584,Howard,Palmer,howard.palmer@example.com,2b +83-00112,Levi,Spencer,levi.spencer@example.com,2b +64-60804,Billy,Day,billy.day@example.com,2b +38-54344,Brandon,Oliver,brandon.oliver@example.com,2b +22-58146,Zoey,Andrews,zoey.andrews@example.com,2b +17-21835,Norma,Taylor,norma.taylor@example.com,2b +24-20622,Janet,Andrews,janet.andrews@example.com,2b +64-64360,Melvin,Bailey,melvin.bailey@example.com,2b +38-24168,Lily,Collins,lily.collins@example.com,2b +67-18153,Camila,Wright,camila.wright@example.com,2b +72-37522,Ray,Diaz,ray.diaz@example.com,2b +33-50800,Bernice,Castillo,bernice.castillo@example.com,2b +87-67073,Emily,Harvey,emily.harvey@example.com,2b +38-32657,Owen,Miller,owen.miller@example.com,2b +10-35047,June,Montgomery,june.montgomery@example.com,2b +41-31163,Marlene,Hicks,marlene.hicks@example.com,2b +72-00507,Letitia,Elliott,letitia.elliott@example.com,2b +02-27075,Claire,Rose,claire.rose@example.com,2b +82-20124,Tanya,Williams,tanya.williams@example.com,2c +38-22506,Nicole,Sims,nicole.sims@example.com,2c +03-48222,Penny,Brown,penny.brown@example.com,2c +78-74780,Nelson,Daniels,nelson.daniels@example.com,2c +32-22724,Nathaniel,Freeman,nathaniel.freeman@example.com,2c +10-03773,Charles,Gregory,charles.gregory@example.com,2c +47-47112,Christian,Cole,christian.cole@example.com,2c +57-37106,Byron,Gutierrez,byron.gutierrez@example.com,2c +38-42351,Danny,Welch,danny.welch@example.com,2c +03-88215,Mabel,West,mabel.west@example.com,2c +84-12888,Larry,Stanley,larry.stanley@example.com,2c +34-45057,Walter,Williams,walter.williams@example.com,2c +82-72321,Darren,Mitchell,darren.mitchell@example.com,2c +86-58117,Gavin,Chapman,gavin.chapman@example.com,2c +45-32221,Valerie,Cox,valerie.cox@example.com,2c +15-20817,Daryl,Carroll,daryl.carroll@example.com,2c +36-42015,Heidi,Miles,heidi.miles@example.com,2c +03-04267,Darrell,Baker,darrell.baker@example.com,2c +35-03321,Leonard,Hart,leonard.hart@example.com,2c +02-17801,Dale,Craig,dale.craig@example.com,2c +11-34344,Laurie,Coleman,laurie.coleman@example.com,2c +76-38224,Vera,Reid,vera.reid@example.com,2c +05-10757,Mia,Rogers,mia.rogers@example.com,2c +21-33576,Larry,Hill,larry.hill@example.com,2c +33-64808,Zack,Flores,zack.flores@example.com,2c +72-67847,Elaine,Edwards,elaine.edwards@example.com,2c +52-17122,Jesse,Hudson,jesse.hudson@example.com,2c +43-34775,Mario,Fernandez,mario.fernandez@example.com,2c +71-44226,Kylie,Gregory,kylie.gregory@example.com,2c +16-03566,Jayden,Dunn,jayden.dunn@example.com,2c +27-73811,Denise,Sullivan,denise.sullivan@example.com,2c +60-38827,Jack,Burns,jack.burns@example.com,2c +31-16755,Alfredo,Riley,alfredo.riley@example.com,2c +43-48825,Mario,Wheeler,mario.wheeler@example.com,2c +37-55372,Joyce,Schmidt,joyce.schmidt@example.com,2c +03-83635,Kelly,Shaw,kelly.shaw@example.com,2c +27-38604,Alberto,Craig,alberto.craig@example.com,2c +77-31100,Neil,Clark,neil.clark@example.com,2c +62-16335,Stanley,Bailey,stanley.bailey@example.com,2c +65-66510,Michele,Andrews,michele.andrews@example.com,2c +28-48508,Mathew,Webb,mathew.webb@example.com,2c +57-56433,Beverly,Kelly,beverly.kelly@example.com,2c +82-62670,Virgil,Harvey,virgil.harvey@example.com,2c +36-35060,Lisa,Grant,lisa.grant@example.com,2c +14-26884,Mason,Murray,mason.murray@example.com,2c +52-56600,Lauren,Herrera,lauren.herrera@example.com,2c +28-14650,Philip,Lawson,philip.lawson@example.com,2c +55-46877,Ruby,Vasquez,ruby.vasquez@example.com,2c +45-22275,Leslie,Perry,leslie.perry@example.com,2c +34-67626,Deanna,Woods,deanna.woods@example.com,2c +71-74546,Alyssa,Perez,alyssa.perez@example.com,2d +18-44437,Sean,Burton,sean.burton@example.com,2d +52-00154,Cathy,Hale,cathy.hale@example.com,2d +55-53464,Willie,Vargas,willie.vargas@example.com,2d +54-42853,Mary,Ford,mary.ford@example.com,2d +74-31226,Meghan,Jensen,meghan.jensen@example.com,2d +02-73125,Melissa,Mcdonalid,melissa.mcdonalid@example.com,2d +44-83383,Tara,Herrera,tara.herrera@example.com,2d +80-63355,Eduardo,Griffin,eduardo.griffin@example.com,2d +50-18771,Madison,Cook,madison.cook@example.com,2d +22-05204,Edgar,Black,edgar.black@example.com,2d +60-43704,Krin,Jacobs,krin.jacobs@example.com,2d +18-33374,Joe,Dixon,joe.dixon@example.com,2d +12-56255,Cory,Vasquez,cory.vasquez@example.com,2d +65-16265,Louise,Nelson,louise.nelson@example.com,2d +06-75513,Erika,Hayes,erika.hayes@example.com,2d +81-25328,Theodore,Jordan,theodore.jordan@example.com,2d +16-38514,Stacey,Jones,stacey.jones@example.com,2d +65-66682,Jordan,Sims,jordan.sims@example.com,2d +70-64707,Nora,Hayes,nora.hayes@example.com,2d +21-34451,Gerald,Carpenter,gerald.carpenter@example.com,2d +77-53445,Michele,Wallace,michele.wallace@example.com,2d +28-08176,Joanne,Howell,joanne.howell@example.com,2d +78-88214,Aaron,Powell,aaron.powell@example.com,2d +73-08676,Samuel,Wagner,samuel.wagner@example.com,2d +67-16867,Kevin,Hayes,kevin.hayes@example.com,2d +10-01577,Darlene,Jenkins,darlene.jenkins@example.com,2d +17-56306,Gabriel,Chavez,gabriel.chavez@example.com,2d +66-75217,Aiden,Fletcher,aiden.fletcher@example.com,2d +64-23318,Sherry,Lawrence,sherry.lawrence@example.com,2d +77-02771,Kay,Jones,kay.jones@example.com,2d +74-86485,Lee,Barnes,lee.barnes@example.com,2d +62-36131,Claude,Jordan,claude.jordan@example.com,2d +85-88704,Sofia,Gibson,sofia.gibson@example.com,2d +10-72485,Hector,Holmes,hector.holmes@example.com,2d +45-00345,Carl,Baker,carl.baker@example.com,2d +53-64461,Erica,Rodriquez,erica.rodriquez@example.com,2d +67-43858,Melanie,Chavez,melanie.chavez@example.com,2d +51-38101,Dwight,Hudson,dwight.hudson@example.com,2d +02-54741,Peter,Lee,peter.lee@example.com,2d +60-25086,Joanne,Moore,joanne.moore@example.com,2d +76-04231,Tonya,Dixon,tonya.dixon@example.com,2d +70-67270,Landon,Byrd,landon.byrd@example.com,2d +63-04032,Marilyn,Lucas,marilyn.lucas@example.com,2d +75-76412,Kirk,Rogers,kirk.rogers@example.com,2d +40-67543,Arlene,Jennings,arlene.jennings@example.com,2d +74-88720,Brennan,James,brennan.james@example.com,2d +34-67770,Joe,Andrews,joe.andrews@example.com,2d +05-10708,Zoe,Cooper,zoe.cooper@example.com,2d +42-68214,Christian,Carr,christian.carr@example.com,2d +56-43404,Darrell,Fernandez,darrell.fernandez@example.com,2e +70-40222,Catherine,Gomez,catherine.gomez@example.com,2e +23-15634,Keith,Hall,keith.hall@example.com,2e +40-35826,Leta,Johnson,leta.johnson@example.com,2e +48-86568,Leslie,Henderson,leslie.henderson@example.com,2e +07-55441,Theresa,Hall,theresa.hall@example.com,2e +83-75363,Christian,Hunt,christian.hunt@example.com,2e +24-22508,Jar,Wright,jar.wright@example.com,2e +53-66137,Sonia,Weaver,sonia.weaver@example.com,2e +60-35078,Stephanie,Bell,stephanie.bell@example.com,2e +85-04632,Katrina,Holmes,katrina.holmes@example.com,2e +46-52802,Josephine,Beck,josephine.beck@example.com,2e +47-67077,Louise,Walters,louise.walters@example.com,2e +64-48007,Charles,Stevens,charles.stevens@example.com,2e +38-87080,Antonio,Mccoy,antonio.mccoy@example.com,2e +24-30252,Keith,Turner,keith.turner@example.com,2e +06-06026,Jerome,Davis,jerome.davis@example.com,2e +58-63504,Floyd,Kennedy,floyd.kennedy@example.com,2e +07-38332,Clinton,Harvey,clinton.harvey@example.com,2e +57-84468,Ethan,Freeman,ethan.freeman@example.com,2e +70-72574,Lauren,Harris,lauren.harris@example.com,2e +16-72855,Derrick,Sanders,derrick.sanders@example.com,2e +41-32102,Jeff,Prescott,jeff.prescott@example.com,2e +11-55368,Joseph,Hughes,joseph.hughes@example.com,2e +84-12762,Samuel,Garcia,samuel.garcia@example.com,2e +33-41535,Veronica,Beck,veronica.beck@example.com,2e +46-82233,Jared,Mason,jared.mason@example.com,2e +44-84185,Andy,Rodriquez,andy.rodriquez@example.com,2e +14-03380,Peggy,Mcdonalid,peggy.mcdonalid@example.com,2e +20-01136,Jennifer,Barnett,jennifer.barnett@example.com,2e +44-53235,Fernando,Silva,fernando.silva@example.com,2e +46-08535,Jeanne,Fields,jeanne.fields@example.com,2e +48-64608,Russell,Johnston,russell.johnston@example.com,2e +03-66554,Toni,Richards,toni.richards@example.com,2e +58-18116,Rosemary,Stevens,rosemary.stevens@example.com,2e +48-73323,Brennan,Beck,brennan.beck@example.com,2e +57-71244,Larry,Burke,larry.burke@example.com,2e +77-70813,Isaiah,Black,isaiah.black@example.com,2e +46-21371,Rachel,Armstrong,rachel.armstrong@example.com,2e +71-53754,Eduardo,Reynolds,eduardo.reynolds@example.com,2e +18-74575,Cherly,Holmes,cherly.holmes@example.com,2e +65-41041,Tommy,Reed,tommy.reed@example.com,2e +57-65180,Kristina,Welch,kristina.welch@example.com,2e +58-21316,Barry,Mills,barry.mills@example.com,2e +37-75101,Carter,Lucas,carter.lucas@example.com,2e +47-48280,Bobbie,Newman,bobbie.newman@example.com,2e +33-75381,Javier,Powell,javier.powell@example.com,2e +38-56580,Adam,Nelson,adam.nelson@example.com,2e +35-02200,Joseph,Steeves ,joseph.steeves@example.com,2e +33-77861,Holly,Marshall,holly.marshall@example.com,2e +13-37203,Duane,Powell,duane.powell@example.com,2g +50-53241,Judy,Henderson,judy.henderson@example.com,2g +45-88123,Katie,Jenkins,katie.jenkins@example.com,2g +85-86355,Theresa,Robertson,theresa.robertson@example.com,2g +14-58262,Bobbie,Rodriquez,bobbie.rodriquez@example.com,2g +70-20886,Emily,Fox,emily.fox@example.com,2g +75-14840,Steve,Howard,steve.howard@example.com,2g +64-26451,Joann,White,joann.white@example.com,2g +84-44875,Jerry,Diaz,jerry.diaz@example.com,2g +21-33310,Jeanne,Powell,jeanne.powell@example.com,2g +06-17531,Debra,Baker,debra.baker@example.com,2g +40-83307,Savannah,Robertson,savannah.robertson@example.com,2g +13-22828,Edwin,Crawford,edwin.crawford@example.com,2g +14-68817,Joshua,Soto,joshua.soto@example.com,2g +24-33301,David,Gomez,david.gomez@example.com,2g +55-40263,Patsy,Nguyen,patsy.nguyen@example.com,2g +42-65466,Mike,Gordon,mike.gordon@example.com,2g +57-34261,Pearl,Russell,pearl.russell@example.com,2g +17-67468,Sean,King,sean.king@example.com,2g +46-04725,Carole,Hopkins,carole.hopkins@example.com,2g +33-77106,John,Jensen,john.jensen@example.com,2g +14-56417,Claudia,Mason,claudia.mason@example.com,2g +03-87014,Fernando,Grant,fernando.grant@example.com,2g +74-24612,John,Wells,john.wells@example.com,2g +42-28638,Erika,Bishop,erika.bishop@example.com,2g +11-66512,Willie,Gibson,willie.gibson@example.com,2g +55-72587,Arnold,Howell,arnold.howell@example.com,2g +55-68137,Alexis,Howard,alexis.howard@example.com,2g +48-76788,Clyde,Garcia,clyde.garcia@example.com,2g +10-38545,Margie,Fox,margie.fox@example.com,2g +15-35637,Annie,Cooper,annie.cooper@example.com,2g +04-55435,Brennan,Hunter,brennan.hunter@example.com,2g +44-10315,Frank,Shelton,frank.shelton@example.com,2g +06-16118,Gabriella,Miles,gabriella.miles@example.com,2g +21-51167,Shawn,Jacobs,shawn.jacobs@example.com,2g +35-58634,Manuel,Welch,manuel.welch@example.com,2g +81-44246,Jerry,Wells,jerry.wells@example.com,2g +33-54266,Brian,Fields,brian.fields@example.com,2g +36-27775,Courtney,Peck,courtney.peck@example.com,2g +20-76858,Allan,Sutton,allan.sutton@example.com,2g +14-15577,Reginald,Butler,reginald.butler@example.com,2g +32-85876,Dwight,Herrera,dwight.herrera@example.com,2g +33-21636,Kristin,Ross,kristin.ross@example.com,2g +06-50044,Anna,Jackson,anna.jackson@example.com,2g +17-78845,Mabel,Palmer,mabel.palmer@example.com,2g +25-41005,Anne,Hernandez,anne.hernandez@example.com,2g +38-62257,Kurt,Davidson,kurt.davidson@example.com,2g +36-57170,Rose,Gilbert,rose.gilbert@example.com,2g +77-34113,Erin,Hawkins,erin.hawkins@example.com,2g +52-62758,Ellen,George,ellen.george@example.com,2g +84-06301,Alexa,Cox,alexa.cox@example.com,2h +46-18746,Tracey,Nelson,tracey.nelson@example.com,2h +24-87634,Tara,Smith,tara.smith@example.com,2h +21-10252,Evelyn,Jimenez,evelyn.jimenez@example.com,2h +54-65187,Lillian,Payne,lillian.payne@example.com,2h +18-00880,Marion,Parker,marion.parker@example.com,2h +20-22518,Lester,Edwards,lester.edwards@example.com,2h +38-22503,Joanne,Torres,joanne.torres@example.com,2h +20-72054,Carmen,Morrison,carmen.morrison@example.com,2h +47-53433,Theresa,Turner,theresa.turner@example.com,2h +84-62116,Billie,Frazier,billie.frazier@example.com,2h +75-57301,Kathy,Hicks,kathy.hicks@example.com,2h +84-63113,Pauline,Dunn,pauline.dunn@example.com,2h +72-16042,Terra,Thomas,terra.thomas@example.com,2h +73-40850,Leona,Snyder,leona.snyder@example.com,2h +86-61024,Christy,Bryant,christy.bryant@example.com,2h +68-52450,Shawn,Green,shawn.green@example.com,2h +00-75230,Joanne,Arnold,joanne.arnold@example.com,2h +86-03381,Brayden,Collins,brayden.collins@example.com,2h +45-26778,Felicia,Sutton,felicia.sutton@example.com,2h +22-14837,Eleanor,Rose,eleanor.rose@example.com,2h +04-41867,Darren,Sims,darren.sims@example.com,2h +67-43717,Savannah,Curtis,savannah.curtis@example.com,2h +72-12327,Eva,Peck,eva.peck@example.com,2h +18-08765,Clifton,Morrison,clifton.morrison@example.com,2h +65-88228,Erin,Lewis,erin.lewis@example.com,2h +07-01325,Soham,Evans,soham.evans@example.com,2h +60-02158,Kristina,Jackson,kristina.jackson@example.com,2h +77-04831,Billy,Fox,billy.fox@example.com,2h +13-68821,Gabriel,Bryant,gabriel.bryant@example.com,2h +36-02613,Kristin,Perez,kristin.perez@example.com,2h +31-67200,Jon,May,jon.may@example.com,2h +06-18603,Lauren,Jordan,lauren.jordan@example.com,2h +38-58430,Theodore,Little,theodore.little@example.com,2h +48-82081,Denise,Hart,denise.hart@example.com,2h +12-40617,Milton,Harper,milton.harper@example.com,2h +81-06326,Priscilla,Jacobs,priscilla.jacobs@example.com,2h +64-04773,Ramona,Obrien,ramona.obrien@example.com,2h +55-86680,Denise,Owens,denise.owens@example.com,2h +43-45623,Doris,Simmmons,doris.simmmons@example.com,2h +86-80662,Theresa,Hughes,theresa.hughes@example.com,2h +56-13174,Jack,Banks,jack.banks@example.com,2h +17-30673,Crystal,Bates,crystal.bates@example.com,2h +12-56271,Roland,Clark,roland.clark@example.com,2h +45-82720,Zachary,Phillips,zachary.phillips@example.com,2h +48-22282,Ray,Wood,ray.wood@example.com,2h +12-57827,Nelson,Webb,nelson.webb@example.com,2h +50-82603,Sally,Russell,sally.russell@example.com,2h +65-17475,Leon,Scott,leon.scott@example.com,2h +86-40217,Yolanda,Martinez,yolanda.martinez@example.com,2h +55-63321,Lydia,Terry,lydia.terry@example.com,3a +10-16811,Amanda,Tucker,amanda.tucker@example.com,3a +85-16460,Claire,Harper,claire.harper@example.com,3a +42-26041,Antonio,Long,antonio.long@example.com,3a +37-44213,Robin,Fox,robin.fox@example.com,3a +44-53848,Kathy,Carter,kathy.carter@example.com,3a +30-86865,Edna,Lawrence,edna.lawrence@example.com,3a +78-17222,Stella,Bailey,stella.bailey@example.com,3a +83-48111,Edna,Gonzalez,edna.gonzalez@example.com,3a +70-22312,Bessie,Garcia,bessie.garcia@example.com,3a +28-57888,Jesus,Rose,jesus.rose@example.com,3a +36-07043,Katrina,Lawson,katrina.lawson@example.com,3a +60-10018,Alexa,Lambert,alexa.lambert@example.com,3a +72-57506,Ethel,Wade,ethel.wade@example.com,3a +13-83754,Janet,Henry,janet.henry@example.com,3a +81-87833,Albert,Bowman,albert.bowman@example.com,3a +55-38721,Terrence,Perez,terrence.perez@example.com,3a +35-31815,Anna,Riley,anna.riley@example.com,3a +66-23425,Billy,Jimenez,billy.jimenez@example.com,3a +23-27318,Leon,Patterson,leon.patterson@example.com,3a +33-32206,Marion,Welch,marion.welch@example.com,3a +87-10560,Russell,Gregory,russell.gregory@example.com,3a +21-23517,Tim,Rivera,tim.rivera@example.com,3a +58-15288,Marie,Mills,marie.mills@example.com,3a +45-66552,Misty,Obrien,misty.obrien@example.com,3a +06-84211,Russell,Vasquez,russell.vasquez@example.com,3a +62-28474,Milton,Wells,milton.wells@example.com,3a +45-10057,Ron,Barnes,ron.barnes@example.com,3a +72-87764,Jayden,Horton,jayden.horton@example.com,3a +88-05086,Ricardo,Nichols,ricardo.nichols@example.com,3a +04-04566,Isabella,Green,isabella.green@example.com,3a +47-00460,Debbie,Richardson,debbie.richardson@example.com,3a +56-04854,Mathew,Hale,mathew.hale@example.com,3a +28-43304,Dwayne,Barnett,dwayne.barnett@example.com,3a +22-12414,Paul,Henry,paul.henry@example.com,3a +74-11015,Peter,Rose,peter.rose@example.com,3a +08-68373,Vivan,Dunn,vivan.dunn@example.com,3a +74-80317,Misty,Peters,misty.peters@example.com,3a +75-32178,Samuel,Cook,samuel.cook@example.com,3a +18-50466,June,Garza,june.garza@example.com,3a +61-11342,Julie,Ellis,julie.ellis@example.com,3a +70-75154,Joanne,James,joanne.james@example.com,3a +41-02342,Marilyn,Kuhn,marilyn.kuhn@example.com,3a +64-48465,Katrina,Williamson,katrina.williamson@example.com,3a +85-24453,Louis,Garza,louis.garza@example.com,3a +75-54885,Charlotte,Washington,charlotte.washington@example.com,3a +33-88474,Priscilla,Wagner,priscilla.wagner@example.com,3a +07-17445,Carrie,Morales,carrie.morales@example.com,3a +52-27201,Gene,Davis,gene.davis@example.com,3a +76-34742,Marjorie,Hughes,marjorie.hughes@example.com,3a +87-88618,Misty,Davidson,misty.davidson@example.com,3b +01-47514,Lena,Harris,lena.harris@example.com,3b +16-87567,Terri,Sanchez,terri.sanchez@example.com,3b +23-54081,Seth,Perkins,seth.perkins@example.com,3b +43-18117,Andrea,Stephens,andrea.stephens@example.com,3b +37-45538,Dave,Dean,dave.dean@example.com,3b +68-71308,Melanie,Campbell,melanie.campbell@example.com,3b +67-18455,Benjamin,Turner,benjamin.turner@example.com,3b +76-06071,Courtney,Ross,courtney.ross@example.com,3b +15-20845,Landon,Smith,landon.smith@example.com,3b +24-51778,Marvin,Lambert,marvin.lambert@example.com,3b +68-42117,John,Hamilton,john.hamilton@example.com,3b +58-57271,Alex,Baker,alex.baker@example.com,3b +03-88128,Harper,Cox,harper.cox@example.com,3b +75-04033,Deanna,Hudson,deanna.hudson@example.com,3b +70-10050,Jane,Austin,jane.austin@example.com,3b +54-77467,Jennie,Byrd,jennie.byrd@example.com,3b +48-71287,Andrew,Coleman,andrew.coleman@example.com,3b +45-58553,Reginald,Wright,reginald.wright@example.com,3b +10-70144,Mae,Harris,mae.harris@example.com,3b +80-85238,Calvin,Jones,calvin.jones@example.com,3b +18-58817,Jeanne,Davidson,jeanne.davidson@example.com,3b +67-15135,Steve,Hoffman,steve.hoffman@example.com,3b +58-41768,Elmer,Mills,elmer.mills@example.com,3b +51-60234,Lois,Thomas,lois.thomas@example.com,3b +70-00385,Bobby,Patterson,bobby.patterson@example.com,3b +65-27336,Jamie,Alexander,jamie.alexander@example.com,3b +18-72340,Miriam,Meyer,miriam.meyer@example.com,3b +14-43881,Samantha,Bishop,samantha.bishop@example.com,3b +15-12646,Hugh,Gonzales,hugh.gonzales@example.com,3b +85-70270,Travis,Murray,travis.murray@example.com,3b +13-54025,Jessie,Kim,jessie.kim@example.com,3b +64-31320,Dana,Garza,dana.garza@example.com,3b +65-06257,Lucas,Mills,lucas.mills@example.com,3b +12-11528,Dwight,Matthews,dwight.matthews@example.com,3b +35-50066,Stephanie,Reynolds,stephanie.reynolds@example.com,3b +05-80225,Derek,Kelly,derek.kelly@example.com,3b +82-24456,Delores,Welch,delores.welch@example.com,3b +74-82783,Sarah,King,sarah.king@example.com,3b +44-55560,Bernard,Newman,bernard.newman@example.com,3b +78-38368,Jordan,Wade,jordan.wade@example.com,3b +34-81772,Joshua,Gomez,joshua.gomez@example.com,3b +18-00540,Ashley,Johnson,ashley.johnson@example.com,3b +02-51632,Amanda,Tucker,amanda.tucker@example.com,3b +07-57125,Don,Perkins,don.perkins@example.com,3b +31-10451,Don,Perkins,don.perkins@example.com,3b +15-66112,Brandy,Powell,brandy.powell@example.com,3b +05-48083,Bryan,Stevens,bryan.stevens@example.com,3b +18-35016,Olivia,Austin,olivia.austin@example.com,3b +71-37104,Tom,Lane,tom.lane@example.com,3b +68-08501,Jane,Kuhn,jane.kuhn@example.com,3c +01-04102,Tracy,Rivera,tracy.rivera@example.com,3c +66-12255,Carlos,Stevens,carlos.stevens@example.com,3c +58-14217,Nicholas,Fox,nicholas.fox@example.com,3c +46-23334,Lucille,Hill,lucille.hill@example.com,3c +61-10422,Jo,Garcia,jo.garcia@example.com,3c +03-16274,Nathan,Carr,nathan.carr@example.com,3c +21-76446,Danielle,Matthews,danielle.matthews@example.com,3c +61-10221,Jeffrey,Barnett,jeffrey.barnett@example.com,3c +87-16754,Marjorie,Little,marjorie.little@example.com,3c +12-16043,Madison,Reed,madison.reed@example.com,3c +81-10602,Earl,Spencer,earl.spencer@example.com,3c +66-13013,Ivan,Hale,ivan.hale@example.com,3c +70-12273,Dawn,Clark,dawn.clark@example.com,3c +15-74101,Ellen,Jennings,ellen.jennings@example.com,3c +26-32028,Pedro,Mason,pedro.mason@example.com,3c +61-24400,Philip,Davis,philip.davis@example.com,3c +21-35742,Leta,Allen,leta.allen@example.com,3c +15-45705,Steve,Phillips,steve.phillips@example.com,3c +83-66461,Hazel,King,hazel.king@example.com,3c +41-08064,Kristin,Tucker,kristin.tucker@example.com,3c +20-88624,Paula,Stewart,paula.stewart@example.com,3c +18-87182,Heidi,Owens,heidi.owens@example.com,3c +16-40511,Sandra,Watts,sandra.watts@example.com,3c +65-43563,Tomothy,Sutton,tomothy.sutton@example.com,3c +04-06652,Joseph,George,joseph.george@example.com,3c +27-23018,Jeffery,Newman,jeffery.newman@example.com,3c +85-46473,Michele,Garcia,michele.garcia@example.com,3c +38-04082,Christina,Vargas,christina.vargas@example.com,3c +06-51174,Sophie,Hill,sophie.hill@example.com,3c +05-41812,Wilma,Fox,wilma.fox@example.com,3c +31-03616,Evan,Lopez,evan.lopez@example.com,3c +72-62121,Ava,Cox,ava.cox@example.com,3c +25-55352,Franklin,Crawford,franklin.crawford@example.com,3c +83-70107,Ralph,Ferguson,ralph.ferguson@example.com,3c +34-55415,Willard,Ruiz,willard.ruiz@example.com,3c +64-04444,Lucas,Hicks,lucas.hicks@example.com,3c +42-75487,Andrew,Porter,andrew.porter@example.com,3c +66-58675,Pamela,Tucker,pamela.tucker@example.com,3c +07-03213,Christina,Barrett,christina.barrett@example.com,3c +02-28745,Alvin,Castro,alvin.castro@example.com,3c +85-85540,Luke,Armstrong,luke.armstrong@example.com,3c +06-50064,Bill,Hughes,bill.hughes@example.com,3c +26-11041,Leah,Marshall,leah.marshall@example.com,3c +14-01872,Jared,Murray,jared.murray@example.com,3c +54-76770,Camila,George,camila.george@example.com,3c +05-65408,Gavin,Cruz,gavin.cruz@example.com,3c +07-48825,Amelia,Hudson,amelia.hudson@example.com,3c +81-25774,Juanita,Gordon,juanita.gordon@example.com,3c +44-11686,Tamara,Hicks,tamara.hicks@example.com,3c +64-23424,Jayden,Young,jayden.young@example.com,3d +75-54217,Nathan,Robinson,nathan.robinson@example.com,3d +43-56086,Nina,Spencer,nina.spencer@example.com,3d +68-67500,Grace,Butler,grace.butler@example.com,3d +85-88413,Tommy,Day,tommy.day@example.com,3d +18-73668,Heather,Caldwell,heather.caldwell@example.com,3d +54-17152,Sue,Nelson,sue.nelson@example.com,3d +28-75404,Marcus,Roberts,marcus.roberts@example.com,3d +56-66588,Same,Stewart,same.stewart@example.com,3d +17-13457,Benjamin,Henry,benjamin.henry@example.com,3d +18-21248,Dawn,Nichols,dawn.nichols@example.com,3d +73-36126,Carmen,Bennett,carmen.bennett@example.com,3d +87-38055,Cindy,Banks,cindy.banks@example.com,3d +78-88657,Cassandra,Soto,cassandra.soto@example.com,3d +52-25650,Alex,Steeves ,alex.steeves@example.com,3d +54-16842,Francis,Jensen,francis.jensen@example.com,3d +51-42240,Dwayne,Woods,dwayne.woods@example.com,3d +26-44507,Mabel,Peck,mabel.peck@example.com,3d +12-47837,Kurt,Flores,kurt.flores@example.com,3d +53-42017,Chester,Pierce,chester.pierce@example.com,3d +33-33736,Jimmie,Howard,jimmie.howard@example.com,3d +01-18177,Claudia,Edwards,claudia.edwards@example.com,3d +06-35820,Tina,Baker,tina.baker@example.com,3d +46-67421,Maurice,Diaz,maurice.diaz@example.com,3d +26-25570,Joann,Steeves ,joann.steeves@example.com,3d +40-81074,Joyce,Gibson,joyce.gibson@example.com,3d +41-87540,Colleen,Watts,colleen.watts@example.com,3d +08-33621,Elijah,Prescott,elijah.prescott@example.com,3d +55-25742,Debbie,Lowe,debbie.lowe@example.com,3d +00-00802,Linda,Gonzales,linda.gonzales@example.com,3d +74-11458,Noelle,Hamilton,noelle.hamilton@example.com,3d +84-45178,Darryl,Snyder,darryl.snyder@example.com,3d +57-01817,Adrian,Gray,adrian.gray@example.com,3d +70-71540,Gabriella,Howell,gabriella.howell@example.com,3d +20-85137,Louis,Walker,louis.walker@example.com,3d +88-51580,Priscilla,Garza,priscilla.garza@example.com,3d +87-77300,Gregory,Davis,gregory.davis@example.com,3d +74-75600,Debbie,Lambert,debbie.lambert@example.com,3d +70-50411,Jo,Davis,jo.davis@example.com,3d +14-18615,Lillian,Sanchez,lillian.sanchez@example.com,3d +06-20728,Jeffrey,Dunn,jeffrey.dunn@example.com,3d +68-47752,Juan,Hicks,juan.hicks@example.com,3d +51-82384,Lois,Harvey,lois.harvey@example.com,3d +86-76175,David,Long,david.long@example.com,3d +56-58223,Ruben,Pena,ruben.pena@example.com,3d +78-40321,Esther,Herrera,esther.herrera@example.com,3d +40-02532,Chad,Flores,chad.flores@example.com,3d +27-15250,Sophie,Reid,sophie.reid@example.com,3d +56-45163,Ann,Mckinney,ann.mckinney@example.com,3d +10-06234,Shawn,Stone,shawn.stone@example.com,3d +32-06876,Jo,Smith,jo.smith@example.com,3e +73-26136,Erika,Elliott,erika.elliott@example.com,3e +72-05856,Floyd,Kelley,floyd.kelley@example.com,3e +01-30411,Leona,Newman,leona.newman@example.com,3e +41-74583,Lena,Turner,lena.turner@example.com,3e +60-60312,Ritthy,Pierce,ritthy.pierce@example.com,3e +57-08143,Erica,Wagner,erica.wagner@example.com,3e +58-44015,Joann,Murray,joann.murray@example.com,3e +60-01645,Vanessa,Lopez,vanessa.lopez@example.com,3e +44-44368,Henry,Perry,henry.perry@example.com,3e +16-58054,Sylvia,Jacobs,sylvia.jacobs@example.com,3e +84-13038,Theodore,Harvey,theodore.harvey@example.com,3e +45-25221,Gerald,Shaw,gerald.shaw@example.com,3e +45-08783,Freddie,Lane,freddie.lane@example.com,3e +60-24840,Mason,Carroll,mason.carroll@example.com,3e +54-06664,Sherri,Johnson,sherri.johnson@example.com,3e +80-22828,Neil,Kelly,neil.kelly@example.com,3e +22-34303,Michael,Reid,michael.reid@example.com,3e +35-77211,Pearl,Torres,pearl.torres@example.com,3e +83-78228,Alfred,Simmmons,alfred.simmmons@example.com,3e +25-42714,Melissa,Scott,melissa.scott@example.com,3e +45-43848,Colleen,Parker,colleen.parker@example.com,3e +70-45821,Anne,Diaz,anne.diaz@example.com,3e +51-88131,Ivan,Fleming,ivan.fleming@example.com,3e +63-01545,Kathy,Reyes,kathy.reyes@example.com,3e +55-42310,Chad,Steeves ,chad.steeves@example.com,3e +64-85650,Ritthy,Peterson,ritthy.peterson@example.com,3e +34-86710,Troy,Wright,troy.wright@example.com,3e +75-08137,Freddie,Butler,freddie.butler@example.com,3e +17-44804,Krin,Hudson,krin.hudson@example.com,3e +00-81587,Liam,Ortiz,liam.ortiz@example.com,3e +43-08075,Kaylee,Rogers,kaylee.rogers@example.com,3e +41-08614,Aubree,Hamilton,aubree.hamilton@example.com,3e +64-64762,Terry,Foster,terry.foster@example.com,3e +42-66056,Amanda,Fletcher,amanda.fletcher@example.com,3e +54-61013,Julian,Henry,julian.henry@example.com,3e +86-83411,Franklin,Fernandez,franklin.fernandez@example.com,3e +42-73471,Jesse,Kelly,jesse.kelly@example.com,3e +88-16480,Edward,Burton,edward.burton@example.com,3e +41-47633,Mildred,Harvey,mildred.harvey@example.com,3e +54-42672,Loretta,Rodriguez,loretta.rodriguez@example.com,3e +55-40857,Everett,Gomez,everett.gomez@example.com,3e +40-58045,Alicia,Spencer,alicia.spencer@example.com,3e +61-03415,Marian,Davidson,marian.davidson@example.com,3e +82-88233,Charlie,Ford,charlie.ford@example.com,3e +15-45512,Mia,Snyder,mia.snyder@example.com,3e +67-76357,Pearl,Woods,pearl.woods@example.com,3e +36-65877,Regina,Willis,regina.willis@example.com,3e +08-68337,Wyatt,Alexander,wyatt.alexander@example.com,3e +48-51734,Sharlene,Perkins,sharlene.perkins@example.com,3e +44-41027,Michele,Turner,michele.turner@example.com,3g +20-62116,Billy,Perry,billy.perry@example.com,3g +27-46006,Jeanne,Fields,jeanne.fields@example.com,3g +34-87084,Delores,Young,delores.young@example.com,3g +21-88300,Joan,Larson,joan.larson@example.com,3g +23-76205,Mattie,Byrd,mattie.byrd@example.com,3g +13-05847,Mike,Patterson,mike.patterson@example.com,3g +21-78661,Carlos,Burton,carlos.burton@example.com,3g +00-83821,Elizabeth,Gonzales,elizabeth.gonzales@example.com,3g +00-25122,Delores,Russell,delores.russell@example.com,3g +34-58586,Cathy,Mckinney,cathy.mckinney@example.com,3g +36-01702,Joyce,Arnold,joyce.arnold@example.com,3g +80-10460,Scarlett,Neal,scarlett.neal@example.com,3g +70-57472,Francisco,Burns,francisco.burns@example.com,3g +44-73116,Armando,Hunt,armando.hunt@example.com,3g +27-74343,Leroy,Herrera,leroy.herrera@example.com,3g +04-25740,Terry,Cooper,terry.cooper@example.com,3g +02-02886,Michael,Black,michael.black@example.com,3g +36-04172,Isabella,West,isabella.west@example.com,3g +28-13332,Sally,Jenkins,sally.jenkins@example.com,3g +34-07230,Tony,Hayes,tony.hayes@example.com,3g +23-53565,Stanley,Powell,stanley.powell@example.com,3g +42-68614,Denise,Davis,denise.davis@example.com,3g +47-43548,Randall,Larson,randall.larson@example.com,3g +18-17283,Jane,Green,jane.green@example.com,3g +63-72668,Byron,Montgomery,byron.montgomery@example.com,3g +72-01578,Alan,Newman,alan.newman@example.com,3g +44-48416,Charles,Douglas,charles.douglas@example.com,3g +47-25408,Veronica,Watts,veronica.watts@example.com,3g +50-01832,Lloyd,Lawson,lloyd.lawson@example.com,3g +16-77443,Addison,Washington,addison.washington@example.com,3g +37-81458,Georgia,Ferguson,georgia.ferguson@example.com,3g +22-25562,Rodney,Reyes,rodney.reyes@example.com,3g +78-36702,Savannah,Soto,savannah.soto@example.com,3g +53-57803,Melvin,Cunningham,melvin.cunningham@example.com,3g +01-73406,Dave,Gibson,dave.gibson@example.com,3g +24-83418,Gene,Day,gene.day@example.com,3g +07-28201,Reginald,Cunningham,reginald.cunningham@example.com,3g +30-80623,Kelly,Peterson,kelly.peterson@example.com,3g +00-01620,Caleb,Pearson,caleb.pearson@example.com,3g +14-74810,Ashley,Fowler,ashley.fowler@example.com,3g +88-77150,Ronald,Holland,ronald.holland@example.com,3g +88-83027,Ross,Sutton,ross.sutton@example.com,3g +20-52463,Corey,Collins,corey.collins@example.com,3g +54-13455,Caroline,Jordan,caroline.jordan@example.com,3g +15-66444,Annette,Watson,annette.watson@example.com,3g +77-11052,Jesse,Warren,jesse.warren@example.com,3g +30-02034,Beverley,Stewart,beverley.stewart@example.com,3g +63-05630,Roberta,May,roberta.may@example.com,3g +67-88860,Vanessa,Rhodes,vanessa.rhodes@example.com,3g +73-13618,Wayne,Torres,wayne.torres@example.com,3h +76-64510,Marion,Bates,marion.bates@example.com,3h +01-77415,Kelly,West,kelly.west@example.com,3h +61-02781,Isaac,Reed,isaac.reed@example.com,3h +70-13033,Ethan,Russell,ethan.russell@example.com,3h +35-00173,Alyssa,Tucker,alyssa.tucker@example.com,3h +20-26708,Russell,Gardner,russell.gardner@example.com,3h +21-57621,Mike,Ruiz,mike.ruiz@example.com,3h +31-80867,Joan,Bates,joan.bates@example.com,3h +14-62165,Stacey,Simpson,stacey.simpson@example.com,3h +04-67087,Nicole,George,nicole.george@example.com,3h +64-42880,Bradley,Ferguson,bradley.ferguson@example.com,3h +48-44235,Carl,Watson,carl.watson@example.com,3h +45-72652,Gabe,Stewart,gabe.stewart@example.com,3h +40-87154,Esther,Stone,esther.stone@example.com,3h +85-12477,Lynn,Torres,lynn.torres@example.com,3h +50-73828,Eileen,Olson,eileen.olson@example.com,3h +80-80515,Pamela,Baker,pamela.baker@example.com,3h +03-74674,Vickie,Olson,vickie.olson@example.com,3h +31-56081,Carla,Kelly,carla.kelly@example.com,3h +84-81280,Alex,Richardson,alex.richardson@example.com,3h +07-63643,Cecil,Matthews,cecil.matthews@example.com,3h +64-07573,Gloria,Hall,gloria.hall@example.com,3h +30-20870,Katie,Bradley,katie.bradley@example.com,3h +88-08108,Delores,Harper,delores.harper@example.com,3h +26-54664,Fred,Kuhn,fred.kuhn@example.com,3h +25-38743,Amy,Walters,amy.walters@example.com,3h +08-55638,Gregory,Johnson,gregory.johnson@example.com,3h +55-01412,Roberto,Williams,roberto.williams@example.com,3h +23-18585,Glen,Hawkins,glen.hawkins@example.com,3h +37-48321,Irma,Rice,irma.rice@example.com,3h +20-26782,Fred,Baker,fred.baker@example.com,3h +13-00283,Gabriel,Gardner,gabriel.gardner@example.com,3h +88-67372,Linda,Richardson,linda.richardson@example.com,3h +33-20578,Emma,Wheeler,emma.wheeler@example.com,3h +38-26737,Beth,Hopkins,beth.hopkins@example.com,3h +15-44534,Terrance,Fox,terrance.fox@example.com,3h +87-25065,Bruce,Turner,bruce.turner@example.com,3h +07-48413,Loretta,Neal,loretta.neal@example.com,3h +13-57244,Sylvia,Peters,sylvia.peters@example.com,3h +50-34061,Nathan,Gutierrez,nathan.gutierrez@example.com,3h +15-18318,Seth,Chapman,seth.chapman@example.com,3h +52-21281,Tanya,Phillips,tanya.phillips@example.com,3h +56-18776,Owen,Burke,owen.burke@example.com,3h +04-30634,Milton,Rose,milton.rose@example.com,3h +86-65183,Amy,Kuhn,amy.kuhn@example.com,3h +24-25687,Jimmie,Moore,jimmie.moore@example.com,3h +42-62318,Lance,Henry,lance.henry@example.com,3h +21-82215,April,Price,april.price@example.com,3h +05-86623,Jeff,Stewart,jeff.stewart@example.com,3h diff --git a/csv/BSIT Courses.csv b/csv/BSIT Courses.csv new file mode 100644 index 0000000..6637a87 --- /dev/null +++ b/csv/BSIT Courses.csv @@ -0,0 +1,24 @@ +"courseCode","description","units","yearLevel" +"IT-TCDISCRT","Discrete Structures","3","1" +"IT-CCPROG2","Computer Programming 2","3","1" +"IT-PURPCOM","Purposive Communication","3","1" +"IT-ARTAPPR","Art Appreciation","3","1" +"IT-GENDSOC","Gender and Society","3","1" +"IT-LVITERA","Living in IT era","3","1" +"IT-PAHFIT2","Rhythmic Activities","2","1" +"IT-NSTP-02","National Service Training Program 2","3","1" +"IT-ACSS","Advanced Computer System Servicing","3","2" +"IT-ELECT2","IT Elective 2 (Object-Oriented Programming)","3","2" +"IT-NET1","Networking 1","3","2" +"IT-ETHICS1","Ethics","3","2" +"IT-FUDBS","Fundamentals of Databse Systems","3","2" +"IT-SSDEV","System Software Design","3","2" +"IT-IPTECH","Integrative Programming and Technologies 1","3","2" +"IT-PAHFIT4","Team Sports and Recreations","2","2" +"IT-JOSERIZ","The life and Works of Jose Rizal","3","3" +"IT-DWADM","Data Warehousing and Data Mining","3","3" +"IT-QUANM","Quantitative Methods","3","3" +"IT-SADMN","System Administration and Maintenance","3","3" +"IT-ELECT4","IT Elective 4(Platform Technologies)","3","3" +"IT-IASEC2","Information Assurance and Security 2","3","3" +"IT-RSRC1","Capstone Project 1","3","3" \ No newline at end of file diff --git a/csv/BSIT Faculty.csv b/csv/BSIT Faculty.csv new file mode 100644 index 0000000..a95470a --- /dev/null +++ b/csv/BSIT Faculty.csv @@ -0,0 +1,31 @@ +facultyId,firstName,lastName,email +22-22204,Marjon,Umbay,marjon.instructor.cdm@gmail.com +50-32545,Monica,Medina,monica.medina@example.com +67-01271,Arthur,Welch,arthur.welch@example.com +13-84820,Craig,Castillo,craig.castillo@example.com +10-70752,Ernest,Gilbert,ernest.gilbert@example.com +07-10834,Stacey,Miller,stacey.miller@example.com +05-52005,Jon,Byrd,jon.byrd@example.com +50-52801,Patsy,Reynolds,patsy.reynolds@example.com +12-26734,Judith,Lucas,judith.lucas@example.com +24-17557,Willie,Gonzales,willie.gonzales@example.com +27-54146,Alan,Diaz,alan.diaz@example.com +17-54046,Clyde,Grant,clyde.grant@example.com +03-80788,Sara,Diaz,sara.diaz@example.com +83-53673,Linda,Mcdonalid,linda.mcdonalid@example.com +60-60337,Roland,Taylor,roland.taylor@example.com +47-30863,Ivan,Gomez,ivan.gomez@example.com +33-26541,Fred,Peterson,fred.peterson@example.com +71-26780,Brent,Turner,brent.turner@example.com +60-22874,Edgar,Robertson,edgar.robertson@example.com +18-76538,Emily,Woods,emily.woods@example.com +47-37806,Jacob,Reid,jacob.reid@example.com +71-84411,Edna,Alvarez,edna.alvarez@example.com +02-82875,Judith,Wilson,judith.wilson@example.com +13-47658,Wayne,Howard,wayne.howard@example.com +60-60585,Aubree,Walker,aubree.walker@example.com +00-36625,Sara,Allen,sara.allen@example.com +07-43674,Ivan,Parker,ivan.parker@example.com +43-53746,Martin,Jacobs,martin.jacobs@example.com +15-08415,Melanie,Mitchelle,melanie.mitchelle@example.com +65-60057,Andrea,Sims,andrea.sims@example.com diff --git a/csv/BSIT Students.csv b/csv/BSIT Students.csv new file mode 100644 index 0000000..4b52e6f --- /dev/null +++ b/csv/BSIT Students.csv @@ -0,0 +1,1001 @@ +studentID,firstName,lastName,email,section +22-00250,Daniel John,Baynosa,danieljohnbyns@gmail.com,2e +70-50102,Armando,Lowe,armando.lowe@example.com,1a +05-34854,April,Peterson,april.peterson@example.com,1a +57-00428,Mia,Allen,mia.allen@example.com,1a +65-03050,Judy,Little,judy.little@example.com,1a +57-62342,Bonnie,Fletcher,bonnie.fletcher@example.com,1a +85-80530,Arron,Reid,arron.reid@example.com,1a +62-07832,Yvonne,Jordan,yvonne.jordan@example.com,1a +60-31520,Ricardo,Kuhn,ricardo.kuhn@example.com,1a +82-60558,Jim,Rhodes,jim.rhodes@example.com,1a +36-56248,Ralph,Black,ralph.black@example.com,1a +02-85127,Gail,Steeves ,gail.steeves@example.com,1a +78-58824,Patsy,Wagner,patsy.wagner@example.com,1a +15-60258,Irma,Rice,irma.rice@example.com,1a +54-12886,Rick,Price,rick.price@example.com,1a +36-02576,Chloe,Baker,chloe.baker@example.com,1a +56-76276,Sue,Reed,sue.reed@example.com,1a +24-40637,Dave,Barrett,dave.barrett@example.com,1a +52-26607,Rita,Williamson,rita.williamson@example.com,1a +70-73157,Arnold,Wade,arnold.wade@example.com,1a +37-33231,Serenity,Rivera,serenity.rivera@example.com,1a +63-05456,Jeremy,Russell,jeremy.russell@example.com,1a +35-77145,Camila,Kim,camila.kim@example.com,1a +31-73055,Henry,Rhodes,henry.rhodes@example.com,1a +08-17465,Dean,Weaver,dean.weaver@example.com,1a +44-26256,Hugh,Peters,hugh.peters@example.com,1a +45-75824,Kelly,Jackson,kelly.jackson@example.com,1a +63-05516,Dylan,Rice,dylan.rice@example.com,1a +54-12280,Marion,Williamson,marion.williamson@example.com,1a +02-06572,Dennis,Hoffman,dennis.hoffman@example.com,1a +17-34847,Julian,Davidson,julian.davidson@example.com,1a +72-74438,Joshua,Fowler,joshua.fowler@example.com,1a +61-80572,Roberta,Gilbert,roberta.gilbert@example.com,1a +06-37516,Daryl,Shelton,daryl.shelton@example.com,1a +75-58737,Stella,Schmidt,stella.schmidt@example.com,1a +57-54662,Myrtle,Lopez,myrtle.lopez@example.com,1a +83-15385,Brian,Romero,brian.romero@example.com,1a +35-12154,Brent,Walters,brent.walters@example.com,1a +52-38144,Kitty,Reid,kitty.reid@example.com,1a +38-27713,Manuel,Neal,manuel.neal@example.com,1a +08-85404,Layla,Carlson,layla.carlson@example.com,1a +74-04081,Kenzi,Henry,kenzi.henry@example.com,1a +00-62532,Ronnie,Elliott,ronnie.elliott@example.com,1a +83-44163,Layla,Baker,layla.baker@example.com,1a +15-20474,Irma,Mendoza,irma.mendoza@example.com,1a +64-60726,Suzanne,Reid,suzanne.reid@example.com,1a +06-33418,Hilda,Riley,hilda.riley@example.com,1a +57-32253,Lillian,Martinez,lillian.martinez@example.com,1a +42-83210,Liam,Wagner,liam.wagner@example.com,1a +55-47014,Debra,Brooks,debra.brooks@example.com,1a +10-55487,Sergio,Murphy,sergio.murphy@example.com,1b +33-54232,Steve,Stanley,steve.stanley@example.com,1b +81-74255,Hailey,Miles,hailey.miles@example.com,1b +61-78848,Stella,Rodriguez,stella.rodriguez@example.com,1b +23-10605,Veronica,Harper,veronica.harper@example.com,1b +13-14787,Rosemary,Nelson,rosemary.nelson@example.com,1b +68-86333,Adam,Spencer,adam.spencer@example.com,1b +83-50533,Oscar,Garza,oscar.garza@example.com,1b +05-08633,Glenda,Marshall,glenda.marshall@example.com,1b +16-24086,Carolyn,Roberts,carolyn.roberts@example.com,1b +11-64124,Cassandra,Hunt,cassandra.hunt@example.com,1b +14-87372,Adam,Holt,adam.holt@example.com,1b +24-58458,Jon,Cox,jon.cox@example.com,1b +81-28654,Erica,Marshall,erica.marshall@example.com,1b +10-68367,Roberta,Moreno,roberta.moreno@example.com,1b +48-27033,Austin,Gilbert,austin.gilbert@example.com,1b +37-77241,Clifton,Davidson,clifton.davidson@example.com,1b +61-13378,Todd,Pierce,todd.pierce@example.com,1b +84-80837,Jennifer,Adams,jennifer.adams@example.com,1b +33-44587,Carlos,Graves,carlos.graves@example.com,1b +43-82850,Jeanne,Perkins,jeanne.perkins@example.com,1b +61-14102,Miriam,Douglas,miriam.douglas@example.com,1b +07-71140,Lois,Holland,lois.holland@example.com,1b +21-66261,Scott,Weaver,scott.weaver@example.com,1b +07-81630,Sheila,Sutton,sheila.sutton@example.com,1b +74-33447,Marian,Sanchez,marian.sanchez@example.com,1b +22-61054,Clifford,Hoffman,clifford.hoffman@example.com,1b +34-88276,Miguel,Jennings,miguel.jennings@example.com,1b +31-81553,Isobel,Kennedy,isobel.kennedy@example.com,1b +63-24828,Dawn,Stone,dawn.stone@example.com,1b +48-86301,Priscilla,Hanson,priscilla.hanson@example.com,1b +24-25508,Terry,Brooks,terry.brooks@example.com,1b +54-77887,Ashley,Carter,ashley.carter@example.com,1b +57-14208,Nora,Hunt,nora.hunt@example.com,1b +80-36177,Calvin,Mckinney,calvin.mckinney@example.com,1b +15-27042,Carmen,Simmons,carmen.simmons@example.com,1b +43-78510,Franklin,Brown,franklin.brown@example.com,1b +11-06154,Roland,Patterson,roland.patterson@example.com,1b +65-87565,Sean,Barrett,sean.barrett@example.com,1b +48-83485,Kylie,Terry,kylie.terry@example.com,1b +81-11865,Tracy,Castro,tracy.castro@example.com,1b +61-45422,Erika,Fox,erika.fox@example.com,1b +80-84064,Norma,Shelton,norma.shelton@example.com,1b +62-76175,Melissa,Ryan,melissa.ryan@example.com,1b +68-51801,Scarlett,Marshall,scarlett.marshall@example.com,1b +44-46017,Francis,Gray,francis.gray@example.com,1b +38-34561,Marilyn,Gordon,marilyn.gordon@example.com,1b +03-16300,Dora,Patterson,dora.patterson@example.com,1b +03-45841,Marsha,Herrera,marsha.herrera@example.com,1b +88-76544,Rebecca,Kim,rebecca.kim@example.com,1b +22-67366,Gabriel,Stanley,gabriel.stanley@example.com,1c +64-34471,Judy,Gregory,judy.gregory@example.com,1c +53-07480,Pauline,Carroll,pauline.carroll@example.com,1c +20-55820,Melvin,Patterson,melvin.patterson@example.com,1c +38-38156,Taylor,Rose,taylor.rose@example.com,1c +24-58077,Alexis,White,alexis.white@example.com,1c +65-88534,Peyton,Fox,peyton.fox@example.com,1c +66-56677,Myrtle,Gilbert,myrtle.gilbert@example.com,1c +16-51131,Jack,Mckinney,jack.mckinney@example.com,1c +36-73700,Mary,Robertson,mary.robertson@example.com,1c +14-44412,Miriam,Washington,miriam.washington@example.com,1c +47-63105,Rhonda,Johnston,rhonda.johnston@example.com,1c +32-12337,Camila,Wright,camila.wright@example.com,1c +56-15408,Micheal,Garcia,micheal.garcia@example.com,1c +13-41484,Jerome,Willis,jerome.willis@example.com,1c +58-34131,Addison,Bennett,addison.bennett@example.com,1c +88-45016,Clinton,Fowler,clinton.fowler@example.com,1c +44-48028,Veronica,Medina,veronica.medina@example.com,1c +54-87651,Vicki,Jackson,vicki.jackson@example.com,1c +00-82188,Neil,Elliott,neil.elliott@example.com,1c +46-78105,Lori,Bell,lori.bell@example.com,1c +33-31518,Ross,Harvey,ross.harvey@example.com,1c +37-55335,Caroline,Willis,caroline.willis@example.com,1c +51-88868,Eugene,Stewart,eugene.stewart@example.com,1c +18-21102,Steven,Sanchez,steven.sanchez@example.com,1c +37-02512,Derrick,Moore,derrick.moore@example.com,1c +37-86522,Samuel,Kennedy,samuel.kennedy@example.com,1c +34-60051,Darlene,Castro,darlene.castro@example.com,1c +34-67025,Christine,Fernandez,christine.fernandez@example.com,1c +88-53584,Charlotte,White,charlotte.white@example.com,1c +18-32426,Marion,George,marion.george@example.com,1c +21-71810,Leo,Burns,leo.burns@example.com,1c +10-17640,Tamara,Franklin,tamara.franklin@example.com,1c +47-53670,Candice,Allen,candice.allen@example.com,1c +22-84672,Darrell,Castro,darrell.castro@example.com,1c +16-71456,Veronica,Evans,veronica.evans@example.com,1c +81-70464,Martin,Allen,martin.allen@example.com,1c +82-36885,Nathan,Silva,nathan.silva@example.com,1c +27-53701,Hector,Richardson,hector.richardson@example.com,1c +15-70101,Kaylee,Dunn,kaylee.dunn@example.com,1c +38-67844,Armando,Garrett,armando.garrett@example.com,1c +88-12033,Frank,Holt,frank.holt@example.com,1c +11-57060,Isobel,Howell,isobel.howell@example.com,1c +30-45887,Eric,Pena,eric.pena@example.com,1c +36-02283,Antonio,Peck,antonio.peck@example.com,1c +77-51737,Martin,Fuller,martin.fuller@example.com,1c +20-07483,Isobel,Gilbert,isobel.gilbert@example.com,1c +56-48510,Kenzi,Shaw,kenzi.shaw@example.com,1c +88-61143,Diana,Green,diana.green@example.com,1c +83-30164,Alfredo,Richards,alfredo.richards@example.com,1c +23-11022,Gabriel,Robinson,gabriel.robinson@example.com,1d +82-47662,Riley,Wood,riley.wood@example.com,1d +56-70167,Eva,Cox,eva.cox@example.com,1d +84-38574,Miriam,Mckinney,miriam.mckinney@example.com,1d +27-81254,Martha,Elliott,martha.elliott@example.com,1d +00-23886,Gregory,Weaver,gregory.weaver@example.com,1d +37-05585,Cecil,Cole,cecil.cole@example.com,1d +86-82322,Stella,Rice,stella.rice@example.com,1d +08-46802,Chester,Allen,chester.allen@example.com,1d +64-74556,Oscar,Robinson,oscar.robinson@example.com,1d +60-03284,Darrell,Lambert,darrell.lambert@example.com,1d +66-88008,Carla,Pearson,carla.pearson@example.com,1d +40-64276,Denise,Brewer,denise.brewer@example.com,1d +26-74662,Noah,Gardner,noah.gardner@example.com,1d +18-77333,Lauren,Obrien,lauren.obrien@example.com,1d +58-17570,Holly,Cooper,holly.cooper@example.com,1d +45-76020,Seth,Hoffman,seth.hoffman@example.com,1d +14-76682,Sharlene,Chapman,sharlene.chapman@example.com,1d +65-44208,Luke,Castillo,luke.castillo@example.com,1d +33-28762,Bernice,Foster,bernice.foster@example.com,1d +70-11658,Terrance,Perkins,terrance.perkins@example.com,1d +31-68475,Chad,Wagner,chad.wagner@example.com,1d +23-52043,Lawrence,Medina,lawrence.medina@example.com,1d +43-63743,Arron,Bryant,arron.bryant@example.com,1d +50-50773,Stacey,Brewer,stacey.brewer@example.com,1d +05-60427,Marion,Foster,marion.foster@example.com,1d +40-60026,Nelson,Dean,nelson.dean@example.com,1d +41-33654,Gilbert,Kuhn,gilbert.kuhn@example.com,1d +50-25823,Albert,Barnes,albert.barnes@example.com,1d +30-46204,Rita,Perry,rita.perry@example.com,1d +16-60742,Jamie,Clark,jamie.clark@example.com,1d +04-65331,Kristin,Burns,kristin.burns@example.com,1d +87-34372,Mia,Jackson,mia.jackson@example.com,1d +30-07731,Sally,Hudson,sally.hudson@example.com,1d +27-42343,Tanya,Nelson,tanya.nelson@example.com,1d +78-11065,Letitia,Coleman,letitia.coleman@example.com,1d +21-65514,Billy,Sutton,billy.sutton@example.com,1d +50-82643,Lisa,Rivera,lisa.rivera@example.com,1d +36-60705,Melinda,Montgomery,melinda.montgomery@example.com,1d +42-46253,Sarah,Gibson,sarah.gibson@example.com,1d +78-13254,Barry,Miles,barry.miles@example.com,1d +27-50524,Lauren,Elliott,lauren.elliott@example.com,1d +52-70467,Tanya,Payne,tanya.payne@example.com,1d +47-42660,Terry,Pierce,terry.pierce@example.com,1d +67-02747,Alicia,Owens,alicia.owens@example.com,1d +40-72168,Alyssa,Schmidt,alyssa.schmidt@example.com,1d +75-33724,Jacqueline,Cruz,jacqueline.cruz@example.com,1d +18-56235,Jamie,Welch,jamie.welch@example.com,1d +47-47327,Ethan,Gardner,ethan.gardner@example.com,1d +38-77636,Jackie,Griffin,jackie.griffin@example.com,1d +37-07245,Jon,Richardson,jon.richardson@example.com,1e +26-26750,Joe,Sims,joe.sims@example.com,1e +45-45766,Annette,Duncan,annette.duncan@example.com,1e +10-55372,Liam,Fisher,liam.fisher@example.com,1e +68-28038,Lucas,Perez,lucas.perez@example.com,1e +26-25261,Laurie,Hunter,laurie.hunter@example.com,1e +26-74162,Joan,Moore,joan.moore@example.com,1e +03-33255,Ruben,Dean,ruben.dean@example.com,1e +50-15405,Mae,Tucker,mae.tucker@example.com,1e +31-47368,Edwin,Hughes,edwin.hughes@example.com,1e +21-00538,Philip,Matthews,philip.matthews@example.com,1e +15-23107,Derek,Carlson,derek.carlson@example.com,1e +67-51427,Billy,Reid,billy.reid@example.com,1e +10-73425,Todd,Lowe,todd.lowe@example.com,1e +11-84533,Rene,Franklin,rene.franklin@example.com,1e +06-03001,Lillie,Bates,lillie.bates@example.com,1e +54-05581,Penny,Vasquez,penny.vasquez@example.com,1e +18-38587,Walter,Holt,walter.holt@example.com,1e +62-88181,Joan,Hart,joan.hart@example.com,1e +63-52887,Felix,Rodriguez,felix.rodriguez@example.com,1e +22-82131,Bryan,Smith,bryan.smith@example.com,1e +78-21156,Sergio,Lewis,sergio.lewis@example.com,1e +40-43224,Dianne,Romero,dianne.romero@example.com,1e +77-66856,Christopher,Nguyen,christopher.nguyen@example.com,1e +05-83753,Jared,Owens,jared.owens@example.com,1e +21-54223,Ana,Austin,ana.austin@example.com,1e +78-50480,Vernon,Brooks,vernon.brooks@example.com,1e +74-18717,Melvin,Russell,melvin.russell@example.com,1e +67-26362,Irma,Schmidt,irma.schmidt@example.com,1e +83-07770,Ruby,Hunt,ruby.hunt@example.com,1e +10-71210,Hugh,Chavez,hugh.chavez@example.com,1e +50-78877,Sarah,Bradley,sarah.bradley@example.com,1e +36-68085,Jennie,Hunter,jennie.hunter@example.com,1e +27-64867,Adrian,West,adrian.west@example.com,1e +25-58302,Andy,Carroll,andy.carroll@example.com,1e +83-25760,Leonard,Grant,leonard.grant@example.com,1e +15-54750,Olivia,Brown,olivia.brown@example.com,1e +22-87744,Miguel,Flores,miguel.flores@example.com,1e +65-73183,Diana,Rice,diana.rice@example.com,1e +66-56584,Carlos,Myers,carlos.myers@example.com,1e +80-63123,Ted,Hopkins,ted.hopkins@example.com,1e +14-02245,Joanne,Banks,joanne.banks@example.com,1e +66-41082,Mia,Fox,mia.fox@example.com,1e +53-44647,Timmothy,Mills,timmothy.mills@example.com,1e +35-24563,Lee,Gregory,lee.gregory@example.com,1e +68-56823,Joy,Kelley,joy.kelley@example.com,1e +77-32103,Edith,Carroll,edith.carroll@example.com,1e +86-71851,Joan,Wright,joan.wright@example.com,1e +03-82806,Zoey,Lucas,zoey.lucas@example.com,1e +31-13267,Cassandra,Taylor,cassandra.taylor@example.com,1e +22-21028,Rosa,Oliver,rosa.oliver@example.com,1f +20-83553,Cory,Harrison,cory.harrison@example.com,1f +67-02025,Felix,Moreno,felix.moreno@example.com,1f +57-01355,Cathy,Gomez,cathy.gomez@example.com,1f +66-26662,Louis,Knight,louis.knight@example.com,1f +65-33572,Alfredo,Pierce,alfredo.pierce@example.com,1f +54-70621,Brianna,Mendoza,brianna.mendoza@example.com,1f +54-88841,Matthew,Oliver,matthew.oliver@example.com,1f +64-83508,Herman,Gordon,herman.gordon@example.com,1f +18-73140,Cameron,Hawkins,cameron.hawkins@example.com,1f +81-47111,Evan,Freeman,evan.freeman@example.com,1f +10-57624,Dean,Jones,dean.jones@example.com,1f +38-34643,Alexander,Robinson,alexander.robinson@example.com,1f +81-32400,Robin,Henry,robin.henry@example.com,1f +51-37878,Daisy,Wells,daisy.wells@example.com,1f +64-67266,Olivia,Ford,olivia.ford@example.com,1f +33-55806,Connie,Simmmons,connie.simmmons@example.com,1f +71-88705,Isaiah,Wheeler,isaiah.wheeler@example.com,1f +45-23701,Tim,Shelton,tim.shelton@example.com,1f +75-38826,Shelly,Hughes,shelly.hughes@example.com,1f +30-21608,Valerie,Burton,valerie.burton@example.com,1f +83-06865,Ida,Harper,ida.harper@example.com,1f +72-70263,Brooklyn,Burns,brooklyn.burns@example.com,1f +42-72471,Lorraine,Ortiz,lorraine.ortiz@example.com,1f +24-30875,Mason,Wade,mason.wade@example.com,1f +01-55086,Esther,Reid,esther.reid@example.com,1f +24-52810,Nina,Hamilton,nina.hamilton@example.com,1f +73-02500,Lynn,Perry,lynn.perry@example.com,1f +78-74460,Nina,Fleming,nina.fleming@example.com,1f +06-12850,Alan,Garcia,alan.garcia@example.com,1f +64-44650,Terry,Collins,terry.collins@example.com,1f +71-62145,Debbie,Reid,debbie.reid@example.com,1f +34-06810,Irma,Sanders,irma.sanders@example.com,1f +16-47140,Tracy,Morales,tracy.morales@example.com,1f +52-35828,Misty,Richards,misty.richards@example.com,1f +00-80556,Hugh,Young,hugh.young@example.com,1f +67-26084,Ross,Fowler,ross.fowler@example.com,1f +58-86411,Laurie,Welch,laurie.welch@example.com,1f +06-02752,Phyllis,Montgomery,phyllis.montgomery@example.com,1f +14-42325,Jimmie,Matthews,jimmie.matthews@example.com,1f +37-66850,Alan,Sutton,alan.sutton@example.com,1f +28-43216,Alma,Collins,alma.collins@example.com,1f +33-21842,Elizabeth,Thomas,elizabeth.thomas@example.com,1f +11-06237,Vera,Graham,vera.graham@example.com,1f +34-76855,Same,Bryant,same.bryant@example.com,1f +47-18160,Ben,Snyder,ben.snyder@example.com,1f +34-12704,Glenda,Marshall,glenda.marshall@example.com,1f +48-23767,Cherly,Beck,cherly.beck@example.com,1f +26-25004,Dwight,Mitchelle,dwight.mitchelle@example.com,1f +22-52332,Bobbie,Martinez,bobbie.martinez@example.com,1f +07-12833,Victoria,Perry,victoria.perry@example.com,2a +71-75666,Lillian,Payne,lillian.payne@example.com,2a +15-67166,Lucy,Hunter,lucy.hunter@example.com,2a +64-60675,Misty,Ford,misty.ford@example.com,2a +12-87477,Marie,Burns,marie.burns@example.com,2a +34-30752,Gordon,Black,gordon.black@example.com,2a +38-47048,Priscilla,Rivera,priscilla.rivera@example.com,2a +41-70432,Loretta,Newman,loretta.newman@example.com,2a +66-64561,Thomas,Fernandez,thomas.fernandez@example.com,2a +53-35108,Addison,Alvarez,addison.alvarez@example.com,2a +44-75680,Irene,Campbell,irene.campbell@example.com,2a +04-15564,April,Lowe,april.lowe@example.com,2a +55-15147,Fernando,Curtis,fernando.curtis@example.com,2a +05-37886,Brent,Bishop,brent.bishop@example.com,2a +65-85133,Lucy,Gardner,lucy.gardner@example.com,2a +05-44806,Mario,Morrison,mario.morrison@example.com,2a +03-73864,Daisy,Phillips,daisy.phillips@example.com,2a +46-78268,Seth,Brown,seth.brown@example.com,2a +27-23431,Charlie,May,charlie.may@example.com,2a +40-14300,Terri,James,terri.james@example.com,2a +84-82700,Darrell,Terry,darrell.terry@example.com,2a +02-65488,Jerry,Elliott,jerry.elliott@example.com,2a +04-51604,Derrick,Stanley,derrick.stanley@example.com,2a +17-82015,Melanie,Oliver,melanie.oliver@example.com,2a +57-38832,Holly,Chapman,holly.chapman@example.com,2a +64-20883,Ruby,Carroll,ruby.carroll@example.com,2a +26-67033,Dean,Holland,dean.holland@example.com,2a +55-12273,Bertha,Caldwell,bertha.caldwell@example.com,2a +34-06323,Debbie,Ruiz,debbie.ruiz@example.com,2a +78-57811,Hilda,Hayes,hilda.hayes@example.com,2a +46-17260,Eileen,Fleming,eileen.fleming@example.com,2a +77-26821,Julie,Fowler,julie.fowler@example.com,2a +42-62230,Rita,Fleming,rita.fleming@example.com,2a +54-76780,Leslie,Gregory,leslie.gregory@example.com,2a +20-52446,Mia,Owens,mia.owens@example.com,2a +85-18642,Marcia,Stanley,marcia.stanley@example.com,2a +47-82314,Rafael,Rose,rafael.rose@example.com,2a +17-12846,Hunter,Craig,hunter.craig@example.com,2a +84-73040,George,Ramirez,george.ramirez@example.com,2a +73-55524,Noelle,Alexander,noelle.alexander@example.com,2a +35-07717,Calvin,Carpenter,calvin.carpenter@example.com,2a +81-26547,Louise,Frazier,louise.frazier@example.com,2a +84-00380,Elaine,Hughes,elaine.hughes@example.com,2a +70-78852,Sophia,Meyer,sophia.meyer@example.com,2a +33-47652,Owen,Barnett,owen.barnett@example.com,2a +01-21472,Alex,Bates,alex.bates@example.com,2a +83-27036,Bruce,Larson,bruce.larson@example.com,2a +61-45237,Adam,Morrison,adam.morrison@example.com,2a +43-10207,Michelle,Peterson,michelle.peterson@example.com,2a +48-32347,Constance,Webb,constance.webb@example.com,2a +75-60070,Isobel,Ford,isobel.ford@example.com,2b +17-38215,Eli,Porter,eli.porter@example.com,2b +82-74271,Tammy,Richardson,tammy.richardson@example.com,2b +47-02481,Carolyn,Hill,carolyn.hill@example.com,2b +58-23424,Harry,Adams,harry.adams@example.com,2b +01-42552,Theresa,Wilson,theresa.wilson@example.com,2b +51-17456,Derek,Rivera,derek.rivera@example.com,2b +13-05823,Kenzi,Curtis,kenzi.curtis@example.com,2b +67-78171,Alberto,Holland,alberto.holland@example.com,2b +67-35043,Norman,Mitchell,norman.mitchell@example.com,2b +84-14024,Alicia,Pearson,alicia.pearson@example.com,2b +45-82338,Constance,Gilbert,constance.gilbert@example.com,2b +72-01066,Nellie,Jennings,nellie.jennings@example.com,2b +74-63651,Mathew,Howell,mathew.howell@example.com,2b +07-35638,Gerald,Knight,gerald.knight@example.com,2b +24-10122,Cherly,Foster,cherly.foster@example.com,2b +11-48648,Danny,Riley,danny.riley@example.com,2b +13-05228,Emily,Thomas,emily.thomas@example.com,2b +34-81766,Alvin,Hernandez,alvin.hernandez@example.com,2b +11-76768,Jose,Arnold,jose.arnold@example.com,2b +27-55731,Wanda,Taylor,wanda.taylor@example.com,2b +11-56602,Andre,Payne,andre.payne@example.com,2b +74-33442,Rodney,Ellis,rodney.ellis@example.com,2b +38-53382,Ritthy,Moore,ritthy.moore@example.com,2b +34-02457,Nevaeh,Dean,nevaeh.dean@example.com,2b +52-35228,Yvonne,Lowe,yvonne.lowe@example.com,2b +13-05103,Herbert,Gibson,herbert.gibson@example.com,2b +67-83424,Stephanie,Matthews,stephanie.matthews@example.com,2b +20-38113,Doris,Martin,doris.martin@example.com,2b +83-26610,Steven,Phillips,steven.phillips@example.com,2b +64-34023,Juanita,Andrews,juanita.andrews@example.com,2b +01-84114,Lois,Garcia,lois.garcia@example.com,2b +85-46463,Joel,Burke,joel.burke@example.com,2b +53-67080,Jeanette,Jensen,jeanette.jensen@example.com,2b +84-78812,Amanda,Reynolds,amanda.reynolds@example.com,2b +61-70784,George,Campbell,george.campbell@example.com,2b +38-68534,Harper,Ward,harper.ward@example.com,2b +46-78537,Alexis,Davidson,alexis.davidson@example.com,2b +05-28115,Sergio,Olson,sergio.olson@example.com,2b +45-83603,Donald,Newman,donald.newman@example.com,2b +53-77376,Jane,Mitchell,jane.mitchell@example.com,2b +85-84701,Jesse,West,jesse.west@example.com,2b +66-17888,Mario,Snyder,mario.snyder@example.com,2b +68-67042,Bernard,Cox,bernard.cox@example.com,2b +00-28286,Manuel,Craig,manuel.craig@example.com,2b +82-28264,Anne,Coleman,anne.coleman@example.com,2b +61-50786,Jimmie,Washington,jimmie.washington@example.com,2b +77-30046,Daryl,Jordan,daryl.jordan@example.com,2b +52-86025,Wyatt,Anderson,wyatt.anderson@example.com,2b +06-55342,Sarah,Medina,sarah.medina@example.com,2b +20-67115,Christy,Nichols,christy.nichols@example.com,2c +31-13708,Mason,Banks,mason.banks@example.com,2c +46-56841,Rick,Weaver,rick.weaver@example.com,2c +54-63260,Dora,Bennett,dora.bennett@example.com,2c +38-41218,Lena,Gregory,lena.gregory@example.com,2c +52-24232,Florence,Simmons,florence.simmons@example.com,2c +33-56442,Esther,Hayes,esther.hayes@example.com,2c +06-32756,Martha,Gordon,martha.gordon@example.com,2c +35-40510,Todd,Gilbert,todd.gilbert@example.com,2c +72-44746,Hector,Watkins,hector.watkins@example.com,2c +65-41636,Louella,Herrera,louella.herrera@example.com,2c +21-87081,Lori,Gilbert,lori.gilbert@example.com,2c +61-07662,Sheila,Richardson,sheila.richardson@example.com,2c +30-68457,Amber,Carr,amber.carr@example.com,2c +20-05836,Nicholas,Hansen,nicholas.hansen@example.com,2c +68-66186,Melinda,Frazier,melinda.frazier@example.com,2c +54-73368,Landon,Byrd,landon.byrd@example.com,2c +21-64232,Seth,Armstrong,seth.armstrong@example.com,2c +34-51408,Allan,Stewart,allan.stewart@example.com,2c +82-58727,John,Carr,john.carr@example.com,2c +86-57167,Nelson,Bryant,nelson.bryant@example.com,2c +77-04364,Diane,Hayes,diane.hayes@example.com,2c +74-86642,Constance,Black,constance.black@example.com,2c +02-88710,Claude,Hart,claude.hart@example.com,2c +78-84402,Peyton,Peters,peyton.peters@example.com,2c +28-57280,Janet,Wheeler,janet.wheeler@example.com,2c +88-16523,Ritthy,Williams,ritthy.williams@example.com,2c +18-26651,Scott,Ramirez,scott.ramirez@example.com,2c +52-26518,Terrence,Hart,terrence.hart@example.com,2c +62-71848,Bradley,Green,bradley.green@example.com,2c +04-52021,Marc,Jordan,marc.jordan@example.com,2c +04-10340,Danielle,Rodriquez,danielle.rodriquez@example.com,2c +54-78278,Juanita,Foster,juanita.foster@example.com,2c +72-41453,Megan,Carroll,megan.carroll@example.com,2c +68-04448,Marion,Harvey,marion.harvey@example.com,2c +02-03245,Everett,Shelton,everett.shelton@example.com,2c +24-20114,Zachary,Wade,zachary.wade@example.com,2c +20-70467,Kathryn,Meyer,kathryn.meyer@example.com,2c +28-20750,Seth,Barnett,seth.barnett@example.com,2c +55-10081,Jane,Sutton,jane.sutton@example.com,2c +13-80483,Luke,Davis,luke.davis@example.com,2c +85-36205,Erik,Lee,erik.lee@example.com,2c +78-34127,Rhonda,Gonzales,rhonda.gonzales@example.com,2c +38-33843,Kathy,Perkins,kathy.perkins@example.com,2c +06-07005,Letitia,Clark,letitia.clark@example.com,2c +83-32831,Ellen,Morales,ellen.morales@example.com,2c +03-12223,Penny,Jennings,penny.jennings@example.com,2c +42-28421,Andrea,Butler,andrea.butler@example.com,2c +82-12181,Florence,Kuhn,florence.kuhn@example.com,2c +10-64574,Leo,Jennings,leo.jennings@example.com,2c +04-76316,Loretta,Howell,loretta.howell@example.com,2d +73-53503,Patsy,Richardson,patsy.richardson@example.com,2d +80-61774,Sandra,Edwards,sandra.edwards@example.com,2d +60-40233,Janet,Adams,janet.adams@example.com,2d +03-40646,Melissa,Torres,melissa.torres@example.com,2d +06-53121,Raymond,Morgan,raymond.morgan@example.com,2d +65-83363,Cindy,Sullivan,cindy.sullivan@example.com,2d +84-81336,Leona,Torres,leona.torres@example.com,2d +23-11408,Jimmie,Craig,jimmie.craig@example.com,2d +76-21813,Brandon,Day,brandon.day@example.com,2d +11-45681,Bradley,Stanley,bradley.stanley@example.com,2d +50-12015,Ernest,Nguyen,ernest.nguyen@example.com,2d +68-37841,Sonia,Nelson,sonia.nelson@example.com,2d +82-11233,Hailey,Ryan,hailey.ryan@example.com,2d +70-17502,Bob,Butler,bob.butler@example.com,2d +31-48617,Celina,Holmes,celina.holmes@example.com,2d +64-68332,Cassandra,Gonzalez,cassandra.gonzalez@example.com,2d +20-86308,Leo,Lawrence,leo.lawrence@example.com,2d +33-38350,Yolanda,Richards,yolanda.richards@example.com,2d +11-85375,Richard,Coleman,richard.coleman@example.com,2d +58-45616,Abigail,Bennett,abigail.bennett@example.com,2d +74-77518,Eugene,Silva,eugene.silva@example.com,2d +05-77408,Allan,Barnes,allan.barnes@example.com,2d +50-17640,Dora,Hunter,dora.hunter@example.com,2d +68-58802,Michelle,Rhodes,michelle.rhodes@example.com,2d +14-45217,Liam,Gardner,liam.gardner@example.com,2d +25-67067,Joshua,Payne,joshua.payne@example.com,2d +12-58162,Priscilla,Kim,priscilla.kim@example.com,2d +27-50565,Maureen,Williamson,maureen.williamson@example.com,2d +41-42617,Lisa,Adams,lisa.adams@example.com,2d +34-52106,Julia,Mendoza,julia.mendoza@example.com,2d +67-15106,Norma,Kuhn,norma.kuhn@example.com,2d +75-48740,Seth,Beck,seth.beck@example.com,2d +03-87057,Leta,Ford,leta.ford@example.com,2d +67-65148,Ross,Lawrence,ross.lawrence@example.com,2d +31-67643,Harry,Kim,harry.kim@example.com,2d +77-31875,Elmer,Gilbert,elmer.gilbert@example.com,2d +70-13807,Zachary,James,zachary.james@example.com,2d +81-11037,Norma,Rose,norma.rose@example.com,2d +38-15665,Alvin,Wheeler,alvin.wheeler@example.com,2d +46-67624,Stacy,Wright,stacy.wright@example.com,2d +16-13572,Jason,West,jason.west@example.com,2d +56-23738,Andy,Black,andy.black@example.com,2d +17-45613,Michael,Curtis,michael.curtis@example.com,2d +01-55434,Derek,Washington,derek.washington@example.com,2d +38-25778,Joel,Gibson,joel.gibson@example.com,2d +67-11360,Jayden,Ferguson,jayden.ferguson@example.com,2d +31-06376,Robert,Hall,robert.hall@example.com,2d +52-47378,Terrance,Ramirez,terrance.ramirez@example.com,2d +88-64476,Frederick,Rogers,frederick.rogers@example.com,2d +00-51670,Joy,Simpson,joy.simpson@example.com,2e +17-23764,Fernando,Gordon,fernando.gordon@example.com,2e +78-35143,Ashley,Mccoy,ashley.mccoy@example.com,2e +65-81362,Clifton,Pierce,clifton.pierce@example.com,2e +58-70500,Margie,Watkins,margie.watkins@example.com,2e +85-42264,Bella,Porter,bella.porter@example.com,2e +03-81624,Byron,Boyd,byron.boyd@example.com,2e +33-33078,Francis,Lynch,francis.lynch@example.com,2e +63-68618,Andrea,Montgomery,andrea.montgomery@example.com,2e +58-36065,Jeff,Carter,jeff.carter@example.com,2e +38-48453,Rick,Miller,rick.miller@example.com,2e +25-44164,Connor,Mendoza,connor.mendoza@example.com,2e +78-48767,Nina,Steward,nina.steward@example.com,2e +27-05056,Ethan,Kuhn,ethan.kuhn@example.com,2e +75-06371,Jennie,Long,jennie.long@example.com,2e +22-31428,Ian,Russell,ian.russell@example.com,2e +04-82155,Tina,Hill,tina.hill@example.com,2e +88-03558,Renee,Gray,renee.gray@example.com,2e +46-81244,Hunter,Day,hunter.day@example.com,2e +17-54546,Stacy,Steeves ,stacy.steeves@example.com,2e +51-33470,Jessie,Mitchell,jessie.mitchell@example.com,2e +83-71222,Robert,Foster,robert.foster@example.com,2e +58-21322,Virgil,Wood,virgil.wood@example.com,2e +04-70121,Danielle,Russell,danielle.russell@example.com,2e +48-38057,Troy,Payne,troy.payne@example.com,2e +74-66356,Byron,Washington,byron.washington@example.com,2e +61-41360,Ernest,Holmes,ernest.holmes@example.com,2e +55-62208,George,Dixon,george.dixon@example.com,2e +05-20603,Judith,Berry,judith.berry@example.com,2e +36-60265,Toni,Chambers,toni.chambers@example.com,2e +42-35362,Rodney,Peters,rodney.peters@example.com,2e +21-85853,Jorge,Castro,jorge.castro@example.com,2e +23-74557,Terrence,Perry,terrence.perry@example.com,2e +53-76217,Monica,Henry,monica.henry@example.com,2e +10-73486,Joanne,Warren,joanne.warren@example.com,2e +68-64041,Marion,Hansen,marion.hansen@example.com,2e +57-84256,Lori,Frazier,lori.frazier@example.com,2e +45-13211,Aubrey,Ray,aubrey.ray@example.com,2e +56-48317,Samuel,Moore,samuel.moore@example.com,2e +32-01743,Carmen,Holt,carmen.holt@example.com,2e +30-35634,Laurie,Carlson,laurie.carlson@example.com,2e +18-22115,Erika,Perkins,erika.perkins@example.com,2e +50-36482,Shelly,Lowe,shelly.lowe@example.com,2e +08-84414,Wayne,Nichols,wayne.nichols@example.com,2e +88-15063,Jim,Watts,jim.watts@example.com,2e +86-01771,Ronald,Pearson,ronald.pearson@example.com,2e +78-68214,Harry,Lawrence,harry.lawrence@example.com,2e +12-56142,Julia,Shaw,julia.shaw@example.com,2e +52-30766,Miriam,Henry,miriam.henry@example.com,2e +47-27507,Alma,Bishop,alma.bishop@example.com,2e +01-36477,Liam,Lopez,liam.lopez@example.com,2g +57-16805,Elsie,Fowler,elsie.fowler@example.com,2g +14-84121,Samuel,Simpson,samuel.simpson@example.com,2g +12-24580,Brad,Bryant,brad.bryant@example.com,2g +73-74132,Shannon,Moore,shannon.moore@example.com,2g +52-07587,Travis,Andrews,travis.andrews@example.com,2g +14-02564,Annette,Vasquez,annette.vasquez@example.com,2g +23-78271,Aubrey,Young,aubrey.young@example.com,2g +62-10401,Vincent,Bishop,vincent.bishop@example.com,2g +31-52383,Alexa,Sanders,alexa.sanders@example.com,2g +80-05642,Zachary,Russell,zachary.russell@example.com,2g +86-08486,Stacey,Wallace,stacey.wallace@example.com,2g +48-74008,Rick,Webb,rick.webb@example.com,2g +52-21722,Christy,Gilbert,christy.gilbert@example.com,2g +72-02502,Steve,Douglas,steve.douglas@example.com,2g +46-17452,Brandie,Silva,brandie.silva@example.com,2g +76-11712,Dave,Torres,dave.torres@example.com,2g +75-03384,Gavin,Perry,gavin.perry@example.com,2g +07-16446,Zachary,Shaw,zachary.shaw@example.com,2g +72-57711,Kenneth,Wagner,kenneth.wagner@example.com,2g +04-48844,Kylie,Duncan,kylie.duncan@example.com,2g +78-85114,Derek,Gray,derek.gray@example.com,2g +31-18102,Debbie,Morgan,debbie.morgan@example.com,2g +58-44524,Christopher,Burton,christopher.burton@example.com,2g +40-62624,Jamie,Kelly,jamie.kelly@example.com,2g +17-70668,Deann,Curtis,deann.curtis@example.com,2g +27-75144,June,Wells,june.wells@example.com,2g +04-66122,Jorge,Brewer,jorge.brewer@example.com,2g +46-47481,Lucille,Kuhn,lucille.kuhn@example.com,2g +47-24047,Juanita,Williamson,juanita.williamson@example.com,2g +84-65311,Dustin,King,dustin.king@example.com,2g +68-44063,Alma,Hunter,alma.hunter@example.com,2g +31-38457,Brett,Spencer,brett.spencer@example.com,2g +38-34671,Marion,Johnston,marion.johnston@example.com,2g +37-05166,Jacqueline,Crawford,jacqueline.crawford@example.com,2g +62-25432,Kristen,Jenkins,kristen.jenkins@example.com,2g +52-07526,Jane,Mccoy,jane.mccoy@example.com,2g +21-03680,Dave,Porter,dave.porter@example.com,2g +05-75617,Ellen,Wood,ellen.wood@example.com,2g +65-88073,Stephen,Bryant,stephen.bryant@example.com,2g +02-23038,Felecia,Gardner,felecia.gardner@example.com,2g +84-55006,Charlene,Thompson,charlene.thompson@example.com,2g +06-72614,George,Lane,george.lane@example.com,2g +40-80154,Anne,Curtis,anne.curtis@example.com,2g +72-38718,Roberta,Pearson,roberta.pearson@example.com,2g +38-46723,Jennie,Chapman,jennie.chapman@example.com,2g +31-43627,Gladys,Pearson,gladys.pearson@example.com,2g +36-65635,Melvin,Lowe,melvin.lowe@example.com,2g +86-06477,Felecia,Herrera,felecia.herrera@example.com,2g +83-76887,Sue,Dunn,sue.dunn@example.com,2g +22-54380,Suzanne,Lynch,suzanne.lynch@example.com,2h +42-14067,Jenny,Wade,jenny.wade@example.com,2h +76-87010,Jordan,Parker,jordan.parker@example.com,2h +04-86463,Lydia,Pierce,lydia.pierce@example.com,2h +07-87840,Frank,Ramos,frank.ramos@example.com,2h +34-63224,Denise,Barnes,denise.barnes@example.com,2h +62-78606,Brad,Lynch,brad.lynch@example.com,2h +77-85478,Harold,Fox,harold.fox@example.com,2h +61-85721,Taylor,Kuhn,taylor.kuhn@example.com,2h +75-20523,Clinton,Ramirez,clinton.ramirez@example.com,2h +23-88614,Marion,Kelley,marion.kelley@example.com,2h +25-58370,Brayden,Morrison,brayden.morrison@example.com,2h +71-06884,Tiffany,Green,tiffany.green@example.com,2h +30-32652,Leo,Powell,leo.powell@example.com,2h +04-27205,Valerie,Arnold,valerie.arnold@example.com,2h +60-05225,Sandra,Spencer,sandra.spencer@example.com,2h +85-80581,Deanna,Morgan,deanna.morgan@example.com,2h +47-71037,Duane,Washington,duane.washington@example.com,2h +64-85027,Crystal,Evans,crystal.evans@example.com,2h +37-58705,Gordon,Rodriquez,gordon.rodriquez@example.com,2h +28-38022,Vernon,Wright,vernon.wright@example.com,2h +74-38558,Lee,Gonzales,lee.gonzales@example.com,2h +51-36382,Colleen,Martin,colleen.martin@example.com,2h +55-21387,Pearl,Martin,pearl.martin@example.com,2h +15-52628,Wanda,Gray,wanda.gray@example.com,2h +40-55587,Vivan,Perez,vivan.perez@example.com,2h +00-20530,Joyce,Crawford,joyce.crawford@example.com,2h +88-07134,Oscar,Henderson,oscar.henderson@example.com,2h +17-64200,Gregory,Sims,gregory.sims@example.com,2h +21-32048,Deann,Diaz,deann.diaz@example.com,2h +62-86018,Philip,Brooks,philip.brooks@example.com,2h +41-70474,Samantha,Ramos,samantha.ramos@example.com,2h +63-12455,Anna,Snyder,anna.snyder@example.com,2h +66-53752,Layla,Long,layla.long@example.com,2h +41-14543,Sebastian,Woods,sebastian.woods@example.com,2h +15-28470,Kylie,Green,kylie.green@example.com,2h +20-83740,Jessie,Hall,jessie.hall@example.com,2h +63-47072,Allen,James,allen.james@example.com,2h +06-66420,Brianna,Bailey,brianna.bailey@example.com,2h +88-63446,Johnni,Cook,johnni.cook@example.com,2h +02-37065,Jackson,Roberts,jackson.roberts@example.com,2h +46-37367,Javier,Kuhn,javier.kuhn@example.com,2h +34-50457,Dana,Mills,dana.mills@example.com,2h +10-07412,Kristina,Barnes,kristina.barnes@example.com,2h +15-76820,Rose,Montgomery,rose.montgomery@example.com,2h +03-56337,Felicia,Ward,felicia.ward@example.com,2h +42-58245,Charles,Carr,charles.carr@example.com,2h +22-24383,Doris,Oliver,doris.oliver@example.com,2h +57-25430,Victoria,Perez,victoria.perez@example.com,2h +26-53573,Dwayne,Jones,dwayne.jones@example.com,2h +72-08551,Joanne,Alvarez,joanne.alvarez@example.com,3a +27-71516,Shelly,Franklin,shelly.franklin@example.com,3a +06-00347,Sofia,Barrett,sofia.barrett@example.com,3a +63-22322,Levi,Campbell,levi.campbell@example.com,3a +73-24156,Jeanne,Schmidt,jeanne.schmidt@example.com,3a +37-58585,Denise,Davis,denise.davis@example.com,3a +41-88831,Stella,Fletcher,stella.fletcher@example.com,3a +10-40031,Kristina,Brewer,kristina.brewer@example.com,3a +56-13383,Evelyn,Byrd,evelyn.byrd@example.com,3a +64-66526,Richard,Fisher,richard.fisher@example.com,3a +53-30036,Bessie,Soto,bessie.soto@example.com,3a +17-64204,Amber,Nguyen,amber.nguyen@example.com,3a +38-42667,Alma,Armstrong,alma.armstrong@example.com,3a +58-05576,Jean,Steward,jean.steward@example.com,3a +50-26533,Stacey,Bradley,stacey.bradley@example.com,3a +70-07051,Suzanne,Henry,suzanne.henry@example.com,3a +82-35422,Roland,Simpson,roland.simpson@example.com,3a +20-34006,Francis,Martinez,francis.martinez@example.com,3a +22-03448,Leslie,Butler,leslie.butler@example.com,3a +44-45572,Maureen,Holt,maureen.holt@example.com,3a +11-85154,Dawn,Knight,dawn.knight@example.com,3a +32-60010,Terry,Reed,terry.reed@example.com,3a +81-30507,Alma,Smith,alma.smith@example.com,3a +83-01234,Philip,Bailey,philip.bailey@example.com,3a +00-74442,Beth,Sims,beth.sims@example.com,3a +82-78482,Dennis,Hunter,dennis.hunter@example.com,3a +68-66556,Jennifer,Arnold,jennifer.arnold@example.com,3a +51-22587,Marc,Palmer,marc.palmer@example.com,3a +21-61860,Brett,Fernandez,brett.fernandez@example.com,3a +70-47683,Edgar,Chapman,edgar.chapman@example.com,3a +43-28154,Sergio,Carlson,sergio.carlson@example.com,3a +86-11275,Anna,Watts,anna.watts@example.com,3a +06-23805,Ruben,Howard,ruben.howard@example.com,3a +36-56641,Melanie,Green,melanie.green@example.com,3a +74-84787,Marjorie,Andrews,marjorie.andrews@example.com,3a +05-27524,June,Walker,june.walker@example.com,3a +35-83220,Craig,Stone,craig.stone@example.com,3a +52-68037,Clarence,Neal,clarence.neal@example.com,3a +26-10057,Leon,Perkins,leon.perkins@example.com,3a +25-70775,Tristan,Hoffman,tristan.hoffman@example.com,3a +20-86844,Teresa,Lucas,teresa.lucas@example.com,3a +00-58453,Douglas,Parker,douglas.parker@example.com,3a +58-32467,Lance,Sutton,lance.sutton@example.com,3a +68-68445,Terrance,Chavez,terrance.chavez@example.com,3a +65-77118,Danielle,Castro,danielle.castro@example.com,3a +00-83268,Frederick,Gutierrez,frederick.gutierrez@example.com,3a +54-67602,Craig,Harvey,craig.harvey@example.com,3a +76-18768,Mildred,Fowler,mildred.fowler@example.com,3a +81-71852,Sara,Owens,sara.owens@example.com,3a +44-50865,Mario,Peters,mario.peters@example.com,3a +30-12684,Seth,Allen,seth.allen@example.com,3b +15-56617,Harper,Pena,harper.pena@example.com,3b +52-15366,Jeremy,Hill,jeremy.hill@example.com,3b +11-71738,Ron,Hudson,ron.hudson@example.com,3b +55-65202,Leona,Beck,leona.beck@example.com,3b +83-43504,Lena,Owens,lena.owens@example.com,3b +37-53668,Bradley,White,bradley.white@example.com,3b +25-84677,Kevin,Franklin,kevin.franklin@example.com,3b +81-06555,Jonathan,Gibson,jonathan.gibson@example.com,3b +66-82557,Brandy,Vasquez,brandy.vasquez@example.com,3b +13-00838,Jose,Romero,jose.romero@example.com,3b +33-10160,Diana,Garcia,diana.garcia@example.com,3b +17-18705,Julio,Hale,julio.hale@example.com,3b +31-41546,Connor,Ryan,connor.ryan@example.com,3b +30-00407,Nicole,Dixon,nicole.dixon@example.com,3b +87-21434,Aubree,Stone,aubree.stone@example.com,3b +58-40168,Bessie,Bryant,bessie.bryant@example.com,3b +37-81675,Danny,Obrien,danny.obrien@example.com,3b +02-30551,Irma,Davidson,irma.davidson@example.com,3b +30-37188,Mattie,Snyder,mattie.snyder@example.com,3b +25-77136,Gladys,Medina,gladys.medina@example.com,3b +74-38256,Kent,Gordon,kent.gordon@example.com,3b +58-00832,Eric,Mitchell,eric.mitchell@example.com,3b +52-20716,Bradley,Oliver,bradley.oliver@example.com,3b +86-27262,Nicholas,Watts,nicholas.watts@example.com,3b +28-20478,Jill,Gray,jill.gray@example.com,3b +53-67837,Herman,Garza,herman.garza@example.com,3b +40-57166,Dawn,Medina,dawn.medina@example.com,3b +40-60171,Bella,Welch,bella.welch@example.com,3b +82-37842,Carolyn,Hawkins,carolyn.hawkins@example.com,3b +63-62360,Philip,Miles,philip.miles@example.com,3b +06-43435,Levi,Collins,levi.collins@example.com,3b +27-37834,Janet,Byrd,janet.byrd@example.com,3b +73-47784,Leo,Porter,leo.porter@example.com,3b +80-11880,Erika,Barnett,erika.barnett@example.com,3b +45-38072,Grace,Morris,grace.morris@example.com,3b +40-61418,Julia,Ward,julia.ward@example.com,3b +86-26462,Doris,Newman,doris.newman@example.com,3b +10-63627,Albert,Caldwell,albert.caldwell@example.com,3b +67-53400,Anita,Nichols,anita.nichols@example.com,3b +20-51536,Jacob,Murray,jacob.murray@example.com,3b +08-78162,Liam,Garrett,liam.garrett@example.com,3b +85-66736,Dawn,Arnold,dawn.arnold@example.com,3b +36-42781,Evan,Burton,evan.burton@example.com,3b +40-36821,Vicki,Carlson,vicki.carlson@example.com,3b +30-53511,Benjamin,Perez,benjamin.perez@example.com,3b +04-63471,Gregory,Clark,gregory.clark@example.com,3b +83-03876,Herbert,Kelly,herbert.kelly@example.com,3b +65-83428,Minnie,Torres,minnie.torres@example.com,3b +60-88672,Mark,Evans,mark.evans@example.com,3b +74-45523,Luis,Nguyen,luis.nguyen@example.com,3c +18-80276,Toni,Patterson,toni.patterson@example.com,3c +23-68305,Joseph,Berry,joseph.berry@example.com,3c +87-82083,Eugene,Morales,eugene.morales@example.com,3c +08-60825,Douglas,Wilson,douglas.wilson@example.com,3c +15-77256,Gary,Montgomery,gary.montgomery@example.com,3c +60-11715,Gene,Silva,gene.silva@example.com,3c +72-40287,Norma,Crawford,norma.crawford@example.com,3c +48-15130,Margie,Pearson,margie.pearson@example.com,3c +74-77337,Jill,Hudson,jill.hudson@example.com,3c +64-71421,Jennifer,Shaw,jennifer.shaw@example.com,3c +16-74675,Eleanor,Hudson,eleanor.hudson@example.com,3c +45-56011,Riley,Kelley,riley.kelley@example.com,3c +26-84144,Sharlene,Berry,sharlene.berry@example.com,3c +26-30364,Bob,Castro,bob.castro@example.com,3c +67-40060,Louise,Rhodes,louise.rhodes@example.com,3c +25-58838,Donald,Willis,donald.willis@example.com,3c +82-56277,Katrina,Long,katrina.long@example.com,3c +38-21806,Debra,Neal,debra.neal@example.com,3c +35-41526,Terry,Shelton,terry.shelton@example.com,3c +03-27516,Ronald,Owens,ronald.owens@example.com,3c +42-12185,Ryan,Lewis,ryan.lewis@example.com,3c +63-82120,Jason,Murphy,jason.murphy@example.com,3c +20-54768,Larry,Sims,larry.sims@example.com,3c +56-42206,Darryl,Wood,darryl.wood@example.com,3c +11-55686,Victoria,Cunningham,victoria.cunningham@example.com,3c +76-28284,Ella,Phillips,ella.phillips@example.com,3c +17-47034,Wilma,Ruiz,wilma.ruiz@example.com,3c +07-85822,Martha,Banks,martha.banks@example.com,3c +46-15841,Isobel,Russell,isobel.russell@example.com,3c +22-33565,Brandon,Griffin,brandon.griffin@example.com,3c +14-70214,Ronnie,Burton,ronnie.burton@example.com,3c +56-27471,Carolyn,Taylor,carolyn.taylor@example.com,3c +45-15537,Deanna,Matthews,deanna.matthews@example.com,3c +07-21516,Daisy,Cruz,daisy.cruz@example.com,3c +28-51606,Valerie,Ortiz,valerie.ortiz@example.com,3c +40-67034,Zack,Mills,zack.mills@example.com,3c +83-26064,Micheal,Welch,micheal.welch@example.com,3c +43-53470,Priscilla,Wade,priscilla.wade@example.com,3c +12-43486,Jesse,Ruiz,jesse.ruiz@example.com,3c +86-23257,Courtney,Flores,courtney.flores@example.com,3c +61-13243,Dwayne,Rogers,dwayne.rogers@example.com,3c +61-10108,Beverley,Bates,beverley.bates@example.com,3c +64-04810,Jeanette,Holt,jeanette.holt@example.com,3c +61-87073,Brianna,Perez,brianna.perez@example.com,3c +73-82236,Daniel,Cook,daniel.cook@example.com,3c +38-51157,Kylie,Davidson,kylie.davidson@example.com,3c +04-37778,Stella,Freeman,stella.freeman@example.com,3c +31-00085,Erin,Crawford,erin.crawford@example.com,3c +78-57645,Ronnie,Hudson,ronnie.hudson@example.com,3c +47-63087,Frederick,Carroll,frederick.carroll@example.com,3d +35-28338,Lloyd,Alexander,lloyd.alexander@example.com,3d +23-26516,Sonia,Fleming,sonia.fleming@example.com,3d +41-32226,Jesse,Sims,jesse.sims@example.com,3d +77-32877,Pedro,Lawson,pedro.lawson@example.com,3d +72-35740,Theresa,Jacobs,theresa.jacobs@example.com,3d +40-81118,Sara,White,sara.white@example.com,3d +80-14604,Addison,Hoffman,addison.hoffman@example.com,3d +03-15742,Anthony,Pearson,anthony.pearson@example.com,3d +52-30706,Elaine,Hernandez,elaine.hernandez@example.com,3d +08-05031,Lisa,Curtis,lisa.curtis@example.com,3d +32-66872,Sharlene,Soto,sharlene.soto@example.com,3d +48-30241,Clifton,James,clifton.james@example.com,3d +12-23258,Tommy,Gardner,tommy.gardner@example.com,3d +37-84731,Myrtle,Crawford,myrtle.crawford@example.com,3d +87-64775,Ashley,Alexander,ashley.alexander@example.com,3d +33-47658,Harvey,Sims,harvey.sims@example.com,3d +87-11782,Eric,Neal,eric.neal@example.com,3d +74-72472,Laurie,Newman,laurie.newman@example.com,3d +15-02302,Warren,Pierce,warren.pierce@example.com,3d +14-21656,Adam,West,adam.west@example.com,3d +17-08574,Julie,Harper,julie.harper@example.com,3d +81-42001,Diana,Ford,diana.ford@example.com,3d +48-62577,Carrie,Herrera,carrie.herrera@example.com,3d +85-13683,Lena,Pearson,lena.pearson@example.com,3d +08-05540,Sandra,Crawford,sandra.crawford@example.com,3d +25-50563,Sharlene,Murray,sharlene.murray@example.com,3d +17-80522,Erica,Bowman,erica.bowman@example.com,3d +45-28332,Gene,Butler,gene.butler@example.com,3d +42-08565,Nathaniel,Carroll,nathaniel.carroll@example.com,3d +38-26436,Howard,Ward,howard.ward@example.com,3d +08-02878,Stephen,Wallace,stephen.wallace@example.com,3d +50-64272,Andrea,Berry,andrea.berry@example.com,3d +01-40081,Ana,Nichols,ana.nichols@example.com,3d +32-00118,Heidi,Rice,heidi.rice@example.com,3d +01-14472,Jar,Beck,jar.beck@example.com,3d +16-27622,Vera,Castillo,vera.castillo@example.com,3d +05-75136,Kirk,Harvey,kirk.harvey@example.com,3d +07-00176,Frederick,Phillips,frederick.phillips@example.com,3d +85-75125,Derrick,Miller,derrick.miller@example.com,3d +26-76448,Sue,Anderson,sue.anderson@example.com,3d +17-21234,Julian,Reyes,julian.reyes@example.com,3d +21-42150,Brent,Herrera,brent.herrera@example.com,3d +63-55310,Andy,Lowe,andy.lowe@example.com,3d +46-70846,Kristen,Terry,kristen.terry@example.com,3d +63-36560,Travis,Ford,travis.ford@example.com,3d +28-48117,Lisa,Lawson,lisa.lawson@example.com,3d +67-72178,Sharlene,Jenkins,sharlene.jenkins@example.com,3d +27-13077,Bernice,Daniels,bernice.daniels@example.com,3d +50-16187,Jared,Reyes,jared.reyes@example.com,3d +36-38221,Stacy,Rose,stacy.rose@example.com,3e +21-34867,Aubree,Castro,aubree.castro@example.com,3e +82-07657,Alvin,Allen,alvin.allen@example.com,3e +45-07678,Charlie,Lewis,charlie.lewis@example.com,3e +83-43815,Nellie,Carr,nellie.carr@example.com,3e +85-42155,Willard,Silva,willard.silva@example.com,3e +44-47125,Franklin,Curtis,franklin.curtis@example.com,3e +54-53120,Arthur,Arnold,arthur.arnold@example.com,3e +82-18628,Valerie,Morrison,valerie.morrison@example.com,3e +73-58687,Richard,Rose,richard.rose@example.com,3e +02-21607,Naomi,Palmer,naomi.palmer@example.com,3e +04-88557,Dustin,Douglas,dustin.douglas@example.com,3e +17-25136,Valerie,Hopkins,valerie.hopkins@example.com,3e +87-52066,Clara,Murphy,clara.murphy@example.com,3e +67-18037,Bessie,Robinson,bessie.robinson@example.com,3e +85-40668,Kirk,Ferguson,kirk.ferguson@example.com,3e +50-55420,Glenda,Lambert,glenda.lambert@example.com,3e +40-88161,Peter,Coleman,peter.coleman@example.com,3e +11-03264,Amber,Berry,amber.berry@example.com,3e +68-75606,Nina,Brown,nina.brown@example.com,3e +65-58053,Bessie,Owens,bessie.owens@example.com,3e +24-51416,Ryan,Lynch,ryan.lynch@example.com,3e +07-30638,Lee,Rodriquez,lee.rodriquez@example.com,3e +40-05513,Suzanne,Carroll,suzanne.carroll@example.com,3e +60-54730,Gerald,Morris,gerald.morris@example.com,3e +03-50637,Lance,Fletcher,lance.fletcher@example.com,3e +08-20156,Lillian,Montgomery,lillian.montgomery@example.com,3e +25-43558,Clayton,Perez,clayton.perez@example.com,3e +80-68774,Craig,Perry,craig.perry@example.com,3e +35-15366,Eduardo,Holmes,eduardo.holmes@example.com,3e +54-15247,Bob,Bowman,bob.bowman@example.com,3e +44-05546,Vivan,Gonzalez,vivan.gonzalez@example.com,3e +64-66516,Beverley,Knight,beverley.knight@example.com,3e +76-18453,Genesis,Hunt,genesis.hunt@example.com,3e +38-52066,Genesis,Matthews,genesis.matthews@example.com,3e +54-34245,Louella,Riley,louella.riley@example.com,3e +20-54802,Glen,Day,glen.day@example.com,3e +52-16060,Lillian,Hamilton,lillian.hamilton@example.com,3e +37-15188,Manuel,Pearson,manuel.pearson@example.com,3e +05-68603,Holly,Kim,holly.kim@example.com,3e +41-21084,Alberto,Jensen,alberto.jensen@example.com,3e +41-58001,Sara,Hart,sara.hart@example.com,3e +16-80782,Irene,Nichols,irene.nichols@example.com,3e +28-67606,Kenzi,Ray,kenzi.ray@example.com,3e +03-83334,Terry,Daniels,terry.daniels@example.com,3e +45-18000,Suzanne,Lucas,suzanne.lucas@example.com,3e +63-35365,Marc,Curtis,marc.curtis@example.com,3e +33-84676,Katrina,Thompson,katrina.thompson@example.com,3e +27-56020,Byron,Little,byron.little@example.com,3e +70-07140,Nicholas,Pena,nicholas.pena@example.com,3e +32-37641,Isaac,Caldwell,isaac.caldwell@example.com,3g +64-61080,Roger,Hanson,roger.hanson@example.com,3g +54-40750,Megan,Edwards,megan.edwards@example.com,3g +63-32663,Letitia,Hill,letitia.hill@example.com,3g +11-84046,Kitty,Craig,kitty.craig@example.com,3g +68-42354,Bradley,Castro,bradley.castro@example.com,3g +50-82036,Alvin,Lowe,alvin.lowe@example.com,3g +53-76677,Melanie,Dean,melanie.dean@example.com,3g +87-36403,Bernard,Caldwell,bernard.caldwell@example.com,3g +73-31811,Ross,Butler,ross.butler@example.com,3g +40-52174,Juan,Mills,juan.mills@example.com,3g +47-07018,Nicole,Stevens,nicole.stevens@example.com,3g +12-41326,Martin,Butler,martin.butler@example.com,3g +88-35472,Cory,Garcia,cory.garcia@example.com,3g +75-40463,Louella,Obrien,louella.obrien@example.com,3g +68-02268,Bertha,Palmer,bertha.palmer@example.com,3g +15-64747,Lydia,Nelson,lydia.nelson@example.com,3g +43-40671,Stephen,James,stephen.james@example.com,3g +22-52262,Lester,Warren,lester.warren@example.com,3g +03-80845,Guy,Hernandez,guy.hernandez@example.com,3g +32-47284,Mason,Douglas,mason.douglas@example.com,3g +02-08530,Eddie,Chavez,eddie.chavez@example.com,3g +37-81743,Luis,Wagner,luis.wagner@example.com,3g +73-73421,Camila,Wagner,camila.wagner@example.com,3g +80-11213,Christine,Marshall,christine.marshall@example.com,3g +13-86882,Roger,Mason,roger.mason@example.com,3g +61-18451,Andy,Burton,andy.burton@example.com,3g +57-70352,Carmen,Graham,carmen.graham@example.com,3g +75-10774,Freddie,Ferguson,freddie.ferguson@example.com,3g +18-04813,Robin,Collins,robin.collins@example.com,3g +02-80172,Charles,Pena,charles.pena@example.com,3g +44-88743,Ava,Ford,ava.ford@example.com,3g +74-54713,Philip,Murray,philip.murray@example.com,3g +61-00332,Ellen,Ryan,ellen.ryan@example.com,3g +13-77187,Charlie,Tucker,charlie.tucker@example.com,3g +00-56578,Diana,Bell,diana.bell@example.com,3g +16-78440,Nelson,Bell,nelson.bell@example.com,3g +61-05746,Mathew,Dixon,mathew.dixon@example.com,3g +06-73617,Anthony,Crawford,anthony.crawford@example.com,3g +36-45570,Dylan,Jennings,dylan.jennings@example.com,3g +10-81105,Randall,Pierce,randall.pierce@example.com,3g +20-86630,Teresa,Hawkins,teresa.hawkins@example.com,3g +06-27311,Wendy,Harrison,wendy.harrison@example.com,3g +30-46478,George,Butler,george.butler@example.com,3g +10-26106,Bradley,Byrd,bradley.byrd@example.com,3g +65-30153,Joe,Obrien,joe.obrien@example.com,3g +43-36131,Bobbie,Douglas,bobbie.douglas@example.com,3g +03-84085,Ann,Ward,ann.ward@example.com,3g +74-30111,Amy,Sullivan,amy.sullivan@example.com,3g +24-77301,Regina,Schmidt,regina.schmidt@example.com,3g +68-75433,Debra,Weaver,debra.weaver@example.com,3h +66-00530,Darlene,Sanchez,darlene.sanchez@example.com,3h +47-37647,Beatrice,Johnson,beatrice.johnson@example.com,3h +57-22511,Arthur,Soto,arthur.soto@example.com,3h +52-50670,Allen,Jordan,allen.jordan@example.com,3h +65-24102,Jeremiah,Franklin,jeremiah.franklin@example.com,3h +37-00075,Jerome,Woods,jerome.woods@example.com,3h +43-04470,Bill,Reid,bill.reid@example.com,3h +23-15824,Calvin,Fuller,calvin.fuller@example.com,3h +00-50828,Leah,Garcia,leah.garcia@example.com,3h +61-02370,Beverly,Oliver,beverly.oliver@example.com,3h +63-77060,Irma,Morgan,irma.morgan@example.com,3h +10-73208,Christy,Long,christy.long@example.com,3h +88-87305,Riley,Collins,riley.collins@example.com,3h +78-45142,Hannah,Reed,hannah.reed@example.com,3h +78-87628,Aiden,Martinez,aiden.martinez@example.com,3h +70-32224,Cecil,Craig,cecil.craig@example.com,3h +42-80420,Austin,Jenkins,austin.jenkins@example.com,3h +32-48547,Cory,Morgan,cory.morgan@example.com,3h +57-62068,Erika,Howell,erika.howell@example.com,3h +12-86283,Claire,Murray,claire.murray@example.com,3h +35-41077,Arthur,Wheeler,arthur.wheeler@example.com,3h +03-44624,Jamie,Wells,jamie.wells@example.com,3h +40-46834,Julia,Crawford,julia.crawford@example.com,3h +88-17106,Julian,Matthews,julian.matthews@example.com,3h +26-86843,Flenn,Douglas,flenn.douglas@example.com,3h +05-35086,Thomas,Mccoy,thomas.mccoy@example.com,3h +84-40426,Guy,Lynch,guy.lynch@example.com,3h +41-45245,Gilbert,Gomez,gilbert.gomez@example.com,3h +08-42046,Aubree,Tucker,aubree.tucker@example.com,3h +60-58028,Jeffrey,Carroll,jeffrey.carroll@example.com,3h +78-63618,Lauren,Stone,lauren.stone@example.com,3h +12-86812,Ashley,Reid,ashley.reid@example.com,3h +31-57023,Marsha,Hunt,marsha.hunt@example.com,3h +03-22857,Nellie,Diaz,nellie.diaz@example.com,3h +05-40841,Audrey,Obrien,audrey.obrien@example.com,3h +64-64701,Riley,Berry,riley.berry@example.com,3h +36-53887,Cathy,Grant,cathy.grant@example.com,3h +73-53461,Debra,Peters,debra.peters@example.com,3h +65-12542,Dustin,Burke,dustin.burke@example.com,3h +03-18144,Franklin,Mitchelle,franklin.mitchelle@example.com,3h +36-18661,Tracy,Simmmons,tracy.simmmons@example.com,3h +26-14563,Roger,Fuller,roger.fuller@example.com,3h +82-36823,Susan,Harrison,susan.harrison@example.com,3h +80-36343,Billy,Holt,billy.holt@example.com,3h +04-44324,Jacob,Weaver,jacob.weaver@example.com,3h +00-21731,Ruben,Reynolds,ruben.reynolds@example.com,3h +28-78861,Charlene,Caldwell,charlene.caldwell@example.com,3h +04-81756,Clifton,Arnold,clifton.arnold@example.com,3h +01-55152,Marilyn,Miles,marilyn.miles@example.com,3h diff --git a/csv/Facilities.csv b/csv/Facilities.csv new file mode 100644 index 0000000..57f209f --- /dev/null +++ b/csv/Facilities.csv @@ -0,0 +1,30 @@ +facilityID,name,description +101,PRRC 101,Lecture Room 101 +102,PRRC 102,Lecture Room 102 +103,PRRC 103,Lecture Room 103 +104,PRRC 104,IOB Simulation Room +105,PRRC 105,Lecture Room 105 +106,PRRC 106,Lecture Room 106 +107,PRRC 107,Lecture Room 107 +108,PRRC 108,Lecture Room 108 +201,PRRC 201,Lecture Room 201 +202,PRRC 202,Lecture Room 202 +203,PRRC 203,Lecture Room 203 +204,PRRC 204,Engineering Professional Laboratory +205,PRRC 205,Educational Technology Room +206,PRRC 206,Lecture Room 206 +207,PRRC 207,Lecture Room 207 +208,PRRC 208,Lecture Room 208 +209,PRRC 209,Lecture Room 209 +301,Predac,Library Extension +302,Lab 1,Computer Laboratory 1 +303,PRRC 303,Office of the Property and Supply +304,Lab 2,Computer Laboratory 2 +305,PRRC 305,Lecture Room 305 +306,ICTO,Information and Communication Technology Office +307,PRRC 307,Lecture Room 307 +308,PRRC 308,Lecture Room 308 +309,PRRC 309,Lecture Room 309 +310,PRRC 310,Lecture Room 310 +COURT,Covered Court,Colegio de Montalban Covered Court +FH,Function Hall,Events Sports and other Functions Hall diff --git a/frontend/css/dashboard.css b/frontend/css/dashboard.css new file mode 100644 index 0000000..db6a4df --- /dev/null +++ b/frontend/css/dashboard.css @@ -0,0 +1,264 @@ + +#sidebar { + position: absolute; + top: 0; + left: 0; + width: 8rem; + height: 100%; + background-color: var(--color-plain-light); +} +#sidebar > img { + width: 100%; + cursor: pointer; +} +#sidebar > img:hover { + filter: brightness(0.75); +} +#sidebar > #logoutIcon { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + text-align: center; + cursor: pointer; +} + +#mainPanel { + position: absolute; + top: 0; + left: 8rem; + height: 100%; + width: calc(100% - 8rem); + overflow-x: auto; +} + +#calendar { + position: relative; + top: 4rem; + left: 4rem; + width: calc(100% - 8rem); + min-width: 160rem; + height: calc(100% - 8rem); + min-height: 80rem; +} +#calendarHeader { + position: absolute; + top: 0; + left: 0; + height: 4rem; + width: 100%; + background-color: var(--color-primary); + color: var(--color-white); + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} +#days { + position: absolute; + top: 4rem; + left: 0; + height: 4rem; + width: 100%; + background-color: var(--color-secondary); + display: flex; + flex-direction: row; + align-items: center; + justify-content: right; +} +#days > * { + width: 12.5%; + text-align: center; + color: var(--color-white); +} +#timeStamps { + position: absolute; + top: 8rem; + left: 0; + width: 12.5%; + height: calc(100% - 12rem); + padding: 2rem 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +} +#timeStamps > * { + font-size: 1.75rem; +} +#daysPanel { + position: absolute; + top: 8rem; + left: 0; + width: 100%; + height: calc(100% - 12rem); + display: flex; + flex-direction: row; + align-items: center; + justify-content: right; +} +#daysPanel > * { + position: relative; + width: 12.5%; + height: 100%; + padding: 0 1rem; +} +#daysPanel > *:nth-child(odd) { + background-color: var(--color-plain-light); +} +#footer { + position: absolute; + bottom: 0; + left: 0; + height: 4rem; + width: 100%; + background-color: var(--color-primary); +} + +.schedule { + position: absolute; + width: calc(100% - 2rem); + background-color: var(--color-plain-light); + text-align: center; +} +.schedule.even { + border: solid 0.5rem var(--color-plain-dark); +} +.schedule.odd { + border: solid 0.5rem var(--color-secondary); +} +.schedule > h6 { + margin: 0; + color: var(--color-white); +} +.schedule.even > h6 { + background-color: var(--color-plain-dark); +} +.schedule.odd > h6 { + background-color: var(--color-secondary); +} + +.requestForm { + position: absolute; + left: -1rem; + width: 100%; + padding: 1rem; + padding-top: 3rem; + background-color: var(--color-plain-light); + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; +} +.requestForm.even { + border: solid 0.5rem var(--color-plain-dark); +} +.requestForm.odd { + border: solid 0.5rem var(--color-secondary); +} +.requestForm > h6 { + position: absolute; + top: 0; + left: 0; + width: 100%; + color: var(--color-plain-light); +} +.requestForm.even > h6 { + background-color: var(--color-plain-dark); +} +.requestForm.odd > h6 { + background-color: var(--color-secondary); +} +.requestForm > .dropDown { + width: 100%; + margin: 0; + padding: 0; + text-align: left; + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; +} +.requestForm > .dropDown > select { + width: 100%; +} +.requestForm > .dropDown > textarea { + width: 100%; + height: 10rem; + resize: none; + padding: 0.5rem; + color: var(--color-plain-dark); + border: solid 0.1rem var(--color-plain-dark); + background-color: var(--color-white); +} +.requestForm > .button { + width: 100%; +} +.requestForm.even > .button { + background-color: var(--color-plain-dark); +} +.requestForm.odd > .button { + background-color: var(--color-secondary); +} + +#requests { + position: absolute; + top: 4rem; + left: 4rem; + width: calc(100% - 8rem); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8rem; +} + +.request { + position: relative; + width: 100%; + border-spacing: 0; +} +.request caption { + height: 4rem; + width: 100%; + border: solid 0.01rem var(--color-plain-dark); + background-color: var(--color-primary); + color: var(--color-white); + text-align: center; +} +.request caption * { + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} +.request thead { + height: 4rem; + color: var(--color-white); + background-color: var(--color-secondary); +} +.request th:nth-child(odd) { + background: rgba(0, 0, 0, 0.125); +} +.request th:nth-child(even) { + background: rgba(0, 0, 0, 0); +} +.request div:nth-child(odd) { + background: rgba(0, 0, 0, 0.125); +} +.request div:nth-child(even) { + background: rgba(0, 0, 0, 0); +} +.request th, .request div { + height: 4rem; + border: solid 0.005rem var(--color-plain-dark); +} +.request div { + display: flex; + align-items: center; + justify-content: center; + gap: 2rem; +} \ No newline at end of file diff --git a/frontend/css/fonts/OpenSans-Bold.ttf b/frontend/css/fonts/OpenSans-Bold.ttf new file mode 100644 index 0000000..98c74e0 Binary files /dev/null and b/frontend/css/fonts/OpenSans-Bold.ttf differ diff --git a/frontend/css/fonts/OpenSans-BoldItalic.ttf b/frontend/css/fonts/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000..8558928 Binary files /dev/null and b/frontend/css/fonts/OpenSans-BoldItalic.ttf differ diff --git a/frontend/css/fonts/OpenSans-Italic.ttf b/frontend/css/fonts/OpenSans-Italic.ttf new file mode 100644 index 0000000..29ff693 Binary files /dev/null and b/frontend/css/fonts/OpenSans-Italic.ttf differ diff --git a/frontend/css/fonts/OpenSans-Regular.ttf b/frontend/css/fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000..67803bb Binary files /dev/null and b/frontend/css/fonts/OpenSans-Regular.ttf differ diff --git a/frontend/css/fonts/OpenSans-SemiBold.ttf b/frontend/css/fonts/OpenSans-SemiBold.ttf new file mode 100644 index 0000000..e5ab464 Binary files /dev/null and b/frontend/css/fonts/OpenSans-SemiBold.ttf differ diff --git a/frontend/css/fonts/OpenSans-SemiBoldItalic.ttf b/frontend/css/fonts/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..cd23e15 Binary files /dev/null and b/frontend/css/fonts/OpenSans-SemiBoldItalic.ttf differ diff --git a/frontend/css/fonts/Raleway-Bold.ttf b/frontend/css/fonts/Raleway-Bold.ttf new file mode 100644 index 0000000..16db3eb Binary files /dev/null and b/frontend/css/fonts/Raleway-Bold.ttf differ diff --git a/frontend/css/fonts/Raleway-Regular.ttf b/frontend/css/fonts/Raleway-Regular.ttf new file mode 100644 index 0000000..9a70667 Binary files /dev/null and b/frontend/css/fonts/Raleway-Regular.ttf differ diff --git a/frontend/css/fonts/fontFace.css b/frontend/css/fonts/fontFace.css new file mode 100644 index 0000000..b621ea7 --- /dev/null +++ b/frontend/css/fonts/fontFace.css @@ -0,0 +1,60 @@ + +/* -------------------- Open Sans -------------------- */ +/* Normal */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: url('./OpenSans-Regular.ttf') format('truetype'); +} +/* Semi Bold */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: url('./OpenSans-SemiBold.ttf') format('truetype'); +} +/* Bold */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: url('./OpenSans-Bold.ttf') format('truetype'); +} +/* Italic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + src: url('./OpenSans-Italic.ttf') format('truetype'); +} +/* Semi Bold Italic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + src: url('./OpenSans-SemiBoldItalic.ttf') format('truetype'); +} +/* Bold Italic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 700; + src: url('./OpenSans-BoldItalic.ttf') format('truetype'); +} + +/* -------------------- Raleway -------------------- */ +/* Normal */ +@font-face { + font-family: 'Raleway'; + font-style: normal; + font-weight: 400; + src: url('./Raleway-Regular.ttf') format('truetype'); +} +/* Bold */ +@font-face { + font-family: 'Raleway'; + font-style: normal; + font-weight: 700; + src: url('./Raleway-Bold.ttf') format('truetype'); +} \ No newline at end of file diff --git a/frontend/css/forgotPassword.css b/frontend/css/forgotPassword.css new file mode 100644 index 0000000..a9fef37 --- /dev/null +++ b/frontend/css/forgotPassword.css @@ -0,0 +1,117 @@ + +#barComplement, #bar, #barComplementBottom, #barBottom { + position: absolute; + z-index: -1; +} +#barComplement { + height: 62.5%; + top: -50%; + left: -12.5%; +} +#bar { + height: 62.5%; + top: -50%; + left: 75%; +} +#barComplementBottom { + height: 62.5%; + top: 75%; + left: 0%; +} +#barBottom { + height: 62.5%; + top: 75%; + left: 87.5%; +} +@media screen and (max-width: 50rem) { + #barComplement { + left: -25%; + } + #bar { + left: 37.5%; + } + #barComplementBottom { + left: 0; + } + #barBottom { + left: 75%; + } +} +@media screen and (max-width: 37.5rem) { + #barComplement { + left: -50%; + } + #bar { + left: 50%; + } + #barComplementBottom { + left: -25%; + } + #barBottom { + left: 75%; + } +} + + +#formPanel { + position: absolute; + width: 37.5%; + height: 100%; + left: calc(50% - 18.75%); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4rem; +} +@media screen and (max-width: 50rem) { + #formPanel { + width: calc(100% - 16rem); + right: 8rem; + } +} +@media screen and (max-width: 37.5rem) { + #formPanel { + width: calc(100% - 8rem); + right: 4rem; + } +} +#logo { + width: 100%; +} + +#formPanel > h1 { + text-align: center; + color: var(--color-primary); +} + +.form { + width: calc(100% - 8rem); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2rem; +} +.form > span { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +#forgotPasswordButton { + width: 100%; +} + +#login { + text-align: center; +} + +#codeInput { + display: none; +} +#newPasswordInput { + display: none; +} \ No newline at end of file diff --git a/frontend/css/index.css b/frontend/css/index.css new file mode 100644 index 0000000..ff4ff81 --- /dev/null +++ b/frontend/css/index.css @@ -0,0 +1,209 @@ +:root { + --color-primary: #106A2E; + --color-secondary: #0D7856; + --color-primary-complement: #F4D35E; + --color-plain-light: #F1F1F1; + --color-plain-dark: #1F1F1F; + + --color-white: #FFFFFF; + --color-black: #000000; + + --transition-speed: 0.25s; +} + +html { + font-size: calc(62.5% * 0.75); + scroll-behavior: smooth; + background-color: var(--color-white); + color: var(--color-plain-dark); + overflow-x: hidden; +} +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Open Sans", sans-serif; + line-height: 100%; +} +body { + image-rendering: crisp-edges; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + font-size: 2rem; +} + +/* Headings */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Raleway, sans-serif; + color: inherit; +} +h1 { + font-size: 5.75rem; +} +h2 { + font-size: 5rem; +} +h3 { + font-size: 4.25rem; +} +h4 { + font-size: 3.5rem; +} +h5 { + font-size: 2.75rem; +} +h6 { + font-size: 2rem; +} + +.noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome, Edge, Opera and Firefox */ + pointer-events: none; +} + +#backgroundPanel { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: -1; +} + +.textInput { + position: relative; + width: 100%; + height: 4rem; + padding: 0 1rem; + border: none; + border-bottom: solid 0.25rem var(--color-plain-dark); + color: var(--color-plain-dark); + + transition: border-bottom var(--transition-speed); +} +.textInput > label { + position: absolute; + height: 100%; + width: calc(100% - 2rem); + color: var(--color-plain-dark); + background-color: none; + display: flex; + align-items: center; + justify-content: left; + z-index: 10; + + transition: height var(--transition-speed), color var(--transition-speed), left var(--transition-speed), width var(--transition-speed); +} +.textInput > label > * { + transition: font-size var(--transition-speed); +} +.textInput > input { + position: absolute; + height: 100%; + width: calc(100% - 2rem); + border: none; + background-color: transparent; + outline: none; + color: var(--color-plain-dark); +} +.textInput:has(input:focus) { + border-bottom: solid 0.25rem var(--color-secondary); +} +.textInput > input:focus ~ label { + height: 1rem; + left: 0.5rem; + width: calc(100% - 1rem); + color: var(--color-secondary); +} +.textInput > input:focus ~ label > * { + font-size: 1rem; +} +.textInput > input:not(:placeholder-shown) ~ label { + height: 1rem !important; + left: 0.5rem; + width: calc(100% - 1rem); +} +.textInput > input:not(:placeholder-shown) ~ label > * { + font-size: 1rem !important; +} + +.button { + position: relative; + padding: 1rem 2.5rem; + border: none; + background-color: var(--color-secondary); + color: var(--color-white); + display: inline-block; + cursor: pointer; + + transition: background-color var(--transition-speed), color var(--transition-speed); +} +.button:hover { + color: var(--color-plain-light); + background-color: var(--color-plain-dark); +} + +.modal { + position: fixed; + top: calc(50% - (30rem / 2)); + left: calc(50% - (60rem / 2)); + height: 30rem; + width: 60rem; + padding-bottom: 4rem; + background-color: var(--color-white); + border: solid 0.25rem var(--color-secondary); + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + z-index: 100; +} +.modal > #header { + height: 4rem; + width: 100%; + background-color: var(--color-secondary); + color: var(--color-white); + padding: 0 4rem; + display: flex; + align-items: center; + justify-content: space-between; +} +.modal > #header > #close { + cursor: pointer; + position: relative; +} +.modal > #header > #close:hover { + background-color: rgba(255, 255, 255, 0.25); +} +.modal > #header > img { + height: 3rem; + width: 3rem; +} +.modal > #content { + width: 100%; + padding: 0 4rem; + text-align: center; +} +.modal > #buttonsPanel { + width: 100%; + padding: 0 4rem; + display: flex; + align-items: center; + justify-content: stretch; + gap: 4rem; +} +.modal > #buttonsPanel > .button { + width: 100%; +} \ No newline at end of file diff --git a/frontend/css/login.css b/frontend/css/login.css new file mode 100644 index 0000000..5324055 --- /dev/null +++ b/frontend/css/login.css @@ -0,0 +1,110 @@ + +#barComplement, #bar, #barComplementBottom, #barBottom { + position: absolute; + z-index: -1; +} +#barComplement { + height: 62.5%; + top: -50%; + left: -12.5%; +} +#bar { + height: 62.5%; + top: -50%; + left: 75%; +} +#barComplementBottom { + height: 62.5%; + top: 75%; + left: 0%; +} +#barBottom { + height: 62.5%; + top: 75%; + left: 87.5%; +} +@media screen and (max-width: 50rem) { + #barComplement { + left: -25%; + } + #bar { + left: 37.5%; + } + #barComplementBottom { + left: 0; + } + #barBottom { + left: 75%; + } +} +@media screen and (max-width: 37.5rem) { + #barComplement { + left: -50%; + } + #bar { + left: 50%; + } + #barComplementBottom { + left: -25%; + } + #barBottom { + left: 75%; + } +} + + +#formPanel { + position: absolute; + width: 37.5%; + height: 100%; + left: calc(50% - 18.75%); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4rem; +} +@media screen and (max-width: 50rem) { + #formPanel { + width: calc(100% - 16rem); + right: 8rem; + } +} +@media screen and (max-width: 37.5rem) { + #formPanel { + width: calc(100% - 8rem); + right: 4rem; + } +} +#logo { + width: 100%; +} + +#formPanel > h1 { + text-align: center; + color: var(--color-primary); +} + +#form { + width: calc(100% - 8rem); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2rem; +} +#form > span { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +#loginButton { + width: 100%; +} + +#register, #forgotPassword { + text-align: center; +} \ No newline at end of file diff --git a/frontend/css/register.css b/frontend/css/register.css new file mode 100644 index 0000000..f98dd3c --- /dev/null +++ b/frontend/css/register.css @@ -0,0 +1,138 @@ + +#halfTrapezoid { + position: absolute; + height: 100%; + overflow: hidden; + z-index: -2; +} +#registerGraphics { + position: absolute; + height: 100%; + width: 50%; + object-fit: contain; + object-position: center; + left: 0; +} +@media screen and (max-width: 50rem) { + #halfTrapezoid { + left: -50%; + } + #registerGraphics { + display: none; + } +} +@media screen and (max-width: 37.5rem) { + #halfTrapezoid { + left: -87.5%; + } +} + +#barComplement, #bar, #barComplementBottom, #barBottom { + position: absolute; + z-index: -1; +} +#barComplement { + height: 62.5%; + top: -50%; + left: 0; +} +#bar { + height: 62.5%; + top: -50%; + left: 62.5%; +} +#barComplementBottom { + height: 62.5%; + top: 75%; + left: 25%; +} +#barBottom { + height: 62.5%; + top: 75%; + left: 87.5%; +} +@media screen and (max-width: 50rem) { + #barComplement { + left: -25%; + } + #bar { + left: 37.5%; + } + #barComplementBottom { + left: 0; + } + #barBottom { + left: 75%; + } +} +@media screen and (max-width: 37.5rem) { + #barComplement { + left: -50%; + } + #bar { + left: 50%; + } + #barComplementBottom { + left: -25%; + } + #barBottom { + left: 75%; + } +} + + +#formPanel { + position: absolute; + width: 37.5%; + height: 100%; + right: 12.5%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4rem; +} +@media screen and (max-width: 50rem) { + #formPanel { + width: calc(100% - 16rem); + right: 8rem; + } +} +@media screen and (max-width: 37.5rem) { + #formPanel { + width: calc(100% - 8rem); + right: 4rem; + } +} +#logo { + width: 100%; +} + +#formPanel > h1 { + text-align: center; + color: var(--color-primary); +} + +#form { + width: calc(100% - 8rem); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2rem; +} +#form > span { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +#registerButton { + width: 100%; +} + +#login { + text-align: center; +} \ No newline at end of file diff --git a/frontend/dashboard_calendar.html b/frontend/dashboard_calendar.html new file mode 100644 index 0000000..2f3a285 --- /dev/null +++ b/frontend/dashboard_calendar.html @@ -0,0 +1,86 @@ + + + + + + + CdMSMS-ICS + + + + + + + + + + + + +
+ + + +
+
+
+ Calendar +
+ +
+
Sunday
+
Monday
+
Tuesday
+
Wednesday
+
Thursday
+
Friday
+
Saturday
+
+ +
+
07:00 - 07:30 AM
+
07:30 - 08:00 AM
+
08:00 - 08:30 AM
+
08:30 - 09:00 AM
+
09:00 - 09:30 AM
+
09:30 - 10:00 AM
+
10:00 - 10:30 AM
+
10:30 - 11:00 AM
+
11:00 - 11:30 AM
+
11:30 - 12:00 NN
+
12:00 - 12:30 NN
+
12:30 - 01:00 PM
+
01:00 - 01:30 PM
+
01:30 - 02:00 PM
+
02:00 - 02:30 PM
+
02:30 - 03:00 PM
+
03:00 - 03:30 PM
+
03:30 - 04:00 PM
+
04:00 - 04:30 PM
+
04:30 - 05:00 PM
+
05:00 - 05:30 PM
+
05:30 - 06:00 PM
+
06:00 - 06:30 PM
+
06:30 - 07:00 PM
+
+ +
+
+
+
+
+
+
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/frontend/dashboard_notification.html b/frontend/dashboard_notification.html new file mode 100644 index 0000000..7ceb19d --- /dev/null +++ b/frontend/dashboard_notification.html @@ -0,0 +1,69 @@ + + + + + + + CdMSMS-ICS + + + + + + + + + + + + +
+ + + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/frontend/forgotPassword.html b/frontend/forgotPassword.html new file mode 100644 index 0000000..a6bd079 --- /dev/null +++ b/frontend/forgotPassword.html @@ -0,0 +1,113 @@ + + + + + + + CdMSMS-ICS + + + + + + + + + + + +
+ +
+ + +

Forgot Password?

+ +
+
+ + +
+ + +
+ +
+
+ + +
+ + +
+ +
+
+ + +
+ + +
+ +
+
+ Already remembered your password? +
+ +
+ Login here +
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/images/CdMSMS-ICS Logo.png b/frontend/images/CdMSMS-ICS Logo.png new file mode 100644 index 0000000..d36e4c2 Binary files /dev/null and b/frontend/images/CdMSMS-ICS Logo.png differ diff --git a/frontend/images/Icon.ico b/frontend/images/Icon.ico new file mode 100644 index 0000000..e7df109 Binary files /dev/null and b/frontend/images/Icon.ico differ diff --git a/frontend/images/Icon.png b/frontend/images/Icon.png new file mode 100644 index 0000000..24ec115 Binary files /dev/null and b/frontend/images/Icon.png differ diff --git a/frontend/images/Logo.png b/frontend/images/Logo.png new file mode 100644 index 0000000..70b21a4 Binary files /dev/null and b/frontend/images/Logo.png differ diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..2ed33b1 --- /dev/null +++ b/frontend/index.js @@ -0,0 +1,228 @@ + +const fs = require('fs'); +const path = require('path'); +const port = parseInt( + fs.readFileSync( + path.join(__dirname, '../backend/.env'), 'utf8' + ).split('\n').find((line) => line.startsWith('PORT')).split('=')[1].replace('"', "") +) + 1; + +const phin = require('phin'); +const cookieParser = require('cookie-parser'); + +const express = require('express'); +const app = express(); + +app.use(express.static(path.join(__dirname))); +app.use(express.static(path.join(__dirname, 'js'))); +app.use(express.static(path.join(__dirname, 'css'))); +app.use(express.static(path.join(__dirname, 'svg'))); +app.use(express.static(path.join(__dirname, 'images'))); + +app.use(cookieParser()); + +app.get('/', (req, res) => { + if (req.headers.cookie) { + const cookie = req.cookies; + if (cookie.token && cookie.identifier && cookie[cookie.identifier]) { + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.redirect('/register'); + } else { + res.redirect('/dashboard_calendar'); + }; + }) + .catch(error => { + console.error(error); + res.redirect('/register'); + }); + } else { + res.redirect('/register'); + }; + } else { + res.redirect('/register'); + }; +}); + +app.get('/login', (req, res) => { + if (req.headers.cookie) { + const cookie = req.cookies; + if (cookie.token && cookie.identifier && cookie[cookie.identifier]) { + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.sendFile(path.join(__dirname, 'login.html')); + } else { + res.redirect('/dashboard_calendar'); + }; + }) + .catch(error => { + console.error(error); + res.sendFile(path.join(__dirname, 'login.html')); + }); + } else { + res.sendFile(path.join(__dirname, 'login.html')); + }; + } else { + res.sendFile(path.join(__dirname, 'login.html')); + }; +}); + +app.get('/register', (req, res) => { + if (req.headers.cookie) { + const cookie = req.cookies; + if (cookie.token && cookie.identifier && cookie[cookie.identifier]) { + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.sendFile(path.join(__dirname, 'register.html')); + } else { + res.redirect('/dashboard_calendar'); + }; + }) + .catch(error => { + console.error(error); + res.sendFile(path.join(__dirname, 'register.html')); + }); + } else { + res.sendFile(path.join(__dirname, 'register.html')); + }; + } else { + res.sendFile(path.join(__dirname, 'register.html')); + }; +}); + +app.get('/forgotPassword', (req, res) => { + if (req.headers.cookie) { + const cookie = req.cookies; + if (cookie.token && cookie.identifier && cookie[cookie.identifier]) { + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.sendFile(path.join(__dirname, 'forgotPassword.html')); + } else { + res.redirect('/dashboard_calendar'); + }; + }) + .catch(error => { + console.error(error); + res.sendFile(path.join(__dirname, 'forgotPassword.html')); + }); + } else { + res.sendFile(path.join(__dirname, 'forgotPassword.html')); + }; + } else { + res.sendFile(path.join(__dirname, 'forgotPassword.html')); + }; +}); + +app.get('/dashboard_calendar', (req, res) => { + if (!req.headers.cookie) { + res.redirect('/login'); + return; + }; + const cookie = req.cookies; + + if (!cookie.token || !cookie.identifier || !cookie[cookie.identifier]) { + res.redirect('/login'); + return; + }; + + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.redirect('/login'); + return; + }; + res.sendFile(path.join(__dirname, 'dashboard_calendar.html')); + }) + .catch(error => { + console.error(error); + res.redirect('/login'); + }); +}); + +app.get('/dashboard_notification', (req, res) => { + if (!req.headers.cookie) { + res.redirect('/login'); + return; + }; + const cookie = req.cookies; + + if (!cookie.token || !cookie.identifier || !cookie[cookie.identifier]) { + res.redirect('/login'); + return; + }; + + phin({ + url: `http://localhost:${port - 1}/api/user/check`, + method: 'POST', + data: JSON.parse(`{ + "token": "${cookie.token}", + "identifier": "${cookie.identifier}", + "${cookie.identifier}": "${cookie[cookie.identifier]}" + }`), + parse: 'json' + }) + .then(response => { + if (response.body.error) { + res.redirect('/login'); + return; + }; + res.sendFile(path.join(__dirname, 'dashboard_notification.html')); + }) + .catch(error => { + console.error(error); + res.redirect('/login'); + }); +}); + +app.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); \ No newline at end of file diff --git a/frontend/js/dashboard_calendar.js b/frontend/js/dashboard_calendar.js new file mode 100644 index 0000000..940e0fd --- /dev/null +++ b/frontend/js/dashboard_calendar.js @@ -0,0 +1,343 @@ +const logoutIcon = document.getElementById('logoutIcon'); +logoutIcon.addEventListener('click', () => { + // Remove all cookies + const cookies = document.cookie.split(';'); + for (const cookie of cookies) { + const eqPos = cookie.indexOf('='); + const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + }; + window.location.href = '/login'; +}); + +const notificationIcon = document.getElementById('notificationIcon'); +notificationIcon.addEventListener('click', () => { + window.location.href = '/dashboard_notification'; +}); + +const calendar = document.getElementById('calendar'); +const timeStamps = document.getElementById('timeStamps'); + +const displaySchedules = () => { + // Remove all schedules + const schedules = document.querySelectorAll('.schedule'); + for (const schedule of schedules) { + schedule.remove(); + }; + for (const requestForm of document.querySelectorAll('.requestForm')) { + requestForm.remove(); + }; + + api('POST', 'user/schedules/', getCookiesJSON()) + .then((res) => { + /** + * @type {{ + * scheduleID: String, + * courseCode: String, + * facilityID: String, + * facultyID: String, + * section: String, + * day: 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday', + * startTime: String, + * endTime: String, + * facultyName: String, + * facilityName: String + * }[]} + */ + const schedules = JSON.parse(res); + const daysPanel = document.getElementById('daysPanel'); + const identifier = getCookiesJSON().identifier; + + const possibleTimes = [ + '07:00:00', '07:30:00', + '08:00:00', '08:30:00', + '09:00:00', '09:30:00', + '10:00:00', '10:30:00', + '11:00:00', '11:30:00', + '12:00:00', '12:30:00', + '13:00:00', '13:30:00', + '14:00:00', '14:30:00', + '15:00:00', '15:30:00', + '16:00:00', '16:30:00', + '17:00:00', '17:30:00', + '18:00:00', '18:30:00', + '19:00:00' + ]; + + for (const schedule of schedules) { + const scheduleElement = document.createElement('div'); + scheduleElement.classList.add('schedule'); + scheduleElement.classList.add(schedule.day); + scheduleElement.classList.add('odd'); + scheduleElement.id = schedule.scheduleID; + scheduleElement.innerHTML = ` +
${schedule.courseCode}
+

${identifier === 'studentID' ? schedule.facultyName : schedule.section}

+

${schedule.facilityName}

+`; + + // Get Top + const startTime = schedule.startTime; + const startTimeIndex = possibleTimes.indexOf(startTime); + const startTimeElement = timeStamps.children[startTimeIndex]; + const top = startTimeElement.offsetTop; + scheduleElement.style.top = `${(top / timeStamps.offsetHeight) * 100}%`; + + // Get Height + const endTime = schedule.endTime; + const endTimeIndex = possibleTimes.indexOf(endTime) - 1; + const endTimeElement = timeStamps.children[endTimeIndex]; + const bottom = endTimeElement.offsetTop + endTimeElement.offsetHeight; + const height = bottom - top; + scheduleElement.style.height = `${(height / timeStamps.offsetHeight) * 100}%`; + + Array.from(daysPanel.children).find(day => day.id === schedule.day).appendChild(scheduleElement); + + scheduleElement.addEventListener('click', async () => { + if (identifier === 'studentID') return; + + for (const requestForm of document.querySelectorAll('.requestForm')) { + requestForm.remove(); + }; + + const requestForm = document.createElement('div'); + requestForm.classList.add('requestForm'); + requestForm.classList.add(schedule.day); + requestForm.classList.add(scheduleElement.classList.contains('even') ? 'even' : 'odd'); + requestForm.style.top = scheduleElement.style.top; + + requestForm.innerHTML = ` +
Request Changes
+ + + + + + + + + + + + + + +`; + + const facilities = []; + + await new Promise((resolve, reject) => + api('POST', 'user/facilities/', getCookiesJSON()) + .then(res => { + const result = JSON.parse(res); + for (const facility of result) { + facilities.push(facility); + }; + resolve(); + }) + .catch(reject) + ); + + const facilityDropDown = requestForm.querySelector('#facilityDropDown'); + for (const facility of facilities) { + const option = document.createElement('option'); + option.value = facility.facilityID; + option.innerText = facility.name; + facilityDropDown.appendChild(option); + }; + facilityDropDown.value = schedule.facilityID; + + const startTimeDropDown = requestForm.querySelector('#startTimeDropDown'); + + const changeListener = () => { + // Add to the endTimesDropDown the remaining times starting from the selected startTime + const endTimeDropDown = requestForm.querySelector('#endTimeDropDown'); + endTimeDropDown.innerHTML = ''; + const selectedStartTime = startTimeDropDown.value; + const startTimeItems = Array.from(startTimeDropDown.children); + const selectedStartTimeIndex = startTimeItems.findIndex(item => item.value === selectedStartTime); + const remainingTimes = startTimeItems.slice(selectedStartTimeIndex + 1); + for (const remainingTime of remainingTimes) { + endTimeDropDown.appendChild(remainingTime.cloneNode(true)); + }; + }; + startTimeDropDown.addEventListener('change', changeListener); + + // Convert schedule's start time to 12-hour format + const startTime12Hour = new Date(`1970-01-01T${schedule.startTime}`).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + startTimeDropDown.value = startTime12Hour; + + changeListener(); + + // Select the schedule's end time + const endTimeDropDown = requestForm.querySelector('#endTimeDropDown'); + const endTime12Hour = new Date(`1970-01-01T${schedule.endTime}`).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); + endTimeDropDown.value = endTime12Hour; + + const cancelButton = requestForm.querySelector('#cancelButton'); + cancelButton.addEventListener('click', () => { + requestForm.remove(); + }); + + if (schedule.day === 'Sunday') + daysPanel.children[1].appendChild(requestForm); + else if (schedule.day === 'Monday') + daysPanel.children[2].appendChild(requestForm); + else if (schedule.day === 'Tuesday') + daysPanel.children[3].appendChild(requestForm); + else if (schedule.day === 'Wednesday') + daysPanel.children[4].appendChild(requestForm); + else if (schedule.day === 'Thursday') + daysPanel.children[5].appendChild(requestForm); + else if (schedule.day === 'Friday') + daysPanel.children[6].appendChild(requestForm); + else if (schedule.day === 'Saturday') + daysPanel.children[5].appendChild(requestForm); + + const submitButton = requestForm.querySelector('#submitButton'); + submitButton.addEventListener('click', () => { + const facilityID = facilityDropDown.value; + const day = requestForm.querySelector('#dayDropDown').value; + const startTime = startTimeDropDown.value; + const endTime = endTimeDropDown.value; + const reason = requestForm.querySelector('#reason').value; + + api('POST', 'user/request/', { + scheduleID: schedule.scheduleID, + facilityID, + day, + startTime, + endTime, + reason, + section: schedule.section.split(' ')[1], + ...getCookiesJSON() + }) + .then(() => { + modal('Success', 'Request submitted successfully.', [{ text: 'OK', onClick: (e, modal, background) => { modal.remove(); background.remove(); displaySchedules(); } }]); + requestForm.remove(); + }) + .catch(err => modal('Error', err.message, [{ text: 'OK', onClick: (e, modal, background) => { modal.remove(); background.remove(); } }])); + }); + }); + }; + + // Sort scheduleElements per day + for (const day of daysPanel.children) { + const scheduleElements = Array.from(day.children).sort((a, b) => { + const aTop = a.offsetTop; + const bTop = b.offsetTop; + return aTop - bTop; + }); + for (const scheduleElement of scheduleElements) { + day.appendChild(scheduleElement); + }; + }; + + // Odd and Even + for (const day of daysPanel.children) { + const scheduleElements = Array.from(day.children); + for (let i = 0; i < scheduleElements.length; i++) { + if (i % 2 === 0) { + scheduleElements[i].classList.remove('even'); + scheduleElements[i].classList.add('odd'); + } else { + scheduleElements[i].classList.remove('odd'); + scheduleElements[i].classList.add('even'); + }; + }; + }; + }) + .catch(err => modal('Error', err.message, [{ text: 'OK', onClick: (e, modal, background) => { modal.remove(); background.remove(); } }])); +}; + +window.addEventListener('load', () => { + displaySchedules(); + api('POST', 'user/identifierHeader/', getCookiesJSON()) + .then(res => { + const calendarHeader = document.getElementById('calendarHeader'); + calendarHeader.innerText = res; + }) + .catch(err => console.error(err)); + + if (getCookiesJSON().identifier === 'facultyID') { + const notificationIcon = document.getElementById('notificationIcon'); + notificationIcon.style.display = 'block'; + }; + + const ws = new WebSocket(`ws://${window.location.host}/`.replace(window.location.port, parseInt(window.location.port) - 1)); + ws.onopen = () => { + ws.send(JSON.stringify({ type: 'join', room: 'calendar_user' })); + }; + ws.onmessage = () => { + displaySchedules(); + }; +}); \ No newline at end of file diff --git a/frontend/js/dashboard_notification.js b/frontend/js/dashboard_notification.js new file mode 100644 index 0000000..9d777c0 --- /dev/null +++ b/frontend/js/dashboard_notification.js @@ -0,0 +1,111 @@ +const logoutIcon = document.getElementById('logoutIcon'); +logoutIcon.addEventListener('click', () => { + // Remove all cookies + const cookies = document.cookie.split(';'); + for (const cookie of cookies) { + const eqPos = cookie.indexOf('='); + const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + }; + window.location.href = '/login'; +}); + +const calendarIcon = document.getElementById('calendarIcon'); +calendarIcon.addEventListener('click', () => { + window.location.href = '/dashboard_calendar'; +}); + + +const requestsPanel = document.getElementById('requests'); +const displayNotifications = () => { + requestsPanel.innerHTML = ''; + api('POST', 'user/requests', getCookiesJSON()) + .then(response => { + /** + * @type {{ + * requestID: String, + * facultyID: String, + * facultyName: String, + * original: { + * facilityID: String, + * facilityName: String, + * day: 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday, + * startTime: String, + * endTime: String + * }, + * request: { + * facilityID: String, + * facilityName: String, + * day: 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday, + * startTime: String, + * endTime: String + * }, + * status: 'pending' | 'rejected' | 'approved', + * requestReason: String, + * rejectReason: String | Null, + * requestDate: String, + * program: 'bsit' | 'bscpe', + * courseCode: String, + * scheduleID: String + * }[]} + */ + const requests = JSON.parse(response); + + for (const request of requests) { + const requestElement = document.createElement('table'); + requestElement.classList.add('request'); + requestElement.innerHTML = ` + +
${request.courseCode}
+ + + +
Original
+ + +
Request
+ + +
Status
+ + + + + +
Facility: ${request.original.facilityName}
+
Day: ${request.original.day}
+
Time: ${request.original.startTime} - ${request.original.endTime}
+ + +
Facility: ${request.request.facilityName}
+
Day: ${request.request.day}
+
Time: ${request.request.startTime} - ${request.request.endTime}
+ + +
Status: ${request.status.charAt(0).toUpperCase() + request.status.slice(1)}
+
${request.status === 'rejected' ? 'Rejection Reason' : 'Reason'}: ${request.status === 'rejected' ? request.rejectReason : request.requestReason}
+
Created: ${new Date(request.requestDate).toLocaleString().split(',')[0]}
+ + + +`; + + requestsPanel.appendChild(requestElement); + }; + }) + .catch(error => { + console.error(error); + }); +}; + +window.addEventListener('load', () => { + displayNotifications(); + + const ws = new WebSocket(`ws://${window.location.host}/`.replace(window.location.port, parseInt(window.location.port) - 1)); + ws.onopen = () => { + ws.send(JSON.stringify({ type: 'join', room: 'notifications_user' })); + }; + ws.onmessage = () => { + displayNotifications(); + }; +}); \ No newline at end of file diff --git a/frontend/js/forgotPassword.js b/frontend/js/forgotPassword.js new file mode 100644 index 0000000..2e06344 --- /dev/null +++ b/frontend/js/forgotPassword.js @@ -0,0 +1,136 @@ + +const emailInput = document.getElementById('emailInput'); +const codeInput = document.getElementById('codeInput'); +const newPasswordInput = document.getElementById('newPasswordInput'); + +const submitRequestButton = document.getElementById('submitRequestButton'); +const submitCodeButton = document.getElementById('submitCodeButton'); +const submitNewPasswordButton = document.getElementById('submitNewPasswordButton'); + +const data = { + email: '', + forgotPasswordCode: '', + password: '' +}; + +submitRequestButton.addEventListener('click', () => { + const email = document.getElementById('email').value; + + if (!email) { + modal('Error', 'Please enter your email', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + + api('POST', 'user/forgotPassword', { email }) + .then(response => { + data.email = email; + modal('Success', response, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + emailInput.style.display = 'none'; + codeInput.style.display = 'flex'; + }) + .catch(error => { + console.error(error); + modal('Error', 'An error occurred', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + }); +}); + +submitCodeButton.addEventListener('click', () => { + const code = document.getElementById('code').value; + + if (!code) { + modal('Error', 'Please enter the code', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + + api('POST', 'user/resetPassword', { email: data.email, forgotPasswordCode: code }) + .then(response => { + data.forgotPasswordCode = code; + modal('Success', response, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + codeInput.style.display = 'none'; + newPasswordInput.style.display = 'flex'; + }) + .catch(error => { + console.error(error); + modal('Error', 'An error occurred', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + }); +}); + +submitNewPasswordButton.addEventListener('click', () => { + const newPassword = document.getElementById('newPassword').value; + + data.password = newPassword; + + api('POST', 'user/changePassword', data) + .then(response => { + modal('Success', response, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + window.location.href = '/login'; + } + } + ]); + }) + .catch(error => { + console.error(error); + modal('Error', 'An error occurred', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + }); +}); \ No newline at end of file diff --git a/frontend/js/globals.js b/frontend/js/globals.js new file mode 100644 index 0000000..d6645d7 --- /dev/null +++ b/frontend/js/globals.js @@ -0,0 +1,113 @@ + +const API_PORT = 3090; + +/** + * @type {( + * method: "GET" | "POST", + * path: String, + * data: Object | Null + * ) => Promise} + */ +const api = (method, path, data) => new Promise((resolve, reject) => { + const uri = `http://localhost:${API_PORT}/api/${path}`; + + // fetch that accepts both string and object data + const options = { + method, + headers: { + 'Content-Type': 'application/json' + }, + body: data ? JSON.stringify(data) : undefined + }; + + fetch(uri, options) + .then(async (response) => { + if (response.ok) { + return await response.text(); + } else { + throw new Error(`HTTP ${response.status}: ${await response.text()}`); + }; + }) + .then(response => { + if (response.error) { + reject(response.error); + } else { + resolve(response); + }; + }) + .catch(error => reject(error)); +}); + +/** + * @type {( + * title: String, + * message: String, + * buttons: { text: String, onClick: ( e: MouseEvent, modal: HTMLElement, background: HTMLElement ) => Void }[] + * ) => Void} + */ +const modal = (title, message, buttons) => { + const modal = document.createElement('div'); + modal.classList.add('modal'); + modal.innerHTML = ` + + +
+

${message}

+
+ +
+
+`; + + document.body.appendChild(modal); + + const background = document.createElement('div'); + background.style.position = 'fixed'; + background.style.top = '0'; + background.style.left = '0'; + background.style.width = '100%'; + background.style.height = '100%'; + background.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + document.body.appendChild(background); + + if (buttons) { + if (buttons.length > 2) throw new Error('Only 2 buttons are allowed'); + + const buttonsPanel = modal.querySelector('#buttonsPanel'); + for (const button of buttons) { + const buttonElement = document.createElement('button'); + buttonElement.classList.add('button'); + buttonElement.innerHTML = ` +
+ ${button.text} +
+ `; + buttonElement.addEventListener('click', e => button.onClick(e, modal, background)); + buttonsPanel.appendChild(buttonElement); + }; + }; + + const close = modal.querySelector('#close'); + close.addEventListener('click', () => { + modal.remove(); + background.remove(); + }); + background.addEventListener('click', () => { + modal.remove(); + background.remove(); + }); +}; + +const getCookiesJSON = () => { + const cookies = document.cookie.split('; '); + const cookiesJSON = {}; + for (const cookie of cookies) { + const [key, value] = cookie.split('='); + cookiesJSON[key] = value; + }; + return cookiesJSON; +}; \ No newline at end of file diff --git a/frontend/js/login.js b/frontend/js/login.js new file mode 100644 index 0000000..0a34e65 --- /dev/null +++ b/frontend/js/login.js @@ -0,0 +1,69 @@ + +const loginButton = document.getElementById('loginButton'); + +loginButton.addEventListener('click', () => { + const user = { + email: document.getElementById('email').value, + password: document.getElementById('password').value + }; + + if ( + user.email === '' + || user.password === '' + || user.email === undefined + || user.password === undefined + ) { + modal('Error', 'All fields are required', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + + api('POST', 'user/login', user) + .then(res => { + if (res.error) { + modal('Error', res.error, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + } else { + modal('Success', 'You have successfully logged in', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + const user = JSON.parse(res); + document.cookie = `token=${user.token}; path=/;`; + document.cookie = `identifier=${user.identifier}; path=/;`; + document.cookie = `${user.identifier}=${user[user.identifier]}; path=/;`; + window.location.href = '/dashboard_calendar'; + }; + }) + .catch(error => { + console.error(error); + modal('Error', error, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + }); +}); \ No newline at end of file diff --git a/frontend/js/register.js b/frontend/js/register.js new file mode 100644 index 0000000..9fedd48 --- /dev/null +++ b/frontend/js/register.js @@ -0,0 +1,114 @@ + +const registerButton = document.getElementById('registerButton'); +registerButton.addEventListener('click', (e) => { + e.preventDefault(); + const user = { + firstName: document.getElementById('firstName').value, + lastName: document.getElementById('lastName').value, + userID: document.getElementById('userID').value, + password: document.getElementById('password').value, + email: document.getElementById('email').value, + password: document.getElementById('password').value, + confirmPassword: document.getElementById('confirmPassword').value + }; + + if ( + user.firstName === '' + || user.lastName === '' + || user.userID === '' + || user.password === '' + || user.email === '' + || user.confirmPassword === '' + || user.firstName === undefined + || user.lastName === undefined + || user.userID === undefined + || user.password === undefined + || user.email === undefined + || user.confirmPassword === undefined + ) { + modal('Error', 'All fields are required', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + if (user.password !== user.confirmPassword) { + modal('Error', 'Passwords do not match', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + if (user.password.length < 8) { + modal('Error', 'Password must be at least 8 characters long', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + if (/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(user.email) === false) { + modal('Error', 'Invalid email address', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + return; + }; + + api('POST', 'user/register', user) + .then(res => { + if (res.error) { + modal('Error', res.error, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + } else { + modal('Success', 'User registered successfully', [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + window.location.href = '/login'; + }; + }) + .catch(err => { + modal('Error', err, [ + { + text: 'OK', + onClick: (e, modal, background) => { + modal.remove(); + background.remove(); + } + } + ]); + console.error(err); + }); +}); \ No newline at end of file diff --git a/frontend/login.html b/frontend/login.html new file mode 100644 index 0000000..a901b13 --- /dev/null +++ b/frontend/login.html @@ -0,0 +1,100 @@ + + + + + + + CdMSMS-ICS + + + + + + + + + + + + +
+ +
+ + +

Login now!

+ +
+
+ + +
+ +
+ + +
+ + +
+ +
+
+ Don't have an account yet? +
+ +
+ Register here +
+
+
+ +
+
+ Forgot your password? +
+ +
+ Reset here +
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..b7df05f --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,754 @@ +{ + "name": "frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cookie-parser": "^1.4.6", + "express": "^4.19.2", + "phin": "^3.7.1" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "dependencies": { + "follow-redirects": "^1.15.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "dependencies": { + "centra": "^2.7.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..c6c4568 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js", + "dev": "nodemon index.js --ignore js/" + }, + "author": "", + "license": "ISC", + "dependencies": { + "cookie-parser": "^1.4.6", + "express": "^4.19.2", + "phin": "^3.7.1" + } +} diff --git a/frontend/register.html b/frontend/register.html new file mode 100644 index 0000000..eb369ca --- /dev/null +++ b/frontend/register.html @@ -0,0 +1,137 @@ + + + + + + + CdMSMS-ICS + + + + + + + + + + + + +
+ +
+ + +

Register now!

+ +
+ +
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+
+ Already have an account? +
+ +
+ Login here +
+
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/svg/Bar Bottom.svg b/frontend/svg/Bar Bottom.svg new file mode 100644 index 0000000..00d870d --- /dev/null +++ b/frontend/svg/Bar Bottom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/svg/Bar Complement Bottom.svg b/frontend/svg/Bar Complement Bottom.svg new file mode 100644 index 0000000..80aba62 --- /dev/null +++ b/frontend/svg/Bar Complement Bottom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/svg/Bar Complement.svg b/frontend/svg/Bar Complement.svg new file mode 100644 index 0000000..ec70810 --- /dev/null +++ b/frontend/svg/Bar Complement.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/svg/Bar.svg b/frontend/svg/Bar.svg new file mode 100644 index 0000000..5e25a0d --- /dev/null +++ b/frontend/svg/Bar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/svg/Calendar Icon Active.svg b/frontend/svg/Calendar Icon Active.svg new file mode 100644 index 0000000..a018405 --- /dev/null +++ b/frontend/svg/Calendar Icon Active.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/svg/Calendar Icon.svg b/frontend/svg/Calendar Icon.svg new file mode 100644 index 0000000..fbf587e --- /dev/null +++ b/frontend/svg/Calendar Icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/svg/Close Window.svg b/frontend/svg/Close Window.svg new file mode 100644 index 0000000..0232c21 --- /dev/null +++ b/frontend/svg/Close Window.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/svg/Half Trapezoid.svg b/frontend/svg/Half Trapezoid.svg new file mode 100644 index 0000000..f621541 --- /dev/null +++ b/frontend/svg/Half Trapezoid.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/svg/Login Graphics.svg b/frontend/svg/Login Graphics.svg new file mode 100644 index 0000000..6be0b95 --- /dev/null +++ b/frontend/svg/Login Graphics.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/svg/Logout Icon.svg b/frontend/svg/Logout Icon.svg new file mode 100644 index 0000000..04c9b66 --- /dev/null +++ b/frontend/svg/Logout Icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/svg/Notification Icon Active.svg b/frontend/svg/Notification Icon Active.svg new file mode 100644 index 0000000..d545f90 --- /dev/null +++ b/frontend/svg/Notification Icon Active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/svg/Notification Icon.svg b/frontend/svg/Notification Icon.svg new file mode 100644 index 0000000..bc0a42f --- /dev/null +++ b/frontend/svg/Notification Icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/svg/Register Graphics.svg b/frontend/svg/Register Graphics.svg new file mode 100644 index 0000000..6be0b95 --- /dev/null +++ b/frontend/svg/Register Graphics.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages.config b/packages.config index 3308d6b..f62d4a0 100644 --- a/packages.config +++ b/packages.config @@ -1,9 +1,22 @@  + + + + + + + - + + + + + + + \ No newline at end of file