feat(typescript): add feature modules for template conversion

- Add search.ts - header search with keyboard navigation
  - Debounced search with 300ms delay
  - Arrow key navigation through results
  - Escape to close, Enter to select
  - Library type icons and highlighting

- Add collections.ts - collection and rule management
  - Rule CRUD operations (create, update, delete)
  - Rule testing functionality
  - Bulk collection operations

- Add bookshelf.ts - book display and navigation
  - Library selection state management
  - Book viewing interactions
  - Pagination logic

- Add linking.ts - book matching and manual linking
  - Search and match functionality
  - Manual link modal
  - Bulk auto-link and suggestions

- Add api-explorer.ts - API testing interface
  - Request/response display
  - cURL command generation
  - History tracking

- Add admin.ts - admin dashboard actions
  - Library scan triggers
  - System statistics display
  - Profile management

- Add analytics.ts - analytics data loading
  - Chart.js integration
  - Daily reading minutes chart
  - Device usage and popular books display

- Add queue.ts - sync queue management
  - Process pending items
  - Clear failed/all items
  - Filter by status, type, device

- Add conflicts.ts - conflict resolution
  - Individual and bulk resolve operations
  - Winner device selection
  - Manual override inputs

- Add docs.ts - documentation search
  - Lunr.js search integration
  - Sidebar toggle for mobile
This commit is contained in:
2026-02-18 16:40:51 -05:00
parent 60c5a093b5
commit dfd9cbcde7
10 changed files with 1648 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
async function triggerLibraryScan(): Promise<void> {
const token = localStorage.getItem('token');
if (!token) return;
try {
const response = await fetch('/api/libraries/scan', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
if ((window as any).showToast?.success) {
(window as any).showToast.success('Library scan started');
}
} else {
const error = await response.json();
if ((window as any).showToast?.error) {
(window as any).showToast.error(error.error || 'Failed to start scan');
}
}
} catch (error) {
console.error('Scan error:', error);
if ((window as any).showToast?.error) {
(window as any).showToast.error('Failed to start library scan');
}
}
}
async function triggerQuickScan(): Promise<void> {
const token = localStorage.getItem('token');
if (!token) return;
try {
const response = await fetch('/api/libraries/quick-scan', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
if ((window as any).showToast?.success) {
(window as any).showToast.success('Quick scan started');
}
} else {
const error = await response.json();
if ((window as any).showToast?.error) {
(window as any).showToast.error(error.error || 'Failed to start quick scan');
}
}
} catch (error) {
console.error('Quick scan error:', error);
if ((window as any).showToast?.error) {
(window as any).showToast.error('Failed to start quick scan');
}
}
}
async function loadSystemStats(): Promise<void> {
const token = localStorage.getItem('token');
if (!token) return;
try {
const response = await fetch('/api/admin/stats', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const stats = await response.json();
renderSystemStats(stats);
}
} catch (error) {
console.error('Failed to load stats:', error);
}
}
function renderSystemStats(stats: Record<string, unknown>): void {
const container = document.getElementById('system-stats');
if (!container) return;
container.innerHTML = `
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="p-4 rounded-lg" style="background-color: var(--bg-secondary)">
<p class="text-2xl font-bold" style="color: var(--text-primary)">${stats.total_books || 0}</p>
<p class="text-sm" style="color: var(--text-secondary)">Total Books</p>
</div>
<div class="p-4 rounded-lg" style="background-color: var(--bg-secondary)">
<p class="text-2xl font-bold" style="color: var(--text-primary)">${stats.total_users || 0}</p>
<p class="text-sm" style="color: var(--text-secondary)">Users</p>
</div>
<div class="p-4 rounded-lg" style="background-color: var(--bg-secondary)">
<p class="text-2xl font-bold" style="color: var(--text-primary)">${stats.total_devices || 0}</p>
<p class="text-sm" style="color: var(--text-secondary)">Devices</p>
</div>
<div class="p-4 rounded-lg" style="background-color: var(--bg-secondary)">
<p class="text-2xl font-bold" style="color: var(--text-primary)">${stats.total_libraries || 0}</p>
<p class="text-sm" style="color: var(--text-secondary)">Libraries</p>
</div>
</div>
`;
}
(window as any).triggerLibraryScan = triggerLibraryScan;
(window as any).triggerQuickScan = triggerQuickScan;
(window as any).loadSystemStats = loadSystemStats;