diff --git a/lib/lanttern/learning_context.ex b/lib/lanttern/learning_context.ex index cd33adc3..85092b42 100644 --- a/lib/lanttern/learning_context.ex +++ b/lib/lanttern/learning_context.ex @@ -229,13 +229,20 @@ defmodule Lanttern.LearningContext do defp set_activity_position_attr(attrs) do strand_id = attrs[:strand_id] || attrs["strand_id"] - position = + positions = from( a in Activity, where: a.strand_id == ^strand_id, - select: count() + select: a.position, + order_by: [desc: a.position] ) - |> Repo.one() + |> Repo.all() + + position = + case Enum.at(positions, 0) do + nil -> 0 + pos -> pos + 1 + end cond do not is_nil(attrs[:strand_id]) -> @@ -282,7 +289,9 @@ defmodule Lanttern.LearningContext do """ def delete_activity(%Activity{} = activity) do - Repo.delete(activity) + activity + |> Activity.delete_changeset() + |> Repo.delete() end @doc """ diff --git a/lib/lanttern/learning_context/activity.ex b/lib/lanttern/learning_context/activity.ex index 2420e639..c1e70bb3 100644 --- a/lib/lanttern/learning_context/activity.ex +++ b/lib/lanttern/learning_context/activity.ex @@ -34,4 +34,14 @@ defmodule Lanttern.LearningContext.Activity do |> validate_required([:name, :description, :position, :strand_id]) |> put_subjects() end + + def delete_changeset(activity) do + activity + |> cast(%{}, []) + |> foreign_key_constraint( + :id, + name: :activities_assessment_points_activity_id_fkey, + message: "Activity has linked assessment points." + ) + end end diff --git a/lib/lanttern_web/live/strand_live/activity.ex b/lib/lanttern_web/live/strand_live/activity.ex index f5b3472c..b0cf74b3 100644 --- a/lib/lanttern_web/live/strand_live/activity.ex +++ b/lib/lanttern_web/live/strand_live/activity.ex @@ -4,6 +4,8 @@ defmodule LantternWeb.StrandLive.Activity do alias Lanttern.LearningContext alias LantternWeb.StrandLive.ActivityTabs + # live components + alias LantternWeb.StrandLive.ActivityFormComponent alias LantternWeb.AssessmentPointLive.ActivityAssessmentPointFormComponent @tabs %{ @@ -14,10 +16,12 @@ defmodule LantternWeb.StrandLive.Activity do # lifecycle + @impl true def mount(_params, _session, socket) do {:ok, assign(socket, :activity, nil), layout: {LantternWeb.Layouts, :app_logged_in_blank}} end + @impl true def handle_params(params, _url, socket) do {:noreply, socket @@ -66,8 +70,34 @@ defmodule LantternWeb.StrandLive.Activity do defp apply_action(socket, _live_action, _params), do: socket + # event handlers + + @impl true + def handle_event("delete_activity", _params, socket) do + case LearningContext.delete_activity(socket.assigns.activity) do + {:ok, _activity} -> + {:noreply, + socket + |> put_flash(:info, "Activity deleted") + |> push_navigate(to: ~p"/strands/#{socket.assigns.activity.strand}?tab=activities")} + + {:error, _changeset} -> + {:noreply, + socket + |> put_flash( + :error, + "Activity has linked assessments. Deleting it would cause some data loss." + )} + end + end + # info handlers + @impl true + def handle_info({ActivityFormComponent, {:saved, activity}}, socket) do + {:noreply, assign(socket, :activity, activity)} + end + def handle_info( {ActivityTabs.AssessmentComponent, {:apply_class_filters, classes_ids}}, socket diff --git a/lib/lanttern_web/live/strand_live/activity.html.heex b/lib/lanttern_web/live/strand_live/activity.html.heex index a324252f..baf01390 100644 --- a/lib/lanttern_web/live/strand_live/activity.html.heex +++ b/lib/lanttern_web/live/strand_live/activity.html.heex @@ -24,27 +24,47 @@
- <.nav_tabs class="container mx-auto lg:max-w-5xl" id="activity-nav-tabs"> - <:tab - patch={~p"/strands/activity/#{@activity}?#{%{tab: "details"}}"} - is_current={@current_tab == :details && "true"} - > - Details & Curriculum - - <:tab - patch={~p"/strands/activity/#{@activity}?#{%{tab: "assessment"}}"} - is_current={@current_tab == :assessment && "true"} - > - Assessment - - <:tab - patch={~p"/strands/activity/#{@activity}?#{%{tab: "notes"}}"} - is_current={@current_tab == :notes && "true"} - icon_name="hero-eye-slash" - > - My notes - - +
+ <.nav_tabs id="activity-nav-tabs"> + <:tab + patch={~p"/strands/activity/#{@activity}?#{%{tab: "details"}}"} + is_current={@current_tab == :details && "true"} + > + Details & Curriculum + + <:tab + patch={~p"/strands/activity/#{@activity}?#{%{tab: "assessment"}}"} + is_current={@current_tab == :assessment && "true"} + > + Assessment + + <:tab + patch={~p"/strands/activity/#{@activity}?#{%{tab: "notes"}}"} + is_current={@current_tab == :notes && "true"} + icon_name="hero-eye-slash" + > + My notes + + + <.menu_button id={"activity-#{@activity.id}"}> + <:menu_items> + <.menu_button_item + id={"edit-activity-#{@activity.id}"} + phx-click={JS.patch(~p"/strands/activity/#{@activity}/edit")} + > + Edit activity + + <.menu_button_item + id={"remove-activity-#{@activity.id}"} + class="text-red-500" + phx-click="delete_activity" + data-confirm="Are you sure?" + > + Delete + + + +
<.live_component @@ -71,3 +91,31 @@ current_user={@current_user} />
+<.slide_over + :if={@live_action == :edit} + id="activity-form-overlay" + show={true} + on_cancel={JS.patch(~p"/strands/activity/#{@activity}")} +> + <:title>Edit activity + <.live_component + module={ActivityFormComponent} + id={@activity.id} + activity={@activity} + action={@live_action} + patch={~p"/strands/activity/#{@activity}"} + notify_parent + /> + <:actions> + <.button + type="button" + theme="ghost" + phx-click={JS.exec("data-cancel", to: "#activity-form-overlay")} + > + Cancel + + <.button type="submit" form="activity-form"> + Save + + + diff --git a/lib/lanttern_web/live/strand_live/details.html.heex b/lib/lanttern_web/live/strand_live/details.html.heex index fb432136..be34eacb 100644 --- a/lib/lanttern_web/live/strand_live/details.html.heex +++ b/lib/lanttern_web/live/strand_live/details.html.heex @@ -49,7 +49,7 @@ id={"edit-strand-#{@strand.id}"} phx-click={JS.patch(~p"/strands/#{@strand}/edit")} > - Edit + Edit strand <.menu_button_item id={"remove-strand-#{@strand.id}"} diff --git a/lib/lanttern_web/router.ex b/lib/lanttern_web/router.ex index 7c69b138..025c6a57 100644 --- a/lib/lanttern_web/router.ex +++ b/lib/lanttern_web/router.ex @@ -72,6 +72,7 @@ defmodule LantternWeb.Router do live "/strands/:id/edit", StrandLive.Details, :edit live "/strands/:id/new_activity", StrandLive.Details, :new_activity live "/strands/activity/:id", StrandLive.Activity, :show + live "/strands/activity/:id/edit", StrandLive.Activity, :edit live "/strands/activity/:id/assessment_point/new", StrandLive.Activity, diff --git a/priv/repo/migrations/20231201184646_set_activities_assessment_points_activity_id_fkey_on_delete_nothing.exs b/priv/repo/migrations/20231201184646_set_activities_assessment_points_activity_id_fkey_on_delete_nothing.exs new file mode 100644 index 00000000..93ed1e12 --- /dev/null +++ b/priv/repo/migrations/20231201184646_set_activities_assessment_points_activity_id_fkey_on_delete_nothing.exs @@ -0,0 +1,18 @@ +defmodule Lanttern.Repo.Migrations.SetActivitiesAssessmentPointsActivityIdFkeyOnDeleteNothing do + use Ecto.Migration + + def change do + execute """ + ALTER TABLE activities_assessment_points + DROP CONSTRAINT activities_assessment_points_activity_id_fkey, + ADD CONSTRAINT activities_assessment_points_activity_id_fkey FOREIGN KEY (activity_id) + REFERENCES activities (id); + """, + """ + ALTER TABLE activities_assessment_points + DROP CONSTRAINT activities_assessment_points_activity_id_fkey, + ADD CONSTRAINT activities_assessment_points_activity_id_fkey FOREIGN KEY (activity_id) + REFERENCES activities (id) ON DELETE CASCADE; + """ + end +end diff --git a/test/lanttern_web/live/strand_live/activity_test.exs b/test/lanttern_web/live/strand_live/activity_test.exs index 7864ddcb..153b4a59 100644 --- a/test/lanttern_web/live/strand_live/activity_test.exs +++ b/test/lanttern_web/live/strand_live/activity_test.exs @@ -78,4 +78,50 @@ defmodule LantternWeb.StrandLive.ActivityTest do assert view |> has_element?("p", "activity description abc") end end + + describe "Activity management" do + test "edit activity", %{conn: conn} do + subject = TaxonomyFixtures.subject_fixture(%{name: "subject abc"}) + strand = LearningContextFixtures.strand_fixture(%{subjects_ids: [subject.id]}) + + activity = + LearningContextFixtures.activity_fixture(%{strand_id: strand.id, name: "activity abc"}) + + {:ok, view, _html} = live(conn, "#{@live_view_base_path}/#{activity.id}/edit") + + assert view + |> has_element?("h2", "Edit activity") + + # add subject + view + |> element("#activity-form #activity_subject_id") + |> render_change(%{"activity" => %{"subject_id" => subject.id}}) + + # submit form with valid field + view + |> element("#activity-form") + |> render_submit(%{ + "activity" => %{ + "name" => "activity name xyz" + } + }) + + assert_patch(view, "#{@live_view_base_path}/#{activity.id}") + + assert view |> has_element?("h1", "activity name xyz") + assert view |> has_element?("span", subject.name) + end + + test "delete activity", %{conn: conn} do + activity = LearningContextFixtures.activity_fixture() + + {:ok, view, _html} = live(conn, "#{@live_view_base_path}/#{activity.id}") + + view + |> element("button#remove-activity-#{activity.id}") + |> render_click() + + assert_redirect(view, "/strands/#{activity.strand_id}?tab=activities") + end + end end