177 lines
7.6 KiB
Text
177 lines
7.6 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>
|
|
</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-2 w-2 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>
|
|
</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>
|