From 308a7f0ff8a39dcc70a761ab08f97753973a3338 Mon Sep 17 00:00:00 2001
From: ChehanHelitha <162694727+ChehanHelitha@users.noreply.github.com>
Date: Sat, 10 May 2025 13:57:32 +0530
Subject: [PATCH 1/6] Connecting current inventory to backend
---
src/app/app.config.ts | 2 +
.../inventory/inventory.component.css | 11 +++
.../inventory/inventory.component.html | 37 +++++++-
.../inventory/inventory.component.ts | 89 +++++++++++++------
.../supplier/supplier.component.html | 8 +-
.../supplier/supplier.component.ts | 4 +-
6 files changed, 111 insertions(+), 40 deletions(-)
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index e36d0ad..247be02 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,6 +1,7 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideRouter } from '@angular/router';
+import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { routes } from './app.routes';
@@ -9,6 +10,7 @@ export const appConfig: ApplicationConfig = {
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideAnimations(),
+ provideHttpClient(withInterceptorsFromDi()),
]
};
diff --git a/src/app/components/supplier-page/inventory/inventory.component.css b/src/app/components/supplier-page/inventory/inventory.component.css
index e69de29..b310323 100644
--- a/src/app/components/supplier-page/inventory/inventory.component.css
+++ b/src/app/components/supplier-page/inventory/inventory.component.css
@@ -0,0 +1,11 @@
+.transactions-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.header p {
+ color: #666;
+ font-size: 16px;
+}
\ No newline at end of file
diff --git a/src/app/components/supplier-page/inventory/inventory.component.html b/src/app/components/supplier-page/inventory/inventory.component.html
index cd48158..894b938 100644
--- a/src/app/components/supplier-page/inventory/inventory.component.html
+++ b/src/app/components/supplier-page/inventory/inventory.component.html
@@ -1,7 +1,25 @@
-
-
-
Current Inventory
-
+
+
+
+
+
+
+
+
Loading inventory...
+
+
+
+
+
+
+ Error!
+ {{ errorMessage }}
+
+
+
@@ -37,4 +55,15 @@ Current Inventory
+
+
+
No inventory data available.
+
+
+
+
+
+
+ © 2025 Supply Chain Management System. All rights reserved.
+
\ No newline at end of file
diff --git a/src/app/components/supplier-page/inventory/inventory.component.ts b/src/app/components/supplier-page/inventory/inventory.component.ts
index 8638833..29a5962 100644
--- a/src/app/components/supplier-page/inventory/inventory.component.ts
+++ b/src/app/components/supplier-page/inventory/inventory.component.ts
@@ -1,14 +1,23 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule, NgClass } from '@angular/common';
+import { HttpClient } from '@angular/common/http'; // Import HttpClient
+import { Observable, of } from 'rxjs'; // Import Observable and of for error handling
+import { catchError, map } from 'rxjs/operators'; // Import operators
+
+// Interface for the data structure coming from your API
+interface ApiInventoryItem {
+ product_name: string;
+ SKU: string;
+ warehouse: string;
+ Quantity_on_hand: number;
+}
-// Optional: Define an interface for better type safety
interface InventoryItem {
productName: string;
sku: string;
warehouseLocation: string;
quantity: number;
- status: string; // Or a specific type like 'Available' | 'Low Stock'
- // Add any other properties your items have
+ status: 'Available' | 'Low Stock' | 'Out of Stock'; // Specific status types
}
@Component({
@@ -19,39 +28,65 @@ interface InventoryItem {
styleUrl: './inventory.component.css'
})
-// Implement OnInit if you fetch data when the component loads
export class InventoryComponent implements OnInit {
- // --- ADD THIS LINE ---
- // Initialize as an empty array. Use 'any[]' for now, or the interface
inventoryItems: InventoryItem[] = [];
- // OR if you don't want strict typing yet:
- // inventoryItems: any[] = [];
+ isLoading: boolean = false; // To show a loading indicator
+ errorMessage: string | null = null; // To display any API errors
+
+ // API URL -
+ // In a real app, the base URL and supplier_id might come from environment variables or a service
+ private apiUrl = 'http://localhost:8000/api/warehouse/supplier-dashboard/?supplier_id=103';
- // Constructor (Inject services here if needed)
- constructor(/* private inventoryService: InventoryService */) { }
+ constructor(private http: HttpClient) { } // Inject HttpClient
- // ngOnInit is a good place to fetch initial data
ngOnInit(): void {
- this.loadInventory(); // Call a method to load data
+ this.loadInventory();
}
loadInventory(): void {
- // Here you would typically call a service to get the data
- // For example:
- // this.inventoryService.getInventory().subscribe(data => {
- // this.inventoryItems = data;
- // });
-
- // --- TEMPORARY Placeholder Data (Remove when using a service) ---
- this.inventoryItems = [
- { productName: 'Turmeric Powder', sku: 'LP123', warehouseLocation: 'Main WH', quantity: 50, status: 'Available' },
- { productName: 'Garam Masala', sku: 'WM456', warehouseLocation: 'Main WH', quantity: 5, status: 'Low Stock' },
- { productName: 'Cinnamon Sticks', sku: 'KB789', warehouseLocation: 'Secondary WH', quantity: 0, status: 'Out of Stock' }
- ];
- // --- END Placeholder ---
- }
+ this.isLoading = true;
+ this.errorMessage = null;
+ this.inventoryItems = []; // Clear previous items
+
+ this.http.get
(this.apiUrl).pipe(
+ map(apiData => {
+ // Transform API data to our InventoryItem structure and calculate status
+ return apiData.map(apiItem => {
+ let currentStatus: 'Available' | 'Low Stock' | 'Out of Stock';
+ const quantity = apiItem.Quantity_on_hand;
- // ... other methods if needed
+ if (quantity === 0) {
+ currentStatus = 'Out of Stock';
+ } else if (quantity > 0 && quantity <= 150000) { // Between 0 (exclusive) and 150000 (inclusive)
+ currentStatus = 'Low Stock';
+ } else { // quantity > 150000
+ currentStatus = 'Available';
+ }
+ return {
+ productName: apiItem.product_name,
+ sku: apiItem.SKU,
+ warehouseLocation: apiItem.warehouse,
+ quantity: quantity,
+ status: currentStatus
+ };
+ });
+ }),
+ catchError(error => {
+ console.error('Error fetching inventory data:', error);
+ this.errorMessage = 'Failed to load inventory data. Please try again later.';
+ return of([]); // Return an empty array on error to prevent breaking the UI
+ })
+ ).subscribe({
+ next: (processedData) => {
+ this.inventoryItems = processedData;
+ this.isLoading = false;
+ },
+ error: () => {
+ // Error handling is already done in catchError, but you can add more here if needed
+ this.isLoading = false;
+ }
+ });
+ }
}
diff --git a/src/app/components/supplier-page/supplier/supplier.component.html b/src/app/components/supplier-page/supplier/supplier.component.html
index 6296999..e92dc67 100644
--- a/src/app/components/supplier-page/supplier/supplier.component.html
+++ b/src/app/components/supplier-page/supplier/supplier.component.html
@@ -17,7 +17,7 @@
diff --git a/src/app/components/supplier-page/supplier/supplier.component.ts b/src/app/components/supplier-page/supplier/supplier.component.ts
index 26df43a..ab5127b 100644
--- a/src/app/components/supplier-page/supplier/supplier.component.ts
+++ b/src/app/components/supplier-page/supplier/supplier.component.ts
@@ -15,8 +15,8 @@ interface Tab {
})
export class SupplierDashboard {
user = {
- name: "John Smith",
- role: "Warehouse Manager",
+ name: "Samuel Johnson",
+ role: "Supplier",
employeeId: "LM-2023-089",
department: "Logistics",
location: "North Distribution Center",
From ed60396b08a2f911edc91b2d2374afeda11a3649 Mon Sep 17 00:00:00 2001
From: ChehanHelitha <162694727+ChehanHelitha@users.noreply.github.com>
Date: Sat, 10 May 2025 17:39:41 +0530
Subject: [PATCH 2/6] Connecting current-requests to bakend
---
.../current-requests.component.html | 123 ++++++---
.../current-requests.component.ts | 240 +++++++++++-------
2 files changed, 241 insertions(+), 122 deletions(-)
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.html b/src/app/components/supplier-page/current-requests/current-requests.component.html
index 0218bf1..060b17e 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.html
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.html
@@ -1,59 +1,120 @@
-
Current Requests & Deadlines
-
+
Current Requests & Deadlines
+
+
+
+
Loading requests...
+
+
+
+
+
+ Error!
+ {{ errorMessage }}
+
+
+
+
0">
- Request ID (PO#)
+ Request ID
Request Date
Product
- Quantity Requested
- Required Delivery Date
+ Quantity
+ Delivery Deadline
Status
Actions
-
{{ request.id }}
{{ request.requestDate | date:'shortDate' }}
{{ request.productName }}
- {{ request.quantity }}
- {{ request.deadline | date:'shortDate' }}
+ {{ request.quantity | number }}
+
+ {{ request.deadline | date:'shortDate' }}
+
-
- {{ request.status }}
-
+
+ {{ request.status }}
+
-
-
- Accept
- Reject
-
- Details
+
+
+ Accept
+
+
+ Reject
+
+
+ Details
-
-
+
+
No current requests found.
-
-
-
-
-
- © 2025 Supply Chain Management System. All rights reserved.
+
+
+
+
+
+
+
Request Details (ID: {{ selectedRequestDetails.id }})
+ ×
+
+
+
+
Product: {{ selectedRequestDetails.productName }}
+
Quantity: {{ selectedRequestDetails.quantity | number }}
+
Request Date: {{ selectedRequestDetails.requestDate | date:'medium' }}
+
Delivery Deadline: {{ selectedRequestDetails.deadline | date:'medium' }}
+
Current Status:
+
+ {{ selectedRequestDetails.status }}
+
-
\ No newline at end of file
+
+ Supplier Name: {{ raw.supplier_name }}
+ Supplier ID: {{ raw.supplier_id }}
+ Product ID: {{ raw.product_id }}
+ Warehouse ID: {{ raw.warehouse_id }}
+ Unit Price: {{ raw.unit_price | currency:'USD':'symbol':'1.2-2' }}
+ Received At: {{ raw.received_at | date:'medium' }}
+ Quality: {{ raw.quality }}
+ Is Defective: {{ raw.is_defective ? 'Yes' : 'No' }}
+
+
+
+
{{ selectedRequestDetails.rawApiData | json }}
+
+
+ Close
+
+
+
+
+
+
+
+
+ © 2025 Supply Chain Management System. All rights reserved.
+
+
\ No newline at end of file
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.ts b/src/app/components/supplier-page/current-requests/current-requests.component.ts
index dcc97fa..a5e6603 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.ts
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.ts
@@ -1,131 +1,189 @@
import { Component, OnInit } from '@angular/core';
-import { CommonModule } from '@angular/common'; // <--- IMPORT THIS
+import { CommonModule, DatePipe } from '@angular/common'; // DatePipe for formatting
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+import { Observable, of, throwError } from 'rxjs';
+import { catchError, map, switchMap, tap } from 'rxjs/operators';
-// Interface to define the structure of a request item
+// Interface for the data structure from your API
+interface ApiRequestItem {
+ request_id: number;
+ supplier_id: number;
+ created_at: string; // ISO date string
+ expected_delivery_date: string; // ISO date string
+ product_id: number;
+ count: number;
+ status: 'pending' | 'accepted' | 'rejected' | 'received' | 'returned'; // API status values
+ received_at: string | null;
+ warehouse_id: number;
+ unit_price: number | null;
+ quality: string | null;
+ is_defective: boolean | null;
+ supplier_name: string;
+ product_name: string;
+}
+
+// Your internal interface for display and component logic
interface RequestItem {
- id: string;
- requestDate: Date | string;
+ id: number; // request_id
+ requestDate: Date;
productName: string;
quantity: number;
- deadline: Date | string;
- status: 'New' | 'Accepted' | 'Rejected' | 'Shipped' | 'Processing';
+ deadline: Date;
+ status: 'Pending' | 'Accepted' | 'Rejected' | 'Received' | 'Returned'; // UI display status
+ rawApiData?: ApiRequestItem;
+ supplierId?: number;
+ productId?: number;
+ receivedAt?: Date | null;
+ warehouseId?: number;
+ unitPrice?: number | null;
+ quality?: string | null;
+ isDefective?: boolean | null;
+ supplierName?: string;
}
@Component({
selector: 'app-current-requests',
- standalone: true, // <--- MAKE SURE THIS IS TRUE
- imports: [CommonModule], // <--- ADD THIS IMPORTS ARRAY WITH CommonModule
+ standalone: true,
+ imports: [CommonModule],
templateUrl: './current-requests.component.html',
- styleUrls: ['./current-requests.component.css']
+ styleUrls: ['./current-requests.component.css'],
+ providers: [DatePipe]
})
export class CurrentRequestsComponent implements OnInit {
-
currentRequests: RequestItem[] = [];
+ isLoading: boolean = false;
+ errorMessage: string | null = null;
+ selectedRequestDetails: RequestItem | null = null;
+ isModalOpen: boolean = false;
+
+ private supplierId = 103; // Example supplier ID - get this dynamically in a real app
- constructor() { }
+ // --- API URL defined directly in the component ---
+ private readonly ORDER_MANAGEMENT_API_BASE_URL = 'http://localhost:YOUR_ORDER_API_PORT/api';
+
+ private apiUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}`;
+
+ constructor(private http: HttpClient, public datePipe: DatePipe) {}
ngOnInit(): void {
- this.loadHardcodedRequests();
+ this.loadRequests();
}
- loadHardcodedRequests(): void {
- const today = new Date();
- const yesterday = new Date(today);
- yesterday.setDate(today.getDate() - 1);
- const tomorrow = new Date(today);
- tomorrow.setDate(today.getDate() + 1);
- const nextWeek = new Date(today);
- nextWeek.setDate(today.getDate() + 7);
-
- // Ensure dates are consistently Dates or consistently strings if needed
- // Using Date objects is generally better for date logic
- this.currentRequests = [
- {
- id: 'PO-1001',
- requestDate: yesterday, // Keep as Date object
- productName: 'Cumin Seeds',
- quantity: 50,
- deadline: tomorrow, // Keep as Date object
- status: 'New'
- },
- {
- id: 'PO-1002',
- requestDate: new Date(new Date().setDate(new Date().getDate() - 5)), // Use new Date() to avoid modifying 'today'
- productName: 'Cloves',
- quantity: 10,
- deadline: nextWeek, // Keep as Date object
- status: 'Accepted'
- },
- {
- id: 'PO-1003',
- requestDate: new Date(new Date().setDate(new Date().getDate() - 10)),
- productName: 'Cinnamon Sticks',
- quantity: 200,
- deadline: yesterday, // Re-use yesterday variable if it's correct scope, or recalculate
- status: 'Processing'
- },
- {
- id: 'PO-1004',
- requestDate: new Date(new Date().setDate(new Date().getDate() - 2)),
- productName: 'Garam Masala',
- quantity: 5,
- deadline: new Date(new Date().setDate(new Date().getDate() + 3)),
- status: 'New'
- },
- {
- id: 'PO-1005',
- requestDate: new Date(new Date().setDate(new Date().getDate() - 15)),
- productName: 'Chili Powder',
- quantity: 25,
- deadline: new Date(new Date().setDate(new Date().getDate() - 8)),
- status: 'Accepted'
+ loadRequests(): void {
+ this.isLoading = true;
+ this.errorMessage = null;
+ this.currentRequests = [];
+
+ this.http.get
(this.apiUrl).pipe(
+ map(apiDataArray => {
+ return apiDataArray.map(apiItem => this.transformApiItemToRequestItem(apiItem));
+ }),
+ catchError((error: HttpErrorResponse) => {
+ console.error('Error fetching requests:', error);
+ this.errorMessage = `Failed to load requests: ${error.statusText || 'Unknown error'}`;
+ return of([]);
+ })
+ ).subscribe({
+ next: (processedData) => {
+ this.currentRequests = processedData;
+ this.isLoading = false;
},
- {
- id: 'PO-1006',
- requestDate: new Date(new Date().setDate(new Date().getDate() - 3)),
- productName: 'Cardamom Pods',
- quantity: 30,
- deadline: new Date(new Date().setDate(new Date().getDate() + 10)),
- status: 'Rejected'
+ error: () => {
+ this.isLoading = false;
}
- ];
- // It's better to recalculate dates if modifying them in loops/assignments
+ });
+ }
+
+ private transformApiItemToRequestItem(apiItem: ApiRequestItem): RequestItem {
+ const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+ return {
+ id: apiItem.request_id,
+ requestDate: new Date(apiItem.created_at),
+ productName: apiItem.product_name,
+ quantity: apiItem.count,
+ deadline: new Date(apiItem.expected_delivery_date),
+ status: capitalize(apiItem.status) as RequestItem['status'],
+ rawApiData: apiItem,
+ supplierId: apiItem.supplier_id,
+ productId: apiItem.product_id,
+ receivedAt: apiItem.received_at ? new Date(apiItem.received_at) : null,
+ warehouseId: apiItem.warehouse_id,
+ unitPrice: apiItem.unit_price,
+ quality: apiItem.quality,
+ isDefective: apiItem.is_defective,
+ supplierName: apiItem.supplier_name
+ };
}
- isOverdue(deadline: Date | string): boolean {
+ isOverdue(deadline: Date): boolean {
const today = new Date();
today.setHours(0, 0, 0, 0);
- const deadlineDate = new Date(deadline); // Convert string or Date object to Date
+ const deadlineDate = new Date(deadline);
deadlineDate.setHours(0, 0, 0, 0);
- return deadlineDate < today;
+ return deadlineDate < today && !this.isToday(deadlineDate);
+ }
+
+ isToday(date: Date): boolean {
+ const today = new Date();
+ return date.getFullYear() === today.getFullYear() &&
+ date.getMonth() === today.getMonth() &&
+ date.getDate() === today.getDate();
}
- getRequestStatusClass(status: string): string {
+ getRequestStatusClass(status: RequestItem['status']): string {
switch (status) {
- case 'New': return 'bg-blue-100 text-blue-700';
+ case 'Pending': return 'bg-blue-100 text-blue-700';
case 'Accepted': return 'bg-green-100 text-green-700';
- case 'Processing': return 'bg-yellow-100 text-yellow-700';
- case 'Shipped': return 'bg-purple-100 text-purple-700';
case 'Rejected': return 'bg-red-100 text-red-700';
+ case 'Received': return 'bg-purple-100 text-purple-700';
+ case 'Returned': return 'bg-orange-100 text-orange-700';
default: return 'bg-gray-100 text-gray-700';
}
}
- acceptRequest(requestId: string): void {
+ updateRequestStatus(requestId: number, newApiStatus: 'accepted' | 'rejected'): void {
+ this.isLoading = true;
+ // Construct the update URL using the base URL defined in the component
+ const updateUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}/${requestId}`;
+
+ this.http.patch(updateUrl, { status: newApiStatus })
+ .pipe(
+ tap(() => {
+ console.log(`Request ${requestId} status update to ${newApiStatus} successful.`);
+ }),
+ catchError((error: HttpErrorResponse) => {
+ console.error(`Error updating request ${requestId}:`, error);
+ this.errorMessage = `Failed to update request ${requestId}. ${error.message || 'Please try again.'}`;
+ this.isLoading = false;
+ return throwError(() => error);
+ }),
+ switchMap(() => {
+ this.loadRequests();
+ return of(null);
+ })
+ )
+ .subscribe({
+ complete: () => {}
+ });
+ }
+
+ acceptRequest(requestId: number): void {
console.log(`Accepting request: ${requestId}`);
- const request = this.currentRequests.find(r => r.id === requestId);
- if (request) {
- request.status = 'Accepted';
- }
- // TODO: Integrate with backend service and blockchain trigger
+ this.updateRequestStatus(requestId, 'accepted');
}
- rejectRequest(requestId: string): void {
+ rejectRequest(requestId: number): void {
console.log(`Rejecting request: ${requestId}`);
- const request = this.currentRequests.find(r => r.id === requestId);
- if (request) {
- request.status = 'Rejected';
- }
- // TODO: Integrate with backend service and blockchain trigger
+ this.updateRequestStatus(requestId, 'rejected');
+ }
+
+ openDetailsModal(request: RequestItem): void {
+ this.selectedRequestDetails = request;
+ this.isModalOpen = true;
+ }
+
+ closeDetailsModal(): void {
+ this.isModalOpen = false;
+ this.selectedRequestDetails = null;
}
}
\ No newline at end of file
From 7fb62d84dff527e91afee4db9151c4f970b2c13b Mon Sep 17 00:00:00 2001
From: moonlander101
Date: Sat, 10 May 2025 20:44:55 +0530
Subject: [PATCH 3/6] Added new routes and Connected backend to current
inventory
---
src/app/app.routes.ts | 25 ++++++++++----
.../current-requests.component.ts | 33 ++++++++++++++-----
2 files changed, 43 insertions(+), 15 deletions(-)
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 24b2764..ca92e76 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -63,13 +63,24 @@ export const routes: Routes = [
component: ForecastComponent,
pathMatch: 'full',
},
- { path: 'order-history', component: OrderHistoryComponent }, // /dashboard/orders
- { path: 'inventory', component: InventoryComponent }, // /dashboard/inventory
- { path: 'product-management', component: ProductManagementComponent }, // /dashboard/product-management
- { path: 'current-requests', component: CurrentRequestsComponent }, // /dashboard/current-requests
- // { path: 'deliveries', component: DeliveriesComponent }, // /dashboard/deliveries
- // { path: 'vendors', component: VendorsComponent },
- { path: 'supplier', component: SupplierDashboard, pathMatch: 'full' },
+
+ // Supplier Routes
+ {
+ path: 'supplier',
+ // canActivate: [roleGuard(3)],
+ children: [
+ { path: '', redirectTo: 'profile', pathMatch: 'full' },
+ { path: 'order-history', component: OrderHistoryComponent },
+ { path: 'inventory', component: InventoryComponent },
+ { path: 'product-management', component: ProductManagementComponent },
+ { path: 'current-requests', component: CurrentRequestsComponent },
+ // { path: 'deliveries', component: DeliveriesComponent },
+ // { path: 'vendors', component: VendorsComponent },
+ { path: 'supplier', component: SupplierDashboard, pathMatch: 'full' },
+ { path: 'profile', component: ProfileComponent },
+ { path: '**', redirectTo: '', pathMatch: 'full' },
+ ],
+ },
],
},
{ path: '**', redirectTo: 'home' }, // Handle 404/unknown routes
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.ts b/src/app/components/supplier-page/current-requests/current-requests.component.ts
index a5e6603..070a9d3 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.ts
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.ts
@@ -3,6 +3,7 @@ import { CommonModule, DatePipe } from '@angular/common'; // DatePipe for format
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
+import { jwtDecode } from 'jwt-decode';
// Interface for the data structure from your API
interface ApiRequestItem {
@@ -41,6 +42,12 @@ interface RequestItem {
supplierName?: string;
}
+interface JWTPayload {
+ user_id: number
+ username: string
+ role_id: number
+}
+
@Component({
selector: 'app-current-requests',
standalone: true,
@@ -59,22 +66,32 @@ export class CurrentRequestsComponent implements OnInit {
private supplierId = 103; // Example supplier ID - get this dynamically in a real app
// --- API URL defined directly in the component ---
- private readonly ORDER_MANAGEMENT_API_BASE_URL = 'http://localhost:YOUR_ORDER_API_PORT/api';
-
- private apiUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}`;
+ private readonly ORDER_MANAGEMENT_API_BASE_URL = 'http://localhost:8000/api/v0/supplier-request';
+ // private apiUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}`;
constructor(private http: HttpClient, public datePipe: DatePipe) {}
ngOnInit(): void {
- this.loadRequests();
+ let decoded : JWTPayload | null = null;
+ const token = localStorage.getItem("token");
+ if (token) {
+ try {
+ decoded = jwtDecode(token);
+ console.log('Decoded token:', decoded);
+ } catch (error) {
+ console.error('Error decoding token:', error);
+ }
+ }
+ this.supplierId = decoded?.user_id || 1
+ this.loadRequests(decoded?.user_id || 1);
}
- loadRequests(): void {
+ loadRequests(user_id : number): void {
this.isLoading = true;
this.errorMessage = null;
this.currentRequests = [];
- this.http.get(this.apiUrl).pipe(
+ this.http.get(`${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier/${user_id}/`).pipe(
map(apiDataArray => {
return apiDataArray.map(apiItem => this.transformApiItemToRequestItem(apiItem));
}),
@@ -144,7 +161,7 @@ export class CurrentRequestsComponent implements OnInit {
updateRequestStatus(requestId: number, newApiStatus: 'accepted' | 'rejected'): void {
this.isLoading = true;
// Construct the update URL using the base URL defined in the component
- const updateUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}/${requestId}`;
+ const updateUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/${requestId}/status/`;
this.http.patch(updateUrl, { status: newApiStatus })
.pipe(
@@ -158,7 +175,7 @@ export class CurrentRequestsComponent implements OnInit {
return throwError(() => error);
}),
switchMap(() => {
- this.loadRequests();
+ this.loadRequests(this.supplierId);
return of(null);
})
)
From a5c2a1160950dcf8f1ed452827c2d439501d4d91 Mon Sep 17 00:00:00 2001
From: ChehanHelitha <162694727+ChehanHelitha@users.noreply.github.com>
Date: Sat, 10 May 2025 22:19:17 +0530
Subject: [PATCH 4/6] Updating current request page
---
.../current-requests.component.html | 99 ++++++++++++---
.../current-requests.component.ts | 113 +++++++++++++-----
.../product-management.component.html | 1 -
3 files changed, 169 insertions(+), 44 deletions(-)
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.html b/src/app/components/supplier-page/current-requests/current-requests.component.html
index 060b17e..c90f8c8 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.html
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.html
@@ -1,8 +1,24 @@
-
Current Requests & Deadlines
+
Current Requests & Order History
+
+
+
+
+
Pending Orders
+
{{ pendingCount }}
+
+
+
Accepted Orders
+
{{ acceptedCount }}
+
+
+
Delivered Orders
+
{{ receivedCount }}
+
+
-
+
@@ -13,8 +29,9 @@
Current Requests & Deadli
{{ errorMessage }}
-
-
0">
+
+
0">
+
Active Orders
@@ -29,14 +46,14 @@ Current Requests & Deadli
-
+
{{ request.id }}
{{ request.requestDate | date:'shortDate' }}
{{ request.productName }}
{{ request.quantity | number }}
+ [class.text-red-600]="isOverdue(request.deadline) && request.status !== 'Accepted'"
+ [class.text-gray-500]="request.status === 'Accepted'">
{{ request.deadline | date:'shortDate' }}
@@ -59,17 +76,71 @@ Current Requests & Deadli
Details
-
-
- No current requests found.
+
+ No active orders found.
-
-
+
+
0">
+
Order History
+
+
+
+
+ Request ID
+ Request Date
+ Product
+ Quantity
+ Outcome Date
+ Status
+ Actions
+
+
+
+
+ {{ request.id }}
+ {{ request.requestDate | date:'shortDate' }}
+ {{ request.productName }}
+ {{ request.quantity | number }}
+
+
+
+ {{ request.receivedAt | date:'shortDate' }} (Received)
+
+
+
+ {{ request.deadline | date:'shortDate' }} (Expected)
+
+
+ N/A
+
+
+
+
+ {{ request.status }}
+
+
+
+ Details
+
+
+
+ No historical orders found.
+
+
+
+
+
+
+
+
+
+
@@ -96,12 +167,11 @@
Request Details (ID: {{ selecte
Product ID: {{ raw.product_id }}
Warehouse ID: {{ raw.warehouse_id }}
Unit Price: {{ raw.unit_price | currency:'USD':'symbol':'1.2-2' }}
- Received At: {{ raw.received_at | date:'medium' }}
+ Received At: {{ raw.received_at | date:'medium' }}
Quality: {{ raw.quality }}
Is Defective: {{ raw.is_defective ? 'Yes' : 'No' }}
-
{{ selectedRequestDetails.rawApiData | json }}
@@ -111,7 +181,6 @@
Request Details (ID: {{ selecte
-
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.ts b/src/app/components/supplier-page/current-requests/current-requests.component.ts
index 070a9d3..e3ae3b4 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.ts
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { CommonModule, DatePipe } from '@angular/common'; // DatePipe for formatting
+import { CommonModule, DatePipe } from '@angular/common';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
@@ -9,11 +9,11 @@ import { jwtDecode } from 'jwt-decode';
interface ApiRequestItem {
request_id: number;
supplier_id: number;
- created_at: string; // ISO date string
- expected_delivery_date: string; // ISO date string
+ created_at: string;
+ expected_delivery_date: string;
product_id: number;
count: number;
- status: 'pending' | 'accepted' | 'rejected' | 'received' | 'returned'; // API status values
+ status: 'pending' | 'accepted' | 'rejected' | 'received' | 'returned';
received_at: string | null;
warehouse_id: number;
unit_price: number | null;
@@ -25,16 +25,16 @@ interface ApiRequestItem {
// Your internal interface for display and component logic
interface RequestItem {
- id: number; // request_id
+ id: number;
requestDate: Date;
productName: string;
quantity: number;
deadline: Date;
- status: 'Pending' | 'Accepted' | 'Rejected' | 'Received' | 'Returned'; // UI display status
+ status: 'Pending' | 'Accepted' | 'Rejected' | 'Received' | 'Returned';
+ receivedAt: Date | null; // Added for "Delivered Date"
rawApiData?: ApiRequestItem;
supplierId?: number;
productId?: number;
- receivedAt?: Date | null;
warehouseId?: number;
unitPrice?: number | null;
quality?: string | null;
@@ -43,9 +43,9 @@ interface RequestItem {
}
interface JWTPayload {
- user_id: number
- username: string
- role_id: number
+ user_id: number;
+ username: string;
+ role_id: number;
}
@Component({
@@ -57,60 +57,112 @@ interface JWTPayload {
providers: [DatePipe]
})
export class CurrentRequestsComponent implements OnInit {
- currentRequests: RequestItem[] = [];
+ // Master list from API
+ // allRequests: RequestItem[] = []; // We can derive filtered lists directly
+
+ // Filtered lists for tables
+ activeRequests: RequestItem[] = [];
+ otherRequests: RequestItem[] = []; // For Rejected, Returned, Received
+
+ // Counts for summary cards
+ pendingCount: number = 0;
+ acceptedCount: number = 0;
+ receivedCount: number = 0; // For 'Received' status orders
+
isLoading: boolean = false;
errorMessage: string | null = null;
selectedRequestDetails: RequestItem | null = null;
isModalOpen: boolean = false;
- private supplierId = 103; // Example supplier ID - get this dynamically in a real app
+ private supplierId!: number; // Will be set in ngOnInit
- // --- API URL defined directly in the component ---
private readonly ORDER_MANAGEMENT_API_BASE_URL = 'http://localhost:8000/api/v0/supplier-request';
- // private apiUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier-request/supplier/${this.supplierId}`;
constructor(private http: HttpClient, public datePipe: DatePipe) {}
ngOnInit(): void {
- let decoded : JWTPayload | null = null;
+ let decoded: JWTPayload | null = null;
const token = localStorage.getItem("token");
if (token) {
try {
decoded = jwtDecode(token);
console.log('Decoded token:', decoded);
+ this.supplierId = decoded?.user_id || 1; // Set supplierId here
} catch (error) {
console.error('Error decoding token:', error);
+ this.supplierId = 1; // Fallback supplierId
}
+ } else {
+ this.supplierId = 1; // Fallback if no token
+ console.warn('No token found, using fallback supplierId.');
}
- this.supplierId = decoded?.user_id || 1
- this.loadRequests(decoded?.user_id || 1);
+ this.loadRequests(); // No need to pass supplierId if it's a class member
}
- loadRequests(user_id : number): void {
+ loadRequests(): void {
this.isLoading = true;
this.errorMessage = null;
- this.currentRequests = [];
+ // Reset lists and counts
+ this.activeRequests = [];
+ this.otherRequests = [];
+ this.pendingCount = 0;
+ this.acceptedCount = 0;
+ this.receivedCount = 0;
+
+ if (!this.supplierId) {
+ this.errorMessage = "Supplier ID not available. Cannot load requests.";
+ this.isLoading = false;
+ return;
+ }
- this.http.get
(`${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier/${user_id}/`).pipe(
+ this.http.get(`${this.ORDER_MANAGEMENT_API_BASE_URL}/supplier/${this.supplierId}/`).pipe(
map(apiDataArray => {
+ // First, transform all API items
return apiDataArray.map(apiItem => this.transformApiItemToRequestItem(apiItem));
}),
+ tap(transformedRequests => {
+ // Second, categorize and count
+ this.processAndCategorizeRequests(transformedRequests);
+ }),
catchError((error: HttpErrorResponse) => {
console.error('Error fetching requests:', error);
this.errorMessage = `Failed to load requests: ${error.statusText || 'Unknown error'}`;
- return of([]);
+ return of([]); // Return empty on error so tap doesn't break if array is expected
})
).subscribe({
- next: (processedData) => {
- this.currentRequests = processedData;
+ // Data processing is now in 'tap', 'next' can be minimal or for final checks
+ next: () => {
this.isLoading = false;
},
error: () => {
+ // Error is caught by catchError, this ensures isLoading is false
this.isLoading = false;
}
});
}
+ private processAndCategorizeRequests(requests: RequestItem[]): void {
+ this.activeRequests = [];
+ this.otherRequests = [];
+ this.pendingCount = 0;
+ this.acceptedCount = 0;
+ this.receivedCount = 0;
+
+ requests.forEach(request => {
+ if (request.status === 'Pending' || request.status === 'Accepted') {
+ this.activeRequests.push(request);
+ } else if (request.status === 'Rejected' || request.status === 'Returned' || request.status === 'Received') {
+ this.otherRequests.push(request);
+ }
+
+ // Update counts
+ if (request.status === 'Pending') this.pendingCount++;
+ if (request.status === 'Accepted') this.acceptedCount++;
+ if (request.status === 'Received') this.receivedCount++;
+ });
+ }
+
+
private transformApiItemToRequestItem(apiItem: ApiRequestItem): RequestItem {
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
return {
@@ -120,10 +172,10 @@ export class CurrentRequestsComponent implements OnInit {
quantity: apiItem.count,
deadline: new Date(apiItem.expected_delivery_date),
status: capitalize(apiItem.status) as RequestItem['status'],
+ receivedAt: apiItem.received_at ? new Date(apiItem.received_at) : null, // Map received_at
rawApiData: apiItem,
supplierId: apiItem.supplier_id,
productId: apiItem.product_id,
- receivedAt: apiItem.received_at ? new Date(apiItem.received_at) : null,
warehouseId: apiItem.warehouse_id,
unitPrice: apiItem.unit_price,
quality: apiItem.quality,
@@ -160,7 +212,6 @@ export class CurrentRequestsComponent implements OnInit {
updateRequestStatus(requestId: number, newApiStatus: 'accepted' | 'rejected'): void {
this.isLoading = true;
- // Construct the update URL using the base URL defined in the component
const updateUrl = `${this.ORDER_MANAGEMENT_API_BASE_URL}/${requestId}/status/`;
this.http.patch(updateUrl, { status: newApiStatus })
@@ -171,16 +222,22 @@ export class CurrentRequestsComponent implements OnInit {
catchError((error: HttpErrorResponse) => {
console.error(`Error updating request ${requestId}:`, error);
this.errorMessage = `Failed to update request ${requestId}. ${error.message || 'Please try again.'}`;
- this.isLoading = false;
+ // Do not set isLoading false here if switchMap follows, let loadRequests handle it
return throwError(() => error);
}),
switchMap(() => {
- this.loadRequests(this.supplierId);
+ this.loadRequests(); // Reload all requests, which will re-filter and update counts
return of(null);
})
)
.subscribe({
- complete: () => {}
+ error: () => {
+ // If switchMap or loadRequests itself errors out and doesn't complete
+ this.isLoading = false;
+ },
+ complete: () => {
+ // isLoading is typically set to false at the end of loadRequests
+ }
});
}
diff --git a/src/app/components/supplier-page/product-management/product-management.component.html b/src/app/components/supplier-page/product-management/product-management.component.html
index b47981b..243e6e0 100644
--- a/src/app/components/supplier-page/product-management/product-management.component.html
+++ b/src/app/components/supplier-page/product-management/product-management.component.html
@@ -1,4 +1,3 @@
-
Manage Products
From e968df0c1c5ec75e82ac395b922dd1c333b3c7cb Mon Sep 17 00:00:00 2001
From: Induwara Rathnayake
Date: Sat, 10 May 2025 23:29:05 +0530
Subject: [PATCH 5/6] Add styles
---
.../components/sidebar/sidebar.component.ts | 54 +-
.../current-requests.component.html | 518 ++++++++++++------
.../inventory/inventory.component.html | 44 +-
.../product-management.component.html | 185 +++++--
.../product-management.component.ts | 1 +
.../dashboard-layout.component.html | 38 +-
.../dashboard-layout.component.ts | 49 +-
7 files changed, 611 insertions(+), 278 deletions(-)
diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts
index 30079e0..90926c2 100644
--- a/src/app/components/sidebar/sidebar.component.ts
+++ b/src/app/components/sidebar/sidebar.component.ts
@@ -24,57 +24,17 @@ export class SidebarComponent {
// d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />`)
},
{
- label: 'Forecast',
- route: 'forecast',
- // icon: this.sanitize(` `)
- },
- {
- label: 'Supplier',
- route: 'supplier',
- // icon: this.sanitize(` `)
- },
- {
- label: 'Orders',
- route: '/orders',
- // icon: this.sanitize(` `)
+ label: 'Product Managment',
+ route: '/dashboard/supplier/product-management',
},
{
- label: 'Inventory',
- route: '/inventory',
- // icon: this.sanitize(` `)
+ label: 'Inventory Managment',
+ route: '/dashboard/supplier/inventory',
},
{
- label: 'Deliveries',
- route: '/deliveries',
- // icon: this.sanitize(` `)
- },
- {
- label: 'Warehouse-Inventory',
- route: '/dashboard/warehouse',
- },
- {
- label: 'Warehouse-Truck Tracking',
- route: '/dashboard/warehouse/truck-tracking',
- },
- {
- label: 'Warehouse-Vendor Orders',
- route: '/dashboard/warehouse/vendor-orders',
- },
- {
- label: 'Warehouse-Supplier Requests',
- route: '/dashboard/warehouse/supplier-requests',
- }
- ];;
+ ];
isClosed = input(false);
}
diff --git a/src/app/components/supplier-page/current-requests/current-requests.component.html b/src/app/components/supplier-page/current-requests/current-requests.component.html
index c90f8c8..a40f986 100644
--- a/src/app/components/supplier-page/current-requests/current-requests.component.html
+++ b/src/app/components/supplier-page/current-requests/current-requests.component.html
@@ -1,189 +1,387 @@
-
-
Current Requests & Order History
-
-
-
-
-
Pending Orders
-
{{ pendingCount }}
-
-
-
Accepted Orders
-
{{ acceptedCount }}
+
+
+
+
+
+
+
+ Order Management
+
+
+ Current Requests & History
+
+
+ Manage incoming orders and view your delivery history
+
+
-
-
Delivered Orders
-
{{ receivedCount }}
+
+
-
-
-
Loading requests...
-
-
+
+
+
+
+
Pending Orders
+
{{ pendingCount }}
+
+
+
+ Accepted Orders
+
+
{{ acceptedCount }}
+
+
+
+ Delivered Orders
+
+
{{ receivedCount }}
+
+
-
-
- Error!
- {{ errorMessage }}
-
+
+
+
+
Loading requests...
+
+
-
-
0">
-
Active Orders
-
-
-
-
- Request ID
- Request Date
- Product
- Quantity
- Delivery Deadline
- Status
- Actions
-
-
-
-
- {{ request.id }}
- {{ request.requestDate | date:'shortDate' }}
- {{ request.productName }}
- {{ request.quantity | number }}
-
- {{ request.deadline | date:'shortDate' }}
-
-
-
- {{ request.status }}
-
-
-
-
-
- Accept
-
-
- Reject
+
+
+ Error!
+ {{ errorMessage }}
+
+
+
+ 0">
+
+
+
+
+ Active Orders
+
+
+
+
+
+
+
+ Request ID
+ Request Date
+ Product
+ Quantity
+ Delivery Deadline
+ Status
+ Actions
+
+
+
+
+
+ {{ request.id }}
+
+
+ {{ request.requestDate | date : "shortDate" }}
+
+ {{ request.productName }}
+ {{ request.quantity | number }}
+
+ {{ request.deadline | date : "shortDate" }}
+
+
+
+ {{ request.status }}
+
+
+
+
+
+ Accept
+
+
+ Reject
+
+
+
+ Details
-
- Details
-
-
-
- No active orders found.
-
-
-
+
+
+
+
+ No active orders found.
+
+
+
+
+
-
-
-
0">
-
Order History
-
-
-
-
- Request ID
- Request Date
- Product
- Quantity
- Outcome Date
- Status
- Actions
-
-
-
-
- {{ request.id }}
- {{ request.requestDate | date:'shortDate' }}
- {{ request.productName }}
- {{ request.quantity | number }}
-
-
-
- {{ request.receivedAt | date:'shortDate' }} (Received)
-
-
-
- {{ request.deadline | date:'shortDate' }} (Expected)
-
-
- N/A
-
-
-
-
- {{ request.status }}
-
-
-
- Details
-
-
-
- No historical orders found.
-
-
-
+
+
0">
+
+
+
+
+ Order History
+
+
+
+
+
+
+
+ Request ID
+ Request Date
+ Product
+ Quantity
+ Outcome Date
+
+ Status
+ Actions
+
+
+
+
+
+
+
+ {{ request.id }}
+
+
+ {{ request.requestDate | date : "shortDate" }}
+
+ {{ request.productName }}
+ {{ request.quantity | number }}
+
+
+
+ {{ request.receivedAt | date : "shortDate" }} (Received)
+
+
+
+ {{ request.deadline | date : "shortDate" }} (Expected)
+
+
+ N/A
+
+
+
+
+ {{ request.status }}
+
+
+
+
+ Details
+
+
+
+
+
+ No historical orders found.
+
+
+
+
+
-
-
-
+
+
-
-
+
+
+
-
Request Details (ID: {{ selectedRequestDetails.id }})
- ×
+
+ Request Details (ID: {{ selectedRequestDetails.id }})
+
+
+ ×
+
-
Product: {{ selectedRequestDetails.productName }}
-
Quantity: {{ selectedRequestDetails.quantity | number }}
-
Request Date: {{ selectedRequestDetails.requestDate | date:'medium' }}
-
Delivery Deadline: {{ selectedRequestDetails.deadline | date:'medium' }}
-
Current Status:
-
- {{ selectedRequestDetails.status }}
-
+
+ Product: {{ selectedRequestDetails.productName }}
+
+
+ Quantity:
+ {{ selectedRequestDetails.quantity | number }}
+
+
+ Request Date:
+ {{ selectedRequestDetails.requestDate | date : "medium" }}
+
+
+ Delivery Deadline:
+ {{ selectedRequestDetails.deadline | date : "medium" }}
+
+
+ Current Status:
+
+ {{ selectedRequestDetails.status }}
+
- Supplier Name: {{ raw.supplier_name }}
- Supplier ID: {{ raw.supplier_id }}
- Product ID: {{ raw.product_id }}
- Warehouse ID: {{ raw.warehouse_id }}
- Unit Price: {{ raw.unit_price | currency:'USD':'symbol':'1.2-2' }}
- Received At: {{ raw.received_at | date:'medium' }}
- Quality: {{ raw.quality }}
- Is Defective: {{ raw.is_defective ? 'Yes' : 'No' }}
+ Supplier Name: {{ raw.supplier_name }}
+ Supplier ID: {{ raw.supplier_id }}
+ Product ID: {{ raw.product_id }}
+ Warehouse ID: {{ raw.warehouse_id }}
+
+ Unit Price:
+ {{ raw.unit_price | currency : "USD" : "symbol" : "1.2-2" }}
+
+
+ Received At:
+ {{ raw.received_at | date : "medium" }}
+
+
+
+ Quality: {{ raw.quality }}
+
+
+ Is Defective: {{ raw.is_defective ? "Yes" : "No" }}
+
-
{{ selectedRequestDetails.rawApiData | json }}
+
{{ selectedRequestDetails.rawApiData | json }}
- Close
+
+ Close
+
-
- © 2025 Supply Chain Management System. All rights reserved.
-
-
\ No newline at end of file
+
+ © 2025 Supply Chain Management System. All rights reserved.
+
+
diff --git a/src/app/components/supplier-page/inventory/inventory.component.html b/src/app/components/supplier-page/inventory/inventory.component.html
index 894b938..a9c539e 100644
--- a/src/app/components/supplier-page/inventory/inventory.component.html
+++ b/src/app/components/supplier-page/inventory/inventory.component.html
@@ -1,16 +1,34 @@
-
+
-
+
+
+
+ © 2025 Supply Chain Management System. All rights reserved.
-
-
-
- © 2025 Supply Chain Management System. All rights reserved.
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/app/components/supplier-page/product-management/product-management.component.html b/src/app/components/supplier-page/product-management/product-management.component.html
index 243e6e0..4c4a0de 100644
--- a/src/app/components/supplier-page/product-management/product-management.component.html
+++ b/src/app/components/supplier-page/product-management/product-management.component.html
@@ -1,60 +1,141 @@
-
-
Manage Products
-
+
+
+
+
+
+
+
+ Supply Management
+
+
+ Product Catalogue
+
+
+ Add, edit, or remove your products in the system
+
+
+
+
+
+
+
+
-
- {{ showAddForm ? 'Cancel Adding Product' : 'Add New Product' }}
+
+ {{ showAddForm ? "Cancel Adding Product" : "Add New Product" }}
-
+
-
-
{{ editingProduct ? 'Edit Product' : 'Add New Product' }}
+
+
+ {{ editingProduct ? "Edit Product" : "Add New Product" }}
+
-
- {{ successMessage }}
-
-
- {{ errorMessage }}
-
+
+ {{ successMessage }}
+
+
+ {{ errorMessage }}
+
-
-
+
-
Your Product Catalogue
-
+
+ Your Product Catalogue
+
+
-
+
Product Name
SKU
@@ -62,26 +143,42 @@ Your Product Catalogue Actions
-
-
+
+
{{ product.name }}
{{ product.sku }}
{{ product.price | currency }}
- Edit
- Delete
+
+ Edit
+
+
+ Delete
+
-
- You haven't added any products yet.
+
+
+ You haven't added any products yet.
+
-
+
-
-
-
- © 2025 Supply Chain Management System. All rights reserved.
-
+
+
+
+
+ © 2025 Supply Chain Management System. All rights reserved.
+
diff --git a/src/app/components/supplier-page/product-management/product-management.component.ts b/src/app/components/supplier-page/product-management/product-management.component.ts
index 1da4800..825e4a2 100644
--- a/src/app/components/supplier-page/product-management/product-management.component.ts
+++ b/src/app/components/supplier-page/product-management/product-management.component.ts
@@ -10,6 +10,7 @@ interface Product {
sku: string;
description?: string; // Optional property
price: number;
+
// Add other product properties
}
diff --git a/src/app/layouts/dashboard-layout/dashboard-layout.component.html b/src/app/layouts/dashboard-layout/dashboard-layout.component.html
index c9ed643..a678dd5 100644
--- a/src/app/layouts/dashboard-layout/dashboard-layout.component.html
+++ b/src/app/layouts/dashboard-layout/dashboard-layout.component.html
@@ -2,10 +2,40 @@
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/app/layouts/dashboard-layout/dashboard-layout.component.ts b/src/app/layouts/dashboard-layout/dashboard-layout.component.ts
index 00fcccf..f19d36b 100644
--- a/src/app/layouts/dashboard-layout/dashboard-layout.component.ts
+++ b/src/app/layouts/dashboard-layout/dashboard-layout.component.ts
@@ -1,21 +1,50 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule, Router, NavigationEnd } from '@angular/router';
import { RouterOutlet } from '@angular/router';
-import { SidebarComponent } from "../../components/sidebar/sidebar.component";
+import { SidebarComponent } from '../../components/sidebar/sidebar.component';
import { LucideAngularModule, FileIcon, PanelLeft } from 'lucide-angular';
+import { filter } from 'rxjs/operators';
@Component({
selector: 'app-dashboard-layout',
- imports: [SidebarComponent, RouterOutlet, LucideAngularModule],
+ standalone: true,
+ imports: [
+ CommonModule,
+ RouterModule,
+ SidebarComponent,
+ RouterOutlet,
+ LucideAngularModule,
+ ],
templateUrl: './dashboard-layout.component.html',
- styleUrl: './dashboard-layout.component.css'
+ styleUrls: ['./dashboard-layout.component.css'],
})
-export class DashboardLayoutComponent {
+export class DashboardLayoutComponent implements OnInit {
readonly FileIcon = FileIcon;
- readonly panelLeft = PanelLeft
- sidebarClosed : boolean = false;
+ readonly panelLeft = PanelLeft;
+ sidebarClosed = false;
+ isProfilePage = false;
+
+ constructor(private router: Router) {}
+
+ ngOnInit() {
+ // Check initial route
+ this.checkIfProfilePage(this.router.url);
+
+ // Subscribe to route changes
+ this.router.events
+ .pipe(filter((event) => event instanceof NavigationEnd))
+ .subscribe((event: NavigationEnd) => {
+ this.checkIfProfilePage(event.url);
+ });
+ }
+
+ checkIfProfilePage(url: string) {
+ // Check if the current URL contains 'profile'
+ this.isProfilePage = url.includes('/profile');
+ }
handleSidebarClosed() {
- console.log("clicked")
- this.sidebarClosed = !this.sidebarClosed
+ this.sidebarClosed = !this.sidebarClosed;
}
-}
+}
\ No newline at end of file
From 039501a998db2cdfee6ce41b92c9d3fd1d614115 Mon Sep 17 00:00:00 2001
From: ChehanHelitha <162694727+ChehanHelitha@users.noreply.github.com>
Date: Sun, 11 May 2025 02:21:01 +0530
Subject: [PATCH 6/6] Connecting backend to product management
---
.../product-management.component.html | 337 +++++++++--------
.../product-management.component.ts | 357 +++++++++++++-----
2 files changed, 451 insertions(+), 243 deletions(-)
diff --git a/src/app/components/supplier-page/product-management/product-management.component.html b/src/app/components/supplier-page/product-management/product-management.component.html
index 4c4a0de..4d57a3d 100644
--- a/src/app/components/supplier-page/product-management/product-management.component.html
+++ b/src/app/components/supplier-page/product-management/product-management.component.html
@@ -4,181 +4,204 @@
-
- Supply Management
-
-
- Product Catalogue
-
-
- Add, edit, or remove your products in the system
-
+
Supply Management
+
Product Catalogue
+
View your products and manage their prices per warehouse.
-
-
+
-
- {{ showAddForm ? "Cancel Adding Product" : "Add New Product" }}
+
+
+ Add Product to Warehouse
-
-
-
- {{ editingProduct ? "Edit Product" : "Add New Product" }}
-
-
-
-
-
- Product Name
-
-
-
-
- SKU
-
-
-
- Description
-
-
-
- Unit Price
-
-
-
-
-
-
- Cancel
-
-
- {{ editingProduct ? "Update Product" : "Save Product" }}
-
+
+
+
+
Loading product catalogue...
+
+
+ {{ successMessage }}
+
+
+ {{ errorMessage }}
+
+
+
+
Your Product Catalogue
+
0">
+
+
+
+
+ Product Name
+ SKU
+ General Price
+ Priced Warehouses
+ Actions
+
+
+
+
+ {{ product.name }}
+ {{ product.sku }}
+ {{ product.price | currency:'USD':'symbol':'1.2-2' }}
+
+ 0; else noWarehouses">
+
+ {{ wh }}
+
+
+ Not priced in any warehouse
+
+
+
+ Set/Update Price
+
+
+
+
+
+
+ No products found in your catalogue.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Add Product to Warehouse
+ ×
+
+
+
+
Select Product
+
+ -- Choose a product --
+ {{ p.name }}
+
+
Product selection is required.
+
+
+
+ SKU (Auto-generated)
+
+
+
+
+
Select Warehouse
+
+ -- Choose a warehouse --
+ {{ wh.name }}
+
+
Warehouse selection is required.
+
+
+
+
Unit Price for this Warehouse
+
+
+ Price is required.
+ Price must be 0 or more.
-
-
-
- {{ successMessage }}
-
- {{ errorMessage }}
+
+
+ Cancel
+
+ Add Product to Warehouse
+
-
+
+
+
-
-
- Your Product Catalogue
-
-
-
-
-
- Product Name
- SKU
- Unit Price
- Actions
-
-
-
-
- {{ product.name }}
- {{ product.sku }}
- {{ product.price | currency }}
-
-
- Edit
-
-
- Delete
-
-
-
-
-
- You haven't added any products yet.
-
-
-
-
+
+
+
+
+
+
Set/Update Price for Warehouse
+ ×
+
+
+
Product: {{ productForPriceEdit.name }}
+
SKU: {{ productForPriceEdit.sku }}
+
+
+
Select Associated Warehouse
+
+
+ {{ filteredWarehousesForModal.length > 0 ? '-- Choose a warehouse --' : 'No associated warehouses for this product' }}
+
+ {{ wh.name }}
+
+
Warehouse selection is required.
+
+
+
New Unit Price for Selected Warehouse
+
+
+ Price is required.
+ Price must be 0 or more.
+
+
+
+ Cancel
+
+ Update Price
+
+
+
+
-
-
+
© 2025 Supply Chain Management System. All rights reserved.
-
-
+
\ No newline at end of file
diff --git a/src/app/components/supplier-page/product-management/product-management.component.ts b/src/app/components/supplier-page/product-management/product-management.component.ts
index 825e4a2..2e97e56 100644
--- a/src/app/components/supplier-page/product-management/product-management.component.ts
+++ b/src/app/components/supplier-page/product-management/product-management.component.ts
@@ -1,17 +1,39 @@
-import { Component, OnInit } from '@angular/core';
-import { FormBuilder, FormGroup, Validators } from '@angular/forms'; // Likely needed for add/edit form
-import { CommonModule } from '@angular/common'; // Ensure this is imported if standalone or in NgModule
-import { ReactiveFormsModule } from '@angular/forms'; // Import if using reactive forms
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { CommonModule, CurrencyPipe } from '@angular/common';
+import { ReactiveFormsModule } from '@angular/forms';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
+import { of, Subject, throwError } from 'rxjs';
+import { catchError, map, takeUntil, tap } from 'rxjs/operators';
+// Interface for the data from your LISTING API
+interface ApiProductListItem {
+ product_name: string;
+ SKU: string;
+ supplier_price: number;
+ warehouses: string[]; // Names of warehouses
+}
-interface Product {
- id: string | number; // Use appropriate type for your ID
+// Interface for your component's product structure (for display in table)
+interface ProductDisplay {
name: string;
sku: string;
- description?: string; // Optional property
price: number;
-
- // Add other product properties
+ warehouses: string[]; // Names of warehouses this product is in
+ description?: string;
+}
+
+// Interface for warehouse selection (with ID)
+interface Warehouse {
+ id: number;
+ name: string;
+}
+
+// Interface for predefined product selection
+interface PredefinedProduct {
+ name: string;
+ id: number; // The actual product_id
+ sku: string; // The generated SKU
}
@Component({
@@ -19,110 +41,273 @@ interface Product {
selector: 'app-product-management',
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './product-management.component.html',
- styleUrls: ['./product-management.component.css']
+ styleUrls: ['./product-management.component.css'],
+ providers: [CurrencyPipe]
})
+export class ProductManagementComponent implements OnInit, OnDestroy {
+ private destroy$ = new Subject
();
-export class ProductManagementComponent implements OnInit {
+ products: ProductDisplay[] = [];
+
+ isAddProductToWarehouseModalOpen: boolean = false;
+ addProductToWarehouseForm!: FormGroup;
+ predefinedProductsForDropdown: PredefinedProduct[] = [];
+
+ editPriceForm!: FormGroup;
+ isEditPriceModalOpen: boolean = false;
+ productForPriceEdit: ProductDisplay | null = null;
+ filteredWarehousesForModal: Warehouse[] = [];
+
+ readonly allAvailableWarehouses: Warehouse[] = [
+ { id: 1, name: 'Colombo Central' },
+ { id: 2, name: 'Kandy Depot' },
+ { id: 3, name: 'Kurunegala Rock' }
+ ];
- // Properties for the component
- products: Product[] = [];
- productForm!: FormGroup; // For Add/Edit
- showAddForm: boolean = false;
- editingProduct: Product | null = null;
successMessage: string | null = null;
errorMessage: string | null = null;
+ isLoading: boolean = false;
+ isSubmitting: boolean = false;
+
+ private readonly supplierId: number = 103;
+ private readonly PRODUCT_LIST_API_URL = 'http://127.0.0.1:8000/api/warehouse/supplier-product/prices/';
+ // Assuming Django URL is fixed to use a single slash:
+ private readonly ADD_OR_UPDATE_PRICE_API_URL = 'http://127.0.0.1:8000/api/warehouse//supplier-product/add_or_update/';
- // Inject FormBuilder and your ProductService
constructor(
- private fb: FormBuilder /*,
- private productService: ProductService */ // Uncomment when service is ready
- ) { }
+ private fb: FormBuilder,
+ private http: HttpClient
+ ) {}
ngOnInit(): void {
+ this.generatePredefinedProducts();
+ this.initAddProductToWarehouseForm();
+ this.initEditPriceForm();
this.loadProducts();
- this.initForm();
}
- initForm(): void {
- this.productForm = this.fb.group({
- id: [null], // Keep track of id for editing
- name: ['', Validators.required],
- sku: ['', Validators.required],
- description: [''],
- price: [0, [Validators.required, Validators.min(0)]]
- });
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
}
- loadProducts(): void {
- // TODO: Replace with actual service call
- // this.productService.getProducts().subscribe(data => this.products = data);
- console.log('Loading products...');
- // Placeholder:
- this.products = [
- { id: 'p1', name: 'Sample Item 1', sku: 'SKU001', price: 19.99 },
- { id: 'p2', name: 'Sample Item 2', sku: 'SKU002', price: 29.99, description: 'A second item' }
+ generatePredefinedProducts(): void {
+ const productNames = [
+ 'Chili Product 1', 'Cinnamon Product 2', 'Pepper Product 3', 'Cinnamon Product 4',
+ 'Chili Product 5', 'Cinnamon Product 6', 'Pepper Product 7', 'Pepper Product 8',
+ 'Chili Product 9', 'Cinnamon Product 10'
];
+ this.predefinedProductsForDropdown = productNames.map(name => {
+ const id = this.extractProductIdFromName(name);
+ return {
+ name: name,
+ id: id || 0,
+ sku: this.generateSku(id) // SKU is generated here
+ };
+ });
}
- onSubmit(): void {
- // Logic for saving/updating product using this.productForm.value
- console.log('Form submitted:', this.productForm.value);
- this.successMessage = 'Product saved successfully!'; // Example message
- // Reset form, reload products etc.
- this.resetForm();
- this.loadProducts(); // Reload list after save/update
+ initAddProductToWarehouseForm(): void {
+ this.addProductToWarehouseForm = this.fb.group({
+ selected_product_id: [null, Validators.required],
+ sku_display: [{ value: '', disabled: true }], // Disabled input for SKU
+ warehouse_id: [null, Validators.required],
+ supplier_price: [null, [Validators.required, Validators.min(0)]]
+ });
+
+ // Auto-update SKU when product changes
+ this.addProductToWarehouseForm.get('selected_product_id')?.valueChanges
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(productId => {
+ const selectedProduct = this.predefinedProductsForDropdown.find(p => p.id === productId);
+ this.addProductToWarehouseForm.get('sku_display')?.setValue(selectedProduct ? selectedProduct.sku : '');
+ });
}
- editProduct(product: Product): void {
- this.editingProduct = product;
- this.productForm.patchValue(product); // Load product data into form
- this.showAddForm = true; // Show the form
- this.successMessage = null;
- this.errorMessage = null;
+ initEditPriceForm(): void {
+ this.editPriceForm = this.fb.group({
+ warehouse_id: [null, Validators.required],
+ supplier_price: [null, [Validators.required, Validators.min(0)]]
+ });
}
- cancelEdit(): void {
- this.resetForm();
+ // Helper to generate SKU "SKUXXX"
+ generateSku(productId: number | null): string {
+ if (productId === null || productId === 0) return 'N/A'; // Or some placeholder
+ return `SKU${String(productId).padStart(3, '0')}`;
}
- resetForm(): void {
- this.editingProduct = null;
- this.productForm.reset();
- this.showAddForm = false;
- this.successMessage = null;
+ // Helper to extract product ID from name "Product Name X"
+ extractProductIdFromName(productName: string): number | null {
+ const match = productName.match(/\s(\d+)$/);
+ if (match && match[1]) {
+ return parseInt(match[1], 10);
+ }
+ console.warn(`Could not extract product ID from name: ${productName}`);
+ return null;
+ }
+
+ loadProducts(): void {
+ this.isLoading = true;
this.errorMessage = null;
+ this.products = [];
+ const apiUrl = `${this.PRODUCT_LIST_API_URL}?supplier_id=${this.supplierId}`;
+
+ this.http.get(apiUrl).pipe(
+ map(apiDataArray =>
+ apiDataArray.map(apiItem => ({
+ name: apiItem.product_name,
+ sku: apiItem.SKU, // This SKU comes from the API for existing products
+ price: apiItem.supplier_price,
+ warehouses: apiItem.warehouses || []
+ }))
+ ),
+ catchError((error: HttpErrorResponse) => {
+ console.error('Error fetching products:', error);
+ this.errorMessage = `Failed to load product catalogue: ${error.statusText || 'Unknown error'}`;
+ return of([]);
+ })
+ ).subscribe({
+ next: (transformedProducts) => {
+ this.products = transformedProducts;
+ this.isLoading = false;
+ },
+ error: () => { this.isLoading = false; }
+ });
+ }
+
+ openAddProductToWarehouseModal(): void {
+ this.isAddProductToWarehouseModalOpen = true;
+ this.addProductToWarehouseForm.reset();
+ this.addProductToWarehouseForm.get('sku_display')?.setValue('');
+ this.clearMessages();
}
- // --- ADD THIS METHOD ---
- deleteProduct(productId: string | number): void {
- // Optional: Add a confirmation dialog
- if (confirm(`Are you sure you want to delete product with ID: ${productId}?`)) {
- console.log('Attempting to delete product with ID:', productId);
-
- // TODO: Replace with actual service call
- // this.productService.deleteProduct(productId).subscribe({
- // next: () => {
- // console.log('Product deleted successfully');
- // this.successMessage = 'Product deleted successfully!';
- // // Remove the product from the local list to update UI instantly
- // this.products = this.products.filter(p => p.id !== productId);
- // this.errorMessage = null;
- // },
- // error: (err) => {
- // console.error('Error deleting product:', err);
- // this.errorMessage = 'Failed to delete product. Please try again.';
- // this.successMessage = null;
- // }
- // });
-
- // --- Placeholder for testing without service ---
- this.products = this.products.filter(p => p.id !== productId);
- this.successMessage = `Placeholder: Product ${productId} deleted.`;
- this.errorMessage = null;
- console.log(`Placeholder: Deleted product ${productId}`);
- // --- End Placeholder ---
+ closeAddProductToWarehouseModal(): void {
+ this.isAddProductToWarehouseModalOpen = false;
+ this.addProductToWarehouseForm.reset();
+ }
+
+ onAddProductToWarehouseSubmit(): void {
+ if (this.addProductToWarehouseForm.invalid) {
+ this.showGeneralFormError("Please select a product, warehouse, and enter a valid price.");
+ return;
+ }
+ this.isSubmitting = true;
+ this.clearMessages();
+
+ const formValue = this.addProductToWarehouseForm.value; // Use getRawValue() if you need disabled field values
+ const payload = {
+ warehouse_id: Number(formValue.warehouse_id),
+ supplier_id: this.supplierId,
+ product_id: Number(formValue.selected_product_id),
+ supplier_price: Number(formValue.supplier_price)
+ // The SKU is not sent in this payload because the backend likely identifies the product by product_id.
+ // The SKU generation is primarily for display consistency in the UI.
+ };
+
+ console.log('Adding product to warehouse with payload:', payload);
+ const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
+
+ this.http.post(this.ADD_OR_UPDATE_PRICE_API_URL, payload, httpOptions).pipe(
+ catchError((error: HttpErrorResponse) => {
+ console.error('Error adding product to warehouse:', error);
+ let detail = error.error && typeof error.error === 'object' ? JSON.stringify(error.error) : (error.error || error.message);
+ this.showGeneralFormError(`Failed to add product: ${error.statusText || 'Unknown error'}. ${detail}`);
+ this.isSubmitting = false;
+ return throwError(() => error);
+ })
+ ).subscribe({
+ next: (response) => {
+ const selectedProduct = this.predefinedProductsForDropdown.find(p => p.id === payload.product_id);
+ this.showGeneralFormSuccess(`Product "${selectedProduct?.name || 'Selected Product'}" successfully added/updated for the warehouse.`);
+ this.isSubmitting = false;
+ this.closeAddProductToWarehouseModal();
+ this.loadProducts();
+ },
+ error: () => { this.isSubmitting = false; }
+ });
+ }
+
+ openEditPriceModal(product: ProductDisplay): void {
+ this.productForPriceEdit = product;
+ this.editPriceForm.reset();
+ this.clearMessages();
+
+ if (product.warehouses && product.warehouses.length > 0) {
+ this.filteredWarehousesForModal = this.allAvailableWarehouses.filter(wh =>
+ product.warehouses.includes(wh.name)
+ );
+ } else {
+ this.filteredWarehousesForModal = [];
+ }
+ if (this.filteredWarehousesForModal.length === 1) {
+ this.editPriceForm.patchValue({ warehouse_id: this.filteredWarehousesForModal[0].id });
+ }
+ this.isEditPriceModalOpen = true;
+ }
+
+ closeEditPriceModal(): void {
+ this.isEditPriceModalOpen = false;
+ this.productForPriceEdit = null;
+ this.filteredWarehousesForModal = [];
+ this.editPriceForm.reset();
+ }
+
+ onUpdatePriceSubmit(): void {
+ if (!this.productForPriceEdit || this.editPriceForm.invalid) {
+ this.showGeneralFormError("Modal Error: Please select a warehouse and enter a valid price.");
+ return;
+ }
+ this.isSubmitting = true;
+ this.clearMessages();
+
+ const productId = this.extractProductIdFromName(this.productForPriceEdit.name);
+ if (productId === null) {
+ this.showGeneralFormError(`Modal Error: Could not determine Product ID for "${this.productForPriceEdit.name}".`);
+ this.isSubmitting = false;
+ return;
}
+
+ const formValue = this.editPriceForm.value;
+ const payload = {
+ warehouse_id: Number(formValue.warehouse_id),
+ supplier_id: this.supplierId,
+ product_id: productId,
+ supplier_price: Number(formValue.supplier_price)
+ };
+ console.log('Updating price with payload:', payload);
+ const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
+
+ this.http.post(this.ADD_OR_UPDATE_PRICE_API_URL, payload, httpOptions).pipe(
+ catchError((error: HttpErrorResponse) => {
+ console.error('Error updating price:', error);
+ let detail = error.error && typeof error.error === 'object' ? JSON.stringify(error.error) : (error.error || error.message);
+ this.showGeneralFormError(`Modal Error: Failed to update price: ${error.statusText}. ${detail}`);
+ this.isSubmitting = false;
+ return throwError(() => error);
+ })
+ ).subscribe({
+ next: (response) => {
+ this.showGeneralFormSuccess(`Price for "${this.productForPriceEdit?.name}" updated successfully.`);
+ this.isSubmitting = false;
+ this.closeEditPriceModal();
+ this.loadProducts();
+ },
+ error: () => { this.isSubmitting = false; }
+ });
}
- // --- END OF METHOD ---
+ clearMessages(): void {
+ this.successMessage = null;
+ this.errorMessage = null;
+ }
+ showGeneralFormError(message: string): void {
+ this.errorMessage = message;
+ this.successMessage = null;
+ }
+ showGeneralFormSuccess(message: string): void {
+ this.successMessage = message;
+ this.errorMessage = null;
+ }
}
\ No newline at end of file