import { ChangeDetectionStrategy, Component, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
MagaryButton,
MagaryCarousel,
MagaryDialog,
MagaryInput,
MagarySelect,
MagaryTable,
MagaryTag,
MagaryToast,
MessageService,
} from 'ng-magary';
import { Product, CartLine, Category } from './ecommerce.models';
@Component({
selector: 'app-ecommerce-page',
standalone: true,
imports: [
CommonModule,
FormsModule,
MagaryCarousel,
MagarySelect,
MagaryInput,
MagaryTable,
MagaryDialog,
MagaryButton,
MagaryTag,
MagaryToast,
],
providers: [MessageService],
templateUrl: './ecommerce.page.html',
styleUrls: ['./ecommerce.page.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EcommercePage {
constructor(private messageService: MessageService) {}
// Search and filters
readonly search = signal('');
readonly selectedCategory = signal<string | null>(null);
readonly selectedSort = signal('name');
// Product data
readonly allProducts = signal<Product[]>([
{
id: '1',
name: 'Wireless Headphones',
category: 'electronics',
price: 99.99,
stock: 'INSTOCK',
image: 'assets/products/headphones.jpg',
description: 'Premium noise-cancelling wireless headphones with 30-hour battery life.',
rating: 4.5,
},
{
id: '2',
name: 'Smart Watch',
category: 'electronics',
price: 299.99,
stock: 'LOWSTOCK',
image: 'assets/products/smartwatch.jpg',
description: 'Advanced fitness tracking with heart rate monitor and GPS.',
rating: 4.8,
},
{
id: '3',
name: 'Laptop Backpack',
category: 'accessories',
price: 49.99,
stock: 'INSTOCK',
image: 'assets/products/backpack.jpg',
description: 'Durable laptop backpack with USB charging port and water-resistant material.',
rating: 4.2,
},
{
id: '4',
name: 'Mechanical Keyboard',
category: 'electronics',
price: 129.99,
stock: 'OUTOFSTOCK',
image: 'assets/products/keyboard.jpg',
description: 'RGB mechanical gaming keyboard with hot-swappable switches.',
rating: 4.6,
},
{
id: '5',
name: 'Desk Lamp',
category: 'home',
price: 39.99,
stock: 'INSTOCK',
image: 'assets/products/lamp.jpg',
description: 'LED desk lamp with adjustable brightness and color temperature.',
rating: 4.3,
},
]);
// Featured products for carousel
readonly featuredProducts = computed(() =>
this.allProducts().filter(p => p.rating && p.rating >= 4.5)
);
// Filtered products based on search and category
readonly filteredProducts = computed(() => {
let products = this.allProducts();
const searchTerm = this.search().toLowerCase();
if (searchTerm) {
products = products.filter(p =>
p.name.toLowerCase().includes(searchTerm) ||
p.description?.toLowerCase().includes(searchTerm)
);
}
const category = this.selectedCategory();
if (category) {
products = products.filter(p => p.category === category);
}
// Apply sorting
const sort = this.selectedSort();
products = [...products].sort((a, b) => {
if (sort === 'name') return a.name.localeCompare(b.name);
if (sort === 'price-asc') return a.price - b.price;
if (sort === 'price-desc') return b.price - a.price;
if (sort === 'rating') return (b.rating || 0) - (a.rating || 0);
return 0;
});
return products;
});
// Shopping cart
readonly cartItems = signal<CartLine[]>([]);
readonly cartTotal = computed(() =>
this.cartItems().reduce((sum, item) => sum + (item.price * item.quantity), 0)
);
// Dialog state
readonly productDialogVisible = signal(false);
readonly selectedProduct = signal<Product | null>(null);
readonly cartDialogVisible = signal(false);
// Filter options
readonly categories: Category[] = [
{ label: 'All Categories', value: '' },
{ label: 'Electronics', value: 'electronics' },
{ label: 'Accessories', value: 'accessories' },
{ label: 'Home', value: 'home' },
];
readonly sortOptions = [
{ label: 'Name', value: 'name' },
{ label: 'Price: Low to High', value: 'price-asc' },
{ label: 'Price: High to Low', value: 'price-desc' },
{ label: 'Rating', value: 'rating' },
];
showProductDetail(product: Product): void {
this.selectedProduct.set(product);
this.productDialogVisible.set(true);
}
addToCart(product: Product, quantity: number = 1): void {
const cart = this.cartItems();
const existingItem = cart.find(item => item.productId === product.id);
if (existingItem) {
existingItem.quantity += quantity;
this.cartItems.set([...cart]);
} else {
this.cartItems.set([
...cart,
{
productId: product.id,
name: product.name,
quantity,
price: product.price,
image: product.image,
},
]);
}
this.messageService.add({
severity: 'success',
summary: 'Added to Cart',
detail: `${product.name} has been added to your cart.`,
});
this.productDialogVisible.set(false);
}
updateCartQuantity(cartLine: CartLine, quantity: number): void {
if (quantity <= 0) {
this.removeFromCart(cartLine);
return;
}
const cart = this.cartItems();
const item = cart.find(i => i.productId === cartLine.productId);
if (item) {
item.quantity = quantity;
this.cartItems.set([...cart]);
}
}
removeFromCart(cartLine: CartLine): void {
this.cartItems.set(
this.cartItems().filter(item => item.productId !== cartLine.productId)
);
this.messageService.add({
severity: 'info',
summary: 'Removed from Cart',
detail: `${cartLine.name} has been removed from your cart.`,
});
}
getStockSeverity(stock: Product['stock']): string {
switch (stock) {
case 'INSTOCK': return 'success';
case 'LOWSTOCK': return 'warning';
case 'OUTOFSTOCK': return 'danger';
}
}
getStockLabel(stock: Product['stock']): string {
switch (stock) {
case 'INSTOCK': return 'In Stock';
case 'LOWSTOCK': return 'Low Stock';
case 'OUTOFSTOCK': return 'Out of Stock';
}
}
}