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
import "../node_modules/bootstrap-icons/icons/calendar2.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-range.svg";
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/save.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
// entry points. Those entry points can be configured

View file

@ -1,7 +1,8 @@
defmodule Shift73k.EctoEnums do
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)
end

View file

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

View file

@ -29,7 +29,7 @@ defmodule Shift73kWeb.ShiftAssignLive.DeleteComponent do
def handle_event("confirm-delete-days-shifts", _params, socket) do
user = socket.assigns.current_user
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") || ""
flash = {:info, "Successfully deleted #{n} assigned shift#{s}"}
send(self(), {:put_flash_message, flash})

View file

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

View file

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

View file

@ -1,17 +1,44 @@
defmodule Shift73kWeb.ShiftLive.Index do
use Shift73kWeb, :live_view
use Timex
alias Shift73k.Shifts
alias Shift73k.Shifts.Shift
alias Shift73kWeb.Roles
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, :shifts, list_shifts())}
def mount(_params, session, socket) do
socket
|> assign_defaults(session)
|> live_okreply()
# {:ok, assign(socket, :shifts, list_shifts())}
end
@impl true
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
defp apply_action(socket, :edit, %{"id" => id}) do
@ -32,15 +59,57 @@ defmodule Shift73kWeb.ShiftLive.Index do
|> assign(:shift, nil)
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
def handle_event("delete", %{"id" => id}, socket) do
shift = Shifts.get_shift!(id)
{:ok, _} = Shifts.delete_shift(shift)
{:noreply, assign(socket, :shifts, list_shifts())}
{:noreply, assign_known_shifts(socket)}
end
defp list_shifts do
Shifts.list_shifts()
@impl true
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

View file

@ -1,5 +1,3 @@
<h1>Listing Shifts</h1>
<%= if @live_action in [:new, :edit] do %>
<%= live_modal @socket, Shift73kWeb.ShiftLive.FormComponent,
id: @shift.id || :new,
@ -9,41 +7,87 @@
return_to: Routes.shift_index_path(@socket, :index) %>
<% 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>
</tr>
</thead>
<tbody id="shifts">
<%= for shift <- @shifts do %>
<tr id="shift-<%= shift.id %>">
<td><%= shift.subject %></td>
<td><%= shift.location %></td>
<td><%= shift.description %></td>
<td><%= shift.time_zone %></td>
<td><%= shift.date %></td>
<td><%= shift.time_zone %></td>
<td><%= shift.time_start %></td>
<td><%= shift.time_end %></td>
<div class="row justify-content-start justify-content-sm-center">
<div class="col-md-10 col-xl-10">
<h2 class="mb-3 mb-sm-0">
<%= icon_div @socket, "bi-calendar2-date", [class: "icon baseline"] %>
My Shifts
</h2>
<%# month navigation %>
<div class="d-flex justify-content-between align-items-end mt-4">
<h3 class="text-muted mb-0">
<%= Timex.format!(@cursor_date, "{Mfull} {YYYY}") %>
</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(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>
</div>
</div>

View file

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

View file

@ -5,6 +5,7 @@ defmodule Shift73kWeb.Roles do
alias Shift73k.Accounts.User
alias Shift73k.Shifts.Templates.ShiftTemplate
alias Shift73k.Shifts.Shift
@type entity :: struct()
@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{}, %ShiftTemplate{}, :index), 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}, :clone), 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
def can?(%User{role: :admin}, %User{}, _any), do: true
def can?(%User{role: :manager}, %User{}, :index), do: true

View file

@ -10,7 +10,13 @@
<li>
<%= 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"] %>
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 %>
</li>
<li>

View file

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