From 910f5004602337ea5ea18c7e181ee86b2d216064 Mon Sep 17 00:00:00 2001 From: florpan Date: Wed, 17 Sep 2025 15:38:29 +0200 Subject: [PATCH] casing i fe. detail embryo --- Frontend/src/App.tsx | 2 + Frontend/src/components/AuthorCard.tsx | 21 +++++--- Frontend/src/components/AuthorList.tsx | 4 +- Frontend/src/components/BookCard.tsx | 30 +++++++---- Frontend/src/components/BookDetail.tsx | 31 +++++++++++ Frontend/src/components/BookList.tsx | 4 +- Frontend/src/components/SeriesList.tsx | 15 +++--- Frontend/src/services/bibblanservice.ts | 14 +++-- Frontend/src/services/calibreservice.ts | 4 +- Frontend/src/styles/styles.css | 22 +++++++- Frontend/src/types/types.ts | 60 ++++++++++++++++++--- Server/Business/Services/DatabaseService.cs | 51 +++++++++++++++++- Server/Controllers/BibblanController.cs | 15 ++++-- Server/ViewModels/BookVm.cs | 13 +++++ 14 files changed, 238 insertions(+), 48 deletions(-) create mode 100644 Frontend/src/components/BookDetail.tsx diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx index 79462ef..cdf21ee 100644 --- a/Frontend/src/App.tsx +++ b/Frontend/src/App.tsx @@ -4,6 +4,7 @@ import Home from "./components/Home"; import BookList from "./components/BookList"; import AuthorList from "./components/AuthorList"; import SeriesList from "./components/SeriesList"; +import BookDetail from "./components/BookDetail"; const App: Component = () => { return ( @@ -48,6 +49,7 @@ const App: Component = () => {
+ diff --git a/Frontend/src/components/AuthorCard.tsx b/Frontend/src/components/AuthorCard.tsx index c5f4501..9be2fad 100644 --- a/Frontend/src/components/AuthorCard.tsx +++ b/Frontend/src/components/AuthorCard.tsx @@ -1,17 +1,22 @@ import { Show, type Component } from "solid-js"; -import { author } from "../types/types"; +import { Author } from "../types/types"; import { Card } from "solid-bootstrap"; -const AuthorCard: Component<{ author: author }> = (props: { - author: author; +const AuthorCard: Component<{ author: Author }> = (props: { + author: Author; }) => { return ( - +
?
+
{props.author.name} diff --git a/Frontend/src/components/AuthorList.tsx b/Frontend/src/components/AuthorList.tsx index 343d575..f7448c5 100644 --- a/Frontend/src/components/AuthorList.tsx +++ b/Frontend/src/components/AuthorList.tsx @@ -1,10 +1,10 @@ import { createSignal, onMount, For, type Component } from "solid-js"; import AuthorCard from "./AuthorCard"; import BibblanService from "../services/bibblanservice"; -import { author } from "../types/types"; +import { Author } from "../types/types"; const BookList: Component = () => { - const [authors, setAuthors] = createSignal([]); + const [authors, setAuthors] = createSignal([]); const [query, setQuery] = createSignal(""); const update = (query: string) => { diff --git a/Frontend/src/components/BookCard.tsx b/Frontend/src/components/BookCard.tsx index 2b87bc8..598e1da 100644 --- a/Frontend/src/components/BookCard.tsx +++ b/Frontend/src/components/BookCard.tsx @@ -1,16 +1,27 @@ -import { Show, type Component } from "solid-js"; -import { book } from "../types/types"; -import { Card } from "solid-bootstrap"; +import { Show, createSignal, type Component } from "solid-js"; +import { Book } from "../types/types"; +import { Card, Modal } from "solid-bootstrap"; + +const BookCard: Component<{ book: Book }> = (props: { book: Book }) => { + const [show, setShow] = createSignal(false); + + function handleClick(): void { + setShow(true); + } -const BookCard: Component<{ book: book }> = (props: { book: book }) => { return ( +
?
- +
{props.book.title} @@ -18,6 +29,7 @@ const BookCard: Component<{ book: book }> = (props: { book: book }) => { Series: {props.book.seriesName} {props.book.seriesNumber} +
); diff --git a/Frontend/src/components/BookDetail.tsx b/Frontend/src/components/BookDetail.tsx new file mode 100644 index 0000000..48c850b --- /dev/null +++ b/Frontend/src/components/BookDetail.tsx @@ -0,0 +1,31 @@ +import { createSignal, onMount, For, Show, type Component } from "solid-js"; +import { useParams } from "@solidjs/router"; +import BibblanService from "../services/bibblanservice"; +import { type BookDetail as BD } from "../types/types"; + +const BookDetail: Component = () => { + const [detail, setDetail] = createSignal({} as BD); + const params = useParams(); + + onMount(() => { + BibblanService.getBook(params.id).then((b) => { + console.log("detail", b); + setDetail(b); + }); + }); + + return ( + <> +

Book detail - {detail()?.book?.title}

+ +
+
+					{JSON.stringify(detail(), null, 2)}
+				
+
+
+ + ); +}; + +export default BookDetail; diff --git a/Frontend/src/components/BookList.tsx b/Frontend/src/components/BookList.tsx index ffff18f..f640a6b 100644 --- a/Frontend/src/components/BookList.tsx +++ b/Frontend/src/components/BookList.tsx @@ -2,10 +2,10 @@ import { createSignal, onMount, For, Show, type Component } from "solid-js"; import { useParams } from "@solidjs/router"; import BookCard from "./BookCard"; import BibblanService from "../services/bibblanservice"; -import { book } from "../types/types"; +import { Book } from "../types/types"; const BookList: Component = () => { - const [books, setBooks] = createSignal([]); + const [books, setBooks] = createSignal([]); const [query, setQuery] = createSignal(""); const params = useParams(); diff --git a/Frontend/src/components/SeriesList.tsx b/Frontend/src/components/SeriesList.tsx index 0d228c3..cb8545d 100644 --- a/Frontend/src/components/SeriesList.tsx +++ b/Frontend/src/components/SeriesList.tsx @@ -1,12 +1,11 @@ import { createSignal, onMount, For, type Component, Show } from "solid-js"; import BookCard from "./BookCard"; import BibblanService from "../services/bibblanservice"; -import { series, listBook, book } from "../types/types"; +import { Series, ListBook, Book } from "../types/types"; import { OverlayTrigger, Popover, Button } from "solid-bootstrap"; const SeriesList: Component = () => { - const [series, setSeries] = createSignal([]); + const [series, setSeries] = createSignal([]); const [query, setQuery] = createSignal(""); - const [selected, setSelected] = createSignal(-1); const update = (query: string) => { setQuery(query); @@ -18,7 +17,7 @@ const SeriesList: Component = () => { }); const distinctAuthors = ( - books: listBook[] + books: ListBook[] ): { id: number; name: string }[] => { return [ ...new Set( @@ -27,21 +26,21 @@ const SeriesList: Component = () => { ].map((x) => JSON.parse(x)) as { id: number; name: string }[]; }; - const minDate = (books: listBook[]): string => { + const minDate = (books: ListBook[]): string => { if (books.length === 0) return ""; const darr = books.map((b) => new Date(b.pubDate)); const d = darr.reduce((min, b) => (b < min ? b : min), darr[0]); return d?.toISOString().substr(0, 10) ?? ""; }; - const maxDate = (books: listBook[]): string => { + const maxDate = (books: ListBook[]): string => { if (books.length === 0) return ""; const darr = books.map((b) => new Date(b.pubDate)); const d = darr.reduce((max, b) => (b > max ? b : max), darr[0]); return d?.toISOString().substr(0, 10) ?? ""; }; - function extendBook(item: listBook, seriesName: string): book { + function extendBook(item: ListBook, seriesName: string): Book { return { ...item, @@ -57,7 +56,7 @@ const SeriesList: Component = () => { }; } - function orderBySeriesIndex(books: listBook[]): listBook[] { + function orderBySeriesIndex(books: ListBook[]): ListBook[] { return [...books].sort((a, b) => { const indexA = a.seriesIndex ?? 0; const indexB = b.seriesIndex ?? 0; diff --git a/Frontend/src/services/bibblanservice.ts b/Frontend/src/services/bibblanservice.ts index 2e7eca3..7d85b0c 100644 --- a/Frontend/src/services/bibblanservice.ts +++ b/Frontend/src/services/bibblanservice.ts @@ -1,10 +1,10 @@ -import { book, author } from "../types/types"; +import { Book, Author, Series, BookDetail } from "../types/types"; const BibblanService = { getBooks: async ( authorid: string | undefined = undefined, query: string | undefined = undefined - ): Promise => { + ): Promise => { let url = "/api/bibblan/books"; if (authorid != undefined) { url += `/author/${authorid}`; @@ -15,9 +15,15 @@ const BibblanService = { return response.json(); }, + getBook: async (id: string): Promise => { + let url = "/api/bibblan/books/" + id; + const response = await fetch(url); + return response.json(); + }, + getAuthors: async ( query: string | undefined = undefined - ): Promise => { + ): Promise => { let url = "/api/bibblan/authors"; if (query != undefined && query.length > 0) { url += `?query=${encodeURIComponent(query)}`; @@ -28,7 +34,7 @@ const BibblanService = { getSeries: async ( query: string | undefined = undefined - ): Promise => { + ): Promise => { let url = "/api/bibblan/series"; if (query != undefined && query.length > 0) { url += `?query=${encodeURIComponent(query)}`; diff --git a/Frontend/src/services/calibreservice.ts b/Frontend/src/services/calibreservice.ts index 509e2f9..04f5edd 100644 --- a/Frontend/src/services/calibreservice.ts +++ b/Frontend/src/services/calibreservice.ts @@ -1,7 +1,7 @@ -import { book } from "../types/types"; +import { Book } from "../types/types"; const CalibreService = { - getBooks: async (): Promise => { + getBooks: async (): Promise => { const response = await fetch("/api/calibre/books"); return response.json(); }, diff --git a/Frontend/src/styles/styles.css b/Frontend/src/styles/styles.css index 0409c86..ac8c978 100644 --- a/Frontend/src/styles/styles.css +++ b/Frontend/src/styles/styles.css @@ -3,7 +3,27 @@ .book-card { .card-img-top { padding: 1rem; - aspect-ratio: 3 / 4; + aspect-ratio: 30 / 47; + background-size: contain; + width: calc(100% - 1.6rem); + margin-left: 0.8rem; + margin-top: 0.8rem; + background-repeat: no-repeat; + background-position: 0 0; + z-index: 1; + + &.missing { + position: absolute; + display: block; + font-size: 7rem; + font-weight: 900; + color: #ccc; + text-align: center; + opacity: 0.8; + z-index: 0; + } + + /* &.author { } */ } } diff --git a/Frontend/src/types/types.ts b/Frontend/src/types/types.ts index 2766f03..7edb30f 100644 --- a/Frontend/src/types/types.ts +++ b/Frontend/src/types/types.ts @@ -1,4 +1,4 @@ -interface book { +export interface Book { id: number; title: string; authorId: number; @@ -12,13 +12,13 @@ interface book { seriesNumber: number; } -interface author { +export interface Author { id: number; name: string; bookCount: number; } -interface listBook { +export interface ListBook { id: number; title: string; authorId: number; @@ -29,10 +29,58 @@ interface listBook { seriesIndex: number; } -interface series { +export interface Series { id: number; name: string; - books: Array; + books: Array; } -export type { book, author, series, listBook }; +export interface Publisher { + id: number; + name: string; + sort: string; +} + +export interface Tag { + id: number; + name: string; +} + +export interface Rating { + id: number; + book: number; + rating: number; +} + +export interface Language { + id: number; + book: number; + langCode: number; + itemOrder: number; +} + +export interface BookComment { + id: number; + book: number; + text: string; +} + +export interface BookData { + id: number; + book: number; + format: string; + uncompressedSize: number; + name: string; +} + +export interface BookDetail { + book: Book; + authors: Author[]; + publishers: Publisher[]; + language: Language[]; + ratings: any[]; + series: Author[]; + tags: Tag[]; + comments: BookComment[]; + data: BookData[]; +} diff --git a/Server/Business/Services/DatabaseService.cs b/Server/Business/Services/DatabaseService.cs index 3702ad1..461a538 100644 --- a/Server/Business/Services/DatabaseService.cs +++ b/Server/Business/Services/DatabaseService.cs @@ -9,6 +9,7 @@ namespace Bibblan.Business.Services public class BookFilter { + public int? Id; public int? Author; public string? Query; } @@ -17,7 +18,7 @@ namespace Bibblan.Business.Services { readonly BibblanOptions settings = options.Value; - public IEnumerable GetBooks(int count, BookFilter filter = null) + public List GetBooks(int count, BookFilter filter = null) { var conn = new NpgsqlConnection(settings.BibblanConnection); var query = "select * from books"; @@ -28,7 +29,12 @@ namespace Bibblan.Business.Services if(filter.Author != null) { query += $"id in (select book from books_authors_link where author = {filter.Author})"; - } else if(!String.IsNullOrWhiteSpace(query)) + } + else if(filter.Id != null) + { + query += $"id = {filter.Id}"; + } + else if(!String.IsNullOrWhiteSpace(query)) { filter.Query = filter.Query.ToLowerInvariant(); query += $"lower(title) like @query or lower(author_sort) like @query"; @@ -38,6 +44,47 @@ namespace Bibblan.Business.Services return conn.Query(query, parameters).Take(count).ToList(); } + public BookDetailVm GetBookDetails(int id) + { + var query = @"select * from books where id = @book; + select * from authors where id in (select author from books_authors_link where book = @book); + select * from books_languages_link where book = @book; + select * from publishers where id in (select publisher from books_publishers_link where book = @book); + select * from books_ratings_link where book = @book; + select * from series where id in (select series from books_series_link where book = @book); + select * from tags where id in (select tag from books_tags_link where book = @book); + select * from comments where book = @book; + select * from data where book = @book;"; + + using (var conn = new NpgsqlConnection(settings.BibblanConnection)) + { + var results = conn.QueryMultiple(query, new { book = id }); + var book = results.ReadFirst(); + var authors = results.Read(); + var lang = results.Read(); + var publishers = results.Read(); + var ratings = results.Read(); + var series = results.Read(); + var tags = results.Read(); + var comments = results.Read(); + var data = results.Read(); + + return new BookDetailVm + { + Book = book, + Authors = authors, + Language = lang, + Publishers = publishers, + Ratings = ratings, + Series = series, + Tags = tags, + Comments = comments, + Data = data + }; + } + + } + public IEnumerable GetAuthors(int count, BookFilter filter = null) { var query = "select a.id, a.name, count(bal.*) as bookcount\r\nfrom authors a\r\nleft join books_authors_link bal on a.id = bal.author\r\ngroup by a.id , a.name "; diff --git a/Server/Controllers/BibblanController.cs b/Server/Controllers/BibblanController.cs index 0dfb6f6..bc3d052 100644 --- a/Server/Controllers/BibblanController.cs +++ b/Server/Controllers/BibblanController.cs @@ -30,7 +30,7 @@ namespace Bibblan.Controllers [HttpGet("books")] public IActionResult GetBooks(string query = null) { - BookFilter filter = query != null ? new BookFilter + BookFilter? filter = query != null ? new BookFilter { Query = query } : null; @@ -39,10 +39,17 @@ namespace Bibblan.Controllers return Ok(books); } + [HttpGet("books/{id}")] + public IActionResult GetBook(int id) + { + var book = _db.GetBookDetails(id); + return Ok(book); + } + [HttpGet("authors")] public IActionResult GetAuthors(string query = null) { - BookFilter filter = query != null ? new BookFilter + BookFilter? filter = query != null ? new BookFilter { Query = query } : null; @@ -72,7 +79,7 @@ namespace Bibblan.Controllers [HttpGet("series")] public IActionResult GetSeries(string query = null) { - BookFilter filter = query != null ? new BookFilter + BookFilter? filter = query != null ? new BookFilter { Query = query } : null; @@ -84,7 +91,7 @@ namespace Bibblan.Controllers [HttpGet("tags")] public IActionResult GetTags(string query = null) { - BookFilter filter = query != null ? new BookFilter + BookFilter? filter = query != null ? new BookFilter { Query = query } : null; diff --git a/Server/ViewModels/BookVm.cs b/Server/ViewModels/BookVm.cs index 1dff4a9..145718d 100644 --- a/Server/ViewModels/BookVm.cs +++ b/Server/ViewModels/BookVm.cs @@ -2,6 +2,19 @@ namespace Bibblan.ViewModels { + public class BookDetailVm + { + public Books Book { get; internal set; } + public IEnumerable Authors { get; internal set; } + public IEnumerable Publishers { get; internal set; } + public IEnumerable Language { get; internal set; } + public IEnumerable Ratings { get; internal set; } + public IEnumerable Series { get; internal set; } + public IEnumerable Tags { get; internal set; } + public IEnumerable Comments { get; internal set; } + public IEnumerable Data { get; internal set; } + } + public class BookVm { public long Id { get; set; }