From 71d25dbbb7447a8118e3ea86f7c7d23439c9abb7 Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Sat, 10 May 2025 12:55:22 +0530 Subject: [PATCH 1/3] admin warehoses display --- src/app/app.config.ts | 3 +- src/app/app.module.ts | 9 + src/app/app.routes.ts | 4 + .../warehouse/warehouse.component.css | 189 ++++++++++++ .../warehouse/warehouse.component.html | 280 ++++++++++++++++++ .../warehouse/warehouse.component.spec.ts | 24 ++ .../warehouse/warehouse.component.ts | 120 ++++++++ .../service/warehouse/warehouse.service.ts | 50 ++++ 8 files changed, 678 insertions(+), 1 deletion(-) create mode 100644 src/app/app.module.ts create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.css create mode 100644 src/app/components/admin-page/warehouse/warehouse.component.html 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/service/warehouse/warehouse.service.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/app.routes.ts b/src/app/app.routes.ts index 9f8119d..963209b 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -21,6 +21,8 @@ import { TransactionsComponent } from './components/warehouse-manager-page/trans import { TruckTrackingComponent } from './components/warehouse-manager-page/truck-tracking/truck-tracking.component'; import { VendorOrdersComponent } from './components/warehouse-manager-page/vendor-orders/vendor-orders.component'; import { SupplierReqSurveyComponent } from './components/warehouse-manager-page/supplier-req-survey/supplier-req-survey.component'; +import {WarehouseComponent} from './components/admin-page/warehouse/warehouse.component'; + export const routes: Routes = [ { @@ -70,7 +72,9 @@ export const routes: Routes = [ canActivate: [roleGuard(1)], children : [ { path : '', redirectTo: 'profile', pathMatch: 'full'}, + { path : 'profile', component: ProfileComponent, pathMatch: 'full'}, + { path: 'warehouse', component: WarehouseComponent, pathMatch: 'full' }, { path: 'forecast', component: ForecastComponent, 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 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..f124818 --- /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 '../../../service/warehouse/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/service/warehouse/warehouse.service.ts b/src/app/service/warehouse/warehouse.service.ts new file mode 100644 index 0000000..57920a4 --- /dev/null +++ b/src/app/service/warehouse/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 From 61d3c43f9834e0e311519b2dd65b38c4dd8875ce Mon Sep 17 00:00:00 2001 From: Rashmika Rathnayaka Date: Sat, 10 May 2025 18:23:28 +0530 Subject: [PATCH 2/3] changes added to vendor --- .env | 0 package-lock.json | 46 ++ package.json | 4 + src/app/app.routes.ts | 16 +- .../cart-summary/cart-summary.component.css | 60 +++ .../cart-summary/cart-summary.component.html | 463 ++++++++++++++++++ .../cart-summary.component.spec.ts | 23 + .../cart-summary/cart-summary.component.ts | 370 ++++++++++++++ .../vendor-page/cart/cart.component.css | 0 .../vendor-page/cart/cart.component.html | 142 ++++++ .../vendor-page/cart/cart.component.spec.ts | 23 + .../vendor-page/cart/cart.component.ts | 77 +++ .../create-order/create-order.component.css | 0 .../create-order/create-order.component.html | 50 ++ .../create-order.component.spec.ts | 23 + .../create-order/create-order.component.ts | 64 +++ .../map-selector/map-selector.component.css | 0 .../map-selector/map-selector.component.html | 7 + .../map-selector.component.spec.ts | 23 + .../map-selector/map-selector.component.ts | 77 +++ .../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 | 191 ++++++++ .../product-section.component.css | 26 + .../product-section.component.html | 199 ++++++++ .../product-section.component.spec.ts | 23 + .../product-section.component.ts | 167 +++++++ src/app/service/order/order.service.ts | 41 ++ src/app/service/warehouse/product.service.ts | 56 +++ src/assets/map-pin.png | Bin 0 -> 113930 bytes src/environments/environment.ts | 5 + src/styles.css | 2 +- 37 files changed, 2763 insertions(+), 3 deletions(-) create mode 100644 .env 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/create-order/create-order.component.css create mode 100644 src/app/components/vendor-page/create-order/create-order.component.html create mode 100644 src/app/components/vendor-page/create-order/create-order.component.spec.ts create mode 100644 src/app/components/vendor-page/create-order/create-order.component.ts create mode 100644 src/app/components/vendor-page/map-selector/map-selector.component.css create mode 100644 src/app/components/vendor-page/map-selector/map-selector.component.html create mode 100644 src/app/components/vendor-page/map-selector/map-selector.component.spec.ts create mode 100644 src/app/components/vendor-page/map-selector/map-selector.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/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 create mode 100644 src/app/service/order/order.service.ts create mode 100644 src/app/service/warehouse/product.service.ts create mode 100644 src/assets/map-pin.png create mode 100644 src/environments/environment.ts diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index 897d24f..777b6ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,9 @@ "@angular/router": "^19.2.0", "@tailwindcss/postcss": "^4.1.4", "chart.js": "^4.4.9", + "dotenv": "^16.5.0", "jwt-decode": "^4.0.0", + "leaflet": "^1.9.4", "lucide-angular": "^0.503.0", "ng2-charts": "^8.0.0", "postcss": "^8.5.3", @@ -31,7 +33,9 @@ "@angular-devkit/build-angular": "^19.2.8", "@angular/cli": "^19.2.8", "@angular/compiler-cli": "^19.2.0", + "@types/google.maps": "^3.58.1", "@types/jasmine": "~5.1.0", + "@types/leaflet": "^1.9.17", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -5082,6 +5086,20 @@ "@types/send": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/google.maps": { + "version": "3.58.1", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz", + "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -5109,6 +5127,16 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/leaflet": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.17.tgz", + "integrity": "sha512-IJ4K6t7I3Fh5qXbQ1uwL3CFVbCi6haW9+53oLWgdKlLP7EaS21byWFJxxqOx9y8I0AP0actXSJLVMbyvxhkUTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -6948,6 +6976,18 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -9170,6 +9210,12 @@ "shell-quote": "^1.8.1" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/less": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", diff --git a/package.json b/package.json index 92bc026..2f565b3 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,9 @@ "@angular/router": "^19.2.0", "@tailwindcss/postcss": "^4.1.4", "chart.js": "^4.4.9", + "dotenv": "^16.5.0", "jwt-decode": "^4.0.0", + "leaflet": "^1.9.4", "lucide-angular": "^0.503.0", "ng2-charts": "^8.0.0", "postcss": "^8.5.3", @@ -33,7 +35,9 @@ "@angular-devkit/build-angular": "^19.2.8", "@angular/cli": "^19.2.8", "@angular/compiler-cli": "^19.2.0", + "@types/google.maps": "^3.58.1", "@types/jasmine": "~5.1.0", + "@types/leaflet": "^1.9.17", "jasmine-core": "~5.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 963209b..46f5b45 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -22,7 +22,12 @@ import { TruckTrackingComponent } from './components/warehouse-manager-page/truc import { VendorOrdersComponent } from './components/warehouse-manager-page/vendor-orders/vendor-orders.component'; import { SupplierReqSurveyComponent } from './components/warehouse-manager-page/supplier-req-survey/supplier-req-survey.component'; import {WarehouseComponent} from './components/admin-page/warehouse/warehouse.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'; +import { OrdersComponent } from './components/vendor-page/orders/orders.component'; +import { CreateOrderComponent } from './components/vendor-page/create-order/create-order.component'; export const routes: Routes = [ { @@ -105,7 +110,14 @@ export const routes: Routes = [ path : 'vendor', canActivate : [roleGuard(4)], children : [ - { path : '', redirectTo: 'profile', pathMatch: 'full'}, + { path : '', redirectTo: 'product-section', pathMatch: 'full'}, + {path : 'product-section', component: ProductSectionComponent, pathMatch: 'full'}, + {path : 'cart', component: CartComponent, pathMatch: 'full'}, + {path : 'orders', component: OrdersComponent, pathMatch: 'full'}, + {path : 'order-summary', component: OrderSummaryComponent, pathMatch: 'full'}, + {path : 'cart-summary', component: CartSummaryComponent, pathMatch: 'full'}, + {path : 'create-order', component: CreateOrderComponent, pathMatch: 'full'}, + { path : 'profile', component: ProfileComponent, pathMatch: 'full'}, // rest... (modify sidebar.ts as well) { path: '**', redirectTo: '', pathMatch: 'full' } ] diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.css b/src/app/components/vendor-page/cart-summary/cart-summary.component.css new file mode 100644 index 0000000..4f59042 --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.css @@ -0,0 +1,60 @@ +/* Add this to your existing CSS */ + +/* Map container styling */ +app-map-selector { + display: block; + border-radius: 0.5rem; + overflow: hidden; +} + +/* Selected location badge styles */ +.location-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background-color: rgba(0, 95, 115, 0.1); + border-radius: 0.5rem; + color: #005f73; + font-size: 0.875rem; + font-weight: 500; + margin-top: 1rem; +} + +/* Map progress animation */ +@keyframes pulse { + 0% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.1); opacity: 0.7; } + 100% { transform: scale(1); opacity: 1; } +} + +.map-loading { + animation: pulse 1.5s infinite; +} + +/* Order confirmation map preview */ +.confirmation-map-preview { + width: 100%; + height: 120px; + border-radius: 0.375rem; + overflow: hidden; + margin-top: 0.5rem; + border: 1px solid rgba(0, 95, 115, 0.2); +} + +/* Add to cart-summary.component.css */ +.delivery-map-container { + background-color: #f9f9f9; + padding: 1rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin-top: 2rem; +} + +.location-details { + margin-top: 1rem; + padding: 0.75rem; + background-color: #ffffff; + border-radius: 6px; + border-left: 4px solid #4285f4; +} \ No newline at end of file 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 new file mode 100644 index 0000000..2acb72a --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.html @@ -0,0 +1,463 @@ +
+ +
+ + + +
+ +
+
+ + +
+
+
+ + + +
+
+ + +
+
+
+ Cart Summary + Delivery Location + Payment + Order Confirmation +
+

Checkout

+

+ Review your items + Choose your delivery location + Complete your purchase + Your order is confirmed +

+
+
+
+ + +
+
+ +
+
+ +
+
+
+ + +
+
+
+ + + +
+ Cart +
+ +
+
+ 2 + + + +
+ Location +
+ +
+
+ 3 + + + +
+ Payment +
+ +
+
+ 4 + + + +
+ Complete +
+
+
+
+ + +
+ +
+ +
+ +
+
+

Your Items

+ + {{ getTotalItems() }} + +
+ +
+ Your cart is empty +
+ +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+
+ + +
+
+ +
+
+
+

Select Delivery Location

+

Move the marker or click on the map to set your exact delivery location

+
+ +
+ +
+ +
+ + +
+

+ + + + + Delivery Address Details +

+ +
+
+ + +
+ First name is required +
+
+
+ + +
+ Last name is required +
+
+
+ +
+ + +
+ Valid phone number is required +
+
+ +
+ + +
+ Address is required +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ + +
+
+
+ + + + +
+
+

Selected Location

+

+ Latitude: {{ selectedLat | number:'1.6-6' }}, Longitude: {{ selectedLng | number:'1.6-6' }} +

+

+ {{ locationAddress }} +

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

+ Order Summary +

+ +
+
+

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

+

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

+

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

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

+ + + + Delivery Information +

+

+ Select your exact location on the map to ensure accurate delivery. Our system will track your order in real-time. +

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

Order Confirmed!

+

Your order has been placed and is being processed

+ +
+

Order #: {{ orderNumber }}

+

{{ orderDate | date:'medium' }}

+ + +
+

+ + + + + Delivery Location +

+

+ {{ deliveryForm.get('address')?.value }}
+ {{ deliveryForm.get('city')?.value }}, {{ deliveryForm.get('state')?.value }} {{ deliveryForm.get('zipCode')?.value }} +

+

+ Coordinates: {{ selectedLat | number:'1.6-6' }}, {{ selectedLng | number:'1.6-6' }} +

+
+ + +
+

+ + + + Track Your Order +

+

+ Use the link below to track your order in real-time +

+ + View Order Status + +
+ +
+ + +
+

Delivery Location

+
+
+

Coordinates: Lat: {{selectedLat?.toFixed(6)}}, Lng: {{selectedLng?.toFixed(6)}}

+

Address: {{locationAddress}}

+
+
+
+
+ + +
+
+
+ +
+
+
\ 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..9c51439 --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts @@ -0,0 +1,370 @@ +import { Component, OnInit, HostListener, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, FormsModule } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; +import { MapSelectorComponent } from '../map-selector/map-selector.component'; +import * as L from 'leaflet'; + +interface CartItem { + id: number; + name: string; + description: string; + price: number; + quantity: number; + image: string; +} + +interface PaymentMethod { + id: string; + name: string; + icon: string; +} + +@Component({ + selector: 'app-cart-summary', + templateUrl: './cart-summary.component.html', + styleUrls: ['./cart-summary.component.css'], + standalone: true, + imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterLink, MapSelectorComponent] +}) +export class CartSummaryComponent implements OnInit, AfterViewInit { + currentStep: 'summary' | 'delivery' | 'payment' | 'confirmation' = 'summary'; + cartItems: CartItem[] = []; + shippingForm: FormGroup; + deliveryForm: FormGroup; + billingForm: FormGroup; + paymentForm: FormGroup; + + // Map and location data + selectedLat: number | null = null; + selectedLng: number | null = null; + locationAddress: string | null = null; + + // Add ViewChild for the confirmation map + @ViewChild('confirmationMap') confirmationMapElement!: ElementRef; + + // Add map property + confirmationMap?: L.Map; + confirmationMarker?: L.Marker; + + shippingCost: number = 5.99; + taxRate: number = 7; + discountCode: string = ''; + discountApplied: boolean = false; + discountAmount: number = 0; + + 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(); + + // UI state + isCartPreviewOpen: boolean = false; + isItemsCollapsed: boolean = false; + isOrderSummaryItemsVisible: boolean = false; + + constructor( + private fb: FormBuilder, + private router: Router + ) { + // Initialize delivery form specifically for map-based delivery + this.deliveryForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + phone: ['', [Validators.required, Validators.pattern(/^\+?[0-9]{10,15}$/)]], + address: ['', Validators.required], + city: ['', Validators.required], + state: ['', Validators.required], + zipCode: ['', [Validators.required, Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]], + instructions: [''], + latitude: [null], + longitude: [null] + }); + + // Initialize shipping form + this.shippingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + state: ['', Validators.required], + zipCode: ['', [Validators.required, Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]], + country: ['United States', Validators.required] + }); + + // Initialize billing form + this.billingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + state: ['', Validators.required], + zipCode: ['', [Validators.required, Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]], + country: ['United States', Validators.required] + }); + + // Initialize payment form + 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 { + this.loadCartItems(); + this.loadUserData(); + this.generateOrderNumber(); + + // Ensure Leaflet marker icons work properly + if (!L.Icon.Default.imagePath) { + delete (L.Icon.Default.prototype as any)._getIconUrl; + + L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'assets/marker-icon-2x.png', + iconUrl: 'assets/marker-icon.png', + shadowUrl: 'assets/marker-shadow.png', + }); + } + } + + ngAfterViewInit(): void { + // If we're in confirmation step right on load, initialize map + // (normally this happens in completeOrder) + if (this.currentStep === 'confirmation' && this.selectedLat && this.selectedLng) { + this.initConfirmationMap(); + } + } + + // Initialize confirmation map + initConfirmationMap(): void { + // Wait for the DOM to be ready + setTimeout(() => { + if (this.confirmationMapElement && this.selectedLat && this.selectedLng) { + // Create the map + this.confirmationMap = L.map(this.confirmationMapElement.nativeElement).setView( + [this.selectedLat, this.selectedLng], 15 + ); + + // Add tile layer + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.confirmationMap); + + // Add marker for delivery location + this.confirmationMarker = L.marker([this.selectedLat, this.selectedLng]) + .addTo(this.confirmationMap) + .bindPopup('Your delivery location') + .openPopup(); + } + }, 100); + } + + loadCartItems(): void { + const savedCart = localStorage.getItem('cart'); + this.cartItems = savedCart ? JSON.parse(savedCart) : []; + } + + loadUserData(): void { + const savedUser = localStorage.getItem('user'); + if (savedUser) { + this.user = JSON.parse(savedUser); + } + } + + generateOrderNumber(): void { + const timestamp = new Date().getTime().toString().slice(-6); + const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); + this.orderNumber = `ORD-${timestamp}-${random}`; + } + + toggleCartPreview(): void { + this.isCartPreviewOpen = !this.isCartPreviewOpen; + } + + toggleItemsCollapse(): void { + this.isItemsCollapsed = !this.isItemsCollapsed; + } + + toggleOrderSummaryItems(): void { + this.isOrderSummaryItemsVisible = !this.isOrderSummaryItemsVisible; + } + + // UPDATED: Enhanced location selection method + onLocationSelected(coords: { lat: number; lng: number }): void { + this.selectedLat = coords.lat; + this.selectedLng = coords.lng; + + // Update the form with coordinates + this.deliveryForm.patchValue({ + latitude: coords.lat, + longitude: coords.lng + }); + + // Optionally: Try to reverse geocode to get address + this.getAddressFromCoordinates(coords.lat, coords.lng); + } + + // UPDATED: Enhanced address lookup with loading state + getAddressFromCoordinates(lat: number, lng: number): void { + // In a real application, you would make an API call to a geocoding service + // For now, we'll just simulate this with a placeholder + + // Simulate a loading state + this.locationAddress = 'Loading address...'; + + // Simulate API call delay + setTimeout(() => { + this.locationAddress = 'Location near Colombo, Sri Lanka'; + }, 1000); + } + + // Close dropdown when clicking elsewhere + @HostListener('document:click', ['$event']) + onDocumentClick(event: MouseEvent): void { + const cartPreviewElement = (event.target as HTMLElement).closest('.cart-dropdown'); + const cartToggleButton = (event.target as HTMLElement).closest('.cart-toggle'); + + if (!cartPreviewElement && !cartToggleButton && this.isCartPreviewOpen) { + this.isCartPreviewOpen = false; + } + } + + getTotalItems(): number { + return this.cartItems.reduce((total, item) => total + item.quantity, 0); + } + + getSubtotal(): string { + const subtotal = this.cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); + return subtotal.toFixed(2); + } + + calculateTax(): number { + const subtotal = parseFloat(this.getSubtotal()); + return (subtotal * this.taxRate) / 100; + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const tax = this.calculateTax(); + const total = subtotal + tax + this.shippingCost - this.discountAmount; + return total.toFixed(2); + } + + applyDiscount(): void { + // Example discount codes + const discountCodes = { + 'SAVE10': 10, + 'WELCOME15': 15, + 'SPRING20': 20 + }; + + const code = this.discountCode.toUpperCase(); + + // Check if valid discount code + if (code && Object.keys(discountCodes).includes(code)) { + const percentage = discountCodes[code as keyof typeof discountCodes]; + const subtotal = parseFloat(this.getSubtotal()); + this.discountAmount = (subtotal * percentage) / 100; + this.discountApplied = true; + + // Show success alert or notification + alert(`Discount of ${percentage}% applied successfully!`); + } else { + this.discountApplied = false; + this.discountAmount = 0; + + // Show error alert or notification + alert('Invalid discount code. Please try another code.'); + } + } + + 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 { + localStorage.setItem('cart', JSON.stringify(this.cartItems)); + } + + proceedToDelivery(): void { + if (this.cartItems.length > 0) { + this.currentStep = 'delivery'; + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + } + + canProceedFromDelivery(): boolean { + return this.deliveryForm.valid && this.selectedLat !== null && this.selectedLng !== null; + } + + proceedToPayment(): void { + if (this.canProceedFromDelivery()) { + this.currentStep = 'payment'; + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + } + + completeOrder(): void { + if (this.paymentForm.valid && this.cartItems.length > 0) { + // Here we would typically submit the order to the backend + // Include the delivery location coordinates + + const orderData = { + items: this.cartItems, + delivery: { + ...this.deliveryForm.value, + coordinates: { + lat: this.selectedLat, + lng: this.selectedLng + } + }, + payment: this.paymentForm.value, + total: parseFloat(this.getTotal()), + orderNumber: this.orderNumber + }; + + console.log('Order data to submit:', orderData); + + // For demo purposes, we just proceed to confirmation + this.currentStep = 'confirmation'; + + // Clear cart after successful order + this.cartItems = []; + localStorage.removeItem('cart'); + + // Initialize the confirmation map to show delivery location + this.initConfirmationMap(); + + // Scroll to top + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + } + + continueShopping(): void { + this.router.navigate(['/dashboard/vendor/products']); + } +} \ 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..93fd4bb --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -0,0 +1,142 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ 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: +

+ {{ item.quantity }} +

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

Your cart is currently empty.

+ + Browse Products + +
s +
+
+
+ + +
+
+ © 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..2c22934 --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.ts @@ -0,0 +1,77 @@ +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/create-order/create-order.component.css b/src/app/components/vendor-page/create-order/create-order.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/create-order/create-order.component.html b/src/app/components/vendor-page/create-order/create-order.component.html new file mode 100644 index 0000000..d7181ce --- /dev/null +++ b/src/app/components/vendor-page/create-order/create-order.component.html @@ -0,0 +1,50 @@ +
+

Create Order

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ 📍 Selected Location: +
+ Latitude: {{ selectedLat }} +
+ Longitude: {{ selectedLng }} +
+ + +
+ +
+ {{ responseMessage }} +
+ +
+ {{ errorMessage }} +
+
diff --git a/src/app/components/vendor-page/create-order/create-order.component.spec.ts b/src/app/components/vendor-page/create-order/create-order.component.spec.ts new file mode 100644 index 0000000..c737c2c --- /dev/null +++ b/src/app/components/vendor-page/create-order/create-order.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateOrderComponent } from './create-order.component'; + +describe('CreateOrderComponent', () => { + let component: CreateOrderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CreateOrderComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CreateOrderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/create-order/create-order.component.ts b/src/app/components/vendor-page/create-order/create-order.component.ts new file mode 100644 index 0000000..8268bd4 --- /dev/null +++ b/src/app/components/vendor-page/create-order/create-order.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { MapSelectorComponent } from '../map-selector/map-selector.component'; + +@Component({ + selector: 'app-create-order', + standalone: true, + imports: [ReactiveFormsModule, CommonModule, MapSelectorComponent], + templateUrl: './create-order.component.html', + styleUrls: ['./create-order.component.css'] +}) +export class CreateOrderComponent { + orderForm: FormGroup; + responseMessage: string = ''; + errorMessage: string = ''; + + selectedLat: number | null = null; + selectedLng: number | null = null; + + constructor(private fb: FormBuilder, private http: HttpClient) { + this.orderForm = this.fb.group({ + order_id: [''], + status: [''], + timestamp: [''], + document: [null] + }); + } + + onFileChange(event: any) { + const file = event.target.files[0]; + this.orderForm.patchValue({ document: file }); + } + + onLocationSelected(coords: { lat: number; lng: number }) { + this.selectedLat = coords.lat; + this.selectedLng = coords.lng; + } + + submitForm() { + const formData = new FormData(); + formData.append('order_id', this.orderForm.value.order_id); + formData.append('status', this.orderForm.value.status); + formData.append('timestamp', this.orderForm.value.timestamp); + formData.append('document', this.orderForm.value.document); + + if (this.selectedLat !== null && this.selectedLng !== null) { + formData.append('latitude', this.selectedLat.toString()); + formData.append('longitude', this.selectedLng.toString()); + } + + this.http.post('http://127.0.0.1:8000/api/create-order/', formData).subscribe({ + next: (res: any) => { + this.responseMessage = `✅ Order Created! CID: ${res.cid}`; + this.errorMessage = ''; + }, + error: (err) => { + this.errorMessage = `❌ ${err.error?.error || 'Failed to create order.'}`; + this.responseMessage = ''; + } + }); + } +} diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.css b/src/app/components/vendor-page/map-selector/map-selector.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.html b/src/app/components/vendor-page/map-selector/map-selector.component.html new file mode 100644 index 0000000..a1acd48 --- /dev/null +++ b/src/app/components/vendor-page/map-selector/map-selector.component.html @@ -0,0 +1,7 @@ +
+ +
+ 📍 Selected Location:
+ Latitude: {{ lat }}
+ Longitude: {{ lng }} +
diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.spec.ts b/src/app/components/vendor-page/map-selector/map-selector.component.spec.ts new file mode 100644 index 0000000..7d64541 --- /dev/null +++ b/src/app/components/vendor-page/map-selector/map-selector.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MapSelectorComponent } from './map-selector.component'; + +describe('MapSelectorComponent', () => { + let component: MapSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MapSelectorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MapSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.ts b/src/app/components/vendor-page/map-selector/map-selector.component.ts new file mode 100644 index 0000000..3924e2e --- /dev/null +++ b/src/app/components/vendor-page/map-selector/map-selector.component.ts @@ -0,0 +1,77 @@ +import { Component, ElementRef, ViewChild, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import * as L from 'leaflet'; +//import 'leaflet/dist/images/marker-icon.png'; +//import 'leaflet/dist/images/marker-shadow.png'; + +// Fix Leaflet marker icon paths +delete (L.Icon.Default.prototype as any)._getIconUrl; + +L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'assets/marker-icon-2x.png', + iconUrl: 'assets/marker-icon.png', + shadowUrl: 'assets/marker-shadow.png', +}); + + +@Component({ + selector: 'app-map-selector', + standalone: true, + imports: [], + templateUrl: './map-selector.component.html', + styleUrls: ['./map-selector.component.css'] +}) +export class MapSelectorComponent implements AfterViewInit { + @ViewChild('mapDiv') mapDiv!: ElementRef; + + @Output() locationSelected = new EventEmitter<{ lat: number; lng: number }>(); + + map!: L.Map; + marker!: L.Marker; + lat = 7.8731; + lng = 80.7718; + + ngAfterViewInit(): void { + this.map = L.map(this.mapDiv.nativeElement).setView([this.lat, this.lng], 7); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(this.map); + + // 🌍 Attempt to get user location + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + const userLat = position.coords.latitude; + const userLng = position.coords.longitude; + + this.lat = userLat; + this.lng = userLng; + this.map.setView([userLat, userLng], 15); + + // 📍 Add initial marker at user's location + this.marker = L.marker([userLat, userLng]).addTo(this.map); + this.locationSelected.emit({ lat: userLat, lng: userLng }); + }, + (error) => { + console.warn('Geolocation failed or denied:', error); + // fallback view already applied + } + ); + } + + this.map.on('click', (e: L.LeafletMouseEvent) => { + const { lat, lng } = e.latlng; + + if (this.marker) { + this.map.removeLayer(this.marker); + } + + this.marker = L.marker([lat, lng]).addTo(this.map); + this.lat = lat; + this.lng = lng; + + // 🔥 Emit the selected coordinates + this.locationSelected.emit({ lat, lng }); + }); + } +} \ 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..6323bbf --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.ts @@ -0,0 +1,191 @@ +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 '../../../service/order/order.service'; + +interface Order { + id: string; + date: Date; + itemsCount: number; + total: number; + status: 'processing' | 'shipped' | 'delivered' | 'cancelled'; + trackingNumber?: string; +} + +interface User { + name: string; +} + +@Component({ + selector: 'app-orders', + standalone: true, + imports: [CommonModule, FormsModule, RouterModule], + templateUrl: './orders.component.html', + styleUrl: './orders.component.css' +}) +export class OrdersComponent implements OnInit { + user: User = { name: 'John Doe' }; + + orders: Order[] = []; + filteredOrders: Order[] = []; + + currentPage = 1; + itemsPerPage = 5; + totalPages = 1; + pageNumbers: number[] = []; + paginationStart = 0; + paginationEnd = 0; + + searchQuery = ''; + statusFilter = 'all'; + dateFilter = 'all'; + + constructor(private orderService: OrderService) {} + + ngOnInit(): void { + this.loadOrdersFromServer(2); // You can pass vendor ID dynamically + } + + 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(); + }, + 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 { + 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/product-section/product-section.component.css b/src/app/components/vendor-page/product-section/product-section.component.css new file mode 100644 index 0000000..d74b792 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.css @@ -0,0 +1,26 @@ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes slideInRight { + from { opacity: 0; transform: translateX(30px); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes slideUp { + from { opacity: 0; transform: translateY(30px); } + to { opacity: 1; transform: translateY(0); } +} + +.animate-fade-in { + animation: fadeIn 0.3s ease-out; +} + +.animate-slide-in-right { + animation: slideInRight 0.3s ease-out; +} + +.animate-slide-up { + animation: slideUp 0.4s ease-out; +} \ No newline at end of file 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..83d727f --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.html @@ -0,0 +1,199 @@ +
+
+ +
+
+
+ + + +
+
+ + +
+ + + +
+
+ Premium Spice Collection +
+

+ Discover Our Authentic Flavors +

+

+ Handcrafted blends from around the world +

+
+
+
+ + +
+ @for (notification of notifications(); track notification.id) { +
+
+ + + + + + + + + + + + + + + {{ notification.message }} +
+ +
+ } +
+ + + +
+ @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..3419d65 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.ts @@ -0,0 +1,167 @@ +import { CommonModule } from '@angular/common'; +import { Component, signal, OnInit, OnDestroy } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { ProductService, Product, ApiProduct } from '../../../service/warehouse/product.service'; + +interface CartItem { + name: string; + subtitle: string; + price: number; + image: string; + quantity: number; +} + +interface Notification { + message: string; + type: 'success' | 'error' | 'info'; + id: 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 { + 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; + + // Cart state + cartCount = signal(0); + cartItems = signal([]); + + // Notification state + notifications = signal([]); + private notificationIdCounter = 0; + + products: Product[] = []; // Store products dynamically + + 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(product => product.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; + } + + // Method to show notifications + showNotification(message: string, type: 'success' | 'error' | 'info' = 'success') { + const id = this.notificationIdCounter++; + const notification = { message, type, id }; + + // Add notification to array + const currentNotifications = this.notifications(); + this.notifications.set([...currentNotifications, notification]); + + // Auto-dismiss after 3 seconds + setTimeout(() => { + this.dismissNotification(id); + }, 3000); + } + + dismissNotification(id: number) { + const currentNotifications = this.notifications(); + this.notifications.set(currentNotifications.filter(n => n.id !== id)); + } + + // Updated to match HTML parameters + 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, item) => sum + item.quantity, 0)); + this.saveCart(); + + // Show success notification + this.showNotification(`Added ${this.selectedQuantity} ${product.name} to cart!`); + + 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 + 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/service/order/order.service.ts b/src/app/service/order/order.service.ts new file mode 100644 index 0000000..2d30792 --- /dev/null +++ b/src/app/service/order/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}/`); + } +} diff --git a/src/app/service/warehouse/product.service.ts b/src/app/service/warehouse/product.service.ts new file mode 100644 index 0000000..688d03a --- /dev/null +++ b/src/app/service/warehouse/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); + } + + + + +} diff --git a/src/assets/map-pin.png b/src/assets/map-pin.png new file mode 100644 index 0000000000000000000000000000000000000000..373e50c4d17a2180c8413a5b03109c1dcc098f3c GIT binary patch literal 113930 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 E0LSqKeE Date: Sat, 10 May 2025 18:23:49 +0530 Subject: [PATCH 3/3] added maps and other libraries --- .../map-selector/map-selector.component.css | 6 ++ .../map-selector/map-selector.component.html | 7 +- .../map-selector/map-selector.component.ts | 64 +++++++++++------- src/assets/marker-icon-2x.png | Bin 0 -> 2464 bytes src/assets/marker-icon.png | Bin 0 -> 1466 bytes src/assets/marker-shadow.png | Bin 0 -> 618 bytes 6 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 src/assets/marker-icon-2x.png create mode 100644 src/assets/marker-icon.png create mode 100644 src/assets/marker-shadow.png diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.css b/src/app/components/vendor-page/map-selector/map-selector.component.css index e69de29..b79ef73 100644 --- a/src/app/components/vendor-page/map-selector/map-selector.component.css +++ b/src/app/components/vendor-page/map-selector/map-selector.component.css @@ -0,0 +1,6 @@ +/* Optional: Make map full width and responsive */ +:host { + display: block; + width: 100%; + } + \ No newline at end of file diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.html b/src/app/components/vendor-page/map-selector/map-selector.component.html index a1acd48..77b1f74 100644 --- a/src/app/components/vendor-page/map-selector/map-selector.component.html +++ b/src/app/components/vendor-page/map-selector/map-selector.component.html @@ -1,7 +1,10 @@
+ +
- 📍 Selected Location:
- Latitude: {{ lat }}
+ Latitude: {{ lat }}
Longitude: {{ lng }}
diff --git a/src/app/components/vendor-page/map-selector/map-selector.component.ts b/src/app/components/vendor-page/map-selector/map-selector.component.ts index 3924e2e..ce4f7d2 100644 --- a/src/app/components/vendor-page/map-selector/map-selector.component.ts +++ b/src/app/components/vendor-page/map-selector/map-selector.component.ts @@ -1,18 +1,13 @@ -import { Component, ElementRef, ViewChild, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import { Component, ElementRef, ViewChild, AfterViewInit, Output, EventEmitter, NgZone } from '@angular/core'; import * as L from 'leaflet'; -//import 'leaflet/dist/images/marker-icon.png'; -//import 'leaflet/dist/images/marker-shadow.png'; -// Fix Leaflet marker icon paths delete (L.Icon.Default.prototype as any)._getIconUrl; - L.Icon.Default.mergeOptions({ iconRetinaUrl: 'assets/marker-icon-2x.png', iconUrl: 'assets/marker-icon.png', shadowUrl: 'assets/marker-shadow.png', }); - @Component({ selector: 'app-map-selector', standalone: true, @@ -22,7 +17,6 @@ L.Icon.Default.mergeOptions({ }) export class MapSelectorComponent implements AfterViewInit { @ViewChild('mapDiv') mapDiv!: ElementRef; - @Output() locationSelected = new EventEmitter<{ lat: number; lng: number }>(); map!: L.Map; @@ -30,6 +24,8 @@ export class MapSelectorComponent implements AfterViewInit { lat = 7.8731; lng = 80.7718; + constructor(private zone: NgZone) {} + ngAfterViewInit(): void { this.map = L.map(this.mapDiv.nativeElement).setView([this.lat, this.lng], 7); @@ -37,41 +33,61 @@ export class MapSelectorComponent implements AfterViewInit { attribution: '© OpenStreetMap contributors' }).addTo(this.map); - // 🌍 Attempt to get user location + // Listen for map clicks + this.map.on('click', (e: L.LeafletMouseEvent) => { + const { lat, lng } = e.latlng; + this.updateMarker(lat, lng, '📍 Selected location'); + }); + } + + getUserLocation(): void { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const userLat = position.coords.latitude; const userLng = position.coords.longitude; - this.lat = userLat; - this.lng = userLng; + this.zone.run(() => { + this.lat = userLat; + this.lng = userLng; + }); + this.map.setView([userLat, userLng], 15); + this.updateMarker(userLat, userLng, '📍 You are here'); + + // Optional circle to show accuracy + L.circle([userLat, userLng], { + radius: position.coords.accuracy, + color: 'blue', + fillOpacity: 0.2 + }).addTo(this.map); - // 📍 Add initial marker at user's location - this.marker = L.marker([userLat, userLng]).addTo(this.map); this.locationSelected.emit({ lat: userLat, lng: userLng }); }, (error) => { - console.warn('Geolocation failed or denied:', error); - // fallback view already applied + console.warn('Geolocation failed:', error); + alert('Unable to get location. Please allow location access.'); } ); + } else { + alert('Geolocation not supported by your browser.'); } + } - this.map.on('click', (e: L.LeafletMouseEvent) => { - const { lat, lng } = e.latlng; + private updateMarker(lat: number, lng: number, label: string): void { + if (this.marker) { + this.map.removeLayer(this.marker); + } - if (this.marker) { - this.map.removeLayer(this.marker); - } + this.marker = L.marker([lat, lng]).addTo(this.map) + .bindPopup(label) + .openPopup(); - this.marker = L.marker([lat, lng]).addTo(this.map); + this.zone.run(() => { this.lat = lat; this.lng = lng; - - // 🔥 Emit the selected coordinates - this.locationSelected.emit({ lat, lng }); }); + + this.locationSelected.emit({ lat, lng }); } -} \ No newline at end of file +} diff --git a/src/assets/marker-icon-2x.png b/src/assets/marker-icon-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..88f9e501888c9c6cb29ad340d9a888627dd1b6d8 GIT binary patch literal 2464 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 e-I|>x8~*?ZhF3snGbtJX0000cOl4 literal 0 HcmV?d00001 diff --git a/src/assets/marker-icon.png b/src/assets/marker-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..950edf24677ded147df13b26f91baa2b0fa70513 GIT binary patch literal 1466 zcmV;r1x5OaP)P001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001