ammocpr/lib/ammoprices_web/components/price_components.ex
Graham McIntire 16a28ac01c
Add Target Sports USA scraper, product filtering, and subsonic detection
- Add subsonic boolean field to products with composite filter indexes
- Add TextDetector utility for detecting subsonic/casing from product titles
- Wire TextDetector into SgAmmo (casing + subsonic) and LuckyGunner (subsonic + fallback casing)
- Extend latest_prices_for_caliber with casing, grain_weight, and subsonic filter options
- Add distinct_grain_weights_for_caliber and distinct_casings_for_caliber queries
- Add filter UI with toggleable casing, grain weight, and subsonic buttons
- Add grain weight column and subsonic badge to product table
- Refresh filter options on PubSub price updates for real-time UI
- Implement Target Sports USA scraper with 18 caliber mappings
- Display all prices in dollar format ($0.38 instead of 38¢)
2026-03-11 16:28:04 -05:00

75 lines
1.8 KiB
Elixir

defmodule AmmopricesWeb.PriceComponents do
@moduledoc false
use Phoenix.Component
attr :cents, :integer, required: true
attr :class, :string, default: nil
def price_per_round(assigns) do
~H"""
<span class={["font-mono tabular-nums", @class]}>
{format_cpr(@cents)}
</span>
"""
end
attr :in_stock, :boolean, required: true
attr :class, :string, default: nil
def stock_badge(assigns) do
~H"""
<span class={[
"inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wider",
if(@in_stock, do: "bg-success/15 text-success", else: "bg-error/15 text-error"),
@class
]}>
<span class={[
"inline-block w-1.5 h-1.5 rounded-full",
if(@in_stock, do: "bg-success", else: "bg-error")
]} />
{if @in_stock, do: "In Stock", else: "Out of Stock"}
</span>
"""
end
attr :url, :string, required: true
attr :name, :string, required: true
attr :class, :string, default: nil
def retailer_link(assigns) do
~H"""
<a
href={@url}
target="_blank"
rel="noopener noreferrer"
class={[
"inline-flex items-center gap-1 text-primary hover:text-primary/80 transition-colors",
@class
]}
>
{@name}
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3 h-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
"""
end
defp format_cpr(nil), do: "--"
defp format_cpr(cents) do
dollars = cents / 100
"$#{:erlang.float_to_binary(dollars, decimals: 2)}"
end
end