mirror of
https://codeberg.org/JasterV/spazio-solazzo.git
synced 2026-04-26 18:20:03 +00:00
refactor: booking start time and end time
This commit is contained in:
parent
9136c9ae6d
commit
b48af6d3e6
23 changed files with 1098 additions and 158 deletions
|
|
@ -46,10 +46,18 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
argument :space_id, :uuid, allow_nil?: false
|
||||
argument :date, :date, allow_nil?: false
|
||||
|
||||
filter expr(
|
||||
space_id == ^arg(:space_id) and date == ^arg(:date) and
|
||||
state == :accepted
|
||||
)
|
||||
prepare fn query, _ctx ->
|
||||
date = Ash.Query.get_argument(query, :date)
|
||||
day_start = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
day_end = DateTime.new!(date, ~T[23:59:59], "Etc/UTC")
|
||||
|
||||
query
|
||||
|> Ash.Query.filter(
|
||||
start_datetime < ^day_end and end_datetime > ^day_start and state == :accepted
|
||||
)
|
||||
end
|
||||
|
||||
filter expr(space_id == ^arg(:space_id))
|
||||
end
|
||||
|
||||
read :list_booking_requests do
|
||||
|
|
@ -73,8 +81,14 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end
|
||||
|
||||
case Ash.Query.get_argument(query, :date) do
|
||||
nil -> query
|
||||
date -> Ash.Query.filter(query, date == ^date)
|
||||
nil ->
|
||||
query
|
||||
|
||||
date ->
|
||||
day_start = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
|
||||
day_end = DateTime.new!(date, ~T[23:59:59], "Etc/UTC")
|
||||
|
||||
Ash.Query.filter(query, start_datetime < ^day_end and end_datetime > ^day_start)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -83,7 +97,6 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
filter expr(state == :requested)
|
||||
end
|
||||
|
||||
|
||||
create :create do
|
||||
argument :space_id, :uuid, allow_nil?: false
|
||||
argument :user_id, :uuid, allow_nil?: true
|
||||
|
|
@ -132,19 +145,19 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end
|
||||
|
||||
change fn changeset, _ctx ->
|
||||
date = Ash.Changeset.get_argument(changeset, :date)
|
||||
start_time = Ash.Changeset.get_argument(changeset, :start_time)
|
||||
end_time = Ash.Changeset.get_argument(changeset, :end_time)
|
||||
|
||||
start_datetime = DateTime.new!(date, start_time, "Etc/UTC")
|
||||
end_datetime = DateTime.new!(date, end_time, "Etc/UTC")
|
||||
|
||||
changeset
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
:date,
|
||||
Ash.Changeset.get_argument(changeset, :date)
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
:start_time,
|
||||
Ash.Changeset.get_argument(changeset, :start_time)
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
:end_time,
|
||||
Ash.Changeset.get_argument(changeset, :end_time)
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(:start_datetime, start_datetime)
|
||||
|> Ash.Changeset.force_change_attribute(:end_datetime, end_datetime)
|
||||
|> Ash.Changeset.force_change_attribute(:date, date)
|
||||
|> Ash.Changeset.force_change_attribute(:start_time, start_time)
|
||||
|> Ash.Changeset.force_change_attribute(:end_time, end_time)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
:customer_name,
|
||||
Ash.Changeset.get_argument(changeset, :customer_name)
|
||||
|
|
@ -173,9 +186,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
customer_phone: booking.customer_phone,
|
||||
customer_comment: booking.customer_comment,
|
||||
space_name: booking.space.name,
|
||||
date: Calendar.strftime(booking.date, "%A, %B %d"),
|
||||
start_time: booking.start_time,
|
||||
end_time: booking.end_time
|
||||
start_datetime: booking.start_datetime,
|
||||
end_datetime: booking.end_datetime
|
||||
}
|
||||
|> RequestCreatedEmailWorker.new()
|
||||
|> Oban.insert!()
|
||||
|
|
@ -237,6 +249,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end_time = DateTime.to_time(end_datetime)
|
||||
|
||||
changeset
|
||||
|> Ash.Changeset.force_change_attribute(:start_datetime, start_datetime)
|
||||
|> Ash.Changeset.force_change_attribute(:end_datetime, end_datetime)
|
||||
|> Ash.Changeset.force_change_attribute(:date, date)
|
||||
|> Ash.Changeset.force_change_attribute(:start_time, start_time)
|
||||
|> Ash.Changeset.force_change_attribute(:end_time, end_time)
|
||||
|
|
@ -274,9 +288,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
customer_email: booking.customer_email,
|
||||
customer_phone: booking.customer_phone,
|
||||
space_name: booking.space.name,
|
||||
date: Calendar.strftime(booking.date, "%A, %B %d"),
|
||||
start_time: booking.start_time,
|
||||
end_time: booking.end_time,
|
||||
start_datetime: booking.start_datetime,
|
||||
end_datetime: booking.end_datetime,
|
||||
action: "accepted"
|
||||
}
|
||||
|> AdminActionEmailWorker.new()
|
||||
|
|
@ -306,9 +319,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
customer_email: booking.customer_email,
|
||||
customer_phone: booking.customer_phone,
|
||||
space_name: booking.space.name,
|
||||
date: Calendar.strftime(booking.date, "%A, %B %d"),
|
||||
start_time: booking.start_time,
|
||||
end_time: booking.end_time,
|
||||
start_datetime: booking.start_datetime,
|
||||
end_datetime: booking.end_datetime,
|
||||
action: "rejected",
|
||||
rejection_reason: booking.rejection_reason
|
||||
}
|
||||
|
|
@ -339,9 +351,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
customer_email: booking.customer_email,
|
||||
customer_phone: booking.customer_phone,
|
||||
space_name: booking.space.name,
|
||||
date: Calendar.strftime(booking.date, "%A, %B %d"),
|
||||
start_time: booking.start_time,
|
||||
end_time: booking.end_time,
|
||||
start_datetime: booking.start_datetime,
|
||||
end_datetime: booking.end_datetime,
|
||||
cancellation_reason: booking.cancellation_reason
|
||||
}
|
||||
|> UserCancellationEmailWorker.new()
|
||||
|
|
@ -368,10 +379,15 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
start_time = input.arguments.start_time
|
||||
end_time = input.arguments.end_time
|
||||
|
||||
start_datetime = DateTime.new!(date, start_time, "Etc/UTC")
|
||||
end_datetime = DateTime.new!(date, end_time, "Etc/UTC")
|
||||
|
||||
query =
|
||||
__MODULE__
|
||||
|> Ash.Query.filter(date == ^date)
|
||||
|> Ash.Query.filter(state == :requested or state == :accepted)
|
||||
|> Ash.Query.filter(
|
||||
start_datetime < ^end_datetime and end_datetime > ^start_datetime and
|
||||
(state == :requested or state == :accepted)
|
||||
)
|
||||
|
||||
query =
|
||||
if space_id do
|
||||
|
|
@ -382,15 +398,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
|
||||
case Ash.read(query) do
|
||||
{:ok, bookings} ->
|
||||
# Filter overlapping bookings
|
||||
overlapping_bookings =
|
||||
Enum.filter(bookings, fn booking ->
|
||||
Time.compare(booking.start_time, end_time) == :lt and
|
||||
Time.compare(start_time, booking.end_time) == :lt
|
||||
end)
|
||||
|
||||
pending_count = Enum.count(overlapping_bookings, &(&1.state == :requested))
|
||||
approved_count = Enum.count(overlapping_bookings, &(&1.state == :accepted))
|
||||
pending_count = Enum.count(bookings, &(&1.state == :requested))
|
||||
approved_count = Enum.count(bookings, &(&1.state == :accepted))
|
||||
|
||||
{:ok, %{pending: pending_count, approved: approved_count}}
|
||||
|
||||
|
|
@ -431,6 +440,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
attribute :start_datetime, :datetime, allow_nil?: false
|
||||
attribute :end_datetime, :datetime, allow_nil?: false
|
||||
attribute :date, :date, allow_nil?: false
|
||||
attribute :customer_name, :string, allow_nil?: false
|
||||
attribute :customer_email, :string, allow_nil?: false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking.AdminActionEmailWorker do
|
|||
use Oban.Worker, queue: :booking_email, max_attempts: 3
|
||||
|
||||
alias SpazioSolazzo.BookingSystem.Booking.Email
|
||||
alias SpazioSolazzo.CalendarExt
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{
|
||||
|
|
@ -15,21 +16,25 @@ defmodule SpazioSolazzo.BookingSystem.Booking.AdminActionEmailWorker do
|
|||
"customer_email" => customer_email,
|
||||
"customer_phone" => customer_phone,
|
||||
"space_name" => space_name,
|
||||
"date" => date,
|
||||
"start_time" => start_time,
|
||||
"end_time" => end_time,
|
||||
"start_datetime" => start_datetime_str,
|
||||
"end_datetime" => end_datetime_str,
|
||||
"action" => "accepted"
|
||||
}
|
||||
}) do
|
||||
{:ok, start_datetime, _} = DateTime.from_iso8601(start_datetime_str)
|
||||
{:ok, end_datetime, _} = DateTime.from_iso8601(end_datetime_str)
|
||||
|
||||
%{
|
||||
booking_id: booking_id,
|
||||
customer_name: customer_name,
|
||||
customer_email: customer_email,
|
||||
customer_phone: customer_phone,
|
||||
space_name: space_name,
|
||||
date: date,
|
||||
start_time: start_time,
|
||||
end_time: end_time
|
||||
start_datetime: start_datetime,
|
||||
end_datetime: end_datetime,
|
||||
date: CalendarExt.format_datetime_date_only(start_datetime),
|
||||
start_time: DateTime.to_time(start_datetime),
|
||||
end_time: DateTime.to_time(end_datetime)
|
||||
}
|
||||
|> Email.booking_request_approved()
|
||||
|> SpazioSolazzo.Mailer.deliver!()
|
||||
|
|
@ -43,20 +48,24 @@ defmodule SpazioSolazzo.BookingSystem.Booking.AdminActionEmailWorker do
|
|||
"customer_name" => customer_name,
|
||||
"customer_email" => customer_email,
|
||||
"space_name" => space_name,
|
||||
"date" => date,
|
||||
"start_time" => start_time,
|
||||
"end_time" => end_time,
|
||||
"start_datetime" => start_datetime_str,
|
||||
"end_datetime" => end_datetime_str,
|
||||
"action" => "rejected",
|
||||
"rejection_reason" => rejection_reason
|
||||
}
|
||||
}) do
|
||||
{:ok, start_datetime, _} = DateTime.from_iso8601(start_datetime_str)
|
||||
{:ok, end_datetime, _} = DateTime.from_iso8601(end_datetime_str)
|
||||
|
||||
%{
|
||||
customer_name: customer_name,
|
||||
customer_email: customer_email,
|
||||
space_name: space_name,
|
||||
date: date,
|
||||
start_time: start_time,
|
||||
end_time: end_time,
|
||||
start_datetime: start_datetime,
|
||||
end_datetime: end_datetime,
|
||||
date: CalendarExt.format_datetime_date_only(start_datetime),
|
||||
start_time: DateTime.to_time(start_datetime),
|
||||
end_time: DateTime.to_time(end_datetime),
|
||||
rejection_reason: rejection_reason
|
||||
}
|
||||
|> Email.booking_request_rejected()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorker do
|
|||
use Oban.Worker, queue: :booking_email, max_attempts: 3
|
||||
|
||||
alias SpazioSolazzo.BookingSystem.Booking.Email
|
||||
alias SpazioSolazzo.CalendarExt
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{
|
||||
|
|
@ -17,11 +18,13 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorker do
|
|||
"customer_phone" => customer_phone,
|
||||
"customer_comment" => customer_comment,
|
||||
"space_name" => space_name,
|
||||
"date" => date,
|
||||
"start_time" => start_time,
|
||||
"end_time" => end_time
|
||||
"start_datetime" => start_datetime_str,
|
||||
"end_datetime" => end_datetime_str
|
||||
}
|
||||
}) do
|
||||
{:ok, start_datetime, _} = DateTime.from_iso8601(start_datetime_str)
|
||||
{:ok, end_datetime, _} = DateTime.from_iso8601(end_datetime_str)
|
||||
|
||||
email_data = %{
|
||||
booking_id: booking_id,
|
||||
customer_name: customer_name,
|
||||
|
|
@ -29,9 +32,11 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorker do
|
|||
customer_phone: customer_phone,
|
||||
customer_comment: customer_comment,
|
||||
space_name: space_name,
|
||||
date: date,
|
||||
start_time: start_time,
|
||||
end_time: end_time,
|
||||
start_datetime: start_datetime,
|
||||
end_datetime: end_datetime,
|
||||
date: CalendarExt.format_datetime_date_only(start_datetime),
|
||||
start_time: DateTime.to_time(start_datetime),
|
||||
end_time: DateTime.to_time(end_datetime),
|
||||
admin_email: admin_email()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking.UserCancellationEmailWorker do
|
|||
use Oban.Worker, queue: :booking_email, max_attempts: 3
|
||||
|
||||
alias SpazioSolazzo.BookingSystem.Booking.Email
|
||||
alias SpazioSolazzo.CalendarExt
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{
|
||||
|
|
@ -14,20 +15,24 @@ defmodule SpazioSolazzo.BookingSystem.Booking.UserCancellationEmailWorker do
|
|||
"customer_email" => customer_email,
|
||||
"customer_phone" => customer_phone,
|
||||
"space_name" => space_name,
|
||||
"date" => date,
|
||||
"start_time" => start_time,
|
||||
"end_time" => end_time,
|
||||
"start_datetime" => start_datetime_str,
|
||||
"end_datetime" => end_datetime_str,
|
||||
"cancellation_reason" => cancellation_reason
|
||||
}
|
||||
}) do
|
||||
{:ok, start_datetime, _} = DateTime.from_iso8601(start_datetime_str)
|
||||
{:ok, end_datetime, _} = DateTime.from_iso8601(end_datetime_str)
|
||||
|
||||
%{
|
||||
customer_name: customer_name,
|
||||
customer_email: customer_email,
|
||||
customer_phone: customer_phone,
|
||||
space_name: space_name,
|
||||
date: date,
|
||||
start_time: start_time,
|
||||
end_time: end_time,
|
||||
start_datetime: start_datetime,
|
||||
end_datetime: end_datetime,
|
||||
date: CalendarExt.format_datetime_date_only(start_datetime),
|
||||
start_time: DateTime.to_time(start_datetime),
|
||||
end_time: DateTime.to_time(end_datetime),
|
||||
cancellation_reason: cancellation_reason,
|
||||
admin_email: admin_email()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,25 +55,24 @@ defmodule SpazioSolazzo.BookingSystem.Space do
|
|||
start_time_arg = input.arguments.start_time
|
||||
end_time_arg = input.arguments.end_time
|
||||
|
||||
start_datetime = DateTime.new!(date_arg, start_time_arg, "Etc/UTC")
|
||||
end_datetime = DateTime.new!(date_arg, end_time_arg, "Etc/UTC")
|
||||
|
||||
# Load the space
|
||||
case Ash.get(__MODULE__, space_id) do
|
||||
{:ok, space} ->
|
||||
# Get accepted bookings for this space on the given date
|
||||
# Get accepted bookings for this space that overlap with the requested time slot
|
||||
query =
|
||||
SpazioSolazzo.BookingSystem.Booking
|
||||
|> Ash.Query.filter(
|
||||
expr(space_id == ^space_id and date == ^date_arg and state == :accepted)
|
||||
expr(
|
||||
space_id == ^space_id and state == :accepted and start_datetime < ^end_datetime and
|
||||
end_datetime > ^start_datetime
|
||||
)
|
||||
)
|
||||
|
||||
case Ash.read(query) do
|
||||
{:ok, bookings} ->
|
||||
# Filter overlapping bookings
|
||||
overlapping_bookings =
|
||||
Enum.filter(bookings, fn booking ->
|
||||
Time.compare(booking.start_time, end_time_arg) == :lt and
|
||||
Time.compare(start_time_arg, booking.end_time) == :lt
|
||||
end)
|
||||
|
||||
{:ok, overlapping_bookings} ->
|
||||
current_count = length(overlapping_bookings)
|
||||
|
||||
availability =
|
||||
|
|
|
|||
|
|
@ -13,4 +13,99 @@ defmodule SpazioSolazzo.CalendarExt do
|
|||
|
||||
"#{start_time} - #{end_time}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a datetime as "Feb 10, 2026"
|
||||
"""
|
||||
def format_datetime_date(%DateTime{} = datetime) do
|
||||
Calendar.strftime(datetime, "%b %d, %Y")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a datetime as "Monday, February 10, 2026"
|
||||
"""
|
||||
def format_datetime_date_long(%DateTime{} = datetime) do
|
||||
Calendar.strftime(datetime, "%A, %B %d, %Y")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a datetime as "Monday, February 10" (for emails)
|
||||
"""
|
||||
def format_datetime_date_only(%DateTime{} = datetime) do
|
||||
Calendar.strftime(datetime, "%A, %B %d")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a time or datetime as "9:00 AM"
|
||||
"""
|
||||
def format_time(%DateTime{} = datetime) do
|
||||
datetime
|
||||
|> DateTime.to_time()
|
||||
|> format_time()
|
||||
end
|
||||
|
||||
def format_time(%Time{} = time) do
|
||||
Calendar.strftime(time, "%I:%M %p")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a time range as "9:00 AM - 5:00 PM"
|
||||
Takes two Time or DateTime structs
|
||||
"""
|
||||
def format_time_range(%DateTime{} = start_dt, %DateTime{} = end_dt) do
|
||||
start_time = DateTime.to_time(start_dt)
|
||||
end_time = DateTime.to_time(end_dt)
|
||||
format_time_range(start_time, end_time)
|
||||
end
|
||||
|
||||
def format_time_range(%Time{} = start_time, %Time{} = end_time) do
|
||||
"#{format_time(start_time)} - #{format_time(end_time)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a booking spans multiple days
|
||||
"""
|
||||
def is_multi_day?(%DateTime{} = start_datetime, %DateTime{} = end_datetime) do
|
||||
start_date = DateTime.to_date(start_datetime)
|
||||
end_date = DateTime.to_date(end_datetime)
|
||||
Date.compare(start_date, end_date) != :eq
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats a datetime range handling both single-day and multi-day bookings.
|
||||
|
||||
Single-day: "Feb 10, 2026 9:00 AM - 5:00 PM"
|
||||
Multi-day: "Feb 10, 2026 9:00 AM - Feb 15, 2026 5:00 PM"
|
||||
"""
|
||||
def format_datetime_range(%DateTime{} = start_datetime, %DateTime{} = end_datetime) do
|
||||
if is_multi_day?(start_datetime, end_datetime) do
|
||||
"#{format_datetime_date(start_datetime)} #{format_time(start_datetime)} - #{format_datetime_date(end_datetime)} #{format_time(end_datetime)}"
|
||||
else
|
||||
"#{format_datetime_date(start_datetime)} #{format_time_range(start_datetime, end_datetime)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the start portion of a datetime range for table display
|
||||
|
||||
Single-day: "Feb 10, 2026 9:00 AM"
|
||||
Multi-day: "Feb 10, 2026 9:00 AM"
|
||||
"""
|
||||
def format_datetime_range_start(%DateTime{} = datetime) do
|
||||
"#{format_datetime_date(datetime)} #{format_time(datetime)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats the end portion of a datetime range for table display
|
||||
|
||||
Single-day: "5:00 PM" (date not shown)
|
||||
Multi-day: "Feb 15, 2026 5:00 PM"
|
||||
"""
|
||||
def format_datetime_range_end(%DateTime{} = start_datetime, %DateTime{} = end_datetime) do
|
||||
if is_multi_day?(start_datetime, end_datetime) do
|
||||
"#{format_datetime_date(end_datetime)} #{format_time(end_datetime)}"
|
||||
else
|
||||
format_time(end_datetime)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("toggle_multi_day", %{"value" => value}, socket) do
|
||||
multi_day = value == "on"
|
||||
def handle_event("toggle_multi_day", _params, socket) do
|
||||
# Toggle the current state
|
||||
multi_day = !socket.assigns.multi_day_mode
|
||||
|
||||
socket =
|
||||
socket
|
||||
|
|
@ -238,12 +239,13 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
defp is_end_date?(_date, _, nil), do: false
|
||||
defp is_end_date?(date, _, end_date), do: Date.compare(date, end_date) == :eq
|
||||
|
||||
defp day_classes(date, socket) do
|
||||
capacity = Map.get(socket.assigns.day_capacities, date, :available)
|
||||
defp day_classes(date, assigns) do
|
||||
# Extract capacity status for the given date
|
||||
capacity = Map.get(assigns.day_capacities, date, :available)
|
||||
is_past = Date.compare(date, Date.utc_today()) == :lt
|
||||
in_range = day_in_range?(date, socket.assigns.selected_date, socket.assigns.start_date, socket.assigns.end_date)
|
||||
is_start = is_start_date?(date, socket.assigns.start_date, socket.assigns.end_date)
|
||||
is_end = is_end_date?(date, socket.assigns.start_date, socket.assigns.end_date)
|
||||
in_range = day_in_range?(date, assigns.selected_date, assigns.start_date, assigns.end_date)
|
||||
is_start = is_start_date?(date, assigns.start_date, assigns.end_date)
|
||||
is_end = is_end_date?(date, assigns.start_date, assigns.end_date)
|
||||
|
||||
base = "aspect-square flex flex-col items-center justify-center transition-all"
|
||||
|
||||
|
|
@ -254,7 +256,7 @@ defmodule SpazioSolazzoWeb.Admin.AdminCalendarComponent do
|
|||
capacity == :over_real_capacity ->
|
||||
[base, "bg-red-50 dark:bg-red-900/20 text-slate-400 dark:text-slate-500 border border-red-300 dark:border-red-800/30 cursor-not-allowed"]
|
||||
|
||||
in_range && socket.assigns.multi_day_mode && socket.assigns.end_date != nil ->
|
||||
in_range && assigns.multi_day_mode && assigns.end_date != nil ->
|
||||
cond do
|
||||
is_start ->
|
||||
[base, "rounded-l-lg bg-primary text-white shadow-lg shadow-primary/30 relative z-10 hover:scale-105"]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
<div class="flex flex-col gap-4">
|
||||
<%!-- Multi-day mode toggle --%>
|
||||
<div class="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-700">
|
||||
<div
|
||||
class="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-700 cursor-pointer"
|
||||
phx-click="toggle_multi_day"
|
||||
phx-target={@myself}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={"multi-day-#{@id}"}
|
||||
phx-change="toggle_multi_day"
|
||||
phx-target={@myself}
|
||||
checked={@multi_day_mode}
|
||||
class="size-4 rounded border-slate-300 dark:border-slate-600 text-primary focus:ring-primary dark:bg-slate-700"
|
||||
class="size-4 rounded border-slate-300 dark:border-slate-600 text-primary focus:ring-primary dark:bg-slate-700 pointer-events-none"
|
||||
readonly
|
||||
/>
|
||||
<label for={"multi-day-#{@id}"} class="text-sm font-semibold text-slate-700 dark:text-slate-300 cursor-pointer select-none">
|
||||
<label for={"multi-day-#{@id}"} class="text-sm font-semibold text-slate-700 dark:text-slate-300 select-none flex-1">
|
||||
Enable Multi-Day Selection
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -125,10 +125,10 @@
|
|||
Space
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Date
|
||||
Start
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Time
|
||||
End
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Customer
|
||||
|
|
@ -174,14 +174,14 @@
|
|||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<p class="text-sm text-slate-900 dark:text-slate-200">
|
||||
{Calendar.strftime(booking.date, "%b %d, %Y")}
|
||||
{SpazioSolazzo.CalendarExt.format_datetime_range_start(booking.start_datetime)}
|
||||
</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<p class="text-sm text-slate-900 dark:text-slate-200">
|
||||
{Calendar.strftime(booking.start_time, "%H:%M")} - {Calendar.strftime(
|
||||
booking.end_time,
|
||||
"%H:%M"
|
||||
{SpazioSolazzo.CalendarExt.format_datetime_range_end(
|
||||
booking.start_datetime,
|
||||
booking.end_datetime
|
||||
)}
|
||||
</p>
|
||||
</td>
|
||||
|
|
@ -278,10 +278,10 @@
|
|||
Space
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Date
|
||||
Start
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Time
|
||||
End
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-bold text-slate-600 dark:text-slate-400 uppercase tracking-wider">
|
||||
Customer
|
||||
|
|
@ -324,14 +324,14 @@
|
|||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<p class="text-sm text-slate-900 dark:text-slate-200">
|
||||
{Calendar.strftime(booking.date, "%b %d, %Y")}
|
||||
{SpazioSolazzo.CalendarExt.format_datetime_range_start(booking.start_datetime)}
|
||||
</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<p class="text-sm text-slate-900 dark:text-slate-200">
|
||||
{Calendar.strftime(booking.start_time, "%H:%M")} - {Calendar.strftime(
|
||||
booking.end_time,
|
||||
"%H:%M"
|
||||
{SpazioSolazzo.CalendarExt.format_datetime_range_end(
|
||||
booking.start_datetime,
|
||||
booking.end_datetime
|
||||
)}
|
||||
</p>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -66,20 +66,14 @@ defmodule SpazioSolazzoWeb.Admin.WalkInLive do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_event("update_customer_name", %{"value" => value}, socket) do
|
||||
{:noreply, assign(socket, customer_name: value)}
|
||||
end
|
||||
|
||||
def handle_event("update_customer_email", %{"value" => value}, socket) do
|
||||
{:noreply, assign(socket, customer_email: value)}
|
||||
end
|
||||
|
||||
def handle_event("update_customer_phone", %{"value" => value}, socket) do
|
||||
{:noreply, assign(socket, customer_phone: value)}
|
||||
end
|
||||
|
||||
def handle_event("update_customer_comment", %{"value" => value}, socket) do
|
||||
{:noreply, assign(socket, customer_comment: value)}
|
||||
def handle_event("update_customer_details", params, socket) do
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
customer_name: Map.get(params, "customer_name", ""),
|
||||
customer_email: Map.get(params, "customer_email", ""),
|
||||
customer_phone: Map.get(params, "customer_phone", ""),
|
||||
customer_comment: Map.get(params, "customer_comment", "")
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_event("create_booking", _, socket) do
|
||||
|
|
|
|||
|
|
@ -204,14 +204,14 @@
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<div class="space-y-4 max-w-2xl">
|
||||
<.form for={%{}} phx-change="update_customer_details" class="space-y-4 max-w-2xl">
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-400">
|
||||
<.icon name="hero-user" class="w-5 h-5" />
|
||||
</span>
|
||||
<input
|
||||
name="customer_name"
|
||||
value={@customer_name}
|
||||
phx-change="update_customer_name"
|
||||
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-900 dark:text-white text-sm rounded-xl pl-10 pr-4 py-3 focus:ring-2 focus:ring-primary focus:border-primary transition-all placeholder:text-slate-400"
|
||||
placeholder="Customer Name"
|
||||
type="text"
|
||||
|
|
@ -223,8 +223,8 @@
|
|||
<.icon name="hero-envelope" class="w-5 h-5" />
|
||||
</span>
|
||||
<input
|
||||
name="customer_email"
|
||||
value={@customer_email}
|
||||
phx-change="update_customer_email"
|
||||
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-900 dark:text-white text-sm rounded-xl pl-10 pr-4 py-3 focus:ring-2 focus:ring-primary focus:border-primary transition-all placeholder:text-slate-400"
|
||||
placeholder="customer@example.com"
|
||||
type="email"
|
||||
|
|
@ -236,8 +236,8 @@
|
|||
<.icon name="hero-phone" class="w-5 h-5" />
|
||||
</span>
|
||||
<input
|
||||
name="customer_phone"
|
||||
value={@customer_phone}
|
||||
phx-change="update_customer_phone"
|
||||
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-900 dark:text-white text-sm rounded-xl pl-10 pr-4 py-3 focus:ring-2 focus:ring-primary focus:border-primary transition-all placeholder:text-slate-400"
|
||||
placeholder="Customer Phone Number (Optional)"
|
||||
type="tel"
|
||||
|
|
@ -246,38 +246,25 @@
|
|||
|
||||
<div class="relative">
|
||||
<textarea
|
||||
name="customer_comment"
|
||||
value={@customer_comment}
|
||||
phx-change="update_customer_comment"
|
||||
rows="3"
|
||||
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-900 dark:text-white text-sm rounded-xl px-4 py-3 focus:ring-2 focus:ring-primary focus:border-primary transition-all placeholder:text-slate-400"
|
||||
placeholder="Additional notes or comments (Optional)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</.form>
|
||||
</article>
|
||||
|
||||
<%!-- Submit button --%>
|
||||
<div class="sticky bottom-6 z-20">
|
||||
<div class="bg-slate-900 dark:bg-primary rounded-2xl p-4 md:p-6 shadow-2xl border border-slate-800 dark:border-slate-600 flex flex-col sm:flex-row justify-between items-center gap-4 backdrop-blur-xl bg-opacity-95 dark:bg-opacity-95">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-yellow-500/20 p-2 rounded-full hidden sm:block">
|
||||
<.icon name="hero-currency-dollar" class="w-5 h-5 text-yellow-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white font-bold">Ready to book?</p>
|
||||
<p class="text-xs text-slate-300 dark:text-slate-200">
|
||||
Payment collected upon arrival.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
phx-click="create_booking"
|
||||
class="w-full sm:w-auto flex items-center justify-center gap-2 overflow-hidden rounded-xl h-11 px-8 bg-yellow-500 hover:bg-yellow-400 transition-colors text-slate-900 text-sm font-bold shadow-lg shadow-yellow-500/20"
|
||||
>
|
||||
<span>Confirm Booking</span>
|
||||
<.icon name="hero-arrow-right" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-center pt-4">
|
||||
<button
|
||||
phx-click="create_booking"
|
||||
class="w-full sm:w-auto flex items-center justify-center gap-2 overflow-hidden rounded-xl h-12 px-10 bg-primary hover:bg-primary/90 transition-colors text-white text-base font-bold shadow-lg shadow-primary/30"
|
||||
>
|
||||
<span>Create Booking</span>
|
||||
<.icon name="hero-check" class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ defmodule SpazioSolazzo.Repo.Migrations.CreateBaseResources do
|
|||
|
||||
create table(:bookings, primary_key: false) do
|
||||
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
|
||||
add :start_datetime, :utc_datetime, null: false
|
||||
add :end_datetime, :utc_datetime, null: false
|
||||
add :date, :date, null: false
|
||||
add :customer_name, :text, null: false
|
||||
add :customer_email, :text, null: false
|
||||
|
|
@ -12,6 +12,30 @@
|
|||
"source": "id",
|
||||
"type": "uuid"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "start_datetime",
|
||||
"type": "utc_datetime"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"precision": null,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"scale": null,
|
||||
"size": null,
|
||||
"source": "end_datetime",
|
||||
"type": "utc_datetime"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "nil",
|
||||
|
|
@ -224,7 +248,7 @@
|
|||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "0EFE49884DF5DC66BF3F1E125132A2F3E18DD66AC732121184A93707260C5225",
|
||||
"hash": "CBA4F1C24A9B1AB8A2E4EF42917C1DE5F143C74D0D175D1A29156ECCE6FD5660",
|
||||
"identities": [],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
|
|
@ -13,9 +13,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorkerTest do
|
|||
"customer_phone" => "+1234567890",
|
||||
"customer_comment" => "Test comment",
|
||||
"space_name" => "Coworking Space",
|
||||
"date" => "Monday, February 02",
|
||||
"start_time" => ~T[09:00:00],
|
||||
"end_time" => ~T[13:00:00]
|
||||
"start_datetime" => "2026-02-02T09:00:00Z",
|
||||
"end_datetime" => "2026-02-02T13:00:00Z"
|
||||
}
|
||||
|
||||
assert :ok = perform_job(RequestCreatedEmailWorker, job_args)
|
||||
|
|
@ -35,9 +34,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorkerTest do
|
|||
"customer_phone" => "+1234567890",
|
||||
"customer_comment" => "Test comment",
|
||||
"space_name" => "Coworking Space",
|
||||
"date" => "Monday, February 02",
|
||||
"start_time" => ~T[09:00:00],
|
||||
"end_time" => ~T[13:00:00]
|
||||
"start_datetime" => "2026-02-02T09:00:00Z",
|
||||
"end_datetime" => "2026-02-02T13:00:00Z"
|
||||
}
|
||||
|
||||
admin_email = Application.get_env(:spazio_solazzo, :admin_email)
|
||||
|
|
@ -59,9 +57,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorkerTest do
|
|||
"customer_phone" => "+1234567890",
|
||||
"customer_comment" => "Another test",
|
||||
"space_name" => "Meeting Room",
|
||||
"date" => "Tuesday, February 03",
|
||||
"start_time" => ~T[14:00:00],
|
||||
"end_time" => ~T[18:00:00]
|
||||
"start_datetime" => "2026-02-03T14:00:00Z",
|
||||
"end_datetime" => "2026-02-03T18:00:00Z"
|
||||
}
|
||||
|
||||
admin_email = Application.get_env(:spazio_solazzo, :admin_email)
|
||||
|
|
@ -85,9 +82,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorkerTest do
|
|||
"customer_phone" => "+1234567890",
|
||||
"customer_comment" => "Test",
|
||||
"space_name" => "Music Room",
|
||||
"date" => "Wednesday, February 04",
|
||||
"start_time" => ~T[10:00:00],
|
||||
"end_time" => ~T[12:00:00]
|
||||
"start_datetime" => "2026-02-04T10:00:00Z",
|
||||
"end_datetime" => "2026-02-04T12:00:00Z"
|
||||
}
|
||||
|
||||
assert :ok = perform_job(RequestCreatedEmailWorker, job_args)
|
||||
|
|
@ -110,9 +106,8 @@ defmodule SpazioSolazzo.BookingSystem.Booking.RequestCreatedEmailWorkerTest do
|
|||
"customer_phone" => "+1234567890",
|
||||
"customer_comment" => "Admin comment",
|
||||
"space_name" => "Coworking Space",
|
||||
"date" => "Thursday, February 05",
|
||||
"start_time" => ~T[09:00:00],
|
||||
"end_time" => ~T[11:00:00]
|
||||
"start_datetime" => "2026-02-05T09:00:00Z",
|
||||
"end_datetime" => "2026-02-05T11:00:00Z"
|
||||
}
|
||||
|
||||
admin_email = Application.get_env(:spazio_solazzo, :admin_email)
|
||||
|
|
|
|||
481
test/spazio_solazzo/booking_system/multi_day_booking_test.exs
Normal file
481
test/spazio_solazzo/booking_system/multi_day_booking_test.exs
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.MultiDayBookingTest do
|
||||
@moduledoc """
|
||||
Tests for multi-day booking functionality using datetime fields.
|
||||
Verifies that bookings can span multiple days and that datetime range
|
||||
queries work correctly for availability checking and listing.
|
||||
"""
|
||||
|
||||
use SpazioSolazzo.DataCase, async: true
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
setup do
|
||||
{:ok, space} =
|
||||
BookingSystem.create_space(
|
||||
"Coworking",
|
||||
"coworking",
|
||||
"Coworking space for testing",
|
||||
5,
|
||||
10
|
||||
)
|
||||
|
||||
%{space: space}
|
||||
end
|
||||
|
||||
describe "multi-day walk-in bookings" do
|
||||
test "can create a multi-day booking spanning 3 days", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 3)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"John Doe",
|
||||
"john@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
assert booking.start_datetime == start_datetime
|
||||
assert booking.end_datetime == end_datetime
|
||||
assert booking.state == :accepted
|
||||
assert booking.customer_name == "John Doe"
|
||||
end
|
||||
|
||||
test "multi-day booking appears in queries for all days it spans", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 4)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[10:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[17:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Jane Smith",
|
||||
"jane@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Should appear on start date
|
||||
{:ok, day1_bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, start_date)
|
||||
assert length(day1_bookings) == 1
|
||||
|
||||
# Should appear on middle date
|
||||
middle_date = Date.add(start_date, 1)
|
||||
{:ok, day2_bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, middle_date)
|
||||
assert length(day2_bookings) == 1
|
||||
|
||||
# Should appear on end date
|
||||
{:ok, day4_bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, end_date)
|
||||
assert length(day4_bookings) == 1
|
||||
|
||||
# Should not appear on day after end date
|
||||
day_after = Date.add(end_date, 1)
|
||||
{:ok, day_after_bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, day_after)
|
||||
assert length(day_after_bookings) == 0
|
||||
end
|
||||
|
||||
test "multi-day booking correctly counts toward availability on all days", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 3)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Test User",
|
||||
"test@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check availability on start date
|
||||
{:ok, availability_day1} =
|
||||
BookingSystem.check_availability(space.id, start_date, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
# Should show reduced availability due to the multi-day booking
|
||||
assert availability_day1 in [:available, :over_public_capacity]
|
||||
|
||||
# Check availability on middle date
|
||||
middle_date = Date.add(start_date, 1)
|
||||
|
||||
{:ok, availability_day2} =
|
||||
BookingSystem.check_availability(space.id, middle_date, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert availability_day2 in [:available, :over_public_capacity]
|
||||
|
||||
# Check availability on end date
|
||||
{:ok, availability_day3} =
|
||||
BookingSystem.check_availability(space.id, end_date, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert availability_day3 in [:available, :over_public_capacity]
|
||||
end
|
||||
|
||||
test "multiple overlapping multi-day bookings correctly fill capacity", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 3)
|
||||
|
||||
# Create 5 multi-day bookings (public capacity)
|
||||
for i <- 1..5 do
|
||||
start_datetime = DateTime.new!(start_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"User #{i}",
|
||||
"user#{i}@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
end
|
||||
|
||||
# Check that public capacity is reached on middle day
|
||||
middle_date = Date.add(start_date, 1)
|
||||
|
||||
{:ok, availability} =
|
||||
BookingSystem.check_availability(space.id, middle_date, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert availability == :over_public_capacity
|
||||
end
|
||||
|
||||
test "can have both single-day and multi-day bookings on the same day", %{space: space} do
|
||||
date = Date.add(Date.utc_today(), 1)
|
||||
|
||||
# Create a multi-day booking
|
||||
multi_start = DateTime.new!(date, ~T[09:00:00], "Etc/UTC")
|
||||
multi_end = DateTime.new!(Date.add(date, 2), ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _multi_booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
multi_start,
|
||||
multi_end,
|
||||
"Multi Day User",
|
||||
"multi@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Create a single-day booking on the same date
|
||||
single_start = DateTime.new!(date, ~T[10:00:00], "Etc/UTC")
|
||||
single_end = DateTime.new!(date, ~T[16:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _single_booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
single_start,
|
||||
single_end,
|
||||
"Single Day User",
|
||||
"single@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Both should appear in the query for that date
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, date)
|
||||
assert length(bookings) == 2
|
||||
|
||||
customer_names = Enum.map(bookings, & &1.customer_name)
|
||||
assert "Multi Day User" in customer_names
|
||||
assert "Single Day User" in customer_names
|
||||
end
|
||||
|
||||
test "slot booking counts correctly include multi-day bookings", %{space: space} do
|
||||
date = Date.add(Date.utc_today(), 1)
|
||||
|
||||
# Create a multi-day booking that includes this date
|
||||
multi_start = DateTime.new!(Date.add(date, -1), ~T[09:00:00], "Etc/UTC")
|
||||
multi_end = DateTime.new!(Date.add(date, 1), ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _multi_booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
multi_start,
|
||||
multi_end,
|
||||
"Multi Day User",
|
||||
"multi@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Create a single-day booking on the same date
|
||||
single_start = DateTime.new!(date, ~T[10:00:00], "Etc/UTC")
|
||||
single_end = DateTime.new!(date, ~T[16:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _single_booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
single_start,
|
||||
single_end,
|
||||
"Single Day User",
|
||||
"single@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Get slot counts for a time range on that date
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, date, ~T[11:00:00], ~T[15:00:00])
|
||||
|
||||
# Should count both bookings
|
||||
assert counts.approved == 2
|
||||
assert counts.pending == 0
|
||||
end
|
||||
|
||||
test "multi-day booking with different start and end times", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 5)
|
||||
|
||||
# Booking starts at 2 PM on day 1 and ends at 11 AM on day 5
|
||||
start_datetime = DateTime.new!(start_date, ~T[14:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[11:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Extended Stay User",
|
||||
"extended@example.com",
|
||||
"+1234567890",
|
||||
"Long term booking"
|
||||
)
|
||||
|
||||
assert booking.start_datetime == start_datetime
|
||||
assert booking.end_datetime == end_datetime
|
||||
assert booking.customer_phone == "+1234567890"
|
||||
assert booking.customer_comment == "Long term booking"
|
||||
|
||||
# Verify it appears on all days
|
||||
for day_offset <- 0..4 do
|
||||
check_date = Date.add(start_date, day_offset)
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, check_date)
|
||||
assert length(bookings) == 1
|
||||
assert hd(bookings).customer_name == "Extended Stay User"
|
||||
end
|
||||
end
|
||||
|
||||
test "multi-day booking does not appear on days outside its range", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 5)
|
||||
end_date = Date.add(Date.utc_today(), 7)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Range Test User",
|
||||
"range@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Should not appear on day before start
|
||||
day_before = Date.add(start_date, -1)
|
||||
{:ok, bookings_before} = BookingSystem.list_accepted_space_bookings_by_date(space.id, day_before)
|
||||
assert length(bookings_before) == 0
|
||||
|
||||
# Should appear on start date
|
||||
{:ok, bookings_start} = BookingSystem.list_accepted_space_bookings_by_date(space.id, start_date)
|
||||
assert length(bookings_start) == 1
|
||||
|
||||
# Should appear on end date
|
||||
{:ok, bookings_end} = BookingSystem.list_accepted_space_bookings_by_date(space.id, end_date)
|
||||
assert length(bookings_end) == 1
|
||||
|
||||
# Should not appear on day after end
|
||||
day_after = Date.add(end_date, 1)
|
||||
{:ok, bookings_after} = BookingSystem.list_accepted_space_bookings_by_date(space.id, day_after)
|
||||
assert length(bookings_after) == 0
|
||||
end
|
||||
|
||||
test "very long multi-day booking (30 days)", %{space: space} do
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 30)
|
||||
|
||||
start_datetime = DateTime.new!(start_date, ~T[09:00:00], "Etc/UTC")
|
||||
end_datetime = DateTime.new!(end_date, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, booking} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
start_datetime,
|
||||
end_datetime,
|
||||
"Long Term User",
|
||||
"longterm@example.com",
|
||||
nil,
|
||||
"Monthly booking"
|
||||
)
|
||||
|
||||
assert booking.start_datetime == start_datetime
|
||||
assert booking.end_datetime == end_datetime
|
||||
|
||||
# Spot check a few days
|
||||
for day_offset <- [0, 10, 20, 29] do
|
||||
check_date = Date.add(start_date, day_offset)
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, check_date)
|
||||
assert length(bookings) == 1
|
||||
end
|
||||
|
||||
# Verify it doesn't appear the day after
|
||||
day_after = Date.add(end_date, 1)
|
||||
{:ok, bookings_after} = BookingSystem.list_accepted_space_bookings_by_date(space.id, day_after)
|
||||
assert length(bookings_after) == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "datetime range overlaps" do
|
||||
test "detects overlap when new booking starts during existing booking", %{space: space} do
|
||||
# Existing booking: Day 1-3
|
||||
day1 = Date.add(Date.utc_today(), 1)
|
||||
day2 = Date.add(Date.utc_today(), 2)
|
||||
day3 = Date.add(Date.utc_today(), 3)
|
||||
|
||||
existing_start = DateTime.new!(day1, ~T[09:00:00], "Etc/UTC")
|
||||
existing_end = DateTime.new!(day3, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _existing} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
existing_start,
|
||||
existing_end,
|
||||
"Existing User",
|
||||
"existing@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check overlap on day 2
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, day2, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert counts.approved == 1
|
||||
end
|
||||
|
||||
test "detects overlap when new booking ends during existing booking", %{space: space} do
|
||||
# Existing booking: Day 3-5
|
||||
day3 = Date.add(Date.utc_today(), 3)
|
||||
day4 = Date.add(Date.utc_today(), 4)
|
||||
day5 = Date.add(Date.utc_today(), 5)
|
||||
|
||||
existing_start = DateTime.new!(day3, ~T[09:00:00], "Etc/UTC")
|
||||
existing_end = DateTime.new!(day5, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _existing} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
existing_start,
|
||||
existing_end,
|
||||
"Existing User",
|
||||
"existing@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check availability on day 4
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, day4, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert counts.approved == 1
|
||||
end
|
||||
|
||||
test "detects overlap when new booking completely contains existing booking", %{space: space} do
|
||||
# Existing booking: Day 3-5
|
||||
day3 = Date.add(Date.utc_today(), 3)
|
||||
day4 = Date.add(Date.utc_today(), 4)
|
||||
day5 = Date.add(Date.utc_today(), 5)
|
||||
|
||||
existing_start = DateTime.new!(day3, ~T[09:00:00], "Etc/UTC")
|
||||
existing_end = DateTime.new!(day5, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _existing} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
existing_start,
|
||||
existing_end,
|
||||
"Existing User",
|
||||
"existing@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check if overlaps on day 4 (middle day)
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, day4, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert counts.approved == 1
|
||||
end
|
||||
|
||||
test "detects overlap when new booking is contained within existing booking", %{space: space} do
|
||||
# Existing booking: Day 1-10
|
||||
day1 = Date.add(Date.utc_today(), 1)
|
||||
day5 = Date.add(Date.utc_today(), 5)
|
||||
day10 = Date.add(Date.utc_today(), 10)
|
||||
|
||||
existing_start = DateTime.new!(day1, ~T[09:00:00], "Etc/UTC")
|
||||
existing_end = DateTime.new!(day10, ~T[18:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _existing} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
existing_start,
|
||||
existing_end,
|
||||
"Existing User",
|
||||
"existing@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check availability on day 5 (middle day within long booking)
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, day5, ~T[10:00:00], ~T[16:00:00])
|
||||
|
||||
assert counts.approved == 1
|
||||
end
|
||||
|
||||
test "no overlap when bookings are on consecutive days with no time overlap", %{space: space} do
|
||||
# First booking: Day 1-2, ending at 12 PM on day 2
|
||||
day1 = Date.add(Date.utc_today(), 1)
|
||||
day2 = Date.add(Date.utc_today(), 2)
|
||||
|
||||
first_start = DateTime.new!(day1, ~T[09:00:00], "Etc/UTC")
|
||||
first_end = DateTime.new!(day2, ~T[12:00:00], "Etc/UTC")
|
||||
|
||||
{:ok, _first} =
|
||||
BookingSystem.create_walk_in(
|
||||
space.id,
|
||||
first_start,
|
||||
first_end,
|
||||
"First User",
|
||||
"first@example.com",
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
# Check availability on day 2 afternoon (after first booking ends)
|
||||
{:ok, counts} =
|
||||
BookingSystem.get_slot_booking_counts(space.id, day2, ~T[13:00:00], ~T[18:00:00])
|
||||
|
||||
assert counts.approved == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
defmodule SpazioSolazzoWeb.Admin.WalkInLiveSimpleTest do
|
||||
use SpazioSolazzoWeb.ConnCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import SpazioSolazzo.AuthHelpers
|
||||
import Ecto.Query
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
defp create_admin_user do
|
||||
user = register_user("admin@example.com", "Admin User")
|
||||
# Directly update role to admin using Ecto
|
||||
{:ok, uuid} = Ecto.UUID.dump(user.id)
|
||||
from(u in "users", where: u.id == ^uuid)
|
||||
|> SpazioSolazzo.Repo.update_all(set: [role: "admin"])
|
||||
user
|
||||
end
|
||||
|
||||
setup do
|
||||
{:ok, space} =
|
||||
BookingSystem.create_space(
|
||||
"Coworking",
|
||||
"coworking",
|
||||
"Coworking space",
|
||||
5,
|
||||
10
|
||||
)
|
||||
|
||||
user = create_admin_user()
|
||||
|
||||
%{space: space, user: user}
|
||||
end
|
||||
|
||||
describe "walk-in booking creation bug" do
|
||||
test "can create booking by directly setting assigns", %{conn: conn, user: user, space: space} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
|
||||
# Simulate the calendar component sending the date_selected message
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
|
||||
# Give it a moment to process
|
||||
:timer.sleep(100)
|
||||
|
||||
# Fill in customer details using the form
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe",
|
||||
"customer_email" => "john@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
# Try to create the booking
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
# Check if it succeeded or failed
|
||||
if html =~ "Walk-in booking created successfully" do
|
||||
IO.puts("✓ SUCCESS: Booking was created")
|
||||
else
|
||||
if html =~ "Please fill in all required fields and select a date" do
|
||||
IO.puts("✗ BUG FOUND: Error message shown even though all fields are filled!")
|
||||
IO.puts("\nThis is the bug the user is experiencing.")
|
||||
else
|
||||
IO.puts("? Unexpected result")
|
||||
end
|
||||
end
|
||||
|
||||
# Verify booking was created
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
|
||||
if length(bookings) > 0 do
|
||||
assert true
|
||||
else
|
||||
flunk("Booking was not created even though all requirements were met")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
245
test/spazio_solazzo_web/live/admin/walk_in_live_test.exs
Normal file
245
test/spazio_solazzo_web/live/admin/walk_in_live_test.exs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
defmodule SpazioSolazzoWeb.Admin.WalkInLiveTest do
|
||||
use SpazioSolazzoWeb.ConnCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import SpazioSolazzo.AuthHelpers
|
||||
import Ecto.Query
|
||||
|
||||
alias SpazioSolazzo.BookingSystem
|
||||
|
||||
defp create_admin_user do
|
||||
user = register_user("admin@example.com", "Admin User")
|
||||
{:ok, uuid} = Ecto.UUID.dump(user.id)
|
||||
from(u in "users", where: u.id == ^uuid)
|
||||
|> SpazioSolazzo.Repo.update_all(set: [role: "admin"])
|
||||
user
|
||||
end
|
||||
|
||||
setup do
|
||||
{:ok, space} =
|
||||
BookingSystem.create_space(
|
||||
"Coworking",
|
||||
"coworking",
|
||||
"Coworking space",
|
||||
5,
|
||||
10
|
||||
)
|
||||
|
||||
user = create_admin_user()
|
||||
|
||||
%{space: space, user: user}
|
||||
end
|
||||
|
||||
describe "walk-in booking form" do
|
||||
test "displays the form with calendar and customer details", %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
assert has_element?(view, "form[phx-change='update_customer_details']")
|
||||
assert has_element?(view, "input[name='customer_name']")
|
||||
assert has_element?(view, "input[name='customer_email']")
|
||||
assert has_element?(view, "button[phx-click='create_booking']")
|
||||
end
|
||||
|
||||
test "creates single-day walk-in booking successfully", %{conn: conn, user: user, space: space} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
|
||||
# Simulate date selection by sending message to LiveView
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
:timer.sleep(50)
|
||||
|
||||
# Fill in customer details
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe",
|
||||
"customer_email" => "john@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
# Submit the form
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Verify booking was created
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_name == "John Doe"
|
||||
assert booking.customer_email == "john@example.com"
|
||||
assert booking.state == :accepted
|
||||
end
|
||||
|
||||
test "shows error when no date is selected", %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
# Fill in customer details without selecting a date
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe",
|
||||
"customer_email" => "john@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
# Try to submit
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Please fill in all required fields and select a date"
|
||||
end
|
||||
|
||||
test "shows error when customer name is missing", %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
:timer.sleep(50)
|
||||
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_email" => "john@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Please fill in all required fields and select a date"
|
||||
end
|
||||
|
||||
test "shows error when customer email is missing", %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
:timer.sleep(50)
|
||||
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Please fill in all required fields and select a date"
|
||||
end
|
||||
|
||||
test "creates multi-day walk-in booking", %{conn: conn, user: user, space: space} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
# Select date range (3 days)
|
||||
start_date = Date.add(Date.utc_today(), 1)
|
||||
end_date = Date.add(Date.utc_today(), 3)
|
||||
|
||||
send(view.pid, {:date_selected, start_date, end_date})
|
||||
:timer.sleep(50)
|
||||
|
||||
# Fill in customer details
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "Jane Smith",
|
||||
"customer_email" => "jane@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
# Submit the form
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Verify booking was created and spans multiple days
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, start_date)
|
||||
assert length(bookings) == 1
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_name == "Jane Smith"
|
||||
|
||||
# Verify booking appears on all days in the range
|
||||
{:ok, day2_bookings} =
|
||||
BookingSystem.list_accepted_space_bookings_by_date(space.id, Date.add(start_date, 1))
|
||||
|
||||
assert length(day2_bookings) == 1
|
||||
|
||||
{:ok, day3_bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, end_date)
|
||||
assert length(day3_bookings) == 1
|
||||
end
|
||||
|
||||
test "includes optional phone and comment", %{conn: conn, user: user, space: space} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
:timer.sleep(50)
|
||||
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe",
|
||||
"customer_email" => "john@example.com",
|
||||
"customer_phone" => "+39 1234567890",
|
||||
"customer_comment" => "Special request"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
{:ok, bookings} = BookingSystem.list_accepted_space_bookings_by_date(space.id, tomorrow)
|
||||
booking = hd(bookings)
|
||||
assert booking.customer_phone == "+39 1234567890"
|
||||
assert booking.customer_comment == "Special request"
|
||||
end
|
||||
|
||||
test "clears form after successful booking", %{conn: conn, user: user} do
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, view, _html} = live(conn, "/admin/walk-in")
|
||||
|
||||
tomorrow = Date.add(Date.utc_today(), 1)
|
||||
send(view.pid, {:date_selected, tomorrow, tomorrow})
|
||||
:timer.sleep(50)
|
||||
|
||||
view
|
||||
|> form("form[phx-change='update_customer_details']", %{
|
||||
"customer_name" => "John Doe",
|
||||
"customer_email" => "john@example.com"
|
||||
})
|
||||
|> render_change()
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("button[phx-click='create_booking']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "Walk-in booking created successfully"
|
||||
|
||||
# Check that form inputs are cleared by verifying empty values
|
||||
html = render(view)
|
||||
assert html =~ "Not selected"
|
||||
assert html =~ ~s(value="")
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue