Add dashboard redesign, custom section builder, and enhanced search functionality
Features: - Complete dashboard redesign with improved UI components and layout - Implement custom section builder for personalized book organization - Add new events tracking system for user interactions - Enhance search functionality with better static search.js - Update TypeScript type definitions for API responses Backend: - Update Go dependencies in go.mod - Add new frontend routes in router Templates: - Update admin and dashboard templates with new components Frontend: - Refactor analytics, collections, conflicts, and queue modules - Add new documentation features in docs.ts - Implement linking between books and collections - Add toast notifications for user feedback - Include placeholder book SVG asset This commit consolidates multiple feature additions and improvements across the entire stack including backend, templates, and frontend.
This commit is contained in:
Vendored
+30
-45
@@ -1,23 +1,8 @@
|
||||
// ============================================
|
||||
// API Type Definitions
|
||||
// ============================================
|
||||
// These types match the JSON responses from /api/* endpoints.
|
||||
// Source of truth: Check what the endpoint ACTUALLY returns:
|
||||
// 1. Database layer: internal/database/queries.sql.go (SearchMediaItemsRow, etc.)
|
||||
// 2. Handler structs: internal/handlers/*.go (check json:"..." tags)
|
||||
// 3. Test by calling endpoint and inspecting JSON response
|
||||
//
|
||||
// When API contracts change:
|
||||
// 1. Find the endpoint function in internal/handlers/*.go
|
||||
// 2. Check what it returns (database row or struct)
|
||||
// 3. Check the JSON tags: `json:"field_name"`
|
||||
// 4. Map pgtype fields to TypeScript types:
|
||||
// - pgtype.Text → string | undefined
|
||||
// - pgtype.UUID → string
|
||||
// - pgtype.Timestamp → string (ISO datetime)
|
||||
// - pgtype.Numeric → number or string (for precision)
|
||||
// 5. Update the interface below with snake_case field names
|
||||
// 6. Run Bruno tests to verify
|
||||
// These types are globally available in all .ts files.
|
||||
// No imports needed - just use the type names directly.
|
||||
// ============================================
|
||||
|
||||
// Matches database.SearchMediaItemsRow from /api/media-items/search
|
||||
@@ -25,7 +10,7 @@
|
||||
// Endpoint: internal/handlers/media.go:SearchMediaItems()
|
||||
// Note: internal/handlers/search.go has an unused MediaItemSummary - ignore it
|
||||
// Used in: search.ts
|
||||
export interface MediaItemSummary {
|
||||
interface MediaItemSummary {
|
||||
id: string;
|
||||
library_id: string;
|
||||
title: string;
|
||||
@@ -77,7 +62,7 @@ export interface MediaItemSummary {
|
||||
// Matches handlers.CollectionData / CollectionResponse JSON response
|
||||
// Source: internal/handlers/collections.go:123-131 CollectionResponse
|
||||
// Used in: collections.ts
|
||||
export interface CollectionData {
|
||||
interface CollectionData {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -90,7 +75,7 @@ export interface CollectionData {
|
||||
// Matches handlers.BookInfo JSON response (internal/handlers/collections.go:66-71)
|
||||
// JSON tags: media_item_id, title, author, cover_image_path
|
||||
// Used in: collections.templ (server-rendered), collections.ts
|
||||
export interface BookInfo {
|
||||
interface BookInfo {
|
||||
media_item_id: string;
|
||||
title: string;
|
||||
author: string;
|
||||
@@ -101,7 +86,7 @@ export interface BookInfo {
|
||||
// CRITICAL: Must match Go handler return types EXACTLY
|
||||
// Source: handlers.SectionData in collections.go (lines 73-81)
|
||||
// Used in: dashboard API responses, TypeScript dashboard components
|
||||
export interface SectionData {
|
||||
interface SectionData {
|
||||
id: string;
|
||||
is_system: boolean;
|
||||
title: string;
|
||||
@@ -115,7 +100,7 @@ export interface SectionData {
|
||||
// Matches database.UserDashboardPreferences and dashboard preferences API
|
||||
// Source: internal/database/models.go:381-390
|
||||
// Used in: dashboard preferences API
|
||||
export interface DashboardPreferences {
|
||||
interface DashboardPreferences {
|
||||
library_id: string;
|
||||
hidden_collections: string[];
|
||||
collection_order: string[];
|
||||
@@ -124,7 +109,7 @@ export interface DashboardPreferences {
|
||||
|
||||
// Matches handlers.UnlinkedBookData JSON response
|
||||
// Used in: unlinked_books.ts, unlinked_books.templ
|
||||
export interface UnlinkedBookData {
|
||||
interface UnlinkedBookData {
|
||||
progress_id: string;
|
||||
device_id: string;
|
||||
device_name: string;
|
||||
@@ -137,7 +122,7 @@ export interface UnlinkedBookData {
|
||||
potential_matches: PotentialMatchData[];
|
||||
}
|
||||
|
||||
export interface PotentialMatchData {
|
||||
interface PotentialMatchData {
|
||||
media_item_id: string;
|
||||
title: string;
|
||||
author: string;
|
||||
@@ -147,7 +132,7 @@ export interface PotentialMatchData {
|
||||
|
||||
// Matches collection rule objects
|
||||
// Used in: collection_rules.ts
|
||||
export interface CollectionRule {
|
||||
interface CollectionRule {
|
||||
id: string;
|
||||
field: 'genre' | 'series' | 'author' | 'language' | 'publisher' | 'copyright_year' | 'tags';
|
||||
operator: 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'greater_than' | 'less_than';
|
||||
@@ -158,31 +143,31 @@ export interface CollectionRule {
|
||||
|
||||
// Matches API test rule responses
|
||||
// Used in: collection_rules.ts (test results)
|
||||
export interface TestRuleMatch {
|
||||
interface TestRuleMatch {
|
||||
title: string;
|
||||
author: string;
|
||||
cover_image_path?: string;
|
||||
}
|
||||
|
||||
// Matches handlers.SearchResponse (internal/handlers/search.go)
|
||||
export interface SearchResponse {
|
||||
interface SearchResponse {
|
||||
results: SearchBookResponse[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface SearchBookResponse {
|
||||
interface SearchBookResponse {
|
||||
id: string;
|
||||
title: string;
|
||||
authors: SearchAuthor[];
|
||||
}
|
||||
|
||||
export interface SearchAuthor {
|
||||
interface SearchAuthor {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
}
|
||||
|
||||
// Matches AuthResponse (internal/handlers/auth.go:59-65)
|
||||
export interface AuthResponse {
|
||||
interface AuthResponse {
|
||||
access_token: string;
|
||||
refresh_token?: string;
|
||||
token_type: string;
|
||||
@@ -190,7 +175,7 @@ export interface AuthResponse {
|
||||
user: UserProfile;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
@@ -202,7 +187,7 @@ export interface UserProfile {
|
||||
|
||||
// Matches handlers.ReadingStatsResponse (internal/handlers/analytics.go:26-35)
|
||||
// Used in: analytics.ts
|
||||
export interface ReadingStatsResponse {
|
||||
interface ReadingStatsResponse {
|
||||
total_books_read: number;
|
||||
total_pages_read: number;
|
||||
total_reading_time_minutes: number;
|
||||
@@ -213,7 +198,7 @@ export interface ReadingStatsResponse {
|
||||
daily_reading_minutes: DailyReading[];
|
||||
}
|
||||
|
||||
export interface DailyReading {
|
||||
interface DailyReading {
|
||||
date: string;
|
||||
minutes: number;
|
||||
pages: number;
|
||||
@@ -222,11 +207,11 @@ export interface DailyReading {
|
||||
// Matches handlers.DeviceUsageResponse (internal/handlers/analytics.go:43-45)
|
||||
// Note: Response is wrapped: { devices: DeviceUsage[] }
|
||||
// Used in: analytics.ts
|
||||
export interface DeviceUsageResponse {
|
||||
interface DeviceUsageResponse {
|
||||
devices: DeviceUsage[];
|
||||
}
|
||||
|
||||
export interface DeviceUsage {
|
||||
interface DeviceUsage {
|
||||
device_id: string;
|
||||
device_name: string;
|
||||
device_type: string;
|
||||
@@ -239,11 +224,11 @@ export interface DeviceUsage {
|
||||
// Matches handlers.PopularBooksResponse (internal/handlers/analytics.go:57-59)
|
||||
// Note: Response is wrapped: { books: PopularBook[] }
|
||||
// Used in: analytics.ts
|
||||
export interface PopularBooksResponse {
|
||||
interface PopularBooksResponse {
|
||||
books: PopularBook[];
|
||||
}
|
||||
|
||||
export interface PopularBook {
|
||||
interface PopularBook {
|
||||
media_item_id: string;
|
||||
title: string;
|
||||
author: string;
|
||||
@@ -254,7 +239,7 @@ export interface PopularBook {
|
||||
|
||||
// Matches handlers.QueueItemResponse (internal/handlers/queue.go:35-51)
|
||||
// Used in: queue.ts
|
||||
export interface QueueItemResponse {
|
||||
interface QueueItemResponse {
|
||||
id: string;
|
||||
device_id: string;
|
||||
device_name: string;
|
||||
@@ -274,7 +259,7 @@ export interface QueueItemResponse {
|
||||
|
||||
// Matches handlers.QueueStatsResponse (internal/handlers/queue.go:27-33)
|
||||
// Used in: queue.ts
|
||||
export interface QueueStatsResponse {
|
||||
interface QueueStatsResponse {
|
||||
pending_count: number;
|
||||
processing_count: number;
|
||||
failed_count: number;
|
||||
@@ -284,7 +269,7 @@ export interface QueueStatsResponse {
|
||||
|
||||
// Matches handlers.ConflictDetailResponse (internal/handlers/conflicts.go:42-53)
|
||||
// Used in: conflicts.ts
|
||||
export interface ConflictDetailResponse {
|
||||
interface ConflictDetailResponse {
|
||||
id: string;
|
||||
media_item_id: string;
|
||||
media_item_title: string;
|
||||
@@ -298,7 +283,7 @@ export interface ConflictDetailResponse {
|
||||
}
|
||||
|
||||
// Matches handlers.ConflictSourceData (internal/handlers/conflicts.go:36-40)
|
||||
export interface ConflictSourceData {
|
||||
interface ConflictSourceData {
|
||||
source: string;
|
||||
timestamp: string;
|
||||
data: Record<string, unknown>;
|
||||
@@ -306,7 +291,7 @@ export interface ConflictSourceData {
|
||||
|
||||
// Matches handlers.ConflictListResponse (internal/handlers/conflicts.go:55-59)
|
||||
// Used in: conflicts.ts
|
||||
export interface ConflictListResponse {
|
||||
interface ConflictListResponse {
|
||||
conflicts: ConflictDetailResponse[];
|
||||
total: number;
|
||||
unresolved: number;
|
||||
@@ -314,7 +299,7 @@ export interface ConflictListResponse {
|
||||
|
||||
// Matches handlers.ConflictResolveResponse (internal/handlers/conflicts.go:61-65)
|
||||
// Used in: conflicts.ts
|
||||
export interface ConflictResolveResponse {
|
||||
interface ConflictResolveResponse {
|
||||
conflict_resolved: boolean;
|
||||
applied_to: Record<string, boolean>;
|
||||
devices_synced: string[];
|
||||
@@ -322,7 +307,7 @@ export interface ConflictResolveResponse {
|
||||
|
||||
// Matches handlers.BulkResolveResponse (internal/handlers/conflicts.go:424-429)
|
||||
// Used in: conflicts.ts
|
||||
export interface BulkResolveResponse {
|
||||
interface BulkResolveResponse {
|
||||
results: ConflictResult[];
|
||||
total: number;
|
||||
success: number;
|
||||
@@ -330,7 +315,7 @@ export interface BulkResolveResponse {
|
||||
}
|
||||
|
||||
// Matches handlers.ConflictResult (internal/handlers/conflicts.go:431-436)
|
||||
export interface ConflictResult {
|
||||
interface ConflictResult {
|
||||
conflict_id: string;
|
||||
status: string;
|
||||
error?: string;
|
||||
|
||||
Reference in New Issue
Block a user