feat(media): add Media Manager UI (grid, upload, delete)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
MediaLibraryPageComponent replaces the coming-soon placeholder at /backoffice/media. Built entirely on Sprint 6 Design System primitives (app-card, app-button, app-input, app-empty-state, app-dialog, app-pagination, app-skeleton) and Sprint 4 Task 2's MediaRepository/MockMediaRepository. - Grid view with per-tile filename/size and delete action - Hidden native file input triggered by an app-button, uploads via MediaLibraryFacade -> MediaRepository.upload() - Delete requires confirmation through app-dialog (destructive action) - Search + pagination wired to MockMediaRepository's list() params - Loading state shows app-skeleton tiles; empty state shows app-empty-state - New mediaLibrary.* translation namespace across en/ru/hy Verified in browser (via a temporary unguarded route, reverted before commit - /backoffice/media itself requires Telegram QR admin auth not available in this session): empty state renders correctly with translated copy, search input and upload button present, no console errors.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { MediaRepository } from '../../../../core/media/media-repository';
|
||||
import { MediaAsset } from '../../../../core/media/models/media-asset.model';
|
||||
|
||||
const PAGE_SIZE = 24;
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MediaLibraryFacade {
|
||||
private readonly repository = inject(MediaRepository);
|
||||
|
||||
readonly items = signal<MediaAsset[]>([]);
|
||||
readonly total = signal(0);
|
||||
readonly loading = signal(false);
|
||||
readonly uploading = signal(false);
|
||||
readonly error = signal<string | null>(null);
|
||||
readonly search = signal('');
|
||||
readonly page = signal(1);
|
||||
|
||||
async load(): Promise<void> {
|
||||
this.loading.set(true);
|
||||
this.error.set(null);
|
||||
try {
|
||||
const result = await this.repository.list({
|
||||
page: this.page(),
|
||||
pageSize: PAGE_SIZE,
|
||||
search: this.search(),
|
||||
});
|
||||
this.items.set(result.items);
|
||||
this.total.set(result.total);
|
||||
} catch {
|
||||
this.error.set('Failed to load media assets.');
|
||||
} finally {
|
||||
this.loading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
setSearch(value: string): void {
|
||||
this.search.set(value);
|
||||
this.page.set(1);
|
||||
void this.load();
|
||||
}
|
||||
|
||||
setPage(page: number): void {
|
||||
this.page.set(page);
|
||||
void this.load();
|
||||
}
|
||||
|
||||
async upload(file: File): Promise<void> {
|
||||
this.uploading.set(true);
|
||||
this.error.set(null);
|
||||
try {
|
||||
await this.repository.upload(file);
|
||||
this.page.set(1);
|
||||
await this.load();
|
||||
} catch {
|
||||
this.error.set('Failed to upload file.');
|
||||
} finally {
|
||||
this.uploading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
this.error.set(null);
|
||||
try {
|
||||
await this.repository.remove(id);
|
||||
await this.load();
|
||||
} catch {
|
||||
this.error.set('Failed to delete file.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user