location_component.ex 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. defmodule VaccinsWeb.LocationComponent do
  2. use VaccinsWeb, :live_component
  3. alias Vaccins.{LocationStore, Search}
  4. @impl true
  5. def mount(socket),
  6. do:
  7. {:ok,
  8. socket
  9. |> assign(
  10. slots_after: [],
  11. slots_before: [],
  12. loading: false,
  13. last_refresh_date: nil,
  14. last_early_slot_seen: nil,
  15. render_as: :description_list
  16. )}
  17. @impl true
  18. def update(assigns, socket) do
  19. {force_refresh, assigns} = assigns |> Map.pop(:force_refresh)
  20. assigns = assigns |> integrate_availabilities
  21. if is_nil(force_refresh) do
  22. {:ok, socket |> assign(assigns) |> assign(loading: false) |> signal_availabilities}
  23. else
  24. trigger_and_signal_query(
  25. assigns |> Map.get(:location, socket.assigns.location),
  26. assigns.id
  27. )
  28. {:ok,
  29. socket |> assign(assigns) |> assign(loading: true, last_refresh_date: get_refresh_time())}
  30. end
  31. end
  32. @impl true
  33. def handle_event("trigger_query", _, socket = %{assigns: %{id: id, location: location}}) do
  34. trigger_and_signal_query(location, id)
  35. {:noreply, socket |> assign(loading: true, last_refresh_date: get_refresh_time())}
  36. end
  37. def handle_event("delete", _, socket = %{assigns: %{id: id}}) do
  38. if :ok == LocationStore.delete_location(id), do: send(self(), {:location_deleted, id})
  39. {:noreply, socket}
  40. end
  41. @impl true
  42. def render(assigns = %{render_as: :description_list}) do
  43. ~L"""
  44. <dl class="location">
  45. <dt>name</dt>
  46. <dd><%= @location.name %></dd>
  47. <dt>location</dt>
  48. <dd><%= @location.location %></dd>
  49. <dt>Status (<%= if @last_refresh_date, do: @last_refresh_date |> Time.to_string() %>) </dt>
  50. <dd>
  51. <%= cond do %>
  52. <%= @loading -> %>...
  53. <%= not has_slots?(assigns) -> %>Pas de créneau <%= if @last_early_slot_seen do %>(<%= @last_early_slot_seen |> DateTime.to_time() |> Time.truncate(:second) |> Time.to_string %>)<% end %>
  54. <%= has_early_slots?(assigns) -> %><span class="alert-danger">Des dispos sous 24h !</span>
  55. <%= has_slots?(assigns) -> %>Des dispos !
  56. <% end %>
  57. </dd>
  58. <dt>booking page</dt>
  59. <dd><%= link @location.booking_page, to: @location.booking_page %></dd>
  60. <%= if has_slots?(assigns) do %>
  61. <dt>Avant 24h</dt>
  62. <dd>
  63. <ul class="slots-list"><%= for d <- @slots_before do %><li><%= d |> Calendar.strftime("%d/%m/%Y %H:%M:%S") %></li><% end %></ul>
  64. </dd>
  65. <dt>Après 24h</dt>
  66. <dd>
  67. <ul class="slots-list"><%= for d <- @slots_after do %><li><%= d |> Calendar.strftime("%d/%m/%Y %H:%M:%S") %></li><% end %></ul>
  68. </dd>
  69. <% end %>
  70. <dt>actions</dt>
  71. <dd>
  72. <ul class="actions-list">
  73. <li><button phx-click="trigger_query" phx-target="<%= @myself %>">Trigger</button></li>
  74. <li><a href="<%= @location |> to_json_query %>"><button>Debug</button></a></li>
  75. <li><button class="alert-danger" phx-click="delete" phx-target="<%= @myself %>" data-confirm="Etes-vous sur?">Delete</button></li>
  76. </ul>
  77. </dd>
  78. </dl>
  79. """
  80. end
  81. @impl true
  82. def render(assigns = %{render_as: :table_row}) do
  83. ~L"""
  84. <td><%= @location.name %></td>
  85. <td><%= @location.location %></td>
  86. <td><%= if @last_refresh_date, do: @last_refresh_date |> Time.to_string() %></td>
  87. <td><%= cond do %>
  88. <%= @loading -> %>...
  89. <%= not has_slots?(assigns) -> %>Pas de créneau <%= if @last_early_slot_seen do %>(<%= @last_early_slot_seen |> DateTime.to_time() |> Time.truncate(:second) |> Time.to_string %>)<% end %>
  90. <%= has_early_slots?(assigns) -> %><span class="alert-danger">Des dispos sous 24h !</span>
  91. <%= has_slots?(assigns) -> %>Des dispos !
  92. <% end %></td>
  93. <td><%= link "Résa.", to: @location.booking_page %></td>
  94. <td>
  95. <ul class="slots-list"><%= for d <- @slots_before do %><li><%= d |> Calendar.strftime("%d/%m/%Y %H:%M") %></li><% end %></ul>
  96. </td>
  97. <td>
  98. <ul class="slots-list"><%= for d <- @slots_after do %><li><%= d |> Calendar.strftime("%d/%m/%Y %H:%M") %></li><% end %></ul>
  99. </td>
  100. <td><ul class="actions-list">
  101. <li><button phx-click="trigger_query" phx-target="<%= @myself %>">Trigger</button></li>
  102. <li><a href="<%= @location |> to_json_query %>"><button>Debug</button></a></li>
  103. <li><button class="alert-danger" phx-click="delete" phx-target="<%= @myself %>" data-confirm="Etes-vous sur?">Delete</button></li>
  104. </ul></td>
  105. """
  106. end
  107. def render_table_header(),
  108. do: ~E"""
  109. <th>Nom</th>
  110. <th>Lieu</th>
  111. <th>Dernier refresh</th>
  112. <th>Status</th>
  113. <th>Lien résa</th>
  114. <th>Slots avant 24h</th>
  115. <th>Slots après 24h</th>
  116. <th>Actions</th>
  117. """
  118. defp integrate_availabilities(assigns = %{availabilities: {:error, reason}}),
  119. do:
  120. assigns
  121. |> Map.put(:slots_after, [])
  122. |> Map.put(:slots_before, [])
  123. defp integrate_availabilities(assigns = %{availabilities: {:ok, after_slots}})
  124. when is_list(after_slots),
  125. do:
  126. assigns
  127. |> Map.put(:slots_after, after_slots |> Enum.take(5))
  128. |> Map.put(:slots_before, [])
  129. defp integrate_availabilities(assigns = %{availabilities: {:ok, before_slots, after_slots}})
  130. when is_list(before_slots),
  131. do:
  132. assigns
  133. |> Map.put(:slots_after, after_slots |> Enum.take(5))
  134. |> Map.put(:slots_before, before_slots |> Enum.take(5))
  135. |> Map.put(:last_early_slot_seen, DateTime.utc_now())
  136. defp integrate_availabilities(assigns), do: assigns
  137. defp has_slots?(assigns = %{slots_before: before, slots_after: after_}),
  138. do: not (before |> Enum.empty?() and after_ |> Enum.empty?())
  139. defp has_early_slots?(assigns = %{slots_before: before}),
  140. do: not (before |> Enum.empty?())
  141. defp trigger_and_signal_query(location, id) do
  142. ref = Search.async_trigger_query(location)
  143. send(self(), {:query_sent, id, ref})
  144. end
  145. defp get_refresh_time() do
  146. with {:ok, now} <-
  147. DateTime.utc_now() |> DateTime.shift_zone("Europe/Paris", Tzdata.TimeZoneDatabase),
  148. do: now |> DateTime.to_time() |> Time.truncate(:second)
  149. end
  150. defp signal_availabilities(socket = %{assigns: %{loading: false}}) do
  151. cond do
  152. socket.assigns |> has_slots? ->
  153. send(self(), {:location_has_slots, socket.assigns.id, socket.assigns |> has_early_slots?})
  154. true ->
  155. send(self(), {:location_no_more_slots, socket.assigns.id})
  156. end
  157. socket
  158. end
  159. defp signal_availabilities(socket), do: socket
  160. defp to_json_query(l = %{availability_query: q, provider: provider}),
  161. do: q |> provider.to_url() |> URI.to_string()
  162. end