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
cc42e1da
Commit
cc42e1da
authored
May 11, 2019
by
Zac Wood
Browse files
Added basic favicons.
Should still add more for mobile devices + PWA support
parent
4d7d8564
Changes
84
Hide whitespace changes
Inline
Side-by-side
schedules/app/controllers/search_controller.rb
View file @
cc42e1da
...
...
@@ -2,8 +2,57 @@ class SearchController < ApplicationController
def
index
redirect_to
(
home_url
)
unless
params
[
:query
].
length
>
1
results
=
SearchHelper
::
GenericItem
.
fetchall
(
String
.
new
(
params
[
:query
]),
semester:
@semester
).
group_by
(
&
:type
)
@instructors
=
results
[
:instructor
]
&
.
map
(
&
:data
)
@courses
=
results
[
:course
]
&
.
map
(
&
:data
)
if
params
[
:query
].
casecmp
(
'god'
).
zero?
bell
=
Instructor
.
find_by_name
(
'Jonathan Bell'
)
redirect_to
(
instructor_url
(
bell
))
end
@instructors
=
nil
@courses
=
nil
/[[:alpha:]]{2,4} \d{3}/
.
match
(
params
[
:query
])
do
|
m
|
subj
,
num
=
m
[
0
].
split
(
' '
)
course
=
Course
.
find_by
(
subject:
subj
.
upcase
,
course_number:
num
)
redirect_to
(
course_url
(
course
))
unless
course
.
nil?
end
/[[:alpha:]]{2,4}/i
.
match
(
params
[
:query
])
do
|
m
|
@courses
=
Course
.
where
(
subject:
m
[
0
].
upcase
)
.
joins
(
:course_sections
)
.
merge
(
CourseSection
.
in_semester
(
@semester
))
.
uniq
if
@courses
.
empty?
@courses
=
Course
.
where
(
"(courses.title LIKE ?)"
,
"%
#{
params
[
:query
]
}
%"
)
.
joins
(
:course_sections
)
.
merge
(
CourseSection
.
in_semester
(
@semester
))
.
uniq
other
=
Course
.
where
(
"(courses.description LIKE ?)"
,
"%
#{
params
[
:query
]
}
%"
)
.
joins
(
:course_sections
)
.
merge
(
CourseSection
.
in_semester
(
@semester
))
.
uniq
@courses
=
[
*
@courses
,
*
other
].
uniq
@instructors
=
Instructor
.
named
(
params
[
:query
])
end
@courses
.
map!
do
|
c
|
c
.
serializable_hash
.
merge
(
url:
course_url
(
c
))
end
gon
.
courses
=
@courses
gon
.
instructors
=
@instructors
end
/[0-9]{5}/
.
match
(
params
[
:query
])
do
|
m
|
redirect_to
(
course_url
(
CourseSection
.
latest_by_crn
(
m
[
0
]).
course
))
end
if
@courses
&
.
count
==
1
&&
@instructors
&
.
count
&
.
zero?
redirect_to
course_url
(
@courses
.
first
[
"id"
])
elsif
@courses
&
.
count
&
.
zero?
&&
@instructors
&
.
count
==
1
redirect_to
instructor_url
(
@instructors
.
first
)
end
end
end
schedules/app/helpers/about_helper.rb
0 → 100644
View file @
cc42e1da
module
AboutHelper
end
schedules/app/helpers/course_sections_helper.rb
0 → 100644
View file @
cc42e1da
module
CourseSectionsHelper
end
schedules/app/helpers/schedules_helper.rb
View file @
cc42e1da
...
...
@@ -19,7 +19,9 @@ module SchedulesHelper
{
title:
s
.
name
,
start:
"
#{
formatted_date
}
T
#{
time
}
"
,
end:
"
#{
formatted_date
}
T
#{
endtime
}
"
end:
"
#{
formatted_date
}
T
#{
endtime
}
"
,
crn:
s
.
crn
,
active:
true
}
end
end
.
flatten
...
...
schedules/app/helpers/search_helper.rb
View file @
cc42e1da
module
SearchHelper
def
in_cart?
(
crn
)
@cart
.
include?
crn
.
to_s
end
class
GenericQueryData
attr_reader
:semester
attr_reader
:sort_mode
...
...
schedules/app/javascript/jsconfig.json
0 → 100644
View file @
cc42e1da
schedules/app/javascript/packs/application.js
View file @
cc42e1da
...
...
@@ -10,6 +10,9 @@
import
'
@babel/polyfill
'
;
import
'
url-polyfill
'
;
import
React
from
'
react
'
;
import
Cart
from
'
src/Cart
'
;
const
elementFromString
=
string
=>
{
const
html
=
new
DOMParser
().
parseFromString
(
string
,
'
text/html
'
);
return
html
.
body
.
firstChild
;
...
...
schedules/app/javascript/packs/home.js
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
ReactDOM
from
'
react-dom
'
;
import
Cart
from
'
src/Cart
'
;
import
QuickAdd
from
'
src/QuickAdd
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
//const calendarUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port == 3000 ? ':3000' : ''}/schedule`;
const
calendarUrl
=
'
/schedule
'
;
ReactDOM
.
render
(
<
QuickAdd
loadCalendar
=
{()
=>
{
window
.
location
.
href
=
calendarUrl
;
}}
/>
,
document
.
getElementById
(
'
quick-add
'
)
);
});
schedules/app/javascript/packs/instructor.js
0 → 100644
View file @
cc42e1da
// /**
// * Either adds or removes a section from the cart depending on
// * if it is currently in the cart.
// */
import
$
from
'
jquery
'
;
import
Cart
from
'
src/Cart
'
;
const
addOrRemoveFromCart
=
async
(
event
,
sectionNode
)
=>
{
event
&&
event
.
stopPropagation
();
if
(
event
.
target
.
tagName
===
'
A
'
)
return
;
const
section
=
{
...
sectionNode
.
dataset
};
Cart
.
toggleCrn
(
section
.
crn
);
const
icon
=
$
(
sectionNode
.
querySelector
(
'
.add-remove-btn #icon
'
));
const
text
=
sectionNode
.
querySelector
(
'
.add-remove-btn .text
'
);
if
(
Cart
.
includesCrn
(
section
.
crn
))
{
icon
.
addClass
(
'
fa-minus
'
).
removeClass
(
'
fa-plus
'
);
text
.
innerText
=
'
Remove
'
;
}
else
{
icon
.
addClass
(
'
fa-plus
'
).
removeClass
(
'
fa-minus
'
);
text
.
innerText
=
'
Add
'
;
}
};
const
initSearchListeners
=
()
=>
{
const
sectionItems
=
Array
.
from
(
document
.
querySelectorAll
(
'
.section-item
'
));
sectionItems
.
forEach
(
item
=>
{
item
.
onclick
=
event
=>
addOrRemoveFromCart
(
event
,
item
);
});
setTimeout
(()
=>
{
sectionItems
.
forEach
(
item
=>
{
const
icon
=
$
(
item
.
querySelector
(
'
.add-remove-btn #icon
'
));
const
text
=
item
.
querySelector
(
'
.add-remove-btn .text
'
);
if
(
Cart
.
includesCrn
(
item
.
dataset
.
crn
))
{
icon
.
addClass
(
'
fa-minus
'
).
removeClass
(
'
fa-plus
'
);
text
.
innerText
=
'
Remove
'
;
}
else
{
icon
.
addClass
(
'
fa-plus
'
).
removeClass
(
'
fa-minus
'
);
text
.
innerText
=
'
Add
'
;
}
});
},
100
);
};
document
.
addEventListener
(
'
DOMContentLoaded
'
,
initSearchListeners
);
schedules/app/javascript/packs/schedules.js
View file @
cc42e1da
import
Cart
from
'
src/cart
'
;
import
React
from
'
react
'
;
import
ReactDOM
from
'
react-dom
'
;
import
Cart
from
'
src/Cart
'
;
import
{
saveAs
}
from
'
file-saver
'
;
import
html2canvas
from
'
html2canvas
'
;
import
$
from
'
jquery
'
;
import
'
fullcalendar
'
;
import
'
moment
'
;
import
CalendarPage
from
'
src/CalendarPage
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
const
eventsTemplate
=
document
.
querySelector
(
'
#events
'
);
if
(
eventsTemplate
)
{
const
eventsJSON
=
eventsTemplate
.
dataset
.
events
;
const
events
=
JSON
.
parse
(
eventsJSON
);
window
.
events
=
events
;
$
(
'
#calendar
'
).
fullCalendar
({
defaultDate
:
new
Date
(
2019
,
0
,
14
),
defaultView
:
'
agendaWeek
'
,
header
:
false
,
events
:
renderEvents
,
columnHeaderFormat
:
'
dddd
'
,
allDaySlot
:
false
,
});
}
initListeners
();
ReactDOM
.
render
(
<
CalendarPage
/>
,
document
.
getElementById
(
'
root
'
));
});
const
renderEvents
=
(
start
,
end
,
timezone
,
callback
)
=>
{
callback
(
window
.
events
);
};
const
remove
=
async
item
=>
{
await
Cart
.
toggleSection
({
...
item
.
dataset
});
location
.
reload
(
true
);
};
/**
* Generates a URL for the current sections in the schedule
* and sets the link in the modal to it.
*/
const
setUrlInModal
=
()
=>
{
document
.
getElementById
(
'
calendar-link
'
).
innerText
=
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}
/api/schedules?crns=
${
Cart
.
_courses
.
join
(
'
,
'
)}
`
;
};
const
downloadIcs
=
async
()
=>
{
const
response
=
await
fetch
(
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}
/api/schedules?crns=
${
Cart
.
_courses
.
join
(
'
,
'
)}
`
);
const
text
=
await
response
.
text
();
...
...
@@ -69,7 +39,7 @@ const initListeners = () => {
document
.
getElementById
(
'
save-image
'
).
onclick
=
saveImage
;
document
.
getElementById
(
'
share-url
'
).
innerText
=
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}
/schedule/view?crns=
${
Cart
.
_courses
.
join
(
'
,
'
)}
`
;
document
.
getElementById
(
'
share-url
'
).
href
=
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}
/schedule/view?crns=
${
Cart
.
_courses
.
join
(
'
,
'
)}
`
;
document
.
getElementById
(
'
share-url
'
).
href
=
`
${
window
.
location
.
protocol
}
//
${
window
.
location
.
hostname
}
/schedule/view?crns=
${
Cart
.
_courses
.
join
(
'
,
'
)}
`
;
};
if
(
!
HTMLCanvasElement
.
prototype
.
toBlob
)
{
...
...
schedules/app/javascript/packs/schedules_view.js
View file @
cc42e1da
import
Cart
from
'
src/
c
art
'
;
import
Cart
from
'
src/
C
art
'
;
import
{
saveAs
}
from
'
file-saver
'
;
import
html2canvas
from
'
html2canvas
'
;
import
$
from
'
jquery
'
;
...
...
schedules/app/javascript/packs/search.js
View file @
cc42e1da
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
import
Cart
from
'
src/cart
'
;
//
import Cart from 'src/cart';
/**
* Either adds or removes a section from the cart depending on
* if it is currently in the cart.
*/
const
addOrRemoveFromCart
=
async
(
event
,
sectionNode
)
=>
{
event
&&
event
.
stopPropagation
(
);
const
section
=
{
...
sectionNode
.
dataset
}
;
//
/**
// * Toggles the display of the schedule
// */
// const toggleSections = course => {
// const sections = course.querySelector('.sections');
//
const chev = $(course.querySelector('#course-chevron')
);
//
const
label = course.querySelector('#chevron-label')
;
await
Cart
.
toggleSection
(
section
);
const
icon
=
$
(
sectionNode
.
querySelector
(
'
.add-remove-btn #icon
'
));
const
text
=
sectionNode
.
querySelector
(
'
.add-remove-btn .text
'
);
if
(
Cart
.
includesSection
(
section
))
{
icon
.
addClass
(
'
fa-minus
'
).
removeClass
(
'
fa-plus
'
);
text
.
innerText
=
'
Remove
'
;
}
else
{
icon
.
addClass
(
'
fa-plus
'
).
removeClass
(
'
fa-minus
'
);
text
.
innerText
=
'
Add
'
;
}
};
// if (sections.style.display === 'flex') {
// sections.style.display = 'none';
// chev.addClass('fa-chevron-down').removeClass('fa-chevron-up');
// label.innerText = 'Expand';
// } else {
// sections.style.display = 'flex';
// chev.addClass('fa-chevron-up').removeClass('fa-chevron-down');
// label.innerText = 'Minimize';
// }
// };
/**
* Toggles the display of the schedule
*/
const
toggleSections
=
course
=>
{
const
sections
=
course
.
querySelector
(
'
.sections
'
);
const
chev
=
$
(
course
.
querySelector
(
'
#course-chevron
'
));
const
label
=
course
.
querySelector
(
'
#chevron-label
'
);
import
React
from
'
react
'
;
import
ReactDOM
from
'
react-dom
'
;
import
SearchList
from
'
src/SearchList
'
;
if
(
sections
.
style
.
display
===
'
flex
'
)
{
sections
.
style
.
display
=
'
none
'
;
chev
.
addClass
(
'
fa-chevron-down
'
).
removeClass
(
'
fa-chevron-up
'
);
label
.
innerText
=
'
Expand
'
;
}
else
{
sections
.
style
.
display
=
'
flex
'
;
chev
.
addClass
(
'
fa-chevron-up
'
).
removeClass
(
'
fa-chevron-down
'
);
label
.
innerText
=
'
Minimize
'
;
}
};
const
initSearchListeners
=
()
=>
{
const
courseCards
=
Array
.
from
(
document
.
querySelectorAll
(
'
.course-card
'
));
courseCards
.
forEach
(
card
=>
{
card
.
onclick
=
()
=>
toggleSections
(
card
);
});
const
sectionItems
=
Array
.
from
(
document
.
querySelectorAll
(
'
.section-item
'
));
sectionItems
.
forEach
(
item
=>
(
item
.
onclick
=
event
=>
addOrRemoveFromCart
(
event
,
item
)));
};
document
.
addEventListener
(
'
DOMContentLoaded
'
,
initSearchListeners
);
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
ReactDOM
.
render
(
<
SearchList
courses
=
{
gon
.
courses
}
instructors
=
{
gon
.
instructors
}
/>, document.getElementById
(
'root'
))
;
});
schedules/app/javascript/src/Calendar.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
BigCalendar
from
'
react-big-calendar
'
;
import
Toolbar
from
'
src/Toolbar
'
;
import
moment
from
'
moment
'
;
import
'
!style-loader!css-loader!react-big-calendar/lib/css/react-big-calendar.css
'
;
import
withSizes
from
'
react-sizes
'
;
const
localizer
=
BigCalendar
.
momentLocalizer
(
moment
);
const
Calendar
=
props
=>
(
<
div
className
=
"full-width"
style
=
{
{
backgroundColor
:
'
white
'
,
padding
:
'
24px
'
}
}
>
<
BigCalendar
localizer
=
{
localizer
}
events
=
{
props
.
events
}
title
=
""
components
=
{
{
toolbar
:
Toolbar
}
}
defaultView
=
"week"
views
=
{
[
'
week
'
,
'
day
'
]
}
startAccessor
=
"start"
endAccessor
=
"end"
defaultDate
=
{
moment
(
'
2019-01-14
'
).
toDate
()
}
formats
=
{
{
dayFormat
:
(
date
,
culture
,
localizer
)
=>
localizer
.
format
(
date
,
'
ddd
'
,
culture
),
dayHeaderFormat
:
(
date
,
culture
,
localizer
)
=>
localizer
.
format
(
date
,
'
ddd
'
,
culture
),
dayRangeHeaderFormat
:
()
=>
''
,
}
}
style
=
{
{
height
:
'
75vh
'
}
}
/>
</
div
>
);
export
default
Calendar
;
schedules/app/javascript/src/CalendarPage.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
Calendar
from
'
src/Calendar
'
;
import
Cart
from
'
src/Cart
'
;
import
SectionList
from
'
src/SectionList
'
;
import
QuickAdd
from
'
src/QuickAdd
'
;
import
moment
from
'
moment
'
;
export
default
class
CalendarPage
extends
React
.
Component
{
state
=
{
events
:
[],
sections
:
[]
};
constructor
(
props
)
{
super
(
props
);
this
.
loadEvents
();
}
loadEvents
=
async
()
=>
{
const
response
=
await
fetch
(
`/schedule/events?crns=
${
Cart
.
crns
.
join
(
'
,
'
)}
`
);
const
json
=
await
response
.
json
();
this
.
setState
({
...
json
});
Cart
.
crns
=
json
.
sections
.
map
(
s
=>
s
.
crn
);
};
events
=
()
=>
{
return
this
.
state
.
events
.
filter
(
e
=>
e
.
active
).
map
(
e
=>
({
...
e
,
start
:
moment
(
e
.
start
).
toDate
(),
end
:
moment
(
e
.
end
).
toDate
()
}));
};
toggleSection
=
crn
=>
{
const
events
=
this
.
state
.
events
.
map
(
e
=>
({
...
e
,
active
:
e
.
crn
==
crn
?
!
e
.
active
:
e
.
active
}));
this
.
setState
({
events
});
};
removeAll
=
()
=>
{
Cart
.
crns
=
[];
location
.
reload
();
};
render
()
{
return
(
<
div
>
<
Calendar
events
=
{
this
.
events
()
}
/>
{
this
.
state
.
sections
.
length
>
0
?
(
<
div
className
=
"d-flex justify-content-between align-items-end"
>
<
h2
className
=
"mt-4"
>
Your Schedule
</
h2
>
{
'
'
}
<
button
type
=
"button"
onClick
=
{
this
.
removeAll
}
className
=
"btn btn-danger mb-8"
>
Remove all sections
</
button
>
</
div
>
)
:
null
}
<
SectionList
onClick
=
{
this
.
toggleSection
}
sections
=
{
this
.
state
.
sections
}
expanded
=
{
true
}
/>
<
QuickAdd
loadCalendar
=
{
this
.
loadEvents
}
/>
</
div
>
);
}
}
schedules/app/javascript/src/Chevron.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
export
default
class
Chevron
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
}
render
()
{
const
base
=
{
display
:
'
block
'
,
textAlign
:
'
center
'
};
return
(
<
div
>
<
div
style
=
{
this
.
props
.
open
?
{
...
base
}
:
{
display
:
'
none
'
}
}
>
<
p
id
=
"chevron-label"
style
=
{
{
marginBottom
:
'
4px
'
,
fontSize
:
'
10px
'
}
}
>
Minimize
</
p
>
<
i
id
=
"course-chevron"
className
=
"fas fa-chevron-up"
/>
</
div
>
<
div
style
=
{
this
.
props
.
open
?
{
display
:
'
none
'
}
:
{
...
base
}
}
>
<
p
id
=
"chevron-label"
style
=
{
{
marginBottom
:
'
4px
'
,
fontSize
:
'
10px
'
}
}
>
Expand
</
p
>
<
i
id
=
"course-chevron"
className
=
"fas fa-chevron-down"
/>
</
div
>
</
div
>
);
}
}
schedules/app/javascript/src/Course.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
SectionList
from
'
src/SectionList
'
;
export
default
class
Course
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
expanded
:
false
,
sections
:
[]
};
}
async
onClick
()
{
if
(
this
.
state
.
sections
.
length
===
0
)
{
const
resp
=
await
fetch
(
`/api/course_sections?course_id=
${
this
.
props
.
id
}
`
);
const
json
=
await
resp
.
json
();
this
.
setState
({
sections
:
json
});
}
this
.
setState
({
expanded
:
!
this
.
state
.
expanded
});
}
prereqs
=
()
=>
{
if
(
this
.
props
.
prereqs
)
{
const
[
first
,
rest
]
=
this
.
props
.
prereqs
.
split
(
'
:
'
);
const
[
reqs
,
note
]
=
rest
.
split
(
'
.
'
);
return
(
<
p
>
<
strong
>
{
first
}
</
strong
>
{
reqs
}
<
sub
>
{
note
}
</
sub
>
</
p
>
);
}
return
<
div
/>;
};
// <% first, rest = course.prereqs.split(':') %>
// <% prereqs, note = rest.split('.') %>
// <p><strong><%= first %>:</strong> <%= prereqs %> <sub><%= note %></sub></p>
render
()
{
const
{
id
,
subject
,
course_number
,
title
,
credits
,
description
,
url
}
=
this
.
props
;
return
(
<
div
className
=
"card course-card"
onClick
=
{
()
=>
this
.
onClick
()
}
>
<
div
className
=
"card-header"
>
<
div
className
=
"row"
>
<
div
className
=
"col"
>
<
a
href
=
{
url
}
>
<
h4
className
=
"title"
>
{
`
${
subject
}
${
course_number
}
`
}
</
h4
>
</
a
>
</
div
>
</
div
>
<
div
className
=
"d-md-flex justify-content-between"
>
<
h5
>
<
em
>
{
title
}
</
em
>
</
h5
>
<
div
className
=
"attr-list justify-content-start"
>
<
div
className
=
"attr"
>
<
div
className
=
"icon"
>
<
i
className
=
"fa fa-book"
/>
</
div
>
{
credits
}
credits
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className
=
"card-body"
>
<
p
>
{
description
}
</
p
>
{
this
.
prereqs
()
}
<
div
className
=
"list-group list-group-flush sections"
style
=
{
{
display
:
'
none
'
}
}
/>
<
SectionList
{
...
this
.
state
}
expandable
=
{
true
}
/>
</
div
>
</
div
>
);
}
}
schedules/app/javascript/src/CourseList.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
Course
from
'
src/Course
'
;
export
default
class
CourseList
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
}
render
()
{
return
(
<
div
>
{
this
.
props
.
courses
.
map
(
course
=>
(
<
Course
key
=
{
course
.
id
}
{
...
course
}
/>
))
}
</
div
>
);
}
}
schedules/app/javascript/src/InstructorCard.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
export
default
class
InstructorCard
extends
React
.
Component
{
render
()
{
const
inst
=
this
.
props
.
instructor
;
return
(
<
div
className
=
"card p-3"
>
<
span
>
<
i
className
=
"fas fa-chalkboard-teacher mr-2"
/>
<
a
href
=
{
`/instructors/
${
inst
.
id
}
`
}
>
{
this
.
props
.
instructor
.
name
}
</
a
>
</
span
>
</
div
>
);
}
}
schedules/app/javascript/src/InstructorList.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
InstructorCard
from
'
src/InstructorCard
'
;
export
default
class
InstructorList
extends
React
.
Component
{
render
()
{
return
<
div
>
{
this
.
props
.
instructors
&&
this
.
props
.
instructors
.
map
(
i
=>
<
InstructorCard
instructor
=
{
i
}
/>)
}
</
div
>;
}
}
schedules/app/javascript/src/QuickAdd.jsx
0 → 100644
View file @
cc42e1da
import
React
from
'
react
'
;
import
Cart
from
'
src/Cart
'
;
export
default
class
QuickAdd
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
crnString
:
''
};
}
add
=
e
=>
{
e
.
preventDefault
();
const
crns
=
this
.
state
.
crnString
.
split
(
'
,
'
);
crns
.
forEach
(
c
=>
c
.
length
===
5
&&
Cart
.
addCrn
(
c
));
this
.
props
.
loadCalendar
();
};
render
()
{
return
(
<
div
>
<
h3
className
=
"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
onSubmit
=
{
this
.
add
}
className
=
"form"
>
<
div
className
=
"input-group"
>
<
input