shift template live crud working
This commit is contained in:
parent
9e8ecb8b89
commit
8aa4b8eee0
14 changed files with 9279 additions and 1123 deletions
lib
shift73k
shift73k_web
live/shift_template_live
templates/layout
|
@ -22,8 +22,8 @@ defmodule Shift73k.ShiftTemplates do
|
|||
end
|
||||
|
||||
def list_shift_templates_by_user_id(user_id) do
|
||||
from(s in ShiftTemplate, where: s.user_id == ^user_id)
|
||||
|> Repo.all()
|
||||
q = from s in ShiftTemplate, where: s.user_id == ^user_id, order_by: s.subject
|
||||
Repo.all(q)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -8,13 +8,12 @@ defmodule Shift73k.ShiftTemplates.ShiftTemplate do
|
|||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "shift_templates" do
|
||||
field :subject, :string, default: "My Work Shift"
|
||||
field :subject, :string
|
||||
field :description, :string
|
||||
field :location, :string
|
||||
field :timezone, :string, default: @time_zone
|
||||
field :start_time, :time, default: ~T[09:00:00]
|
||||
field :length_hours, :integer, default: 8
|
||||
field :length_minutes, :integer, default: 0
|
||||
field :time_zone, :string, default: @time_zone
|
||||
field :time_start, :time, default: ~T[09:00:00]
|
||||
field :time_end, :time, default: ~T[17:00:00]
|
||||
|
||||
belongs_to(:user, Shift73k.Accounts.User)
|
||||
|
||||
|
@ -26,22 +25,61 @@ defmodule Shift73k.ShiftTemplates.ShiftTemplate do
|
|||
shift_template
|
||||
|> cast(attrs, [
|
||||
:subject,
|
||||
:description,
|
||||
:location,
|
||||
:timezone,
|
||||
:start_time,
|
||||
:length_hours,
|
||||
:length_minutes,
|
||||
:description,
|
||||
:time_zone,
|
||||
:time_start,
|
||||
:time_end,
|
||||
:user_id
|
||||
])
|
||||
|> validate_required([
|
||||
:subject,
|
||||
:timezone,
|
||||
:start_time,
|
||||
:length_hours
|
||||
:time_zone,
|
||||
:time_start,
|
||||
:time_end
|
||||
])
|
||||
|> validate_number(:length_hours, greater_than_or_equal_to: 0, less_than_or_equal_to: 24)
|
||||
|> validate_number(:length_minutes, greater_than_or_equal_to: 0, less_than: 60)
|
||||
|> validate_inclusion(:timezone, Tzdata.zone_list())
|
||||
|> validate_length(:subject, count: :codepoints, max: 280)
|
||||
|> validate_length(:location, count: :codepoints, max: 280)
|
||||
|> validate_change(:time_end, fn :time_end, time_end ->
|
||||
shift_length = shift_length(get_time_start(attrs), time_end)
|
||||
|
||||
cond do
|
||||
shift_length == 0 ->
|
||||
[time_end: "end time cannot equal start time"]
|
||||
|
||||
shift_length >= 16 * 3600 ->
|
||||
[time_end: "you don't want to work 16 or more hours!"]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end)
|
||||
|> validate_inclusion(:time_zone, Tzdata.zone_list())
|
||||
end
|
||||
|
||||
defp get_time_start(%{"time_start" => time_start}), do: time_start
|
||||
defp get_time_start(%{time_start: time_start}), do: time_start
|
||||
defp get_time_start(_), do: nil
|
||||
|
||||
def shift_length(time_start, time_end) do
|
||||
cond do
|
||||
time_end > time_start ->
|
||||
Time.diff(time_end, time_start)
|
||||
|
||||
time_start > time_end ->
|
||||
len1 = Time.diff(~T[23:59:59], time_start) + 1
|
||||
len2 = Time.diff(time_end, ~T[00:00:00])
|
||||
len1 + len2
|
||||
|
||||
true ->
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def shift_length_h_m_tuple(time_start, time_end) do
|
||||
shift_length_seconds = shift_length(time_start, time_end)
|
||||
h = shift_length_seconds |> Integer.floor_div(3600)
|
||||
m = shift_length_seconds |> rem(3600) |> Integer.floor_div(60)
|
||||
{h, m}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
<div class="modal-body">
|
||||
|
||||
Are you sure you want to delete "<%= @delete_shift_template.subject %>
|
||||
(<%= @delete_shift_template.start_time |> Calendar.strftime("%I:%M%P") %>
|
||||
(<%= @delete_shift_template.time_start |> Calendar.strftime("%I:%M%P") %>
|
||||
—
|
||||
<%=
|
||||
@delete_shift_template.start_time
|
||||
|> Time.add((60 * 60 * @delete_shift_template.length_hours) + ((@delete_shift_template.length_minutes || 0) * 60))
|
||||
|> Calendar.strftime("%I:%M%P")
|
||||
%>)"?
|
||||
<%= @delete_shift_template.time_end |> Calendar.strftime("%I:%M%P") %>)"?
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -2,54 +2,96 @@ defmodule Shift73kWeb.ShiftTemplateLive.FormComponent do
|
|||
use Shift73kWeb, :live_component
|
||||
|
||||
alias Shift73k.ShiftTemplates
|
||||
alias Shift73k.ShiftTemplates.ShiftTemplate
|
||||
|
||||
@impl true
|
||||
def update(%{shift_template: shift_template} = assigns, socket) do
|
||||
changeset = ShiftTemplates.change_shift_template(shift_template)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:changeset, changeset)}
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:changeset, changeset)
|
||||
|> assign_shift_length(shift_template.time_start, shift_template.time_end)
|
||||
|> live_okreply()
|
||||
end
|
||||
|
||||
defp assign_shift_length(socket, time_start, time_end) do
|
||||
shift_length = ShiftTemplate.shift_length_h_m_tuple(time_start, time_end)
|
||||
assign(socket, :shift_length, shift_length)
|
||||
end
|
||||
|
||||
defp prep_shift_template_params(shift_template_params, current_user) do
|
||||
time_start = Time.from_iso8601!("T#{shift_template_params["time_start"]}:00")
|
||||
time_end = Time.from_iso8601!("T#{shift_template_params["time_end"]}:00")
|
||||
|
||||
shift_template_params
|
||||
|> Map.put("time_start", time_start)
|
||||
|> Map.put("time_end", time_end)
|
||||
|> Map.put("user_id", current_user.id)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"shift_template" => shift_template_params}, socket) do
|
||||
shift_template_params =
|
||||
prep_shift_template_params(shift_template_params, socket.assigns.current_user)
|
||||
|
||||
changeset =
|
||||
socket.assigns.shift_template
|
||||
|> ShiftTemplates.change_shift_template(shift_template_params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
socket
|
||||
|> assign(:changeset, changeset)
|
||||
|> assign_shift_length(
|
||||
shift_template_params["time_start"],
|
||||
shift_template_params["time_end"]
|
||||
)
|
||||
|> live_noreply()
|
||||
end
|
||||
|
||||
def handle_event("save", %{"shift_template" => shift_template_params}, socket) do
|
||||
save_shift_template(socket, socket.assigns.action, shift_template_params)
|
||||
save_shift_template(
|
||||
socket,
|
||||
socket.assigns.action,
|
||||
prep_shift_template_params(shift_template_params, socket.assigns.current_user)
|
||||
)
|
||||
end
|
||||
|
||||
defp save_shift_template(socket, :edit, shift_template_params) do
|
||||
case ShiftTemplates.update_shift_template(socket.assigns.shift_template, shift_template_params) do
|
||||
{:ok, _shift_template} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Shift template updated successfully")
|
||||
|> push_redirect(to: socket.assigns.return_to)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
@impl true
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, push_event(socket, "modal-please-hide", %{})}
|
||||
end
|
||||
|
||||
defp save_shift_template(socket, :new, shift_template_params) do
|
||||
case ShiftTemplates.create_shift_template(shift_template_params) do
|
||||
{:ok, _shift_template} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Shift template created successfully")
|
||||
|> push_redirect(to: socket.assigns.return_to)}
|
||||
flash = {:info, "Shift template created successfully"}
|
||||
send(self(), {:put_flash_message, flash})
|
||||
|
||||
socket
|
||||
|> push_event("modal-please-hide", %{})
|
||||
|> live_noreply()
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_shift_template(socket, :edit, shift_template_params) do
|
||||
case ShiftTemplates.update_shift_template(
|
||||
socket.assigns.shift_template,
|
||||
shift_template_params
|
||||
) do
|
||||
{:ok, _shift_template} ->
|
||||
flash = {:info, "Shift template updated successfully"}
|
||||
send(self(), {:put_flash_message, flash})
|
||||
|
||||
socket
|
||||
|> push_event("modal-please-hide", %{})
|
||||
|> live_noreply()
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,39 +6,107 @@
|
|||
|
||||
<div class="modal-body">
|
||||
|
||||
<%= label f, :subject, "Subject/Title", class: "form-label" %>
|
||||
<div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :subject) %>">
|
||||
<%= icon_div @socket, "bi-tag", [class: "icon is-left text-muted fs-5"] %>
|
||||
<%= text_input f, :subject,
|
||||
value: input_value(f, :subject),
|
||||
class: input_class(f, :subject, "form-control"),
|
||||
autofocus: true,
|
||||
aria_describedby: error_ids(f, :subject)
|
||||
%>
|
||||
<%= error_tag f, :subject %>
|
||||
</div>
|
||||
|
||||
<%= label f, :subject %>
|
||||
<%= text_input f, :subject %>
|
||||
<%= error_tag f, :subject %>
|
||||
<div class="row gx-2 gx-sm-3">
|
||||
|
||||
<%= label f, :description %>
|
||||
<%= text_input f, :description %>
|
||||
<%= error_tag f, :description %>
|
||||
<div class="col-6" phx-feedback-for="<%= input_id(f, :time_start) %>">
|
||||
<%= label f, :time_start, "Start", class: "form-label" %>
|
||||
<%= time_input f, :time_start,
|
||||
precision: :minute,
|
||||
value: input_value(f, :time_start),
|
||||
class: input_class(f, :time_start, "form-control"),
|
||||
aria_describedby: error_ids(f, :time_start)
|
||||
%>
|
||||
</div>
|
||||
|
||||
<%= label f, :location %>
|
||||
<%= text_input f, :location %>
|
||||
<%= error_tag f, :location %>
|
||||
<div class="col-6" phx-feedback-for="<%= input_id(f, :time_end) %>">
|
||||
<%= label f, :time_end, "End", class: "form-label" %>
|
||||
<%= time_input f, :time_end,
|
||||
precision: :minute,
|
||||
value: input_value(f, :time_end),
|
||||
class: input_class(f, :time_end, "form-control"),
|
||||
aria_describedby: error_ids(f, :time_end)
|
||||
%>
|
||||
</div>
|
||||
|
||||
<%= label f, :timezone %>
|
||||
<%= text_input f, :timezone %>
|
||||
<%= error_tag f, :timezone %>
|
||||
</div>
|
||||
|
||||
<%= label f, :start_time %>
|
||||
<%= time_select f, :start_time %>
|
||||
<%= error_tag f, :start_time %>
|
||||
<div class="valid-feedback d-block text-primary">Shift length: <%= @shift_length |> elem(0) %>h <%= @shift_length |> elem(1) %>m</div>
|
||||
|
||||
<div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(f, :time_start) %>">
|
||||
<%= error_tag f, :time_start %>
|
||||
</div>
|
||||
<div class="phx-orphaned-feedback" phx-feedback-for="<%= input_id(f, :time_end) %>">
|
||||
<%= error_tag f, :time_end %>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<%= label f, :location, class: "form-label mt-3" %>
|
||||
<div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :location) %>">
|
||||
<%= icon_div @socket, "bi-geo", [class: "icon is-left text-muted fs-5"] %>
|
||||
<%= text_input f, :location,
|
||||
value: input_value(f, :location),
|
||||
class: input_class(f, :location, "form-control"),
|
||||
aria_describedby: error_ids(f, :location)
|
||||
%>
|
||||
<%= error_tag f, :location %>
|
||||
</div>
|
||||
|
||||
|
||||
<%= label f, :description, class: "form-label" %>
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :description) %>">
|
||||
|
||||
<%= textarea f, :description,
|
||||
value: input_value(f, :description),
|
||||
class: input_class(f, :description, "form-control"),
|
||||
aria_describedby: error_ids(f, :description)
|
||||
%>
|
||||
<%= error_tag f, :description %>
|
||||
</div>
|
||||
|
||||
|
||||
<%= label f, :time_zone, class: "form-label" %>
|
||||
<div class="inner-addon left-addon mb-3" phx-feedback-for="<%= input_id(f, :time_zone) %>">
|
||||
<%= icon_div @socket, "bi-map", [class: "icon is-left text-muted fs-5"] %>
|
||||
<%= text_input f, :time_zone,
|
||||
value: input_value(f, :time_zone),
|
||||
class: "form-control",
|
||||
list: "tz_list"
|
||||
%>
|
||||
<datalist id="tz_list">
|
||||
<%= for tz_name <- Tzdata.zone_list() do %>
|
||||
<option value="<%= tz_name %>"></option>
|
||||
<% end %>
|
||||
end
|
||||
</datalist>
|
||||
<div class="valid-feedback d-block text-primary">Type to search & select from list of known time zones (<%= link "formatted per IANA", to: "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones", target: "_blank" %>)</div>
|
||||
<%= error_tag f, :time_zone %>
|
||||
</div>
|
||||
|
||||
<%= label f, :length_hours %>
|
||||
<%= number_input f, :length_hours %>
|
||||
<%= error_tag f, :length_hours %>
|
||||
|
||||
<%= label f, :length_minutes %>
|
||||
<%= number_input f, :length_minutes %>
|
||||
<%= error_tag f, :length_minutes %>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<%= submit "Save", phx_disable_with: "Saving..." %>
|
||||
<%= link "Cancel", to: "#", class: "btn", phx_click: "cancel", phx_target: @myself %>
|
||||
<%= submit "Save",
|
||||
class: "btn btn-primary ",
|
||||
disabled: !@changeset.valid?,
|
||||
aria_disabled: !@changeset.valid? && "true" || false,
|
||||
phx_disable_with: "Saving..."
|
||||
%>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
|
||||
<div class="row justify-content-start justify-content-sm-center">
|
||||
<div class="col-md-12 col-lg-10 col-xxl-8">
|
||||
<div class="col-md-10 col-xl-10">
|
||||
|
||||
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center">
|
||||
<h2 class="mb-3 mb-sm-0">
|
||||
|
@ -35,7 +35,7 @@
|
|||
|
||||
<%= for shift <- @shift_templates do %>
|
||||
|
||||
<div class="col-12 col-md-6 col-xl-4 ">
|
||||
<div class="col-12 col-lg-6">
|
||||
|
||||
<div class="card mt-4">
|
||||
<h5 class="card-header">
|
||||
|
@ -49,23 +49,25 @@
|
|||
<tr>
|
||||
<th scope="row" class="text-end">
|
||||
<%= icon_div @socket, "bi-hourglass", [class: "icon baseline text-muted"] %>
|
||||
<span class="visually-hidden">Hours</span>
|
||||
<span class="visually-hidden">Hours:</span>
|
||||
</th>
|
||||
<td>
|
||||
<%= shift.start_time |> Calendar.strftime("%I:%M%P") %>
|
||||
<%= shift.time_start |> Calendar.strftime("%I:%M%P") %>
|
||||
—
|
||||
<%=
|
||||
shift.start_time
|
||||
|> Time.add((60 * 60 * shift.length_hours) + ((shift.length_minutes || 0) * 60))
|
||||
|> Calendar.strftime("%I:%M%P")
|
||||
%>
|
||||
<%= shift.time_end |> Calendar.strftime("%I:%M%P") %>
|
||||
<span class="text-muted">
|
||||
<span class="visually-hidden">Shift length:</span>
|
||||
<% shift_length = ShiftTemplate.shift_length_h_m_tuple(shift.time_start, shift.time_end) %>
|
||||
(<%= shift_length |> elem(0) %>h <%= shift_length |> elem(1) %>m)
|
||||
</span>
|
||||
<span class="valid-feedback d-block text-muted mt-n1">TZ: <%= shift.time_zone %></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row" class="text-end">
|
||||
<%= icon_div @socket, "bi-geo", [class: "icon baseline text-muted"] %>
|
||||
<span class="visually-hidden">Location</span>
|
||||
<span class="visually-hidden">Location:</span>
|
||||
</th>
|
||||
<td>
|
||||
<%= if shift.location do %>
|
||||
|
@ -78,11 +80,11 @@
|
|||
<tr>
|
||||
<th scope="row" class="text-end">
|
||||
<%= icon_div @socket, "bi-justify-left", [class: "icon baseline text-muted"] %>
|
||||
<span class="visually-hidden">Description</span>
|
||||
<span class="visually-hidden">Description:</span>
|
||||
</th>
|
||||
<td>
|
||||
<%= if shift.description do %>
|
||||
<%= shift.description %>
|
||||
<%= text_to_html shift.description %>
|
||||
<% else %>
|
||||
<span class="text-muted fst-italic">none</span>
|
||||
<% end %>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</span>
|
||||
</button>
|
||||
<% else %>
|
||||
<%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light") do %>
|
||||
<%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-block d-lg-none") do %>
|
||||
<%= icon_div @conn, "bi-door-open", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Log in
|
||||
<% end %>
|
||||
|
@ -68,6 +68,13 @@
|
|||
|
||||
<%= render "navbar/_user_menu.html", assigns %>
|
||||
|
||||
<%= if !@current_user do %>
|
||||
<%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-none d-lg-block") do %>
|
||||
<%= icon_div @conn, "bi-door-open", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Log in
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue