Fix maintenance window form crash on mount
The form template iterated over changeset error tuples directly (e.g. changeset.errors[:name] returns a tuple, not a list), causing Protocol.UndefinedError for Enumerable on Tuple. Rewrote form to use to_form/2 and <.input> components per LiveView conventions. Errors are now handled properly by the input component.
This commit is contained in:
parent
0cc519b6bf
commit
e09b306f9c
2 changed files with 70 additions and 132 deletions
|
|
@ -1,4 +1,5 @@
|
|||
defmodule ToweropsWeb.MaintenanceLive.Form do
|
||||
@moduledoc false
|
||||
use ToweropsWeb, :live_view
|
||||
|
||||
alias Towerops.Maintenance
|
||||
|
|
@ -21,13 +22,14 @@ defmodule ToweropsWeb.MaintenanceLive.Form do
|
|||
def handle_params(params, _url, socket) do
|
||||
case socket.assigns.live_action do
|
||||
:new ->
|
||||
changeset = MaintenanceWindow.changeset(%MaintenanceWindow{suppress_alerts: true}, %{})
|
||||
window = %MaintenanceWindow{suppress_alerts: true, recurring: false}
|
||||
changeset = MaintenanceWindow.changeset(window, %{})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, t("New Maintenance Window"))
|
||||
|> assign(:window, %MaintenanceWindow{suppress_alerts: true, recurring: false})
|
||||
|> assign(:changeset, changeset)
|
||||
|> assign(:window, window)
|
||||
|> assign(:form, to_form(changeset))
|
||||
|> assign(:scope_type, "org")}
|
||||
|
||||
:edit ->
|
||||
|
|
@ -46,7 +48,7 @@ defmodule ToweropsWeb.MaintenanceLive.Form do
|
|||
socket
|
||||
|> assign(:page_title, t("Edit Maintenance Window"))
|
||||
|> assign(:window, window)
|
||||
|> assign(:changeset, changeset)
|
||||
|> assign(:form, to_form(changeset))
|
||||
|> assign(:scope_type, scope_type)}
|
||||
end
|
||||
end
|
||||
|
|
@ -58,7 +60,7 @@ defmodule ToweropsWeb.MaintenanceLive.Form do
|
|||
|> MaintenanceWindow.changeset(params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
{:noreply, assign(socket, :form, to_form(changeset))}
|
||||
end
|
||||
|
||||
def handle_event("change_scope", %{"scope_type" => scope_type}, socket) do
|
||||
|
|
@ -97,7 +99,7 @@ defmodule ToweropsWeb.MaintenanceLive.Form do
|
|||
|> push_navigate(to: ~p"/maintenance/#{window.id}")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
{:noreply, assign(socket, :form, to_form(changeset))}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -110,7 +112,7 @@ defmodule ToweropsWeb.MaintenanceLive.Form do
|
|||
|> push_navigate(to: ~p"/maintenance/#{window.id}")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
{:noreply, assign(socket, :form, to_form(changeset))}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,48 +17,35 @@
|
|||
|
||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-6">
|
||||
<.form
|
||||
for={@changeset}
|
||||
for={@form}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
id="maintenance-form"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<%!-- Name --%>
|
||||
<div>
|
||||
<label for="maintenance_window_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{t("Name")} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="maintenance_window[name]"
|
||||
id="maintenance_window_name"
|
||||
value={Ecto.Changeset.get_field(@changeset, :name) || ""}
|
||||
required
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
||||
placeholder={t("e.g., Tower firmware upgrade")}
|
||||
/>
|
||||
<%= for {msg, _} <- (@changeset.errors[:name] || []) do %>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<.input
|
||||
field={@form[:name]}
|
||||
type="text"
|
||||
label={t("Name") <> " *"}
|
||||
required
|
||||
placeholder={t("e.g., Tower firmware upgrade")}
|
||||
/>
|
||||
|
||||
<%!-- Reason --%>
|
||||
<div>
|
||||
<label for="maintenance_window_reason" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{t("Reason")}
|
||||
</label>
|
||||
<textarea
|
||||
name="maintenance_window[reason]"
|
||||
id="maintenance_window_reason"
|
||||
rows="3"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
||||
placeholder={t("Optional description of the maintenance work")}
|
||||
>{Ecto.Changeset.get_field(@changeset, :reason) || ""}</textarea>
|
||||
</div>
|
||||
<.input
|
||||
field={@form[:reason]}
|
||||
type="textarea"
|
||||
label={t("Reason")}
|
||||
rows="3"
|
||||
placeholder={t("Optional description of the maintenance work")}
|
||||
/>
|
||||
|
||||
<%!-- Scope --%>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{t("Scope")}</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{t("Scope")}
|
||||
</label>
|
||||
<div class="flex items-center gap-4 mb-3">
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
|
|
@ -99,115 +86,64 @@
|
|||
</div>
|
||||
|
||||
<%= if @scope_type == "site" do %>
|
||||
<select
|
||||
name="maintenance_window[site_id]"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
||||
>
|
||||
<option value="">{t("Select a site...")}</option>
|
||||
<%= for site <- @sites do %>
|
||||
<option value={site.id} selected={Ecto.Changeset.get_field(@changeset, :site_id) == site.id}>
|
||||
{site.name}
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<.input
|
||||
field={@form[:site_id]}
|
||||
type="select"
|
||||
prompt={t("Select a site...")}
|
||||
options={Enum.map(@sites, &{&1.name, &1.id})}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<%= if @scope_type == "device" do %>
|
||||
<select
|
||||
name="maintenance_window[device_id]"
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
||||
>
|
||||
<option value="">{t("Select a device...")}</option>
|
||||
<%= for device <- @devices do %>
|
||||
<option value={device.id} selected={Ecto.Changeset.get_field(@changeset, :device_id) == device.id}>
|
||||
{device.name}
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<.input
|
||||
field={@form[:device_id]}
|
||||
type="select"
|
||||
prompt={t("Select a device...")}
|
||||
options={Enum.map(@devices, &{&1.name, &1.id})}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%!-- Time Range --%>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="maintenance_window_starts_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{t("Starts At")} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="maintenance_window[starts_at]"
|
||||
id="maintenance_window_starts_at"
|
||||
value={format_datetime_local(Ecto.Changeset.get_field(@changeset, :starts_at))}
|
||||
required
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
||||
/>
|
||||
<%= for {msg, _} <- (@changeset.errors[:starts_at] || []) do %>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<label for="maintenance_window_ends_at" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{t("Ends At")} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="maintenance_window[ends_at]"
|
||||
id="maintenance_window_ends_at"
|
||||
value={format_datetime_local(Ecto.Changeset.get_field(@changeset, :ends_at))}
|
||||
required
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white"
|
||||
/>
|
||||
<%= for {msg, _} <- (@changeset.errors[:ends_at] || []) do %>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{msg}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<.input
|
||||
field={@form[:starts_at]}
|
||||
type="datetime-local"
|
||||
label={t("Starts At") <> " *"}
|
||||
required
|
||||
value={format_datetime_local(@form[:starts_at].value)}
|
||||
/>
|
||||
<.input
|
||||
field={@form[:ends_at]}
|
||||
type="datetime-local"
|
||||
label={t("Ends At") <> " *"}
|
||||
required
|
||||
value={format_datetime_local(@form[:ends_at].value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<%!-- Suppress Alerts --%>
|
||||
<div>
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="hidden" name="maintenance_window[suppress_alerts]" value="false" />
|
||||
<input
|
||||
type="checkbox"
|
||||
name="maintenance_window[suppress_alerts]"
|
||||
value="true"
|
||||
checked={Ecto.Changeset.get_field(@changeset, :suppress_alerts) == true}
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Suppress alerts during this window")}</span>
|
||||
</label>
|
||||
</div>
|
||||
<.input
|
||||
field={@form[:suppress_alerts]}
|
||||
type="checkbox"
|
||||
label={t("Suppress alerts during this window")}
|
||||
/>
|
||||
|
||||
<%!-- Recurring --%>
|
||||
<div>
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||
<input type="hidden" name="maintenance_window[recurring]" value="false" />
|
||||
<input
|
||||
type="checkbox"
|
||||
name="maintenance_window[recurring]"
|
||||
value="true"
|
||||
checked={Ecto.Changeset.get_field(@changeset, :recurring) == true}
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Recurring")}</span>
|
||||
</label>
|
||||
</div>
|
||||
<.input
|
||||
field={@form[:recurring]}
|
||||
type="checkbox"
|
||||
label={t("Recurring")}
|
||||
/>
|
||||
|
||||
<%!-- Recurrence Rule --%>
|
||||
<%= if Ecto.Changeset.get_field(@changeset, :recurring) == true do %>
|
||||
<div>
|
||||
<label for="maintenance_window_recurrence_rule" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{t("Recurrence Rule")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="maintenance_window[recurrence_rule]"
|
||||
id="maintenance_window_recurrence_rule"
|
||||
value={Ecto.Changeset.get_field(@changeset, :recurrence_rule) || ""}
|
||||
class="block w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:ring-blue-500 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500"
|
||||
placeholder={t("e.g., FREQ=WEEKLY;BYDAY=SU")}
|
||||
/>
|
||||
</div>
|
||||
<%= if Phoenix.HTML.Form.input_value(@form, :recurring) == true do %>
|
||||
<.input
|
||||
field={@form[:recurrence_rule]}
|
||||
type="text"
|
||||
label={t("Recurrence Rule")}
|
||||
placeholder={t("e.g., FREQ=WEEKLY;BYDAY=SU")}
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<%!-- Submit --%>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue