Skip to content

Commit

Permalink
refactor: refactored `LantternWeb.AssessmentPointsFilterViewOverlayCo…
Browse files Browse the repository at this point in the history
…mponent`

adjusted filter view overlay implementation, based on `mix phx.gen.live` generators pattern.

- added "/dashboard/filter_view/new" and "/dashboard/filter_view/:id/edit" routes to handle overlay display

- moved `<.slide_over>` component to dashboard live view, reducing the scope of the live component to form only
  • Loading branch information
endoooo committed Nov 5, 2023
1 parent e389187 commit 2e71059
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 277 deletions.
5 changes: 3 additions & 2 deletions lib/lanttern/explorer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ defmodule Lanttern.Explorer do
end

@doc """
Creates a assessment_points_filter_view.
Creates an assessment_points_filter_view.
## Examples
Expand All @@ -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
Expand Down
136 changes: 136 additions & 0 deletions lib/lanttern_web/live/dashboard_live/filter_view_form_component.ex
Original file line number Diff line number Diff line change
@@ -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"""
<div>
<.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.
</.error_block>
<.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" />
<div class="flex gap-6">
<fieldset class="flex-1">
<legend class="text-base font-semibold leading-6 text-ltrn-subtle">Classes</legend>
<div class="mt-4 divide-y divide-ltrn-lighter border-b border-t border-ltrn-lighter">
<.check_field
:for={opt <- @classes}
id={"class-#{opt.id}"}
field={@form[:classes_ids]}
opt={opt}
/>
</div>
</fieldset>
<fieldset class="flex-1">
<legend class="text-base font-semibold leading-6 text-ltrn-subtle">Subjects</legend>
<div class="mt-4 divide-y divide-ltrn-lighter border-b border-t border-ltrn-lighter">
<.check_field
:for={opt <- @subjects}
id={"subject-#{opt.id}"}
field={@form[:subjects_ids]}
opt={opt}
/>
</div>
</fieldset>
</div>
</.form>
</div>
"""
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
105 changes: 37 additions & 68 deletions lib/lanttern_web/live/dashboard_live/index.ex
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
</.menu_button_item>
Expand Down Expand Up @@ -95,49 +90,59 @@ 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)

{: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)

case Explorer.delete_assessment_points_filter_view(assessment_points_filter_view) 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}
Expand All @@ -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
43 changes: 30 additions & 13 deletions lib/lanttern_web/live/dashboard_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
</.link>
filter views
</h3>
<button
<.link
type="button"
class="shrink-0 flex items-center gap-2 font-display text-base underline hover:text-ltrn-primary"
phx-click="add_filter_view"
patch={~p"/dashboard/filter_view/new"}
>
Create new view <.icon name="hero-plus-circle" class="w-6 h-6 text-ltrn-primary" />
</button>
</.link>
</div>
<%= if @filter_view_count > 0 do %>
<div
Expand All @@ -39,7 +39,7 @@
phx-update="stream"
>
<.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}
/>
Expand All @@ -48,12 +48,29 @@
<.empty_state>You don't have any filter view yet</.empty_state>
<% end %>
</div>
<.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 %></: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>
<.button type="submit" form="assessment-points-filter-view-form" phx-disable-with="Saving...">
Save
</.button>
</:actions>
</.slide_over>
Loading

0 comments on commit 2e71059

Please sign in to comment.