From d5d0c3274534c6c7b5dab82ca1297236e4591745 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sun, 12 Apr 2026 09:20:13 -0500 Subject: [PATCH] Use daisyUI components for stats, alerts, cards, and tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - about: stat cards → daisyUI stat, warning/donate boxes → alert - submit: tab buttons → daisyUI tabs-box, info/instruction boxes → alert - backfill: enrichment/DB stat boxes → daisyUI stat - beacon show: stats container → daisyUI card - contact show: info/status boxes → alert, data source cards → card - ADIF: strip APP_ prefix from app-defined fields, normalize grid case --- lib/microwaveprop/radio/adif_import.ex | 47 +++++- lib/microwaveprop_web/live/about_live.ex | 66 ++++++--- lib/microwaveprop_web/live/backfill_live.ex | 79 +++++----- .../live/beacon_live/show.ex | 66 +++++---- .../live/contact_live/show.ex | 140 ++++++++++-------- lib/microwaveprop_web/live/submit_live.ex | 117 +++++++-------- test/microwaveprop/radio/adif_import_test.exs | 53 +++++++ 7 files changed, 345 insertions(+), 223 deletions(-) diff --git a/lib/microwaveprop/radio/adif_import.ex b/lib/microwaveprop/radio/adif_import.ex index fb5b0cc9..936a566a 100644 --- a/lib/microwaveprop/radio/adif_import.ex +++ b/lib/microwaveprop/radio/adif_import.ex @@ -69,12 +69,20 @@ defmodule Microwaveprop.Radio.AdifImport do # -- ADIF parser ----------------------------------------------------------- + # Header-only ADIF tags that never appear inside QSO records + @header_only_tags ~w(ADIF_VER PROGRAMID PROGRAMVERSION CREATED_TIMESTAMP) + defp parse_adif(string) do # Strip header if present (everything before ) body = case Regex.run(~r//i, string, return: :index) do - [{pos, len}] -> String.slice(string, (pos + len)..-1//1) - nil -> string + [{pos, len}] -> + String.slice(string, (pos + len)..-1//1) + + nil -> + # No — strip header-only tags (ADIF_VER, PROGRAMID, etc.) + # and any preceding plain text so they don't leak into the first record. + strip_header_tags(string) end # Split on to get individual records @@ -89,7 +97,8 @@ defmodule Microwaveprop.Radio.AdifImport do |> Regex.scan(record_string, return: :index) |> Enum.reduce(%{}, fn indices, acc -> [{tag_start, _tag_len}, {name_start, name_len}, {size_start, size_len} | _] = indices - name = record_string |> String.slice(name_start, name_len) |> String.upcase() + raw_name = record_string |> String.slice(name_start, name_len) |> String.upcase() + name = strip_app_prefix(raw_name) size = record_string |> String.slice(size_start, size_len) |> String.to_integer() # Value starts right after the closing > @@ -98,17 +107,43 @@ defmodule Microwaveprop.Radio.AdifImport do value_offset = tag_start + (tag_text |> String.split(">", parts: 2) |> List.first() |> byte_size()) + 1 value = String.slice(record_string, value_offset, size) - Map.put(acc, name, value) + # Standard ADIF fields take precedence over app-defined ones + if raw_name != name and Map.has_key?(acc, name) do + acc + else + Map.put(acc, name, value) + end end) end + # Strip APP_PROGRAMNAME_ prefix so app-defined fields resolve to standard names. + # e.g. APP_N1MM_MYGRID → MYGRID, APP_LOGGER32_CALL → CALL + defp strip_app_prefix("APP_" <> rest) do + case String.split(rest, "_", parts: 2) do + [_program, field] -> field + _ -> "APP_" <> rest + end + end + + defp strip_app_prefix(name), do: name + + # Strip known header-only tags and any plain text before the first record tag. + defp strip_header_tags(string) do + Enum.reduce(@header_only_tags, string, fn tag, acc -> + Regex.replace(~r/<#{tag}:\d+(?::[^>]*)?>.*?(?=<)/is, acc, "") + end) + end + + defp normalize_grid(nil), do: nil + defp normalize_grid(g), do: g |> String.trim() |> String.upcase() + # -- row building ---------------------------------------------------------- defp build_row({fields, row_num}, submitter_email) do station1 = fields["STATION_CALLSIGN"] || fields["OPERATOR"] station2 = fields["CALL"] - grid1 = fields["MY_GRIDSQUARE"] - grid2 = fields["GRIDSQUARE"] + grid1 = normalize_grid(fields["MY_GRIDSQUARE"]) + grid2 = normalize_grid(fields["GRIDSQUARE"]) mode = fields["MODE"] band = resolve_band(fields) timestamp = parse_adif_datetime(fields["QSO_DATE"], fields["TIME_ON"]) diff --git a/lib/microwaveprop_web/live/about_live.ex b/lib/microwaveprop_web/live/about_live.ex index aeb738b6..0fa49d0e 100644 --- a/lib/microwaveprop_web/live/about_live.ex +++ b/lib/microwaveprop_web/live/about_live.ex @@ -39,7 +39,7 @@ defmodule MicrowavepropWeb.AboutLive do band, based on real atmospheric conditions and what we've learned from the contacts you and everyone else have already made.

-
+ @@ -170,7 +200,7 @@ defmodule MicrowavepropWeb.AboutLive do
-
+