diff --git a/lib/ammoprices_web/live/caliber_live/show.ex b/lib/ammoprices_web/live/caliber_live/show.ex index f35e2f7..b7b75ca 100644 --- a/lib/ammoprices_web/live/caliber_live/show.ex +++ b/lib/ammoprices_web/live/caliber_live/show.ex @@ -181,11 +181,10 @@ defmodule AmmopricesWeb.CaliberLive.Show do chart_data = %{ labels: Enum.map(daily_data, &Date.to_iso8601(&1.date)), avg: Enum.map(daily_data, & &1.avg_ppr), - min: Enum.map(daily_data, & &1.min_ppr), - max: Enum.map(daily_data, & &1.max_ppr) + min: Enum.map(daily_data, & &1.min_ppr) } - assign(socket, :chart_data, chart_data) + assign(socket, chart_data: chart_data, chart_empty?: daily_data == []) end defp load_price_stats(socket) do @@ -279,14 +278,25 @@ defmodule AmmopricesWeb.CaliberLive.Show do -
- +
+
+ +
+
+

+ No price history available for this range yet. +

+
@@ -504,10 +514,6 @@ defmodule AmmopricesWeb.CaliberLive.Show do const ctx = canvas.getContext("2d") - // Get CSS custom property values for theming - const style = getComputedStyle(document.documentElement) - const textColor = style.getPropertyValue("color") || "#666" - return new Chart(ctx, { type: "line", data: { diff --git a/lib/ammoprices_web/live/home_live.ex b/lib/ammoprices_web/live/home_live.ex index d04a6bf..4ff1533 100644 --- a/lib/ammoprices_web/live/home_live.ex +++ b/lib/ammoprices_web/live/home_live.ex @@ -16,9 +16,11 @@ defmodule AmmopricesWeb.HomeLive do @impl true def mount(_params, _session, socket) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Ammoprices.PubSub, "prices:updated") + end + calibers = Catalog.list_calibers() - cheapest = Prices.cheapest_per_caliber() - cheapest_map = Map.new(cheapest, fn r -> {r.caliber_id, r.min_ppr} end) grouped = calibers @@ -28,12 +30,25 @@ defmodule AmmopricesWeb.HomeLive do end) {:ok, - assign(socket, + socket + |> assign( page_title: "AmmoCPR", categories: @categories, - calibers_by_category: grouped, - cheapest_map: cheapest_map - )} + calibers_by_category: grouped + ) + |> assign_cheapest_map()} + end + + @impl true + def handle_info({:prices_updated, _}, socket) do + {:noreply, assign_cheapest_map(socket)} + end + + defp assign_cheapest_map(socket) do + cheapest_map = + Map.new(Prices.cheapest_per_caliber(), fn r -> {r.caliber_id, r.min_ppr} end) + + assign(socket, :cheapest_map, cheapest_map) end @impl true diff --git a/test/ammoprices_web/live/caliber_live/show_test.exs b/test/ammoprices_web/live/caliber_live/show_test.exs index 5fc4840..c26e00c 100644 --- a/test/ammoprices_web/live/caliber_live/show_test.exs +++ b/test/ammoprices_web/live/caliber_live/show_test.exs @@ -182,6 +182,22 @@ defmodule AmmopricesWeb.CaliberLive.ShowTest do assert has_element?(view, "#filter-casing-steel") end + test "shows empty chart state when no data exists in range", %{conn: conn, caliber: caliber} do + {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") + + assert has_element?(view, "#price-chart-empty") + end + + test "hides empty chart state when data exists in range", %{conn: conn, caliber: caliber, retailer: retailer} do + product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) + now = DateTime.truncate(DateTime.utc_now(), :second) + snapshot_fixture(%{product: product, price_per_round_cents: 28, in_stock: true, recorded_at: now}) + + {:ok, view, _html} = live(conn, "/calibers/#{caliber.slug}") + + refute has_element?(view, "#price-chart-empty") + end + test "chart container uses phx-update=ignore to survive DOM patches", %{ conn: conn, caliber: caliber, diff --git a/test/ammoprices_web/live/home_live_test.exs b/test/ammoprices_web/live/home_live_test.exs index 5771375..3c4001d 100644 --- a/test/ammoprices_web/live/home_live_test.exs +++ b/test/ammoprices_web/live/home_live_test.exs @@ -33,5 +33,19 @@ defmodule AmmopricesWeb.HomeLiveTest do assert html =~ "25" end + + test "updates cheapest prices via PubSub broadcast", %{conn: conn} do + caliber = caliber_fixture(%{name: "9mm Luger", slug: "9mm-luger", category: "handgun"}) + retailer = retailer_fixture() + product = product_fixture(%{caliber: caliber, retailer: retailer, in_stock: true}) + now = DateTime.truncate(DateTime.utc_now(), :second) + + {:ok, view, _html} = live(conn, "/") + + snapshot_fixture(%{product: product, price_per_round_cents: 17, in_stock: true, recorded_at: now}) + Phoenix.PubSub.broadcast(Ammoprices.PubSub, "prices:updated", {:prices_updated, %{}}) + + assert render(view) =~ "0.17" + end end end