Files
shopit-front/src/app/shared/components/cart/cart.component.ts

186 lines
6.1 KiB
TypeScript

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<string>('CARRITO');
readonly showClose = input<boolean>(false);
readonly items = input<CartItemMock[]>([]);
readonly subtotal = input<number>(0);
readonly discount = input<number>(0);
readonly total = input<number>(0);
readonly backgroundColor = input<string>('#ffffff');
readonly closed = output<void>();
protected readonly quantityOverrides = signal<Record<number, number>>({});
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);
}
});
}
}