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:
+141
-118
@@ -1,181 +1,204 @@
|
||||
async function loadCollections(): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/collections', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
try {
|
||||
const response = await fetch("/api/collections", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
renderCollections(data.collections || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load collections:', error);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
renderCollections(data.collections || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load collections:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderCollections(collections: CollectionData[]): void {
|
||||
const container = document.getElementById('collections-list');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("collections-list");
|
||||
if (!container) return;
|
||||
|
||||
if (collections.length === 0) {
|
||||
container.innerHTML = '<p class="text-center p-4" style="color: var(--text-secondary)">No collections yet</p>';
|
||||
return;
|
||||
}
|
||||
if (collections.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p class="text-center p-4" style="color: var(--text-secondary)">No collections yet</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = collections.map(collection => `
|
||||
container.innerHTML = collections
|
||||
.map(
|
||||
(collection) => `
|
||||
<a href="/collections/${collection.id}" class="block p-4 rounded-lg border transition-colors hover:border-opacity-50"
|
||||
style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="text-2xl">${collection.icon || '📁'}</span>
|
||||
<span class="text-2xl">${collection.icon || "📁"}</span>
|
||||
<div>
|
||||
<h3 class="font-medium" style="color: var(--text-primary)">${collection.name}</h3>
|
||||
${collection.description ? `<p class="text-sm" style="color: var(--text-secondary)">${collection.description}</p>` : ''}
|
||||
${collection.description ? `<p class="text-sm" style="color: var(--text-secondary)">${collection.description}</p>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
async function loadCollectionRules(collectionId: string): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const rules: CollectionRule[] = await response.json();
|
||||
renderRules(rules);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load rules:', error);
|
||||
if (response.ok) {
|
||||
const rules: CollectionRule[] = await response.json();
|
||||
renderRules(rules);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load rules:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderRules(rules: CollectionRule[]): void {
|
||||
const container = document.getElementById('rules-list');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("rules-list");
|
||||
if (!container) return;
|
||||
|
||||
if (rules.length === 0) {
|
||||
container.innerHTML = '<p class="text-center p-4" style="color: var(--text-secondary)">No rules defined</p>';
|
||||
return;
|
||||
}
|
||||
if (rules.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p class="text-center p-4" style="color: var(--text-secondary)">No rules defined</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = rules.map(rule => `
|
||||
container.innerHTML = rules
|
||||
.map(
|
||||
(rule) => `
|
||||
<div class="p-3 rounded-lg border mb-2 flex justify-between items-center"
|
||||
style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div>
|
||||
<p class="font-medium" style="color: var(--text-primary)">${rule.field} ${rule.operator} "${rule.value}"</p>
|
||||
<p class="text-sm" style="color: var(--text-secondary)">Priority: ${rule.priority} | ${rule.enabled ? 'Enabled' : 'Disabled'}</p>
|
||||
<p class="text-sm" style="color: var(--text-secondary)">Priority: ${rule.priority} | ${rule.enabled ? "Enabled" : "Disabled"}</p>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button onclick="window.editRule('${rule.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Edit</button>
|
||||
<button onclick="window.deleteRule('${rule.id}')" class="btn-secondary px-2 py-1 rounded text-sm">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
async function createRule(collectionId: string, rule: Partial<CollectionRule>): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
async function createRule(
|
||||
collectionId: string,
|
||||
rule: Partial<CollectionRule>,
|
||||
): Promise<void> {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(rule)
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(rule),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
if ((window as any).showToast?.success) {
|
||||
(window as any).showToast.success('Rule created');
|
||||
}
|
||||
loadCollectionRules(collectionId);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error(error.error || 'Failed to create rule');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create rule:', error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error('Failed to create rule');
|
||||
}
|
||||
if (response.ok) {
|
||||
if ((window as any).showToast?.success) {
|
||||
(window as any).showToast.success("Rule created");
|
||||
}
|
||||
loadCollectionRules(collectionId);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error(error.error || "Failed to create rule");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create rule:", error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error("Failed to create rule");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRule(collectionId: string, ruleId: string): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
if (!confirm('Are you sure you want to delete this rule?')) return;
|
||||
if (!confirm("Are you sure you want to delete this rule?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules/${ruleId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/collections/${collectionId}/rules/${ruleId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
if ((window as any).showToast?.success) {
|
||||
(window as any).showToast.success('Rule deleted');
|
||||
}
|
||||
loadCollectionRules(collectionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete rule:', error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error('Failed to delete rule');
|
||||
}
|
||||
if (response.ok) {
|
||||
if ((window as any).showToast?.success) {
|
||||
(window as any).showToast.success("Rule deleted");
|
||||
}
|
||||
loadCollectionRules(collectionId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete rule:", error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error("Failed to delete rule");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testRule(collectionId: string, rule: Partial<CollectionRule>): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
async function testRule(
|
||||
collectionId: string,
|
||||
rule: Partial<CollectionRule>,
|
||||
): Promise<void> {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/collections/${collectionId}/rules/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(rule)
|
||||
});
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/collections/${collectionId}/rules/test`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(rule),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const results = await response.json();
|
||||
renderTestResults(results);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to test rule:', error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error('Failed to test rule');
|
||||
}
|
||||
if (response.ok) {
|
||||
const results = await response.json();
|
||||
renderTestResults(results);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to test rule:", error);
|
||||
if ((window as any).showToast?.error) {
|
||||
(window as any).showToast.error("Failed to test rule");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderTestResults(results: unknown[]): void {
|
||||
const container = document.getElementById('test-results');
|
||||
if (!container) return;
|
||||
const container = document.getElementById("test-results");
|
||||
if (!container) return;
|
||||
|
||||
if (!results || (Array.isArray(results) && results.length === 0)) {
|
||||
container.innerHTML = '<p class="p-2 text-sm" style="color: var(--text-secondary)">No matching books found</p>';
|
||||
return;
|
||||
}
|
||||
if (!results || (Array.isArray(results) && results.length === 0)) {
|
||||
container.innerHTML =
|
||||
'<p class="p-2 text-sm" style="color: var(--text-secondary)">No matching books found</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `<p class="p-2 text-sm" style="color: var(--text-secondary)">${Array.isArray(results) ? results.length : 0} matching books</p>`;
|
||||
container.innerHTML = `<p class="p-2 text-sm" style="color: var(--text-secondary)">${Array.isArray(results) ? results.length : 0} matching books</p>`;
|
||||
}
|
||||
|
||||
(window as any).loadCollections = loadCollections;
|
||||
|
||||
Reference in New Issue
Block a user