299 lines
16 KiB
Plaintext
299 lines
16 KiB
Plaintext
<div class="space-y-6">
|
|
<!-- Page header -->
|
|
<div class="border-b border-gray-200 pb-5">
|
|
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">SMS Logs</h1>
|
|
<p class="mt-2 text-sm text-gray-600">View and filter all SMS messages across your gateways.</p>
|
|
</div>
|
|
|
|
<!-- Filters card -->
|
|
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 px-6 py-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900">Filters</h3>
|
|
<i class="fas fa-filter text-gray-400"></i>
|
|
</div>
|
|
|
|
<%= form_with url: admin_logs_path, method: :get, local: true do |f| %>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<!-- Direction filter -->
|
|
<div>
|
|
<%= label_tag :direction, "Direction", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= select_tag :direction,
|
|
options_for_select([["All Directions", ""], ["Inbound", "inbound"], ["Outbound", "outbound"]], params[:direction]),
|
|
class: "block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
|
</div>
|
|
|
|
<!-- Status filter -->
|
|
<div>
|
|
<%= label_tag :status, "Status", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= select_tag :status,
|
|
options_for_select([["All Statuses", ""], ["Pending", "pending"], ["Queued", "queued"], ["Sent", "sent"], ["Delivered", "delivered"], ["Failed", "failed"]], params[:status]),
|
|
class: "block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
|
</div>
|
|
|
|
<!-- Phone number filter -->
|
|
<div>
|
|
<%= label_tag :phone_number, "Phone Number", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<div class="relative">
|
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
<i class="fas fa-phone text-gray-400"></i>
|
|
</div>
|
|
<%= text_field_tag :phone_number, params[:phone_number],
|
|
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
|
|
placeholder: "Search phone..." %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gateway filter -->
|
|
<div>
|
|
<%= label_tag :gateway_id, "Gateway", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<%= select_tag :gateway_id,
|
|
options_for_select([["All Gateways", ""]] + Gateway.order(:name).pluck(:name, :id), params[:gateway_id]),
|
|
class: "block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
|
</div>
|
|
|
|
<!-- Start date filter -->
|
|
<div>
|
|
<%= label_tag :start_date, "Start Date", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<div class="relative">
|
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
<i class="fas fa-calendar text-gray-400"></i>
|
|
</div>
|
|
<%= date_field_tag :start_date, params[:start_date],
|
|
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- End date filter -->
|
|
<div>
|
|
<%= label_tag :end_date, "End Date", class: "block text-sm font-medium text-gray-700 mb-1" %>
|
|
<div class="relative">
|
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
<i class="fas fa-calendar text-gray-400"></i>
|
|
</div>
|
|
<%= date_field_tag :end_date, params[:end_date],
|
|
class: "block w-full pl-10 rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter buttons -->
|
|
<div class="flex items-center gap-3 mt-6 pt-4 border-t border-gray-200">
|
|
<%= submit_tag "Apply Filters",
|
|
class: "inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 transition-all duration-200" do %>
|
|
<i class="fas fa-filter"></i>
|
|
Apply Filters
|
|
<% end %>
|
|
<%= link_to admin_logs_path,
|
|
class: "inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-200 transition-all duration-200" do %>
|
|
<i class="fas fa-times"></i>
|
|
Clear Filters
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
|
|
<!-- Messages table card -->
|
|
<div class="rounded-xl bg-white shadow-sm ring-1 ring-gray-900/5 overflow-hidden">
|
|
<% if @messages.any? %>
|
|
<!-- Table header with count -->
|
|
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
|
<p class="text-sm text-gray-700">
|
|
Showing <span class="font-semibold"><%= @messages.size %></span> of <span class="font-semibold"><%= @pagy.count %></span> messages
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Message ID</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Phone Number</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Message</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Direction</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Status</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Gateway</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Retries</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Created</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500">Processed</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
<% @messages.each do |msg| %>
|
|
<tr class="hover:bg-gray-50 transition-colors cursor-pointer" onclick="toggleErrorRow('error-<%= msg.id %>')">
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
|
<code class="rounded bg-gray-100 px-2 py-1 text-xs font-mono text-gray-800"><%= msg.message_id[0..15] %>...</code>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
|
|
<%= msg.phone_number %>
|
|
</td>
|
|
<td class="px-6 py-4 text-sm text-gray-700">
|
|
<div class="max-w-md whitespace-pre-wrap break-words"><%= msg.message_body %></div>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
|
<% if msg.direction == "outbound" %>
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
|
|
<i class="fas fa-arrow-up"></i> Outbound
|
|
</span>
|
|
<% else %>
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">
|
|
<i class="fas fa-arrow-down"></i> Inbound
|
|
</span>
|
|
<% end %>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
|
<% case msg.status %>
|
|
<% when "delivered" %>
|
|
<span class="inline-flex rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10">Delivered</span>
|
|
<% when "sent" %>
|
|
<span class="inline-flex rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">Sent</span>
|
|
<% when "failed" %>
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10">
|
|
<i class="fas fa-exclamation-circle"></i> Failed
|
|
</span>
|
|
<% when "pending" %>
|
|
<span class="inline-flex rounded-full bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-yellow-700/10">Pending</span>
|
|
<% when "queued" %>
|
|
<span class="inline-flex rounded-full bg-purple-50 px-2 py-1 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10">Queued</span>
|
|
<% else %>
|
|
<span class="inline-flex rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-700/10"><%= msg.status.titleize %></span>
|
|
<% end %>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
<%= msg.gateway&.name || "-" %>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-center text-gray-500">
|
|
<% if msg.retry_count > 0 %>
|
|
<span class="inline-flex items-center justify-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800">
|
|
<%= msg.retry_count %>
|
|
</span>
|
|
<% else %>
|
|
-
|
|
<% end %>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
<%= msg.created_at.strftime("%m/%d/%y %H:%M") %>
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
<% if msg.delivered_at %>
|
|
<span class="text-green-600"><%= msg.delivered_at.strftime("%m/%d/%y %H:%M") %></span>
|
|
<% elsif msg.sent_at %>
|
|
<span class="text-blue-600"><%= msg.sent_at.strftime("%m/%d/%y %H:%M") %></span>
|
|
<% elsif msg.failed_at %>
|
|
<span class="text-red-600"><%= msg.failed_at.strftime("%m/%d/%y %H:%M") %></span>
|
|
<% else %>
|
|
-
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% if msg.error_message.present? %>
|
|
<tr id="error-<%= msg.id %>" class="hidden bg-red-50">
|
|
<td colspan="9" class="px-6 py-4">
|
|
<div class="flex items-start gap-3">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-exclamation-triangle text-red-600"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-semibold text-red-800">Error Message:</p>
|
|
<p class="text-sm text-red-700 mt-1"><%= msg.error_message %></p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<% if @pagy.pages > 1 %>
|
|
<div class="border-t border-gray-200 px-6 py-4 bg-gray-50">
|
|
<div class="flex items-center justify-between">
|
|
<!-- Page info -->
|
|
<div class="text-sm text-gray-700">
|
|
Showing
|
|
<span class="font-semibold"><%= @pagy.from %></span>
|
|
to
|
|
<span class="font-semibold"><%= @pagy.to %></span>
|
|
of
|
|
<span class="font-semibold"><%= @pagy.count %></span>
|
|
results
|
|
</div>
|
|
|
|
<!-- Pagination controls -->
|
|
<nav class="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
|
<% if @pagy.prev %>
|
|
<%= link_to admin_logs_path(params.permit!.merge(page: @pagy.prev)),
|
|
class: "relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" do %>
|
|
<i class="fas fa-chevron-left"></i>
|
|
<span class="sr-only">Previous</span>
|
|
<% end %>
|
|
<% else %>
|
|
<span class="relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold text-gray-400 ring-1 ring-inset ring-gray-300 cursor-not-allowed">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</span>
|
|
<% end %>
|
|
|
|
<% @pagy.series.each do |item| %>
|
|
<% if item.is_a?(Integer) %>
|
|
<% if item == @pagy.page %>
|
|
<span class="relative z-10 inline-flex items-center bg-blue-600 px-4 py-2 text-sm font-semibold text-white ring-1 ring-inset ring-blue-600"><%= item %></span>
|
|
<% else %>
|
|
<%= link_to item, admin_logs_path(params.permit!.merge(page: item)),
|
|
class: "relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" %>
|
|
<% end %>
|
|
<% elsif item == :gap %>
|
|
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300">...</span>
|
|
<% end %>
|
|
<% end %>
|
|
|
|
<% if @pagy.next %>
|
|
<%= link_to admin_logs_path(params.permit!.merge(page: @pagy.next)),
|
|
class: "relative inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" do %>
|
|
<span class="sr-only">Next</span>
|
|
<i class="fas fa-chevron-right"></i>
|
|
<% end %>
|
|
<% else %>
|
|
<span class="relative inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold text-gray-400 ring-1 ring-inset ring-gray-300 cursor-not-allowed">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</span>
|
|
<% end %>
|
|
</nav>
|
|
|
|
<!-- Page size selector -->
|
|
<div class="text-sm text-gray-700">
|
|
<%= form_with url: admin_logs_path, method: :get, local: true, class: "inline-flex items-center gap-2" do |f| %>
|
|
<label for="items" class="font-medium">Per page:</label>
|
|
<%= select_tag :items,
|
|
options_for_select([10, 25, 50, 100], params[:items] || 50),
|
|
onchange: "this.form.submit()",
|
|
class: "rounded-md border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500" %>
|
|
<% params.except(:items, :authenticity_token).each do |key, value| %>
|
|
<%= hidden_field_tag key, value %>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% else %>
|
|
<!-- Empty state -->
|
|
<div class="px-6 py-14 text-center">
|
|
<i class="fas fa-inbox text-4xl text-gray-300"></i>
|
|
<p class="mt-4 text-sm font-medium text-gray-900">No messages found</p>
|
|
<p class="mt-2 text-sm text-gray-500">Try adjusting your filters to see more results.</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function toggleErrorRow(id) {
|
|
const row = document.getElementById(id);
|
|
if (row) {
|
|
row.classList.toggle('hidden');
|
|
}
|
|
}
|
|
</script>
|