Files
sanasto-wiki/app/views/entries/index.html.erb

202 lines
12 KiB
Plaintext
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.
<% content_for :title, "Sanasto Wiki" %>
<div class="min-h-screen flex flex-col">
<header class="bg-white border-b border-slate-200">
<div class="max-w-7xl mx-auto px-4">
<div class="h-16 flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-xl font-bold tracking-tight text-indigo-600">Sanasto</span>
<span class="text-xl font-light text-slate-400">Wiki</span>
</div>
<div class="flex items-center gap-3">
<span class="text-xs font-semibold text-emerald-700 bg-emerald-50 px-3 py-1 rounded-full border border-emerald-200">Read-only public view</span>
<%= link_to "Download XLSX",
download_entries_path(format: :xlsx),
class: "text-xs font-bold text-indigo-700 px-3 py-2 rounded-md border border-indigo-200 bg-indigo-50 hover:bg-indigo-100 transition" %>
<%= link_to "Sign In", "#", class: "bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-semibold hover:bg-indigo-700 transition" %>
</div>
</div>
<div class="pb-6 space-y-4">
<%= form_with url: entries_path, method: :get, local: true do |form| %>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<% if @query.present? %>
<div class="absolute inset-y-0 right-0 pr-4 flex items-center">
<%= link_to "×",
entries_path(category: @category.presence, language: @language_code.presence, starts_with: @starts_with.presence),
class: "text-slate-400 hover:text-slate-600 text-2xl leading-none",
aria: { label: "Clear search" } %>
</div>
<% end %>
<%= form.text_field :q,
value: @query,
placeholder: "Search words, phrases, or biblical terms...",
class: "block w-full pl-11 pr-10 py-4 bg-white border border-slate-200 rounded-2xl shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" %>
</div>
<% end %>
<div class="flex flex-wrap gap-2">
<% base_params = { q: @query.presence, category: @category.presence, language: @language_code.presence, starts_with: @starts_with.presence }.compact %>
<% all_category_params = base_params.except(:category) %>
<%= link_to "All",
entries_path(all_category_params),
class: "px-4 py-1.5 rounded-full #{@category.blank? ? 'bg-indigo-100 text-indigo-700' : 'bg-white border border-slate-200 text-slate-600 hover:border-indigo-300'} text-xs font-bold uppercase tracking-wider" %>
<% Entry.categories.keys.each do |category_name| %>
<%= link_to category_name.tr('_', ' ').capitalize,
entries_path(base_params.merge(category: category_name)),
class: "px-4 py-1.5 rounded-full #{@category == category_name ? 'bg-indigo-100 text-indigo-700' : 'bg-white border border-slate-200 text-slate-600 hover:border-indigo-300'} text-xs font-bold uppercase tracking-wider" %>
<% end %>
</div>
<div class="flex flex-wrap gap-2">
<% all_language_params = base_params.except(:language, :starts_with, :page) %>
<%= link_to "All Languages",
entries_path(all_language_params),
class: "px-3 py-1.5 rounded-full #{@language_code.blank? ? 'bg-slate-900 text-white' : 'bg-white border border-slate-200 text-slate-600 hover:border-slate-300'} text-xs font-semibold uppercase tracking-wider" %>
<% @supported_languages.each do |language| %>
<%= link_to "#{language.name} (#{language.code.upcase})",
entries_path(all_language_params.merge(language: language.code)),
class: "px-3 py-1.5 rounded-full #{@language_code == language.code ? 'bg-slate-900 text-white' : 'bg-white border border-slate-200 text-slate-600 hover:border-slate-300'} text-xs font-semibold uppercase tracking-wider" %>
<% end %>
</div>
<% if @language_code.present? %>
<div class="flex flex-wrap gap-2 text-xs">
<% alphabet_params = base_params.merge(language: @language_code).except(:starts_with, :page) %>
<%= link_to "All",
entries_path(alphabet_params),
class: "px-2.5 py-1 rounded-md #{@starts_with.blank? ? 'bg-indigo-600 text-white' : 'bg-white border border-slate-200 text-slate-600 hover:border-indigo-300'}" %>
<% alphabet_letters(@language_code).each do |letter| %>
<%= link_to letter,
entries_path(alphabet_params.merge(starts_with: letter)),
class: "px-2.5 py-1 rounded-md #{@starts_with == letter ? 'bg-indigo-600 text-white' : 'bg-white border border-slate-200 text-slate-600 hover:border-indigo-300'}" %>
<% end %>
</div>
<% end %>
</div>
</div>
</header>
<section class="bg-slate-50 border-b border-slate-200">
<div class="max-w-7xl mx-auto px-4 py-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white border border-slate-200 rounded-xl p-4 shadow-sm">
<div class="text-xs uppercase tracking-widest text-slate-400">Fully Translated</div>
<div class="text-2xl font-bold text-slate-900"><%= number_with_delimiter(@complete_entries_count) %></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4 shadow-sm">
<div class="text-xs uppercase tracking-widest text-slate-400">Needs Review</div>
<div class="text-2xl font-bold text-amber-600"><%= number_with_delimiter(@needs_review_count) %></div>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-4 shadow-sm">
<div class="text-xs uppercase tracking-widest text-slate-400">Missing Translations</div>
<div class="text-2xl font-bold text-red-600"><%= number_with_delimiter(@missing_entries_count) %></div>
</div>
</div>
</div>
</section>
<main class="flex-1 overflow-hidden">
<div class="max-w-7xl mx-auto px-4 h-full flex flex-col py-6">
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-lg font-bold text-slate-900">Translation Table</h2>
<p class="text-sm text-slate-500">Public glossary entries with verified status and language coverage.</p>
</div>
<div class="text-xs text-slate-500"><%= @total_entries %> entries</div>
</div>
<div class="flex-1 bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
<div class="h-full overflow-auto">
<table class="min-w-full text-sm border-separate border-spacing-0">
<thead class="sticky top-0 z-20 bg-slate-50 border-b border-slate-200">
<tr>
<% preferred_language_code = @language_code.presence || "fi" %>
<% preferred_language = @display_languages.find { |language| language.code == preferred_language_code } %>
<th class="sticky left-0 z-30 bg-slate-50 text-left px-4 py-3 border-b border-slate-200">
<div class="text-xs font-semibold text-slate-700"><%= preferred_language&.name || "Finnish" %>
<span class="text-[10px] uppercase tracking-widest text-slate-400"><%= preferred_language_code.upcase %></span></div>
<div class="text-[10px] text-slate-400">Category / Status</div>
</th>
<% table_languages = @display_languages.reject { |language| language.code == preferred_language_code } %>
<% if preferred_language_code != "en" %>
<% english_language, other_languages = table_languages.partition { |language| language.code == "en" } %>
<% table_languages = english_language + other_languages %>
<% end %>
<% table_languages.each do |language| %>
<th class="px-4 py-3 text-left border-b border-slate-200">
<div class="text-xs font-semibold text-slate-700"><%= language.name %>
<span class="text-[10px] uppercase tracking-widest text-slate-400"><%= language.code.upcase %></span></div>
<div class="mt-1 text-[10px] font-semibold text-emerald-600">
<%= @language_completion.fetch(language, 0) %>% complete
</div>
</th>
<% end %>
</tr>
</thead>
<tbody class="bg-white text-slate-700">
<% if @entries.empty? %>
<tr>
<td colspan="<%= table_languages.size + 1 %>" class="px-6 py-6 text-slate-500">
No entries matched your filters.
</td>
</tr>
<% else %>
<% @entries.each do |entry| %>
<% translation_values = table_languages.map { |language| entry.public_send(language.code) } %>
<% missing_any = translation_values.any?(&:blank?) %>
<tr class="border-b border-slate-100 <%= missing_any ? 'bg-red-50/40' : '' %>">
<td class="sticky left-0 z-10 bg-white px-4 py-4 border-b border-slate-100 w-72">
<% primary_text = entry.public_send(preferred_language_code).presence || "Untitled" %>
<div class="font-semibold text-slate-900"><%= primary_text %></div>
<div class="mt-1 text-xs text-slate-500 flex items-center gap-2">
<span class="uppercase tracking-wider"><%= format_entry_category(entry) %></span>
<% if entry.verified? %>
<span class="text-emerald-600 font-semibold">Verified</span>
<% else %>
<span class="text-amber-600 font-semibold">Unverified</span>
<% end %>
<%= link_to "View",
entry_path(entry),
class: "text-indigo-600 font-semibold hover:underline" %>
</div>
</td>
<% table_languages.each do |language| %>
<% translation = entry.public_send(language.code) %>
<td class="px-4 py-4 border-b border-slate-100">
<% if translation.present? %>
<span class="text-slate-900"><%= translation %></span>
<% else %>
<span class="text-slate-400">—</span>
<% end %>
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="flex items-center justify-between mt-4 text-sm text-slate-600">
<div>Page <%= @page %> of <%= [@total_pages, 1].max %></div>
<div class="flex items-center gap-2">
<% previous_page = @page > 1 ? @page - 1 : nil %>
<% next_page = @page < @total_pages ? @page + 1 : nil %>
<% pagination_params = { q: @query.presence, category: @category.presence, language: @language_code.presence, starts_with: @starts_with.presence }.compact %>
<%= link_to "Previous", previous_page ? entries_path(pagination_params.merge(page: previous_page)) : "#",
class: "px-3 py-1.5 rounded-md border border-slate-200 #{previous_page ? 'hover:border-indigo-300' : 'text-slate-300 cursor-not-allowed'}" %>
<%= link_to "Next", next_page ? entries_path(pagination_params.merge(page: next_page)) : "#",
class: "px-3 py-1.5 rounded-md border border-slate-200 #{next_page ? 'hover:border-indigo-300' : 'text-slate-300 cursor-not-allowed'}" %>
</div>
</div>
</div>
</main>
</div>