feat(web): update frontend TypeScript modules and API types
This commit updates the web frontend TypeScript modules: Core modules: - admin.ts: Admin panel functionality and user management - analytics.ts: Analytics dashboard and data visualization - api-explorer.ts: Interactive API documentation explorer - api.ts: Core API client with request/response handling - collections.ts: Book collection management UI - conflicts.ts: Sync conflict resolution interface - custom-section-builder.ts: Dynamic section builder for UI - docs.ts: Documentation viewer and navigation - dom.ts: DOM manipulation utilities and helpers - header.ts: Application header with navigation - library.ts: Library view and book grid management - linking.ts: Device-book linking interface - password_validation.ts: Client-side password strength validation - queue.ts: Device sync queue management UI - search.ts: Full-text search with Lunr integration - storage.ts: Local storage and cache management - theme.ts: Theme management and CSS variable updates - themeDropdown.ts: Theme selector dropdown component - toast.ts: Toast notification system - woodPaneling.ts: Visual theme effects - woodPanelingInit.ts: Visual effects initialization Type definitions: - api.d.ts: Updated TypeScript definitions for API responses These updates enhance the frontend with improved functionality for book management, device synchronization, and user experience.
This commit is contained in:
+63
-53
@@ -1,47 +1,47 @@
|
||||
async function loadAnalytics(): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const [statsRes, devicesRes, popularRes] = await Promise.all([
|
||||
fetch('/api/analytics/stats', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
}),
|
||||
fetch('/api/analytics/devices', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
}),
|
||||
fetch('/api/analytics/popular', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
})
|
||||
]);
|
||||
try {
|
||||
const [statsRes, devicesRes, popularRes] = await Promise.all([
|
||||
fetch("/api/analytics/stats", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
fetch("/api/analytics/devices", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
fetch("/api/analytics/popular", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
]);
|
||||
|
||||
if (statsRes.ok) {
|
||||
const stats: ReadingStatsResponse = await statsRes.json();
|
||||
renderReadingStats(stats);
|
||||
}
|
||||
|
||||
if (devicesRes.ok) {
|
||||
const devices: DeviceUsageResponse = await devicesRes.json();
|
||||
renderDeviceUsage(devices);
|
||||
}
|
||||
|
||||
if (popularRes.ok) {
|
||||
const popular: PopularBooksResponse = await popularRes.json();
|
||||
renderPopularBooks(popular);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load analytics:', error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error('Failed to load analytics data');
|
||||
}
|
||||
if (statsRes.ok) {
|
||||
const stats: ReadingStatsResponse = await statsRes.json();
|
||||
renderReadingStats(stats);
|
||||
}
|
||||
|
||||
if (devicesRes.ok) {
|
||||
const devices: DeviceUsageResponse = await devicesRes.json();
|
||||
renderDeviceUsage(devices);
|
||||
}
|
||||
|
||||
if (popularRes.ok) {
|
||||
const popular: PopularBooksResponse = await popularRes.json();
|
||||
renderPopularBooks(popular);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load analytics:", error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error("Failed to load analytics data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderReadingStats(stats: ReadingStatsResponse): void {
|
||||
const container = document.getElementById('reading-stats');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("reading-stats");
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
container.innerHTML = `
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="stat-card p-4 rounded-lg" style="background-color: var(--bg-secondary)">
|
||||
<p class="text-2xl font-bold" style="color: var(--text-primary)">${stats.total_books_read}</p>
|
||||
@@ -64,15 +64,18 @@ function renderReadingStats(stats: ReadingStatsResponse): void {
|
||||
}
|
||||
|
||||
function renderDeviceUsage(devices: DeviceUsageResponse): void {
|
||||
const container = document.getElementById('device-usage');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("device-usage");
|
||||
if (!container) return;
|
||||
|
||||
if (!devices.devices || devices.devices.length === 0) {
|
||||
container.innerHTML = '<p style="color: var(--text-secondary)">No device usage data available</p>';
|
||||
return;
|
||||
}
|
||||
if (!devices.devices || devices.devices.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p style="color: var(--text-secondary)">No device usage data available</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = devices.devices.map(device => `
|
||||
container.innerHTML = devices.devices
|
||||
.map(
|
||||
(device) => `
|
||||
<div class="p-3 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
@@ -85,19 +88,24 @@ function renderDeviceUsage(devices: DeviceUsageResponse): void {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function renderPopularBooks(popular: PopularBooksResponse): void {
|
||||
const container = document.getElementById('popular-books');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("popular-books");
|
||||
if (!container) return;
|
||||
|
||||
if (!popular.books || popular.books.length === 0) {
|
||||
container.innerHTML = '<p style="color: var(--text-secondary)">No reading history available</p>';
|
||||
return;
|
||||
}
|
||||
if (!popular.books || popular.books.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p style="color: var(--text-secondary)">No reading history available</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = popular.books.map(book => `
|
||||
container.innerHTML = popular.books
|
||||
.map(
|
||||
(book) => `
|
||||
<div class="p-3 rounded-lg border flex items-center space-x-3" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="flex-1">
|
||||
<p class="font-medium" style="color: var(--text-primary)">${book.title}</p>
|
||||
@@ -108,9 +116,11 @@ function renderPopularBooks(popular: PopularBooksResponse): void {
|
||||
<p class="text-sm" style="color: var(--text-secondary)">${Math.round(book.avg_completion * 100)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadAnalytics);
|
||||
document.addEventListener("DOMContentLoaded", loadAnalytics);
|
||||
|
||||
(window as any).loadAnalytics = loadAnalytics;
|
||||
|
||||
Reference in New Issue
Block a user