- 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¢)
75 lines
1.8 KiB
Elixir
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
|