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