diff --git a/lib/lanttern/explorer.ex b/lib/lanttern/explorer.ex
index 9ec76ba5..0ab7b8a8 100644
--- a/lib/lanttern/explorer.ex
+++ b/lib/lanttern/explorer.ex
@@ -66,7 +66,7 @@ defmodule Lanttern.Explorer do
end
@doc """
- Creates a assessment_points_filter_view.
+ Creates an assessment_points_filter_view.
## Examples
@@ -78,7 +78,8 @@ defmodule Lanttern.Explorer do
"""
def create_assessment_points_filter_view(attrs \\ %{}) do
- %AssessmentPointsFilterView{}
+ # add classes and subjects to force return with preloaded classes/subjects
+ %AssessmentPointsFilterView{classes: [], subjects: []}
|> AssessmentPointsFilterView.changeset(attrs)
|> Repo.insert()
end
diff --git a/lib/lanttern_web/live/dashboard_live/filter_view_form_component.ex b/lib/lanttern_web/live/dashboard_live/filter_view_form_component.ex
new file mode 100644
index 00000000..219a339f
--- /dev/null
+++ b/lib/lanttern_web/live/dashboard_live/filter_view_form_component.ex
@@ -0,0 +1,136 @@
+defmodule LantternWeb.DashboardLive.FilterViewFormComponent do
+ @moduledoc """
+ Assessment points filter view form component.
+
+ This form is used inside a `<.slide_over>` component,
+ where the "submit" button is rendered.
+ """
+
+ use LantternWeb, :live_component
+ alias Lanttern.Explorer
+ alias Lanttern.Explorer.AssessmentPointsFilterView
+
+ def render(assigns) do
+ ~H"""
+
+ <.form
+ id="assessment-points-filter-view-form"
+ for={@form}
+ phx-change="validate"
+ phx-submit="save"
+ phx-target={@myself}
+ >
+ <.error_block :if={@form.source.action in [:insert, :update]} class="mb-6">
+ Oops, something went wrong! Please check the errors below.
+
+ <.input field={@form[:id]} type="hidden" />
+ <.input field={@form[:profile_id]} type="hidden" />
+ <.input field={@form[:name]} label="Filter view name" phx-debounce="1500" class="mb-6" />
+
+
+ Classes
+
+ <.check_field
+ :for={opt <- @classes}
+ id={"class-#{opt.id}"}
+ field={@form[:classes_ids]}
+ opt={opt}
+ />
+
+
+
+ Subjects
+
+ <.check_field
+ :for={opt <- @subjects}
+ id={"subject-#{opt.id}"}
+ field={@form[:subjects_ids]}
+ opt={opt}
+ />
+
+
+
+
+
+ """
+ end
+
+ # lifecycle
+
+ def mount(socket) do
+ classes = Lanttern.Schools.list_classes()
+ subjects = Lanttern.Taxonomy.list_subjects()
+
+ socket =
+ socket
+ |> assign(:classes, classes)
+ |> assign(:subjects, subjects)
+ |> assign(:action, "create")
+
+ {:ok, socket}
+ end
+
+ def update(%{filter_view: filter_view} = assigns, socket) do
+ changeset =
+ filter_view
+ |> Map.put(:classes_ids, Enum.map(filter_view.classes, &"#{&1.id}"))
+ |> Map.put(:subjects_ids, Enum.map(filter_view.subjects, &"#{&1.id}"))
+ |> Explorer.change_assessment_points_filter_view()
+
+ socket =
+ socket
+ |> assign(assigns)
+ |> assign(:form, to_form(changeset))
+ |> assign(:filter_view, filter_view)
+
+ {:ok, socket}
+ end
+
+ def update(assigns, socket),
+ do: {:ok, assign(socket, assigns)}
+
+ # event handlers
+
+ def handle_event("validate", %{"assessment_points_filter_view" => params}, socket) do
+ form =
+ %AssessmentPointsFilterView{}
+ |> Explorer.change_assessment_points_filter_view(params)
+ |> Map.put(:action, :validate)
+ |> to_form()
+
+ {:noreply, assign(socket, form: form)}
+ end
+
+ def handle_event("save", %{"assessment_points_filter_view" => params}, socket),
+ do: save_filter_view(socket, socket.assigns.action, params)
+
+ defp save_filter_view(socket, :new_filter_view, params) do
+ case Explorer.create_assessment_points_filter_view(params) do
+ {:ok, assessment_points_filter_view} ->
+ notify_parent({:created, assessment_points_filter_view})
+ {:noreply, socket}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, form: to_form(changeset))}
+ end
+ end
+
+ defp save_filter_view(socket, :edit_filter_view, params) do
+ # force classes_ids and subjects_ids inclusion to remove filters if needed
+ params =
+ params
+ |> Map.put_new("classes_ids", [])
+ |> Map.put_new("subjects_ids", [])
+
+ case Explorer.update_assessment_points_filter_view(socket.assigns.filter_view, params) do
+ {:ok, assessment_points_filter_view} ->
+ notify_parent({:updated, assessment_points_filter_view})
+ {:noreply, socket}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign(socket, form: to_form(changeset))}
+ end
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+end
diff --git a/lib/lanttern_web/live/dashboard_live/index.ex b/lib/lanttern_web/live/dashboard_live/index.ex
index 2217d08a..c706e167 100644
--- a/lib/lanttern_web/live/dashboard_live/index.ex
+++ b/lib/lanttern_web/live/dashboard_live/index.ex
@@ -1,15 +1,11 @@
defmodule LantternWeb.DashboardLive.Index do
@moduledoc """
- ### PubSub subscription topics
-
- - "dashboard:profile_id" on `handle_params`
-
- Expected broadcasted messages in `handle_info/2` documentation.
+ Dashboard live view
"""
use LantternWeb, :live_view
- alias Phoenix.PubSub
alias Lanttern.Explorer
+ alias Lanttern.Explorer.AssessmentPointsFilterView
# view components
@@ -42,8 +38,7 @@ defmodule LantternWeb.DashboardLive.Index do
<:menu_items>
<.menu_button_item
id={"edit-filter-view-#{@id}"}
- phx-click="edit_filter_view"
- phx-value-id={@filter_view.id}
+ phx-click={JS.patch(~p"/dashboard/filter_view/#{@filter_view.id}/edit")}
>
Edit
@@ -95,19 +90,17 @@ defmodule LantternWeb.DashboardLive.Index do
def mount(_params, _session, socket) do
profile_id = socket.assigns.current_user.current_profile_id
- if connected?(socket) do
- PubSub.subscribe(
- Lanttern.PubSub,
- "dashboard:#{profile_id}"
+ filter_views =
+ Explorer.list_assessment_points_filter_views(
+ profile_id: profile_id,
+ preloads: [:subjects, :classes]
)
- end
- filter_views = list_filter_views(profile_id)
filter_view_count = length(filter_views)
socket =
socket
- |> stream(:assessment_points_filter_views, filter_views)
+ |> stream(:filter_views, filter_views)
|> assign(:filter_view_count, filter_view_count)
|> assign(:current_filter_view_id, nil)
|> assign(:show_filter_view_overlay, false)
@@ -115,21 +108,33 @@ defmodule LantternWeb.DashboardLive.Index do
{:ok, socket}
end
- # event handlers
-
- def handle_event("add_filter_view", _params, socket) do
- {:noreply, assign(socket, :show_filter_view_overlay, true)}
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
- def handle_event("edit_filter_view", %{"id" => filter_view_id} = _params, socket) do
- socket =
- socket
- |> assign(:current_filter_view_id, String.to_integer(filter_view_id))
- |> assign(:show_filter_view_overlay, true)
+ defp apply_action(socket, :edit_filter_view, %{"id" => id}) do
+ socket
+ |> assign(:filter_view_overlay_title, "Edit assessment points filter view")
+ |> assign(
+ :filter_view,
+ Explorer.get_assessment_points_filter_view!(id, preloads: [:subjects, :classes])
+ )
+ end
- {:noreply, socket}
+ defp apply_action(socket, :new_filter_view, _params) do
+ socket
+ |> assign(:filter_view_overlay_title, "Create assessment points filter view")
+ |> assign(:filter_view, %AssessmentPointsFilterView{
+ profile_id: socket.assigns.current_user.current_profile_id,
+ classes: [],
+ subjects: []
+ })
end
+ defp apply_action(socket, :index, _params), do: socket
+
+ # event handlers
+
def handle_event("delete_filter_view", %{"id" => filter_view_id} = _params, socket) do
assessment_points_filter_view = Explorer.get_assessment_points_filter_view!(filter_view_id)
@@ -137,7 +142,7 @@ defmodule LantternWeb.DashboardLive.Index do
{:ok, _} ->
socket =
socket
- |> stream_delete(:assessment_points_filter_views, assessment_points_filter_view)
+ |> stream_delete(:filter_views, assessment_points_filter_view)
|> update(:filter_view_count, fn count -> count - 1 end)
{:noreply, socket}
@@ -158,66 +163,30 @@ defmodule LantternWeb.DashboardLive.Index do
# info handlers
- @doc """
- Handles sent or broadcasted messages from children Live Components.
-
- ## Clauses
-
- #### Assessment points filter view create success
-
- Broadcasted to `"dashboard:profile_id"` from `LantternWeb.AssessmentPointsFilterViewOverlayComponent`.
-
- handle_info({:assessment_points_filter_view_created, assessment_points_filter_view}, socket)
-
- #### Assessment points filter view update success
-
- Broadcasted to `"dashboard:profile_id"` from `LantternWeb.AssessmentPointsFilterViewOverlayComponent`.
-
- handle_info({:assessment_points_filter_view_updated, assessment_points_filter_view}, socket)
-
- """
-
def handle_info(
- {:assessment_points_filter_view_created, _assessment_points_filter_view},
+ {LantternWeb.DashboardLive.FilterViewFormComponent, {:created, filter_view}},
socket
) do
socket =
socket
- |> stream(
- :assessment_points_filter_views,
- list_filter_views(socket.assigns.current_user.current_profile_id),
- reset: true
- )
+ |> stream_insert(:filter_views, filter_view)
|> put_flash(:info, "Assessment points filter view created.")
|> update(:filter_view_count, fn count -> count + 1 end)
- |> assign(:show_filter_view_overlay, false)
+ |> push_patch(to: ~p"/dashboard")
{:noreply, socket}
end
def handle_info(
- {:assessment_points_filter_view_updated, assessment_points_filter_view},
+ {LantternWeb.DashboardLive.FilterViewFormComponent, {:updated, filter_view}},
socket
) do
socket =
socket
- |> stream_insert(
- :assessment_points_filter_views,
- assessment_points_filter_view
- )
+ |> stream_insert(:filter_views, filter_view)
|> put_flash(:info, "Assessment points filter view updated.")
- |> assign(:current_filter_view_id, false)
- |> assign(:show_filter_view_overlay, false)
+ |> push_patch(to: ~p"/dashboard")
{:noreply, socket}
end
-
- # helpers
-
- defp list_filter_views(profile_id) do
- Explorer.list_assessment_points_filter_views(
- profile_id: profile_id,
- preloads: [:subjects, :classes]
- )
- end
end
diff --git a/lib/lanttern_web/live/dashboard_live/index.html.heex b/lib/lanttern_web/live/dashboard_live/index.html.heex
index c6c51d38..23790d7e 100644
--- a/lib/lanttern_web/live/dashboard_live/index.html.heex
+++ b/lib/lanttern_web/live/dashboard_live/index.html.heex
@@ -24,13 +24,13 @@
filter views
-
Create new view <.icon name="hero-plus-circle" class="w-6 h-6 text-ltrn-primary" />
-
+
<%= if @filter_view_count > 0 do %>
<.filter_view_card
- :for={{dom_id, filter_view} <- @streams.assessment_points_filter_views}
+ :for={{dom_id, filter_view} <- @streams.filter_views}
id={dom_id}
filter_view={filter_view}
/>
@@ -48,12 +48,29 @@
<.empty_state>You don't have any filter view yet
<% end %>
-<.live_component
- module={LantternWeb.AssessmentPointsFilterViewOverlayComponent}
- id="create-assessment-points-filter-view-overlay"
- current_user={@current_user}
- show={@show_filter_view_overlay}
- topic={"dashboard:#{@current_user.current_profile_id}"}
- on_cancel={JS.push("cancel_filter_view")}
- filter_view_id={@current_filter_view_id}
-/>
+<.slide_over
+ :if={@live_action in [:new_filter_view, :edit_filter_view]}
+ id="assessment-points-filter-view-overlay"
+ show={true}
+ on_cancel={JS.patch(~p"/dashboard")}
+>
+ <:title><%= @filter_view_overlay_title %>
+ <.live_component
+ module={LantternWeb.DashboardLive.FilterViewFormComponent}
+ id={@filter_view.id || :new}
+ action={@live_action}
+ filter_view={@filter_view}
+ />
+ <:actions>
+ <.button
+ type="button"
+ theme="ghost"
+ phx-click={JS.exec("data-cancel", to: "#assessment-points-filter-view-overlay")}
+ >
+ Cancel
+
+ <.button type="submit" form="assessment-points-filter-view-form" phx-disable-with="Saving...">
+ Save
+
+
+
diff --git a/lib/lanttern_web/live_components/assessment_points_filter_view_overlay_component.ex b/lib/lanttern_web/live_components/assessment_points_filter_view_overlay_component.ex
deleted file mode 100644
index cfca9ee2..00000000
--- a/lib/lanttern_web/live_components/assessment_points_filter_view_overlay_component.ex
+++ /dev/null
@@ -1,186 +0,0 @@
-defmodule LantternWeb.AssessmentPointsFilterViewOverlayComponent do
- @moduledoc """
- ### PubSub: expected broadcast messages
-
- All messages should be broadcast to topic in assigns, following `{:key, msg}` pattern.
-
- - `:assessment_points_filter_view_created`
-
- ### Expected external assigns:
-
- attr :id, :string, required: true
- attr :current_user, User, required: true
- attr :show, :boolean, required: true
- attr :on_cancel, JS, default: %JS{}
- attr :filter_view_id, :integer, doc: "For updating views"
- attr :topic, :string
- """
-
- use LantternWeb, :live_component
- alias Phoenix.PubSub
- alias Lanttern.Explorer
- alias Lanttern.Explorer.AssessmentPointsFilterView
-
- def render(assigns) do
- ~H"""
-
- <.slide_over :if={@show} id={@id} show={true} on_cancel={Map.get(assigns, :on_cancel, %JS{})}>
- <:title>
- <%= if @action == "create", do: "Create", else: "Update" %> assessment points filter view
-
- <.form
- id="assessment-points-filter-view-form"
- for={@form}
- phx-change="validate"
- phx-submit={@action}
- phx-target={@myself}
- >
- <.error_block :if={@form.source.action in [:insert, :update]} class="mb-6">
- Oops, something went wrong! Please check the errors below.
-
- <.input field={@form[:id]} type="hidden" />
- <.input field={@form[:profile_id]} type="hidden" />
- <.input field={@form[:name]} label="Filter view name" phx-debounce="1500" class="mb-6" />
-
-
- Classes
-
- <.check_field
- :for={opt <- @classes}
- id={"class-#{opt.id}"}
- field={@form[:classes_ids]}
- opt={opt}
- />
-
-
-
- Subjects
-
- <.check_field
- :for={opt <- @subjects}
- id={"subject-#{opt.id}"}
- field={@form[:subjects_ids]}
- opt={opt}
- />
-
-
-
-
- <:actions>
- <.button type="button" theme="ghost" phx-click={JS.exec("data-cancel", to: "##{@id}")}>
- Cancel
-
- <.button
- type="submit"
- form="assessment-points-filter-view-form"
- phx-disable-with="Saving..."
- >
- Save
-
-
-
-
- """
- end
-
- # lifecycle
-
- def mount(socket) do
- classes = Lanttern.Schools.list_classes()
- subjects = Lanttern.Taxonomy.list_subjects()
-
- socket =
- socket
- |> assign(:classes, classes)
- |> assign(:subjects, subjects)
- |> assign(:action, "create")
-
- {:ok, socket}
- end
-
- def update(%{show: true, filter_view_id: id} = assigns, socket) when is_integer(id) do
- filter_view = Explorer.get_assessment_points_filter_view!(id, preloads: [:classes, :subjects])
-
- changeset =
- filter_view
- |> Map.put(:classes_ids, Enum.map(filter_view.classes, &"#{&1.id}"))
- |> Map.put(:subjects_ids, Enum.map(filter_view.subjects, &"#{&1.id}"))
- |> Explorer.change_assessment_points_filter_view()
-
- socket =
- socket
- |> assign(assigns)
- |> assign(:form, to_form(changeset))
- |> assign(:action, "update")
- |> assign(:filter_view, filter_view)
-
- {:ok, socket}
- end
-
- def update(%{show: true} = assigns, socket) do
- changeset =
- %AssessmentPointsFilterView{profile_id: assigns.current_user.current_profile.id}
- |> Explorer.change_assessment_points_filter_view()
-
- socket =
- socket
- |> assign(assigns)
- |> assign(:form, to_form(changeset))
- |> assign(:action, "create")
-
- {:ok, socket}
- end
-
- def update(assigns, socket),
- do: {:ok, assign(socket, assigns)}
-
- # event handlers
-
- def handle_event("validate", %{"assessment_points_filter_view" => params}, socket) do
- form =
- %AssessmentPointsFilterView{}
- |> Explorer.change_assessment_points_filter_view(params)
- |> Map.put(:action, :validate)
- |> to_form()
-
- {:noreply, assign(socket, form: form)}
- end
-
- def handle_event("create", %{"assessment_points_filter_view" => params}, socket) do
- case Explorer.create_assessment_points_filter_view(params) do
- {:ok, assessment_points_filter_view} ->
- msg = {:assessment_points_filter_view_created, assessment_points_filter_view}
-
- if socket.assigns.topic do
- PubSub.broadcast(Lanttern.PubSub, socket.assigns.topic, msg)
- end
-
- {:noreply, socket}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, form: to_form(changeset))}
- end
- end
-
- def handle_event("update", %{"assessment_points_filter_view" => params}, socket) do
- # force classes_ids and subjects_ids inclusion to remove filters if needed
- params =
- params
- |> Map.put_new("classes_ids", [])
- |> Map.put_new("subjects_ids", [])
-
- case Explorer.update_assessment_points_filter_view(socket.assigns.filter_view, params) do
- {:ok, assessment_points_filter_view} ->
- msg = {:assessment_points_filter_view_updated, assessment_points_filter_view}
-
- if socket.assigns.topic do
- PubSub.broadcast(Lanttern.PubSub, socket.assigns.topic, msg)
- end
-
- {:noreply, socket}
-
- {:error, %Ecto.Changeset{} = changeset} ->
- {:noreply, assign(socket, form: to_form(changeset))}
- end
- end
-end
diff --git a/lib/lanttern_web/router.ex b/lib/lanttern_web/router.ex
index 5e709e0f..5d4291a9 100644
--- a/lib/lanttern_web/router.ex
+++ b/lib/lanttern_web/router.ex
@@ -41,7 +41,15 @@ defmodule LantternWeb.Router do
{LantternWeb.UserAuth, :ensure_authenticated},
{LantternWeb.Path, :put_path_in_socket}
] do
- live "/dashboard", DashboardLive.Index
+ live "/dashboard", DashboardLive.Index, :index
+
+ live "/dashboard/filter_view/new",
+ DashboardLive.Index,
+ :new_filter_view
+
+ live "/dashboard/filter_view/:id/edit",
+ DashboardLive.Index,
+ :edit_filter_view
live "/assessment_points", AssessmentPointLive.Explorer
live "/assessment_points/:id", AssessmentPointLive.Show
diff --git a/test/lanttern/explorer_test.exs b/test/lanttern/explorer_test.exs
index 01bf8252..6b97a2e5 100644
--- a/test/lanttern/explorer_test.exs
+++ b/test/lanttern/explorer_test.exs
@@ -14,7 +14,8 @@ defmodule Lanttern.ExplorerTest do
test "list_assessment_points_filter_views/1 returns all assessment_points_filter_views" do
assessment_points_filter_view = assessment_points_filter_view_fixture()
- assert Explorer.list_assessment_points_filter_views() == [assessment_points_filter_view]
+ [expected] = Explorer.list_assessment_points_filter_views()
+ assert expected.id == assessment_points_filter_view.id
end
test "list_assessment_points_filter_views/1 with preloads returns all assessment_points_filter_views with preloaded data" do
@@ -44,8 +45,8 @@ defmodule Lanttern.ExplorerTest do
test "get_assessment_points_filter_view!/2 returns the assessment_points_filter_view with given id" do
assessment_points_filter_view = assessment_points_filter_view_fixture()
- assert Explorer.get_assessment_points_filter_view!(assessment_points_filter_view.id) ==
- assessment_points_filter_view
+ expected = Explorer.get_assessment_points_filter_view!(assessment_points_filter_view.id)
+ assert expected.id == assessment_points_filter_view.id
end
test "get_assessment_points_filter_view!/2 with preloads returns the assessment_points_filter_view with given id with preloaded data" do
@@ -126,8 +127,9 @@ defmodule Lanttern.ExplorerTest do
@invalid_attrs
)
- assert assessment_points_filter_view ==
- Explorer.get_assessment_points_filter_view!(assessment_points_filter_view.id)
+ expected = Explorer.get_assessment_points_filter_view!(assessment_points_filter_view.id)
+ assert expected.id == assessment_points_filter_view.id
+ assert expected.name == assessment_points_filter_view.name
end
test "delete_assessment_points_filter_view/1 deletes the assessment_points_filter_view" do
diff --git a/test/lanttern_web/live/dashboard_live_test.exs b/test/lanttern_web/live/dashboard_live_test.exs
index 5aa3f793..e311ba5b 100644
--- a/test/lanttern_web/live/dashboard_live_test.exs
+++ b/test/lanttern_web/live/dashboard_live_test.exs
@@ -64,7 +64,7 @@ defmodule LantternWeb.DashboardLiveTest do
# open create modal
view
- |> element("button", "Create new view")
+ |> element("a", "Create new view")
|> render_click()
# submit form
@@ -88,7 +88,7 @@ defmodule LantternWeb.DashboardLiveTest do
# "click" remove
view
- |> element("button#remove-filter-view-assessment_points_filter_views-#{filter_view.id}")
+ |> element("button#remove-filter-view-filter_views-#{filter_view.id}")
|> render_click()
# assert view is removed