6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
795 lines
21 KiB
HTML
795 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Global Content Network — Landvex/QuiXzoom</title>
|
|
<style>
|
|
:root {
|
|
--primary: #2563eb;
|
|
--primary-dark: #1d4ed8;
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--gray-50: #f9fafb;
|
|
--gray-100: #f3f4f6;
|
|
--gray-200: #e5e7eb;
|
|
--gray-300: #d1d5db;
|
|
--gray-600: #4b5563;
|
|
--gray-800: #1f2937;
|
|
--gray-900: #111827;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: var(--gray-50);
|
|
color: var(--gray-800);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 0 24px;
|
|
}
|
|
|
|
/* Header */
|
|
.header {
|
|
background: var(--gray-900);
|
|
color: white;
|
|
padding: 16px 0;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.header-content {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.logo-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
background: var(--primary);
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.nav {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.nav-btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--gray-300);
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.nav-btn:hover, .nav-btn.active {
|
|
background: rgba(255,255,255,0.1);
|
|
color: white;
|
|
}
|
|
|
|
/* Dashboard Grid */
|
|
.dashboard {
|
|
padding: 32px 0;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.page-subtitle {
|
|
color: var(--gray-600);
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
color: var(--gray-600);
|
|
margin-bottom: 8px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: var(--gray-900);
|
|
}
|
|
|
|
.stat-change {
|
|
font-size: 13px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.stat-change.positive { color: var(--success); }
|
|
.stat-change.negative { color: var(--danger); }
|
|
.stat-change.warning { color: var(--warning); }
|
|
|
|
/* Content Sections */
|
|
.section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
margin-bottom: 24px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--primary-dark);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--gray-100);
|
|
color: var(--gray-800);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--gray-200);
|
|
}
|
|
|
|
/* Tables */
|
|
.table-container {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
}
|
|
|
|
th {
|
|
text-align: left;
|
|
padding: 12px;
|
|
font-weight: 600;
|
|
color: var(--gray-600);
|
|
border-bottom: 2px solid var(--gray-200);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--gray-100);
|
|
}
|
|
|
|
tr:hover {
|
|
background: var(--gray-50);
|
|
}
|
|
|
|
/* Status badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 10px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.badge-published {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
|
|
.badge-draft {
|
|
background: var(--gray-100);
|
|
color: var(--gray-600);
|
|
}
|
|
|
|
.badge-review {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.badge-outdated {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.badge-ai {
|
|
background: #dbeafe;
|
|
color: #1e40af;
|
|
}
|
|
|
|
/* Progress bars */
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: 8px;
|
|
background: var(--gray-200);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
.progress-fill.high { background: var(--success); }
|
|
.progress-fill.medium { background: var(--warning); }
|
|
.progress-fill.low { background: var(--danger); }
|
|
|
|
/* Site cards */
|
|
.sites-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.site-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
border-left: 4px solid var(--primary);
|
|
}
|
|
|
|
.site-card.tier-global {
|
|
border-left-color: var(--primary);
|
|
}
|
|
|
|
.site-card.tier-national {
|
|
border-left-color: var(--success);
|
|
}
|
|
|
|
.site-card.tier-regional {
|
|
border-left-color: var(--warning);
|
|
}
|
|
|
|
.site-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: start;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.site-domain {
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.site-tier {
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
background: var(--gray-100);
|
|
color: var(--gray-600);
|
|
}
|
|
|
|
.site-languages {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.lang-tag {
|
|
font-size: 12px;
|
|
padding: 2px 8px;
|
|
background: var(--gray-100);
|
|
border-radius: 4px;
|
|
color: var(--gray-600);
|
|
}
|
|
|
|
/* Workflow steps */
|
|
.workflow {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.workflow-step {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.workflow-step.pending {
|
|
background: var(--gray-100);
|
|
color: var(--gray-600);
|
|
}
|
|
|
|
.workflow-step.active {
|
|
background: #dbeafe;
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.workflow-step.completed {
|
|
background: #d1fae5;
|
|
color: var(--success);
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.sites-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.nav {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Loading */
|
|
.loading {
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--gray-200);
|
|
border-top-color: var(--primary);
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<div class="container header-content">
|
|
<div class="logo">
|
|
<div class="logo-icon">🌐</div>
|
|
<span>Global Content Network</span>
|
|
</div>
|
|
<nav class="nav">
|
|
<button class="nav-btn active" data-view="dashboard">Dashboard</button>
|
|
<button class="nav-btn" data-view="sites">Sites</button>
|
|
<button class="nav-btn" data-view="content">Content</button>
|
|
<button class="nav-btn" data-view="translations">Translations</button>
|
|
<button class="nav-btn" data-view="glossary">Glossary</button>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="dashboard container">
|
|
<!-- Dashboard View -->
|
|
<div id="dashboard-view">
|
|
<h1 class="page-title">Dashboard</h1>
|
|
<p class="page-subtitle">Overview of your global content network</p>
|
|
|
|
<div class="stats-grid" id="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-label">Total Pages</div>
|
|
<div class="stat-value" id="stat-pages">-</div>
|
|
<div class="stat-change">Across all brands</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Translations</div>
|
|
<div class="stat-value" id="stat-translations">-</div>
|
|
<div class="stat-change">Total localized versions</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Outdated</div>
|
|
<div class="stat-value" id="stat-outdated">-</div>
|
|
<div class="stat-change warning">Need attention</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Languages</div>
|
|
<div class="stat-value" id="stat-languages">-</div>
|
|
<div class="stat-change positive">Active locales</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Translation Status by Language</h2>
|
|
</div>
|
|
<div id="language-coverage">
|
|
<div class="loading"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Recent Activity</h2>
|
|
</div>
|
|
<div class="table-container">
|
|
<table id="activity-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Action</th>
|
|
<th>Entity</th>
|
|
<th>Language</th>
|
|
<th>Domain</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="activity-body">
|
|
<tr><td colspan="5"><div class="loading"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sites View -->
|
|
<div id="sites-view" class="hidden">
|
|
<h1 class="page-title">Sites</h1>
|
|
<p class="page-subtitle">All sites in the global network</p>
|
|
<div class="sites-grid" id="sites-grid">
|
|
<div class="loading"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content View -->
|
|
<div id="content-view" class="hidden">
|
|
<h1 class="page-title">Content</h1>
|
|
<p class="page-subtitle">Manage source content</p>
|
|
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Pages</h2>
|
|
<button class="btn btn-primary" onclick="showCreatePage()">+ New Page</button>
|
|
</div>
|
|
<div class="table-container">
|
|
<table id="pages-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Title</th>
|
|
<th>Brand</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Version</th>
|
|
<th>Updated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="pages-body">
|
|
<tr><td colspan="7"><div class="loading"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Translations View -->
|
|
<div id="translations-view" class="hidden">
|
|
<h1 class="page-title">Translations</h1>
|
|
<p class="page-subtitle">Manage localized content</p>
|
|
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Translation Queue</h2>
|
|
</div>
|
|
<div class="table-container">
|
|
<table id="jobs-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Page</th>
|
|
<th>Language</th>
|
|
<th>Domain</th>
|
|
<th>Status</th>
|
|
<th>Priority</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="jobs-body">
|
|
<tr><td colspan="6"><div class="loading"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Glossary View -->
|
|
<div id="glossary-view" class="hidden">
|
|
<h1 class="page-title">Glossary</h1>
|
|
<p class="page-subtitle">Approved terminology across languages</p>
|
|
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Terms</h2>
|
|
<button class="btn btn-primary" onclick="showCreateTerm()">+ New Term</button>
|
|
</div>
|
|
<div class="table-container">
|
|
<table id="glossary-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Term</th>
|
|
<th>Brand</th>
|
|
<th>Category</th>
|
|
<th>Status</th>
|
|
<th>Translations</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="glossary-body">
|
|
<tr><td colspan="5"><div class="loading"></div></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
// State
|
|
let currentView = 'dashboard';
|
|
const API_BASE = '/api';
|
|
|
|
// Navigation
|
|
document.querySelectorAll('.nav-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const view = btn.dataset.view;
|
|
switchView(view);
|
|
});
|
|
});
|
|
|
|
function switchView(view) {
|
|
currentView = view;
|
|
|
|
// Update nav
|
|
document.querySelectorAll('.nav-btn').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.view === view);
|
|
});
|
|
|
|
// Show/hide views
|
|
document.querySelectorAll('[id$="-view"]').forEach(el => {
|
|
el.classList.toggle('hidden', !el.id.startsWith(view));
|
|
});
|
|
|
|
// Load data
|
|
if (view === 'dashboard') loadDashboard();
|
|
if (view === 'sites') loadSites();
|
|
if (view === 'content') loadContent();
|
|
if (view === 'translations') loadTranslations();
|
|
if (view === 'glossary') loadGlossary();
|
|
}
|
|
|
|
// API helpers
|
|
async function api(endpoint) {
|
|
const res = await fetch(`${API_BASE}${endpoint}`);
|
|
return res.json();
|
|
}
|
|
|
|
// Load dashboard data
|
|
async function loadDashboard() {
|
|
try {
|
|
const analytics = await api('/analytics/translation-status');
|
|
|
|
document.getElementById('stat-pages').textContent = analytics.totalPages;
|
|
document.getElementById('stat-translations').textContent = analytics.totalTranslations;
|
|
document.getElementById('stat-outdated').textContent = analytics.outdatedPages;
|
|
document.getElementById('stat-languages').textContent = Object.keys(analytics.coverage.byLanguage).length;
|
|
|
|
// Language coverage
|
|
const coverageHtml = Object.entries(analytics.coverage.byLanguage).map(([lang, data]) => {
|
|
const pct = Math.round((data.published / data.total) * 100) || 0;
|
|
const color = pct >= 80 ? 'high' : pct >= 50 ? 'medium' : 'low';
|
|
return `
|
|
<div style="margin-bottom: 16px;">
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
|
<span style="font-weight: 500;">${lang}</span>
|
|
<span style="color: var(--gray-600);">${data.published}/${data.total} published</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill ${color}" style="width: ${pct}%"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
document.getElementById('language-coverage').innerHTML = coverageHtml || '<p style="color: var(--gray-600);">No translations yet</p>';
|
|
|
|
// Recent activity
|
|
const changelog = await api('/changelog?limit=10');
|
|
const activityBody = document.getElementById('activity-body');
|
|
activityBody.innerHTML = changelog.map(entry => `
|
|
<tr>
|
|
<td>${new Date(entry.performedAt).toLocaleString()}</td>
|
|
<td><span class="badge badge-${entry.action === 'published' ? 'published' : 'draft'}">${entry.action}</span></td>
|
|
<td>${entry.entityType} #${entry.entityId}</td>
|
|
<td>${entry.languageCode || '-'}</td>
|
|
<td>${entry.domain || '-'}</td>
|
|
</tr>
|
|
`).join('') || '<tr><td colspan="5" style="text-align: center; color: var(--gray-600);">No activity yet</td></tr>';
|
|
|
|
} catch (err) {
|
|
console.error('Failed to load dashboard:', err);
|
|
}
|
|
}
|
|
|
|
// Load sites
|
|
async function loadSites() {
|
|
try {
|
|
const sites = await api('/sites');
|
|
const grid = document.getElementById('sites-grid');
|
|
|
|
grid.innerHTML = sites.map(site => `
|
|
<div class="site-card tier-${site.tier}">
|
|
<div class="site-header">
|
|
<div>
|
|
<div class="site-domain">${site.domain}</div>
|
|
<div style="font-size: 13px; color: var(--gray-600); margin-top: 2px;">${site.brand}</div>
|
|
</div>
|
|
<span class="site-tier">${site.tier}</span>
|
|
</div>
|
|
<div style="font-size: 13px; color: var(--gray-600);">
|
|
Default: ${site.defaultLanguage}
|
|
</div>
|
|
<div class="site-languages">
|
|
${site.languages.map(l => `<span class="lang-tag">${l.code}</span>`).join('')}
|
|
</div>
|
|
<div style="margin-top: 12px; font-size: 13px;">
|
|
${Object.entries(site.features).filter(([_, v]) => v).map(([k]) => k).join(', ')}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} catch (err) {
|
|
console.error('Failed to load sites:', err);
|
|
}
|
|
}
|
|
|
|
// Load content
|
|
async function loadContent() {
|
|
try {
|
|
const data = await api('/pages?limit=50');
|
|
const tbody = document.getElementById('pages-body');
|
|
|
|
tbody.innerHTML = data.pages.map(page => `
|
|
<tr>
|
|
<td><strong>${page.title}</strong><br><span style="font-size: 12px; color: var(--gray-600);">${page.slug}</span></td>
|
|
<td><span class="badge badge-${page.brand === 'landvex' ? 'draft' : 'ai'}">${page.brand}</span></td>
|
|
<td>${page.contentType}</td>
|
|
<td><span class="badge badge-${page.status}">${page.status}</span></td>
|
|
<td>v${page.version}</td>
|
|
<td>${new Date(page.updatedAt).toLocaleDateString()}</td>
|
|
<td>
|
|
<button class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;" onclick="viewPage(${page.id})">View</button>
|
|
${page.needsTranslationUpdate ? '<span style="color: var(--danger); margin-left: 8px;">⚠️</span>' : ''}
|
|
</td>
|
|
</tr>
|
|
`).join('') || '<tr><td colspan="7" style="text-align: center;">No pages yet</td></tr>';
|
|
} catch (err) {
|
|
console.error('Failed to load content:', err);
|
|
}
|
|
}
|
|
|
|
// Load translations
|
|
async function loadTranslations() {
|
|
try {
|
|
const jobs = await api('/jobs?limit=50');
|
|
const tbody = document.getElementById('jobs-body');
|
|
|
|
tbody.innerHTML = jobs.map(job => `
|
|
<tr>
|
|
<td>Page #${job.pageId}</td>
|
|
<td>${job.targetLanguage}</td>
|
|
<td>${job.targetDomain}</td>
|
|
<td><span class="badge badge-${job.status === 'completed' ? 'published' : job.status === 'failed' ? 'outdated' : 'review'}">${job.status}</span></td>
|
|
<td>${job.priority}</td>
|
|
<td>${new Date(job.createdAt).toLocaleDateString()}</td>
|
|
</tr>
|
|
`).join('') || '<tr><td colspan="6" style="text-align: center;">No jobs in queue</td></tr>';
|
|
} catch (err) {
|
|
console.error('Failed to load translations:', err);
|
|
}
|
|
}
|
|
|
|
// Load glossary
|
|
async function loadGlossary() {
|
|
try {
|
|
const terms = await api('/glossary');
|
|
const tbody = document.getElementById('glossary-body');
|
|
|
|
tbody.innerHTML = terms.map(term => {
|
|
const translations = term.translations || {};
|
|
const translationCount = Object.keys(translations).length;
|
|
|
|
return `
|
|
<tr>
|
|
<td><strong>${term.term}</strong></td>
|
|
<td><span class="badge badge-${term.brand === 'both' ? 'published' : 'draft'}">${term.brand}</span></td>
|
|
<td>${term.category}</td>
|
|
<td><span class="badge badge-${term.status}">${term.status}</span></td>
|
|
<td>${translationCount} languages</td>
|
|
</tr>
|
|
`;
|
|
}).join('') || '<tr><td colspan="5" style="text-align: center;">No glossary entries yet</td></tr>';
|
|
} catch (err) {
|
|
console.error('Failed to load glossary:', err);
|
|
}
|
|
}
|
|
|
|
// Actions
|
|
function showCreatePage() {
|
|
alert('Create page functionality - implement modal form');
|
|
}
|
|
|
|
function showCreateTerm() {
|
|
alert('Create term functionality - implement modal form');
|
|
}
|
|
|
|
function viewPage(id) {
|
|
alert(`View page ${id} - implement detail view`);
|
|
}
|
|
|
|
// Initial load
|
|
loadDashboard();
|
|
</script>
|
|
</body>
|
|
</html>
|