diff --git a/projects/ui/src/lib/components/po-helper/interfaces/po-helper-custom-action.interface.ts b/projects/ui/src/lib/components/po-helper/interfaces/po-helper-custom-action.interface.ts new file mode 100644 index 0000000000..af4469bf88 --- /dev/null +++ b/projects/ui/src/lib/components/po-helper/interfaces/po-helper-custom-action.interface.ts @@ -0,0 +1,18 @@ +/** + * @docsPrivate + * + * @description + * + * Interface interna para configuração de ações customizadas no po-helper. + * Utilizada exclusivamente para repasse de configuração entre os componentes de página (po-page-default, po-page-header) e o po-helper. + */ +export interface PoHelperCustomAction { + /** Ícone a ser exibido no botão. */ + icon: string; + + /** Label de acessibilidade para o botão. */ + ariaLabel?: string; + + /** Função callback a ser executada ao clicar no botão. */ + action: Function; +} diff --git a/projects/ui/src/lib/components/po-helper/po-helper-base.component.spec.ts b/projects/ui/src/lib/components/po-helper/po-helper-base.component.spec.ts index d258bc4464..8c880e48ec 100644 --- a/projects/ui/src/lib/components/po-helper/po-helper-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-helper/po-helper-base.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PoHelperBaseComponent } from './po-helper-base.component'; import { PoHelperOptions } from './interfaces/po-helper.interface'; +import { PoHelperCustomAction } from './interfaces/po-helper-custom-action.interface'; describe('PoHelperBaseComponent:', () => { let component: PoHelperBaseComponent; @@ -34,6 +35,42 @@ describe('PoHelperBaseComponent:', () => { fixture.componentRef.setInput('p-helper', options); expect(component.helper()).toEqual(options); }); + + describe('p-custom-action:', () => { + it('should have customAction input with undefined as default value', () => { + expect(component.customAction()).toBeUndefined(); + }); + + it('should accept a valid PoHelperCustomAction object', () => { + const action: PoHelperCustomAction = { + icon: 'ICON_REFRESH', + ariaLabel: 'Refresh', + action: () => {} + }; + fixture.componentRef.setInput('p-custom-action', action); + expect(component.customAction()).toEqual(action); + }); + + it('should accept a PoHelperCustomAction without ariaLabel', () => { + const action: PoHelperCustomAction = { + icon: 'ICON_REFRESH', + action: () => {} + }; + fixture.componentRef.setInput('p-custom-action', action); + expect(component.customAction()).toEqual(action); + expect(component.customAction().ariaLabel).toBeUndefined(); + }); + + it('should accept undefined value', () => { + fixture.componentRef.setInput('p-custom-action', undefined); + expect(component.customAction()).toBeUndefined(); + }); + + it('should accept null value', () => { + fixture.componentRef.setInput('p-custom-action', null); + expect(component.customAction()).toBeNull(); + }); + }); }); describe('transformHelper:', () => { diff --git a/projects/ui/src/lib/components/po-helper/po-helper-base.component.ts b/projects/ui/src/lib/components/po-helper/po-helper-base.component.ts index df28c7fa47..2266122222 100644 --- a/projects/ui/src/lib/components/po-helper/po-helper-base.component.ts +++ b/projects/ui/src/lib/components/po-helper/po-helper-base.component.ts @@ -1,7 +1,9 @@ import { Component, HostBinding, input } from '@angular/core'; -import { PoHelperOptions } from './interfaces/po-helper.interface'; + import { validateSizeFn } from '../../utils/util'; import { PoFieldSize } from '../../enums/po-field-size.enum'; +import { PoHelperOptions } from './interfaces/po-helper.interface'; +import { PoHelperCustomAction } from './interfaces/po-helper-custom-action.interface'; /** * @description * @@ -86,6 +88,9 @@ export class PoHelperBaseComponent { transform: this.transformHelper.bind(this) }); + /** @docsPrivate */ + customAction = input(undefined, { alias: 'p-custom-action' }); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-helper/po-helper.component.html b/projects/ui/src/lib/components/po-helper/po-helper.component.html index e6f99bc024..0a1ac2f24b 100644 --- a/projects/ui/src/lib/components/po-helper/po-helper.component.html +++ b/projects/ui/src/lib/components/po-helper/po-helper.component.html @@ -1,52 +1,68 @@ -
- - -
- - @for (fragment of contentFragments(); track $index) { - {{ fragment.text }} +
+} @else { +
+ + +
+ + @for (fragment of contentFragments(); track $index) { + {{ fragment.text }} + } + + @if (helper()?.type === 'help' && helper()?.footerAction) { + + + } - - @if (helper()?.type === 'help' && helper()?.footerAction) { - - - - } -
-
-
+
+ + +} diff --git a/projects/ui/src/lib/components/po-helper/po-helper.component.spec.ts b/projects/ui/src/lib/components/po-helper/po-helper.component.spec.ts index 5f8b193c68..81a2c8e7f5 100644 --- a/projects/ui/src/lib/components/po-helper/po-helper.component.spec.ts +++ b/projects/ui/src/lib/components/po-helper/po-helper.component.spec.ts @@ -609,4 +609,80 @@ describe('PoHelperComponent', () => { expect(component['contentFragments']()).toEqual([]); }); }); + + describe('executeCustomAction:', () => { + it('should call action function from customAction', () => { + const actionSpy = jasmine.createSpy('action'); + (component as any).customAction = () => ({ icon: 'ICON_REFRESH', action: actionSpy }); + + const event = new MouseEvent('click', { bubbles: true, cancelable: true }); + spyOn(event, 'preventDefault'); + spyOn(event, 'stopPropagation'); + + component.executeCustomAction(event); + + expect(event.preventDefault).toHaveBeenCalled(); + expect(event.stopPropagation).toHaveBeenCalled(); + expect(actionSpy).toHaveBeenCalled(); + }); + + it('should set refreshAnimating to true when action is called', () => { + const actionSpy = jasmine.createSpy('action'); + (component as any).customAction = () => ({ icon: 'ICON_REFRESH', action: actionSpy }); + + const event = new MouseEvent('click'); + component.executeCustomAction(event); + + expect(component.refreshAnimating).toBeTrue(); + }); + + it('should not call action if customAction is undefined', () => { + (component as any).customAction = () => undefined; + + const event = new MouseEvent('click'); + expect(() => component.executeCustomAction(event)).not.toThrow(); + expect(component.refreshAnimating).toBeFalse(); + }); + + it('should not call action if action is not a function', () => { + (component as any).customAction = () => ({ icon: 'ICON_REFRESH', action: 'not a function' }); + + const event = new MouseEvent('click'); + expect(() => component.executeCustomAction(event)).not.toThrow(); + expect(component.refreshAnimating).toBeFalse(); + }); + + it('should reset and retrigger animation on rapid successive clicks', () => { + const actionSpy = jasmine.createSpy('action'); + (component as any).customAction = () => ({ icon: 'ICON_REFRESH', action: actionSpy }); + const cdrSpy = spyOn(component['cdr'], 'detectChanges'); + + const event = new MouseEvent('click'); + + // First click + component.executeCustomAction(event); + expect(component.refreshAnimating).toBeTrue(); + expect(cdrSpy).not.toHaveBeenCalled(); + + // Second click while animation is still running + component.executeCustomAction(event); + expect(cdrSpy).toHaveBeenCalled(); + expect(component.refreshAnimating).toBeTrue(); + expect(actionSpy).toHaveBeenCalledTimes(2); + }); + }); + + describe('onRefreshAnimationEnd:', () => { + it('should set refreshAnimating to false', () => { + component.refreshAnimating = true; + component.onRefreshAnimationEnd(); + expect(component.refreshAnimating).toBeFalse(); + }); + }); + + describe('refreshAnimating:', () => { + it('should be false by default', () => { + expect(component.refreshAnimating).toBeFalse(); + }); + }); }); diff --git a/projects/ui/src/lib/components/po-helper/po-helper.component.ts b/projects/ui/src/lib/components/po-helper/po-helper.component.ts index 07cc6ddab7..9ab12aa482 100644 --- a/projects/ui/src/lib/components/po-helper/po-helper.component.ts +++ b/projects/ui/src/lib/components/po-helper/po-helper.component.ts @@ -1,18 +1,19 @@ import { + computed, Component, - ElementRef, - ViewChild, - AfterViewInit, OnDestroy, OnChanges, + ViewChild, + ElementRef, + AfterViewInit, SimpleChanges, - ChangeDetectorRef, - computed + ChangeDetectorRef } from '@angular/core'; + +import { PoButtonComponent } from '../po-button'; import { PoHelperBaseComponent } from './po-helper-base.component'; import { PoHelperOptions } from './interfaces/po-helper.interface'; import { PoPopoverComponent } from '../po-popover/po-popover.component'; -import { PoButtonComponent } from '../po-button'; import { parseSafeText, PoTextFragment, PoFormattingTag } from '../../utils/safe-text-parser'; /** Tags de formatação aceitas pelo po-helper. */ @@ -49,6 +50,7 @@ export class PoHelperComponent extends PoHelperBaseComponent implements AfterVie @ViewChild('popover', { static: false }) popover: PoPopoverComponent; @ViewChild(PoButtonComponent, { read: ElementRef, static: true }) poButton: PoButtonComponent; + refreshAnimating = false; private static instances: Array = []; private static idCounter = 0; protected popoverPosition = 'right'; @@ -224,4 +226,22 @@ export class PoHelperComponent extends PoHelperBaseComponent implements AfterVie window.removeEventListener('focusin', this.boundFocusIn, true); } } + + executeCustomAction(event: Event): void { + event.preventDefault(); + event.stopPropagation(); + const action = this.customAction(); + if (action && typeof action.action === 'function') { + if (this.refreshAnimating) { + this.refreshAnimating = false; + this.cdr.detectChanges(); + } + this.refreshAnimating = true; + action.action(); + } + } + + onRefreshAnimationEnd(): void { + this.refreshAnimating = false; + } } diff --git a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.spec.ts b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.spec.ts index 248c3db779..b0d883877b 100644 --- a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.spec.ts @@ -1,5 +1,5 @@ -import { Directive } from '@angular/core'; -import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Component, Directive } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { expectPropertiesValues } from '../../../util-test/util-expect.spec'; import { poLocaleDefault } from './../../../services/po-language/po-language.constant'; @@ -15,12 +15,21 @@ class PoPageDefaultComponent extends PoPageDefaultBaseComponent { getVisibleActions() {} } +@Component({ + selector: 'po-page-default-test-host', + template: '', + standalone: false +}) +class PoPageDefaultTestHostComponent extends PoPageDefaultComponent {} + describe('PoPageDefaultBaseComponent:', () => { let languageService: PoLanguageService; let component: PoPageDefaultComponent; beforeEach(async () => { - await TestBed.configureTestingModule({}).compileComponents(); + await TestBed.configureTestingModule({ + declarations: [PoPageDefaultTestHostComponent] + }).compileComponents(); languageService = new PoLanguageService(); @@ -294,5 +303,108 @@ describe('PoPageDefaultBaseComponent:', () => { expect(result).toBeNull(); }); }); + + describe('p-refresh:', () => { + let fixtureHost: ComponentFixture; + let hostComponent: PoPageDefaultTestHostComponent; + + beforeEach(() => { + fixtureHost = TestBed.createComponent(PoPageDefaultTestHostComponent); + hostComponent = fixtureHost.componentInstance; + fixtureHost.detectChanges(); + }); + + it('should have refresh input with null as default value', () => { + expect(hostComponent.refresh()).toBeNull(); + }); + + it('should accept a function value', () => { + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + expect(hostComponent.refresh()).toBe(fn); + }); + + it('should accept null value', () => { + fixtureHost.componentRef.setInput('p-refresh', null); + expect(hostComponent.refresh()).toBeNull(); + }); + }); + + describe('refreshCustomAction:', () => { + let fixtureHost: ComponentFixture; + let hostComponent: PoPageDefaultTestHostComponent; + + beforeEach(() => { + fixtureHost = TestBed.createComponent(PoPageDefaultTestHostComponent); + hostComponent = fixtureHost.componentInstance; + fixtureHost.detectChanges(); + }); + + it('should return undefined when refresh is null', () => { + fixtureHost.componentRef.setInput('p-refresh', null); + expect((hostComponent as any).refreshCustomAction()).toBeUndefined(); + }); + + it('should return undefined when refresh is not a function', () => { + fixtureHost.componentRef.setInput('p-refresh', 'not a function'); + expect((hostComponent as any).refreshCustomAction()).toBeUndefined(); + }); + + it('should return PoHelperCustomAction when refresh is a valid function', () => { + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + + expect(result).toBeDefined(); + expect(result.icon).toBe('ICON_REFRESH'); + expect(result.action).toBe(fn); + }); + + it('should return ariaLabel based on language (pt)', () => { + hostComponent['language'] = 'pt'; + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + expect(result.ariaLabel).toBe('Atualizar'); + }); + + it('should return ariaLabel based on language (en)', () => { + hostComponent['language'] = 'en'; + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + expect(result.ariaLabel).toBe('Refresh'); + }); + + it('should return ariaLabel based on language (es)', () => { + hostComponent['language'] = 'es'; + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + expect(result.ariaLabel).toBe('Actualizar'); + }); + + it('should return ariaLabel based on language (ru)', () => { + hostComponent['language'] = 'ru'; + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + expect(result.ariaLabel).toBe('Обновить'); + }); + + it('should fallback to EN ariaLabel for unsupported language', () => { + hostComponent['language'] = 'zw'; + const fn = () => {}; + fixtureHost.componentRef.setInput('p-refresh', fn); + + const result = (hostComponent as any).refreshCustomAction(); + expect(result.ariaLabel).toBe('Refresh'); + }); + }); }); }); diff --git a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.ts b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.ts index 26fecffbbb..b9ea83db8b 100644 --- a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.ts +++ b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default-base.component.ts @@ -1,17 +1,17 @@ -import { Directive, HostBinding, HostListener, Input, ViewChild, input, output } from '@angular/core'; - -import { poLocaleDefault } from './../../../services/po-language/po-language.constant'; -import { PoLanguageService } from './../../../services/po-language/po-language.service'; +import { Directive, HostBinding, HostListener, Input, ViewChild, computed, input, output } from '@angular/core'; import { PoFieldSize } from '../../../enums/po-field-size.enum'; +import { PoPageHeaderType } from './enums/po-page-header-type.enum'; +import { PoPageAction } from '../interfaces/po-page-action.interface'; import { getDefaultSizeFn, validateSizeFn } from '../../../utils/util'; +import { PoPageActionsLayout } from './enums/po-page-actions-layout.enum'; import { PoBreadcrumb } from '../../po-breadcrumb/po-breadcrumb.interface'; +import { PoPageDefaultLiterals } from './po-page-default-literals.interface'; import { PoHelperOptions } from '../../po-helper/interfaces/po-helper.interface'; -import { PoPageAction } from '../interfaces/po-page-action.interface'; import { PoPageContentComponent } from '../po-page-content/po-page-content.component'; -import { PoPageActionsLayout } from './enums/po-page-actions-layout.enum'; -import { PoPageHeaderType } from './enums/po-page-header-type.enum'; -import { PoPageDefaultLiterals } from './po-page-default-literals.interface'; +import { poLocaleDefault } from './../../../services/po-language/po-language.constant'; +import { PoLanguageService } from './../../../services/po-language/po-language.service'; +import { PoHelperCustomAction } from '../../po-helper/interfaces/po-helper-custom-action.interface'; export const poPageDefaultLiteralsDefault = { en: { @@ -35,6 +35,13 @@ export const backNavigationAriaLabels = { ru: 'Назад' }; +export const refreshAriaLabels = { + en: 'Refresh', + es: 'Actualizar', + pt: 'Atualizar', + ru: 'Обновить' +}; + /** * @description * @@ -260,6 +267,39 @@ export abstract class PoPageDefaultBaseComponent { return this._pageHeaderType; } + /** + * @optional + * + * @description + * + * Define a função de callback executada ao clicar no botão de atualização (refresh) ao lado do subtítulo da página. + * + * Quando não houver subtítulo (`p-subtitle`), o refresh será exibido logo abaixo do título. + * + * > Esta propriedade possui precedência sobre a configuração de `p-helper`. + * + * Exemplo de uso: + * ```html + * + * ``` + */ + refresh = input(null, { alias: 'p-refresh' }); + + protected readonly refreshCustomAction = computed(() => { + const fn = this.refresh(); + if (fn && typeof fn === 'function') { + return { + icon: 'ICON_REFRESH', + ariaLabel: refreshAriaLabels[this.language] || refreshAriaLabels['en'], + action: fn + }; + } + return undefined; + }); + /** * @optional * diff --git a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default.component.html b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default.component.html index 1465d81b64..a0a1b8b747 100644 --- a/projects/ui/src/lib/components/po-page/po-page-default/po-page-default.component.html +++ b/projects/ui/src/lib/components/po-page/po-page-default/po-page-default.component.html @@ -83,7 +83,8 @@ @case ('secondary') { * * + * + * + * + * + * */ @Component({ selector: 'po-page-default', diff --git a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.html b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.html index 446ba62e39..f36efacb48 100644 --- a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.html +++ b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.html @@ -6,6 +6,7 @@ [p-literals]="customLiterals ?? {}" [p-page-actions-layout]="pageActionsLayout" [p-page-header-type]="pageHeaderType" + [p-refresh]="showRefresh ? onRefresh : null" [p-title]="title" [p-subtitle]="subtitle" (p-back)="onBack()" @@ -20,7 +21,10 @@ - + + + + @if (showHelper) { diff --git a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.ts b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.ts index 3053400b56..2c71225fab 100644 --- a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.ts +++ b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-labs/sample-po-page-default-labs.component.ts @@ -43,6 +43,7 @@ export class SamplePoPageDefaultLabsComponent implements OnInit { helperTitle: string = ''; helperType: 'help' | 'info' = 'info'; showHelper: boolean = false; + showRefresh: boolean = false; public readonly helperTypeOptions: Array = [ { label: 'help', value: 'help' }, @@ -155,6 +156,10 @@ export class SamplePoPageDefaultLabsComponent implements OnInit { this.poNotification.information('Back button clicked (p-back event)'); } + onRefresh = (): void => { + this.poNotification.success('Page refreshed (p-refresh event)'); + }; + restore() { this.actions = []; this.breadcrumb = { items: [] }; @@ -170,6 +175,7 @@ export class SamplePoPageDefaultLabsComponent implements OnInit { this.pageActionsLayout = 'default'; this.pageHeaderType = 'primary'; this.showHelper = false; + this.showRefresh = false; this.subtitle = ''; this.title = 'PO Page Default'; this.restoreActionForm(); diff --git a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.html b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.html new file mode 100644 index 0000000000..6429cd9895 --- /dev/null +++ b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.html @@ -0,0 +1,3 @@ + + + diff --git a/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.ts b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.ts new file mode 100644 index 0000000000..956f3bc63f --- /dev/null +++ b/projects/ui/src/lib/components/po-page/po-page-default/samples/sample-po-page-default-refresh/sample-po-page-default-refresh.component.ts @@ -0,0 +1,81 @@ +import { Component, OnInit } from '@angular/core'; + +import { PoNotificationService, PoTableColumn } from '@po-ui/ng-components'; + +@Component({ + selector: 'sample-po-page-default-refresh', + templateUrl: './sample-po-page-default-refresh.component.html', + standalone: false +}) +export class SamplePoPageDefaultRefreshComponent implements OnInit { + columns: Array = []; + items: Array = []; + loading: boolean = false; + + private readonly allItems: Array = [ + { id: 1, product: 'Notebook Pro', quantity: 12, price: 4599.9, status: 'Available' }, + { id: 2, product: 'Wireless Mouse', quantity: 85, price: 129.9, status: 'Available' }, + { id: 3, product: 'Mechanical Keyboard', quantity: 34, price: 459.9, status: 'Available' }, + { id: 4, product: 'Monitor 27"', quantity: 7, price: 2199.9, status: 'Low stock' }, + { id: 5, product: 'USB-C Hub', quantity: 0, price: 249.9, status: 'Out of stock' }, + { id: 6, product: 'Webcam HD', quantity: 23, price: 349.9, status: 'Available' }, + { id: 7, product: 'Headset Bluetooth', quantity: 41, price: 599.9, status: 'Available' }, + { id: 8, product: 'External SSD 1TB', quantity: 3, price: 689.9, status: 'Low stock' } + ]; + + constructor(private readonly poNotification: PoNotificationService) {} + + ngOnInit(): void { + this.columns = this.getColumns(); + this.loadItems(); + } + + onRefresh = (): void => { + this.loading = true; + + setTimeout(() => { + this.refreshItems(); + this.loading = false; + this.poNotification.success('Inventory data refreshed successfully.'); + }, 1000); + }; + + private getColumns(): Array { + return [ + { property: 'id', label: 'ID', width: '60px' }, + { property: 'product', label: 'Product' }, + { property: 'quantity', label: 'Quantity', width: '100px' }, + { property: 'price', label: 'Price', type: 'currency', format: 'BRL', width: '140px' }, + { + property: 'status', + label: 'Status', + type: 'label', + width: '130px', + labels: [ + { value: 'Available', color: 'color-10', label: 'Available' }, + { value: 'Low stock', color: 'color-08', label: 'Low stock' }, + { value: 'Out of stock', color: 'color-07', label: 'Out of stock' } + ] + } + ]; + } + + private loadItems(): void { + this.items = [...this.allItems]; + } + + private refreshItems(): void { + this.items = this.allItems.map(item => ({ + ...item, + quantity: item.quantity + Math.floor(Math.random() * 10), + status: this.getStatus(item.quantity + Math.floor(Math.random() * 10)) + })); + } + + private getStatus(quantity: number): string { + if (quantity === 0) { + return 'Out of stock'; + } + return quantity <= 5 ? 'Low stock' : 'Available'; + } +} diff --git a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.spec.ts b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.spec.ts index 9d29fe1aa3..a42d5e31e2 100644 --- a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.spec.ts @@ -19,6 +19,12 @@ describe('PoPageHeaderBaseComponent', () => { }); }); + describe('customAction property:', () => { + it('should have customAction input with undefined as default value', () => { + expect(component.customAction()).toBeUndefined(); + }); + }); + describe('subtitle property:', () => { it('should store the raw subtitle value', () => { component.subtitle = 'Texto simples'; diff --git a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.ts b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.ts index c01d9eb9df..1273cbfbc1 100644 --- a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.ts +++ b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header-base.component.ts @@ -3,6 +3,7 @@ import { Directive, Input, input } from '@angular/core'; import { PoBreadcrumb } from '../../po-breadcrumb/po-breadcrumb.interface'; import { PoHelperOptions } from '../../po-helper/interfaces/po-helper.interface'; import { parseSafeText, PoFormattingTag, PoTextFragment } from '../../../utils/safe-text-parser'; +import { PoHelperCustomAction } from '../../po-helper/interfaces/po-helper-custom-action.interface'; /** Tags aceitas pelo subtítulo do po-page-header. */ const PAGE_SUBTITLE_ALLOWED_TAGS: Array = ['b', 'i', 'u', 'strong', 'em']; @@ -23,6 +24,9 @@ export class PoPageHeaderBaseComponent { /** Define o conteúdo do po-helper. */ helper = input(undefined, { alias: 'p-helper' }); + /** Define a ação customizada para o po-helper (uso interno entre componentes). */ + customAction = input(undefined, { alias: 'p-custom-action' }); + /** Define o tamanho dos componentes no header. */ @Input('p-size') size: string; diff --git a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header.component.html b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header.component.html index 30b5143040..e85f8f22cd 100644 --- a/projects/ui/src/lib/components/po-page/po-page-header/po-page-header.component.html +++ b/projects/ui/src/lib/components/po-page/po-page-header/po-page-header.component.html @@ -46,15 +46,23 @@

> } - @if (helper()) { + @if (customAction()) { + + } @else if (helper()) { } } - @if (title && !subtitle && helper()) { -
- -
+ @if (title && !subtitle) { + @if (customAction()) { +
+ +
+ } @else if (helper()) { +
+ +
+ } } }