saving shifts working

This commit is contained in:
Adam Piontek 2021-03-16 11:00:33 -04:00
parent 4276399c20
commit ecd4d83e3f
9 changed files with 141 additions and 67 deletions

View file

@ -31,7 +31,7 @@
@import "../node_modules/bootstrap/scss/accordion"; @import "../node_modules/bootstrap/scss/accordion";
// @import "../node_modules/bootstrap/scss/breadcrumb"; // @import "../node_modules/bootstrap/scss/breadcrumb";
@import "../node_modules/bootstrap/scss/pagination"; @import "../node_modules/bootstrap/scss/pagination";
// @import "../node_modules/bootstrap/scss/badge"; @import "../node_modules/bootstrap/scss/badge";
@import "../node_modules/bootstrap/scss/alert"; @import "../node_modules/bootstrap/scss/alert";
@import "../node_modules/bootstrap/scss/progress"; @import "../node_modules/bootstrap/scss/progress";
// @import "../node_modules/bootstrap/scss/list-group"; // @import "../node_modules/bootstrap/scss/list-group";

View file

@ -63,16 +63,32 @@
} }
/* calendar table rounded */ /* calendar table rounded */
table.table.table-calendar span.badge {
width: 100%;
max-width: 100%;
overflow: hidden;
text-overflow: clip;
white-space: nowrap;
padding-right: 0.7em;
border-right: 0.7em solid transparent;
margin-bottom: 0.1em;
}
table.table.table-calendar thead tr th, table.table.table-calendar thead tr th,
table.table.table-calendar tbody tr td { table.table.table-calendar tbody tr td {
width: 14%; width: 2.5rem;
max-width: 2.5rem;
white-space: nowrap;
} }
table.table.table-calendar tbody tr td { table.table.table-calendar tbody tr td {
font-size: $font-size-sm; font-size: $font-size-sm;
height: 3.5rem; height: 4.1rem;
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
@include media-breakpoint-up(sm) {
height: 4.3rem;
padding: 0.2rem 0.5rem;
}
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
height: 4.5rem; height: 4.7rem;
padding: 0.2rem 0.5rem; padding: 0.2rem 0.5rem;
} }
@include media-breakpoint-up(lg) { @include media-breakpoint-up(lg) {
@ -90,6 +106,9 @@ table.table.table-calendar tbody tr td {
} }
} }
table.table.table-rounded > :not(:last-child) > :last-child > * {
border-bottom-color: $dark;
}
table.table.table-rounded { table.table.table-rounded {
border-collapse: separate; border-collapse: separate;
border-spacing: 0; border-spacing: 0;
@ -103,7 +122,7 @@ table.table.table-rounded {
th { th {
border-top: 1px solid $table-border-color; border-top: 1px solid $table-border-color;
border-right: 1px solid $table-border-color; border-right: 1px solid $table-border-color;
border-bottom: 2px solid $black !important; // border-bottom: 2px solid $dark !important;
border-left: none; border-left: none;
&:first-child { &:first-child {
border-top-left-radius: $border-radius; border-top-left-radius: $border-radius;

View file

@ -21,6 +21,16 @@ defmodule Shift73k.Shifts do
Repo.all(Shift) Repo.all(Shift)
end end
def list_shifts_by_user_between_dates(user_id, start_date, end_date) do
q = from(
s in Shift,
select: %{date: s.date, subject: s.subject, time_start: s.time_start, time_end: s.time_end},
where: s.user_id == ^user_id and s.date >= ^start_date and s.date < ^end_date,
order_by: [s.date, s.time_start]
)
Repo.all(q)
end
@doc """ @doc """
Gets a single shift. Gets a single shift.

View file

@ -22,7 +22,7 @@ defmodule Shift73k.Shifts.Templates do
end end
def list_shift_templates_by_user_id(user_id) do def list_shift_templates_by_user_id(user_id) do
q = from s in ShiftTemplate, where: s.user_id == ^user_id, order_by: s.subject q = from s in ShiftTemplate, where: s.user_id == ^user_id, order_by: [s.subject, s.time_start]
Repo.all(q) Repo.all(q)
end end

View file

@ -2,7 +2,11 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
use Shift73kWeb, :live_view use Shift73kWeb, :live_view
use Timex use Timex
alias Ecto.Multi
alias Shift73k.Repo
alias Shift73k.EctoEnums.WeekdayEnum alias Shift73k.EctoEnums.WeekdayEnum
alias Shift73k.Shifts
alias Shift73k.Shifts.Shift
alias Shift73k.Shifts.Templates alias Shift73k.Shifts.Templates
alias Shift73k.Shifts.Templates.ShiftTemplate alias Shift73k.Shifts.Templates.ShiftTemplate
@ -30,7 +34,7 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> assign_shift_template_changeset() |> assign_shift_template_changeset()
|> init_today(Timex.today()) |> init_today(Timex.today())
|> init_calendar() |> init_calendar()
|> init_known_shifts() |> assign_known_shifts()
|> live_noreply() |> live_noreply()
end end
@ -41,10 +45,12 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
assign(socket, :shift_length, format_shift_length(shift)) assign(socket, :shift_length, format_shift_length(shift))
end end
defp init_known_shifts(%{assigns: %{current_user: user, week_rows: weeks}} = socket) do defp assign_known_shifts(socket) do
days = weeks |> List.flatten() user = socket.assigns.current_user
first = weeks |> List.flatten() |> List.first() first = socket.assigns.day_first
socket last = socket.assigns.day_last
known_shifts = Shifts.list_shifts_by_user_between_dates(user.id, first, last)
assign(socket, :known_shifts, known_shifts)
end end
defp init_calendar(%{assigns: %{current_user: user}} = socket) do defp init_calendar(%{assigns: %{current_user: user}} = socket) do
@ -170,7 +176,7 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
day_last: last day_last: last
] ]
assign(socket, assigns) assign(socket, assigns) |> assign_known_shifts()
end end
@impl true @impl true
@ -206,41 +212,13 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
cond do cond do
direction == "now" -> Timex.today() direction == "now" -> Timex.today()
true -> true ->
months = m = direction == "prev" && -1 || 1 months = direction == "prev" && -1 || 1
Timex.shift(socket.assigns.cursor_date, months: months) Timex.shift(socket.assigns.cursor_date, months: months)
end end
{:noreply, set_month(socket, new_cursor)} {:noreply, set_month(socket, new_cursor)}
end end
# @impl true
# def handle_event("prev-month", _, socket) do
# cursor_date = Timex.shift(socket.assigns.cursor_date, months: -1)
# {first, last, rows} = week_rows(cursor_date, socket.assigns.current_user.week_start_at)
# assigns = [
# cursor_date: cursor_date,
# week_rows: week_rows(cursor_date, socket.assigns.current_user.week_start_at)
# ]
# {:noreply, assign(socket, assigns)}
# end
# @impl true
# def handle_event("next-month", _, socket) do
# cursor_date = Timex.shift(socket.assigns.cursor_date, months: 1)
# {first, last, rows} = week_rows(cursor_date, socket.assigns.current_user.week_start_at)
# assigns = [
# cursor_date: cursor_date,
# week_rows: rows,
# day_first: first,
# day_last: last
# ]
# {:noreply, assign(socket, assigns)}
# end
@impl true @impl true
def handle_event("toggle-template-details", %{"target_id" => target_id}, socket) do def handle_event("toggle-template-details", %{"target_id" => target_id}, socket) do
socket socket
@ -274,4 +252,55 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
def handle_event("clear-days", _params, socket) do def handle_event("clear-days", _params, socket) do
{:noreply, assign(socket, :selected_days, [])} {:noreply, assign(socket, :selected_days, [])}
end end
@impl true
def handle_event("save-days", _params, socket) do
# 1. collect attrs from loaded shift template
shift_data = shift_data_from_template(socket.assigns.shift_template)
# 2. create list of shift attrs to insert
to_insert = Enum.map(socket.assigns.selected_days, &shift_from_day_and_shift_data(&1, shift_data))
# 3. insert the data
{status, msg} = insert_shifts(to_insert, length(socket.assigns.selected_days))
socket
|> put_flash(status, msg)
|> assign(:selected_days, [])
|> assign_known_shifts()
|> live_noreply()
end
defp shift_data_from_template(shift_template) do
shift_template
|> Map.from_struct()
|> Map.drop([:__meta__, :id, :inserted_at, :updated_at, :user])
end
defp shift_from_day_and_shift_data(day, shift_data) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
date_data = %{date: Date.from_iso8601!(day), inserted_at: now, updated_at: now}
Map.merge(shift_data, date_data)
end
defp insert_shifts(to_insert, day_count) do
Multi.new()
|> Multi.insert_all(:insert_all, Shift, to_insert)
|> Repo.transaction()
|> case do
{:ok, %{insert_all: {n, _}}} ->
if n == day_count do
{:success, "Successfully assigned shift to #{n} day(s)"}
else
{:warning, "Some error, only #{n} day(s) inserted but #{day_count} were selected"}
end
_ -> {:error, "Ope, unknown error inserting shifts, page the dev"}
end
end
def shifts_to_show(day_shifts) do
if length(day_shifts) == 1 || length(day_shifts) > 2,
do: Enum.take(day_shifts, 1),
else: day_shifts
end
end end

View file

@ -4,7 +4,7 @@
</h2> </h2>
<div class="row justify-content-center mt-4"> <div class="row justify-content-center mt-4">
<div class="col-12 col-lg-9 col-xl-8 col-xxl-7 d-flex justify-content-start align-items-end"> <div class="col-12 col-lg-9 col-xl-8 col-xxl-7 d-flex justify-content-center align-items-end">
<%= form_for :template_select, "#", [phx_change: "change-selected-template"], fn sts -> %> <%= form_for :template_select, "#", [phx_change: "change-selected-template"], fn sts -> %>
<%= label sts, :template, "Select shift template to assign to dates", class: "form-label" %> <%= label sts, :template, "Select shift template to assign to dates", class: "form-label" %>
@ -15,8 +15,8 @@
<% end %> <% 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"> <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", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, (@show_template_btn_active && "bi-binoculars-fill" || "bi-binoculars"), [class: "icon baseline"] %>
Details <span class="d-none d-sm-inline">Details</span>
</button> </button>
</div> </div>
@ -137,8 +137,6 @@
</div> </div>
<button type="submit" class="d-hidden"></button>
<% end %> <% end %>
</div> </div>
@ -167,15 +165,15 @@
</h3> </h3>
<div> <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(Timex.today(), :month), do: "disabled" %>> <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(Timex.today(), :month), do: "disabled" %>>
<%= icon_div @socket, "bi-asterisk", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-asterisk", [class: "icon baseline"] %>
Today <span class="d-none d-sm-inline">Today</span>
</button> </button>
<button type="button" phx-click="month-nav" phx-value-month="prev" class="btn btn-primary"> <button type="button" phx-click="month-nav" phx-value-month="prev" class="btn btn-primary">
<%= icon_div @socket, "bi-chevron-left", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-chevron-left", [class: "icon baseline"] %>
Prev <span class="d-none d-sm-inline">Prev</span>
</button> </button>
<button type="button" phx-click="month-nav" phx-value-month="next" class="btn btn-primary"> <button type="button" phx-click="month-nav" phx-value-month="next" class="btn btn-primary">
Next <span class="d-none d-sm-inline">Next</span>
<%= icon_div @socket, "bi-chevron-right", [class: "icon baseline", style: "margin-left:0.125rem;"] %> <%= icon_div @socket, "bi-chevron-right", [class: "icon baseline", style: "margin-left:0.125rem;"] %>
</button> </button>
</div> </div>
@ -196,9 +194,27 @@
<%= for week <- @week_rows do %> <%= for week <- @week_rows do %>
<tr> <tr>
<%= for day <- week do %> <%= for day <- week do %>
<%# day |> NaiveDateTime.to_date() |> IO.inspect() %>
<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 %>">
<%= Timex.format!(day, "{0D}") %><%= if day.month != @cursor_date.month, do: "-#{Timex.format!(day, "{Mshort}")}" %> <%= Timex.format!(day, "{0D}") %><%= if day.month != @cursor_date.month, do: "-#{Timex.format!(day, "{Mshort}")}" %>
<% day_shifts = Enum.filter(@known_shifts, fn s -> s.date == day end) %>
<% shifts_to_show = shifts_to_show(day_shifts) %>
<%= for shift <- shifts_to_show do %>
<span class="badge bg-primary text-start d-block">
<span>
<%= shift.time_start |> Timex.format!("{h12}:{m}{am}") |> String.trim_trailing("m") %>
<%= shift.subject %>
</span>
</span>
<% end %>
<%= if length(day_shifts) > 2 do %>
<span class="badge bg-primary text-start d-block"><span><%= length(day_shifts) - 1 %> more&hellip;</span></span>
<% end %>
</td> </td>
<% end %> <% end %>
</tr> </tr>
@ -207,16 +223,16 @@
</table> </table>
<div class="row justify-content-end mt-4"> <div class="row justify-content-end my-4">
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-warning" phx-click="clear-days"> <button class="btn btn-outline-dark" phx-click="clear-days">
<%= icon_div @socket, "bi-eraser", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-eraser", [class: "icon baseline"] %>
Clear Clear
</button> </button>
<button class="btn btn-primary" phx-click="save-days"> <button class="btn btn-primary" phx-click="save-days" <%= if (!@shift_template_changeset.valid? || Enum.empty?(@selected_days)), do: "disabled" %>>
<%= icon_div @socket, "bi-save", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-save", [class: "icon baseline"] %>
Save assigned shifts Save assigned shifts
</button> </button>

View file

@ -25,7 +25,7 @@
My Shift Templates My Shift Templates
</h2> </h2>
<%= live_patch to: Routes.shift_template_index_path(@socket, :new), class: "btn btn-primary" do %> <%= live_patch to: Routes.shift_template_index_path(@socket, :new), class: "btn btn-primary" do %>
<%= icon_div @socket, "bi-plus-circle-dotted", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-plus-circle-dotted", [class: "icon baseline"] %>
New Shift Template New Shift Template
<% end %> <% end %>
</div> </div>
@ -100,14 +100,14 @@
<%= if Roles.can?(@current_user, template, :edit) do %> <%= if Roles.can?(@current_user, template, :edit) do %>
<%= live_patch to: Routes.shift_template_index_path(@socket, :edit, template), class: "btn btn-primary btn-sm text-nowrap" do %> <%= live_patch to: Routes.shift_template_index_path(@socket, :edit, template), class: "btn btn-primary btn-sm text-nowrap" do %>
<%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-pencil", [class: "icon baseline"] %>
Edit Edit
<% end %> <% end %>
<% end %> <% end %>
<%= if Roles.can?(@current_user, template, :clone) do %> <%= if Roles.can?(@current_user, template, :clone) do %>
<%= live_patch to: Routes.shift_template_index_path(@socket, :clone, template), class: "btn btn-outline-primary btn-sm text-nowrap" do %> <%= live_patch to: Routes.shift_template_index_path(@socket, :clone, template), class: "btn btn-outline-primary btn-sm text-nowrap" do %>
<%= icon_div @socket, "bi-clipboard-plus", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-clipboard-plus", [class: "icon baseline"] %>
Clone Clone
<% end %> <% end %>
<% end %> <% end %>
@ -116,7 +116,7 @@
<%= if Roles.can?(@current_user, template, :delete) do %> <%= if Roles.can?(@current_user, template, :delete) do %>
<button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= template.id %>"> <button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= template.id %>">
<%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-trash", [class: "icon baseline"] %>
Delete Delete
</button> </button>
<% end %> <% end %>

View file

@ -98,14 +98,14 @@
<%= if Roles.can?(@current_user, user, :edit) do %> <%= if Roles.can?(@current_user, user, :edit) do %>
<%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-primary btn-sm text-nowrap" do %> <%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-primary btn-sm text-nowrap" do %>
<%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-pencil", [class: "icon baseline"] %>
Edit Edit
<% end %> <% end %>
<% end %> <% end %>
<%= if Roles.can?(@current_user, user, :delete) do %> <%= if Roles.can?(@current_user, user, :delete) do %>
<button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>"> <button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>">
<%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-trash", [class: "icon baseline"] %>
Delete Delete
</button> </button>
<% end %> <% end %>
@ -180,14 +180,14 @@
<%= if Roles.can?(@current_user, user, :edit) do %> <%= if Roles.can?(@current_user, user, :edit) do %>
<%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm text-nowrap" do %> <%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm text-nowrap" do %>
<%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-pencil", [class: "icon baseline"] %>
Edit Edit
<% end %> <% end %>
<% end %> <% end %>
<%= if Roles.can?(@current_user, user, :delete) do %> <%= if Roles.can?(@current_user, user, :delete) do %>
<button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>"> <button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>">
<%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %> <%= icon_div @socket, "bi-trash", [class: "icon baseline"] %>
Delete Delete
</button> </button>
<% end %> <% end %>

View file

@ -16,7 +16,7 @@
</button> </button>
<% else %> <% else %>
<%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-block d-lg-none") 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;"] %> <%= icon_div @conn, "bi-door-open", [class: "icon baseline"] %>
Log in Log in
<% end %> <% end %>
<% end %> <% end %>
@ -70,7 +70,7 @@
<%= if !@current_user do %> <%= 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 %> <%= 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;"] %> <%= icon_div @conn, "bi-door-open", [class: "icon baseline"] %>
Log in Log in
<% end %> <% end %>
<% end %> <% end %>