- Replace gray->cool-steel, blue/indigo->cerulean, red->sweet-salmon, yellow/amber->wheat - Dark sidebar: sidebar/footer use cool-steel-800 bg, text lightened for contrast - ~11,600 color replacements across ~99 files - Update tests for new color class names
177 lines
8 KiB
Text
177 lines
8 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_scope={@current_scope}
|
|
active_page="coverage"
|
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
|
>
|
|
<.header>
|
|
{@page_title}
|
|
<:subtitle>
|
|
{t(
|
|
"Predict signal coverage for each transmit antenna using LIDAR terrain and building data."
|
|
)}
|
|
</:subtitle>
|
|
<:actions>
|
|
<.button :if={@sites != []} navigate={~p"/coverage/new"} variant="primary">
|
|
<.icon name="hero-plus" class="h-5 w-5" /> {t("New Coverage")}
|
|
</.button>
|
|
</:actions>
|
|
</.header>
|
|
|
|
<%= cond do %>
|
|
<% @sites == [] -> %>
|
|
<div class="flex items-center justify-center py-16">
|
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
|
<div class="items-center text-center">
|
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
|
<.icon
|
|
name="hero-signal"
|
|
class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400"
|
|
/>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
|
{t("Add a site first")}
|
|
</h3>
|
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
|
{t(
|
|
"Coverage predictions are computed from a site's location. Add a site, then return here to plan its coverage."
|
|
)}
|
|
</p>
|
|
<div class="mt-4 flex gap-2">
|
|
<.button navigate={~p"/sites/new"} variant="primary">
|
|
<.icon name="hero-plus" class="h-4 w-4" /> {t("Add Site")}
|
|
</.button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% @coverages == [] -> %>
|
|
<div class="flex items-center justify-center py-16">
|
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
|
<div class="items-center text-center">
|
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
|
<.icon
|
|
name="hero-signal"
|
|
class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400"
|
|
/>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
|
{t("No coverages yet")}
|
|
</h3>
|
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
|
{t("Create your first coverage to predict where a tower's antenna reaches.")}
|
|
</p>
|
|
<div class="mt-4 flex gap-2">
|
|
<.button navigate={~p"/coverage/new"} variant="primary">
|
|
<.icon name="hero-plus" class="h-4 w-4" /> {t("New Coverage")}
|
|
</.button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% true -> %>
|
|
<div class="mt-6 overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
|
<table
|
|
id="coverages-table"
|
|
class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10"
|
|
>
|
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/80">
|
|
<tr>
|
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Name")}
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Site")}
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Antenna")}
|
|
</th>
|
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Freq (GHz)")}
|
|
</th>
|
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("TX (dBm)")}
|
|
</th>
|
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Range (mi)")}
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Status")}
|
|
</th>
|
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
|
{t("Actions")}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-cool-steel-200 dark:bg-cool-steel-900 dark:divide-white/10">
|
|
<%= for coverage <- @coverages do %>
|
|
<tr id={"coverage-#{coverage.id}"}>
|
|
<td class="px-4 py-2 text-sm">
|
|
<.link
|
|
navigate={~p"/coverage/#{coverage.id}"}
|
|
class="font-medium text-cerulean-600 hover:underline dark:text-cerulean-400"
|
|
>
|
|
{coverage.name}
|
|
</.link>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
|
<.link navigate={~p"/sites/#{coverage.site_id}"} class="hover:underline">
|
|
{coverage.site && coverage.site.name}
|
|
</.link>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
|
<span class="font-mono text-xs">{coverage.antenna_slug}</span>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300 font-mono">
|
|
{Float.round(coverage.frequency_mhz / 1000.0, 3)}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300">
|
|
{coverage.tx_power_dbm}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300 font-mono">
|
|
{Float.round(coverage.radius_m / 1609.344, 2)}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
|
status_badge_class(coverage.status)
|
|
]}>
|
|
{coverage.status}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-right">
|
|
<div class="flex justify-end gap-2">
|
|
<.link
|
|
navigate={~p"/coverage/#{coverage.id}/edit"}
|
|
class="text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
|
>
|
|
<.icon name="hero-pencil-square" class="h-4 w-4" />
|
|
</.link>
|
|
<button
|
|
type="button"
|
|
phx-click="recompute"
|
|
phx-value-id={coverage.id}
|
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
|
title={t("Recompute")}
|
|
>
|
|
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
phx-click="delete"
|
|
phx-value-id={coverage.id}
|
|
data-confirm={t("Delete this coverage?")}
|
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
|
title={t("Delete")}
|
|
>
|
|
<.icon name="hero-trash" class="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% end %>
|
|
</Layouts.authenticated>
|