From a8a3207be252fd9a3bd6dfbf66e4e1ab1c9b6074 Mon Sep 17 00:00:00 2001 From: Maksym Diachuk Date: Sun, 7 Dec 2025 13:20:40 +0200 Subject: [PATCH] Implement rating events. Implement filtration by rating --- .../app/components/book-card/book-card.html | 73 +- .../src/app/components/book-card/book-card.ts | 16 +- .../components/filter-panel/filter-panel.css | 59 +- .../components/filter-panel/filter-panel.html | 84 +- .../components/search-books/search-books.css | 37 +- .../components/search-books/search-books.html | 204 +- .../components/search-books/search-books.ts | 111 +- book-bazaar/src/app/model/book.ts | 4 + .../src/app/services/book/book-service.ts | 19 +- .../java/event/BookRatingUpdatedEvent.java | 475 ++++ .../bookservice/dto/book/BookResponse.java | 2 + .../model/book/BookSpecificationBuilder.java | 10 +- ...fication.java => NumberSpecification.java} | 2 +- ...r.java => NumberSpecificationBuilder.java} | 4 +- .../bookservice/mapper/BookMapper.java | 2 + .../org/library/bookservice/model/Book.java | 6 + .../listener/BookRatingUpdateListener.java | 28 + .../src/main/resources/application-dev.yml | 5 + .../src/main/resources/application-prod.yml | 5 + .../src/main/resources/application.properties | 6 + .../resources/avro/book-rating-updated.avsc | 19 + .../db/migration/V02__add_rating_to_book.sql | 5 + data/seeding/reviews.json | 2502 +++++++++++++++++ .../java/event/BookRatingUpdatedEvent.java | 475 ++++ .../datagen/InitialDataGenerator.java | 100 +- .../reviewService/datagen/ReviewSeedDto.java | 15 + .../consumer}/BookDeletedListener.java | 2 +- .../producer/BookRatingUpdatedProducer.java | 28 + .../service/ReviewMetricsService.java | 7 +- .../reviewService/service/ReviewService.java | 1 + .../src/main/resources/application-dev.yml | 9 + .../src/main/resources/application-prod.yml | 11 +- .../src/main/resources/application.properties | 7 +- .../resources/avro/book-rating-updated.avsc | 19 + 34 files changed, 4146 insertions(+), 206 deletions(-) create mode 100644 bookService/src/main/java/event/BookRatingUpdatedEvent.java rename bookService/src/main/java/org/library/bookservice/filtering/model/book/{PriceSpecification.java => NumberSpecification.java} (92%) rename bookService/src/main/java/org/library/bookservice/filtering/model/book/{PriceSpecificationBuilder.java => NumberSpecificationBuilder.java} (80%) create mode 100644 bookService/src/main/java/org/library/bookservice/service/listener/BookRatingUpdateListener.java create mode 100644 bookService/src/main/resources/avro/book-rating-updated.avsc create mode 100644 bookService/src/main/resources/db/migration/V02__add_rating_to_book.sql create mode 100644 data/seeding/reviews.json create mode 100644 reviewService/src/main/java/event/BookRatingUpdatedEvent.java create mode 100644 reviewService/src/main/java/org/library/reviewService/datagen/ReviewSeedDto.java rename reviewService/src/main/java/org/library/reviewService/{eventListener => integration/consumer}/BookDeletedListener.java (93%) create mode 100644 reviewService/src/main/java/org/library/reviewService/integration/producer/BookRatingUpdatedProducer.java create mode 100644 reviewService/src/main/resources/avro/book-rating-updated.avsc diff --git a/book-bazaar/src/app/components/book-card/book-card.html b/book-bazaar/src/app/components/book-card/book-card.html index 54aa45f..1bf349d 100644 --- a/book-bazaar/src/app/components/book-card/book-card.html +++ b/book-bazaar/src/app/components/book-card/book-card.html @@ -1,49 +1,66 @@
- - -
+ + +
+ [src]="book().imageUrl || 'assets/default-book-cover.jpg'" [alt]="book().title + ' cover'" />
- - + {{ book().title }} - - {{ book().author?.name }} + + + {{ book().author?.name }} + + @if (book().releaseDate) { + + {{ book().releaseDate | date:'yyyy' }} + }
- - @if(book().price) { -
- {{ book().price | currency }} +
+
+ star
+ + {{ (book().averageRating | number:'1.1-1') || '0.0' }} + + + ({{ book().totalReviews || 0 }}) + +
+ + + @if(book().price) { +
+ {{ book().price | currency }} +
} -
- @if(book().category) { - - {{ book().category?.name }} - +
+ @if(book().category) { + + {{ book().category?.name }} + } @for (genre of book().bookGenres?.slice(0, 2); track genre.id) { - - {{ genre.name }} - + + {{ genre.name }} + }
diff --git a/book-bazaar/src/app/components/book-card/book-card.ts b/book-bazaar/src/app/components/book-card/book-card.ts index 4639ce5..50ed386 100644 --- a/book-bazaar/src/app/components/book-card/book-card.ts +++ b/book-bazaar/src/app/components/book-card/book-card.ts @@ -6,11 +6,23 @@ import { MatChipSet, MatChip } from "@angular/material/chips"; import { MatTooltipModule } from '@angular/material/tooltip'; import { Category } from '../../model/category'; import { Genre } from '../../model/genre'; -import { CurrencyPipe } from '@angular/common'; +import { CurrencyPipe, DatePipe, DecimalPipe } from '@angular/common'; +import { MatIcon } from '@angular/material/icon'; @Component({ selector: 'app-book-card', - imports: [MatCard, RouterModule, MatCardTitleGroup, MatCardTitle, MatCardSubtitle, MatChipSet, MatChip, MatTooltipModule, CurrencyPipe], + imports: [ + MatCard, + RouterModule, + MatCardTitleGroup, + MatCardTitle, + MatCardSubtitle, + MatTooltipModule, + CurrencyPipe, + MatIcon, // <--- + DatePipe, // <--- + DecimalPipe + ], templateUrl: './book-card.html', styleUrl: './book-card.css', }) diff --git a/book-bazaar/src/app/components/filter-panel/filter-panel.css b/book-bazaar/src/app/components/filter-panel/filter-panel.css index 015e905..8f3a1d7 100644 --- a/book-bazaar/src/app/components/filter-panel/filter-panel.css +++ b/book-bazaar/src/app/components/filter-panel/filter-panel.css @@ -1,19 +1,22 @@ /* Робимо скролбар тонким і сучасним */ .custom-scrollbar::-webkit-scrollbar { - width: 6px; /* Тонкий скрол */ + width: 6px; + /* Тонкий скрол */ } .custom-scrollbar::-webkit-scrollbar-track { - background: transparent; + background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background-color: #e5e7eb; /* Світло-сірий (Tailwind gray-200) */ + background-color: #e5e7eb; + /* Світло-сірий (Tailwind gray-200) */ border-radius: 20px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { - background-color: #d1d5db; /* Трохи темніший при наведенні */ + background-color: #d1d5db; + /* Трохи темніший при наведенні */ } /* Для Firefox */ @@ -23,11 +26,51 @@ } /* Зменшуємо відступи у чекбоксів, щоб вони виглядали компактніше */ -::ng-deep .mat-mdc-checkbox .mdc-form-field { - padding-right: 0 !important; +::ng-deep .small-checkbox { + width: 20px !important; /* Компактна ширина */ + height: 20px !important; /* Компактна висота */ + margin-right: 8px !important; /* Відступ до тексту */ + padding: 0 !important; } -::ng-deep .mat-mdc-checkbox-touch-target { - width: 32px !important; +/* 2. Зменшуємо візуальну "коробочку" (сам квадрат) */ +::ng-deep .small-checkbox .mdc-checkbox { + padding: 0 !important; + width: 20px !important; + height: 20px !important; +} + +/* 3. Зменшуємо сам SVG квадратик (background) */ +::ng-deep .small-checkbox .mdc-checkbox__background { + width: 16px !important; /* Робимо квадрат меншим (стандарт 18px) */ + height: 16px !important; + top: 2px !important; /* Центруємо вручну (20 - 16) / 2 */ + left: 2px !important; +} + +/* 4. Прибираємо зайві відступи навколо ripple контейнера */ +::ng-deep .small-checkbox .mat-mdc-checkbox-touch-target { + width: auto !important; + height: auto !important; + position: absolute !important; + inset: 0 !important; /* Розтягуємо на весь контейнер */ +} + +/* 5. ВИПРАВЛЕННЯ RIPPLE (Хвилі): Робимо рівне коло і центруємо */ +::ng-deep .small-checkbox .mat-mdc-checkbox-ripple, +::ng-deep .small-checkbox .mdc-checkbox__ripple { + position: absolute !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; /* Ідеальне центрування */ + width: 32px !important; /* Фіксований розмір кола */ height: 32px !important; + border-radius: 50% !important; /* Гарантуємо коло, а не еліпс */ +} + +/* Коригування тексту лейблу */ +::ng-deep .mat-mdc-checkbox .mdc-label { + font-size: 14px; + line-height: 1.2; + padding-left: 0 !important; /* Прибираємо зайвий паддінг, якщо є */ } \ No newline at end of file diff --git a/book-bazaar/src/app/components/filter-panel/filter-panel.html b/book-bazaar/src/app/components/filter-panel/filter-panel.html index 9e2c707..1343517 100644 --- a/book-bazaar/src/app/components/filter-panel/filter-panel.html +++ b/book-bazaar/src/app/components/filter-panel/filter-panel.html @@ -1,60 +1,42 @@ -
- -
-

- tune - Filters -

- @if (hasSelectedFilters()) { - - } -
- +
@for (filter of filters(); track filter.name) { -
- -

- {{ filter.label }} - - {{ filter.options.length }} - -

+
-
- @for (option of visibleOptions(filter); track option) { - - } -
+

+ {{ filter.label }} + + {{ filter.options.length }} + +

- @if (filter.options.length > filter.defaultVisibleCount) { - +
@for (option of visibleOptions(filter); track option) { + }
+ + @if (filter.options.length > filter.defaultVisibleCount) { + + } +
}
\ No newline at end of file diff --git a/book-bazaar/src/app/components/search-books/search-books.css b/book-bazaar/src/app/components/search-books/search-books.css index 64c9225..9b5f4d6 100644 --- a/book-bazaar/src/app/components/search-books/search-books.css +++ b/book-bazaar/src/app/components/search-books/search-books.css @@ -4,24 +4,53 @@ /* Робимо скролбар тонким і сучасним */ .custom-scrollbar::-webkit-scrollbar { - width: 6px; /* Тонкий скрол */ + width: 6px; + /* Тонкий скрол */ } .custom-scrollbar::-webkit-scrollbar-track { - background: transparent; + background: transparent; } .custom-scrollbar::-webkit-scrollbar-thumb { - background-color: #e5e7eb; /* Світло-сірий (Tailwind gray-200) */ + background-color: #e5e7eb; + /* Світло-сірий (Tailwind gray-200) */ border-radius: 20px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { - background-color: #d1d5db; /* Трохи темніший при наведенні */ + background-color: #d1d5db; + /* Трохи темніший при наведенні */ } /* Для Firefox */ .custom-scrollbar { scrollbar-width: thin; scrollbar-color: #e5e7eb transparent; +} + +::ng-deep .density-compact { + --mdc-outlined-text-field-container-shape: 8px !important; + --mdc-outlined-text-field-outline-width: 1px !important; +} + +::ng-deep .density-compact .mat-mdc-text-field-wrapper { + height: 36px !important; + padding-top: 0 !important; +} + +::ng-deep .density-compact .mat-mdc-form-field-flex { + height: 36px !important; + align-items: center !important; +} + +::ng-deep .density-compact .mat-mdc-form-field-infix { + padding-top: 6px !important; + padding-bottom: 0 !important; + min-height: 36px !important; + border-top-width: 0px !important; +} + +mat-slider { + width: 100%; } \ No newline at end of file diff --git a/book-bazaar/src/app/components/search-books/search-books.html b/book-bazaar/src/app/components/search-books/search-books.html index b864182..5b0044d 100644 --- a/book-bazaar/src/app/components/search-books/search-books.html +++ b/book-bazaar/src/app/components/search-books/search-books.html @@ -1,33 +1,34 @@ -
- +
+
-
-
+
+