Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions app/controllers/classroom_modules_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class ClassroomModulesController < AdminController
before_action :set_classroom_module

def update
if @classroom_module.update(classroom_module_params)
respond_to do |format|
format.turbo_stream
format.html { redirect_to schedule_classroom_url(@classroom_module.classroom_program.classroom) }
end
else
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
dom_id(@classroom_module),
partial: "classroom_modules/row",
locals: { classroom_module: @classroom_module }
), status: :unprocessable_entity
end
format.html { redirect_to schedule_classroom_url(@classroom_module.classroom_program.classroom) }
end
end
end

private

def set_classroom_module
@classroom_module = ClassroomModule.find(params[:id])
end

def classroom_module_params
params.expect(classroom_module: [ :publish_on ])
end
end
10 changes: 9 additions & 1 deletion app/controllers/classrooms_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
class ClassroomsController < AdminController
before_action :set_classroom, only: %i[ edit update ]
before_action :set_classroom, only: %i[edit update schedule]

def edit
build_missing_program_enrollments
end

def schedule
@classroom_programs = @classroom.classroom_programs
.includes(:program, classroom_modules: :content_module)
.order("programs.name")
@active_enrollment = @classroom_programs.find { |cp| cp.id.to_s == params[:classroom_program_id] } || @classroom_programs.first
end

def update
respond_to do |format|
if @classroom.update(classroom_params)
@classroom.classroom_programs.reload.each(&:generate_modules!)
format.html { redirect_to school_students_url(@classroom.school), notice: "Classroom was successfully updated.", status: :see_other }
format.json { render :show, status: :ok, location: @classroom }
else
Expand Down
9 changes: 5 additions & 4 deletions app/controllers/content_modules_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ def update
end

def destroy
@content_module.destroy!
redirect_to content_modules_path, notice: "Module was successfully deleted.", status: :see_other
rescue ActiveRecord::DeleteRestrictionError
redirect_to content_modules_path, alert: "Cannot delete a module that has been assigned to classrooms."
if @content_module.destroy
redirect_to content_modules_path, notice: "Module was successfully deleted.", status: :see_other
else
redirect_to content_modules_path, alert: "Cannot delete a module that has been assigned to classrooms."
end
end

private
Expand Down
10 changes: 10 additions & 0 deletions app/controllers/student_homes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@ class StudentHomesController < ApplicationController

def index
@student = Current.student
classroom = @student.classroom
@classroom_programs = classroom.classroom_programs
.includes(:program)
.order("programs.name")
@active_program = @classroom_programs.find { |cp| cp.id.to_s == params[:classroom_program_id] } || @classroom_programs.first
@published_modules = if @active_program
@active_program.classroom_modules.published.includes(content_module: :links)
else
[]
end
end
end
10 changes: 10 additions & 0 deletions app/javascript/controllers/publish_now_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["date"]

setToday() {
this.dateTarget.value = new Date().toISOString().split("T")[0]
this.element.requestSubmit()
}
}
6 changes: 6 additions & 0 deletions app/models/classroom_module.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class ClassroomModule < ApplicationRecord
belongs_to :classroom_program
belongs_to :content_module

scope :published, -> { where.not(publish_on: nil).where("publish_on <= ?", Date.current) }
end
19 changes: 19 additions & 0 deletions app/models/classroom_program.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
class ClassroomProgram < ApplicationRecord
belongs_to :classroom
belongs_to :program
has_many :classroom_modules, dependent: :destroy

enum :level, { basic: "basic", moderate: "moderate", advanced: "advanced" }, validate: true

validates :level, presence: true
validate :level_unchanged_if_modules_scheduled, on: :update

def generate_modules!
current_cm_ids = program.content_modules.where(level: level).pluck(:id)
classroom_modules.where.not(content_module_id: current_cm_ids).destroy_all
current_cm_ids.each do |cm_id|
classroom_modules.find_or_create_by!(content_module_id: cm_id)
end
end

private

def level_unchanged_if_modules_scheduled
return unless level_changed?
if classroom_modules.where.not(publish_on: nil).exists?
errors.add(:level, "cannot be changed when modules have publish dates set")
end
end
end
1 change: 1 addition & 0 deletions app/models/content_module.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class ContentModule < ApplicationRecord
belongs_to :program
has_many :links, dependent: :destroy
has_many :classroom_modules, dependent: :restrict_with_error

enum :level, { basic: "basic", moderate: "moderate", advanced: "advanced" }, validate: true

Expand Down
24 changes: 24 additions & 0 deletions app/views/classroom_modules/_row.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<tr id="<%= dom_id(classroom_module) %>">
<td><%= classroom_module.content_module.name %></td>
<td>
<%= form_with model: classroom_module, url: classroom_module_path(classroom_module),
data: { controller: "publish-now" } do |form| %>
<div class="flex items-center gap-2">
<%= form.date_field :publish_on, value: classroom_module.publish_on, class: "input input-sm",
data: { publish_now_target: "date" } %>
<%= form.submit "Save", class: "btn btn-xs btn-primary" %>
<button type="button" class="btn btn-xs btn-ghost"
data-action="publish-now#setToday">Publish Now</button>
</div>
<% end %>
</td>
<td>
<% if classroom_module.publish_on.nil? %>
<span class="badge badge-ghost">Unscheduled</span>
<% elsif classroom_module.publish_on > Date.current %>
<span class="badge badge-warning">Scheduled</span>
<% else %>
<span class="badge badge-success">Published</span>
<% end %>
</td>
</tr>
3 changes: 3 additions & 0 deletions app/views/classroom_modules/update.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= turbo_stream.replace dom_id(@classroom_module) do %>
<%= render "row", classroom_module: @classroom_module %>
<% end %>
21 changes: 16 additions & 5 deletions app/views/classrooms/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@
<li>Edit Classroom</li>
</ul>
</nav>
<section class="py-10 flex items-center justify-center">
<div class="card card-border bg-base-100 shadow-md w-96">
<div class="card-body">
<h1 class="card-title">Editing Classroom</h1>
<%= render "form", classroom: @classroom %>
<section class="py-10 flex justify-center">
<div class="flex flex-col gap-6 w-full max-w-lg">
<div class="card card-border bg-base-100 shadow-md">
<div class="card-body">
<h1 class="card-title">Editing Classroom</h1>
<%= render "form", classroom: @classroom %>
</div>
</div>

<div class="card card-border bg-base-100 shadow-md">
<div class="card-body">
<div class="flex justify-between items-center">
<h2 class="card-title text-lg">Module Schedule</h2>
<%= link_to "Manage Schedule", schedule_classroom_path(@classroom), class: "btn btn-sm btn-primary" %>
</div>
</div>
</div>
</div>
</section>
53 changes: 53 additions & 0 deletions app/views/classrooms/schedule.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<% content_for :title, "Schedule — #{@classroom.name}" %>

<nav class="breadcrumbs text-sm">
<ul>
<li><%= link_to "All Schools", schools_path %></li>
<li><%= link_to @classroom.school.name, school_students_path(@classroom.school) %></li>
<li><%= link_to "Edit Classroom", edit_classroom_path(@classroom) %></li>
<li>Schedule</li>
</ul>
</nav>

<header class="flex justify-between items-center mb-4">
<h1 class="text-2xl">Schedule: <%= @classroom.name %></h1>
</header>

<% if @classroom_programs.any? %>
<div role="tablist" class="tabs tabs-border mb-6">
<% @classroom_programs.each do |enrollment| %>
<%= link_to schedule_classroom_path(@classroom, classroom_program_id: enrollment.id),
role: "tab",
aria: { selected: enrollment == @active_enrollment },
class: "tab #{"tab-active" if enrollment == @active_enrollment}" do %>
<%= enrollment.program.name %>
<% end %>
<% end %>
</div>

<% if @active_enrollment %>
<% modules = @active_enrollment.classroom_modules.sort_by { |m| m.content_module.position.to_i } %>
<% if modules.any? %>
<div class="overflow-x-auto rounded-box border border-base-content/5 bg-base-100">
<table class="table">
<thead>
<tr>
<th>Module</th>
<th>Publish Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<% modules.each do |classroom_module| %>
<%= render "classroom_modules/row", classroom_module: classroom_module %>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p class="text-sm opacity-60">No modules for this enrollment yet.</p>
<% end %>
<% end %>
<% else %>
<p class="text-sm opacity-60">No program enrollments yet. <%= link_to "Edit the classroom", edit_classroom_path(@classroom), class: "link" %> to add programs.</p>
<% end %>
1 change: 1 addition & 0 deletions app/views/content_modules/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<% @programs.each do |program| %>
<%= link_to content_modules_path(program_id: program.id),
role: "tab",
aria: { selected: program == @active_program },
class: "tab #{"tab-active" if program == @active_program}" do %>
<%= program.name %>
<% end %>
Expand Down
55 changes: 46 additions & 9 deletions app/views/student_homes/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
<section class="py-10 flex items-center justify-center">
<div class="card card-border bg-base-100 shadow-md w-96">
<div class="card-body">
<h1 class="card-title">Welcome to EndsideOut!</h1>
<p>You are logged in as <strong><%= @student.full_name %></strong></p>
<div class="card-actions justify-end">
<%= button_to "Logout", student_session_path, method: :delete, class: "btn btn-primary" %>
</div>
<% content_for :title, "Home" %>

<section class="py-10">
<header class="flex justify-between items-center mb-6">
<h1 class="text-2xl">Welcome, <%= @student.first_name %>!</h1>
<%= button_to "Logout", student_session_path, method: :delete, class: "btn btn-sm btn-ghost" %>
</header>

<% if @classroom_programs.size > 1 %>
<div role="tablist" class="tabs tabs-border mb-6">
<% @classroom_programs.each do |enrollment| %>
<%= link_to student_homes_path(classroom_program_id: enrollment.id),
role: "tab",
aria: { selected: enrollment == @active_program },
class: "tab #{"tab-active" if enrollment == @active_program}" do %>
<%= enrollment.program.name %>
<% end %>
<% end %>
</div>
</div>
<% end %>

<% sorted = @published_modules.sort_by { |m| m.content_module.position.to_i }
latest_date = sorted.map(&:publish_on).max %>
<% if sorted.any? %>
<% sorted.each do |classroom_module| %>
<details class="collapse collapse-arrow bg-base-100 border border-base-300 mb-2"
<%= "open" if classroom_module.publish_on == latest_date %>>
<summary class="collapse-title font-semibold">
<%= classroom_module.content_module.name %>
</summary>
<div class="collapse-content">
<ul class="flex flex-col gap-2 pt-2">
<% classroom_module.content_module.links.each do |link| %>
<li>
<a href="<%= safe_external_url(link.url) %>" target="_blank" rel="noopener noreferrer"
class="link inline-flex items-center gap-1">
<%= link.title %> ↗
</a>
</li>
<% end %>
</ul>
</div>
</details>
<% end %>
<% else %>
<p class="text-sm opacity-60">No modules published yet.</p>
<% end %>
</section>
5 changes: 4 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
scope :admin do
resources :schools do
resources :students, shallow: true
resources :classrooms, shallow: true, only: %i[edit update]
resources :classrooms, shallow: true, only: %i[edit update] do
member { get :schedule }
end
end
resources :content_modules do
resources :links, shallow: true
end
resources :classroom_modules, only: %i[update]
end
root to: "schools#index"

Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20260517183252_create_classroom_modules.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateClassroomModules < ActiveRecord::Migration[8.1]
def change
create_table :classroom_modules do |t|
t.references :classroom_program, null: false, foreign_key: true
t.references :content_module, null: false, foreign_key: true
t.date :publish_on

t.timestamps
end

add_index :classroom_modules, [ :classroom_program_id, :content_module_id ], unique: true,
name: "index_classroom_modules_on_classroom_program_and_content_module"
end
end
15 changes: 14 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading