Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create blocked time slots and predefined courses #46

Merged
merged 15 commits into from
Nov 1, 2024
Merged
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Metrics/CyclomaticComplexity:
Max: 15
Metrics/ClassLength:
Max: 250
Metrics/ParameterLists:
Max: 10
AllCops:
NewCops: enable
TargetRubyVersion: 3.3.x
Expand Down
7 changes: 7 additions & 0 deletions app/assets/stylesheets/room_bookings.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
.is-hover {
--bs-bg-opacity: 0.1;
background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;
}

.section-item:hover {
background-color: #f0f9ff; /* Light blue shade to indicate hover */
transition: background-color 0.3s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow effect */
cursor: pointer;
}
73 changes: 70 additions & 3 deletions app/controllers/room_bookings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,84 @@ def index
@schedule = Schedule.find(params[:schedule_id])
@rooms = @schedule.rooms.where(is_active: true).where.not(building_code: 'ONLINE')
@tabs = TimeSlot.distinct.pluck(:day)
@active_tab = params[:active_tab] || @tabs[0]
@active_tab = params[:active_tab] || session[:active_rb_tab] || @tabs[0]
session[:active_rb_tab] = @active_tab
@time_slots = TimeSlot.where(time_slots: { day: @active_tab }).to_a
@time_slots.sort_by! { |ts| Time.parse(ts.start_time) }
@instructors = @schedule.instructors

# Fetch room bookings only for the specified schedule
@room_bookings = RoomBooking.joins(:room, :time_slot)
.where(rooms: { schedule_id: }, time_slots: { day: @active_tab })
@room_bookings = RoomBooking.includes(
room: {},
section: [:course],
time_slot: {},
instructor: {}
).where(rooms: { schedule_id: }, time_slots: { day: @active_tab })

# Organize room bookings in a hash with room_id and time_slot_id as keys
@bookings_matrix = @room_bookings.each_with_object({}) do |booking, hash|
hash[[booking.room_id, booking.time_slot_id]] = booking
end
end

def create
room_booking = RoomBooking.new(room_booking_params)
@schedule = Schedule.find(params[:schedule_id])

respond_to do |format|
if room_booking.save
format.html { redirect_to schedule_room_bookings_path(@schedule), notice: 'Movie was successfully created.' }
format.json { render :index, status: :created }
else
flash[:alert] = 'Did not work'
render json: { error: 'Failed to create room booking', details: room_booking.errors.full_messages }, status: :unprocessable_entity
end
end
end

def destroy
@schedule = Schedule.find(params[:schedule_id])
@room_booking = RoomBooking.find_by(id: params[:id])

if @room_booking
@room_booking.destroy
flash[:notice] = 'Room booking deleted successfully.'
else
flash[:alert] = 'Room booking not found.'
end

redirect_to schedule_room_bookings_path(@schedule) # Redirect to the list of room bookings or another appropriate page
end

def toggle_lock
@room_booking = RoomBooking.find(params[:id])

# Toggle the `is_locked` value
@room_booking.update(is_locked: !@room_booking.is_locked)

# Optionally add a notice or alert to show success or failure
flash[:notice] = 'Room booking lock status updated successfully.'

# Redirect to the previous page or another relevant page
redirect_back(fallback_location: room_bookings_path)
end

def update_instructor
@booking = RoomBooking.find(params[:id])

if @booking.update(instructor_id: params[:room_booking][:instructor_id])
flash[:notice] = 'Instructor updated successfully.'
else
flash[:alert] = 'Failed to update instructor.'
end

# Redirect to the previous page or another relevant page
redirect_back(fallback_location: room_bookings_path)
end

private

def room_booking_params
params.require(:room_booking).permit(:room_id, :time_slot_id, :is_available, :is_lab, :instructor_id, :section_id)
end
end
7 changes: 7 additions & 0 deletions app/helpers/room_bookings_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@

# Room Bookings Helper
module RoomBookingsHelper
def available_sections(schedule)
@sections = Section.joins(:course)
.where(courses: { schedule_id: schedule.id })
.left_joins(:room_booking)
.where(room_booking: { id: nil })
render partial: '/shared/courses_list', locals: { sections: @sections }
end
end
2 changes: 1 addition & 1 deletion app/javascript/room_bookings.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("turbo:load", function () {
const tds = document.querySelectorAll(".grid-view-matrix td");

tds.forEach((td) => {
Expand Down
1 change: 1 addition & 0 deletions app/models/instructor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
class Instructor < ApplicationRecord
belongs_to :schedule
has_many :instructor_preferences, dependent: :destroy
has_many :room_bookings
end
2 changes: 2 additions & 0 deletions app/models/room_booking.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
class RoomBooking < ApplicationRecord
belongs_to :room
belongs_to :time_slot
belongs_to :section
belongs_to :instructor, optional: true
end
1 change: 1 addition & 0 deletions app/models/section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
# Section model
class Section < ApplicationRecord
belongs_to :course
has_one :room_booking
end
2 changes: 1 addition & 1 deletion app/models/time_slot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TimeSlot < ApplicationRecord
private

def end_time_after_start_time
return unless end_time.present? && start_time.present? && end_time <= start_time
return unless end_time.present? && start_time.present? && Time.parse(end_time) <= Time.parse(start_time)

errors.add(:end_time, 'must be after the start time')
end
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">

<%= stylesheet_link_tag "https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css", integrity: "sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65", crossorigin: "anonymous" %>
<%= stylesheet_link_tag "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css", integrity: "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH", crossorigin: "anonymous" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= javascript_include_tag 'room_bookings' %>
Expand Down
155 changes: 113 additions & 42 deletions app/views/room_bookings/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,120 @@
</div>
</div>
<% if [email protected]? %>
<nav class="navbar navbar-expand-sm navbar-light border-bottom border-2 border-dark px-4 pb-0 fw-bold">
<div class="collapse navbar-collapse" id="scheduleNavbar">
<ul class="navbar-nav">
<% @tabs.each do |tab| %>
<li id="<%= tab %>" class="nav-item">
<a class="nav-link <%= "active" if @active_tab == tab %>" href="<%= (@active_tab == tab && "#") || schedule_room_bookings_path(@schedule, active_tab: tab) %>"><%= tab %></a>
</li>
<% end %>
</ul>
</div>
</nav>
<div class="w-12">
<table class="table table-bordered table-hover grid-view-matrix">
<thead class="table-secondary">
<tr>
<th>Times \ Rooms</th>
<% @rooms.each do |room| %>
<th scope="col" ><%= room.building_code %> <%= room.room_number %></th>

<% end %>
</tr>
</thead>
<tbody>
<% @time_slots.each do |time_slot| %>
<tr>
<th scope="row" class="table-secondary"><%= time_slot.start_time %> - <%= time_slot.end_time %></th>

<% @rooms.each do |room| %>
<% booking = @bookings_matrix[[room.id, time_slot.id]] %>

<td>
<% if booking %>
<%= booking.is_available ? 'Available' : 'Booked' %> |
<%= booking.is_lab ? 'Lab' : 'Lecture' %>
<% else %>
<div class="row">
<div class="col-10">
<nav class="navbar navbar-expand-sm navbar-light border-bottom border-2 border-dark px-4 pb-0 fw-bold">
<div class="collapse navbar-collapse" id="scheduleNavbar">
<ul class="navbar-nav">
<% @tabs.each do |tab| %>
<li id="<%= tab %>" class="nav-item">
<a class="nav-link <%= "active" if @active_tab == tab %>" href="<%= (@active_tab == tab && "#") || schedule_room_bookings_path(@schedule, active_tab: tab) %>"><%= tab %></a>
</li>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</ul>
</div>
</nav>
<div class="w-12">
<table class="table table-bordered table-hover grid-view-matrix border-black">
<thead class="table-secondary">
<tr>
<th>24 hr</th>
<% @rooms.each do |room| %>
<th scope="col" ><%= room.building_code %> <%= room.room_number %></th>

<% end %>
</tr>
</thead>
<tbody>
<% @time_slots.each do |time_slot| %>
<tr>
<th scope="row" class="table-secondary"><%= time_slot.start_time %> - <%= time_slot.end_time %></th>

<% @rooms.each do |room| %>
<% booking = @bookings_matrix[[room.id, time_slot.id]] %>

<td class="table-cell <%= "bg-primary-subtle" if room.is_lab? %>" data-room-id="<%= room.id %>" data-time-slot-id="<%= time_slot.id %>">
<% if booking %>
<strong>
<%= booking.section.course.course_number %>
(<%= booking.section.section_number %>)
</strong> |
<%= booking.section.seats_alloted %>
<%= link_to "Delete",
schedule_room_booking_path(@schedule,booking),
method: :delete,
data: {
confirm: "Are you sure you want to delete this booking?",
turbo_method: :delete
},
class: "btn btn-danger" %>
<%= link_to (booking.is_locked ? "Locked" : "Unlocked"),
toggle_lock_schedule_room_booking_path(@schedule, booking),
method: :patch,
class: "btn btn-warning",
data: {
confirm: "Are you sure you want to toggle the lock status?",
turbo_method: :patch
} %>
<%= form_with model: booking,
url: update_instructor_schedule_room_booking_path(@schedule, booking),
method: :patch,
data: {
turbo_method: :patch
},
remote: true do |form| %>
<div class="field">
<%= form.select :instructor_id, options_from_collection_for_select(@instructors, :id, :first_name, booking.instructor_id), { prompt: "Select Instructor" }, onchange: "this.form.submit()" %>
</div>
<% end %>

<% else %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div id="courses-list" class="col-2 bg-secondary-subtle vh-100 overflow-auto p-3">
<%= available_sections(@schedule) %>
</div>
</div>
<% else %>
<p class="container h1 text-secondary">No rooms added to this schedule, click on View Data to Add Rooms!</p>
<% end %>
<% end %>
<script>
document.addEventListener("turbo:load", () => {
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
function attachEventListeners() {
document.querySelectorAll(".table-cell").forEach(cell => {
cell.addEventListener("click", function() {

const roomId = this.dataset.roomId;
const timeSlotId = this.dataset.timeSlotId;

// Update cell classes
document.querySelectorAll('td.table-cell').forEach(cell => {
cell.classList.remove('bg-warning-subtle');
cell.classList.remove('bg-warning');
if (cell.dataset.roomId === roomId && cell.dataset.timeSlotId === timeSlotId) {
cell.classList.add('bg-warning');
} else if (cell.dataset.roomId === roomId || cell.dataset.timeSlotId === timeSlotId) {
cell.classList.add('bg-warning-subtle');
}
});

document.querySelectorAll('a.course-select-btn').forEach(button => {
const sectionId = button.dataset.section
button.href = `/schedules/1/room_bookings?room_booking%5Broom_id%5D=${roomId}&room_booking%5Bsection_id%5D=${sectionId}&room_booking%5Btime_slot_id%5D=${timeSlotId}`
})
});
});
}

// Initial attachment of event listeners
attachEventListeners();
});
</script>
35 changes: 35 additions & 0 deletions app/views/shared/_courses_list.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<% if sections.present? %>
<div id="courses-msg">Select a slot first</div>
<div class="mt-3">
<% sections.each do |section| %>
<div class="section-item list-group-item list-group-item-action justify-content-between align-items-center bg-light border rounded-3 mb-2 p-2"
data-room-id="<%= params[:room_id] %>"
data-time-slot-id="<%= params[:time_slot_id] %>"
data-section-id="<%= section.id %>">
<div class="d-flex row w-12">
<strong class="mb-1"><%= section.course.course_number %>(<%= section.section_number %>)</strong>
<small class="text-muted">Seats: <%= section.seats_alloted %></small>
</div>
<div class="badge bg-info-subtle text-dark rounded">Type: <%= section.course.lecture_type %></div>
<%= link_to(
'Select',
schedule_room_bookings_path(
schedule_id: params[:schedule_id],
room_booking: {
room_id: params[:room_id],
time_slot_id: params[:time_slot_id],
section_id: section.id
}
),
data: {
turbo_method: :post,
section: section.id
},
class: 'btn btn-primary my-1 text-xs course-select-btn'
) %>
</div>
<% end %>
</div>
<% else %>
<p>No courses available for the selected room and time slot.</p>
<% end %>
10 changes: 9 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@
post 'time_slots', to: 'time_slots#filter', as: 'filter_time_slots'

get '/time_slots', to: 'time_slots#index'
resources :room_bookings, only: [:index]

resources :room_bookings, only: %i[index create destroy] do
member do
patch :toggle_lock
patch :update_instructor
end
end
end

# Show Time Slot View
resources :time_slots, only: [:index]

resources :room_bookings, only: [:create]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddInstructorRefToRoomBookings < ActiveRecord::Migration[7.2]
def change
add_reference :room_bookings, :instructor, null: false, foreign_key: true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class ChangeInstructorIdInRoomBookingsToNullable < ActiveRecord::Migration[7.2]
def change
change_column_null :room_bookings, :instructor_id, true
end
end
Loading