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

205 lines
9.1 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="sites"
>
<.header>
{@page_title}
<:subtitle>{t("Manage your site locations and hierarchy")}</:subtitle>
<:actions>
<.link navigate={~p"/sites-map"} class="btn btn-outline btn-sm 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 bg-base-100 shadow-md border border-base-200 dark:border-white/10 max-w-md w-full">
<div class="card-body 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="card-title 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="card-actions mt-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-hidden 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="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("QoE")}
</th>
<th class="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="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="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="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
{t("Last Check")}
</th>
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
</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="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="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="px-4 py-2.5 text-sm text-gray-500 dark:text-gray-400 max-w-xs truncate">
{site.location || "—"}
</td>
<td class="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="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>
<td class="px-4 py-2.5 text-right">
<.link
navigate={~p"/devices/new?site_id=#{site.id}"}
class="inline-flex items-center gap-1 text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
<.icon name="hero-plus" class="h-3.5 w-3.5" /> {t("Add Device")}
</.link>
</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>