diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index d388dd9441db7031b13425671d1ec087cf2e8bf6..094625f7998bd737fd69e7012a5790d20f40fe88 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -26,7 +26,7 @@ select { } .session-holder { @apply w-full; } .session { - @apply border p-2 bg-slate-100 border-slate-300 rounded-md w-full flex flex-col; + @apply border p-2 bg-slate-100 border-slate-300 rounded-md w-full flex flex-col hover:shadow-2xl; &.translators-needed { @apply bg-red-200 border-red-400; diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 553638913002561f835a97dee79ff712106b2faf..fe2197fe6d8c8cfb63d5475b0aa4c32e27ad90df 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -9,10 +9,20 @@ class AssignmentsController < ApplicationController end def create + params[:user_id] ||= params[:assignment][:user_id] + if params[:user_id].nil? or params[:user_id].empty? + flash.now[:alert] = 'Please select a user to assign.' + + 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: 'Please select a user to assign.' } + end + return + end @session = Session.find_by(ref_id: params[:session_ref_id]) @conference = Conference.find_by(slug: params[:conference_slug]) @user = User.find(params[:user_id]) - @assignment = @session.assignments.new(user: @user) + @assignment = Assignment.new(user: @user, session: @session) if @assignment.save flash.now[:success] = 'User assigned successfully.' diff --git a/app/models/assignment.rb b/app/models/assignment.rb index b6db13245d0af1ae3e1810ba1f139c4eb2cd8137..3426f2244d12b0885bdf0726c9a4d485c0fc5660 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -3,4 +3,26 @@ class Assignment < ApplicationRecord belongs_to :session validates :user_id, uniqueness: { scope: :session_id, message: "has already been assigned to this session" } + + validate :no_overlapping_assignments + + private + + def no_overlapping_assignments + return if session.blank? || user.blank? + + overlapping_assignments = user.assignments + .joins(:session) + .where.not(id: id) + .where( + "sessions.starts_at < ? AND sessions.ends_at > ?", + session.ends_at, session.starts_at + ) + + logger.debug(overlapping_assignments) + + if overlapping_assignments.exists? + errors.add(:base, "This assignment overlaps with another assignment for this user.") + end + end end diff --git a/app/subscribers/notifications_subscriber.rb b/app/subscribers/notifications_subscriber.rb index c6aad96807b5b6ce00be5156564a0bfbf5ac9d83..5d07e66250f88c1a3b53553dd0943e9ad9e1fec4 100644 --- a/app/subscribers/notifications_subscriber.rb +++ b/app/subscribers/notifications_subscriber.rb @@ -13,6 +13,17 @@ class NotificationsSubscriber conference = record.conference + # record.assignments.includes(:user).each do |assignment| + # Notification.create!( + # channel: 'telegram_group_chat', + + # ) + # end + Notification.create!( + channel: 'telegram_group_chat', + target: + ) + Revision.create!( conference:, target_type: model_name, diff --git a/app/views/assignments/_user_add.html.erb b/app/views/assignments/_user_add.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..65c56c26f5803261fc9ad57a11a67e204cb0c501 --- /dev/null +++ b/app/views/assignments/_user_add.html.erb @@ -0,0 +1,26 @@ +<% disabled = local_assigns.fetch(:disabled, false) %> +<% if not disabled %> +<span class="inline-flex items-center 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> +<% else %> +<span class="inline-flex items-center 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" title="<%= user.name %>"> + <span><%= user.name %></span> + <div class="group relative -mr-1 size-3.5 rounded-sm"> + <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 fill-gray-600/50"> + <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> + </button> +</span> +<% end %> \ No newline at end of file diff --git a/app/views/conferences/show.html.erb b/app/views/conferences/show.html.erb index bc490b8289b5f3997e57f2090ca3ae7f7f22f22b..34d625f429af897139667de442e3a1949bcdaa68 100644 --- a/app/views/conferences/show.html.erb +++ b/app/views/conferences/show.html.erb @@ -104,7 +104,7 @@ current_time = Time.zone.now.in_time_zone(@conference.time_zone).advance(days: 1 <h4><%= stage.name %></h4> <div class="stage-sessions"> <% sessions.each do |session| %> - <div class="session-holder" 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;"> + <div class="session-holder hover:z-30" style="position: absolute; top: <%= (session.starts_at - timeline_starts_at) / 3600.0 * pixels_per_hour %>px; max-height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour %>px; height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour %>px;"> <%= render partial: "sessions/session", locals: { session: session } %> </div> <% end %> diff --git a/app/views/sessions/_assignment_form.html.erb b/app/views/sessions/_assignment_form.html.erb index 686a20f4020bc8e4724eacb24002eee7fee22045..1f6dabc129bb21bf1b6bbc642e780d54aec4aa3f 100644 --- a/app/views/sessions/_assignment_form.html.erb +++ b/app/views/sessions/_assignment_form.html.erb @@ -2,7 +2,7 @@ <% if unassigned_users.length > 0 %> <div class="text-sm"> <%= form_with url: conference_session_assignments_path(session.conference, session), method: :post, data: { turbo_frame: dom_id(session) } do |f| %> - <%= f.select :user_id, options_from_collection_for_select(unassigned_users, :id, :name), {}, { class: "text-sm" } %> + <%= f.select :user_id, options_from_collection_for_select(unassigned_users, :id, :name), { disabled: '', prompt: '-' }, { class: "text-sm" } %> <%= f.submit "Assign", class: 'primary text-sm' %> <% if @assignment&.errors&.any? %> <div class="alert alert-danger"> diff --git a/app/views/sessions/_session.html.erb b/app/views/sessions/_session.html.erb index 0534eb41a0c0a3174760a7096769f33fff670ce7..4079c1a7289888e9c9c7044a8b56dcee3b2fb5c7 100644 --- a/app/views/sessions/_session.html.erb +++ b/app/views/sessions/_session.html.erb @@ -1,7 +1,27 @@ +<% unassigned_users = @users - session.assignments.collect(&:user) %> <%= turbo_frame_tag dom_id(session) do %> - <div class="session text-sm w-full h-full <%= session.translators_needed? ? "translators-needed" : "no-translators-needed" %> <%= session.backup_needed? ? "backup-needed" : "no-backup-needed" %> <%= session.has_assignees? ? "has-assignees" : "no-assignees" %>"> - <h4><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> - <p class="session-time"><%= session.starts_at.strftime('%H:%M') %> - <%= session.ends_at.strftime('%H:%M') %> @ <%= session.stage.name %> · <%= session.language %><% if session.is_interpreted %> <strong>(int)</strong><% end %></p> + <div class="session shadow hover:shadow-lg overflow-scroll hover:h-max text-sm w-full h-full <%= session.translators_needed? ? "translators-needed" : "no-translators-needed" %> <%= session.backup_needed? ? "backup-needed" : "no-backup-needed" %> <%= session.has_assignees? ? "has-assignees" : "no-assignees" %>"> + <h4> + <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> + <% if false %> + <div> + <span class="session-time"><%= session.starts_at.strftime('%H:%M') %> - <%= session.ends_at.strftime('%H:%M') %></span> + – + <span class="session-location text-sm font-medium"><%= session.stage.name %></span> + </div> + <% end %> + + <% if false and (session.translators_needed? or session.backup_needed?) %> + <% if session.translators_needed? %> + <div class="border font-medium text-sm border-red-400 bg-red-200 px-2 py-1 shadow-inner rounded-md inline w-max">needs translators</div> + <% else %> + <div class="border font-medium text-sm border-amber-400 bg-amber-200 px-2 py-1 shadow-inner rounded-md inline w-max">needs backup</div> + <% end %> + <% end %> + + <small>assigned (<%= session.assignments.length %>)</small> <ul class="inline-flex flex-wrap gap-1 my-1"> <% session.assignments.each do |assignment| %> <li> @@ -9,6 +29,25 @@ </li> <% end %> </ul> - <%= render partial: "sessions/assignment_form", locals: { session: session } %> + <hr> + <% if @assignment&.errors&.any? %> + <div class="alert alert-danger bg-red-600 border-red-800 text-white border p-2 shadow z-30 rounded-md" onclick="this.parentNode.removeChild(this)"> + <div class="float-right"><span class="sr-only">Close</span> + <svg viewBox="0 0 14 14" class="size-3.5 stroke-white"> + <path d="M4 4l6 6m0-6l-6 6" /> + </svg> + <span class="absolute -inset-1"></span> + </div> + <%= @assignment.errors.full_messages.join(", ") %> + </div> + <% end %> + <small>unassigned (<%= unassigned_users.length %>)</small> + <ul class="flex flex-row flex-wrap gap-1 my-1 flex-shrink-0"> + <% unassigned_users.each do |user| %> + <li> + <span class="unassigned-user"><%= render partial: 'assignments/user_add', locals: { user: user, session: session } %></span> + </li> + <% end %> + </ul> </div> <% end %> diff --git a/db/migrate/20240526121600_add_telegram_username_to_user.rb b/db/migrate/20240526121600_add_telegram_username_to_user.rb new file mode 100644 index 0000000000000000000000000000000000000000..90ff2b9b87bb351cd43fc9d027393835e23827a3 --- /dev/null +++ b/db/migrate/20240526121600_add_telegram_username_to_user.rb @@ -0,0 +1,5 @@ +class AddTelegramUsernameToUser < ActiveRecord::Migration[7.1] + def change + add_column :users, :telegram_username, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 31489c275614c9813a70299809031db4782ff753..c3f6ce6618899243a05b2fad1730239e4494ac57 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_05_26_063139) do +ActiveRecord::Schema[7.1].define(version: 2024_05_26_121600) do create_table "assignments", force: :cascade do |t| t.integer "user_id", null: false t.integer "session_id", null: false @@ -226,6 +226,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_26_063139) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "avatar_color" + t.string "telegram_username" end add_foreign_key "assignments", "sessions" diff --git a/db/seeds.rb b/db/seeds.rb index de2afc9522f0db2c128a54f926698320ed2689c0..ebbcef77ea32e1a7807d7246f1e07f9d8dd2b98b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -37,6 +37,10 @@ end %w[Teal hdsjulian Sophie bergpiratin sblsg Max aerowaffle ningwie Senana ToniHDS].each do |username| User.find_or_create_by!(name: username, email: "c3lingo+#{username}@x.moeffju.net") end +u = User.find_by(name: 'sblsg') +u.name = 'sebalis' +u.telegram_username = 'sblsg' +u.save! NotificationChannel.create!( name: 'telegram_group_chat',