diff --git a/assets/js/app.js b/assets/js/app.js index 6e1eb82..9482ead 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -268,6 +268,19 @@ Hooks.EmptyNickname = { }, }; +Hooks.SearchableSelect = { + mounted() { + this.handleEvent("update_hidden_field", (payload) => { + if (payload.id === this.el.id) { + this.el.value = payload.value; + // Trigger a change event to update the form + const event = new Event('input', { bubbles: true }); + this.el.dispatchEvent(event); + } + }); + } +}; + Hooks.PostForm = { onPress(e, submitBtn, TA) { if (e.key == "Enter" && !e.shiftKey) { diff --git a/lib/claper_web/live/admin_live/event_live/form_component.ex b/lib/claper_web/live/admin_live/event_live/form_component.ex index a356eda..6af40a8 100644 --- a/lib/claper_web/live/admin_live/event_live/form_component.ex +++ b/lib/claper_web/live/admin_live/event_live/form_component.ex @@ -61,17 +61,16 @@ defmodule ClaperWeb.AdminLive.EventLive.FormComponent do /> <.live_component - module={ClaperWeb.AdminLive.FormFieldComponent} + module={ClaperWeb.AdminLive.SearchableSelectComponent} id="event-user-id" form={@form} field={:user_id} - type="select" label="Assigned User" - select_options={@user_options} - prompt="Select a user" + options={@user_options} + placeholder="Search for a user..." required={true} width_class="sm:col-span-6" - description="The user who owns this event" + description="The user who owns this event (required)" /> diff --git a/lib/claper_web/live/admin_live/form_field_component.ex b/lib/claper_web/live/admin_live/form_field_component.ex index 7098152..e30fb58 100644 --- a/lib/claper_web/live/admin_live/form_field_component.ex +++ b/lib/claper_web/live/admin_live/form_field_component.ex @@ -80,7 +80,9 @@ defmodule ClaperWeb.AdminLive.FormFieldComponent do @field, [ class: "checkbox checkbox-primary", - checked: Phoenix.HTML.Form.input_value(@form, @field) == true || Phoenix.HTML.Form.input_value(@form, @field) == "true" + checked: + Phoenix.HTML.Form.input_value(@form, @field) == true || + Phoenix.HTML.Form.input_value(@form, @field) == "true" ] ++ @extra_attrs )} {@checkbox_label || @label} diff --git a/lib/claper_web/live/admin_live/searchable_select_component.ex b/lib/claper_web/live/admin_live/searchable_select_component.ex new file mode 100644 index 0000000..a1a213c --- /dev/null +++ b/lib/claper_web/live/admin_live/searchable_select_component.ex @@ -0,0 +1,170 @@ +defmodule ClaperWeb.AdminLive.SearchableSelectComponent do + use ClaperWeb, :live_component + + @impl true + def render(assigns) do + ~H""" +
+
+ + + + + +
+
+ """ + end + + @impl true + def mount(socket) do + {:ok, + socket + |> assign(:show_dropdown, false) + |> assign(:search_term, "") + |> assign(:filtered_options, []) + |> assign(:selected_value, nil) + |> assign(:display_value, "")} + end + + @impl true + def update(assigns, socket) do + socket = + socket + |> assign(assigns) + |> assign_new(:placeholder, fn -> "Select..." end) + |> assign_new(:required, fn -> false end) + |> assign_new(:description, fn -> nil end) + |> assign_new(:width_class, fn -> nil end) + |> assign_new(:options, fn -> [] end) + |> update_filtered_options() + |> update_display_value() + + {:ok, socket} + end + + @impl true + def handle_event("search", %{"value" => search_term}, socket) do + socket = + socket + |> assign(:search_term, search_term) + |> assign(:show_dropdown, true) + |> update_filtered_options() + + {:noreply, socket} + end + + def handle_event("select_option", %{"value" => value, "label" => label}, socket) do + socket = + socket + |> assign(:selected_value, value) + |> assign(:display_value, label) + |> assign(:show_dropdown, false) + |> assign(:search_term, label) + + {:noreply, socket} + end + + def handle_event("open_dropdown", _params, socket) do + socket = + socket + |> assign(:show_dropdown, true) + |> update_filtered_options() + + {:noreply, socket} + end + + def handle_event("close_dropdown", _params, socket) do + {:noreply, assign(socket, :show_dropdown, false)} + end + + defp update_filtered_options(socket) do + search_term = String.downcase(socket.assigns[:search_term] || "") + + filtered = + if search_term == "" do + socket.assigns.options + else + Enum.filter(socket.assigns.options, fn {label, _value} -> + String.contains?(String.downcase(label), search_term) + end) + end + + assign(socket, :filtered_options, filtered) + end + + defp update_display_value(socket) do + current_value = get_field_value(socket.assigns.form, socket.assigns.field) + + display_value = + if current_value do + case Enum.find(socket.assigns.options, fn {_label, value} -> + to_string(value) == to_string(current_value) + end) do + {label, _value} -> label + nil -> "" + end + else + "" + end + + socket + |> assign(:selected_value, current_value) + |> assign(:display_value, display_value) + |> assign(:search_term, display_value) + end + + defp get_field_value(form, field) do + Map.get(form.data, field) || Map.get(form.params, to_string(field)) + end +end