Skip to content

Commit

Permalink
feat: create differentiation rubric in assessment point detail view
Browse files Browse the repository at this point in the history
- created `LantternWeb.AssessmentPointLive.DifferentiationRubricComponent` to isolate each entry differentiation rubric tab panel, transfering part of the code from `LantternWeb.AssessmentPointLive.RubricsOverlayComponent` (and reducing the overal complexity)

- added tab "management" in `<.person_tab>` component
  • Loading branch information
endoooo committed Nov 8, 2023
1 parent fbfb24c commit 069205f
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 140 deletions.
22 changes: 15 additions & 7 deletions lib/lanttern_web/components/navigation_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule LantternWeb.NavigationComponents do
"""
use Phoenix.Component

# alias Phoenix.LiveView.JS
alias Phoenix.LiveView.JS
import LantternWeb.CoreComponents

@doc """
Expand All @@ -17,23 +17,31 @@ defmodule LantternWeb.NavigationComponents do
"""
attr :id, :string, default: nil
attr :person, :map, required: true
attr :container_selector, :string, required: true
attr :theme, :string, default: "subtle", doc: "subtle | cyan"
attr :is_current, :boolean, default: false
attr :rest, :global
attr :rest, :global, doc: "aria-controls is required"

def person_tab(assigns) do
~H"""
<button
id={@id}
type="button"
role="tab"
aria-selected={if @is_current, do: "true", else: "false"}
tabindex={if @is_current, do: "0", else: "-1"}
aria-selected="false"
tabindex="-1"
class={[
"flex items-center gap-2 p-1 rounded-full focus:outline-ltrn-primary",
person_tab_theme_style(@theme),
if(@is_current, do: "outline outline-2 outline-ltrn-dark")
"aria-selected:outline aria-selected:outline-2 aria-selected:outline-ltrn-dark",
person_tab_theme_style(@theme)
]}
phx-click={
JS.hide(to: "#{@container_selector} div[role=tabpanel]")
|> JS.set_attribute({"aria-selected", "false"},
to: "#{@container_selector} button[role=tab]"
)
|> JS.show(to: "##{@rest[:"aria-controls"]}")
|> JS.set_attribute({"aria-selected", "true"})
}
{@rest}
>
<.profile_icon profile_name={@person.name} size="xs" theme={@theme} />
Expand Down
9 changes: 6 additions & 3 deletions lib/lanttern_web/live/assessment_point_live/details.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule LantternWeb.AssessmentPointLive.Details do
alias LantternWeb.AssessmentPointLive.AssessmentPointEntryEditorComponent
alias LantternWeb.AssessmentPointLive.AssessmentPointUpdateFormComponent
alias LantternWeb.AssessmentPointLive.RubricsOverlayComponent
alias LantternWeb.AssessmentPointLive.DifferentiationRubricComponent
alias LantternWeb.AssessmentPointLive.FeedbackFormComponent
alias LantternWeb.AssessmentPointLive.FeedbackCommentFormComponent

Expand Down Expand Up @@ -424,9 +425,11 @@ defmodule LantternWeb.AssessmentPointLive.Details do
)}
end

def handle_info({RubricsOverlayComponent, {:error, error_msg}}, socket) do
{:noreply, put_flash(socket, :error, error_msg)}
end
def handle_info({RubricsOverlayComponent, {:error, error_msg}}, socket),
do: {:noreply, put_flash(socket, :error, error_msg)}

def handle_info({DifferentiationRubricComponent, {:error, error_msg}}, socket),
do: {:noreply, put_flash(socket, :error, error_msg)}

def handle_info({FeedbackFormComponent, {:created, feedback}}, socket) do
socket =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
defmodule LantternWeb.AssessmentPointLive.DifferentiationRubricComponent do
use LantternWeb, :live_component

alias Lanttern.Assessments
alias Lanttern.Rubrics
alias Lanttern.Rubrics.Rubric
alias LantternWeb.RubricsLive.FormComponent, as: RubricsFormComponent

def render(assigns) do
~H"""
<div id={"entry-#{@entry.id}-differentiation-panel"} role="tabpanel" class="mt-6 hidden">
<.form
id={"entry-#{@entry.id}-rubric-form"}
for={@form}
class="flex items-end gap-2"
phx-change="rubric_selected"
phx-submit="save_rubric"
phx-target={@myself}
>
<.input
field={@form[:rubric_id]}
type="select"
label="Rubric"
options={@rubric_options}
prompt="No differentiation rubric"
class="flex-1"
/>
<.button
type="submit"
disabled={!@has_rubric_change || @is_creating_rubric}
class={["shrink-0", if(!@has_rubric_change || @is_creating_rubric, do: "hidden")]}
>
Save
</.button>
</.form>
<%= if @is_creating_rubric do %>
<.live_component
module={RubricsFormComponent}
id={"entry-#{@entry.id}"}
action={:new}
rubric={
%Rubric{
scale_id: @entry.scale_id,
is_differentiation: true
}
}
hide_diff_and_scale
show_submit
notify_component={@myself}
notify_parent={false}
class="mt-6"
/>
<% end %>
<%= if @rubric && !@is_creating_rubric do %>
<section class="mt-8">
<h5 class="-mb-2 font-display font-black text-lg text-ltrn-subtle">
Differentiation descriptors
</h5>
<.rubric_descriptors descriptors={@rubric.descriptors} class="mt-6" />
</section>
<% end %>
</div>
"""
end

# function components

attr :descriptors, :list, required: true
attr :class, :any, default: nil

defp rubric_descriptors(assigns) do
~H"""
<div :for={descriptor <- @descriptors} class={@class}>
<%= if descriptor.scale_type == "ordinal" do %>
<.badge style_from_ordinal_value={descriptor.ordinal_value}>
<%= descriptor.ordinal_value.name %>
</.badge>
<% else %>
<.badge>
<%= descriptor.score %>
</.badge>
<% end %>
<.markdown class="mt-2" text={descriptor.descriptor} />
</div>
"""
end

# lifecycle

def mount(socket) do
{:ok,
socket
|> assign(:is_creating_rubric, false)
|> assign(:has_rubric_change, false)}
end

def update(%{entry: entry} = assigns, socket) do
# this is N+1, but it's *kind of* ok because we
# don't expect too many diff rubrics in a real scenario
rubric =
case entry.differentiation_rubric_id do
nil -> nil
rubric_id -> Rubrics.get_full_rubric!(rubric_id)
end

form =
%{"rubric_id" => entry.differentiation_rubric_id}
|> to_form(as: :entry_rubric)

{:ok,
socket
|> assign(assigns)
|> assign(:rubric, rubric)
|> assign(:form, form)}
end

def update(%{action: {RubricsFormComponent, {:created, rubric}}}, socket) do
entry = socket.assigns.entry

entry
|> Assessments.update_assessment_point_entry(%{
differentiation_rubric_id: rubric.id
})
|> case do
{:ok, assessment_point_entry} ->
notify_component(
socket.assigns.notify_component,
{:new_diff_rubric_linked, assessment_point_entry.id, rubric}
)

{:ok,
socket
|> assign(:rubric, Rubrics.get_full_rubric!(rubric.id))
|> assign(:form, to_form(%{"rubric_id" => rubric.id}, as: :entry_rubric))
|> assign(:is_creating_rubric, false)
|> assign(:has_rubric_change, false)}

{:error, _changeset} ->
notify_parent({:error, "Couldn't link rubric to assessment point"})
{:ok, socket}
end
end

# event handlers

def handle_event("rubric_selected", %{"entry_rubric" => %{"rubric_id" => ""}}, socket) do
has_rubric_change = socket.assigns.entry.differentiation_rubric_id != nil

{:noreply,
socket
|> assign(:rubric, nil)
|> assign(:has_rubric_change, has_rubric_change)
|> assign(:is_creating_rubric, false)
|> assign(:form, to_form(%{"rubric_id" => ""}, as: :entry_rubric))}
end

def handle_event("rubric_selected", %{"entry_rubric" => %{"rubric_id" => "new"}}, socket) do
{:noreply,
socket
|> assign(:is_creating_rubric, true)
|> assign(:form, to_form(%{"rubric_id" => "new"}, as: :entry_rubric))}
end

def handle_event("rubric_selected", %{"entry_rubric" => %{"rubric_id" => rubric_id}}, socket) do
has_rubric_change = rubric_id != "#{socket.assigns.entry.differentiation_rubric_id}"

{:noreply,
socket
|> assign(:rubric, Rubrics.get_full_rubric!(rubric_id))
|> assign(:has_rubric_change, has_rubric_change)
|> assign(:is_creating_rubric, false)
|> assign(:form, to_form(%{"rubric_id" => "new"}, as: :entry_rubric))}
end

def handle_event("save_rubric", %{"entry_rubric" => %{"rubric_id" => rubric_id}}, socket) do
rubric_id = if rubric_id == "", do: nil, else: rubric_id

socket.assigns.entry
|> Assessments.update_assessment_point_entry(%{
differentiation_rubric_id: rubric_id
})
|> case do
{:ok, assessment_point_entry} ->
notify_component(
socket.assigns.notify_component,
{:diff_rubric_linked, assessment_point_entry.id, rubric_id}
)

socket =
socket
|> assign(:has_rubric_change, false)
|> assign(:is_creating_rubric, false)

{:noreply, socket}

{:error, _changeset} ->
notify_parent({:error, "Couldn't link differentiation rubric to assessment point entry"})
{:noreply, socket}
end
end

# helpers

defp notify_component(cid, msg), do: send_update(cid, action: {__MODULE__, msg})

defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
Loading

0 comments on commit 069205f

Please sign in to comment.