Skip to content
Snippets Groups Projects
Unverified Commit d3a5e1ff authored by tribut's avatar tribut :man_dancing:
Browse files

Allow users to register themselves as volunteers ("candidates")

parent 1dac9d61
No related branches found
No related tags found
No related merge requests found
require 'icalendar/tzinfo'
class CandidatesController < ApplicationController
def create
@session = Session.find_by(ref_id: params[:session_ref_id])
@conference = Conference.find_by(slug: params[:conference_slug])
@user = User.find(session[:user_id])
@candidate = Candidate.find_or_initialize_by(user: @user, session: @session).tap do |candidate_|
candidate_.note = params[:note]
candidate_.save!
end
if @candidate
Rails.logger.debug("Saved candidate #{@candidate.inspect}")
#@session = Session.find_by(ref_id: params[:session_ref_id])
Turbo::StreamsChannel.broadcast_replace_to(
@session.conference,
target: helpers.dom_id(@session),
partial: "sessions/session",
locals: { session: @session }
)
flash.now[:success] = 'User assigned successfully.'
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(@session), partial: "sessions/session", locals: { session: @session }) }
format.html { redirect_to conference_session_path(@session.conference, @session), success: 'User assigned successfully.' }
end
else
flash.now[:alert] = 'Failed to record candidate.'
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(@session), partial: "sessions/session", locals: { session: @session }), status: :unprocessable_entity }
format.html { render :show, status: :unprocessable_entity }
end
end
end
def destroy
@candidate = Candidate.find(params[:id])
@session = @candidate.session
if @candidate&.destroy
Rails.logger.debug("destroyed candidate entry")
Turbo::StreamsChannel.broadcast_replace_later_to(
@session.conference,
target: helpers.dom_id(@session),
partial: "sessions/session",
locals: { session: @session }
)
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(@session), partial: "sessions/session", locals: { session: @session }) }
format.html { redirect_to conference_session_path(@session.conference, @session), notice: 'Candidate removed successfully.' }
end
else
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(helpers.dom_id(@session), partial: "sessions/session", locals: { session: @session }), status: :unprocessable_entity }
format.html { redirect_to conference_session_path(@session.conference, @session), alert: 'Failed to remove candidate.' }
end
end
end
end
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form", "link"];
submitWithPrompt(event) {
event.preventDefault();
const reason = prompt("Why are you especially qualified? (Please keep it short, thanks)");
if (reason !== null && reason.trim() !== "") {
const input = document.createElement("input");
input.type = "hidden";
input.name = "note";
input.value = reason;
this.formTarget.appendChild(input);
this.formTarget.requestSubmit();
}
}
}
class Candidate < ApplicationRecord
belongs_to :user
belongs_to :session
validates :user_id, uniqueness: { scope: :session_id, message: "has already recorded interest for this session" }
end
......@@ -3,6 +3,7 @@ class Session < ApplicationRecord
belongs_to :stage
has_many :assignments
has_many :users, through: :assignments
has_many :candidates
has_many :session_speakers, dependent: :destroy
has_many :speakers, through: :session_speakers
......
class User < ApplicationRecord
has_many :assignments
has_many :candidates
has_secure_password
......
<% user = candidate.user %>
<% session = candidate.session %>
<span class="gap-x-0.5 rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10" style="background-color: <%= user.avatar_color %>" title="<%= user.name %>">
<span style="color: <%= user.text_color %>"><%= user.name %></span>
<button type="button" class="group relative -mr-1 size-3.5 rounded-sm hover:bg-gray-500/20">
<%= link_to conference_session_assignments_path(session.conference, session, user_id: user.id), data: { turbo_method: :post, turbo_frame: dom_id(session) } do %>
<span class="sr-only">Add</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" stroke-width="1" class="size-3.5 stroke-gray-600/50 group-hover:stroke-gray-600/75 fill-gray-600/50" style="stroke: <%= user.text_color %>; fill: <%= user.text_color %>">
<path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
</svg>
<span class="absolute -inset-1"></span>
<% end %>
</button>
</span>
<% if candidate.note %>
<span class="relative ml-1.5"><span class="max-w-60 max-h-14 overflow-scroll bg-gray-600 text-white text-xs font-medium px-2 py-1 rounded-md relative inline-block"><%= candidate.note %></span><span class="w-0 h-0 border-t-[6px] border-t-transparent border-b-[6px] border-b-transparent border-r-[6px] border-r-gray-600 absolute left-[-5px] top-[20%] transform -translate-y-1/2"></span> </span>
<% end %>
......@@ -5,7 +5,7 @@
<small class="text-2xs uppercase font-light bg-black/10 rounded-sm p-1 mr-1 lang-<%= session.language %>"><%= session.language %></small>
<%= link_to session.title, session.url, target: "_top" %>
</h4>
<div>
<div class="relative">
<span class="session-time text-xs mr-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="inline-block size-4 stroke-slate-500">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
......@@ -20,6 +20,24 @@
</svg>
<%= session.stage.name %>
</span>
<% if logged_in? %>
<span class="absolute top-0 right-0 text-3xl">
<% if candidate = session.candidates.find_by(user_id: current_user.id) %>
<%= link_to conference_session_candidate_path(session.conference, session, candidate), class: "pr-1", title: "Withdraw", aria_label: "Withdraw", data: { turbo_method: :delete, turbo_frame: dom_id(session) }, method: :delete do %>
🙅
<% end %>
<% else %>
<%= link_to conference_session_candidates_path(session.conference, session), class: "pr-1", title: "Volunteer Myself", aria_label: "Volunteer Myself", data: { turbo_method: :post, turbo_frame: dom_id(session) } do %>
🙋
<% end %>
<%= form_with url: conference_session_candidates_path(session.conference, session), method: :post, class: "inline", data: { turbo_frame: dom_id(session), controller: "bothhands", bothhands_target: "form" } do |form| %>
<%= link_to conference_session_candidates_path(session.conference, session), class: "pr-1", title: "Volunteer (with special skills)", aria_label: "Volunteer (with special skills)", data: { action: "bothhands#submitWithPrompt", turbo_prefetch:"false" } do %>
🙌
<% end %>
<% end %>
<% end %>
</span>
<% end %>
</div>
<div>
<ul class="flex flex-wrap text-xs gap-1">
......@@ -57,6 +75,15 @@
<%= @assignment.errors.full_messages.join(", ") %>
</div>
<% end %>
<small>candidates (<%= session.candidates.length %>)</small>
<ul class="inline-flex flex-wrap gap-1 my-1">
<% session.candidates.sort_by { |cand| cand.note.nil? ? 1:0 }.each do |candidate| %>
<li class="inline-flex items-end">
<span class="assigned-user"><%= render partial: 'candidates/user_avatar', locals: { candidate: } %></span>
</li>
<% end %>
</ul>
<hr>
<small>unassigned (<%= unassigned_users.length %>)</small>
<%= render partial: 'assignments/filteredlist', locals: { session: session, users: unassigned_users } %>
</div>
......
......@@ -25,6 +25,7 @@ Rails.application.routes.draw do
get ':date', action: :show, on: :member, as: :date, date: /\d{4}-\d{2}-\d{2}/
resources :sessions, param: :ref_id do
resources :assignments, only: [:create, :destroy]
resources :candidates, only: [:create, :destroy]
end
resources :speakers, param: :ref_id
end
......
class CreateCandidates < ActiveRecord::Migration[7.1]
def change
create_table :candidates do |t|
t.references :user, null: false, foreign_key: true
t.references :session, null: false, foreign_key: true
t.string :note, null: true
t.timestamps
end
end
end
......@@ -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_05_27_191332) do
ActiveRecord::Schema[7.1].define(version: 2024_11_18_161733) do
create_table "assignments", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "session_id", null: false
......@@ -20,6 +20,16 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_27_191332) do
t.index ["user_id"], name: "index_assignments_on_user_id"
end
create_table "candidates", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "session_id", null: false
t.string "note"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["session_id"], name: "index_candidates_on_session_id"
t.index ["user_id"], name: "index_candidates_on_user_id"
end
create_table "conferences", force: :cascade do |t|
t.string "name"
t.datetime "starts_at"
......@@ -270,6 +280,8 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_27_191332) do
add_foreign_key "assignments", "sessions"
add_foreign_key "assignments", "users"
add_foreign_key "candidates", "sessions"
add_foreign_key "candidates", "users"
add_foreign_key "relevant_stages", "conferences"
add_foreign_key "relevant_stages", "stages"
add_foreign_key "revision_sets", "conferences"
......
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
note: MyString
two:
note: MyString
require "test_helper"
class CandidateTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment