diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index 5770c72..6df8573 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -16,6 +16,51 @@ export interface PurchaseStatusResponse { status: string | null; } +export interface PurchaseSummaryResponse extends PurchaseStatusResponse { + id: number; + created_at: string | null; + total: string; +} + +export interface PurchaseDetailAttributeResponse { + name: string; + value: string | null; +} + +export interface PurchaseDetailItemResponse { + id: number; + quantity: number; + unit_price: string; + line_total: string; + product: { + id: number; + nombre: string; + slug: string; + imagen: string | null; + } | null; + variant: { + id: number; + attributes: PurchaseDetailAttributeResponse[]; + } | null; +} + +export interface PurchaseDetailResponse extends PurchaseStatusResponse { + id: number; + cart_id: number | null; + tenant_codigo: string; + user_id: number; + created_at: string | null; + payment_method: string | null; + dni: string | null; + telefono: string | null; + nombre_apellido: string | null; + email: string | null; + items_source: 'purchase' | 'cart' | null; + items: PurchaseDetailItemResponse[]; + subtotal: string; + total: string; +} + @Injectable({ providedIn: 'root' }) @@ -62,13 +107,13 @@ export class CheckoutService { }; } - async getPurchases(tenantCode: string, status?: string): Promise<{ data: any[] }> { + async getPurchases(tenantCode: string, status?: string): Promise<{ data: PurchaseSummaryResponse[] }> { let url = `${environment.url}tenants/${tenantCode}/compras`; if (status) { url += `?status=${status}`; } const response = await firstValueFrom( - this.http.get<{ data: any[] }>(url) + this.http.get<{ data: PurchaseSummaryResponse[] }>(url) ); if (!response) { throw new Error('Error al obtener las compras.'); @@ -76,20 +121,18 @@ export class CheckoutService { return response; } - async getPurchase(tenantCode: string, purchaseId: string | number): Promise { + async getPurchase(tenantCode: string, purchaseId: string | number): Promise { const response = await firstValueFrom( - this.http.get<{ data?: PurchaseStatusResponse } | PurchaseStatusResponse>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}`) + this.http.get<{ data?: PurchaseDetailResponse } | PurchaseDetailResponse>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}`) ); - const purchase = this.extractResponseData(response); + const purchase = this.extractResponseData(response); if (!purchase) { throw new Error('Error al obtener la compra.'); } - return { - status: purchase.status ?? null - }; + return purchase; } private extractResponseData(response: T | { data?: T } | null | undefined): T | null { diff --git a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.html b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.html index 5813035..6d8fd24 100644 --- a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.html +++ b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.html @@ -1,49 +1,47 @@
- @if (product.discountPercentage) { - -{{ product.discountPercentage }}% + @if (item.discountPercentage) { + -{{ item.discountPercentage }}% } - - @if (product.imageUrl) { - + + @if (item.imageUrl) { + } @else { }
-
- - -
-

{{ product.name }}

- -
+
+
+

{{ item.title }}

+ +
+ @if (item.originalPrice) { + $ {{ item.originalPrice }} + } + $ {{ item.price }} +
+
+ +
+
+ Cantidad: + {{ item.quantity }} unidad{{ item.quantity === 1 ? '' : 'es' }} +
+ + @for (attribute of item.attributes; track attribute.label + attribute.value) {
- Cantidad: - {{ product.quantity }} unidad + {{ attribute.label }}: + {{ attribute.value }}
- @for (attribute of product.attributes; track attribute.label) { -
- {{ attribute.label }}: - {{ attribute.value }} -
- } -
+ }
- -
-
- @if (product.originalPrice) { - $ {{ product.originalPrice }} - } - $ {{ product.discountedPrice }} + @if (item.transferPrice) { +
+ Precio por transferencia + ${{ item.transferPrice }}
- -
- ${{ product.transferPrice }} con transferencia -
-
- + }
diff --git a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.scss b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.scss index 060a46d..6457985 100644 --- a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.scss +++ b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.scss @@ -84,6 +84,12 @@ font-weight: bold; } +.cart-item-price-label { + color: #a0a0a0; + font-size: 9px; + font-weight: 325; +} + .cart-item-attributes { gap: 0.125rem; } @@ -102,4 +108,6 @@ color: #666666; font-size: 9px; line-height: 1.2; + display: grid; + justify-items: end; } diff --git a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.ts b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.ts index 5277d78..fb2e298 100644 --- a/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.ts +++ b/src/app/features/store/pages/account-page/components/purchase-item/purchase-item.ts @@ -1,5 +1,20 @@ import { Component, Input } from '@angular/core'; +export type PurchaseItemViewModel = { + id: number; + title: string; + imageUrl: string | null; + quantity: number; + attributes: Array<{ + label: string; + value: string | null; + }>; + price: string; + originalPrice?: string | null; + transferPrice?: string | null; + discountPercentage?: string | number | null; +}; + @Component({ selector: 'app-purchase-item', standalone: true, @@ -8,5 +23,5 @@ import { Component, Input } from '@angular/core'; styleUrl: './purchase-item.scss', }) export class PurchaseItem { - @Input() product!: any; + @Input() item!: PurchaseItemViewModel; } diff --git a/src/app/features/store/pages/account-page/components/purchase-list-item/purchase-list-item.ts b/src/app/features/store/pages/account-page/components/purchase-list-item/purchase-list-item.ts index b423d1c..96fa8ef 100644 --- a/src/app/features/store/pages/account-page/components/purchase-list-item/purchase-list-item.ts +++ b/src/app/features/store/pages/account-page/components/purchase-list-item/purchase-list-item.ts @@ -9,6 +9,6 @@ import { RouterLink } from '@angular/router'; styleUrl: './purchase-list-item.scss', }) export class PurchaseListItem { - @Input() purchase!: { id: string; date: string }; + @Input() purchase!: { id: number; date: string }; } diff --git a/src/app/features/store/pages/account-page/components/purchase-list/purchase-list.ts b/src/app/features/store/pages/account-page/components/purchase-list/purchase-list.ts index c702aee..dcda539 100644 --- a/src/app/features/store/pages/account-page/components/purchase-list/purchase-list.ts +++ b/src/app/features/store/pages/account-page/components/purchase-list/purchase-list.ts @@ -1,10 +1,15 @@ import { Component, OnInit, inject, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PurchaseListItem } from '../purchase-list-item/purchase-list-item'; -import { CheckoutService } from '../../../../../../core/services/checkout.service'; +import { CheckoutService, PurchaseSummaryResponse } from '../../../../../../core/services/checkout.service'; import { ToastService } from '../../../../../../core/services/toast.service'; import { TenantService } from '../../../../../../core/services/tenant.service'; +type PurchaseListViewModel = { + id: number; + date: string; +}; + @Component({ selector: 'app-purchase-list', standalone: true, @@ -17,7 +22,7 @@ export class PurchaseList implements OnInit { private readonly toastService = inject(ToastService); private readonly tenantService = inject(TenantService); - purchases = signal<{ id: string; date: string }[]>([]); + purchases = signal([]); isLoading = signal(true); async ngOnInit(): Promise { @@ -25,9 +30,9 @@ export class PurchaseList implements OnInit { const tenantCode = this.tenantService.tenant()?.codigo || ''; const response = await this.checkoutService.getPurchases(tenantCode, 'paid'); - const mappedPurchases = response.data.map((purchase: any) => ({ - id: `#${purchase.id.toString().padStart(5, '0')}`, - date: '08/06/2026' // Fecha hardcodeada temporalmente + const mappedPurchases = response.data.map((purchase: PurchaseSummaryResponse) => ({ + id: purchase.id, + date: this.formatDate(purchase.created_at), })); this.purchases.set(mappedPurchases); } catch (error) { @@ -36,4 +41,22 @@ export class PurchaseList implements OnInit { this.isLoading.set(false); } } + + private formatDate(value: string | null): string { + if (!value) { + return '-'; + } + + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return '-'; + } + + return new Intl.DateTimeFormat('es-AR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }).format(date); + } } diff --git a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.html b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.html index 3e5a77e..102edd5 100644 --- a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.html +++ b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.html @@ -6,22 +6,28 @@

MIS COMPRAS

-
-
- Compra #{{ purchase.id }}. - Fecha de compra: {{ purchase.date }} + @if (isLoading()) { +

Cargando detalle de compra...

+ } @else if (purchase(); as purchase) { +
+
+ Compra {{ purchase.id }}. + Fecha de compra: {{ purchase.date }} +
+
+ Total: + ${{ purchase.total }} +
-
- Total: - ${{ purchase.total }} + +

Productos:

+ +
+ @for (item of purchase.items; track item.id) { + + } @empty { +

No hay productos para mostrar en esta compra.

+ }
-
- -

Productos:

- -
- @for (product of purchase.products; track product.id) { - - } -
+ }
diff --git a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.scss b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.scss index 2ed0010..ed094dd 100644 --- a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.scss +++ b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.scss @@ -42,3 +42,10 @@ font-weight: bold; font-size: 17px; } + +.purchase-loading, +.purchase-empty { + color: #666666; + font-size: 12px; + margin: 0; +} diff --git a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts index bc93012..34dfb89 100644 --- a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts +++ b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts @@ -1,7 +1,18 @@ import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; -import { ActivatedRoute, RouterLink } from '@angular/router'; -import { PurchaseItem } from '../../components/purchase-item/purchase-item'; +import { Component, OnInit, inject, signal } from '@angular/core'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; + +import { CheckoutService, PurchaseDetailResponse } from '../../../../../../core/services/checkout.service'; +import { TenantService } from '../../../../../../core/services/tenant.service'; +import { ToastService } from '../../../../../../core/services/toast.service'; +import { PurchaseItem, PurchaseItemViewModel } from '../../components/purchase-item/purchase-item'; + +type PurchaseDetailViewModel = { + id: number; + date: string; + total: string; + items: PurchaseItemViewModel[]; +}; @Component({ selector: 'app-purchase-detail-page', @@ -10,80 +21,75 @@ import { PurchaseItem } from '../../components/purchase-item/purchase-item'; templateUrl: './purchase-detail-page.html', styleUrl: './purchase-detail-page.scss', }) -export class PurchaseDetailPage { - purchaseId = 'A00013'; +export class PurchaseDetailPage implements OnInit { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly checkoutService = inject(CheckoutService); + private readonly tenantService = inject(TenantService); + private readonly toastService = inject(ToastService); - // Mocked data based on the provided image - purchase = { - id: this.purchaseId, - date: '15/04/2026', - total: '365.480,00', - products: [ - { - id: 1, - name: 'PANTALÓN RECTO DE SCUBA NEGRO', - quantity: 1, - attributes: [ - { label: 'Color', value: 'Beige' }, - { label: 'Talle', value: 'S' } - ], - originalPrice: '95.000', - discountedPrice: '76.000', - transferPrice: '74.800', - discountPercentage: '20', - imageUrl: '' // placeholder - }, - { - id: 2, - name: 'CALZA SIN TIRO DE LYCRA METALIZADA BORDEAU', - quantity: 1, - attributes: [ - { label: 'Color', value: 'Beige' }, - { label: 'Talle', value: 'M' } - ], - originalPrice: '68.500', - discountedPrice: '61.000', - transferPrice: '59.800', - discountPercentage: '10', - imageUrl: '' - }, - { - id: 3, - name: 'CALZA TÉRMICA UNISEX CON PROCESO SENSE Y CINTURA CON MAYOR AGARRE', - quantity: 1, - attributes: [ - { label: 'Color', value: 'Beige' }, - { label: 'Talle', value: 'M' } - ], - originalPrice: null, - discountedPrice: '120.000', - transferPrice: '180.000', - discountPercentage: null, - imageUrl: '' - }, - { - id: 4, - name: 'CALZA SIN TIRO DE LYCRA METALIZADA BORDEAU', - quantity: 1, - attributes: [ - { label: 'Color', value: 'Beige' }, - { label: 'Talle', value: 'M' } - ], - originalPrice: '68.500', - discountedPrice: '61.000', - transferPrice: '59.800', - discountPercentage: '10', - imageUrl: '' - } - ] - }; + readonly isLoading = signal(true); + readonly purchase = signal(null); - constructor(private route: ActivatedRoute) { - this.route.params.subscribe(params => { - if (params['id']) { - this.purchaseId = params['id']; - this.purchase.id = this.purchaseId; - } - }); + async ngOnInit(): Promise { + const purchaseId = this.route.snapshot.paramMap.get('id'); + const tenant = this.tenantService.tenant(); + + if (!purchaseId || !tenant) { + void this.router.navigate(['/mi-cuenta/compras']); + return; + } + + try { + const response = await this.checkoutService.getPurchase(tenant.codigo, purchaseId); + + this.purchase.set({ + id: response.id, + date: this.formatDate(response.created_at), + total: response.total, + items: this.mapItems(response), + }); + } catch (error) { + console.error('Failed to fetch purchase detail:', error); + this.toastService.danger('Hubo un error al cargar el detalle de la compra'); + void this.router.navigate(['/mi-cuenta/compras']); + } finally { + this.isLoading.set(false); + } + } + + private formatDate(value: string | null): string { + if (!value) { + return '-'; + } + + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return '-'; + } + + return new Intl.DateTimeFormat('es-AR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }).format(date); + } + + private mapItems(purchase: PurchaseDetailResponse): PurchaseItemViewModel[] { + return purchase.items.map((item) => ({ + id: item.id, + title: item.product?.nombre ?? 'Producto sin nombre', + imageUrl: item.product?.imagen ?? null, + quantity: item.quantity, + attributes: (item.variant?.attributes ?? []).map((attribute) => ({ + label: attribute.name, + value: attribute.value, + })), + price: item.line_total, + originalPrice: null, + transferPrice: null, + discountPercentage: null, + })); } }