diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb index eb478bf..13e9b32 100644 --- a/app/controllers/activities_controller.rb +++ b/app/controllers/activities_controller.rb @@ -1,5 +1,7 @@ class ActivitiesController < ApplicationController - before_action :set_organization, only: %i[create] + before_action :require_current_project! + before_action :set_organization + before_action :set_activity, only: %i[edit update destroy] def create @activity = @organization.activities.build(activity_params) @@ -14,10 +16,36 @@ class ActivitiesController < ApplicationController end end + def edit + @page_title = "Modifica attività" + end + + def update + if @activity.update(activity_params) + redirect_to @organization, notice: "Attività aggiornata." + else + @page_title = "Modifica attività" + render :edit, status: :unprocessable_entity + end + end + + def destroy + @activity.destroy! + redirect_to @organization, notice: "Attività eliminata." + end + private def set_organization @organization = Organization.find(params[:organization_id]) + return if current_user.admin? + return if @organization.projects.merge(available_projects).exists? + + redirect_to organizations_path, alert: "Organizzazione non disponibile per i tuoi progetti." and return + end + + def set_activity + @activity = @organization.activities.find(params[:id]) end def activity_params diff --git a/app/models/contact.rb b/app/models/contact.rb index 4ef6ef3..1af5a95 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -18,7 +18,7 @@ class Contact < ApplicationRecord q = "%#{sanitize_sql_like(query.strip)}%" where( - "first_name ILIKE :q OR last_name ILIKE :q OR email ILIKE :q OR phone ILIKE :q OR mobile ILIKE :q", + "contacts.first_name ILIKE :q OR contacts.last_name ILIKE :q OR contacts.email ILIKE :q OR contacts.phone ILIKE :q OR contacts.mobile ILIKE :q", q: q ) } diff --git a/app/views/activities/edit.html.erb b/app/views/activities/edit.html.erb new file mode 100644 index 0000000..e556117 --- /dev/null +++ b/app/views/activities/edit.html.erb @@ -0,0 +1,36 @@ +
+

Modifica attività

+

<%= @organization.name %>

+ + <%= form_with model: [@organization, @activity], class: "#{card_class} p-4 md:p-6 space-y-5" do |f| %> + <%= render "shared/errors", object: @activity %> + +
+ <%= f.label :activity_type, "Tipo", class: "block text-sm font-medium" %> + <%= f.select :activity_type, Catalog::ACTIVITY_TYPES.map { |k, v| [v, k] }, {}, class: input_class %> +
+ +
+ <%= f.label :subject, "Oggetto", class: "block text-sm font-medium" %> + <%= f.text_field :subject, required: true, class: input_class %> +
+ +
+ <%= f.label :description, "Descrizione", class: "block text-sm font-medium" %> + <%= f.text_area :description, rows: 4, class: input_class %> +
+ +
+ <%= f.label :happened_at, "Data e ora", class: "block text-sm font-medium" %> + <%= f.datetime_local_field :happened_at, + value: @activity.happened_at&.in_time_zone&.strftime("%Y-%m-%dT%H:%M"), + required: true, + class: input_class %> +
+ +
+ <%= f.submit "Salva modifiche", class: "#{btn_primary} w-full sm:w-auto" %> + <%= link_to "Annulla", @organization, class: "#{btn_secondary} w-full sm:w-auto" %> +
+ <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index e44f2b0..ba1c547 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -70,8 +70,9 @@ <%= current_project.name %> <% end %> - <%= form_with url: search_path, method: :get, class: "min-w-0 flex-1" do %> - <%= text_field_tag :q, params[:q], placeholder: "Cerca…", class: "#{input_class} min-h-10 py-2", aria: { label: "Cerca in #{current_project.name}" } %> + <%= form_with url: search_path, method: :get, authenticity_token: false, class: "flex min-w-0 flex-1 items-center gap-1.5", data: { turbo: false } do %> + <%= search_field_tag :q, params[:q], placeholder: "Cerca…", class: "#{input_class} min-h-10 py-2", aria: { label: "Cerca in #{current_project.name}" }, autocomplete: "off" %> + <%= submit_tag "Cerca", class: "#{btn_secondary} shrink-0 px-3" %> <% end %>
<% end %> <% if @activities.blank? %>

Nessuna attività.

<% end %> diff --git a/config/routes.rb b/config/routes.rb index d03f4e2..f74b83f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,7 +33,7 @@ Rails.application.routes.draw do patch "/settings/goal(/:id)", to: "settings#update_goal", as: :update_goal_settings resources :organizations do - resources :activities, only: %i[create] + resources :activities, only: %i[create edit update destroy] end resources :contacts resources :opportunities do diff --git a/test/controllers/activities_controller_test.rb b/test/controllers/activities_controller_test.rb new file mode 100644 index 0000000..a5e8fb0 --- /dev/null +++ b/test/controllers/activities_controller_test.rb @@ -0,0 +1,53 @@ +require "test_helper" + +class ActivitiesControllerTest < ActionDispatch::IntegrationTest + setup do + @org = organizations(:acme) + @activity = activities(:note) + login_as users(:admin) + end + + test "organization show lets you edit and delete timeline entries" do + get organization_path(@org, project_code: "matchlivetv") + assert_response :success + assert_match edit_organization_activity_path(@org, @activity, project_code: "matchlivetv"), response.body + assert_match(/Elimina/, response.body) + end + + test "can open edit form for a timeline activity" do + get edit_organization_activity_path(@org, @activity, project_code: "matchlivetv") + assert_response :success + assert_match(/Modifica attività/, response.body) + assert_match(/Prima nota/, response.body) + end + + test "can update a timeline activity" do + patch organization_activity_path(@org, @activity, project_code: "matchlivetv"), params: { + activity: { + activity_type: "note", + subject: "Nota corretta", + description: "Testo aggiornato", + happened_at: Time.zone.parse("2026-08-18 21:00") + } + } + assert_redirected_to organization_path(@org, project_code: "matchlivetv") + @activity.reload + assert_equal "Nota corretta", @activity.subject + assert_equal "Testo aggiornato", @activity.description + end + + test "can delete a timeline activity" do + assert_difference "Activity.count", -1 do + delete organization_activity_path(@org, @activity, project_code: "matchlivetv") + end + assert_redirected_to organization_path(@org, project_code: "matchlivetv") + end + + test "rejects blank subject on update" do + patch organization_activity_path(@org, @activity, project_code: "matchlivetv"), params: { + activity: { subject: "", activity_type: "note", happened_at: Time.current } + } + assert_response :unprocessable_entity + assert_equal "Prima nota", @activity.reload.subject + end +end diff --git a/test/controllers/search_controller_test.rb b/test/controllers/search_controller_test.rb new file mode 100644 index 0000000..2cc9582 --- /dev/null +++ b/test/controllers/search_controller_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class SearchControllerTest < ActionDispatch::IntegrationTest + test "header includes a search form with submit" do + login_as users(:admin) + get organizations_path(project_code: "matchlivetv") + assert_response :success + assert_includes response.body, 'action="/p/matchlivetv/search"' + assert_includes response.body, 'method="get"' + assert_includes response.body, 'name="q"' + assert_includes response.body, 'type="submit"' + end + + test "search finds organization by name" do + login_as users(:admin) + get search_path(project_code: "matchlivetv", q: "ASD Test") + assert_response :success + assert_match(/ASD Test Calcio/, response.body) + end + + test "search finds contact by last name" do + login_as users(:admin) + get search_path(project_code: "matchlivetv", q: "Rossi") + assert_response :success + assert_match(/Mario Rossi/, response.body) + end + + test "empty search does not error" do + login_as users(:admin) + get search_path(project_code: "matchlivetv") + assert_response :success + assert_match(/Nessun risultato/, response.body) + end +end