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
69e7ffaa
Commit
69e7ffaa
authored
Oct 10, 2018
by
Zac Wood
Browse files
Merge branch 'calendar-view' into 'dev-v2'
Calendar view See merge request
!31
parents
a09028af
a4018412
Pipeline
#3210
passed with stage
in 2 minutes and 16 seconds
Changes
20
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
schedules/app/assets/javascripts/application.js
View file @
69e7ffaa
...
...
@@ -27,12 +27,12 @@ document.addEventListener('DOMContentLoaded', () => {
this
.
schedule
=
new
Schedule
();
});
/** Loads FontAwesome icons on load; fixes weird flickering */
document
.
addEventListener
(
'
turbolinks:load
'
,
()
=>
{
FontAwesome
.
dom
.
i2svg
();
});
const
setSemester
=
async
select
=>
{
const
resp
=
await
fetch
(
`/sessions/update?semester_id=
${
select
.
value
}
`
);
location
.
reload
(
true
);
};
/** Loads FontAwesome icons on load; fixes weird flickering */
document
.
addEventListener
(
'
turbolinks:load
'
,
()
=>
{
FontAwesome
.
dom
.
i2svg
();
});
schedules/app/assets/javascripts/schedule.js
View file @
69e7ffaa
class
Schedule
{
constructor
()
{
this
.
isOpen
=
false
;
this
.
_courses
=
{};
// {title, id, sections: {id, crn}}
const
cartData
=
document
.
querySelector
(
'
#cart-data
'
);
const
courses
=
Array
.
from
(
cartData
.
content
.
children
);
for
(
const
course
of
courses
)
{
const
{
id
,
title
}
=
course
.
dataset
;
const
sections
=
Array
.
from
(
course
.
children
).
map
(
node
=>
({
...
node
.
dataset
}));
this
.
_courses
[
id
]
=
{
id
,
title
,
sections
};
}
document
.
getElementById
(
'
course-counter
'
).
innerText
=
Object
.
keys
(
this
.
_courses
).
length
;
this
.
_ids
=
Array
.
from
(
document
.
getElementById
(
'
schedule
'
).
children
).
map
(
e
=>
e
.
dataset
.
crn
);
}
get
ids
()
{
return
this
.
_ids
;
get
crns
()
{
return
Object
.
keys
(
this
.
_courses
)
.
map
(
cid
=>
this
.
_courses
[
cid
].
sections
.
map
(
s
=>
s
.
crn
))
.
reduce
((
prev
,
curr
)
=>
[...
prev
,
...
curr
],
[]);
}
set
ids
(
ids
)
{
this
.
_ids
=
ids
;
document
.
getElementById
(
'
course-counter
'
).
innerText
=
ids
.
length
;
fetch
(
'
/sessions/update?crns=
'
+
ids
.
join
(
'
,
'
),
{
cache
:
'
no-store
'
});
get
ids
()
{
return
Object
.
keys
(
this
.
_courses
)
.
map
(
cid
=>
this
.
_courses
[
cid
].
sections
.
map
(
s
=>
s
.
id
))
.
reduce
((
prev
,
curr
)
=>
[...
prev
,
...
curr
],
[]);
}
toggle
()
{
...
...
@@ -30,36 +42,111 @@ class Schedule {
this
.
isOpen
=
!
this
.
isOpen
;
}
addToSchedule
(
section
)
{
if
(
this
.
ids
.
includes
(
section
.
dataset
.
crn
))
return
;
addCourse
(
course
)
{
this
.
_courses
[
course
.
id
]
=
course
;
const
parent
=
document
.
querySelector
(
'
#schedule
'
);
const
current
=
parent
.
querySelector
(
`#schedule-
${
course
.
id
}
`
);
const
newNode
=
this
.
_constructCourseNode
(
course
);
if
(
current
!==
null
)
parent
.
replaceChild
(
newNode
,
current
);
else
parent
.
appendChild
(
newNode
);
document
.
getElementById
(
'
course-counter
'
).
innerText
=
Object
.
keys
(
this
.
_courses
).
length
;
fetch
(
`/sessions/update?section_ids=
${
this
.
ids
.
join
(
'
,
'
)}
`
,
{
cache
:
'
no-store
'
});
}
removeCourse
(
id
)
{
const
sectionIds
=
this
.
_courses
[
id
].
sections
.
map
(
s
=>
s
.
id
);
for
(
const
sectionId
of
sectionIds
)
{
const
sectionCard
=
document
.
querySelector
(
`#section-
${
sectionId
}
`
);
sectionCard
&&
sectionCard
.
classList
.
remove
(
'
selected
'
);
}
delete
this
.
_courses
[
id
];
const
parent
=
document
.
querySelector
(
'
#schedule
'
);
const
current
=
parent
.
querySelector
(
`#schedule-
${
id
}
`
);
parent
.
removeChild
(
current
);
document
.
getElementById
(
'
course-counter
'
).
innerText
=
Object
.
keys
(
this
.
_courses
).
length
;
fetch
(
`/sessions/update?section_ids=
${
this
.
ids
.
join
(
'
,
'
)}
`
,
{
cache
:
'
no-store
'
});
}
courseContainingSection
(
id
)
{
for
(
const
courseId
in
this
.
_courses
)
{
const
course
=
this
.
_courses
[
courseId
];
for
(
const
section
of
course
.
sections
)
{
if
(
section
.
id
==
id
)
return
course
;
}
}
return
undefined
;
}
includesSection
(
id
)
{
return
!!
this
.
courseContainingSection
(
id
);
}
// section: { id, crn }
addSection
(
section
)
{
const
course
=
this
.
_courses
[
section
.
cid
];
if
(
course
)
{
course
.
sections
.
push
(
section
);
this
.
ids
=
[...
this
.
ids
,
section
.
dataset
.
crn
];
const
courseNode
=
document
.
querySelector
(
'
#schedule
'
).
querySelector
(
`#schedule-
${
course
.
id
}
`
);
const
crnList
=
courseNode
.
querySelector
(
'
.crns
'
);
crnList
.
innerText
=
course
.
sections
.
map
(
s
=>
`#
${
s
.
crn
}
`
);
section
.
classList
.
remove
(
'
section-item
'
);
section
.
classList
.
remove
(
'
selected
'
);
section
.
classList
.
add
(
'
schedule-
section
-card
'
);
section
.
onclick
=
()
=>
removeFromSchedule
(
section
)
;
fetch
(
`/sessions/update?section_ids=
${
this
.
ids
.
join
(
'
,
'
)}
`
,
{
cache
:
'
no-store
'
}
);
}
else
{
const
courseCard
=
document
.
getElementById
(
`course-
${
section
.
cid
}
`
);
const
title
=
courseCard
.
querySelector
(
'
#title
'
).
innerText
;
document
.
getElementById
(
'
schedule
'
).
appendChild
(
section
);
this
.
addCourse
({
title
,
id
:
section
.
cid
,
sections
:
[
section
]
});
}
}
removeFromSchedule
(
id
)
{
const
cart
=
document
.
getElementById
(
'
schedule
'
);
const
section
=
cart
.
querySelector
(
`#section-
${
id
}
`
);
cart
.
removeChild
(
section
);
removeSection
(
section
)
{
const
course
=
this
.
courseContainingSection
(
section
.
id
);
course
.
sections
=
course
.
sections
.
filter
(
s
=>
s
.
id
!==
section
.
id
);
const
schedule
=
document
.
querySelector
(
'
#schedule
'
);
const
courseNode
=
schedule
.
querySelector
(
`#schedule-
${
course
.
id
}
`
);
const
crnList
=
courseNode
.
querySelector
(
'
.crns
'
);
if
(
course
.
sections
.
length
===
0
)
{
this
.
removeCourse
(
section
.
cid
);
}
else
{
crnList
.
innerText
=
course
.
sections
.
map
(
s
=>
`#
${
s
.
crn
}
`
);
}
this
.
ids
=
this
.
ids
.
filter
(
_id
=>
_id
!=
id
);
fetch
(
`/sessions/update?section_ids=
${
this
.
ids
.
join
(
'
,
'
)}
`
,
{
cache
:
'
no-store
'
}
);
}
async
downloadIcs
()
{
const
cal
=
await
fetch
(
`/api/schedules?crns=
${
this
.
id
s
.
join
(
'
,
'
)}
`
);
const
cal
=
await
fetch
(
`/api/schedules?crns=
${
this
.
crn
s
.
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
.
id
s
.
join
(
'
,
'
)}
`
;
const
url
=
`webcal://
${
window
.
location
.
hostname
}
/api/schedule?crns=
${
this
.
crn
s
.
join
(
'
,
'
)}
`
;
window
.
open
(
url
,
'
_self
'
);
}
_constructCourseNode
(
course
)
{
let
html
=
`<li id="schedule-
${
course
.
id
}
" class="list-group-item schedule-section-card" onclick="removeCourse(
${
course
.
id
}
)">`
;
html
+=
`<div style="display: flex; justify-content: space-between;">`
;
html
+=
`<b style="min-width: 15%">
${
course
.
title
}
</b>`
;
html
+=
`<span class="crns" style="color: gray; font-size: 10pt;">`
;
html
+=
course
.
sections
.
map
(
s
=>
`#
${
s
.
crn
}
`
).
join
(
'
,
'
);
html
+=
`</span>`
;
html
+=
`</div>`
;
html
+=
`</li>`
;
return
elementFromString
(
html
);
}
}
const
removeCourse
=
id
=>
{
this
.
schedule
.
removeCourse
(
id
);
};
schedules/app/assets/javascripts/search.js
View file @
69e7ffaa
...
...
@@ -3,17 +3,30 @@
const
sectionWithCrn
=
crn
=>
document
.
getElementById
(
'
search-list
'
).
querySelector
(
`[data-crn="
${
crn
}
"]`
);
const
addCourse
=
(
event
,
id
)
=>
{
const
courseCard
=
document
.
getElementById
(
`course-
${
id
}
`
);
const
title
=
courseCard
.
querySelector
(
'
#title
'
).
innerText
;
const
sectionsItems
=
Array
.
from
(
courseCard
.
querySelectorAll
(
'
li
'
));
const
sections
=
sectionsItems
.
map
(
li
=>
({
...
li
.
dataset
}));
this
.
schedule
.
addCourse
({
title
,
id
,
sections
});
sectionsItems
.
forEach
(
s
=>
s
.
classList
.
add
(
'
selected
'
));
event
.
stopPropagation
();
};
/**
* Either adds or removes a section from the schedule depending on
* if it is currently in the schedule.
*/
const
addOrRemoveFromSchedule
=
(
event
,
section
)
=>
{
if
(
this
.
schedule
.
ids
.
includes
(
section
.
dataset
.
crn
))
{
this
.
schedule
.
removeFromSchedule
(
section
.
dataset
.
crn
);
section
.
classList
.
remove
(
'
selected
'
);
const
addOrRemoveFromSchedule
=
(
event
,
sectionNode
)
=>
{
const
section
=
{
...
sectionNode
.
dataset
};
if
(
this
.
schedule
.
includesSection
(
section
.
id
))
{
this
.
schedule
.
removeSection
(
section
);
sectionNode
.
classList
.
remove
(
'
selected
'
);
}
else
{
this
.
schedule
.
add
ToSchedule
(
section
.
cloneNode
(
true
)
);
section
.
classList
.
add
(
'
selected
'
);
this
.
schedule
.
add
Section
(
section
);
section
Node
.
classList
.
add
(
'
selected
'
);
}
event
.
stopPropagation
();
...
...
schedules/app/assets/stylesheets/application.scss
View file @
69e7ffaa
...
...
@@ -99,3 +99,12 @@ body {
margin-bottom
:
16px
;
padding
:
12px
;
}
#add-course-btn
:hover
{
background-color
:
rgba
(
0
,
0
,
0
,
0
.2
);
}
#calendar
{
background-color
:
white
;
padding
:
16px
;
}
schedules/app/controllers/application_controller.rb
View file @
69e7ffaa
...
...
@@ -12,15 +12,17 @@ class ApplicationController < ActionController::Base
end
def
set_cart
@cart
=
cookies
[
:crns
].
split
(
','
).
map
do
|
crn
|
s
=
CourseSection
.
find_by_crn
(
crn
)
s
if
s
.
course
.
semester
==
@semester
sections
=
cookies
[
:section_ids
].
split
(
','
).
map
do
|
id
|
CourseSection
.
find_by_id
(
id
)
end
@cart
.
compact!
@cart
=
sections
.
group_by
do
|
s
|
s
.
course
.
id
end
end
def
set_cookies
cookies
[
:crns
]
=
""
if
cookies
[
:crns
].
nil?
cookies
[
:section_ids
]
=
""
if
cookies
[
:section_ids
].
nil?
end
end
schedules/app/controllers/schedules_controller.rb
View file @
69e7ffaa
...
...
@@ -15,4 +15,19 @@ class SchedulesController < ApplicationController
@schedule
=
Schedule
.
new
crns
render
plain:
@schedule
.
to_ical
# render a plaintext iCal file
end
def
show
@events
=
@cart
.
map
do
|
_cid
,
sections
|
s
=
sections
.
first
formatted_date
=
Date
.
today
.
to_s
.
tr
(
'-'
,
''
)
formatted_time
=
Time
.
parse
(
s
.
start_time
).
strftime
(
"%H%M%S"
)
formatted_endtime
=
Time
.
parse
(
s
.
end_time
).
strftime
(
"%H%M%S"
)
{
title:
s
.
name
,
start:
"
#{
formatted_date
}
T
#{
formatted_time
}
"
,
end:
"
#{
formatted_date
}
#{
formatted_endtime
}
"
}
end
end
end
schedules/app/controllers/sessions_controller.rb
View file @
69e7ffaa
class
SessionsController
<
ApplicationController
def
update
update_cookie
:crns
update_cookie
:section_ids
update_cookie
:semester_id
head
:ok
...
...
schedules/app/helpers/application_helper.rb
View file @
69e7ffaa
module
ApplicationHelper
def
in_cart?
(
id
)
@cart
.
select
{
|
_cid
,
sections
|
sections
.
select
{
|
s
|
s
.
id
==
id
}.
count
.
positive?
}.
count
.
positive?
end
end
schedules/app/views/layouts/application.html.erb
View file @
69e7ffaa
...
...
@@ -5,10 +5,13 @@
<%=
csrf_meta_tags
%>
<%=
javascript_include_tag
'masonstrap.min'
%>
<%=
javascript_include_tag
'moment.min'
%>
<%=
javascript_include_tag
'fullcalendar.min'
%>
<%=
javascript_include_tag
'FileSaver'
%>
<%=
javascript_include_tag
'application'
%>
<%=
stylesheet_link_tag
'masonstrap.min'
%>
<%=
stylesheet_link_tag
'masonstrap.min'
%>
<%=
stylesheet_link_tag
'fullcalendar.min'
%>
<%=
stylesheet_link_tag
'application'
%>
</head>
...
...
schedules/app/views/schedules/show.html.erb
0 → 100644
View file @
69e7ffaa
<div
id=
"calendar"
></div>
<template
id=
"events"
data-events=
"
<%=
@events
.
to_json
%>
"
></template>
<script>
/* const cal = document.querySelector('#calendar');
* var calendar = new Calendar(cal, {
* defaultView: 'agendaWeek'
* }); */
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
const
eventsJSON
=
document
.
querySelector
(
'
#events
'
).
dataset
.
events
;
const
events
=
JSON
.
parse
(
eventsJSON
);
console
.
log
(
events
);
$
(
'
#calendar
'
).
fullCalendar
({
defaultView
:
'
agendaWeek
'
,
header
:
{
right
:
'
next
'
},
events
:
events
});
});
</script>
schedules/app/views/search/index.html.erb
View file @
69e7ffaa
...
...
@@ -5,8 +5,3 @@
<p>
Please try again!
</p>
<%
end
%>
<%=
javascript_tag
do
%>
document.addEventListener('DOMContentLoaded', () => {
this.search = new Search();
});
<%
end
%>
schedules/app/views/shared/_cart.html.erb
View file @
69e7ffaa
...
...
@@ -4,10 +4,20 @@
<div
class=
"col order-1 order-lg-1"
id=
"cart"
>
<div
class=
"card"
>
<div
class=
"card-body"
>
<h3
class=
"card-title"
>
Your Schedule
</h3>
<h3
class=
"card-title"
>
<%=
link_to
'Your Schedule'
,
schedule_path
%>
</h3>
</div>
<ul
class=
"list-group list-group-flush"
id=
"schedule"
>
<%=
render
partial:
'shared/section'
,
collection:
@cart
,
locals:
{
in_cart:
true
}
%>
<%
@cart
.
each
do
|
cid
,
sections
|
%>
<%
course
=
Course
.
find_by_id
(
cid
)
%>
<li
id=
"schedule-
<%=
cid
%>
"
class=
"list-group-item schedule-section-card"
onclick=
"removeCourse(
<%=
cid
%>
)"
>
<div
style=
"display: flex; justify-content: space-between;"
>
<b
style=
"min-width: 15%"
>
<%=
"
#{
course
.
subject
}
#{
course
.
course_number
}
"
%>
</b>
<span
class=
"crns"
style=
"color: gray; font-size: 10pt;"
>
<%=
sections
.
map
{
|
s
|
"#
#{
s
.
crn
}
"
}.
join
(
', '
)
%>
</span>
</div>
</li>
<%
end
%>
</ul>
<div
class=
"card-body"
>
<button
type=
"button"
class=
"btn btn-primary"
data-toggle=
"modal"
data-target=
"#exportModal"
onclick=
"setUrlInModal()"
>
...
...
@@ -17,6 +27,16 @@
</div>
</div>
<template
id=
"cart-data"
>
<%
@cart
.
each
do
|
cid
,
sections
|
%>
<%
course
=
Course
.
find_by_id
cid
%>
<div
data-id=
"
<%=
cid
%>
"
data-title=
"
<%=
"
#{
course
.
subject
}
#{
course
.
course_number
}
"
%>
"
>
<%
sections
.
each
do
|
s
|
%>
<div
data-id=
"
<%=
s
.
id
%>
"
data-crn=
"
<%=
s
.
crn
%>
"
></div>
<%
end
%>
</div>
<%
end
%>
</template>
</div>
</div>
...
...
schedules/app/views/shared/_course.html.erb
View file @
69e7ffaa
<%
expanded
=
false
unless
defined?
expanded
%>
<div
class=
"card"
id=
"course-
<%=
course
.
id
%>
"
onclick=
"toggleSections(this)"
>
<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
class=
"card-body"
>
<div
style=
"display: flex; justify-content: space-between"
>
<h3
id=
"title"
>
<%=
"
#{
course
.
subject
}
#{
course
.
course_number
}
"
%>
</h3>
<div
style=
"display: flex; flex-direction: column; justify-content: center;"
>
<div
style=
"display: flex"
>
<h5><em>
<%=
course
.
title
%>
</em>
.
<%=
course
.
credits
%>
credits.
</h5>
<h4
id=
"add-course-btn"
onclick=
"addCourse(event, '
<%=
course
.
id
%>
');"
>
<i
class=
"fas fa-plus"
style=
"color: green"
></i>
</h4>
</div>
</div>
</div>
<div
style=
"clear: both"
>
</div>
<div
style=
"clear: both"
>
</div>
<p
class=
"description"
>
<%=
course
.
description
%>
</p>
<p
class=
"description"
>
<%=
course
.
description
%>
</p>
<%
unless
course
.
prereqs
.
nil?
||
course
.
prereqs
.
empty?
%>
<%
first
,
rest
=
course
.
prereqs
.
split
(
':'
)
%>
<%
prereqs
,
note
=
rest
.
split
(
'.'
)
%>
<p><strong>
<%=
first
%>
:
</strong>
<%=
prereqs
%>
<sub>
<%=
note
%>
</sub></p>
<%
end
%>
<%
unless
course
.
prereqs
.
nil?
||
course
.
prereqs
.
empty?
%>
<%
first
,
rest
=
course
.
prereqs
.
split
(
':'
)
%>
<%
prereqs
,
note
=
rest
.
split
(
'.'
)
%>
<p><strong>
<%=
first
%>
:
</strong>
<%=
prereqs
%>
<sub>
<%=
note
%>
</sub></p>
<%
end
%>
<div
class=
"d-block"
style=
"text-align: center"
>
<p
style=
"margin-bottom:-4px; font-size: 10px;"
>
Expand
</p>
<i
class=
"fas fa-chevron-down"
></i>
</div>
<div
class=
"d-block"
style=
"text-align: center"
>
<p
style=
"margin-bottom:-4px; font-size: 10px;"
>
Expand
</p>
<i
class=
"fas fa-chevron-down"
></i>
</div>
<!-- List of Course Sections -->
<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:
fal
se
}
%>
<%
else
%>
<%=
render
partial:
'shared/section'
,
collection:
course
.
course_sections
,
locals:
{
in_cart:
fal
se
}
%>
<%
end
%>
</ul>
</div>
<!-- List of Course Sections -->
<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:
{
course:
cour
se
}
%>
<%
else
%>
<%=
render
partial:
'shared/section'
,
collection:
course
.
course_sections
,
locals:
{
course:
cour
se
}
%>
<%
end
%>
</ul>
</div>
</div>
schedules/app/views/shared/_navbar.html.erb
View file @
69e7ffaa
...
...
@@ -7,7 +7,7 @@
Schedules
</a>
<select
onchange=
"setSemester(this)"
>
<%
for
s
emester
in
S
emester
.
all
%>
<%
S
emester
.
all
.
each
do
|
s
emester
|
%>
<option
value=
"
<%=
semester
.
id
%>
"
<%
if
@semester
==
semester
%>
selected
<%
end
%>
>
<%=
"
#{
semester
.
season
}
#{
semester
.
year
}
"
%>
</option>
...
...
schedules/app/views/shared/_section.html.erb
View file @
69e7ffaa
<%
if
in_cart
%>
<li
id=
"section-
<%=
section
.
crn
%>
"
class=
"list-group-item schedule-section-card"
data-crn=
"
<%=
section
.
crn
%>
"
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>
<%=
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=
"addOrRemoveFromSchedule(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>
<%=
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>
<%
end
%>
<li
id=
"section-
<%=
section
.
id
%>
"
class=
"list-group-item section-item
<%=
"selected"
if
in_cart?
section
.
id
%>
"
data-crn=
"
<%=
section
.
crn
%>
"
data-id=
"
<%=
section
.
id
%>
"
data-cid=
"
<%=
course
.
id
%>
"
onclick=
"addOrRemoveFromSchedule(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>
<%=
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>
schedules/config/initializers/assets.rb
View file @
69e7ffaa
...
...
@@ -16,4 +16,7 @@ Rails.application.config.assets.precompile += %w(
schedule.js
masonstrap.min.css
masonstrap.min.js
moment.min.js
fullcalendar.min.js
fullcalendar.min.css
)
schedules/config/routes.rb
View file @
69e7ffaa
...
...
@@ -4,6 +4,7 @@ Rails.application.routes.draw do
get
'sessions/update'
,
as:
'update_session'
resources
:instructors
,
only:
[
:index
,
:show
]
get
'schedule'
,
to:
'schedules#show'
,
as:
'schedule'
scope
:api
do
# Register /api routes
resources
:courses
,
only:
[
:index
,
:show
]
...
...
schedules/lib/assets/javascripts/fullcalendar.min.js
0 → 100644
View file @
69e7ffaa
This source diff could not be displayed because it is too large. You can
view the blob
instead.
schedules/lib/assets/javascripts/moment.min.js
0 → 100644
View file @
69e7ffaa
This diff is collapsed.
Click to expand it.
schedules/lib/assets/stylesheets/fullcalendar.min.css
0 → 100644
View file @
69e7ffaa
/*!
* FullCalendar v3.9.0
* Docs & License: https://fullcalendar.io/
* (c) 2018 Adam Shaw
*/
.fc
button
,
.fc
table
,
body
.fc
{
font-size
:
1em
}
.fc-bg
,
.fc-row
.fc-bgevent-skeleton
,
.fc-row
.fc-highlight-skeleton
{
bottom
:
0
}
.fc-icon
,
.fc-unselectable
{
-webkit-touch-callout
:
none
;
-khtml-user-select
:
none
}
.fc
{
direction
:
ltr
;
text-align
:
left
}
.fc-rtl
{
text-align
:
right
}
.fc
th
,
.fc-basic-view
td
.fc-week-number
,
.fc-icon
,
.fc-toolbar
{
text-align
:
center
}
.fc-highlight
{
background
:
#bce8f1
;
opacity
:
.3
}
.fc-bgevent
{
background
:
#8fdf82
;
opacity
:
.3
}
.fc-nonbusiness
{
background
:
#d7d7d7
}
.fc
button
{
-moz-box-sizing
:
border-box
;
-webkit-box-sizing
:
border-box
;
box-sizing
:
border-box
;
margin
:
0
;
height
:
2.1em
;
padding
:
0
.6em
;
white-space
:
nowrap
;
cursor
:
pointer
}
.fc
button
::-moz-focus-inner
{
margin
:
0
;
padding
:
0
}
.fc-state-default
{
border
:
1px
solid
;
background-color
:
#f5f5f5
;
background-image
:
-moz-linear-gradient
(
top
,
#fff
,
#e6e6e6
);
background-image
:
-webkit-gradient
(
linear
,
0
0
,
0
100%
,
from
(
#fff
),
to
(
#e6e6e6
));
background-image
:
-webkit-linear-gradient
(
top
,
#fff
,
#e6e6e6
);
background-image
:
-o-linear-gradient
(
top
,
#fff
,
#e6e6e6
);
background-image
:
linear-gradient
(
to
bottom
,
#fff
,
#e6e6e6
);
background-repeat
:
repeat-x
;
border-color
:
#e6e6e6
#e6e6e6
#bfbfbf
;
border-color
:
rgba
(
0
,
0
,
0
,
.1
)
rgba
(
0
,
0
,
0
,
.1
)
rgba
(
0
,
0
,
0
,
.25
);
color
:
#333
;
text-shadow
:
0
1px
1px
rgba
(
255
,
255
,
255
,
.75
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
.2
),
0
1px
2px
rgba
(
0
,
0
,
0
,
.05
)}
.fc-state-default.fc-corner-left
{
border-top-left-radius
:
4px
;
border-bottom-left-radius
:
4px
}
.fc-state-default.fc-corner-right
{
border-top-right-radius
:
4px
;
border-bottom-right-radius
:
4px
}
.fc
button
.fc-icon
{
position
:
relative
;
top
:
-.05em
;
margin
:
0
.2em
;
vertical-align
:
middle
}
.fc-state-active
,
.fc-state-disabled
,
.fc-state-down
,
.fc-state-hover
{
color
:
#333
;
background-color
:
#e6e6e6
}
.fc-state-hover
{
color
:
#333
;
text-decoration
:
none
;
background-position
:
0
-15px
;
-webkit-transition
:
background-position
.1s
linear
;
-moz-transition
:
background-position
.1s
linear
;
-o-transition
:
background-position
.1s
linear
;
transition
:
background-position
.1s
linear
}
.fc-state-active
,
.fc-state-down
{
background-color
:
#ccc
;
background-image
:
none
;
box-shadow
:
inset
0
2px
4px
rgba
(
0
,
0
,
0
,
.15
),
0
1px
2px
rgba
(
0
,
0
,
0
,
.05
)}
.fc-state-disabled
{
cursor
:
default
;
background-image
:
none
;
opacity
:
.65
;
box-shadow
:
none
}
.fc-event.fc-draggable
,
.fc-event
[
href
],
.fc-popover
.fc-header
.fc-close
,
a
[
data-goto
]
{
cursor
:
pointer
}
.fc-button-group
{
display
:
inline-block
}
.fc
.fc-button-group
>*
{
float
:
left
;
margin
:
0
0
0
-1px
}
.fc
.fc-button-group
>
:first-child
{
margin-left
:
0
}
.fc-popover
{
position
:
absolute
;
box-shadow
:
0
2px
6px
rgba
(
0
,
0
,
0
,
.15
)}
.fc-popover
.fc-header
{
padding
:
2px
4px
}
.fc-popover
.fc-header
.fc-title
{
margin
:
0
2px
}
.fc-ltr
.fc-popover
.fc-header
.fc-title
,
.fc-rtl
.fc-popover
.fc-header
.fc-close
{
float
:
left
}
.fc-ltr
.fc-popover
.fc-header
.fc-close
,
.fc-rtl
.fc-popover
.fc-header
.fc-title
{
float
:
right
}
.fc-divider
{
border-style
:
solid
;
border-width
:
1px
}
hr
.fc-divider
{
height
:
0
;
margin
:
0
;
padding
:
0
0
2px
;
border-width
:
1px
0
}
.fc-bg
table
,
.fc-row
.fc-bgevent-skeleton
table
,
.fc-row
.fc-highlight-skeleton
table
{
height
:
100%
}
.fc-clear
{
clear
:
both
}
.fc-bg
,
.fc-bgevent-skeleton
,
.fc-helper-skeleton
,
.fc-highlight-skeleton
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
}
.fc
table
{
width
:
100%
;
box-sizing
:
border-box
;
table-layout
:
fixed
;
border-collapse
:
collapse
;
border-spacing
:
0
}
.fc
td
,
.fc
th
{
border-style
:
solid
;
border-width
:
1px
;
padding
:
0
;
vertical-align
:
top
}
.fc
td
.fc-today
{
border-style
:
double
}
a
[
data-goto
]
:hover
{
text-decoration
:
underline
}
.fc
.fc-row
{
border-style
:
solid
;
border-width
:
0
}
.fc-row
table
{
border-left
:
0
hidden
transparent
;
border-right
:
0
hidden
transparent
;
border-bottom
:
0
hidden
transparent
}
.fc-row
:first-child
table
{
border-top
:
0
hidden
transparent
}
.fc-row
{
position
:
relative
}
.fc-row
.fc-bg
{
z-index
:
1
}
.fc-row
.fc-bgevent-skeleton
td
,
.fc-row
.fc-highlight-skeleton
td
{
border-color
:
transparent
}
.fc-row
.fc-bgevent-skeleton
{
z-index
:
2
}
.fc-row
.fc-highlight-skeleton
{
z-index
:
3
}
.fc-row
.fc-content-skeleton
{
position
:
relative
;
z-index
:
4
;
padding-bottom
:
2px
}
.fc-row
.fc-helper-skeleton
{
z-index
:
5
}
.fc
.fc-row
.fc-content-skeleton
table
,
.fc
.fc-row
.fc-content-skeleton
td
,
.fc
.fc-row
.fc-helper-skeleton
td
{
background
:
0
0
;
border-color
:
transparent
}
.fc-row
.fc-content-skeleton
td
,
.fc-row
.fc-helper-skeleton
td
{
border-bottom
:
0
}
.fc-row
.fc-content-skeleton
tbody
td
,
.fc-row
.fc-helper-skeleton
tbody
td
{
border-top
:
0
}
.fc-scroller
{
-webkit-overflow-scrolling
:
touch
}
.fc-icon
,
.fc-row.fc-rigid
,
.fc-time-grid-event
{
overflow
:
hidden
}
.fc-scroller
>
.fc-day-grid
,
.fc-scroller
>
.fc-time-grid
{
position
:
relative
;
width
:
100%
}
.fc-event
{
position
:
relative
;
display
:
block
;
font-size
:
.85em
;
line-height
:
1.3
;
border-radius
:
3px
;
border
:
1px
solid
#3a87ad
}
.fc-event
,
.fc-event-dot
{
background-color
:
#3a87ad
}
.fc-event
,
.fc-event
:hover
{
color
:
#fff
;
text-decoration
:
none
}
.fc-not-allowed
,
.fc-not-allowed
.fc-event
{
cursor
:
not-allowed
}
.fc-event
.fc-bg
{
z-index
:
1
;
background
:
#fff
;
opacity
:
.25
}
.fc-event
.fc-content
{
position
:
relative
;
z-index
:
2
}
.fc-event
.fc-resizer
{
position
:
absolute
;
z-index
:
4
;
display
:
none
}
.fc-event.fc-allow-mouse-resize
.fc-resizer
,
.fc-event.fc-selected
.fc-resizer
{
display
:
block
}
.fc-event.fc-selected
.fc-resizer
:before
{
content
:
""
;
position
:
absolute
;
z-index
:
9999
;
top
:
50%
;
left
:
50%
;
width
:
40px
;
height
:
40px
;
margin-left
:
-20px
;
margin-top
:
-20px
}
.fc-event.fc-selected
{
z-index
:
9999
!important
;
box-shadow
:
0
2px
5px
rgba
(
0
,
0
,
0
,
.2
)}
.fc-event.fc-selected.fc-dragging
{
box-shadow
:
0
2px
7px
rgba
(
0
,
0
,
0
,
.3
)}
.fc-h-event.fc-selected
:before
{
content
:
""
;
position
:
absolute
;
z-index
:
3
;
top
:
-10px
;
bottom
:
-10px
;
left
:
0
;
right
:
0
}
.fc-ltr
.fc-h-event.fc-not-start
,
.fc-rtl
.fc-h-event.fc-not-end
{
margin-left
:
0
;
border-left-width
:
0
;
padding-left
:
1px
;
border-top-left-radius
:
0
;
border-bottom-left-radius
:
0
}
.fc-ltr
.fc-h-event.fc-not-end
,
.fc-rtl
.fc-h-event.fc-not-start
{
margin-right
:
0
;
border-right-width
:
0
;
padding-right
:
1px
;
border-top-right-radius
:
0
;
border-bottom-right-radius
:
0
}
.fc-ltr
.fc-h-event
.fc-start-resizer
,
.fc-rtl
.fc-h-event
.fc-end-resizer
{
cursor
:
w-resize
;
left
:
-1px
}
.fc-ltr
.fc-h-event
.fc-end-resizer
,
.fc-rtl
.fc-h-event
.fc-start-resizer
{
cursor
:
e-resize
;
right
:
-1px
}
.fc-h-event.fc-allow-mouse-resize
.fc-resizer
{
width
:
7px
;
top
:
-1px
;
bottom
:
-1px
}
.fc-h-event.fc-selected
.fc-resizer
{
border-radius
:
4px
;
border-width
:
1px
;
width
:
6px
;
height
:
6px
;
border-style
:
solid
;
border-color
:
inherit
;
background
:
#fff
;
top
:
50%
;
margin-top
:
-4px
}
.fc-ltr
.fc-h-event.fc-selected
.fc-start-resizer
,
.fc-rtl
.fc-h-event.fc-selected
.fc-end-resizer
{
margin-left
:
-4px
}
.fc-ltr
.fc-h-event.fc-selected
.fc-end-resizer
,
.fc-rtl
.fc-h-event.fc-selected
.fc-start-resizer
{
margin-right
:
-4px
}
.fc-day-grid-event
{
margin
:
1px
2px
0
;
padding
:
0
1px
}
tr
:first-child
>
td
>
.fc-day-grid-event
{
margin-top
:
2px
}
.fc-day-grid-event.fc-selected
:after
{
content
:
""
;
position
:
absolute
;
z-index
:
1
;
top
:
-1px
;
right
:
-1px
;
bottom
:
-1px
;
left
:
-1px
;
background
:
#000
;
opacity
:
.25
}
.fc-day-grid-event
.fc-content
{
white-space
:
nowrap
;
overflow
:
hidden
}
.fc-day-grid-event
.fc-time
{
font-weight
:
700
}
.fc-ltr
.fc-day-grid-event.fc-allow-mouse-resize
.fc-start-resizer
,
.fc-rtl
.fc-day-grid-event.fc-allow-mouse-resize
.fc-end-resizer
{
margin-left
:
-2px
}
.fc-ltr
.fc-day-grid-event.fc-allow-mouse-resize
.fc-end-resizer
,
.fc-rtl
.fc-day-grid-event.fc-allow-mouse-resize
.fc-start-resizer
{
margin-right
:
-2px
}
a
.fc-more
{
margin
:
1px
3px
;
font-size
:
.85em
;
cursor
:
pointer
;
text-decoration
:
none
}
a
.fc-more
:hover
{
text-decoration
:
underline
}
.fc.fc-bootstrap3
a
,
.ui-widget
.fc-event
{
text-decoration
:
none
}
.fc-limited
{
display
:
none
}
.fc-icon
,
.fc-toolbar
.fc-center
{
display
:
inline-block
}
.fc-day-grid
.fc-row
{
z-index
:
1
}
.fc-more-popover
{
z-index
:
2
;
width
:
220px
}
.fc-more-popover
.fc-event-container
{
padding
:
10px
}
.fc-bootstrap3
.fc-popover
.panel-body
,
.fc-bootstrap4
.fc-popover
.card-body
{
padding
:
0
}
.fc-now-indicator
{
position
:
absolute
;
border
:
0
solid
red
}
.fc-bootstrap3
.fc-today.alert
,
.fc-bootstrap4
.fc-today.alert
{
border-radius
:
0
}
.fc-unselectable
{
-webkit-user-select
:
none
;
-moz-user-select
:
none
;
-ms-user-select
:
none
;
user-select
:
none
;
-webkit-tap-highlight-color
:
transparent
}
.fc-unthemed
.fc-content
,
.fc-unthemed
.fc-divider
,
.fc-unthemed
.fc-list-heading
td
,
.fc-unthemed
.fc-list-view
,
.fc-unthemed
.fc-popover
,
.fc-unthemed
.fc-row
,
.fc-unthemed
tbody
,
.fc-unthemed
td
,
.fc-unthemed
th
,
.fc-unthemed
thead
{
border-color
:
#ddd
}
.fc-unthemed
.fc-popover
{
background-color
:
#fff
;
border-width
:
1px
;
border-style
:
solid
}
.fc-unthemed
.fc-divider
,
.fc-unthemed
.fc-list-heading
td
,
.fc-unthemed
.fc-popover
.fc-header
{
background
:
#eee
}
.fc-unthemed
td
.fc-today
{
background
:
#fcf8e3
}
.fc-unthemed
.fc-disabled-day
{
background
:
#d7d7d7
;
opacity
:
.3
}
.fc-icon
{
height
:
1em
;
line-height
:
1em
;
font-size
:
1em
;
font-family
:
"Courier New"
,
Courier
,
monospace
;
-webkit-user-select
:
none
;
-moz-user-select
:
none
;
-ms-user-select
:
none
;
user-select
:
none
}
.fc-icon
:after
{
position
:
relative
}
.fc-icon-left-single-arrow
:after
{
content
:
"\2039"
;
font-weight
:
700
;
font-size
:
200%
;
top
:
-7%
}
.fc-icon-right-single-arrow
:after
{
content
:
"\203A"
;
font-weight
:
700
;
font-size
:
200%
;
top
:
-7%
}
.fc-icon-left-double-arrow
:after
{
content
:
"\AB"
;
font-size
:
160%
;
top
:
-7%
}
.fc-icon-right-double-arrow
:after
{
content
:
"\BB"
;
font-size
:
160%
;
top
:
-7%
}
.fc-icon-left-triangle
:after
{
content
:
"\25C4"
;
font-size
:
125%
;
top
:
3%
}
.fc-icon-right-triangle
:after
{
content
:
"\25BA"
;
font-size
:
125%
;
top
:
3%
}
.fc-icon-down-triangle
:after
{
content
:
"\25BC"
;
font-size
:
125%
;
top
:
2%
}
.fc-icon-x
:after
{
content
:
"\D7"
;
font-size
:
200%
;
top
:
6%
}
.fc-unthemed
.fc-popover
.fc-header
.fc-close
{
color
:
#666
;
font-size
:
.9em
;
margin-top
:
2px
}
.fc-unthemed
.fc-list-item
:hover
td
{
background-color
:
#f5f5f5
}
.ui-widget
.fc-disabled-day
{
background-image
:
none
}
.fc-bootstrap3
.fc-time-grid
.fc-slats
table
,
.fc-bootstrap4
.fc-time-grid
.fc-slats
table
,
.fc-time-grid
.fc-slats
.ui-widget-content
{
background
:
0
0
}
.fc-popover
>
.ui-widget-header
+
.ui-widget-content
{
border-top
:
0
}
.fc-bootstrap3
hr
.fc-divider
,
.fc-bootstrap4
hr
.fc-divider
{
border-color
:
inherit
}
.ui-widget
.fc-event
{
color
:
#fff
;
font-weight
:
400
}
.ui-widget
td
.fc-axis
{
font-weight
:
400
}
.fc.fc-bootstrap3
a
[
data-goto
]
:hover
{
text-decoration
:
underline
}
.fc.fc-bootstrap4
a
{
text-decoration
:
none
}
.fc.fc-bootstrap4
a
[
data-goto
]
:hover
{
text-decoration
:
underline
}
.fc-bootstrap4
a
.fc-event
:not
([
href
])
:not
([
tabindex
])
{
color
:
#fff
}
.fc-bootstrap4
.fc-popover.card
{
position
:
absolute
}
.fc-toolbar.fc-header-toolbar
{
margin-bottom
:
1em
}
.fc-toolbar.fc-footer-toolbar
{
margin-top
:
1em
}
.fc-toolbar
.fc-left
{
float
:
left
}
.fc-toolbar
.fc-right
{
float
:
right
}
.fc
.fc-toolbar
>*>*
{
float
:
left
;
margin-left
:
.75em
}
.fc
.fc-toolbar
>*>
:first-child
{
margin-left
:
0
}
.fc-toolbar
h2
{
margin
:
0
}
.fc-toolbar
button
{
position
:
relative
}
.fc-toolbar
.fc-state-hover
,
.fc-toolbar
.ui-state-hover
{
z-index
:
2
}
.fc-toolbar
.fc-state-down
{
z-index
:
3
}
.fc-toolbar
.fc-state-active
,
.fc-toolbar
.ui-state-active
{
z-index
:
4
}
.fc-toolbar
button
:focus
{
z-index
:
5
}
.fc-view-container
*,
.fc-view-container
:after
,
.fc-view-container
:before
{
-webkit-box-sizing
:
content-box
;
-moz-box-sizing
:
content-box
;
box-sizing
:
content-box
}
.fc-view
,
.fc-view
>
table
{
position
:
relative
;
z-index
:
1
}
.fc-basicDay-view
.fc-content-skeleton
,
.fc-basicWeek-view
.fc-content-skeleton
{
padding-bottom
:
1em
}
.fc-basic-view
.fc-body
.fc-row
{
min-height
:
4em
}
.fc-row.fc-rigid
.fc-content-skeleton
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
}
.fc-day-top.fc-other-month
{
opacity
:
.3
}
.fc-basic-view
.fc-day-number
,
.fc-basic-view
.fc-week-number
{
padding
:
2px
}
.fc-basic-view
th
.fc-day-number
,
.fc-basic-view
th
.fc-week-number
{