fix icons on pages

This commit is contained in:
Graham McIntire 2025-07-04 09:55:22 -05:00
parent 1b4ac972de
commit bebead0fb0
No known key found for this signature in database
3 changed files with 492 additions and 273 deletions

View file

@ -16,133 +16,334 @@
<% _symbol_img = "/aprs-symbols/aprs-symbols-24-#{symbol_table_num}@2x.png" %>
<% _symbol_index = symbol_code_ord - 33 %>
<div class="max-w-3xl mx-auto mt-10 p-8 bg-white rounded-xl shadow-lg">
<h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
APRS station <span class="text-blue-700">{@callsign}</span>
<div class="min-h-screen bg-gray-50">
<!-- Page header -->
<div class="bg-white shadow-sm border-b">
<div class="px-4 sm:px-6 lg:px-8">
<div class="py-4 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1">
<div class="flex items-center">
<h1 class="text-xl font-bold leading-7 text-gray-900 sm:truncate sm:text-2xl">
APRS Station
</h1>
<div class="ml-3 flex items-center space-x-3">
<span class="inline-flex items-center rounded-md bg-blue-50 px-2 py-1 text-sm font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
{@callsign}
</span>
<%= if @packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@packet) %>
<% symbol_table =
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/",
do: "0",
else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
</div>
</div>
</div>
<div class="mt-3 flex md:ml-4 md:mt-0">
<div class="flex space-x-2">
<.link
navigate={~p"/packets/#{@callsign}"}
class="inline-flex items-center rounded-md bg-white px-3 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
View packets
</.link>
<%= if AprsmeWeb.MapLive.PacketUtils.has_weather_packets?(@callsign) do %>
<.link
navigate={~p"/weather/#{@callsign}"}
class="inline-flex items-center rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Weather charts
</.link>
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-4">
<%= if @packet do %>
<% {symbol_table_id, symbol_code} = AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@packet) %>
<% symbol_table = if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/", do: "0", else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; overflow: hidden; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
<.link navigate={~p"/packets/#{@callsign}"} class="ml-2 text-sm text-blue-600 hover:underline">
View packets
</.link>
<%= if AprsmeWeb.MapLive.PacketUtils.has_weather_packets?(@callsign) do %>
<.link
navigate={~p"/weather/#{@callsign}"}
class="ml-2 text-sm text-blue-600 hover:underline"
>
Weather charts
</.link>
<% end %>
</h1>
<%= if @packet do %>
<div class="text-sm text-gray-500 mb-6">
{@packet.comment || ""}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div>
<div class="mb-2">
<span class="font-semibold">Location:</span> {@packet.lat || ""}, {@packet.lon || ""}
<!-- Comment section -->
<%= if @packet.comment && @packet.comment != "" do %>
<div class="mb-4">
<div class="rounded-lg bg-blue-50 p-3">
<div class="flex">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 text-blue-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.75.75 0 00.736-.686L11.477 4.5a.75.75 0 00-1.491-.154L9.477 9.5H9z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="ml-2">
<p class="text-sm text-blue-700">
{@packet.comment}
</p>
</div>
</div>
</div>
</div>
<div class="mb-2">
<span class="font-semibold">Last position:</span> {AprsmeWeb.MapLive.PacketUtils.get_timestamp(
@packet
)}
<% end %>
<!-- Station details -->
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 mb-6">
<!-- Position information -->
<div class="bg-white overflow-hidden shadow-sm rounded-lg border">
<div class="px-4 py-3">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium text-gray-900">Position Information</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium text-gray-500">Location</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">
{@packet.lat || ""}, {@packet.lon || ""}
</dd>
</div>
<div>
<dt class="text-xs font-medium text-gray-500">Last Position</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">
{AprsmeWeb.MapLive.PacketUtils.get_timestamp(@packet)}
</dd>
</div>
<div>
<dt class="text-xs font-medium text-gray-500">Altitude</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">{@packet.altitude} ft</dd>
</div>
<div>
<dt class="text-xs font-medium text-gray-500">Course</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">{@packet.course}°</dd>
</div>
<div>
<dt class="text-xs font-medium text-gray-500">Speed</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">{@packet.speed} MPH</dd>
</div>
</dl>
</div>
</div>
<div class="mb-2"><span class="font-semibold">Altitude:</span> {@packet.altitude} ft</div>
<div class="mb-2"><span class="font-semibold">Course:</span> {@packet.course}°</div>
<div class="mb-2"><span class="font-semibold">Speed:</span> {@packet.speed} MPH</div>
</div>
<div>
<div class="mb-2">
<span class="font-semibold">Device:</span> {@packet.manufacturer || ""} {@packet.equipment_type ||
""}
</div>
<div class="mb-2"><span class="font-semibold">Path:</span> {@packet.path || ""}</div>
<div class="mb-2">
<span class="font-semibold">Raw:</span>
<%= if is_binary(@packet.raw_packet) do %>
{Aprsme.EncodingUtils.sanitize_string(@packet.raw_packet)}
<% else %>
{@packet.raw_packet || ""}
<% end %>
<!-- Device information -->
<div class="bg-white overflow-hidden shadow-sm rounded-lg border">
<div class="px-4 py-3">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0020.75 3H3.75A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium text-gray-900">Device Information</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium text-gray-500">Device</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">
{@packet.manufacturer || ""} {@packet.equipment_type || ""}
</dd>
</div>
<div>
<dt class="text-xs font-medium text-gray-500">Path</dt>
<dd class="mt-1 text-sm font-semibold text-gray-900">{@packet.path || ""}</dd>
</div>
<div class="col-span-2">
<dt class="text-xs font-medium text-gray-500">Raw Packet</dt>
<dd class="mt-1 text-xs font-mono text-gray-900 break-all">
<%= if is_binary(@packet.raw_packet) do %>
{Aprsme.EncodingUtils.sanitize_string(@packet.raw_packet)}
<% else %>
{@packet.raw_packet || ""}
<% end %>
</dd>
</div>
</dl>
</div>
</div>
</div>
</div>
<h2 class="text-xl font-semibold mt-8 mb-2">Stations near current position</h2>
<div class="overflow-x-auto">
<table class="min-w-full text-sm border rounded-lg">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-2 text-left">Callsign</th>
<th class="px-4 py-2 text-left">Distance</th>
<th class="px-4 py-2 text-left">Last heard</th>
</tr>
</thead>
<tbody>
<%= for neighbor <- @neighbors do %>
<tr class="border-t">
<td class="px-4 py-2 font-mono text-blue-700 flex items-center gap-2">
<.link navigate={~p"/info/#{neighbor.callsign}"} class="hover:underline">
{neighbor.callsign}
</.link>
<%= if neighbor.packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(neighbor.packet) %>
<% symbol_table =
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/",
do: "0",
else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; overflow: hidden; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
<!-- Neighboring stations -->
<div class="bg-white shadow-sm rounded-lg border">
<div class="px-4 py-3">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium text-gray-900">Stations Near Current Position</h3>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
class="py-2 pl-3 pr-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
</div>
Callsign
</th>
<th
scope="col"
class="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Distance
</th>
<th
scope="col"
class="px-2 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Last Heard
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<%= for neighbor <- @neighbors do %>
<tr>
<td class="whitespace-nowrap py-2 pl-3 pr-2 text-sm font-medium text-gray-900">
<div class="flex items-center space-x-2">
<.link
navigate={~p"/info/#{neighbor.callsign}"}
class="text-blue-600 hover:text-blue-900"
>
{neighbor.callsign}
</.link>
<%= if neighbor.packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(neighbor.packet) %>
<% symbol_table =
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/",
do: "0",
else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
</div>
</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">
{neighbor.distance || ""}
</td>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">
{neighbor.last_heard || ""}
</td>
</tr>
<% end %>
</td>
<td class="px-4 py-2">{neighbor.distance || ""}</td>
<td class="px-4 py-2">{neighbor.last_heard || ""}</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="text-gray-600">No recent packet data available for this callsign.</div>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<% else %>
<div class="text-center py-8">
<svg
class="mx-auto h-8 w-8 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423L16.5 15.75l.394 1.183a2.25 2.25 0 001.423 1.423L19.5 18.75l-1.183.394a2.25 2.25 0 00-1.423 1.423z"
/>
</svg>
<h3 class="mt-2 text-sm font-semibold text-gray-900">No data available</h3>
<p class="mt-1 text-sm text-gray-500">
No recent packet data available for this callsign.
</p>
</div>
<% end %>
</div>
</div>

View file

@ -68,4 +68,18 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
end
defp calc_dew_point(_, _), do: nil
@doc """
Gets weather field value, returning "0" instead of "N/A" for missing numeric data.
"""
def get_weather_field_zero(packet, key) do
value = PacketUtils.get_weather_field(packet, key)
# Return "0" for missing numeric fields, keep other values as-is
case value do
"N/A" -> "0"
nil -> "0"
_ -> value
end
end
end

View file

@ -1,160 +1,164 @@
<%= if @weather_packet do %>
<div class="max-w-lg mx-auto mt-8 p-6 bg-white rounded shadow">
<h1 class="text-2xl font-bold mb-2 flex items-center gap-2">
Weather for {@callsign}
<%= if @weather_packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@weather_packet) %>
<% symbol_table = if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/", do: "0", else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div class="w-full min-h-screen bg-slate-50 py-8 px-2 md:px-8">
<section class="w-full bg-white rounded-xl shadow-lg p-6 md:p-10 mb-8">
<h1 class="text-3xl font-bold mb-6 flex flex-wrap items-center gap-4">
Weather for {@callsign}
<%= if @weather_packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@weather_packet) %>
<% symbol_table = if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/", do: "0", else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
<.link
navigate={~p"/info/#{@callsign}"}
class="ml-2 text-blue-600 hover:underline text-base font-normal"
>
info
</.link>
<.link
navigate={~p"/#{@callsign}"}
class="ml-2 text-blue-600 hover:underline text-base font-normal"
>
view on map
</.link>
</h1>
<div class="text-base text-gray-600 mb-8">
{PacketUtils.get_timestamp(@weather_packet)}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
<div>
<strong>Temperature:</strong>
{PacketUtils.get_weather_field(@weather_packet, :temperature)}°F
</div>
<div>
<strong>Humidity:</strong>
{PacketUtils.get_weather_field(@weather_packet, :humidity)}%
</div>
<div>
<strong>Wind:</strong>
{PacketUtils.get_weather_field(@weather_packet, :wind_direction)}° @ {PacketUtils.get_weather_field(
@weather_packet,
:wind_speed
)} mph
</div>
<div>
<strong>Gusts:</strong>
{PacketUtils.get_weather_field(@weather_packet, :wind_gust)} mph
</div>
<div>
<strong>Pressure:</strong>
{PacketUtils.get_weather_field(@weather_packet, :pressure)} hPa
</div>
<div>
<strong>Rain (1h):</strong>
{get_weather_field_zero(@weather_packet, :rain_1h)} in
</div>
<div>
<strong>Rain (24h):</strong>
{get_weather_field_zero(@weather_packet, :rain_24h)} in
</div>
<div>
<strong>Rain (since midnight):</strong>
{get_weather_field_zero(@weather_packet, :rain_since_midnight)} in
</div>
</div>
<div class="mt-4 text-sm text-gray-500">
<strong>Raw comment:</strong> {@weather_packet.comment || @weather_packet["comment"]}
</div>
</section>
<section class="w-full bg-white rounded-xl shadow p-6 md:p-8 mt-8">
<h2 class="text-xl font-semibold mb-4">Weather History Graphs</h2>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :temperature, nil)))) do %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; overflow: hidden; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
id="temp-chart"
phx-hook="PlotlyTempChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<.link
navigate={~p"/info/#{@callsign}"}
class="ml-2 text-blue-600 hover:underline text-base font-normal"
>
info
</.link>
<.link
navigate={~p"/#{@callsign}"}
class="ml-2 text-blue-600 hover:underline text-base font-normal"
>
view on map
</.link>
</h1>
<div class="text-gray-600 mb-4">
{PacketUtils.get_timestamp(@weather_packet)}
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<strong>Temperature:</strong>
{PacketUtils.get_weather_field(@weather_packet, :temperature)}°F
</div>
<div>
<strong>Humidity:</strong>
{PacketUtils.get_weather_field(@weather_packet, :humidity)}%
</div>
<div>
<strong>Wind:</strong>
{PacketUtils.get_weather_field(@weather_packet, :wind_direction)}° @ {PacketUtils.get_weather_field(
@weather_packet,
:wind_speed
)} mph
</div>
<div>
<strong>Gusts:</strong>
{PacketUtils.get_weather_field(@weather_packet, :wind_gust)} mph
</div>
<div>
<strong>Pressure:</strong>
{PacketUtils.get_weather_field(@weather_packet, :pressure)} hPa
</div>
<div>
<strong>Rain (1h):</strong>
{PacketUtils.get_weather_field(@weather_packet, :rain_1h)} in
</div>
<div>
<strong>Rain (24h):</strong>
{PacketUtils.get_weather_field(@weather_packet, :rain_24h)} in
</div>
<div>
<strong>Rain (since midnight):</strong>
{PacketUtils.get_weather_field(@weather_packet, :rain_since_midnight)} in
</div>
</div>
<div class="mt-4 text-sm text-gray-500">
<strong>Raw comment:</strong> {@weather_packet.comment || @weather_packet["comment"]}
</div>
</div>
<div class="max-w-3xl mx-auto mt-8 p-6 bg-white rounded shadow">
<h2 class="text-xl font-bold mb-4">Weather History Graphs</h2>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :temperature, nil)))) do %>
<div
id="temp-chart"
phx-hook="PlotlyTempChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :humidity, nil)))) do %>
<div
id="humidity-chart"
phx-hook="PlotlyHumidityChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :pressure, nil)))) do %>
<div
id="pressure-chart"
phx-hook="PlotlyPressureChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :wind_direction, nil)))) do %>
<div
id="wind-direction-chart"
phx-hook="PlotlyWindDirectionChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :wind_speed, nil)))) do %>
<div
id="wind-speed-chart"
phx-hook="PlotlyWindSpeedChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_1h, nil)))) or Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_24h, nil)))) or Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_since_midnight, nil)))) do %>
<div
id="rain-chart"
phx-hook="PlotlyRainChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :luminosity, nil)))) do %>
<div
id="luminosity-chart"
phx-hook="PlotlyLuminosityChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<script src="https://cdn.plot.ly/plotly-latest.min.js">
</script>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :humidity, nil)))) do %>
<div
id="humidity-chart"
phx-hook="PlotlyHumidityChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :pressure, nil)))) do %>
<div
id="pressure-chart"
phx-hook="PlotlyPressureChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :wind_direction, nil)))) do %>
<div
id="wind-direction-chart"
phx-hook="PlotlyWindDirectionChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :wind_speed, nil)))) do %>
<div
id="wind-speed-chart"
phx-hook="PlotlyWindSpeedChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_1h, nil)))) or Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_24h, nil)))) or Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :rain_since_midnight, nil)))) do %>
<div
id="rain-chart"
phx-hook="PlotlyRainChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<%= if Enum.any?(@weather_history, &(not is_nil(Map.get(&1, :luminosity, nil)))) do %>
<div
id="luminosity-chart"
phx-hook="PlotlyLuminosityChart"
data-weather-history={@weather_history_json}
style="height:350px;"
>
</div>
<% end %>
<script src="https://cdn.plot.ly/plotly-latest.min.js">
</script>
</section>
</div>
<% else %>
<div class="max-w-lg mx-auto mt-8 p-6 bg-white rounded shadow text-center">
<h1 class="text-2xl font-bold mb-2">Weather for {@callsign}</h1>
<div class="text-gray-600">No recent weather data available for this callsign.</div>
<div class="w-full min-h-screen bg-slate-50 py-8 px-2 md:px-8 flex items-center justify-center">
<div class="w-full max-w-lg bg-white rounded-xl shadow-lg p-8 text-center">
<h1 class="text-2xl font-bold mb-2">Weather for {@callsign}</h1>
<div class="text-gray-600">No recent weather data available for this callsign.</div>
</div>
</div>
<% end %>