Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
SRCT
schedules
Commits
b6309e4e
Commit
b6309e4e
authored
May 11, 2019
by
Zac Wood
Browse files
Merge branch 'revert-
ea27d283
' into 'master'
Revert "Merge branch '44-instructor-search' into 'master'" See merge request
!49
parents
ea27d283
5ca634f2
Pipeline
#4315
passed with stage
in 3 minutes and 51 seconds
Changes
84
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
schedules/app/javascript/src/SearchList.jsx
deleted
100644 → 0
View file @
ea27d283
import
React
from
'
react
'
;
import
CourseList
from
'
src/CourseList
'
;
import
InstructorList
from
'
src/InstructorList
'
;
export
default
class
SearchList
extends
React
.
Component
{
render
()
{
return
(
<
div
>
<
InstructorList
instructors
=
{
this
.
props
.
instructors
}
/>
<
CourseList
courses
=
{
this
.
props
.
courses
}
/>
</
div
>
);
}
}
schedules/app/javascript/src/Section.jsx
deleted
100644 → 0
View file @
ea27d283
import
React
from
'
react
'
;
import
Cart
from
'
src/Cart
'
;
import
Stars
from
'
src/Stars
'
;
export
default
class
Section
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
inCart
:
Cart
.
includesCrn
(
this
.
props
.
crn
)
};
}
onClick
=
e
=>
{
e
.
stopPropagation
();
console
.
log
(
e
.
target
.
tagName
);
if
(
e
.
target
.
tagName
===
'
A
'
)
return
;
Cart
.
toggleCrn
(
this
.
props
.
crn
);
this
.
setState
({
inCart
:
Cart
.
includesCrn
(
this
.
props
.
crn
)
});
this
.
props
.
onClick
&&
this
.
props
.
onClick
(
this
.
props
.
crn
);
};
render
()
{
const
{
name
,
title
,
crn
,
instructor_name
,
instructor_url
,
teaching_rating
,
course_rating
,
location
,
days
,
start_time
,
end_time
}
=
this
.
props
;
const
{
inCart
}
=
this
.
state
;
const
percent
=
teaching_rating
?
<
Stars
percent
=
{
(
teaching_rating
[
0
]
/
5
)
*
100
}
/>
:
null
;
const
remove
=
(
<
span
className
=
"float-right text-center add-remove-btn"
style
=
{
inCart
?
{}
:
{
display
:
'
none
'
}
}
>
<
i
id
=
"icon"
className
=
"fas fa-minus"
/>
<
br
/>
<
span
className
=
"text"
>
Remove
</
span
>
</
span
>
);
const
add
=
(
<
span
className
=
"float-right text-center add-remove-btn"
style
=
{
inCart
?
{
display
:
'
none
'
}
:
{}
}
>
<
i
id
=
"icon"
className
=
"fas fa-plus"
/>
<
br
/>
<
span
className
=
"text"
>
Add
</
span
>
</
span
>
);
return
(
<
li
className
=
"list-group-item section-item"
onClick
=
{
this
.
onClick
}
>
<
p
>
<
b
>
{
name
}
</
b
>
:
{
title
}{
'
'
}
<
em
>
(#
{
crn
}
)
</
em
>
</
p
>
{
remove
}
{
add
}
<
i
className
=
"fas fa-chalkboard-teacher"
/>
<
a
href
=
{
instructor_url
}
>
{
instructor_name
}
</
a
>
{
percent
}
<
br
/>
<
i
className
=
"fas fa-map-marker-alt"
/>
{
location
}
<
br
/>
<
i
className
=
"fas fa-clock"
/>
{
days
}
,
{
start_time
}
-
{
end_time
}
<
br
/>
</
li
>
);
}
}
schedules/app/javascript/src/SectionList.jsx
deleted
100644 → 0
View file @
ea27d283
import
React
from
'
react
'
;
import
Chevron
from
'
src/Chevron
'
;
import
Section
from
'
src/Section
'
;
export
default
class
SectionList
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
}
render
()
{
return
(
<
div
>
{
this
.
props
.
expandable
?
<
Chevron
open
=
{
this
.
props
.
expanded
}
/>
:
null
}
{
this
.
props
.
expanded
?
(
<
div
className
=
"d-flex list-group list-group-flush sections"
>
{
this
.
props
.
sections
.
map
(
section
=>
(
<
Section
key
=
{
section
.
id
}
onClick
=
{
this
.
props
.
onClick
}
{
...
section
}
/>
))
}
</
div
>
)
:
(
<
div
/>
)
}
</
div
>
);
}
}
schedules/app/javascript/src/Stars.jsx
deleted
100644 → 0
View file @
ea27d283
import
React
from
'
react
'
;
export
default
class
Stars
extends
React
.
Component
{
render
()
{
return
(
<
div
className
=
"star-rating"
>
<
div
className
=
"back-stars"
>
<
i
className
=
"fas fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fas fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fas fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fas fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fas fa-star"
aria
-
hidden
=
"true"
/>
<
div
className
=
"front-stars"
style
=
{
{
width
:
`
${
this
.
props
.
percent
}
%`
}
}
>
<
i
className
=
"fa fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fa fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fa fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fa fa-star"
aria
-
hidden
=
"true"
/>
<
i
className
=
"fa fa-star"
aria
-
hidden
=
"true"
/>
</
div
>
</
div
>
</
div
>
);
}
}
schedules/app/javascript/src/Toolbar.jsx
deleted
100644 → 0
View file @
ea27d283
import
React
from
'
react
'
;
import
BigCalendar
from
'
react-big-calendar
'
;
import
Toolbar
from
'
react-big-calendar/lib/Toolbar
'
;
import
'
!style-loader!css-loader!react-big-calendar/lib/css/react-big-calendar.css
'
;
import
withSizes
from
'
react-sizes
'
;
class
CustomToolbar
extends
Toolbar
{
render
()
{
const
{
label
,
isMobile
}
=
this
.
props
;
if
(
isMobile
&&
label
===
''
)
{
this
.
view
(
'
day
'
);
}
return
(
<
div
className
=
"rbc-toolbar d-flex justify-content-between"
>
{
!
isMobile
&&
(
<
span
className
=
"rbc-btn-group"
>
<
button
type
=
"button"
onClick
=
{
()
=>
this
.
view
(
'
day
'
)
}
>
Day
</
button
>
<
button
type
=
"button"
onClick
=
{
()
=>
this
.
view
(
'
week
'
)
}
>
Week
</
button
>
</
span
>
)
}
<
span
className
=
"rbc-toolbar-label"
>
{
this
.
props
.
label
}
</
span
>
{
this
.
props
.
view
===
'
day
'
&&
(
<
span
className
=
"rbc-btn-group"
>
{
this
.
props
.
label
!==
'
Sun
'
&&
(
<
button
type
=
"button"
onClick
=
{
()
=>
this
.
navigate
(
'
PREV
'
)
}
>
Back
</
button
>
)
}
{
this
.
props
.
label
!==
'
Sat
'
&&
(
<
button
type
=
"button"
onClick
=
{
()
=>
this
.
navigate
(
'
NEXT
'
)
}
>
Next
</
button
>
)
}
</
span
>
)
}
</
div
>
);
}
navigate
=
action
=>
{
console
.
log
(
action
);
this
.
props
.
onNavigate
(
action
);
};
view
=
action
=>
{
this
.
props
.
onView
(
action
);
};
}
const
mapSizesToProps
=
({
width
})
=>
({
isMobile
:
width
<
1000
,
});
export
default
withSizes
(
mapSizesToProps
)(
CustomToolbar
);
schedules/app/javascript/src/cart.js
View file @
b6309e4e
//import '@babel/polyfill';
class
Cart
{
constructor
()
{
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
(
document
.
getElementById
(
'
cart-counter
'
).
innerText
=
this
.
crns
.
length
))
;
}
this
.
isOpen
=
false
;
this
.
_courses
=
[];
get
crns
()
{
const
crnString
=
localStorage
.
getItem
(
'
crns
'
);
if
(
!
crnString
)
return
[]
;
return
JSON
.
parse
(
crnString
);
const
cartData
=
document
.
getElementById
(
'
cart-data
'
);
if
(
cartData
)
{
this
.
_courses
=
JSON
.
parse
(
cartData
.
dataset
.
cart
)
;
}
}
set
crns
(
crnList
)
{
localStorage
.
setItem
(
'
crns
'
,
JSON
.
stringify
(
crnList
));
document
.
getElementById
(
'
cart-counter
'
).
innerText
=
crnList
.
length
;
_parseData
()
{
const
cartData
=
document
.
getElementById
(
'
cart-data
'
);
if
(
cartData
)
{
this
.
_courses
=
JSON
.
parse
(
cartData
.
dataset
.
cart
);
}
}
addCrn
(
crn
)
{
if
(
!
this
.
includesCrn
(
crn
))
{
this
.
crns
=
[...
this
.
crns
,
crn
];
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
;
}
toggleCrn
(
crn
)
{
if
(
!
this
.
includesCrn
(
crn
))
{
this
.
crns
=
[...
this
.
crns
,
crn
];
}
else
{
this
.
crns
=
this
.
crns
.
filter
(
c
=>
c
!=
crn
);
set
courses
(
courses
)
{
this
.
_courses
=
courses
;
for
(
const
courseId
in
this
.
_courses
)
{
if
(
this
.
_courses
[
courseId
].
length
===
0
)
delete
this
.
_courses
[
courseId
];
}
document
.
getElementById
(
'
course-counter
'
).
innerText
=
Object
.
keys
(
this
.
_courses
).
length
;
}
includesCrn
(
crn
)
{
return
this
.
crns
.
filter
(
c
=>
c
==
crn
).
length
>
0
;
async
toggleSection
(
section
)
{
const
resp
=
await
fetch
(
`/sessions/cart?&crn=
${
section
.
crn
}
`
,
{
cache
:
'
no-store
'
,
credentials
:
'
same-origin
'
});
const
json
=
await
resp
.
json
();
this
.
courses
=
json
;
}
includesSection
(
obj
)
{
for
(
const
key
in
this
.
_courses
)
{
const
list
=
this
.
_courses
[
key
];
if
(
list
.
includes
(
obj
.
crn
))
return
true
;
}
return
false
;
}
}
export
default
new
Cart
();
const
cart
=
new
Cart
();
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
cart
.
_parseData
());
export
default
cart
;
schedules/app/models/course.rb
View file @
b6309e4e
...
...
@@ -9,4 +9,53 @@ class Course < ApplicationRecord
def
full_name
"
#{
subject
}
#{
course_number
}
"
end
def
self
.
from_subject
(
base_query
,
subject
)
base_query
.
where
(
"courses.subject = ?"
,
subject
.
upcase
)
end
def
self
.
from_course_number
(
base_query
,
course_number
)
base_query
.
where
(
"courses.course_number = ?"
,
course_number
)
end
def
self
.
from_title
(
base_query
,
title
)
puts
title
# Temporary really disgusting regex that I hate with all my heart
title
=
(
title
+
" "
).
upcase
.
gsub
(
/(I+) +/
,
'\1$'
).
gsub
(
/ +/
,
"% "
).
tr
(
'$'
,
' '
)
base_query
.
where
(
"UPPER(courses.title) LIKE UPPER(?) or UPPER(courses.title) LIKE UPPER(?)"
,
"%
#{
title
.
strip
}
"
,
"%
#{
title
}
%"
)
end
# Given a list of filters, collect a list of matching elements. This makes it
# so you can just pass the arguments straight thru
def
self
.
fetch
(
filters
)
# join with course_sections so that we can get a section count for each course and then sort by that
query
=
Course
.
left_outer_joins
(
:course_sections
)
.
select
(
"courses.*, COUNT(course_sections.id) AS section_count"
)
.
group
(
"courses.id"
)
.
order
(
"section_count DESC"
)
filters
.
each
do
|
filter
,
value
|
case
filter
when
"subject"
query
=
from_subject
(
query
,
value
)
when
"course_number"
query
=
from_course_number
(
query
,
value
)
when
"title"
query
=
from_title
(
query
,
value
)
when
"instructor"
query
=
Instructor
.
from_name
(
query
.
joins
(
"INNER JOIN instructors ON course_sections.instructor_id = instructors.id"
),
value
)
end
end
query
end
# build_set builds
def
self
.
build_set
(
sections
)
courses
=
[].
to_set
sections
.
each
do
|
s
|
courses
.
add
s
.
course
end
courses
end
end
schedules/app/models/course_section.rb
View file @
b6309e4e
...
...
@@ -14,26 +14,6 @@ class CourseSection < ApplicationRecord
validates
:course_id
,
presence:
true
validates
:semester_id
,
presence:
true
serialize
:rating_questions
,
Array
scope
:in_semester
,
->
(
semester
)
{
where
(
semester:
semester
)
}
def
teaching_rating
if
rating_questions
.
empty?
nil
else
"
#{
rating_questions
[
0
][
'instr_mean'
]
}
/
#{
rating_questions
[
0
][
'resp'
]
}
responses"
end
end
def
course_rating
if
rating_questions
.
empty?
nil
else
"
#{
rating_questions
[
1
][
'instr_mean'
]
}
/
#{
rating_questions
[
1
][
'resp'
]
}
responses"
end
end
def
overlaps?
(
other
)
t1_start
,
t1_end
=
Time
.
parse
(
start_time
),
Time
.
parse
(
end_time
)
t2_start
,
t2_end
=
Time
.
parse
(
other
.
start_time
),
Time
.
parse
(
other
.
end_time
)
...
...
schedules/app/models/instructor.rb
View file @
b6309e4e
class
Instructor
<
ApplicationRecord
has_many
:course_sections
scope
:named
,
->
(
name
)
{
name
.
split
(
' '
).
reduce
(
all
)
do
|
query
,
comp
|
query
.
where
(
"upper(instructors.name) LIKE ?"
,
"%
#{
comp
.
upcase
}
%"
)
end
}
def
self
.
from_name
(
base_query
,
name
)
base_query
.
where
(
"upper(instructors.name) LIKE ?"
,
"%
#{
name
.
upcase
}
%"
)
end
def
rating
(
question
=
0
,
sections
=
CourseSection
.
where
(
instructor_id:
id
))
total
=
0
resp
=
0
sections
.
each
do
|
s
|
next
if
s
.
rating_questions
.
empty?
resp
+=
s
.
rating_questions
[
question
][
"resp"
].
to_i
total
+=
s
.
rating_questions
[
question
][
"instr_mean"
].
to_f
*
s
.
rating_questions
[
0
][
"resp"
].
to_i
end
[(
total
/
resp
).
round
(
2
),
resp
]
unless
resp
.
zero?
end
end
schedules/app/views/about/index.html.erb
deleted
100644 → 0
View file @
ea27d283
<div
class=
"jumbotron text-center"
>
<h1><i
class=
"fas fa-calendar-alt"
></i>
SRCT Schedules
</h1>
<p
class=
"lead"
>
Version 3.0
</p>
<hr
/>
Last updated: 2:00am, 4/14/19
</div>
<h3>
Thank you to our contributors who make Schedules possible!
</h3>
Zac Wood, David Haynes, Zach Perkins, Gilberto Barrientos, Michael Bailey, Nic Anderson
<br
/><br/>
<h3>
Questions?
</h3>
All data in Schedules is sourced from data made publicly avaiable by GMU.
<ul>
<li>
Course and section data can be found on
<a
href=
"https://patriotweb.gmu.edu/pls/prod/bwckschd.p_disp_dyn_sched"
>
Patriot Web
</a></li>
<li>
Course review data can be found
<a
href=
"https://crserating.gmu.edu/ReportOnline/"
>
here
</a></li>
</ul>
Please contact SRCT at
<a
href=
"mailto:srct@gmu.edu"
>
srct@gmu.edu
</a>
with any other questions.
schedules/app/views/course_sections/show.html.erb
deleted
100644 → 0
View file @
ea27d283
<h1>
<%=
@section
.
name
%>
-
<%=
@section
.
semester
.
to_s
%>
-
<%=
@section
.
instructor
.
name
%>
</h1>
<ol>
<%
@section
.
rating_questions
.
each
do
|
q
|
%>
<b>
<li>
<%=
q
[
"q"
]
%>
</li>
</b>
Instructor mean:
<%=
q
[
"instr_mean"
]
%>
, Responses:
<%=
q
[
"resp"
]
%>
<%
end
%>
</ol>
schedules/app/views/courses/show.html.erb
View file @
b6309e4e
...
...
@@ -29,5 +29,5 @@
</div>
<%=
javascript_pack_tag
'
instructor
'
%>
<%=
javascript_pack_tag
'
search
'
%>
<%=
stylesheet_link_tag
'search'
%>
schedules/app/views/home/index.html.erb
View file @
b6309e4e
<div
class=
"jumbotron text-center"
>
<h1>
<i
class=
"fas fa-calendar-alt"
></i>
SRCT Schedules
</h1>
<h1>
SRCT Schedules
</h1>
<p
class=
"lead"
>
Build, share, and export your schedule. Search for classes and professors.
</p>
<hr>
<p>
...
...
@@ -36,6 +36,24 @@
</div>
</div>
<div
id=
"quick-add"
/>
<%=
javascript_pack_tag
'home'
%>
<h3
class=
"quick-add-header"
>
Quick add
</h3>
<p>
Want to quickly generate a calendar populated with your semester's classes? Enter the CRNs in a comma separated list below.
</p>
<form
action=
"/sessions/add_bulk"
class=
"form"
>
<div
class=
"input-group"
>
<input
id=
"crns"
name=
"crns"
type=
"text"
class=
"form-control"
placeholder=
"12345,54321,..."
aria-describedby=
"basic-addon2"
autocomplete=
"off"
>
<div
class=
"input-group-append"
>
<button
type=
"submit"
class=
"btn btn-primary"
type=
"button"
>
Populate Calendar
</button>
</div>
</div>
</form>
schedules/app/views/instructors/show.html.erb
View file @
b6309e4e
<div
class=
"row"
>
<div
class=
"col-lg-4 col-12
mb-4
"
>
<div
class=
"col-lg-4 col-12"
>
<h1>
<%=
@instructor
.
name
%>
</h1>
<%
unless
@rating
[
:teaching
].
nil?
%>
Average teaching rating:
<%=
@rating
[
:teaching
][
0
]
%>
/
<%=
@rating
[
:teaching
][
1
]
%>
responses
<%
if
@past
.
count
.
positive?
%>
<strong>
Previously taught:
</strong>
<ul>
<%
@past
.
each
do
|
c
|
%>
<li>
<%=
link_to
(
c
.
full_name
,
course_path
(
c
))
%>
</li>
<%
end
%>
</ul>
<%
end
%>
</div>
<div
class=
"col-lg-8 col-12"
>
<%
@semesters
.
each
do
|
semester
,
sections
|
%>
<h2>
<%=
semester
%>
</h2>
<%=
render
(
partial:
'shared/section'
,
collection:
sections
)
%>
<br/>
<div
class =
"col-lg-8 col-12"
>
<h3>
<%=
@semester
.
to_s
%>
</h3>
<%
if
@courses
.
any?
%>
<%=
render
(
partial:
'shared/course'
,
collection:
@courses
,
locals:
{
expanded:
true
})
%>
<%
else
%>
<p>
<%=
@instructor
.
name
%>
is not teaching any courses this semester...
</p>
<%
end
%>
</div>
</div>
<%=
javascript_pack_tag
'
instructor
'
%>
<%=
javascript_pack_tag
'
search
'
%>
<%=
stylesheet_link_tag
'search'
%>
schedules/app/views/layouts/application.html.erb
View file @
b6309e4e
...
...
@@ -5,8 +5,6 @@
<%=
csrf_meta_tags
%>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<%=
Gon
::
Base
.
render_data
%>
<%=
javascript_include_tag
'masonstrap.min'
%>
<%=
stylesheet_link_tag
'masonstrap.min'
%>
...
...
@@ -18,7 +16,7 @@
<meta
property=
"og:url"
content=
"https://schedules.gmu.edu/"
>
<meta
property=
"og:type"
content=
"website"
>
<meta
property=
"og:title"
content=
"SRCT Schedules"
>
<meta
property=
"og:description"
content=
"
Browse the GMU catalog, see course reviews, build and share your
schedule."
>
<meta
property=
"og:description"
content=
"
Easily generate a calendar with your class
schedule."
>
<meta
property=
"og:site_name"
content=
"SRCT Schedules"
>
<meta
property=
"og:locale"
content=
"en_US"
>
<meta
property=
"article:author"
content=
"SRCT"
>
...
...
@@ -29,11 +27,7 @@
<meta
name=
"twitter:creator"
content=
"@MasonSRCT"
>
<meta
name=
"twitter:url"
content=
"https://schedules.gmu.edu/"
>
<meta
name=
"twitter:title"
content=
"SRCT Schedules"
>
<meta
name=
"twitter:description"
content=
"Browse the GMU catalog, see course reviews, build and share your schedule."
>
<!-- favicons -->
<%=
favicon_link_tag
%>
<link
href=
"
<%=
asset_path
'favicon-32x32.png'
%>
"
sizes=
"32x32"
rel=
"shortcut icon"
type=
"image/png"
/>
<meta
name=
"twitter:description"
content=
"Easily generate a calendar with your class schedule."
>
</head>
<body>
...
...
schedules/app/views/schedules/show.html.erb
View file @
b6309e4e
<%=
javascript_pack_tag
'schedules'
%>
<%=
stylesheet_link_tag
'schedules'
%>
<%=
javascript_include_tag
'moment.min'
%>
<%=
stylesheet_link_tag
'fullcalendar.min'
%>
<button
id=
"open-modal-btn"
type=
"button"
class=
"btn btn-primary"
data-toggle=
"modal"
data-target=
"#exportModal"
>
Export Schedule
</button>
<button
id=
"save-image"
class=
"btn btn-secondary"
>
Save Image
</button>
<div
id=
"
root
"
></div>
<div
id=
"
calendar
"
></div>
<h3>
Quick add
</h3>
<p>
Populate your calendar quickly by entering a comma separated list of CRNs.
</p>
<form
action=
"/sessions/add_bulk"
class=
"form"
>
<div
class=
"input-group"
>
<input
id=
"crns"
name=
"crns"
type=
"text"
class=
"form-control"
placeholder=
"12345,54321,..."
aria-describedby=
"basic-addon2"
autocomplete=
"off"
>
<div
class=
"input-group-append"
>
<button
type=
"submit"
class=
"btn btn-primary"
type=
"button"
>
Populate Calendar
</button>
</div>
</div>
</form>
<h3
id=
"share-header"
>
Share
</h3>
Want to share your schedule with your friends? Send them this link:
<br/>
<a
id=
"share-url"
></a>
<template
id=
"events"
data-events=
"
<%=
@events
.
to_json
%>
"
></template>
<hr
/>
<h2>
Selected Courses
</h2>
<%=
render
partial:
'shared/section'
,
collection:
@all
%>
<!-- Export Modal -->
...
...
schedules/app/views/search/index.html.erb
View file @
b6309e4e
<div
id=
"root"
></div>
<%
unless
@instructors
.
nil?
%>
<h2>
Instructors
</h2>
<div
class=
"row"
>