towerops/lib/towerops_web/live/site_live/index.html.heex

202 lines
8.9 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="sites"
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
>
<.header>
{@page_title}
<:subtitle>{t("Manage your site locations and hierarchy")}</:subtitle>
<:actions>
<.link
navigate={~p"/sites-map"}
class="btn text-sm !py-1 !px-3 border border-gray-300 bg-transparent dark:border-gray-600 gap-1.5 mr-2"
>
<.icon name="hero-map" class="h-4 w-4" /> Map View
</.link>
<.button navigate={~p"/sites/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> New Site
</.button>
</:actions>
</.header>
<%= if @sites == [] do %>
<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-blue-100 dark:bg-blue-900/40 p-4 mb-2">
<.icon name="hero-map-pin-solid" class="h-12 w-12 text-blue-500 dark:text-blue-400" />
</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{t("No Sites Yet")}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
{t("Create your first site to organize devices by tower location.")}
</p>
<div class="mt-4 flex gap-2">
<.button navigate={~p"/sites/new"} variant="primary">
<.icon name="hero-plus" class="h-4 w-4" /> Add Site
</.button>
</div>
</div>
</div>
</div>
<% else %>
<%!-- Sites table --%>
<div class="mt-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
<thead class="bg-gray-50 dark:bg-gray-800/80">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Site")}
</th>
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Devices")}
</th>
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Down")}
</th>
<th
class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400"
title={t("Quality of Experience — Preseem network quality score (0100)")}
>
{t("QoE")}
</th>
<th class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Subscribers")}
</th>
<th class="hidden md:table-cell px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Location")}
</th>
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Coordinates")}
</th>
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Last Check")}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 bg-white dark:divide-white/5 dark:bg-gray-800/50">
<tr :for={site <- @sites} class="hover:bg-gray-50 dark:hover:bg-white/5">
<td class="px-4 py-2.5">
<.link
navigate={~p"/sites/#{site.id}"}
class="flex items-center gap-2 text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
>
<span class={[
"h-3 w-3 rounded-full flex-shrink-0",
health_dot(@summary_map[site.id] && @summary_map[site.id].site_health)
]}>
</span>
{site.name}
</.link>
<p :if={site.parent_site} class="text-xs text-gray-500 dark:text-gray-400 ml-4">
↳ {site.parent_site.name}
</p>
</td>
<td class="px-4 py-2.5 text-right text-sm text-gray-600 dark:text-gray-400">
{if @summary_map[site.id], do: @summary_map[site.id].device_count, else: 0}
</td>
<td class="px-4 py-2.5 text-right text-sm">
<%= if @summary_map[site.id] && @summary_map[site.id].devices_down > 0 do %>
<span class="text-red-600 dark:text-red-400">
{@summary_map[site.id].devices_down}
</span>
<% else %>
<span class="text-gray-400 dark:text-gray-500">0</span>
<% end %>
</td>
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm">
<%= if @summary_map[site.id] && @summary_map[site.id].avg_qoe do %>
<span class={qoe_color(@summary_map[site.id].avg_qoe)}>
{format_qoe(@summary_map[site.id].avg_qoe)}
</span>
<% else %>
<span class="text-gray-400 dark:text-gray-500">—</span>
<% end %>
</td>
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm text-gray-600 dark:text-gray-400">
<%= if @summary_map[site.id] && @summary_map[site.id].subscribers do %>
{@summary_map[site.id].subscribers}
<% else %>
<span class="text-gray-400 dark:text-gray-500">—</span>
<% end %>
</td>
<td class="hidden md:table-cell px-4 py-2.5 text-sm text-gray-500 dark:text-gray-400 max-w-xs truncate">
{site.location || "—"}
</td>
<td class="hidden lg:table-cell px-4 py-2.5 text-right">
<%= if site.latitude && site.longitude do %>
<span class="text-xs font-mono text-gray-400 dark:text-gray-500">
{Float.round(site.latitude, 4)}, {Float.round(site.longitude, 4)}
</span>
<% else %>
<span class="text-gray-400 dark:text-gray-500 text-sm">—</span>
<% end %>
</td>
<td class="hidden lg:table-cell px-4 py-2.5 text-right text-xs text-gray-500 dark:text-gray-400">
<span class="text-gray-400 dark:text-gray-500">—</span>
</td>
</tr>
</tbody>
</table>
</div>
<%!-- Insights --%>
<%= if @insights != [] do %>
<div class="mt-8">
<div class="flex items-center justify-between mb-3">
<h2 class="text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Insights")}
</h2>
<.link
navigate={~p"/insights"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all →
</.link>
</div>
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10 divide-y divide-gray-100 dark:divide-white/5">
<div
:for={insight <- @insights}
id={"insight-#{insight.id}"}
class="flex items-start justify-between gap-3 px-4 py-3 bg-white dark:bg-gray-800/50 hover:bg-gray-50 dark:hover:bg-white/5"
>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-1.5">
<span class={[
"inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium",
urgency_classes(insight.urgency)
]}>
{insight.urgency}
</span>
<span class={[
"inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium",
source_classes(insight.source)
]}>
{insight.source}
</span>
</div>
<p class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{insight.title}
</p>
<p
:if={insight.description}
class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-1"
>
{insight.description}
</p>
</div>
<button
type="button"
phx-click="dismiss_insight"
phx-value-id={insight.id}
class="flex-shrink-0 rounded p-1 text-gray-300 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-600 dark:hover:bg-white/10 dark:hover:text-gray-400"
title="Dismiss"
>
<.icon name="hero-x-mark" class="h-3.5 w-3.5" />
</button>
</div>
</div>
</div>
<% end %>
<% end %>
</Layouts.authenticated>