diff --git a/lib/shift73k/shifts/templates/shift_template.ex b/lib/shift73k/shifts/templates/shift_template.ex index 56ae8ac5..37c4949d 100644 --- a/lib/shift73k/shifts/templates/shift_template.ex +++ b/lib/shift73k/shifts/templates/shift_template.ex @@ -57,6 +57,7 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do [] end end) + |> validate_not_nil([:time_zone]) |> validate_inclusion(:time_zone, Tzdata.zone_list(), message: "must be a valid IANA tz database time zone" ) @@ -72,4 +73,14 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do |> Map.from_struct() |> Map.drop([:__meta__, :id, :inserted_at, :updated_at, :user, :is_fave_of_user]) end + + def validate_not_nil(changeset, fields) do + Enum.reduce(fields, changeset, fn field, changeset -> + if get_field(changeset, field) == nil do + add_error(changeset, field, "nil") + else + changeset + end + end) + end end diff --git a/lib/shift73k_web/live/shift_assign_live/delete_component.html.heex b/lib/shift73k_web/live/shift_assign_live/delete_component.html.heex new file mode 100644 index 00000000..0f3a91d7 --- /dev/null +++ b/lib/shift73k_web/live/shift_assign_live/delete_component.html.heex @@ -0,0 +1,35 @@ +<div> + + <div class="modal-body"> + + <p>Are you sure you want to delete all assigned shifts from the selected days?</p> + + <%= for {y, data} <- @date_map do %> + <dt><%= y %></dt> + <% months = Map.keys(data) %> + <dd> + <%= for {m, i} <- Enum.with_index(months, 1) do %> + <%= data |> Map.get(m) |> hd() |> Calendar.strftime("%b") %>: + <% days = Map.get(data, m) %> + <%= for {d, i} <- Enum.with_index(days, 1) do %> + <%= d.day %><%= if i < length(days) do %>,<% end %> + <% end %> + + <%= if i < length(months) do %><br /><% end %> + <% end %> + </dd> + <% end %> + + </div> + <div class="modal-footer"> + + <%= link "Cancel", to: "#", class: "btn btn-outline-dark", phx_click: "hide", phx_target: "##{@modal_id}" %> + <%= link "Confirm Delete", to: "#", + class: "btn btn-danger", + phx_click: "confirm-delete-days-shifts", + phx_target: @myself + %> + + </div> + +</div> \ No newline at end of file diff --git a/lib/shift73k_web/live/shift_assign_live/delete_component.html.leex b/lib/shift73k_web/live/shift_assign_live/delete_component.html.leex deleted file mode 100644 index 81aa79a6..00000000 --- a/lib/shift73k_web/live/shift_assign_live/delete_component.html.leex +++ /dev/null @@ -1,31 +0,0 @@ -<div class="modal-body"> - - <p>Are you sure you want to delete all assigned shifts from the selected days?</p> - - <%= for {y, data} <- @date_map do %> - <dt><%= y %></dt> - <% months = Map.keys(data) %> - <dd> - <%= for {m, i} <- Enum.with_index(months, 1) do %> - <%= data |> Map.get(m) |> hd() |> Calendar.strftime("%b") %>: - <% days = Map.get(data, m) %> - <%= for {d, i} <- Enum.with_index(days, 1) do %> - <%= d.day %><%= if i < length(days) do %>,<% end %> - <% end %> - - <%= if i < length(months) do %><br /><% end %> - <% end %> - </dd> - <% end %> - -</div> -<div class="modal-footer"> - - <%= link "Cancel", to: "#", class: "btn btn-outline-dark", phx_click: "hide", phx_target: "##{@modal_id}" %> - <%= link "Confirm Delete", to: "#", - class: "btn btn-danger", - phx_click: "confirm-delete-days-shifts", - phx_target: @myself - %> - -</div> diff --git a/lib/shift73k_web/live/shift_assign_live/index.ex b/lib/shift73k_web/live/shift_assign_live/index.ex index 670c32b9..24777847 100644 --- a/lib/shift73k_web/live/shift_assign_live/index.ex +++ b/lib/shift73k_web/live/shift_assign_live/index.ex @@ -1,5 +1,6 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do use Shift73kWeb, :live_view + import Shift73k, only: [app_time_zone: 0] alias Shift73k.Repo alias Shift73k.Shifts diff --git a/lib/shift73k_web/live/shift_assign_live/index.html.leex b/lib/shift73k_web/live/shift_assign_live/index.html.heex similarity index 75% rename from lib/shift73k_web/live/shift_assign_live/index.html.leex rename to lib/shift73k_web/live/shift_assign_live/index.html.heex index 9c88881c..ce1d37a4 100644 --- a/lib/shift73k_web/live/shift_assign_live/index.html.leex +++ b/lib/shift73k_web/live/shift_assign_live/index.html.heex @@ -23,14 +23,19 @@ %> <% end %> - <button type="button" class="ms-2 btn btn-primary text-nowrap <%= if @show_template_btn_active, do: "active" %>" id="#templateDetailsBtn" phx-click="toggle-template-details" phx-value-target_id="#templateDetailsCol"> - <%= icon_div @socket, (@show_template_btn_active && "bi-binoculars-fill" || "bi-binoculars"), [class: "icon baseline"] %> + <% details_button_class = "ms-2 btn btn-primary text-nowrap" + details_button_class = if @show_template_btn_active, do: "#{details_button_class} active", else: details_button_class %> + <button type="button" class={details_button_class} id="#templateDetailsBtn" phx-click="toggle-template-details" phx-value-target_id="#templateDetailsCol"> + <i class={@show_template_btn_active && "bi bi-binoculars-fill me-sm-1" || "bi bi-binoculars me-sm-1"}></i> <span class="d-none d-sm-inline">Details</span> </button> </div> - <div class="col-12 col-lg-9 col-xl-8 col-xxl-7 <%= @show_template_details && "collapse show" || "collapse" %>" id="#templateDetailsCol" phx-hook="BsCollapse"> + + <% template_details_div_class = "col-12 col-lg-9 col-xl-8 col-xxl-7 collapse" + template_details_div_class = if @show_template_details, do: "#{template_details_div_class} show", else: template_details_div_class %> + <div class={template_details_div_class} id="#templateDetailsCol" phx-hook="BsCollapse"> <div class="card mt-4"> <div class="card-body"> @@ -40,8 +45,8 @@ <div class="col-12 col-md-6"> <%= label stf, :subject, "Subject/Title", class: "form-label" %> - <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(stf, :subject) %>"> - <%= icon_div @socket, "bi-tag", [class: "icon is-left text-muted fs-5"] %> + <div class="inner-addon left-addon mb-3" phx-feedback-for={input_id(stf, :subject)}> + <i class="bi bi-tag icon is-left text-muted fs-5"></i> <%= text_input stf, :subject, value: input_value(stf, :subject), class: input_class(stf, :subject, "form-control"), @@ -56,25 +61,27 @@ <div class="col-12 col-md-6 mb-3"> <div class="row gx-2 gx-sm-3"> - <div class="col-6" phx-feedback-for="<%= input_id(stf, :time_start) %>"> + <div class="col-6" phx-feedback-for={input_id(stf, :time_start)}> <%= label stf, :time_start, "Start", class: "form-label" %> <%= time_input stf, :time_start, precision: :minute, value: input_value(stf, :time_start), class: input_class(stf, :time_start, "form-control"), disabled: @shift_template.id != @custom_shift.id, - aria_describedby: error_ids(stf, :time_start) + aria_describedby: error_ids(stf, :time_start), + required: true %> </div> - <div class="col-6" phx-feedback-for="<%= input_id(stf, :time_end) %>"> + <div class="col-6" phx-feedback-for={input_id(stf, :time_end)}> <%= label stf, :time_end, "End", class: "form-label" %> <%= time_input stf, :time_end, precision: :minute, value: input_value(stf, :time_end), class: input_class(stf, :time_end, "form-control"), disabled: @shift_template.id != @custom_shift.id, - aria_describedby: error_ids(stf, :time_end) + aria_describedby: error_ids(stf, :time_end), + required: true %> </div> @@ -82,18 +89,18 @@ <div class="valid-feedback d-block text-primary">Shift length: <%= @shift_length %></div> - <div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(stf, :time_start) %>"> + <div class="phx-orphaned-feedback" phx-feedback-for={input_id(stf, :time_start)}> <%= error_tag stf, :time_start %> </div> - <div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(stf, :time_end) %>"> + <div class="phx-orphaned-feedback" phx-feedback-for={input_id(stf, :time_end)}> <%= error_tag stf, :time_end %> </div> </div> <div class="col-12 col-md-6"> <%= label stf, :location, class: "form-label" %> - <div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(stf, :location) %>"> - <%= icon_div @socket, "bi-geo", [class: "icon is-left text-muted fs-5"] %> + <div class="inner-addon left-addon mb-3" phx-feedback-for={input_id(stf, :location)}> + <i class="bi bi-geo icon is-left text-muted fs-5"></i> <%= text_input stf, :location, value: input_value(stf, :location), class: input_class(stf, :location, "form-control"), @@ -107,18 +114,19 @@ <div class="col-12 col-md-6"> <%= label stf, :time_zone, class: "form-label" %> - <div class="inner-addon left-addon mb-3 mb-md-0" phx-feedback-for="<%= input_id(stf, :time_zone) %>"> - <%= icon_div @socket, "bi-map", [class: "icon is-left text-muted fs-5"] %> + <div class="inner-addon left-addon mb-3 mb-md-0" phx-feedback-for={input_id(stf, :time_zone)}> + <i class="bi bi-map icon is-left text-muted fs-5"></i> <%= text_input stf, :time_zone, value: input_value(stf, :time_zone), class: input_class(stf, :time_zone, "form-control"), disabled: @shift_template.id != @custom_shift.id, phx_debounce: 250, - list: "tz_list" + list: "tz_list", + placeholder: "Default: #{app_time_zone()}" %> <datalist id="tz_list"> <%= for tz_name <- Tzdata.zone_list() do %> - <option value="<%= tz_name %>"></option> + <option value={tz_name}></option> <% end %> end </datalist> @@ -131,7 +139,7 @@ <div class="col-12"> <%= label stf, :description, class: "form-label" %> - <div phx-feedback-for="<%= input_id(stf, :description) %>"> + <div phx-feedback-for={input_id(stf, :description)}> <%= textarea stf, :description, value: input_value(stf, :description), @@ -173,17 +181,17 @@ <%= Calendar.strftime(@cursor_date, "%B %Y") %> </h3> <div> - <button type="button" phx-click="month-nav" phx-value-month="now" class="btn btn-info text-white" <%= if Map.get(@cursor_date, :month) == Map.get(Date.utc_today(), :month), do: "disabled" %>> - <%= icon_div @socket, "bi-asterisk", [class: "icon baseline"] %> + <button type="button" phx-click="month-nav" phx-value-month="now" class="btn btn-info text-white" disabled={if Map.get(@cursor_date, :month) == Map.get(Date.utc_today(), :month), do: :true, else: :false}> + <i class="bi bi-asterisk me-sm-1"></i> <span class="d-none d-sm-inline">Today</span> </button> <button type="button" phx-click="month-nav" phx-value-month="prev" class="btn btn-primary"> - <%= icon_div @socket, "bi-chevron-left", [class: "icon baseline"] %> + <i class="bi bi-chevron-left me-sm-1"></i> <span class="d-none d-sm-inline">Prev</span> </button> <button type="button" phx-click="month-nav" phx-value-month="next" class="btn btn-primary"> <span class="d-none d-sm-inline">Next</span> - <%= icon_div @socket, "bi-chevron-right", [class: "icon baseline", style: "margin-left:0.125rem;"] %> + <i class="bi bi-chevron-right ms-sm-1"></i> </button> </div> </div> @@ -203,7 +211,7 @@ <%= for week <- @week_rows do %> <tr> <%= for day <- week do %> - <td class="<%= day_color(day, @current_date, @cursor_date, @selected_days) %>" phx-click="select-day" phx-value-day="<%= day %>"> + <td class={day_color(day, @current_date, @cursor_date, @selected_days)} phx-click="select-day" phx-value-day={day}> <%= Calendar.strftime(day, "%d") %><%= if day.month != @cursor_date.month, do: "-#{Calendar.strftime(day, "%b")}" %> @@ -235,19 +243,16 @@ <div class="row justify-content-center justify-content-lg-end my-5"> <div class="col-12 col-sm-10 col-md-8 col-lg-auto d-flex flex-column-reverse flex-lg-row"> - <button class="btn btn-outline-danger mb-1 mb-lg-0 me-lg-1" phx-click="delete-days-shifts" <%= if Enum.empty?(@selected_days), do: "disabled" %>> - <%= icon_div @socket, "bi-trash", [class: "icon baseline"] %> - Delete shifts from selected days + <button class="btn btn-outline-danger mb-1 mb-lg-0 me-lg-1" phx-click="delete-days-shifts" disabled={if Enum.empty?(@selected_days), do: :true, else: :false}> + <i class="bi bi-trash me-1"></i> Delete shifts from selected days </button> - <button class="btn btn-outline-dark mb-1 mb-lg-0 me-lg-1" phx-click="clear-days" <%= if Enum.empty?(@selected_days), do: "disabled" %>> - <%= icon_div @socket, "bi-eraser", [class: "icon baseline"] %> - De-select all selected + <button class="btn btn-outline-dark mb-1 mb-lg-0 me-lg-1" phx-click="clear-days" disabled={if Enum.empty?(@selected_days), do: :true, else: :false}> + <i class="bi bi-eraser me-1"></i> De-select all selected </button> - <button class="btn btn-primary mb-1 mb-lg-0" phx-click="save-days" <%= if (!@shift_template_changeset.valid? || Enum.empty?(@selected_days)), do: "disabled" %>> - <%= icon_div @socket, "bi-save", [class: "icon baseline"] %> - Assign shifts to selected days + <button class="btn btn-primary mb-1 mb-lg-0" phx-click="save-days" disabled={if (!@shift_template_changeset.valid? || Enum.empty?(@selected_days)), do: :true, else: :false}> + <i class="bi bi-save me-1"></i> Assign shifts to selected days </button> </div> diff --git a/lib/shift73k_web/live/shift_template_live/form_component.ex b/lib/shift73k_web/live/shift_template_live/form_component.ex index d4263bcd..ef000a36 100644 --- a/lib/shift73k_web/live/shift_template_live/form_component.ex +++ b/lib/shift73k_web/live/shift_template_live/form_component.ex @@ -1,5 +1,6 @@ defmodule Shift73kWeb.ShiftTemplateLive.FormComponent do use Shift73kWeb, :live_component + import Shift73k, only: [app_time_zone: 0] alias Shift73k.Shifts.Templates alias Shift73k.Shifts.Templates.ShiftTemplate diff --git a/lib/shift73k_web/live/shift_template_live/form_component.html.heex b/lib/shift73k_web/live/shift_template_live/form_component.html.heex index a2ce3b52..66f944d9 100644 --- a/lib/shift73k_web/live/shift_template_live/form_component.html.heex +++ b/lib/shift73k_web/live/shift_template_live/form_component.html.heex @@ -1,11 +1,6 @@ <div> - <%= form_for @changeset, "#", [ - id: "shift_template-form", - phx_target: @myself, - phx_change: "validate", - phx_submit: "save" - ], fn f -> %> + <.form let={f} for={@changeset} phx-change="validate" phx-submit="save" phx-target={@myself} id="shift_template-form"> <div class="modal-body"> @@ -92,7 +87,8 @@ value: input_value(f, :time_zone), class: input_class(f, :time_zone, "form-control"), phx_debounce: 250, - list: "tz_list" + list: "tz_list", + placeholder: "Default: #{app_time_zone()}" %> <datalist id="tz_list"> <%= for tz_name <- Tzdata.zone_list() do %> @@ -118,6 +114,6 @@ </div> - <% end %> + </.form> </div>