feat(ticket-selector): implement zoomable image feature for seating map
This commit is contained in:
parent
f0c1e74c1b
commit
ebae063a0f
|
|
@ -107,6 +107,17 @@
|
|||
</div>
|
||||
|
||||
@if (imageUrl(); as image) {
|
||||
<img class="ticket-selector__map" [src]="image" [alt]="'Plano de ubicaciones de ' + title()" />
|
||||
<button
|
||||
type="button"
|
||||
class="ticket-selector__map-button"
|
||||
aria-label="Ampliar plano de ubicaciones"
|
||||
(click)="openImage(image)"
|
||||
>
|
||||
<img
|
||||
class="ticket-selector__map"
|
||||
[src]="image"
|
||||
[alt]="'Plano de ubicaciones de ' + title()"
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -153,11 +153,27 @@
|
|||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
&__map {
|
||||
&__map-button {
|
||||
display: block;
|
||||
width: min(100%, 720px);
|
||||
max-height: 680px;
|
||||
margin: 3rem auto 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: 0.4rem;
|
||||
background: transparent;
|
||||
cursor: zoom-in;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 3px solid var(--tenant-primary);
|
||||
outline-offset: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__map {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 680px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
|
@ -188,7 +204,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
&__map {
|
||||
&__map-button {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ describe('ProductTicketSelectorComponent', () => {
|
|||
};
|
||||
catalogService.withoutLoading.mockReturnValue(catalogService);
|
||||
cartService.withoutLoading.mockReturnValue(cartService);
|
||||
const modalService = {
|
||||
openConfirmDelete: vi.fn().mockReturnValue(of(true)),
|
||||
openImage: vi.fn(),
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductTicketSelectorComponent],
|
||||
|
|
@ -112,7 +116,7 @@ describe('ProductTicketSelectorComponent', () => {
|
|||
{ provide: CartService, useValue: cartService },
|
||||
{
|
||||
provide: ModalService,
|
||||
useValue: { openConfirmDelete: vi.fn().mockReturnValue(of(true)) },
|
||||
useValue: modalService,
|
||||
},
|
||||
{ provide: ToastService, useValue: { danger: toastDanger } },
|
||||
],
|
||||
|
|
@ -125,7 +129,7 @@ describe('ProductTicketSelectorComponent', () => {
|
|||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
return { fixture, getVariantOptions, cartService, toastDanger };
|
||||
return { fixture, getVariantOptions, cartService, toastDanger, modalService };
|
||||
}
|
||||
|
||||
it('loads one map, recalculates the cascade locally and reserves only after the last select', async () => {
|
||||
|
|
@ -226,6 +230,25 @@ describe('ProductTicketSelectorComponent', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('opens the zoomable image modal when the seating map is clicked', async () => {
|
||||
const { fixture, modalService } = await createComponent({
|
||||
maps: [mapResponse([variant(401, 'general', 'A', '1', '1')])],
|
||||
});
|
||||
fixture.componentRef.setInput('imageUrl', '/images/plano.png');
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.nativeElement.querySelector(
|
||||
'button[aria-label="Ampliar plano de ubicaciones"]',
|
||||
) as HTMLButtonElement;
|
||||
button.click();
|
||||
|
||||
expect(modalService.openImage).toHaveBeenCalledWith({
|
||||
title: 'Entrada',
|
||||
src: '/images/plano.png',
|
||||
alt: 'Plano de ubicaciones de Entrada',
|
||||
});
|
||||
});
|
||||
|
||||
it('clears only the seat after a stock conflict when the row still has alternatives', async () => {
|
||||
const failed = variant(401, 'general', 'A', '1', '1');
|
||||
const alternative = variant(402, 'general', 'A', '1', '2');
|
||||
|
|
|
|||
|
|
@ -154,6 +154,14 @@ export class ProductTicketSelectorComponent {
|
|||
if (this.hasSelection() && variantIds.length > 0) this.buy.emit(variantIds);
|
||||
}
|
||||
|
||||
protected openImage(src: string): void {
|
||||
this.modalService.openImage({
|
||||
title: this.title(),
|
||||
src,
|
||||
alt: `Plano de ubicaciones de ${this.title()}`,
|
||||
});
|
||||
}
|
||||
|
||||
protected addRow(): void {
|
||||
if (!this.canAddRow()) return;
|
||||
const row = this.createRow(this.nextRowId++);
|
||||
|
|
|
|||
Loading…
Reference in New Issue