feat(layout): enhance topbar with customizable templates and full-page support

- Updated app.layout.component.html to include start, center, and end templates for the topbar.
- Modified app.layout.component.ts to manage new template references and added isFullPage getter.
- Refactored app.topbar.component.html to utilize new templates and improved layout structure.
- Enhanced app.topbar.component.ts to accept new input properties for templates.
- Updated layout.service.ts to manage full-page state and new topbar slots.
- Added new payment bridge services for POS functionality.
- Introduced greater validator for form validation.
- Improved dialog component to support mobile drawer and responsive design.
- Updated styles for better mobile support and layout adjustments.
- Added new main menu sidebar for POS with dynamic content.
This commit is contained in:
2026-04-30 16:27:42 +03:30
parent c89d4027d6
commit 8104f1b7a7
56 changed files with 1130 additions and 434 deletions
@@ -1,7 +1,10 @@
<div class="layout-wrapper" [ngClass]="containerClass">
<app-topbar [showMenu]="showMenu()">
<ng-container [ngTemplateOutlet]="topBarMoreAction"></ng-container>
</app-topbar>
<app-topbar
[showMenu]="showMenu()"
[startTemplate]="topBarStartAction"
[centerTemplate]="topBarCenterAction"
[endTemplate]="topBarEndAction || topBarMoreAction"
/>
@if (fullLoading) {
<div class="flex justify-center align-items-center grow items-center">
<p-progressSpinner></p-progressSpinner>
@@ -10,12 +13,12 @@
@if (showMenu()) {
<app-sidebar></app-sidebar>
}
<div [class]="`layout-main-container ${!showMenu() ? 'hideMenu' : ''} grow`">
<div [class]="`layout-main-container ${!showMenu() ? 'hideMenu' : ''} grow ${isFullPage ? 'isFullPage' : ''}`">
<div class="layout-main flex flex-col gap-4">
@if (showBreadcrumb) {
<app-breadcrumb class="rounded-md overflow-hidden shrink-0" />
}
<div [class]="`rounded-md flex flex-col grow ${isFixedContentSize ? 'overflow-auto' : 'overflow-hidden'}`">
<div [class]="`rounded-md flex flex-col grow`">
<!-- style="container-type: size" -->
<div class="h-full">
@if (content) {
@@ -47,6 +47,9 @@ export class AppLayout {
private authService = inject(AuthService);
topBarMoreAction?: Maybe<TemplateRef<any>> = null;
topBarStartAction?: Maybe<TemplateRef<any>> = null;
topBarCenterAction?: Maybe<TemplateRef<any>> = null;
topBarEndAction?: Maybe<TemplateRef<any>> = null;
constructor(
public layoutService: LayoutService,
@@ -137,6 +140,9 @@ export class AppLayout {
get isFixedContentSize(): boolean {
return this.layoutService.isFixedContentSize() || false;
}
get isFullPage(): boolean {
return this.layoutService.isFullPage() || false;
}
showMenu = computed(() => {
return this.layoutService.menuItems().length > 0;
@@ -144,6 +150,9 @@ export class AppLayout {
ngOnInit() {
this.layoutService.headerSlot$.subscribe((tpl) => (this.topBarMoreAction = tpl));
this.layoutService.topbarStartSlot$.subscribe((tpl) => (this.topBarStartAction = tpl));
this.layoutService.topbarCenterSlot$.subscribe((tpl) => (this.topBarCenterAction = tpl));
this.layoutService.topbarEndSlot$.subscribe((tpl) => (this.topBarEndAction = tpl));
if (window.location.pathname === '/') {
if (!this.authService.currentAccount()) {
@@ -1,43 +1,44 @@
<div class="layout-topbar shrink-0">
<div class="layout-topbar-logo-container">
@if (showMenu) {
<button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
<i class="pi pi-bars"></i>
</button>
}
<div class="layout-topbar-logo">
<img [src]="logo" width="32" />
<span class="text-xl">{{ panelTitle() }}</span>
</div>
</div>
<div class="layout-topbar-actions items-center">
<ng-content></ng-content>
<div class="layout-config-menu">
<button type="button" class="layout-topbar-action" (click)="toggleDarkMode()">
<i
[ngClass]="{ 'pi ': true, 'pi-moon': layoutService.isDarkTheme(), 'pi-sun': !layoutService.isDarkTheme() }"
></i>
</button>
<!-- <div class="relative">
<button
class="layout-topbar-action layout-topbar-action-highlight"
pStyleClass="@next"
enterFromClass="hidden"
enterActiveClass="animate-scalein"
leaveToClass="hidden"
leaveActiveClass="animate-fadeout"
[hideOnOutsideClick]="true"
>
<i class="pi pi-palette"></i>
<p-toolbar>
<ng-template #start>
@if (startTemplate) {
<ng-container [ngTemplateOutlet]="startTemplate"></ng-container>
} @else {
@if (showMenu) {
<button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
<i class="pi pi-bars"></i>
</button>
<app-configurator></app-configurator>
</div> -->
</div>
}
<div class="flex items-center gap-2">
<img [src]="logo" width="32" />
<span class="text-xl">{{ panelTitle() }}</span>
</div>
}
</ng-template>
<ng-template #center>
@if (centerTemplate) {
<ng-container [ngTemplateOutlet]="centerTemplate"></ng-container>
}
</ng-template>
<ng-template #end>
@if (endTemplate) {
<ng-container [ngTemplateOutlet]="endTemplate"></ng-container>
} @else {
<div class="flex items-center gap-2">
<ng-content></ng-content>
<p-button
type="button"
[icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`"
text
(click)="toggleDarkMode()"
>
<i [ngClass]="{}"></i>
</p-button>
<div class="">
<p-menu #menu [model]="profileMenuItems" [popup]="true" />
<p-button (click)="menu.toggle($event)" icon="pi pi-user" text severity="contrast" size="large" />
</div>
</div>
</div>
<div class="">
<p-menu #menu [model]="profileMenuItems" [popup]="true" />
<p-button (click)="menu.toggle($event)" icon="pi pi-user" text severity="contrast" size="large" />
</div>
</div>
}
</ng-template>
</p-toolbar>
@@ -1,22 +1,26 @@
import { AuthService } from '@/core/services/auth.service';
import { CommonModule } from '@angular/common';
import { Component, computed, inject, Input } from '@angular/core';
import { Component, computed, inject, Input, TemplateRef } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { Button } from 'primeng/button';
import { Button, ButtonIcon } from 'primeng/button';
import { MenuModule } from 'primeng/menu';
import { StyleClassModule } from 'primeng/styleclass';
import { Toolbar } from 'primeng/toolbar';
import images from 'src/assets/images';
import { LayoutService } from '../service/layout.service';
@Component({
selector: 'app-topbar',
standalone: true,
imports: [RouterModule, CommonModule, StyleClassModule, MenuModule, Button],
imports: [RouterModule, CommonModule, StyleClassModule, MenuModule, Button, Toolbar, ButtonIcon],
templateUrl: './app.topbar.component.html',
})
export class AppTopbar {
@Input() showMenu: boolean = false;
@Input() startTemplate?: TemplateRef<any> | null = null;
@Input() centerTemplate?: TemplateRef<any> | null = null;
@Input() endTemplate?: TemplateRef<any> | null = null;
constructor(public layoutService: LayoutService) {}
private authService: AuthService = inject(AuthService);
+30
View File
@@ -21,6 +21,7 @@ interface LayoutState {
staticMenuMobileActive?: boolean;
menuHoverActive?: boolean;
isFixedContentSize?: boolean;
isFullPage?: boolean;
}
interface MenuChangeEvent {
@@ -47,6 +48,7 @@ export class LayoutService {
staticMenuMobileActive: false,
menuHoverActive: false,
isFixedContentSize: true,
isFullPage: false,
};
layoutConfig = signal<layoutConfig>(this._config);
@@ -90,6 +92,7 @@ export class LayoutService {
isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay');
isFixedContentSize = computed(() => this.layoutState().isFixedContentSize);
isFullPage = computed(() => this.layoutState().isFullPage);
transitionComplete = signal<boolean>(false);
@@ -144,6 +147,13 @@ export class LayoutService {
}));
}
changeIsFullPage(status: boolean) {
this.layoutState.update((prev) => ({
...prev,
isFullPage: status,
}));
}
toggleDarkMode(config?: layoutConfig): void {
const _config = config || this.layoutConfig();
localStorage.setItem('isDarkTheme', JSON.stringify(_config.darkTheme));
@@ -221,8 +231,28 @@ export class LayoutService {
private headerSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
headerSlot$ = this.headerSlot.asObservable();
private topbarStartSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarStartSlot$ = this.topbarStartSlot.asObservable();
private topbarCenterSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarCenterSlot$ = this.topbarCenterSlot.asObservable();
private topbarEndSlot = new BehaviorSubject<TemplateRef<any> | null>(null);
topbarEndSlot$ = this.topbarEndSlot.asObservable();
setHeaderSlot(tpl: TemplateRef<any> | null) {
// Backward-compatible alias for topbar end slot.
this.setTopbarEndSlot(tpl);
this.headerSlot.next(tpl);
}
setTopbarStartSlot(tpl: TemplateRef<any> | null) {
this.topbarStartSlot.next(tpl);
}
setTopbarCenterSlot(tpl: TemplateRef<any> | null) {
this.topbarCenterSlot.next(tpl);
}
setTopbarEndSlot(tpl: TemplateRef<any> | null) {
this.topbarEndSlot.next(tpl);
}
}