neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Sit the Plani's two cards side by side, and show its settings
c514686 · Gabriel Morell · 2026-09-04 14:54
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/show.ex
+31
−0
@@ -172,6 +172,37 @@ defmodule RoomSanctumWeb.PlaniLive.Show do
| 172 | 172 | } |
| 173 | 173 | end |
| 174 | 174 | |
| 175 | + @doc """ | |
| 176 | + Everything a Plani was configured with, for the settings card. | |
| 177 | + | |
| 178 | + Built here rather than read field by field in the template so that the | |
| 179 | + "not set" cases are decided once: a blank bike limit means everything inside | |
| 180 | + the radius, and saying so is more use than an empty cell. | |
| 181 | + """ | |
| 182 | + def settings(plani) do | |
| 183 | + [ | |
| 184 | + {"Home foci", :text, foci_name(plani.home_foci_id)}, | |
| 185 | + {"Ankyra", :text, plani.ankyra_id && "##{plani.ankyra_id}"}, | |
| 186 | + {"Client", :text, plani.client_id}, | |
| 187 | + {"Radius", :text, "#{plani.radius} m"}, | |
| 188 | + {"How many of each", :text, plani.limit}, | |
| 189 | + {"Bikes and docks per source", :text, plani.bike_limit || "all within the radius"}, | |
| 190 | + {"One card per stop", :flag, plani.break_out}, | |
| 191 | + {"One stop per line", :flag, plani.nearest_per_route}, | |
| 192 | + {"Following tint", :text, plani.follow_tint} | |
| 193 | + ] | |
| 194 | + end | |
| 195 | + | |
| 196 | + # A foci that has been deleted out from under a Plani should show as missing | |
| 197 | + # rather than take the page down -- `get_foci!` raises. | |
| 198 | + defp foci_name(nil), do: nil | |
| 199 | + | |
| 200 | + defp foci_name(id) do | |
| 201 | + Configuration.get_foci!(id).name | |
| 202 | + rescue | |
| 203 | + Ecto.NoResultsError -> "missing (##{id})" | |
| 204 | + end | |
| 205 | + | |
| 175 | 206 | @doc "A point as something readable, or nil." |
| 176 | 207 | def coordinates(%{anchor: %Geo.Point{coordinates: {lon, lat}}}) do |
| 177 | 208 | "#{Float.round(lat, 5)}, #{Float.round(lon, 5)}" |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/show.html.heex
+42
−3
@@ -120,8 +120,11 @@
| 120 | 120 | </div> |
| 121 | 121 | </div> |
| 122 | 122 | |
| 123 | −<div class="mx-4 py-2 flex flex-wrap"> | |
| 124 | − <div class="card card-compact w-full md:w-1/2 bg-base-100 shadow-xl m-2"> | |
| 123 | +<%!-- A grid rather than flex-wrap: these were already md:w-1/2, but the m-2 | |
| 124 | + beside it made each half a full rem wider than half, so the pair never | |
| 125 | + fitted on one row and silently stacked. A gap belongs to the container. --%> | |
| 126 | +<div class="mx-4 py-2 grid gap-4 md:grid-cols-2 items-start"> | |
| 127 | + <div class="card card-compact w-full bg-base-100 shadow-xl"> | |
| 125 | 128 | <div class="card-body"> |
| 126 | 129 | <h3 class="card-title"> |
| 127 | 130 | <i class="fas fa-fw fa-location-crosshairs"></i> Where it is |
@@ -153,7 +156,7 @@
| 153 | 156 | </div> |
| 154 | 157 | </div> |
| 155 | 158 | |
| 156 | − <div class="card card-compact w-full md:w-1/2 bg-base-100 shadow-xl m-2"> | |
| 159 | + <div class="card card-compact w-full bg-base-100 shadow-xl"> | |
| 157 | 160 | <div class="card-body"> |
| 158 | 161 | <h3 class="card-title"><i class="fas fa-fw fa-layer-group"></i> What it is asking</h3> |
| 159 | 162 | <% asked = RoomSanctumWeb.PlaniLive.Show.asked(@plani, @sources) %> |
@@ -204,3 +207,39 @@
| 204 | 207 | </div> |
| 205 | 208 | </div> |
| 206 | 209 | </div> |
| 210 | + | |
| 211 | +<div class="mx-4 py-2"> | |
| 212 | + <%!-- Collapsed by default, and with a checkbox rather than an event: this is | |
| 213 | + a reference the reader opens once to check a number, not state the | |
| 214 | + server has any business remembering. --%> | |
| 215 | + <div class="collapse collapse-arrow bg-base-100 shadow-xl"> | |
| 216 | + <input type="checkbox" /> | |
| 217 | + <div class="collapse-title card-title text-base"> | |
| 218 | + <i class="fas fa-fw fa-sliders"></i> How it is set up | |
| 219 | + </div> | |
| 220 | + <div class="collapse-content"> | |
| 221 | + <div class="grid gap-x-6 gap-y-1 sm:grid-cols-2 lg:grid-cols-3"> | |
| 222 | + <div | |
| 223 | + :for={{label, kind, value} <- RoomSanctumWeb.PlaniLive.Show.settings(@plani)} | |
| 224 | + class="flex items-center justify-between gap-2 border-b border-base-300 py-1" | |
| 225 | + > | |
| 226 | + <span class="text-sm opacity-70"><%= label %></span> | |
| 227 | + <%= case {kind, value} do %> | |
| 228 | + <% {:flag, true} -> %> | |
| 229 | + <span class="badge badge-accent badge-sm gap-1"> | |
| 230 | + <i class="fas fa-fw fa-check text-xs"></i> on | |
| 231 | + </span> | |
| 232 | + <% {:flag, _} -> %> | |
| 233 | + <span class="badge badge-ghost badge-sm">off</span> | |
| 234 | + <% {_, nil} -> %> | |
| 235 | + <span class="text-sm opacity-40">not set</span> | |
| 236 | + <% {_, ""} -> %> | |
| 237 | + <span class="text-sm opacity-40">not set</span> | |
| 238 | + <% {_, shown} -> %> | |
| 239 | + <span class="font-mono text-sm"><%= shown %></span> | |
| 240 | + <% end %> | |
| 241 | + </div> | |
| 242 | + </div> | |
| 243 | + </div> | |
| 244 | + </div> | |
| 245 | +</div> |
added
apps/room_sanctum/test/room_sanctum_web/plani_settings_test.exs
+39
−0
@@ -0,0 +1,39 @@
| 1 | +defmodule RoomSanctumWeb.PlaniSettingsTest do | |
| 2 | + use ExUnit.Case, async: true | |
| 3 | + | |
| 4 | + alias RoomSanctum.Configuration.Plani | |
| 5 | + alias RoomSanctumWeb.PlaniLive.Show | |
| 6 | + | |
| 7 | + defp rows(plani), do: Show.settings(plani) |> Map.new(fn {l, k, v} -> {l, {k, v}} end) | |
| 8 | + | |
| 9 | + test "a blank bike limit says what blank means" do | |
| 10 | + # "not set" would be true and useless: what it actually does is publish | |
| 11 | + # everything inside the radius, which is the thing worth knowing. | |
| 12 | + settings = rows(%Plani{radius: 800, limit: 5, bike_limit: nil}) | |
| 13 | + | |
| 14 | + assert {:text, "all within the radius"} = settings["Bikes and docks per source"] | |
| 15 | + end | |
| 16 | + | |
| 17 | + test "a set bike limit shows the number" do | |
| 18 | + assert {:text, 12} = rows(%Plani{bike_limit: 12})["Bikes and docks per source"] | |
| 19 | + end | |
| 20 | + | |
| 21 | + test "the flags are flags, so the template can draw them as flags" do | |
| 22 | + settings = rows(%Plani{break_out: true, nearest_per_route: false}) | |
| 23 | + | |
| 24 | + assert {:flag, true} = settings["One card per stop"] | |
| 25 | + assert {:flag, false} = settings["One stop per line"] | |
| 26 | + end | |
| 27 | + | |
| 28 | + test "the radius carries its unit" do | |
| 29 | + assert {:text, "800 m"} = rows(%Plani{radius: 800})["Radius"] | |
| 30 | + end | |
| 31 | + | |
| 32 | + test "nothing configured is nil rather than a made-up blank" do | |
| 33 | + settings = rows(%Plani{}) | |
| 34 | + | |
| 35 | + assert {:text, nil} = settings["Following tint"] | |
| 36 | + assert {:text, nil} = settings["Home foci"] | |
| 37 | + assert {:text, nil} = settings["Client"] | |
| 38 | + end | |
| 39 | +end |
Parents: eac203f