Commit 3918e500 authored by Zac Wood's avatar Zac Wood
Browse files

Merge branch 'instructor-page' into 'dev-v2'

Instructor page

See merge request !26
parents ea5409e8 25bd1134
Pipeline #2977 passed with stage
in 2 minutes and 13 seconds
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
class Schedule {
constructor() {
this.isOpen = false;
this._ids = Array.from(document.getElementById('schedule').children).map(e => e.dataset.crn);
}
get ids() {
return this._ids;
}
set ids(ids) {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
fetch('/search/update?ids=' + ids.join(','), { cache: 'no-store' });
}
toggle() {
const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon');
if (this.isOpen) {
list.style.display = 'none';
icon.style.color = 'black';
} else {
list.style.display = 'block';
icon.style.color = 'green';
}
this.isOpen = !this.isOpen;
}
addToSchedule(section) {
if (this.ids.includes(section.dataset.crn)) return;
this.ids = [...this.ids, section.dataset.crn];
section.classList.remove('section-item');
section.classList.remove('selected');
section.classList.add('schedule-section-card');
section.onclick = () => removeFromSchedule(section);
document.getElementById('schedule').appendChild(section);
}
removeFromSchedule(id) {
const cart = document.getElementById('schedule');
const section = cart.querySelector(`#section-${id}`);
cart.removeChild(section);
this.ids = this.ids.filter(_id => _id != id);
}
async downloadIcs() {
const cal = await fetch(`/api/schedules?crns=${this.ids.join(',')}`);
const text = await cal.text();
var blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'test.ics');
}
async addToSystemCalendar() {
const url = `webcal://${window.location.hostname}/api/schedule?crns=${this.ids.join(',')}`;
window.open(url, '_self');
}
}
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
class Schedule {
constructor() {
this.isOpen = false;
this._ids = Array.from(document.getElementById('schedule').children).map(e => e.dataset.crn);
}
get ids() {
return this._ids;
}
set ids(ids) {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
fetch('/search/update?ids=' + ids.join(','), { cache: 'no-store' });
}
toggle() {
const list = document.getElementById('cart');
const icon = document.getElementById('schedule-icon');
if (this.isOpen) {
list.style.display = 'none';
icon.style.color = 'black';
} else {
list.style.display = 'block';
icon.style.color = 'green';
}
this.isOpen = !this.isOpen;
}
addToSchedule(section) {
if (this.ids.includes(section.dataset.crn)) return;
this.ids = [...this.ids, section.dataset.crn];
section.classList.remove('section-item');
section.classList.remove('selected');
section.classList.add('schedule-section-card');
section.onclick = () => removeFromSchedule(section);
document.getElementById('schedule').appendChild(section);
}
removeFromSchedule(id) {
const cart = document.getElementById('schedule');
const section = cart.querySelector(`#section-${id}`);
cart.removeChild(section);
this.ids = this.ids.filter(_id => _id != id);
}
_constructSectionCard(section) {
const str = `
<li id="section-${section.crn}" class="list-group-item schedule-section-card" onclick="removeFromSchedule(this)">
<span style="float:left"><b class="subj">${section.name}</b>: ${section.title}</span>
<span style="float:right"><i class="fas fa-map-marker-alt"></i> ${section.location} </span>
<div style="clear: both"></div>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> TODO </span>
<span style="float:right"><i class="fas fa-clock"></i> ${section.days}, ${section.start_time}-${section.end_time} </span>
<div style="clear: both"></div>
</li>`;
return elementFromString(str);
}
}
class Search {
sectionWithCrn(crn) {
return document.getElementById('search-list').querySelector(`[data-crn="${crn}"]`);
}
}
const toggleSchedule = () => this.schedule.toggle();
const addToSchedule = (event, section) => {
section.classList.add('selected');
......@@ -86,8 +16,12 @@ const addToSchedule = (event, section) => {
};
const removeFromSchedule = section => {
this.search.sectionWithCrn(section.dataset.crn).classList.remove('selected');
this.schedule.removeFromSchedule(section.id.split('-')[1]);
const sectionInSearch = this.search.sectionWithCrn(section.dataset.crn);
if (sectionInSearch) {
sectionInSearch.classList.remove('selected');
}
this.schedule.removeFromSchedule(section.dataset.crn);
};
const toggleSections = course => {
......@@ -102,15 +36,3 @@ const toggleSections = course => {
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `https://${window.location.hostname}/api/schedule?crns=${this.schedule.ids.join(',')}`;
};
const downloadIcs = async () => {
const cal = await fetch(`/api/schedules?crns=${this.schedule.ids.join(',')}`);
const text = await cal.text();
var blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'test.ics');
};
const addToSystemCalendar = async () => {
const url = `webcal://${window.location.hostname}/api/schedule?crns=${this.schedule.ids.join(',')}`;
window.open(url, '_self');
};
......@@ -14,7 +14,83 @@
*= require_self
*/
// @import "bootstrap";
body {
background-color: #E4E4E4;
}
// @import "font-awesome-sprockets";
// @import "font-awesome";
.card {
margin-bottom: 12px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
transition: 0.3s;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
box-shadow: 0 0 20px rgba(0,0,0,0.4);
}
.list-group-item:hover {
transition: 0.15s;
background-color: lightgray;
}
.list-group-item.selected {
background-color: lightgreen;
}
.schedule-section-card:hover {
transition: 0.15s;
background-color: red;
}
.align-vertical {
display: flex;
align-items: center;
}
.align-left {
display: flex;
justify-content: flex-start;
align-items: center;
}
.align-center {
display: flex;
justify-content: center;
align-items: center;
}
.align-right {
display: flex;
justify-content: flex-end;
align-items: center;
}
#navbar {
margin-top: 8px;
margin-bottom: 48px;
}
#logo {
font-size: 24pt;
color: black;
}
.form-control:focus {
border-color: transparent;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(0, 0, 255, 0.5);
}
#cart {
display: none;
}
.card .small {
margin-bottom: 16px;
padding: 12px;
}
// Place all the styles related to the instructors controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the search controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
body {
background-color: #E4E4E4;
}
// .container {
// margin: auto;
// width: 100%;
// }
.card {
margin-bottom: 12px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
transition: 0.3s;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
box-shadow: 0 0 20px rgba(0,0,0,0.4);
}
.list-group-item:hover {
transition: 0.15s;
background-color: lightgray;
}
.list-group-item.selected {
background-color: lightgreen;
}
.schedule-section-card:hover {
transition: 0.15s;
background-color: red;
}
.align-vertical {
display: flex;
align-items: center;
}
.align-left {
display: flex;
justify-content: flex-start;
align-items: center;
}
.align-center {
display: flex;
justify-content: center;
align-items: center;
}
.align-right {
display: flex;
justify-content: flex-end;
align-items: center;
}
#navbar {
margin-top: 8px;
margin-bottom: 48px;
}
#logo {
font-size: 24pt;
color: black;
}
.form-control:focus {
border-color: transparent;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.075) inset, 0px 0px 0px rgba(0, 0, 255, 0.5);
}
#cart {
display: none;
}
.card .small {
margin-bottom: 16px;
padding: 12px;
}
class InstructorsController < ApplicationController
before_action :set_instructor, only: [:show]
def index
@instructors = Instructor.all
end
def show
sections = CourseSection.where instructor: @instructor
# TODO: move this to a model somewhere
@courses = [].to_set
sections.each do |s|
@courses.add s.course
end
end
private
def set_instructor
@instructor = Instructor.find_by_id params[:id]
end
end
<h1>Instructors</h1>
<ul>
<% @instructors.each do |i| %>
<li><%= link_to i.name, instructor_path(i) %></li>
<% end %>
</ul>
<h1><%= @instructor.name %></h1>
<% if @courses.any? %>
<%= render partial: 'shared/course', collection: @courses, locals: { expanded: true } %>
<% else %>
<p><%= @instructor.name %> has not taught any courses...</p>
<% end %>
<!DOCTYPE html>
<html>
<html data-turbolinks="false">
<head>
<title>Schedules</title>
<%= csrf_meta_tags %>
<link href="https://srct.gmu.io/masonstrap/css/masonstrap.min.css" rel="stylesheet">
<script src="https://srct.gmu.io/masonstrap/js/masonstrap.min.js"></script>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'masonstrap.min' %>
<%= javascript_include_tag 'FileSaver' %>
<%= javascript_include_tag 'application' %>
<%= stylesheet_link_tag 'masonstrap.min' %>
<%= stylesheet_link_tag 'application' %>
</head>
<body>
<body data-turbolinks="false">
<%= render partial: 'shared/navbar' %>
<%= yield %>
<%= render partial: 'shared/cart'%>
</body>
<%= javascript_tag do %>
document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
});
document.addEventListener('turbolinks:load', () => {
FontAwesome.dom.i2svg();
});
<% end %>
</html>
......@@ -5,48 +5,8 @@
<p>Please try again!</p>
<% end %>
<!-- Export Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Your calendar has been generated!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5>Apple Calendar</h5>
To add your schedule to Apple Calendar, click the "Add to calendar" button below. If you are on a device
running macOS or iOS, this will open a dialogue which will walk you through adding the calendar.
<hr />
<h5>Google Calendar</h5>
<strong>On desktop:</strong>
<br />
Open your <a href="https://calendar.google.com/">Google Calendar</a>. Click the "Settings" button in the top
right, and then click the Settings tab. In the menu on the left, click "Add calendar" and "From URL". Now,
paste the following link inside the text box: <br />
<code id="calendar-link"></code>
<br />
<strong>On mobile (Android only):</strong>
<br />
Click the "Download calendar file" button. This will download the calendar file which you may then open and
add to your calendar.
<hr />
<h5>.ics file</h5>
To download a .ics file containing your schedule, click the "Download calendar file" button below.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="downloadIcs()">Download calendar file</button>
<button type="button" class="btn btn-primary" onclick="addToSystemCalendar()">Add to system calendar</button>
</div>
</div>
</div>
</div>
<%= javascript_tag do %>
document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
this.search = new Search();
});
<% end %>
......@@ -21,3 +21,42 @@
</div>
</div>
<!-- Export Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Your calendar has been generated!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5>Apple Calendar</h5>
To add your schedule to Apple Calendar, click the "Add to calendar" button below. If you are on a device
running macOS or iOS, this will open a dialogue which will walk you through adding the calendar.
<hr />
<h5>Google Calendar</h5>
<strong>On desktop:</strong>
<br />
Open your <a href="https://calendar.google.com/">Google Calendar</a>. Click the "Settings" button in the top
right, and then click the Settings tab. In the menu on the left, click "Add calendar" and "From URL". Now,
paste the following link inside the text box: <br />
<code id="calendar-link"></code>
<br />
<strong>On mobile (Android only):</strong>
<br />
Click the "Download calendar file" button. This will download the calendar file which you may then open and
add to your calendar.
<hr />
<h5>.ics file</h5>
To download a .ics file containing your schedule, click the "Download calendar file" button below.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="downloadIcs()">Download calendar file</button>
<button type="button" class="btn btn-primary" onclick="addToSystemCalendar()">Add to system calendar</button>
</div>
</div>
</div>
</div>
<% expanded = false unless defined? expanded %>
<div class="card" id="course-<%= course.id %>" onclick="toggleSections(this)" data-course="<%= course.to_json %>">
<div class="card-body">
<div>
......@@ -14,8 +16,12 @@
</div>
<!-- List of Course Sections -->
<ul class="list-group list-group-flush" id="sections" style="display:none">
<%= render partial: 'shared/section', collection: course.course_sections, locals: { in_cart: false } %>
<ul class="list-group list-group-flush" id="sections" style="display: <%= expanded ? "block" : "none" %>">
<% if defined?(@instructor) %>
<%= render partial: 'shared/section', collection: course.course_sections.where(instructor: @instructor), locals: { in_cart: false } %>
<% else %>
<%= render partial: 'shared/section', collection: course.course_sections, locals: { in_cart: false } %>
<% end %>
</ul>
</div>
</div>
......@@ -7,7 +7,7 @@
</a>
</div>
<div class="col-4 col-sm align-center order-0 order-sm-1" onclick="toggleSchedule()">
<div class="col-4 col-sm align-center order-0 order-sm-1" onclick="window.schedule.toggle()">
<h1 style="margin-top:24px">
<span class="fa-layers fa-fw" id="schedule-icon">
<i class="fas fa-shopping-cart"></i>
......
......@@ -7,21 +7,21 @@
<span style="float:left"><b class="subj"><%= "#{section.name}" %></b>: <%= section.title %></span>
<span style="float:right"><i class="fas fa-map-marker-alt"></i> <%= section.location %></span>
<div style="clear: both"></div>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> <%= section.instructor.name %></span>
<span style="float:left"><i class="fas fa-chalkboard-teacher"></i> <%= link_to section.instructor.name, instructor_path(section.instructor) %> </span>
<span style="float:right"><i class="fas fa-clock"></i> <%= "#{section.days}, #{section.start_time}-#{section.end_time}" %></span>
<div style="clear: both"></div>
</li>
<% else %>
<li id="section-<%= section.crn %>"
class="list-group-item section-item <%= "selected" if @cart.include? section %>"
data-crn="<%= section.crn %>"
onclick="addToSchedule(event, this)"
class="list-group-item section-item <%= "selected" if @cart.include? section %>"
data-crn="<%= section.crn %>"
onclick="addToSchedule(event, this)"
>
<span style="float:left"><b class="subj"><%= "#{section.name}" %></b>: <%= section.title %></span>