Compare commits
5 Commits
c461d9bd5f
...
6231128288
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6231128288 | ||
|
|
fd8e7e1b28 | ||
|
|
000bb78112 | ||
|
|
d3d6632375 | ||
|
|
1e84d67e24 |
@@ -64,6 +64,8 @@
|
|||||||
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (error) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else if (treeRows.length === 0) {
|
} @else if (treeRows.length === 0) {
|
||||||
<app-empty-state [title]="'adminCategories.emptyTitle' | translate" [description]="'adminCategories.emptyDescription' | translate">
|
<app-empty-state [title]="'adminCategories.emptyTitle' | translate" [description]="'adminCategories.emptyDescription' | translate">
|
||||||
<span slot="actions">
|
<span slot="actions">
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export class AdminCategoriesListComponent {
|
|||||||
@Input() flatCategories: AdminCategory[] = [];
|
@Input() flatCategories: AdminCategory[] = [];
|
||||||
@Input() filters!: { search: string; visibility: 'all' | 'visible' | 'hidden'; includeDeleted: boolean };
|
@Input() filters!: { search: string; visibility: 'all' | 'visible' | 'hidden'; includeDeleted: boolean };
|
||||||
@Input() loading = false;
|
@Input() loading = false;
|
||||||
|
@Input() error: string | null = null;
|
||||||
@Input() viewMode: AdminCategoriesViewMode = 'tree';
|
@Input() viewMode: AdminCategoriesViewMode = 'tree';
|
||||||
@Input() density: AdminCategoriesDensity = 'comfortable';
|
@Input() density: AdminCategoriesDensity = 'comfortable';
|
||||||
@Input() visibleColumns: AdminCategoryColumn[] = [...ALL_CATEGORY_COLUMNS];
|
@Input() visibleColumns: AdminCategoryColumn[] = [...ALL_CATEGORY_COLUMNS];
|
||||||
|
|||||||
@@ -131,11 +131,13 @@ export class AdminCategoriesFacade {
|
|||||||
readonly filters = signal<AdminCategoryListFilters>({ search: '', visibility: 'all', includeDeleted: false });
|
readonly filters = signal<AdminCategoryListFilters>({ search: '', visibility: 'all', includeDeleted: false });
|
||||||
readonly categories = signal<AdminCategory[]>([]);
|
readonly categories = signal<AdminCategory[]>([]);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
|
readonly error = signal<string | null>(null);
|
||||||
readonly draft = signal<AdminCategory | null>(null);
|
readonly draft = signal<AdminCategory | null>(null);
|
||||||
readonly editorMode = signal<AdminCategoryEditorMode>('create');
|
readonly editorMode = signal<AdminCategoryEditorMode>('create');
|
||||||
readonly dirty = signal(false);
|
readonly dirty = signal(false);
|
||||||
readonly slugTaken = signal(false);
|
readonly slugTaken = signal(false);
|
||||||
readonly slugCheckError = signal(false);
|
readonly slugCheckError = signal(false);
|
||||||
|
readonly mutationError = signal<string | null>(null);
|
||||||
private savedSnapshot: string | null = null;
|
private savedSnapshot: string | null = null;
|
||||||
private draftStorageKey: string | null = null;
|
private draftStorageKey: string | null = null;
|
||||||
|
|
||||||
@@ -174,6 +176,7 @@ export class AdminCategoriesFacade {
|
|||||||
*/
|
*/
|
||||||
loadList(): void {
|
loadList(): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
this.error.set(null);
|
||||||
this.gateway.loadCategories({ search: '', visibility: 'all', includeDeleted: true }).pipe(take(1)).subscribe({
|
this.gateway.loadCategories({ search: '', visibility: 'all', includeDeleted: true }).pipe(take(1)).subscribe({
|
||||||
next: categories => {
|
next: categories => {
|
||||||
this.categories.set(categories);
|
this.categories.set(categories);
|
||||||
@@ -182,6 +185,7 @@ export class AdminCategoriesFacade {
|
|||||||
error: () => {
|
error: () => {
|
||||||
this.categories.set([]);
|
this.categories.set([]);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
this.error.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -323,10 +327,11 @@ export class AdminCategoriesFacade {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDraft(publish: boolean): void {
|
saveDraft(publish: boolean, onSuccess?: () => void): void {
|
||||||
const draft = this.draft();
|
const draft = this.draft();
|
||||||
if (!draft || this.slugTaken() || this.slugCheckError()) return;
|
if (!draft || this.slugTaken() || this.slugCheckError()) return;
|
||||||
|
|
||||||
|
this.mutationError.set(null);
|
||||||
const toSave: AdminCategory = { ...draft, status: publish ? 'published' : 'draft', updatedAt: new Date().toISOString() };
|
const toSave: AdminCategory = { ...draft, status: publish ? 'published' : 'draft', updatedAt: new Date().toISOString() };
|
||||||
const request = this.editorMode() === 'create' ? this.gateway.createCategory(toSave) : this.gateway.updateCategory(toSave);
|
const request = this.editorMode() === 'create' ? this.gateway.createCategory(toSave) : this.gateway.updateCategory(toSave);
|
||||||
|
|
||||||
@@ -339,7 +344,9 @@ export class AdminCategoriesFacade {
|
|||||||
this.savedSnapshot = JSON.stringify(saved);
|
this.savedSnapshot = JSON.stringify(saved);
|
||||||
this.dirty.set(false);
|
this.dirty.set(false);
|
||||||
this.loadList();
|
this.loadList();
|
||||||
}
|
onSuccess?.();
|
||||||
|
},
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +361,11 @@ export class AdminCategoriesFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: string): void {
|
deleteOne(id: string): void {
|
||||||
this.gateway.deleteCategory(id).pipe(take(1)).subscribe({ next: () => { this.loadList(); this.loadDashboardStats(); } });
|
this.mutationError.set(null);
|
||||||
|
this.gateway.deleteCategory(id).pipe(take(1)).subscribe({
|
||||||
|
next: () => { this.loadList(); this.loadDashboardStats(); },
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreOne(id: string): void {
|
restoreOne(id: string): void {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|||||||
[allCategories]="facade.categories()"
|
[allCategories]="facade.categories()"
|
||||||
[filters]="facade.filters()"
|
[filters]="facade.filters()"
|
||||||
[loading]="facade.loading()"
|
[loading]="facade.loading()"
|
||||||
|
[error]="facade.error()"
|
||||||
[viewMode]="facade.viewMode()"
|
[viewMode]="facade.viewMode()"
|
||||||
[density]="facade.density()"
|
[density]="facade.density()"
|
||||||
[visibleColumns]="facade.visibleColumns()"
|
[visibleColumns]="facade.visibleColumns()"
|
||||||
@@ -70,6 +71,14 @@ import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|||||||
<app-button variant="primary" (click)="deleteBlockedMessage.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
<app-button variant="primary" (click)="deleteBlockedMessage.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
</div>
|
</div>
|
||||||
</app-dialog>
|
</app-dialog>
|
||||||
|
}
|
||||||
|
@if (facade.mutationError()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'common.errorTitle' | translate" size="sm" (closed)="facade.mutationError.set(null)">
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="facade.mutationError.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
}`,
|
}`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import { AdminCategoriesFacade } from '../facade/admin-categories.facade';
|
|||||||
import { AdminCategoryFormComponent } from '../components/admin-category-form.component';
|
import { AdminCategoryFormComponent } from '../components/admin-category-form.component';
|
||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
|
import { DialogComponent } from '../../../../shared/ui/dialog/dialog.component';
|
||||||
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin-category-editor-page',
|
selector: 'app-admin-category-editor-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminCategoryFormComponent, TranslatePipe],
|
imports: [AdminCategoryFormComponent, TranslatePipe, DialogComponent, ButtonComponent],
|
||||||
template: `@if (facade.draft(); as draft) {<main class="editor-page"><header><h1>{{ title() | translate }}</h1></header><app-admin-category-form [category]="draft" [parentOptions]="parentOptions()" [breadcrumb]="facade.breadcrumbFor(draft.parentId)" [children]="facade.childrenOf(draft.id)" [slugTaken]="facade.slugTaken()" [slugCheckError]="facade.slugCheckError()" [locales]="facade.supportedLocales()" [mode]="facade.editorMode()" [health]="facade.health(draft)" (categoryChange)="facade.updateDraft($event)" (saveDraft)="save(false)" (publish)="save(true)" /></main>} @else {<main class="editor-page"><p>{{ 'common.loading' | translate }}</p></main>}`,
|
template: `@if (facade.draft(); as draft) {<main class="editor-page"><header><h1>{{ title() | translate }}</h1></header><app-admin-category-form [category]="draft" [parentOptions]="parentOptions()" [breadcrumb]="facade.breadcrumbFor(draft.parentId)" [children]="facade.childrenOf(draft.id)" [slugTaken]="facade.slugTaken()" [slugCheckError]="facade.slugCheckError()" [locales]="facade.supportedLocales()" [mode]="facade.editorMode()" [health]="facade.health(draft)" (categoryChange)="facade.updateDraft($event)" (saveDraft)="save(false)" (publish)="save(true)" /></main>} @else {<main class="editor-page"><p>{{ 'common.loading' | translate }}</p></main>}
|
||||||
|
@if (facade.mutationError()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'common.errorTitle' | translate" size="sm" (closed)="facade.mutationError.set(null)">
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="facade.mutationError.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
|
}`,
|
||||||
styles: [`.editor-page { max-width: 1120px; margin: 0 auto; padding: 24px; display: grid; gap: 16px; } .editor-page h1, .editor-page p { margin: 0; }`],
|
styles: [`.editor-page { max-width: 1120px; margin: 0 auto; padding: 24px; display: grid; gap: 16px; } .editor-page h1, .editor-page p { margin: 0; }`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
@@ -41,8 +51,9 @@ export class AdminCategoryEditorPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save(publish: boolean): void {
|
save(publish: boolean): void {
|
||||||
this.facade.saveDraft(publish);
|
this.facade.saveDraft(publish, () => {
|
||||||
void this.router.navigate([this.languageService.currentLanguage(), 'backoffice', 'categories']);
|
void this.router.navigate([this.languageService.currentLanguage(), 'backoffice', 'categories']);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private descendantIds(id: string): string[] {
|
private descendantIds(id: string): string[] {
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ export class AdminCustomersFacade {
|
|||||||
|
|
||||||
readonly customers = signal<AdminCustomer[]>([]);
|
readonly customers = signal<AdminCustomer[]>([]);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
|
readonly error = signal<string | null>(null);
|
||||||
readonly search = signal('');
|
readonly search = signal('');
|
||||||
readonly selected = signal<AdminCustomer | null>(null);
|
readonly selected = signal<AdminCustomer | null>(null);
|
||||||
|
readonly selectedLoading = signal(false);
|
||||||
|
readonly selectedError = signal<string | null>(null);
|
||||||
|
|
||||||
private buildCustomers(orders: AdminOrder[]): AdminCustomer[] {
|
private buildCustomers(orders: AdminOrder[]): AdminCustomer[] {
|
||||||
const byEmail = new Map<string, AdminOrder[]>();
|
const byEmail = new Map<string, AdminOrder[]>();
|
||||||
@@ -41,6 +44,7 @@ export class AdminCustomersFacade {
|
|||||||
|
|
||||||
loadList(): void {
|
loadList(): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
this.error.set(null);
|
||||||
this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 100000 }).pipe(take(1)).subscribe({
|
this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 100000 }).pipe(take(1)).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
this.customers.set(this.buildCustomers(result.items));
|
this.customers.set(this.buildCustomers(result.items));
|
||||||
@@ -49,16 +53,24 @@ export class AdminCustomersFacade {
|
|||||||
error: () => {
|
error: () => {
|
||||||
this.customers.set([]);
|
this.customers.set([]);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
this.error.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDetail(email: string): void {
|
loadDetail(email: string): void {
|
||||||
|
this.selectedLoading.set(true);
|
||||||
|
this.selectedError.set(null);
|
||||||
this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 100000 }).pipe(take(1)).subscribe({
|
this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 100000 }).pipe(take(1)).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
const decoded = decodeURIComponent(email);
|
const decoded = decodeURIComponent(email);
|
||||||
const customers = this.buildCustomers(result.items);
|
const customers = this.buildCustomers(result.items);
|
||||||
this.selected.set(customers.find(customer => customer.email === decoded) ?? null);
|
this.selected.set(customers.find(customer => customer.email === decoded) ?? null);
|
||||||
|
this.selectedLoading.set(false);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.selectedLoading.set(false);
|
||||||
|
this.selectedError.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,12 @@
|
|||||||
<app-order-timeline [entries]="activity()" [showOrderNumber]="true" />
|
<app-order-timeline [entries]="activity()" [showOrderNumber]="true" />
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
} @else if (facade.selectedError()) {
|
||||||
|
<div class="customer-detail-error" role="alert">
|
||||||
|
<p>{{ 'common.errorTitle' | translate }}</p>
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<app-button variant="secondary" size="sm" (click)="back()">{{ 'adminCustomers.back' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<p>{{ 'common.loading' | translate }}</p>
|
<p>{{ 'common.loading' | translate }}</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (facade.error()) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else if (facade.filteredCustomers().length === 0) {
|
} @else if (facade.filteredCustomers().length === 0) {
|
||||||
<app-empty-state [title]="'adminCustomers.emptyTitle' | translate" [description]="'adminCustomers.emptyDescription' | translate" />
|
<app-empty-state [title]="'adminCustomers.emptyTitle' | translate" [description]="'adminCustomers.emptyDescription' | translate" />
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ export class AdminOrdersFacade {
|
|||||||
readonly orders = signal<AdminOrder[]>([]);
|
readonly orders = signal<AdminOrder[]>([]);
|
||||||
readonly total = signal(0);
|
readonly total = signal(0);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
|
readonly error = signal<string | null>(null);
|
||||||
readonly selected = signal<AdminOrder | null>(null);
|
readonly selected = signal<AdminOrder | null>(null);
|
||||||
|
readonly selectedLoading = signal(false);
|
||||||
|
readonly selectedError = signal<string | null>(null);
|
||||||
|
|
||||||
readonly viewMode = signal<AdminOrdersViewMode>((this.localStorage.getItem(VIEW_MODE_KEY) as AdminOrdersViewMode) || 'table');
|
readonly viewMode = signal<AdminOrdersViewMode>((this.localStorage.getItem(VIEW_MODE_KEY) as AdminOrdersViewMode) || 'table');
|
||||||
readonly density = signal<AdminOrdersDensity>((this.localStorage.getItem(DENSITY_KEY) as AdminOrdersDensity) || 'comfortable');
|
readonly density = signal<AdminOrdersDensity>((this.localStorage.getItem(DENSITY_KEY) as AdminOrdersDensity) || 'comfortable');
|
||||||
@@ -76,6 +79,7 @@ export class AdminOrdersFacade {
|
|||||||
|
|
||||||
loadList(): void {
|
loadList(): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
this.error.set(null);
|
||||||
this.gateway.loadOrders(this.filters()).pipe(take(1)).subscribe({
|
this.gateway.loadOrders(this.filters()).pipe(take(1)).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
this.orders.set(result.items);
|
this.orders.set(result.items);
|
||||||
@@ -86,6 +90,7 @@ export class AdminOrdersFacade {
|
|||||||
this.orders.set([]);
|
this.orders.set([]);
|
||||||
this.total.set(0);
|
this.total.set(0);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
this.error.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -96,7 +101,18 @@ export class AdminOrdersFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadDetail(id: string): void {
|
loadDetail(id: string): void {
|
||||||
this.gateway.loadOrder(id).pipe(take(1)).subscribe({ next: order => this.selected.set(order) });
|
this.selectedLoading.set(true);
|
||||||
|
this.selectedError.set(null);
|
||||||
|
this.gateway.loadOrder(id).pipe(take(1)).subscribe({
|
||||||
|
next: order => {
|
||||||
|
this.selected.set(order);
|
||||||
|
this.selectedLoading.set(false);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.selectedLoading.set(false);
|
||||||
|
this.selectedError.set('common.errorDescription');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(id: string, status: AdminOrderStatus): void {
|
setStatus(id: string, status: AdminOrderStatus): void {
|
||||||
|
|||||||
@@ -103,6 +103,12 @@
|
|||||||
(confirmed)="confirmRefund()"
|
(confirmed)="confirmRefund()"
|
||||||
(cancelled)="pendingRefundId.set(null)" />
|
(cancelled)="pendingRefundId.set(null)" />
|
||||||
</main>
|
</main>
|
||||||
|
} @else if (facade.selectedError()) {
|
||||||
|
<div class="order-detail-error" role="alert">
|
||||||
|
<p>{{ 'common.errorTitle' | translate }}</p>
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<app-button variant="secondary" size="sm" (click)="back()">{{ 'adminOrders.back' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<p>{{ 'common.loading' | translate }}</p>
|
<p>{{ 'common.loading' | translate }}</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@
|
|||||||
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (facade.error()) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else if (facade.orders().length === 0) {
|
} @else if (facade.orders().length === 0) {
|
||||||
<app-empty-state [title]="'adminOrders.emptyTitle' | translate" [description]="'adminOrders.emptyDescription' | translate" />
|
<app-empty-state [title]="'adminOrders.emptyTitle' | translate" [description]="'adminOrders.emptyDescription' | translate" />
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -78,6 +78,8 @@
|
|||||||
}
|
}
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (error) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else if (products.length === 0) {
|
} @else if (products.length === 0) {
|
||||||
<app-empty-state [title]="'adminProducts.emptyTitle' | translate" [description]="'adminProducts.emptyDescription' | translate">
|
<app-empty-state [title]="'adminProducts.emptyTitle' | translate" [description]="'adminProducts.emptyDescription' | translate">
|
||||||
<span slot="actions">
|
<span slot="actions">
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export class AdminProductsListComponent {
|
|||||||
@Input() total = 0;
|
@Input() total = 0;
|
||||||
@Input() selectedIds: string[] = [];
|
@Input() selectedIds: string[] = [];
|
||||||
@Input() loading = false;
|
@Input() loading = false;
|
||||||
|
@Input() error: string | null = null;
|
||||||
@Input() infiniteScroll = false;
|
@Input() infiniteScroll = false;
|
||||||
@Input() viewMode: AdminProductsViewMode = 'table';
|
@Input() viewMode: AdminProductsViewMode = 'table';
|
||||||
@Input() density: AdminProductsDensity = 'comfortable';
|
@Input() density: AdminProductsDensity = 'comfortable';
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export class AdminProductsFacade {
|
|||||||
readonly infiniteScroll = signal(false);
|
readonly infiniteScroll = signal(false);
|
||||||
readonly categories = signal<AdminProductCategoryOption[]>([]);
|
readonly categories = signal<AdminProductCategoryOption[]>([]);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
|
readonly error = signal<string | null>(null);
|
||||||
|
readonly mutationError = signal<string | null>(null);
|
||||||
readonly selectedIds = signal<string[]>([]);
|
readonly selectedIds = signal<string[]>([]);
|
||||||
readonly draft = signal<AdminProduct | null>(null);
|
readonly draft = signal<AdminProduct | null>(null);
|
||||||
readonly dirty = signal(false);
|
readonly dirty = signal(false);
|
||||||
@@ -100,6 +102,7 @@ export class AdminProductsFacade {
|
|||||||
|
|
||||||
loadList(): void {
|
loadList(): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
this.error.set(null);
|
||||||
this.gateway.loadProducts(this.filters()).pipe(take(1)).subscribe({
|
this.gateway.loadProducts(this.filters()).pipe(take(1)).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
this.products.set(result.items);
|
this.products.set(result.items);
|
||||||
@@ -110,6 +113,7 @@ export class AdminProductsFacade {
|
|||||||
this.products.set([]);
|
this.products.set([]);
|
||||||
this.total.set(0);
|
this.total.set(0);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
this.error.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -302,18 +306,26 @@ export class AdminProductsFacade {
|
|||||||
this.dirty.set(true);
|
this.dirty.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDraft(): void {
|
saveDraft(onSuccess?: () => void): void {
|
||||||
const draft = this.draft();
|
const draft = this.draft();
|
||||||
if (!draft) return;
|
if (!draft) return;
|
||||||
|
|
||||||
|
this.mutationError.set(null);
|
||||||
const request = this.editorMode() === 'create'
|
const request = this.editorMode() === 'create'
|
||||||
? this.gateway.createProduct(draft)
|
? this.gateway.createProduct(draft)
|
||||||
: this.gateway.updateProduct(draft);
|
: this.gateway.updateProduct(draft);
|
||||||
|
|
||||||
request.pipe(take(1)).subscribe({ next: () => { this.dirty.set(false); this.loadList(); } });
|
request.pipe(take(1)).subscribe({
|
||||||
|
next: () => { this.dirty.set(false); this.loadList(); onSuccess?.(); },
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOne(id: string): void {
|
deleteOne(id: string): void {
|
||||||
this.gateway.deleteProduct(id).pipe(take(1)).subscribe({ next: () => this.loadList() });
|
this.mutationError.set(null);
|
||||||
|
this.gateway.deleteProduct(id).pipe(take(1)).subscribe({
|
||||||
|
next: () => this.loadList(),
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import { AdminProductsFacade } from '../facade/admin-products.facade';
|
|||||||
import { AdminProductFormComponent } from '../components/admin-product-form.component';
|
import { AdminProductFormComponent } from '../components/admin-product-form.component';
|
||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
|
import { DialogComponent } from '../../../../shared/ui/dialog/dialog.component';
|
||||||
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin-product-editor-page',
|
selector: 'app-admin-product-editor-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminProductFormComponent, TranslatePipe],
|
imports: [AdminProductFormComponent, TranslatePipe, DialogComponent, ButtonComponent],
|
||||||
template: `@if (facade.draft(); as draft) {<main class="editor-page"><header><h1>{{ title() | translate }}</h1></header><app-admin-product-form [product]="draft" [categories]="facade.categories()" [allProducts]="facade.products()" [locales]="facade.supportedLocales()" [mode]="facade.editorMode()" [health]="facade.health(draft)" (productChange)="facade.updateDraft($event)" (save)="save()" /></main>} @else {<main class="editor-page"><p>{{ 'common.loading' | translate }}</p></main>}`,
|
template: `@if (facade.draft(); as draft) {<main class="editor-page"><header><h1>{{ title() | translate }}</h1></header><app-admin-product-form [product]="draft" [categories]="facade.categories()" [allProducts]="facade.products()" [locales]="facade.supportedLocales()" [mode]="facade.editorMode()" [health]="facade.health(draft)" (productChange)="facade.updateDraft($event)" (save)="save()" /></main>} @else {<main class="editor-page"><p>{{ 'common.loading' | translate }}</p></main>}
|
||||||
|
@if (facade.mutationError()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'common.errorTitle' | translate" size="sm" (closed)="facade.mutationError.set(null)">
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="facade.mutationError.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
|
}`,
|
||||||
styles: [`.editor-page { max-width: 1120px; margin: 0 auto; padding: 24px; display: grid; gap: 16px; } .editor-page h1, .editor-page p { margin: 0; }`],
|
styles: [`.editor-page { max-width: 1120px; margin: 0 auto; padding: 24px; display: grid; gap: 16px; } .editor-page h1, .editor-page p { margin: 0; }`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
@@ -33,7 +43,8 @@ export class AdminProductEditorPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
this.facade.saveDraft();
|
this.facade.saveDraft(() => {
|
||||||
void this.router.navigate([this.languageService.currentLanguage(), 'backoffice', 'products']);
|
void this.router.navigate([this.languageService.currentLanguage(), 'backoffice', 'products']);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { AdminProductsListComponent } from '../components/admin-products-list.co
|
|||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
import { ConfirmDialogComponent } from '../../../../shared/ui/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../../../../shared/ui/confirm-dialog/confirm-dialog.component';
|
||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
|
import { DialogComponent } from '../../../../shared/ui/dialog/dialog.component';
|
||||||
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin-products-list-page',
|
selector: 'app-admin-products-list-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [AdminProductsListComponent, ConfirmDialogComponent, TranslatePipe],
|
imports: [AdminProductsListComponent, ConfirmDialogComponent, TranslatePipe, DialogComponent, ButtonComponent],
|
||||||
template: `<app-admin-products-list
|
template: `<app-admin-products-list
|
||||||
[products]="facade.products()"
|
[products]="facade.products()"
|
||||||
[categories]="facade.categories()"
|
[categories]="facade.categories()"
|
||||||
@@ -17,6 +19,7 @@ import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|||||||
[total]="facade.total()"
|
[total]="facade.total()"
|
||||||
[selectedIds]="facade.selectedIds()"
|
[selectedIds]="facade.selectedIds()"
|
||||||
[loading]="facade.loading()"
|
[loading]="facade.loading()"
|
||||||
|
[error]="facade.error()"
|
||||||
[infiniteScroll]="facade.infiniteScroll()"
|
[infiniteScroll]="facade.infiniteScroll()"
|
||||||
[viewMode]="facade.viewMode()"
|
[viewMode]="facade.viewMode()"
|
||||||
[density]="facade.density()"
|
[density]="facade.density()"
|
||||||
@@ -56,7 +59,15 @@ import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|||||||
[message]="'adminProducts.confirmBulkDelete' | translate"
|
[message]="'adminProducts.confirmBulkDelete' | translate"
|
||||||
[destructive]="true"
|
[destructive]="true"
|
||||||
(confirmed)="confirmBulkDelete()"
|
(confirmed)="confirmBulkDelete()"
|
||||||
(cancelled)="bulkDeleteConfirmOpen.set(false)" />`,
|
(cancelled)="bulkDeleteConfirmOpen.set(false)" />
|
||||||
|
@if (facade.mutationError()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'common.errorTitle' | translate" size="sm" (closed)="facade.mutationError.set(null)">
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="facade.mutationError.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
|
}`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class AdminProductsListPageComponent {
|
export class AdminProductsListPageComponent {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
}
|
}
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (facade.error()) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else {
|
} @else {
|
||||||
<div class="report-grid">
|
<div class="report-grid">
|
||||||
<div class="report-card">
|
<div class="report-card">
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { AdminAnalyticsFacade } from '../../analytics/facade/admin-analytics.fac
|
|||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||||
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
|
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
|
||||||
|
import { EmptyStateComponent } from '../../../../shared/ui/empty-state/empty-state.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin-reports-page',
|
selector: 'app-admin-reports-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe, ButtonComponent, SkeletonComponent],
|
imports: [TranslatePipe, ButtonComponent, SkeletonComponent, EmptyStateComponent],
|
||||||
templateUrl: './admin-reports-page.component.html',
|
templateUrl: './admin-reports-page.component.html',
|
||||||
styleUrls: ['./admin-reports-page.component.scss'],
|
styleUrls: ['./admin-reports-page.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ export class AdminTransactionsFacade {
|
|||||||
readonly transactions = signal<AdminTransaction[]>([]);
|
readonly transactions = signal<AdminTransaction[]>([]);
|
||||||
readonly total = signal(0);
|
readonly total = signal(0);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
|
readonly error = signal<string | null>(null);
|
||||||
|
|
||||||
loadList(): void {
|
loadList(): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
this.error.set(null);
|
||||||
this.gateway.loadTransactions(this.filters()).pipe(take(1)).subscribe({
|
this.gateway.loadTransactions(this.filters()).pipe(take(1)).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
this.transactions.set(result.items);
|
this.transactions.set(result.items);
|
||||||
@@ -24,6 +26,7 @@ export class AdminTransactionsFacade {
|
|||||||
this.transactions.set([]);
|
this.transactions.set([]);
|
||||||
this.total.set(0);
|
this.total.set(0);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
|
this.error.set('common.errorDescription');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
@for (i of [1,2,3,4]; track i) { <app-skeleton shape="rect" height="40px" /> }
|
||||||
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
<span class="sr-only">{{ 'common.loading' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
} @else if (facade.error()) {
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate" />
|
||||||
} @else if (facade.transactions().length === 0) {
|
} @else if (facade.transactions().length === 0) {
|
||||||
<app-empty-state [title]="'adminTransactions.emptyTitle' | translate" [description]="'adminTransactions.emptyDescription' | translate" />
|
<app-empty-state [title]="'adminTransactions.emptyTitle' | translate" [description]="'adminTransactions.emptyDescription' | translate" />
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class AdminUsersFacade {
|
|||||||
readonly invitations = signal<AdminInvitation[]>([]);
|
readonly invitations = signal<AdminInvitation[]>([]);
|
||||||
readonly loading = signal(false);
|
readonly loading = signal(false);
|
||||||
readonly error = signal(false);
|
readonly error = signal(false);
|
||||||
|
readonly mutationError = signal<string | null>(null);
|
||||||
readonly sessionsTarget = signal<AdminUser | null>(null);
|
readonly sessionsTarget = signal<AdminUser | null>(null);
|
||||||
readonly sessions = signal<AdminSession[]>([]);
|
readonly sessions = signal<AdminSession[]>([]);
|
||||||
readonly auditTarget = signal<AdminUser | null>(null);
|
readonly auditTarget = signal<AdminUser | null>(null);
|
||||||
@@ -33,16 +34,28 @@ export class AdminUsersFacade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRole(userId: string, roleId: string): void {
|
setRole(userId: string, roleId: string): void {
|
||||||
this.gateway.setUserRole(userId, roleId).pipe(take(1)).subscribe({ next: () => this.loadAll() });
|
this.mutationError.set(null);
|
||||||
|
this.gateway.setUserRole(userId, roleId).pipe(take(1)).subscribe({
|
||||||
|
next: () => this.loadAll(),
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(userId: string, status: AdminUserStatus): void {
|
setStatus(userId: string, status: AdminUserStatus): void {
|
||||||
this.gateway.setUserStatus(userId, status).pipe(take(1)).subscribe({ next: () => this.loadAll() });
|
this.mutationError.set(null);
|
||||||
|
this.gateway.setUserStatus(userId, status).pipe(take(1)).subscribe({
|
||||||
|
next: () => this.loadAll(),
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
invite(email: string, roleId: string, scope: AdminUserScope): void {
|
invite(email: string, roleId: string, scope: AdminUserScope): void {
|
||||||
if (!email.trim()) return;
|
if (!email.trim()) return;
|
||||||
this.gateway.inviteUser(email.trim(), roleId, scope).pipe(take(1)).subscribe({ next: () => this.loadAll() });
|
this.mutationError.set(null);
|
||||||
|
this.gateway.inviteUser(email.trim(), roleId, scope).pipe(take(1)).subscribe({
|
||||||
|
next: () => this.loadAll(),
|
||||||
|
error: () => this.mutationError.set('common.errorDescription')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
revokeInvitation(id: string): void {
|
revokeInvitation(id: string): void {
|
||||||
|
|||||||
@@ -128,4 +128,13 @@
|
|||||||
[destructive]="true"
|
[destructive]="true"
|
||||||
(confirmed)="confirmSuspend()"
|
(confirmed)="confirmSuspend()"
|
||||||
(cancelled)="pendingSuspendUserId.set(null)" />
|
(cancelled)="pendingSuspendUserId.set(null)" />
|
||||||
|
|
||||||
|
@if (facade.mutationError()) {
|
||||||
|
<app-dialog [open]="true" [titleText]="'common.errorTitle' | translate" size="sm" (closed)="facade.mutationError.set(null)">
|
||||||
|
<p>{{ 'common.errorDescription' | translate }}</p>
|
||||||
|
<div class="app-confirm-dialog__actions">
|
||||||
|
<app-button variant="primary" (click)="facade.mutationError.set(null)">{{ 'common.confirm' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-dialog>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -6,6 +6,14 @@
|
|||||||
<app-skeleton shape="text" height="16px" />
|
<app-skeleton shape="text" height="16px" />
|
||||||
<app-skeleton shape="text" width="70%" height="16px" />
|
<app-skeleton shape="text" width="70%" height="16px" />
|
||||||
</section>
|
</section>
|
||||||
|
} @else if (error()) {
|
||||||
|
<section class="static-page__state">
|
||||||
|
<app-empty-state [title]="'common.errorTitle' | translate" [description]="'common.errorDescription' | translate">
|
||||||
|
<div slot="actions">
|
||||||
|
<app-button variant="primary" [routerLink]="homeRoute()">{{ 'staticPages.backHome' | translate }}</app-button>
|
||||||
|
</div>
|
||||||
|
</app-empty-state>
|
||||||
|
</section>
|
||||||
} @else if (notFound()) {
|
} @else if (notFound()) {
|
||||||
<section class="static-page__state">
|
<section class="static-page__state">
|
||||||
<app-empty-state title="404" [description]="'staticPages.notFound' | translate">
|
<app-empty-state title="404" [description]="'staticPages.notFound' | translate">
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export class StaticPageComponent {
|
|||||||
|
|
||||||
readonly loading = signal(true);
|
readonly loading = signal(true);
|
||||||
readonly notFound = signal(false);
|
readonly notFound = signal(false);
|
||||||
|
readonly error = signal(false);
|
||||||
readonly title = signal('');
|
readonly title = signal('');
|
||||||
readonly homeRoute = signal('');
|
readonly homeRoute = signal('');
|
||||||
readonly dir = signal<'ltr' | 'rtl'>('ltr');
|
readonly dir = signal<'ltr' | 'rtl'>('ltr');
|
||||||
@@ -76,21 +77,32 @@ export class StaticPageComponent {
|
|||||||
private loadByKey(key: string): void {
|
private loadByKey(key: string): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
this.notFound.set(false);
|
this.notFound.set(false);
|
||||||
|
this.error.set(false);
|
||||||
|
|
||||||
this.staticPageResolver.resolveByKey(key, this.languageService.currentLanguage()).subscribe(page => {
|
this.staticPageResolver.resolveByKey(key, this.languageService.currentLanguage()).subscribe({
|
||||||
this.applyPage(page?.title ?? '', page?.html ?? '', !page);
|
next: page => this.applyPage(page?.title ?? '', page?.html ?? '', !page),
|
||||||
|
error: () => this.applyError()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadByPath(path: string): void {
|
private loadByPath(path: string): void {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
this.notFound.set(false);
|
this.notFound.set(false);
|
||||||
|
this.error.set(false);
|
||||||
|
|
||||||
this.staticPageResolver.resolveByRoute(path, this.languageService.currentLanguage()).subscribe(page => {
|
this.staticPageResolver.resolveByRoute(path, this.languageService.currentLanguage()).subscribe({
|
||||||
this.applyPage(page?.title ?? '', page?.html ?? '', !page);
|
next: page => this.applyPage(page?.title ?? '', page?.html ?? '', !page),
|
||||||
|
error: () => this.applyError()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyError(): void {
|
||||||
|
this.title.set('');
|
||||||
|
this.safeHtml.set(this.sanitizer.bypassSecurityTrustHtml(''));
|
||||||
|
this.error.set(true);
|
||||||
|
this.loading.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
private applyPage(title: string, html: string, notFound: boolean): void {
|
private applyPage(title: string, html: string, notFound: boolean): void {
|
||||||
if (notFound) {
|
if (notFound) {
|
||||||
this.title.set('');
|
this.title.set('');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { Observable, of, map, switchMap } from 'rxjs';
|
import { Observable, of, map, switchMap, catchError } from 'rxjs';
|
||||||
import { CategoryFacade } from '../../facades/platform/category.facade';
|
import { CategoryFacade } from '../../facades/platform/category.facade';
|
||||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||||
import { LanguageService } from '../../services/language.service';
|
import { LanguageService } from '../../services/language.service';
|
||||||
@@ -19,7 +19,11 @@ export class DataSourceResolverService {
|
|||||||
|
|
||||||
resolve(widget: WidgetConfig, section: SectionConfig): Observable<unknown> {
|
resolve(widget: WidgetConfig, section: SectionConfig): Observable<unknown> {
|
||||||
return this.widgetManifest.getWidget(widget.type).pipe(
|
return this.widgetManifest.getWidget(widget.type).pipe(
|
||||||
switchMap((definition) => this.resolveByDefinition(definition, widget, section))
|
switchMap((definition) => this.resolveByDefinition(definition, widget, section)),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(`Failed to resolve widget data for ${widget.type}:${widget.id}`, error);
|
||||||
|
return of({ section, settings: {} });
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user