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
|
def list_chores do
|
||||||
logs = from(log in ChoreLog, limit: 1)
|
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
|
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
|
def create_chore(attrs \\ %{}) do
|
||||||
%Chore{}
|
%Chore{}
|
||||||
|
@ -24,9 +25,12 @@ defmodule ChoreTracker.Chores do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_chore(%Chore{} = chore, attrs) do
|
def update_chore(%Chore{} = chore, attrs) do
|
||||||
chore
|
Ecto.Multi.new()
|
||||||
|> change_chore(attrs)
|
|> Ecto.Multi.update(:chore, change_chore(chore, attrs))
|
||||||
|> Repo.update()
|
|> Ecto.Multi.run(:update_next_assignee, fn _repo, _changeset ->
|
||||||
|
save_next_chore_assignee(chore)
|
||||||
|
end)
|
||||||
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_chore(%Chore{} = chore) do
|
def delete_chore(%Chore{} = chore) do
|
||||||
|
@ -41,6 +45,14 @@ defmodule ChoreTracker.Chores do
|
||||||
|> Changeset.put_assoc(:assignees, assignees)
|
|> Changeset.put_assoc(:assignees, assignees)
|
||||||
end
|
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
|
def get_last_chore_log_for_assignee(%Chore{} = chore, %User{} = user) do
|
||||||
from(log in ChoreLog,
|
from(log in ChoreLog,
|
||||||
where: log.chore_id == ^chore.id and log.user_id == ^user.id,
|
where: log.chore_id == ^chore.id and log.user_id == ^user.id,
|
||||||
|
@ -51,9 +63,15 @@ defmodule ChoreTracker.Chores do
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_chore_execution(%User{} = user, %Chore{} = chore) do
|
def log_chore_execution(%User{} = user, %Chore{} = chore) do
|
||||||
%ChoreLog{}
|
Ecto.Multi.new()
|
||||||
|> ChoreLog.changeset(%{user_id: user.id, chore_id: chore.id})
|
|> Ecto.Multi.insert(
|
||||||
|> Repo.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
|
end
|
||||||
|
|
||||||
def update_chore_log(%ChoreLog{} = chore_log, attrs) do
|
def update_chore_log(%ChoreLog{} = chore_log, attrs) do
|
||||||
|
@ -83,6 +101,11 @@ defmodule ChoreTracker.Chores do
|
||||||
chore.starts_at
|
chore.starts_at
|
||||||
end
|
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
|
def next_chore_assignee(%Chore{} = chore) do
|
||||||
chore.assignees
|
chore.assignees
|
||||||
|> Enum.map(&{&1, get_last_chore_log_for_assignee(chore, &1)})
|
|> 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)
|
|> Enum.map(fn {assignee, _log} -> assignee end)
|
||||||
|> List.first()
|
|> List.first()
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule ChoreTracker.Chores.Chore do
|
defmodule ChoreTracker.Chores.Chore do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias ChoreTracker.Chores
|
||||||
|
alias ChoreTracker.Accounts
|
||||||
|
|
||||||
schema "chores" do
|
schema "chores" do
|
||||||
field :name, :string
|
field :name, :string
|
||||||
|
@ -12,9 +14,11 @@ defmodule ChoreTracker.Chores.Chore do
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
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",
|
join_through: "chore_assignees",
|
||||||
on_replace: :delete
|
on_replace: :delete
|
||||||
end
|
end
|
||||||
|
@ -22,7 +26,15 @@ defmodule ChoreTracker.Chores.Chore do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(chore, attrs) do
|
def changeset(chore, attrs) do
|
||||||
chore
|
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_required([:name, :emoji, :period, :period_unit, :starts_at])
|
||||||
|> validate_length(:emoji, min: 1, max: 1)
|
|> validate_length(:emoji, min: 1, max: 1)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
defmodule ChoreTrackerWeb.ChoreLive.Show do
|
defmodule ChoreTrackerWeb.ChoreLive.Show do
|
||||||
|
alias ChoreTracker.Repo
|
||||||
use ChoreTrackerWeb, :live_view
|
use ChoreTrackerWeb, :live_view
|
||||||
|
|
||||||
alias ChoreTracker.Chores
|
alias ChoreTracker.Chores
|
||||||
|
@ -11,15 +12,18 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
chore = Chores.get_chore!(id)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|> assign(:chore, Chores.get_chore!(id))}
|
|> update_state(chore)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Chore"
|
defp page_title(:show), do: "Show Chore"
|
||||||
defp page_title(:edit), do: "Edit Chore"
|
defp page_title(:edit), do: "Edit Chore"
|
||||||
|
|
||||||
|
@impl true
|
||||||
def handle_event("log_execution", _params, socket) do
|
def handle_event("log_execution", _params, socket) do
|
||||||
%{current_user: user, chore: chore} = socket.assigns
|
%{current_user: user, chore: chore} = socket.assigns
|
||||||
|
|
||||||
|
@ -28,7 +32,31 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> put_flash(:info, "Chore execution logged.")
|
|> put_flash(:info, "Chore execution logged.")
|
||||||
|> assign(:chore, Chores.get_chore!(chore.id))}
|
|> update_state(Chores.get_chore!(chore.id))}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -59,13 +59,22 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-6">
|
<div class="col-span-6">
|
||||||
<p class="text-sm font-medium text-zinc-500 mb-2">Next assignee</p>
|
<label for="chore_next_assignee_id" class="text-sm font-medium text-zinc-500">
|
||||||
|
Next assignee
|
||||||
<%= if next_assignee = Chores.next_chore_assignee(@chore) do %>
|
</label>
|
||||||
<p><%= Accounts.display_user(next_assignee) %></p>
|
<.form
|
||||||
<% else %>
|
for={@assignee_form}
|
||||||
<p>None</p>
|
class="flex flex-wrap gap-2 items-end"
|
||||||
<% 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>
|
||||||
|
|
||||||
<div class="col-span-12">
|
<div class="col-span-12">
|
||||||
|
|
|
@ -36,7 +36,7 @@ defmodule ChoreTrackerWeb.OverviewLive do
|
||||||
<p><%= display_relative(next_date) %></p>
|
<p><%= display_relative(next_date) %></p>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={chore} label="Next assignee">
|
<:col :let={chore} label="Next assignee">
|
||||||
<%= case Chores.next_chore_assignee(chore) do
|
<%= case chore.next_assignee do
|
||||||
nil -> "No assignee"
|
nil -> "No assignee"
|
||||||
%Accounts.User{} = user -> Accounts.display_user(user)
|
%Accounts.User{} = user -> Accounts.display_user(user)
|
||||||
end %>
|
end %>
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule ChoreTracker.Repo.Migrations.CreateChores do
|
||||||
add :period, :integer
|
add :period, :integer
|
||||||
add :period_unit, :string
|
add :period_unit, :string
|
||||||
add :starts_at, :date
|
add :starts_at, :date
|
||||||
|
add :next_assignee_id, references(:users, on_delete: :nilify_all)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue