diff --git a/angular.json b/angular.json index c445018..7b19bb0 100644 --- a/angular.json +++ b/angular.json @@ -2,7 +2,8 @@ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "cli": { - "packageManager": "npm" + "packageManager": "npm", + "analytics": false }, "newProjectRoot": "projects", "projects": { diff --git a/public/images/pantalon-scuba.png b/public/images/pantalon-scuba.png new file mode 100644 index 0000000..61ffe1e Binary files /dev/null and b/public/images/pantalon-scuba.png differ diff --git a/public/images/zapatillas-running.png b/public/images/zapatillas-running.png new file mode 100644 index 0000000..fbe1916 Binary files /dev/null and b/public/images/zapatillas-running.png differ diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html index fa8f3ab..7d1373d 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html @@ -256,4 +256,31 @@ + +
+
+ Reusable Component +

Tarjetas de Producto (ProductCard)

+ +

+ Las tarjetas se adaptan al tamaño del contenedor padre. A continuación se presentan dentro de una grilla de Bootstrap. +

+ +
+ @for (product of testProducts; track product.title) { +
+ +
+ } +
+
+
+ diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts index b212cf6..48e0ef3 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts @@ -1,11 +1,12 @@ import { Component, inject } from '@angular/core'; import { ButtonComponent } from '../../../../shared/components/button/button.component'; import { InputComponent } from '../../../../shared/components/input/input.component'; +import { ProductCardComponent } from '../../../../shared/components/product-card/product-card.component'; import { TenantService } from '../../../../core/services/tenant.service'; @Component({ selector: 'app-reutilizables-test-page', - imports: [ButtonComponent, InputComponent], + imports: [ButtonComponent, InputComponent, ProductCardComponent], templateUrl: './reutilizables-test-page.component.html', styleUrl: './reutilizables-test-page.component.scss' }) @@ -28,6 +29,35 @@ export class ReutilizablesTestPageComponent { protected clearTrigger = 0; protected editableDisabled = true; + protected readonly testProducts = [ + { + title: 'Pantalón recto de scuba negro', + originalPrice: 95000, + discount: 20, + transferPrice: 74800, + imageUrl: '/images/pantalon-scuba.png' + }, + { + title: 'Zapatillas de running azul', + originalPrice: 120000, + discount: 15, + transferPrice: 98000, + imageUrl: '/images/zapatillas-running.png' + }, + { + title: 'Remera básica blanca (Sin Descuento)', + originalPrice: 32000, + discount: null, + transferPrice: 30000, + imageUrl: null + } + ]; + + protected onProductBuy(productTitle: string): void { + console.log(`Comprando: ${productTitle}`); + alert(`Comprando: ${productTitle}`); + } + protected onFileChange(file: File | null): void { this.fileName = file?.name ?? ''; } @@ -37,3 +67,4 @@ export class ReutilizablesTestPageComponent { this.clearTrigger += 1; } } + diff --git a/src/app/shared/components/product-card/product-card.component.html b/src/app/shared/components/product-card/product-card.component.html new file mode 100644 index 0000000..837e4a3 --- /dev/null +++ b/src/app/shared/components/product-card/product-card.component.html @@ -0,0 +1,50 @@ +
+ +
+ @if (imageUrl()) { + + } @else { +
+ +
+ } + + + @if (discount() && discount()! > 0) { + + -{{ discount() }}% + + } +
+ + +
+ +

{{ title() }}

+ + +
+ + {{ formattedDiscountedPrice() }} + + + @if (discount() && discount()! > 0) { + {{ formattedOriginalPrice() }} + } +
+ + +
+ {{ formattedTransferPrice() }} + con transferencia +
+ + +
+ + {{ buttonText() }} + +
+
+
+ diff --git a/src/app/shared/components/product-card/product-card.component.scss b/src/app/shared/components/product-card/product-card.component.scss new file mode 100644 index 0000000..6219253 --- /dev/null +++ b/src/app/shared/components/product-card/product-card.component.scss @@ -0,0 +1,76 @@ +:host { + display: block; + width: 100%; + height: 100%; +} + +.product-card { + width: 100%; + height: 100%; + transition: transform 0.2s ease, box-shadow 0.2s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.08) !important; + } + + &__image-container { + width: 100%; + aspect-ratio: 1 / 1; + overflow: hidden; + } + + &__discount-badge { + background-color: var(--tenant-primary) !important; + font-size: 13px; + font-weight: 325; + z-index: 10; + } + + &__title { + font-size: 12px; + font-weight: 325; + letter-spacing: 0.5px; + line-height: 1.4; + } + + &__price-discounted { + color: var(--tenant-primary) !important; + font-size: 24px; + font-weight: 425; + } + + + &__price-original { + font-size: 11px; + font-weight: 325; + color: #DDDDDD; + } + + &__price-transfer { + font-size: 12px; + color: #6c757d; + } + + &__price-transfer-value { + font-weight: 400; + } + + &__price-transfer-label { + font-weight: 300; + } + + &__action { + app-button { + display: block; + width: 100%; + + ::ng-deep .btn { + width: 100%; + min-width: 100%; + border-radius: 8px; + } + } + } +} + diff --git a/src/app/shared/components/product-card/product-card.component.ts b/src/app/shared/components/product-card/product-card.component.ts new file mode 100644 index 0000000..33976c4 --- /dev/null +++ b/src/app/shared/components/product-card/product-card.component.ts @@ -0,0 +1,69 @@ +import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; +import { ButtonComponent } from '../button/button.component'; + +@Component({ + selector: 'app-product-card', + standalone: true, + imports: [ButtonComponent], + templateUrl: './product-card.component.html', + styleUrl: './product-card.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProductCardComponent { + // Configurable inputs + readonly imageUrl = input(null); + readonly title = input(''); + readonly originalPrice = input(0); + readonly discount = input(null); + readonly transferPrice = input(null); + readonly buttonText = input('Comprar'); + + // Interactive events + readonly buy = output(); + + // Computed discounted price + readonly discountedPrice = computed(() => { + const original = this.originalPrice(); + const disc = this.discount(); + if (!disc || disc <= 0) { + return original; + } + return original * (1 - disc / 100); + }); + + // Computed transfer price + readonly computedTransferPrice = computed(() => { + const givenTransfer = this.transferPrice(); + if (givenTransfer !== null) { + return givenTransfer; + } + // Fallback if not specified: use the discounted price + return this.discountedPrice(); + }); + + // Formatted string for discounted price: "$ 76.000" + readonly formattedDiscountedPrice = computed(() => { + return this.formatCurrency(this.discountedPrice()); + }); + + // Formatted string for original price (if has discount): "$ 95.000" + readonly formattedOriginalPrice = computed(() => { + return this.formatCurrency(this.originalPrice()); + }); + + // Formatted string for transfer price: "$ 74.800" + readonly formattedTransferPrice = computed(() => { + return this.formatCurrency(this.computedTransferPrice()); + }); + + /** + * Helper method to format currency numbers to Argentine Pesos style "$ XX.XXX". + * This manual implementation is safe from runtime/SSR locale variations. + */ + 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(',')}`; + } +}