Skip to content

Commit a767102

Browse files
feat: Add generic ListView and ImagePopup components, integrate them into Veil and Gallery pages with view mode toggling, and create new VeilCard, Portfolio, Treatments, and VeilsCatalog pages.
1 parent 5ff2fbb commit a767102

19 files changed

Lines changed: 384 additions & 422 deletions

frontend/src/entities/gallery/constants/gallery.constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export enum GalleryCategories {
22
ALL = "all",
33
VISAGE = "visage",
4-
MEDICAL_SPA = "medical_spa",
5-
BRIDAL_VEILS = "bridal_veils",
4+
MEDICAL_SPA = "medical spa",
5+
BRIDAL_VEILS = "bridal veils",
66
INTERIOR = "interior",
77
PRODUCT = "product",
88
}

frontend/src/pages/gallery/gallery.component.html

Lines changed: 12 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -61,53 +61,14 @@ <h2 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@galleryTitle">Gallery
6161
</div>
6262
} @else {
6363
<!-- List View -->
64-
<div class="bg-white rounded-xl shadow-card overflow-hidden border border-gray-100 animate-page-enter">
65-
<div class="overflow-x-auto">
66-
<table class="min-w-full divide-y divide-gray-100">
67-
<thead class="bg-gray-50">
68-
<tr>
69-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" i18n="@@galleryTableImage">Image</th>
70-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" i18n="@@galleryTableInfo">File Info</th>
71-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" i18n="@@galleryTableCategory">Category</th>
72-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" i18n="@@galleryTableDate">Date</th>
73-
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" i18n="@@galleryTableStatus">Status</th>
74-
<th scope="col" class="relative px-6 py-3">
75-
<span class="sr-only" i18n="@@galleryTableActions">Actions</span>
76-
</th>
77-
</tr>
78-
</thead>
79-
<tbody class="bg-white divide-y divide-gray-100">
80-
@for(image of filteredImages(); track image.id; let i = $index) {
81-
<tr class="hover:bg-gray-50/50 transition-colors duration-200 reveal-item" [style.animation-delay.ms]="i * 50">
82-
<td class="px-6 py-4 whitespace-nowrap">
83-
<img [src]="image.imageUrl" [alt]="image.alt" width="64" height="64" class="w-16 h-16 object-cover rounded-lg shadow-sm"/>
84-
</td>
85-
<td class="px-6 py-4 whitespace-nowrap">
86-
<div class="text-sm font-medium text-gray-900">{{ image.title }}</div>
87-
</td>
88-
<td class="px-6 py-4 whitespace-nowrap">
89-
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
90-
{{ image.category }}
91-
</span>
92-
</td>
93-
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
94-
{{ image.createdAt | date:'mediumDate' }}
95-
</td>
96-
<td class="px-6 py-4 whitespace-nowrap">
97-
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
98-
[class]="image.status === 'published' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'">
99-
{{ image.status }}
100-
</span>
101-
</td>
102-
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-4">
103-
<button (click)="openModal(image)" class="text-primary hover:text-primary-hover transition-colors" i18n="@@galleryActionEdit">Edit</button>
104-
<button (click)="deleteImage(image.id)" class="text-red-500 hover:text-red-700 transition-colors" i18n="@@galleryActionDelete">Delete</button>
105-
</td>
106-
</tr>
107-
}
108-
</tbody>
109-
</table>
110-
</div>
64+
<div class="animate-page-enter">
65+
<app-list-view
66+
[items]="filteredImages()"
67+
[columns]="columns()"
68+
(edit)="openModal($event)"
69+
(delete)="deleteImage($event.toString())"
70+
(view)="openImageModal($event)"
71+
></app-list-view>
11172
</div>
11273
}
11374
</div>
@@ -123,41 +84,7 @@ <h2 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@galleryTitle">Gallery
12384
}
12485

12586
<!-- Image Modal -->
126-
@if (selectedImage()) {
127-
<div class="fixed inset-0 z-[100] flex items-center justify-center" role="dialog" aria-modal="true">
128-
<!-- Backdrop -->
129-
<div class="absolute inset-0 bg-black/80 backdrop-blur-md transition-opacity animate-fade-in" (click)="closeImageModal()"></div>
130-
131-
<!-- Close Button -->
132-
<button (click)="closeImageModal()" class="fixed top-6 right-6 z-[110] text-white/50 hover:text-white transition-colors p-2 hover:bg-white/10 rounded-full cursor-pointer">
133-
<span class="sr-only">Close</span>
134-
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
135-
</button>
136-
137-
<!-- Modal Content -->
138-
<div class="relative z-[105] w-full h-full p-4 md:p-12 flex flex-col items-center justify-center pointer-events-none">
139-
<div class="pointer-events-auto relative rounded-lg overflow-hidden shadow-2xl bg-transparent animate-slide-up flex items-center justify-center min-w-[300px] min-h-[300px]">
140-
141-
<!-- Loading State -->
142-
@if (isImageLoading()) {
143-
<div class="absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/40 backdrop-blur-sm">
144-
<div class="w-12 h-12 border-4 border-white/20 border-t-gold rounded-full animate-spin mb-3"></div>
145-
<p class="text-white/60 text-[10px] uppercase tracking-[0.2em] font-medium animate-pulse" i18n="@@galleryLoading">Loading Asset</p>
146-
</div>
147-
}
148-
149-
<img [src]="selectedImage()"
150-
(load)="onImageLoad()"
151-
alt="Gallery image full screen view"
152-
class="max-w-full max-h-[85vh] object-contain shadow-2xl transition-all duration-700 ease-out"
153-
[class.opacity-0]="isImageLoading()"
154-
[class.scale-95]="isImageLoading()"
155-
[class.opacity-100]="!isImageLoading()"
156-
[class.scale-100]="!isImageLoading()"
157-
>
158-
</div>
159-
160-
<p class="text-white/40 text-xs mt-6 font-medium tracking-[0.3em] uppercase animate-fade-in text-center" i18n="@@galleryFooter">Mavluda Beauty • Portfolio</p>
161-
</div>
162-
</div>
163-
}
87+
<app-image-popup
88+
[imageUrl]="selectedImage()"
89+
(close)="closeImageModal()"
90+
></app-image-popup>

frontend/src/pages/gallery/gallery.component.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import { GalleryService, GALLERY_CATEGORIES, GalleryCategories } from "@entities
1212
import { Gallery, ImageCategory } from "@shared/models";
1313
import { GalleryFormComponent } from "./ui/gallery-form/gallery-form.component";
1414
import { GalleryCardComponent } from "./ui/gallery-card/gallery-card.component";
15-
import { convertFormData } from "@shared/lib/object";
15+
import { ImagePopupComponent, ListViewComponent, ListViewColumn } from "@shared/ui";
16+
import { convertFormData, excludeFormDataProperties } from "@shared/lib/object";
17+
import { linkServerConvert } from "@shared/lib";
1618

1719
@Component({
1820
selector: "app-gallery",
1921
standalone: true,
20-
imports: [CommonModule, FormsModule, GalleryFormComponent, GalleryCardComponent],
22+
imports: [CommonModule, FormsModule, GalleryFormComponent, GalleryCardComponent, ImagePopupComponent, ListViewComponent],
2123
changeDetection: ChangeDetectionStrategy.OnPush,
2224
templateUrl: "./gallery.component.html",
2325
styleUrls: ["./gallery.component.scss"],
@@ -29,13 +31,20 @@ export class GalleryComponent implements OnInit {
2931
isModalOpen = signal(false);
3032
viewMode = signal<"grid" | "list">("grid");
3133
selectedImage = signal<string | null>(null);
32-
isImageLoading = signal(false);
3334

3435
images = this.galleryService.images;
3536

3637
filters = signal<ImageCategory[]>(GALLERY_CATEGORIES);
3738
activeFilter = signal<ImageCategory>(GalleryCategories.ALL);
3839

40+
columns = signal<ListViewColumn[]>([
41+
{ key: 'title', label: 'File Info', type: 'text' },
42+
{ key: 'category', label: 'Category', type: 'badge' },
43+
{ key: 'createdAt', label: 'Date', type: 'date' },
44+
{ key: 'status', label: 'Status', type: 'status' },
45+
{ key: 'actions', label: 'Actions', type: 'actions' }
46+
]);
47+
3948
filteredImages = computed(() => {
4049
const filter = this.activeFilter();
4150
if (filter === GalleryCategories.ALL) {
@@ -79,7 +88,7 @@ export class GalleryComponent implements OnInit {
7988
const { data: image, file } = event;
8089
const args: any[] = [{ ...image }];
8190
if (file) args.push(file);
82-
const formData = convertFormData(...args);
91+
const formData = excludeFormDataProperties(convertFormData(...args), ["id", "createdAt", "updatedAt"]);
8392

8493
if (!image.id) {
8594
// New image
@@ -103,17 +112,11 @@ export class GalleryComponent implements OnInit {
103112
}
104113

105114
openImageModal(imageUrl: string) {
106-
this.selectedImage.set(imageUrl);
107-
this.isImageLoading.set(true);
115+
this.selectedImage.set(linkServerConvert(imageUrl));
108116
}
109117

110118
closeImageModal() {
111119
this.selectedImage.set(null);
112-
this.isImageLoading.set(false);
113-
}
114-
115-
onImageLoad() {
116-
this.isImageLoading.set(false);
117120
}
118121

119122
onDragOver(event: DragEvent) {

frontend/src/pages/portfolio/portfolio.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export class PortfolioPageComponent implements OnInit {
2525
filters: (ImageCategory | "All Works")[] = [
2626
"All Works",
2727
"visage",
28-
"medical_spa",
29-
"bridal_veils",
28+
"medical spa",
29+
"bridal veils",
3030
"interior",
3131
"product",
3232
];
@@ -35,8 +35,8 @@ export class PortfolioPageComponent implements OnInit {
3535
categoryFilters: string[] = [
3636
"All Works",
3737
"visage",
38-
"medical_spa",
39-
"bridal_veils",
38+
"medical spa",
39+
"bridal veils",
4040
"interior",
4141
"product",
4242
];

0 commit comments

Comments
 (0)