Membership Fee 6 - UI Components & LiveViews closes #280 #304
1 changed files with 397 additions and 74 deletions
|
|
@ -16,7 +16,9 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
|
|
||||||
alias Mv.Membership
|
alias Mv.Membership
|
||||||
alias Mv.MembershipFees.MembershipFeeType
|
alias Mv.MembershipFees.MembershipFeeType
|
||||||
|
alias Mv.MembershipFees.MembershipFeeCycle
|
||||||
alias Mv.MembershipFees.CycleGenerator
|
alias Mv.MembershipFees.CycleGenerator
|
||||||
|
alias Mv.MembershipFees.CalendarCycles
|
||||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
@ -56,6 +58,26 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
<.icon name="hero-arrow-path" class="size-4" />
|
<.icon name="hero-arrow-path" class="size-4" />
|
||||||
{if(@regenerating, do: gettext("Regenerating..."), else: gettext("Regenerate Cycles"))}
|
{if(@regenerating, do: gettext("Regenerating..."), else: gettext("Regenerate Cycles"))}
|
||||||
</.button>
|
</.button>
|
||||||
|
<.button
|
||||||
|
:if={Enum.any?(@cycles)}
|
||||||
|
phx-click="delete_all_cycles"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-sm btn-error btn-outline"
|
||||||
|
title={gettext("Delete all cycles")}
|
||||||
|
>
|
||||||
|
<.icon name="hero-trash" class="size-4" />
|
||||||
|
{gettext("Delete All Cycles")}
|
||||||
|
</.button>
|
||||||
|
<.button
|
||||||
|
:if={@member.membership_fee_type}
|
||||||
|
phx-click="open_create_cycle_modal"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
title={gettext("Create a new cycle manually")}
|
||||||
|
>
|
||||||
|
<.icon name="hero-plus" class="size-4" />
|
||||||
|
{gettext("Create Cycle")}
|
||||||
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Cycles Table --%>
|
<%!-- Cycles Table --%>
|
||||||
|
|
@ -79,7 +101,15 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={cycle} label={gettext("Amount")}>
|
<:col :let={cycle} label={gettext("Amount")}>
|
||||||
<span class="font-mono">{MembershipFeeHelpers.format_currency(cycle.amount)}</span>
|
<span
|
||||||
|
class="font-mono cursor-pointer hover:text-primary"
|
||||||
|
phx-click="edit_cycle_amount"
|
||||||
|
phx-value-cycle_id={cycle.id}
|
||||||
|
phx-target={@myself}
|
||||||
|
title={gettext("Click to edit amount")}
|
||||||
|
>
|
||||||
|
{MembershipFeeHelpers.format_currency(cycle.amount)}
|
||||||
|
</span>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={cycle} label={gettext("Status")}>
|
<:col :let={cycle} label={gettext("Status")}>
|
||||||
|
|
@ -92,80 +122,59 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:action :let={cycle}>
|
<:action :let={cycle}>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="flex gap-1 flex-wrap">
|
||||||
<label tabindex="0" class="btn btn-ghost btn-xs">
|
<button
|
||||||
<.icon name="hero-ellipsis-vertical" class="size-4" />
|
:if={cycle.status != :paid}
|
||||||
</label>
|
type="button"
|
||||||
<ul
|
phx-click="mark_cycle_status"
|
||||||
tabindex="0"
|
phx-value-cycle_id={cycle.id}
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"
|
phx-value-status="paid"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-sm btn-success"
|
||||||
|
title={gettext("Mark as paid")}
|
||||||
>
|
>
|
||||||
<li>
|
<.icon name="hero-check-circle" class="size-4" />
|
||||||
<button
|
{gettext("Paid")}
|
||||||
type="button"
|
</button>
|
||||||
phx-click="mark_cycle_status"
|
<button
|
||||||
phx-value-cycle_id={cycle.id}
|
type="button"
|
||||||
phx-value-status="paid"
|
phx-click="mark_cycle_status"
|
||||||
phx-target={@myself}
|
phx-value-cycle_id={cycle.id}
|
||||||
class={if(cycle.status == :paid, do: "active", else: "")}
|
phx-value-status="suspended"
|
||||||
>
|
phx-target={@myself}
|
||||||
<.icon name="hero-check-circle" class="size-4" />
|
class={[
|
||||||
{gettext("Mark as paid")}
|
"btn btn-sm",
|
||||||
</button>
|
if(cycle.status == :suspended, do: "btn-warning", else: "btn-outline btn-warning")
|
||||||
</li>
|
]}
|
||||||
<li>
|
title={gettext("Mark as suspended")}
|
||||||
<button
|
>
|
||||||
type="button"
|
<.icon name="hero-pause-circle" class="size-4" />
|
||||||
phx-click="mark_cycle_status"
|
{gettext("Suspended")}
|
||||||
phx-value-cycle_id={cycle.id}
|
</button>
|
||||||
phx-value-status="unpaid"
|
<button
|
||||||
phx-target={@myself}
|
:if={cycle.status != :unpaid}
|
||||||
class={if(cycle.status == :unpaid, do: "active", else: "")}
|
type="button"
|
||||||
>
|
phx-click="mark_cycle_status"
|
||||||
<.icon name="hero-x-circle" class="size-4" />
|
phx-value-cycle_id={cycle.id}
|
||||||
{gettext("Mark as unpaid")}
|
phx-value-status="unpaid"
|
||||||
</button>
|
phx-target={@myself}
|
||||||
</li>
|
class="btn btn-sm btn-error"
|
||||||
<li>
|
title={gettext("Mark as unpaid")}
|
||||||
<button
|
>
|
||||||
type="button"
|
<.icon name="hero-x-circle" class="size-4" />
|
||||||
phx-click="mark_cycle_status"
|
{gettext("Unpaid")}
|
||||||
phx-value-cycle_id={cycle.id}
|
</button>
|
||||||
phx-value-status="suspended"
|
<button
|
||||||
phx-target={@myself}
|
type="button"
|
||||||
class={if(cycle.status == :suspended, do: "active", else: "")}
|
phx-click="delete_cycle"
|
||||||
>
|
phx-value-cycle_id={cycle.id}
|
||||||
<.icon name="hero-pause-circle" class="size-4" />
|
phx-target={@myself}
|
||||||
{gettext("Mark as suspended")}
|
class="btn btn-sm btn-error btn-outline"
|
||||||
</button>
|
title={gettext("Delete cycle")}
|
||||||
</li>
|
>
|
||||||
<li>
|
<.icon name="hero-trash" class="size-4" />
|
||||||
<hr class="my-1" />
|
{gettext("Delete")}
|
||||||
</li>
|
</button>
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
phx-click="edit_cycle_amount"
|
|
||||||
phx-value-cycle_id={cycle.id}
|
|
||||||
phx-target={@myself}
|
|
||||||
>
|
|
||||||
<.icon name="hero-pencil" class="size-4" />
|
|
||||||
{gettext("Edit amount")}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
phx-click="delete_cycle"
|
|
||||||
phx-value-cycle_id={cycle.id}
|
|
||||||
phx-target={@myself}
|
|
||||||
class="text-error"
|
|
||||||
>
|
|
||||||
<.icon name="hero-trash" class="size-4" />
|
|
||||||
{gettext("Delete")}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</:action>
|
</:action>
|
||||||
</.table>
|
</.table>
|
||||||
|
|
@ -243,6 +252,130 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%!-- Delete All Cycles Confirmation Modal --%>
|
||||||
|
<%= if @deleting_all_cycles do %>
|
||||||
|
<dialog id="delete-all-cycles-modal" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="text-lg font-bold text-error">{gettext("Delete All Cycles")}</h3>
|
||||||
|
<div class="alert alert-warning mt-4">
|
||||||
|
<.icon name="hero-exclamation-triangle" class="size-5" />
|
||||||
|
<div>
|
||||||
|
<h4 class="font-bold">{gettext("Warning")}</h4>
|
||||||
|
<p>
|
||||||
|
{gettext("You are about to delete all %{count} cycles for this member.",
|
||||||
|
count: length(@cycles)
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="mt-2">
|
||||||
|
{gettext("This action cannot be undone.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control w-full mt-4">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">
|
||||||
|
{gettext("Type '%{confirmation}' to confirm", confirmation: gettext("Yes"))}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
phx-keyup="update_delete_all_confirmation"
|
||||||
|
phx-target={@myself}
|
||||||
|
value={@delete_all_confirmation || ""}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder={gettext("Yes")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button phx-click="cancel_delete_all_cycles" phx-target={@myself} class="btn">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
phx-click="confirm_delete_all_cycles"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="btn btn-error"
|
||||||
|
disabled={
|
||||||
|
@delete_all_confirmation != gettext("Yes") && @delete_all_confirmation != "Yes"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{gettext("Delete All")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%!-- Create Cycle Modal --%>
|
||||||
|
<%= if @creating_cycle do %>
|
||||||
|
<dialog id="create-cycle-modal" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="text-lg font-bold">{gettext("Create Cycle")}</h3>
|
||||||
|
<form phx-submit="create_cycle" phx-target={@myself}>
|
||||||
|
<div class="form-control w-full mt-4">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">{gettext("Date")}</span>
|
||||||
|
moritz marked this conversation as resolved
|
|||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="date"
|
||||||
|
value={@create_cycle_date || ""}
|
||||||
|
phx-change="update_create_cycle_date"
|
||||||
|
phx-target={@myself}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">
|
||||||
|
{gettext(
|
||||||
|
"The cycle period will be calculated based on this date and the interval."
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<%= if @create_cycle_date do %>
|
||||||
|
<div class="form-control w-full mt-4">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">{gettext("Cycle Period")}</span>
|
||||||
|
</label>
|
||||||
|
<div class="text-sm text-base-content/70">
|
||||||
|
{format_create_cycle_period(
|
||||||
|
@create_cycle_date,
|
||||||
|
@member.membership_fee_type.interval
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control w-full mt-4">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">{gettext("Amount")}</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="amount"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
value={Decimal.to_string(@member.membership_fee_type.amount)}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<%= if @create_cycle_error do %>
|
||||||
|
<div class="alert alert-error mt-4">
|
||||||
|
<.icon name="hero-exclamation-circle" class="size-5" />
|
||||||
|
<span>{@create_cycle_error}</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button type="button" phx-click="cancel_create_cycle" phx-target={@myself} class="btn">
|
||||||
|
{gettext("Cancel")}
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">{gettext("Create")}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
@ -273,6 +406,11 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
|> assign_new(:interval_warning, fn -> nil end)
|
|> assign_new(:interval_warning, fn -> nil end)
|
||||||
|> assign_new(:editing_cycle, fn -> nil end)
|
|> assign_new(:editing_cycle, fn -> nil end)
|
||||||
|> assign_new(:deleting_cycle, fn -> nil end)
|
|> assign_new(:deleting_cycle, fn -> nil end)
|
||||||
|
|> assign_new(:deleting_all_cycles, fn -> false end)
|
||||||
|
|> assign_new(:delete_all_confirmation, fn -> "" end)
|
||||||
|
|> assign_new(:creating_cycle, fn -> false end)
|
||||||
|
|> assign_new(:create_cycle_date, fn -> nil end)
|
||||||
|
|> assign_new(:create_cycle_error, fn -> nil end)
|
||||||
|> assign_new(:regenerating, fn -> false end)}
|
|> assign_new(:regenerating, fn -> false end)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -509,6 +647,173 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("delete_all_cycles", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:deleting_all_cycles, true)
|
||||||
|
|> assign(:delete_all_confirmation, "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("cancel_delete_all_cycles", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:deleting_all_cycles, false)
|
||||||
|
|> assign(:delete_all_confirmation, "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("update_delete_all_confirmation", %{"value" => value}, socket) do
|
||||||
|
{:noreply, assign(socket, :delete_all_confirmation, value)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("confirm_delete_all_cycles", _params, socket) do
|
||||||
|
member = socket.assigns.member
|
||||||
|
cycles = socket.assigns.cycles
|
||||||
|
|
||||||
|
# Delete all cycles
|
||||||
|
results =
|
||||||
|
Enum.map(cycles, fn cycle ->
|
||||||
|
Ash.destroy(cycle)
|
||||||
|
end)
|
||||||
|
|
||||||
|
# Check if all deletions were successful
|
||||||
|
errors = Enum.filter(results, &match?({:error, _}, &1))
|
||||||
|
|
||||||
|
if Enum.empty?(errors) do
|
||||||
|
# Reload member to get updated cycles
|
||||||
|
updated_member =
|
||||||
|
member
|
||||||
|
|> Ash.load!([
|
||||||
|
:membership_fee_type,
|
||||||
|
membership_fee_cycles: [:membership_fee_type]
|
||||||
|
])
|
||||||
|
|
||||||
|
updated_cycles =
|
||||||
|
Enum.sort_by(
|
||||||
|
updated_member.membership_fee_cycles || [],
|
||||||
|
& &1.cycle_start,
|
||||||
|
{:desc, Date}
|
||||||
|
)
|
||||||
|
|
||||||
|
send(self(), {:member_updated, updated_member})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:member, updated_member)
|
||||||
|
|> assign(:cycles, updated_cycles)
|
||||||
|
|> assign(:deleting_all_cycles, false)
|
||||||
|
|> assign(:delete_all_confirmation, "")
|
||||||
|
|> put_flash(:info, gettext("All cycles deleted"))}
|
||||||
|
else
|
||||||
|
error_msg =
|
||||||
|
Enum.map_join(errors, ", ", fn {:error, error} -> format_error(error) end)
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:deleting_all_cycles, false)
|
||||||
|
|> assign(:delete_all_confirmation, "")
|
||||||
|
|> put_flash(:error, gettext("Failed to delete some cycles: %{errors}", errors: error_msg))}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("open_create_cycle_modal", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:creating_cycle, true)
|
||||||
|
|> assign(:create_cycle_date, nil)
|
||||||
|
|> assign(:create_cycle_error, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("cancel_create_cycle", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:creating_cycle, false)
|
||||||
|
|> assign(:create_cycle_date, nil)
|
||||||
|
|> assign(:create_cycle_error, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("update_create_cycle_date", %{"value" => date_str}, socket) do
|
||||||
|
date =
|
||||||
|
case Date.from_iso8601(date_str) do
|
||||||
|
{:ok, date} -> date
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:create_cycle_date, date)
|
||||||
|
|> assign(:create_cycle_error, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("create_cycle", %{"date" => date_str, "amount" => amount_str}, socket) do
|
||||||
|
member = socket.assigns.member
|
||||||
|
|
||||||
|
with {:ok, date} <- Date.from_iso8601(date_str),
|
||||||
|
{amount, _} when is_struct(amount, Decimal) <- Decimal.parse(amount_str),
|
||||||
|
cycle_start <-
|
||||||
|
CalendarCycles.calculate_cycle_start(date, member.membership_fee_type.interval),
|
||||||
|
:ok <- validate_cycle_not_exists(socket.assigns.cycles, cycle_start) do
|
||||||
|
attrs = %{
|
||||||
|
cycle_start: cycle_start,
|
||||||
|
amount: amount,
|
||||||
|
status: :unpaid,
|
||||||
|
member_id: member.id,
|
||||||
|
membership_fee_type_id: member.membership_fee_type_id
|
||||||
|
}
|
||||||
|
|
||||||
|
case Ash.create(MembershipFeeCycle, attrs) do
|
||||||
|
{:ok, _new_cycle} ->
|
||||||
|
# Reload member with cycles
|
||||||
|
updated_member =
|
||||||
|
member
|
||||||
|
|> Ash.load!([
|
||||||
|
:membership_fee_type,
|
||||||
|
membership_fee_cycles: [:membership_fee_type]
|
||||||
|
])
|
||||||
|
|
||||||
|
cycles =
|
||||||
|
Enum.sort_by(
|
||||||
|
updated_member.membership_fee_cycles || [],
|
||||||
|
& &1.cycle_start,
|
||||||
|
{:desc, Date}
|
||||||
|
)
|
||||||
|
|
||||||
|
send(self(), {:member_updated, updated_member})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:member, updated_member)
|
||||||
|
|> assign(:cycles, cycles)
|
||||||
|
|> assign(:creating_cycle, false)
|
||||||
|
|> assign(:create_cycle_date, nil)
|
||||||
|
|> assign(:create_cycle_error, nil)
|
||||||
|
|> put_flash(:info, gettext("Cycle created successfully"))}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:create_cycle_error, format_error(error))}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
:error ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:create_cycle_error, gettext("Invalid date format"))}
|
||||||
|
|
||||||
|
{:error, :invalid_amount} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:create_cycle_error, gettext("Invalid amount format"))}
|
||||||
|
|
||||||
|
{:error, :cycle_exists} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
:create_cycle_error,
|
||||||
|
gettext("A cycle for this period already exists")
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Helper functions
|
# Helper functions
|
||||||
|
|
||||||
defp get_available_fee_types(member) do
|
defp get_available_fee_types(member) do
|
||||||
|
|
@ -559,6 +864,24 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
||||||
defp format_error(error) when is_binary(error), do: error
|
defp format_error(error) when is_binary(error), do: error
|
||||||
defp format_error(_error), do: gettext("An error occurred")
|
defp format_error(_error), do: gettext("An error occurred")
|
||||||
|
|
||||||
|
defp validate_cycle_not_exists(cycles, cycle_start) do
|
||||||
|
if Enum.any?(cycles, &(&1.cycle_start == cycle_start)) do
|
||||||
|
{:error, :cycle_exists}
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_create_cycle_period(date, interval) when is_struct(date, Date) do
|
||||||
|
cycle_start = CalendarCycles.calculate_cycle_start(date, interval)
|
||||||
|
cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, interval)
|
||||||
|
|
||||||
|
MembershipFeeHelpers.format_cycle_range(cycle_start, interval) <>
|
||||||
|
" (#{Calendar.strftime(cycle_start, "%d.%m.%Y")} - #{Calendar.strftime(cycle_end, "%d.%m.%Y")})"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_create_cycle_period(_date, _interval), do: ""
|
||||||
|
|
||||||
# Helper component for section box
|
# Helper component for section box
|
||||||
attr :title, :string, required: true
|
attr :title, :string, required: true
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue
If I select a date I get an error and the modal closes and the member overview is shown