From 1a8693eb8a8dd7d7488f53eedd668e1206af29c5 Mon Sep 17 00:00:00 2001 From: Prabath Samarasinghe Date: Wed, 7 May 2025 14:33:19 +0530 Subject: [PATCH 1/9] vendor side pages --- src/app/app.routes.ts | 15 +- .../product-section.component.html | 3 +- .../product-section.component.ts | 11 + .../components/sidebar/sidebar.component.ts | 6 + .../cart-summary/cart-summary.component.css | 0 .../cart-summary/cart-summary.component.html | 313 ++++++++++++++++++ .../cart-summary.component.spec.ts | 23 ++ .../cart-summary/cart-summary.component.ts | 183 ++++++++++ .../vendor-page/cart/cart.component.css | 0 .../vendor-page/cart/cart.component.html | 145 ++++++++ .../vendor-page/cart/cart.component.spec.ts | 23 ++ .../vendor-page/cart/cart.component.ts | 75 +++++ .../order-summary/order-summary.component.css | 0 .../order-summary.component.html | 180 ++++++++++ .../order-summary.component.spec.ts | 23 ++ .../order-summary/order-summary.component.ts | 132 ++++++++ .../vendor-page/orders/orders.component.css | 0 .../vendor-page/orders/orders.component.html | 230 +++++++++++++ .../orders/orders.component.spec.ts | 23 ++ .../vendor-page/orders/orders.component.ts | 228 +++++++++++++ .../vendor-page/vender/vendor.component.css | 0 .../vendor-page/vender/vendor.component.html | 67 ++++ .../vender/vendor.component.spec.ts | 23 ++ .../vendor-page/vender/vendor.component.ts | 15 + 24 files changed, 1716 insertions(+), 2 deletions(-) create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.css create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.html create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.ts create mode 100644 src/app/components/vendor-page/cart/cart.component.css create mode 100644 src/app/components/vendor-page/cart/cart.component.html create mode 100644 src/app/components/vendor-page/cart/cart.component.spec.ts create mode 100644 src/app/components/vendor-page/cart/cart.component.ts create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.css create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.html create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.spec.ts create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.ts create mode 100644 src/app/components/vendor-page/orders/orders.component.css create mode 100644 src/app/components/vendor-page/orders/orders.component.html create mode 100644 src/app/components/vendor-page/orders/orders.component.spec.ts create mode 100644 src/app/components/vendor-page/orders/orders.component.ts create mode 100644 src/app/components/vendor-page/vender/vendor.component.css create mode 100644 src/app/components/vendor-page/vender/vendor.component.html create mode 100644 src/app/components/vendor-page/vender/vendor.component.spec.ts create mode 100644 src/app/components/vendor-page/vender/vendor.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 7308758..ec50cf8 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -15,6 +15,12 @@ import { authGuard } from './gaurds/auth.guard' import { roleGuard } from './gaurds/role.guard'; import { VendorSignupComponent } from './components/auth-page/vendor-signup/vendor-signup.component'; import { SupplierSignupComponent } from './components/auth-page/supplier-signup/supplier-signup.component'; +import { VendorComponent } from './components/vendor-page/vender/vendor.component'; +import { ProductSectionComponent } from './components/home-page/product-section/product-section.component'; +import { CartComponent } from './components/vendor-page/cart/cart.component'; +import { OrderSummaryComponent } from './components/vendor-page/order-summary/order-summary.component'; +import { CartSummaryComponent } from './components/vendor-page/cart-summary/cart-summary.component'; +import { OrdersComponent } from './components/vendor-page/orders/orders.component'; export const routes: Routes = [ { @@ -35,7 +41,7 @@ export const routes: Routes = [ { path: 'dashboard', component: DashboardLayoutComponent, - canActivate: [authGuard], + // canActivate: [authGuard], children: [ { path: '', component: ProfileComponent, pathMatch: 'full' }, // /dashboard { path: 'profile', component: ProfileComponent, pathMatch: 'full' }, @@ -51,6 +57,13 @@ export const routes: Routes = [ // { path: 'deliveries', component: DeliveriesComponent }, // /dashboard/deliveries // { path: 'vendors', component: VendorsComponent }, { path: 'supplier', component: SupplierDashboard, pathMatch: 'full' }, + { path: 'vendor', component: VendorComponent, pathMatch: 'full' }, + { path: 'vendor/products', component: ProductSectionComponent, pathMatch: 'full' }, + { path: 'vendor/cart', component: CartComponent, pathMatch: 'full' }, + { path: 'vendor/orders/order-details/:id', component: OrderSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/cart/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/orders', component: OrdersComponent, pathMatch: 'full' }, ] }, { path: '**', redirectTo: 'home' }, // Handle 404/unknown routes diff --git a/src/app/components/home-page/product-section/product-section.component.html b/src/app/components/home-page/product-section/product-section.component.html index b68850d..59291f6 100644 --- a/src/app/components/home-page/product-section/product-section.component.html +++ b/src/app/components/home-page/product-section/product-section.component.html @@ -102,6 +102,7 @@

@@ -110,7 +111,7 @@

+
+ First name is required +
+
+
+ + +
+ Last name is required +
+
+

+ +
+ + +
+ Address is required +
+
+ +
+
+ + +
+ City is required +
+
+
+ + +
+ Country is required +
+
+
+ + +
+ Postal code is required +
+
+
+ +
+ + +
+ Valid phone number is required +
+
+ + + + + +
+
+

+ Order Summary +

+ +
+
+

+ Subtotal ({{ getTotalItems() }} items): + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ shippingCost.toFixed(2) }} +

+

+ Tax ({{ taxRate }}%): + ${{ calculateTax().toFixed(2) }} +

+
+ + +
+
+ + +
+
+ Discount applied: -${{ discountAmount.toFixed(2) }} +
+
+ + +
+ Total: + ${{ getTotal() }} +
+ + + + + +
+
+
+ + + + +
+
+ + + +
+

Your cart is currently empty.

+ Browse Products +
+
+ + + + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+ \ No newline at end of file diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts new file mode 100644 index 0000000..3f495bb --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CartSummaryComponent } from './cart-summary.component'; + +describe('CartSummaryComponent', () => { + let component: CartSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CartSummaryComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CartSummaryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts new file mode 100644 index 0000000..ae58ad7 --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts @@ -0,0 +1,183 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; + +interface CartItem { + id: number; + name: string; + description: string; + price: number; + quantity: number; + image: string; +} + +@Component({ + selector: 'app-cart-summary', + templateUrl: './cart-summary.component.html', + styleUrls: ['./cart-summary.component.css'], + standalone: true, + imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterLink] +}) +export class CartSummaryComponent implements OnInit { + cartItems: CartItem[] = []; + shippingForm: FormGroup; + shippingCost: number = 5.99; + taxRate: number = 7; + discountCode: string = ''; + discountApplied: boolean = false; + discountAmount: number = 0; + user = { name: 'Guest User' }; // Placeholder, would normally come from a user service + + constructor( + private fb: FormBuilder, + private router: Router + ) { + this.shippingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + country: ['', Validators.required], + postalCode: ['', Validators.required], + phone: ['', [Validators.required, Validators.pattern('^[0-9]{10,15}$')]] + }); + } + + ngOnInit(): void { + // Load cart items from local storage or service + this.loadCartItems(); + // Load user data if available + this.loadUserData(); + } + + loadCartItems(): void { + // This would typically come from a service or localStorage + // For demo purposes, we'll initialize with some sample data + this.cartItems = [ + { + id: 1, + name: 'Product 1', + description: 'High-quality product with premium features', + price: 49.99, + quantity: 2, + image: '/assets/images/product1.jpg' + }, + { + id: 2, + name: 'Product 2', + description: 'Versatile and durable for everyday use', + price: 29.99, + quantity: 1, + image: '/assets/images/product2.jpg' + } + ]; + } + + loadUserData(): void { + // In a real app, this would load data from a user service + const savedUser = localStorage.getItem('user'); + if (savedUser) { + this.user = JSON.parse(savedUser); + + // Pre-fill the form with user data if available + const userData = JSON.parse(savedUser); + if (userData.address) { + this.shippingForm.patchValue({ + firstName: userData.firstName || '', + lastName: userData.lastName || '', + address: userData.address.street || '', + city: userData.address.city || '', + country: userData.address.country || '', + postalCode: userData.address.postalCode || '', + phone: userData.phone || '' + }); + } + } + } + + increaseQuantity(index: number): void { + this.cartItems[index].quantity += 1; + this.updateCart(); + } + + decreaseQuantity(index: number): void { + if (this.cartItems[index].quantity > 1) { + this.cartItems[index].quantity -= 1; + this.updateCart(); + } + } + + removeItem(index: number): void { + this.cartItems.splice(index, 1); + this.updateCart(); + } + + updateCart(): void { + // Update cart in local storage or service + localStorage.setItem('cartItems', JSON.stringify(this.cartItems)); + } + + getSubtotal(): string { + const subtotal = this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0); + return subtotal.toFixed(2); + } + + calculateTax(): number { + return (parseFloat(this.getSubtotal()) * this.taxRate) / 100; + } + + getTotalItems(): number { + return this.cartItems.reduce((acc, item) => acc + item.quantity, 0); + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const tax = this.calculateTax(); + const total = subtotal + this.shippingCost + tax - this.discountAmount; + return total.toFixed(2); + } + + applyDiscount(): void { + // Simple discount logic - could be expanded to check against valid codes from backend + const validDiscountCodes = { + 'SAVE10': 10, + 'WELCOME20': 20, + 'LOYAL15': 15 + }; + + type DiscountCodeKey = keyof typeof validDiscountCodes; + + if (this.discountCode && (this.discountCode as DiscountCodeKey) in validDiscountCodes) { + const discountPercent = validDiscountCodes[this.discountCode as DiscountCodeKey]; + this.discountAmount = (parseFloat(this.getSubtotal()) * discountPercent) / 100; + this.discountApplied = true; + } else { + this.discountAmount = 0; + this.discountApplied = false; + // You could add an error message here + } + } + + canProceed(): boolean { + return this.cartItems.length > 0 && this.shippingForm.valid; + } + + proceedToPayment(): void { + if (this.canProceed()) { + // Save shipping information + const shippingData = this.shippingForm.value; + localStorage.setItem('shippingDetails', JSON.stringify(shippingData)); + + // Navigate to payment page + this.router.navigate(['/payment']); + } else { + // Mark all form fields as touched to trigger validation messages + Object.keys(this.shippingForm.controls).forEach(key => { + const control = this.shippingForm.get(key); + control?.markAsTouched(); + }); + } + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.css b/src/app/components/vendor-page/cart/cart.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/cart/cart.component.html b/src/app/components/vendor-page/cart/cart.component.html new file mode 100644 index 0000000..fe6ea22 --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -0,0 +1,145 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Your Shopping Cart +
+

+ Carefully selected for you, +

+

+ {{ user.name }} +

+

Review your selections

+
+
+
+ + +
+
+

+ Items in Your Cart +

+ +
+
+ {{ item.name }} +

{{ item.name }}

+

{{ item.subtitle }}

+

+ Category: {{ item.category }} +

+

+ ${{ item.price.toFixed(2) }} +

+
+
+ Qty: + +
+ +
+
+
+ + +
+
+
+ Subtotal: + ${{ getSubtotal() }} +
+
+ Shipping: + ${{ getShipping() }} +
+
+ Total: + ${{ getTotal() }} +
+
+ +
+ + + +
+
+ + + +
+

Your cart is currently empty.

+ + Browse Products + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.spec.ts b/src/app/components/vendor-page/cart/cart.component.spec.ts new file mode 100644 index 0000000..03dcc4a --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CartComponent } from './cart.component'; + +describe('CartComponent', () => { + let component: CartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CartComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/cart/cart.component.ts b/src/app/components/vendor-page/cart/cart.component.ts new file mode 100644 index 0000000..c3f7981 --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { RouterLink } from '@angular/router'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-cart', + standalone: true, + imports: [CommonModule, RouterLink, FormsModule], + templateUrl: './cart.component.html', +}) +export class CartComponent implements OnInit { + user = { + name: "Alice Fernando", + role: "Tea Supplier", + }; + + cartItems: any[] = []; + shippingCost: number = 5.99; + + constructor(private router: Router) {} + + ngOnInit(): void { + this.loadCartItems(); + } + + loadCartItems(): void { + this.cartItems = JSON.parse(localStorage.getItem("cart") || '[]'); + } + + removeFromCart(index: number): void { + this.cartItems.splice(index, 1); + this.saveCartToStorage(); + } + + updateQuantity(index: number, quantity: number): void { + if (quantity > 0) { + this.cartItems[index].quantity = quantity; + this.saveCartToStorage(); + } + } + + saveCartToStorage(): void { + localStorage.setItem("cart", JSON.stringify(this.cartItems)); + } + + getSubtotal(): string { + let subtotal = 0; + this.cartItems.forEach((item: any) => { + subtotal += item.price * (item.quantity || 1); + }); + return subtotal.toFixed(2); + } + + getShipping(): string { + return this.cartItems.length > 0 ? this.shippingCost.toFixed(2) : '0.00'; + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const shipping = parseFloat(this.getShipping()); + return (subtotal + shipping).toFixed(2); + } + + proceedToCheckout(): void { + if (this.cartItems.length > 0) { + this.router.navigate(['order-summery']); + } + } + + continueShopping(): void { + this.router.navigate(['/products']); + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.css b/src/app/components/vendor-page/order-summary/order-summary.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.html b/src/app/components/vendor-page/order-summary/order-summary.component.html new file mode 100644 index 0000000..0439921 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.html @@ -0,0 +1,180 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Order Summary +
+

Thanks for your order,

+

{{ user.name }}

+

Here's a summary of your purchase

+
+
+
+ + +
+
+ +
+
+

Order Status

+ {{ order.status }} +
+ +
+ +
+
+
+ + +
+
+
+ + {{ i + 1 }} +
+ {{ step }} +
+
+
+
+ + +
+ +
+

Your Items

+ +
+
+ {{ item.name }} +
+
+

{{ item.name }}

+

+ ${{ (item.price * item.quantity).toFixed(2) }} +

+
+

{{ item.description }}

+
+ Qty: {{ item.quantity }} + Unit Price: ${{ item.price.toFixed(2) }} +
+
+ {{ item.status }} +
+
+
+
+
+ + +
+
+

Order Details

+ +
+

+ Order ID: + {{ order.id }} +

+

+ Date: + {{ getFormattedDate() }} +

+

+ Estimated Delivery: + {{ getEstimatedDelivery() }} +

+
+ +
+

Shipping Address

+

{{ order.shippingAddress }}

+
+ +
+ +
+

+ Subtotal: + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ order.shippingCost.toFixed(2) }} +

+

+ Tax: + ${{ order.tax.toFixed(2) }} +

+
+ +
+ Total: + ${{ getTotal() }} +
+ +
+ + +
+
+
+
+ + + +
+
+ + + +
+

You don't have any orders yet.

+ + Start Shopping + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts b/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts new file mode 100644 index 0000000..3fe9901 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OrderSummaryComponent } from './order-summary.component'; + +describe('OrderSummaryComponent', () => { + let component: OrderSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OrderSummaryComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OrderSummaryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.ts b/src/app/components/vendor-page/order-summary/order-summary.component.ts new file mode 100644 index 0000000..3817bb2 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.ts @@ -0,0 +1,132 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-order-summary', + standalone: true, + imports: [CommonModule, RouterLink], + templateUrl: './order-summary.component.html', + styleUrl: './order-summary.component.css', +}) +export class OrderSummaryComponent implements OnInit { + user = { + name: 'Prabath', + email: 'prabath@example.com', + role: 'Customer' + }; + + order = { + id: 'ORD123456', + date: '2025-05-07', + shippingAddress: '123 Main Street, Colombo, Sri Lanka', + status: 'Processing', + shippingCost: 5.99, + tax: 2.50, + estimatedDelivery: '2025-05-14' + }; + + orderItems = [ + { + name: 'Ceylon Cinnamon', + quantity: 2, + price: 10.0, + image: 'path/to/image1.jpg', + description: 'Premium quality Ceylon cinnamon sticks, 100g package', + status: 'Processing' + }, + { + name: 'Black Pepper', + quantity: 1, + price: 5.5, + image: 'path/to/image2.jpg', + description: 'Organic black pepper, 50g package', + status: 'Processing' + }, + ]; + + // Order progress tracking + orderSteps = ['Confirmed', 'Processing', 'Shipped', 'Delivered']; + currentStep = 1; // 0-based index (1 = Processing) + + constructor(private router: Router) {} + + ngOnInit(): void { + // Load order data from localStorage if available + const savedOrder = localStorage.getItem('currentOrder'); + if (savedOrder) { + try { + const orderData = JSON.parse(savedOrder); + if (orderData.items && orderData.items.length > 0) { + this.orderItems = orderData.items; + } + + // Clear cart after successful order + localStorage.removeItem('cart'); + } catch (error) { + console.error('Error parsing order data:', error); + } + } + } + + getSubtotal(): string { + return this.orderItems + .reduce((acc, item) => acc + item.price * item.quantity, 0) + .toFixed(2); + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const shipping = this.order.shippingCost; + const tax = this.order.tax; + + return (subtotal + shipping + tax).toFixed(2); + } + + getFormattedDate(): string { + const date = new Date(this.order.date); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + getEstimatedDelivery(): string { + const date = new Date(this.order.estimatedDelivery); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + getProgressWidth(): string { + const progress = ((this.currentStep + 1) / this.orderSteps.length) * 100; + return `${progress}%`; + } + + getStatusClass(status: string): string { + const baseClasses = 'px-3 py-1 rounded-full text-xs font-medium'; + + switch(status.toLowerCase()) { + case 'processing': + return `${baseClasses} bg-blue-100 text-blue-800`; + case 'shipped': + return `${baseClasses} bg-purple-100 text-purple-800`; + case 'delivered': + return `${baseClasses} bg-green-100 text-green-800`; + case 'cancelled': + return `${baseClasses} bg-red-100 text-red-800`; + default: + return `${baseClasses} bg-gray-100 text-gray-800`; + } + } + + trackOrder(): void { + // Implementation for order tracking functionality + // This could navigate to a dedicated tracking page + alert(`Tracking order ${this.order.id}...`); + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/orders/orders.component.css b/src/app/components/vendor-page/orders/orders.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/orders/orders.component.html b/src/app/components/vendor-page/orders/orders.component.html new file mode 100644 index 0000000..c386993 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.html @@ -0,0 +1,230 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Order History +
+

Your purchase history,

+

{{ user.name }}

+

Track and manage your previous orders

+
+
+
+ + +
+
+ +
+
+

Your Orders

+ + {{ orders.length }} orders + +
+ +
+ +
+ +
+ + + +
+
+ + + + + + +
+
+ + +
+
+ + + + +
+
+ +
+
+
+

Order #{{ order.id }}

+

{{ formatDate(order.date) }}

+
+ + {{ order.status }} + +
+

Items: {{ order.itemsCount }}

+

Total: ${{ order.total.toFixed(2) }}

+
+ + +
+
+ + + +
+
+
+ + +
+
+ Showing {{ paginationStart }} - {{ paginationEnd }} of {{ orders.length }} orders +
+
+ + +
+ +
+ + +
+
+
+ + + +
+
+ + + +
+

No orders found

+

+ {{ orders.length > 0 ? 'No orders match your current filters.' : 'You haven\'t placed any orders yet.' }} +

+
+ + + Browse Products + +
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/orders/orders.component.spec.ts b/src/app/components/vendor-page/orders/orders.component.spec.ts new file mode 100644 index 0000000..4f12d69 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OrdersComponent } from './orders.component'; + +describe('OrdersComponent', () => { + let component: OrdersComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OrdersComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OrdersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/orders/orders.component.ts b/src/app/components/vendor-page/orders/orders.component.ts new file mode 100644 index 0000000..95a0679 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.ts @@ -0,0 +1,228 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +interface Order { + id: string; + date: Date; + itemsCount: number; + total: number; + status: 'processing' | 'shipped' | 'delivered' | 'cancelled'; + trackingNumber?: string; +} + +interface User { + name: string; + // Add other user properties as needed +} + +@Component({ + selector: 'app-orders', + standalone: true, + imports: [CommonModule, FormsModule, RouterModule], + templateUrl: './orders.component.html', + styleUrl: './orders.component.css' +}) +export class OrdersComponent implements OnInit { + // User data + user: User = { + name: 'John Doe' + }; + + // Orders data + orders: Order[] = []; + filteredOrders: Order[] = []; + + // Pagination + currentPage: number = 1; + itemsPerPage: number = 5; + totalPages: number = 1; + pageNumbers: number[] = []; + paginationStart: number = 0; + paginationEnd: number = 0; + + // Filters + searchQuery: string = ''; + statusFilter: string = 'all'; + dateFilter: string = 'all'; + + constructor() {} + + ngOnInit(): void { + // Load mock order data + this.loadMockOrders(); + this.filterOrders(); + this.calculatePagination(); + } + + loadMockOrders(): void { + // Mock data for demonstration + this.orders = [ + { + id: '1001', + date: new Date(2025, 3, 15), // April 15, 2025 + itemsCount: 3, + total: 129.99, + status: 'delivered' + }, + { + id: '1002', + date: new Date(2025, 4, 1), // May 1, 2025 + itemsCount: 1, + total: 49.99, + status: 'shipped', + trackingNumber: 'TRACK123456' + }, + { + id: '1003', + date: new Date(2025, 4, 5), // May 5, 2025 + itemsCount: 2, + total: 89.98, + status: 'processing' + }, + { + id: '1004', + date: new Date(2025, 3, 10), // April 10, 2025 + itemsCount: 4, + total: 159.96, + status: 'cancelled' + }, + { + id: '1005', + date: new Date(2025, 2, 20), // March 20, 2025 + itemsCount: 2, + total: 79.98, + status: 'delivered' + }, + { + id: '1006', + date: new Date(2025, 1, 15), // February 15, 2025 + itemsCount: 1, + total: 29.99, + status: 'delivered' + }, + { + id: '1007', + date: new Date(2025, 4, 3), // May 3, 2025 + itemsCount: 3, + total: 109.97, + status: 'shipped', + trackingNumber: 'TRACK789012' + } + ]; + } + + filterOrders(): void { + let filtered = [...this.orders]; + + // Apply search filter + if (this.searchQuery.trim()) { + const query = this.searchQuery.toLowerCase().trim(); + filtered = filtered.filter(order => + order.id.toLowerCase().includes(query) + ); + } + + // Apply status filter + if (this.statusFilter !== 'all') { + filtered = filtered.filter(order => + order.status === this.statusFilter + ); + } + + // Apply date filter + if (this.dateFilter !== 'all') { + const today = new Date(); + const daysAgo = parseInt(this.dateFilter); + const cutoffDate = new Date(); + cutoffDate.setDate(today.getDate() - daysAgo); + + filtered = filtered.filter(order => + order.date >= cutoffDate + ); + } + + this.filteredOrders = filtered; + this.currentPage = 1; // Reset to first page when filters change + this.calculatePagination(); + } + + calculatePagination(): void { + this.totalPages = Math.ceil(this.filteredOrders.length / this.itemsPerPage); + + // Generate page numbers array + this.pageNumbers = []; + for (let i = 1; i <= this.totalPages; i++) { + this.pageNumbers.push(i); + } + + // Calculate items showing + const startIndex = (this.currentPage - 1) * this.itemsPerPage; + this.paginationStart = startIndex + 1; + this.paginationEnd = Math.min(startIndex + this.itemsPerPage, this.filteredOrders.length); + } + + prevPage(): void { + if (this.currentPage > 1) { + this.currentPage--; + this.calculatePagination(); + } + } + + nextPage(): void { + if (this.currentPage < this.totalPages) { + this.currentPage++; + this.calculatePagination(); + } + } + + goToPage(page: number): void { + this.currentPage = page; + this.calculatePagination(); + } + + resetFilters(): void { + this.searchQuery = ''; + this.statusFilter = 'all'; + this.dateFilter = 'all'; + this.filterOrders(); + } + + canTrack(order: Order): boolean { + return (order.status === 'shipped' || order.status === 'processing') && + !!order.trackingNumber; + } + + trackOrder(orderId: string): void { + // This would typically navigate to a tracking page or open a modal + const order = this.orders.find(o => o.id === orderId); + if (order?.trackingNumber) { + alert(`Tracking order #${orderId} with tracking number: ${order.trackingNumber}`); + // In a real application, you would implement proper tracking functionality + } + } + + formatDate(date: Date): string { + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + + getStatusClass(status: string): string { + switch (status) { + case 'processing': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800'; + case 'shipped': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800'; + case 'delivered': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800'; + case 'cancelled': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800'; + default: + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800'; + } + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/vender/vendor.component.css b/src/app/components/vendor-page/vender/vendor.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/vender/vendor.component.html b/src/app/components/vendor-page/vender/vendor.component.html new file mode 100644 index 0000000..6200e2d --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.html @@ -0,0 +1,67 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Vendor Ordering Flow +
+

Welcome back,

+

{{ user.name }}

+

{{ user.role }}

+
+
+
+ + + + + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
diff --git a/src/app/components/vendor-page/vender/vendor.component.spec.ts b/src/app/components/vendor-page/vender/vendor.component.spec.ts new file mode 100644 index 0000000..c0d25f1 --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VendorComponent } from './vendor.component'; + +describe('VendorComponent', () => { + let component: VendorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VendorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VendorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/vender/vendor.component.ts b/src/app/components/vendor-page/vender/vendor.component.ts new file mode 100644 index 0000000..adab49a --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'app-vendor', + templateUrl: './vendor.component.html', + styleUrls: ['./vendor.component.css'], + imports: [RouterLink], +}) +export class VendorComponent { + user = { + name: "Alice Fernando", + role: "Tea Supplier", + }; +} From 11cf09ba60dba264e24ec91ebb41e0d502fd5794 Mon Sep 17 00:00:00 2001 From: Prabath Samarasinghe Date: Thu, 8 May 2025 11:44:06 +0530 Subject: [PATCH 2/9] Order processing method --- src/app/app.routes.ts | 3 +- .../cart-summary/cart-summary.component.html | 568 ++++++++++++++---- .../cart-summary/cart-summary.component.ts | 166 ++++- .../vendor-page/cart/cart.component.html | 13 +- .../product-section.component.css | 25 + .../product-section.component.html | 167 +++++ .../product-section.component.spec.ts | 23 + .../product-section.component.ts | 171 ++++++ 8 files changed, 983 insertions(+), 153 deletions(-) create mode 100644 src/app/components/vendor-page/product-section/product-section.component.css create mode 100644 src/app/components/vendor-page/product-section/product-section.component.html create mode 100644 src/app/components/vendor-page/product-section/product-section.component.spec.ts create mode 100644 src/app/components/vendor-page/product-section/product-section.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index ec50cf8..2172773 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -16,7 +16,7 @@ import { roleGuard } from './gaurds/role.guard'; import { VendorSignupComponent } from './components/auth-page/vendor-signup/vendor-signup.component'; import { SupplierSignupComponent } from './components/auth-page/supplier-signup/supplier-signup.component'; import { VendorComponent } from './components/vendor-page/vender/vendor.component'; -import { ProductSectionComponent } from './components/home-page/product-section/product-section.component'; +import { ProductSectionComponent } from './components/vendor-page/product-section/product-section.component'; import { CartComponent } from './components/vendor-page/cart/cart.component'; import { OrderSummaryComponent } from './components/vendor-page/order-summary/order-summary.component'; import { CartSummaryComponent } from './components/vendor-page/cart-summary/cart-summary.component'; @@ -62,7 +62,6 @@ export const routes: Routes = [ { path: 'vendor/cart', component: CartComponent, pathMatch: 'full' }, { path: 'vendor/orders/order-details/:id', component: OrderSummaryComponent, pathMatch: 'full' }, { path: 'vendor/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, - { path: 'vendor/cart/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, { path: 'vendor/orders', component: OrdersComponent, pathMatch: 'full' }, ] }, diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.html b/src/app/components/vendor-page/cart-summary/cart-summary.component.html index e38913d..e7661aa 100644 --- a/src/app/components/vendor-page/cart-summary/cart-summary.component.html +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.html @@ -1,107 +1,126 @@
- -
-
-
- - - -
+ +
+
+
+ + +
- - -
-
-
- Cart Summary -
-

Review your order,

-

{{ user.name }}

-

Confirm your selections before purchase

+
+ + +
+
+
+ Cart Summary + Payment + Order Confirmation
+

+ Review your order, + Almost done, + Thank you for your order, +

+

{{ user.name }}

+

+ Confirm your selections before purchase + Secure checkout to complete your purchase + Your order has been successfully placed +

- - -
-
- -
-
- -
-
-
- - -
-
-
- -
- Cart +
+ + +
+
+ +
+
+ +
+
+
+ + +
+
+
+
-
-
- -
- Summary + Cart +
+
+
+
-
-
- 3 -
- Payment + Summary +
+
+
+ 3
-
-
- 4 -
- Confirmation + Payment +
+
+
+ 4
+ Confirmation
- +
+ + +
-

Order Items

+

Order Items

+ class="bg-white rounded-lg shadow-sm p-5 border border-slate-200 mb-4 flex flex-col sm:flex-row"> {{ item.name }}
-

{{ item.name }}

-

+

{{ item.name }}

+

${{ (item.price * item.quantity).toFixed(2) }}

-

{{ item.description }}

+

{{ item.description }}

- Qty: -
+ Qty: +
- {{ item.quantity }} + class="px-3 py-1 text-slate-600 hover:bg-slate-100">- + {{ item.quantity }} + class="px-3 py-1 text-slate-600 hover:bg-slate-100">+
@@ -111,17 +130,17 @@

{{ item.name }}

-
-

Shipping Address

+
+

Shipping Address

- +
@@ -129,11 +148,11 @@

Shipping Address

- +
@@ -143,11 +162,11 @@

Shipping Address

- +
@@ -157,11 +176,11 @@

Shipping Address

- +
@@ -169,10 +188,10 @@

Shipping Address

- +
@@ -200,11 +219,11 @@

Shipping Address

- +
@@ -214,42 +233,42 @@

Shipping Address

- +
-
-

+
+

Order Summary

-

+

Subtotal ({{ getTotalItems() }} items): ${{ getSubtotal() }}

-

+

Shipping: ${{ shippingCost.toFixed(2) }}

-

+

Tax ({{ taxRate }}%): ${{ calculateTax().toFixed(2) }}

-
+
@@ -260,7 +279,7 @@

+
Total: ${{ getTotal() }}
@@ -269,45 +288,374 @@

Proceed to Payment - +

- +
-
+
-

Your cart is currently empty.

+

Your cart is currently empty.

Browse Products
-
- - -
-
- © 2025 Vendor Ordering System. All rights reserved. + + +
+ +
+ +
+

Payment Method

+ + +
+
+
+ {{ method.name }} + {{ method.name }} +
+
+
+ + +
+
+
+ + +
+ Cardholder name is required +
+
+ +
+ +
+ +
+
+ Visa + Mastercard + Amex +
+
+
+
+ Enter a valid card number +
+
+ +
+
+ + +
+ Enter a valid expiry date +
+
+
+ + +
+ Enter a valid CVV +
+
+
+ +
+ + +
+
+
+ + +
+ PayPal +

After clicking "Complete Order", you will be redirected to PayPal to complete your purchase securely.

+
+

Total: ${{ getTotal() }}

+
+
+ + +
+ +

After clicking "Complete Order", you'll be prompted to confirm payment.

+
+

Total: ${{ getTotal() }}

+
+
+ + +
+
+

Billing Address

+
+ + +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+

+ Order Summary +

+ +
+
+
+
+ {{ item.name }} +
+

{{ item.name }}

+

Qty: {{ item.quantity }}

+
+
+

+ ${{ (item.price * item.quantity).toFixed(2) }} +

+
+
+ +
+

+ Subtotal: + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ shippingCost.toFixed(2) }} +

+

+ Tax: + ${{ calculateTax().toFixed(2) }} +

+
+ Discount: + -${{ discountAmount.toFixed(2) }} +
+
+ +
+ Total: + ${{ getTotal() }} +
+ + + + +
+
+
+
+ + +
+
+
+ + + +
+ +

Order Confirmed!

+

Thank you for your purchase, {{ user.name }}. Your order has been received and is being processed.

+ +
+
+
+

ORDER NUMBER

+

{{ orderNumber }}

+
+
+

DATE

+

{{ orderDate | date:'mediumDate' }}

+
+
+

TOTAL

+

${{ getTotal() }}

+
+
+

PAYMENT METHOD

+

{{ getSelectedPaymentMethod()?.name }}

+
+
+
+ + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved.
-
\ No newline at end of file +

+
\ No newline at end of file diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts index ae58ad7..7be7342 100644 --- a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts @@ -13,6 +13,12 @@ interface CartItem { image: string; } +interface PaymentMethod { + id: string; + name: string; + icon: string; +} + @Component({ selector: 'app-cart-summary', templateUrl: './cart-summary.component.html', @@ -21,14 +27,30 @@ interface CartItem { imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterLink] }) export class CartSummaryComponent implements OnInit { + currentStep: 'summary' | 'payment' | 'confirmation' = 'summary'; cartItems: CartItem[] = []; shippingForm: FormGroup; + billingForm: FormGroup; + paymentForm: FormGroup; + shippingCost: number = 5.99; taxRate: number = 7; discountCode: string = ''; discountApplied: boolean = false; discountAmount: number = 0; - user = { name: 'Guest User' }; // Placeholder, would normally come from a user service + + user = { name: 'Guest User' }; + sameAsShipping: boolean = true; + + paymentMethods: PaymentMethod[] = [ + { id: 'credit_card', name: 'Credit Card', icon: 'https://th.bing.com/th/id/R.d4653ffdddd3f73959889432f9d7d5f8?rik=0ZtmYR5u4PqXJQ&pid=ImgRaw&r=0' }, + { id: 'paypal', name: 'PayPal', icon: 'https://logodix.com/logo/370282.jpg' }, + { id: 'bank_transfer', name: 'Bank Transfer', icon: 'https://www.sevenjackpots.com/wp-content/uploads/2021/04/bank-transfer-logo.png' } + ]; + + selectedPaymentMethod: string = 'credit_card'; + orderNumber: string = ''; + orderDate: Date = new Date(); constructor( private fb: FormBuilder, @@ -43,19 +65,35 @@ export class CartSummaryComponent implements OnInit { postalCode: ['', Validators.required], phone: ['', [Validators.required, Validators.pattern('^[0-9]{10,15}$')]] }); + + this.billingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + country: ['', Validators.required], + postalCode: ['', Validators.required] + }); + + this.paymentForm = this.fb.group({ + cardholderName: ['', Validators.required], + cardNumber: ['', [Validators.required, Validators.pattern('^[0-9]{16,19}$')]], + expiryDate: ['', [Validators.required, Validators.pattern('^(0[1-9]|1[0-2])\/?([0-9]{2})$')]], + cvv: ['', [Validators.required, Validators.pattern('^[0-9]{3,4}$')]], + saveCard: [false] + }); } ngOnInit(): void { - // Load cart items from local storage or service this.loadCartItems(); - // Load user data if available this.loadUserData(); + this.generateOrderNumber(); } loadCartItems(): void { - // This would typically come from a service or localStorage - // For demo purposes, we'll initialize with some sample data - this.cartItems = [ + // Load from localStorage or service + const savedCart = localStorage.getItem('cart'); + this.cartItems = savedCart ? JSON.parse(savedCart) : [ { id: 1, name: 'Product 1', @@ -76,12 +114,10 @@ export class CartSummaryComponent implements OnInit { } loadUserData(): void { - // In a real app, this would load data from a user service const savedUser = localStorage.getItem('user'); if (savedUser) { this.user = JSON.parse(savedUser); - // Pre-fill the form with user data if available const userData = JSON.parse(savedUser); if (userData.address) { this.shippingForm.patchValue({ @@ -97,6 +133,11 @@ export class CartSummaryComponent implements OnInit { } } + generateOrderNumber(): void { + const randomNum = Math.floor(Math.random() * 90000) + 10000; + this.orderNumber = `ORD-${randomNum}`; + } + increaseQuantity(index: number): void { this.cartItems[index].quantity += 1; this.updateCart(); @@ -115,48 +156,40 @@ export class CartSummaryComponent implements OnInit { } updateCart(): void { - // Update cart in local storage or service - localStorage.setItem('cartItems', JSON.stringify(this.cartItems)); + localStorage.setItem('cart', JSON.stringify(this.cartItems)); } - getSubtotal(): string { - const subtotal = this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0); - return subtotal.toFixed(2); + getSubtotal(): number { + return parseFloat((this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0)).toFixed(2)); } calculateTax(): number { - return (parseFloat(this.getSubtotal()) * this.taxRate) / 100; + return parseFloat((this.getSubtotal() * this.taxRate / 100).toFixed(2)); } getTotalItems(): number { return this.cartItems.reduce((acc, item) => acc + item.quantity, 0); } - getTotal(): string { - const subtotal = parseFloat(this.getSubtotal()); + getTotal(): number { + const subtotal = this.getSubtotal(); const tax = this.calculateTax(); - const total = subtotal + this.shippingCost + tax - this.discountAmount; - return total.toFixed(2); + return parseFloat((subtotal + this.shippingCost + tax - this.discountAmount).toFixed(2)); } applyDiscount(): void { - // Simple discount logic - could be expanded to check against valid codes from backend - const validDiscountCodes = { + const validDiscountCodes: { [key: string]: number } = { 'SAVE10': 10, 'WELCOME20': 20, 'LOYAL15': 15 }; - type DiscountCodeKey = keyof typeof validDiscountCodes; - - if (this.discountCode && (this.discountCode as DiscountCodeKey) in validDiscountCodes) { - const discountPercent = validDiscountCodes[this.discountCode as DiscountCodeKey]; - this.discountAmount = (parseFloat(this.getSubtotal()) * discountPercent) / 100; + if (this.discountCode && validDiscountCodes[this.discountCode]) { + this.discountAmount = (this.getSubtotal() * validDiscountCodes[this.discountCode]) / 100; this.discountApplied = true; } else { this.discountAmount = 0; this.discountApplied = false; - // You could add an error message here } } @@ -166,18 +199,85 @@ export class CartSummaryComponent implements OnInit { proceedToPayment(): void { if (this.canProceed()) { - // Save shipping information const shippingData = this.shippingForm.value; localStorage.setItem('shippingDetails', JSON.stringify(shippingData)); - - // Navigate to payment page - this.router.navigate(['/payment']); + this.currentStep = 'payment'; + this.updateBillingAddress(); } else { - // Mark all form fields as touched to trigger validation messages Object.keys(this.shippingForm.controls).forEach(key => { - const control = this.shippingForm.get(key); - control?.markAsTouched(); + this.shippingForm.get(key)?.markAsTouched(); + }); + } + } + + selectPaymentMethod(methodId: string): void { + this.selectedPaymentMethod = methodId; + } + + getSelectedPaymentMethod(): PaymentMethod | undefined { + return this.paymentMethods.find(method => method.id === this.selectedPaymentMethod); + } + + updateBillingAddress(): void { + if (this.sameAsShipping) { + const shippingData = this.shippingForm.value; + this.billingForm.patchValue({ + firstName: shippingData.firstName, + lastName: shippingData.lastName, + address: shippingData.address, + city: shippingData.city, + country: shippingData.country, + postalCode: shippingData.postalCode }); } } + + canCompleteOrder(): boolean { + if (this.selectedPaymentMethod === 'credit_card') { + return this.paymentForm.valid; + } + return true; // For other payment methods that don't require form validation + } + + completeOrder(): void { + if (this.canCompleteOrder()) { + // In a real app, you would send the order to your backend here + const orderData = { + orderNumber: this.orderNumber, + date: new Date(), + items: this.cartItems, + shipping: this.shippingForm.value, + billing: this.sameAsShipping ? this.shippingForm.value : this.billingForm.value, + payment: { + method: this.selectedPaymentMethod, + details: this.selectedPaymentMethod === 'credit_card' ? this.paymentForm.value : null + }, + subtotal: this.getSubtotal(), + shippingCost: this.shippingCost, + tax: this.calculateTax(), + discount: this.discountAmount, + total: this.getTotal() + }; + + // Save order to localStorage (in a real app, you would send to backend) + localStorage.setItem('currentOrder', JSON.stringify(orderData)); + + // Clear cart + localStorage.removeItem('cart'); + this.cartItems = []; + + // Move to confirmation step + this.currentStep = 'confirmation'; + } else { + if (this.selectedPaymentMethod === 'credit_card') { + Object.keys(this.paymentForm.controls).forEach(key => { + this.paymentForm.get(key)?.markAsTouched(); + }); + } + } + } + + backToSummary(): void { + this.currentStep = 'summary'; + } } \ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.html b/src/app/components/vendor-page/cart/cart.component.html index fe6ea22..e38029f 100644 --- a/src/app/components/vendor-page/cart/cart.component.html +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -67,12 +67,9 @@

{{ item.name }}

Qty: - +

+ {{ item.quantity }} +

Proceed to Checkout @@ -126,7 +123,7 @@

{{ item.name }}

Your cart is currently empty.

Browse Products diff --git a/src/app/components/vendor-page/product-section/product-section.component.css b/src/app/components/vendor-page/product-section/product-section.component.css new file mode 100644 index 0000000..f86e874 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.css @@ -0,0 +1,25 @@ +/* Add to product-section.component.css */ +.animate-fade-in { + animation: fadeIn 0.3s ease-in-out; +} + +.animate-slide-up { + animation: slideUp 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + diff --git a/src/app/components/vendor-page/product-section/product-section.component.html b/src/app/components/vendor-page/product-section/product-section.component.html new file mode 100644 index 0000000..c9b7b1e --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.html @@ -0,0 +1,167 @@ +
+
+ +
+
+
+ + + +
+
+ + +
+ + + +
+
+ Premium Spice Collection +
+

+ Discover Our Authentic Flavors +

+

+ Handcrafted blends from around the world +

+
+
+
+ + +
+ @for (category of categories; track category) { + + } +
+ + +
+ @for (product of filteredProducts; track product.name) { +
+
+ +
+
+

+ {{ product.name }} +

+

{{ product.subtitle }}

+

+ ${{ product.price }} +

+ +
+
+ } +
+ + + @if(selectedProduct) { +
+
+ + + + +
+ +

+ {{ selectedProduct.name }} +

+

{{ selectedProduct.subtitle }}

+

+ ${{ selectedProduct.price }} +

+

+ Our spices are ethically sourced and expertly curated for + exceptional flavor in every dish. +

+
+ +
+ Quantity: +
+ + +
+ + + +
+
+
+
+
+
+
+ } +
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/product-section/product-section.component.spec.ts b/src/app/components/vendor-page/product-section/product-section.component.spec.ts new file mode 100644 index 0000000..e2856b3 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductSectionComponent } from './product-section.component'; + +describe('ProductSectionComponent', () => { + let component: ProductSectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductSectionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductSectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/product-section/product-section.component.ts b/src/app/components/vendor-page/product-section/product-section.component.ts new file mode 100644 index 0000000..5f12113 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.ts @@ -0,0 +1,171 @@ +import { CommonModule } from '@angular/common'; +import { Component, signal, OnInit, OnDestroy } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { FormsModule } from '@angular/forms'; + +interface Product { + name: string; + subtitle: string; + price: number; + category: string; + image: string; +} + +interface CartItem { + name: string; + subtitle: string; + price: number; + image: string; + quantity: number; +} + +@Component({ + selector: 'app-product-section', + standalone: true, + imports: [ + CommonModule, + RouterLink, + FormsModule, + ], + templateUrl: './product-section.component.html', + styleUrl: './product-section.component.css', +}) +export class ProductSectionComponent implements OnInit, OnDestroy { + selectedCategory = 'All'; + categories = ['All', 'Powders', 'Whole Spices', 'Blends']; + selectedProduct: Product | null = null; + selectedQuantity = 1; + + // Cart state + cartCount = signal(0); + cartItems = signal([]); + + products = [ + { + name: 'Turmeric Powder', + subtitle: 'Organic • 200g', + price: 4.99, + category: 'Powders', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Garam Masala', + subtitle: 'Premium Blend • 150g', + price: 6.49, + category: 'Blends', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cinnamon Sticks', + subtitle: 'Ceylon • 100g', + price: 3.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Chili Powder', + subtitle: 'Smoky Heat • 100g', + price: 2.99, + category: 'Powders', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cloves', + subtitle: 'Whole • 50g', + price: 5.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Black Pepper', + subtitle: 'Tellicherry • 100g', + price: 4.49, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cardamom Pods', + subtitle: 'Green • 75g', + price: 7.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cumin Seeds', + subtitle: 'Whole • 150g', + price: 2.49, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + ]; + + constructor() { + this.loadCart(); + } + + get filteredProducts() { + return this.selectedCategory === 'All' + ? this.products + : this.products.filter((p) => p.category === this.selectedCategory); + } + + updateQuantity(value: string) { + this.selectedQuantity = +value; + } + + openProductModal(product: Product) { + this.selectedProduct = product; + this.selectedQuantity = 1; // Reset quantity when opening modal + } + + closeProductModal() { + this.selectedProduct = null; + } + + addToCart(product: Product) { + const currentCart = this.cartItems(); + const existingItem = currentCart.find(item => item.name === product.name); + + if (existingItem) { + // Update quantity if item exists + existingItem.quantity += this.selectedQuantity; + } else { + // Add new item to cart + currentCart.push({ + ...product, + quantity: this.selectedQuantity + }); + } + + // Update cart state + this.cartItems.set([...currentCart]); + this.cartCount.set(currentCart.reduce((sum) => sum + 1, 0)); + this.saveCart(); + this.closeProductModal(); + } + + private saveCart() { + localStorage.setItem('cart', JSON.stringify(this.cartItems())); + } + + private loadCart() { + const savedCart = localStorage.getItem('cart'); + if (savedCart) { + this.cartItems.set(JSON.parse(savedCart)); + this.cartCount.set(this.cartItems().reduce((sum, item) => sum + item.quantity, 0)); + } + } + + // Handle keyboard events + ngOnInit(): void { + window.addEventListener('keydown', this.handleKeydown); + } + + ngOnDestroy(): void { + window.removeEventListener('keydown', this.handleKeydown); + } + + handleKeydown = (e: KeyboardEvent) => { + if (e.key === 'Escape') this.closeProductModal(); + }; +} \ No newline at end of file From 5c3ac7cdba526000167c981e85e5120dd736d88d Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Fri, 9 May 2025 03:16:05 +0530 Subject: [PATCH 3/9] add app.config and route correction --- src/app/app.config.ts | 3 ++- src/app/app.module.ts | 9 +++++++++ src/app/components/vendor-page/cart/cart.component.html | 2 +- src/app/components/vendor-page/cart/cart.component.ts | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/app/app.module.ts diff --git a/src/app/app.config.ts b/src/app/app.config.ts index e36d0ad..642c257 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,7 +1,7 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideAnimations } from '@angular/platform-browser/animations'; import { provideRouter } from '@angular/router'; - +import { provideHttpClient } from '@angular/common/http'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { @@ -9,6 +9,7 @@ export const appConfig: ApplicationConfig = { provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimations(), + provideHttpClient(), ] }; diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 0000000..180b92c --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,9 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; + +bootstrapApplication(AppComponent, { + providers: [ + provideHttpClient(withInterceptorsFromDi()) + ] +}); \ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.html b/src/app/components/vendor-page/cart/cart.component.html index e38029f..feb3f05 100644 --- a/src/app/components/vendor-page/cart/cart.component.html +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -105,7 +105,7 @@

{{ item.name }}

Proceed to Checkout Continue Shopping diff --git a/src/app/components/vendor-page/cart/cart.component.ts b/src/app/components/vendor-page/cart/cart.component.ts index c3f7981..2c22934 100644 --- a/src/app/components/vendor-page/cart/cart.component.ts +++ b/src/app/components/vendor-page/cart/cart.component.ts @@ -10,6 +10,8 @@ import { FormsModule } from '@angular/forms'; imports: [CommonModule, RouterLink, FormsModule], templateUrl: './cart.component.html', }) + + export class CartComponent implements OnInit { user = { name: "Alice Fernando", From a0b54d42ae6abaf5db6baaa3df72c564c1743d69 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Fri, 9 May 2025 03:16:25 +0530 Subject: [PATCH 4/9] html correction --- .../product-section/product-section.component.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/components/vendor-page/product-section/product-section.component.html b/src/app/components/vendor-page/product-section/product-section.component.html index c9b7b1e..fd4ccad 100644 --- a/src/app/components/vendor-page/product-section/product-section.component.html +++ b/src/app/components/vendor-page/product-section/product-section.component.html @@ -18,7 +18,7 @@
- + @@ -47,16 +47,17 @@

@for (category of categories; track category) { - + }
From 842f83fde901d9544fa51e9f44655eddbda1ed51 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Fri, 9 May 2025 03:16:42 +0530 Subject: [PATCH 5/9] connect with backend --- .../product-section.component.ts | 120 +++++++----------- src/app/services/product.service.ts | 56 ++++++++ 2 files changed, 100 insertions(+), 76 deletions(-) create mode 100644 src/app/services/product.service.ts diff --git a/src/app/components/vendor-page/product-section/product-section.component.ts b/src/app/components/vendor-page/product-section/product-section.component.ts index 5f12113..a05da78 100644 --- a/src/app/components/vendor-page/product-section/product-section.component.ts +++ b/src/app/components/vendor-page/product-section/product-section.component.ts @@ -2,14 +2,7 @@ import { CommonModule } from '@angular/common'; import { Component, signal, OnInit, OnDestroy } from '@angular/core'; import { RouterLink } from '@angular/router'; import { FormsModule } from '@angular/forms'; - -interface Product { - name: string; - subtitle: string; - price: number; - category: string; - image: string; -} +import { ProductService, Product, ApiProduct } from '../../../services/product.service'; interface CartItem { name: string; @@ -30,9 +23,29 @@ interface CartItem { templateUrl: './product-section.component.html', styleUrl: './product-section.component.css', }) + + + + + export class ProductSectionComponent implements OnInit, OnDestroy { - selectedCategory = 'All'; - categories = ['All', 'Powders', 'Whole Spices', 'Blends']; + categories = [ + { id: 'All', name: 'All' }, + { id: 1, name: 'Powders' }, + { id: 2, name: 'Whole Spices' }, + { id: 3, name: 'Blends' } + ]; + + + selectedCategory: string | number | 'All' = 'All'; // Default to 'All' + + + categoryMap: { [key: number]: string } = { + 1: 'Powders', + 2: 'Whole Spices', + 3: 'Blends', + }; + selectedProduct: Product | null = null; selectedQuantity = 1; @@ -40,73 +53,32 @@ export class ProductSectionComponent implements OnInit, OnDestroy { cartCount = signal(0); cartItems = signal([]); - products = [ - { - name: 'Turmeric Powder', - subtitle: 'Organic • 200g', - price: 4.99, - category: 'Powders', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Garam Masala', - subtitle: 'Premium Blend • 150g', - price: 6.49, - category: 'Blends', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Cinnamon Sticks', - subtitle: 'Ceylon • 100g', - price: 3.99, - category: 'Whole Spices', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Chili Powder', - subtitle: 'Smoky Heat • 100g', - price: 2.99, - category: 'Powders', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Cloves', - subtitle: 'Whole • 50g', - price: 5.99, - category: 'Whole Spices', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Black Pepper', - subtitle: 'Tellicherry • 100g', - price: 4.49, - category: 'Whole Spices', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Cardamom Pods', - subtitle: 'Green • 75g', - price: 7.99, - category: 'Whole Spices', - image: 'assets/images/products/spices-bg1.jpg', - }, - { - name: 'Cumin Seeds', - subtitle: 'Whole • 150g', - price: 2.49, - category: 'Whole Spices', - image: 'assets/images/products/spices-bg1.jpg', - }, - ]; + products: Product[] = []; // Store products dynamically - constructor() { + constructor(private productService: ProductService) { this.loadCart(); } + ngOnInit(): void { + this.productService.getProducts().subscribe((products: ApiProduct[]) => { + this.products = products.map((product) => ({ + name: product.product_name, + subtitle: 'Description', + price: +product.unit_price, + category: product.category, // keep as number + image: 'assets/images/products/spices-bg1.jpg', + })); + }); + + + // Handle keyboard events + window.addEventListener('keydown', this.handleKeydown); + } + get filteredProducts() { return this.selectedCategory === 'All' ? this.products - : this.products.filter((p) => p.category === this.selectedCategory); + : this.products.filter(product => product.category === this.selectedCategory); } updateQuantity(value: string) { @@ -157,15 +129,11 @@ export class ProductSectionComponent implements OnInit, OnDestroy { } // Handle keyboard events - ngOnInit(): void { - window.addEventListener('keydown', this.handleKeydown); - } - ngOnDestroy(): void { window.removeEventListener('keydown', this.handleKeydown); } - + handleKeydown = (e: KeyboardEvent) => { if (e.key === 'Escape') this.closeProductModal(); }; -} \ No newline at end of file +} diff --git a/src/app/services/product.service.ts b/src/app/services/product.service.ts new file mode 100644 index 0000000..688d03a --- /dev/null +++ b/src/app/services/product.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface ApiProduct { + id: number; + product_SKU: string; + product_name: string; + + unit_price: string; + category: number; + } + +// "id": 1, +// "product_SKU": "SKU001", +// "product_name": "Cardamom Product 1", +// "unit_price": "1257.29", +// "created_at": "2025-05-07T14:36:06.635310Z", +// "updated_at": "2025-05-07T14:36:06.635332Z", +// "category": 3 + + export interface Product { + name: string; + subtitle: string; + price: number; + category: number; // keep it number + image: string; + } + + + +@Injectable({ + providedIn: 'root', +}) +export class ProductService { + private apiUrl = 'http://127.0.0.1:8000/api/product/products/'; // Your API endpoint + + constructor(private http: HttpClient) {} + + // Fetch all products from the API + getProducts(): Observable { + console.log('Fetching products from API...'); + return this.http.get(this.apiUrl); + + } + // You can implement methods for filtering and sorting if needed + // Example of category filter method + filterProductsByCategory(products: Product[], category: number): Product[] { + + return products.filter(product => product.category === category); + } + + + + +} From ddcbb36e13695c47868e3c8d6fcab045bf4e4b28 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Fri, 9 May 2025 16:57:01 +0530 Subject: [PATCH 6/9] add service --- src/app/services/order.service.ts | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/app/services/order.service.ts diff --git a/src/app/services/order.service.ts b/src/app/services/order.service.ts new file mode 100644 index 0000000..2d30792 --- /dev/null +++ b/src/app/services/order.service.ts @@ -0,0 +1,41 @@ +// src/app/services/order.service.ts +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface Product { + product_id: number; + count: number; + unit_price: number; +} + +export interface OrderDetails { + warehouse_id: number; + nearest_city: string; + latitude: string; + longitude: string; +} + +export interface OrderResponse { + order_id: number; + user_id: number; + status: string; + blockchain_tx_id: string; + ipfs_hash: string; + created_at: string; + products: Product[]; + details: OrderDetails; +} + +@Injectable({ + providedIn: 'root' +}) +export class OrderService { + private baseUrl = 'http://127.0.0.1:8000/api/v0/orders/vendor'; + + constructor(private http: HttpClient) {} + + getOrdersByVendorId(vendorId: number): Observable { + return this.http.get(`${this.baseUrl}/${vendorId}/`); + } +} From a087a3a6f673b5d523a976717ba72c384d191eb3 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Fri, 9 May 2025 16:57:15 +0530 Subject: [PATCH 7/9] api connection --- .../vendor-page/orders/orders.component.ts | 111 ++++++------------ 1 file changed, 37 insertions(+), 74 deletions(-) diff --git a/src/app/components/vendor-page/orders/orders.component.ts b/src/app/components/vendor-page/orders/orders.component.ts index 95a0679..cdb2cdb 100644 --- a/src/app/components/vendor-page/orders/orders.component.ts +++ b/src/app/components/vendor-page/orders/orders.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { OrderService, OrderResponse } from '../../../services/order.service'; interface Order { id: string; @@ -14,7 +15,6 @@ interface Order { interface User { name: string; - // Add other user properties as needed } @Component({ @@ -25,92 +25,55 @@ interface User { styleUrl: './orders.component.css' }) export class OrdersComponent implements OnInit { - // User data - user: User = { - name: 'John Doe' - }; + user: User = { name: 'John Doe' }; - // Orders data orders: Order[] = []; filteredOrders: Order[] = []; - // Pagination - currentPage: number = 1; - itemsPerPage: number = 5; - totalPages: number = 1; + currentPage = 1; + itemsPerPage = 5; + totalPages = 1; pageNumbers: number[] = []; - paginationStart: number = 0; - paginationEnd: number = 0; + paginationStart = 0; + paginationEnd = 0; - // Filters - searchQuery: string = ''; - statusFilter: string = 'all'; - dateFilter: string = 'all'; + searchQuery = ''; + statusFilter = 'all'; + dateFilter = 'all'; - constructor() {} + constructor(private orderService: OrderService) {} ngOnInit(): void { - // Load mock order data - this.loadMockOrders(); - this.filterOrders(); - this.calculatePagination(); + this.loadOrdersFromServer(2); // You can pass vendor ID dynamically } - loadMockOrders(): void { - // Mock data for demonstration - this.orders = [ - { - id: '1001', - date: new Date(2025, 3, 15), // April 15, 2025 - itemsCount: 3, - total: 129.99, - status: 'delivered' - }, - { - id: '1002', - date: new Date(2025, 4, 1), // May 1, 2025 - itemsCount: 1, - total: 49.99, - status: 'shipped', - trackingNumber: 'TRACK123456' - }, - { - id: '1003', - date: new Date(2025, 4, 5), // May 5, 2025 - itemsCount: 2, - total: 89.98, - status: 'processing' - }, - { - id: '1004', - date: new Date(2025, 3, 10), // April 10, 2025 - itemsCount: 4, - total: 159.96, - status: 'cancelled' + loadOrdersFromServer(vendorId: number): void { + this.orderService.getOrdersByVendorId(vendorId).subscribe({ + next: (data: OrderResponse[]) => { + this.orders = data.map(order => ({ + id: order.order_id.toString(), + date: new Date(order.created_at), + itemsCount: order.products.reduce((sum, p) => sum + p.count, 0), + total: order.products.reduce((sum, p) => sum + (p.unit_price * p.count), 0), + status: this.mapStatus(order.status), + trackingNumber: order.blockchain_tx_id + })); + this.filterOrders(); + this.calculatePagination(); }, - { - id: '1005', - date: new Date(2025, 2, 20), // March 20, 2025 - itemsCount: 2, - total: 79.98, - status: 'delivered' - }, - { - id: '1006', - date: new Date(2025, 1, 15), // February 15, 2025 - itemsCount: 1, - total: 29.99, - status: 'delivered' - }, - { - id: '1007', - date: new Date(2025, 4, 3), // May 3, 2025 - itemsCount: 3, - total: 109.97, - status: 'shipped', - trackingNumber: 'TRACK789012' + error: err => { + console.error('Error fetching orders:', err); } - ]; + }); + } + + mapStatus(apiStatus: string): 'processing' | 'shipped' | 'delivered' | 'cancelled' { + const status = apiStatus.toLowerCase(); + if (status.includes('pending')) return 'processing'; + if (status.includes('shipped')) return 'shipped'; + if (status.includes('delivered')) return 'delivered'; + if (status.includes('cancelled')) return 'cancelled'; + return 'processing'; } filterOrders(): void { From fd165af8d1edaaa3487449c50e9f292e64f36af9 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Sat, 10 May 2025 00:22:15 +0530 Subject: [PATCH 8/9] warehouses component create --- src/app/app.routes.ts | 4 + .../warehouse/warehouse.component.css | 189 ++++++++++++ .../warehouse/warehouse.component.html | 280 ++++++++++++++++++ 3 files changed, 473 insertions(+) create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.css create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.html diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 2172773..f1d42b7 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -22,6 +22,9 @@ import { OrderSummaryComponent } from './components/vendor-page/order-summary/or import { CartSummaryComponent } from './components/vendor-page/cart-summary/cart-summary.component'; import { OrdersComponent } from './components/vendor-page/orders/orders.component'; +import { WarehouseComponent } from './components/admin-page/warehouse/warehouse.component'; + + export const routes: Routes = [ { path: '', @@ -50,6 +53,7 @@ export const routes: Routes = [ component: ForecastComponent, pathMatch: 'full', }, + { path: 'warehouse', component: WarehouseComponent, pathMatch: 'full' }, { path: 'order-history', component: OrderHistoryComponent }, // /dashboard/orders { path: 'inventory', component: InventoryComponent }, // /dashboard/inventory { path: 'product-management', component: ProductManagementComponent}, // /dashboard/product-management diff --git a/src/app/components/admin-page/warehouse/warehouse.component.css b/src/app/components/admin-page/warehouse/warehouse.component.css new file mode 100644 index 0000000..1daef03 --- /dev/null +++ b/src/app/components/admin-page/warehouse/warehouse.component.css @@ -0,0 +1,189 @@ +/* Warehouse Card Styles */ +.warehouse-card { + transition: transform 0.3s ease, box-shadow 0.3s ease; + } + + .warehouse-card:hover { + transform: translateY(-3px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); + } + + /* Gradient Backgrounds */ + .warehouse-header-gradient { + background-image: linear-gradient(135deg, #4f46e5 0%, #3b82f6 100%); + } + + .inventory-header-gradient { + background-image: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + } + + /* Custom Badge Styles */ + .custom-badge { + border-radius: 9999px; + padding: 0.25rem 0.75rem; + font-size: 0.75rem; + font-weight: 500; + display: inline-flex; + align-items: center; + } + + .badge-blue { + background-color: rgba(59, 130, 246, 0.1); + color: #1e40af; + } + + .badge-green { + background-color: rgba(16, 185, 129, 0.1); + color: #065f46; + } + + .badge-amber { + background-color: rgba(245, 158, 11, 0.1); + color: #92400e; + } + + /* Status Indicators */ + .status-indicator { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 6px; + } + + .status-active { + background-color: #10b981; + } + + .status-inactive { + background-color: #ef4444; + } + + /* Button Animations */ + .btn-primary { + transition: all 0.2s ease; + position: relative; + overflow: hidden; + } + + .btn-primary:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 5px; + height: 5px; + background: rgba(255, 255, 255, 0.5); + opacity: 0; + border-radius: 100%; + transform: scale(1, 1) translate(-50%); + transform-origin: 50% 50%; + } + + .btn-primary:focus:not(:active)::after { + animation: ripple 1s ease-out; + } + + @keyframes ripple { + 0% { + transform: scale(0, 0); + opacity: 0.5; + } + 20% { + transform: scale(25, 25); + opacity: 0.3; + } + 100% { + opacity: 0; + transform: scale(40, 40); + } + } + + /* Search Input Focus Effect */ + .search-input:focus { + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); + } + + /* Table Row Hover Effect */ + .inventory-row { + transition: background-color 0.2s ease; + } + + .inventory-row:hover { + background-color: rgba(243, 244, 246, 0.8); + } + + /* Loading Animation */ + .loading-spinner { + animation: spin 1s linear infinite; + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + /* Back Button Hover Effect */ + .back-button { + transition: transform 0.2s ease; + } + + .back-button:hover { + transform: translateX(-3px); + } + + /* Responsive Adjustments */ + @media (max-width: 768px) { + .warehouse-grid { + grid-template-columns: 1fr; + } + + .stats-container { + flex-direction: column; + } + + .stats-item { + margin-bottom: 1rem; + } + } + + /* Inventory Quantity Tags */ + .quantity-high { + background-color: #d1fae5; + color: #065f46; + } + + .quantity-medium { + background-color: #fef3c7; + color: #92400e; + } + + .quantity-low { + background-color: #fee2e2; + color: #b91c1c; + } + + /* Page Transitions */ + .page-enter { + opacity: 0; + transform: translateY(10px); + } + + .page-enter-active { + opacity: 1; + transform: translateY(0); + transition: opacity 300ms, transform 300ms; + } + + .page-exit { + opacity: 1; + } + + .page-exit-active { + opacity: 0; + transition: opacity 300ms; + } \ No newline at end of file diff --git a/src/app/components/admin-page/warehouse/warehouse.component.html b/src/app/components/admin-page/warehouse/warehouse.component.html new file mode 100644 index 0000000..d906565 --- /dev/null +++ b/src/app/components/admin-page/warehouse/warehouse.component.html @@ -0,0 +1,280 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Warehouse Management +
+

+ {{ selectedWarehouse ? 'Inventory for' : 'All Warehouses' }} +

+

+ {{ selectedWarehouse ? selectedWarehouse.warehouse_name : 'Warehouse Directory' }} +

+

+ {{ selectedWarehouse ? 'View and manage inventory' : 'Manage your storage facilities' }} +

+
+
+
+ + +
+
+ + +
+ +
+
+

Warehouses

+ + {{ warehouses.length }} locations + +
+ +
+ +
+ +
+ + + +
+
+
+
+ + +
+
+
+
+

{{ warehouse.warehouse_name }}

+ ID: {{ warehouse.id }} +
+
+
+ + + + + {{ warehouse.location_x }}, {{ warehouse.location_y }} +
+
+ + + + Capacity: {{ parseFloat(warehouse.capacity).toLocaleString() }} units +
+
+ + + + Created: {{ formatDate(warehouse.created_at) }} +
+ +
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + +
+
+
+ + +
+
+
+
+ + + +

{{ selectedWarehouse.warehouse_name }}

+ ID: {{ selectedWarehouse.id }} +
+

{{ selectedWarehouse.location_x }}, {{ selectedWarehouse.location_y }}

+
+
+
+
Warehouse Capacity
+
{{ parseFloat(selectedWarehouse.capacity).toLocaleString() }} units
+
+
+
+
+ + +
+
+
+ + + + +
+
+ +
+
+
+

{{ item.product_name }}

+

Category: {{ item.category }}

+
+ + {{ item.product_count.toLocaleString() }} units + +
+

Supplied: {{ formatDate(item.supplied_date) }} by {{ item.supplied_by }}

+
+ + + +
+
+
+
+
+
+ + + +
+
+ Loading... +
+
+ + + +
+
+ + + +
+

No warehouses found

+

+ {{ warehouses.length > 0 ? 'No warehouses match your search.' : 'There are no warehouses in the system yet.' }} +

+
+ + +
+
+
+ + + +
+
+ + + +
+

No inventory items found

+

+ {{ inventoryProducts.length > 0 ? 'No inventory items match your search.' : 'This warehouse is currently empty.' }} +

+
+ + +
+
+
+
+
+
\ No newline at end of file From 7e5acce06c697099c0b474818e7d4ff3471ea0be Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Sat, 10 May 2025 00:22:41 +0530 Subject: [PATCH 9/9] add warehouse inventory and details --- .../warehouse/warehouse.component.spec.ts | 24 ++++ .../warehouse/warehouse.component.ts | 120 ++++++++++++++++++ src/app/services/warehouse.service.ts | 50 ++++++++ 3 files changed, 194 insertions(+) create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.spec.ts create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.ts create mode 100644 src/app/services/warehouse.service.ts diff --git a/src/app/components/admin-page/warehouse/warehouse.component.spec.ts b/src/app/components/admin-page/warehouse/warehouse.component.spec.ts new file mode 100644 index 0000000..65e7fda --- /dev/null +++ b/src/app/components/admin-page/warehouse/warehouse.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +import { WarehouseComponent } from './warehouse.component'; + +describe('WarehouseComponent', () => { + let component: WarehouseComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [WarehouseComponent, HttpClientTestingModule] + }) + .compileComponents(); + + fixture = TestBed.createComponent(WarehouseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/components/admin-page/warehouse/warehouse.component.ts b/src/app/components/admin-page/warehouse/warehouse.component.ts new file mode 100644 index 0000000..844d000 --- /dev/null +++ b/src/app/components/admin-page/warehouse/warehouse.component.ts @@ -0,0 +1,120 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { WarehouseService, Warehouse, WarehouseInventory, InventoryProductDetail } from '../../../services/warehouse.service'; + +@Component({ + selector: 'app-warehouse', + standalone: true, + imports: [CommonModule, FormsModule, RouterModule], + templateUrl: './warehouse.component.html', + styleUrls: ['./warehouse.component.css'] +}) +export class WarehouseComponent implements OnInit { + warehouses: Warehouse[] = []; + inventoryData: WarehouseInventory | null = null; + inventoryProducts: InventoryProductDetail[] = []; + selectedWarehouse: Warehouse | null = null; + loading = false; + searchQuery = ''; + filteredWarehouses: Warehouse[] = []; + + // For inventory view + inventoryLoading = false; + inventorySearchQuery = ''; + filteredInventoryProducts: InventoryProductDetail[] = []; + + constructor(private warehouseService: WarehouseService) { } + + ngOnInit(): void { + this.loadWarehouses(); + } + + // Add this method to make parseFloat available in the template + parseFloat(value: string): number { + return parseFloat(value); + } + + loadWarehouses(): void { + this.loading = true; + this.warehouseService.getWarehouses().subscribe({ + next: (data) => { + this.warehouses = data; + this.filteredWarehouses = [...this.warehouses]; + this.loading = false; + }, + error: (error) => { + console.error('Error loading warehouses', error); + this.loading = false; + } + }); + } + + selectWarehouse(warehouse: Warehouse): void { + this.selectedWarehouse = warehouse; + this.loadInventory(warehouse.id); + } + + loadInventory(warehouseId: number): void { + this.inventoryLoading = true; + this.warehouseService.getWarehouseInventory(warehouseId).subscribe({ + next: (data) => { + this.inventoryData = data; + this.inventoryProducts = data.inventory_product_details; + this.filteredInventoryProducts = [...this.inventoryProducts]; + this.inventoryLoading = false; + }, + error: (error) => { + console.error('Error loading inventory', error); + this.inventoryLoading = false; + } + }); + } + + backToWarehouses(): void { + this.selectedWarehouse = null; + this.inventoryData = null; + this.inventoryProducts = []; + } + + filterWarehouses(): void { + if (!this.searchQuery.trim()) { + this.filteredWarehouses = [...this.warehouses]; + return; + } + + const query = this.searchQuery.toLowerCase(); + this.filteredWarehouses = this.warehouses.filter(warehouse => + warehouse.warehouse_name.toLowerCase().includes(query) || + warehouse.location_x.toLowerCase().includes(query) || + warehouse.location_y.toLowerCase().includes(query) + ); + } + + filterInventory(): void { + if (!this.inventorySearchQuery.trim()) { + this.filteredInventoryProducts = [...this.inventoryProducts]; + return; + } + + const query = this.inventorySearchQuery.toLowerCase(); + this.filteredInventoryProducts = this.inventoryProducts.filter(item => + item.product_name.toLowerCase().includes(query) || + item.category.toLowerCase().includes(query) || + item.supplied_by.toLowerCase().includes(query) + ); + } + + formatDate(dateString: string): string { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + + formatNumber(value: number): string { + return value.toLocaleString(); + } +} \ No newline at end of file diff --git a/src/app/services/warehouse.service.ts b/src/app/services/warehouse.service.ts new file mode 100644 index 0000000..57920a4 --- /dev/null +++ b/src/app/services/warehouse.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export interface Warehouse { + id: number; + location_x: string; + location_y: string; + warehouse_name: string; + capacity: string; + created_at: string; +} + +export interface InventoryProductDetail { + product_name: string; + category: string; + supplied_by: string; + supplied_date: string; + product_count: number; +} + +export interface WarehouseInventory { + warehouse_city: string; + capacity: number; + last_restocked: string; + current_stock_level: number; + inventory_product_details: InventoryProductDetail[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class WarehouseService { + private warehousesUrl = 'http://127.0.0.1:8000/api/warehouse/warehouses/'; + private inventoryUrl = 'http://127.0.0.1:8000/api/warehouse/inventory/'; + + constructor(private http: HttpClient) {} + + // Fetch all warehouses from the API + getWarehouses(): Observable { + console.log('Fetching warehouses from API...'); + return this.http.get(this.warehousesUrl); + } + + // Fetch inventory for a specific warehouse + getWarehouseInventory(warehouseId: number): Observable { + console.log(`Fetching inventory for warehouse ID: ${warehouseId}`); + return this.http.get(`${this.inventoryUrl}?warehouse_id=${warehouseId}`); + } +} \ No newline at end of file