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

Room Time Slot Matrix Added #43

Merged
merged 13 commits into from
Oct 30, 2024
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Metrics/ClassLength:
Max: 250
AllCops:
NewCops: enable
Documentation:
Enabled: false
4 changes: 4 additions & 0 deletions app/assets/stylesheets/room_bookings.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.is-hover {
--bs-bg-opacity: 0.1;
background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;
}
1 change: 1 addition & 0 deletions app/controllers/courses_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

# Courses Controller
class CoursesController < ApplicationController
helper_method :sort_column, :sort_direction
before_action :set_schedule, only: [:index]
Expand Down
23 changes: 23 additions & 0 deletions app/controllers/room_bookings_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

# Room Bookings Controller
class RoomBookingsController < ApplicationController
def index
schedule_id = params[:schedule_id]
@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]
@time_slots = TimeSlot.where(time_slots: { day: @active_tab }).to_a
@time_slots.sort_by! { |ts| Time.parse(ts.start_time) }

# Fetch room bookings only for the specified schedule
@room_bookings = RoomBooking.joins(:room, :time_slot)
.where(rooms: { schedule_id: 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
end
1 change: 1 addition & 0 deletions app/controllers/time_slots_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

# Time Slot Controller
class TimeSlotsController < ApplicationController
def filter
@schedule = Schedule.find(params[:schedule_id])
Expand Down
1 change: 1 addition & 0 deletions app/helpers/courses_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

# Courses Helper
module CoursesHelper
end
5 changes: 5 additions & 0 deletions app/helpers/room_bookings_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

# Room Bookings Helper
module RoomBookingsHelper
end
1 change: 1 addition & 0 deletions app/helpers/time_slots_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

# Time Slot Helper
module TimeSlotsHelper
end
16 changes: 16 additions & 0 deletions app/javascript/room_bookings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
document.addEventListener("DOMContentLoaded", function () {
const tds = document.querySelectorAll(".grid-view-matrix td");

tds.forEach((td) => {
const table = td.closest("table");
const colIndex = td.cellIndex;
const cols = table.querySelectorAll(`td:nth-child(${colIndex + 1})`);

td.addEventListener("mouseover", () => {
cols.forEach((hover) => hover.classList.add("is-hover"));
});
td.addEventListener("mouseleave", () => {
cols.forEach((hover) => hover.classList.remove("is-hover"));
});
});
})
1 change: 1 addition & 0 deletions app/models/room.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Room Model
class Room < ApplicationRecord
belongs_to :schedule
has_many :room_bookings, dependent: :destroy
enum :campus, { NONE: 0, CS: 1, GV: 2 }

validates :building_code, presence: true
Expand Down
6 changes: 6 additions & 0 deletions app/models/room_booking.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

class RoomBooking < ApplicationRecord
belongs_to :room
belongs_to :time_slot
end
2 changes: 2 additions & 0 deletions app/models/time_slot.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class TimeSlot < ApplicationRecord
has_many :room_bookings, dependent: :destroy

validates :day, presence: true
validates :start_time, presence: true
validates :end_time, presence: true
Expand Down
1 change: 0 additions & 1 deletion app/services/csv_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ def parse_room_csv(schedule_id)
)
end
end

# Flash data to be received by controller
{ notice: 'Rooms successfully uploaded.' }
rescue StandardError => e
Expand Down
1 change: 1 addition & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<%= stylesheet_link_tag "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css", integrity: "sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65", crossorigin: "anonymous" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= javascript_include_tag 'room_bookings' %>
</head>

<body>
Expand Down
64 changes: 64 additions & 0 deletions app/views/room_bookings/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<div class="px-4 py-2 border-bottom">
<div class="col-12 row justify-content-between align-items-center">
<a class="col-3 schedule-name" href=<%= schedule_path(@schedule) %>>
<div class="h1">
<%= @schedule.schedule_name %>
</div>
</a>
<div class="col-6 row justify-content-end align-items-center" style="height: 2.5em;">
<%= link_to 'View Data', schedule_rooms_path(@schedule.id), class: 'col-5 btn btn-secondary px-4 mx-1' %>
<% if [email protected]? %>
<button class="col-5 btn btn-dark px-4">
Generate Remaining
</button>
<% end %>
</div>
</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 %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p class="container h1 text-secondary">No rooms added to this schedule, click on View Data to Add Rooms!</p>
<% end %>
4 changes: 1 addition & 3 deletions app/views/shared/_insched_nav.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
</div>
</a>
<div class="col-6 row justify-content-end align-items-center" style="height: 2.5em;">
<button class="col btn btn-secondary px-4 mx-1">
Add Predefined Courses
</button>
<%= link_to 'Add Predefined Courses', schedule_room_bookings_path(@schedule), class: 'col btn btn-secondary px-4 mx-1' %>
<button class="col btn btn-dark px-4">
Export
</button>
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
resources :instructors, only: [:index]
post :upload_instructors, on: :member


resources :courses, only: [:index]
post :upload_courses, on: :member

post 'time_slots', to: 'time_slots#filter', as: 'filter_time_slots'

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

# Show Time Slot View
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20241027021034_create_room_bookings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class CreateRoomBookings < ActiveRecord::Migration[7.2]
def change
create_table :room_bookings do |t|
t.references :room, null: false, foreign_key: true
t.references :time_slot, null: false, foreign_key: true
t.boolean :is_available
t.boolean :is_lab

t.timestamps
end
end
end
25 changes: 15 additions & 10 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control syste
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 20_241_023_233_115) do
ActiveRecord::Schema[7.2].define(version: 20_241_027_021_034) do
create_table 'courses', force: :cascade do |t|
t.string 'course_number'
t.integer 'max_seats'
Expand All @@ -24,7 +24,6 @@
t.index ['schedule_id'], name: 'index_courses_on_schedule_id'
end


create_table 'instructors', force: :cascade do |t|
t.integer 'id_number'
t.string 'last_name'
Expand All @@ -40,6 +39,17 @@
t.index ['schedule_id'], name: 'index_instructors_on_schedule_id'
end

create_table 'room_bookings', force: :cascade do |t|
t.integer 'room_id', null: false
t.integer 'time_slot_id', null: false
t.boolean 'is_available'
t.boolean 'is_lab'
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index ['room_id'], name: 'index_room_bookings_on_room_id'
t.index ['time_slot_id'], name: 'index_room_bookings_on_time_slot_id'
end

create_table 'rooms', force: :cascade do |t|
t.integer 'campus'
t.boolean 'is_lecture_hall'
Expand Down Expand Up @@ -70,13 +80,8 @@
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index ['course_id'], name: 'index_sections_on_course_id'

end





create_table 'time_slots', force: :cascade do |t|
t.string 'day'
t.string 'start_time'
Expand All @@ -97,10 +102,10 @@
t.index ['email'], name: 'index_users_on_email', unique: true
end


add_foreign_key 'courses', 'schedules'
add_foreign_key 'instructors', 'schedules'
add_foreign_key 'room_bookings', 'rooms'
add_foreign_key 'room_bookings', 'time_slots'
add_foreign_key 'rooms', 'schedules'
add_foreign_key 'sections', 'courses'

end
2 changes: 2 additions & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# MovieGenre.find_or_create_by!(name: genre_name)
# end

TimeSlot.delete_all

TimeSlot.create([
{ day: 'MWF', start_time: '8:00', end_time: '8:50', slot_type: 'LEC' },
{ day: 'MWF', start_time: '9:10', end_time: '10:00', slot_type: 'LEC' },
Expand Down
44 changes: 44 additions & 0 deletions features/room_bookings.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Feature: Rooms Page
Scenario: Checking a room booking
Given I am logged in as a user with first name "Test"
And a schedule exists with the schedule name "Sched 1" and semester name "Fall 2024"
And the following rooms exist for that schedule:
| campus | building_code | room_number | capacity | is_active | is_lab | is_learning_studio | is_lecture_hall |
| CS | BLDG1 | 101 | 30 | true | true | true | true |
| GV | BLDG2 | 102 | 50 | true | true | true | true |
| CS | BLDG3 | 102 | 50 | false | true | true | true |
And the following time slots exist for that schedule:
| day | start_time | end_time | slot_type |
| MWF | 09:00 | 10:00 | "LEC" |
| MW | 08:00 | 10:00 | "LEC" |
When I visit the room bookings page for "Sched 1"
Then I should see "View Data"
And I should see "09:00 - 10:00"
And I should see "BLDG1 101"

Scenario: Checking a room booking and changing tab
Given I am logged in as a user with first name "Test"
And a schedule exists with the schedule name "Sched 1" and semester name "Fall 2024"
And the following rooms exist for that schedule:
| campus | building_code | room_number | capacity | is_active | is_lab | is_learning_studio | is_lecture_hall |
| CS | BLDG1 | 101 | 30 | true | true | true | true |
| GV | BLDG2 | 102 | 50 | true | true | true | true |
| CS | BLDG3 | 102 | 50 | false | true | true | true |
And the following time slots exist for that schedule:
| day | start_time | end_time | slot_type |
| MWF | 09:00 | 10:00 | "LEC" |
| MW | 08:00 | 10:00 | "LEC" |
When I visit the room bookings page for "Sched 1"
And I click "MW"
Then I should see "View Data"
And I should see "08:00 - 10:00"
And I should not see "09:00 - 10:00"
And I should see "BLDG1 101"

Scenario: Checking empty room bookings
Given I am logged in as a user with first name "Test"
And a schedule exists with the schedule name "Sched 1" and semester name "Fall 2024"
When I visit the room bookings page for "Sched 1"
Then I should see "View Data"
And I should not see "Generate Remaining"
And I should see "No rooms added to this schedule, click on View Data to Add Rooms!"
22 changes: 22 additions & 0 deletions features/step_definitions/room_bookings_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

Given(/I am on the room bookings page for "(.*)"/) do |schedule_name|
@schedule = Schedule.find(schedule_name: schedule_name)
visit schedule_room_bookings_path(@schedule)
end

When(/^I visit the room bookings page for "(.*)"$/) do |schedule_name|
@schedule = Schedule.where(schedule_name: schedule_name)[0]
visit schedule_room_bookings_path(schedule_id: @schedule.id)
end

Given('the following time slots exist for that schedule:') do |table|
table.hashes.each do |time_slot|
TimeSlot.create!(
day: time_slot['day'],
start_time: time_slot['start_time'],
end_time: time_slot['end_time'],
slot_type: time_slot['slot_type']
)
end
end
Loading