Make next assignee reassignable
This commit is contained in:
parent
3b86eeaa23
commit
f9b0847b7f
6 changed files with 104 additions and 21 deletions
|
@ -12,10 +12,11 @@ defmodule ChoreTracker.Chores do
|
|||
|
||||
def list_chores do
|
||||
logs = from(log in ChoreLog, limit: 1)
|
||||
from(chore in Chore, preload: [:assignees, logs: ^logs]) |> Repo.all()
|
||||
from(chore in Chore, preload: [:assignees, :next_assignee, logs: ^logs]) |> Repo.all()
|
||||
end
|
||||
|
||||
def get_chore!(id), do: Repo.get!(Chore, id) |> Repo.preload([{:logs, :user}, :assignees])
|
||||
def get_chore!(id),
|
||||
do: Repo.get!(Chore, id) |> Repo.preload([{:logs, :user}, :assignees, :next_assignee])
|
||||
|
||||
def create_chore(attrs \\ %{}) do
|
||||
%Chore{}
|
||||
|
@ -24,9 +25,12 @@ defmodule ChoreTracker.Chores do
|
|||
end
|
||||
|
||||
def update_chore(%Chore{} = chore, attrs) do
|
||||
chore
|
||||
|> change_chore(attrs)
|
||||
|> Repo.update()
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:chore, change_chore(chore, attrs))
|
||||
|> Ecto.Multi.run(:update_next_assignee, fn _repo, _changeset ->
|
||||
save_next_chore_assignee(chore)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def delete_chore(%Chore{} = chore) do
|
||||
|
@ -41,6 +45,14 @@ defmodule ChoreTracker.Chores do
|
|||
|> Changeset.put_assoc(:assignees, assignees)
|
||||
end
|
||||
|
||||
def update_chore_assignee(%Chore{} = chore, attrs) do
|
||||
change_chore_assignee(chore, attrs) |> Repo.update()
|
||||
end
|
||||
|
||||
def change_chore_assignee(%Chore{} = chore, attrs \\ %{}) do
|
||||
chore |> Ecto.Changeset.cast(attrs, [:next_assignee_id])
|
||||
end
|
||||
|
||||
def get_last_chore_log_for_assignee(%Chore{} = chore, %User{} = user) do
|
||||
from(log in ChoreLog,
|
||||
where: log.chore_id == ^chore.id and log.user_id == ^user.id,
|
||||
|
@ -51,9 +63,15 @@ defmodule ChoreTracker.Chores do
|
|||
end
|
||||
|
||||
def log_chore_execution(%User{} = user, %Chore{} = chore) do
|
||||
%ChoreLog{}
|
||||
|> ChoreLog.changeset(%{user_id: user.id, chore_id: chore.id})
|
||||
|> Repo.insert()
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.insert(
|
||||
:log,
|
||||
%ChoreLog{} |> ChoreLog.changeset(%{user_id: user.id, chore_id: chore.id})
|
||||
)
|
||||
|> Ecto.Multi.run(:chore, fn _repo, _change ->
|
||||
save_next_chore_assignee(chore)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def update_chore_log(%ChoreLog{} = chore_log, attrs) do
|
||||
|
@ -83,6 +101,11 @@ defmodule ChoreTracker.Chores do
|
|||
chore.starts_at
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculate next assignee for the chore.
|
||||
Assigns the person who last did the task the longest time ago.
|
||||
Returns `nil` if chore has no assignees.
|
||||
"""
|
||||
def next_chore_assignee(%Chore{} = chore) do
|
||||
chore.assignees
|
||||
|> Enum.map(&{&1, get_last_chore_log_for_assignee(chore, &1)})
|
||||
|
@ -98,4 +121,14 @@ defmodule ChoreTracker.Chores do
|
|||
|> Enum.map(fn {assignee, _log} -> assignee end)
|
||||
|> List.first()
|
||||
end
|
||||
|
||||
defp save_next_chore_assignee(%Chore{} = chore) do
|
||||
next_assignee = next_chore_assignee(chore)
|
||||
|
||||
if next_assignee do
|
||||
chore |> update_chore_assignee(%{next_assignee_id: next_assignee.id})
|
||||
else
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule ChoreTracker.Chores.Chore do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias ChoreTracker.Chores
|
||||
alias ChoreTracker.Accounts
|
||||
|
||||
schema "chores" do
|
||||
field :name, :string
|
||||
|
@ -12,9 +14,11 @@ defmodule ChoreTracker.Chores.Chore do
|
|||
|
||||
timestamps(type: :utc_datetime)
|
||||
|
||||
has_many :logs, ChoreTracker.Chores.ChoreLog, preload_order: [desc: :inserted_at]
|
||||
has_many :logs, Chores.ChoreLog, preload_order: [desc: :inserted_at]
|
||||
|
||||
many_to_many :assignees, ChoreTracker.Accounts.User,
|
||||
belongs_to :next_assignee, Accounts.User
|
||||
|
||||
many_to_many :assignees, Accounts.User,
|
||||
join_through: "chore_assignees",
|
||||
on_replace: :delete
|
||||
end
|
||||
|
@ -22,7 +26,15 @@ defmodule ChoreTracker.Chores.Chore do
|
|||
@doc false
|
||||
def changeset(chore, attrs) do
|
||||
chore
|
||||
|> cast(attrs, [:name, :description, :emoji, :period, :period_unit, :starts_at])
|
||||
|> cast(attrs, [
|
||||
:name,
|
||||
:description,
|
||||
:emoji,
|
||||
:period,
|
||||
:period_unit,
|
||||
:starts_at,
|
||||
:next_assignee_id
|
||||
])
|
||||
|> validate_required([:name, :emoji, :period, :period_unit, :starts_at])
|
||||
|> validate_length(:emoji, min: 1, max: 1)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule ChoreTrackerWeb.ChoreLive.Show do
|
||||
alias ChoreTracker.Repo
|
||||
use ChoreTrackerWeb, :live_view
|
||||
|
||||
alias ChoreTracker.Chores
|
||||
|
@ -11,15 +12,18 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
|
|||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
chore = Chores.get_chore!(id)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||
|> assign(:chore, Chores.get_chore!(id))}
|
||||
|> update_state(chore)}
|
||||
end
|
||||
|
||||
defp page_title(:show), do: "Show Chore"
|
||||
defp page_title(:edit), do: "Edit Chore"
|
||||
|
||||
@impl true
|
||||
def handle_event("log_execution", _params, socket) do
|
||||
%{current_user: user, chore: chore} = socket.assigns
|
||||
|
||||
|
@ -28,7 +32,31 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
|
|||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Chore execution logged.")
|
||||
|> assign(:chore, Chores.get_chore!(chore.id))}
|
||||
|> update_state(Chores.get_chore!(chore.id))}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("change_assignee", params, socket) do
|
||||
%{"chore" => chore_params} = params
|
||||
chore = socket.assigns.chore
|
||||
|
||||
case Chores.update_chore_assignee(chore, chore_params) do
|
||||
{:ok, chore} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Assignee changed")
|
||||
|> update_state(chore)}
|
||||
end
|
||||
end
|
||||
|
||||
def assignee_options() do
|
||||
Accounts.list_users() |> Enum.map(&{Accounts.display_user(&1), &1.id})
|
||||
end
|
||||
|
||||
defp update_state(socket, chore) do
|
||||
socket
|
||||
|> assign(:chore, chore |> Repo.preload(:next_assignee))
|
||||
|> assign(:assignee_form, to_form(Chores.change_chore_assignee(chore)))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,13 +59,22 @@
|
|||
</div>
|
||||
|
||||
<div class="col-span-6">
|
||||
<p class="text-sm font-medium text-zinc-500 mb-2">Next assignee</p>
|
||||
|
||||
<%= if next_assignee = Chores.next_chore_assignee(@chore) do %>
|
||||
<p><%= Accounts.display_user(next_assignee) %></p>
|
||||
<% else %>
|
||||
<p>None</p>
|
||||
<% end %>
|
||||
<label for="chore_next_assignee_id" class="text-sm font-medium text-zinc-500">
|
||||
Next assignee
|
||||
</label>
|
||||
<.form
|
||||
for={@assignee_form}
|
||||
class="flex flex-wrap gap-2 items-end"
|
||||
phx-submit="change_assignee"
|
||||
>
|
||||
<.input
|
||||
field={@assignee_form[:next_assignee_id]}
|
||||
prompt="None"
|
||||
type="select"
|
||||
options={assignee_options()}
|
||||
/>
|
||||
<.button type="submit">Save</.button>
|
||||
</.form>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12">
|
||||
|
|
|
@ -36,7 +36,7 @@ defmodule ChoreTrackerWeb.OverviewLive do
|
|||
<p><%= display_relative(next_date) %></p>
|
||||
</:col>
|
||||
<:col :let={chore} label="Next assignee">
|
||||
<%= case Chores.next_chore_assignee(chore) do
|
||||
<%= case chore.next_assignee do
|
||||
nil -> "No assignee"
|
||||
%Accounts.User{} = user -> Accounts.display_user(user)
|
||||
end %>
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule ChoreTracker.Repo.Migrations.CreateChores do
|
|||
add :period, :integer
|
||||
add :period_unit, :string
|
||||
add :starts_at, :date
|
||||
add :next_assignee_id, references(:users, on_delete: :nilify_all)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue