basic shift display working

This commit is contained in:
Adam Piontek 2021-03-20 12:45:50 -04:00
parent 3cb8dcef18
commit 8a1b7a4852
12 changed files with 233 additions and 108 deletions

View file

@ -36,6 +36,7 @@ import "../node_modules/bootstrap-icons/icons/people.svg"; // users management
// calendar/event icons // calendar/event icons
import "../node_modules/bootstrap-icons/icons/calendar2.svg"; import "../node_modules/bootstrap-icons/icons/calendar2.svg";
import "../node_modules/bootstrap-icons/icons/calendar2-plus.svg"; import "../node_modules/bootstrap-icons/icons/calendar2-plus.svg";
import "../node_modules/bootstrap-icons/icons/calendar2-date.svg";
import "../node_modules/bootstrap-icons/icons/calendar2-event.svg"; import "../node_modules/bootstrap-icons/icons/calendar2-event.svg";
import "../node_modules/bootstrap-icons/icons/calendar2-range.svg"; import "../node_modules/bootstrap-icons/icons/calendar2-range.svg";
import "../node_modules/bootstrap-icons/icons/clock-history.svg"; // shift template import "../node_modules/bootstrap-icons/icons/clock-history.svg"; // shift template
@ -53,6 +54,7 @@ import "../node_modules/bootstrap-icons/icons/binoculars-fill.svg";
import "../node_modules/bootstrap-icons/icons/eraser.svg"; import "../node_modules/bootstrap-icons/icons/eraser.svg";
import "../node_modules/bootstrap-icons/icons/save.svg"; import "../node_modules/bootstrap-icons/icons/save.svg";
import "../node_modules/bootstrap-icons/icons/asterisk.svg"; import "../node_modules/bootstrap-icons/icons/asterisk.svg";
import "../node_modules/bootstrap-icons/icons/card-list.svg";
// webpack automatically bundles all modules in your // webpack automatically bundles all modules in your
// entry points. Those entry points can be configured // entry points. Those entry points can be configured

View file

@ -1,7 +1,8 @@
defmodule Shift73k.EctoEnums do defmodule Shift73k.EctoEnums do
import EctoEnum import EctoEnum
@weekdays [:mon, :tue, :wed, :thu, :fri, :sat, :sun] |> Enum.with_index(1) @weekdays [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
|> Enum.with_index(1)
defenum(WeekdayEnum, @weekdays) defenum(WeekdayEnum, @weekdays)
end end

View file

@ -21,26 +21,26 @@ 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 defp query_shifts_by_user(user_id) do
from(s in Shift) from(s in Shift)
|> select([s], %{ |> where([s], s.user_id == ^user_id)
date: s.date, end
subject: s.subject,
time_start: s.time_start, def list_shifts_by_user_in_date_range(user_id, %Date.Range{} = date_range) do
time_end: s.time_end query_shifts_by_user(user_id)
}) |> where([s], s.date >= ^date_range.first)
|> where([s], s.user_id == ^user_id and s.date >= ^start_date and s.date <= ^end_date) |> where([s], s.date <= ^date_range.last)
|> order_by([s], [s.date, s.time_start]) |> order_by([s], [s.date, s.time_start])
|> Repo.all() |> Repo.all()
end end
defp query_shifts_by_user_on_list_of_dates(user_id, date_list) do defp query_shifts_by_user_from_list_of_dates(user_id, date_list) do
from(s in Shift) query_shifts_by_user(user_id)
|> where([s], s.user_id == ^user_id and s.date in ^date_list) |> where([s], s.date in ^date_list)
end end
def list_shifts_by_user_on_list_of_dates(user_id, date_list) do def list_shifts_by_user_from_list_of_dates(user_id, date_list) do
query_shifts_by_user_on_list_of_dates(user_id, date_list) query_shifts_by_user_from_list_of_dates(user_id, date_list)
|> Repo.all() |> Repo.all()
end end
@ -112,8 +112,8 @@ defmodule Shift73k.Shifts do
Repo.delete(shift) Repo.delete(shift)
end end
def delete_shifts_by_user_on_list_of_dates(user_id, date_list) do def delete_shifts_by_user_from_list_of_dates(user_id, date_list) do
query_shifts_by_user_on_list_of_dates(user_id, date_list) query_shifts_by_user_from_list_of_dates(user_id, date_list)
|> Repo.delete_all() |> Repo.delete_all()
end end

View file

@ -29,7 +29,7 @@ defmodule Shift73kWeb.ShiftAssignLive.DeleteComponent do
def handle_event("confirm-delete-days-shifts", _params, socket) do def handle_event("confirm-delete-days-shifts", _params, socket) do
user = socket.assigns.current_user user = socket.assigns.current_user
date_list = socket.assigns.date_list date_list = socket.assigns.date_list
{n, _} = Shifts.delete_shifts_by_user_on_list_of_dates(user.id, date_list) {n, _} = Shifts.delete_shifts_by_user_from_list_of_dates(user.id, date_list)
s = (n > 1 && "s") || "" s = (n > 1 && "s") || ""
flash = {:info, "Successfully deleted #{n} assigned shift#{s}"} flash = {:info, "Successfully deleted #{n} assigned shift#{s}"}
send(self(), {:put_flash_message, flash}) send(self(), {:put_flash_message, flash})

View file

@ -27,6 +27,8 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
@impl true @impl true
def handle_params(_params, _url, socket) do def handle_params(_params, _url, socket) do
user = socket.assigns.current_user
socket socket
|> init_shift_templates() |> init_shift_templates()
|> init_shift_template() |> init_shift_template()
@ -34,9 +36,9 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> assign_shift_length() |> assign_shift_length()
|> assign_shift_template_changeset() |> assign_shift_template_changeset()
|> assign_modal_close_handlers() |> assign_modal_close_handlers()
|> assign(:day_names, day_names(user.week_start_at))
|> init_today(Timex.today()) |> init_today(Timex.today())
|> init_calendar() |> update_calendar()
|> assign_known_shifts()
|> live_noreply() |> live_noreply()
end end
@ -52,20 +54,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 assign_known_shifts(socket) do defp assign_known_shifts(%{assigns: %{current_user: user}} = socket) do
user = socket.assigns.current_user date_range = socket.assigns.date_range
first = socket.assigns.day_first known_shifts = Shifts.list_shifts_by_user_in_date_range(user.id, date_range)
last = socket.assigns.day_last
known_shifts = Shifts.list_shifts_by_user_between_dates(user.id, first, last)
assign(socket, :known_shifts, known_shifts) assign(socket, :known_shifts, known_shifts)
end end
defp init_calendar(%{assigns: %{current_user: user}} = socket) do
days = day_names(user.week_start_at)
{first, last, rows} = week_rows(socket.assigns.cursor_date, user.week_start_at)
assign(socket, day_names: days, week_rows: rows, day_first: first, day_last: last)
end
defp init_today(socket, today) do defp init_today(socket, today) do
assign(socket, current_date: today, cursor_date: today) assign(socket, current_date: today, cursor_date: today)
end end
@ -88,7 +82,7 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
defp init_shift_templates(%{assigns: %{current_user: user}} = socket) do defp init_shift_templates(%{assigns: %{current_user: user}} = socket) do
shift_templates = shift_templates =
Templates.list_shift_templates_by_user_id(user.id) Templates.list_shift_templates_by_user_id(user.id)
|> Enum.map(fn t -> shift_template_option(t, user.fave_shift_template_id) end) |> Stream.map(fn t -> shift_template_option(t, user.fave_shift_template_id) end)
|> Enum.concat([@custom_shift_opt]) |> Enum.concat([@custom_shift_opt])
assign(socket, :shift_templates, shift_templates) assign(socket, :shift_templates, shift_templates)
@ -123,23 +117,31 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> Enum.map(&Timex.day_shortname/1) |> Enum.map(&Timex.day_shortname/1)
end end
defp week_rows(cursor_date, week_start_at) do defp date_range(cursor_date, week_start_at) do
first =
cursor_date
|> Timex.beginning_of_month()
|> Timex.beginning_of_week(week_start_at)
last = last =
cursor_date cursor_date
|> Timex.end_of_month() |> Timex.end_of_month()
|> Timex.end_of_week(week_start_at) |> Timex.end_of_week(week_start_at)
week_rows = cursor_date
Interval.new(from: first, until: last, right_open: false) |> Timex.beginning_of_month()
|> Enum.map(&NaiveDateTime.to_date(&1)) |> Timex.beginning_of_week(week_start_at)
|> Enum.chunk_every(7) |> Date.range(last)
end
{first, last, week_rows} defp assign_date_range(%{assigns: %{current_user: user}} = socket) do
date_range = date_range(socket.assigns.cursor_date, user.week_start_at)
assign(socket, :date_range, date_range)
end
defp week_rows(%Date.Range{} = date_range) do
Interval.new(from: date_range.first, until: date_range.last, right_open: false)
|> Stream.map(&NaiveDateTime.to_date(&1))
|> Enum.chunk_every(7)
end
defp assign_week_rows(%{assigns: %{date_range: date_range}} = socket) do
assign(socket, :week_rows, week_rows(date_range))
end end
def day_color(day, current_date, cursor_date, selected_days) do def day_color(day, current_date, cursor_date, selected_days) do
@ -180,17 +182,11 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
end end
end end
defp set_month(socket, new_cursor_date) do defp update_calendar(socket) do
{first, last, rows} = week_rows(new_cursor_date, socket.assigns.current_user.week_start_at) socket
|> assign_date_range()
assigns = [ |> assign_week_rows()
cursor_date: new_cursor_date, |> assign_known_shifts()
week_rows: rows,
day_first: first,
day_last: last
]
assign(socket, assigns) |> assign_known_shifts()
end end
@impl true @impl true
@ -236,7 +232,10 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
Timex.shift(socket.assigns.cursor_date, months: months) Timex.shift(socket.assigns.cursor_date, months: months)
end end
{:noreply, set_month(socket, new_cursor)} socket
|> assign(:cursor_date, new_cursor)
|> update_calendar()
|> live_noreply()
end end
@impl true @impl true
@ -260,9 +259,9 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
@impl true @impl true
def handle_event("select-day", %{"day" => day}, socket) do def handle_event("select-day", %{"day" => day}, socket) do
selected_days = selected_days =
case day_index = Enum.find_index(socket.assigns.selected_days, fn d -> d == day end) do case Enum.member?(socket.assigns.selected_days, day) do
nil -> [day | socket.assigns.selected_days] false -> [day | socket.assigns.selected_days]
_ -> List.delete_at(socket.assigns.selected_days, day_index) true -> Enum.reject(socket.assigns.selected_days, fn d -> d == day end)
end end
{:noreply, assign(socket, :selected_days, selected_days)} {:noreply, assign(socket, :selected_days, selected_days)}

View file

@ -10,7 +10,7 @@
<h2 class="mb-3 mb-sm-0"> <h2 class="mb-3 mb-sm-0">
<%= icon_div @socket, "bi-calendar2-plus", [class: "icon baseline"] %> <%= icon_div @socket, "bi-calendar2-plus", [class: "icon baseline"] %>
Assign Shift To Dates Schedule Shifts
</h2> </h2>
<div class="row justify-content-center mt-4"> <div class="row justify-content-center mt-4">

View file

@ -1,17 +1,44 @@
defmodule Shift73kWeb.ShiftLive.Index do defmodule Shift73kWeb.ShiftLive.Index do
use Shift73kWeb, :live_view use Shift73kWeb, :live_view
use Timex
alias Shift73k.Shifts alias Shift73k.Shifts
alias Shift73k.Shifts.Shift alias Shift73k.Shifts.Shift
alias Shift73kWeb.Roles
@impl true @impl true
def mount(_params, _session, socket) do def mount(_params, session, socket) do
{:ok, assign(socket, :shifts, list_shifts())} socket
|> assign_defaults(session)
|> live_okreply()
# {:ok, assign(socket, :shifts, list_shifts())}
end end
@impl true @impl true
def handle_params(params, _url, socket) do def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)} current_user = socket.assigns.current_user
live_action = socket.assigns.live_action
shift = shift_from_params(params)
if Roles.can?(current_user, shift, live_action) do
socket
# |> assign_shift_templates()
# |> assign_modal_close_handlers()
|> init_today(Timex.today())
|> assign_date_range()
|> assign_known_shifts()
|> assign(:delete_shift, nil)
|> apply_action(socket.assigns.live_action, params)
|> live_noreply()
else
socket
|> put_flash(:error, "Unauthorised")
|> redirect(to: "/")
|> live_noreply()
end
# {:noreply, apply_action(socket, socket.assigns.live_action, params)}
end end
defp apply_action(socket, :edit, %{"id" => id}) do defp apply_action(socket, :edit, %{"id" => id}) do
@ -32,15 +59,57 @@ defmodule Shift73kWeb.ShiftLive.Index do
|> assign(:shift, nil) |> assign(:shift, nil)
end end
defp shift_from_params(params)
defp shift_from_params(%{"id" => id}),
do: Shifts.get_shift!(id)
defp shift_from_params(_params), do: %Shift{}
defp init_today(socket, today) do
assign(socket, current_date: today, cursor_date: today)
end
defp assign_date_range(%{assigns: %{cursor_date: cursor_date}} = socket) do
assign(socket, date_range: date_range(cursor_date))
end
defp date_range(cursor_date) do
cursor_date
|> Timex.beginning_of_month()
|> Date.range(Timex.end_of_month(cursor_date))
end
defp assign_known_shifts(socket) do
user = socket.assigns.current_user
shifts = Shifts.list_shifts_by_user_in_date_range(user.id, socket.assigns.date_range)
assign(socket, :shifts, shifts)
end
@impl true @impl true
def handle_event("delete", %{"id" => id}, socket) do def handle_event("delete", %{"id" => id}, socket) do
shift = Shifts.get_shift!(id) shift = Shifts.get_shift!(id)
{:ok, _} = Shifts.delete_shift(shift) {:ok, _} = Shifts.delete_shift(shift)
{:noreply, assign(socket, :shifts, list_shifts())} {:noreply, assign_known_shifts(socket)}
end end
defp list_shifts do @impl true
Shifts.list_shifts() def handle_event("month-nav", %{"month" => direction}, socket) do
new_cursor =
cond do
direction == "now" ->
Timex.today()
true ->
months = (direction == "prev" && -1) || 1
Timex.shift(socket.assigns.cursor_date, months: months)
end
socket
|> assign(:cursor_date, new_cursor)
|> assign_date_range()
|> assign_known_shifts()
|> live_noreply()
end end
end end

View file

@ -1,5 +1,3 @@
<h1>Listing Shifts</h1>
<%= if @live_action in [:new, :edit] do %> <%= if @live_action in [:new, :edit] do %>
<%= live_modal @socket, Shift73kWeb.ShiftLive.FormComponent, <%= live_modal @socket, Shift73kWeb.ShiftLive.FormComponent,
id: @shift.id || :new, id: @shift.id || :new,
@ -9,41 +7,87 @@
return_to: Routes.shift_index_path(@socket, :index) %> return_to: Routes.shift_index_path(@socket, :index) %>
<% end %> <% end %>
<table>
<thead>
<tr>
<th>Subject</th>
<th>Location</th>
<th>Description</th>
<th>Time zone</th>
<th>Date</th>
<th>Time zone</th>
<th>Time start</th>
<th>Time end</th>
<th></th> <div class="row justify-content-start justify-content-sm-center">
</tr> <div class="col-md-10 col-xl-10">
</thead>
<tbody id="shifts"> <h2 class="mb-3 mb-sm-0">
<%= for shift <- @shifts do %> <%= icon_div @socket, "bi-calendar2-date", [class: "icon baseline"] %>
<tr id="shift-<%= shift.id %>"> My Shifts
<td><%= shift.subject %></td> </h2>
<td><%= shift.location %></td>
<td><%= shift.description %></td> <%# month navigation %>
<td><%= shift.time_zone %></td> <div class="d-flex justify-content-between align-items-end mt-4">
<td><%= shift.date %></td> <h3 class="text-muted mb-0">
<td><%= shift.time_zone %></td> <%= Timex.format!(@cursor_date, "{Mfull} {YYYY}") %>
<td><%= shift.time_start %></td> </h3>
<td><%= shift.time_end %></td> <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" %>>
<%= icon_div @socket, "bi-asterisk", [class: "icon baseline"] %>
<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"] %>
<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;"] %>
</button>
</div>
</div>
<dl>
<%= for day <- Enum.to_list(@date_range) do %>
<% Date.day_of_week(day, @current_user.week_start_at) |> IO.inspect(label: "day in date_range") %>
<%= if Date.day_of_week(day, @current_user.week_start_at) == 1 do %>
<div class="border-top mt-4 mb-4"></div>
<% end %>
<dt>
<%= Timex.format!(day, "{WDfull}, {Mshort} {D}") %>
</dt>
<% day_shifts = Enum.filter(@shifts, fn s -> s.date == day end) %>
<%= if !Enum.empty?(day_shifts) do %>
<dd id="day-<%= day.day %>">
<%= for shift <- day_shifts do %>
<div class="row gx-2" id="shift-<%= shift.id %>">
<div class="col-4 col-md-3 col-lg-2 text-end">
<div>
<%= format_shift_time(shift.time_start) |> String.trim_trailing("m") %>
&mdash;
<%= format_shift_time(shift.time_end) |> String.trim_trailing("m") %>
</div>
<div style="font-size: smaller;"><%= shift.time_zone %></div>
</div>
<div class="col-8 col-md-9 col-lg-10">
<div>
<%= shift.subject %>
<%= if shift.location do %>
<span class="text-muted">(<%= shift.location %>)</span>
<% end %>
</div>
<div style="font-size: smaller;"><%= shift.description %></div>
<div style="font-size: smaller;">
<span><%= live_redirect "Show", to: Routes.shift_show_path(@socket, :show, shift) %></span>
<span><%= live_patch "Edit", to: Routes.shift_index_path(@socket, :edit, shift) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: shift.id, data: [confirm: "Are you sure?"] %></span>
</div>
</div>
</div>
<% end %>
</dd>
<% else %>
<dd><em>No shift scheduled</em></dd>
<% end %>
<% end %>
</dl>
<td>
<span><%= live_redirect "Show", to: Routes.shift_show_path(@socket, :show, shift) %></span>
<span><%= live_patch "Edit", to: Routes.shift_index_path(@socket, :edit, shift) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: shift.id, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= live_patch "New Shift", to: Routes.shift_index_path(@socket, :new) %></span> <span><%= live_patch "New Shift", to: Routes.shift_index_path(@socket, :new) %></span>
</div>
</div>

View file

@ -26,11 +26,6 @@
<%= @shift.description %> <%= @shift.description %>
</li> </li>
<li>
<strong>Time zone:</strong>
<%= @shift.time_zone %>
</li>
<li> <li>
<strong>Date:</strong> <strong>Date:</strong>
<%= @shift.date %> <%= @shift.date %>

View file

@ -5,6 +5,7 @@ defmodule Shift73kWeb.Roles do
alias Shift73k.Accounts.User alias Shift73k.Accounts.User
alias Shift73k.Shifts.Templates.ShiftTemplate alias Shift73k.Shifts.Templates.ShiftTemplate
alias Shift73k.Shifts.Shift
@type entity :: struct() @type entity :: struct()
@type action :: :new | :index | :edit | :show | :delete | :edit_role @type action :: :new | :index | :edit | :show | :delete | :edit_role
@ -16,11 +17,19 @@ defmodule Shift73kWeb.Roles do
def can?(%User{role: :admin}, %ShiftTemplate{}, _any), do: true def can?(%User{role: :admin}, %ShiftTemplate{}, _any), do: true
def can?(%User{}, %ShiftTemplate{}, :index), do: true def can?(%User{}, %ShiftTemplate{}, :index), do: true
def can?(%User{}, %ShiftTemplate{}, :new), do: true def can?(%User{}, %ShiftTemplate{}, :new), do: true
# def can?(%User{}, %ShiftTemplate{}, :show), do: true # def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :show), do: true
def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :edit), do: true def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :edit), do: true
def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :clone), do: true def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :clone), do: true
def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :delete), do: true def can?(%User{id: id}, %ShiftTemplate{user_id: id}, :delete), do: true
# Shifts / Shift
def can?(%User{role: :admin}, %Shift{}, _any), do: true
def can?(%User{}, %Shift{}, :index), do: true
def can?(%User{}, %Shift{}, :new), do: true
def can?(%User{id: id}, %Shift{user_id: id}, :show), do: true
def can?(%User{id: id}, %Shift{user_id: id}, :edit), do: true
def can?(%User{id: id}, %Shift{user_id: id}, :delete), do: true
# Accounts / User # Accounts / User
def can?(%User{role: :admin}, %User{}, _any), do: true def can?(%User{role: :admin}, %User{}, _any), do: true
def can?(%User{role: :manager}, %User{}, :index), do: true def can?(%User{role: :manager}, %User{}, :index), do: true

View file

@ -10,7 +10,13 @@
<li> <li>
<%= link nav_link_opts(@conn, to: Routes.shift_assign_index_path(@conn, :index), class: "dropdown-item") do %> <%= link nav_link_opts(@conn, to: Routes.shift_assign_index_path(@conn, :index), class: "dropdown-item") do %>
<%= icon_div @conn, "bi-calendar2-plus", [class: "icon baseline me-1"] %> <%= icon_div @conn, "bi-calendar2-plus", [class: "icon baseline me-1"] %>
Assign Shift To Dates Schedule Shifts
<% end %>
</li>
<li>
<%= link nav_link_opts(@conn, to: Routes.shift_index_path(@conn, :index), class: "dropdown-item") do %>
<%= icon_div @conn, "bi-card-list", [class: "icon baseline me-1"] %>
My Scheduled Shifts
<% end %> <% end %>
</li> </li>
<li> <li>

View file

@ -58,7 +58,7 @@ extra_mock_users = ~s([
mock_users = mock_users =
extra_mock_users extra_mock_users
|> Jason.decode!() |> Jason.decode!()
|> Enum.concat(mock_users) |> Stream.concat(mock_users)
|> Enum.map(fn e -> |> Enum.map(fn e ->
add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"]) add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"])