defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do @moduledoc """ Admin calendar for managing bookings, visualizing capacity, and selecting date ranges. """ use SpazioSolazzoWeb, :live_component alias SpazioSolazzo.CalendarExt @doc "Resets the calendar selection state." def reset(id) do send_update(__MODULE__, id: id, reset: true) end def update(%{reset: true}, socket) do socket = socket |> assign(start_date: nil) |> assign(end_date: nil) |> assign(selected_date: nil) {:ok, socket} end def update(assigns, socket) do first_day = assigns[:first_day_of_month] || socket.assigns[:first_day_of_month] || Date.utc_today() |> Date.beginning_of_month() grid = CalendarExt.build_calendar_grid(first_day) socket = socket |> assign(assigns) |> assign_new(:booking_counts, fn -> %{} end) |> assign_new(:multi_day_mode, fn -> false end) |> assign_new(:start_date, fn -> nil end) |> assign_new(:end_date, fn -> nil end) |> assign_new(:selected_date, fn -> nil end) |> assign(first_day_of_month: first_day) |> assign(grid: grid) {:ok, socket} end def handle_event("prev_month", _, socket) do new_date = Date.shift(socket.assigns.first_day_of_month, month: -1) grid = CalendarExt.build_calendar_grid(new_date) socket = socket |> assign(first_day_of_month: new_date) |> assign(grid: grid) send(self(), {:change_month, new_date}) {:noreply, socket} end def handle_event("next_month", _, socket) do new_date = Date.shift(socket.assigns.first_day_of_month, month: 1) grid = CalendarExt.build_calendar_grid(new_date) socket = socket |> assign(first_day_of_month: new_date) |> assign(grid: grid) send(self(), {:change_month, new_date}) {:noreply, socket} end def handle_event("toggle_multi_day", _, socket) do new_mode = !socket.assigns.multi_day_mode send(self(), {:multi_day_mode_toggle, new_mode}) {:noreply, assign(socket, multi_day_mode: new_mode, start_date: nil, end_date: nil, selected_date: nil )} end def handle_event("select_date", %{"date" => d}, socket) do date = Date.from_iso8601!(d) if socket.assigns.multi_day_mode do handle_multi_select(socket, date) else send(self(), {:date_selected, date, date}) {:noreply, assign(socket, selected_date: date, start_date: nil, end_date: nil)} end end defp handle_multi_select( %{assigns: %{start_date: start_date, end_date: end_date}} = socket, date ) do cond do is_nil(start_date) -> # Start Selection {:noreply, assign(socket, start_date: date)} is_nil(end_date) -> # End Selection (Order correctly) {new_start, new_end} = if Date.compare(date, start_date) == :lt, do: {date, start_date}, else: {start_date, date} send(self(), {:date_selected, new_start, new_end}) {:noreply, assign(socket, start_date: new_start, end_date: new_end)} true -> # Reset {:noreply, assign(socket, start_date: date, end_date: nil)} end end def render(assigns) do ~H"""
<%!-- Toolbar --%>

{Calendar.strftime(@first_day_of_month, "%B %Y")}

MoTuWeThFrSaSu
<%= for date <- @grid do %> <% # Uses the booking_counts passed from parent count = Map.get(@booking_counts, date, 0) is_current = date.month == @first_day_of_month.month %>
<%!-- Header Row: Date & Badge --%>
{date.day} <%= if count > 0 and is_current do %> <.link navigate={~p"/admin/bookings?date=#{Date.to_string(date)}"} class="badge badge-info badge-xs text-white font-bold hover:scale-110 transition-transform" title={"#{count} bookings"} > {count} <% end %>
<%= if @multi_day_mode do %> {if @start_date == date, do: echo_label("Start")} {if @end_date == date, do: echo_label("End")} <% end %> <%= if Date.compare(date, Date.utc_today()) != :lt do %> <% end %>
<% end %>
""" end defp day_classes(date, %{ start_date: start_date, end_date: end_date, selected_date: selected_date, multi_day_mode: multi }) do is_past = Date.compare(date, Date.utc_today()) == :lt is_start = start_date == date is_end = end_date == date is_sel = selected_date == date in_range = CalendarExt.date_in_range?(date, start_date, end_date) base = "relative aspect-square flex flex-col p-2 transition-all border border-slate-200 dark:border-slate-700 " cond do is_past -> base <> "bg-slate-50 dark:bg-slate-800/50 text-slate-300 cursor-not-allowed" is_start -> base <> "bg-primary text-white rounded-l-lg z-10 shadow-md" is_end -> base <> "bg-primary text-white rounded-r-lg z-10 shadow-md" in_range && multi -> base <> "bg-primary/20 text-slate-900 dark:text-white" is_sel -> base <> "bg-primary text-white rounded-lg shadow-md" true -> base <> "bg-white dark:bg-slate-800 hover:bg-slate-50 text-slate-700 dark:text-slate-200" end end defp echo_label(text) do assigns = %{text: text} ~H""" {@text} """ end end