import { ChangeDetectionStrategy, Component, computed, inject, input, output, signal } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { Subject, EMPTY } from 'rxjs'; import { catchError, debounceTime, groupBy, mergeMap, switchMap, tap } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CartItemAttribute, CartItemComponent } from '../cart-item/cart-item.component'; import { ModalService } from '../../../core/services/modal.service'; import { CartService } from '../../../core/services/cart/cart.service'; import { ToastService } from '../../../core/services/toast.service'; export interface CartItemMock { productVariantId?: number; imageUrl: string | null; product: string; originalPrice: number | null; discountedPrice: number; discountPercentage: number | null; attributes: CartItemAttribute[]; quantity: number; } @Component({ selector: 'app-cart', standalone: true, imports: [CartItemComponent], templateUrl: './cart.component.html', styleUrl: './cart.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) export class CartComponent { private readonly cartService = inject(CartService); private readonly modalService = inject(ModalService); private readonly toastService = inject(ToastService); private readonly quantityUpdates$ = new Subject<{ productVariantId: number; quantity: number }>(); readonly title = input('CARRITO'); readonly showClose = input(false); readonly items = input([]); readonly subtotal = input(0); readonly discount = input(0); readonly total = input(0); readonly backgroundColor = input('#ffffff'); readonly closed = output(); protected readonly quantityOverrides = signal>({}); constructor() { this.quantityUpdates$.pipe( groupBy(update => update.productVariantId), mergeMap(group$ => group$.pipe( debounceTime(1000), switchMap(update => this.cartService.updateItemQuantity(update.productVariantId, update.quantity).pipe( tap({ next: (res) => { const msg = res.message || 'Cantidad de producto actualizada.'; this.toastService.success(msg); this.clearOverride(update.productVariantId); }, error: (err: HttpErrorResponse) => { console.error('Error updating cart quantity', err); const msg = err.error?.message || 'Error al actualizar la cantidad del producto.'; this.toastService.danger(msg); this.clearOverride(update.productVariantId); } }), catchError(() => EMPTY) )) )), takeUntilDestroyed() ).subscribe(); } protected getItemQuantity(item: CartItemMock): number { if (item.productVariantId !== undefined && this.quantityOverrides()[item.productVariantId] !== undefined) { return this.quantityOverrides()[item.productVariantId]; } return item.quantity; } private clearOverride(productVariantId: number): void { this.quantityOverrides.update((overrides) => { const copy = { ...overrides }; delete copy[productVariantId]; return copy; }); } protected onItemQuantityChange(index: number, newQuantity: number): void { const mockItem = this.items()[index]; const productVariantId = mockItem?.productVariantId; if (productVariantId) { this.quantityOverrides.update((overrides) => ({ ...overrides, [productVariantId]: newQuantity })); this.quantityUpdates$.next({ productVariantId, quantity: newQuantity }); } else { const item = this.cartService.cart()?.items[index]; if (item) { this.quantityOverrides.update((overrides) => ({ ...overrides, [item.product_variant_id]: newQuantity })); this.quantityUpdates$.next({ productVariantId: item.product_variant_id, quantity: newQuantity }); } } } protected onItemRemove(index: number): void { const target = this.resolveRemoveTarget(index); if (!target) { return; } this.modalService.openConfirmDelete({ title: 'Eliminar producto', content: `Se eliminara "${target.productName}" del carrito. Esta accion no se puede deshacer.`, confirmLabel: 'Eliminar', cancelLabel: 'Cancelar' }).subscribe((confirmed) => { if (confirmed) { this.removeItem(target.productVariantId); } }); } protected readonly formattedSubtotal = computed(() => this.formatCurrency(this.subtotal())); protected readonly formattedDiscount = computed(() => this.formatCurrency(this.discount())); protected readonly formattedTotal = computed(() => this.formatCurrency(this.total())); private formatCurrency(value: number): string { const rounded = Math.round(value); const parts = rounded.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '.'); return `$ ${parts.join(',')}`; } private resolveRemoveTarget( index: number ): { productVariantId: number; productName: string } | null { const mockItem = this.items()[index]; const productVariantId = mockItem?.productVariantId; if (productVariantId) { return { productVariantId, productName: mockItem.product }; } const item = this.cartService.cart()?.items[index]; if (!item) { return null; } return { productVariantId: item.product_variant_id, productName: item.product?.nombre ?? 'este producto' }; } private removeItem(productVariantId: number): void { this.cartService.removeItem(productVariantId).subscribe({ next: (res) => { const msg = res.message || 'Producto eliminado del carrito.'; this.toastService.info(msg); }, error: (err: HttpErrorResponse) => { console.error('Error removing item from cart', err); const msg = err.error?.message || 'Error al eliminar el producto del carrito.'; this.toastService.danger(msg); } }); } }