Commit 737a7ffd authored by Zac Wood's avatar Zac Wood
Browse files

Added start of v2 front end.

Done with only Rails + Masonstrap + vanilla Javascript to
reduce all the unneeded complexity that came with React
parent 894575fb
......@@ -70,3 +70,7 @@ gem 'apipie-rails'
# Markdown for API docs
gem 'maruku'
# gem 'jquery-rails'
# gem 'font-awesome-sass', '~> 5.3.1'
......@@ -13,3 +13,11 @@
//= require rails-ujs
//= require turbolinks
//= require_tree .
// require jquery3
// require popper
// require bootstrap-sprockets
const elementFromString = string => {
const html = new DOMParser().parseFromString(string, 'text/html');
return html.body.firstChild;
};
// 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('cart-list').children).map(e => Number(e.id));
}
get ids() {
return this._ids;
}
set ids(ids) {
this._ids = ids;
document.getElementById('course-counter').innerText = ids.length;
}
toggle() {
const list = document.getElementById('schedule-list');
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.id)) return;
this.ids = [...this.ids, section.id];
fetch('/search/add/' + section.id);
const courses = document.getElementById('cart-list');
const newCourseCard = this._constructSectionCard(section);
courses.appendChild(newCourseCard);
}
removeFromSchedule(id) {
const cart = document.getElementById('cart-list');
const children = Array.from(cart.children);
const withId = children.findIndex(c => c.id == id);
cart.removeChild(children[withId]);
this.ids = this.ids.filter(_id => _id != Number(id));
fetch('/search/remove/' + id);
}
_constructSectionCard(section) {
const str = `
<li id="${section.id}" class="list-group-item" 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 {
constructor() {
const searchItems = Array.from(document.getElementById('search-list').children);
this.courses = searchItems.map(s => JSON.parse(s.dataset.course));
console.log(this.courses);
}
courseWithId(id) {
return this.courses.filter(c => c.id == id)[0];
}
}
const toggleSchedule = () => this.schedule.toggle();
const addToSchedule = (event, section) => {
console.log(section.dataset.section);
this.schedule.addToSchedule(JSON.parse(section.dataset.section));
event.stopPropagation();
};
const removeFromSchedule = section => {
this.schedule.removeFromSchedule(section.id);
};
const toggleSections = course => {
const sections = course.querySelector('#sections');
if (sections.style.display === 'block') {
course.querySelector('#sections').style.display = 'none';
} else {
course.querySelector('#sections').style.display = 'block';
}
};
document.addEventListener('DOMContentLoaded', () => {
this.schedule = new Schedule();
this.search = new Search();
});
......@@ -13,3 +13,8 @@
*= require_tree .
*= require_self
*/
// @import "bootstrap";
// @import "font-awesome-sprockets";
// @import "font-awesome";
// Place all the styles related to the Search controller here.
// 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);
}
.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);
}
#schedule-list {
display: none;
}
.card .small {
margin-bottom: 16px;
padding: 12px;
}
# Configures the application.
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
before_action :set_cookies
def set_cookies
cookies[:ids] = "" if cookies[:ids].nil?
end
end
class SearchController < ApplicationController
def index
@courses = Course.where(subject: 'HNRS')
@cart = cookies[:ids].split(',').map do |id|
CourseSection.find_by_id id
end
end
def add
ids = cookies[:ids].split(',').to_set
ids.add(params[:id])
cookies[:ids] = ids.to_a.join(',')
end
def remove
ids = cookies[:ids].split(',').to_set
ids.delete(params[:id])
puts ids
cookies[:ids] = ids.to_a.join(',')
end
end
......@@ -3,6 +3,9 @@
<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' %>
......
<h1>Search#index</h1>
<p>Find me in app/views/search/index.html.erb</p>
<div class="container-fluid">
<div class="row align-left align-sm-center align-md-right" id="navbar">
<div class="col-8 col-sm align-center">
<a href="" id="logo">
<i class="fas fa-calendar-alt"></i>
Schedules
</a>
</div>
<div class="col-4 col-sm align-center order-0 order-sm-1" onclick="toggleSchedule()">
<h1 style="margin-top:24px">
<!-- <h2><i class="fas fa-shopping-cart" id="schedule-icon"></i></h2> -->
<span class="fa-layers fa-fw" id="schedule-icon">
<i class="fas fa-shopping-cart"></i>
<span class="fa-layers fa-fw">
<i class="fas fa-circle" data-fa-transform="shrink-3 up-12 right-12" style="color:gray"></i>
<span id="course-counter" class="fa-layers-text fa-inverse" data-fa-transform="shrink-10 up-12 right-12" style="font-weight:600">
<%= @cart.length %>
</span>
</span>
</span>
</h1>
</div>
<div class="col-12 col-sm align-center order-1 order-sm-0">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search by CRN, course, professor..." aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-secondary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<!-- List of Courses -->
<div class="col-lg-7 col-md-10 mx-auto order-2 order-lg-0" id="search-list">
<% @courses.each do |course| %>
<div class="card" id="<%= course.id %>" onclick="toggleSections(this)" data-course="<%= course.to_json %>">
<div class="card-body">
<div>
<h3 style="float: left"><%= "#{course.subject} #{course.course_number}" %></h3>
<h5 style="float: right"><em><%= course.title %></em>. <%= course.credits %> credits.</h5>
</div>
<div style="clear: both"> </div>
<p class="description"><%= course.description %></p>
<div class="d-block" style="text-align: center">
<i class="fas fa-chevron-down"></i>
</div>
<!-- List of Course Sections -->
<ul class="list-group list-group-flush" id="sections" style="display:none">
<% course.course_sections.each do |section| %>
<li id="<%= section.id %>" class="list-group-item section-item" data-section="<%= section.to_json %>" onclick="addToSchedule(event, 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> <%= section.instructor.name %></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>
<% end %>
</ul>
</div>
</div>
<% end %>
</div>
<div class="col order-1 order-lg-1" id="schedule-list">
<div class="card">
<div class="card-body">
<h3 class="card-title">Your Schedule</h3>
<div style="clear: both"> </div>
</div>
<ul class="list-group list-group-flush" id="cart-list">
<% @cart.each do |section| %>
<li id="<%= section.id %>" class="list-group-item" data-section="<%= section.to_json %>" 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> <%= section.instructor.name %></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>
<% end %>
</ul>
<div class="card-body">
<button class="btn btn-primary">Export Schedule</button>
</div>
</div>
</div>
</div>
</div>
# Registers all routes for the app.
Rails.application.routes.draw do
get 'search', to: 'search#index'
get 'search/add/:id', to: 'search#add'
get 'search/remove/:id', to: 'search#remove'
scope :api do # Register /api routes
resources :courses, only: [:index, :show]
resources :course_sections, only: [:index]
......
require 'test_helper'
class SearchControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get search_index_url
assert_response :success
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment