package templates import ( "bookhoard/internal/database" "fmt" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) func activeClass(current, target string) string { base := "block px-4 py-2 rounded-lg " if current == target { return base + "bg-accent text-bg-primary" } return base + "hover:opacity-80" } func ContainsString(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false } func uuidToString(id pgtype.UUID) string { if !id.Valid { return "" } u, err := uuid.FromBytes(id.Bytes[0:16]) if err != nil { return "" } return u.String() } // renderStars converts rating (1-10 scale) to star display // Always displays 5 stars total with filled stars in yellow // and empty stars in grey (using theme variable) // Rating scale: 1-10 where each 2 points = 1 full star, odd numbers = half stars func renderStars(rating int32) string { const totalStars = 5 fullStars := rating / 2 hasHalf := rating%2 != 0 var stars strings.Builder for i := int32(0); i < totalStars; i++ { if i < fullStars { // Filled star - accent color (theme-aware highlight) stars.WriteString(``) } else if i == fullStars && hasHalf { // Half star - gradient from accent (left) to grey (right) stars.WriteString(``) } else { // Empty star - grey using theme variable stars.WriteString(``) } } return stars.String() } // formatFileSize converts bytes to human-readable format func formatFileSize(bytes int64) string { const ( KB = 1024 MB = KB * 1024 GB = MB * 1024 ) switch { case bytes >= GB: return fmt.Sprintf("%.2f GB", float64(bytes)/float64(GB)) case bytes >= MB: return fmt.Sprintf("%.2f MB", float64(bytes)/float64(MB)) case bytes >= KB: return fmt.Sprintf("%.2f KB", float64(bytes)/float64(KB)) default: return fmt.Sprintf("%d B", bytes) } } // getExternalURL generates URL for external book services // Priority: ID > ISBN > Title+Author search func getExternalURL(service string, id string, isbn pgtype.Text, title string, author pgtype.Text) string { baseURL := "" searchTerm := "" // Determine search term: ID > ISBN > Title+Author if id != "" { searchTerm = id } else if isbn.Valid && isbn.String != "" { searchTerm = isbn.String } else { // Build title+author search query if author.Valid && author.String != "" { searchTerm = fmt.Sprintf("%s %s", title, author.String) } else { searchTerm = title } } // Build URL based on service switch service { case "goodreads": if id != "" { baseURL = "https://www.goodreads.com/book/show/" } else { baseURL = "https://www.goodreads.com/search?q=" } case "openlibrary": if id != "" { baseURL = "https://openlibrary.org/books/" } else { baseURL = "https://openlibrary.org/search?q=" } case "googlebooks": if id != "" { baseURL = "https://books.google.com/books?id=" } else { baseURL = "https://www.google.com/search?tbm=bks&q=" } case "amazon": // Amazon doesn't have direct book IDs, always search baseURL = "https://www.amazon.com/s?k=" if isbn.Valid && isbn.String != "" { searchTerm = isbn.String } } return baseURL + searchTerm } // getBookRating returns the rating value or 0 if not rated func getBookRating(rating *database.MediaRatings) int32 { if rating != nil { return rating.Rating } return 0 }