| defmodule MedicodeWeb.Components.CodeSelect do |
| @moduledoc """ |
| An auto-complete select field that allows users to search for codes to add by either `code` or `description`. |
| """ |
|
|
| use MedicodeWeb, :live_component |
|
|
| import MedicodeWeb.Components, only: [code_display: 1] |
|
|
| alias Medicode.Feedback |
| alias Medicode.Transcriptions |
|
|
| @impl Phoenix.LiveComponent |
| def mount(socket) do |
| socket = |
| socket |
| |> assign(:codes, []) |
| |> assign(:selected_code, nil) |
| |> assign(:submitted_code_feedback, nil) |
|
|
| {:ok, socket} |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def update( |
| %{id: id, text: text, current_user: current_user, chunk: chunk} = _assigns, |
| socket |
| ) do |
| form = to_form(%{"search_term" => ""}, as: id) |
|
|
| socket = |
| socket |
| |> assign(:form, form) |
| |> assign(:text, text) |
| |> assign(:current_user, current_user) |
| |> assign(:chunk, chunk) |
|
|
| {:ok, socket} |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def render(assigns) do |
| ~H""" |
| <div class="w-full" phx-click-away="clear-codes" phx-target={@myself}> |
| <%= if is_nil(@selected_code) do %> |
| <.form for={@form} phx-change="suggest-code" phx-target={@myself} class="relative"> |
| <.input |
| type="text" |
| field={@form[:search_term]} |
| class="w-full rounded" |
| style={if length(@codes) > 0, do: "border-radius: 0.25rem 0.25rem 0 0 !important;"} |
| placeholder="Search for another code..." |
| /> |
| |
| <div |
| :if={length(@codes) > 0} |
| class="absolute z-10 w-full bg-white border-l border-r border-b border-zinc-400 rounded-b overflow-hidden" |
| > |
| <button |
| :for={code <- @codes} |
| type="button" |
| class="w-full text-left px-2 py-1 text-sm border-b border-zinc-100 last:border-b-0 hover:bg-blue-200" |
| phx-click="choose-code" |
| phx-value-code={code.code} |
| phx-target={@myself} |
| > |
| <%= code.code %>: <%= code.description %> |
| </button> |
| </div> |
| </.form> |
| <% else %> |
| <div class="px-[14px]"> |
| <div class="flex justify-between pb-1 border-b border-zinc-300"> |
| <p class="leading-normal font-bold text-type-black-primary uppercase"> |
| User-selected code |
| </p> |
| <button type="button" phx-click="clear-code" phx-target={@myself}> |
| <.icon name="hero-x-circle" /> |
| </button> |
| </div> |
| |
| <.code_display code={@selected_code.code} label={@selected_code.description} /> |
| </div> |
| <% end %> |
| </div> |
| """ |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def handle_event("suggest-code", params, socket) do |
| form_id = socket.assigns.form.id |
| %{^form_id => %{"search_term" => value}} = params |
| suggested_codes = Medicode.Coding.search_for_code_vector(value) |
|
|
| {:noreply, assign(socket, :codes, suggested_codes)} |
| end |
|
|
| def handle_event("clear-codes", _params, socket) do |
| {:noreply, assign(socket, :codes, [])} |
| end |
|
|
| def handle_event("choose-code", %{"code" => code}, socket) do |
| selected_code = Enum.find(socket.assigns.codes, &(&1.code == code)) |
| text_vector = Medicode.Coding.compute_vector_as_list(socket.assigns.text) |
|
|
| code_feedback = |
| Feedback.insert_and_return(%{ |
| text: socket.assigns.text, |
| text_vector: text_vector, |
| response: true, |
| code_vector_id: selected_code.id |
| }) |
|
|
| Transcriptions.upsert_code_vector_for_transcription_chunk(%{ |
| assigned_by_user_id: socket.assigns.current_user.id, |
| code_vector_id: selected_code.id, |
| transcription_chunk_id: socket.assigns.chunk.id, |
| cosine_similarity: |
| Medicode.Coding.get_cosine_similarity(text_vector, selected_code.description_vector) |
| }) |
|
|
| Phoenix.PubSub.broadcast( |
| :medicode_pubsub, |
| "transcriptions:#{socket.assigns.chunk.transcription_id}", |
| {:transcription_chunk_updated, socket.assigns.chunk.id} |
| ) |
|
|
| socket = |
| if is_nil(code_feedback) do |
| put_flash(socket, :error, "Failed to record feedback") |
| else |
| socket |
| |> assign(:selected_code, selected_code) |
| |> assign(:submitted_code_feedback, code_feedback) |
| |> assign(:codes, []) |
| end |
|
|
| {:noreply, socket} |
| end |
|
|
| @impl Phoenix.LiveComponent |
| def handle_event( |
| "clear-code", |
| _params, |
| %{assigns: %{submitted_code_feedback: submitted_code_feedback}} = socket |
| ) do |
| |
| Feedback.delete(submitted_code_feedback) |
|
|
| socket = |
| socket |
| |> assign(:codes, []) |
| |> assign(:selected_code, nil) |
| |> assign(:submitted_code_feedback, nil) |
|
|
| {:noreply, socket} |
| end |
| end |
|
|