Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 45 additions & 28 deletions book-bazaar/src/app/components/book-card/book-card.html
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
<a [routerLink]="['/book-details', book().id]" class="block h-full no-underline text-inherit group">
<div class="h-full">
<mat-card appearance="outlined" class="h-full flex flex-col w-full border-gray-200 bg-white transition-all duration-300 group-hover:shadow-lg group-hover:border-primary/20 group-hover:-translate-y-1">

<div class="p-6 flex justify-center items-center bg-gray-50 rounded-t-lg border-b border-gray-100 aspect-[3/4] relative overflow-hidden">
<mat-card appearance="outlined"
class="h-full flex flex-col w-full border-gray-200 bg-white transition-all duration-300 group-hover:shadow-lg group-hover:border-primary/20 group-hover:-translate-y-1">

<div
class="p-6 flex justify-center items-center bg-gray-50 rounded-t-lg border-b border-gray-100 aspect-[3/4] relative overflow-hidden">
<img class="book-cover-image transition-transform duration-500 group-hover:scale-105"
[src]="book().imageUrl || 'assets/default-book-cover.jpg'"
[alt]="book().title + ' cover'" />
[src]="book().imageUrl || 'assets/default-book-cover.jpg'" [alt]="book().title + ' cover'" />
</div>

<div class="flex flex-col flex-1 p-5">
<mat-card-title-group class="mb-3 block">
<mat-card-title
class="title mb-2 text-lg font-bold leading-tight text-gray-900 line-clamp-1"
[matTooltip]="book().title || ''"
matTooltipShowDelay="500">
<mat-card-title-group class="mb-1 block"> <mat-card-title
class="title mb-1 text-lg font-bold leading-tight text-gray-900 line-clamp-1"
[matTooltip]="book().title || ''" matTooltipShowDelay="500">
{{ book().title }}
</mat-card-title>
<mat-card-subtitle class="subtitle text-sm font-medium text-gray-500">
{{ book().author?.name }}

<mat-card-subtitle class="subtitle text-sm font-medium text-gray-500 flex items-center gap-2">
<span class="truncate max-w-[70%]">{{ book().author?.name }}</span>

@if (book().releaseDate) {
<span class="text-gray-300">•</span>
<span>{{ book().releaseDate | date:'yyyy' }}</span>
}
</mat-card-subtitle>
</mat-card-title-group>

<div class="mt-auto pt-3 border-t border-dashed border-gray-100 flex flex-col gap-3">

@if(book().price) {
<div class="text-primary font-bold text-lg">
{{ book().price | currency }}
<div class="flex items-center gap-1 mb-3">
<div class="flex text-yellow-400">
<mat-icon class="!w-4 !h-4 !text-[16px]">star</mat-icon>
</div>
<span class="text-sm font-bold text-gray-700 pt-0.5">
{{ (book().averageRating | number:'1.1-1') || '0.0' }}
</span>
<span class="text-xs text-gray-400 pt-0.5 ml-0.5">
({{ book().totalReviews || 0 }})
</span>
</div>


@if(book().price) {
<div class="text-primary font-bold text-lg">
{{ book().price | currency }}
</div>
}

<div class="flex flex-wrap gap-2">
@if(book().category) {
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-50 text-purple-700 cursor-pointer hover:bg-purple-100 transition-colors border border-purple-100"
(click)="searchByCategory($event, book().category!)">
{{ book().category?.name }}
</span>
<div class="flex flex-wrap gap-2 h-14 overflow-hidden content-start">
@if(book().category) {
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-50 text-purple-700 cursor-pointer hover:bg-purple-100 transition-colors border border-purple-100"
(click)="searchByCategory($event, book().category!)">
{{ book().category?.name }}
</span>
}

@for (genre of book().bookGenres?.slice(0, 2); track genre.id) {
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 cursor-pointer hover:bg-blue-100 transition-colors border border-blue-100"
(click)="searchByGenre($event, genre)">
{{ genre.name }}
</span>
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 cursor-pointer hover:bg-blue-100 transition-colors border border-blue-100"
(click)="searchByGenre($event, genre)">
{{ genre.name }}
</span>
}
</div>
</div>
Expand Down
16 changes: 14 additions & 2 deletions book-bazaar/src/app/components/book-card/book-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})
Expand Down
59 changes: 51 additions & 8 deletions book-bazaar/src/app/components/filter-panel/filter-panel.css
Original file line number Diff line number Diff line change
@@ -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 */
Expand All @@ -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; /* Прибираємо зайвий паддінг, якщо є */
}
84 changes: 33 additions & 51 deletions book-bazaar/src/app/components/filter-panel/filter-panel.html
Original file line number Diff line number Diff line change
@@ -1,60 +1,42 @@
<div class="flex flex-col w-full bg-white rounded-xl border border-gray-100 p-5 shadow-sm">

<div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-100">
<h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
<mat-icon class="text-primary scale-90">tune</mat-icon>
Filters
</h3>
@if (hasSelectedFilters()) {
<button
mat-button
color="warn"
class="!min-w-0 !px-2 !h-8 text-xs font-medium uppercase tracking-wide opacity-80 hover:opacity-100"
(click)="clearFilters()">
Clear all
</button>
}
</div>

<div class="flex flex-col w-full bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
<div class="flex flex-col gap-8">
@for (filter of filters(); track filter.name) {
<div class="flex flex-col group">

<h3 class="text-sm font-bold text-gray-900 uppercase tracking-wider mb-3 flex justify-between items-center">
{{ filter.label }}
<span class="text-xs text-gray-400 font-normal bg-gray-50 px-2 py-0.5 rounded-full border border-gray-100">
{{ filter.options.length }}
</span>
</h3>
<div class="flex flex-col group">

<div class="flex flex-col gap-1.5">
@for (option of visibleOptions(filter); track option) {
<label class="flex items-center gap-3 cursor-pointer p-1.5 -ml-1.5 rounded-lg transition-colors hover:bg-gray-50 group/item">

<mat-checkbox
color="primary"
class="custom-checkbox scale-90"
[checked]="isSelected(filter.name, option)"
(change)="toggleOption(filter.name, option)"
(click)="$event.stopPropagation()">
</mat-checkbox>

<span class="text-sm text-gray-600 group-hover/item:text-primary transition-colors select-none">
{{ option }}
</span>
</label>
}
</div>
<h3 class="text-xs font-bold text-gray-900 uppercase tracking-wider mb-2 flex justify-between items-center">
{{ filter.label }}
<span class="text-[10px] text-gray-400 font-normal bg-gray-50 px-1.5 py-0.5 rounded border border-gray-100">
{{ filter.options.length }}
</span>
</h3>

@if (filter.options.length > filter.defaultVisibleCount) {
<button
class="text-xs font-semibold text-primary mt-3 self-start hover:underline flex items-center gap-1 opacity-90 hover:opacity-100 transition-opacity"
(click)="toggleExpanded(filter.name)">
{{ filter.expanded ? 'Show less' : 'Show all (' + filter.options.length + ')' }}
<mat-icon class="!w-3 !h-3 !text-[12px] leading-3">{{ filter.expanded ? 'expand_less' : 'expand_more' }}</mat-icon>
</button>
<div class="flex flex-col gap-0.5"> @for (option of visibleOptions(filter); track option) {
<label
class="flex items-center gap-2 cursor-pointer py-1 px-1 -ml-1 rounded transition-colors hover:bg-gray-50 group/item">

<mat-checkbox color="primary" class="custom-checkbox small-checkbox"
[checked]="isSelected(filter.name, option)" (change)="toggleOption(filter.name, option)"
(click)="$event.stopPropagation()">
</mat-checkbox>

<span
class="text-sm text-gray-600 group-hover/item:text-primary transition-colors select-none leading-tight -ml-1">
{{ option }}
</span>
</label>
}
</div>

@if (filter.options.length > filter.defaultVisibleCount) {
<button
class="text-xs font-medium text-primary mt-2 self-start hover:underline flex items-center gap-1 opacity-90 hover:opacity-100 transition-opacity"
(click)="toggleExpanded(filter.name)">
{{ filter.expanded ? 'Show less' : 'Show all' }}
<mat-icon class="!w-3 !h-3 !text-[12px] leading-3">{{ filter.expanded ? 'expand_less' : 'expand_more'
}}</mat-icon>
</button>
}
</div>
}
</div>
</div>
37 changes: 33 additions & 4 deletions book-bazaar/src/app/components/search-books/search-books.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}
Loading
Loading