much progress on shift assigning & app navigation

This commit is contained in:
Adam Piontek 2021-03-19 16:38:52 -04:00
parent 4541070f75
commit 8957f2d1dd
33 changed files with 363 additions and 330 deletions

View File

@ -22,13 +22,26 @@ defmodule Shift73k.Shifts do
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)
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)
|> 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)
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)
|> Repo.all()
end
@doc """
@ -99,6 +112,11 @@ 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)
|> Repo.delete_all()
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking shift changes.

View File

@ -22,7 +22,11 @@ defmodule Shift73k.Shifts.Templates do
end
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, s.time_start]
q =
from s in ShiftTemplate,
where: s.user_id == ^user_id,
order_by: [fragment("lower(?)", s.subject), s.time_start]
Repo.all(q)
end

View File

@ -19,6 +19,7 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
field :time_end, :time, default: ~T[17:00:00]
belongs_to(:user, Shift73k.Accounts.User)
has_one(:is_fave_of_user, Shift73k.Accounts.User, foreign_key: :fave_shift_template_id)
timestamps()
end
@ -58,7 +59,9 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
[]
end
end)
|> validate_inclusion(:time_zone, Timex.timezones(), message: "must be a valid IANA tz database time zone")
|> validate_inclusion(:time_zone, Timex.timezones(),
message: "must be a valid IANA tz database time zone"
)
end
defp time_start_from_attrs(%{"time_start" => time_start}), do: time_start

View File

@ -1,16 +0,0 @@
defmodule Shift73kWeb.OtherController do
use Shift73kWeb, :controller
def index(conn, _params) do
conn
|> put_flash(:success, "Log in was a success. Good for you.")
|> put_flash(:error, "Lorem ipsum dolor sit amet consectetur adipisicing elit.")
|> put_flash(
:info,
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatibus dolore sunt quia aperiam sint id reprehenderit? Dolore incidunt alias inventore accusantium nulla optio, ducimus eius aliquam hic, pariatur voluptate distinctio."
)
|> put_flash(:warning, "Oh no, there's nothing to worry about!")
|> put_flash(:primary, "Something in the brand color.")
|> render("index.html")
end
end

View File

@ -16,10 +16,10 @@ defmodule Shift73kWeb.ModalComponent do
<div class="modal-header">
<h5 class="modal-title"><%= Keyword.get(@opts, :title, "Modal title") %></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close" phx-click="hide" phx-target="<%= @myself %>" aria-label="Close"></button>
</div>
<%= live_component @socket, @component, @opts %>
<%= live_component @socket, @component, Keyword.put(@opts, :modal_id, @id) %>
</div>
</div>
@ -27,6 +27,11 @@ defmodule Shift73kWeb.ModalComponent do
"""
end
@impl true
def update(assigns, socket) do
socket |> assign(assigns) |> live_okreply()
end
@impl true
def handle_event("close", _, socket) do
send(self(), {:close_modal, true})
@ -35,6 +40,6 @@ defmodule Shift73kWeb.ModalComponent do
@impl true
def handle_event("hide", _, socket) do
{:noreply, push_event(socket, "modal-please-hide", %{})}
socket |> push_event("modal-please-hide", %{}) |> live_noreply()
end
end

View File

@ -1,40 +0,0 @@
defmodule Shift73kWeb.PageLive do
use Shift73kWeb, :live_view
@impl true
def mount(_params, session, socket) do
socket = assign_defaults(socket, session)
{:ok, assign(socket, query: "", results: %{})}
end
@impl true
def handle_event("suggest", %{"q" => query}, socket) do
{:noreply, assign(socket, results: search(query), query: query)}
end
@impl true
def handle_event("search", %{"q" => query}, socket) do
case search(query) do
%{^query => vsn} ->
{:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
_ ->
{:noreply,
socket
|> put_flash(:error, "No dependencies found matching \"#{query}\"")
|> assign(results: %{}, query: query)}
end
end
defp search(query) do
if not Shift73kWeb.Endpoint.config(:code_reloader) do
raise "action disabled when not in development"
end
for {app, desc, vsn} <- Application.started_applications(),
app = to_string(app),
String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
into: %{},
do: {app, vsn}
end
end

View File

@ -1,49 +0,0 @@
<section class="phx-hero">
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
<p>Peace of mind from prototype to production</p>
<form phx-change="suggest" phx-submit="search">
<input type="text" name="q" value="<%= @query %>" placeholder="Live dependency search" list="results" autocomplete="off"/>
<datalist id="results">
<%= for {app, _vsn} <- @results do %>
<option value="<%= app %>"><%= app %></option>
<% end %>
</datalist>
<button type="submit" phx-disable-with="Searching...">Go to Hexdocs</button>
</form>
</section>
<section class="row align-items-start">
<article class="col">
<h2>Resources</h2>
<ul>
<li>
<a href="https://hexdocs.pm/phoenix/overview.html">Guides &amp; Docs</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix">Source</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md">v1.5 Changelog</a>
</li>
</ul>
</article>
<article class="col">
<h2>Help</h2>
<ul>
<li>
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
</li>
<li>
<a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
</li>
<li>
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
</li>
<li>
<a href="https://elixir-slackin.herokuapp.com/">Elixir on Slack</a>
</li>
</ul>
</article>
</section>

View File

@ -0,0 +1,42 @@
defmodule Shift73kWeb.ShiftAssignLive.DeleteComponent do
use Shift73kWeb, :live_component
alias Shift73k.Shifts
@impl true
def update(assigns, socket) do
socket
|> assign(assigns)
|> assign_dates()
|> live_okreply()
end
defp assign_dates(%{assigns: %{delete_days_shifts: daylist}} = socket) do
date_list = Enum.map(daylist, &Date.from_iso8601!/1)
year_map = Enum.group_by(date_list, fn d -> d.year end)
assign(socket, date_list: date_list, date_map: build_date_map(year_map))
end
def build_date_map(year_map) do
year_map
|> Map.keys()
|> Enum.reduce(year_map, fn y, acc ->
Map.put(acc, y, Enum.group_by(acc[y], fn d -> d.month end))
end)
end
@impl true
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)
s = (n > 1 && "s") || ""
flash = {:info, "Successfully deleted #{n} assigned shift#{s}"}
send(self(), {:put_flash_message, flash})
send(self(), {:clear_selected_days, true})
socket
|> push_event("modal-please-hide", %{})
|> live_noreply()
end
end

View File

@ -0,0 +1,31 @@
<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() |> Timex.format!("{Mshort}") %>:
<% 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>

View File

@ -18,9 +18,10 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
socket
|> assign_defaults(session)
|> assign(:custom_shift, @custom_shift)
|> assign(:show_template_btn_active, :false)
|> assign(:show_template_details, :false)
|> assign(:show_template_btn_active, false)
|> assign(:show_template_details, false)
|> assign(:selected_days, [])
|> assign(:delete_days_shifts, nil)
|> live_okreply()
end
@ -32,12 +33,18 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> show_details_if_custom()
|> assign_shift_length()
|> assign_shift_template_changeset()
|> assign_modal_close_handlers()
|> init_today(Timex.today())
|> init_calendar()
|> assign_known_shifts()
|> live_noreply()
end
defp assign_modal_close_handlers(socket) do
to = Routes.shift_assign_index_path(socket, :index)
assign(socket, modal_return_to: to, modal_close_action: :return)
end
defp get_shift_template("custom-shift"), do: @custom_shift
defp get_shift_template(template_id), do: Templates.get_shift_template(template_id)
@ -56,11 +63,11 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
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])
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])
assign(socket, current_date: today, cursor_date: today)
end
defp assign_shift_template_changeset(%{assigns: %{shift_template: shift}} = socket) do
@ -71,7 +78,7 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
defp init_shift_template(socket) do
first_list_id = socket.assigns.shift_templates |> hd() |> elem(1)
fave_id = socket.assigns.current_user.fave_shift_template_id
assign_shift_template(socket, (fave_id || first_list_id))
assign_shift_template(socket, fave_id || first_list_id)
end
defp assign_shift_template(socket, template_id) do
@ -83,14 +90,17 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
Templates.list_shift_templates_by_user_id(user.id)
|> Enum.map(fn t -> shift_template_option(t, user.fave_shift_template_id) end)
|> Enum.concat([@custom_shift_opt])
assign(socket, :shift_templates, shift_templates)
end
defp shift_template_option(template, fave_id) do
label =
template.subject <> " (" <>
format_shift_time(template.time_start) <> "" <>
format_shift_time(template.time_end) <> ")"
template.subject <>
" (" <>
format_shift_time(template.time_start) <>
"" <>
format_shift_time(template.time_end) <> ")"
label =
case fave_id == template.id do
@ -126,7 +136,7 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
week_rows =
Interval.new(from: first, until: last, right_open: false)
|> Enum.map(& NaiveDateTime.to_date(&1))
|> Enum.map(&NaiveDateTime.to_date(&1))
|> Enum.chunk_every(7)
{first, last, week_rows}
@ -141,11 +151,14 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
true -> "bg-triangle-white"
end
Timex.compare(day, current_date, :days) == 0 -> "bg-info text-white"
Timex.compare(day, current_date, :days) == 0 ->
"bg-info text-white"
day.month != cursor_date.month -> "bg-light text-gray"
day.month != cursor_date.month ->
"bg-light text-gray"
true -> ""
true ->
""
end
end
@ -157,11 +170,12 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
end
defp show_details_if_custom(socket) do
if (socket.assigns.shift_template.id != @custom_shift.id) || socket.assigns.show_template_details do
if socket.assigns.shift_template.id != @custom_shift.id ||
socket.assigns.show_template_details do
socket
else
socket
|> assign(:show_template_btn_active, :true)
|> assign(:show_template_btn_active, true)
|> push_event("toggle-template-details", %{targetId: "#templateDetailsCol"})
end
end
@ -197,7 +211,11 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
end
@impl true
def handle_event("change-selected-template", %{"template_select" => %{"template" => template_id}}, socket) do
def handle_event(
"change-selected-template",
%{"template_select" => %{"template" => template_id}},
socket
) do
socket
|> assign_shift_template(template_id)
|> show_details_if_custom()
@ -210,9 +228,11 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
def handle_event("month-nav", %{"month" => direction}, socket) do
new_cursor =
cond do
direction == "now" -> Timex.today()
direction == "now" ->
Timex.today()
true ->
months = direction == "prev" && -1 || 1
months = (direction == "prev" && -1) || 1
Timex.shift(socket.assigns.cursor_date, months: months)
end
@ -229,12 +249,12 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
@impl true
def handle_event("collapse-shown", %{"target_id" => _target_id}, socket) do
{:noreply, assign(socket, :show_template_details, :true)}
{:noreply, assign(socket, :show_template_details, true)}
end
@impl true
def handle_event("collapse-hidden", %{"target_id" => _target_id}, socket) do
{:noreply, assign(socket, :show_template_details, :false)}
{:noreply, assign(socket, :show_template_details, false)}
end
@impl true
@ -248,6 +268,14 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
{:noreply, assign(socket, :selected_days, selected_days)}
end
@impl true
def handle_event("delete-days-shifts", _params, socket) do
socket
|> assign(:modal_close_action, :delete_days_shifts)
|> assign(:delete_days_shifts, socket.assigns.selected_days)
|> live_noreply()
end
@impl true
def handle_event("clear-days", _params, socket) do
{:noreply, assign(socket, :selected_days, [])}
@ -259,7 +287,8 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
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))
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))
@ -271,10 +300,37 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> live_noreply()
end
@impl true
def handle_info({:put_flash_message, {flash_type, msg}}, socket) do
socket |> put_flash(flash_type, msg) |> live_noreply()
end
@impl true
def handle_info({:clear_selected_days, _}, socket) do
socket |> assign(:selected_days, []) |> live_noreply()
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: :return}} = socket) do
socket
|> copy_flash()
|> push_patch(to: socket.assigns.modal_return_to)
|> live_noreply()
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: assign_key}} = socket) do
socket
|> assign(assign_key, nil)
|> assign_modal_close_handlers()
|> 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])
|> Map.drop([:__meta__, :id, :inserted_at, :updated_at, :user, :is_fave_of_user])
end
defp shift_from_day_and_shift_data(day, shift_data) do
@ -289,12 +345,16 @@ defmodule Shift73kWeb.ShiftAssignLive.Index do
|> Repo.transaction()
|> case do
{:ok, %{insert_all: {n, _}}} ->
s = (n > 1 && "s") || ""
if n == day_count do
{:success, "Successfully assigned shift to #{n} day(s)"}
{:success, "Successfully assigned shift to #{n} day#{s}"}
else
{:warning, "Some error, only #{n} day(s) inserted but #{day_count} were selected"}
{:warning, "Some error, only #{n} day#{s} inserted but #{day_count} were selected"}
end
_ -> {:error, "Ope, unknown error inserting shifts, page the dev"}
_ ->
{:error, "Ope, unknown error inserting shifts, page the dev"}
end
end

View File

@ -1,3 +1,13 @@
<%= if @delete_days_shifts do %>
<%= live_modal @socket, Shift73kWeb.ShiftAssignLive.DeleteComponent,
id: "delete-days-shifts-#{@current_user.id}",
title: "Delete Shifts From Selected Days",
delete_days_shifts: @delete_days_shifts,
current_user: @current_user
%>
<% end %>
<h2 class="mb-3 mb-sm-0">
<%= icon_div @socket, "bi-calendar2-plus", [class: "icon baseline"] %>
Assign Shift To Dates
@ -223,17 +233,22 @@
</table>
<div class="row justify-content-end my-4">
<div class="col-auto">
<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-dark" phx-click="clear-days">
<%= icon_div @socket, "bi-eraser", [class: "icon baseline"] %>
Clear
<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>
<button class="btn btn-primary" phx-click="save-days" <%= if (!@shift_template_changeset.valid? || Enum.empty?(@selected_days)), do: "disabled" %>>
<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>
<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"] %>
Save assigned shifts
Assign shifts to selected days
</button>
</div>

View File

@ -11,11 +11,6 @@ defmodule Shift73kWeb.ShiftTemplateLive.DeleteComponent do
|> live_okreply()
end
@impl true
def handle_event("cancel", _, socket) do
{:noreply, push_event(socket, "modal-please-hide", %{})}
end
@impl true
def handle_event("confirm", %{"id" => id, "subject" => subject}, socket) do
id

View File

@ -8,7 +8,7 @@
</div>
<div class="modal-footer">
<%= link "Cancel", to: "#", class: "btn me-2", phx_click: "cancel", phx_target: @myself %>
<%= 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",

View File

@ -52,11 +52,6 @@ defmodule Shift73kWeb.ShiftTemplateLive.FormComponent do
)
end
@impl true
def handle_event("cancel", _, socket) do
{:noreply, push_event(socket, "modal-please-hide", %{})}
end
defp save_shift_template(socket, :new, params) do
case Templates.create_shift_template(params) do
{:ok, _shift_template} ->

View File

@ -104,7 +104,7 @@
</div>
<div class="modal-footer">
<%= link "Cancel", to: "#", class: "btn", phx_click: "cancel", phx_target: @myself %>
<%= link "Cancel", to: "#", class: "btn btn-outline-dark", phx_click: "hide", phx_target: "##{@modal_id}" %>
<%= submit "Save",
class: "btn btn-primary ",
disabled: !@changeset.valid?,

View File

@ -23,7 +23,7 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
if Roles.can?(current_user, shift_template, live_action) do
socket
|> assign_shift_templates()
|> assign(:modal_return_to, Routes.shift_template_index_path(socket, :index))
|> assign_modal_close_handlers()
|> assign(:delete_shift_template, nil)
|> apply_action(socket.assigns.live_action, params)
|> live_noreply()
@ -35,6 +35,11 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
end
end
defp assign_modal_close_handlers(socket) do
to = Routes.shift_template_index_path(socket, :index)
assign(socket, modal_return_to: to, modal_close_action: :return)
end
defp apply_action(socket, :clone, %{"id" => id}) do
socket
|> assign(:page_title, "Clone Shift Template")
@ -74,9 +79,13 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
@impl true
def handle_event("delete-modal", %{"id" => id}, socket) do
{:noreply, assign(socket, :delete_shift_template, Templates.get_shift_template!(id))}
socket
|> assign(:modal_close_action, :delete_shift_template)
|> assign(:delete_shift_template, Templates.get_shift_template!(id))
|> live_noreply()
end
@impl true
def handle_event("set-user-fave-shift-template", %{"id" => shift_template_id}, socket) do
user_id = socket.assigns.current_user.id
Accounts.set_user_fave_shift_template(user_id, shift_template_id)
@ -87,6 +96,7 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
|> live_noreply()
end
@impl true
def handle_event("unset-user-fave-shift-template", _params, socket) do
user_id = socket.assigns.current_user.id
Accounts.unset_user_fave_shift_template(user_id)
@ -98,13 +108,24 @@ defmodule Shift73kWeb.ShiftTemplateLive.Index do
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_return_to: to}} = socket) do
socket |> copy_flash() |> push_patch(to: to) |> live_noreply()
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: :return}} = socket) do
socket
|> copy_flash()
|> push_patch(to: socket.assigns.modal_return_to)
|> live_noreply()
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: assign_key}} = socket) do
socket
|> assign(assign_key, nil)
|> assign_modal_close_handlers()
|> assign_shift_templates()
|> live_noreply()
end
@impl true
def handle_info({:put_flash_message, {flash_type, msg}}, socket) do
socket |> put_flash(flash_type, msg) |> live_noreply()
end
end

View File

@ -11,8 +11,7 @@
<%= live_modal @socket, Shift73kWeb.ShiftTemplateLive.DeleteComponent,
id: @delete_shift_template.id,
title: "Delete Shift Template",
delete_shift_template: @delete_shift_template,
current_user: @current_user %>
delete_shift_template: @delete_shift_template %>
<% end %>

View File

@ -5,14 +5,7 @@ defmodule Shift73kWeb.UserManagement.DeleteComponent do
@impl true
def update(assigns, socket) do
socket
|> assign(assigns)
|> live_okreply()
end
@impl true
def handle_event("cancel", _, socket) do
{:noreply, push_event(socket, "modal-please-hide", %{})}
socket |> assign(assigns) |> live_okreply()
end
@impl true

View File

@ -5,7 +5,7 @@
</div>
<div class="modal-footer">
<%= link "Cancel", to: "#", class: "btn me-2", phx_click: "cancel", phx_target: @myself %>
<%= 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",

View File

@ -80,11 +80,6 @@ defmodule Shift73kWeb.UserManagement.FormComponent do
save_user(socket, user_params)
end
@impl true
def handle_event("cancel", _, socket) do
{:noreply, push_event(socket, "modal-please-hide", %{})}
end
def role_description(role) when is_atom(role) do
Keyword.get(User.roles(), role)
end

View File

@ -49,7 +49,7 @@
</div>
<div class="modal-footer">
<%= link "Cancel", to: "#", class: "btn", phx_click: "cancel", phx_target: @myself %>
<%= link "Cancel", to: "#", class: "btn btn-outline-dark", phx_click: "hide", phx_target: "##{@modal_id}" %>
<%= submit "Save",
class: "btn btn-primary ",
disabled: !@changeset.valid?,

View File

@ -28,9 +28,8 @@ defmodule Shift73kWeb.UserManagementLive.Index do
if Roles.can?(current_user, user, live_action) do
socket
|> assign(:query, query_map(params))
|> assign_modal_return_to()
|> assign_modal_close_handlers()
|> assign(:delete_user, nil)
|> assign(:page, nil)
|> request_page_query()
|> apply_action(socket.assigns.live_action, params)
|> live_noreply()
@ -65,9 +64,9 @@ defmodule Shift73kWeb.UserManagementLive.Index do
|> assign(:user, Accounts.get_user!(id))
end
def assign_modal_return_to(%{assigns: %{query: query}} = socket) do
defp assign_modal_close_handlers(%{assigns: %{query: query}} = socket) do
to = Routes.user_management_index_path(socket, :index, Enum.into(query, []))
assign(socket, :modal_return_to, to)
assign(socket, modal_return_to: to, modal_close_action: :return)
end
defp user_from_params(params)
@ -98,7 +97,10 @@ defmodule Shift73kWeb.UserManagementLive.Index do
@impl true
def handle_event("delete-modal", %{"id" => id}, socket) do
{:noreply, assign(socket, :delete_user, Accounts.get_user!(id))}
socket
|> assign(:modal_close_action, :delete_user)
|> assign(:delete_user, Accounts.get_user!(id))
|> live_noreply()
end
@impl true
@ -114,17 +116,13 @@ defmodule Shift73kWeb.UserManagementLive.Index do
end
@impl true
def handle_event(
"sort-change",
%{"sort_by" => column} = params,
%{assigns: %{query: query}} = socket
) do
(column == query.sort_by &&
send(
self(),
{:query_update, %{"sort_order" => (query.sort_order == "asc" && "desc") || "asc"}}
)) ||
def handle_event("sort-change", %{"sort_by" => column} = params, socket) do
if column == socket.assigns.query.sort_by do
order = (socket.assigns.query.sort_order == "asc" && "desc") || "asc"
send(self(), {:query_update, %{"sort_order" => order}})
else
send(self(), {:query_update, Map.put(params, "sort_order", "asc")})
end
{:noreply, socket}
end
@ -169,8 +167,20 @@ defmodule Shift73kWeb.UserManagementLive.Index do
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_return_to: to}} = socket) do
socket |> copy_flash() |> push_patch(to: to) |> live_noreply()
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: :return}} = socket) do
socket
|> copy_flash()
|> push_patch(to: socket.assigns.modal_return_to)
|> live_noreply()
end
@impl true
def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: assign_key}} = socket) do
socket
|> assign(assign_key, nil)
|> assign_modal_close_handlers()
|> request_page_query()
|> live_noreply()
end
@impl true

View File

@ -11,8 +11,8 @@
<%= live_modal @socket, Shift73kWeb.UserManagement.DeleteComponent,
id: @delete_user.id,
title: "Delete User",
delete_user: @delete_user,
current_user: @current_user %>
delete_user: @delete_user
%>
<% end %>

View File

@ -0,0 +1,36 @@
defmodule Shift73kWeb.Redirector do
import Phoenix.Controller, only: [redirect: 2]
def init([to: _] = opts), do: opts
def init([external: _] = opts), do: opts
def init(_default), do: raise("Missing required to: / external: option in redirect")
def call(conn, [to: to]) do
redirect(conn, to: append_query_string(conn, to))
end
def call(conn, [external: url]) do
external = url
|> URI.parse
|> merge_query_string(conn)
|> URI.to_string
redirect(conn, external: external)
end
defp append_query_string(%Plug.Conn{query_string: ""}, path), do: path
defp append_query_string(%Plug.Conn{query_string: query}, path), do: "#{path}?#{query}"
defp merge_query_string(%URI{query: nil} = destination_uri, %Plug.Conn{query_string: source}) do
%{destination_uri | query: source}
end
defp merge_query_string(%URI{query: destination} = destination_uri, %Plug.Conn{query_string: source}) do
merged_query = Map.merge(
URI.decode_query(destination),
URI.decode_query(source)
)
%{destination_uri | query: URI.encode_query(merged_query)}
end
end

View File

@ -32,8 +32,7 @@ defmodule Shift73kWeb.Router do
scope "/", Shift73kWeb do
pipe_through([:browser])
live("/", PageLive, :index)
get("/other", OtherController, :index)
get("/", Redirector, to: "/assign")
end
# Other scopes may use custom stacks.

View File

@ -2,7 +2,7 @@
<div class="container">
<h1 class="fs-4 my-0 py-0 lh-base">
<%= link to: Routes.page_path(@conn, :index), class: "navbar-brand fs-4" do %>
<%= link to: "/", class: "navbar-brand fs-4" do %>
<%= icon_div @conn, "bi-calendar2-week", [class: "icon baseline me-1"] %>
<span class="fw-light">Shift73k</span>
<% end %>
@ -26,14 +26,14 @@
<%# nav LEFT items %>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<%= if @current_user do %>
<li class="nav-item">
<%= link nav_link_opts(@conn, to: Routes.shift_template_index_path(@conn, :index), class: "nav-link") do %>
<%= icon_div @conn, "bi-clock-history", [class: "icon baseline me-1"] %>
Templates
<% end %>
</li>
<% end %>
<%#= if @current_user do %>
<%# <li class="nav-item"> %>
<%#= link nav_link_opts(@conn, to: Routes.shift_template_index_path(@conn, :index), class: "nav-link") do %>
<%#= icon_div @conn, "bi-clock-history", [class: "icon baseline me-1"] %>
<%# Templates %>
<%# end %>
<%# </li> %>
<%# end %>
<%# normal navbar link example %>
<%# <li class="nav-item"> %>
@ -66,13 +66,19 @@
<%# nav RIGHT items %>
<ul class="navbar-nav">
<%= render "navbar/_user_menu.html", assigns %>
<%= if @current_user do %>
<%= render "navbar/_shifts_menu.html", assigns %>
<%= render "navbar/_user_menu.html", assigns %>
<% else %>
<%= 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"] %>
Log in
<% end %>
<% end %>
</ul>

View File

@ -0,0 +1,25 @@
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false">
<%= icon_div @conn, "bi-calendar2", [class: "icon baseline me-1"] %>
Shifts
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu">
<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
<% end %>
</li>
<li>
<%= link nav_link_opts(@conn, to: Routes.shift_template_index_path(@conn, :index), class: "dropdown-item") do %>
<%= icon_div @conn, "bi-clock-history", [class: "icon baseline me-1"] %>
My Shift Templates
<% end %>
</li>
</ul>
</li>

View File

@ -1,5 +1,3 @@
<%= if @current_user do %>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false">
@ -36,5 +34,3 @@
</ul>
</li>
<% end %>

View File

@ -1,41 +0,0 @@
<h1 class="text-3xl font-bold leading-tight text-gunmetal-200">
Other Page
</h1>
<h2 class="text-xl leading-tight text-gunmetal-400">
With a subtitle no less!
</h2>
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<!-- Replace with your content -->
<%# <div class="px-4 py-6 sm:px-0">
<div class="border-4 border-dashed border-gray-200 rounded-lg h-96"></div>
</div> %>
<p>
Praesent velit justo, auctor ut nibh id, fermentum eleifend nunc. Cras sed purus dignissim, ornare elit et, ultrices elit. Sed quis neque consequat, laoreet ante a, hendrerit elit. Maecenas dapibus sed nulla vitae consectetur. Duis sollicitudin augue nisl, et rhoncus enim tempor at. Fusce scelerisque sollicitudin purus sit amet iaculis. Phasellus lacinia mi ut laoreet accumsan. Sed sagittis erat nec sem placerat, ut volutpat neque porttitor. Suspendisse tempor mauris vel mollis sagittis. In ut laoreet arcu. Duis sed felis in dui imperdiet luctus nec faucibus sem. Donec commodo urna ut enim fringilla, quis lacinia ligula malesuada. Quisque feugiat fermentum pretium. Integer sed porttitor lacus, sed bibendum diam. Aliquam dapibus neque et pharetra interdum.
</p>
<!-- /End replace -->
</div>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Aliquam ultrices elit purus, eget dignissim orci pulvinar id. Curabitur tincidunt, ligula eu condimentum porttitor, nibh sapien scelerisque urna, nec cursus nisi nisi a neque. Mauris hendrerit orci blandit, suscipit ante nec, porttitor neque. Nunc.
</div>
<div class="modal-footer">
<button type="button" class="btn" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>

View File

@ -1,3 +0,0 @@
defmodule Shift73kWeb.OtherView do
use Shift73kWeb, :view
end

View File

@ -10,7 +10,7 @@ defmodule Shift73k.Repo.Migrations.CreateShiftTemplates do
add :time_zone, :string, null: false
add :time_start, :time, null: false
add :time_end, :time, null: false
add :user_id, references(:users, on_delete: :nothing, type: :binary_id)
add :user_id, references(:users, on_delete: :delete_all, type: :binary_id)
timestamps()
end

View File

@ -3,7 +3,7 @@ defmodule Shift73k.Repo.Migrations.AddUserDefaultShiftColumn do
def change do
alter table(:users) do
add(:fave_shift_template_id, references(:shift_templates, type: :binary_id, on_delete: :delete_all))
add(:fave_shift_template_id, references(:shift_templates, type: :binary_id, on_delete: :nilify_all))
end
end
end

View File

@ -1,66 +0,0 @@
defmodule Shift73kWeb.PageLiveTest do
use Shift73kWeb.ConnCase
import Phoenix.LiveViewTest
import Shift73k.AccountsFixtures
test "disconnected and connected render with authentication should redirect to index page", %{
conn: conn
} do
conn = conn |> log_in_user(user_fixture())
{:ok, page_live, disconnected_html} = live(conn, "/")
assert disconnected_html =~ "Welcome to Phoenix!"
assert render(page_live) =~ "Welcome to Phoenix!"
end
test "logs out when force logout on logged user", %{
conn: conn
} do
user = user_fixture()
conn = conn |> log_in_user(user)
{:ok, page_live, disconnected_html} = live(conn, "/")
assert disconnected_html =~ "Welcome to Phoenix!"
assert render(page_live) =~ "Welcome to Phoenix!"
Shift73k.Accounts.logout_user(user)
# Assert our liveview process is down
ref = Process.monitor(page_live.pid)
assert_receive {:DOWN, ^ref, _, _, _}
refute Process.alive?(page_live.pid)
# Assert our liveview was redirected, following first to /users/force_logout, then to "/", and then to "/users/log_in"
assert_redirect(page_live, "/users/force_logout")
conn = get(conn, "/users/force_logout")
assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application."
end
test "doesn't log out when force logout on another user", %{
conn: conn
} do
user1 = user_fixture()
user2 = user_fixture()
conn = conn |> log_in_user(user2)
{:ok, page_live, disconnected_html} = live(conn, "/")
assert disconnected_html =~ "Welcome to Phoenix!"
assert render(page_live) =~ "Welcome to Phoenix!"
Shift73k.Accounts.logout_user(user1)
# Assert our liveview is alive
ref = Process.monitor(page_live.pid)
refute_receive {:DOWN, ^ref, _, _, _}
assert Process.alive?(page_live.pid)
# If we are able to rerender the page it means nothing happened
assert render(page_live) =~ "Welcome to Phoenix!"
end
end