diff --git a/Gemfile b/Gemfile index 2712516251dafd4a5ae7e8615e086d87461354cd..4717b9b16532d4a2ae157e1ddcad4cade2dab9d4 100644 --- a/Gemfile +++ b/Gemfile @@ -61,3 +61,7 @@ gem "solid_queue" gem "httparty" gem "tailwindcss-rails", "~> 2.6" + +gem "hotwire-rails", "~> 0.1.3" + +gem "importmap-rails", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 6d767c6db4d900a73468eb7a8b094116f8e02695..a07fabb1b1449192b9db3d215e5a0f70c3d70bac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -108,11 +108,19 @@ GEM raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) + hotwire-rails (0.1.3) + rails (>= 6.0.0) + stimulus-rails + turbo-rails httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) i18n (1.14.4) concurrent-ruby (~> 1.0) + importmap-rails (2.0.1) + actionpack (>= 6.0.0) + activesupport (>= 6.0.0) + railties (>= 6.0.0) io-console (0.7.2) irb (1.12.0) rdoc @@ -229,6 +237,8 @@ GEM sqlite3 (1.7.3-aarch64-linux) sqlite3 (1.7.3-arm64-darwin) sqlite3 (1.7.3-x86_64-linux) + stimulus-rails (1.3.3) + railties (>= 6.0.0) stringio (3.1.0) strscan (3.1.0) tailwindcss-rails (2.6.0-aarch64-linux) @@ -239,6 +249,10 @@ GEM railties (>= 7.0.0) thor (1.3.1) timeout (0.4.1) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) web-console (4.2.1) @@ -264,7 +278,9 @@ DEPENDENCIES bootsnap capybara debug + hotwire-rails (~> 0.1.3) httparty + importmap-rails (~> 2.0) jbuilder puma (>= 5.0) rails (~> 7.1.2) diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 338a0e82666ddaaf6f02347084fff71f72fd4604..d39ca5501f1720c4179c2159e2c38bccf2016e50 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,5 @@ //= link_tree ../images //= link_directory ../stylesheets .css //= link_tree ../builds +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..820907634111ecd4ae7098e5dbd307f5bc43384a --- /dev/null +++ b/app/controllers/assignments_controller.rb @@ -0,0 +1,8 @@ +class AssignmentsController < ApplicationController + def index + @assignments = Assignment.all + if params[:user_id] + @assignments = @assignments.where(user_id: params[:user_id]) + end + end +end diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index 3614abd3e656e217591a777b41072e7443d78deb..038e5903b44193d0af9e09a34410cd2454bfc5da 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -7,5 +7,6 @@ class ConferencesController < ApplicationController @conference = Conference.find_by(slug: params[:slug]) @sessions = @conference.sessions.where.not(starts_at: nil).includes(:stage).order(:starts_at) @sessions_by_date = @sessions.group_by{ |x| x.starts_at.to_date } + @users = User.all end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 47fa91b9d1809af607fc10f6764667136dcb553f..5f107b097892485cbbb6052bac926a8008030df0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -16,4 +16,44 @@ class SessionsController < ApplicationController # Further filtering options can be added here end + + def show + @conference = Conference.find_by(slug: params[:slug]) + @session = Session.includes(:stage).find_by(ref_id: params[:ref_id]) + @users = User.all + end + + def assign_user + @session = Session.find_by(ref_id: params[:ref_id]) + @conference = Conference.find_by(slug: params[:slug]) + @user = User.find(params[:user_id]) + @assignment = @session.assignments.new(session: @session, user: @user) + @users = User.all + + if @assignment.save + flash.now[:success] = 'User assigned successfully.' + respond_to do |format| + # format.turbo_stream + format.html { redirect_to conference_session_path(@session.conference, @session), success: 'User assigned successfully.' } + end + else + flash.now[:alert] = 'Failed to assign user.' + respond_to do |format| + # format.turbo_stream { render :show, status: :unprocessable_entity } + format.html { render :show, status: :unprocessable_entity, alert: 'Failed to assign user.' } + end + end + end + + def unassign_user + @session = Session.find_by(ref_id: params[:ref_id]) + @user = User.find(params[:user_id]) + @assignment = Assignment.find_by(session: @session, user: @user) + + if @assignment&.destroy + redirect_to conference_session_path(@session.conference, @session), notice: 'User removed successfully.' + else + redirect_to conference_session_path(@session.conference, @session), alert: 'Failed to remove user.' + end + end end diff --git a/app/helpers/assignments_helper.rb b/app/helpers/assignments_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..6f7c33b927756135ddc70aa905e5778d1f41db6b --- /dev/null +++ b/app/helpers/assignments_helper.rb @@ -0,0 +1,2 @@ +module AssignmentsHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 0000000000000000000000000000000000000000..e239947abfd19eb083fb16ac650b9a49b63ff6e4 --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "controllers" +import "@hotwired/turbo-rails" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000000000000000000000000000000000000..1213e85c7ac805be18b4ea04f93ab028cc975255 --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000000000000000000000000000000000000..5975c0789d7cf1cc918034ba31a8f102e06e93b9 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000000000000000000000000000000000000..54ad4cad4d448fb5598447916ac5535d03376761 --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,11 @@ +// Import and register all your controllers from the importmap under controllers/* + +import { application } from "controllers/application" + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) + +// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) +// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" +// lazyLoadControllersFrom("controllers", application) diff --git a/app/models/assignment.rb b/app/models/assignment.rb index e8bb8a3b18409ebf8444e4842f1056f0c68fd300..b6db13245d0af1ae3e1810ba1f139c4eb2cd8137 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -1,4 +1,6 @@ class Assignment < ApplicationRecord belongs_to :user belongs_to :session + + validates :user_id, uniqueness: { scope: :session_id, message: "has already been assigned to this session" } end diff --git a/app/models/conference.rb b/app/models/conference.rb index 07e4145c189bd467423e151d072b1b81eae38589..40b40a551f68f9104764dd76c72882b6f9b35639 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -6,4 +6,8 @@ class Conference < ApplicationRecord def days (starts_at.to_date..ends_at.to_date) end + + def to_param + slug + end end diff --git a/app/models/session.rb b/app/models/session.rb index 687e2268d68f9f74905269f243ac01480ec500a8..6a18ee751259d3064d9d318f6441cfaa2cf656c3 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -5,4 +5,8 @@ class Session < ApplicationRecord has_many :users, through: :assignments validates :ref_id, uniqueness: { scope: :conference_id } + + def to_param + ref_id + end end diff --git a/app/views/assignments/index.html.erb b/app/views/assignments/index.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..139f9496ed637b6d4901a8d2cd221ea0caff9c2c --- /dev/null +++ b/app/views/assignments/index.html.erb @@ -0,0 +1,10 @@ +<div> + <% @assignments.group_by(&:user).each do |user, assignments| %> + <h4><%= user.name %></h4> + <ul> + <% assignments.each do |assignment| %> + <li><%= link_to assignment.session.title, assignment.session %></li> + <% end %> + </ul> + <% end %> +</div> \ No newline at end of file diff --git a/app/views/assignments/show.html.erb b/app/views/assignments/show.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..6c530750c9b9ffc4b68ac98681587d7fb112c510 --- /dev/null +++ b/app/views/assignments/show.html.erb @@ -0,0 +1,4 @@ +<div> + <h1 class="font-bold text-4xl">Assignments#show</h1> + <p>Find me in app/views/assignments/show.html.erb</p> +</div> diff --git a/app/views/conferences/show.html.erb b/app/views/conferences/show.html.erb index 3c732431fc288417fa3f0baa1766243704730d94..911fe41065d47a9e9ff231e29a362086281b2b3f 100644 --- a/app/views/conferences/show.html.erb +++ b/app/views/conferences/show.html.erb @@ -52,10 +52,15 @@ current_time = @sessions_by_date[@conference.days.first].first.starts_at.advance <h4><%= stage.name %></h4> <div class="stage-sessions"> <% sessions.each do |session| %> - <div class="session" style="position: absolute; top: <%= (session.starts_at - timeline_starts_at) / 3600.0 * pixels_per_hour %>px; height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour%>px;"> - <h4><%= session.title %></h4> + <div class="session" style="position: absolute; top: <%= (session.starts_at - timeline_starts_at) / 3600.0 * pixels_per_hour %>px; height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour%>px; overflow: scroll;"> + <h4><%= link_to session.title, [@conference, session] %></h4> <p class="session-time"><%= session.starts_at.strftime('%H:%M') %> - <%= session.ends_at.strftime('%H:%M') %></p> - <div class="session-desc"><%= session.description.html_safe %></div> + <%#<div class="session-desc"><%= session.description.html_safe %><%#/div>%> + <%= form_with url: assign_user_session_path(@conference, session), method: :post, local: true do |f| %> + <%= f.label :user_id, "Assign User" %> + <%= f.select :user_id, options_from_collection_for_select(@users, :id, :name) %> + <%= f.submit "Assign" %> + <% end %> </div> <% end %> </div> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index f02f8a20840d3597bc624d0e96212d9b600729b7..9f79c2f38b69d91c34b45ed70fdeae02cdfdee1e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,6 +8,7 @@ <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "application" %> + <%= javascript_importmap_tags %> </head> <body> diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..32b19c4c919f50510558cad797b20244f437c1d4 --- /dev/null +++ b/app/views/sessions/show.html.erb @@ -0,0 +1,34 @@ +<div> + <h6><%= @session.conference.name %></h6> + <h1><%= @session.title %></h1> + <h2><%= @session.language %> <%= @session.is_interpreted ? "(live interpretation)" : "" %></h2> + <h2><%= @session.starts_at.strftime("%Y-%m-%d") %> <%= @session.starts_at.strftime("%H:%M") %> – <%= @session.ends_at.strftime("%H:%M") %></h2> + <h2><%= @session.stage.name %>· <%= @session.session_format %> · <%= @session.track %></h2> + <p><%= @session.description.html_safe %></p> + + <h3>Assigned Users</h3> + <ul> + <% @session.users.each do |user| %> + <li> + <%= user.name %> + <%= link_to '[Remove]', unassign_user_session_path([@session.conference, @session], user_id: user.id), data: { turbo_method: :delete, confirm: 'Are you sure?' } %> + </li> + <% end %> + </ul> + + <%= form_with url: assign_user_session_path([@session.conference, @session]), method: :post, local: true do |f| %> + <%= f.label :user_id, "Assign User" %> + <%= f.select :user_id, options_from_collection_for_select(@users, :id, :name) %> + <%= f.submit "Assign" %> + <div id="flash"> + <% flash.each do |key, value| %> + <div class="alert alert-<%= key %>"><%= value %></div> + <% end %> + </div> + <% if @assignment&.errors&.any? %> + <div class="alert alert-danger"> + <%= @assignment.errors.full_messages.join(", ") %> + </div> + <% end %> + <% end %> +</div> \ No newline at end of file diff --git a/bin/importmap b/bin/importmap new file mode 100755 index 0000000000000000000000000000000000000000..36502ab16c7adf42dfc804702966e684d68610dd --- /dev/null +++ b/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/config/importmap.rb b/config/importmap.rb new file mode 100644 index 0000000000000000000000000000000000000000..cb0480c0c0a7e50463e1bd0e2665a6866ae0435c --- /dev/null +++ b/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin_all_from "app/javascript/controllers", under: "controllers" +pin "@hotwired/turbo-rails", to: "turbo.min.js" diff --git a/config/routes.rb b/config/routes.rb index a6369e49458d4141ab49b9e347bc410f2790b026..2521790b545f03b3183b840e6a27e9cc9d694cb3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Rails.application.routes.draw do - get 'sessions/index' # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. @@ -10,7 +9,20 @@ Rails.application.routes.draw do # root "posts#index" root "conferences#index" - resources :conferences, path: '/', param: :slug do - resources :sessions, only: [:index] + resources :conferences, param: :slug do + resources :sessions do + resource :assigments + end end + + resources :assignments + resources :sessions, param: :ref_id + + # get 'conferences/:slug', to: 'conferences#show', as: :conference + # get 'conferences/:slug/:date', to: 'conferences#show', as: :conference_day + # get 'conferences/:slug/session/:ref_id', to: 'sessions#show', as: :conference_session + # post 'conferences/:slug/session/:ref_id/assignments', to: 'sessions#assign_user', as: :assign_user_session + # delete 'conferences/:slug/session/:ref_id/assignments', to: 'sessions#unassign_user', as: :unassign_user_session + # get 'assignments', to: 'assignments#index', as: :assignments + # get 'assignments/:user_id', to: 'assignments#index', as: :user_assignments end diff --git a/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb b/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb new file mode 100644 index 0000000000000000000000000000000000000000..3bc825cbcb08cc81c4d6f44903b4c17a07142da6 --- /dev/null +++ b/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb @@ -0,0 +1,5 @@ +class RenameFormatToSessionFormatInSessions < ActiveRecord::Migration[7.1] + def change + rename_column :sessions, :format, :session_format + end +end diff --git a/db/schema.rb b/db/schema.rb index cb01004b01595856fb00914da91c7720bd1bbfad..eff275dd45355b447281057086c23c2dbcf6140f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_04_06_184312) do +ActiveRecord::Schema[7.1].define(version: 2024_05_24_091736) do create_table "assignments", force: :cascade do |t| t.integer "user_id", null: false t.integer "session_id", null: false @@ -39,7 +39,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_06_184312) do t.string "language" t.string "status" t.text "description" - t.string "format" + t.string "session_format" t.string "track" t.boolean "is_interpreted" t.datetime "starts_at" diff --git a/db/seeds.rb b/db/seeds.rb index 42df632a0a6debcf994906c1081ae757b29892c2..766011bb79a257594a522f844088145c87add8ec 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -29,3 +29,7 @@ Conference.find_or_create_by!(slug: "rp2024") do |c| } c.import_job_class = "republica_2023_or_later" end + +User.find_or_create_by!(email: "teal@teal.is") do |user| + user.name = "Teal" +end diff --git a/test/controllers/assignments_controller_test.rb b/test/controllers/assignments_controller_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..e733d3acd7c088c3d8905b7cc94ef6140e6e9be1 --- /dev/null +++ b/test/controllers/assignments_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class AssignmentsControllerTest < ActionDispatch::IntegrationTest + test "should get show" do + get assignments_show_url + assert_response :success + end +end diff --git a/vendor/javascript/.keep b/vendor/javascript/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391