diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.html b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.html index 2d31ade..37fd036 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.html +++ b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.html @@ -1,12 +1,14 @@
- + @if (!disabled()) { + + }

{{ title() }}

diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.scss b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.scss index 73b2910..3b6ece5 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.scss +++ b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.scss @@ -87,6 +87,7 @@ } .ticket--disabled { + grid-template-columns: minmax(0, 1fr) 78px auto; color: #8a8a8a; } @@ -116,4 +117,16 @@ grid-column: 3; grid-row: 1 / span 2; } + + .ticket--disabled { + grid-template-columns: minmax(0, 1fr) auto; + } + + .ticket--disabled .ticket__status { + grid-column: 1; + } + + .ticket--disabled .ticket__actions { + grid-column: 2; + } } diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.spec.ts b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.spec.ts index e69cccf..f7eb8d0 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.spec.ts +++ b/src/app/features/store/pages/account-page/pages/tickets-page/components/ticket/ticket.component.spec.ts @@ -56,6 +56,7 @@ describe('TicketComponent', () => { expect(qrButton?.disabled).toBe(true); expect(element.querySelector('[aria-label="Descargar ticket"]')).toBeNull(); expect(element.querySelector('[aria-label="Compartir ticket"]')).toBeNull(); + expect(element.querySelector('input[type="checkbox"]')).toBeNull(); qrButton?.click(); expect(viewQr).not.toHaveBeenCalled(); diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.html b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.html index 7f0a81e..a61587f 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.html +++ b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.html @@ -7,7 +7,7 @@ TICKETS ACTIVOS @@ -30,17 +30,33 @@ } } @else if (tickets().length) { - @for (ticket of tickets(); track ticket.id) { + @for (ticket of activeTickets(); track ticket.id) { } + + @if (inactiveTickets().length) { +
+
+

TICKETS INACTIVOS

+
+ + @for (ticket of inactiveTickets(); track ticket.id) { + + } +
+ } } @else {

Aún no tenés tickets disponibles.

} diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.scss b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.scss index cb56854..40ad53e 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.scss +++ b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.scss @@ -19,6 +19,21 @@ border-bottom: 1px solid #dddddd; } +.tickets-list__header--inactive { + justify-content: flex-start; +} + +.tickets-list__header--inactive h2 { + margin: 0; + color: #a0a0a0; + font-size: 14px; + font-weight: 700; +} + +.tickets-list__inactive { + margin-top: 44px; +} + .tickets-list__select-all { display: flex; align-items: center; diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.spec.ts b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.spec.ts new file mode 100644 index 0000000..751147b --- /dev/null +++ b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.spec.ts @@ -0,0 +1,79 @@ +import { By } from '@angular/platform-browser'; +import { TestBed } from '@angular/core/testing'; +import { describe, expect, it, vi } from 'vitest'; + +import { ToastService } from '../../../../../../core/services/toast.service'; +import { TicketComponent } from './components/ticket/ticket.component'; +import { TicketResponse, TicketService } from './ticket.service'; +import { TicketsPage } from './tickets-page'; + +const ticket = (overrides: Partial): TicketResponse => ({ + id: 1, + tenant_code: 'tenant', + ticket: 'ticket-1', + name: 'Entrada general', + description: null, + source_catalog_item_id: null, + source_variant_id: null, + starts_at: '2026-10-09T00:00:00.000Z', + expires_at: null, + used_at: null, + is_valid: true, + is_expired: false, + is_used: false, + ...overrides, +}); + +describe('TicketsPage', () => { + it('separates expired and used tickets into the disabled inactive list', async () => { + const tickets = [ + ticket({ id: 1 }), + ticket({ id: 2, is_valid: false, is_expired: true }), + ticket({ id: 3, is_valid: false, is_used: true, used_at: '2026-10-10T00:00:00.000Z' }), + ]; + + await TestBed.configureTestingModule({ + imports: [TicketsPage], + providers: [{ provide: ToastService, useValue: { danger: vi.fn() } }], + }) + .overrideComponent(TicketsPage, { + set: { + providers: [ + { provide: TicketService, useValue: { getTickets: () => Promise.resolve(tickets) } }, + ], + }, + }) + .compileComponents(); + + const fixture = TestBed.createComponent(TicketsPage); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + const ticketComponents = fixture.debugElement.queryAll(By.directive(TicketComponent)); + const [activeTicket, expiredTicket, usedTicket] = ticketComponents.map( + (element) => element.componentInstance as TicketComponent, + ); + + expect(ticketComponents).toHaveLength(3); + expect(activeTicket.disabled()).toBe(false); + expect(expiredTicket.disabled()).toBe(true); + expect(expiredTicket.expired()).toBe(true); + expect(usedTicket.disabled()).toBe(true); + expect(usedTicket.expired()).toBe(false); + expect(fixture.nativeElement.textContent).toContain('TICKETS INACTIVOS'); + + const page = fixture.nativeElement as HTMLElement; + const inactiveElements = Array.from( + page.querySelectorAll('.tickets-list__inactive app-ticket'), + ); + expect(inactiveElements).toHaveLength(2); + inactiveElements.forEach((element) => { + expect(element.querySelector('.ticket--disabled')).not.toBeNull(); + expect(element.querySelector('app-button button')?.disabled).toBe(true); + expect(element.querySelector('input[type="checkbox"]')).toBeNull(); + expect(element.querySelector('[aria-label="Descargar ticket"]')).toBeNull(); + expect(element.querySelector('[aria-label="Compartir ticket"]')).toBeNull(); + }); + }); +}); diff --git a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.ts b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.ts index 34852a6..be00505 100644 --- a/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.ts +++ b/src/app/features/store/pages/account-page/pages/tickets-page/tickets-page.ts @@ -17,10 +17,17 @@ export class TicketsPage implements OnInit { private readonly toastService = inject(ToastService); protected readonly tickets = signal([]); + protected readonly activeTickets = computed(() => + this.tickets().filter((ticket) => !this.isInactive(ticket)), + ); + protected readonly inactiveTickets = computed(() => + this.tickets().filter((ticket) => this.isInactive(ticket)), + ); protected readonly selectedIds = signal>(new Set()); protected readonly isLoading = signal(true); protected readonly allSelected = computed( - () => this.tickets().length > 0 && this.selectedIds().size === this.tickets().length, + () => + this.activeTickets().length > 0 && this.selectedIds().size === this.activeTickets().length, ); async ngOnInit(): Promise { @@ -34,7 +41,9 @@ export class TicketsPage implements OnInit { } protected toggleAll(selected: boolean): void { - this.selectedIds.set(selected ? new Set(this.tickets().map((ticket) => ticket.id)) : new Set()); + this.selectedIds.set( + selected ? new Set(this.activeTickets().map((ticket) => ticket.id)) : new Set(), + ); } protected toggleTicket(ticketId: number, selected: boolean): void { @@ -49,6 +58,10 @@ export class TicketsPage implements OnInit { return this.selectedIds().has(ticketId); } + protected isInactive(ticket: TicketResponse): boolean { + return ticket.is_expired || ticket.is_used; + } + protected formatDate(ticket: TicketResponse): string | null { const value = ticket.starts_at ?? ticket.expires_at; if (!value) return null;